Repository: chibivue-land/chibivue Branch: main Commit: ab71c44c228c Files: 4744 Total size: 10.2 MB Directory structure: gitextract_gbw6qa65/ ├── .github/ │ ├── contributing.md │ └── workflows/ │ ├── check.yml │ └── deploy.yml ├── .gitignore ├── .oxlintrc.json ├── .textlintrc.json ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── CODEOWNERS ├── LICENSE ├── README-zh-cn.md ├── README-zh-tw.md ├── README.md ├── book/ │ ├── figures-src/ │ │ ├── README.md │ │ └── legacy-drawio/ │ │ ├── c1c2map.drawio │ │ ├── c1c2map_deleted.drawio │ │ ├── c1c2map_inserted.drawio │ │ ├── c1c2map_inserted_correct.drawio │ │ └── reactive_observer.drawio │ ├── impls/ │ │ ├── 00_introduction/ │ │ │ └── 010_project_setup/ │ │ │ ├── examples/ │ │ │ │ └── playground/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ └── main.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite.config.ts │ │ │ ├── package.json │ │ │ ├── packages/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── 10_minimum_example/ │ │ │ ├── 010_create_app/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ └── index.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 015_package_architecture/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── renderer.ts │ │ │ │ │ └── runtime-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── nodeOps.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 020_simple_h_function/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ └── runtime-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ └── events.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ └── patchProp.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 030_reactive_system/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ └── runtime-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ └── events.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ └── patchProp.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 040_vdom_system/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ └── runtime-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ └── events.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ └── patchProp.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 050_component_system/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ └── runtime-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ └── events.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ └── patchProp.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 050_component_system2/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ └── runtime-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ └── events.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ └── patchProp.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 050_component_system3/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ └── events.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 060_template_compiler/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ └── events.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 060_template_compiler2/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ └── events.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 060_template_compiler3/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ └── events.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 070_sfc_compiler/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ └── events.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── plugin-sample/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── .vscode/ │ │ │ │ │ │ └── extensions.json │ │ │ │ │ ├── README.md │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── HelloWorld.vue │ │ │ │ │ │ ├── main.ts │ │ │ │ │ │ ├── plugin.sample.js │ │ │ │ │ │ ├── style.css │ │ │ │ │ │ └── vite-env.d.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ ├── tsconfig.node.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 070_sfc_compiler2/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ └── events.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 070_sfc_compiler3/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ └── events.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ └── 070_sfc_compiler4/ │ │ │ ├── examples/ │ │ │ │ └── playground/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── App.vue │ │ │ │ │ └── main.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite.config.ts │ │ │ ├── package.json │ │ │ ├── packages/ │ │ │ │ ├── @extensions/ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ └── index.ts │ │ │ │ ├── compiler-core/ │ │ │ │ │ ├── ast.ts │ │ │ │ │ ├── codegen.ts │ │ │ │ │ ├── compile.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── options.ts │ │ │ │ │ └── parse.ts │ │ │ │ ├── compiler-dom/ │ │ │ │ │ └── index.ts │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── parse.ts │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ ├── index.ts │ │ │ │ ├── reactivity/ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ ├── dep.ts │ │ │ │ │ ├── effect.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── reactive.ts │ │ │ │ ├── runtime-core/ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ ├── component.ts │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ ├── h.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── renderer.ts │ │ │ │ │ └── vnode.ts │ │ │ │ ├── runtime-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ └── events.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ └── patchProp.ts │ │ │ │ └── shared/ │ │ │ │ ├── general.ts │ │ │ │ └── index.ts │ │ │ ├── tests/ │ │ │ │ └── e2e.spec.ts │ │ │ └── tsconfig.json │ │ ├── 20_basic_virtual_dom/ │ │ │ ├── 010_patch_keyed_children/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ └── events.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 020_bit_flags/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ └── events.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── shapeFlags.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 040_scheduler/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ └── events.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── shapeFlags.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 050_next_tick/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── reactive.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ └── events.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── shapeFlags.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ └── 060_other_props/ │ │ │ ├── examples/ │ │ │ │ └── playground/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ └── main.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite.config.ts │ │ │ ├── package.json │ │ │ ├── packages/ │ │ │ │ ├── @extensions/ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ └── index.ts │ │ │ │ ├── compiler-core/ │ │ │ │ │ ├── ast.ts │ │ │ │ │ ├── codegen.ts │ │ │ │ │ ├── compile.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── options.ts │ │ │ │ │ └── parse.ts │ │ │ │ ├── compiler-dom/ │ │ │ │ │ └── index.ts │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── parse.ts │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ ├── index.ts │ │ │ │ ├── reactivity/ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ ├── dep.ts │ │ │ │ │ ├── effect.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── reactive.ts │ │ │ │ ├── runtime-core/ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ ├── component.ts │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ ├── h.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── renderer.ts │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ └── vnode.ts │ │ │ │ ├── runtime-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ └── style.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ └── patchProp.ts │ │ │ │ └── shared/ │ │ │ │ ├── general.ts │ │ │ │ ├── index.ts │ │ │ │ └── shapeFlags.ts │ │ │ ├── tests/ │ │ │ │ └── e2e.spec.ts │ │ │ └── tsconfig.json │ │ ├── 30_basic_reactivity_system/ │ │ │ ├── 010_ref/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── shapeFlags.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 020_shallow_ref/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── shapeFlags.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 030_to_ref/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 040_to_refs/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 050_computed/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 060_computed_setter/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 070_watch/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 080_watch_api_extends/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 090_watch_effect/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 100_reactive_proxy_target_type/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 110_template_refs/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 120_proxy_handler_improvement/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 130_cleanup_effects/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 140_effect_scope/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ └── 150_other_apis/ │ │ │ ├── examples/ │ │ │ │ └── playground/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ └── main.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite.config.ts │ │ │ ├── package.json │ │ │ ├── packages/ │ │ │ │ ├── @extensions/ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ └── index.ts │ │ │ │ ├── compiler-core/ │ │ │ │ │ ├── ast.ts │ │ │ │ │ ├── codegen.ts │ │ │ │ │ ├── compile.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── options.ts │ │ │ │ │ └── parse.ts │ │ │ │ ├── compiler-dom/ │ │ │ │ │ └── index.ts │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── parse.ts │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ ├── index.ts │ │ │ │ ├── reactivity/ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ ├── computed.ts │ │ │ │ │ ├── dep.ts │ │ │ │ │ ├── effect.ts │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactive.ts │ │ │ │ │ └── ref.ts │ │ │ │ ├── runtime-core/ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ ├── component.ts │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ ├── h.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── renderer.ts │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ └── vnode.ts │ │ │ │ ├── runtime-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ └── style.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ └── patchProp.ts │ │ │ │ └── shared/ │ │ │ │ ├── general.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shapeFlags.ts │ │ │ │ └── typeUtils.ts │ │ │ ├── tests/ │ │ │ │ └── e2e.spec.ts │ │ │ └── tsconfig.json │ │ ├── 40_basic_component_system/ │ │ │ ├── 010_lifecycle_hooks/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 020_provide_inject/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 030_component_proxy/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 040_setup_context/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 050_component_slot/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 060_slot_extend/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ └── parse.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ └── 070_options_api/ │ │ │ ├── examples/ │ │ │ │ └── playground/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ └── main.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite.config.ts │ │ │ ├── package.json │ │ │ ├── packages/ │ │ │ │ ├── @extensions/ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ └── index.ts │ │ │ │ ├── compiler-core/ │ │ │ │ │ ├── ast.ts │ │ │ │ │ ├── codegen.ts │ │ │ │ │ ├── compile.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── options.ts │ │ │ │ │ └── parse.ts │ │ │ │ ├── compiler-dom/ │ │ │ │ │ └── index.ts │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── parse.ts │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ ├── index.ts │ │ │ │ ├── reactivity/ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ ├── computed.ts │ │ │ │ │ ├── dep.ts │ │ │ │ │ ├── effect.ts │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactive.ts │ │ │ │ │ └── ref.ts │ │ │ │ ├── runtime-core/ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ ├── component.ts │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ ├── enums.ts │ │ │ │ │ ├── h.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── renderer.ts │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ └── vnode.ts │ │ │ │ ├── runtime-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ └── style.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ └── patchProp.ts │ │ │ │ └── shared/ │ │ │ │ ├── general.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shapeFlags.ts │ │ │ │ └── typeUtils.ts │ │ │ ├── tests/ │ │ │ │ └── e2e.spec.ts │ │ │ └── tsconfig.json │ │ ├── 50_basic_template_compiler/ │ │ │ ├── 010_transformer/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ └── transformElement.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 020_v_bind/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ └── vBind.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 022_transform_expression/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ └── vBind.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 025_v_on/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ └── patchProp.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 027_event_modifier/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 027_event_modifier2/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 030_fragment/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 035_comment/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 040_v_if_and_structural_directive/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 050_v_for/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.js │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 060_resolve_components/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── Counter.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 080_component_slot_outlet/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ ├── Comp.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 085_component_slot_insert/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ ├── Comp.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ ├── vOn.ts │ │ │ │ │ │ │ └── vSlot.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 090_other_directives/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ │ ├── vOn.ts │ │ │ │ │ │ └── vText.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ └── 100_chore_compiler/ │ │ │ ├── examples/ │ │ │ │ └── playground/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── App.vue │ │ │ │ │ └── main.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite.config.ts │ │ │ ├── package.json │ │ │ ├── packages/ │ │ │ │ ├── @extensions/ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ └── index.ts │ │ │ │ ├── compiler-core/ │ │ │ │ │ ├── ast.ts │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ ├── codegen.ts │ │ │ │ │ ├── compile.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── options.ts │ │ │ │ │ ├── parse.ts │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ ├── transform.ts │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── compiler-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ └── transforms/ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ ├── vOn.ts │ │ │ │ │ └── vText.ts │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── parse.ts │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ ├── index.ts │ │ │ │ ├── reactivity/ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ ├── computed.ts │ │ │ │ │ ├── dep.ts │ │ │ │ │ ├── effect.ts │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactive.ts │ │ │ │ │ └── ref.ts │ │ │ │ ├── runtime-core/ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ ├── component.ts │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ ├── enums.ts │ │ │ │ │ ├── h.ts │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── renderer.ts │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ └── vnode.ts │ │ │ │ ├── runtime-dom/ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ └── style.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ └── shared/ │ │ │ │ ├── domTagConfig.ts │ │ │ │ ├── general.ts │ │ │ │ ├── index.ts │ │ │ │ ├── makeMap.ts │ │ │ │ ├── normalizeProp.ts │ │ │ │ ├── shapeFlags.ts │ │ │ │ └── typeUtils.ts │ │ │ ├── tests/ │ │ │ │ └── e2e.spec.ts │ │ │ └── tsconfig.json │ │ ├── 60_basic_sfc_compiler/ │ │ │ ├── .gitkeep │ │ │ ├── 010_script_setup/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ │ ├── vOn.ts │ │ │ │ │ │ └── vText.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileScript.ts │ │ │ │ │ │ ├── compileSfc.ts │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 020_define_props/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ ├── Child.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ │ ├── vOn.ts │ │ │ │ │ │ └── vText.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileScript.ts │ │ │ │ │ │ ├── compileSfc.ts │ │ │ │ │ │ ├── compileStyle.ts │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 030_define_emits/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ ├── Child.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ │ ├── vOn.ts │ │ │ │ │ │ └── vText.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileScript.ts │ │ │ │ │ │ ├── compileSfc.ts │ │ │ │ │ │ ├── compileStyle.ts │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 040_scoped_css/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ ├── Child.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ │ ├── vOn.ts │ │ │ │ │ │ └── vText.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileScript.ts │ │ │ │ │ │ ├── compileSfc.ts │ │ │ │ │ │ ├── compileStyle.ts │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 050_props_destructure/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ ├── Child.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ │ ├── vOn.ts │ │ │ │ │ │ └── vText.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileScript.ts │ │ │ │ │ │ ├── compileSfc.ts │ │ │ │ │ │ ├── compileStyle.ts │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ └── 060_type_based_macros/ │ │ │ ├── examples/ │ │ │ │ └── playground/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── App.vue │ │ │ │ │ ├── Child.vue │ │ │ │ │ └── main.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite.config.ts │ │ │ ├── package.json │ │ │ ├── packages/ │ │ │ │ ├── @extensions/ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ └── index.ts │ │ │ │ ├── compiler-core/ │ │ │ │ │ ├── ast.ts │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ ├── codegen.ts │ │ │ │ │ ├── compile.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── options.ts │ │ │ │ │ ├── parse.ts │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ ├── transform.ts │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── compiler-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ └── transforms/ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ ├── vOn.ts │ │ │ │ │ └── vText.ts │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ ├── compileScript.ts │ │ │ │ │ ├── compileSfc.ts │ │ │ │ │ ├── compileStyle.ts │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── parse.ts │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ ├── index.ts │ │ │ │ ├── reactivity/ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ ├── computed.ts │ │ │ │ │ ├── dep.ts │ │ │ │ │ ├── effect.ts │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactive.ts │ │ │ │ │ └── ref.ts │ │ │ │ ├── runtime-core/ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ ├── component.ts │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ ├── enums.ts │ │ │ │ │ ├── h.ts │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── renderer.ts │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ └── vnode.ts │ │ │ │ ├── runtime-dom/ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ └── style.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ └── shared/ │ │ │ │ ├── domTagConfig.ts │ │ │ │ ├── general.ts │ │ │ │ ├── index.ts │ │ │ │ ├── makeMap.ts │ │ │ │ ├── normalizeProp.ts │ │ │ │ ├── shapeFlags.ts │ │ │ │ └── typeUtils.ts │ │ │ ├── tests/ │ │ │ │ └── e2e.spec.ts │ │ │ └── tsconfig.json │ │ ├── 90_web_application_essentials/ │ │ │ ├── 010_ssr/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ │ ├── vOn.ts │ │ │ │ │ │ └── vText.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── hydration.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ ├── server-renderer/ │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── ssrInterpolate.ts │ │ │ │ │ │ │ ├── ssrRenderAttrs.ts │ │ │ │ │ │ │ ├── ssrRenderList.ts │ │ │ │ │ │ │ └── ssrUtils.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── render.ts │ │ │ │ │ │ └── renderToString.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 020_keep_alive/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ ├── Counter.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ │ ├── vOn.ts │ │ │ │ │ │ └── vText.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── KeepAlive.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ ├── server-renderer/ │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── ssrInterpolate.ts │ │ │ │ │ │ │ ├── ssrRenderAttrs.ts │ │ │ │ │ │ │ ├── ssrRenderList.ts │ │ │ │ │ │ │ └── ssrUtils.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── render.ts │ │ │ │ │ │ └── renderToString.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 030_transition/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ │ ├── vOn.ts │ │ │ │ │ │ └── vText.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── KeepAlive.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── Transition.ts │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ ├── server-renderer/ │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── ssrInterpolate.ts │ │ │ │ │ │ │ ├── ssrRenderAttrs.ts │ │ │ │ │ │ │ ├── ssrRenderList.ts │ │ │ │ │ │ │ └── ssrUtils.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── render.ts │ │ │ │ │ │ └── renderToString.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ ├── 040_static_hoisting/ │ │ │ │ ├── examples/ │ │ │ │ │ └── playground/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.vue │ │ │ │ │ │ └── main.ts │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── packages/ │ │ │ │ │ ├── @extensions/ │ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── compiler-core/ │ │ │ │ │ │ ├── ast.ts │ │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ │ ├── codegen.ts │ │ │ │ │ │ ├── compile.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── options.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ │ ├── transform.ts │ │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── compiler-dom/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ │ └── transforms/ │ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ │ ├── vOn.ts │ │ │ │ │ │ └── vText.ts │ │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── parse.ts │ │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactivity/ │ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ │ ├── computed.ts │ │ │ │ │ │ ├── dep.ts │ │ │ │ │ │ ├── effect.ts │ │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reactive.ts │ │ │ │ │ │ └── ref.ts │ │ │ │ │ ├── runtime-core/ │ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ │ ├── component.ts │ │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── KeepAlive.ts │ │ │ │ │ │ ├── enums.ts │ │ │ │ │ │ ├── h.ts │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── renderer.ts │ │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ │ └── vnode.ts │ │ │ │ │ ├── runtime-dom/ │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── Transition.ts │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── modules/ │ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ │ ├── server-renderer/ │ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ │ ├── ssrInterpolate.ts │ │ │ │ │ │ │ ├── ssrRenderAttrs.ts │ │ │ │ │ │ │ ├── ssrRenderList.ts │ │ │ │ │ │ │ └── ssrUtils.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── render.ts │ │ │ │ │ │ └── renderToString.ts │ │ │ │ │ └── shared/ │ │ │ │ │ ├── domTagConfig.ts │ │ │ │ │ ├── general.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── makeMap.ts │ │ │ │ │ ├── normalizeProp.ts │ │ │ │ │ ├── shapeFlags.ts │ │ │ │ │ └── typeUtils.ts │ │ │ │ ├── tests/ │ │ │ │ │ └── e2e.spec.ts │ │ │ │ └── tsconfig.json │ │ │ └── 050_patch_flags/ │ │ │ ├── examples/ │ │ │ │ └── playground/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── App.vue │ │ │ │ │ └── main.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite.config.ts │ │ │ ├── package.json │ │ │ ├── packages/ │ │ │ │ ├── @extensions/ │ │ │ │ │ └── vite-plugin-chibivue/ │ │ │ │ │ └── index.ts │ │ │ │ ├── compiler-core/ │ │ │ │ │ ├── ast.ts │ │ │ │ │ ├── babelUtils.ts │ │ │ │ │ ├── codegen.ts │ │ │ │ │ ├── compile.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── options.ts │ │ │ │ │ ├── parse.ts │ │ │ │ │ ├── runtimeHelpers.ts │ │ │ │ │ ├── transform.ts │ │ │ │ │ ├── transforms/ │ │ │ │ │ │ ├── transformElement.ts │ │ │ │ │ │ ├── transformExpression.ts │ │ │ │ │ │ ├── transformSlotOutlet.ts │ │ │ │ │ │ ├── vBind.ts │ │ │ │ │ │ ├── vFor.ts │ │ │ │ │ │ ├── vIf.ts │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── compiler-dom/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── parserOptions.ts │ │ │ │ │ └── transforms/ │ │ │ │ │ ├── vHtml.ts │ │ │ │ │ ├── vOn.ts │ │ │ │ │ └── vText.ts │ │ │ │ ├── compiler-sfc/ │ │ │ │ │ ├── compileTemplate.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── parse.ts │ │ │ │ │ └── rewriteDefault.ts │ │ │ │ ├── index.ts │ │ │ │ ├── reactivity/ │ │ │ │ │ ├── baseHandler.ts │ │ │ │ │ ├── collectionHandlers.ts │ │ │ │ │ ├── computed.ts │ │ │ │ │ ├── dep.ts │ │ │ │ │ ├── effect.ts │ │ │ │ │ ├── effectScope.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── reactive.ts │ │ │ │ │ └── ref.ts │ │ │ │ ├── runtime-core/ │ │ │ │ │ ├── apiCreateApp.ts │ │ │ │ │ ├── apiDefineComponent.ts │ │ │ │ │ ├── apiInject.ts │ │ │ │ │ ├── apiLifecycle.ts │ │ │ │ │ ├── apiWatch.ts │ │ │ │ │ ├── component.ts │ │ │ │ │ ├── componentEmits.ts │ │ │ │ │ ├── componentOptions.ts │ │ │ │ │ ├── componentProps.ts │ │ │ │ │ ├── componentPublicInstance.ts │ │ │ │ │ ├── componentRenderContext.ts │ │ │ │ │ ├── componentSlots.ts │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── KeepAlive.ts │ │ │ │ │ ├── enums.ts │ │ │ │ │ ├── h.ts │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ ├── renderList.ts │ │ │ │ │ │ ├── renderSlot.ts │ │ │ │ │ │ ├── resolveAssets.ts │ │ │ │ │ │ └── toHandlers.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── renderer.ts │ │ │ │ │ ├── rendererTemplateRef.ts │ │ │ │ │ ├── scheduler.ts │ │ │ │ │ └── vnode.ts │ │ │ │ ├── runtime-dom/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── Transition.ts │ │ │ │ │ ├── directives/ │ │ │ │ │ │ └── vOn.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modules/ │ │ │ │ │ │ ├── attrs.ts │ │ │ │ │ │ ├── class.ts │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ ├── props.ts │ │ │ │ │ │ └── style.ts │ │ │ │ │ ├── nodeOps.ts │ │ │ │ │ ├── patchProp.ts │ │ │ │ │ └── runtimeHelpers.ts │ │ │ │ ├── server-renderer/ │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ ├── ssrInterpolate.ts │ │ │ │ │ │ ├── ssrRenderAttrs.ts │ │ │ │ │ │ ├── ssrRenderList.ts │ │ │ │ │ │ └── ssrUtils.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── render.ts │ │ │ │ │ └── renderToString.ts │ │ │ │ └── shared/ │ │ │ │ ├── domTagConfig.ts │ │ │ │ ├── general.ts │ │ │ │ ├── index.ts │ │ │ │ ├── makeMap.ts │ │ │ │ ├── normalizeProp.ts │ │ │ │ ├── patchFlags.ts │ │ │ │ ├── shapeFlags.ts │ │ │ │ └── typeUtils.ts │ │ │ ├── tests/ │ │ │ │ └── e2e.spec.ts │ │ │ └── tsconfig.json │ │ └── bonus/ │ │ └── hyper-ultimate-super-extreme-minimal-vue/ │ │ ├── examples/ │ │ │ └── playground/ │ │ │ ├── .gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── App.vue │ │ │ │ └── main.ts │ │ │ ├── tsconfig.json │ │ │ └── vite.config.ts │ │ ├── package.json │ │ ├── packages/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── online-book/ │ │ ├── .vitepress/ │ │ │ ├── config/ │ │ │ │ ├── en.ts │ │ │ │ ├── index.ts │ │ │ │ ├── ja.ts │ │ │ │ ├── shared.ts │ │ │ │ ├── zh-cn.ts │ │ │ │ └── zh-tw.ts │ │ │ └── theme/ │ │ │ ├── Layout.vue │ │ │ ├── chibivue-theme-light.json │ │ │ ├── chibivue-theme.json │ │ │ ├── components/ │ │ │ │ ├── CustomHome.vue │ │ │ │ ├── FeatureCard.vue │ │ │ │ ├── FeaturesSection.vue │ │ │ │ ├── HeroSection.vue │ │ │ │ ├── KawaikoNote.vue │ │ │ │ ├── SidebarEnhancer.vue │ │ │ │ └── WipBanner.vue │ │ │ ├── index.ts │ │ │ └── main.css │ │ └── src/ │ │ ├── 00-introduction/ │ │ │ ├── 010-about.md │ │ │ ├── 020-what-is-vue.md │ │ │ ├── 030-vue-core-components.md │ │ │ └── 040-setup-project.md │ │ ├── 10-minimum-example/ │ │ │ ├── 010-create-app-api.md │ │ │ ├── 015-package-architecture.md │ │ │ ├── 020-simple-h-function.md │ │ │ ├── 025-event-handler-and-attrs.md │ │ │ ├── 030-prerequisite-knowledge-for-the-reactivity-system.md │ │ │ ├── 035-try-implementing-a-minimum-reactivity-system.md │ │ │ ├── 040-minimum-virtual-dom.md │ │ │ ├── 050-minimum-component.md │ │ │ ├── 051-component-props.md │ │ │ ├── 052-component-emits.md │ │ │ ├── 060-template-compiler.md │ │ │ ├── 061-template-compiler-impl.md │ │ │ ├── 070-more-complex-parser.md │ │ │ ├── 080-template-binding.md │ │ │ ├── 090-prerequisite-knowledge-for-the-sfc.md │ │ │ ├── 091-parse-sfc.md │ │ │ ├── 092-compile-sfc-template.md │ │ │ ├── 093-compile-sfc-script.md │ │ │ ├── 094-compile-sfc-style.md │ │ │ └── 100-break.md │ │ ├── 20-basic-virtual-dom/ │ │ │ ├── 010-patch-keyed-children.md │ │ │ ├── 020-bit-flags.md │ │ │ ├── 030-scheduler.md │ │ │ └── 040-patch-other-attrs.md │ │ ├── 30-basic-reactivity-system/ │ │ │ ├── 005-reactivity-optimization.md │ │ │ ├── 010-ref-api.md │ │ │ ├── 020-computed-watch.md │ │ │ ├── 030-reactive-proxy-handlers.md │ │ │ ├── 040-effect-scope.md │ │ │ └── 050-other-apis.md │ │ ├── 40-basic-component-system/ │ │ │ ├── 010-lifecycle-hooks.md │ │ │ ├── 020-provide-inject.md │ │ │ ├── 030-component-proxy-setup-context.md │ │ │ ├── 040-component-slot.md │ │ │ └── 050-options-api.md │ │ ├── 50-basic-template-compiler/ │ │ │ ├── 010-transform.md │ │ │ ├── 020-v-bind.md │ │ │ ├── 022-transform-expression.md │ │ │ ├── 025-v-on.md │ │ │ ├── 027-event-modifier.md │ │ │ ├── 030-fragment.md │ │ │ ├── 035-comment.md │ │ │ ├── 040-v-if-and-structural-directive.md │ │ │ ├── 050-v-for.md │ │ │ ├── 070-resolve-component.md │ │ │ ├── 080-component-slot-outlet.md │ │ │ ├── 080-slot.md │ │ │ ├── 085-component-slot-insert.md │ │ │ ├── 090-other-directives.md │ │ │ ├── 100-chore-compiler.md │ │ │ ├── 110-parser-optimization.md │ │ │ └── 500-custom-directive.md │ │ ├── 60-basic-sfc-compiler/ │ │ │ ├── 010-script-setup.md │ │ │ ├── 020-define-props.md │ │ │ ├── 030-define-emits.md │ │ │ ├── 040-scoped-css.md │ │ │ ├── 050-props-destructure.md │ │ │ └── 060-type-based-macros.md │ │ ├── 90-web-application-essentials/ │ │ │ ├── 010-plugins/ │ │ │ │ ├── 010-router.md │ │ │ │ ├── 020-preprocessors.md │ │ │ │ ├── 020-store.md │ │ │ │ ├── 030-data-fetch.md │ │ │ │ └── 040-language-tools.md │ │ │ ├── 020-ssr/ │ │ │ │ ├── 010-create-ssr-app.md │ │ │ │ ├── 020-hydration.md │ │ │ │ └── 030-compiler-ssr.md │ │ │ ├── 030-builtins/ │ │ │ │ ├── 010-keep-alive.md │ │ │ │ ├── 020-suspense.md │ │ │ │ └── 030-transition.md │ │ │ ├── 040-optimizations/ │ │ │ │ ├── 010-static-hoisting.md │ │ │ │ ├── 020-patch-flags.md │ │ │ │ └── 030-tree-flattening.md │ │ │ └── 050-vapor/ │ │ │ ├── 010-introduction.md │ │ │ ├── 020-vapor-compiler.md │ │ │ └── 030-vapor-ssr.md │ │ ├── bonus/ │ │ │ ├── debug-vuejs-core.md │ │ │ └── hyper-ultimate-super-extreme-minimal-vue/ │ │ │ ├── 15-min-impl.md │ │ │ └── index.md │ │ ├── index.md │ │ ├── ja/ │ │ │ ├── 00-introduction/ │ │ │ │ ├── 010-about.md │ │ │ │ ├── 020-what-is-vue.md │ │ │ │ ├── 030-vue-core-components.md │ │ │ │ └── 040-setup-project.md │ │ │ ├── 10-minimum-example/ │ │ │ │ ├── 010-create-app-api.md │ │ │ │ ├── 015-package-architecture.md │ │ │ │ ├── 020-simple-h-function.md │ │ │ │ ├── 025-event-handler-and-attrs.md │ │ │ │ ├── 030-prerequisite-knowledge-for-the-reactivity-system.md │ │ │ │ ├── 035-try-implementing-a-minimum-reactivity-system.md │ │ │ │ ├── 040-minimum-virtual-dom.md │ │ │ │ ├── 050-minimum-component.md │ │ │ │ ├── 051-component-props.md │ │ │ │ ├── 052-component-emits.md │ │ │ │ ├── 060-template-compiler.md │ │ │ │ ├── 061-template-compiler-impl.md │ │ │ │ ├── 070-more-complex-parser.md │ │ │ │ ├── 080-template-binding.md │ │ │ │ ├── 090-prerequisite-knowledge-for-the-sfc.md │ │ │ │ ├── 091-parse-sfc.md │ │ │ │ ├── 092-compile-sfc-template.md │ │ │ │ ├── 093-compile-sfc-script.md │ │ │ │ ├── 094-compile-sfc-style.md │ │ │ │ └── 100-break.md │ │ │ ├── 20-basic-virtual-dom/ │ │ │ │ ├── 010-patch-keyed-children.md │ │ │ │ ├── 020-bit-flags.md │ │ │ │ ├── 030-scheduler.md │ │ │ │ └── 040-patch-other-attrs.md │ │ │ ├── 30-basic-reactivity-system/ │ │ │ │ ├── 005-reactivity-optimization.md │ │ │ │ ├── 010-ref-api.md │ │ │ │ ├── 020-computed-watch.md │ │ │ │ ├── 030-reactive-proxy-handlers.md │ │ │ │ ├── 040-effect-scope.md │ │ │ │ └── 050-other-apis.md │ │ │ ├── 40-basic-component-system/ │ │ │ │ ├── 010-lifecycle-hooks.md │ │ │ │ ├── 020-provide-inject.md │ │ │ │ ├── 030-component-proxy-setup-context.md │ │ │ │ ├── 040-component-slot.md │ │ │ │ └── 050-options-api.md │ │ │ ├── 50-basic-template-compiler/ │ │ │ │ ├── 010-transform.md │ │ │ │ ├── 020-v-bind.md │ │ │ │ ├── 022-transform-expression.md │ │ │ │ ├── 025-v-on.md │ │ │ │ ├── 027-event-modifier.md │ │ │ │ ├── 030-fragment.md │ │ │ │ ├── 035-comment.md │ │ │ │ ├── 040-v-if-and-structural-directive.md │ │ │ │ ├── 050-v-for.md │ │ │ │ ├── 070-resolve-component.md │ │ │ │ ├── 080-component-slot-outlet.md │ │ │ │ ├── 085-component-slot-insert.md │ │ │ │ ├── 090-other-directives.md │ │ │ │ ├── 100-chore-compiler.md │ │ │ │ ├── 110-parser-optimization.md │ │ │ │ └── 500-custom-directive.md │ │ │ ├── 60-basic-sfc-compiler/ │ │ │ │ ├── 010-script-setup.md │ │ │ │ ├── 020-define-props.md │ │ │ │ ├── 030-define-emits.md │ │ │ │ ├── 040-scoped-css.md │ │ │ │ ├── 050-props-destructure.md │ │ │ │ └── 060-type-based-macros.md │ │ │ ├── 90-web-application-essentials/ │ │ │ │ ├── 010-plugins/ │ │ │ │ │ ├── 010-router.md │ │ │ │ │ ├── 020-preprocessors.md │ │ │ │ │ ├── 020-store.md │ │ │ │ │ ├── 030-data-fetch.md │ │ │ │ │ └── 040-language-tools.md │ │ │ │ ├── 020-ssr/ │ │ │ │ │ ├── 010-create-ssr-app.md │ │ │ │ │ ├── 020-hydration.md │ │ │ │ │ └── 030-compiler-ssr.md │ │ │ │ ├── 030-builtins/ │ │ │ │ │ ├── 010-keep-alive.md │ │ │ │ │ ├── 020-suspense.md │ │ │ │ │ └── 030-transition.md │ │ │ │ ├── 040-optimizations/ │ │ │ │ │ ├── 010-static-hoisting.md │ │ │ │ │ ├── 020-patch-flags.md │ │ │ │ │ └── 030-tree-flattening.md │ │ │ │ └── 050-vapor/ │ │ │ │ ├── 010-introduction.md │ │ │ │ ├── 020-vapor-compiler.md │ │ │ │ └── 030-vapor-ssr.md │ │ │ ├── bonus/ │ │ │ │ ├── debug-vuejs-core.md │ │ │ │ └── hyper-ultimate-super-extreme-minimal-vue/ │ │ │ │ ├── 15-min-impl.md │ │ │ │ └── index.md │ │ │ └── index.md │ │ ├── zh-cn/ │ │ │ ├── 00-introduction/ │ │ │ │ ├── 010-about.md │ │ │ │ ├── 020-what-is-vue.md │ │ │ │ ├── 030-vue-core-components.md │ │ │ │ └── 040-setup-project.md │ │ │ ├── 10-minimum-example/ │ │ │ │ ├── 010-create-app-api.md │ │ │ │ ├── 015-package-architecture.md │ │ │ │ ├── 020-simple-h-function.md │ │ │ │ ├── 025-event-handler-and-attrs.md │ │ │ │ ├── 030-prerequisite-knowledge-for-the-reactivity-system.md │ │ │ │ ├── 035-try-implementing-a-minimum-reactivity-system.md │ │ │ │ ├── 040-minimum-virtual-dom.md │ │ │ │ ├── 050-minimum-component.md │ │ │ │ ├── 051-component-props.md │ │ │ │ ├── 052-component-emits.md │ │ │ │ ├── 060-template-compiler.md │ │ │ │ ├── 061-template-compiler-impl.md │ │ │ │ ├── 070-more-complex-parser.md │ │ │ │ ├── 080-template-binding.md │ │ │ │ ├── 090-prerequisite-knowledge-for-the-sfc.md │ │ │ │ ├── 091-parse-sfc.md │ │ │ │ ├── 092-compile-sfc-template.md │ │ │ │ ├── 093-compile-sfc-script.md │ │ │ │ ├── 094-compile-sfc-style.md │ │ │ │ └── 100-break.md │ │ │ ├── 20-basic-virtual-dom/ │ │ │ │ ├── 010-patch-keyed-children.md │ │ │ │ ├── 020-bit-flags.md │ │ │ │ ├── 030-scheduler.md │ │ │ │ └── 040-patch-other-attrs.md │ │ │ ├── 30-basic-reactivity-system/ │ │ │ │ ├── 005-reactivity-optimization.md │ │ │ │ ├── 010-ref-api.md │ │ │ │ ├── 020-computed-watch.md │ │ │ │ ├── 030-reactive-proxy-handlers.md │ │ │ │ ├── 040-effect-scope.md │ │ │ │ └── 050-other-apis.md │ │ │ ├── 40-basic-component-system/ │ │ │ │ ├── 010-lifecycle-hooks.md │ │ │ │ ├── 020-provide-inject.md │ │ │ │ ├── 030-component-proxy-setup-context.md │ │ │ │ ├── 040-component-slot.md │ │ │ │ └── 050-options-api.md │ │ │ ├── 50-basic-template-compiler/ │ │ │ │ ├── 010-transform.md │ │ │ │ ├── 020-v-bind.md │ │ │ │ ├── 022-transform-expression.md │ │ │ │ ├── 025-v-on.md │ │ │ │ ├── 027-event-modifier.md │ │ │ │ ├── 030-fragment.md │ │ │ │ ├── 035-comment.md │ │ │ │ ├── 040-v-if-and-structural-directive.md │ │ │ │ ├── 050-v-for.md │ │ │ │ ├── 070-resolve-component.md │ │ │ │ ├── 080-component-slot-outlet.md │ │ │ │ ├── 080-slot.md │ │ │ │ ├── 085-component-slot-insert.md │ │ │ │ ├── 090-other-directives.md │ │ │ │ ├── 100-chore-compiler.md │ │ │ │ ├── 110-parser-optimization.md │ │ │ │ └── 500-custom-directive.md │ │ │ ├── 60-basic-sfc-compiler/ │ │ │ │ ├── 010-script-setup.md │ │ │ │ ├── 020-define-props.md │ │ │ │ ├── 030-define-emits.md │ │ │ │ ├── 040-scoped-css.md │ │ │ │ ├── 050-props-destructure.md │ │ │ │ └── 060-type-based-macros.md │ │ │ ├── 90-web-application-essentials/ │ │ │ │ ├── 010-plugins/ │ │ │ │ │ ├── 010-router.md │ │ │ │ │ ├── 020-preprocessors.md │ │ │ │ │ ├── 020-store.md │ │ │ │ │ ├── 030-data-fetch.md │ │ │ │ │ └── 040-language-tools.md │ │ │ │ ├── 020-ssr/ │ │ │ │ │ ├── 010-create-ssr-app.md │ │ │ │ │ ├── 020-hydration.md │ │ │ │ │ └── 030-compiler-ssr.md │ │ │ │ ├── 030-builtins/ │ │ │ │ │ ├── 010-keep-alive.md │ │ │ │ │ ├── 020-suspense.md │ │ │ │ │ └── 030-transition.md │ │ │ │ ├── 040-optimizations/ │ │ │ │ │ ├── 010-static-hoisting.md │ │ │ │ │ ├── 020-patch-flags.md │ │ │ │ │ └── 030-tree-flattening.md │ │ │ │ └── 050-vapor/ │ │ │ │ ├── 010-introduction.md │ │ │ │ ├── 020-vapor-compiler.md │ │ │ │ └── 030-vapor-ssr.md │ │ │ ├── bonus/ │ │ │ │ ├── debug-vuejs-core.md │ │ │ │ └── hyper-ultimate-super-extreme-minimal-vue/ │ │ │ │ ├── 15-min-impl.md │ │ │ │ └── index.md │ │ │ └── index.md │ │ └── zh-tw/ │ │ ├── 00-introduction/ │ │ │ ├── 010-about.md │ │ │ ├── 020-what-is-vue.md │ │ │ ├── 030-vue-core-components.md │ │ │ └── 040-setup-project.md │ │ ├── 10-minimum-example/ │ │ │ ├── 010-create-app-api.md │ │ │ ├── 015-package-architecture.md │ │ │ ├── 020-simple-h-function.md │ │ │ ├── 025-event-handler-and-attrs.md │ │ │ ├── 030-prerequisite-knowledge-for-the-reactivity-system.md │ │ │ ├── 035-try-implementing-a-minimum-reactivity-system.md │ │ │ ├── 040-minimum-virtual-dom.md │ │ │ ├── 050-minimum-component.md │ │ │ ├── 051-component-props.md │ │ │ ├── 052-component-emits.md │ │ │ ├── 060-template-compiler.md │ │ │ ├── 061-template-compiler-impl.md │ │ │ ├── 070-more-complex-parser.md │ │ │ ├── 080-template-binding.md │ │ │ ├── 090-prerequisite-knowledge-for-the-sfc.md │ │ │ ├── 091-parse-sfc.md │ │ │ ├── 092-compile-sfc-template.md │ │ │ ├── 093-compile-sfc-script.md │ │ │ ├── 094-compile-sfc-style.md │ │ │ └── 100-break.md │ │ ├── 20-basic-virtual-dom/ │ │ │ ├── 010-patch-keyed-children.md │ │ │ ├── 020-bit-flags.md │ │ │ ├── 030-scheduler.md │ │ │ └── 040-patch-other-attrs.md │ │ ├── 30-basic-reactivity-system/ │ │ │ ├── 005-reactivity-optimization.md │ │ │ ├── 010-ref-api.md │ │ │ ├── 020-computed-watch.md │ │ │ ├── 030-reactive-proxy-handlers.md │ │ │ ├── 040-effect-scope.md │ │ │ └── 050-other-apis.md │ │ ├── 40-basic-component-system/ │ │ │ ├── 010-lifecycle-hooks.md │ │ │ ├── 020-provide-inject.md │ │ │ ├── 030-component-proxy-setup-context.md │ │ │ ├── 040-component-slot.md │ │ │ └── 050-options-api.md │ │ ├── 50-basic-template-compiler/ │ │ │ ├── 010-transform.md │ │ │ ├── 020-v-bind.md │ │ │ ├── 022-transform-expression.md │ │ │ ├── 025-v-on.md │ │ │ ├── 027-event-modifier.md │ │ │ ├── 030-fragment.md │ │ │ ├── 035-comment.md │ │ │ ├── 040-v-if-and-structural-directive.md │ │ │ ├── 050-v-for.md │ │ │ ├── 070-resolve-component.md │ │ │ ├── 080-component-slot-outlet.md │ │ │ ├── 080-slot.md │ │ │ ├── 085-component-slot-insert.md │ │ │ ├── 090-other-directives.md │ │ │ ├── 100-chore-compiler.md │ │ │ ├── 110-parser-optimization.md │ │ │ └── 500-custom-directive.md │ │ ├── 60-basic-sfc-compiler/ │ │ │ ├── 010-script-setup.md │ │ │ ├── 020-define-props.md │ │ │ ├── 030-define-emits.md │ │ │ ├── 040-scoped-css.md │ │ │ ├── 050-props-destructure.md │ │ │ └── 060-type-based-macros.md │ │ ├── 90-web-application-essentials/ │ │ │ ├── 010-plugins/ │ │ │ │ ├── 010-router.md │ │ │ │ ├── 020-preprocessors.md │ │ │ │ ├── 020-store.md │ │ │ │ ├── 030-data-fetch.md │ │ │ │ └── 040-language-tools.md │ │ │ ├── 020-ssr/ │ │ │ │ ├── 010-create-ssr-app.md │ │ │ │ ├── 020-hydration.md │ │ │ │ └── 030-compiler-ssr.md │ │ │ ├── 030-builtins/ │ │ │ │ ├── 010-keep-alive.md │ │ │ │ ├── 020-suspense.md │ │ │ │ └── 030-transition.md │ │ │ ├── 040-optimizations/ │ │ │ │ ├── 010-static-hoisting.md │ │ │ │ ├── 020-patch-flags.md │ │ │ │ └── 030-tree-flattening.md │ │ │ └── 050-vapor/ │ │ │ ├── 010-introduction.md │ │ │ ├── 020-vapor-compiler.md │ │ │ └── 030-vapor-ssr.md │ │ ├── bonus/ │ │ │ ├── debug-vuejs-core.md │ │ │ └── hyper-ultimate-super-extreme-minimal-vue/ │ │ │ ├── 15-min-impl.md │ │ │ └── index.md │ │ └── index.md │ └── playground/ │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── scripts/ │ │ └── generate-chapters.ts │ ├── src/ │ │ ├── App.vue │ │ ├── main.ts │ │ ├── style.css │ │ ├── types.ts │ │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.ts ├── examples/ │ ├── app/ │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.vue │ │ │ ├── components/ │ │ │ │ ├── CompilerMacroDemo.vue │ │ │ │ ├── SimpleBtn.vue │ │ │ │ └── SimpleCard.vue │ │ │ ├── main.ts │ │ │ ├── router.ts │ │ │ ├── store/ │ │ │ │ └── count.store.ts │ │ │ └── views/ │ │ │ ├── compiler-macro.vue │ │ │ ├── directive.vue │ │ │ ├── index.vue │ │ │ ├── inline.ts │ │ │ ├── options-api.vue │ │ │ ├── props-emits.vue │ │ │ ├── state.vue │ │ │ ├── store-counter.vue │ │ │ └── todo-list.vue │ │ └── vite.config.ts │ └── vapor/ │ ├── index.html │ ├── package.json │ ├── src/ │ │ ├── App.vue │ │ ├── Counter.vue │ │ ├── MyComponent.vapor.ts │ │ └── main.ts │ └── vite.config.ts ├── impl/ │ ├── @extensions/ │ │ ├── chibivue-fetch/ │ │ │ ├── package.json │ │ │ └── src/ │ │ │ ├── index.ts │ │ │ ├── queryCache.ts │ │ │ ├── types.ts │ │ │ ├── useMutation.ts │ │ │ └── useQuery.ts │ │ ├── chibivue-language-core/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── index.ts │ │ │ │ ├── languagePlugin.ts │ │ │ │ ├── parseSfc.ts │ │ │ │ ├── types.ts │ │ │ │ └── virtualCode.ts │ │ │ └── tsconfig.json │ │ ├── chibivue-language-server/ │ │ │ ├── bin/ │ │ │ │ └── chibivue-language-server.js │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── index.ts │ │ │ │ └── server.ts │ │ │ └── tsconfig.json │ │ ├── chibivue-router/ │ │ │ ├── package.json │ │ │ └── src/ │ │ │ ├── RouterView.ts │ │ │ ├── history.ts │ │ │ ├── index.ts │ │ │ ├── injectionSymbols.ts │ │ │ ├── router.ts │ │ │ ├── types/ │ │ │ │ └── index.ts │ │ │ └── useApi.ts │ │ ├── chibivue-store/ │ │ │ ├── package.json │ │ │ └── src/ │ │ │ ├── createStore.ts │ │ │ ├── index.ts │ │ │ ├── rootStore.ts │ │ │ └── store.ts │ │ ├── global.d.ts │ │ ├── vite-plugin-chibivue/ │ │ │ ├── package.json │ │ │ └── src/ │ │ │ ├── index.ts │ │ │ ├── main.ts │ │ │ ├── script.ts │ │ │ ├── template.ts │ │ │ └── utils/ │ │ │ ├── descriptorCache.ts │ │ │ └── query.ts │ │ └── vscode-chibivue/ │ │ ├── language-configuration.json │ │ ├── package.json │ │ ├── src/ │ │ │ └── extension.ts │ │ ├── syntaxes/ │ │ │ └── vue.tmLanguage.json │ │ └── tsconfig.json │ ├── chibivue/ │ │ ├── package.json │ │ └── src/ │ │ └── index.ts │ ├── compiler-core/ │ │ ├── package.json │ │ └── src/ │ │ ├── ast.ts │ │ ├── babelUtils.ts │ │ ├── codegen.ts │ │ ├── compile.ts │ │ ├── index.ts │ │ ├── options.ts │ │ ├── parse.ts │ │ ├── runtimeHelpers.ts │ │ ├── transform.ts │ │ ├── transforms/ │ │ │ ├── hoistStatic.ts │ │ │ ├── transformElement.ts │ │ │ ├── transformExpression.ts │ │ │ ├── vBind.ts │ │ │ ├── vFor.ts │ │ │ ├── vIf.ts │ │ │ ├── vModel.ts │ │ │ └── vOn.ts │ │ └── utils.ts │ ├── compiler-dom/ │ │ ├── package.json │ │ └── src/ │ │ ├── codegen.ts │ │ ├── index.ts │ │ ├── parserOptions.ts │ │ ├── runtimeHelpers.ts │ │ └── transforms/ │ │ ├── vHtml.ts │ │ ├── vModel.ts │ │ ├── vOn.ts │ │ ├── vShow.ts │ │ └── vText.ts │ ├── compiler-sfc/ │ │ ├── package.json │ │ └── src/ │ │ ├── compileScript.ts │ │ ├── compileStyle.ts │ │ ├── compileTemplate.ts │ │ ├── index.ts │ │ ├── parse.ts │ │ └── rewriteDefault.ts │ ├── compiler-ssr/ │ │ ├── package.json │ │ └── src/ │ │ ├── index.ts │ │ ├── runtimeHelpers.ts │ │ ├── ssrCodegenTransform.ts │ │ └── transforms/ │ │ ├── ssrTransformComponent.ts │ │ ├── ssrTransformElement.ts │ │ ├── ssrVFor.ts │ │ └── ssrVIf.ts │ ├── compiler-vapor/ │ │ ├── package.json │ │ └── src/ │ │ ├── codegen.ts │ │ ├── compile.ts │ │ ├── index.ts │ │ ├── ir.ts │ │ ├── runtimeHelpers.ts │ │ └── transform.ts │ ├── reactivity/ │ │ ├── package.json │ │ └── src/ │ │ ├── baseHandler.ts │ │ ├── collectionHandlers.ts │ │ ├── computed.ts │ │ ├── dep.ts │ │ ├── effect.ts │ │ ├── effectScope.ts │ │ ├── index.ts │ │ ├── reactive.ts │ │ └── ref.ts │ ├── runtime-core/ │ │ ├── package.json │ │ └── src/ │ │ ├── apiAsyncComponent.ts │ │ ├── apiComputed.ts │ │ ├── apiCreateApp.ts │ │ ├── apiDefineComponent.ts │ │ ├── apiInject.ts │ │ ├── apiLifecycle.ts │ │ ├── apiWatch.ts │ │ ├── component.ts │ │ ├── componentEmits.ts │ │ ├── componentOptions.ts │ │ ├── componentProps.ts │ │ ├── componentPublicInstance.ts │ │ ├── componentRenderContext.ts │ │ ├── componentRenderUtils.ts │ │ ├── componentSlots.ts │ │ ├── components/ │ │ │ ├── KeepAlive.ts │ │ │ └── Teleport.ts │ │ ├── directives.ts │ │ ├── enums.ts │ │ ├── h.ts │ │ ├── helpers/ │ │ │ ├── renderList.ts │ │ │ ├── resolveAssets.ts │ │ │ └── toHandlers.ts │ │ ├── index.ts │ │ ├── renderer.ts │ │ ├── rendererTemplateRef.ts │ │ ├── scheduler.ts │ │ └── vnode.ts │ ├── runtime-dom/ │ │ ├── package.json │ │ └── src/ │ │ ├── components/ │ │ │ └── Transition.ts │ │ ├── directives/ │ │ │ ├── vModel.ts │ │ │ ├── vOn.ts │ │ │ └── vShow.ts │ │ ├── index.ts │ │ ├── modules/ │ │ │ ├── attrs.ts │ │ │ ├── events.ts │ │ │ └── style.ts │ │ ├── nodeOps.ts │ │ ├── patchProp.ts │ │ └── runtimeHelpers.ts │ ├── runtime-vapor/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ ├── apiCreateVaporApp.ts │ │ ├── component.ts │ │ ├── hydration.ts │ │ └── index.ts │ ├── server-renderer/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── helpers/ │ │ │ │ ├── ssrInterpolate.ts │ │ │ │ ├── ssrRenderAttrs.ts │ │ │ │ ├── ssrRenderList.ts │ │ │ │ └── ssrUtils.ts │ │ │ ├── index.ts │ │ │ ├── render.ts │ │ │ ├── renderToString.ts │ │ │ └── renderVapor.ts │ │ └── tsconfig.json │ └── shared/ │ ├── package.json │ └── src/ │ ├── domAttrConfig.ts │ ├── domTagConfig.ts │ ├── escapeHtml.ts │ ├── index.ts │ ├── makeMap.ts │ ├── normalizeProp.ts │ ├── patchFlags.ts │ ├── shapeFlags.ts │ ├── toDisplayString.ts │ └── typeUtils.ts ├── package.json ├── pnpm-workspace.yaml ├── rolldown.config.ts ├── rules/ │ └── prh-punctuation-mark.yml ├── tools/ │ ├── book-figures/ │ │ └── generate.mjs │ ├── book-size/ │ │ ├── book/ │ │ │ ├── char-counts.json │ │ │ └── count-chars.ts │ │ └── pkg/ │ │ └── files.txt │ ├── chibivue-playground/ │ │ ├── main.ts │ │ └── template/ │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.vue │ │ │ ├── main.ts │ │ │ ├── router.ts │ │ │ ├── store/ │ │ │ │ └── counter.ts │ │ │ └── views/ │ │ │ ├── counter.vue │ │ │ └── store.vue │ │ └── vite.config.ts │ ├── create-chibivue/ │ │ ├── main.ts │ │ └── template/ │ │ ├── examples/ │ │ │ └── playground/ │ │ │ ├── .gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── main.ts │ │ │ ├── tsconfig.json │ │ │ └── vite.config.js │ │ ├── package.json │ │ ├── packages/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── translator/ │ │ └── ja2en/ │ │ ├── completion.ts │ │ ├── constant.ts │ │ ├── init.ts │ │ └── main.ts │ └── vue-playground/ │ ├── generate.ts │ ├── helpers.ts │ ├── locale.ts │ ├── main.ts │ ├── prompt.ts │ └── template/ │ ├── index.html │ ├── package.json.template │ ├── setup/ │ │ ├── dev.ts.template │ │ └── start.ts │ ├── src/ │ │ ├── App.vue │ │ └── main.ts │ ├── tsconfig.json.template │ ├── vite.config.ts │ └── vue-standalone.js.template ├── tsconfig.json ├── vite.config.shared.ts └── vitest.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/contributing.md ================================================ # Contributing to chibivue Are you looking at this page now because you're interested in contributing to chibivue? If so, I'd be very pleased. I'd appreciate it if you could create some pull request, no matter how minor. I've written some guides below on how to contribute, so please take a moment to read them. Thank you to all chibivue fans. 💖 ubugeeei. --- ## Guide to the main directories/files First, let's talk about the top-level directories: ```sh book # Contains materials related to the online book impl # 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.) examples # Contains sample code using the packages. Not directly related to the online book. tools # Development and reader tools for the online book. .github # Contains CI configuration files and contribution guides. ``` Now, let's take a closer look at the book directory. ```sh book |- images # Contains image files used in the online book. |- online-book # The main body of the online book. It is a Vitepress project. |- impls # Contains the source code for each chapter. ``` ## Guide about the method of contribution ### Before submitting a pull request ### Forking the repository Access https://github.com/chibivue-land/chibivue and click on the `Fork` button on this page to fork it to your own account. You can choose any name for the repository. Feel free to set other information as well. ### Setting up the local environment #### Installing the necessary tools - [Node.js](https://nodejs.org/en) (v24+) - [pnpm](https://pnpm.io/) (v10+) ### Getting Started First, install the dependencies and set up the playground. ```sh pnpm i && pnpm setup ``` Then, you can start the development server. ```sh pnpm dev ``` ### Available Scripts | Script | Description | |--------|-------------| | **Book** | | | `dev` | Start online book dev server | | `build` | Build online book | | `preview` | Preview built online book | | `lint:text` | Lint book text | | **Setup** | | | `setup` | Install dependencies and generate playground | | `setup:dev` | Generate playground files to examples/playground | | `setup:vue` | Set up Vue.js core comparison environment | | `setup:book` | Generate chibivue implementation for book readers | | **Implementation** | | | `impl:dev` | Start playground dev server | | `impl:dev:app` | Start app example dev server | | `impl:dev:vapor` | Start vapor mode example dev server | | `impl:dev:vue` | Start Vue.js core dev server for comparison | | `impl:build` | Build all packages | | `impl:clean` | Remove all dist folders | | `impl:check` | Run all checks (lint, fmt, typecheck, build, test) | | **Quality** | | | `check` | Run type checking (tsgo) | | `lint` | Run linter (oxlint) | | `lint:fix` | Run linter with auto-fix | | `fmt` | Format code (oxfmt) | | `fmt:check` | Check code formatting | | `test` | Run tests once | | `test:watch` | Run tests in watch mode | ### Running book chapter implementations If you want to run the source code for each chapter, you can do so with the following command. ```sh cd book/impls/${section-name}/${chapter-name} pnpm dev ``` ### Book Playground The project includes a WebContainer-based playground (`book/playground`) that allows readers to try each chapter's implementation directly in the browser. To start the playground: ```sh pnpm play # Start the playground dev server ``` The playground supports: - Selecting different chapters to explore - Editing code with Monaco editor - Running the development server in the browser - Persisting edits to localStorage - Resetting files to their original state If you modify chapter implementations in `book/impls/`, run `pnpm play:generate` to update the playground data. #### Creating a branch (start making changes) Clone the forked repository and create a branch. Before creating a branch, make sure the upstream is set to the main branch. As for the branch name, if it is related to a specific issue, please use the format `${issue-number}-${description (kebab-case)}`. Please make the description as clear and unique as possible. There are no strict rules at the moment, but please avoid very short names or names that are too generic (lack uniqueness). If it is not related to a specific issue, the issue number is not necessary. It would be helpful if you could create an issue whenever possible. It is not necessary for simple typo fixes. Also, if the changes are expected to be significant or critical in content, it would be appreciated if you could consult with @ubugeeei in advance. (If such changes are made without consultation, there is a possibility that the PR will be rejected depending on the case.) ### Creating commits There are no strict rules regarding the content and granularity of commit messages to the working branch. For commit messages, we have provided a configuration file for git-cz (changelog.config.cjs), so it is recommended to use it (but not required). If you want to use git-cz, you need to install it locally. ```sh npm install -g git-cz ``` ```sh # When you run the cz command with the staged changes, an interactive shell will start. git cz ``` Of course, if you have any suggestions for the git-cz configuration file, please feel free to send a PR. Regarding commit messages, if the issue number is included in the branch name, the issue number will be automatically included in the commit message. This is achieved by husky, and you can check the details in `/.husky/commit-msg`. ### Creating a Pull Request Once 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. Please make the title and description of the Pull Request as clear as possible. Please always notify @ubugeeei when the work is completed to keep track of whether the work is in progress or completed. You can mention @ubugeeei in the PR comment. The same applies when the changes are completed after the review. Also, please make sure to check that the CI has succeeded before reporting completion. Basically, all PRs are managed by @ubugeeei, so please contact @ubugeeei for any inquiries. ## Guide about the contents of contribution This is a guide to the changes you make. Here are a few points to keep in mind. - 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). - When making changes to the source code of each chapter, please appropriately incorporate those changes into the source code of subsequent chapters. - When including images, figures, or text from other sources, please make sure to provide proper attribution. ================================================ FILE: .github/workflows/check.yml ================================================ name: ci on: push: branches: - '**' permissions: contents: read jobs: check: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node uses: actions/setup-node@v4 with: node-version-file: 'package.json' - uses: pnpm/action-setup@v4 name: Install pnpm - name: Get pnpm store directory shell: bash run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache@v4 name: Setup pnpm cache with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies run: pnpm install - name: Install Playwright browsers run: pnpm exec playwright install --with-deps chromium - name: Check fmt run: pnpm fmt:check - name: Check lint run: pnpm lint - name: Check lint text run: pnpm lint:text - name: Check types run: pnpm check - name: Check impl building run: pnpm impl:build - name: Check test run: pnpm test - name: Build book run: pnpm build ================================================ FILE: .github/workflows/deploy.yml ================================================ name: Deploy chibivue Book to Pages on: push: branches: [main] workflow_dispatch: permissions: contents: read pages: write id-token: write concurrency: group: pages cancel-in-progress: false jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Check if it's the original repo and the main branch run: | if [[ "${{ github.repository }}" == "ubugeeei/chibivue" && "${{ github.ref }}" == "refs/heads/main" ]]; then echo "This is the original repo's main branch, running the workflow." else echo "This is a fork or a different branch, skipping the workflow." exit 0 fi - name: Setup Node uses: actions/setup-node@v4 with: node-version-file: 'package.json' - uses: pnpm/action-setup@v4 name: Install pnpm - name: Get pnpm store directory shell: bash run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache@v4 name: Setup pnpm cache with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Setup Pages uses: actions/configure-pages@v5 - name: Install dependencies run: pnpm install - name: Build with VitePress run: pnpm build - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: book/online-book/.vitepress/dist deploy: runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .gitignore ================================================ node_modules dist examples/playground examples/vuejs-core .env tools/translator/ja2en/input.md tools/translator/ja2en/output.md book/online-book/.vitepress/cache book/online-book/.vitepress/dist # for dts-bundle temp # VSCode extension *.vsix .vscode-test out ================================================ FILE: .oxlintrc.json ================================================ { "$schema": "https://raw.githubusercontent.com/oxc-project/oxlint/main/npm/oxlint/configuration_schema.json", "ignorePatterns": ["examples/vuejs-core"], "rules": { "no-unused-vars": "off", "no-unused-expressions": "off", "no-useless-escape": "off", "no-this-alias": "off", "no-async-promise-executor": "off", "only-used-in-recursion": "off", "no-non-null-asserted-optional-chain": "off", "no-wrapper-object-types": "off", "unicorn/no-new-array": "off", "unicorn/no-useless-spread": "off", "unicorn/no-useless-fallback-in-spread": "off" } } ================================================ FILE: .textlintrc.json ================================================ { "rules": { "ja-space-between-half-and-full-width": { "space": "always" }, "prh": { "rulePaths": ["rules/prh-punctuation-mark.yml"] } } } ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": [ "oxc.oxc-vscode" ] } ================================================ FILE: .vscode/settings.json ================================================ { "cSpell.words": [ "antfu", "augmentor", "chainsi", "chibi", "chibivue", "citty", "consola", "cytoscape", "deduped", "deindent", "estree", "fizzbuzz", "hyperscript", "jiti", "klass", "lightningcss", "nocheck", "onwarn", "oxfmt", "oxlint", "paren", "Parens", "RAWTEXT", "RCDATA", "resetsix", "rolldown", "textlint", "tokei", "unref", "vdom", "vitepress", "vnode", "Vnodes", "vuejs" ], "material-icon-theme.folders.associations": { "book": "resource", "impls": "vue", "online-book": "vuepress", "vuejs-core": "vue", "@extensions": "lib" }, "material-icon-theme.files.associations": {}, "editor.formatOnSave": true, "editor.defaultFormatter": "oxc.oxc-vscode", "editor.codeActionsOnSave": { "source.fixAll.oxc": "explicit" }, "oxc.enable": true } ================================================ FILE: CODEOWNERS ================================================ * @ubugeeei /book/online-book/src/zh-cn @resetsix /book/online-book/src/zh-tw @resetsix /book/online-book/.vitepress/config/zh-cn.ts @resetsix /book/online-book/.vitepress/config/zh-tw.ts @resetsix ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2023-present ubugeeei Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README-zh-cn.md ================================================

