[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: 'ci'\non:\n  push:\n    branches:\n      - '**'\n  pull_request:\n    branches:\n      - main\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v2\n\n      - name: Set node version to 16\n        uses: actions/setup-node@v4\n        with:\n          node-version-file: '.node-version'\n          cache: 'pnpm'\n\n      - run: pnpm install\n\n      - name: Run tests\n        run: pnpm test\n"
  },
  {
    "path": ".github/workflows/issue-close-require.yml",
    "content": "name: Issue Close Require\n\non:\n  schedule:\n    - cron: \"0 0 * * *\"\n\njobs:\n  close-issues:\n    runs-on: ubuntu-latest\n    steps:\n      - name: need reproduction\n        uses: actions-cool/issues-helper@v3\n        with:\n          actions: \"close-issues\"\n          token: ${{ secrets.GITHUB_TOKEN }}\n          labels: \"need reproduction\"\n          inactive-day: 3\n"
  },
  {
    "path": ".github/workflows/issue-labeled.yml",
    "content": "name: Issue Labeled\n\non:\n  issues:\n    types: [labeled]\n\njobs:\n  reply-labeled:\n    runs-on: ubuntu-latest\n    steps:\n      - name: contribution welcome\n        if: github.event.label.name == 'contribution welcome' || github.event.label.name == 'help wanted'\n        uses: actions-cool/issues-helper@v3\n        with:\n          actions: \"create-comment, remove-labels\"\n          token: ${{ secrets.GITHUB_TOKEN }}\n          issue-number: ${{ github.event.issue.number }}\n          body: |\n            Hello @${{ github.event.issue.user.login }}. We like your proposal/feedback and would appreciate a contribution via a Pull Request by you or another community member. We thank you in advance for your contribution and are looking forward to reviewing it!\n          labels: \"pending triage, need reproduction\"\n\n      - name: remove pending\n        if: contains(github.event.label.description, '(priority)') && contains(github.event.issue.labels.*.name, 'pending triage')\n        uses: actions-cool/issues-helper@v3\n        with:\n          actions: \"remove-labels\"\n          token: ${{ secrets.GITHUB_TOKEN }}\n          issue-number: ${{ github.event.issue.number }}\n          labels: \"pending triage\"\n\n      - name: remove enhancement pending\n        if: \"(github.event.label.name == 'enhancement' || contains(github.event.label.description, '(priority)')) && contains(github.event.issue.labels.*.name, 'enhancement: pending triage')\"\n        uses: actions-cool/issues-helper@v3\n        with:\n          actions: \"remove-labels\"\n          token: ${{ secrets.GITHUB_TOKEN }}\n          issue-number: ${{ github.event.issue.number }}\n          labels: \"enhancement: pending triage\"\n\n      - name: need reproduction\n        if: github.event.label.name == 'need reproduction'\n        uses: actions-cool/issues-helper@v3\n        with:\n          actions: \"create-comment, remove-labels\"\n          token: ${{ secrets.GITHUB_TOKEN }}\n          issue-number: ${{ github.event.issue.number }}\n          body: |\n            Hello @${{ github.event.issue.user.login }}. Please provide a [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) using a GitHub repository or [StackBlitz](https://stackblitz.com/edit/vitejs-vite-xj3ufp?file=src/App.vue). Issues marked with `need reproduction` will be closed if they have no activity within 3 days.\n          labels: \"pending triage\"\n"
  },
  {
    "path": ".github/workflows/release-tag.yml",
    "content": "on:\n  push:\n    tags:\n      - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10\n\nname: Create Release\n\njobs:\n  build:\n    name: Create Release\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@master\n      - name: Create Release for Tag\n        id: release_tag\n        uses: yyx990803/release-tag@master\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: ${{ github.ref }}\n          body: |\n            Please refer to [CHANGELOG.md](https://github.com/vitejs/vite-plugin-vue2/blob/main/CHANGELOG.md) for details.\n"
  },
  {
    "path": ".gitignore",
    "content": "dist\nnode_modules\nTODOs.md\ntemp\n.DS_Store\n"
  },
  {
    "path": ".node-version",
    "content": "v16\n"
  },
  {
    "path": ".prettierrc",
    "content": "semi: false\nsingleQuote: true\nprintWidth: 80\ntrailingComma: 'none'\narrowParens: 'avoid'\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## [2.3.3](https://github.com/vitejs/vite-plugin-vue2/compare/v2.3.2...v2.3.3) (2024-11-26)\n\n\n\n## [2.3.2](https://github.com/vitejs/vite-plugin-vue2/compare/v2.3.1...v2.3.2) (2024-11-26)\n\n\n### Features\n\n* support vite 6 ([#104](https://github.com/vitejs/vite-plugin-vue2/issues/104)) ([80a7442](https://github.com/vitejs/vite-plugin-vue2/commit/80a74425bbae7b5eaf549d32d30b2d046912a797))\n\n\n\n## [2.3.1](https://github.com/vitejs/vite-plugin-vue2/compare/v2.3.0...v2.3.1) (2023-11-16)\n\n\n### Bug Fixes\n\n* exports types ([5f48994](https://github.com/vitejs/vite-plugin-vue2/commit/5f489944477ed6732c3bb36dd18f029fad970c9d))\n\n\n\n# [2.3.0](https://github.com/vitejs/vite-plugin-vue2/compare/v2.2.0...v2.3.0) (2023-11-16)\n\n\n### Features\n\n* Vite 5 Support ([#94](https://github.com/vitejs/vite-plugin-vue2/issues/94)) ([f080464](https://github.com/vitejs/vite-plugin-vue2/commit/f0804641009b42f34ef5c785fe8caf746ec94fec))\n\n\n\n# [2.2.0](https://github.com/vitejs/vite-plugin-vue2/compare/v2.1.0...v2.2.0) (2022-12-10)\n\n\n### Features\n\n* Update for Vite 4.x support ([[#71](https://github.com/vitejs/vite-plugin-vue2/issues/71)](https://github.com/vitejs/vite-plugin-vue2/issues/71)) ([#72](https://github.com/vitejs/vite-plugin-vue2/issues/72)) ([d2360be](https://github.com/vitejs/vite-plugin-vue2/commit/d2360be65b37cdf51a27843925d352866dff23d1))\n\n\n\n# [2.1.0](https://github.com/vitejs/vite-plugin-vue2/compare/v2.0.1...v2.1.0) (2022-11-30)\n\n\n### Bug Fixes\n\n* **esbuild:** transpile with esnext in dev ([#60](https://github.com/vitejs/vite-plugin-vue2/issues/60)) ([bd87898](https://github.com/vitejs/vite-plugin-vue2/commit/bd87898be4d02bd52cc8af0072db9e59a5dbd8fa))\n* invalidate script module cache when it changed in hot update ([#67](https://github.com/vitejs/vite-plugin-vue2/issues/67)) ([b8e6133](https://github.com/vitejs/vite-plugin-vue2/commit/b8e6133b54bce820d93d0e4f9a9982198cdd60ee))\n\n\n### Features\n\n* resolve complier from peer dep when unable to resolve from the root ([#68](https://github.com/vitejs/vite-plugin-vue2/issues/68)) ([0ea62d2](https://github.com/vitejs/vite-plugin-vue2/commit/0ea62d2b4f8a84e87b332f4f2749aeba7f8e3145))\n\n\n\n## [2.0.1](https://github.com/vitejs/vite-plugin-vue2/compare/v2.0.0...v2.0.1) (2022-11-09)\n\n\n### Bug Fixes\n\n* allow overwriting template.transformAssetUrls.includeAbsolute ([#48](https://github.com/vitejs/vite-plugin-vue2/issues/48)) ([7db0767](https://github.com/vitejs/vite-plugin-vue2/commit/7db076705b79d383b84e13cb375a7aa9f9f1545c))\n\n\n\n## [2.0.0](https://github.com/vitejs/vite-plugin-vue2/compare/v1.1.2...v2.0.0) (2022-09-13)\n\n\n### Breaking Changes\n\n* only support Vite 3 ([#28](https://github.com/vitejs/vite-plugin-vue2/pull/28))\n\n### Bug Fixes\n\n* handle undefined on import.meta.hot.accept ([b668430](https://github.com/vitejs/vite-plugin-vue2/commit/b66843045b16516fc91512c67c4f87b6d3f4d45e))\n\n\n### Features\n\n* add compiler option in Options ([#45](https://github.com/vitejs/vite-plugin-vue2/issues/45)) ([fb47586](https://github.com/vitejs/vite-plugin-vue2/commit/fb4758637c0506e9b0e7ea6883568287f60ae077))\n\n\n\n## [1.1.2](https://github.com/vitejs/vite-plugin-vue2/compare/v1.1.1...v1.1.2) (2022-07-01)\n\n\n### Bug Fixes\n\n* force resolution of vue to deal with deps requiring vue ([9a78726](https://github.com/vitejs/vite-plugin-vue2/commit/9a78726d77ef9aadf3c07dacd4c27828fe8f4ac8)), closes [#16](https://github.com/vitejs/vite-plugin-vue2/issues/16)\n* handle decorators in ts rewriteDefault fallback ([2d24d2a](https://github.com/vitejs/vite-plugin-vue2/commit/2d24d2a4a692e59b789efc9b34119cc3650bf89e)), closes [#17](https://github.com/vitejs/vite-plugin-vue2/issues/17)\n\n\n\n## [1.1.1](https://github.com/vitejs/vite-plugin-vue2/compare/v1.1.0...v1.1.1) (2022-06-28)\n\n\n### Bug Fixes\n\n* handle no template code in .vue file ([#11](https://github.com/vitejs/vite-plugin-vue2/issues/11)) ([42b0851](https://github.com/vitejs/vite-plugin-vue2/commit/42b0851425e39d5e7138114f1cc3d431cadc52ab)), closes [#15](https://github.com/vitejs/vite-plugin-vue2/issues/15)\n\n\n\n# [1.1.0](https://github.com/vitejs/vite-plugin-vue2/compare/v1.0.1...v1.1.0) (2022-06-20)\n\n\n### Features\n\n* support css v-bind ([5146fd8](https://github.com/vitejs/vite-plugin-vue2/commit/5146fd8d2b852c8aed07d081811e7b81894211eb))\n\n\n\n## 1.0.1 (2022-06-17)\n\n\n### Bug Fixes\n\n* disable prettify by default ([cd80f72](https://github.com/vitejs/vite-plugin-vue2/commit/cd80f7231d50bbf04919852e7cc72623070d9f40))\n\n\n### Features\n\n* it is working ([6c387d8](https://github.com/vitejs/vite-plugin-vue2/commit/6c387d8172d76b17df3d13a37f87d0e203bc4523))\n\n\n\n# 1.0.0 (2022-06-17)\n\n\n### Features\n\n* it is working ([6c387d8](https://github.com/vitejs/vite-plugin-vue2/commit/6c387d8172d76b17df3d13a37f87d0e203bc4523))\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019-present, Yuxi (Evan) You and contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# @vitejs/plugin-vue2 [![npm](https://img.shields.io/npm/v/@vitejs/plugin-vue2.svg)](https://npmjs.com/package/@vitejs/plugin-vue2)\n\n> [!CAUTION]\n> Vue 2 has reached EOL, and this project is no longer actively maintained.\n\n---\n\n> Note: this plugin only works with Vue@^2.7.0.\n\n```js\n// vite.config.js\nimport vue from '@vitejs/plugin-vue2'\n\nexport default {\n  plugins: [vue()]\n}\n```\n\n## Options\n\n```ts\nexport interface Options {\n  include?: string | RegExp | (string | RegExp)[]\n  exclude?: string | RegExp | (string | RegExp)[]\n\n  isProduction?: boolean\n\n  // options to pass on to vue/compiler-sfc\n  script?: Partial<Pick<SFCScriptCompileOptions, 'babelParserPlugins'>>\n  template?: Partial<\n    Pick<\n      SFCTemplateCompileOptions,\n      | 'compiler'\n      | 'compilerOptions'\n      | 'preprocessOptions'\n      | 'transpileOptions'\n      | 'transformAssetUrls'\n      | 'transformAssetUrlsOptions'\n    >\n  >\n  style?: Partial<Pick<SFCStyleCompileOptions, 'trim'>>\n}\n```\n\n## Asset URL handling\n\nWhen `@vitejs/plugin-vue2` compiles the `<template>` blocks in SFCs, it also converts any encountered asset URLs into ESM imports.\n\nFor example, the following template snippet:\n\n```vue\n<img src=\"../image.png\" />\n```\n\nIs the same as:\n\n```vue\n<script setup>\nimport _imports_0 from '../image.png'\n</script>\n```\n\n```vue\n<img :src=\"_imports_0\" />\n```\n\nBy default the following tag/attribute combinations are transformed, and can be configured using the `template.transformAssetUrls` option.\n\n```js\n{\n  video: ['src', 'poster'],\n  source: ['src'],\n  img: ['src'],\n  image: ['xlink:href', 'href'],\n  use: ['xlink:href', 'href']\n}\n```\n\nNote that only attribute values that are static strings are transformed. Otherwise, you'd need to import the asset manually, e.g. `import imgUrl from '../image.png'`.\n\n## Example for passing options to `vue/compiler-sfc`:\n\n```ts\nimport vue from '@vitejs/plugin-vue2'\n\nexport default {\n  plugins: [\n    vue({\n      template: {\n        compilerOptions: {\n          // ...\n        },\n        transformAssetUrls: {\n          // ...\n        }\n      }\n    })\n  ]\n}\n```\n\n## Example for transforming custom blocks\n\n```ts\nimport vue from '@vitejs/plugin-vue2'\n\nconst vueI18nPlugin = {\n  name: 'vue-i18n',\n  transform(code, id) {\n    if (!/vue&type=i18n/.test(id)) {\n      return\n    }\n    if (/\\.ya?ml$/.test(id)) {\n      code = JSON.stringify(require('js-yaml').load(code.trim()))\n    }\n    return `export default Comp => {\n      Comp.i18n = ${code}\n    }`\n  }\n}\n\nexport default {\n  plugins: [vue(), vueI18nPlugin]\n}\n```\n\n## License\n\nMIT\n"
  },
  {
    "path": "build.config.ts",
    "content": "import { defineBuildConfig } from 'unbuild'\n\nexport default defineBuildConfig({\n  entries: ['src/index'],\n  externals: ['vite', 'vue/compiler-sfc'],\n  clean: true,\n  declaration: true,\n  rollup: {\n    emitCJS: true,\n    inlineDependencies: true\n  }\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@vitejs/plugin-vue2\",\n  \"version\": \"2.3.4\",\n  \"license\": \"MIT\",\n  \"author\": \"Evan You\",\n  \"packageManager\": \"pnpm@7.33.5\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"./dist/index.cjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.mjs\",\n      \"require\": \"./dist/index.cjs\"\n    }\n  },\n  \"scripts\": {\n    \"dev\": \"unbuild --stub\",\n    \"build\": \"unbuild && esno scripts/patchCJS.ts\",\n    \"test\": \"vitest run\",\n    \"release\": \"node scripts/release.js\",\n    \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s\"\n  },\n  \"engines\": {\n    \"node\": \"^14.18.0 || >= 16.0.0\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vitejs/vite-plugin-vue2.git\",\n    \"directory\": \"packages/plugin-vue\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/vitejs/vite-plugin-vue2/issues\"\n  },\n  \"homepage\": \"https://github.com/vitejs/vite-plugin-vue2/#readme\",\n  \"peerDependencies\": {\n    \"vite\": \"^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0\",\n    \"vue\": \"^2.7.0-0\"\n  },\n  \"devDependencies\": {\n    \"@types/fs-extra\": \"^9.0.13\",\n    \"conventional-changelog-cli\": \"^2.2.2\",\n    \"debug\": \"^4.3.4\",\n    \"enquirer\": \"^2.3.6\",\n    \"esno\": \"^0.16.3\",\n    \"execa\": \"^4.1.0\",\n    \"fs-extra\": \"^10.1.0\",\n    \"hash-sum\": \"^2.0.0\",\n    \"minimist\": \"^1.2.6\",\n    \"picocolors\": \"^1.0.0\",\n    \"prettier\": \"^2.7.1\",\n    \"puppeteer\": \"^14.4.0\",\n    \"rollup\": \"^2.75.6\",\n    \"semver\": \"^7.3.7\",\n    \"slash\": \"^3.0.0\",\n    \"source-map\": \"^0.6.1\",\n    \"unbuild\": \"^0.7.4\",\n    \"vite\": \"^3.0.0\",\n    \"vitest\": \"^0.15.1\",\n    \"vue\": \"^2.7.0-beta.8\"\n  }\n}\n"
  },
  {
    "path": "playground/.pnpm-debug.log",
    "content": "{\n  \"0 debug pnpm:scope\": {\n    \"selected\": 1\n  },\n  \"1 error pnpm\": {\n    \"errno\": 1,\n    \"code\": \"ELIFECYCLE\",\n    \"pkgid\": \"vite-vue2-playground@1.0.0\",\n    \"stage\": \"dev\",\n    \"script\": \"vite --debug\",\n    \"pkgname\": \"vite-vue2-playground\",\n    \"err\": {\n      \"name\": \"pnpm\",\n      \"message\": \"vite-vue2-playground@1.0.0 dev: `vite --debug`\\nExit status 1\",\n      \"code\": \"ELIFECYCLE\",\n      \"stack\": \"pnpm: vite-vue2-playground@1.0.0 dev: `vite --debug`\\nExit status 1\\n    at EventEmitter.<anonymous> (/Users/evan/Library/pnpm/global/5/.pnpm/pnpm@7.1.9/node_modules/pnpm/dist/pnpm.cjs:106976:20)\\n    at EventEmitter.emit (node:events:390:28)\\n    at ChildProcess.<anonymous> (/Users/evan/Library/pnpm/global/5/.pnpm/pnpm@7.1.9/node_modules/pnpm/dist/pnpm.cjs:93542:18)\\n    at ChildProcess.emit (node:events:390:28)\\n    at maybeClose (node:internal/child_process:1064:16)\\n    at Process.ChildProcess._handle.onexit (node:internal/child_process:301:5)\"\n    }\n  },\n  \"2 warn pnpm:global\": \" Local package.json exists, but node_modules missing, did you mean to install?\"\n}"
  },
  {
    "path": "playground/App.vue",
    "content": "<script setup lang=\"ts\">\nimport ScriptSetup from './ScriptSetup.vue'\nimport TestMultiplySrcImport from './src-import/TestMultiplySrcImport.vue'\nimport TestBlockSrcImport from './src-import/TestBlockSrcImport.vue'\nimport TestScopedCss from './css/TestScopedCss.vue'\nimport TestCssModules from './css/TestCssModules.vue'\nimport TestEmptyCss from './css/TestEmptyCss.vue'\nimport TestCustomBlock from './custom/TestCustomBlock.vue'\nimport TestHmr from './hmr/TestHmr.vue'\nimport TestAssets from './test-assets/TestAssets.vue'\nimport TestES2020Features from './TestES2020Features.vue'\nimport TestComponent from './test-component/TestComponent.vue'\nimport TestCssVBind from './css/TestCssVBind.vue'\n</script>\n\n<template>\n  <div>\n    <h1>Vite-Plugin-Vue2 Playground</h1>\n    <ScriptSetup msg=\"prop from parent\" />\n    <!-- <TestMultiplySrcImport /> -->\n    <TestBlockSrcImport />\n    <TestScopedCss />\n    <TestCssModules />\n    <TestCustomBlock />\n    <TestEmptyCss />\n    <TestHmr />\n    <TestAssets />\n    <TestES2020Features />\n    <TestComponent />\n    <TestCssVBind/>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/ScriptSetup.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\ndefineProps<{\n  msg: string\n}>()\n\nconst count = ref(0)\n\nconst vRed = {\n  bind(el: HTMLElement) {\n    el.style.color = 'red'\n  }\n}\n</script>\n\n<template>\n  <div v-red class=\"script-setup\">\n    This should be red.\n    <span class=\"prop\">{{ msg }}</span>\n    <button @click=\"count++\">{{ count }}</button>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/TestES2020Features.vue",
    "content": "<script lang=\"ts\">\nimport Vue from 'vue'\n\nexport default Vue.extend({\n  name: 'TestES2020Features',\n\n  data() {\n    return {\n      spreadArray: ['s', 'p', 'r', 'ead'],\n    }\n  },\n\n  computed: {\n    nullish() {\n      return {\n        a: {\n          d: undefined as unknown as undefined | { e: string },\n          b: {\n            c: '2',\n          },\n        },\n      }\n    },\n  },\n})\n</script>\n\n<template>\n  <div>\n    <h2>ES2020 Features</h2>\n    <h3>Nullish Coalescing and Optional Chaining</h3>\n    <code>\n      [nullish.a.b.c.d ?? 'not found']\n      <br>\n      //returns {{ nullish.a.d?.e ?? 'not found' }}\n      <br>\n      <br>\n      [nullish.a.b.c ?? 'not found']\n      <br>\n      //returns {{ nullish.a.b.c ?? 'not found' }}\n    </code>\n    <h3>Spread Operator</h3>\n    <code>\n      [\"Test\", 1, ...('abc').split('')]\n      <br>\n      //returns {{ ['Test', 1, ...'abc'.split('')] }}\n    </code>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/css/TestCssModules.vue",
    "content": "<script>\nimport imported from './testCssModules.module.css'\n\nexport default {\n  data: () => ({ imported }),\n}\n</script>\n\n<template>\n  <div>\n    <h2>CSS Modules</h2>\n    <div class=\"css-modules-sfc\" :class=\"$style.blue\">\n      &lt;style module&gt; - this should be blue\n    </div>\n    <div class=\"css-modules-import\" :class=\"imported.turquoise\">\n      CSS modules import - this should be orange\n    </div>\n  </div>\n</template>\n\n<style module>\n.blue {\n  color: blue;\n}\n</style>\n"
  },
  {
    "path": "playground/css/TestCssVBind.vue",
    "content": "<script setup>\nimport { ref } from 'vue'\n\nconst color = ref('red')\n</script>\n\n<template>\n  <div>\n    <h2>CSS v-bind</h2>\n    <span class=\"css-v-bind\" @click=\"color = color === 'red' ? 'green' : 'red'\"\n      >This should be {{ color }}</span\n    >\n  </div>\n</template>\n\n<style scoped>\nspan {\n  color: v-bind(color);\n}\n</style>\n"
  },
  {
    "path": "playground/css/TestEmptyCss.vue",
    "content": "<template>\n  <div>\n    <h2>Empty CSS</h2>\n    <div>\n      &lt;style&gt;: empty style\n    </div>\n  </div>\n</template>\n\n<style scoped></style>\n<style module></style>\n"
  },
  {
    "path": "playground/css/TestScopedCss.vue",
    "content": "<template>\n  <div>\n    <h2>Scoped CSS</h2>\n    <div class=\"style-scoped\">\n      &lt;style scoped&gt;: only this should be purple\n    </div>\n  </div>\n</template>\n\n<style scoped>\ndiv {\n  color: rgb(138, 43, 226);\n}\n</style>\n"
  },
  {
    "path": "playground/css/testCssModules.module.css",
    "content": ".turquoise {\n  color: rgb(255, 140, 0);\n}\n"
  },
  {
    "path": "playground/custom/TestCustomBlock.vue",
    "content": "<script>\nexport default {\n  name: 'TestCustomBlock',\n  data() {\n    return {\n      custom: '',\n      customLang: '',\n      customSrc: '',\n    }\n  },\n  created() {\n    this.custom = this.$options.__customBlock.custom\n    this.customLang = this.$options.__customBlock.customLang\n    this.customSrc = this.$options.__customBlock.customSrc\n  },\n}\n</script>\n\n<template>\n  <div>\n    <p class=\"custom-block\">\n      {{ custom }}\n    </p>\n    <p class=\"custom-block-lang\">\n      {{ customLang }}\n    </p>\n    <p class=\"custom-block-src\">\n      {{ customSrc }}\n    </p>\n  </div>\n</template>\n\n<custom>\nexport default {\n  \"custom\": \"Custom Block\"\n}\n</custom>\n\n<custom lang=\"json\">\n{\n  \"customLang\": \"Custom Block\"\n}\n</custom>\n\n<custom src=\"./custom.json\"></custom>\n"
  },
  {
    "path": "playground/custom/custom.json",
    "content": "{\n  \"customSrc\": \"Custom Block\"\n}\n"
  },
  {
    "path": "playground/hmr/TestHmr.vue",
    "content": "<script>\nexport default {\n  data() {\n    return {\n      count: 0,\n    }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <h2>Hot Module Replacement</h2>\n    <p>\n      <span>\n        HMR: click button and edit template part of <code>./TestHmr.vue</code>,\n        count should not reset\n      </span>\n      <button class=\"hmr-increment\" @click=\"count++\">\n        &gt;&gt;&gt; {{ count }} &lt;&lt;&lt;\n      </button>\n    </p>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Vite App</title>\n</head>\n<body>\n  <div id=\"app\"></div>\n  <script type=\"module\" src=\"./main.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "playground/main.js",
    "content": "import Vue from 'vue'\nimport App from './App.vue'\n\nnew Vue({\n  render: h => h(App),\n}).$mount('#app')\n"
  },
  {
    "path": "playground/package.json",
    "content": "{\n  \"name\": \"vite-vue2-playground\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"serve\": \"vite preview\"\n  }\n}\n"
  },
  {
    "path": "playground/shims.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\ndeclare module '*.vue' {\n  import Vue from 'vue'\n\n  export default Vue\n}\n"
  },
  {
    "path": "playground/src-import/TestBlockSrcImport.vue",
    "content": "<script src=\"./script.ts\"></script>\n<template src=\"./template.html\" />\n<style src=\"./style.css\" scoped></style>\n"
  },
  {
    "path": "playground/src-import/TestMultiplySrcImport.vue",
    "content": "<script src=\"./script.ts\"></script>\n<template>\n  <div>Multiply .vue file which has same src file reference should works!</div>\n</template>\n<style src=\"./style.css\" scoped></style>\n<style src=\"./style.css\" scoped></style>\n"
  },
  {
    "path": "playground/src-import/script.ts",
    "content": "const msg = 'hello from <script src=\"./script.ts\">'\n\nexport default {\n  data() {\n    return {\n      msg,\n    }\n  },\n}\n"
  },
  {
    "path": "playground/src-import/style.css",
    "content": ".src-imports-style {\n  color: rgb(119, 136, 153);\n}\n"
  },
  {
    "path": "playground/src-import/template.html",
    "content": "<div>\n  <h2>SFC Src Imports</h2>\n  <div class=\"src-imports-script\">{{ msg }}</div>\n  <div class=\"src-imports-style\">This should be light gray</div>\n</div>\n"
  },
  {
    "path": "playground/test-assets/TestAssets.vue",
    "content": "<script>\nimport filepath from './nested/testAssets.png'\n\nexport default {\n  data() {\n    return {\n      filepath,\n    }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <h2>Static Asset Handling</h2>\n    <p class=\"asset-import\">\n      Path for assets import from js: <code>{{ filepath }}</code>\n    </p>\n    <p>\n      Relative asset reference in template:\n      <img src=\"./nested/testAssets.png\" style=\"width: 30px;\">\n    </p>\n    <p>\n      Alias asset reference in template:\n      <img src=\"@/test-assets/nested/testAssets.png\" style=\"width: 30px;\">\n    </p>\n    <p>\n      Absolute asset reference in template:\n      <img src=\"/favicon.ico\" style=\"width: 30px;\">\n    </p>\n    <p>\n      Absolute asset reference without protocol header in the template:\n      <img src=\"//cli.vuejs.org/favicon.png\" style=\"width: 30px;\">\n    </p>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/test-component/TestComponent.vue",
    "content": "<script>\nimport TestRecursive from './recursive/TestRecursive.vue'\nimport TestAsyncComponent from './async/TestAsyncComponent.vue'\n\nexport default {\n  name: 'TestRecursion',\n  components: {\n    TestRecursive,\n    TestAsyncComponent\n  }\n}\n</script>\n\n<template>\n  <div>\n    <h2>Vue Component</h2>\n\n    <TestRecursive />\n    <TestAsyncComponent />\n  </div>\n</template>\n"
  },
  {
    "path": "playground/test-component/async/TestAsyncComponent.vue",
    "content": "<script>\nexport default {\n  components: {\n    componentA: () => import('./componentA.vue')\n  },\n  beforeCreate() {\n    this.$options.components.componentB = () => import('./componentB.vue')\n  }\n}\n</script>\n\n<template>\n  <div>\n    <h3>Async Component</h3>\n    <componentA />\n    <componentB />\n\n    <pre>\nexport default {\n  components: {\n    componentA: () => import('./componentA.vue')\n  },\n  beforeCreate() {\n    this.$options.components.componentB = () => import('./componentB.vue')\n  }\n}\n    </pre>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/test-component/async/componentA.vue",
    "content": "<template>\n  <p class=\"async-component-a\">This is componentA</p>\n</template>\n"
  },
  {
    "path": "playground/test-component/async/componentB.vue",
    "content": "<template>\n  <p class=\"async-component-b\">This is componentB</p>\n</template>\n"
  },
  {
    "path": "playground/test-component/recursive/TestRecursive.vue",
    "content": "<script>\nimport TestRecursiveTree from './TestRecursiveTree.vue'\nimport treedata from './treedata.json'\n\nexport default {\n  name: 'TestRecursion',\n  components: {\n    TestRecursiveTree\n  },\n  data() {\n    return {\n      treedata\n    }\n  }\n}\n</script>\n\n<template>\n  <div>\n    <h3>Recursive Component</h3>\n    <TestRecursiveTree :treedata=\"treedata\"></TestRecursiveTree>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/test-component/recursive/TestRecursiveTree.vue",
    "content": "<script>\nexport default {\n  name: 'TestRecursiveTree',\n  props: ['treedata'],\n  methods: {\n    isEmpty(data) {\n      return !data || !data.length\n    }\n  }\n}\n</script>\n\n<template>\n  <div class=\"test-recursive-tree\">\n    <div class=\"test-recursive-item\" v-for=\"item in treedata\" :key=\"item.label\">\n      <h5>{{ item.label }}</h5>\n\n      <TestRecursiveTree\n        v-if=\"!isEmpty(item.children)\"\n        :treedata=\"item.children\"\n      >\n      </TestRecursiveTree>\n    </div>\n  </div>\n</template>\n\n<style scoped>\n.test-recursive-item {\n  padding-left: 10px;\n}\n</style>\n"
  },
  {
    "path": "playground/test-component/recursive/treedata.json",
    "content": "[\n  {\n    \"label\": \"name-1\",\n    \"children\": [\n      {\n        \"label\": \"name-1-1\",\n        \"children\": [\n          {\n            \"label\": \"name-1-1-1\"\n          }\n        ]\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "playground/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"include\": [\".\"],\n  \"compilerOptions\": {\n    \"jsx\": \"preserve\"\n  },\n  \"vueCompilerOptions\": {\n    \"target\": 2.7\n  }\n}\n"
  },
  {
    "path": "playground/vite.config.ts",
    "content": "import { defineConfig } from 'vite'\nimport vue from '../src/index'\n\nconst config = defineConfig({\n  resolve: {\n    alias: {\n      '@': __dirname\n    }\n  },\n  build: {\n    sourcemap: true,\n    minify: false\n  },\n  plugins: [\n    vue(),\n    {\n      name: 'customBlock',\n      transform(code, id) {\n        if (/type=custom/i.test(id)) {\n          const transformedAssignment = code\n            .trim()\n            .replace(/export default/, 'const __customBlock =')\n          return {\n            code: `${transformedAssignment}\n            export default function (Comp) {\n              if (!Comp.__customBlock) {\n                Comp.__customBlock = {};\n              }\n              Object.assign(Comp.__customBlock, __customBlock);\n            }`,\n            map: null\n          }\n        }\n      }\n    }\n  ]\n})\n\nexport default config\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - playground\n"
  },
  {
    "path": "scripts/patchCJS.ts",
    "content": "/**\n\nIt converts\n\n```ts\nexports[\"default\"] = vuePlugin;\nexports.parseVueRequest = parseVueRequest;\n```\n\nto\n\n```ts\nmodule.exports = vuePlugin;\nmodule.exports[\"default\"] = vuePlugin;\nmodule.exports.parseVueRequest = parseVueRequest;\n```\n*/\n\nimport { readFileSync, writeFileSync } from 'fs'\nimport colors from 'picocolors'\n\nconst indexPath = 'dist/index.cjs'\nlet code = readFileSync(indexPath, 'utf-8')\n\nconst matchMixed = code.match(/\\nexports\\[\"default\"\\] = (\\w+);/)\nif (matchMixed) {\n  const name = matchMixed[1]\n\n  const lines = code.trimEnd().split('\\n')\n\n  // search from the end to prepend `modules.` to `export[xxx]`\n  for (let i = lines.length - 1; i > 0; i--) {\n    if (lines[i].startsWith('exports')) lines[i] = 'module.' + lines[i]\n    else {\n      // at the beginning of exports, export the default function\n      lines[i] += `\\nmodule.exports = ${name};`\n      break\n    }\n  }\n\n  writeFileSync(indexPath, lines.join('\\n'))\n\n  console.log(colors.bold(`${indexPath} CJS patched`))\n} else {\n  const matchDefault = code.match(/\\nmodule.exports = (\\w+);/)\n\n  if (matchDefault) {\n    code += `module.exports[\"default\"] = ${matchDefault[1]};\\n`\n    writeFileSync(indexPath, code)\n    console.log(colors.bold(`${indexPath} CJS patched`))\n  } else {\n    console.error(colors.red(`${indexPath} CJS patch failed`))\n    process.exit(1)\n  }\n}\n"
  },
  {
    "path": "scripts/release.js",
    "content": "const args = require('minimist')(process.argv.slice(2))\nconst fs = require('fs')\nconst path = require('path')\nconst colors = require('picocolors')\nconst semver = require('semver')\nconst currentVersion = require('../package.json').version\nconst { prompt } = require('enquirer')\nconst execa = require('execa')\n\nconst preId =\n  args.preid ||\n  (semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0])\nconst isDryRun = args.dry\nconst skipTests = args.skipTests\nconst skipBuild = args.skipBuild\n\nconst versionIncrements = [\n  'patch',\n  'minor',\n  'major',\n  ...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : [])\n]\n\nconst inc = i => semver.inc(currentVersion, i, preId)\nconst run = (bin, args, opts = {}) =>\n  execa(bin, args, { stdio: 'inherit', ...opts })\nconst dryRun = (bin, args, opts = {}) =>\n  console.log(colors.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)\nconst runIfNotDry = isDryRun ? dryRun : run\nconst step = msg => console.log(colors.cyan(msg))\n\nasync function main() {\n  let targetVersion = args._[0]\n\n  if (!targetVersion) {\n    // no explicit version, offer suggestions\n    const { release } = await prompt({\n      type: 'select',\n      name: 'release',\n      message: 'Select release type',\n      choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])\n    })\n\n    if (release === 'custom') {\n      targetVersion = (\n        await prompt({\n          type: 'input',\n          name: 'version',\n          message: 'Input custom version',\n          initial: currentVersion\n        })\n      ).version\n    } else {\n      targetVersion = release.match(/\\((.*)\\)/)[1]\n    }\n  }\n\n  if (!semver.valid(targetVersion)) {\n    throw new Error(`invalid target version: ${targetVersion}`)\n  }\n\n  const { yes } = await prompt({\n    type: 'confirm',\n    name: 'yes',\n    message: `Releasing v${targetVersion}. Confirm?`\n  })\n\n  if (!yes) {\n    return\n  }\n\n  // run tests before release\n  step('\\nRunning tests...')\n  if (!skipTests && !isDryRun) {\n    await run('pnpm', ['test'])\n  } else {\n    console.log(`(skipped)`)\n  }\n\n  // update package version\n  updatePackage(targetVersion)\n\n  // build all packages with types\n  step('\\nBuilding for production...')\n  if (!skipBuild && !isDryRun) {\n    await run('pnpm', ['run', 'build'])\n  } else {\n    console.log(`(skipped)`)\n  }\n\n  // generate changelog\n  step('\\nGenerating changelog...')\n  await run(`pnpm`, ['run', 'changelog'])\n\n  // update pnpm-lock.yaml\n  step('\\nUpdating lockfile...')\n  await run(`pnpm`, ['install', '--prefer-offline'])\n\n  const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })\n  if (stdout) {\n    step('\\nCommitting changes...')\n    await runIfNotDry('git', ['add', '-A'])\n    await runIfNotDry('git', ['commit', '-m', `release: v${targetVersion}`])\n  } else {\n    console.log('No changes to commit.')\n  }\n\n  // publish packages\n  step('\\nPublishing...')\n  await publishPackage(targetVersion, runIfNotDry)\n\n  // push to GitHub\n  step('\\nPushing to GitHub...')\n  await runIfNotDry('git', ['tag', `v${targetVersion}`])\n  await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])\n  await runIfNotDry('git', ['push'])\n\n  if (isDryRun) {\n    console.log(`\\nDry run finished - run git diff to see package changes.`)\n  }\n  console.log()\n}\n\nfunction updatePackage(version) {\n  const pkgRoot = path.resolve(__dirname, '../')\n  const pkgPath = path.resolve(pkgRoot, 'package.json')\n  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))\n  pkg.version = version\n  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\\n')\n}\n\nasync function publishPackage(version, runIfNotDry) {\n  const pkgRoot = path.resolve(__dirname, '../')\n  const pkgPath = path.resolve(pkgRoot, 'package.json')\n  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))\n  const publishedName = pkg.name\n  if (pkg.private) {\n    return\n  }\n\n  let releaseTag = null\n  if (args.tag) {\n    releaseTag = args.tag\n  } else if (version.includes('alpha')) {\n    releaseTag = 'alpha'\n  } else if (version.includes('beta')) {\n    releaseTag = 'beta'\n  } else if (version.includes('rc')) {\n    releaseTag = 'rc'\n  }\n\n  step(`Publishing ${publishedName}...`)\n  try {\n    await runIfNotDry(\n      'pnpm',\n      [\n        'publish',\n        ...(releaseTag ? ['--tag', releaseTag] : []),\n        '--access',\n        'public'\n      ],\n      {\n        cwd: pkgRoot,\n        stdio: 'pipe'\n      }\n    )\n    console.log(\n      colors.green(`Successfully published ${publishedName}@${version}`)\n    )\n  } catch (e) {\n    if (e.stderr.match(/previously published/)) {\n      console.log(colors.red(`Skipping already published: ${publishedName}`))\n    } else {\n      throw e\n    }\n  }\n}\n\nmain().catch(err => {\n  updatePackage(currentVersion)\n  console.error(err)\n})\n"
  },
  {
    "path": "src/compiler.ts",
    "content": "// extend the descriptor so we can store the scopeId on it\ndeclare module 'vue/compiler-sfc' {\n  interface SFCDescriptor {\n    id: string\n  }\n}\n\nimport { createRequire } from 'node:module'\nimport type * as _compiler from 'vue/compiler-sfc'\n\nexport function resolveCompiler(root: string): typeof _compiler {\n  // resolve from project root first, then fallback to peer dep (if any)\n  const compiler = tryRequire('vue/compiler-sfc', root) || tryRequire('vue/compiler-sfc')\n\n  if (!compiler) {\n    throw new Error(\n      `Failed to resolve vue/compiler-sfc.\\n` +\n        `@vitejs/plugin-vue2 requires vue (>=2.7.0) ` +\n        `to be present in the dependency tree.`\n    )\n  }\n\n  return compiler\n}\n\nconst _require = createRequire(import.meta.url)\n\nfunction tryRequire(id: string, from?: string) {\n  try {\n    return from\n      ? _require(_require.resolve(id, { paths: [from] }))\n      : _require(id)\n  } catch (e) {}\n}\n"
  },
  {
    "path": "src/handleHotUpdate.ts",
    "content": "import type { SFCBlock, SFCDescriptor } from 'vue/compiler-sfc'\nimport type { HmrContext, ModuleNode } from 'vite'\nimport {\n  createDescriptor,\n  getDescriptor,\n  setPrevDescriptor\n} from './utils/descriptorCache'\nimport { getResolvedScript, setResolvedScript } from './script'\nimport type { ResolvedOptions } from '.'\n\nconst directRequestRE = /(\\?|&)direct\\b/\n\n/**\n * Vite-specific HMR handling\n */\nexport async function handleHotUpdate(\n  { file, modules, read, server }: HmrContext,\n  options: ResolvedOptions\n): Promise<ModuleNode[] | void> {\n  const prevDescriptor = getDescriptor(file, options, false)\n  if (!prevDescriptor) {\n    // file hasn't been requested yet (e.g. async component)\n    return\n  }\n\n  setPrevDescriptor(file, prevDescriptor)\n\n  const content = await read()\n  const { descriptor } = createDescriptor(file, content, options)\n\n  let needRerender = false\n  const affectedModules = new Set<ModuleNode | undefined>()\n  const mainModule = modules.find(\n    (m) => !/type=/.test(m.url) || /type=script/.test(m.url)\n  )\n  const templateModule = modules.find((m) => /type=template/.test(m.url))\n\n  const scriptChanged = hasScriptChanged(prevDescriptor, descriptor)\n  if (scriptChanged) {\n    let scriptModule: ModuleNode | undefined\n    if (\n      (descriptor.scriptSetup?.lang && !descriptor.scriptSetup.src) ||\n      (descriptor.script?.lang && !descriptor.script.src)\n    ) {\n      const scriptModuleRE = new RegExp(\n        `type=script.*&lang\\.${\n          descriptor.scriptSetup?.lang || descriptor.script?.lang\n        }$`\n      )\n      scriptModule = modules.find((m) => scriptModuleRE.test(m.url))\n    }\n    affectedModules.add(scriptModule || mainModule)\n  }\n\n  if (!isEqualBlock(descriptor.template, prevDescriptor.template)) {\n    // when a <script setup> component's template changes, it will need correct\n    // binding metadata. However, when reloading the template alone the binding\n    // metadata will not be available since the script part isn't loaded.\n    // in this case, reuse the compiled script from previous descriptor.\n    if (!scriptChanged) {\n      setResolvedScript(\n        descriptor,\n        getResolvedScript(prevDescriptor, false)!,\n        false\n      )\n    }\n    affectedModules.add(templateModule)\n    needRerender = true\n  }\n\n  let didUpdateStyle = false\n  const prevStyles = prevDescriptor.styles || []\n  const nextStyles = descriptor.styles || []\n\n  // force reload if CSS vars injection changed\n  // if (prevDescriptor.cssVars.join('') !== descriptor.cssVars.join('')) {\n  //   affectedModules.add(mainModule)\n  // }\n\n  // force reload if scoped status has changed\n  if (prevStyles.some((s) => s.scoped) !== nextStyles.some((s) => s.scoped)) {\n    // template needs to be invalidated as well\n    affectedModules.add(templateModule)\n    affectedModules.add(mainModule)\n  }\n\n  // only need to update styles if not reloading, since reload forces\n  // style updates as well.\n  for (let i = 0; i < nextStyles.length; i++) {\n    const prev = prevStyles[i]\n    const next = nextStyles[i]\n    if (!prev || !isEqualBlock(prev, next)) {\n      didUpdateStyle = true\n      const mod = modules.find(\n        (m) =>\n          m.url.includes(`type=style&index=${i}`) &&\n          m.url.endsWith(`.${next.lang || 'css'}`) &&\n          !directRequestRE.test(m.url)\n      )\n      if (mod) {\n        affectedModules.add(mod)\n        if (mod.url.includes('&inline')) {\n          affectedModules.add(mainModule)\n        }\n      } else {\n        // new style block - force reload\n        affectedModules.add(mainModule)\n      }\n    }\n  }\n  if (prevStyles.length > nextStyles.length) {\n    // style block removed - force reload\n    affectedModules.add(mainModule)\n  }\n\n  const prevCustoms = prevDescriptor.customBlocks || []\n  const nextCustoms = descriptor.customBlocks || []\n\n  // custom blocks update causes a reload\n  // because the custom block contents is changed and it may be used in JS.\n  if (prevCustoms.length !== nextCustoms.length) {\n    // block removed/added, force reload\n    affectedModules.add(mainModule)\n  } else {\n    for (let i = 0; i < nextCustoms.length; i++) {\n      const prev = prevCustoms[i]\n      const next = nextCustoms[i]\n      if (!prev || !isEqualBlock(prev, next)) {\n        const mod = modules.find((m) =>\n          m.url.includes(`type=${prev.type}&index=${i}`)\n        )\n        if (mod) {\n          affectedModules.add(mod)\n        } else {\n          affectedModules.add(mainModule)\n        }\n      }\n    }\n  }\n\n  const updateType = []\n  if (needRerender) {\n    updateType.push(`template`)\n    // template is inlined into main, add main module instead\n    if (!templateModule) {\n      affectedModules.add(mainModule)\n    } else if (mainModule && !affectedModules.has(mainModule)) {\n      const styleImporters = [...mainModule.importers].filter((m) =>\n        /\\.css($|\\?)/.test(m.url)\n      )\n      styleImporters.forEach((m) => affectedModules.add(m))\n    }\n  }\n  if (didUpdateStyle) {\n    updateType.push(`style`)\n  }\n  return [...affectedModules].filter(Boolean) as ModuleNode[]\n}\n\nexport function isEqualBlock(a: SFCBlock | null, b: SFCBlock | null): boolean {\n  if (!a && !b) return true\n  if (!a || !b) return false\n  // src imports will trigger their own updates\n  if (a.src && b.src && a.src === b.src) return true\n  if (a.content !== b.content) return false\n  const keysA = Object.keys(a.attrs)\n  const keysB = Object.keys(b.attrs)\n  if (keysA.length !== keysB.length) {\n    return false\n  }\n  return keysA.every((key) => a.attrs[key] === b.attrs[key])\n}\n\nexport function isOnlyTemplateChanged(\n  prev: SFCDescriptor,\n  next: SFCDescriptor\n): boolean {\n  return (\n    !hasScriptChanged(prev, next) &&\n    prev.styles.length === next.styles.length &&\n    prev.styles.every((s, i) => isEqualBlock(s, next.styles[i])) &&\n    prev.customBlocks.length === next.customBlocks.length &&\n    prev.customBlocks.every((s, i) => isEqualBlock(s, next.customBlocks[i]))\n  )\n}\n\nfunction hasScriptChanged(prev: SFCDescriptor, next: SFCDescriptor): boolean {\n  if (!isEqualBlock(prev.script, next.script)) {\n    return true\n  }\n  if (!isEqualBlock(prev.scriptSetup, next.scriptSetup)) {\n    return true\n  }\n\n  // vue core #3176\n  // <script setup lang=\"ts\"> prunes non-unused imports\n  // the imports pruning depends on template, so script may need to re-compile\n  // based on template changes\n  const prevResolvedScript = getResolvedScript(prev, false)\n  // this is only available in vue@^3.2.23\n  const prevImports = prevResolvedScript?.imports\n  if (prevImports) {\n    return next.shouldForceReload(prevImports)\n  }\n\n  return false\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import fs from 'node:fs'\nimport { createFilter } from 'vite'\nimport type { Plugin, ViteDevServer } from 'vite'\nimport type {\n  SFCBlock,\n  SFCScriptCompileOptions,\n  SFCStyleCompileOptions,\n  SFCTemplateCompileOptions\n} from 'vue/compiler-sfc'\nimport type * as _compiler from 'vue/compiler-sfc'\nimport { resolveCompiler } from './compiler'\nimport { parseVueRequest } from './utils/query'\nimport { getDescriptor, getSrcDescriptor } from './utils/descriptorCache'\nimport { getResolvedScript } from './script'\nimport { transformMain } from './main'\nimport { handleHotUpdate } from './handleHotUpdate'\nimport { transformTemplateAsModule } from './template'\nimport { transformStyle } from './style'\nimport { NORMALIZER_ID, normalizerCode } from './utils/componentNormalizer'\nimport { HMR_RUNTIME_ID, hmrRuntimeCode } from './utils/hmrRuntime'\n\nexport { parseVueRequest } from './utils/query'\nexport type { VueQuery } from './utils/query'\n\nexport interface Options {\n  include?: string | RegExp | (string | RegExp)[]\n  exclude?: string | RegExp | (string | RegExp)[]\n\n  isProduction?: boolean\n\n  // options to pass on to vue/compiler-sfc\n  script?: Partial<Pick<SFCScriptCompileOptions, 'babelParserPlugins'>>\n  template?: Partial<\n    Pick<\n      SFCTemplateCompileOptions,\n      | 'compiler'\n      | 'compilerOptions'\n      | 'preprocessOptions'\n      | 'transpileOptions'\n      | 'transformAssetUrls'\n      | 'transformAssetUrlsOptions'\n    >\n  >\n  style?: Partial<Pick<SFCStyleCompileOptions, 'trim'>>\n\n  // customElement?: boolean | string | RegExp | (string | RegExp)[]\n  // reactivityTransform?: boolean | string | RegExp | (string | RegExp)[]\n  compiler?: typeof _compiler\n}\n\nexport interface ResolvedOptions extends Options {\n  compiler: typeof _compiler\n  root: string\n  sourceMap: boolean\n  cssDevSourcemap: boolean\n  devServer?: ViteDevServer\n  devToolsEnabled?: boolean\n}\n\nexport default function vuePlugin(rawOptions: Options = {}): Plugin {\n  const {\n    include = /\\.vue$/,\n    exclude\n    // customElement = /\\.ce\\.vue$/,\n    // reactivityTransform = false\n  } = rawOptions\n\n  const filter = createFilter(include, exclude)\n\n  let options: ResolvedOptions = {\n    isProduction: process.env.NODE_ENV === 'production',\n    compiler: null as any, // to be set in buildStart\n    ...rawOptions,\n    include,\n    exclude,\n    // customElement,\n    // reactivityTransform,\n    root: process.cwd(),\n    sourceMap: true,\n    cssDevSourcemap: false,\n    devToolsEnabled: process.env.NODE_ENV !== 'production'\n  }\n\n  return {\n    name: 'vite:vue2',\n\n    handleHotUpdate(ctx) {\n      if (!filter(ctx.file)) {\n        return\n      }\n      return handleHotUpdate(ctx, options)\n    },\n\n    configResolved(config) {\n      options = {\n        ...options,\n        root: config.root,\n        isProduction: config.isProduction,\n        sourceMap: config.command === 'build' ? !!config.build.sourcemap : true,\n        cssDevSourcemap: config.css?.devSourcemap ?? false,\n        devToolsEnabled: !config.isProduction\n      }\n      if (!config.resolve.alias.some(({ find }) => find === 'vue')) {\n        config.resolve.alias.push({\n          find: 'vue',\n          replacement: 'vue/dist/vue.runtime.esm.js'\n        })\n      }\n    },\n\n    configureServer(server) {\n      options.devServer = server\n    },\n\n    buildStart() {\n      options.compiler = options.compiler || resolveCompiler(options.root)\n    },\n\n    async resolveId(id) {\n      // component export helper\n      if (id === NORMALIZER_ID || id === HMR_RUNTIME_ID) {\n        return id\n      }\n      // serve sub-part requests (*?vue) as virtual modules\n      if (parseVueRequest(id).query.vue) {\n        return id\n      }\n    },\n\n    load(id, opt) {\n      const ssr = opt?.ssr === true\n      if (id === NORMALIZER_ID) {\n        return normalizerCode\n      }\n      if (id === HMR_RUNTIME_ID) {\n        return hmrRuntimeCode\n      }\n\n      const { filename, query } = parseVueRequest(id)\n      // select corresponding block for sub-part virtual modules\n      if (query.vue) {\n        if (query.src) {\n          return fs.readFileSync(filename, 'utf-8')\n        }\n        const descriptor = getDescriptor(filename, options)!\n        let block: SFCBlock | null | undefined\n        if (query.type === 'script') {\n          // handle <scrip> + <script setup> merge via compileScript()\n          block = getResolvedScript(descriptor, ssr)\n        } else if (query.type === 'template') {\n          block = descriptor.template!\n        } else if (query.type === 'style') {\n          block = descriptor.styles[query.index!]\n        } else if (query.index != null) {\n          block = descriptor.customBlocks[query.index]\n        }\n        if (block) {\n          return {\n            code: block.content,\n            map: block.map as any\n          }\n        }\n      }\n    },\n\n    async transform(code, id, opt) {\n      const ssr = opt?.ssr === true\n      const { filename, query } = parseVueRequest(id)\n      if (query.raw) {\n        return\n      }\n      if (!filter(filename) && !query.vue) {\n        // if (\n        //   !query.vue &&\n        //   refTransformFilter(filename) &&\n        //   options.compiler.shouldTransformRef(code)\n        // ) {\n        //   return options.compiler.transformRef(code, {\n        //     filename,\n        //     sourceMap: true\n        //   })\n        // }\n        return\n      }\n\n      if (!query.vue) {\n        // main request\n        return transformMain(code, filename, options, this, ssr)\n      } else {\n        // sub block request\n        const descriptor = query.src\n          ? getSrcDescriptor(filename, query)!\n          : getDescriptor(filename, options)!\n\n        if (query.type === 'template') {\n          return {\n            code: await transformTemplateAsModule(\n              code,\n              descriptor,\n              options,\n              this,\n              ssr\n            ),\n            map: {\n              mappings: ''\n            }\n          }\n        } else if (query.type === 'style') {\n          return transformStyle(\n            code,\n            descriptor,\n            Number(query.index),\n            options,\n            this,\n            filename\n          )\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main.ts",
    "content": "import path from 'node:path'\nimport type { SFCBlock, SFCDescriptor } from 'vue/compiler-sfc'\nimport type { PluginContext, TransformPluginContext } from 'rollup'\nimport type { RawSourceMap } from 'source-map'\nimport { transformWithEsbuild } from 'vite'\nimport {\n  createDescriptor,\n  getPrevDescriptor,\n  setSrcDescriptor\n} from './utils/descriptorCache'\nimport { resolveScript } from './script'\nimport { transformTemplateInMain } from './template'\nimport { isOnlyTemplateChanged } from './handleHotUpdate'\nimport { createRollupError } from './utils/error'\nimport type { ResolvedOptions } from '.'\nimport { NORMALIZER_ID } from './utils/componentNormalizer'\nimport { HMR_RUNTIME_ID } from './utils/hmrRuntime'\n\nexport async function transformMain(\n  code: string,\n  filename: string,\n  options: ResolvedOptions,\n  pluginContext: TransformPluginContext,\n  ssr: boolean\n  // asCustomElement: boolean\n) {\n  const { devServer, isProduction, devToolsEnabled } = options\n\n  // prev descriptor is only set and used for hmr\n  const prevDescriptor = getPrevDescriptor(filename)\n  const { descriptor, errors } = createDescriptor(filename, code, options)\n\n  if (errors.length) {\n    errors.forEach(error =>\n      pluginContext.error(createRollupError(filename, error))\n    )\n    return null\n  }\n\n  // feature information\n  const hasScoped = descriptor.styles.some(s => s.scoped)\n  const hasCssModules = descriptor.styles.some(s => s.module)\n  const hasFunctional =\n    descriptor.template && descriptor.template.attrs.functional\n\n  // script\n  const { code: scriptCode, map: scriptMap } = await genScriptCode(\n    descriptor,\n    options,\n    pluginContext,\n    ssr\n  )\n\n  // template\n  const templateCode = await genTemplateCode(\n    descriptor,\n    options,\n    pluginContext,\n    ssr\n  )\n\n  // styles\n  const stylesCode = await genStyleCode(descriptor, pluginContext)\n\n  // custom blocks\n  const customBlocksCode = await genCustomBlockCode(descriptor, pluginContext)\n\n  const output: string[] = [\n    scriptCode,\n    templateCode,\n    stylesCode,\n    customBlocksCode\n  ]\n\n  output.push(\n    `/* normalize component */\nimport __normalizer from \"${NORMALIZER_ID}\"\nvar __component__ = /*#__PURE__*/__normalizer(\n  _sfc_main,\n  _sfc_render,\n  _sfc_staticRenderFns,\n  ${hasFunctional ? 'true' : 'false'},\n  ${hasCssModules ? `_sfc_injectStyles` : `null`},\n  ${hasScoped ? JSON.stringify(descriptor.id) : 'null'},\n  null,\n  null\n)`\n  )\n\n  if (devToolsEnabled || (devServer && !isProduction)) {\n    // expose filename during serve for devtools to pickup\n    output.push(\n      `__component__.options.__file = ${JSON.stringify(\n        isProduction ? path.basename(filename) : filename\n      )}`\n    )\n  }\n\n  // HMR\n  if (\n    devServer &&\n    devServer.config.server.hmr !== false &&\n    !ssr &&\n    !isProduction\n  ) {\n    const id = JSON.stringify(descriptor.id)\n    output.push(\n      `import __VUE_HMR_RUNTIME__ from \"${HMR_RUNTIME_ID}\"`,\n      `if (!__VUE_HMR_RUNTIME__.isRecorded(${id})) {`,\n      `  __VUE_HMR_RUNTIME__.createRecord(${id}, __component__.options)`,\n      `}`\n    )\n    // check if the template is the only thing that changed\n    if (\n      hasFunctional ||\n      (prevDescriptor && isOnlyTemplateChanged(prevDescriptor, descriptor))\n    ) {\n      output.push(`export const _rerender_only = true`)\n    }\n    output.push(\n      `import.meta.hot.accept(mod => {`,\n      `  if (!mod) return`,\n      `  const { default: updated, _rerender_only } = mod`,\n      `  if (_rerender_only) {`,\n      `    __VUE_HMR_RUNTIME__.rerender(${id}, updated)`,\n      `  } else {`,\n      `    __VUE_HMR_RUNTIME__.reload(${id}, updated)`,\n      `  }`,\n      `})`\n    )\n  }\n\n  // SSR module registration by wrapping user setup\n  if (ssr) {\n    // TODO\n  }\n\n  let resolvedMap: RawSourceMap | undefined = scriptMap\n\n  output.push(`export default __component__.exports`)\n\n  // handle TS transpilation\n  let resolvedCode = output.join('\\n')\n  if (\n    (descriptor.script?.lang === 'ts' ||\n      descriptor.scriptSetup?.lang === 'ts') &&\n    !descriptor.script?.src // only normal script can have src\n  ) {\n    const { code, map } = await transformWithEsbuild(\n      resolvedCode,\n      filename,\n      {\n        loader: 'ts',\n        target: 'esnext',\n        sourcemap: options.sourceMap\n      },\n      resolvedMap\n    )\n    resolvedCode = code\n    resolvedMap = resolvedMap ? (map as any) : resolvedMap\n  }\n\n  return {\n    code: resolvedCode,\n    map: resolvedMap || {\n      mappings: ''\n    },\n    meta: {\n      vite: {\n        lang: descriptor.script?.lang || descriptor.scriptSetup?.lang || 'js'\n      }\n    }\n  }\n}\n\nasync function genTemplateCode(\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n  pluginContext: PluginContext,\n  ssr: boolean\n) {\n  const template = descriptor.template\n\n  if (!template) {\n    return 'const _sfc_render = null; const _sfc_staticRenderFns = null'\n  }\n\n  const hasScoped = descriptor.styles.some(style => style.scoped)\n\n  // If the template is not using pre-processor AND is not using external src,\n  // compile and inline it directly in the main module. When served in vite this\n  // saves an extra request per SFC which can improve load performance.\n  if (!template.lang && !template.src) {\n    return transformTemplateInMain(\n      template.content,\n      descriptor,\n      options,\n      pluginContext,\n      ssr\n    )\n  } else {\n    if (template.src) {\n      await linkSrcToDescriptor(\n        template.src,\n        descriptor,\n        pluginContext,\n        hasScoped\n      )\n    }\n    const src = template.src || descriptor.filename\n    const srcQuery = template.src\n      ? hasScoped\n        ? `&src=${descriptor.id}`\n        : '&src=true'\n      : ''\n    const scopedQuery = hasScoped ? `&scoped=${descriptor.id}` : ``\n    const attrsQuery = attrsToQuery(template.attrs, 'js', true)\n    const query = `?vue&type=template${srcQuery}${scopedQuery}${attrsQuery}`\n    const request = JSON.stringify(src + query)\n    return `import { render as _sfc_render, staticRenderFns as _sfc_staticRenderFns } from ${request}`\n  }\n}\n\nasync function genScriptCode(\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n  pluginContext: PluginContext,\n  ssr: boolean\n): Promise<{\n  code: string\n  map: RawSourceMap | undefined\n}> {\n  let scriptCode = `const _sfc_main = {}`\n  let map: RawSourceMap | undefined\n\n  const script = resolveScript(descriptor, options, ssr)\n  if (script) {\n    // If the script is js/ts and has no external src, it can be directly placed\n    // in the main module.\n    if (\n      (!script.lang || (script.lang === 'ts' && options.devServer)) &&\n      !script.src\n    ) {\n      const userPlugins = options.script?.babelParserPlugins || []\n      const defaultPlugins =\n        script.lang === 'ts'\n          ? userPlugins.includes('decorators')\n            ? (['typescript'] as const)\n            : (['typescript', 'decorators-legacy'] as const)\n          : []\n      scriptCode = options.compiler.rewriteDefault(\n        script.content,\n        '_sfc_main',\n        [...defaultPlugins, ...userPlugins]\n      )\n      map = script.map\n    } else {\n      if (script.src) {\n        await linkSrcToDescriptor(script.src, descriptor, pluginContext, false)\n      }\n      const src = script.src || descriptor.filename\n      const langFallback = (script.src && path.extname(src).slice(1)) || 'js'\n      const attrsQuery = attrsToQuery(script.attrs, langFallback)\n      const srcQuery = script.src ? `&src=true` : ``\n      const query = `?vue&type=script${srcQuery}${attrsQuery}`\n      const request = JSON.stringify(src + query)\n      scriptCode =\n        `import _sfc_main from ${request}\\n` + `export * from ${request}` // support named exports\n    }\n  }\n  return {\n    code: scriptCode,\n    map\n  }\n}\n\nasync function genStyleCode(\n  descriptor: SFCDescriptor,\n  pluginContext: PluginContext\n) {\n  let stylesCode = ``\n  let cssModulesMap: Record<string, string> | undefined\n  if (descriptor.styles.length) {\n    for (let i = 0; i < descriptor.styles.length; i++) {\n      const style = descriptor.styles[i]\n      if (style.src) {\n        await linkSrcToDescriptor(\n          style.src,\n          descriptor,\n          pluginContext,\n          style.scoped\n        )\n      }\n      const src = style.src || descriptor.filename\n      // do not include module in default query, since we use it to indicate\n      // that the module needs to export the modules json\n      const attrsQuery = attrsToQuery(style.attrs, 'css')\n      const srcQuery = style.src\n        ? style.scoped\n          ? `&src=${descriptor.id}`\n          : '&src=true'\n        : ''\n      const directQuery = `` // asCustomElement ? `&inline` : ``\n      const scopedQuery = style.scoped ? `&scoped=${descriptor.id}` : ``\n      const query = `?vue&type=style&index=${i}${srcQuery}${directQuery}${scopedQuery}`\n      const styleRequest = src + query + attrsQuery\n      if (style.module) {\n        const [importCode, nameMap] = genCSSModulesCode(\n          i,\n          styleRequest,\n          style.module\n        )\n        stylesCode += importCode\n        Object.assign((cssModulesMap ||= {}), nameMap)\n      } else {\n        stylesCode += `\\nimport ${JSON.stringify(styleRequest)}`\n      }\n      // TODO SSR critical CSS collection\n    }\n  }\n  if (cssModulesMap) {\n    const mappingCode =\n      Object.entries(cssModulesMap).reduce(\n        (code, [key, value]) => code + `\"${key}\":${value},\\n`,\n        '{\\n'\n      ) + '}'\n    stylesCode += `\\nconst __cssModules = ${mappingCode}`\n    stylesCode += `\\nfunction _sfc_injectStyles(ctx) {\n      for (var key in __cssModules) {\n        this[key] = __cssModules[key]\n      }\n    }`\n  }\n  return stylesCode\n}\n\nfunction genCSSModulesCode(\n  index: number,\n  request: string,\n  moduleName: string | boolean\n): [importCode: string, nameMap: Record<string, string>] {\n  const styleVar = `style${index}`\n  const exposedName = typeof moduleName === 'string' ? moduleName : '$style'\n  // inject `.module` before extension so vite handles it as css module\n  const moduleRequest = request.replace(/\\.(\\w+)$/, '.module.$1')\n  return [\n    `\\nimport ${styleVar} from ${JSON.stringify(moduleRequest)}`,\n    { [exposedName]: styleVar }\n  ]\n}\n\nasync function genCustomBlockCode(\n  descriptor: SFCDescriptor,\n  pluginContext: PluginContext\n) {\n  let code = ''\n  for (let index = 0; index < descriptor.customBlocks.length; index++) {\n    const block = descriptor.customBlocks[index]\n    if (block.src) {\n      await linkSrcToDescriptor(block.src, descriptor, pluginContext, false)\n    }\n    const src = block.src || descriptor.filename\n    const attrsQuery = attrsToQuery(block.attrs, block.type)\n    const srcQuery = block.src ? `&src=true` : ``\n    const query = `?vue&type=${block.type}&index=${index}${srcQuery}${attrsQuery}`\n    const request = JSON.stringify(src + query)\n    code += `import block${index} from ${request}\\n`\n    code += `if (typeof block${index} === 'function') block${index}(_sfc_main)\\n`\n  }\n  return code\n}\n\n/**\n * For blocks with src imports, it is important to link the imported file\n * with its owner SFC descriptor so that we can get the information about\n * the owner SFC when compiling that file in the transform phase.\n */\nasync function linkSrcToDescriptor(\n  src: string,\n  descriptor: SFCDescriptor,\n  pluginContext: PluginContext,\n  scoped?: boolean\n) {\n  const srcFile =\n    (await pluginContext.resolve(src, descriptor.filename))?.id || src\n  // #1812 if the src points to a dep file, the resolved id may contain a\n  // version query.\n  setSrcDescriptor(srcFile.replace(/\\?.*$/, ''), descriptor, scoped)\n}\n\n// these are built-in query parameters so should be ignored\n// if the user happen to add them as attrs\nconst ignoreList = ['id', 'index', 'src', 'type', 'lang', 'module', 'scoped']\n\nfunction attrsToQuery(\n  attrs: SFCBlock['attrs'],\n  langFallback?: string,\n  forceLangFallback = false\n): string {\n  let query = ``\n  for (const name in attrs) {\n    const value = attrs[name]\n    if (!ignoreList.includes(name)) {\n      query += `&${encodeURIComponent(name)}${\n        value ? `=${encodeURIComponent(value)}` : ``\n      }`\n    }\n  }\n  if (langFallback || attrs.lang) {\n    query +=\n      `lang` in attrs\n        ? forceLangFallback\n          ? `&lang.${langFallback}`\n          : `&lang.${attrs.lang}`\n        : `&lang.${langFallback}`\n  }\n  return query\n}\n"
  },
  {
    "path": "src/script.ts",
    "content": "import type { SFCDescriptor, SFCScriptBlock } from 'vue/compiler-sfc'\nimport type { ResolvedOptions } from '.'\n\n// ssr and non ssr builds would output different script content\nconst clientCache = new WeakMap<SFCDescriptor, SFCScriptBlock | null>()\nconst ssrCache = new WeakMap<SFCDescriptor, SFCScriptBlock | null>()\n\nexport function getResolvedScript(\n  descriptor: SFCDescriptor,\n  ssr: boolean\n): SFCScriptBlock | null | undefined {\n  return (ssr ? ssrCache : clientCache).get(descriptor)\n}\n\nexport function setResolvedScript(\n  descriptor: SFCDescriptor,\n  script: SFCScriptBlock,\n  ssr: boolean\n): void {\n  ;(ssr ? ssrCache : clientCache).set(descriptor, script)\n}\n\nexport function resolveScript(\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n  ssr: boolean\n): SFCScriptBlock | null {\n  if (!descriptor.script && !descriptor.scriptSetup) {\n    return null\n  }\n\n  const cacheToUse = ssr ? ssrCache : clientCache\n  const cached = cacheToUse.get(descriptor)\n  if (cached) {\n    return cached\n  }\n\n  const resolved = options.compiler.compileScript(descriptor, {\n    ...options.script,\n    id: descriptor.id,\n    isProd: options.isProduction,\n    sourceMap: options.sourceMap\n  })\n\n  cacheToUse.set(descriptor, resolved)\n  return resolved\n}\n"
  },
  {
    "path": "src/style.ts",
    "content": "import type { SFCDescriptor } from 'vue/compiler-sfc'\nimport type { ExistingRawSourceMap, TransformPluginContext } from 'rollup'\nimport type { RawSourceMap } from 'source-map'\nimport { formatPostcssSourceMap } from 'vite'\nimport type { ResolvedOptions } from '.'\n\nexport async function transformStyle(\n  code: string,\n  descriptor: SFCDescriptor,\n  index: number,\n  options: ResolvedOptions,\n  pluginContext: TransformPluginContext,\n  filename: string\n) {\n  const block = descriptor.styles[index]\n  // vite already handles pre-processors and CSS module so this is only\n  // applying SFC-specific transforms like scoped mode and CSS vars rewrite (v-bind(var))\n  const result = await options.compiler.compileStyleAsync({\n    ...options.style,\n    filename: descriptor.filename,\n    id: `data-v-${descriptor.id}`,\n    isProd: options.isProduction,\n    source: code,\n    scoped: !!block.scoped,\n    ...(options.cssDevSourcemap\n      ? {\n          postcssOptions: {\n            map: {\n              from: filename,\n              inline: false,\n              annotation: false\n            }\n          }\n        }\n      : {})\n  })\n\n  if (result.errors.length) {\n    result.errors.forEach((error: any) => {\n      if (error.line && error.column) {\n        error.loc = {\n          file: descriptor.filename,\n          line: error.line + getLine(descriptor.source, block.start),\n          column: error.column\n        }\n      }\n      pluginContext.error(error)\n    })\n    return null\n  }\n\n  const map = result.map\n    ? await formatPostcssSourceMap(\n        // version property of result.map is declared as string\n        // but actually it is a number\n        result.map as Omit<RawSourceMap, 'version'> as ExistingRawSourceMap,\n        filename\n      )\n    : ({ mappings: '' } as any)\n\n  return {\n    code: result.code,\n    map: map\n  }\n}\n\nfunction getLine(source: string, start: number) {\n  const lines = source.split(/\\r?\\n/g)\n  let cur = 0\n  for (let i = 0; i < lines.length; i++) {\n    cur += lines[i].length\n    if (cur >= start) {\n      return i\n    }\n  }\n}\n"
  },
  {
    "path": "src/template.ts",
    "content": "// @ts-ignore\nimport hash from 'hash-sum'\nimport type { SFCDescriptor, SFCTemplateCompileOptions } from 'vue/compiler-sfc'\nimport type { PluginContext, TransformPluginContext } from 'rollup'\nimport { getResolvedScript } from './script'\nimport { createRollupError } from './utils/error'\nimport type { ResolvedOptions } from '.'\nimport path from 'node:path'\nimport slash from 'slash'\nimport { HMR_RUNTIME_ID } from './utils/hmrRuntime'\n\nexport async function transformTemplateAsModule(\n  code: string,\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n  pluginContext: TransformPluginContext,\n  ssr: boolean\n): Promise<string> {\n  let returnCode = compile(code, descriptor, options, pluginContext, ssr)\n  if (\n    options.devServer &&\n    options.devServer.config.server.hmr !== false &&\n    !ssr &&\n    !options.isProduction\n  ) {\n    returnCode += `\\nimport __VUE_HMR_RUNTIME__ from \"${HMR_RUNTIME_ID}\"`\n    returnCode += `\\nimport.meta.hot.accept((updated) => {\n      __VUE_HMR_RUNTIME__.rerender(${JSON.stringify(descriptor.id)}, updated)\n    })`\n  }\n\n  return returnCode + `\\nexport { render, staticRenderFns }`\n}\n\n/**\n * transform the template directly in the main SFC module\n */\nexport function transformTemplateInMain(\n  code: string,\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n  pluginContext: PluginContext,\n  ssr: boolean\n): string {\n  return compile(code, descriptor, options, pluginContext, ssr)\n    .replace(/var (render|staticRenderFns) =/g, 'var _sfc_$1 =')\n    .replace(/(render._withStripped)/, '_sfc_$1')\n}\n\nexport function compile(\n  code: string,\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n  pluginContext: PluginContext,\n  ssr: boolean\n): string {\n  const filename = descriptor.filename\n  const result = options.compiler.compileTemplate({\n    ...resolveTemplateCompilerOptions(descriptor, options, ssr)!,\n    source: code\n  })\n\n  if (result.errors.length) {\n    result.errors.forEach((error) =>\n      pluginContext.error(\n        typeof error === 'string'\n          ? { id: filename, message: error }\n          : createRollupError(filename, error)\n      )\n    )\n  }\n\n  if (result.tips.length) {\n    result.tips.forEach((tip) =>\n      pluginContext.warn({\n        id: filename,\n        message: typeof tip === 'string' ? tip : tip.msg\n      })\n    )\n  }\n\n  return transformRequireToImport(result.code)\n}\n\nfunction resolveTemplateCompilerOptions(\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n  ssr: boolean\n): Omit<SFCTemplateCompileOptions, 'source'> | undefined {\n  const block = descriptor.template\n  if (!block) {\n    return\n  }\n  const resolvedScript = getResolvedScript(descriptor, ssr)\n  const hasScoped = descriptor.styles.some((s) => s.scoped)\n  const { id, filename } = descriptor\n\n  let preprocessOptions = block.lang && options.template?.preprocessOptions\n  if (block.lang === 'pug') {\n    preprocessOptions = {\n      doctype: 'html',\n      ...preprocessOptions\n    }\n  }\n\n  const transformAssetUrls = options.template?.transformAssetUrls ?? true\n  let assetUrlOptions\n  if (options.devServer) {\n    // during dev, inject vite base so that compiler-sfc can transform\n    // relative paths directly to absolute paths without incurring an extra import\n    // request\n    if (filename.startsWith(options.root)) {\n      assetUrlOptions = {\n        base:\n          (options.devServer.config.server?.origin ?? '') +\n          options.devServer.config.base +\n          slash(path.relative(options.root, path.dirname(filename)))\n      }\n    }\n  } else if (transformAssetUrls !== false) {\n    // build: force all asset urls into import requests so that they go through\n    // the assets plugin for asset registration\n    assetUrlOptions = {\n      includeAbsolute: true\n    }\n  }\n\n  return {\n    transformAssetUrls,\n    ...options.template,\n    filename,\n    isProduction: options.isProduction,\n    isFunctional: !!block.attrs.functional,\n    optimizeSSR: ssr,\n    transformAssetUrlsOptions: {\n      ...assetUrlOptions,\n      ...options.template?.transformAssetUrlsOptions\n    },\n    preprocessLang: block.lang,\n    preprocessOptions,\n    bindings: resolvedScript ? resolvedScript.bindings : undefined,\n    prettify: false,\n    compilerOptions: {\n      whitespace: 'condense',\n      outputSourceRange: true,\n      ...options.template?.compilerOptions,\n      scopeId: hasScoped ? `data-v-${id}` : undefined\n    }\n  }\n}\n\nfunction transformRequireToImport(code: string): string {\n  const imports: Record<string, string> = {}\n  let strImports = ''\n\n  code = code.replace(\n    /require\\((\"(?:[^\"\\\\]|\\\\.)+\"|'(?:[^'\\\\]|\\\\.)+')\\)/g,\n    (_, name): any => {\n      if (!(name in imports)) {\n        // #81 compat unicode assets name\n        imports[name] = `__$_require_${hash(name)}__`\n        strImports += `import ${imports[name]} from ${name}\\n`\n      }\n\n      return imports[name]\n    }\n  )\n\n  return strImports + code\n}\n"
  },
  {
    "path": "src/utils/componentNormalizer.ts",
    "content": "export const NORMALIZER_ID = '\\0plugin-vue2:normalizer'\n\n// IMPORTANT: Do NOT use ES2015 features in this file (except for modules).\n// This module is a runtime utility for cleaner component module output and will\n// be included in the final webpack user bundle.\nexport const normalizerCode = `\nexport default function normalizeComponent (\n    scriptExports,\n    render,\n    staticRenderFns,\n    functionalTemplate,\n    injectStyles,\n    scopeId,\n    moduleIdentifier, /* server only */\n    shadowMode /* vue-cli only */\n) {\n  // Vue.extend constructor export interop\n  var options = typeof scriptExports === 'function'\n      ? scriptExports.options\n      : scriptExports\n\n  // render functions\n  if (render) {\n    options.render = render\n    options.staticRenderFns = staticRenderFns\n    options._compiled = true\n  }\n\n  // functional template\n  if (functionalTemplate) {\n    options.functional = true\n  }\n\n  // scopedId\n  if (scopeId) {\n    options._scopeId = 'data-v-' + scopeId\n  }\n\n  var hook\n  if (moduleIdentifier) { // server build\n    hook = function (context) {\n      // 2.3 injection\n      context =\n          context || // cached call\n          (this.$vnode && this.$vnode.ssrContext) || // stateful\n          (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional\n      // 2.2 with runInNewContext: true\n      if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {\n        context = __VUE_SSR_CONTEXT__\n      }\n      // inject component styles\n      if (injectStyles) {\n        injectStyles.call(this, context)\n      }\n      // register component module identifier for async chunk inference\n      if (context && context._registeredComponents) {\n        context._registeredComponents.add(moduleIdentifier)\n      }\n    }\n    // used by ssr in case component is cached and beforeCreate\n    // never gets called\n    options._ssrRegister = hook\n  } else if (injectStyles) {\n    hook = shadowMode\n        ? function () {\n          injectStyles.call(\n              this,\n              (options.functional ? this.parent : this).$root.$options.shadowRoot\n          )\n        }\n        : injectStyles\n  }\n\n  if (hook) {\n    if (options.functional) {\n      // for template-only hot-reload because in that case the render fn doesn't\n      // go through the normalizer\n      options._injectStyles = hook\n      // register for functional component in vue file\n      var originalRender = options.render\n      options.render = function renderWithStyleInjection (h, context) {\n        hook.call(context)\n        return originalRender(h, context)\n      }\n    } else {\n      // inject component registration as beforeCreate hook\n      var existing = options.beforeCreate\n      options.beforeCreate = existing\n          ? [].concat(existing, hook)\n          : [hook]\n    }\n  }\n\n  return {\n    exports: scriptExports,\n    options: options\n  }\n}`\n"
  },
  {
    "path": "src/utils/descriptorCache.ts",
    "content": "import fs from 'node:fs'\nimport path from 'node:path'\nimport { createHash } from 'node:crypto'\nimport slash from 'slash'\nimport type { SFCDescriptor } from 'vue/compiler-sfc'\nimport type { ResolvedOptions, VueQuery } from '..'\n\n// compiler-sfc should be exported so it can be re-used\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor\n  errors: Error[]\n}\n\nconst cache = new Map<string, SFCDescriptor>()\nconst prevCache = new Map<string, SFCDescriptor | undefined>()\n\nexport function createDescriptor(\n  filename: string,\n  source: string,\n  { root, isProduction, sourceMap, compiler }: ResolvedOptions\n): SFCParseResult {\n  let descriptor: SFCDescriptor\n  let errors: any[] = []\n  try {\n    descriptor = compiler.parse({\n      source,\n      filename,\n      sourceMap\n    })\n  } catch (e) {\n    errors = [e]\n    descriptor = compiler.parse({ source: ``, filename })\n  }\n\n  // ensure the path is normalized in a way that is consistent inside\n  // project (relative to root) and on different systems.\n  const normalizedPath = slash(path.normalize(path.relative(root, filename)))\n  descriptor.id = getHash(normalizedPath + (isProduction ? source : ''))\n\n  cache.set(filename, descriptor)\n  return { descriptor, errors }\n}\n\nexport function getPrevDescriptor(filename: string): SFCDescriptor | undefined {\n  return prevCache.get(filename)\n}\n\nexport function setPrevDescriptor(\n  filename: string,\n  entry: SFCDescriptor\n): void {\n  prevCache.set(filename, entry)\n}\n\nexport function getDescriptor(\n  filename: string,\n  options: ResolvedOptions,\n  createIfNotFound = true\n): SFCDescriptor | undefined {\n  if (cache.has(filename)) {\n    return cache.get(filename)!\n  }\n  if (createIfNotFound) {\n    const { descriptor, errors } = createDescriptor(\n      filename,\n      fs.readFileSync(filename, 'utf-8'),\n      options\n    )\n    if (errors.length) {\n      throw errors[0]\n    }\n    return descriptor\n  }\n}\n\nexport function getSrcDescriptor(\n  filename: string,\n  query: VueQuery\n): SFCDescriptor {\n  if (query.scoped) {\n    return cache.get(`${filename}?src=${query.src}`)!\n  }\n  return cache.get(filename)!\n}\n\nexport function setSrcDescriptor(\n  filename: string,\n  entry: SFCDescriptor,\n  scoped?: boolean\n): void {\n  if (scoped) {\n    // if multiple Vue files use the same src file, they will be overwritten\n    // should use other key\n    cache.set(`${filename}?src=${entry.id}`, entry)\n    return\n  }\n  cache.set(filename, entry)\n}\n\nfunction getHash(text: string): string {\n  return createHash('sha256').update(text).digest('hex').substring(0, 8)\n}\n"
  },
  {
    "path": "src/utils/error.ts",
    "content": "import type { RollupError } from 'rollup'\nimport { WarningMessage } from 'vue/compiler-sfc'\n\nexport function createRollupError(\n  id: string,\n  error: Error | WarningMessage\n): RollupError {\n  if ('msg' in error) {\n    return {\n      id,\n      plugin: 'vue',\n      message: error.msg,\n      name: 'vue-compiler-error'\n    }\n  } else {\n    return {\n      id,\n      plugin: 'vue',\n      message: error.message,\n      name: error.name,\n      stack: error.stack\n    }\n  }\n}\n"
  },
  {
    "path": "src/utils/hmrRuntime.ts",
    "content": "export const HMR_RUNTIME_ID = '\\0plugin-vue2:hmr-runtime'\n\nexport const hmrRuntimeCode = `\nvar __VUE_HMR_RUNTIME__ = Object.create(null)\nvar map = Object.create(null)\n\n__VUE_HMR_RUNTIME__.createRecord = function (id, options) {\n  if(map[id]) { return }\n\n  var Ctor = null\n  if (typeof options === 'function') {\n    Ctor = options\n    options = Ctor.options\n  }\n  makeOptionsHot(id, options)\n  map[id] = {\n    Ctor: Ctor,\n    options: options,\n    instances: []\n  }\n}\n\n__VUE_HMR_RUNTIME__.isRecorded = function (id) {\n  return typeof map[id] !== 'undefined'\n}\n\nfunction makeOptionsHot(id, options) {\n  if (options.functional) {\n    var render = options.render\n    options.render = function (h, ctx) {\n      var instances = map[id].instances\n      if (ctx && instances.indexOf(ctx.parent) < 0) {\n        instances.push(ctx.parent)\n      }\n      return render(h, ctx)\n    }\n  } else {\n    injectHook(options, 'beforeCreate', function() {\n      var record = map[id]\n      if (!record.Ctor) {\n        record.Ctor = this.constructor\n      }\n      record.instances.push(this)\n    })\n    injectHook(options, 'beforeDestroy', function() {\n      var instances = map[id].instances\n      instances.splice(instances.indexOf(this), 1)\n    })\n  }\n}\n\nfunction injectHook(options, name, hook) {\n  var existing = options[name]\n  options[name] = existing\n    ? Array.isArray(existing) ? existing.concat(hook) : [existing, hook]\n    : [hook]\n}\n\nfunction tryWrap(fn) {\n  return function (id, arg) {\n    try {\n      fn(id, arg)\n    } catch (e) {\n      console.error(e)\n      console.warn(\n        'Something went wrong during Vue component hot-reload. Full reload required.'\n      )\n    }\n  }\n}\n\nfunction updateOptions (oldOptions, newOptions) {\n  for (var key in oldOptions) {\n    if (!(key in newOptions)) {\n      delete oldOptions[key]\n    }\n  }\n  for (var key$1 in newOptions) {\n    oldOptions[key$1] = newOptions[key$1]\n  }\n}\n\n__VUE_HMR_RUNTIME__.rerender = tryWrap(function (id, options) {\n  var record = map[id]\n  if (!options) {\n    record.instances.slice().forEach(function (instance) {\n      instance.$forceUpdate()\n    })\n    return\n  }\n  if (typeof options === 'function') {\n    options = options.options\n  }\n  if(record.functional){\n    record.render = options.render\n    record.staticRenderFns = options.staticRenderFns\n    __VUE_HMR_RUNTIME__.reload(id, record)\n    return\n  }\n  if (record.Ctor) {\n    record.Ctor.options.render = options.render\n    record.Ctor.options.staticRenderFns = options.staticRenderFns\n    record.instances.slice().forEach(function (instance) {\n      instance.$options.render = options.render\n      instance.$options.staticRenderFns = options.staticRenderFns\n      // reset static trees\n      // pre 2.5, all static trees are cached together on the instance\n      if (instance._staticTrees) {\n        instance._staticTrees = []\n      }\n      // 2.5.0\n      if (Array.isArray(record.Ctor.options.cached)) {\n        record.Ctor.options.cached = []\n      }\n      // 2.5.3\n      if (Array.isArray(instance.$options.cached)) {\n        instance.$options.cached = []\n      }\n\n      // post 2.5.4: v-once trees are cached on instance._staticTrees.\n      // Pure static trees are cached on the staticRenderFns array\n      // (both already reset above)\n\n      // 2.6: temporarily mark rendered scoped slots as unstable so that\n      // child components can be forced to update\n      var restore = patchScopedSlots(instance)\n      instance.$forceUpdate()\n      instance.$nextTick(restore)\n    })\n  } else {\n    // functional or no instance created yet\n    record.options.render = options.render\n    record.options.staticRenderFns = options.staticRenderFns\n\n    // handle functional component re-render\n    if (record.options.functional) {\n      // rerender with full options\n      if (Object.keys(options).length > 2) {\n        updateOptions(record.options, options)\n      } else {\n        // template-only rerender.\n        // need to inject the style injection code for CSS modules\n        // to work properly.\n        var injectStyles = record.options._injectStyles\n        if (injectStyles) {\n          var render = options.render\n          record.options.render = function (h, ctx) {\n            injectStyles.call(ctx)\n            return render(h, ctx)\n          }\n        }\n      }\n      record.options._Ctor = null\n      // 2.5.3\n      if (Array.isArray(record.options.cached)) {\n        record.options.cached = []\n      }\n      record.instances.slice().forEach(function (instance) {\n        instance.$forceUpdate()\n      })\n    }\n  }\n})\n\n__VUE_HMR_RUNTIME__.reload = tryWrap(function (id, options) {\n  var record = map[id]\n  if (options) {\n    if (typeof options === 'function') {\n      options = options.options\n    }\n    makeOptionsHot(id, options)\n    if (record.Ctor) {\n      var newCtor = record.Ctor.super.extend(options)\n      // prevent record.options._Ctor from being overwritten accidentally\n      newCtor.options._Ctor = record.options._Ctor\n      record.Ctor.options = newCtor.options\n      record.Ctor.cid = newCtor.cid\n      record.Ctor.prototype = newCtor.prototype\n      if (newCtor.release) {\n        // temporary global mixin strategy used in < 2.0.0-alpha.6\n        newCtor.release()\n      }\n    } else {\n      updateOptions(record.options, options)\n    }\n  }\n  record.instances.slice().forEach(function (instance) {\n    if (instance.$vnode && instance.$vnode.context) {\n      instance.$vnode.context.$forceUpdate()\n    } else {\n      console.warn(\n        'Root or manually mounted instance modified. Full reload required.'\n      )\n    }\n  })\n})\n\n// 2.6 optimizes template-compiled scoped slots and skips updates if child\n// only uses scoped slots. We need to patch the scoped slots resolving helper\n// to temporarily mark all scoped slots as unstable in order to force child\n// updates.\nfunction patchScopedSlots (instance) {\n  if (!instance._u) { return }\n  // https://github.com/vuejs/vue/blob/dev/src/core/instance/render-helpers/resolve-scoped-slots.js\n  var original = instance._u\n  instance._u = function (slots) {\n    try {\n      // 2.6.4 ~ 2.6.6\n      return original(slots, true)\n    } catch (e) {\n      // 2.5 / >= 2.6.7\n      return original(slots, null, true)\n    }\n  }\n  return function () {\n    instance._u = original\n  }\n}\nexport default __VUE_HMR_RUNTIME__\n`\n"
  },
  {
    "path": "src/utils/query.ts",
    "content": "export interface VueQuery {\n  vue?: boolean\n  src?: string\n  type?: 'script' | 'template' | 'style' | 'custom'\n  index?: number\n  lang?: string\n  raw?: boolean\n  scoped?: boolean\n}\n\nexport function parseVueRequest(id: string): {\n  filename: string\n  query: VueQuery\n} {\n  const [filename, rawQuery] = id.split(`?`, 2)\n  const query = Object.fromEntries(new URLSearchParams(rawQuery)) as VueQuery\n  if (query.vue != null) {\n    query.vue = true\n  }\n  if (query.index != null) {\n    query.index = Number(query.index)\n  }\n  if (query.raw != null) {\n    query.raw = true\n  }\n  if (query.scoped != null) {\n    query.scoped = true\n  }\n  return {\n    filename,\n    query\n  }\n}\n"
  },
  {
    "path": "test/test.spec.ts",
    "content": "import type puppeteer from 'puppeteer'\nimport { beforeAll, afterAll, describe, test, expect } from 'vitest'\nimport {\n  expectByPolling,\n  getComputedColor,\n  getEl,\n  getText,\n  killServer,\n  postTest,\n  preTest,\n  startServer,\n  updateFile\n} from './util'\n\nbeforeAll(async () => {\n  await preTest()\n})\n\nafterAll(postTest)\n\ndescribe('vite-plugin-vue2', () => {\n  describe('dev', () => {\n    declareTests(false)\n  })\n\n  describe('build', () => {\n    declareTests(true)\n  })\n})\n\nexport function declareTests(isBuild: boolean) {\n  let page: puppeteer.Page = undefined!\n\n  beforeAll(async () => {\n    page = await startServer(isBuild)\n  })\n\n  afterAll(async () => {\n    await killServer()\n  })\n\n  test('SFC <script setup>', async () => {\n    const el = await page.$('.script-setup')\n    // custom directive\n    expect(await getComputedColor(el!)).toBe('rgb(255, 0, 0)')\n    // defineProps\n    expect(await getText((await page.$('.prop'))!)).toMatch('prop from parent')\n    // state\n    const button = (await page.$('.script-setup button'))!\n    expect(await getText(button)).toMatch('0')\n    // click\n    await page.click('.script-setup button')\n    expect(await getText(button)).toMatch('1')\n    if (!isBuild) {\n      // hmr\n      await updateFile('ScriptSetup.vue', content =>\n        content.replace(`{{ count }}`, `{{ count }}!`)\n      )\n      await expectByPolling(() => getText(button!), '1!')\n    }\n  })\n\n  if (!isBuild) {\n    test('hmr (vue re-render)', async () => {\n      const button = await page.$('.hmr-increment')\n      await button!.click()\n      expect(await getText(button!)).toMatch('>>> 1 <<<')\n\n      await updateFile('hmr/TestHmr.vue', content =>\n        content.replace('{{ count }}', 'count is {{ count }}')\n      )\n      // note: using the same button to ensure the component did only re-render\n      // if it's a reload, it would have replaced the button with a new one.\n      await expectByPolling(() => getText(button!), 'count is 1')\n    })\n\n    test('hmr (vue reload)', async () => {\n      await updateFile('hmr/TestHmr.vue', content =>\n        content.replace('count: 0', 'count: 1337')\n      )\n      await expectByPolling(() => getText('.hmr-increment'), 'count is 1337')\n    })\n  }\n\n  test('SFC <style scoped>', async () => {\n    const el = await page.$('.style-scoped')\n    expect(await getComputedColor(el!)).toBe('rgb(138, 43, 226)')\n    if (!isBuild) {\n      await updateFile('css/TestScopedCss.vue', content =>\n        content.replace('rgb(138, 43, 226)', 'rgb(0, 0, 0)')\n      )\n      await expectByPolling(() => getComputedColor(el!), 'rgb(0, 0, 0)')\n    }\n  })\n\n  test('SFC <style module>', async () => {\n    const el = await page.$('.css-modules-sfc')\n    expect(await getComputedColor(el!)).toBe('rgb(0, 0, 255)')\n    if (!isBuild) {\n      await updateFile('css/TestCssModules.vue', content =>\n        content.replace('color: blue;', 'color: rgb(0, 0, 0);')\n      )\n      // css module results in component reload so must use fresh selector\n      await expectByPolling(\n        () => getComputedColor('.css-modules-sfc'),\n        'rgb(0, 0, 0)'\n      )\n    }\n  })\n\n  test('SFC <custom>', async () => {\n    expect(await getText('.custom-block')).toMatch('Custom Block')\n    expect(await getText('.custom-block-lang')).toMatch('Custom Block')\n    expect(await getText('.custom-block-src')).toMatch('Custom Block')\n  })\n\n  test('SFC src imports', async () => {\n    expect(await getText('.src-imports-script')).toMatch('src=\"./script.ts\"')\n    const el = await getEl('.src-imports-style')\n    expect(await getComputedColor(el!)).toBe('rgb(119, 136, 153)')\n    if (!isBuild) {\n      // test style first, should not reload the component\n      await updateFile('src-import/style.css', c =>\n        c.replace('rgb(119, 136, 153)', 'rgb(0, 0, 0)')\n      )\n      await expectByPolling(() => getComputedColor(el!), 'rgb(0, 0, 0)')\n      // script\n      await updateFile('src-import/script.ts', c => c.replace('hello', 'bye'))\n      await expectByPolling(() => getText('.src-imports-script'), 'bye from')\n      // template\n      // todo fix test, file change is only triggered one event.\n      // src/node/server/serverPluginHmr.ts is not triggered, maybe caused by chokidar\n      // await updateFile('src-import/template.html', (c) =>\n      //   c.replace('gray', 'red')\n      // )\n      // await expectByPolling(\n      //   () => getText('.src-imports-style'),\n      //   'This should be light red'\n      // )\n    }\n  })\n\n  test('SFC Recursive Component', async () => {\n    expect(await getText('.test-recursive-item')).toMatch(/name-1-1-1/)\n  })\n\n  test('SFC Async Component', async () => {\n    expect(await getText('.async-component-a')).toMatch('This is componentA')\n    expect(await getText('.async-component-b')).toMatch('This is componentB')\n  })\n\n  test('css v-bind', async () => {\n    const el = await getEl('.css-v-bind')\n    expect(await getComputedColor(el!)).toBe(`rgb(255, 0, 0)`)\n    await el!.click()\n    expect(await getComputedColor(el!)).toBe(`rgb(0, 128, 0)`)\n  })\n}\n"
  },
  {
    "path": "test/util.ts",
    "content": "import path from 'path'\nimport fs from 'fs-extra'\nimport execa from 'execa'\nimport { expect } from 'vitest'\nimport type { ElementHandle } from 'puppeteer'\nimport puppeteer from 'puppeteer'\n\nlet devServer: any\nlet browser: puppeteer.Browser\nlet page: puppeteer.Page\nlet binPath: string\nconst fixtureDir = path.join(__dirname, '../playground')\nconst tempDir = path.join(__dirname, '../temp')\n\nexport async function preTest() {\n  try {\n    await fs.remove(tempDir)\n  } catch (e) {}\n  await fs.copy(fixtureDir, tempDir)\n  binPath = path.resolve(tempDir, '../node_modules/vite/bin/vite.js')\n\n  await build()\n}\n\nasync function build() {\n  console.log('building...')\n  const buildOutput = await execa(binPath, ['build'], {\n    cwd: tempDir\n  })\n  expect(buildOutput.stderr).toBe('')\n  console.log('build complete. running build tests...')\n}\n\nexport async function postTest() {\n  try {\n    await fs.remove(tempDir)\n  } catch (e) {}\n}\n\nexport async function startServer(isBuild: boolean) {\n  // start dev server\n  devServer = execa(binPath, {\n    cwd: isBuild ? path.join(tempDir, '/dist') : tempDir\n  })\n\n  browser = await puppeteer.launch({\n    args: ['--no-sandbox', '--disable-setuid-sandbox']\n    // Enable if puppeteer can't detect chrome's path on MacOS\n    // executablePath:\n    // '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n  })\n\n  await new Promise(resolve => {\n    devServer.stdout.on('data', (data: Buffer) => {\n      if (data.toString().match('ready in')) {\n        console.log('dev server running.')\n        resolve('')\n      }\n    })\n  })\n\n  console.log('launching browser')\n  page = await browser.newPage()\n  await page.goto('http://localhost:5173')\n  return page\n}\n\nexport async function killServer() {\n  if (browser) await browser.close()\n  if (devServer) {\n    devServer.kill('SIGTERM', {\n      forceKillAfterTimeout: 2000\n    })\n  }\n}\n\nexport async function getEl(selectorOrEl: string | ElementHandle) {\n  return typeof selectorOrEl === 'string'\n    ? await page.$(selectorOrEl)\n    : selectorOrEl\n}\n\nexport async function getText(selectorOrEl: string | ElementHandle) {\n  const el = await getEl(selectorOrEl)\n  return el ? el.evaluate(el => el.textContent) : null\n}\n\nexport async function getComputedColor(selectorOrEl: string | ElementHandle) {\n  return (await getEl(selectorOrEl))!.evaluate(el => getComputedStyle(el).color)\n}\n\nexport const timeout = (n: number) =>\n  new Promise(resolve => setTimeout(resolve, n))\n\nexport async function updateFile(\n  file: string,\n  replacer: (content: string) => string\n) {\n  const compPath = path.join(tempDir, file)\n  const content = await fs.readFile(compPath, 'utf-8')\n  await fs.writeFile(compPath, replacer(content))\n}\n\n// poll until it updates\nexport async function expectByPolling(poll: () => Promise<any>, expected: any) {\n  const maxTries = 100\n  for (let tries = 0; tries < maxTries; tries++) {\n    const actual = (await poll()) || ''\n    if (actual.includes(expected) || tries === maxTries - 1) {\n      expect(actual).toMatch(expected)\n      break\n    } else {\n      await timeout(50)\n    }\n  }\n}\n"
  },
  {
    "path": "test/vitest.config.ts",
    "content": "/// <reference types=\"vitest\" />\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  test: {\n    testTimeout: 100000\n  }\n})\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"include\": [\"src\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"target\": \"ES2020\",\n    \"module\": \"ES2020\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"declaration\": true,\n    \"sourceMap\": true,\n    \"noImplicitOverride\": true,\n    \"noUnusedLocals\": true,\n    \"esModuleInterop\": true,\n    \"baseUrl\": \".\"\n  }\n}\n"
  }
]