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