chibivue

编写 Vue.js:从一行 "Hello, World" 开始,逐步构建。

在线书籍 · Discord · 赞助

English · 繁體中文

--- **chibivue** 是 [Vue.js](https://github.com/vuejs/core) 的最小化教学实现. - 响应式系统 - 虚拟 DOM 和补丁渲染 - 组件系统 - 模板编译器 - SFC 编译器 - Vapor Mode(实验性) > "chibi" 在日语中意思是 "小"。 ## 在线书籍 [![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) | 语言 | 链接 | |------|------| | English | https://book.chibivue.land | | 日本語 | https://book.chibivue.land/ja | | 简体中文 | https://book.chibivue.land/zh-cn | | 繁體中文 | https://book.chibivue.land/zh-tw | ## 快速开始 ### 环境要求 - [Node.js](https://nodejs.org/) v24+ - [pnpm](https://pnpm.io/) v10+ ### 本地阅读书籍 ```sh git clone https://github.com/chibivue-land/chibivue cd chibivue pnpm install pnpm dev ``` ### 尝试实现 ```sh pnpm setup # 生成 playground pnpm impl:dev # 启动开发服务器 ``` ## 附加章节 **超极限超极端最小 Vue** 仅用 **110 行**代码实现 createApp,虚拟 DOM,响应式,模板编译器和 SFC 编译器. [阅读章节](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) ## 贡献 请查看 [contributing.md](.github/contributing.md). ## 社区 加入我们的 [Discord 服务器](https://discord.gg/aVHvmbmSRy) 参与讨论,提问和获取公告. ---
## 赞助商 ubugeeei's sponsors [成为赞助商](https://github.com/sponsors/ubugeeei)
================================================ FILE: README-zh-tw.md ================================================

chibivue

編寫 Vue.js:從一行 "Hello, World" 開始,逐步構建。

線上書籍 · Discord · 贊助

English · 简体中文

--- **chibivue** 是 [Vue.js](https://github.com/vuejs/core) 的最小化教學實現. - 響應式系統 - 虛擬 DOM 和補丁渲染 - 組件系統 - 模板編譯器 - SFC 編譯器 - Vapor Mode(實驗性) > "chibi" 在日語中意思是 "小"。 ## 線上書籍 [![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) | 語言 | 連結 | |------|------| | English | https://book.chibivue.land | | 日本語 | https://book.chibivue.land/ja | | 简体中文 | https://book.chibivue.land/zh-cn | | 繁體中文 | https://book.chibivue.land/zh-tw | ## 快速開始 ### 環境要求 - [Node.js](https://nodejs.org/) v24+ - [pnpm](https://pnpm.io/) v10+ ### 本地閱讀書籍 ```sh git clone https://github.com/chibivue-land/chibivue cd chibivue pnpm install pnpm dev ``` ### 嘗試實現 ```sh pnpm setup # 生成 playground pnpm impl:dev # 啟動開發伺服器 ``` ## 附加章節 **超極限超極端最小 Vue** 僅用 **110 行**程式碼實現 createApp,虛擬 DOM,響應式,模板編譯器和 SFC 編譯器. [閱讀章節](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) ## 貢獻 請查看 [contributing.md](.github/contributing.md). ## 社群 加入我們的 [Discord 伺服器](https://discord.gg/aVHvmbmSRy) 參與討論,提問和獲取公告. ---
## 贊助商 ubugeeei's sponsors [成為贊助商](https://github.com/sponsors/ubugeeei)
================================================ FILE: README.md ================================================

chibivue

Writing Vue.js: Step by Step, from just one line of "Hello, World".

Online Book · Discord · Sponsor

简体中文 · 繁體中文

--- **chibivue** is a minimal implementation of [Vue.js](https://github.com/vuejs/core) for educational purposes. - Reactivity System - Virtual DOM & Patch Rendering - Component System - Template Compiler - SFC Compiler - Vapor Mode (experimental) > "chibi" means "small" in Japanese. ## Online Book [![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) | Language | URL | |----------|-----| | English | https://book.chibivue.land | | 日本語 | https://book.chibivue.land/ja | | 简体中文 | https://book.chibivue.land/zh-cn | | 繁體中文 | https://book.chibivue.land/zh-tw | ## Quick Start ### Requirements - [Node.js](https://nodejs.org/) v24+ - [pnpm](https://pnpm.io/) v10+ ### Read the Book Locally ```sh git clone https://github.com/chibivue-land/chibivue cd chibivue pnpm install pnpm dev ``` ### Try the Implementation ```sh pnpm setup # Generate playground pnpm impl:dev # Start dev server ``` ## Bonus Track **Hyper Ultimate Super Extreme Minimal Vue** Implements createApp, Virtual DOM, Reactivity, Template Compiler, and SFC Compiler in just **110 lines**. [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) ## Contributing See [contributing.md](.github/contributing.md). ## Community Join our [Discord Server](https://discord.gg/aVHvmbmSRy) for discussions, questions, and announcements. ---
## Sponsors ubugeeei's sponsors [Become a Sponsor](https://github.com/sponsors/ubugeeei)
================================================ FILE: book/figures-src/README.md ================================================ # Book figure assets The online book serves figure assets from `book/online-book/src/public/figures`. Use paths that mirror the book structure: ```txt figures//
/. ``` Examples: - `figures/_brand/logo.png` - `figures/_people/ubugeeei-avatar.jpg` - `figures/_sponsors/ubugeeei-sponsors.png` - `figures/10-minimum-example/reactivity/target-map-structure.svg` - `figures/20-basic-virtual-dom/patch-keyed-children/inserted-child-keyed-match.svg` - `figures/50-basic-template-compiler/v-bind/transform-vbind-flow.svg` Generated explanatory diagrams are SVG files created by `tools/book-figures/generate.mjs`. Regenerate them with: ```sh node tools/book-figures/generate.mjs ``` Screenshots should keep their closest article directory and use descriptive names such as `*-result.png`, `*-console.png`, or `*-flow.png`. Legacy pre-rebrand assets are archived here: - `legacy-drawio/`: old draw.io source files - `legacy-raster/`: old exported raster diagrams - `unreferenced/`: old files that are not currently used by the online book ================================================ FILE: book/figures-src/legacy-drawio/c1c2map.drawio ================================================ ================================================ FILE: book/figures-src/legacy-drawio/c1c2map_deleted.drawio ================================================ ================================================ FILE: book/figures-src/legacy-drawio/c1c2map_inserted.drawio ================================================ ================================================ FILE: book/figures-src/legacy-drawio/c1c2map_inserted_correct.drawio ================================================ ================================================ FILE: book/figures-src/legacy-drawio/reactive_observer.drawio ================================================ ================================================ FILE: book/impls/00_introduction/010_project_setup/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/00_introduction/010_project_setup/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/00_introduction/010_project_setup/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/00_introduction/010_project_setup/examples/playground/src/main.ts ================================================ import "chibivue"; ================================================ FILE: book/impls/00_introduction/010_project_setup/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/00_introduction/010_project_setup/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/00_introduction/010_project_setup/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/00_introduction/010_project_setup/packages/index.ts ================================================ console.log("Hello chibivue!"); ================================================ FILE: book/impls/00_introduction/010_project_setup/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/010_create_app/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/010_create_app/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/010_create_app/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/010_create_app/examples/playground/src/main.ts ================================================ import { createApp } from "chibivue"; const app = createApp({ render() { return "Hello world."; }, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/010_create_app/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/010_create_app/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/010_create_app/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/010_create_app/packages/index.ts ================================================ export type Options = { render: () => string; }; export type App = { mount: (selector: string) => void; }; export const createApp = (options: Options): App => { return { mount: (selector) => { const root = document.querySelector(selector); if (root) { root.innerHTML = options.render(); } }, }; }; ================================================ FILE: book/impls/10_minimum_example/010_create_app/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createApp } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("10_minimum_example/010_create_app", () => { it("should render a message", () => { const app = createApp({ render() { return "Hello world."; }, }); app.mount("#host"); expect(host.innerHTML).toBe("Hello world."); }); }); ================================================ FILE: book/impls/10_minimum_example/010_create_app/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/015_package_architecture/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/examples/playground/src/main.ts ================================================ import { createApp } from "chibivue"; const app = createApp({ render() { return "Hello world."; }, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/packages/index.ts ================================================ export * from "./runtime-dom"; ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { const message = rootComponent.render!(); render(message, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/packages/runtime-core/component.ts ================================================ import type { ComponentOptions } from "./componentOptions"; export type Component = ComponentOptions; ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { render?: Function; }; ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/packages/runtime-core/renderer.ts ================================================ export type RootRenderFunction = ( message: string, container: HostElement, ) => void; export interface RendererOptions { setElementText(node: HostNode, text: string): void; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { setElementText: hostSetElementText } = options; const render: RootRenderFunction = (message, container) => { hostSetElementText(container, message); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; const { render } = createRenderer(nodeOps); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: RendererOptions = { setElementText(node, text) { node.textContent = text; }, }; ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createApp } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("10_minimum_example/015_package_architecture", () => { it("should render a message", () => { const app = createApp({ render() { return "Hello world."; }, }); app.mount("#host"); expect(host.innerHTML).toBe("Hello world."); }); }); ================================================ FILE: book/impls/10_minimum_example/015_package_architecture/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/examples/playground/src/main.ts ================================================ import { createApp, h } from "chibivue"; const app = createApp({ render() { return h("div", { id: "my-app" }, [ h("p", { style: "color: red; font-weight: bold;" }, ["Hello world."]), h( "button", { onClick() { alert("Hello world!"); }, }, ["click me!"], ), ]); }, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { const vnode = rootComponent.render!(); render(vnode, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/component.ts ================================================ import type { ComponentOptions } from "./componentOptions"; export type Component = ComponentOptions; ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { render?: Function; }; ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/h.ts ================================================ import type { VNode, VNodeProps } from "./vnode"; export function h(type: string, props: VNodeProps, children: (VNode | string)[]) { return { type, props, children }; } ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/renderer.ts ================================================ import type { VNode } from "./vnode"; export type RootRenderFunction = ( vnode: VNode, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, insert: hostInsert, } = options; function renderVNode(vnode: VNode | string) { if (typeof vnode === "string") return hostCreateText(vnode); const el = hostCreateElement(vnode.type); Object.entries(vnode.props).forEach(([key, value]) => { hostPatchProp(el, key, value); }); for (const child of vnode.children) { const childEl = renderVNode(child); hostInsert(childEl, el); } return el; } const render: RootRenderFunction = (vnode, container) => { const el = renderVNode(vnode); hostInsert(el, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/vnode.ts ================================================ export interface VNode { type: string; props: VNodeProps; children: (VNode | string)[]; } export interface VNodeProps { [key: string]: any; } ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text: string) => { return document.createTextNode(text); }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, }; ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createApp, h } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("10_minimum_example/020_simple_h_function", () => { it("should render with h function", () => { const app = createApp({ render() { return h("div", { id: "my-app" }, [h("p", {}, ["Hello world."])]); }, }); app.mount("#host"); expect(host.innerHTML).toBe('

Hello world.

'); }); it("should handle click events", () => { const onClick = vi.fn(); const app = createApp({ render() { return h("button", { id: "btn", onClick }, ["click me"]); }, }); app.mount("#host"); const btn = host.querySelector("#btn") as HTMLButtonElement; btn.click(); expect(onClick).toHaveBeenCalled(); }); }); ================================================ FILE: book/impls/10_minimum_example/020_simple_h_function/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/030_reactive_system/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/examples/playground/src/main.ts ================================================ import { createApp, h, reactive } from "chibivue"; const app = createApp({ setup() { const state = reactive({ count: 0 }); const increment = () => state.count++; return function render() { return h("div", { id: "my-app" }, [ h("p", {}, [`count: ${state.count}`]), h("button", { onClick: increment }, ["increment"]), ]); }; }, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/apiCreateApp.ts ================================================ import { ReactiveEffect } from "../reactivity"; import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { const componentRender = rootComponent.setup!(); const updateComponent = () => { const vnode = componentRender(); render(vnode, rootContainer); }; const effect = new ReactiveEffect(updateComponent); effect.run(); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/component.ts ================================================ import type { ComponentOptions } from "./componentOptions"; export type Component = ComponentOptions; ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { render?: Function; setup?: () => Function; }; ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/h.ts ================================================ import type { VNode, VNodeProps } from "./vnode"; export function h(type: string, props: VNodeProps, children: (VNode | string)[]) { return { type, props, children }; } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/renderer.ts ================================================ import type { VNode } from "./vnode"; export type RootRenderFunction = ( vnode: VNode, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, insert: hostInsert, } = options; function renderVNode(vnode: VNode | string) { if (typeof vnode === "string") return hostCreateText(vnode); const el = hostCreateElement(vnode.type); Object.entries(vnode.props).forEach(([key, value]) => { hostPatchProp(el, key, value); }); for (const child of vnode.children) { const childEl = renderVNode(child); hostInsert(childEl, el); } return el; } const render: RootRenderFunction = (vnode, container) => { while (container.firstChild) container.removeChild(container.firstChild); const el = renderVNode(vnode); hostInsert(el, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/vnode.ts ================================================ export interface VNode { type: string; props: VNodeProps; children: (VNode | string)[]; } export interface VNodeProps { [key: string]: any; } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text: string) => { return document.createTextNode(text); }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, }; ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createApp, h, reactive } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("10_minimum_example/030_reactive_system", () => { it("should render reactive state", () => { const state = reactive({ count: 0 }); const onClick = vi.fn(() => { state.count++; }); const app = createApp({ setup() { return function render() { return h("div", { id: "my-app" }, [ h("p", {}, [`count: ${state.count}`]), h("button", { id: "btn", onClick }, ["increment"]), ]); }; }, }); app.mount("#host"); expect(host.innerHTML).toBe( '

count: 0

', ); const btn = host.querySelector("#btn") as HTMLButtonElement; btn.click(); expect(onClick).toHaveBeenCalled(); expect(host.innerHTML).toBe( '

count: 1

', ); }); }); ================================================ FILE: book/impls/10_minimum_example/030_reactive_system/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/040_vdom_system/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/examples/playground/src/main.ts ================================================ import { createApp, h, reactive } from "chibivue"; const app = createApp({ setup() { const state = reactive({ count: 0 }); const increment = () => state.count++; return function render() { return h("div", { id: "my-app" }, [ h("p", {}, [`count: ${state.count}`]), h("button", { onClick: increment }, ["increment"]), ]); }; }, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/component.ts ================================================ import type { ComponentOptions } from "./componentOptions"; export type Component = ComponentOptions; ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { render?: Function; setup?: () => Function; }; ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import type { Component } from "./component"; import { Text, type VNode, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, } = options; const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => { const { type } = n2; if (type === Text) { processText(n1, n2, container); } else { processElement(n1, n2, container); } }; const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountElement(n2, container); } else { patchElement(n1, n2); } }; const mountElement = (vnode: VNode, container: RendererElement) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = (children: VNode[], container: RendererElement) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container); } }; const patchElement = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; for (let i = 0; i < c2.length; i++) { const child = (c2[i] = normalizeVNode(c2[i])); patch(c1[i], child, container); } }; const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const render: RootRenderFunction = (rootComponent, container) => { const componentRender = rootComponent.setup!(); let n1: VNode | null = null; const updateComponent = () => { const n2 = componentRender(); patch(n1, n2, container); n1 = n2; }; const effect = new ReactiveEffect(updateComponent); effect.run(); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/vnode.ts ================================================ export type VNodeTypes = string | typeof Text; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, }; ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createApp, h, reactive } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("10_minimum_example/040_vdom_system", () => { it("should render reactive state (vdom patch)", () => { const state = reactive({ count: 0 }); const onClick = vi.fn(() => { state.count++; }); const app = createApp({ setup() { return function render() { return h("div", { id: "my-app" }, [ h("p", {}, [`count: ${state.count}`]), h("button", { id: "btn", onClick }, ["increment"]), ]); }; }, }); app.mount("#host"); expect(host.innerHTML).toBe( '

count: 0

', ); const btn = host.querySelector("#btn") as HTMLButtonElement; btn.click(); expect(onClick).toHaveBeenCalled(); expect(host.innerHTML).toBe( '

count: 1

', ); }); }); ================================================ FILE: book/impls/10_minimum_example/040_vdom_system/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/050_component_system/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/050_component_system/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/050_component_system/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/050_component_system/examples/playground/src/main.ts ================================================ import { createApp, h, reactive } from "chibivue"; const CounterComponent = { setup() { const state = reactive({ count: 0 }); const increment = () => state.count++; return () => h("div", {}, [ h("p", {}, [`count: ${state.count}`]), h("button", { onClick: increment }, ["increment"]), ]); }, }; const app = createApp({ setup: () => { return () => h("div", { id: "my-app" }, [ h(CounterComponent, {}, []), h(CounterComponent, {}, []), h(CounterComponent, {}, []), ]); }, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/050_component_system/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/050_component_system/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/050_component_system/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import type { ComponentOptions } from "./componentOptions"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; isMounted: boolean; } export type InternalRenderFunction = { (): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, isMounted: false, }; return instance; } ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { render?: Function; setup?: () => Function; }; ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { type Component, type ComponentInternalInstance, type InternalRenderFunction, createComponentInstance, } from "./component"; import { Text, type VNode, createVNode, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, parentNode: hostParentNode, } = options; const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => { const { type } = n2; if (type === Text) { processText(n1, n2, container); } else if (typeof type === "string") { processElement(n1, n2, container); } else if (typeof type === "object") { processComponent(n1, n2, container); } else { // do nothing } }; const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountElement(n2, container); } else { patchElement(n1, n2); } }; const mountElement = (vnode: VNode, container: RendererElement) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = (children: VNode[], container: RendererElement) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container); } }; const patchElement = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; for (let i = 0; i < c2.length; i++) { const child = (c2[i] = normalizeVNode(c2[i])); patch(c1[i], child, container); } }; const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountComponent(n2, container); } else { updateComponent(n1, n2); } }; const mountComponent = (initialVNode: VNode, container: RendererElement) => { const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); const component = initialVNode.type as Component; if (component.setup) { instance.render = component.setup() as InternalRenderFunction; } setupRenderEffect(instance, initialVNode, container); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, ) => { const componentUpdateFn = () => { const { render } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render())); patch(null, subTree, container); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render()); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-core/vnode.ts ================================================ import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; component: ComponentInternalInstance | null; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined, component: null, }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/10_minimum_example/050_component_system/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/050_component_system/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createApp, h, reactive } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("10_minimum_example/050_component_system", () => { it("should render component", () => { const state = reactive({ count: 0 }); const onClick = vi.fn(() => { state.count++; }); const Comp = { setup() { return () => h("div", {}, [ h("p", {}, [`count: ${state.count}`]), h("button", { id: "btn", onClick }, ["increment"]), ]); }, }; const App = createApp({ setup: () => { return () => h("div", { id: "my-app" }, [h(Comp, {}, [])]); }, }); App.mount("#host"); expect(host.innerHTML).toBe( // prettier-ignore '
' + '
' + '

count: 0

' + '' + '
' + '
', ); const btn = host.querySelector("#btn") as HTMLButtonElement; btn.click(); expect(onClick).toHaveBeenCalled(); expect(host.innerHTML).toBe( // prettier-ignore '
' + '
' + '

count: 1

' + '' + '
' + '
', ); }); }); ================================================ FILE: book/impls/10_minimum_example/050_component_system/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/050_component_system2/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/050_component_system2/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/examples/playground/src/main.ts ================================================ import { createApp, h, reactive } from "chibivue"; const MyComponent = { props: { message: { type: String } }, setup(props: { message: string }) { return () => h("div", { id: "my-app" }, [`message: ${props.message}`]); }, }; const app = createApp({ setup() { const state = reactive({ message: "hello" }); const changeMessage = () => { state.message += "!"; }; return () => h("div", { id: "my-app" }, [ h(MyComponent, { message: state.message }, []), h("button", { onClick: changeMessage }, ["change message"]), ]); }, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/050_component_system2/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/050_component_system2/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import type { ComponentOptions } from "./componentOptions"; import type { Props } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; isMounted: boolean; } export type InternalRenderFunction = { (): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, isMounted: false, }; return instance; } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: (props: Record) => Function; render?: Function; }; ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.assign(props, rawProps); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; if (options && options.hasOwnProperty(key)) { props[key] = value; } } } } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { type Component, type ComponentInternalInstance, type InternalRenderFunction, createComponentInstance, } from "./component"; import { initProps, updateProps } from "./componentProps"; import { Text, type VNode, createVNode, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, parentNode: hostParentNode, } = options; const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => { const { type } = n2; if (type === Text) { processText(n1, n2, container); } else if (typeof type === "string") { processElement(n1, n2, container); } else if (typeof type === "object") { processComponent(n1, n2, container); } else { // do nothing } }; const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountElement(n2, container); } else { patchElement(n1, n2); } }; const mountElement = (vnode: VNode, container: RendererElement) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = (children: VNode[], container: RendererElement) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container); } }; const patchElement = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; for (let i = 0; i < c2.length; i++) { const child = (c2[i] = normalizeVNode(c2[i])); patch(c1[i], child, container); } }; const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountComponent(n2, container); } else { updateComponent(n1, n2); } }; const mountComponent = (initialVNode: VNode, container: RendererElement) => { const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); const { props } = instance.vnode; initProps(instance, props); const component = initialVNode.type as Component; if (component.setup) { instance.render = component.setup(instance.props) as InternalRenderFunction; } setupRenderEffect(instance, initialVNode, container); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, ) => { const componentUpdateFn = () => { const { render } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render())); patch(null, subTree, container); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render()); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-core/vnode.ts ================================================ import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; component: ComponentInternalInstance | null; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined, component: null, }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/10_minimum_example/050_component_system2/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/050_component_system2/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createApp, h, reactive } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("10_minimum_example/050_component_system2", () => { it("should component props", () => { const state = reactive({ count: 0 }); const onClick = vi.fn(() => { state.count++; }); const Comp = { props: { count: { type: Number } }, setup(props: { count: number }) { return () => h("div", {}, [`count: ${props.count}`]); }, }; const App = createApp({ setup() { return () => h("div", { id: "my-app" }, [ h(Comp, { count: state.count }, []), h("button", { id: "btn", onClick }, ["increment"]), ]); }, }); App.mount("#host"); expect(host.innerHTML).toBe( // prettier-ignore '
' + '
count: 0
' + '' + '
', ); const btn = host.querySelector("#btn") as HTMLButtonElement; btn.click(); expect(onClick).toHaveBeenCalled(); expect(host.innerHTML).toBe( // prettier-ignore '
' + '
count: 1
' + '' + '
', ); }); }); ================================================ FILE: book/impls/10_minimum_example/050_component_system2/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/050_component_system3/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/050_component_system3/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/examples/playground/src/main.ts ================================================ import { createApp, h, reactive } from "chibivue"; const MyComponent = { props: { someMessage: { type: String } }, setup(props: any, { emit }: any) { return () => h("div", {}, [ h("p", {}, [`someMessage: ${props.someMessage}`]), h("button", { onClick: () => emit("click:change-message") }, ["change message"]), ]); }, }; const app = createApp({ setup() { const state = reactive({ message: "hello" }); const changeMessage = () => { state.message += "!"; }; return () => h("div", { id: "my-app" }, [ h( MyComponent, { "some-message": state.message, "onClick:change-message": changeMessage, }, [], ), ]); }, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/050_component_system3/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/050_component_system3/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import type { Props } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; isMounted: boolean; } export type InternalRenderFunction = { (): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function; render?: Function; }; ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { type Component, type ComponentInternalInstance, type InternalRenderFunction, createComponentInstance, } from "./component"; import { initProps, updateProps } from "./componentProps"; import { Text, type VNode, createVNode, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, parentNode: hostParentNode, } = options; const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => { const { type } = n2; if (type === Text) { processText(n1, n2, container); } else if (typeof type === "string") { processElement(n1, n2, container); } else if (typeof type === "object") { processComponent(n1, n2, container); } else { // do nothing } }; const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountElement(n2, container); } else { patchElement(n1, n2); } }; const mountElement = (vnode: VNode, container: RendererElement) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = (children: VNode[], container: RendererElement) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container); } }; const patchElement = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; for (let i = 0; i < c2.length; i++) { const child = (c2[i] = normalizeVNode(c2[i])); patch(c1[i], child, container); } }; const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountComponent(n2, container); } else { updateComponent(n1, n2); } }; const mountComponent = (initialVNode: VNode, container: RendererElement) => { const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); const { props } = instance.vnode; initProps(instance, props); const component = initialVNode.type as Component; if (component.setup) { instance.render = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; } setupRenderEffect(instance, initialVNode, container); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, ) => { const componentUpdateFn = () => { const { render } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render())); patch(null, subTree, container); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render()); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-core/vnode.ts ================================================ import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; component: ComponentInternalInstance | null; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined, component: null, }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/shared/general.ts ================================================ const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/10_minimum_example/050_component_system3/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/10_minimum_example/050_component_system3/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createApp, h, reactive } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("10_minimum_example/050_component_system3", () => { it("should component props and handle emitted event", () => { const state = reactive({ count: 0 }); const onClickIncrement = vi.fn(() => { state.count++; }); const Comp = { props: { count: { type: Number } }, setup(props: { count: number }, { emit }: any) { return () => h("div", {}, [ h("div", {}, [`count: ${props.count}`]), h("button", { id: "btn", onClick: () => emit("click:increment") }, ["increment"]), ]); }, }; const App = createApp({ setup() { return () => h("div", { id: "my-app" }, [ h(Comp, { count: state.count, "onClick:increment": onClickIncrement }, []), ]); }, }); App.mount("#host"); expect(host.innerHTML).toBe( // prettier-ignore '
' + '
' + '
count: 0
' + '' + '
' + '
', ); const btn = host.querySelector("#btn") as HTMLButtonElement; btn.click(); expect(onClickIncrement).toHaveBeenCalled(); expect(host.innerHTML).toBe( // prettier-ignore '
' + '
' + '
count: 1
' + '' + '
' + '
', ); }); }); ================================================ FILE: book/impls/10_minimum_example/050_component_system3/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/060_template_compiler/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/examples/playground/src/main.ts ================================================ import { createApp } from "chibivue"; const app = createApp({ template: `Hello World!!`, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, ATTRIBUTE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/compiler-core/codegen.ts ================================================ export const generate = ({ tag, props, textContent, }: { tag: string; props: Record; textContent: string; }): string => { return `return () => { const { h } = ChibiVue; return h("${tag}", { ${Object.entries(props) .map(([k, v]) => `${k}: "${v}"`) .join(", ")} }, ["${textContent}"]); }`; }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import { baseParse } from "./parse"; export function baseCompile(template: string) { const parseResult = baseParse(template); const code = generate(parseResult); return code; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/compiler-core/parse.ts ================================================ export const baseParse = ( content: string, ): { tag: string; props: Record; textContent: string } => { const matched = content.match(/<(\w+)\s+([^>]*)>([^<]*)<\/\1>/); if (!matched) return { tag: "", props: {}, textContent: "" }; const [_, tag, attrs, textContent] = matched; const props: Record = {}; attrs.replace(/(\w+)=["']([^"']*)["']/g, (_, key: string, value: string) => { props[key] = value; return ""; }); return { tag, props, textContent }; }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/compiler-dom/index.ts ================================================ import { baseCompile } from "../compiler-core"; export function compile(template: string) { return baseCompile(template); } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; isMounted: boolean; } export type InternalRenderFunction = { (): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { instance.render = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; } // ------------------------ ここ if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function; render?: Function; template?: string; }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { Text, type VNode, createVNode, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, parentNode: hostParentNode, } = options; const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => { const { type } = n2; if (type === Text) { processText(n1, n2, container); } else if (typeof type === "string") { processElement(n1, n2, container); } else if (typeof type === "object") { processComponent(n1, n2, container); } else { // do nothing } }; const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountElement(n2, container); } else { patchElement(n1, n2); } }; const mountElement = (vnode: VNode, container: RendererElement) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = (children: VNode[], container: RendererElement) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container); } }; const patchElement = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; for (let i = 0; i < c2.length; i++) { const child = (c2[i] = normalizeVNode(c2[i])); patch(c1[i], child, container); } }; const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountComponent(n2, container); } else { updateComponent(n1, n2); } }; const mountComponent = (initialVNode: VNode, container: RendererElement) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, ) => { const componentUpdateFn = () => { const { render } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render())); patch(null, subTree, container); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render()); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/vnode.ts ================================================ import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; component: ComponentInternalInstance | null; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined, component: null, }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/shared/general.ts ================================================ const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createApp } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("10_minimum_example/060_template_compiler", () => { it("should render template option", () => { const app = createApp({ template: `Hello World!!`, }); app.mount("#host"); expect(host.innerHTML).toBe('Hello World!!'); }); }); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/examples/playground/src/main.ts ================================================ import { createApp } from "chibivue"; const app = createApp({ template: `

Hello, chibivue!

Vue.js Logo

chibivue is the minimal Vue.js

`, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, ATTRIBUTE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/compiler-core/codegen.ts ================================================ import { type ElementNode, NodeTypes, type TemplateChildNode, type TextNode } from "./ast"; export const generate = ({ children }: { children: TemplateChildNode[] }): string => { return `return function render() { const { h } = ChibiVue; return ${genNode(children[0])}; }`; }; const genNode = (node: TemplateChildNode): string => { switch (node.type) { case NodeTypes.ELEMENT: return genElement(node); case NodeTypes.TEXT: return genText(node); default: return ""; } }; const genElement = (el: ElementNode): string => { return `h("${el.tag}", {${el.props .map(({ name, value }) => `${name}: "${value?.content}"`) .join(", ")}}, [${el.children.map((it) => genNode(it)).join(", ")}])`; }; const genText = (text: TextNode): string => { return `\`${text.content}\``; }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import { baseParse } from "./parse"; export function baseCompile(template: string) { const parseResult = baseParse(template.trim()); const code = generate(parseResult); return code; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/compiler-core/parse.ts ================================================ import { type AttributeNode, type ElementNode, NodeTypes, type Position, type SourceLocation, type TemplateChildNode, type TextNode, } from "./ast"; export interface ParserContext { readonly originalSource: string; source: string; offset: number; line: number; column: number; } function createParserContext(content: string): ParserContext { return { originalSource: content, source: content, column: 1, line: 1, offset: 0, }; } export const baseParse = (content: string): { children: TemplateChildNode[] } => { const context = createParserContext(content); const children = parseChildren(context, []); return { children: children }; }; function parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] { const nodes: TemplateChildNode[] = []; while (!isEnd(context, ancestors)) { const s = context.source; let node: TemplateChildNode | undefined = undefined; if (s[0] === "<") { if (/[a-z]/i.test(s[1])) { node = parseElement(context, ancestors); } } if (!node) { node = parseText(context); } pushNode(nodes, node); } return nodes; } function advanceBy(context: ParserContext, numberOfCharacters: number): void { const { source } = context; advancePositionWithMutation(context, source, numberOfCharacters); context.source = source.slice(numberOfCharacters); } function advancePositionWithMutation( pos: Position, source: string, numberOfCharacters: number = source.length, ): Position { let linesCount = 0; let lastNewLinePos = -1; for (let i = 0; i < numberOfCharacters; i++) { if (source.charCodeAt(i) === 10 /* newline char code */) { linesCount++; lastNewLinePos = i; } } pos.offset += numberOfCharacters; pos.line += linesCount; pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; return pos; } function isEnd(context: ParserContext, ancestors: ElementNode[]): boolean { const s = context.source; if (startsWith(s, "= 0; --i) { if (startsWithEndTagOpen(s, ancestors[i].tag)) { return true; } } } return !s; } function startsWith(source: string, searchString: string): boolean { return source.startsWith(searchString); } function advanceSpaces(context: ParserContext): void { const match = /^[\t\r\n\f ]+/.exec(context.source); if (match) { advanceBy(context, match[0].length); } } function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void { if (node.type === NodeTypes.TEXT) { const prev = last(nodes); if (prev && prev.type === NodeTypes.TEXT) { prev.content += node.content; return; } } nodes.push(node); } function parseText(context: ParserContext): TextNode { const endToken = "<"; let endIndex = context.source.length; const index = context.source.indexOf(endToken, 1); if (index !== -1 && endIndex > index) { endIndex = index; } const start = getCursor(context); const content = parseTextData(context, endIndex); return { type: NodeTypes.TEXT, content, loc: getSelection(context, start), }; } const enum TagType { Start, End, } function parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined { // Start tag. const element = parseTag(context, TagType.Start); // TODO: if (element.isSelfClosing) { return element; } // Children. ancestors.push(element); const children = parseChildren(context, ancestors); ancestors.pop(); element.children = children; // End tag. if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(context, TagType.End); // TODO: } return element; } function parseTag(context: ParserContext, type: TagType): ElementNode { // Tag open. const start = getCursor(context); const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!; const tag = match[1]; advanceBy(context, match[0].length); advanceSpaces(context); // Attributes. let props = parseAttributes(context, type); // Tag close. let isSelfClosing = false; isSelfClosing = startsWith(context.source, "/>"); advanceBy(context, isSelfClosing ? 2 : 1); return { type: NodeTypes.ELEMENT, tag, props, children: [], isSelfClosing, loc: getSelection(context, start), }; } function parseAttributes(context: ParserContext, type: TagType): AttributeNode[] { const props = []; const attributeNames = new Set(); while ( context.source.length > 0 && !startsWith(context.source, ">") && !startsWith(context.source, "/>") ) { const attr = parseAttribute(context, attributeNames); if (type === TagType.Start) { props.push(attr); } advanceSpaces(context); } return props; } type AttributeValue = | { content: string; loc: SourceLocation; } | undefined; function parseAttribute(context: ParserContext, nameSet: Set): AttributeNode { // Name. const start = getCursor(context); const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!; const name = match[0]; nameSet.add(name); advanceBy(context, name.length); // Value let value: AttributeValue = undefined; if (/^[\t\r\n\f ]*=/.test(context.source)) { advanceSpaces(context); advanceBy(context, 1); advanceSpaces(context); value = parseAttributeValue(context); } const loc = getSelection(context, start); return { type: NodeTypes.ATTRIBUTE, name, value: value && { type: NodeTypes.TEXT, content: value.content, loc: value.loc, }, loc, }; } function parseAttributeValue(context: ParserContext): AttributeValue { const start = getCursor(context); let content: string; const quote = context.source[0]; const isQuoted = quote === `"` || quote === `'`; if (isQuoted) { // Quoted value. advanceBy(context, 1); const endIndex = context.source.indexOf(quote); if (endIndex === -1) { content = parseTextData(context, context.source.length); } else { content = parseTextData(context, endIndex); advanceBy(context, 1); } } else { // Unquoted const match = /^[^\t\r\n\f >]+/.exec(context.source); if (!match) { return undefined; } content = parseTextData(context, match[0].length); } return { content, loc: getSelection(context, start) }; } function parseTextData(context: ParserContext, length: number): string { const rawText = context.source.slice(0, length); advanceBy(context, length); return rawText; } function getCursor(context: ParserContext): Position { const { column, line, offset } = context; return { column, line, offset }; } function getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation { end = end || getCursor(context); return { start, end, source: context.originalSource.slice(start.offset, end.offset), }; } function last(xs: T[]): T | undefined { return xs[xs.length - 1]; } function startsWithEndTagOpen(source: string, tag: string): boolean { return ( startsWith(source, "]/.test(source[2 + tag.length] || ">") ); } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/compiler-dom/index.ts ================================================ import { baseCompile } from "../compiler-core"; export function compile(template: string) { return baseCompile(template); } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; isMounted: boolean; } export type InternalRenderFunction = { (): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { instance.render = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; } // ------------------------ ここ if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function; render?: Function; template?: string; }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { Text, type VNode, createVNode, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, parentNode: hostParentNode, } = options; const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => { const { type } = n2; if (type === Text) { processText(n1, n2, container); } else if (typeof type === "string") { processElement(n1, n2, container); } else if (typeof type === "object") { processComponent(n1, n2, container); } else { // do nothing } }; const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountElement(n2, container); } else { patchElement(n1, n2); } }; const mountElement = (vnode: VNode, container: RendererElement) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = (children: VNode[], container: RendererElement) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container); } }; const patchElement = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; for (let i = 0; i < c2.length; i++) { const child = (c2[i] = normalizeVNode(c2[i])); patch(c1[i], child, container); } }; const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountComponent(n2, container); } else { updateComponent(n1, n2); } }; const mountComponent = (initialVNode: VNode, container: RendererElement) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, ) => { const componentUpdateFn = () => { const { render } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render())); patch(null, subTree, container); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render()); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/vnode.ts ================================================ import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; component: ComponentInternalInstance | null; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined, component: null, }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/shared/general.ts ================================================ const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createApp } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("10_minimum_example/060_template_compiler", () => { it("should render template option", () => { const app = createApp({ template: `

Hello, chibivue!

Vue.js Logo

chibivue is the minimal Vue.js

`, }); app.mount("#host"); expect(host.innerHTML).toBe( `

Hello, chibivue!

Vue.js Logo

chibivue is the minimal Vue.js

`, ); }); }); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler2/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/examples/playground/src/main.ts ================================================ import { createApp, reactive } from "chibivue"; const app = createApp({ setup() { const state = reactive({ message: "Hello, chibivue!", input: "" }); const changeMessage = () => { state.message += "!"; }; const handleInput = (e: InputEvent) => { state.input = (e.target as HTMLInputElement)?.value ?? ""; }; return { state, changeMessage, handleInput }; }, template: `

{{ state.message }}

Vue.js Logo

chibivue is the minimal Vue.js


input value: {{ state.input }}

`, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, INTERPOLATION, ATTRIBUTE, DIRECTIVE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode | InterpolationNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE; name: string; exp: string; arg: string; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } export interface InterpolationNode extends Node { type: NodeTypes.INTERPOLATION; content: string; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/compiler-core/codegen.ts ================================================ import { toHandlerKey } from "../shared"; import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type TemplateChildNode, type TextNode, } from "./ast"; export const generate = ({ children }: { children: TemplateChildNode[] }): string => { return `return function render(_ctx) { with (_ctx) { const { h } = ChibiVue; return ${genNode(children[0])}; } }`; }; const genNode = (node: TemplateChildNode): string => { switch (node.type) { case NodeTypes.ELEMENT: return genElement(node); case NodeTypes.TEXT: return genText(node); case NodeTypes.INTERPOLATION: return genInterpolation(node); default: return ""; } }; const genElement = (el: ElementNode): string => { return `h("${el.tag}", {${el.props .map((prop) => genProp(prop)) .join(", ")}}, [${el.children.map((it) => genNode(it)).join(", ")}])`; }; const genProp = (prop: AttributeNode | DirectiveNode): string => { switch (prop.type) { case NodeTypes.ATTRIBUTE: return `${prop.name}: "${prop.value?.content}"`; case NodeTypes.DIRECTIVE: { switch (prop.name) { case "on": return `${toHandlerKey(prop.arg)}: ${prop.exp}`; default: // TODO: other directives throw new Error(`unexpected directive name. got "${prop.name}"`); } } default: throw new Error(`unexpected prop type.`); } }; const genText = (text: TextNode): string => { return `\`${text.content}\``; }; const genInterpolation = (node: InterpolationNode): string => { return `${node.content}`; }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import { baseParse } from "./parse"; export function baseCompile(template: string) { const parseResult = baseParse(template.trim()); const code = generate(parseResult); return code; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/compiler-core/parse.ts ================================================ import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type Position, type SourceLocation, type TemplateChildNode, type TextNode, } from "./ast"; export interface ParserContext { readonly originalSource: string; source: string; offset: number; line: number; column: number; } function createParserContext(content: string): ParserContext { return { originalSource: content, source: content, column: 1, line: 1, offset: 0, }; } export const baseParse = (content: string): { children: TemplateChildNode[] } => { const context = createParserContext(content); const children = parseChildren(context, []); return { children: children }; }; function parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] { const nodes: TemplateChildNode[] = []; while (!isEnd(context, ancestors)) { const s = context.source; let node: TemplateChildNode | undefined = undefined; if (startsWith(s, "{{")) { node = parseInterpolation(context); } else if (s[0] === "<") { if (/[a-z]/i.test(s[1])) { node = parseElement(context, ancestors); } } if (!node) { node = parseText(context); } pushNode(nodes, node); } return nodes; } function advanceBy(context: ParserContext, numberOfCharacters: number): void { const { source } = context; advancePositionWithMutation(context, source, numberOfCharacters); context.source = source.slice(numberOfCharacters); } function advancePositionWithMutation( pos: Position, source: string, numberOfCharacters: number = source.length, ): Position { let linesCount = 0; let lastNewLinePos = -1; for (let i = 0; i < numberOfCharacters; i++) { if (source.charCodeAt(i) === 10 /* newline char code */) { linesCount++; lastNewLinePos = i; } } pos.offset += numberOfCharacters; pos.line += linesCount; pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; return pos; } function isEnd(context: ParserContext, ancestors: ElementNode[]): boolean { const s = context.source; if (startsWith(s, "= 0; --i) { if (startsWithEndTagOpen(s, ancestors[i].tag)) { return true; } } } return !s; } function startsWith(source: string, searchString: string): boolean { return source.startsWith(searchString); } function advanceSpaces(context: ParserContext): void { const match = /^[\t\r\n\f ]+/.exec(context.source); if (match) { advanceBy(context, match[0].length); } } function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void { if (node.type === NodeTypes.TEXT) { const prev = last(nodes); if (prev && prev.type === NodeTypes.TEXT) { prev.content += node.content; return; } } nodes.push(node); } function parseInterpolation(context: ParserContext): InterpolationNode | undefined { const [open, close] = ["{{", "}}"]; const closeIndex = context.source.indexOf(close, open.length); if (closeIndex === -1) return undefined; const start = getCursor(context); advanceBy(context, open.length); const innerStart = getCursor(context); const innerEnd = getCursor(context); const rawContentLength = closeIndex - open.length; const rawContent = context.source.slice(0, rawContentLength); const preTrimContent = parseTextData(context, rawContentLength); const content = preTrimContent.trim(); const startOffset = preTrimContent.indexOf(content); if (startOffset > 0) { advancePositionWithMutation(innerStart, rawContent, startOffset); } const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset); advancePositionWithMutation(innerEnd, rawContent, endOffset); advanceBy(context, close.length); return { type: NodeTypes.INTERPOLATION, content, loc: getSelection(context, start), }; } function parseText(context: ParserContext): TextNode { const endTokens = ["<", "{{"]; let endIndex = context.source.length; for (let i = 0; i < endTokens.length; i++) { const index = context.source.indexOf(endTokens[i], 1); if (index !== -1 && endIndex > index) { endIndex = index; } } const start = getCursor(context); const content = parseTextData(context, endIndex); return { type: NodeTypes.TEXT, content, loc: getSelection(context, start), }; } const enum TagType { Start, End, } function parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined { // Start tag. const element = parseTag(context, TagType.Start); // TODO: if (element.isSelfClosing) { return element; } // Children. ancestors.push(element); const children = parseChildren(context, ancestors); ancestors.pop(); element.children = children; // End tag. if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(context, TagType.End); // TODO: } return element; } function parseTag(context: ParserContext, type: TagType): ElementNode { // Tag open. const start = getCursor(context); const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!; const tag = match[1]; advanceBy(context, match[0].length); advanceSpaces(context); // Attributes. let props = parseAttributes(context, type); // Tag close. let isSelfClosing = false; isSelfClosing = startsWith(context.source, "/>"); advanceBy(context, isSelfClosing ? 2 : 1); return { type: NodeTypes.ELEMENT, tag, props, children: [], isSelfClosing, loc: getSelection(context, start), }; } function parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] { const props = []; const attributeNames = new Set(); while ( context.source.length > 0 && !startsWith(context.source, ">") && !startsWith(context.source, "/>") ) { const attr = parseAttribute(context, attributeNames); if (type === TagType.Start) { props.push(attr); } advanceSpaces(context); } return props; } type AttributeValue = | { content: string; loc: SourceLocation; } | undefined; function parseAttribute( context: ParserContext, nameSet: Set, ): AttributeNode | DirectiveNode { // Name. const start = getCursor(context); const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!; const name = match[0]; nameSet.add(name); advanceBy(context, name.length); // Value let value: AttributeValue = undefined; if (/^[\t\r\n\f ]*=/.test(context.source)) { advanceSpaces(context); advanceBy(context, 1); advanceSpaces(context); value = parseAttributeValue(context); } // directive const loc = getSelection(context, start); if (/^(v-[A-Za-z0-9-]|@)/.test(name)) { const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(name)!; let dirName = match[1] || (startsWith(name, "@") ? "on" : ""); let arg = ""; if (match[2]) arg = match[2]; return { type: NodeTypes.DIRECTIVE, name: dirName, exp: value?.content ?? "", loc, arg, }; } return { type: NodeTypes.ATTRIBUTE, name, value: value && { type: NodeTypes.TEXT, content: value.content, loc: value.loc, }, loc, }; } function parseAttributeValue(context: ParserContext): AttributeValue { const start = getCursor(context); let content: string; const quote = context.source[0]; const isQuoted = quote === `"` || quote === `'`; if (isQuoted) { // Quoted value. advanceBy(context, 1); const endIndex = context.source.indexOf(quote); if (endIndex === -1) { content = parseTextData(context, context.source.length); } else { content = parseTextData(context, endIndex); advanceBy(context, 1); } } else { // Unquoted const match = /^[^\t\r\n\f >]+/.exec(context.source); if (!match) { return undefined; } content = parseTextData(context, match[0].length); } return { content, loc: getSelection(context, start) }; } function parseTextData(context: ParserContext, length: number): string { const rawText = context.source.slice(0, length); advanceBy(context, length); return rawText; } function getCursor(context: ParserContext): Position { const { column, line, offset } = context; return { column, line, offset }; } function getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation { end = end || getCursor(context); return { start, end, source: context.originalSource.slice(start.offset, end.offset), }; } function last(xs: T[]): T | undefined { return xs[xs.length - 1]; } function startsWithEndTagOpen(source: string, tag: string): boolean { return ( startsWith(source, "]/.test(source[2 + tag.length] || ">") ); } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/compiler-dom/index.ts ================================================ import { baseCompile } from "../compiler-core"; export function compile(template: string) { return baseCompile(template); } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; setupState: Data; isMounted: boolean; } export type InternalRenderFunction = { (ctx: Data): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately setupState: {}, isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { const setupResult = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; // setupResultの型によって分岐をする if (typeof setupResult === "function") { instance.render = setupResult; } else if (typeof setupResult === "object" && setupResult !== null) { instance.setupState = setupResult; } else { // do nothing } } if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function | Record | void; render?: Function; template?: string; }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { Text, type VNode, createVNode, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, parentNode: hostParentNode, } = options; const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => { const { type } = n2; if (type === Text) { processText(n1, n2, container); } else if (typeof type === "string") { processElement(n1, n2, container); } else if (typeof type === "object") { processComponent(n1, n2, container); } else { // do nothing } }; const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountElement(n2, container); } else { patchElement(n1, n2); } }; const mountElement = (vnode: VNode, container: RendererElement) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = (children: VNode[], container: RendererElement) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container); } }; const patchElement = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; for (let i = 0; i < c2.length; i++) { const child = (c2[i] = normalizeVNode(c2[i])); patch(c1[i], child, container); } }; const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountComponent(n2, container); } else { updateComponent(n1, n2); } }; const mountComponent = (initialVNode: VNode, container: RendererElement) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, ) => { const componentUpdateFn = () => { const { render, setupState } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render(setupState))); patch(null, subTree, container); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render(setupState)); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/vnode.ts ================================================ import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; component: ComponentInternalInstance | null; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined, component: null, }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/shared/general.ts ================================================ const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createApp, reactive } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("10_minimum_example/060_template_compiler3", () => { it("should render with template and mustache binding", () => { const state = reactive({ message: "Hello chibivue!" }); const app = createApp({ setup() { return { state }; }, template: `
{{ state.message }}
`, }); app.mount("#host"); expect(host.innerHTML).toBe("
Hello chibivue!
"); }); }); ================================================ FILE: book/impls/10_minimum_example/060_template_compiler3/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/examples/playground/src/main.ts ================================================ import { createApp, reactive } from "chibivue"; const app = createApp({ setup() { const state = reactive({ message: "Hello, chibivue!", input: "" }); const changeMessage = () => { state.message += "!"; }; const handleInput = (e: InputEvent) => { state.input = (e.target as HTMLInputElement)?.value ?? ""; }; return { state, changeMessage, handleInput }; }, template: `

{{ state.message }}

Vue.js Logo

chibivue is the minimal Vue.js


input value: {{ state.input }}

`, }); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, }); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, INTERPOLATION, ATTRIBUTE, DIRECTIVE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode | InterpolationNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE; name: string; exp: string; arg: string; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } export interface InterpolationNode extends Node { type: NodeTypes.INTERPOLATION; content: string; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-core/codegen.ts ================================================ import { toHandlerKey } from "../shared"; import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type TemplateChildNode, type TextNode, } from "./ast"; export const generate = ({ children }: { children: TemplateChildNode[] }): string => { return `return function render(_ctx) { with (_ctx) { const { h } = ChibiVue; return ${genNode(children[0])}; } }`; }; const genNode = (node: TemplateChildNode): string => { switch (node.type) { case NodeTypes.ELEMENT: return genElement(node); case NodeTypes.TEXT: return genText(node); case NodeTypes.INTERPOLATION: return genInterpolation(node); default: return ""; } }; const genElement = (el: ElementNode): string => { return `h("${el.tag}", {${el.props .map((prop) => genProp(prop)) .join(", ")}}, [${el.children.map((it) => genNode(it)).join(", ")}])`; }; const genProp = (prop: AttributeNode | DirectiveNode): string => { switch (prop.type) { case NodeTypes.ATTRIBUTE: return `${prop.name}: "${prop.value?.content}"`; case NodeTypes.DIRECTIVE: { switch (prop.name) { case "on": return `${toHandlerKey(prop.arg)}: ${prop.exp}`; default: // TODO: other directives throw new Error(`unexpected directive name. got "${prop.name}"`); } } default: throw new Error(`unexpected prop type.`); } }; const genText = (text: TextNode): string => { return `\`${text.content}\``; }; const genInterpolation = (node: InterpolationNode): string => { return `${node.content}`; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import { baseParse } from "./parse"; export function baseCompile(template: string) { const parseResult = baseParse(template.trim()); const code = generate(parseResult); return code; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-core/parse.ts ================================================ import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type Position, type SourceLocation, type TemplateChildNode, type TextNode, } from "./ast"; export interface ParserContext { readonly originalSource: string; source: string; offset: number; line: number; column: number; } function createParserContext(content: string): ParserContext { return { originalSource: content, source: content, column: 1, line: 1, offset: 0, }; } export const baseParse = (content: string): { children: TemplateChildNode[] } => { const context = createParserContext(content); const children = parseChildren(context, []); return { children: children }; }; function parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] { const nodes: TemplateChildNode[] = []; while (!isEnd(context, ancestors)) { const s = context.source; let node: TemplateChildNode | undefined = undefined; if (startsWith(s, "{{")) { node = parseInterpolation(context); } else if (s[0] === "<") { if (/[a-z]/i.test(s[1])) { node = parseElement(context, ancestors); } } if (!node) { node = parseText(context); } pushNode(nodes, node); } return nodes; } function advanceBy(context: ParserContext, numberOfCharacters: number): void { const { source } = context; advancePositionWithMutation(context, source, numberOfCharacters); context.source = source.slice(numberOfCharacters); } function advancePositionWithMutation( pos: Position, source: string, numberOfCharacters: number = source.length, ): Position { let linesCount = 0; let lastNewLinePos = -1; for (let i = 0; i < numberOfCharacters; i++) { if (source.charCodeAt(i) === 10 /* newline char code */) { linesCount++; lastNewLinePos = i; } } pos.offset += numberOfCharacters; pos.line += linesCount; pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; return pos; } function isEnd(context: ParserContext, ancestors: ElementNode[]): boolean { const s = context.source; if (startsWith(s, "= 0; --i) { if (startsWithEndTagOpen(s, ancestors[i].tag)) { return true; } } } return !s; } function startsWith(source: string, searchString: string): boolean { return source.startsWith(searchString); } function advanceSpaces(context: ParserContext): void { const match = /^[\t\r\n\f ]+/.exec(context.source); if (match) { advanceBy(context, match[0].length); } } function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void { if (node.type === NodeTypes.TEXT) { const prev = last(nodes); if (prev && prev.type === NodeTypes.TEXT) { prev.content += node.content; return; } } nodes.push(node); } function parseInterpolation(context: ParserContext): InterpolationNode | undefined { const [open, close] = ["{{", "}}"]; const closeIndex = context.source.indexOf(close, open.length); if (closeIndex === -1) return undefined; const start = getCursor(context); advanceBy(context, open.length); const innerStart = getCursor(context); const innerEnd = getCursor(context); const rawContentLength = closeIndex - open.length; const rawContent = context.source.slice(0, rawContentLength); const preTrimContent = parseTextData(context, rawContentLength); const content = preTrimContent.trim(); const startOffset = preTrimContent.indexOf(content); if (startOffset > 0) { advancePositionWithMutation(innerStart, rawContent, startOffset); } const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset); advancePositionWithMutation(innerEnd, rawContent, endOffset); advanceBy(context, close.length); return { type: NodeTypes.INTERPOLATION, content, loc: getSelection(context, start), }; } function parseText(context: ParserContext): TextNode { const endTokens = ["<", "{{"]; let endIndex = context.source.length; for (let i = 0; i < endTokens.length; i++) { const index = context.source.indexOf(endTokens[i], 1); if (index !== -1 && endIndex > index) { endIndex = index; } } const start = getCursor(context); const content = parseTextData(context, endIndex); return { type: NodeTypes.TEXT, content, loc: getSelection(context, start), }; } const enum TagType { Start, End, } function parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined { // Start tag. const element = parseTag(context, TagType.Start); // TODO: if (element.isSelfClosing) { return element; } // Children. ancestors.push(element); const children = parseChildren(context, ancestors); ancestors.pop(); element.children = children; // End tag. if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(context, TagType.End); // TODO: } return element; } function parseTag(context: ParserContext, type: TagType): ElementNode { // Tag open. const start = getCursor(context); const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!; const tag = match[1]; advanceBy(context, match[0].length); advanceSpaces(context); // Attributes. let props = parseAttributes(context, type); // Tag close. let isSelfClosing = false; isSelfClosing = startsWith(context.source, "/>"); advanceBy(context, isSelfClosing ? 2 : 1); return { type: NodeTypes.ELEMENT, tag, props, children: [], isSelfClosing, loc: getSelection(context, start), }; } function parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] { const props = []; const attributeNames = new Set(); while ( context.source.length > 0 && !startsWith(context.source, ">") && !startsWith(context.source, "/>") ) { const attr = parseAttribute(context, attributeNames); if (type === TagType.Start) { props.push(attr); } advanceSpaces(context); } return props; } type AttributeValue = | { content: string; loc: SourceLocation; } | undefined; function parseAttribute( context: ParserContext, nameSet: Set, ): AttributeNode | DirectiveNode { // Name. const start = getCursor(context); const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!; const name = match[0]; nameSet.add(name); advanceBy(context, name.length); // Value let value: AttributeValue = undefined; if (/^[\t\r\n\f ]*=/.test(context.source)) { advanceSpaces(context); advanceBy(context, 1); advanceSpaces(context); value = parseAttributeValue(context); } // directive const loc = getSelection(context, start); if (/^(v-[A-Za-z0-9-]|@)/.test(name)) { const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(name)!; let dirName = match[1] || (startsWith(name, "@") ? "on" : ""); let arg = ""; if (match[2]) arg = match[2]; return { type: NodeTypes.DIRECTIVE, name: dirName, exp: value?.content ?? "", loc, arg, }; } return { type: NodeTypes.ATTRIBUTE, name, value: value && { type: NodeTypes.TEXT, content: value.content, loc: value.loc, }, loc, }; } function parseAttributeValue(context: ParserContext): AttributeValue { const start = getCursor(context); let content: string; const quote = context.source[0]; const isQuoted = quote === `"` || quote === `'`; if (isQuoted) { // Quoted value. advanceBy(context, 1); const endIndex = context.source.indexOf(quote); if (endIndex === -1) { content = parseTextData(context, context.source.length); } else { content = parseTextData(context, endIndex); advanceBy(context, 1); } } else { // Unquoted const match = /^[^\t\r\n\f >]+/.exec(context.source); if (!match) { return undefined; } content = parseTextData(context, match[0].length); } return { content, loc: getSelection(context, start) }; } function parseTextData(context: ParserContext, length: number): string { const rawText = context.source.slice(0, length); advanceBy(context, length); return rawText; } function getCursor(context: ParserContext): Position { const { column, line, offset } = context; return { column, line, offset }; } function getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation { end = end || getCursor(context); return { start, end, source: context.originalSource.slice(start.offset, end.offset), }; } function last(xs: T[]): T | undefined { return xs[xs.length - 1]; } function startsWithEndTagOpen(source: string, tag: string): boolean { return ( startsWith(source, "]/.test(source[2 + tag.length] || ">") ); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-dom/index.ts ================================================ import { baseCompile } from "../compiler-core"; export function compile(template: string) { return baseCompile(template); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; setupState: Data; isMounted: boolean; } export type InternalRenderFunction = { (ctx: Data): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately setupState: {}, isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { const setupResult = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; // setupResultの型によって分岐をする if (typeof setupResult === "function") { instance.render = setupResult; } else if (typeof setupResult === "object" && setupResult !== null) { instance.setupState = setupResult; } else { // do nothing } } if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function | Record | void; render?: Function; template?: string; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { Text, type VNode, createVNode, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, parentNode: hostParentNode, } = options; const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => { const { type } = n2; if (type === Text) { processText(n1, n2, container); } else if (typeof type === "string") { processElement(n1, n2, container); } else if (typeof type === "object") { processComponent(n1, n2, container); } else { // do nothing } }; const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountElement(n2, container); } else { patchElement(n1, n2); } }; const mountElement = (vnode: VNode, container: RendererElement) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = (children: VNode[], container: RendererElement) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container); } }; const patchElement = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; for (let i = 0; i < c2.length; i++) { const child = (c2[i] = normalizeVNode(c2[i])); patch(c1[i], child, container); } }; const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountComponent(n2, container); } else { updateComponent(n1, n2); } }; const mountComponent = (initialVNode: VNode, container: RendererElement) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, ) => { const componentUpdateFn = () => { const { render, setupState } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render(setupState))); patch(null, subTree, container); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render(setupState)); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/vnode.ts ================================================ import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; component: ComponentInternalInstance | null; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined, component: null, }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/shared/general.ts ================================================ const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/.vscode/extensions.json ================================================ { "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/README.md ================================================ # Vue 3 + TypeScript + Vite This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/package.json ================================================ { "name": "plugin-sample", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vue-tsc && vite build", "preview": "vite preview" }, "dependencies": { "vue": "^3.2.47" }, "devDependencies": { "@vitejs/plugin-vue": "^6.0.5", "typescript": "^5.9.3", "vite": "^8.0.0", "vue-tsc": "^2.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/App.vue ================================================ ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/components/HelloWorld.vue ================================================ ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/main.ts ================================================ import { createApp } from "vue"; import "./style.css"; import App from "./App.vue"; import "./plugin.sample.js"; createApp(App).mount("#app"); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/plugin.sample.js ================================================ function fizzbuzz(n) { for (let i = 1; i <= n; i++) { i % 3 === 0 && i % 5 === 0 ? console.log("fizzbuzz") : i % 3 === 0 ? console.log("fizz") : i % 5 === 0 ? console.log("buzz") : console.log(i); } } fizzbuzz(Math.floor(Math.random() * 100) + 1); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/style.css ================================================ :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; color-scheme: light dark; color: rgba(255, 255, 255, 0.87); background-color: #242424; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; } a { font-weight: 500; color: #646cff; text-decoration: inherit; } a:hover { color: #535bf2; } body { margin: 0; display: flex; place-items: center; min-width: 320px; min-height: 100vh; } h1 { font-size: 3.2em; line-height: 1.1; } button { border-radius: 8px; border: 1px solid transparent; padding: 0.6em 1.2em; font-size: 1em; font-weight: 500; font-family: inherit; background-color: #1a1a1a; cursor: pointer; transition: border-color 0.25s; } button:hover { border-color: #646cff; } button:focus, button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } .card { padding: 2em; } #app { max-width: 1280px; margin: 0 auto; padding: 2rem; text-align: center; } @media (prefers-color-scheme: light) { :root { color: #213547; background-color: #ffffff; } a:hover { color: #747bff; } button { background-color: #f9f9f9; } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/vite-env.d.ts ================================================ /// ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "preserve", /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/tsconfig.node.json ================================================ { "compilerOptions": { "composite": true, "skipLibCheck": true, "module": "ESNext", "moduleResolution": "bundler", "allowSyntheticDefaultImports": true }, "include": ["vite.config.ts"] } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/vite.config.ts ================================================ import { type Plugin, defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue(), myPlugin()], }); function myPlugin(): Plugin { return { name: "vite:my-plugin", transform(code, id) { if (id.endsWith(".sample.js")) { let result = ""; for (let i = 0; i < 100; i++) { result += `console.log("HelloWorld from plugin! (${i})");\n`; } result += code; return { code: result }; } }, }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/tests/e2e.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { compile } from "../packages/compiler-dom"; describe("10_minimum_example/070_sfc_compiler", () => { it("should compile basic template to render function", () => { const template = `
Hello World
`; const code = compile(template); expect(code).toContain("function render"); expect(code).toContain("h("); expect(code).toContain('"div"'); expect(code).toContain("Hello World"); }); it("should compile template with nested elements", () => { const template = `

Nested content

`; const code = compile(template); expect(code).toContain("function render"); expect(code).toContain('"div"'); expect(code).toContain('"p"'); expect(code).toContain("Nested content"); }); it("should compile template with mustache interpolation", () => { const template = `

{{ message }}

`; const code = compile(template); expect(code).toContain("function render"); expect(code).toContain("with (_ctx)"); expect(code).toContain("message"); }); }); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/src/App.vue ================================================ ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/src/main.ts ================================================ import { createApp } from "chibivue"; // @ts-expect-error import App from "./App.vue"; const app = createApp(App); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; import chibivue from "../../packages/@extensions/vite-plugin-chibivue"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, plugins: [chibivue()], }); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/@extensions/vite-plugin-chibivue/index.ts ================================================ import type { Plugin } from "vite"; import { createFilter } from "vite"; import { parse } from "../../compiler-sfc"; export default function vitePluginChibivue(): Plugin { const filter = createFilter(/\.vue$/); return { name: "vite:chibivue", transform(code, id) { if (!filter(id)) return; const { descriptor } = parse(code, { filename: id }); console.log("🚀 ~ file: index.ts:14 ~ transform ~ descriptor:", descriptor); return { code: `export default {}` }; }, }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, INTERPOLATION, ATTRIBUTE, DIRECTIVE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode | InterpolationNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE; name: string; exp: string; arg: string; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } export interface InterpolationNode extends Node { type: NodeTypes.INTERPOLATION; content: string; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-core/codegen.ts ================================================ import { toHandlerKey } from "../shared"; import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type TemplateChildNode, type TextNode, } from "./ast"; export const generate = ({ children }: { children: TemplateChildNode[] }): string => { return `return function render(_ctx) { with (_ctx) { const { h } = ChibiVue; return ${genNode(children[0])}; } }`; }; const genNode = (node: TemplateChildNode): string => { switch (node.type) { case NodeTypes.ELEMENT: return genElement(node); case NodeTypes.TEXT: return genText(node); case NodeTypes.INTERPOLATION: return genInterpolation(node); default: return ""; } }; const genElement = (el: ElementNode): string => { return `h("${el.tag}", {${el.props .map((prop) => genProp(prop)) .join(", ")}}, [${el.children.map((it) => genNode(it)).join(", ")}])`; }; const genProp = (prop: AttributeNode | DirectiveNode): string => { switch (prop.type) { case NodeTypes.ATTRIBUTE: return `${prop.name}: "${prop.value?.content}"`; case NodeTypes.DIRECTIVE: { switch (prop.name) { case "on": return `${toHandlerKey(prop.arg)}: ${prop.exp}`; default: // TODO: other directives throw new Error(`unexpected directive name. got "${prop.name}"`); } } default: throw new Error(`unexpected prop type.`); } }; const genText = (text: TextNode): string => { return `\`${text.content}\``; }; const genInterpolation = (node: InterpolationNode): string => { return `${node.content}`; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import { baseParse } from "./parse"; export function baseCompile(template: string) { const parseResult = baseParse(template.trim()); const code = generate(parseResult); return code; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; export * from "./ast"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-core/parse.ts ================================================ import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type Position, type SourceLocation, type TemplateChildNode, type TextNode, } from "./ast"; export interface ParserContext { readonly originalSource: string; source: string; offset: number; line: number; column: number; } function createParserContext(content: string): ParserContext { return { originalSource: content, source: content, column: 1, line: 1, offset: 0, }; } export const baseParse = (content: string): { children: TemplateChildNode[] } => { const context = createParserContext(content); const children = parseChildren(context, []); return { children: children }; }; function parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] { const nodes: TemplateChildNode[] = []; while (!isEnd(context, ancestors)) { const s = context.source; let node: TemplateChildNode | undefined = undefined; if (startsWith(s, "{{")) { node = parseInterpolation(context); } else if (s[0] === "<") { if (/[a-z]/i.test(s[1])) { node = parseElement(context, ancestors); } } if (!node) { node = parseText(context); } pushNode(nodes, node); } return nodes; } function advanceBy(context: ParserContext, numberOfCharacters: number): void { const { source } = context; advancePositionWithMutation(context, source, numberOfCharacters); context.source = source.slice(numberOfCharacters); } function advancePositionWithMutation( pos: Position, source: string, numberOfCharacters: number = source.length, ): Position { let linesCount = 0; let lastNewLinePos = -1; for (let i = 0; i < numberOfCharacters; i++) { if (source.charCodeAt(i) === 10 /* newline char code */) { linesCount++; lastNewLinePos = i; } } pos.offset += numberOfCharacters; pos.line += linesCount; pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; return pos; } function isEnd(context: ParserContext, ancestors: ElementNode[]): boolean { const s = context.source; if (startsWith(s, "= 0; --i) { if (startsWithEndTagOpen(s, ancestors[i].tag)) { return true; } } } return !s; } function startsWith(source: string, searchString: string): boolean { return source.startsWith(searchString); } function advanceSpaces(context: ParserContext): void { const match = /^[\t\r\n\f ]+/.exec(context.source); if (match) { advanceBy(context, match[0].length); } } function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void { if (node.type === NodeTypes.TEXT) { const prev = last(nodes); if (prev && prev.type === NodeTypes.TEXT) { prev.content += node.content; return; } } nodes.push(node); } function parseInterpolation(context: ParserContext): InterpolationNode | undefined { const [open, close] = ["{{", "}}"]; const closeIndex = context.source.indexOf(close, open.length); if (closeIndex === -1) return undefined; const start = getCursor(context); advanceBy(context, open.length); const innerStart = getCursor(context); const innerEnd = getCursor(context); const rawContentLength = closeIndex - open.length; const rawContent = context.source.slice(0, rawContentLength); const preTrimContent = parseTextData(context, rawContentLength); const content = preTrimContent.trim(); const startOffset = preTrimContent.indexOf(content); if (startOffset > 0) { advancePositionWithMutation(innerStart, rawContent, startOffset); } const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset); advancePositionWithMutation(innerEnd, rawContent, endOffset); advanceBy(context, close.length); return { type: NodeTypes.INTERPOLATION, content, loc: getSelection(context, start), }; } function parseText(context: ParserContext): TextNode { const endTokens = ["<", "{{"]; let endIndex = context.source.length; for (let i = 0; i < endTokens.length; i++) { const index = context.source.indexOf(endTokens[i], 1); if (index !== -1 && endIndex > index) { endIndex = index; } } const start = getCursor(context); const content = parseTextData(context, endIndex); return { type: NodeTypes.TEXT, content, loc: getSelection(context, start), }; } const enum TagType { Start, End, } function parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined { // Start tag. const element = parseTag(context, TagType.Start); // TODO: if (element.isSelfClosing) { return element; } // Children. ancestors.push(element); const children = parseChildren(context, ancestors); ancestors.pop(); element.children = children; // End tag. if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(context, TagType.End); // TODO: } return element; } function parseTag(context: ParserContext, type: TagType): ElementNode { // Tag open. const start = getCursor(context); const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!; const tag = match[1]; advanceBy(context, match[0].length); advanceSpaces(context); // Attributes. let props = parseAttributes(context, type); // Tag close. let isSelfClosing = false; isSelfClosing = startsWith(context.source, "/>"); advanceBy(context, isSelfClosing ? 2 : 1); return { type: NodeTypes.ELEMENT, tag, props, children: [], isSelfClosing, loc: getSelection(context, start), }; } function parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] { const props = []; const attributeNames = new Set(); while ( context.source.length > 0 && !startsWith(context.source, ">") && !startsWith(context.source, "/>") ) { const attr = parseAttribute(context, attributeNames); if (type === TagType.Start) { props.push(attr); } advanceSpaces(context); } return props; } type AttributeValue = | { content: string; loc: SourceLocation; } | undefined; function parseAttribute( context: ParserContext, nameSet: Set, ): AttributeNode | DirectiveNode { // Name. const start = getCursor(context); const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!; const name = match[0]; nameSet.add(name); advanceBy(context, name.length); // Value let value: AttributeValue = undefined; if (/^[\t\r\n\f ]*=/.test(context.source)) { advanceSpaces(context); advanceBy(context, 1); advanceSpaces(context); value = parseAttributeValue(context); } // directive const loc = getSelection(context, start); if (/^(v-[A-Za-z0-9-]|@)/.test(name)) { const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(name)!; let dirName = match[1] || (startsWith(name, "@") ? "on" : ""); let arg = ""; if (match[2]) arg = match[2]; return { type: NodeTypes.DIRECTIVE, name: dirName, exp: value?.content ?? "", loc, arg, }; } return { type: NodeTypes.ATTRIBUTE, name, value: value && { type: NodeTypes.TEXT, content: value.content, loc: value.loc, }, loc, }; } function parseAttributeValue(context: ParserContext): AttributeValue { const start = getCursor(context); let content: string; const quote = context.source[0]; const isQuoted = quote === `"` || quote === `'`; if (isQuoted) { // Quoted value. advanceBy(context, 1); const endIndex = context.source.indexOf(quote); if (endIndex === -1) { content = parseTextData(context, context.source.length); } else { content = parseTextData(context, endIndex); advanceBy(context, 1); } } else { // Unquoted const match = /^[^\t\r\n\f >]+/.exec(context.source); if (!match) { return undefined; } content = parseTextData(context, match[0].length); } return { content, loc: getSelection(context, start) }; } function parseTextData(context: ParserContext, length: number): string { const rawText = context.source.slice(0, length); advanceBy(context, length); return rawText; } function getCursor(context: ParserContext): Position { const { column, line, offset } = context; return { column, line, offset }; } function getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation { end = end || getCursor(context); return { start, end, source: context.originalSource.slice(start.offset, end.offset), }; } function last(xs: T[]): T | undefined { return xs[xs.length - 1]; } function startsWithEndTagOpen(source: string, tag: string): boolean { return ( startsWith(source, "]/.test(source[2 + tag.length] || ">") ); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-dom/index.ts ================================================ import { baseCompile, baseParse } from "../compiler-core"; export function compile(template: string) { return baseCompile(template); } export function parse(template: string) { return baseParse(template); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-sfc/compileTemplate.ts ================================================ import type { TemplateChildNode } from "../compiler-core"; export interface TemplateCompiler { compile(template: string): string; parse(template: string): { children: TemplateChildNode[] }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-sfc/index.ts ================================================ export * from "./parse"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-sfc/parse.ts ================================================ import { type ElementNode, NodeTypes, type SourceLocation } from "../compiler-core"; import * as CompilerDOM from "../compiler-dom"; import type { TemplateCompiler } from "./compileTemplate"; export interface SFCDescriptor { id: string; filename: string; source: string; template: SFCTemplateBlock | null; script: SFCScriptBlock | null; styles: SFCStyleBlock[]; } export interface SFCBlock { type: string; content: string; loc: SourceLocation; } export interface SFCTemplateBlock extends SFCBlock { type: "template"; } export interface SFCScriptBlock extends SFCBlock { type: "script"; } export declare interface SFCStyleBlock extends SFCBlock { type: "style"; } export interface SFCParseOptions { filename?: string; sourceRoot?: string; compiler?: TemplateCompiler; } export interface SFCParseResult { descriptor: SFCDescriptor; } export const DEFAULT_FILENAME = "anonymous.vue"; export function parse( source: string, { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {}, ): SFCParseResult { const descriptor: SFCDescriptor = { id: undefined!, filename, source, template: null, script: null, styles: [], }; const ast = compiler.parse(source); ast.children.forEach((node) => { if (node.type !== NodeTypes.ELEMENT) return; switch (node.tag) { case "template": { descriptor.template = createBlock(node, source) as SFCTemplateBlock; break; } case "script": { const scriptBlock = createBlock(node, source) as SFCScriptBlock; descriptor.script = scriptBlock; break; } case "style": { descriptor.styles.push(createBlock(node, source) as SFCStyleBlock); break; } default: { break; } } }); return { descriptor }; } function createBlock(node: ElementNode, source: string): SFCBlock { const type = node.tag; let { start, end } = node.loc; start = node.children[0].loc.start; end = node.children[node.children.length - 1].loc.end; const content = source.slice(start.offset, end.offset); const loc = { source: content, start, end }; const block: SFCBlock = { type, content, loc }; return block; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; setupState: Data; isMounted: boolean; } export type InternalRenderFunction = { (ctx: Data): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately setupState: {}, isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { const setupResult = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; // setupResultの型によって分岐をする if (typeof setupResult === "function") { instance.render = setupResult; } else if (typeof setupResult === "object" && setupResult !== null) { instance.setupState = setupResult; } else { // do nothing } } if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function | Record | void; render?: Function; template?: string; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { Text, type VNode, createVNode, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, parentNode: hostParentNode, } = options; const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => { const { type } = n2; if (type === Text) { processText(n1, n2, container); } else if (typeof type === "string") { processElement(n1, n2, container); } else if (typeof type === "object") { processComponent(n1, n2, container); } else { // do nothing } }; const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountElement(n2, container); } else { patchElement(n1, n2); } }; const mountElement = (vnode: VNode, container: RendererElement) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = (children: VNode[], container: RendererElement) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container); } }; const patchElement = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; for (let i = 0; i < c2.length; i++) { const child = (c2[i] = normalizeVNode(c2[i])); patch(c1[i], child, container); } }; const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountComponent(n2, container); } else { updateComponent(n1, n2); } }; const mountComponent = (initialVNode: VNode, container: RendererElement) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, ) => { const componentUpdateFn = () => { const { render, setupState } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render(setupState))); patch(null, subTree, container); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render(setupState)); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/vnode.ts ================================================ import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; component: ComponentInternalInstance | null; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined, component: null, }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/shared/general.ts ================================================ const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/tests/e2e.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { compile } from "../packages/compiler-dom"; import { parse } from "../packages/compiler-sfc"; describe("10_minimum_example/070_sfc_compiler2", () => { it("should parse SFC with template, script, and style blocks", () => { const source = ` `.trim(); const { descriptor } = parse(source); expect(descriptor.template).not.toBeNull(); expect(descriptor.template?.content).toContain("
Hello
"); expect(descriptor.script).not.toBeNull(); expect(descriptor.script?.content).toContain("export default"); expect(descriptor.styles.length).toBe(1); expect(descriptor.styles[0].content).toContain("color: red"); }); it("should compile template to render function code", () => { const template = `
Hello
`; const code = compile(template); expect(code).toContain("function render"); expect(code).toContain("h("); expect(code).toContain('"div"'); expect(code).toContain("Hello"); }); it("should compile template with interpolation", () => { const template = `

{{ message }}

`; const code = compile(template); expect(code).toContain("function render"); expect(code).toContain("with (_ctx)"); expect(code).toContain("message"); }); }); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler2/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/src/App.vue ================================================ ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/src/main.ts ================================================ import { createApp } from "chibivue"; // @ts-expect-error import App from "./App.vue"; const app = createApp(App); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; import chibivue from "../../packages/@extensions/vite-plugin-chibivue"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, plugins: [chibivue()], }); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/@extensions/vite-plugin-chibivue/index.ts ================================================ import type { Plugin } from "vite"; import { createFilter } from "vite"; import { parse } from "../../compiler-sfc"; import { compile } from "../../compiler-dom"; export default function vitePluginChibivue(): Plugin { const filter = createFilter(/\.vue$/); return { name: "vite:chibivue", transform(code, id) { if (!filter(id)) return; const outputs = []; outputs.push("import * as ChibiVue from 'chibivue'\n"); const { descriptor } = parse(code, { filename: id }); const templateCode = compile(descriptor.template?.content ?? "", { isBrowser: false, }); outputs.push(templateCode); outputs.push("\n"); outputs.push(`export default { render }`); return { code: outputs.join("\n") }; }, }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, INTERPOLATION, ATTRIBUTE, DIRECTIVE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode | InterpolationNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE; name: string; exp: string; arg: string; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } export interface InterpolationNode extends Node { type: NodeTypes.INTERPOLATION; content: string; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/codegen.ts ================================================ import { toHandlerKey } from "../shared"; import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type TemplateChildNode, type TextNode, } from "./ast"; import type { CompilerOptions } from "./options"; export const generate = ( { children, }: { children: TemplateChildNode[]; }, option: Required, ): string => { return `${option.isBrowser ? "return " : ""}function render(_ctx) { ${option.isBrowser ? "with (_ctx) {" : ""} const { h } = ChibiVue; return ${genNode(children[0], option)}; ${option.isBrowser ? "}" : ""} }`; }; const genNode = (node: TemplateChildNode, option: Required): string => { switch (node.type) { case NodeTypes.ELEMENT: return genElement(node, option); case NodeTypes.TEXT: return genText(node); case NodeTypes.INTERPOLATION: return genInterpolation(node, option); default: return ""; } }; const genElement = (el: ElementNode, option: Required): string => { return `h("${el.tag}", {${el.props .map((prop) => genProp(prop, option)) .join(", ")}}, [${el.children.map((it) => genNode(it, option)).join(", ")}])`; }; const genProp = ( prop: AttributeNode | DirectiveNode, option: Required, ): string => { switch (prop.type) { case NodeTypes.ATTRIBUTE: return `${prop.name}: "${prop.value?.content}"`; case NodeTypes.DIRECTIVE: { switch (prop.name) { case "on": return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? "" : "_ctx."}${prop.exp}`; default: // TODO: other directives throw new Error(`unexpected directive name. got "${prop.name}"`); } } default: throw new Error(`unexpected prop type.`); } }; const genText = (text: TextNode): string => { return `\`${text.content}\``; }; const genInterpolation = (node: InterpolationNode, option: Required): string => { return `${option.isBrowser ? "" : "_ctx."}${node.content}`; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import type { CompilerOptions } from "./options"; import { baseParse } from "./parse"; export function baseCompile(template: string, option: Required) { const parseResult = baseParse(template.trim()); const code = generate(parseResult, option); return code; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; export * from "./ast"; export * from "./options"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/options.ts ================================================ export type CompilerOptions = { isBrowser?: boolean; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/parse.ts ================================================ import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type Position, type SourceLocation, type TemplateChildNode, type TextNode, } from "./ast"; export interface ParserContext { readonly originalSource: string; source: string; offset: number; line: number; column: number; } function createParserContext(content: string): ParserContext { return { originalSource: content, source: content, column: 1, line: 1, offset: 0, }; } export const baseParse = (content: string): { children: TemplateChildNode[] } => { const context = createParserContext(content); const children = parseChildren(context, []); return { children: children }; }; function parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] { const nodes: TemplateChildNode[] = []; while (!isEnd(context, ancestors)) { const s = context.source; let node: TemplateChildNode | undefined = undefined; if (startsWith(s, "{{")) { node = parseInterpolation(context); } else if (s[0] === "<") { if (/[a-z]/i.test(s[1])) { node = parseElement(context, ancestors); } } if (!node) { node = parseText(context); } pushNode(nodes, node); } return nodes; } function advanceBy(context: ParserContext, numberOfCharacters: number): void { const { source } = context; advancePositionWithMutation(context, source, numberOfCharacters); context.source = source.slice(numberOfCharacters); } function advancePositionWithMutation( pos: Position, source: string, numberOfCharacters: number = source.length, ): Position { let linesCount = 0; let lastNewLinePos = -1; for (let i = 0; i < numberOfCharacters; i++) { if (source.charCodeAt(i) === 10 /* newline char code */) { linesCount++; lastNewLinePos = i; } } pos.offset += numberOfCharacters; pos.line += linesCount; pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; return pos; } function isEnd(context: ParserContext, ancestors: ElementNode[]): boolean { const s = context.source; if (startsWith(s, "= 0; --i) { if (startsWithEndTagOpen(s, ancestors[i].tag)) { return true; } } } return !s; } function startsWith(source: string, searchString: string): boolean { return source.startsWith(searchString); } function advanceSpaces(context: ParserContext): void { const match = /^[\t\r\n\f ]+/.exec(context.source); if (match) { advanceBy(context, match[0].length); } } function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void { if (node.type === NodeTypes.TEXT) { const prev = last(nodes); if (prev && prev.type === NodeTypes.TEXT) { prev.content += node.content; return; } } nodes.push(node); } function parseInterpolation(context: ParserContext): InterpolationNode | undefined { const [open, close] = ["{{", "}}"]; const closeIndex = context.source.indexOf(close, open.length); if (closeIndex === -1) return undefined; const start = getCursor(context); advanceBy(context, open.length); const innerStart = getCursor(context); const innerEnd = getCursor(context); const rawContentLength = closeIndex - open.length; const rawContent = context.source.slice(0, rawContentLength); const preTrimContent = parseTextData(context, rawContentLength); const content = preTrimContent.trim(); const startOffset = preTrimContent.indexOf(content); if (startOffset > 0) { advancePositionWithMutation(innerStart, rawContent, startOffset); } const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset); advancePositionWithMutation(innerEnd, rawContent, endOffset); advanceBy(context, close.length); return { type: NodeTypes.INTERPOLATION, content, loc: getSelection(context, start), }; } function parseText(context: ParserContext): TextNode { const endTokens = ["<", "{{"]; let endIndex = context.source.length; for (let i = 0; i < endTokens.length; i++) { const index = context.source.indexOf(endTokens[i], 1); if (index !== -1 && endIndex > index) { endIndex = index; } } const start = getCursor(context); const content = parseTextData(context, endIndex); return { type: NodeTypes.TEXT, content, loc: getSelection(context, start), }; } const enum TagType { Start, End, } function parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined { // Start tag. const element = parseTag(context, TagType.Start); // TODO: if (element.isSelfClosing) { return element; } // Children. ancestors.push(element); const children = parseChildren(context, ancestors); ancestors.pop(); element.children = children; // End tag. if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(context, TagType.End); // TODO: } return element; } function parseTag(context: ParserContext, type: TagType): ElementNode { // Tag open. const start = getCursor(context); const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!; const tag = match[1]; advanceBy(context, match[0].length); advanceSpaces(context); // Attributes. let props = parseAttributes(context, type); // Tag close. let isSelfClosing = false; isSelfClosing = startsWith(context.source, "/>"); advanceBy(context, isSelfClosing ? 2 : 1); return { type: NodeTypes.ELEMENT, tag, props, children: [], isSelfClosing, loc: getSelection(context, start), }; } function parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] { const props = []; const attributeNames = new Set(); while ( context.source.length > 0 && !startsWith(context.source, ">") && !startsWith(context.source, "/>") ) { const attr = parseAttribute(context, attributeNames); if (type === TagType.Start) { props.push(attr); } advanceSpaces(context); } return props; } type AttributeValue = | { content: string; loc: SourceLocation; } | undefined; function parseAttribute( context: ParserContext, nameSet: Set, ): AttributeNode | DirectiveNode { // Name. const start = getCursor(context); const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!; const name = match[0]; nameSet.add(name); advanceBy(context, name.length); // Value let value: AttributeValue = undefined; if (/^[\t\r\n\f ]*=/.test(context.source)) { advanceSpaces(context); advanceBy(context, 1); advanceSpaces(context); value = parseAttributeValue(context); } // directive const loc = getSelection(context, start); if (/^(v-[A-Za-z0-9-]|@)/.test(name)) { const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(name)!; let dirName = match[1] || (startsWith(name, "@") ? "on" : ""); let arg = ""; if (match[2]) arg = match[2]; return { type: NodeTypes.DIRECTIVE, name: dirName, exp: value?.content ?? "", loc, arg, }; } return { type: NodeTypes.ATTRIBUTE, name, value: value && { type: NodeTypes.TEXT, content: value.content, loc: value.loc, }, loc, }; } function parseAttributeValue(context: ParserContext): AttributeValue { const start = getCursor(context); let content: string; const quote = context.source[0]; const isQuoted = quote === `"` || quote === `'`; if (isQuoted) { // Quoted value. advanceBy(context, 1); const endIndex = context.source.indexOf(quote); if (endIndex === -1) { content = parseTextData(context, context.source.length); } else { content = parseTextData(context, endIndex); advanceBy(context, 1); } } else { // Unquoted const match = /^[^\t\r\n\f >]+/.exec(context.source); if (!match) { return undefined; } content = parseTextData(context, match[0].length); } return { content, loc: getSelection(context, start) }; } function parseTextData(context: ParserContext, length: number): string { const rawText = context.source.slice(0, length); advanceBy(context, length); return rawText; } function getCursor(context: ParserContext): Position { const { column, line, offset } = context; return { column, line, offset }; } function getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation { end = end || getCursor(context); return { start, end, source: context.originalSource.slice(start.offset, end.offset), }; } function last(xs: T[]): T | undefined { return xs[xs.length - 1]; } function startsWithEndTagOpen(source: string, tag: string): boolean { return ( startsWith(source, "]/.test(source[2 + tag.length] || ">") ); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-dom/index.ts ================================================ import { type CompilerOptions, baseCompile, baseParse } from "../compiler-core"; export function compile(template: string, option?: CompilerOptions) { const defaultOption: Required = { isBrowser: true }; if (option) Object.assign(defaultOption, option); return baseCompile(template, defaultOption); } export function parse(template: string) { return baseParse(template); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-sfc/compileTemplate.ts ================================================ import type { TemplateChildNode } from "../compiler-core"; export interface TemplateCompiler { compile(template: string): string; parse(template: string): { children: TemplateChildNode[] }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-sfc/index.ts ================================================ export * from "./parse"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-sfc/parse.ts ================================================ import { type ElementNode, NodeTypes, type SourceLocation } from "../compiler-core"; import * as CompilerDOM from "../compiler-dom"; import type { TemplateCompiler } from "./compileTemplate"; export interface SFCDescriptor { id: string; filename: string; source: string; template: SFCTemplateBlock | null; script: SFCScriptBlock | null; styles: SFCStyleBlock[]; } export interface SFCBlock { type: string; content: string; loc: SourceLocation; } export interface SFCTemplateBlock extends SFCBlock { type: "template"; } export interface SFCScriptBlock extends SFCBlock { type: "script"; } export declare interface SFCStyleBlock extends SFCBlock { type: "style"; } export interface SFCParseOptions { filename?: string; sourceRoot?: string; compiler?: TemplateCompiler; } export interface SFCParseResult { descriptor: SFCDescriptor; } export const DEFAULT_FILENAME = "anonymous.vue"; export function parse( source: string, { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {}, ): SFCParseResult { const descriptor: SFCDescriptor = { id: undefined!, filename, source, template: null, script: null, styles: [], }; const ast = compiler.parse(source); ast.children.forEach((node) => { if (node.type !== NodeTypes.ELEMENT) return; switch (node.tag) { case "template": { descriptor.template = createBlock(node, source) as SFCTemplateBlock; break; } case "script": { const scriptBlock = createBlock(node, source) as SFCScriptBlock; descriptor.script = scriptBlock; break; } case "style": { descriptor.styles.push(createBlock(node, source) as SFCStyleBlock); break; } default: { break; } } }); return { descriptor }; } function createBlock(node: ElementNode, source: string): SFCBlock { const type = node.tag; let { start, end } = node.loc; start = node.children[0].loc.start; end = node.children[node.children.length - 1].loc.end; const content = source.slice(start.offset, end.offset); const loc = { source: content, start, end }; const block: SFCBlock = { type, content, loc }; return block; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; setupState: Data; isMounted: boolean; } export type InternalRenderFunction = { (ctx: Data): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately setupState: {}, isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { const setupResult = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; // setupResultの型によって分岐をする if (typeof setupResult === "function") { instance.render = setupResult; } else if (typeof setupResult === "object" && setupResult !== null) { instance.setupState = setupResult; } else { // do nothing } } if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function | Record | void; render?: Function; template?: string; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { Text, type VNode, createVNode, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, parentNode: hostParentNode, } = options; const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => { const { type } = n2; if (type === Text) { processText(n1, n2, container); } else if (typeof type === "string") { processElement(n1, n2, container); } else if (typeof type === "object") { processComponent(n1, n2, container); } else { // do nothing } }; const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountElement(n2, container); } else { patchElement(n1, n2); } }; const mountElement = (vnode: VNode, container: RendererElement) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = (children: VNode[], container: RendererElement) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container); } }; const patchElement = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; for (let i = 0; i < c2.length; i++) { const child = (c2[i] = normalizeVNode(c2[i])); patch(c1[i], child, container); } }; const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountComponent(n2, container); } else { updateComponent(n1, n2); } }; const mountComponent = (initialVNode: VNode, container: RendererElement) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, ) => { const componentUpdateFn = () => { const { render, setupState } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render(setupState))); patch(null, subTree, container); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render(setupState)); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/vnode.ts ================================================ import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; component: ComponentInternalInstance | null; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined, component: null, }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/shared/general.ts ================================================ const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/tests/e2e.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { compile } from "../packages/compiler-dom"; import { parse } from "../packages/compiler-sfc"; describe("10_minimum_example/070_sfc_compiler3", () => { it("should parse SFC with template and script blocks", () => { const source = ` `.trim(); const { descriptor } = parse(source); expect(descriptor.template).not.toBeNull(); expect(descriptor.template?.content).toContain("{{ message }}"); expect(descriptor.script).not.toBeNull(); expect(descriptor.script?.content).toContain('message: "hello"'); }); it("should compile template for non-browser mode", () => { const template = `
{{ count }}
`; const code = compile(template, { isBrowser: false }); expect(code).toContain("function render"); expect(code).toContain("_ctx.count"); expect(code).not.toContain("with (_ctx)"); }); it("should compile template for browser mode", () => { const template = `
{{ count }}
`; const code = compile(template, { isBrowser: true }); expect(code).toContain("function render"); expect(code).toContain("with (_ctx)"); }); }); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler3/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/src/App.vue ================================================ ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/src/main.ts ================================================ import { createApp } from "chibivue"; // @ts-expect-error import App from "./App.vue"; const app = createApp(App); app.mount("#app"); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; import chibivue from "../../packages/@extensions/vite-plugin-chibivue"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, plugins: [chibivue()], }); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" }, "dependencies": { "@babel/parser": "^7.28.6", "magic-string": "^0.30.21" } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/@extensions/vite-plugin-chibivue/index.ts ================================================ import fs from "node:fs"; import type { Plugin } from "vite"; import { createFilter } from "vite"; import { parse, rewriteDefault } from "../../compiler-sfc"; import { compile } from "../../compiler-dom"; export default function vitePluginChibivue(): Plugin { const filter = createFilter(/\.vue$/); return { name: "vite:chibivue", resolveId(id) { if (id.match(/\.vue\.css$/)) return id; }, load(id) { if (id.match(/\.vue\.css$/)) { const filename = id.replace(/\.css$/, ""); const content = fs.readFileSync(filename, "utf-8"); const { descriptor } = parse(content, { filename }); const styles = descriptor.styles.map((it) => it.content).join("\n"); return { code: styles }; } }, transform(code, id) { if (!filter(id)) return; const outputs = []; outputs.push("import * as ChibiVue from 'chibivue'"); outputs.push(`import '${id}.css'`); const { descriptor } = parse(code, { filename: id }); const SFC_MAIN = "_sfc_main"; const scriptCode = rewriteDefault(descriptor.script?.content ?? "", SFC_MAIN); outputs.push(scriptCode); const templateCode = compile(descriptor.template?.content ?? "", { isBrowser: false, }); outputs.push(templateCode); outputs.push("\n"); outputs.push(`export default { ...${SFC_MAIN}, render }`); return { code: outputs.join("\n") }; }, }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, INTERPOLATION, ATTRIBUTE, DIRECTIVE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode | InterpolationNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE; name: string; exp: string; arg: string; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } export interface InterpolationNode extends Node { type: NodeTypes.INTERPOLATION; content: string; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/codegen.ts ================================================ import { toHandlerKey } from "../shared"; import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type TemplateChildNode, type TextNode, } from "./ast"; import type { CompilerOptions } from "./options"; export const generate = ( { children, }: { children: TemplateChildNode[]; }, option: Required, ): string => { return `${option.isBrowser ? "return " : ""}function render(_ctx) { ${option.isBrowser ? "with (_ctx) {" : ""} const { h } = ChibiVue; return ${genNode(children[0], option)}; ${option.isBrowser ? "}" : ""} }`; }; const genNode = (node: TemplateChildNode, option: Required): string => { switch (node.type) { case NodeTypes.ELEMENT: return genElement(node, option); case NodeTypes.TEXT: return genText(node); case NodeTypes.INTERPOLATION: return genInterpolation(node, option); default: return ""; } }; const genElement = (el: ElementNode, option: Required): string => { return `h("${el.tag}", {${el.props .map((prop) => genProp(prop, option)) .join(", ")}}, [${el.children.map((it) => genNode(it, option)).join(", ")}])`; }; const genProp = ( prop: AttributeNode | DirectiveNode, option: Required, ): string => { switch (prop.type) { case NodeTypes.ATTRIBUTE: return `${prop.name}: "${prop.value?.content}"`; case NodeTypes.DIRECTIVE: { switch (prop.name) { case "on": return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? "" : "_ctx."}${prop.exp}`; default: // TODO: other directives throw new Error(`unexpected directive name. got "${prop.name}"`); } } default: throw new Error(`unexpected prop type.`); } }; const genText = (text: TextNode): string => { return `\`${text.content}\``; }; const genInterpolation = (node: InterpolationNode, option: Required): string => { return `${option.isBrowser ? "" : "_ctx."}${node.content}`; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import type { CompilerOptions } from "./options"; import { baseParse } from "./parse"; export function baseCompile(template: string, option: Required) { const parseResult = baseParse(template.trim()); const code = generate(parseResult, option); return code; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; export * from "./ast"; export * from "./options"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/options.ts ================================================ export type CompilerOptions = { isBrowser?: boolean; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/parse.ts ================================================ import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type Position, type SourceLocation, type TemplateChildNode, type TextNode, } from "./ast"; export interface ParserContext { readonly originalSource: string; source: string; offset: number; line: number; column: number; } function createParserContext(content: string): ParserContext { return { originalSource: content, source: content, column: 1, line: 1, offset: 0, }; } export const baseParse = (content: string): { children: TemplateChildNode[] } => { const context = createParserContext(content); const children = parseChildren(context, []); return { children: children }; }; function parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] { const nodes: TemplateChildNode[] = []; while (!isEnd(context, ancestors)) { const s = context.source; let node: TemplateChildNode | undefined = undefined; if (startsWith(s, "{{")) { node = parseInterpolation(context); } else if (s[0] === "<") { if (/[a-z]/i.test(s[1])) { node = parseElement(context, ancestors); } } if (!node) { node = parseText(context); } pushNode(nodes, node); } return nodes; } function advanceBy(context: ParserContext, numberOfCharacters: number): void { const { source } = context; advancePositionWithMutation(context, source, numberOfCharacters); context.source = source.slice(numberOfCharacters); } function advancePositionWithMutation( pos: Position, source: string, numberOfCharacters: number = source.length, ): Position { let linesCount = 0; let lastNewLinePos = -1; for (let i = 0; i < numberOfCharacters; i++) { if (source.charCodeAt(i) === 10 /* newline char code */) { linesCount++; lastNewLinePos = i; } } pos.offset += numberOfCharacters; pos.line += linesCount; pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; return pos; } function isEnd(context: ParserContext, ancestors: ElementNode[]): boolean { const s = context.source; if (startsWith(s, "= 0; --i) { if (startsWithEndTagOpen(s, ancestors[i].tag)) { return true; } } } return !s; } function startsWith(source: string, searchString: string): boolean { return source.startsWith(searchString); } function advanceSpaces(context: ParserContext): void { const match = /^[\t\r\n\f ]+/.exec(context.source); if (match) { advanceBy(context, match[0].length); } } function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void { if (node.type === NodeTypes.TEXT) { const prev = last(nodes); if (prev && prev.type === NodeTypes.TEXT) { prev.content += node.content; return; } } nodes.push(node); } function parseInterpolation(context: ParserContext): InterpolationNode | undefined { const [open, close] = ["{{", "}}"]; const closeIndex = context.source.indexOf(close, open.length); if (closeIndex === -1) return undefined; const start = getCursor(context); advanceBy(context, open.length); const innerStart = getCursor(context); const innerEnd = getCursor(context); const rawContentLength = closeIndex - open.length; const rawContent = context.source.slice(0, rawContentLength); const preTrimContent = parseTextData(context, rawContentLength); const content = preTrimContent.trim(); const startOffset = preTrimContent.indexOf(content); if (startOffset > 0) { advancePositionWithMutation(innerStart, rawContent, startOffset); } const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset); advancePositionWithMutation(innerEnd, rawContent, endOffset); advanceBy(context, close.length); return { type: NodeTypes.INTERPOLATION, content, loc: getSelection(context, start), }; } function parseText(context: ParserContext): TextNode { const endTokens = ["<", "{{"]; let endIndex = context.source.length; for (let i = 0; i < endTokens.length; i++) { const index = context.source.indexOf(endTokens[i], 1); if (index !== -1 && endIndex > index) { endIndex = index; } } const start = getCursor(context); const content = parseTextData(context, endIndex); return { type: NodeTypes.TEXT, content, loc: getSelection(context, start), }; } const enum TagType { Start, End, } function parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined { // Start tag. const element = parseTag(context, TagType.Start); // TODO: if (element.isSelfClosing) { return element; } // Children. ancestors.push(element); const children = parseChildren(context, ancestors); ancestors.pop(); element.children = children; // End tag. if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(context, TagType.End); // TODO: } return element; } function parseTag(context: ParserContext, type: TagType): ElementNode { // Tag open. const start = getCursor(context); const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!; const tag = match[1]; advanceBy(context, match[0].length); advanceSpaces(context); // Attributes. let props = parseAttributes(context, type); // Tag close. let isSelfClosing = false; isSelfClosing = startsWith(context.source, "/>"); advanceBy(context, isSelfClosing ? 2 : 1); return { type: NodeTypes.ELEMENT, tag, props, children: [], isSelfClosing, loc: getSelection(context, start), }; } function parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] { const props = []; const attributeNames = new Set(); while ( context.source.length > 0 && !startsWith(context.source, ">") && !startsWith(context.source, "/>") ) { const attr = parseAttribute(context, attributeNames); if (type === TagType.Start) { props.push(attr); } advanceSpaces(context); } return props; } type AttributeValue = | { content: string; loc: SourceLocation; } | undefined; function parseAttribute( context: ParserContext, nameSet: Set, ): AttributeNode | DirectiveNode { // Name. const start = getCursor(context); const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!; const name = match[0]; nameSet.add(name); advanceBy(context, name.length); // Value let value: AttributeValue = undefined; if (/^[\t\r\n\f ]*=/.test(context.source)) { advanceSpaces(context); advanceBy(context, 1); advanceSpaces(context); value = parseAttributeValue(context); } // directive const loc = getSelection(context, start); if (/^(v-[A-Za-z0-9-]|@)/.test(name)) { const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(name)!; let dirName = match[1] || (startsWith(name, "@") ? "on" : ""); let arg = ""; if (match[2]) arg = match[2]; return { type: NodeTypes.DIRECTIVE, name: dirName, exp: value?.content ?? "", loc, arg, }; } return { type: NodeTypes.ATTRIBUTE, name, value: value && { type: NodeTypes.TEXT, content: value.content, loc: value.loc, }, loc, }; } function parseAttributeValue(context: ParserContext): AttributeValue { const start = getCursor(context); let content: string; const quote = context.source[0]; const isQuoted = quote === `"` || quote === `'`; if (isQuoted) { // Quoted value. advanceBy(context, 1); const endIndex = context.source.indexOf(quote); if (endIndex === -1) { content = parseTextData(context, context.source.length); } else { content = parseTextData(context, endIndex); advanceBy(context, 1); } } else { // Unquoted const match = /^[^\t\r\n\f >]+/.exec(context.source); if (!match) { return undefined; } content = parseTextData(context, match[0].length); } return { content, loc: getSelection(context, start) }; } function parseTextData(context: ParserContext, length: number): string { const rawText = context.source.slice(0, length); advanceBy(context, length); return rawText; } function getCursor(context: ParserContext): Position { const { column, line, offset } = context; return { column, line, offset }; } function getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation { end = end || getCursor(context); return { start, end, source: context.originalSource.slice(start.offset, end.offset), }; } function last(xs: T[]): T | undefined { return xs[xs.length - 1]; } function startsWithEndTagOpen(source: string, tag: string): boolean { return ( startsWith(source, "]/.test(source[2 + tag.length] || ">") ); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-dom/index.ts ================================================ import { type CompilerOptions, baseCompile, baseParse } from "../compiler-core"; export function compile(template: string, option?: CompilerOptions) { const defaultOption: Required = { isBrowser: true }; if (option) Object.assign(defaultOption, option); return baseCompile(template, defaultOption); } export function parse(template: string) { return baseParse(template); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-sfc/compileTemplate.ts ================================================ import type { TemplateChildNode } from "../compiler-core"; export interface TemplateCompiler { compile(template: string): string; parse(template: string): { children: TemplateChildNode[] }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-sfc/index.ts ================================================ export * from "./parse"; export * from "./rewriteDefault"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-sfc/parse.ts ================================================ import { type ElementNode, NodeTypes, type SourceLocation } from "../compiler-core"; import * as CompilerDOM from "../compiler-dom"; import type { TemplateCompiler } from "./compileTemplate"; export interface SFCDescriptor { id: string; filename: string; source: string; template: SFCTemplateBlock | null; script: SFCScriptBlock | null; styles: SFCStyleBlock[]; } export interface SFCBlock { type: string; content: string; loc: SourceLocation; } export interface SFCTemplateBlock extends SFCBlock { type: "template"; } export interface SFCScriptBlock extends SFCBlock { type: "script"; } export declare interface SFCStyleBlock extends SFCBlock { type: "style"; } export interface SFCParseOptions { filename?: string; sourceRoot?: string; compiler?: TemplateCompiler; } export interface SFCParseResult { descriptor: SFCDescriptor; } export const DEFAULT_FILENAME = "anonymous.vue"; export function parse( source: string, { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {}, ): SFCParseResult { const descriptor: SFCDescriptor = { id: undefined!, filename, source, template: null, script: null, styles: [], }; const ast = compiler.parse(source); ast.children.forEach((node) => { if (node.type !== NodeTypes.ELEMENT) return; switch (node.tag) { case "template": { descriptor.template = createBlock(node, source) as SFCTemplateBlock; break; } case "script": { const scriptBlock = createBlock(node, source) as SFCScriptBlock; descriptor.script = scriptBlock; break; } case "style": { descriptor.styles.push(createBlock(node, source) as SFCStyleBlock); break; } default: { break; } } }); return { descriptor }; } function createBlock(node: ElementNode, source: string): SFCBlock { const type = node.tag; let { start, end } = node.loc; start = node.children[0].loc.start; end = node.children[node.children.length - 1].loc.end; const content = source.slice(start.offset, end.offset); const loc = { source: content, start, end }; const block: SFCBlock = { type, content, loc }; return block; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-sfc/rewriteDefault.ts ================================================ import { parse } from "@babel/parser"; import MagicString from "magic-string"; const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/; const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)(?:as)?(\s*)default/s; export function rewriteDefault(input: string, as: string): string { if (!hasDefaultExport(input)) { return input + `\nconst ${as} = {}`; } const s = new MagicString(input); const ast = parse(input, { sourceType: "module", }).program.body; ast.forEach((node) => { if (node.type === "ExportDefaultDeclaration") { if (node.declaration.type === "ClassDeclaration") { s.overwrite(node.start!, node.declaration.id.start!, `class `); s.append(`\nconst ${as} = ${node.declaration.id.name}`); } else { s.overwrite(node.start!, node.declaration.start!, `const ${as} = `); } } if (node.type === "ExportNamedDeclaration") { for (const specifier of node.specifiers) { if ( specifier.type === "ExportSpecifier" && specifier.exported.type === "Identifier" && specifier.exported.name === "default" ) { if (node.source) { if (specifier.local.name === "default") { const end = specifierEnd(input, specifier.local.end!, node.end!); s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = __VUE_DEFAULT__`); continue; } else { const end = specifierEnd(input, specifier.exported.end!, node.end!); s.prepend( `import { ${input.slice( specifier.local.start!, specifier.local.end!, )} } from '${node.source.value}'\n`, ); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); continue; } } const end = specifierEnd(input, specifier.end!, node.end!); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); } } } }); return s.toString(); } export function hasDefaultExport(input: string): boolean { return defaultExportRE.test(input) || namedDefaultExportRE.test(input); } function specifierEnd(input: string, end: number, nodeEnd: number | null) { // export { default , foo } ... let hasCommas = false; let oldEnd = end; while (end < nodeEnd!) { if (/\s/.test(input.charAt(end))) { end++; } else if (input.charAt(end) === ",") { end++; hasCommas = true; break; } else if (input.charAt(end) === "}") { break; } } return hasCommas ? end : oldEnd; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; setupState: Data; isMounted: boolean; } export type InternalRenderFunction = { (ctx: Data): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately setupState: {}, isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { const setupResult = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; // setupResultの型によって分岐をする if (typeof setupResult === "function") { instance.render = setupResult; } else if (typeof setupResult === "object" && setupResult !== null) { instance.setupState = setupResult; } else { // do nothing } } if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } const { render } = component; if (render) { instance.render = render as InternalRenderFunction; } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function | Record | void; render?: Function; template?: string; }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { Text, type VNode, createVNode, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, parentNode: hostParentNode, } = options; const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => { const { type } = n2; if (type === Text) { processText(n1, n2, container); } else if (typeof type === "string") { processElement(n1, n2, container); } else if (typeof type === "object") { processComponent(n1, n2, container); } else { // do nothing } }; const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountElement(n2, container); } else { patchElement(n1, n2); } }; const mountElement = (vnode: VNode, container: RendererElement) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = (children: VNode[], container: RendererElement) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container); } }; const patchElement = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; for (let i = 0; i < c2.length; i++) { const child = (c2[i] = normalizeVNode(c2[i])); patch(c1[i], child, container); } }; const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => { if (n1 == null) { mountComponent(n2, container); } else { updateComponent(n1, n2); } }; const mountComponent = (initialVNode: VNode, container: RendererElement) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, ) => { const componentUpdateFn = () => { const { render, setupState } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render(setupState))); patch(null, subTree, container); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render(setupState)); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container); }; return { render }; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/vnode.ts ================================================ import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; component: ComponentInternalInstance | null; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined, component: null, }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/shared/general.ts ================================================ const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/tests/e2e.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { compile } from "../packages/compiler-dom"; import { parse, rewriteDefault } from "../packages/compiler-sfc"; describe("10_minimum_example/070_sfc_compiler4", () => { it("should parse SFC with template, script, and style blocks", () => { const source = ` `.trim(); const { descriptor } = parse(source); expect(descriptor.template).not.toBeNull(); expect(descriptor.template?.content).toContain('class="container"'); expect(descriptor.script).not.toBeNull(); expect(descriptor.script?.content).toContain("export default"); expect(descriptor.styles.length).toBe(1); expect(descriptor.styles[0].content).toContain(".container"); }); it("should rewrite default export", () => { const input = `export default { setup() { return {} } }`; const result = rewriteDefault(input, "_sfc_main"); expect(result).toContain("const _sfc_main ="); expect(result).not.toContain("export default"); }); it("should handle missing default export", () => { const input = `const foo = 1;`; const result = rewriteDefault(input, "_sfc_main"); expect(result).toContain("const _sfc_main = {}"); }); it("should compile template with attributes", () => { const template = `
content
`; const code = compile(template, { isBrowser: false }); expect(code).toContain("function render"); expect(code).toContain('"div"'); expect(code).toContain("class"); expect(code).toContain("box"); }); it("should compile nested elements", () => { const template = `
nested
`; const code = compile(template, { isBrowser: false }); expect(code).toContain('"div"'); expect(code).toContain('"span"'); expect(code).toContain("nested"); }); }); ================================================ FILE: book/impls/10_minimum_example/070_sfc_compiler4/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/src/main.ts ================================================ import { createApp, h, reactive } from "chibivue"; const app = createApp({ setup() { const state = reactive({ list: [{ key: "a" }, { key: "b" }, { key: "c" }, { key: "d" }], }); const updateList = () => { state.list = [{ key: "a" }, { key: "b" }, { key: "d" }, { key: "c" }]; }; return () => h("div", { id: "app" }, [ h( "ul", {}, state.list.map((item) => h("li", { key: item.key }, [item.key])), ), h("button", { onClick: updateList }, ["update"]), ]); }, }); app.mount("#app"); ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; import chibivue from "../../packages/@extensions/vite-plugin-chibivue"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, plugins: [chibivue()], }); ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" }, "dependencies": { "@babel/parser": "^7.28.6", "magic-string": "^0.30.21" } } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/@extensions/vite-plugin-chibivue/index.ts ================================================ import fs from "node:fs"; import type { Plugin } from "vite"; import { createFilter } from "vite"; import { parse, rewriteDefault } from "../../compiler-sfc"; import { compile } from "../../compiler-dom"; export default function vitePluginChibivue(): Plugin { const filter = createFilter(/\.vue$/); return { name: "vite:chibivue", resolveId(id) { if (id.match(/\.vue\.css$/)) return id; }, load(id) { if (id.match(/\.vue\.css$/)) { const filename = id.replace(/\.css$/, ""); const content = fs.readFileSync(filename, "utf-8"); const { descriptor } = parse(content, { filename }); const styles = descriptor.styles.map((it) => it.content).join("\n"); return { code: styles }; } }, transform(code, id) { if (!filter(id)) return; const outputs = []; outputs.push("import * as ChibiVue from 'chibivue'"); outputs.push(`import '${id}.css'`); const { descriptor } = parse(code, { filename: id }); const SFC_MAIN = "_sfc_main"; const scriptCode = rewriteDefault(descriptor.script?.content ?? "", SFC_MAIN); outputs.push(scriptCode); const templateCode = compile(descriptor.template?.content ?? "", { isBrowser: false, }); outputs.push(templateCode); outputs.push("\n"); outputs.push(`export default { ...${SFC_MAIN}, render }`); return { code: outputs.join("\n") }; }, }; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, INTERPOLATION, ATTRIBUTE, DIRECTIVE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode | InterpolationNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE; name: string; exp: string; arg: string; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } export interface InterpolationNode extends Node { type: NodeTypes.INTERPOLATION; content: string; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/codegen.ts ================================================ import { toHandlerKey } from "../shared"; import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type TemplateChildNode, type TextNode, } from "./ast"; import type { CompilerOptions } from "./options"; export const generate = ( { children, }: { children: TemplateChildNode[]; }, option: Required, ): string => { return `${option.isBrowser ? "return " : ""}function render(_ctx) { ${option.isBrowser ? "with (_ctx) {" : ""} const { h } = ChibiVue; return ${genNode(children[0], option)}; ${option.isBrowser ? "}" : ""} }`; }; const genNode = (node: TemplateChildNode, option: Required): string => { switch (node.type) { case NodeTypes.ELEMENT: return genElement(node, option); case NodeTypes.TEXT: return genText(node); case NodeTypes.INTERPOLATION: return genInterpolation(node, option); default: return ""; } }; const genElement = (el: ElementNode, option: Required): string => { return `h("${el.tag}", {${el.props .map((prop) => genProp(prop, option)) .join(", ")}}, [${el.children.map((it) => genNode(it, option)).join(", ")}])`; }; const genProp = ( prop: AttributeNode | DirectiveNode, option: Required, ): string => { switch (prop.type) { case NodeTypes.ATTRIBUTE: return `${prop.name}: "${prop.value?.content}"`; case NodeTypes.DIRECTIVE: { switch (prop.name) { case "on": return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? "" : "_ctx."}${prop.exp}`; default: // TODO: other directives throw new Error(`unexpected directive name. got "${prop.name}"`); } } default: throw new Error(`unexpected prop type.`); } }; const genText = (text: TextNode): string => { return `\`${text.content}\``; }; const genInterpolation = (node: InterpolationNode, option: Required): string => { return `${option.isBrowser ? "" : "_ctx."}${node.content}`; }; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import type { CompilerOptions } from "./options"; import { baseParse } from "./parse"; export function baseCompile(template: string, option: Required) { const parseResult = baseParse(template.trim()); const code = generate(parseResult, option); return code; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; export * from "./ast"; export * from "./options"; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/options.ts ================================================ export type CompilerOptions = { isBrowser?: boolean; }; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/parse.ts ================================================ import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type Position, type SourceLocation, type TemplateChildNode, type TextNode, } from "./ast"; export interface ParserContext { readonly originalSource: string; source: string; offset: number; line: number; column: number; } function createParserContext(content: string): ParserContext { return { originalSource: content, source: content, column: 1, line: 1, offset: 0, }; } export const baseParse = (content: string): { children: TemplateChildNode[] } => { const context = createParserContext(content); const children = parseChildren(context, []); return { children: children }; }; function parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] { const nodes: TemplateChildNode[] = []; while (!isEnd(context, ancestors)) { const s = context.source; let node: TemplateChildNode | undefined = undefined; if (startsWith(s, "{{")) { node = parseInterpolation(context); } else if (s[0] === "<") { if (/[a-z]/i.test(s[1])) { node = parseElement(context, ancestors); } } if (!node) { node = parseText(context); } pushNode(nodes, node); } return nodes; } function advanceBy(context: ParserContext, numberOfCharacters: number): void { const { source } = context; advancePositionWithMutation(context, source, numberOfCharacters); context.source = source.slice(numberOfCharacters); } function advancePositionWithMutation( pos: Position, source: string, numberOfCharacters: number = source.length, ): Position { let linesCount = 0; let lastNewLinePos = -1; for (let i = 0; i < numberOfCharacters; i++) { if (source.charCodeAt(i) === 10 /* newline char code */) { linesCount++; lastNewLinePos = i; } } pos.offset += numberOfCharacters; pos.line += linesCount; pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; return pos; } function isEnd(context: ParserContext, ancestors: ElementNode[]): boolean { const s = context.source; if (startsWith(s, "= 0; --i) { if (startsWithEndTagOpen(s, ancestors[i].tag)) { return true; } } } return !s; } function startsWith(source: string, searchString: string): boolean { return source.startsWith(searchString); } function advanceSpaces(context: ParserContext): void { const match = /^[\t\r\n\f ]+/.exec(context.source); if (match) { advanceBy(context, match[0].length); } } function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void { if (node.type === NodeTypes.TEXT) { const prev = last(nodes); if (prev && prev.type === NodeTypes.TEXT) { prev.content += node.content; return; } } nodes.push(node); } function parseInterpolation(context: ParserContext): InterpolationNode | undefined { const [open, close] = ["{{", "}}"]; const closeIndex = context.source.indexOf(close, open.length); if (closeIndex === -1) return undefined; const start = getCursor(context); advanceBy(context, open.length); const innerStart = getCursor(context); const innerEnd = getCursor(context); const rawContentLength = closeIndex - open.length; const rawContent = context.source.slice(0, rawContentLength); const preTrimContent = parseTextData(context, rawContentLength); const content = preTrimContent.trim(); const startOffset = preTrimContent.indexOf(content); if (startOffset > 0) { advancePositionWithMutation(innerStart, rawContent, startOffset); } const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset); advancePositionWithMutation(innerEnd, rawContent, endOffset); advanceBy(context, close.length); return { type: NodeTypes.INTERPOLATION, content, loc: getSelection(context, start), }; } function parseText(context: ParserContext): TextNode { const endTokens = ["<", "{{"]; let endIndex = context.source.length; for (let i = 0; i < endTokens.length; i++) { const index = context.source.indexOf(endTokens[i], 1); if (index !== -1 && endIndex > index) { endIndex = index; } } const start = getCursor(context); const content = parseTextData(context, endIndex); return { type: NodeTypes.TEXT, content, loc: getSelection(context, start), }; } const enum TagType { Start, End, } function parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined { // Start tag. const element = parseTag(context, TagType.Start); // TODO: if (element.isSelfClosing) { return element; } // Children. ancestors.push(element); const children = parseChildren(context, ancestors); ancestors.pop(); element.children = children; // End tag. if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(context, TagType.End); // TODO: } return element; } function parseTag(context: ParserContext, type: TagType): ElementNode { // Tag open. const start = getCursor(context); const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!; const tag = match[1]; advanceBy(context, match[0].length); advanceSpaces(context); // Attributes. let props = parseAttributes(context, type); // Tag close. let isSelfClosing = false; isSelfClosing = startsWith(context.source, "/>"); advanceBy(context, isSelfClosing ? 2 : 1); return { type: NodeTypes.ELEMENT, tag, props, children: [], isSelfClosing, loc: getSelection(context, start), }; } function parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] { const props = []; const attributeNames = new Set(); while ( context.source.length > 0 && !startsWith(context.source, ">") && !startsWith(context.source, "/>") ) { const attr = parseAttribute(context, attributeNames); if (type === TagType.Start) { props.push(attr); } advanceSpaces(context); } return props; } type AttributeValue = | { content: string; loc: SourceLocation; } | undefined; function parseAttribute( context: ParserContext, nameSet: Set, ): AttributeNode | DirectiveNode { // Name. const start = getCursor(context); const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!; const name = match[0]; nameSet.add(name); advanceBy(context, name.length); // Value let value: AttributeValue = undefined; if (/^[\t\r\n\f ]*=/.test(context.source)) { advanceSpaces(context); advanceBy(context, 1); advanceSpaces(context); value = parseAttributeValue(context); } // directive const loc = getSelection(context, start); if (/^(v-[A-Za-z0-9-]|@)/.test(name)) { const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(name)!; let dirName = match[1] || (startsWith(name, "@") ? "on" : ""); let arg = ""; if (match[2]) arg = match[2]; return { type: NodeTypes.DIRECTIVE, name: dirName, exp: value?.content ?? "", loc, arg, }; } return { type: NodeTypes.ATTRIBUTE, name, value: value && { type: NodeTypes.TEXT, content: value.content, loc: value.loc, }, loc, }; } function parseAttributeValue(context: ParserContext): AttributeValue { const start = getCursor(context); let content: string; const quote = context.source[0]; const isQuoted = quote === `"` || quote === `'`; if (isQuoted) { // Quoted value. advanceBy(context, 1); const endIndex = context.source.indexOf(quote); if (endIndex === -1) { content = parseTextData(context, context.source.length); } else { content = parseTextData(context, endIndex); advanceBy(context, 1); } } else { // Unquoted const match = /^[^\t\r\n\f >]+/.exec(context.source); if (!match) { return undefined; } content = parseTextData(context, match[0].length); } return { content, loc: getSelection(context, start) }; } function parseTextData(context: ParserContext, length: number): string { const rawText = context.source.slice(0, length); advanceBy(context, length); return rawText; } function getCursor(context: ParserContext): Position { const { column, line, offset } = context; return { column, line, offset }; } function getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation { end = end || getCursor(context); return { start, end, source: context.originalSource.slice(start.offset, end.offset), }; } function last(xs: T[]): T | undefined { return xs[xs.length - 1]; } function startsWithEndTagOpen(source: string, tag: string): boolean { return ( startsWith(source, "]/.test(source[2 + tag.length] || ">") ); } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-dom/index.ts ================================================ import { type CompilerOptions, baseCompile, baseParse } from "../compiler-core"; export function compile(template: string, option?: CompilerOptions) { const defaultOption: Required = { isBrowser: true }; if (option) Object.assign(defaultOption, option); return baseCompile(template, defaultOption); } export function parse(template: string) { return baseParse(template); } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-sfc/compileTemplate.ts ================================================ import type { TemplateChildNode } from "../compiler-core"; export interface TemplateCompiler { compile(template: string): string; parse(template: string): { children: TemplateChildNode[] }; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-sfc/index.ts ================================================ export * from "./parse"; export * from "./rewriteDefault"; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-sfc/parse.ts ================================================ import { type ElementNode, NodeTypes, type SourceLocation } from "../compiler-core"; import * as CompilerDOM from "../compiler-dom"; import type { TemplateCompiler } from "./compileTemplate"; export interface SFCDescriptor { id: string; filename: string; source: string; template: SFCTemplateBlock | null; script: SFCScriptBlock | null; styles: SFCStyleBlock[]; } export interface SFCBlock { type: string; content: string; loc: SourceLocation; } export interface SFCTemplateBlock extends SFCBlock { type: "template"; } export interface SFCScriptBlock extends SFCBlock { type: "script"; } export declare interface SFCStyleBlock extends SFCBlock { type: "style"; } export interface SFCParseOptions { filename?: string; sourceRoot?: string; compiler?: TemplateCompiler; } export interface SFCParseResult { descriptor: SFCDescriptor; } export const DEFAULT_FILENAME = "anonymous.vue"; export function parse( source: string, { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {}, ): SFCParseResult { const descriptor: SFCDescriptor = { id: undefined!, filename, source, template: null, script: null, styles: [], }; const ast = compiler.parse(source); ast.children.forEach((node) => { if (node.type !== NodeTypes.ELEMENT) return; switch (node.tag) { case "template": { descriptor.template = createBlock(node, source) as SFCTemplateBlock; break; } case "script": { const scriptBlock = createBlock(node, source) as SFCScriptBlock; descriptor.script = scriptBlock; break; } case "style": { descriptor.styles.push(createBlock(node, source) as SFCStyleBlock); break; } default: { break; } } }); return { descriptor }; } function createBlock(node: ElementNode, source: string): SFCBlock { const type = node.tag; let { start, end } = node.loc; start = node.children[0].loc.start; end = node.children[node.children.length - 1].loc.end; const content = source.slice(start.offset, end.offset); const loc = { source: content, start, end }; const block: SFCBlock = { type, content, loc }; return block; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-sfc/rewriteDefault.ts ================================================ import { parse } from "@babel/parser"; import MagicString from "magic-string"; const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/; const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)(?:as)?(\s*)default/s; export function rewriteDefault(input: string, as: string): string { if (!hasDefaultExport(input)) { return input + `\nconst ${as} = {}`; } const s = new MagicString(input); const ast = parse(input, { sourceType: "module", }).program.body; ast.forEach((node) => { if (node.type === "ExportDefaultDeclaration") { if (node.declaration.type === "ClassDeclaration") { s.overwrite(node.start!, node.declaration.id.start!, `class `); s.append(`\nconst ${as} = ${node.declaration.id.name}`); } else { s.overwrite(node.start!, node.declaration.start!, `const ${as} = `); } } if (node.type === "ExportNamedDeclaration") { for (const specifier of node.specifiers) { if ( specifier.type === "ExportSpecifier" && specifier.exported.type === "Identifier" && specifier.exported.name === "default" ) { if (node.source) { if (specifier.local.name === "default") { const end = specifierEnd(input, specifier.local.end!, node.end!); s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = __VUE_DEFAULT__`); continue; } else { const end = specifierEnd(input, specifier.exported.end!, node.end!); s.prepend( `import { ${input.slice( specifier.local.start!, specifier.local.end!, )} } from '${node.source.value}'\n`, ); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); continue; } } const end = specifierEnd(input, specifier.end!, node.end!); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); } } } }); return s.toString(); } export function hasDefaultExport(input: string): boolean { return defaultExportRE.test(input) || namedDefaultExportRE.test(input); } function specifierEnd(input: string, end: number, nodeEnd: number | null) { // export { default , foo } ... let hasCommas = false; let oldEnd = end; while (end < nodeEnd!) { if (/\s/.test(input.charAt(end))) { end++; } else if (input.charAt(end) === ",") { end++; hasCommas = true; break; } else if (input.charAt(end) === "}") { break; } } return hasCommas ? end : oldEnd; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/reactivity/baseHandler.ts ================================================ import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (res !== null && typeof res === "object") { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; setupState: Data; isMounted: boolean; } export type InternalRenderFunction = { (ctx: Data): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately setupState: {}, isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { const setupResult = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; // setupResultの型によって分岐をする if (typeof setupResult === "function") { instance.render = setupResult; } else if (typeof setupResult === "object" && setupResult !== null) { instance.setupState = setupResult; } else { // do nothing } } if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } const { render } = component; if (render) { instance.render = render as InternalRenderFunction; } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function | Record | void; render?: Function; template?: string; }; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; remove(child: HostNode): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, insert: hostInsert, remove: hostRemove, parentNode: hostParentNode, } = options; const patch = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const { type } = n2; if (type === Text) { processText(n1, n2, container, anchor); } else if (typeof type === "string") { processElement(n1, n2, container, anchor); } else if (typeof type === "object") { processComponent(n1, n2, container, anchor); } else { // do nothing } }; const processElement = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { mountElement(n2, container, anchor); } else { patchElement(n1, n2, anchor); } }; const mountElement = ( vnode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el, anchor); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = ( children: VNode[], container: RendererElement, anchor: RendererElement | null, ) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container, anchor); } }; const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el, anchor); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = ( n1: VNode, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const c1 = n1.children as VNode[]; const c2 = n2.children as VNode[]; patchKeyedChildren(c1, c2, container, anchor); }; const patchKeyedChildren = ( c1: VNode[], c2: VNode[], container: RendererElement, parentAnchor: RendererElement | null, ) => { let i = 0; const l2 = c2.length; const e1 = c1.length - 1; const e2 = l2 - 1; const keyToNewIndexMap: Map = new Map(); for (i = 0; i <= e2; i++) { const nextChild = (c2[i] = normalizeVNode(c2[i])); if (nextChild.key != null) { keyToNewIndexMap.set(nextChild.key, i); } } let j; let patched = 0; const toBePatched = e2 + 1; let moved = false; let maxNewIndexSoFar = 0; const newIndexToOldIndexMap = new Array(toBePatched); for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0; for (i = 0; i <= e1; i++) { const prevChild = c1[i]; if (patched >= toBePatched) { // all new children have been patched so this can only be a removal unmount(prevChild); continue; } let newIndex; if (prevChild.key != null) { newIndex = keyToNewIndexMap.get(prevChild.key); } else { // key-less node, try to locate a key-less node of the same type for (j = 0; j <= e2; j++) { if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) { newIndex = j; break; } } } if (newIndex === undefined) { unmount(prevChild); } else { newIndexToOldIndexMap[newIndex] = i + 1; if (newIndex >= maxNewIndexSoFar) { maxNewIndexSoFar = newIndex; } else { moved = true; } patch(prevChild, c2[newIndex] as VNode, container, null); patched++; } } const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; j = increasingNewIndexSequence.length - 1; for (i = toBePatched - 1; i >= 0; i--) { const nextIndex = i; const nextChild = c2[nextIndex] as VNode; const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor; if (newIndexToOldIndexMap[i] === 0) { // mount new patch(null, nextChild, container, anchor); } else if (moved) { // move if: // There is no stable subsequence (e.g. a reverse) // OR current node is not among the stable sequence if (j < 0 || i !== increasingNewIndexSequence[j]) { move(nextChild, container, anchor); } else { j--; } } } }; const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => { const { el, type } = vnode; if (typeof type === "object") { move(vnode.component!.subTree, container, anchor); return; } hostInsert(el!, container, anchor); }; const unmount = (vnode: VNode) => { const { type, children } = vnode; if (typeof type === "object") { unmountComponent(vnode.component!); } else if (Array.isArray(children)) { unmountChildren(children as VNode[]); } remove(vnode); }; const remove = (vnode: VNode) => { const { el } = vnode; hostRemove(el!); }; const unmountComponent = (instance: ComponentInternalInstance) => { const { subTree } = instance; unmount(subTree); }; const unmountChildren = (children: VNode[]) => { for (let i = 0; i < children.length; i++) { unmount(children[i]); } }; const processText = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { mountComponent(n2, container, anchor); } else { updateComponent(n1, n2); } }; const mountComponent = ( initialVNode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container, anchor); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const componentUpdateFn = () => { const { render, setupState } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render(setupState))); patch(null, subTree, container, anchor); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render(setupState)); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container, null); }; return { render }; } // https://en.wikipedia.org/wiki/Longest_increasing_subsequence function getSequence(arr: number[]): number[] { const p = arr.slice(); const result = [0]; let i, j, u, v, c; const len = arr.length; for (i = 0; i < len; i++) { const arrI = arr[i]; if (arrI !== 0) { j = result[result.length - 1]; if (arr[j] < arrI) { p[i] = j; result.push(i); continue; } u = 0; v = result.length - 1; while (u < v) { c = (u + v) >> 1; if (arr[result[c]] < arrI) { u = c + 1; } else { v = c; } } if (arrI < arr[result[u]]) { if (u > 0) { p[i] = result[u - 1]; } result[u] = i; } } } u = result.length; v = result[u - 1]; while (u-- > 0) { result[u] = v; v = p[v]; } return result; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/vnode.ts ================================================ import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; key: string | number | symbol | null; component: ComponentInternalInstance | null; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const vnode: VNode = { type, props, children: children, el: undefined, key: props?.key ?? null, component: null, }; return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } export function isSameVNodeType(n1: VNode, n2: VNode): boolean { return n1.type === n2.type && n1.key === n2.key; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, remove: (child) => { const parent = child.parentNode; if (parent) { parent.removeChild(child); } }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/shared/general.ts ================================================ const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createApp, h, reactive } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("20_basic_virtual_dom/010_patch_keyed_children", () => { it("should patch keyed children correctly", async () => { const state = reactive({ list: [1, 2, 3] }); const app = createApp({ setup() { return () => h( "ul", {}, state.list.map((item) => h("li", { key: item }, [String(item)])), ); }, }); app.mount("#host"); expect(host.innerHTML).toBe('
  • 1
  • 2
  • 3
'); state.list = [3, 2, 1]; await Promise.resolve(); expect(host.innerHTML).toBe('
  • 3
  • 2
  • 1
'); }); }); ================================================ FILE: book/impls/20_basic_virtual_dom/010_patch_keyed_children/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/src/main.ts ================================================ import { createApp, h, reactive } from "chibivue"; const app = createApp({ setup() { const state = reactive({ list: [{ key: "a" }, { key: "b" }, { key: "c" }, { key: "d" }], }); const updateList = () => { state.list = [{ key: "a" }, { key: "b" }, { key: "d" }, { key: "c" }]; }; return () => h("div", { id: "app" }, [ h( "ul", {}, state.list.map((item) => h("li", { key: item.key }, [item.key])), ), h("button", { onClick: updateList }, ["update"]), ]); }, }); app.mount("#app"); ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; import chibivue from "../../packages/@extensions/vite-plugin-chibivue"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, plugins: [chibivue()], }); ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" }, "dependencies": { "@babel/parser": "^7.28.6", "magic-string": "^0.30.21" } } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/@extensions/vite-plugin-chibivue/index.ts ================================================ import fs from "node:fs"; import type { Plugin } from "vite"; import { createFilter } from "vite"; import { parse, rewriteDefault } from "../../compiler-sfc"; import { compile } from "../../compiler-dom"; export default function vitePluginChibivue(): Plugin { const filter = createFilter(/\.vue$/); return { name: "vite:chibivue", resolveId(id) { if (id.match(/\.vue\.css$/)) return id; }, load(id) { if (id.match(/\.vue\.css$/)) { const filename = id.replace(/\.css$/, ""); const content = fs.readFileSync(filename, "utf-8"); const { descriptor } = parse(content, { filename }); const styles = descriptor.styles.map((it) => it.content).join("\n"); return { code: styles }; } }, transform(code, id) { if (!filter(id)) return; const outputs = []; outputs.push("import * as ChibiVue from 'chibivue'"); outputs.push(`import '${id}.css'`); const { descriptor } = parse(code, { filename: id }); const SFC_MAIN = "_sfc_main"; const scriptCode = rewriteDefault(descriptor.script?.content ?? "", SFC_MAIN); outputs.push(scriptCode); const templateCode = compile(descriptor.template?.content ?? "", { isBrowser: false, }); outputs.push(templateCode); outputs.push("\n"); outputs.push(`export default { ...${SFC_MAIN}, render }`); return { code: outputs.join("\n") }; }, }; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, INTERPOLATION, ATTRIBUTE, DIRECTIVE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode | InterpolationNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE; name: string; exp: string; arg: string; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } export interface InterpolationNode extends Node { type: NodeTypes.INTERPOLATION; content: string; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/codegen.ts ================================================ import { toHandlerKey } from "../shared"; import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type TemplateChildNode, type TextNode, } from "./ast"; import type { CompilerOptions } from "./options"; export const generate = ( { children, }: { children: TemplateChildNode[]; }, option: Required, ): string => { return `${option.isBrowser ? "return " : ""}function render(_ctx) { ${option.isBrowser ? "with (_ctx) {" : ""} const { h } = ChibiVue; return ${genNode(children[0], option)}; ${option.isBrowser ? "}" : ""} }`; }; const genNode = (node: TemplateChildNode, option: Required): string => { switch (node.type) { case NodeTypes.ELEMENT: return genElement(node, option); case NodeTypes.TEXT: return genText(node); case NodeTypes.INTERPOLATION: return genInterpolation(node, option); default: return ""; } }; const genElement = (el: ElementNode, option: Required): string => { return `h("${el.tag}", {${el.props .map((prop) => genProp(prop, option)) .join(", ")}}, [${el.children.map((it) => genNode(it, option)).join(", ")}])`; }; const genProp = ( prop: AttributeNode | DirectiveNode, option: Required, ): string => { switch (prop.type) { case NodeTypes.ATTRIBUTE: return `${prop.name}: "${prop.value?.content}"`; case NodeTypes.DIRECTIVE: { switch (prop.name) { case "on": return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? "" : "_ctx."}${prop.exp}`; default: // TODO: other directives throw new Error(`unexpected directive name. got "${prop.name}"`); } } default: throw new Error(`unexpected prop type.`); } }; const genText = (text: TextNode): string => { return `\`${text.content}\``; }; const genInterpolation = (node: InterpolationNode, option: Required): string => { return `${option.isBrowser ? "" : "_ctx."}${node.content}`; }; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import type { CompilerOptions } from "./options"; import { baseParse } from "./parse"; export function baseCompile(template: string, option: Required) { const parseResult = baseParse(template.trim()); const code = generate(parseResult, option); return code; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; export * from "./ast"; export * from "./options"; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/options.ts ================================================ export type CompilerOptions = { isBrowser?: boolean; }; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/parse.ts ================================================ import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type Position, type SourceLocation, type TemplateChildNode, type TextNode, } from "./ast"; export interface ParserContext { readonly originalSource: string; source: string; offset: number; line: number; column: number; } function createParserContext(content: string): ParserContext { return { originalSource: content, source: content, column: 1, line: 1, offset: 0, }; } export const baseParse = (content: string): { children: TemplateChildNode[] } => { const context = createParserContext(content); const children = parseChildren(context, []); return { children: children }; }; function parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] { const nodes: TemplateChildNode[] = []; while (!isEnd(context, ancestors)) { const s = context.source; let node: TemplateChildNode | undefined = undefined; if (startsWith(s, "{{")) { node = parseInterpolation(context); } else if (s[0] === "<") { if (/[a-z]/i.test(s[1])) { node = parseElement(context, ancestors); } } if (!node) { node = parseText(context); } pushNode(nodes, node); } return nodes; } function advanceBy(context: ParserContext, numberOfCharacters: number): void { const { source } = context; advancePositionWithMutation(context, source, numberOfCharacters); context.source = source.slice(numberOfCharacters); } function advancePositionWithMutation( pos: Position, source: string, numberOfCharacters: number = source.length, ): Position { let linesCount = 0; let lastNewLinePos = -1; for (let i = 0; i < numberOfCharacters; i++) { if (source.charCodeAt(i) === 10 /* newline char code */) { linesCount++; lastNewLinePos = i; } } pos.offset += numberOfCharacters; pos.line += linesCount; pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; return pos; } function isEnd(context: ParserContext, ancestors: ElementNode[]): boolean { const s = context.source; if (startsWith(s, "= 0; --i) { if (startsWithEndTagOpen(s, ancestors[i].tag)) { return true; } } } return !s; } function startsWith(source: string, searchString: string): boolean { return source.startsWith(searchString); } function advanceSpaces(context: ParserContext): void { const match = /^[\t\r\n\f ]+/.exec(context.source); if (match) { advanceBy(context, match[0].length); } } function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void { if (node.type === NodeTypes.TEXT) { const prev = last(nodes); if (prev && prev.type === NodeTypes.TEXT) { prev.content += node.content; return; } } nodes.push(node); } function parseInterpolation(context: ParserContext): InterpolationNode | undefined { const [open, close] = ["{{", "}}"]; const closeIndex = context.source.indexOf(close, open.length); if (closeIndex === -1) return undefined; const start = getCursor(context); advanceBy(context, open.length); const innerStart = getCursor(context); const innerEnd = getCursor(context); const rawContentLength = closeIndex - open.length; const rawContent = context.source.slice(0, rawContentLength); const preTrimContent = parseTextData(context, rawContentLength); const content = preTrimContent.trim(); const startOffset = preTrimContent.indexOf(content); if (startOffset > 0) { advancePositionWithMutation(innerStart, rawContent, startOffset); } const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset); advancePositionWithMutation(innerEnd, rawContent, endOffset); advanceBy(context, close.length); return { type: NodeTypes.INTERPOLATION, content, loc: getSelection(context, start), }; } function parseText(context: ParserContext): TextNode { const endTokens = ["<", "{{"]; let endIndex = context.source.length; for (let i = 0; i < endTokens.length; i++) { const index = context.source.indexOf(endTokens[i], 1); if (index !== -1 && endIndex > index) { endIndex = index; } } const start = getCursor(context); const content = parseTextData(context, endIndex); return { type: NodeTypes.TEXT, content, loc: getSelection(context, start), }; } const enum TagType { Start, End, } function parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined { // Start tag. const element = parseTag(context, TagType.Start); // TODO: if (element.isSelfClosing) { return element; } // Children. ancestors.push(element); const children = parseChildren(context, ancestors); ancestors.pop(); element.children = children; // End tag. if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(context, TagType.End); // TODO: } return element; } function parseTag(context: ParserContext, type: TagType): ElementNode { // Tag open. const start = getCursor(context); const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!; const tag = match[1]; advanceBy(context, match[0].length); advanceSpaces(context); // Attributes. let props = parseAttributes(context, type); // Tag close. let isSelfClosing = false; isSelfClosing = startsWith(context.source, "/>"); advanceBy(context, isSelfClosing ? 2 : 1); return { type: NodeTypes.ELEMENT, tag, props, children: [], isSelfClosing, loc: getSelection(context, start), }; } function parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] { const props = []; const attributeNames = new Set(); while ( context.source.length > 0 && !startsWith(context.source, ">") && !startsWith(context.source, "/>") ) { const attr = parseAttribute(context, attributeNames); if (type === TagType.Start) { props.push(attr); } advanceSpaces(context); } return props; } type AttributeValue = | { content: string; loc: SourceLocation; } | undefined; function parseAttribute( context: ParserContext, nameSet: Set, ): AttributeNode | DirectiveNode { // Name. const start = getCursor(context); const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!; const name = match[0]; nameSet.add(name); advanceBy(context, name.length); // Value let value: AttributeValue = undefined; if (/^[\t\r\n\f ]*=/.test(context.source)) { advanceSpaces(context); advanceBy(context, 1); advanceSpaces(context); value = parseAttributeValue(context); } // directive const loc = getSelection(context, start); if (/^(v-[A-Za-z0-9-]|@)/.test(name)) { const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(name)!; let dirName = match[1] || (startsWith(name, "@") ? "on" : ""); let arg = ""; if (match[2]) arg = match[2]; return { type: NodeTypes.DIRECTIVE, name: dirName, exp: value?.content ?? "", loc, arg, }; } return { type: NodeTypes.ATTRIBUTE, name, value: value && { type: NodeTypes.TEXT, content: value.content, loc: value.loc, }, loc, }; } function parseAttributeValue(context: ParserContext): AttributeValue { const start = getCursor(context); let content: string; const quote = context.source[0]; const isQuoted = quote === `"` || quote === `'`; if (isQuoted) { // Quoted value. advanceBy(context, 1); const endIndex = context.source.indexOf(quote); if (endIndex === -1) { content = parseTextData(context, context.source.length); } else { content = parseTextData(context, endIndex); advanceBy(context, 1); } } else { // Unquoted const match = /^[^\t\r\n\f >]+/.exec(context.source); if (!match) { return undefined; } content = parseTextData(context, match[0].length); } return { content, loc: getSelection(context, start) }; } function parseTextData(context: ParserContext, length: number): string { const rawText = context.source.slice(0, length); advanceBy(context, length); return rawText; } function getCursor(context: ParserContext): Position { const { column, line, offset } = context; return { column, line, offset }; } function getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation { end = end || getCursor(context); return { start, end, source: context.originalSource.slice(start.offset, end.offset), }; } function last(xs: T[]): T | undefined { return xs[xs.length - 1]; } function startsWithEndTagOpen(source: string, tag: string): boolean { return ( startsWith(source, "]/.test(source[2 + tag.length] || ">") ); } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-dom/index.ts ================================================ import { type CompilerOptions, baseCompile, baseParse } from "../compiler-core"; export function compile(template: string, option?: CompilerOptions) { const defaultOption: Required = { isBrowser: true }; if (option) Object.assign(defaultOption, option); return baseCompile(template, defaultOption); } export function parse(template: string) { return baseParse(template); } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-sfc/compileTemplate.ts ================================================ import type { TemplateChildNode } from "../compiler-core"; export interface TemplateCompiler { compile(template: string): string; parse(template: string): { children: TemplateChildNode[] }; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-sfc/index.ts ================================================ export * from "./parse"; export * from "./rewriteDefault"; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-sfc/parse.ts ================================================ import { type ElementNode, NodeTypes, type SourceLocation } from "../compiler-core"; import * as CompilerDOM from "../compiler-dom"; import type { TemplateCompiler } from "./compileTemplate"; export interface SFCDescriptor { id: string; filename: string; source: string; template: SFCTemplateBlock | null; script: SFCScriptBlock | null; styles: SFCStyleBlock[]; } export interface SFCBlock { type: string; content: string; loc: SourceLocation; } export interface SFCTemplateBlock extends SFCBlock { type: "template"; } export interface SFCScriptBlock extends SFCBlock { type: "script"; } export declare interface SFCStyleBlock extends SFCBlock { type: "style"; } export interface SFCParseOptions { filename?: string; sourceRoot?: string; compiler?: TemplateCompiler; } export interface SFCParseResult { descriptor: SFCDescriptor; } export const DEFAULT_FILENAME = "anonymous.vue"; export function parse( source: string, { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {}, ): SFCParseResult { const descriptor: SFCDescriptor = { id: undefined!, filename, source, template: null, script: null, styles: [], }; const ast = compiler.parse(source); ast.children.forEach((node) => { if (node.type !== NodeTypes.ELEMENT) return; switch (node.tag) { case "template": { descriptor.template = createBlock(node, source) as SFCTemplateBlock; break; } case "script": { const scriptBlock = createBlock(node, source) as SFCScriptBlock; descriptor.script = scriptBlock; break; } case "style": { descriptor.styles.push(createBlock(node, source) as SFCStyleBlock); break; } default: { break; } } }); return { descriptor }; } function createBlock(node: ElementNode, source: string): SFCBlock { const type = node.tag; let { start, end } = node.loc; start = node.children[0].loc.start; end = node.children[node.children.length - 1].loc.end; const content = source.slice(start.offset, end.offset); const loc = { source: content, start, end }; const block: SFCBlock = { type, content, loc }; return block; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-sfc/rewriteDefault.ts ================================================ import { parse } from "@babel/parser"; import MagicString from "magic-string"; const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/; const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)(?:as)?(\s*)default/s; export function rewriteDefault(input: string, as: string): string { if (!hasDefaultExport(input)) { return input + `\nconst ${as} = {}`; } const s = new MagicString(input); const ast = parse(input, { sourceType: "module", }).program.body; ast.forEach((node) => { if (node.type === "ExportDefaultDeclaration") { if (node.declaration.type === "ClassDeclaration") { s.overwrite(node.start!, node.declaration.id.start!, `class `); s.append(`\nconst ${as} = ${node.declaration.id.name}`); } else { s.overwrite(node.start!, node.declaration.start!, `const ${as} = `); } } if (node.type === "ExportNamedDeclaration") { for (const specifier of node.specifiers) { if ( specifier.type === "ExportSpecifier" && specifier.exported.type === "Identifier" && specifier.exported.name === "default" ) { if (node.source) { if (specifier.local.name === "default") { const end = specifierEnd(input, specifier.local.end!, node.end!); s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = __VUE_DEFAULT__`); continue; } else { const end = specifierEnd(input, specifier.exported.end!, node.end!); s.prepend( `import { ${input.slice( specifier.local.start!, specifier.local.end!, )} } from '${node.source.value}'\n`, ); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); continue; } } const end = specifierEnd(input, specifier.end!, node.end!); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); } } } }); return s.toString(); } export function hasDefaultExport(input: string): boolean { return defaultExportRE.test(input) || namedDefaultExportRE.test(input); } function specifierEnd(input: string, end: number, nodeEnd: number | null) { // export { default , foo } ... let hasCommas = false; let oldEnd = end; while (end < nodeEnd!) { if (/\s/.test(input.charAt(end))) { end++; } else if (input.charAt(end) === ",") { end++; hasCommas = true; break; } else if (input.charAt(end) === "}") { break; } } return hasCommas ? end : oldEnd; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/reactivity/baseHandler.ts ================================================ import { isObject } from "../shared"; import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (isObject(res)) { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export class ReactiveEffect { constructor(public fn: () => T) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { effect.run(); } } } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; setupState: Data; isMounted: boolean; } export type InternalRenderFunction = { (ctx: Data): VNodeChild; }; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately setupState: {}, isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { const setupResult = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; // setupResultの型によって分岐をする if (typeof setupResult === "function") { instance.render = setupResult; } else if (typeof setupResult === "object" && setupResult !== null) { instance.setupState = setupResult; } else { // do nothing } } if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } const { render } = component; if (render) { instance.render = render as InternalRenderFunction; } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function | Record | void; render?: Function; template?: string; }; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { ShapeFlags } from "../shared/shapeFlags"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; remove(child: HostNode): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, setElementText: hostSetElementText, insert: hostInsert, remove: hostRemove, parentNode: hostParentNode, } = options; const patch = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const { type, shapeFlag } = n2; if (type === Text) { processText(n1, n2, container, anchor); } else if (shapeFlag & ShapeFlags.ELEMENT) { processElement(n1, n2, container, anchor); } else if (shapeFlag & ShapeFlags.COMPONENT) { processComponent(n1, n2, container, anchor); } else { // do nothing } }; const processElement = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { mountElement(n2, container, anchor); } else { patchElement(n1, n2, anchor); } }; const mountElement = ( vnode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el, anchor); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = ( children: VNode[], container: RendererElement, anchor: RendererElement | null, ) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container, anchor); } }; const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el, anchor); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = ( n1: VNode, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const c1 = n1 && n1.children; const prevShapeFlag = n1 ? n1.shapeFlag : 0; const c2 = n2.children; const { shapeFlag } = n2; if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { unmountChildren(c1 as VNode[]); } if (c2 !== c1) { hostSetElementText(container, c2 as string); } } else { if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor); } else { unmountChildren(c1 as VNode[]); } } else { if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { hostSetElementText(container, ""); } if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren(c2 as VNode[], container, anchor); } } } }; const patchKeyedChildren = ( c1: VNode[], c2: VNode[], container: RendererElement, parentAnchor: RendererElement | null, ) => { let i = 0; const l2 = c2.length; const e1 = c1.length - 1; const e2 = l2 - 1; const keyToNewIndexMap: Map = new Map(); for (i = 0; i <= e2; i++) { const nextChild = (c2[i] = normalizeVNode(c2[i])); if (nextChild.key != null) { keyToNewIndexMap.set(nextChild.key, i); } } let j; let patched = 0; const toBePatched = e2 + 1; let moved = false; let maxNewIndexSoFar = 0; const newIndexToOldIndexMap = new Array(toBePatched); for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0; for (i = 0; i <= e1; i++) { const prevChild = c1[i]; if (patched >= toBePatched) { // all new children have been patched so this can only be a removal unmount(prevChild); continue; } let newIndex; if (prevChild.key != null) { newIndex = keyToNewIndexMap.get(prevChild.key); } else { // key-less node, try to locate a key-less node of the same type for (j = 0; j <= e2; j++) { if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) { newIndex = j; break; } } } if (newIndex === undefined) { unmount(prevChild); } else { newIndexToOldIndexMap[newIndex] = i + 1; if (newIndex >= maxNewIndexSoFar) { maxNewIndexSoFar = newIndex; } else { moved = true; } patch(prevChild, c2[newIndex] as VNode, container, null); patched++; } } const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; j = increasingNewIndexSequence.length - 1; for (i = toBePatched - 1; i >= 0; i--) { const nextIndex = i; const nextChild = c2[nextIndex] as VNode; const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor; if (newIndexToOldIndexMap[i] === 0) { // mount new patch(null, nextChild, container, anchor); } else if (moved) { // move if: // There is no stable subsequence (e.g. a reverse) // OR current node is not among the stable sequence if (j < 0 || i !== increasingNewIndexSequence[j]) { move(nextChild, container, anchor); } else { j--; } } } }; const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => { const { el, shapeFlag } = vnode; if (shapeFlag & ShapeFlags.COMPONENT) { move(vnode.component!.subTree, container, anchor); return; } hostInsert(el!, container, anchor); }; const unmount = (vnode: VNode) => { const { children, shapeFlag } = vnode; if (shapeFlag & ShapeFlags.COMPONENT) { unmountComponent(vnode.component!); } else if (Array.isArray(children)) { unmountChildren(children as VNode[]); } remove(vnode); }; const remove = (vnode: VNode) => { const { el } = vnode; hostRemove(el!); }; const unmountComponent = (instance: ComponentInternalInstance) => { const { subTree } = instance; unmount(subTree); }; const unmountChildren = (children: VNode[]) => { for (let i = 0; i < children.length; i++) { unmount(children[i]); } }; const processText = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { mountComponent(n2, container, anchor); } else { updateComponent(n1, n2); } }; const mountComponent = ( initialVNode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container, anchor); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const componentUpdateFn = () => { const { render, setupState } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render(setupState))); patch(null, subTree, container, anchor); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render(setupState)); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn)); const update = (instance.update = () => effect.run()); update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container, null); }; return { render }; } // https://en.wikipedia.org/wiki/Longest_increasing_subsequence function getSequence(arr: number[]): number[] { const p = arr.slice(); const result = [0]; let i, j, u, v, c; const len = arr.length; for (i = 0; i < len; i++) { const arrI = arr[i]; if (arrI !== 0) { j = result[result.length - 1]; if (arr[j] < arrI) { p[i] = j; result.push(i); continue; } u = 0; v = result.length - 1; while (u < v) { c = (u + v) >> 1; if (arr[result[c]] < arrI) { u = c + 1; } else { v = c; } } if (arrI < arr[result[u]]) { if (u > 0) { p[i] = result[u - 1]; } result[u] = i; } } } u = result.length; v = result[u - 1]; while (u-- > 0) { result[u] = v; v = p[v]; } return result; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/vnode.ts ================================================ import { isObject, isString } from "../shared"; import { ShapeFlags } from "../shared/shapeFlags"; import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; key: string | number | symbol | null; component: ComponentInternalInstance | null; shapeFlag: number; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0; const vnode: VNode = { type, props, children: children, el: undefined, key: props?.key ?? null, component: null, shapeFlag, }; normalizeChildren(vnode, children); return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } export function normalizeChildren(vnode: VNode, children: unknown) { let type = 0; if (children == null) { children = null; } else if (Array.isArray(children)) { type = ShapeFlags.ARRAY_CHILDREN; } else { children = String(children); type = ShapeFlags.TEXT_CHILDREN; } vnode.children = children as VNodeNormalizedChildren; vnode.shapeFlag |= type; } export function isSameVNodeType(n1: VNode, n2: VNode): boolean { return n1.type === n2.type && n1.key === n2.key; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, remove: (child) => { const parent = child.parentNode; if (parent) { parent.removeChild(child); } }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/shared/general.ts ================================================ export const isString = (val: unknown): val is string => typeof val === "string"; export const isObject = (val: unknown): val is Record => val !== null && typeof val === "object"; const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/packages/shared/shapeFlags.ts ================================================ export const enum ShapeFlags { ELEMENT = 1, COMPONENT = 1 << 2, TEXT_CHILDREN = 1 << 3, ARRAY_CHILDREN = 1 << 4, } ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createApp, h, reactive } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("20_basic_virtual_dom/020_bit_flags", () => { it("should render and update with bit flags optimization", async () => { const state = reactive({ count: 0 }); const app = createApp({ setup() { return () => h("div", {}, [`count: ${state.count}`]); }, }); app.mount("#host"); expect(host.innerHTML).toBe("
count: 0
"); state.count++; await Promise.resolve(); expect(host.innerHTML).toBe("
count: 1
"); }); }); ================================================ FILE: book/impls/20_basic_virtual_dom/020_bit_flags/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/src/main.ts ================================================ import { createApp, h, reactive } from "chibivue"; const app = createApp({ setup() { const state = reactive({ message: "Hello World", }); const updateList = () => { state.message = "Hello ChibiVue!"; state.message = "Hello ChibiVue!!"; state.message = "Hello ChibiVue!!"; state.message = "Hello ChibiVue!!"; state.message = "Hello ChibiVue!!"; state.message = "Hello ChibiVue!! last"; }; return () => { console.log("😎 rendered!"); return h("div", { id: "app" }, [ h("p", {}, [`message: ${state.message}`]), h("button", { onClick: updateList }, ["update"]), ]); }; }, }); app.mount("#app"); ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; import chibivue from "../../packages/@extensions/vite-plugin-chibivue"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, plugins: [chibivue()], }); ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" }, "dependencies": { "@babel/parser": "^7.28.6", "magic-string": "^0.30.21" } } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/@extensions/vite-plugin-chibivue/index.ts ================================================ import fs from "node:fs"; import type { Plugin } from "vite"; import { createFilter } from "vite"; import { parse, rewriteDefault } from "../../compiler-sfc"; import { compile } from "../../compiler-dom"; export default function vitePluginChibivue(): Plugin { const filter = createFilter(/\.vue$/); return { name: "vite:chibivue", resolveId(id) { if (id.match(/\.vue\.css$/)) return id; }, load(id) { if (id.match(/\.vue\.css$/)) { const filename = id.replace(/\.css$/, ""); const content = fs.readFileSync(filename, "utf-8"); const { descriptor } = parse(content, { filename }); const styles = descriptor.styles.map((it) => it.content).join("\n"); return { code: styles }; } }, transform(code, id) { if (!filter(id)) return; const outputs = []; outputs.push("import * as ChibiVue from 'chibivue'"); outputs.push(`import '${id}.css'`); const { descriptor } = parse(code, { filename: id }); const SFC_MAIN = "_sfc_main"; const scriptCode = rewriteDefault(descriptor.script?.content ?? "", SFC_MAIN); outputs.push(scriptCode); const templateCode = compile(descriptor.template?.content ?? "", { isBrowser: false, }); outputs.push(templateCode); outputs.push("\n"); outputs.push(`export default { ...${SFC_MAIN}, render }`); return { code: outputs.join("\n") }; }, }; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, INTERPOLATION, ATTRIBUTE, DIRECTIVE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode | InterpolationNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE; name: string; exp: string; arg: string; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } export interface InterpolationNode extends Node { type: NodeTypes.INTERPOLATION; content: string; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/codegen.ts ================================================ import { toHandlerKey } from "../shared"; import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type TemplateChildNode, type TextNode, } from "./ast"; import type { CompilerOptions } from "./options"; export const generate = ( { children, }: { children: TemplateChildNode[]; }, option: Required, ): string => { return `${option.isBrowser ? "return " : ""}function render(_ctx) { ${option.isBrowser ? "with (_ctx) {" : ""} const { h } = ChibiVue; return ${genNode(children[0], option)}; ${option.isBrowser ? "}" : ""} }`; }; const genNode = (node: TemplateChildNode, option: Required): string => { switch (node.type) { case NodeTypes.ELEMENT: return genElement(node, option); case NodeTypes.TEXT: return genText(node); case NodeTypes.INTERPOLATION: return genInterpolation(node, option); default: return ""; } }; const genElement = (el: ElementNode, option: Required): string => { return `h("${el.tag}", {${el.props .map((prop) => genProp(prop, option)) .join(", ")}}, [${el.children.map((it) => genNode(it, option)).join(", ")}])`; }; const genProp = ( prop: AttributeNode | DirectiveNode, option: Required, ): string => { switch (prop.type) { case NodeTypes.ATTRIBUTE: return `${prop.name}: "${prop.value?.content}"`; case NodeTypes.DIRECTIVE: { switch (prop.name) { case "on": return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? "" : "_ctx."}${prop.exp}`; default: // TODO: other directives throw new Error(`unexpected directive name. got "${prop.name}"`); } } default: throw new Error(`unexpected prop type.`); } }; const genText = (text: TextNode): string => { return `\`${text.content}\``; }; const genInterpolation = (node: InterpolationNode, option: Required): string => { return `${option.isBrowser ? "" : "_ctx."}${node.content}`; }; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import type { CompilerOptions } from "./options"; import { baseParse } from "./parse"; export function baseCompile(template: string, option: Required) { const parseResult = baseParse(template.trim()); const code = generate(parseResult, option); return code; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; export * from "./ast"; export * from "./options"; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/options.ts ================================================ export type CompilerOptions = { isBrowser?: boolean; }; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/parse.ts ================================================ import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type Position, type SourceLocation, type TemplateChildNode, type TextNode, } from "./ast"; export interface ParserContext { readonly originalSource: string; source: string; offset: number; line: number; column: number; } function createParserContext(content: string): ParserContext { return { originalSource: content, source: content, column: 1, line: 1, offset: 0, }; } export const baseParse = (content: string): { children: TemplateChildNode[] } => { const context = createParserContext(content); const children = parseChildren(context, []); return { children: children }; }; function parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] { const nodes: TemplateChildNode[] = []; while (!isEnd(context, ancestors)) { const s = context.source; let node: TemplateChildNode | undefined = undefined; if (startsWith(s, "{{")) { node = parseInterpolation(context); } else if (s[0] === "<") { if (/[a-z]/i.test(s[1])) { node = parseElement(context, ancestors); } } if (!node) { node = parseText(context); } pushNode(nodes, node); } return nodes; } function advanceBy(context: ParserContext, numberOfCharacters: number): void { const { source } = context; advancePositionWithMutation(context, source, numberOfCharacters); context.source = source.slice(numberOfCharacters); } function advancePositionWithMutation( pos: Position, source: string, numberOfCharacters: number = source.length, ): Position { let linesCount = 0; let lastNewLinePos = -1; for (let i = 0; i < numberOfCharacters; i++) { if (source.charCodeAt(i) === 10 /* newline char code */) { linesCount++; lastNewLinePos = i; } } pos.offset += numberOfCharacters; pos.line += linesCount; pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; return pos; } function isEnd(context: ParserContext, ancestors: ElementNode[]): boolean { const s = context.source; if (startsWith(s, "= 0; --i) { if (startsWithEndTagOpen(s, ancestors[i].tag)) { return true; } } } return !s; } function startsWith(source: string, searchString: string): boolean { return source.startsWith(searchString); } function advanceSpaces(context: ParserContext): void { const match = /^[\t\r\n\f ]+/.exec(context.source); if (match) { advanceBy(context, match[0].length); } } function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void { if (node.type === NodeTypes.TEXT) { const prev = last(nodes); if (prev && prev.type === NodeTypes.TEXT) { prev.content += node.content; return; } } nodes.push(node); } function parseInterpolation(context: ParserContext): InterpolationNode | undefined { const [open, close] = ["{{", "}}"]; const closeIndex = context.source.indexOf(close, open.length); if (closeIndex === -1) return undefined; const start = getCursor(context); advanceBy(context, open.length); const innerStart = getCursor(context); const innerEnd = getCursor(context); const rawContentLength = closeIndex - open.length; const rawContent = context.source.slice(0, rawContentLength); const preTrimContent = parseTextData(context, rawContentLength); const content = preTrimContent.trim(); const startOffset = preTrimContent.indexOf(content); if (startOffset > 0) { advancePositionWithMutation(innerStart, rawContent, startOffset); } const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset); advancePositionWithMutation(innerEnd, rawContent, endOffset); advanceBy(context, close.length); return { type: NodeTypes.INTERPOLATION, content, loc: getSelection(context, start), }; } function parseText(context: ParserContext): TextNode { const endTokens = ["<", "{{"]; let endIndex = context.source.length; for (let i = 0; i < endTokens.length; i++) { const index = context.source.indexOf(endTokens[i], 1); if (index !== -1 && endIndex > index) { endIndex = index; } } const start = getCursor(context); const content = parseTextData(context, endIndex); return { type: NodeTypes.TEXT, content, loc: getSelection(context, start), }; } const enum TagType { Start, End, } function parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined { // Start tag. const element = parseTag(context, TagType.Start); // TODO: if (element.isSelfClosing) { return element; } // Children. ancestors.push(element); const children = parseChildren(context, ancestors); ancestors.pop(); element.children = children; // End tag. if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(context, TagType.End); // TODO: } return element; } function parseTag(context: ParserContext, type: TagType): ElementNode { // Tag open. const start = getCursor(context); const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!; const tag = match[1]; advanceBy(context, match[0].length); advanceSpaces(context); // Attributes. let props = parseAttributes(context, type); // Tag close. let isSelfClosing = false; isSelfClosing = startsWith(context.source, "/>"); advanceBy(context, isSelfClosing ? 2 : 1); return { type: NodeTypes.ELEMENT, tag, props, children: [], isSelfClosing, loc: getSelection(context, start), }; } function parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] { const props = []; const attributeNames = new Set(); while ( context.source.length > 0 && !startsWith(context.source, ">") && !startsWith(context.source, "/>") ) { const attr = parseAttribute(context, attributeNames); if (type === TagType.Start) { props.push(attr); } advanceSpaces(context); } return props; } type AttributeValue = | { content: string; loc: SourceLocation; } | undefined; function parseAttribute( context: ParserContext, nameSet: Set, ): AttributeNode | DirectiveNode { // Name. const start = getCursor(context); const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!; const name = match[0]; nameSet.add(name); advanceBy(context, name.length); // Value let value: AttributeValue = undefined; if (/^[\t\r\n\f ]*=/.test(context.source)) { advanceSpaces(context); advanceBy(context, 1); advanceSpaces(context); value = parseAttributeValue(context); } // directive const loc = getSelection(context, start); if (/^(v-[A-Za-z0-9-]|@)/.test(name)) { const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(name)!; let dirName = match[1] || (startsWith(name, "@") ? "on" : ""); let arg = ""; if (match[2]) arg = match[2]; return { type: NodeTypes.DIRECTIVE, name: dirName, exp: value?.content ?? "", loc, arg, }; } return { type: NodeTypes.ATTRIBUTE, name, value: value && { type: NodeTypes.TEXT, content: value.content, loc: value.loc, }, loc, }; } function parseAttributeValue(context: ParserContext): AttributeValue { const start = getCursor(context); let content: string; const quote = context.source[0]; const isQuoted = quote === `"` || quote === `'`; if (isQuoted) { // Quoted value. advanceBy(context, 1); const endIndex = context.source.indexOf(quote); if (endIndex === -1) { content = parseTextData(context, context.source.length); } else { content = parseTextData(context, endIndex); advanceBy(context, 1); } } else { // Unquoted const match = /^[^\t\r\n\f >]+/.exec(context.source); if (!match) { return undefined; } content = parseTextData(context, match[0].length); } return { content, loc: getSelection(context, start) }; } function parseTextData(context: ParserContext, length: number): string { const rawText = context.source.slice(0, length); advanceBy(context, length); return rawText; } function getCursor(context: ParserContext): Position { const { column, line, offset } = context; return { column, line, offset }; } function getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation { end = end || getCursor(context); return { start, end, source: context.originalSource.slice(start.offset, end.offset), }; } function last(xs: T[]): T | undefined { return xs[xs.length - 1]; } function startsWithEndTagOpen(source: string, tag: string): boolean { return ( startsWith(source, "]/.test(source[2 + tag.length] || ">") ); } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-dom/index.ts ================================================ import { type CompilerOptions, baseCompile, baseParse } from "../compiler-core"; export function compile(template: string, option?: CompilerOptions) { const defaultOption: Required = { isBrowser: true }; if (option) Object.assign(defaultOption, option); return baseCompile(template, defaultOption); } export function parse(template: string) { return baseParse(template); } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-sfc/compileTemplate.ts ================================================ import type { TemplateChildNode } from "../compiler-core"; export interface TemplateCompiler { compile(template: string): string; parse(template: string): { children: TemplateChildNode[] }; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-sfc/index.ts ================================================ export * from "./parse"; export * from "./rewriteDefault"; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-sfc/parse.ts ================================================ import { type ElementNode, NodeTypes, type SourceLocation } from "../compiler-core"; import * as CompilerDOM from "../compiler-dom"; import type { TemplateCompiler } from "./compileTemplate"; export interface SFCDescriptor { id: string; filename: string; source: string; template: SFCTemplateBlock | null; script: SFCScriptBlock | null; styles: SFCStyleBlock[]; } export interface SFCBlock { type: string; content: string; loc: SourceLocation; } export interface SFCTemplateBlock extends SFCBlock { type: "template"; } export interface SFCScriptBlock extends SFCBlock { type: "script"; } export declare interface SFCStyleBlock extends SFCBlock { type: "style"; } export interface SFCParseOptions { filename?: string; sourceRoot?: string; compiler?: TemplateCompiler; } export interface SFCParseResult { descriptor: SFCDescriptor; } export const DEFAULT_FILENAME = "anonymous.vue"; export function parse( source: string, { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {}, ): SFCParseResult { const descriptor: SFCDescriptor = { id: undefined!, filename, source, template: null, script: null, styles: [], }; const ast = compiler.parse(source); ast.children.forEach((node) => { if (node.type !== NodeTypes.ELEMENT) return; switch (node.tag) { case "template": { descriptor.template = createBlock(node, source) as SFCTemplateBlock; break; } case "script": { const scriptBlock = createBlock(node, source) as SFCScriptBlock; descriptor.script = scriptBlock; break; } case "style": { descriptor.styles.push(createBlock(node, source) as SFCStyleBlock); break; } default: { break; } } }); return { descriptor }; } function createBlock(node: ElementNode, source: string): SFCBlock { const type = node.tag; let { start, end } = node.loc; start = node.children[0].loc.start; end = node.children[node.children.length - 1].loc.end; const content = source.slice(start.offset, end.offset); const loc = { source: content, start, end }; const block: SFCBlock = { type, content, loc }; return block; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-sfc/rewriteDefault.ts ================================================ import { parse } from "@babel/parser"; import MagicString from "magic-string"; const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/; const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)(?:as)?(\s*)default/s; export function rewriteDefault(input: string, as: string): string { if (!hasDefaultExport(input)) { return input + `\nconst ${as} = {}`; } const s = new MagicString(input); const ast = parse(input, { sourceType: "module", }).program.body; ast.forEach((node) => { if (node.type === "ExportDefaultDeclaration") { if (node.declaration.type === "ClassDeclaration") { s.overwrite(node.start!, node.declaration.id.start!, `class `); s.append(`\nconst ${as} = ${node.declaration.id.name}`); } else { s.overwrite(node.start!, node.declaration.start!, `const ${as} = `); } } if (node.type === "ExportNamedDeclaration") { for (const specifier of node.specifiers) { if ( specifier.type === "ExportSpecifier" && specifier.exported.type === "Identifier" && specifier.exported.name === "default" ) { if (node.source) { if (specifier.local.name === "default") { const end = specifierEnd(input, specifier.local.end!, node.end!); s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = __VUE_DEFAULT__`); continue; } else { const end = specifierEnd(input, specifier.exported.end!, node.end!); s.prepend( `import { ${input.slice( specifier.local.start!, specifier.local.end!, )} } from '${node.source.value}'\n`, ); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); continue; } } const end = specifierEnd(input, specifier.end!, node.end!); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); } } } }); return s.toString(); } export function hasDefaultExport(input: string): boolean { return defaultExportRE.test(input) || namedDefaultExportRE.test(input); } function specifierEnd(input: string, end: number, nodeEnd: number | null) { // export { default , foo } ... let hasCommas = false; let oldEnd = end; while (end < nodeEnd!) { if (/\s/.test(input.charAt(end))) { end++; } else if (input.charAt(end) === ",") { end++; hasCommas = true; break; } else if (input.charAt(end) === "}") { break; } } return hasCommas ? end : oldEnd; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/reactivity/baseHandler.ts ================================================ import { isObject } from "../shared"; import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (isObject(res)) { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export type EffectScheduler = (...args: any[]) => any; export class ReactiveEffect { constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, ) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { if (effect.scheduler) { effect.scheduler(); } else { effect.run(); } } } } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { uid: number; type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; setupState: Data; isMounted: boolean; } export type InternalRenderFunction = { (ctx: Data): VNodeChild; }; let uid = 0; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { uid: uid++, type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately setupState: {}, isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { const setupResult = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; // setupResultの型によって分岐をする if (typeof setupResult === "function") { instance.render = setupResult; } else if (typeof setupResult === "object" && setupResult !== null) { instance.setupState = setupResult; } else { // do nothing } } if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } const { render } = component; if (render) { instance.render = render as InternalRenderFunction; } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function | Record | void; render?: Function; template?: string; }; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { ShapeFlags } from "../shared/shapeFlags"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { type SchedulerJob, queueJob } from "./scheduler"; import { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; remove(child: HostNode): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, setElementText: hostSetElementText, insert: hostInsert, remove: hostRemove, parentNode: hostParentNode, } = options; const patch = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const { type, shapeFlag } = n2; if (type === Text) { processText(n1, n2, container, anchor); } else if (shapeFlag & ShapeFlags.ELEMENT) { processElement(n1, n2, container, anchor); } else if (shapeFlag & ShapeFlags.COMPONENT) { processComponent(n1, n2, container, anchor); } else { // do nothing } }; const processElement = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { mountElement(n2, container, anchor); } else { patchElement(n1, n2, anchor); } }; const mountElement = ( vnode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el, anchor); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = ( children: VNode[], container: RendererElement, anchor: RendererElement | null, ) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container, anchor); } }; const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el, anchor); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = ( n1: VNode, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const c1 = n1 && n1.children; const prevShapeFlag = n1 ? n1.shapeFlag : 0; const c2 = n2.children; const { shapeFlag } = n2; if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { unmountChildren(c1 as VNode[]); } if (c2 !== c1) { hostSetElementText(container, c2 as string); } } else { if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor); } else { unmountChildren(c1 as VNode[]); } } else { if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { hostSetElementText(container, ""); } if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren(c2 as VNode[], container, anchor); } } } }; const patchKeyedChildren = ( c1: VNode[], c2: VNode[], container: RendererElement, parentAnchor: RendererElement | null, ) => { let i = 0; const l2 = c2.length; const e1 = c1.length - 1; const e2 = l2 - 1; const keyToNewIndexMap: Map = new Map(); for (i = 0; i <= e2; i++) { const nextChild = (c2[i] = normalizeVNode(c2[i])); if (nextChild.key != null) { keyToNewIndexMap.set(nextChild.key, i); } } let j; let patched = 0; const toBePatched = e2 + 1; let moved = false; let maxNewIndexSoFar = 0; const newIndexToOldIndexMap = new Array(toBePatched); for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0; for (i = 0; i <= e1; i++) { const prevChild = c1[i]; if (patched >= toBePatched) { // all new children have been patched so this can only be a removal unmount(prevChild); continue; } let newIndex; if (prevChild.key != null) { newIndex = keyToNewIndexMap.get(prevChild.key); } else { // key-less node, try to locate a key-less node of the same type for (j = 0; j <= e2; j++) { if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) { newIndex = j; break; } } } if (newIndex === undefined) { unmount(prevChild); } else { newIndexToOldIndexMap[newIndex] = i + 1; if (newIndex >= maxNewIndexSoFar) { maxNewIndexSoFar = newIndex; } else { moved = true; } patch(prevChild, c2[newIndex] as VNode, container, null); patched++; } } const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; j = increasingNewIndexSequence.length - 1; for (i = toBePatched - 1; i >= 0; i--) { const nextIndex = i; const nextChild = c2[nextIndex] as VNode; const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor; if (newIndexToOldIndexMap[i] === 0) { // mount new patch(null, nextChild, container, anchor); } else if (moved) { // move if: // There is no stable subsequence (e.g. a reverse) // OR current node is not among the stable sequence if (j < 0 || i !== increasingNewIndexSequence[j]) { move(nextChild, container, anchor); } else { j--; } } } }; const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => { const { el, shapeFlag } = vnode; if (shapeFlag & ShapeFlags.COMPONENT) { move(vnode.component!.subTree, container, anchor); return; } hostInsert(el!, container, anchor); }; const unmount = (vnode: VNode) => { const { children, shapeFlag } = vnode; if (shapeFlag & ShapeFlags.COMPONENT) { unmountComponent(vnode.component!); } else if (Array.isArray(children)) { unmountChildren(children as VNode[]); } remove(vnode); }; const remove = (vnode: VNode) => { const { el } = vnode; hostRemove(el!); }; const unmountComponent = (instance: ComponentInternalInstance) => { const { subTree } = instance; unmount(subTree); }; const unmountChildren = (children: VNode[]) => { for (let i = 0; i < children.length; i++) { unmount(children[i]); } }; const processText = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { mountComponent(n2, container, anchor); } else { updateComponent(n1, n2); } }; const mountComponent = ( initialVNode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container, anchor); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const componentUpdateFn = () => { const { render, setupState } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render(setupState))); patch(null, subTree, container, anchor); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render(setupState)); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () => queueJob(update), )); const update: SchedulerJob = (instance.update = () => effect.run()); update.id = instance.uid; update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container, null); }; return { render }; } // https://en.wikipedia.org/wiki/Longest_increasing_subsequence function getSequence(arr: number[]): number[] { const p = arr.slice(); const result = [0]; let i, j, u, v, c; const len = arr.length; for (i = 0; i < len; i++) { const arrI = arr[i]; if (arrI !== 0) { j = result[result.length - 1]; if (arr[j] < arrI) { p[i] = j; result.push(i); continue; } u = 0; v = result.length - 1; while (u < v) { c = (u + v) >> 1; if (arr[result[c]] < arrI) { u = c + 1; } else { v = c; } } if (arrI < arr[result[u]]) { if (u > 0) { p[i] = result[u - 1]; } result[u] = i; } } } u = result.length; v = result[u - 1]; while (u-- > 0) { result[u] = v; v = p[v]; } return result; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/scheduler.ts ================================================ export interface SchedulerJob extends Function { id?: number; } const queue: SchedulerJob[] = []; let flushIndex = 0; let isFlushing = false; let isFlushPending = false; const resolvedPromise = Promise.resolve() as Promise; export function queueJob(job: SchedulerJob) { if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) { if (job.id == null) { queue.push(job); } else { queue.splice(findInsertionIndex(job.id), 0, job); } queueFlush(); } } function queueFlush() { if (!isFlushing && !isFlushPending) { isFlushPending = true; resolvedPromise.then(() => { isFlushPending = false; isFlushing = true; queue.forEach((job) => { job(); }); flushIndex = 0; queue.length = 0; isFlushing = false; }); } } function findInsertionIndex(id: number) { let start = flushIndex + 1; let end = queue.length; while (start < end) { const middle = (start + end) >>> 1; const middleJobId = getId(queue[middle]); middleJobId < id ? (start = middle + 1) : (end = middle); } return start; } const getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id); ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/vnode.ts ================================================ import { isObject, isString } from "../shared"; import { ShapeFlags } from "../shared/shapeFlags"; import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; key: string | number | symbol | null; component: ComponentInternalInstance | null; shapeFlag: number; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0; const vnode: VNode = { type, props, children: children, el: undefined, key: props?.key ?? null, component: null, shapeFlag, }; normalizeChildren(vnode, children); return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } export function normalizeChildren(vnode: VNode, children: unknown) { let type = 0; if (children == null) { children = null; } else if (Array.isArray(children)) { type = ShapeFlags.ARRAY_CHILDREN; } else { children = String(children); type = ShapeFlags.TEXT_CHILDREN; } vnode.children = children as VNodeNormalizedChildren; vnode.shapeFlag |= type; } export function isSameVNodeType(n1: VNode, n2: VNode): boolean { return n1.type === n2.type && n1.key === n2.key; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, remove: (child) => { const parent = child.parentNode; if (parent) { parent.removeChild(child); } }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/shared/general.ts ================================================ export const isString = (val: unknown): val is string => typeof val === "string"; export const isObject = (val: unknown): val is Record => val !== null && typeof val === "object"; const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/packages/shared/shapeFlags.ts ================================================ export const enum ShapeFlags { ELEMENT = 1, COMPONENT = 1 << 2, TEXT_CHILDREN = 1 << 3, ARRAY_CHILDREN = 1 << 4, } ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createApp, h, reactive } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("20_basic_virtual_dom/040_scheduler", () => { it("should batch multiple updates in scheduler", async () => { const state = reactive({ count: 0 }); const app = createApp({ setup() { return () => h("div", {}, [`count: ${state.count}`]); }, }); app.mount("#host"); expect(host.innerHTML).toBe("
count: 0
"); // Multiple synchronous updates should be batched state.count++; state.count++; state.count++; // DOM should not update synchronously expect(host.innerHTML).toBe("
count: 0
"); // Wait for scheduler to flush await Promise.resolve(); expect(host.innerHTML).toBe("
count: 3
"); }); }); ================================================ FILE: book/impls/20_basic_virtual_dom/040_scheduler/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/src/main.ts ================================================ import { createApp, h, nextTick, reactive } from "chibivue"; const app = createApp({ setup() { const state = reactive({ count: 0, }); const updateState = async () => { state.count++; await nextTick(); const p = document.getElementById("count-p"); if (p) { console.log("😎 p.textContent", p.textContent); } }; return () => { return h("div", { id: "app" }, [ h("p", { id: "count-p" }, [`${state.count}`]), h("button", { onClick: updateState }, ["update"]), ]); }; }, }); app.mount("#app"); ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; import chibivue from "../../packages/@extensions/vite-plugin-chibivue"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, plugins: [chibivue()], }); ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" }, "dependencies": { "@babel/parser": "^7.28.6", "magic-string": "^0.30.21" } } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/@extensions/vite-plugin-chibivue/index.ts ================================================ import fs from "node:fs"; import type { Plugin } from "vite"; import { createFilter } from "vite"; import { parse, rewriteDefault } from "../../compiler-sfc"; import { compile } from "../../compiler-dom"; export default function vitePluginChibivue(): Plugin { const filter = createFilter(/\.vue$/); return { name: "vite:chibivue", resolveId(id) { if (id.match(/\.vue\.css$/)) return id; }, load(id) { if (id.match(/\.vue\.css$/)) { const filename = id.replace(/\.css$/, ""); const content = fs.readFileSync(filename, "utf-8"); const { descriptor } = parse(content, { filename }); const styles = descriptor.styles.map((it) => it.content).join("\n"); return { code: styles }; } }, transform(code, id) { if (!filter(id)) return; const outputs = []; outputs.push("import * as ChibiVue from 'chibivue'"); outputs.push(`import '${id}.css'`); const { descriptor } = parse(code, { filename: id }); const SFC_MAIN = "_sfc_main"; const scriptCode = rewriteDefault(descriptor.script?.content ?? "", SFC_MAIN); outputs.push(scriptCode); const templateCode = compile(descriptor.template?.content ?? "", { isBrowser: false, }); outputs.push(templateCode); outputs.push("\n"); outputs.push(`export default { ...${SFC_MAIN}, render }`); return { code: outputs.join("\n") }; }, }; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, INTERPOLATION, ATTRIBUTE, DIRECTIVE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode | InterpolationNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE; name: string; exp: string; arg: string; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } export interface InterpolationNode extends Node { type: NodeTypes.INTERPOLATION; content: string; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/codegen.ts ================================================ import { toHandlerKey } from "../shared"; import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type TemplateChildNode, type TextNode, } from "./ast"; import type { CompilerOptions } from "./options"; export const generate = ( { children, }: { children: TemplateChildNode[]; }, option: Required, ): string => { return `${option.isBrowser ? "return " : ""}function render(_ctx) { ${option.isBrowser ? "with (_ctx) {" : ""} const { h } = ChibiVue; return ${genNode(children[0], option)}; ${option.isBrowser ? "}" : ""} }`; }; const genNode = (node: TemplateChildNode, option: Required): string => { switch (node.type) { case NodeTypes.ELEMENT: return genElement(node, option); case NodeTypes.TEXT: return genText(node); case NodeTypes.INTERPOLATION: return genInterpolation(node, option); default: return ""; } }; const genElement = (el: ElementNode, option: Required): string => { return `h("${el.tag}", {${el.props .map((prop) => genProp(prop, option)) .join(", ")}}, [${el.children.map((it) => genNode(it, option)).join(", ")}])`; }; const genProp = ( prop: AttributeNode | DirectiveNode, option: Required, ): string => { switch (prop.type) { case NodeTypes.ATTRIBUTE: return `${prop.name}: "${prop.value?.content}"`; case NodeTypes.DIRECTIVE: { switch (prop.name) { case "on": return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? "" : "_ctx."}${prop.exp}`; default: // TODO: other directives throw new Error(`unexpected directive name. got "${prop.name}"`); } } default: throw new Error(`unexpected prop type.`); } }; const genText = (text: TextNode): string => { return `\`${text.content}\``; }; const genInterpolation = (node: InterpolationNode, option: Required): string => { return `${option.isBrowser ? "" : "_ctx."}${node.content}`; }; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import type { CompilerOptions } from "./options"; import { baseParse } from "./parse"; export function baseCompile(template: string, option: Required) { const parseResult = baseParse(template.trim()); const code = generate(parseResult, option); return code; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; export * from "./ast"; export * from "./options"; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/options.ts ================================================ export type CompilerOptions = { isBrowser?: boolean; }; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/parse.ts ================================================ import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type Position, type SourceLocation, type TemplateChildNode, type TextNode, } from "./ast"; export interface ParserContext { readonly originalSource: string; source: string; offset: number; line: number; column: number; } function createParserContext(content: string): ParserContext { return { originalSource: content, source: content, column: 1, line: 1, offset: 0, }; } export const baseParse = (content: string): { children: TemplateChildNode[] } => { const context = createParserContext(content); const children = parseChildren(context, []); return { children: children }; }; function parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] { const nodes: TemplateChildNode[] = []; while (!isEnd(context, ancestors)) { const s = context.source; let node: TemplateChildNode | undefined = undefined; if (startsWith(s, "{{")) { node = parseInterpolation(context); } else if (s[0] === "<") { if (/[a-z]/i.test(s[1])) { node = parseElement(context, ancestors); } } if (!node) { node = parseText(context); } pushNode(nodes, node); } return nodes; } function advanceBy(context: ParserContext, numberOfCharacters: number): void { const { source } = context; advancePositionWithMutation(context, source, numberOfCharacters); context.source = source.slice(numberOfCharacters); } function advancePositionWithMutation( pos: Position, source: string, numberOfCharacters: number = source.length, ): Position { let linesCount = 0; let lastNewLinePos = -1; for (let i = 0; i < numberOfCharacters; i++) { if (source.charCodeAt(i) === 10 /* newline char code */) { linesCount++; lastNewLinePos = i; } } pos.offset += numberOfCharacters; pos.line += linesCount; pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; return pos; } function isEnd(context: ParserContext, ancestors: ElementNode[]): boolean { const s = context.source; if (startsWith(s, "= 0; --i) { if (startsWithEndTagOpen(s, ancestors[i].tag)) { return true; } } } return !s; } function startsWith(source: string, searchString: string): boolean { return source.startsWith(searchString); } function advanceSpaces(context: ParserContext): void { const match = /^[\t\r\n\f ]+/.exec(context.source); if (match) { advanceBy(context, match[0].length); } } function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void { if (node.type === NodeTypes.TEXT) { const prev = last(nodes); if (prev && prev.type === NodeTypes.TEXT) { prev.content += node.content; return; } } nodes.push(node); } function parseInterpolation(context: ParserContext): InterpolationNode | undefined { const [open, close] = ["{{", "}}"]; const closeIndex = context.source.indexOf(close, open.length); if (closeIndex === -1) return undefined; const start = getCursor(context); advanceBy(context, open.length); const innerStart = getCursor(context); const innerEnd = getCursor(context); const rawContentLength = closeIndex - open.length; const rawContent = context.source.slice(0, rawContentLength); const preTrimContent = parseTextData(context, rawContentLength); const content = preTrimContent.trim(); const startOffset = preTrimContent.indexOf(content); if (startOffset > 0) { advancePositionWithMutation(innerStart, rawContent, startOffset); } const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset); advancePositionWithMutation(innerEnd, rawContent, endOffset); advanceBy(context, close.length); return { type: NodeTypes.INTERPOLATION, content, loc: getSelection(context, start), }; } function parseText(context: ParserContext): TextNode { const endTokens = ["<", "{{"]; let endIndex = context.source.length; for (let i = 0; i < endTokens.length; i++) { const index = context.source.indexOf(endTokens[i], 1); if (index !== -1 && endIndex > index) { endIndex = index; } } const start = getCursor(context); const content = parseTextData(context, endIndex); return { type: NodeTypes.TEXT, content, loc: getSelection(context, start), }; } const enum TagType { Start, End, } function parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined { // Start tag. const element = parseTag(context, TagType.Start); // TODO: if (element.isSelfClosing) { return element; } // Children. ancestors.push(element); const children = parseChildren(context, ancestors); ancestors.pop(); element.children = children; // End tag. if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(context, TagType.End); // TODO: } return element; } function parseTag(context: ParserContext, type: TagType): ElementNode { // Tag open. const start = getCursor(context); const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!; const tag = match[1]; advanceBy(context, match[0].length); advanceSpaces(context); // Attributes. let props = parseAttributes(context, type); // Tag close. let isSelfClosing = false; isSelfClosing = startsWith(context.source, "/>"); advanceBy(context, isSelfClosing ? 2 : 1); return { type: NodeTypes.ELEMENT, tag, props, children: [], isSelfClosing, loc: getSelection(context, start), }; } function parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] { const props = []; const attributeNames = new Set(); while ( context.source.length > 0 && !startsWith(context.source, ">") && !startsWith(context.source, "/>") ) { const attr = parseAttribute(context, attributeNames); if (type === TagType.Start) { props.push(attr); } advanceSpaces(context); } return props; } type AttributeValue = | { content: string; loc: SourceLocation; } | undefined; function parseAttribute( context: ParserContext, nameSet: Set, ): AttributeNode | DirectiveNode { // Name. const start = getCursor(context); const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!; const name = match[0]; nameSet.add(name); advanceBy(context, name.length); // Value let value: AttributeValue = undefined; if (/^[\t\r\n\f ]*=/.test(context.source)) { advanceSpaces(context); advanceBy(context, 1); advanceSpaces(context); value = parseAttributeValue(context); } // directive const loc = getSelection(context, start); if (/^(v-[A-Za-z0-9-]|@)/.test(name)) { const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(name)!; let dirName = match[1] || (startsWith(name, "@") ? "on" : ""); let arg = ""; if (match[2]) arg = match[2]; return { type: NodeTypes.DIRECTIVE, name: dirName, exp: value?.content ?? "", loc, arg, }; } return { type: NodeTypes.ATTRIBUTE, name, value: value && { type: NodeTypes.TEXT, content: value.content, loc: value.loc, }, loc, }; } function parseAttributeValue(context: ParserContext): AttributeValue { const start = getCursor(context); let content: string; const quote = context.source[0]; const isQuoted = quote === `"` || quote === `'`; if (isQuoted) { // Quoted value. advanceBy(context, 1); const endIndex = context.source.indexOf(quote); if (endIndex === -1) { content = parseTextData(context, context.source.length); } else { content = parseTextData(context, endIndex); advanceBy(context, 1); } } else { // Unquoted const match = /^[^\t\r\n\f >]+/.exec(context.source); if (!match) { return undefined; } content = parseTextData(context, match[0].length); } return { content, loc: getSelection(context, start) }; } function parseTextData(context: ParserContext, length: number): string { const rawText = context.source.slice(0, length); advanceBy(context, length); return rawText; } function getCursor(context: ParserContext): Position { const { column, line, offset } = context; return { column, line, offset }; } function getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation { end = end || getCursor(context); return { start, end, source: context.originalSource.slice(start.offset, end.offset), }; } function last(xs: T[]): T | undefined { return xs[xs.length - 1]; } function startsWithEndTagOpen(source: string, tag: string): boolean { return ( startsWith(source, "]/.test(source[2 + tag.length] || ">") ); } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-dom/index.ts ================================================ import { type CompilerOptions, baseCompile, baseParse } from "../compiler-core"; export function compile(template: string, option?: CompilerOptions) { const defaultOption: Required = { isBrowser: true }; if (option) Object.assign(defaultOption, option); return baseCompile(template, defaultOption); } export function parse(template: string) { return baseParse(template); } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-sfc/compileTemplate.ts ================================================ import type { TemplateChildNode } from "../compiler-core"; export interface TemplateCompiler { compile(template: string): string; parse(template: string): { children: TemplateChildNode[] }; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-sfc/index.ts ================================================ export * from "./parse"; export * from "./rewriteDefault"; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-sfc/parse.ts ================================================ import { type ElementNode, NodeTypes, type SourceLocation } from "../compiler-core"; import * as CompilerDOM from "../compiler-dom"; import type { TemplateCompiler } from "./compileTemplate"; export interface SFCDescriptor { id: string; filename: string; source: string; template: SFCTemplateBlock | null; script: SFCScriptBlock | null; styles: SFCStyleBlock[]; } export interface SFCBlock { type: string; content: string; loc: SourceLocation; } export interface SFCTemplateBlock extends SFCBlock { type: "template"; } export interface SFCScriptBlock extends SFCBlock { type: "script"; } export declare interface SFCStyleBlock extends SFCBlock { type: "style"; } export interface SFCParseOptions { filename?: string; sourceRoot?: string; compiler?: TemplateCompiler; } export interface SFCParseResult { descriptor: SFCDescriptor; } export const DEFAULT_FILENAME = "anonymous.vue"; export function parse( source: string, { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {}, ): SFCParseResult { const descriptor: SFCDescriptor = { id: undefined!, filename, source, template: null, script: null, styles: [], }; const ast = compiler.parse(source); ast.children.forEach((node) => { if (node.type !== NodeTypes.ELEMENT) return; switch (node.tag) { case "template": { descriptor.template = createBlock(node, source) as SFCTemplateBlock; break; } case "script": { const scriptBlock = createBlock(node, source) as SFCScriptBlock; descriptor.script = scriptBlock; break; } case "style": { descriptor.styles.push(createBlock(node, source) as SFCStyleBlock); break; } default: { break; } } }); return { descriptor }; } function createBlock(node: ElementNode, source: string): SFCBlock { const type = node.tag; let { start, end } = node.loc; start = node.children[0].loc.start; end = node.children[node.children.length - 1].loc.end; const content = source.slice(start.offset, end.offset); const loc = { source: content, start, end }; const block: SFCBlock = { type, content, loc }; return block; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-sfc/rewriteDefault.ts ================================================ import { parse } from "@babel/parser"; import MagicString from "magic-string"; const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/; const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)(?:as)?(\s*)default/s; export function rewriteDefault(input: string, as: string): string { if (!hasDefaultExport(input)) { return input + `\nconst ${as} = {}`; } const s = new MagicString(input); const ast = parse(input, { sourceType: "module", }).program.body; ast.forEach((node) => { if (node.type === "ExportDefaultDeclaration") { if (node.declaration.type === "ClassDeclaration") { s.overwrite(node.start!, node.declaration.id.start!, `class `); s.append(`\nconst ${as} = ${node.declaration.id.name}`); } else { s.overwrite(node.start!, node.declaration.start!, `const ${as} = `); } } if (node.type === "ExportNamedDeclaration") { for (const specifier of node.specifiers) { if ( specifier.type === "ExportSpecifier" && specifier.exported.type === "Identifier" && specifier.exported.name === "default" ) { if (node.source) { if (specifier.local.name === "default") { const end = specifierEnd(input, specifier.local.end!, node.end!); s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = __VUE_DEFAULT__`); continue; } else { const end = specifierEnd(input, specifier.exported.end!, node.end!); s.prepend( `import { ${input.slice( specifier.local.start!, specifier.local.end!, )} } from '${node.source.value}'\n`, ); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); continue; } } const end = specifierEnd(input, specifier.end!, node.end!); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); } } } }); return s.toString(); } export function hasDefaultExport(input: string): boolean { return defaultExportRE.test(input) || namedDefaultExportRE.test(input); } function specifierEnd(input: string, end: number, nodeEnd: number | null) { // export { default , foo } ... let hasCommas = false; let oldEnd = end; while (end < nodeEnd!) { if (/\s/.test(input.charAt(end))) { end++; } else if (input.charAt(end) === ",") { end++; hasCommas = true; break; } else if (input.charAt(end) === "}") { break; } } return hasCommas ? end : oldEnd; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/reactivity/baseHandler.ts ================================================ import { isObject } from "../shared"; import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (isObject(res)) { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export type EffectScheduler = (...args: any[]) => any; export class ReactiveEffect { constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, ) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { if (effect.scheduler) { effect.scheduler(); } else { effect.run(); } } } } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { uid: number; type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; setupState: Data; isMounted: boolean; } export type InternalRenderFunction = { (ctx: Data): VNodeChild; }; let uid = 0; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { uid: uid++, type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately setupState: {}, isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { const setupResult = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; // setupResultの型によって分岐をする if (typeof setupResult === "function") { instance.render = setupResult; } else if (typeof setupResult === "object" && setupResult !== null) { instance.setupState = setupResult; } else { // do nothing } } if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } const { render } = component; if (render) { instance.render = render as InternalRenderFunction; } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function | Record | void; render?: Function; template?: string; }; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; export { nextTick } from "./scheduler"; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { ShapeFlags } from "../shared/shapeFlags"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { type SchedulerJob, queueJob } from "./scheduler"; import { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp(el: HostElement, key: string, value: any): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; remove(child: HostNode): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, setElementText: hostSetElementText, insert: hostInsert, remove: hostRemove, parentNode: hostParentNode, } = options; const patch = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const { type, shapeFlag } = n2; if (type === Text) { processText(n1, n2, container, anchor); } else if (shapeFlag & ShapeFlags.ELEMENT) { processElement(n1, n2, container, anchor); } else if (shapeFlag & ShapeFlags.COMPONENT) { processComponent(n1, n2, container, anchor); } else { // do nothing } }; const processElement = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { mountElement(n2, container, anchor); } else { patchElement(n1, n2, anchor); } }; const mountElement = ( vnode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el, anchor); if (props) { for (const key in props) { hostPatchProp(el, key, props[key]); } } hostInsert(el, container); }; const mountChildren = ( children: VNode[], container: RendererElement, anchor: RendererElement | null, ) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container, anchor); } }; const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => { const el = (n2.el = n1.el!); const props = n2.props; patchChildren(n1, n2, el, anchor); for (const key in props) { if (props[key] !== n1.props?.[key]) { hostPatchProp(el, key, props[key]); } } }; const patchChildren = ( n1: VNode, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const c1 = n1 && n1.children; const prevShapeFlag = n1 ? n1.shapeFlag : 0; const c2 = n2.children; const { shapeFlag } = n2; if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { unmountChildren(c1 as VNode[]); } if (c2 !== c1) { hostSetElementText(container, c2 as string); } } else { if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor); } else { unmountChildren(c1 as VNode[]); } } else { if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { hostSetElementText(container, ""); } if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren(c2 as VNode[], container, anchor); } } } }; const patchKeyedChildren = ( c1: VNode[], c2: VNode[], container: RendererElement, parentAnchor: RendererElement | null, ) => { let i = 0; const l2 = c2.length; const e1 = c1.length - 1; const e2 = l2 - 1; const keyToNewIndexMap: Map = new Map(); for (i = 0; i <= e2; i++) { const nextChild = (c2[i] = normalizeVNode(c2[i])); if (nextChild.key != null) { keyToNewIndexMap.set(nextChild.key, i); } } let j; let patched = 0; const toBePatched = e2 + 1; let moved = false; let maxNewIndexSoFar = 0; const newIndexToOldIndexMap = new Array(toBePatched); for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0; for (i = 0; i <= e1; i++) { const prevChild = c1[i]; if (patched >= toBePatched) { // all new children have been patched so this can only be a removal unmount(prevChild); continue; } let newIndex; if (prevChild.key != null) { newIndex = keyToNewIndexMap.get(prevChild.key); } else { // key-less node, try to locate a key-less node of the same type for (j = 0; j <= e2; j++) { if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) { newIndex = j; break; } } } if (newIndex === undefined) { unmount(prevChild); } else { newIndexToOldIndexMap[newIndex] = i + 1; if (newIndex >= maxNewIndexSoFar) { maxNewIndexSoFar = newIndex; } else { moved = true; } patch(prevChild, c2[newIndex] as VNode, container, null); patched++; } } const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; j = increasingNewIndexSequence.length - 1; for (i = toBePatched - 1; i >= 0; i--) { const nextIndex = i; const nextChild = c2[nextIndex] as VNode; const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor; if (newIndexToOldIndexMap[i] === 0) { // mount new patch(null, nextChild, container, anchor); } else if (moved) { // move if: // There is no stable subsequence (e.g. a reverse) // OR current node is not among the stable sequence if (j < 0 || i !== increasingNewIndexSequence[j]) { move(nextChild, container, anchor); } else { j--; } } } }; const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => { const { el, shapeFlag } = vnode; if (shapeFlag & ShapeFlags.COMPONENT) { move(vnode.component!.subTree, container, anchor); return; } hostInsert(el!, container, anchor); }; const unmount = (vnode: VNode) => { const { children, shapeFlag } = vnode; if (shapeFlag & ShapeFlags.COMPONENT) { unmountComponent(vnode.component!); } else if (Array.isArray(children)) { unmountChildren(children as VNode[]); } remove(vnode); }; const remove = (vnode: VNode) => { const { el } = vnode; hostRemove(el!); }; const unmountComponent = (instance: ComponentInternalInstance) => { const { subTree } = instance; unmount(subTree); }; const unmountChildren = (children: VNode[]) => { for (let i = 0; i < children.length; i++) { unmount(children[i]); } }; const processText = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { mountComponent(n2, container, anchor); } else { updateComponent(n1, n2); } }; const mountComponent = ( initialVNode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container, anchor); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const componentUpdateFn = () => { const { render, setupState } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render(setupState))); patch(null, subTree, container, anchor); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render(setupState)); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () => queueJob(update), )); const update: SchedulerJob = (instance.update = () => effect.run()); update.id = instance.uid; update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container, null); }; return { render }; } // https://en.wikipedia.org/wiki/Longest_increasing_subsequence function getSequence(arr: number[]): number[] { const p = arr.slice(); const result = [0]; let i, j, u, v, c; const len = arr.length; for (i = 0; i < len; i++) { const arrI = arr[i]; if (arrI !== 0) { j = result[result.length - 1]; if (arr[j] < arrI) { p[i] = j; result.push(i); continue; } u = 0; v = result.length - 1; while (u < v) { c = (u + v) >> 1; if (arr[result[c]] < arrI) { u = c + 1; } else { v = c; } } if (arrI < arr[result[u]]) { if (u > 0) { p[i] = result[u - 1]; } result[u] = i; } } } u = result.length; v = result[u - 1]; while (u-- > 0) { result[u] = v; v = p[v]; } return result; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/scheduler.ts ================================================ export interface SchedulerJob extends Function { id?: number; } const queue: SchedulerJob[] = []; let flushIndex = 0; let isFlushing = false; let isFlushPending = false; const resolvedPromise = Promise.resolve() as Promise; let currentFlushPromise: Promise | null = null; export function nextTick(this: T, fn?: (this: T) => void): Promise { const p = currentFlushPromise || resolvedPromise; return fn ? p.then(this ? fn.bind(this) : fn) : p; } export function queueJob(job: SchedulerJob) { if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) { if (job.id == null) { queue.push(job); } else { queue.splice(findInsertionIndex(job.id), 0, job); } queueFlush(); } } function queueFlush() { if (!isFlushing && !isFlushPending) { isFlushPending = true; currentFlushPromise = resolvedPromise.then(() => { isFlushPending = false; isFlushing = true; queue.forEach((job) => { job(); }); flushIndex = 0; queue.length = 0; isFlushing = false; currentFlushPromise = null; }); } } function findInsertionIndex(id: number) { let start = flushIndex + 1; let end = queue.length; while (start < end) { const middle = (start + end) >>> 1; const middleJobId = getId(queue[middle]); middleJobId < id ? (start = middle + 1) : (end = middle); } return start; } const getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id); ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/vnode.ts ================================================ import { isObject, isString } from "../shared"; import { ShapeFlags } from "../shared/shapeFlags"; import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; key: string | number | symbol | null; component: ComponentInternalInstance | null; shapeFlag: number; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0; const vnode: VNode = { type, props, children: children, el: undefined, key: props?.key ?? null, component: null, shapeFlag, }; normalizeChildren(vnode, children); return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } export function normalizeChildren(vnode: VNode, children: unknown) { let type = 0; if (children == null) { children = null; } else if (Array.isArray(children)) { type = ShapeFlags.ARRAY_CHILDREN; } else { children = String(children); type = ShapeFlags.TEXT_CHILDREN; } vnode.children = children as VNodeNormalizedChildren; vnode.shapeFlag |= type; } export function isSameVNodeType(n1: VNode, n2: VNode): boolean { return n1.type === n2.type && n1.key === n2.key; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-dom/nodeOps.ts ================================================ import type { RendererOptions } from "../runtime-core"; export const nodeOps: Omit, "patchProp"> = { createElement: (tagName) => { return document.createElement(tagName); }, createText: (text) => { return document.createTextNode(text); }, setText: (node, text) => { node.nodeValue = text; }, setElementText(node, text) { node.textContent = text; }, insert: (child, parent, anchor) => { parent.insertBefore(child, anchor || null); }, remove: (child) => { const parent = child.parentNode; if (parent) { parent.removeChild(child); } }, parentNode: (node) => { return node.parentNode; }, }; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-dom/patchProp.ts ================================================ import type { RendererOptions } from "../runtime-core"; import { patchAttr } from "./modules/attrs"; import { patchEvent } from "./modules/events"; type DOMRendererOptions = RendererOptions; const onRE = /^on[^a-z]/; export const isOn = (key: string) => onRE.test(key); export const patchProp: DOMRendererOptions["patchProp"] = (el, key, value) => { if (isOn(key)) { patchEvent(el, key, value); } else { patchAttr(el, key, value); } }; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/shared/general.ts ================================================ export const isString = (val: unknown): val is string => typeof val === "string"; export const isObject = (val: unknown): val is Record => val !== null && typeof val === "object"; const hasOwnProperty = Object.prototype.hasOwnProperty; export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); const camelizeRE = /-(\w)/g; export const camelize = (str: string): string => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); }; export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``); ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/shared/index.ts ================================================ export * from "./general"; ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/packages/shared/shapeFlags.ts ================================================ export const enum ShapeFlags { ELEMENT = 1, COMPONENT = 1 << 2, TEXT_CHILDREN = 1 << 3, ARRAY_CHILDREN = 1 << 4, } ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/tests/e2e.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createApp, h, nextTick, reactive } from "../packages"; let host: HTMLElement; const initHost = () => { host = document.createElement("div"); host.setAttribute("id", "host"); document.body.appendChild(host); }; beforeEach(() => initHost()); afterEach(() => host.remove()); describe("20_basic_virtual_dom/050_next_tick", () => { it("should update DOM after nextTick", async () => { const state = reactive({ count: 0 }); const app = createApp({ setup() { return () => h("div", {}, [`count: ${state.count}`]); }, }); app.mount("#host"); expect(host.innerHTML).toBe("
count: 0
"); state.count++; await nextTick(); expect(host.innerHTML).toBe("
count: 1
"); }); }); ================================================ FILE: book/impls/20_basic_virtual_dom/050_next_tick/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "lib": ["DOM", "ESNext"], "strict": true, "paths": { "chibivue": ["./packages"] }, "moduleResolution": "Bundler", "allowJs": true, "esModuleInterop": true }, "include": ["packages/**/*.ts", "examples/**/**.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/examples/playground/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/examples/playground/index.html ================================================ chibivue
================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/examples/playground/package.json ================================================ { "name": "playground", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^5.9.3", "vite": "^8.0.0" }, "license": "MIT" } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/examples/playground/src/main.ts ================================================ import { createApp, h, nextTick, reactive } from "chibivue"; const app = createApp({ setup() { const state = reactive({ count: 0, }); const updateState = async () => { state.count++; await nextTick(); const p = document.getElementById("count-p"); if (p) { console.log("😎 p.textContent", p.textContent); } }; return () => { return h("div", { id: "app" }, [ h("p", { id: "count-p" }, [`${state.count}`]), h("button", { onClick: updateState }, ["update"]), ]); }; }, }); app.mount("#app"); ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/examples/playground/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Bundler", "strict": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "skipLibCheck": true, "paths": { "chibivue": ["../../packages"] } }, "include": ["src"] } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/examples/playground/vite.config.ts ================================================ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; import chibivue from "../../packages/@extensions/vite-plugin-chibivue"; const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))); export default defineConfig({ resolve: { alias: { chibivue: path.resolve(dirname, "../../packages"), }, }, plugins: [chibivue()], }); ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/package.json ================================================ { "name": "01_project_setup", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "cd examples/playground && pnpm i && pnpm run dev" }, "keywords": [], "author": "ubugeeei ", "license": "MIT", "devDependencies": { "@types/node": "^24.10.9" }, "dependencies": { "@babel/parser": "^7.28.6", "magic-string": "^0.30.21" } } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/@extensions/vite-plugin-chibivue/index.ts ================================================ import fs from "node:fs"; import type { Plugin } from "vite"; import { createFilter } from "vite"; import { parse, rewriteDefault } from "../../compiler-sfc"; import { compile } from "../../compiler-dom"; export default function vitePluginChibivue(): Plugin { const filter = createFilter(/\.vue$/); return { name: "vite:chibivue", resolveId(id) { if (id.match(/\.vue\.css$/)) return id; }, load(id) { if (id.match(/\.vue\.css$/)) { const filename = id.replace(/\.css$/, ""); const content = fs.readFileSync(filename, "utf-8"); const { descriptor } = parse(content, { filename }); const styles = descriptor.styles.map((it) => it.content).join("\n"); return { code: styles }; } }, transform(code, id) { if (!filter(id)) return; const outputs = []; outputs.push("import * as ChibiVue from 'chibivue'"); outputs.push(`import '${id}.css'`); const { descriptor } = parse(code, { filename: id }); const SFC_MAIN = "_sfc_main"; const scriptCode = rewriteDefault(descriptor.script?.content ?? "", SFC_MAIN); outputs.push(scriptCode); const templateCode = compile(descriptor.template?.content ?? "", { isBrowser: false, }); outputs.push(templateCode); outputs.push("\n"); outputs.push(`export default { ...${SFC_MAIN}, render }`); return { code: outputs.join("\n") }; }, }; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/ast.ts ================================================ export const enum NodeTypes { ELEMENT, TEXT, INTERPOLATION, ATTRIBUTE, DIRECTIVE, } export interface Node { type: NodeTypes; loc: SourceLocation; } export interface ElementNode extends Node { type: NodeTypes.ELEMENT; tag: string; props: Array; children: TemplateChildNode[]; isSelfClosing: boolean; } export interface TextNode extends Node { type: NodeTypes.TEXT; content: string; } export type TemplateChildNode = ElementNode | TextNode | InterpolationNode; export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE; name: string; value: TextNode | undefined; } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE; name: string; exp: string; arg: string; } export interface SourceLocation { start: Position; end: Position; source: string; } export interface Position { offset: number; // from start of file line: number; column: number; } export interface InterpolationNode extends Node { type: NodeTypes.INTERPOLATION; content: string; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/codegen.ts ================================================ import { toHandlerKey } from "../shared"; import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type TemplateChildNode, type TextNode, } from "./ast"; import type { CompilerOptions } from "./options"; export const generate = ( { children, }: { children: TemplateChildNode[]; }, option: Required, ): string => { return `${option.isBrowser ? "return " : ""}function render(_ctx) { ${option.isBrowser ? "with (_ctx) {" : ""} const { h } = ChibiVue; return ${genNode(children[0], option)}; ${option.isBrowser ? "}" : ""} }`; }; const genNode = (node: TemplateChildNode, option: Required): string => { switch (node.type) { case NodeTypes.ELEMENT: return genElement(node, option); case NodeTypes.TEXT: return genText(node); case NodeTypes.INTERPOLATION: return genInterpolation(node, option); default: return ""; } }; const genElement = (el: ElementNode, option: Required): string => { return `h("${el.tag}", {${el.props .map((prop) => genProp(prop, option)) .join(", ")}}, [${el.children.map((it) => genNode(it, option)).join(", ")}])`; }; const genProp = ( prop: AttributeNode | DirectiveNode, option: Required, ): string => { switch (prop.type) { case NodeTypes.ATTRIBUTE: return `${prop.name}: "${prop.value?.content}"`; case NodeTypes.DIRECTIVE: { switch (prop.name) { case "on": return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? "" : "_ctx."}${prop.exp}`; default: // TODO: other directives throw new Error(`unexpected directive name. got "${prop.name}"`); } } default: throw new Error(`unexpected prop type.`); } }; const genText = (text: TextNode): string => { return `\`${text.content}\``; }; const genInterpolation = (node: InterpolationNode, option: Required): string => { return `${option.isBrowser ? "" : "_ctx."}${node.content}`; }; ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/compile.ts ================================================ import { generate } from "./codegen"; import type { CompilerOptions } from "./options"; import { baseParse } from "./parse"; export function baseCompile(template: string, option: Required) { const parseResult = baseParse(template.trim()); const code = generate(parseResult, option); return code; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/index.ts ================================================ export * from "./codegen"; export * from "./compile"; export * from "./parse"; export * from "./ast"; export * from "./options"; ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/options.ts ================================================ export type CompilerOptions = { isBrowser?: boolean; }; ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/parse.ts ================================================ import { type AttributeNode, type DirectiveNode, type ElementNode, type InterpolationNode, NodeTypes, type Position, type SourceLocation, type TemplateChildNode, type TextNode, } from "./ast"; export interface ParserContext { readonly originalSource: string; source: string; offset: number; line: number; column: number; } function createParserContext(content: string): ParserContext { return { originalSource: content, source: content, column: 1, line: 1, offset: 0, }; } export const baseParse = (content: string): { children: TemplateChildNode[] } => { const context = createParserContext(content); const children = parseChildren(context, []); return { children: children }; }; function parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] { const nodes: TemplateChildNode[] = []; while (!isEnd(context, ancestors)) { const s = context.source; let node: TemplateChildNode | undefined = undefined; if (startsWith(s, "{{")) { node = parseInterpolation(context); } else if (s[0] === "<") { if (/[a-z]/i.test(s[1])) { node = parseElement(context, ancestors); } } if (!node) { node = parseText(context); } pushNode(nodes, node); } return nodes; } function advanceBy(context: ParserContext, numberOfCharacters: number): void { const { source } = context; advancePositionWithMutation(context, source, numberOfCharacters); context.source = source.slice(numberOfCharacters); } function advancePositionWithMutation( pos: Position, source: string, numberOfCharacters: number = source.length, ): Position { let linesCount = 0; let lastNewLinePos = -1; for (let i = 0; i < numberOfCharacters; i++) { if (source.charCodeAt(i) === 10 /* newline char code */) { linesCount++; lastNewLinePos = i; } } pos.offset += numberOfCharacters; pos.line += linesCount; pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; return pos; } function isEnd(context: ParserContext, ancestors: ElementNode[]): boolean { const s = context.source; if (startsWith(s, "= 0; --i) { if (startsWithEndTagOpen(s, ancestors[i].tag)) { return true; } } } return !s; } function startsWith(source: string, searchString: string): boolean { return source.startsWith(searchString); } function advanceSpaces(context: ParserContext): void { const match = /^[\t\r\n\f ]+/.exec(context.source); if (match) { advanceBy(context, match[0].length); } } function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void { if (node.type === NodeTypes.TEXT) { const prev = last(nodes); if (prev && prev.type === NodeTypes.TEXT) { prev.content += node.content; return; } } nodes.push(node); } function parseInterpolation(context: ParserContext): InterpolationNode | undefined { const [open, close] = ["{{", "}}"]; const closeIndex = context.source.indexOf(close, open.length); if (closeIndex === -1) return undefined; const start = getCursor(context); advanceBy(context, open.length); const innerStart = getCursor(context); const innerEnd = getCursor(context); const rawContentLength = closeIndex - open.length; const rawContent = context.source.slice(0, rawContentLength); const preTrimContent = parseTextData(context, rawContentLength); const content = preTrimContent.trim(); const startOffset = preTrimContent.indexOf(content); if (startOffset > 0) { advancePositionWithMutation(innerStart, rawContent, startOffset); } const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset); advancePositionWithMutation(innerEnd, rawContent, endOffset); advanceBy(context, close.length); return { type: NodeTypes.INTERPOLATION, content, loc: getSelection(context, start), }; } function parseText(context: ParserContext): TextNode { const endTokens = ["<", "{{"]; let endIndex = context.source.length; for (let i = 0; i < endTokens.length; i++) { const index = context.source.indexOf(endTokens[i], 1); if (index !== -1 && endIndex > index) { endIndex = index; } } const start = getCursor(context); const content = parseTextData(context, endIndex); return { type: NodeTypes.TEXT, content, loc: getSelection(context, start), }; } const enum TagType { Start, End, } function parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined { // Start tag. const element = parseTag(context, TagType.Start); // TODO: if (element.isSelfClosing) { return element; } // Children. ancestors.push(element); const children = parseChildren(context, ancestors); ancestors.pop(); element.children = children; // End tag. if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(context, TagType.End); // TODO: } return element; } function parseTag(context: ParserContext, type: TagType): ElementNode { // Tag open. const start = getCursor(context); const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!; const tag = match[1]; advanceBy(context, match[0].length); advanceSpaces(context); // Attributes. let props = parseAttributes(context, type); // Tag close. let isSelfClosing = false; isSelfClosing = startsWith(context.source, "/>"); advanceBy(context, isSelfClosing ? 2 : 1); return { type: NodeTypes.ELEMENT, tag, props, children: [], isSelfClosing, loc: getSelection(context, start), }; } function parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] { const props = []; const attributeNames = new Set(); while ( context.source.length > 0 && !startsWith(context.source, ">") && !startsWith(context.source, "/>") ) { const attr = parseAttribute(context, attributeNames); if (type === TagType.Start) { props.push(attr); } advanceSpaces(context); } return props; } type AttributeValue = | { content: string; loc: SourceLocation; } | undefined; function parseAttribute( context: ParserContext, nameSet: Set, ): AttributeNode | DirectiveNode { // Name. const start = getCursor(context); const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!; const name = match[0]; nameSet.add(name); advanceBy(context, name.length); // Value let value: AttributeValue = undefined; if (/^[\t\r\n\f ]*=/.test(context.source)) { advanceSpaces(context); advanceBy(context, 1); advanceSpaces(context); value = parseAttributeValue(context); } // directive const loc = getSelection(context, start); if (/^(v-[A-Za-z0-9-]|@)/.test(name)) { const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(name)!; let dirName = match[1] || (startsWith(name, "@") ? "on" : ""); let arg = ""; if (match[2]) arg = match[2]; return { type: NodeTypes.DIRECTIVE, name: dirName, exp: value?.content ?? "", loc, arg, }; } return { type: NodeTypes.ATTRIBUTE, name, value: value && { type: NodeTypes.TEXT, content: value.content, loc: value.loc, }, loc, }; } function parseAttributeValue(context: ParserContext): AttributeValue { const start = getCursor(context); let content: string; const quote = context.source[0]; const isQuoted = quote === `"` || quote === `'`; if (isQuoted) { // Quoted value. advanceBy(context, 1); const endIndex = context.source.indexOf(quote); if (endIndex === -1) { content = parseTextData(context, context.source.length); } else { content = parseTextData(context, endIndex); advanceBy(context, 1); } } else { // Unquoted const match = /^[^\t\r\n\f >]+/.exec(context.source); if (!match) { return undefined; } content = parseTextData(context, match[0].length); } return { content, loc: getSelection(context, start) }; } function parseTextData(context: ParserContext, length: number): string { const rawText = context.source.slice(0, length); advanceBy(context, length); return rawText; } function getCursor(context: ParserContext): Position { const { column, line, offset } = context; return { column, line, offset }; } function getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation { end = end || getCursor(context); return { start, end, source: context.originalSource.slice(start.offset, end.offset), }; } function last(xs: T[]): T | undefined { return xs[xs.length - 1]; } function startsWithEndTagOpen(source: string, tag: string): boolean { return ( startsWith(source, "]/.test(source[2 + tag.length] || ">") ); } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-dom/index.ts ================================================ import { type CompilerOptions, baseCompile, baseParse } from "../compiler-core"; export function compile(template: string, option?: CompilerOptions) { const defaultOption: Required = { isBrowser: true }; if (option) Object.assign(defaultOption, option); return baseCompile(template, defaultOption); } export function parse(template: string) { return baseParse(template); } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-sfc/compileTemplate.ts ================================================ import type { TemplateChildNode } from "../compiler-core"; export interface TemplateCompiler { compile(template: string): string; parse(template: string): { children: TemplateChildNode[] }; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-sfc/index.ts ================================================ export * from "./parse"; export * from "./rewriteDefault"; ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-sfc/parse.ts ================================================ import { type ElementNode, NodeTypes, type SourceLocation } from "../compiler-core"; import * as CompilerDOM from "../compiler-dom"; import type { TemplateCompiler } from "./compileTemplate"; export interface SFCDescriptor { id: string; filename: string; source: string; template: SFCTemplateBlock | null; script: SFCScriptBlock | null; styles: SFCStyleBlock[]; } export interface SFCBlock { type: string; content: string; loc: SourceLocation; } export interface SFCTemplateBlock extends SFCBlock { type: "template"; } export interface SFCScriptBlock extends SFCBlock { type: "script"; } export declare interface SFCStyleBlock extends SFCBlock { type: "style"; } export interface SFCParseOptions { filename?: string; sourceRoot?: string; compiler?: TemplateCompiler; } export interface SFCParseResult { descriptor: SFCDescriptor; } export const DEFAULT_FILENAME = "anonymous.vue"; export function parse( source: string, { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {}, ): SFCParseResult { const descriptor: SFCDescriptor = { id: undefined!, filename, source, template: null, script: null, styles: [], }; const ast = compiler.parse(source); ast.children.forEach((node) => { if (node.type !== NodeTypes.ELEMENT) return; switch (node.tag) { case "template": { descriptor.template = createBlock(node, source) as SFCTemplateBlock; break; } case "script": { const scriptBlock = createBlock(node, source) as SFCScriptBlock; descriptor.script = scriptBlock; break; } case "style": { descriptor.styles.push(createBlock(node, source) as SFCStyleBlock); break; } default: { break; } } }); return { descriptor }; } function createBlock(node: ElementNode, source: string): SFCBlock { const type = node.tag; let { start, end } = node.loc; start = node.children[0].loc.start; end = node.children[node.children.length - 1].loc.end; const content = source.slice(start.offset, end.offset); const loc = { source: content, start, end }; const block: SFCBlock = { type, content, loc }; return block; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-sfc/rewriteDefault.ts ================================================ import { parse } from "@babel/parser"; import MagicString from "magic-string"; const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/; const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)(?:as)?(\s*)default/s; export function rewriteDefault(input: string, as: string): string { if (!hasDefaultExport(input)) { return input + `\nconst ${as} = {}`; } const s = new MagicString(input); const ast = parse(input, { sourceType: "module", }).program.body; ast.forEach((node) => { if (node.type === "ExportDefaultDeclaration") { if (node.declaration.type === "ClassDeclaration") { s.overwrite(node.start!, node.declaration.id.start!, `class `); s.append(`\nconst ${as} = ${node.declaration.id.name}`); } else { s.overwrite(node.start!, node.declaration.start!, `const ${as} = `); } } if (node.type === "ExportNamedDeclaration") { for (const specifier of node.specifiers) { if ( specifier.type === "ExportSpecifier" && specifier.exported.type === "Identifier" && specifier.exported.name === "default" ) { if (node.source) { if (specifier.local.name === "default") { const end = specifierEnd(input, specifier.local.end!, node.end!); s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = __VUE_DEFAULT__`); continue; } else { const end = specifierEnd(input, specifier.exported.end!, node.end!); s.prepend( `import { ${input.slice( specifier.local.start!, specifier.local.end!, )} } from '${node.source.value}'\n`, ); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); continue; } } const end = specifierEnd(input, specifier.end!, node.end!); s.overwrite(specifier.start!, end, ``); s.append(`\nconst ${as} = ${specifier.local.name}`); } } } }); return s.toString(); } export function hasDefaultExport(input: string): boolean { return defaultExportRE.test(input) || namedDefaultExportRE.test(input); } function specifierEnd(input: string, end: number, nodeEnd: number | null) { // export { default , foo } ... let hasCommas = false; let oldEnd = end; while (end < nodeEnd!) { if (/\s/.test(input.charAt(end))) { end++; } else if (input.charAt(end) === ",") { end++; hasCommas = true; break; } else if (input.charAt(end) === "}") { break; } } return hasCommas ? end : oldEnd; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/index.ts ================================================ export * from "./runtime-core"; export * from "./runtime-dom"; export * from "./reactivity"; import { compile } from "./compiler-dom"; import { type InternalRenderFunction, registerRuntimeCompiler } from "./runtime-core"; import * as runtimeDom from "./runtime-dom"; function compileToFunction(template: string): InternalRenderFunction { const code = compile(template); return new Function("ChibiVue", code)(runtimeDom); } registerRuntimeCompiler(compileToFunction); ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/reactivity/baseHandler.ts ================================================ import { isObject } from "../shared"; import { track, trigger } from "./effect"; import { reactive } from "./reactive"; export const mutableHandlers: ProxyHandler = { get(target: object, key: string | symbol, receiver: object) { track(target, key); const res = Reflect.get(target, key, receiver); if (isObject(res)) { return reactive(res); } return res; }, set(target: object, key: string | symbol, value: unknown, receiver: object) { let oldValue = (target as any)[key]; Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); } return true; }, }; const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/reactivity/dep.ts ================================================ import type { ReactiveEffect } from "./effect"; export type Dep = Set; export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep: Dep = new Set(effects); return dep; }; ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/reactivity/effect.ts ================================================ import { type Dep, createDep } from "./dep"; type KeyToDepMap = Map; const targetMap = new WeakMap(); export let activeEffect: ReactiveEffect | undefined; export type EffectScheduler = (...args: any[]) => any; export class ReactiveEffect { constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, ) {} run() { let parent: ReactiveEffect | undefined = activeEffect; activeEffect = this; const res = this.fn(); activeEffect = parent; return res; } } export function track(target: object, key: unknown) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = createDep())); } if (activeEffect) { dep.add(activeEffect); } } export function trigger(target: object, key?: unknown) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { const effects = [...dep]; for (const effect of effects) { if (effect.scheduler) { effect.scheduler(); } else { effect.run(); } } } } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/reactivity/index.ts ================================================ export { reactive } from "./reactive"; export { ReactiveEffect } from "./effect"; ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/reactivity/reactive.ts ================================================ import { mutableHandlers } from "./baseHandler"; export function reactive(target: T): T { const proxy = new Proxy(target, mutableHandlers); return proxy as T; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/apiCreateApp.ts ================================================ import type { Component } from "./component"; import type { RootRenderFunction } from "./renderer"; export interface App { mount(rootContainer: HostElement | string): void; } export type CreateAppFunction = (rootComponent: Component) => App; export function createAppAPI( render: RootRenderFunction, ): CreateAppFunction { return function createApp(rootComponent) { const app: App = { mount(rootContainer: HostElement) { render(rootComponent, rootContainer); }, }; return app; }; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/component.ts ================================================ import type { ReactiveEffect } from "../reactivity"; import { emit } from "./componentEmits"; import type { ComponentOptions } from "./componentOptions"; import { type Props, initProps } from "./componentProps"; import type { VNode, VNodeChild } from "./vnode"; export type Component = ComponentOptions; export type Data = Record; export interface ComponentInternalInstance { uid: number; type: Component; vnode: VNode; subTree: VNode; next: VNode | null; effect: ReactiveEffect; render: InternalRenderFunction; update: () => void; propsOptions: Props; props: Data; emit: (event: string, ...args: any[]) => void; setupState: Data; isMounted: boolean; } export type InternalRenderFunction = { (ctx: Data): VNodeChild; }; let uid = 0; export function createComponentInstance(vnode: VNode): ComponentInternalInstance { const type = vnode.type as Component; const instance: ComponentInternalInstance = { uid: uid++, type, vnode, next: null, effect: null!, subTree: null!, update: null!, render: null!, propsOptions: type.props || {}, props: {}, emit: null!, // to be set immediately setupState: {}, isMounted: false, }; instance.emit = emit.bind(null, instance); return instance; } export const setupComponent = (instance: ComponentInternalInstance) => { const { props } = instance.vnode; initProps(instance, props); const component = instance.type as Component; if (component.setup) { const setupResult = component.setup(instance.props, { emit: instance.emit, }) as InternalRenderFunction; // setupResultの型によって分岐をする if (typeof setupResult === "function") { instance.render = setupResult; } else if (typeof setupResult === "object" && setupResult !== null) { instance.setupState = setupResult; } else { // do nothing } } if (compile && !component.render) { const template = component.template ?? ""; if (template) { instance.render = compile(template); } } const { render } = component; if (render) { instance.render = render as InternalRenderFunction; } }; type CompileFunction = (template: string) => InternalRenderFunction; let compile: CompileFunction | undefined; export function registerRuntimeCompiler(_compile: any) { compile = _compile; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/componentEmits.ts ================================================ import { camelize, toHandlerKey } from "../shared"; import type { ComponentInternalInstance } from "./component"; export function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) { const props = instance.vnode.props || {}; let args = rawArgs; let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]; if (handler) handler(...args); } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/componentOptions.ts ================================================ export type ComponentOptions = { props?: Record; setup?: ( props: Record, ctx: { emit: (event: string, ...args: any[]) => void }, ) => Function | Record | void; render?: Function; template?: string; }; ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/componentProps.ts ================================================ import { reactive } from "../reactivity"; import { camelize, hasOwn } from "../shared"; import type { ComponentInternalInstance, Data } from "./component"; export type Props = Record; export interface PropOptions { type?: PropType | true | null; required?: boolean; default?: null | undefined | object; } export type PropType = { new (...args: any[]): T & {} }; export function initProps(instance: ComponentInternalInstance, rawProps: Data | null) { const props: Data = {}; setFullProps(instance, rawProps, props); instance.props = reactive(props); } export function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) { const { props } = instance; Object.entries(rawProps ?? {}).forEach(([key, value]) => { props[camelize(key)] = value; }); } function setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) { const options = instance.propsOptions; if (rawProps) { for (let key in rawProps) { const value = rawProps[key]; // kebab -> camel let camelKey; if (options && hasOwn(options, (camelKey = camelize(key)))) { props[camelKey] = value; } } } } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/h.ts ================================================ import { type VNode, type VNodeProps, createVNode } from "./vnode"; export function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) { return createVNode(type, props, children); } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/index.ts ================================================ export type { App, CreateAppFunction } from "./apiCreateApp"; export { createAppAPI } from "./apiCreateApp"; export { registerRuntimeCompiler, type InternalRenderFunction } from "./component"; export type { RendererOptions } from "./renderer"; export { createRenderer } from "./renderer"; export { h } from "./h"; export { nextTick } from "./scheduler"; ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/renderer.ts ================================================ import { ReactiveEffect } from "../reactivity"; import { ShapeFlags } from "../shared/shapeFlags"; import { type Component, type ComponentInternalInstance, createComponentInstance, setupComponent, } from "./component"; import { updateProps } from "./componentProps"; import { type SchedulerJob, queueJob } from "./scheduler"; import { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from "./vnode"; export type RootRenderFunction = ( vnode: Component, container: HostElement, ) => void; export interface RendererOptions { patchProp( el: HostElement, key: string, prevValue: any, nextValue: any, prevChildren?: VNode[], unmountChildren?: (children: VNode[]) => void, ): void; createElement(type: string): HostElement; createText(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostNode, text: string): void; insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void; remove(child: HostNode): void; parentNode(node: HostNode): HostNode | null; } export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export function createRenderer(options: RendererOptions) { const { patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, setText: hostSetText, setElementText: hostSetElementText, insert: hostInsert, remove: hostRemove, parentNode: hostParentNode, } = options; const patch = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const { type, shapeFlag } = n2; if (type === Text) { processText(n1, n2, container, anchor); } else if (shapeFlag & ShapeFlags.ELEMENT) { processElement(n1, n2, container, anchor); } else if (shapeFlag & ShapeFlags.COMPONENT) { processComponent(n1, n2, container, anchor); } else { // do nothing } }; const processElement = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { mountElement(n2, container, anchor); } else { patchElement(n1, n2, anchor); } }; const mountElement = ( vnode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { let el: RendererElement; const { type, props } = vnode; el = vnode.el = hostCreateElement(type as string); mountChildren(vnode.children as VNode[], el, anchor); if (props) { for (const key in props) { hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren); } } hostInsert(el, container); }; const mountChildren = ( children: VNode[], container: RendererElement, anchor: RendererElement | null, ) => { for (let i = 0; i < children.length; i++) { const child = (children[i] = normalizeVNode(children[i])); patch(null, child, container, anchor); } }; const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => { const el = (n2.el = n1.el!); const oldProps = n1.props || {}; const newProps = n2.props || {}; patchChildren(n1, n2, el, anchor); for (const key in oldProps) { if (!(key in newProps)) { hostPatchProp(el, key, oldProps[key], null); } } for (const key in newProps) { const next = newProps[key]; const prev = oldProps[key]; if (next !== prev) { hostPatchProp(el, key, prev, next); } } }; const patchChildren = ( n1: VNode, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const c1 = n1 && n1.children; const prevShapeFlag = n1 ? n1.shapeFlag : 0; const c2 = n2.children; const { shapeFlag } = n2; if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { unmountChildren(c1 as VNode[]); } if (c2 !== c1) { hostSetElementText(container, c2 as string); } } else { if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor); } else { unmountChildren(c1 as VNode[]); } } else { if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { hostSetElementText(container, ""); } if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren(c2 as VNode[], container, anchor); } } } }; const patchKeyedChildren = ( c1: VNode[], c2: VNode[], container: RendererElement, parentAnchor: RendererElement | null, ) => { let i = 0; const l2 = c2.length; const e1 = c1.length - 1; const e2 = l2 - 1; const keyToNewIndexMap: Map = new Map(); for (i = 0; i <= e2; i++) { const nextChild = (c2[i] = normalizeVNode(c2[i])); if (nextChild.key != null) { keyToNewIndexMap.set(nextChild.key, i); } } let j; let patched = 0; const toBePatched = e2 + 1; let moved = false; let maxNewIndexSoFar = 0; const newIndexToOldIndexMap = new Array(toBePatched); for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0; for (i = 0; i <= e1; i++) { const prevChild = c1[i]; if (patched >= toBePatched) { // all new children have been patched so this can only be a removal unmount(prevChild); continue; } let newIndex; if (prevChild.key != null) { newIndex = keyToNewIndexMap.get(prevChild.key); } else { // key-less node, try to locate a key-less node of the same type for (j = 0; j <= e2; j++) { if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) { newIndex = j; break; } } } if (newIndex === undefined) { unmount(prevChild); } else { newIndexToOldIndexMap[newIndex] = i + 1; if (newIndex >= maxNewIndexSoFar) { maxNewIndexSoFar = newIndex; } else { moved = true; } patch(prevChild, c2[newIndex] as VNode, container, null); patched++; } } const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; j = increasingNewIndexSequence.length - 1; for (i = toBePatched - 1; i >= 0; i--) { const nextIndex = i; const nextChild = c2[nextIndex] as VNode; const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor; if (newIndexToOldIndexMap[i] === 0) { // mount new patch(null, nextChild, container, anchor); } else if (moved) { // move if: // There is no stable subsequence (e.g. a reverse) // OR current node is not among the stable sequence if (j < 0 || i !== increasingNewIndexSequence[j]) { move(nextChild, container, anchor); } else { j--; } } } }; const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => { const { el, shapeFlag } = vnode; if (shapeFlag & ShapeFlags.COMPONENT) { move(vnode.component!.subTree, container, anchor); return; } hostInsert(el!, container, anchor); }; const unmount = (vnode: VNode) => { const { children, shapeFlag } = vnode; if (shapeFlag & ShapeFlags.COMPONENT) { unmountComponent(vnode.component!); } else if (Array.isArray(children)) { unmountChildren(children as VNode[]); } remove(vnode); }; const remove = (vnode: VNode) => { const { el } = vnode; hostRemove(el!); }; const unmountComponent = (instance: ComponentInternalInstance) => { const { subTree } = instance; unmount(subTree); }; const unmountChildren = (children: VNode[]) => { for (let i = 0; i < children.length; i++) { unmount(children[i]); } }; const processText = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor); } else { const el = (n2.el = n1.el!); if (n2.children !== n1.children) { hostSetText(el, n2.children as string); } } }; const processComponent = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererElement | null, ) => { if (n1 == null) { mountComponent(n2, container, anchor); } else { updateComponent(n1, n2); } }; const mountComponent = ( initialVNode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { // prettier-ignore const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode)); setupComponent(instance); setupRenderEffect(instance, initialVNode, container, anchor); }; const setupRenderEffect = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, anchor: RendererElement | null, ) => { const componentUpdateFn = () => { const { render, setupState } = instance; if (!instance.isMounted) { const subTree = (instance.subTree = normalizeVNode(render(setupState))); patch(null, subTree, container, anchor); initialVNode.el = subTree.el; instance.isMounted = true; } else { let { next, vnode } = instance; if (next) { next.el = vnode.el; next.component = instance; instance.vnode = next; instance.next = null; updateProps(instance, next.props); } else { next = vnode; } const prevTree = instance.subTree; const nextTree = normalizeVNode(render(setupState)); instance.subTree = nextTree; patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor); next.el = nextTree.el; } }; const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () => queueJob(update), )); const update: SchedulerJob = (instance.update = () => effect.run()); update.id = instance.uid; update(); }; const updateComponent = (n1: VNode, n2: VNode) => { const instance = (n2.component = n1.component)!; instance.next = n2; instance.update(); }; const render: RootRenderFunction = (rootComponent, container) => { const vnode = createVNode(rootComponent, {}, []); patch(null, vnode, container, null); }; return { render }; } // https://en.wikipedia.org/wiki/Longest_increasing_subsequence function getSequence(arr: number[]): number[] { const p = arr.slice(); const result = [0]; let i, j, u, v, c; const len = arr.length; for (i = 0; i < len; i++) { const arrI = arr[i]; if (arrI !== 0) { j = result[result.length - 1]; if (arr[j] < arrI) { p[i] = j; result.push(i); continue; } u = 0; v = result.length - 1; while (u < v) { c = (u + v) >> 1; if (arr[result[c]] < arrI) { u = c + 1; } else { v = c; } } if (arrI < arr[result[u]]) { if (u > 0) { p[i] = result[u - 1]; } result[u] = i; } } } u = result.length; v = result[u - 1]; while (u-- > 0) { result[u] = v; v = p[v]; } return result; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/scheduler.ts ================================================ export interface SchedulerJob extends Function { id?: number; } const queue: SchedulerJob[] = []; let flushIndex = 0; let isFlushing = false; let isFlushPending = false; const resolvedPromise = Promise.resolve() as Promise; let currentFlushPromise: Promise | null = null; export function nextTick(this: T, fn?: (this: T) => void): Promise { const p = currentFlushPromise || resolvedPromise; return fn ? p.then(this ? fn.bind(this) : fn) : p; } export function queueJob(job: SchedulerJob) { if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) { if (job.id == null) { queue.push(job); } else { queue.splice(findInsertionIndex(job.id), 0, job); } queueFlush(); } } function queueFlush() { if (!isFlushing && !isFlushPending) { isFlushPending = true; currentFlushPromise = resolvedPromise.then(() => { isFlushPending = false; isFlushing = true; queue.forEach((job) => { job(); }); flushIndex = 0; queue.length = 0; isFlushing = false; currentFlushPromise = null; }); } } function findInsertionIndex(id: number) { let start = flushIndex + 1; let end = queue.length; while (start < end) { const middle = (start + end) >>> 1; const middleJobId = getId(queue[middle]); middleJobId < id ? (start = middle + 1) : (end = middle); } return start; } const getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id); ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/vnode.ts ================================================ import { isArray, isObject, isString } from "../shared"; import { ShapeFlags } from "../shared/shapeFlags"; import type { ComponentInternalInstance } from "./component"; export type VNodeTypes = string | typeof Text | object; export const Text = Symbol(); export interface VNode { type: VNodeTypes; props: VNodeProps | null; children: VNodeNormalizedChildren; el: HostNode | undefined; key: string | number | symbol | null; component: ComponentInternalInstance | null; shapeFlag: number; } export interface VNodeProps { [key: string]: any; } export type VNodeNormalizedChildren = string | VNodeArrayChildren; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; type VNodeChildAtom = VNode | string; export function createVNode( type: VNodeTypes, props: VNodeProps | null, children: VNodeNormalizedChildren, ): VNode { const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0; const vnode: VNode = { type, props, children: children, el: undefined, key: props?.key ?? null, component: null, shapeFlag, }; normalizeChildren(vnode, children); return vnode; } export function normalizeVNode(child: VNodeChild): VNode { if (typeof child === "object") { return { ...child } as VNode; } else { return createVNode(Text, null, String(child)); } } export function normalizeChildren(vnode: VNode, children: unknown) { let type = 0; if (children == null) { children = null; } else if (isArray(children)) { type = ShapeFlags.ARRAY_CHILDREN; } else { children = String(children); type = ShapeFlags.TEXT_CHILDREN; } vnode.children = children as VNodeNormalizedChildren; vnode.shapeFlag |= type; } export function isSameVNodeType(n1: VNode, n2: VNode): boolean { return n1.type === n2.type && n1.key === n2.key; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/index.ts ================================================ import { type CreateAppFunction, createAppAPI, createRenderer } from "../runtime-core"; import { nodeOps } from "./nodeOps"; import { patchProp } from "./patchProp"; const { render } = createRenderer({ ...nodeOps, patchProp }); const _createApp = createAppAPI(render); export const createApp = ((...args) => { const app = _createApp(...args); const { mount } = app; app.mount = (selector: string) => { const container = document.querySelector(selector); if (!container) return; mount(container); }; return app; }) as CreateAppFunction; export * from "../runtime-core"; ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/modules/attrs.ts ================================================ export function patchAttr(el: Element, key: string, value: any) { if (value == null) { el.removeAttribute(key); } else { el.setAttribute(key, value); } } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/modules/class.ts ================================================ export function patchClass(el: Element, value: string | null) { if (value == null) { el.removeAttribute("class"); } else { el.className = value; } } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/modules/events.ts ================================================ interface Invoker extends EventListener { value: EventValue; } type EventValue = Function; export function addEventListener(el: Element, event: string, handler: EventListener) { el.addEventListener(event, handler); } export function removeEventListener(el: Element, event: string, handler: EventListener) { el.removeEventListener(event, handler); } export function patchEvent( el: Element & { _vei?: Record }, rawName: string, value: EventValue | null, ) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); const existingInvoker = invokers[rawName]; if (value && existingInvoker) { // patch existingInvoker.value = value; } else { const name = parseName(rawName); if (value) { // add const invoker = (invokers[rawName] = createInvoker(value)); addEventListener(el, name, invoker); } else if (existingInvoker) { // remove removeEventListener(el, name, existingInvoker); invokers[rawName] = undefined; } } } function parseName(rawName: string): string { return rawName.slice(2).toLocaleLowerCase(); } function createInvoker(initialValue: EventValue) { const invoker: Invoker = (e: Event) => { invoker.value(e); }; invoker.value = initialValue; return invoker; } ================================================ FILE: book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/modules/props.ts ================================================ export function patchDOMProp( el: any, key: string, value: any, // the following args are passed only due to potential innerHTML/textContent // overriding existing VNodes, in which case the old tree must be properly prevChildren: any, unmountChildren: any, ) { if (key === "innerHTML" || key === "textContent") { if (prevChildren) { unmountChildren(prevChildren); } el[key] = value == null ? "" : value; return; } let needRemove = false; if (value === "" || value == null) { const type = typeof el[key]; if (type === "boolean") { // e.g. must be set as attribute if (key === "list" && el.tagName === "INPUT") { return false; } //