This commit is contained in:
Evan You 2025-06-26 07:41:28 +00:00 committed by GitHub
commit 54440580d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
296 changed files with 35518 additions and 2702 deletions

View File

@ -290,27 +290,39 @@ This is made possible via several configurations:
```mermaid
flowchart LR
vue["vue"]
compiler-sfc["@vue/compiler-sfc"]
compiler-dom["@vue/compiler-dom"]
compiler-vapor["@vue/compiler-vapor"]
compiler-core["@vue/compiler-core"]
vue["vue"]
runtime-dom["@vue/runtime-dom"]
runtime-vapor["@vue/runtime-vapor"]
runtime-core["@vue/runtime-core"]
reactivity["@vue/reactivity"]
subgraph "Runtime Packages"
runtime-dom --> runtime-core
runtime-vapor --> runtime-core
runtime-core --> reactivity
end
subgraph "Compiler Packages"
compiler-sfc --> compiler-core
compiler-sfc --> compiler-dom
compiler-sfc --> compiler-vapor
compiler-dom --> compiler-core
compiler-vapor --> compiler-core
end
vue --> compiler-sfc
vue ---> compiler-dom
vue --> runtime-dom
vue --> compiler-vapor
vue --> runtime-vapor
%% Highlight class
classDef highlight stroke:#35eb9a,stroke-width:3px;
class compiler-vapor,runtime-vapor highlight;
```
There are some rules to follow when importing across package boundaries:

View File

@ -9,6 +9,7 @@ on:
branches:
- main
- minor
- vapor
jobs:
test:
@ -16,7 +17,7 @@ jobs:
uses: ./.github/workflows/test.yml
continuous-release:
if: github.repository == 'vuejs/core'
if: github.repository == 'vuejs/core' && github.ref_name != 'vapor'
runs-on: ubuntu-latest
steps:
- name: Checkout

View File

@ -80,6 +80,32 @@ jobs:
- name: verify treeshaking
run: node scripts/verify-treeshaking.js
e2e-vapor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup cache for Chromium binary
uses: actions/cache@v4
with:
path: ~/.cache/puppeteer
key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install pnpm
uses: pnpm/action-setup@v4.0.0
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm install
- run: node node_modules/puppeteer/install.mjs
- name: Run e2e tests
run: pnpm run test-e2e-vapor
lint-and-test-dts:
runs-on: ubuntu-latest
env:

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ TODOs.md
dts-build/packages
*.tsbuildinfo
*.tgz
packages-private/benchmark/reference

View File

@ -14,5 +14,6 @@
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.formatOnSave": true
"editor.formatOnSave": true,
"vitest.disableWorkspaceWarning": true
}

View File

@ -106,7 +106,7 @@ export default tseslint.config(
// Packages targeting DOM
{
files: ['packages/{vue,vue-compat,runtime-dom}/**'],
files: ['packages/{vue,vue-compat,runtime-dom,runtime-vapor}/**'],
rules: {
'no-restricted-globals': ['error', ...NodeGlobals],
},
@ -126,6 +126,7 @@ export default tseslint.config(
files: [
'packages-private/template-explorer/**',
'packages-private/sfc-playground/**',
'packages-private/local-playground/**',
],
rules: {
'no-restricted-globals': ['error', ...NodeGlobals],
@ -152,6 +153,8 @@ export default tseslint.config(
'./*.{js,ts}',
'packages/*/*.js',
'packages/vue/*/*.js',
'packages-private/benchmark/*',
'packages-private/e2e-utils/*',
],
rules: {
'no-restricted-globals': 'off',

View File

@ -9,16 +9,18 @@
"build-dts": "tsc -p tsconfig.build.json --noCheck && rollup -c rollup.dts.config.js",
"clean": "rimraf --glob packages/*/dist temp .eslintcache",
"size": "run-s \"size-*\" && node scripts/usage-size.js",
"size-global": "node scripts/build.js vue runtime-dom -f global -p --size",
"size-global": "node scripts/build.js vue runtime-dom compiler-dom -f global -p --size",
"size-esm-runtime": "node scripts/build.js vue -f esm-bundler-runtime",
"size-esm": "node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler",
"size-esm": "node scripts/build.js runtime-shared runtime-dom runtime-core reactivity shared runtime-vapor -f esm-bundler",
"check": "tsc --incremental --noEmit",
"lint": "eslint --cache .",
"format": "prettier --write --cache .",
"format-check": "prettier --check --cache .",
"test": "vitest",
"test-unit": "vitest --project unit",
"test-unit": "vitest --project unit --project unit-jsdom",
"test-e2e": "node scripts/build.js vue -f global -d && vitest --project e2e",
"test-e2e-vapor": "pnpm run prepare-e2e-vapor && vitest --project e2e-vapor",
"prepare-e2e-vapor": "node scripts/build.js -f cjs+esm-bundler+esm-bundler-runtime && pnpm run -C packages-private/vapor-e2e-test build",
"test-dts": "run-s build-dts test-dts-only",
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
"test-coverage": "vitest run --project unit --coverage",
@ -29,19 +31,17 @@
"release": "node scripts/release.js",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
"dev-compiler": "run-p \"dev template-explorer\" serve",
"dev-sfc": "run-s dev-sfc-prepare dev-sfc-run",
"dev-sfc-prepare": "node scripts/pre-dev-sfc.js || npm run build-all-cjs",
"dev-sfc-serve": "vite packages-private/sfc-playground --host",
"dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev vue -ipf esm-browser-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
"dev-prepare-cjs": "node scripts/prepare-cjs.js || node scripts/build.js -f cjs",
"dev-compiler": "run-p \"dev template-explorer\" serve open",
"dev-sfc": "run-s dev-prepare-cjs dev-sfc-run",
"dev-sfc-serve": "vite packages-private/sfc-playground",
"dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-browser-vapor\" \"dev vue -ipf esm-browser-vapor\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
"dev-vapor": "pnpm -C packages-private/local-playground run dev",
"serve": "serve",
"open": "open http://localhost:3000/packages-private/template-explorer/local.html",
"build-sfc-playground": "run-s build-all-cjs build-runtime-esm build-browser-esm build-ssr-esm build-sfc-playground-self",
"build-all-cjs": "node scripts/build.js vue runtime compiler reactivity shared -af cjs",
"build-runtime-esm": "node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler-runtime && node scripts/build.js vue -f esm-browser-runtime",
"build-browser-esm": "node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler && node scripts/build.js vue -f esm-browser",
"build-ssr-esm": "node scripts/build.js compiler-sfc server-renderer -f esm-browser",
"build-sfc-playground-self": "cd packages-private/sfc-playground && npm run build",
"build-sfc-playground": "run-s build-sfc-deps build-sfc-playground-self",
"build-sfc-deps": "node scripts/build.js -f ~global+global-runtime",
"build-sfc-playground-self": "pnpm run -C packages-private/sfc-playground build",
"preinstall": "npx only-allow pnpm",
"postinstall": "simple-git-hooks"
},
@ -74,6 +74,7 @@
"@types/node": "^22.14.1",
"@types/semver": "^7.7.0",
"@types/serve-handler": "^6.1.4",
"@vitest/ui": "^3.0.2",
"@vitest/coverage-v8": "^3.1.3",
"@vitest/eslint-plugin": "^1.1.44",
"@vue/consolidate": "1.0.0",

1
packages-private/benchmark/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
results/*

View File

@ -0,0 +1,136 @@
<script setup>
import { shallowRef, triggerRef } from 'vue'
import { buildData } from './data'
import { defer, wrap } from './profiling'
const selected = shallowRef()
const rows = shallowRef([])
// Bench Add: https://jsbench.me/45lzxprzmu/1
const add = wrap('add', () => {
rows.value.push(...buildData(1000))
triggerRef(rows)
})
const remove = wrap('remove', id => {
rows.value.splice(
rows.value.findIndex(d => d.id === id),
1,
)
triggerRef(rows)
})
const select = wrap('select', id => {
selected.value = id
})
const run = wrap('run', () => {
rows.value = buildData()
selected.value = undefined
})
const update = wrap('update', () => {
const _rows = rows.value
for (let i = 0, len = _rows.length; i < len; i += 10) {
_rows[i].label.value += ' !!!'
}
})
const runLots = wrap('runLots', () => {
rows.value = buildData(10000)
selected.value = undefined
})
const clear = wrap('clear', () => {
rows.value = []
selected.value = undefined
})
const swapRows = wrap('swap', () => {
const _rows = rows.value
if (_rows.length > 998) {
const d1 = _rows[1]
const d998 = _rows[998]
_rows[1] = d998
_rows[998] = d1
triggerRef(rows)
}
})
async function bench() {
for (let i = 0; i < 30; i++) {
rows.value = []
await runLots()
await defer()
}
}
const globalThis = window
</script>
<template>
<h1>Vue.js (VDOM) Benchmark</h1>
<div style="display: flex; gap: 4px; margin-bottom: 4px">
<label>
<input
type="checkbox"
:value="globalThis.doProfile"
@change="globalThis.doProfile = $event.target.checked"
/>
Profiling
</label>
<label>
<input
type="checkbox"
:value="globalThis.reactivity"
@change="globalThis.reactivity = $event.target.checked"
/>
Reactivity Cost
</label>
</div>
<div
id="control"
style="display: flex; flex-direction: column; width: fit-content; gap: 6px"
>
<button @click="bench">Benchmark mounting</button>
<button id="run" @click="run">Create 1,000 rows</button>
<button id="runlots" @click="runLots">Create 10,000 rows</button>
<button id="add" @click="add">Append 1,000 rows</button>
<button id="update" @click="update">Update every 10th row</button>
<button id="clear" @click="clear">Clear</button>
<button id="swaprows" @click="swapRows">Swap Rows</button>
</div>
<div id="time"></div>
<table class="table table-hover table-striped test-data">
<tbody>
<tr
v-for="row of rows"
:key="row.id"
:class="selected === row.id ? 'danger' : ''"
>
<td class="col-md-1">{{ row.id }}</td>
<td class="col-md-4">
<a @click="select(row.id)">{{ row.label.value }}</a>
</td>
<td class="col-md-1">
<a @click="remove(row.id)">
<span class="glyphicon glyphicon-remove" aria-hidden="true">x</span>
</a>
</td>
<td class="col-md-6"></td>
</tr>
</tbody>
</table>
<span
class="preloadicon glyphicon glyphicon-remove"
aria-hidden="true"
></span>
</template>
<style>
.danger {
background-color: red;
}
</style>

View File

@ -0,0 +1,136 @@
<script setup vapor>
import { shallowRef, triggerRef } from 'vue'
import { buildData } from './data'
import { defer, wrap } from './profiling'
const selected = shallowRef()
const rows = shallowRef([])
// Bench Add: https://jsbench.me/45lzxprzmu/1
const add = wrap('add', () => {
rows.value.push(...buildData(1000))
triggerRef(rows)
})
const remove = wrap('remove', id => {
rows.value.splice(
rows.value.findIndex(d => d.id === id),
1,
)
triggerRef(rows)
})
const select = wrap('select', id => {
selected.value = id
})
const run = wrap('run', () => {
rows.value = buildData()
selected.value = undefined
})
const update = wrap('update', () => {
const _rows = rows.value
for (let i = 0, len = _rows.length; i < len; i += 10) {
_rows[i].label.value += ' !!!'
}
})
const runLots = wrap('runLots', () => {
rows.value = buildData(10000)
selected.value = undefined
})
const clear = wrap('clear', () => {
rows.value = []
selected.value = undefined
})
const swapRows = wrap('swap', () => {
const _rows = rows.value
if (_rows.length > 998) {
const d1 = _rows[1]
const d998 = _rows[998]
_rows[1] = d998
_rows[998] = d1
triggerRef(rows)
}
})
async function bench() {
for (let i = 0; i < 30; i++) {
rows.value = []
await runLots()
await defer()
}
}
const globalThis = window
</script>
<template>
<h1>Vue.js (Vapor) Benchmark</h1>
<div style="display: flex; gap: 4px; margin-bottom: 4px">
<label>
<input
type="checkbox"
:value="globalThis.doProfile"
@change="globalThis.doProfile = $event.target.checked"
/>
Profiling
</label>
<label>
<input
type="checkbox"
:value="globalThis.reactivity"
@change="globalThis.reactivity = $event.target.checked"
/>
Reactivity Cost
</label>
</div>
<div
id="control"
style="display: flex; flex-direction: column; width: fit-content; gap: 6px"
>
<button @click="bench">Benchmark mounting</button>
<button id="run" @click="run">Create 1,000 rows</button>
<button id="runlots" @click="runLots">Create 10,000 rows</button>
<button id="add" @click="add">Append 1,000 rows</button>
<button id="update" @click="update">Update every 10th row</button>
<button id="clear" @click="clear">Clear</button>
<button id="swaprows" @click="swapRows">Swap Rows</button>
</div>
<div id="time"></div>
<table class="table table-hover table-striped test-data">
<tbody>
<tr
v-for="row of rows"
:key="row.id"
:class="selected === row.id ? 'danger' : ''"
>
<td class="col-md-1">{{ row.id }}</td>
<td class="col-md-4">
<a @click="select(row.id)">{{ row.label.value }}</a>
</td>
<td class="col-md-1">
<a @click="remove(row.id)">
<span class="glyphicon glyphicon-remove" aria-hidden="true">x</span>
</a>
</td>
<td class="col-md-6"></td>
</tr>
</tbody>
</table>
<span
class="preloadicon glyphicon glyphicon-remove"
aria-hidden="true"
></span>
</template>
<style>
.danger {
background-color: red;
}
</style>

View File

@ -0,0 +1,78 @@
import { shallowRef } from 'vue'
let ID = 1
function _random(max: number) {
return Math.round(Math.random() * 1000) % max
}
export function buildData(count = 1000) {
const adjectives = [
'pretty',
'large',
'big',
'small',
'tall',
'short',
'long',
'handsome',
'plain',
'quaint',
'clean',
'elegant',
'easy',
'angry',
'crazy',
'helpful',
'mushy',
'odd',
'unsightly',
'adorable',
'important',
'inexpensive',
'cheap',
'expensive',
'fancy',
]
const colours = [
'red',
'yellow',
'blue',
'green',
'pink',
'brown',
'purple',
'brown',
'white',
'black',
'orange',
]
const nouns = [
'table',
'chair',
'house',
'bbq',
'desk',
'car',
'pony',
'cookie',
'sandwich',
'burger',
'pizza',
'mouse',
'keyboard',
]
const data = []
for (let i = 0; i < count; i++)
data.push({
id: ID++,
label: shallowRef(
adjectives[_random(adjectives.length)] +
' ' +
colours[_random(colours.length)] +
' ' +
nouns[_random(nouns.length)],
),
})
return data
}

View File

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue Vapor Benchmark</title>
<style>
html {
color-scheme: light dark;
}
</style>
</head>
<body class="done">
<div id="app"></div>
<script type="module" src="./index.ts"></script>
</body>
</html>

View File

@ -0,0 +1,5 @@
if (import.meta.env.IS_VAPOR) {
import('./vapor')
} else {
import('./vdom')
}

View File

@ -0,0 +1,94 @@
/* eslint-disable no-console */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-restricted-globals */
import { nextTick } from 'vue'
declare global {
var doProfile: boolean
var reactivity: boolean
var recordTime: boolean
var times: Record<string, number[]>
}
globalThis.recordTime = true
globalThis.doProfile = false
globalThis.reactivity = false
export const defer = () => new Promise(r => requestIdleCallback(r))
const times: Record<string, number[]> = (globalThis.times = {})
export function wrap(
id: string,
fn: (...args: any[]) => any,
): (...args: any[]) => Promise<void> {
return async (...args) => {
if (!globalThis.recordTime) {
return fn(...args)
}
document.body.classList.remove('done')
const { doProfile } = globalThis
await nextTick()
doProfile && console.profile(id)
const start = performance.now()
fn(...args)
await nextTick()
let time: number
if (globalThis.reactivity) {
time = performance.measure(
'flushJobs-measure',
'flushJobs-start',
'flushJobs-end',
).duration
performance.clearMarks()
performance.clearMeasures()
} else {
time = performance.now() - start
}
const prevTimes = times[id] || (times[id] = [])
prevTimes.push(time)
const { min, max, median, mean, std } = compute(prevTimes)
const msg =
`${id}: min: ${min} / ` +
`max: ${max} / ` +
`median: ${median}ms / ` +
`mean: ${mean}ms / ` +
`time: ${time.toFixed(2)}ms / ` +
`std: ${std} ` +
`over ${prevTimes.length} runs`
doProfile && console.profileEnd(id)
console.log(msg)
const timeEl = document.getElementById('time')!
timeEl.textContent = msg
document.body.classList.add('done')
}
}
function compute(array: number[]) {
const n = array.length
const max = Math.max(...array)
const min = Math.min(...array)
const mean = array.reduce((a, b) => a + b) / n
const std = Math.sqrt(
array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n,
)
const median = array.slice().sort((a, b) => a - b)[Math.floor(n / 2)]
return {
max: round(max),
min: round(min),
mean: round(mean),
std: round(std),
median: round(median),
}
}
function round(n: number) {
return +n.toFixed(2)
}

View File

@ -0,0 +1,4 @@
import { createVaporApp } from 'vue'
import App from './AppVapor.vue'
createVaporApp(App as any).mount('#app')

View File

@ -0,0 +1,4 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

View File

@ -0,0 +1,393 @@
// @ts-check
import path from 'node:path'
import { parseArgs } from 'node:util'
import { mkdir, rm, writeFile } from 'node:fs/promises'
import Vue from '@vitejs/plugin-vue'
import { build } from 'vite'
import connect from 'connect'
import sirv from 'sirv'
import { launch } from 'puppeteer'
import colors from 'picocolors'
import { exec, getSha } from '../../scripts/utils.js'
import process from 'node:process'
import readline from 'node:readline'
// Thanks to https://github.com/krausest/js-framework-benchmark (Apache-2.0 license)
const {
values: {
skipLib,
skipApp,
skipBench,
vdom,
noVapor,
port: portStr,
count: countStr,
warmupCount: warmupCountStr,
noHeadless,
noMinify,
reference,
},
} = parseArgs({
allowNegative: true,
allowPositionals: true,
options: {
skipLib: {
type: 'boolean',
short: 'l',
},
skipApp: {
type: 'boolean',
short: 'a',
},
skipBench: {
type: 'boolean',
short: 'b',
},
noVapor: {
type: 'boolean',
},
vdom: {
type: 'boolean',
short: 'v',
},
port: {
type: 'string',
short: 'p',
default: '8193',
},
count: {
type: 'string',
short: 'c',
default: '30',
},
warmupCount: {
type: 'string',
short: 'w',
default: '5',
},
noHeadless: {
type: 'boolean',
},
noMinify: {
type: 'boolean',
},
reference: {
type: 'boolean',
short: 'r',
},
},
})
const port = +(/** @type {string}*/ (portStr))
const count = +(/** @type {string}*/ (countStr))
const warmupCount = +(/** @type {string}*/ (warmupCountStr))
const sha = await getSha(true)
if (!skipLib && !reference) {
await buildLib()
}
if (!skipApp && !reference) {
await rm('client/dist', { recursive: true }).catch(() => {})
vdom && (await buildApp(false))
!noVapor && (await buildApp(true))
}
const server = startServer()
if (!skipBench) {
await benchmark()
server.close()
}
async function buildLib() {
console.info(colors.blue('Building lib...'))
/** @type {import('node:child_process').SpawnOptions} */
const options = {
cwd: path.resolve(import.meta.dirname, '../..'),
stdio: 'inherit',
env: { ...process.env, BENCHMARK: 'true' },
}
const [{ ok }, { ok: ok2 }, { ok: ok3 }] = await Promise.all([
exec(
'pnpm',
`run --silent build shared compiler-core compiler-dom -pf cjs`.split(' '),
options,
),
exec(
'pnpm',
'run --silent build compiler-sfc compiler-ssr compiler-vapor -f cjs'.split(
' ',
),
options,
),
exec(
'pnpm',
`run --silent build shared reactivity runtime-core runtime-dom runtime-vapor vue -f esm-bundler+esm-bundler-runtime`.split(
' ',
),
options,
),
])
if (!ok || !ok2 || !ok3) {
console.error('Failed to build')
process.exit(1)
}
}
/** @param {boolean} isVapor */
async function buildApp(isVapor) {
console.info(
colors.blue(`\nBuilding ${isVapor ? 'Vapor' : 'Virtual DOM'} app...\n`),
)
process.env.NODE_ENV = 'production'
const CompilerSFC = await import(
'../../packages/compiler-sfc/dist/compiler-sfc.cjs.js'
)
const runtimePath = path.resolve(
import.meta.dirname,
'../../packages/vue/dist/vue.runtime.esm-bundler.js',
)
const mode = isVapor ? 'vapor' : 'vdom'
await build({
root: './client',
base: `/${mode}`,
define: {
'import.meta.env.IS_VAPOR': String(isVapor),
},
build: {
minify: !noMinify,
outDir: path.resolve('./client/dist', mode),
rollupOptions: {
onwarn(log, handler) {
if (log.code === 'INVALID_ANNOTATION') return
handler(log)
},
},
},
resolve: {
alias: {
vue: runtimePath,
},
},
clearScreen: false,
plugins: [
Vue({
compiler: CompilerSFC,
}),
],
})
}
function startServer() {
const server = connect()
.use(sirv(reference ? './reference' : './client/dist', { dev: true }))
.listen(port)
printPort()
process.on('SIGTERM', () => server.close())
return server
}
async function benchmark() {
console.info(colors.blue(`\nStarting benchmark...`))
const browser = await initBrowser()
await mkdir('results', { recursive: true }).catch(() => {})
if (!noVapor) {
await doBench(browser, true)
}
if (vdom) {
await doBench(browser, false)
}
await browser.close()
}
/**
* @param {boolean} isVapor
*/
function getURL(isVapor) {
return `http://localhost:${port}/${reference ? '' : isVapor ? 'vapor' : 'vdom'}/`
}
/**
*
* @param {import('puppeteer').Browser} browser
* @param {boolean} isVapor
*/
async function doBench(browser, isVapor) {
const mode = reference ? `reference` : isVapor ? 'vapor' : 'vdom'
console.info('\n\nmode:', mode)
const page = await browser.newPage()
page.emulateCPUThrottling(4)
await page.goto(getURL(isVapor), {
waitUntil: 'networkidle0',
})
await forceGC()
const t = performance.now()
console.log('warmup run')
await eachRun(() => withoutRecord(benchOnce), warmupCount)
console.log('benchmark run')
await eachRun(benchOnce, count)
console.info(
'Total time:',
colors.cyan(((performance.now() - t) / 1000).toFixed(2)),
's',
)
const times = await getTimes()
const result =
/** @type {Record<string, typeof compute>} */
Object.fromEntries(Object.entries(times).map(([k, v]) => [k, compute(v)]))
console.table(result)
await writeFile(
`results/benchmark-${sha}-${mode}.json`,
JSON.stringify(result, undefined, 2),
)
await page.close()
return result
async function benchOnce() {
await clickButton('run') // test: create rows
await clickButton('update') // partial update
await clickButton('swaprows') // swap rows
await select() // test: select row, remove row
await clickButton('clear') // clear rows
await withoutRecord(() => clickButton('run'))
await clickButton('add') // append rows to large table
await withoutRecord(() => clickButton('clear'))
await clickButton('runlots') // create many rows
await withoutRecord(() => clickButton('clear'))
// TODO replace all rows
}
function getTimes() {
return page.evaluate(() => /** @type {any} */ (globalThis).times)
}
async function forceGC() {
await page.evaluate(
`window.gc({type:'major',execution:'sync',flavor:'last-resort'})`,
)
}
/** @param {() => any} fn */
async function withoutRecord(fn) {
const currentRecordTime = await page.evaluate(() => globalThis.recordTime)
await page.evaluate(() => (globalThis.recordTime = false))
await fn()
await page.evaluate(
currentRecordTime => (globalThis.recordTime = currentRecordTime),
currentRecordTime,
)
}
/** @param {string} id */
async function clickButton(id) {
await page.click(`#${id}`)
await wait()
}
async function select() {
for (let i = 1; i <= 10; i++) {
await page.click(`tbody > tr:nth-child(2) > td:nth-child(2) > a`)
await page.waitForSelector(`tbody > tr:nth-child(2).danger`)
await page.click(`tbody > tr:nth-child(2) > td:nth-child(3) > a`)
await wait()
}
}
async function wait() {
await page.waitForSelector('.done')
}
}
/**
* @param {Function} bench
* @param {number} count
*/
async function eachRun(bench, count) {
for (let i = 0; i < count; i++) {
readline.cursorTo(process.stdout, 0)
readline.clearLine(process.stdout, 0)
process.stdout.write(`${i + 1}/${count}`)
await bench()
}
if (count === 0) {
process.stdout.write('0/0 (skip)')
}
process.stdout.write('\n')
}
async function initBrowser() {
const disableFeatures = [
'Translate', // avoid translation popups
'PrivacySandboxSettings4', // avoid privacy popup
'IPH_SidePanelGenericMenuFeature', // bookmark popup see https://github.com/krausest/js-framework-benchmark/issues/1688
]
const args = [
'--js-flags=--expose-gc', // needed for gc() function
'--no-default-browser-check',
'--disable-sync',
'--no-first-run',
'--ash-no-nudges',
'--disable-extensions',
`--disable-features=${disableFeatures.join(',')}`,
]
const headless = !noHeadless
console.info('headless:', headless)
const browser = await launch({
headless: headless,
args,
})
console.log('browser version:', colors.blue(await browser.version()))
return browser
}
/** @param {number[]} array */
function compute(array) {
const n = array.length
const max = Math.max(...array)
const min = Math.min(...array)
const mean = array.reduce((a, b) => a + b) / n
const std = Math.sqrt(
array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n,
)
const median = array.slice().sort((a, b) => a - b)[Math.floor(n / 2)]
return {
max: round(max),
min: round(min),
mean: round(mean),
std: round(std),
median: round(median),
}
}
/** @param {number} n */
function round(n) {
return +n.toFixed(2)
}
function printPort() {
const vaporLink = !noVapor
? `\n${reference ? `Reference` : `Vapor`}: ${colors.blue(getURL(true))}`
: ''
const vdomLink = vdom ? `\nvDom: ${colors.blue(getURL(false))}` : ''
console.info(`\n\nServer started at`, vaporLink, vdomLink)
}

View File

@ -0,0 +1,20 @@
{
"name": "benchmark",
"version": "0.0.0",
"author": "三咲智子 Kevin Deng <sxzz@sxzz.moe>",
"license": "MIT",
"type": "module",
"scripts": {
"dev": "pnpm start --noMinify --skipBench --vdom",
"start": "node index.js"
},
"dependencies": {
"@vitejs/plugin-vue": "catalog:",
"connect": "^3.7.0",
"sirv": "^2.0.4",
"vite": "catalog:"
},
"devDependencies": {
"@types/connect": "^3.4.38"
}
}

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["es2022", "dom"],
"allowJs": true,
"moduleDetection": "force",
"module": "preserve",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"types": ["node", "vite/client"],
"strict": true,
"noUnusedLocals": true,
"declaration": true,
"esModuleInterop": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"noEmit": true,
"paths": {
"vue": ["../packages/vue/src/runtime-with-vapor.ts"],
"@vue/*": ["../packages/*/src"]
}
},
"include": ["**/*"]
}

View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue Vapor</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/main.ts"></script>
</body>
</html>

View File

@ -0,0 +1,22 @@
{
"name": "playground",
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "node ./setup/vite.js",
"build": "vite build -c vite.prod.config.ts",
"prepreview": "cd ../ && pnpm run build runtime-vapor -f esm-bundler",
"preview": "pnpm run build && vite preview -c vite.prod.config.ts"
},
"dependencies": {
"@vueuse/core": "^11.1.0",
"vue": "workspace:*"
},
"devDependencies": {
"@vitejs/plugin-vue": "catalog:",
"@vue/compiler-sfc": "workspace:*",
"vite": "catalog:",
"vite-hyper-config": "^0.4.0",
"vite-plugin-inspect": "^0.8.7"
}
}

View File

@ -0,0 +1,66 @@
// @ts-check
import path from 'node:path'
const resolve = (/** @type {string} */ p) =>
path.resolve(import.meta.dirname, '../../../packages', p)
/**
* @param {Object} [env]
* @param {boolean} [env.browser]
* @returns {import('vite').Plugin}
*/
export function DevPlugin({ browser = false } = {}) {
return {
name: 'dev-plugin',
config() {
return {
resolve: {
alias: {
vue: resolve('vue/src/runtime-with-vapor.ts'),
'@vue/runtime-core': resolve('runtime-core/src'),
'@vue/runtime-dom': resolve('runtime-dom/src'),
'@vue/runtime-vapor': resolve('runtime-vapor/src'),
'@vue/compiler-core': resolve('compiler-core/src'),
'@vue/compiler-dom': resolve('compiler-dom/src'),
'@vue/compiler-vapor': resolve('compiler-vapor/src'),
'@vue/compiler-sfc': resolve('compiler-sfc/src'),
'@vue/compiler-ssr': resolve('compiler-ssr/src'),
'@vue/reactivity': resolve('reactivity/src'),
'@vue/shared': resolve('shared/src'),
'@vue/runtime-shared': resolve('runtime-shared/src'),
},
},
define: {
__COMMIT__: `"__COMMIT__"`,
__VERSION__: `"0.0.0"`,
__DEV__: `true`,
// this is only used during Vue's internal tests
__TEST__: `false`,
// If the build is expected to run directly in the browser (global / esm builds)
__BROWSER__: String(browser),
__GLOBAL__: String(false),
__ESM_BUNDLER__: String(true),
__ESM_BROWSER__: String(false),
// is targeting Node (SSR)?
__NODE_JS__: String(false),
// need SSR-specific branches?
__SSR__: String(false),
__BENCHMARK__: 'false',
// 2.x compat build
__COMPAT__: String(false),
// feature flags
__FEATURE_SUSPENSE__: `true`,
__FEATURE_OPTIONS_API__: `true`,
__FEATURE_PROD_DEVTOOLS__: `false`,
__FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: `false`,
},
}
},
}
}

View File

@ -0,0 +1,14 @@
// @ts-check
import { startVite } from 'vite-hyper-config'
import { DevPlugin } from './dev.js'
startVite(
undefined,
{ plugins: [DevPlugin()] },
{
deps: {
inline: ['@vitejs/plugin-vue'],
},
},
)

View File

@ -0,0 +1,5 @@
*
!.gitignore
!App.vue
!main.ts
!style.css

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { ref } from 'vue'
import VaporComp from './VaporComp.vue'
const msg = ref('hello')
const passSlot = ref(true)
</script>
<template>
<input v-model="msg" />
<button @click="passSlot = !passSlot">toggle #test slot</button>
<VaporComp :msg="msg">
<template #default="{ foo }">
<div>slot props: {{ foo }}</div>
<div>component prop: {{ msg }}</div>
</template>
<template #test v-if="passSlot"> A test slot </template>
</VaporComp>
</template>

View File

@ -0,0 +1 @@
import './_entry'

View File

@ -0,0 +1,6 @@
.red {
color: red;
}
.green {
color: green;
}

View File

@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"isolatedDeclarations": false,
"allowJs": true
},
"include": ["./**/*", "../../packages/*/src"]
}

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite'
import Inspect from 'vite-plugin-inspect'
import { DevPlugin } from './setup/dev'
import Vue from '@vitejs/plugin-vue'
import * as CompilerSFC from '@vue/compiler-sfc'
export default defineConfig({
clearScreen: false,
plugins: [
Vue({
compiler: CompilerSFC,
}),
DevPlugin(),
Inspect(),
],
optimizeDeps: {
exclude: ['@vueuse/core'],
},
})

View File

@ -0,0 +1,26 @@
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import * as CompilerSFC from '@vue/compiler-sfc'
export default defineConfig({
build: {
modulePreload: false,
target: 'esnext',
minify: 'terser',
terserOptions: {
format: { comments: false },
compress: {
pure_getters: true,
},
},
},
clearScreen: false,
plugins: [
Vue({
compiler: CompilerSFC,
features: {
optionsAPI: false,
},
}),
],
})

View File

@ -1,6 +1,12 @@
<script setup lang="ts">
import Header from './Header.vue'
import { Repl, useStore, SFCOptions, useVueImportMap } from '@vue/repl'
import {
Repl,
type SFCOptions,
useStore,
useVueImportMap,
StoreState,
} from '@vue/repl'
import Monaco from '@vue/repl/monaco-editor'
import { ref, watchEffect, onMounted, computed } from 'vue'
@ -20,13 +26,17 @@ const initAutoSave: boolean = JSON.parse(
)
const autoSave = ref(initAutoSave)
const { productionMode, vueVersion, importMap } = useVueImportMap({
runtimeDev: import.meta.env.PROD
? `${location.origin}/vue.runtime.esm-browser.js`
: `${location.origin}/src/vue-dev-proxy`,
runtimeProd: import.meta.env.PROD
? `${location.origin}/vue.runtime.esm-browser.prod.js`
: `${location.origin}/src/vue-dev-proxy-prod`,
const { vueVersion, productionMode, importMap } = useVueImportMap({
runtimeDev: () => {
return import.meta.env.PROD
? `${location.origin}/vue.runtime-with-vapor.esm-browser.js`
: `${location.origin}/src/vue-dev-proxy`
},
runtimeProd: () => {
return import.meta.env.PROD
? `${location.origin}/vue.runtime-with-vapor.esm-browser.prod.js`
: `${location.origin}/src/vue-dev-proxy-prod`
},
serverRenderer: import.meta.env.PROD
? `${location.origin}/server-renderer.esm-browser.js`
: `${location.origin}/src/vue-server-renderer-dev-proxy`,
@ -46,6 +56,8 @@ if (hash.startsWith('__SSR__')) {
useSSRMode.value = true
}
const files: StoreState['files'] = ref(Object.create(null))
// enable experimental features
const sfcOptions = computed(
(): SFCOptions => ({
@ -53,11 +65,13 @@ const sfcOptions = computed(
inlineTemplate: productionMode.value,
isProd: productionMode.value,
propsDestructure: true,
// vapor: useVaporMode.value,
},
style: {
isProd: productionMode.value,
},
template: {
// vapor: useVaporMode.value,
isProd: productionMode.value,
compilerOptions: {
isCustomElement: (tag: string) =>
@ -69,8 +83,9 @@ const sfcOptions = computed(
const store = useStore(
{
builtinImportMap: importMap,
files,
vueVersion,
builtinImportMap: importMap,
sfcOptions,
},
hash,
@ -145,8 +160,10 @@ onMounted(() => {
:clearConsole="false"
:preview-options="{
customCode: {
importCode: `import { initCustomFormatter } from 'vue'`,
useCode: `if (window.devtoolsFormatters) {
importCode: `import { initCustomFormatter, vaporInteropPlugin } from 'vue'`,
useCode: `
app.use(vaporInteropPlugin)
if (window.devtoolsFormatters) {
const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter)
window.devtoolsFormatters.splice(index, 1)
initCustomFormatter()

View File

@ -21,6 +21,7 @@ const emit = defineEmits([
'toggle-theme',
'toggle-ssr',
'toggle-prod',
'toggle-vapor',
'toggle-autosave',
'reload-page',
])
@ -215,6 +216,7 @@ h1 img {
}
.toggle-prod span,
.toggle-vapor span,
.toggle-ssr span,
.toggle-autosave span {
font-size: 12px;
@ -242,6 +244,15 @@ h1 img {
background-color: var(--green);
}
.toggle-vapor span {
background-color: var(--btn-bg);
}
.toggle-vapor.enabled span {
color: #fff;
background-color: var(--green);
}
.toggle-dark svg {
width: 18px;
height: 18px;

View File

@ -1,2 +1,2 @@
// serve vue to the iframe sandbox during dev.
export * from 'vue/dist/vue.runtime.esm-browser.prod.js'
export * from 'vue/dist/vue.runtime-with-vapor.esm-browser.prod.js'

View File

@ -1,2 +1,2 @@
// serve vue to the iframe sandbox during dev.
export * from 'vue'
export * from 'vue/dist/vue.runtime-with-vapor.esm-browser.js'

View File

@ -0,0 +1,9 @@
<script setup lang="ts">
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1>{{ msg }}</h1>
</template>

View File

@ -53,6 +53,8 @@ function copyVuePlugin(): Plugin {
copyFile(`vue/dist/vue.esm-browser.prod.js`)
copyFile(`vue/dist/vue.runtime.esm-browser.js`)
copyFile(`vue/dist/vue.runtime.esm-browser.prod.js`)
copyFile(`vue/dist/vue.runtime-with-vapor.esm-browser.js`)
copyFile(`vue/dist/vue.runtime-with-vapor.esm-browser.prod.js`)
copyFile(`server-renderer/dist/server-renderer.esm-browser.js`)
},
}

View File

@ -11,6 +11,7 @@
"enableNonBrowserBranches": true
},
"dependencies": {
"@vue/compiler-vapor": "workspace:^",
"monaco-editor": "^0.52.2",
"source-map-js": "^1.2.1"
}

View File

@ -1,15 +1,18 @@
import type * as m from 'monaco-editor'
import type { CompilerError } from '@vue/compiler-dom'
import { compile } from '@vue/compiler-dom'
import {
type CompilerError,
type CompilerOptions,
compile,
} from '@vue/compiler-dom'
import { compile as ssrCompile } from '@vue/compiler-ssr'
compile as vaporCompile,
} from '@vue/compiler-vapor'
// import { compile as ssrCompile } from '@vue/compiler-ssr'
import {
compilerOptions,
defaultOptions,
initOptions,
ssrMode,
vaporMode,
} from './options'
import { toRaw, watchEffect } from '@vue/runtime-dom'
import { SourceMapConsumer } from 'source-map-js'
@ -77,10 +80,16 @@ window.init = () => {
console.clear()
try {
const errors: CompilerError[] = []
const compileFn = ssrMode.value ? ssrCompile : compile
const compileFn = /* ssrMode.value ? ssrCompile : */ (
vaporMode.value ? vaporCompile : compile
) as typeof vaporCompile
const start = performance.now()
const { code, ast, map } = compileFn(source, {
...compilerOptions,
prefixIdentifiers:
compilerOptions.prefixIdentifiers ||
compilerOptions.mode === 'module' ||
compilerOptions.ssr,
filename: 'ExampleTemplate.vue',
sourceMap: true,
onError: err => {

View File

@ -1,8 +1,9 @@
import { createApp, h, reactive, ref } from 'vue'
import type { CompilerOptions } from '@vue/compiler-dom'
import type { CompilerOptions } from '@vue/compiler-vapor'
import { BindingTypes } from '@vue/compiler-core'
export const ssrMode = ref(false)
export const vaporMode = ref(true)
export const defaultOptions: CompilerOptions = {
mode: 'module',
@ -39,11 +40,11 @@ const App = {
compilerOptions.prefixIdentifiers || compilerOptions.mode === 'module'
return [
h('h1', `Vue 3 Template Explorer`),
h('h1', `Vue Template Explorer`),
h(
'a',
{
href: `https://github.com/vuejs/core/tree/${__COMMIT__}`,
href: `https://github.com/vuejs/vue/tree/${__COMMIT__}`,
target: `_blank`,
},
`@${__COMMIT__}`,
@ -222,6 +223,18 @@ const App = {
}),
h('label', { for: 'compat' }, 'v2 compat mode'),
]),
h('li', [
h('input', {
type: 'checkbox',
id: 'vapor',
checked: vaporMode.value,
onChange(e: Event) {
vaporMode.value = (e.target as HTMLInputElement).checked
},
}),
h('label', { for: 'vapor' }, 'vapor'),
]),
]),
]),
]

View File

@ -3,5 +3,5 @@
"compilerOptions": {
"isolatedDeclarations": false
},
"include": ["."]
"include": [".", "../packages/vue/__tests__/e2e/e2eUtils.ts"]
}

View File

@ -0,0 +1,195 @@
import path from 'node:path'
import {
E2E_TIMEOUT,
setupPuppeteer,
} from '../../../packages/vue/__tests__/e2e/e2eUtils'
import connect from 'connect'
import sirv from 'sirv'
describe('e2e: todomvc', () => {
const {
page,
click,
isVisible,
count,
text,
value,
isChecked,
isFocused,
classList,
enterValue,
clearValue,
timeout,
} = setupPuppeteer()
let server: any
const port = '8194'
beforeAll(() => {
server = connect()
.use(sirv(path.resolve(import.meta.dirname, '../dist')))
.listen(port)
process.on('SIGTERM', () => server && server.close())
})
afterAll(() => {
server.close()
})
async function removeItemAt(n: number) {
const item = (await page().$('.todo:nth-child(' + n + ')'))!
const itemBBox = (await item.boundingBox())!
await page().mouse.move(itemBBox.x + 10, itemBBox.y + 10)
await click('.todo:nth-child(' + n + ') .destroy')
}
test(
'vapor',
async () => {
const baseUrl = `http://localhost:${port}/todomvc/`
await page().goto(baseUrl)
expect(await isVisible('.main')).toBe(false)
expect(await isVisible('.footer')).toBe(false)
expect(await count('.filters .selected')).toBe(1)
expect(await text('.filters .selected')).toBe('All')
expect(await count('.todo')).toBe(0)
await enterValue('.new-todo', 'test')
expect(await count('.todo')).toBe(1)
expect(await isVisible('.todo .edit')).toBe(false)
expect(await text('.todo label')).toBe('test')
expect(await text('.todo-count strong')).toBe('1')
expect(await isChecked('.todo .toggle')).toBe(false)
expect(await isVisible('.main')).toBe(true)
expect(await isVisible('.footer')).toBe(true)
expect(await isVisible('.clear-completed')).toBe(false)
expect(await value('.new-todo')).toBe('')
await enterValue('.new-todo', 'test2')
expect(await count('.todo')).toBe(2)
expect(await text('.todo:nth-child(2) label')).toBe('test2')
expect(await text('.todo-count strong')).toBe('2')
// toggle
await click('.todo .toggle')
expect(await count('.todo.completed')).toBe(1)
expect(await classList('.todo:nth-child(1)')).toContain('completed')
expect(await text('.todo-count strong')).toBe('1')
expect(await isVisible('.clear-completed')).toBe(true)
await enterValue('.new-todo', 'test3')
expect(await count('.todo')).toBe(3)
expect(await text('.todo:nth-child(3) label')).toBe('test3')
expect(await text('.todo-count strong')).toBe('2')
await enterValue('.new-todo', 'test4')
await enterValue('.new-todo', 'test5')
expect(await count('.todo')).toBe(5)
expect(await text('.todo-count strong')).toBe('4')
// toggle more
await click('.todo:nth-child(4) .toggle')
await click('.todo:nth-child(5) .toggle')
expect(await count('.todo.completed')).toBe(3)
expect(await text('.todo-count strong')).toBe('2')
// remove
await removeItemAt(1)
expect(await count('.todo')).toBe(4)
expect(await count('.todo.completed')).toBe(2)
expect(await text('.todo-count strong')).toBe('2')
await removeItemAt(2)
expect(await count('.todo')).toBe(3)
expect(await count('.todo.completed')).toBe(2)
expect(await text('.todo-count strong')).toBe('1')
// remove all
await click('.clear-completed')
expect(await count('.todo')).toBe(1)
expect(await text('.todo label')).toBe('test2')
expect(await count('.todo.completed')).toBe(0)
expect(await text('.todo-count strong')).toBe('1')
expect(await isVisible('.clear-completed')).toBe(false)
// prepare to test filters
await enterValue('.new-todo', 'test')
await enterValue('.new-todo', 'test')
await click('.todo:nth-child(2) .toggle')
await click('.todo:nth-child(3) .toggle')
// active filter
await click('.filters li:nth-child(2) a')
await timeout(1)
expect(await count('.todo')).toBe(1)
expect(await count('.todo.completed')).toBe(0)
// add item with filter active
await enterValue('.new-todo', 'test')
expect(await count('.todo')).toBe(2)
// completed filter
await click('.filters li:nth-child(3) a')
await timeout(1)
expect(await count('.todo')).toBe(2)
expect(await count('.todo.completed')).toBe(2)
// filter on page load
await page().goto(`${baseUrl}#active`)
expect(await count('.todo')).toBe(2)
expect(await count('.todo.completed')).toBe(0)
expect(await text('.todo-count strong')).toBe('2')
// completed on page load
await page().goto(`${baseUrl}#completed`)
expect(await count('.todo')).toBe(2)
expect(await count('.todo.completed')).toBe(2)
expect(await text('.todo-count strong')).toBe('2')
// toggling with filter active
await click('.todo .toggle')
expect(await count('.todo')).toBe(1)
await click('.filters li:nth-child(2) a')
await timeout(1)
expect(await count('.todo')).toBe(3)
await click('.todo .toggle')
expect(await count('.todo')).toBe(2)
// editing triggered by blur
await click('.filters li:nth-child(1) a')
await timeout(1)
await click('.todo:nth-child(1) label', { clickCount: 2 })
expect(await count('.todo.editing')).toBe(1)
expect(await isFocused('.todo:nth-child(1) .edit')).toBe(true)
await clearValue('.todo:nth-child(1) .edit')
await page().type('.todo:nth-child(1) .edit', 'edited!')
await click('.new-todo') // blur
expect(await count('.todo.editing')).toBe(0)
expect(await text('.todo:nth-child(1) label')).toBe('edited!')
// editing triggered by enter
await click('.todo label', { clickCount: 2 })
await enterValue('.todo:nth-child(1) .edit', 'edited again!')
expect(await count('.todo.editing')).toBe(0)
expect(await text('.todo:nth-child(1) label')).toBe('edited again!')
// cancel
await click('.todo label', { clickCount: 2 })
await clearValue('.todo:nth-child(1) .edit')
await page().type('.todo:nth-child(1) .edit', 'edited!')
await page().keyboard.press('Escape')
expect(await count('.todo.editing')).toBe(0)
expect(await text('.todo:nth-child(1) label')).toBe('edited again!')
// empty value should remove
await click('.todo label', { clickCount: 2 })
await enterValue('.todo:nth-child(1) .edit', ' ')
expect(await count('.todo')).toBe(3)
// toggle all
await click('.toggle-all+label')
expect(await count('.todo.completed')).toBe(3)
await click('.toggle-all+label')
expect(await count('.todo:not(.completed)')).toBe(3)
},
E2E_TIMEOUT,
)
})

View File

@ -0,0 +1,84 @@
import path from 'node:path'
import {
E2E_TIMEOUT,
setupPuppeteer,
} from '../../../packages/vue/__tests__/e2e/e2eUtils'
import connect from 'connect'
import sirv from 'sirv'
describe('vdom / vapor interop', () => {
const { page, click, text, enterValue } = setupPuppeteer()
let server: any
const port = '8193'
beforeAll(() => {
server = connect()
.use(sirv(path.resolve(import.meta.dirname, '../dist')))
.listen(port)
process.on('SIGTERM', () => server && server.close())
})
afterAll(() => {
server.close()
})
test(
'should work',
async () => {
const baseUrl = `http://localhost:${port}/interop/`
await page().goto(baseUrl)
expect(await text('.vapor > h2')).toContain('Vapor component in VDOM')
expect(await text('.vapor-prop')).toContain('hello')
const t = await text('.vdom-slot-in-vapor-default')
expect(t).toContain('slot prop: slot prop')
expect(t).toContain('component prop: hello')
await click('.change-vdom-slot-in-vapor-prop')
expect(await text('.vdom-slot-in-vapor-default')).toContain(
'slot prop: changed',
)
expect(await text('.vdom-slot-in-vapor-test')).toContain('A test slot')
await click('.toggle-vdom-slot-in-vapor')
expect(await text('.vdom-slot-in-vapor-test')).toContain(
'fallback content',
)
await click('.toggle-vdom-slot-in-vapor')
expect(await text('.vdom-slot-in-vapor-test')).toContain('A test slot')
expect(await text('.vdom > h2')).toContain('VDOM component in Vapor')
expect(await text('.vdom-prop')).toContain('hello')
const tt = await text('.vapor-slot-in-vdom-default')
expect(tt).toContain('slot prop: slot prop')
expect(tt).toContain('component prop: hello')
await click('.change-vapor-slot-in-vdom-prop')
expect(await text('.vapor-slot-in-vdom-default')).toContain(
'slot prop: changed',
)
expect(await text('.vapor-slot-in-vdom-test')).toContain('fallback')
await click('.toggle-vapor-slot-in-vdom-default')
expect(await text('.vapor-slot-in-vdom-default')).toContain(
'default slot fallback',
)
await click('.toggle-vapor-slot-in-vdom-default')
await enterValue('input', 'bye')
expect(await text('.vapor-prop')).toContain('bye')
expect(await text('.vdom-slot-in-vapor-default')).toContain('bye')
expect(await text('.vdom-prop')).toContain('bye')
expect(await text('.vapor-slot-in-vdom-default')).toContain('bye')
},
E2E_TIMEOUT,
)
})

View File

@ -0,0 +1,2 @@
<a href="/interop/">VDOM / Vapor interop</a>
<a href="/todomvc/">Vapor TodoMVC</a>

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { ref } from 'vue'
import VaporComp from './VaporComp.vue'
const msg = ref('hello')
const passSlot = ref(true)
</script>
<template>
<input v-model="msg" />
<button class="toggle-vdom-slot-in-vapor" @click="passSlot = !passSlot">
toggle #test slot
</button>
<VaporComp :msg="msg">
<template #default="{ foo }">
<div>slot prop: {{ foo }}</div>
<div>component prop: {{ msg }}</div>
</template>
<template #test v-if="passSlot">A test slot</template>
</VaporComp>
</template>

View File

@ -0,0 +1,50 @@
<script setup vapor lang="ts">
import { ref } from 'vue'
import VdomComp from './VdomComp.vue'
defineProps<{
msg: string
}>()
const ok = ref(true)
const passSlot = ref(true)
const slotProp = ref('slot prop')
</script>
<template>
<div class="vapor" style="border: 2px solid red; padding: 10px">
<h2>This is a Vapor component in VDOM</h2>
<p class="vapor-prop">props.msg: {{ msg }}</p>
<button @click="ok = !ok">Toggle slots</button>
<div v-if="ok" style="border: 2px solid orange; padding: 10px">
<h3>vdom slots in vapor component</h3>
<button
class="change-vdom-slot-in-vapor-prop"
@click="slotProp = 'changed'"
>
change slot prop
</button>
<div class="vdom-slot-in-vapor-default">
#default: <slot :foo="slotProp" />
</div>
<div class="vdom-slot-in-vapor-test">
#test: <slot name="test">fallback content</slot>
</div>
</div>
<button
class="toggle-vapor-slot-in-vdom-default"
@click="passSlot = !passSlot"
>
Toggle default slot to vdom
</button>
<VdomComp :msg="msg">
<template #default="{ foo }" v-if="passSlot">
<div>slot prop: {{ foo }}</div>
<div>component prop: {{ msg }}</div>
</template>
</VdomComp>
</div>
</template>

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{
msg: string
}>()
const bar = ref('slot prop')
</script>
<template>
<div class="vdom" style="border: 2px solid blue; padding: 10px">
<h2>This is a VDOM component in Vapor</h2>
<p class="vdom-prop">props.msg: {{ msg }}</p>
<div style="border: 2px solid aquamarine; padding: 10px">
<h3>vapor slots in vdom</h3>
<button class="change-vapor-slot-in-vdom-prop" @click="bar = 'changed'">
Change slot prop
</button>
<div class="vapor-slot-in-vdom-default">
#default: <slot :foo="bar">default slot fallback</slot>
</div>
<div class="vapor-slot-in-vdom-test">
#test <slot name="test">fallback</slot>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,2 @@
<script type="module" src="./main.ts"></script>
<div id="app"></div>

View File

@ -0,0 +1,4 @@
import { createApp, vaporInteropPlugin } from 'vue'
import App from './App.vue'
createApp(App).use(vaporInteropPlugin).mount('#app')

View File

@ -0,0 +1,18 @@
{
"name": "vapor-e2e-test",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build"
},
"devDependencies": {
"@types/connect": "^3.4.38",
"@vitejs/plugin-vue": "catalog:",
"connect": "^3.7.0",
"sirv": "^2.0.4",
"vite": "catalog:",
"vue": "workspace:*"
}
}

View File

@ -0,0 +1,228 @@
<script setup vapor>
import {
reactive,
computed,
onMounted,
onUnmounted,
watchPostEffect,
} from 'vue'
const STORAGE_KEY = 'todos-vuejs-3.x'
const todoStorage = {
fetch() {
const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
todos.forEach((todo, index) => {
todo.id = index
})
todoStorage.uid = todos.length
return todos
},
save(todos) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
},
}
const filters = {
all(todos) {
return todos
},
active(todos) {
return todos.filter(todo => {
return !todo.completed
})
},
completed(todos) {
return todos.filter(function (todo) {
return todo.completed
})
},
}
function pluralize(n) {
return n === 1 ? 'item' : 'items'
}
const state = reactive({
todos: todoStorage.fetch(),
editedTodo: null,
newTodo: '',
beforeEditCache: '',
visibility: 'all',
remaining: computed(() => {
return filters.active(state.todos).length
}),
remainingText: computed(() => {
return ` ${pluralize(state.remaining)} left`
}),
filteredTodos: computed(() => {
return filters[state.visibility](state.todos)
}),
allDone: computed({
get: function () {
return state.remaining === 0
},
set: function (value) {
state.todos.forEach(todo => {
todo.completed = value
})
},
}),
})
watchPostEffect(() => {
todoStorage.save(state.todos)
})
onMounted(() => {
window.addEventListener('hashchange', onHashChange)
onHashChange()
})
onUnmounted(() => {
window.removeEventListener('hashchange', onHashChange)
})
function onHashChange() {
const visibility = window.location.hash.replace(/#\/?/, '')
if (filters[visibility]) {
state.visibility = visibility
} else {
window.location.hash = ''
state.visibility = 'all'
}
}
function addTodo() {
const value = state.newTodo && state.newTodo.trim()
if (!value) {
return
}
state.todos.push({
id: todoStorage.uid++,
title: value,
completed: false,
})
state.newTodo = ''
}
function removeTodo(todo) {
state.todos.splice(state.todos.indexOf(todo), 1)
}
function editTodo(todo) {
state.beforeEditCache = todo.title
state.editedTodo = todo
}
function doneEdit(todo) {
if (!state.editedTodo) {
return
}
state.editedTodo = null
todo.title = todo.title.trim()
if (!todo.title) {
removeTodo(todo)
}
}
function cancelEdit(todo) {
state.editedTodo = null
todo.title = state.beforeEditCache
}
function removeCompleted() {
state.todos = filters.active(state.todos)
}
// vapor custom directive
const vTodoFocus = (el, value) => {
watchPostEffect(() => value() && el.focus())
}
</script>
<template>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input
class="new-todo"
autofocus
autocomplete="off"
placeholder="What needs to be done?"
v-model="state.newTodo"
@keyup.enter="addTodo"
/>
</header>
<section class="main" v-show="state.todos.length">
<input
id="toggle-all"
class="toggle-all"
type="checkbox"
v-model="state.allDone"
/>
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li
v-for="todo in state.filteredTodos"
class="todo"
:key="todo.id"
:class="{
completed: todo.completed,
editing: todo === state.editedTodo,
}"
>
<div class="view">
<input class="toggle" type="checkbox" v-model="todo.completed" />
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
<input
class="edit"
type="text"
v-model="todo.title"
v-todo-focus="todo === state.editedTodo"
@blur="doneEdit(todo)"
@keyup.enter="doneEdit(todo)"
@keyup.escape="cancelEdit(todo)"
/>
</li>
</ul>
</section>
<footer class="footer" v-show="state.todos.length">
<span class="todo-count">
<strong>{{ state.remaining }}</strong>
<span>{{ state.remainingText }}</span>
</span>
<ul class="filters">
<li>
<a href="#/all" :class="{ selected: state.visibility === 'all' }"
>All</a
>
</li>
<li>
<a
href="#/active"
:class="{ selected: state.visibility === 'active' }"
>Active</a
>
</li>
<li>
<a
href="#/completed"
:class="{ selected: state.visibility === 'completed' }"
>Completed</a
>
</li>
</ul>
<button
class="clear-completed"
@click="removeCompleted"
v-show="state.todos.length > state.remaining"
>
Clear completed
</button>
</footer>
</section>
</template>

View File

@ -0,0 +1,2 @@
<script type="module" src="./main.ts"></script>
<div id="app"></div>

View File

@ -0,0 +1,5 @@
import { createVaporApp } from 'vue'
import App from './App.vue'
import 'todomvc-app-css/index.css'
createVaporApp(App).mount('#app')

View File

@ -0,0 +1,20 @@
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import * as CompilerSFC from 'vue/compiler-sfc'
import { resolve } from 'node:path'
export default defineConfig({
plugins: [
Vue({
compiler: CompilerSFC,
}),
],
build: {
rollupOptions: {
input: {
interop: resolve(import.meta.dirname, 'interop/index.html'),
todomvc: resolve(import.meta.dirname, 'todomvc/index.html'),
},
},
},
})

View File

@ -86,6 +86,13 @@ export interface Position {
column: number
}
export type AllNode =
| ParentNode
| ExpressionNode
| TemplateChildNode
| AttributeNode
| DirectiveNode
export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode
export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode

View File

@ -12,6 +12,7 @@ import type {
Program,
} from '@babel/types'
import { walk } from 'estree-walker'
import { type BindingMetadata, BindingTypes } from './options'
/**
* Return value indicates whether the AST walked can be a constant
@ -308,8 +309,8 @@ export const isFunctionType = (node: Node): node is Function => {
return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
}
export const isStaticProperty = (node: Node): node is ObjectProperty =>
node &&
export const isStaticProperty = (node?: Node): node is ObjectProperty =>
!!node &&
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
!node.computed
@ -510,3 +511,77 @@ export function unwrapTSNode(node: Node): Node {
return node
}
}
export function isStaticNode(node: Node): boolean {
node = unwrapTSNode(node)
switch (node.type) {
case 'UnaryExpression': // void 0, !true
return isStaticNode(node.argument)
case 'LogicalExpression': // 1 > 2
case 'BinaryExpression': // 1 + 2
return isStaticNode(node.left) && isStaticNode(node.right)
case 'ConditionalExpression': {
// 1 ? 2 : 3
return (
isStaticNode(node.test) &&
isStaticNode(node.consequent) &&
isStaticNode(node.alternate)
)
}
case 'SequenceExpression': // (1, 2)
case 'TemplateLiteral': // `foo${1}`
return node.expressions.every(expr => isStaticNode(expr))
case 'ParenthesizedExpression': // (1)
return isStaticNode(node.expression)
case 'StringLiteral':
case 'NumericLiteral':
case 'BooleanLiteral':
case 'NullLiteral':
case 'BigIntLiteral':
return true
}
return false
}
export function isConstantNode(node: Node, bindings: BindingMetadata): boolean {
if (isStaticNode(node)) return true
node = unwrapTSNode(node)
switch (node.type) {
case 'Identifier':
const type = bindings[node.name]
return type === BindingTypes.LITERAL_CONST
case 'RegExpLiteral':
return true
case 'ObjectExpression':
return node.properties.every(prop => {
// { bar() {} } object methods are not considered static nodes
if (prop.type === 'ObjectMethod') return false
// { ...{ foo: 1 } }
if (prop.type === 'SpreadElement')
return isConstantNode(prop.argument, bindings)
// { foo: 1 }
return (
(!prop.computed || isConstantNode(prop.key, bindings)) &&
isConstantNode(prop.value, bindings)
)
})
case 'ArrayExpression':
return node.elements.every(element => {
// [1, , 3]
if (element === null) return true
// [1, ...[2, 3]]
if (element.type === 'SpreadElement')
return isConstantNode(element.argument, bindings)
// [1, 2]
return isConstantNode(element, bindings)
})
}
return false
}

View File

@ -105,22 +105,38 @@ const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
export interface CodegenResult {
export interface BaseCodegenResult {
code: string
preamble: string
ast: RootNode
ast: unknown
map?: RawSourceMap
helpers?: Set<string> | Set<symbol>
}
enum NewlineType {
export interface CodegenResult extends BaseCodegenResult {
ast: RootNode
helpers: Set<symbol>
}
export enum NewlineType {
/** Start with `\n` */
Start = 0,
/** Ends with `\n` */
End = -1,
/** No `\n` included */
None = -2,
/** Don't know, calc it */
Unknown = -3,
}
export interface CodegenContext
extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
extends Omit<
Required<CodegenOptions>,
| 'bindingMetadata'
| 'inline'
| 'vaporRuntimeModuleName'
| 'expressionPlugins'
> {
source: string
code: string
line: number
@ -398,6 +414,7 @@ export function generate(
code: context.code,
preamble: isSetupInlined ? preambleContext.code : ``,
map: context.map ? context.map.toJSON() : undefined,
helpers: ast.helpers,
}
}

View File

@ -17,21 +17,26 @@ export {
createTransformContext,
traverseNode,
createStructuralDirectiveTransform,
getSelfName,
type NodeTransform,
type StructuralDirectiveTransform,
type DirectiveTransform,
} from './transform'
export {
generate,
NewlineType,
type CodegenContext,
type CodegenResult,
type CodegenSourceMapGenerator,
type RawSourceMap,
type BaseCodegenResult,
} from './codegen'
export {
ErrorCodes,
errorMessages,
createCompilerError,
defaultOnError,
defaultOnWarn,
type CoreCompilerError,
type CompilerError,
} from './errors'
@ -52,6 +57,7 @@ export {
transformExpression,
processExpression,
stringifyExpression,
isLiteralWhitelisted,
} from './transforms/transformExpression'
export {
buildSlots,
@ -75,4 +81,5 @@ export {
checkCompatEnabled,
warnDeprecation,
CompilerDeprecationTypes,
type CompilerCompatOptions,
} from './compat/compatConfig'

View File

@ -174,6 +174,12 @@ interface SharedTransformCodegenOptions {
* @default mode === 'module'
*/
prefixIdentifiers?: boolean
/**
* A list of parser plugins to enable for `@babel/parser`, which is used to
* parse expressions in bindings and interpolations.
* https://babeljs.io/docs/en/next/babel-parser#plugins
*/
expressionPlugins?: ParserPlugin[]
/**
* Control whether generate SSR-optimized render functions instead.
* The resulting function must be attached to the component via the
@ -272,12 +278,6 @@ export interface TransformOptions
* @default false
*/
cacheHandlers?: boolean
/**
* A list of parser plugins to enable for `@babel/parser`, which is used to
* parse expressions in bindings and interpolations.
* https://babeljs.io/docs/en/next/babel-parser#plugins
*/
expressionPlugins?: ParserPlugin[]
/**
* SFC scoped styles ID
*/

View File

@ -123,6 +123,11 @@ export interface TransformContext
filters?: Set<string>
}
export function getSelfName(filename: string): string | null {
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
return nameMatch ? capitalize(camelize(nameMatch[1])) : null
}
export function createTransformContext(
root: RootNode,
{
@ -150,11 +155,10 @@ export function createTransformContext(
compatConfig,
}: TransformOptions,
): TransformContext {
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
const context: TransformContext = {
// options
filename,
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
selfName: getSelfName(filename),
prefixIdentifiers,
hoistStatic,
hmr,

View File

@ -44,7 +44,8 @@ import { parseExpression } from '@babel/parser'
import { IS_REF, UNREF } from '../runtimeHelpers'
import { BindingTypes } from '../options'
const isLiteralWhitelisted = /*@__PURE__*/ makeMap('true,false,null,this')
export const isLiteralWhitelisted: (key: string) => boolean =
/*@__PURE__*/ makeMap('true,false,null,this')
export const transformExpression: NodeTransform = (node, context) => {
if (node.type === NodeTypes.INTERPOLATION) {

View File

@ -160,7 +160,7 @@ export const isMemberExpressionBrowser = (exp: ExpressionNode): boolean => {
export const isMemberExpressionNode: (
exp: ExpressionNode,
context: TransformContext,
context: Pick<TransformContext, 'expressionPlugins'>,
) => boolean = __BROWSER__
? (NOOP as any)
: (exp, context) => {
@ -185,7 +185,7 @@ export const isMemberExpressionNode: (
export const isMemberExpression: (
exp: ExpressionNode,
context: TransformContext,
context: Pick<TransformContext, 'expressionPlugins'>,
) => boolean = __BROWSER__ ? isMemberExpressionBrowser : isMemberExpressionNode
const fnExpRE =
@ -196,7 +196,7 @@ export const isFnExpressionBrowser: (exp: ExpressionNode) => boolean = exp =>
export const isFnExpressionNode: (
exp: ExpressionNode,
context: TransformContext,
context: Pick<TransformContext, 'expressionPlugins'>,
) => boolean = __BROWSER__
? (NOOP as any)
: (exp, context) => {
@ -227,7 +227,7 @@ export const isFnExpressionNode: (
export const isFnExpression: (
exp: ExpressionNode,
context: TransformContext,
context: Pick<TransformContext, 'expressionPlugins'>,
) => boolean = __BROWSER__ ? isFnExpressionBrowser : isFnExpressionNode
export function advancePositionWithClone(
@ -279,6 +279,7 @@ export function assert(condition: boolean, msg?: string): void {
}
}
/** find directive */
export function findDir(
node: ElementNode,
name: string | RegExp,

View File

@ -48,7 +48,7 @@ if (__TEST__) {
}
}
export const DOMErrorMessages: { [code: number]: string } = {
export const DOMErrorMessages: Record<DOMErrorCodes, string> = {
[DOMErrorCodes.X_V_HTML_NO_EXPRESSION]: `v-html is missing expression.`,
[DOMErrorCodes.X_V_HTML_WITH_CHILDREN]: `v-html will override element children.`,
[DOMErrorCodes.X_V_TEXT_NO_EXPRESSION]: `v-text is missing expression.`,
@ -60,4 +60,7 @@ export const DOMErrorMessages: { [code: number]: string } = {
[DOMErrorCodes.X_V_SHOW_NO_EXPRESSION]: `v-show is missing expression.`,
[DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN]: `<Transition> expects exactly one child element or component.`,
[DOMErrorCodes.X_IGNORED_SIDE_EFFECT_TAG]: `Tags with side effect (<script> and <style>) are ignored in client component templates.`,
// just to fulfill types
[DOMErrorCodes.__EXTEND_POINT__]: ``,
}

View File

@ -74,4 +74,6 @@ export {
DOMErrorCodes,
DOMErrorMessages,
} from './errors'
export { resolveModifiers } from './transforms/vOn'
export { isValidHTMLNesting } from './htmlNesting'
export * from '@vue/compiler-core'

View File

@ -15,7 +15,7 @@ import {
isStaticExp,
} from '@vue/compiler-core'
import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../runtimeHelpers'
import { capitalize, makeMap } from '@vue/shared'
import { capitalize, isString, makeMap } from '@vue/shared'
const isEventOptionModifier = /*@__PURE__*/ makeMap(`passive,once,capture`)
const isNonKeyModifier = /*@__PURE__*/ makeMap(
@ -30,12 +30,16 @@ const isNonKeyModifier = /*@__PURE__*/ makeMap(
const maybeKeyModifier = /*@__PURE__*/ makeMap('left,right')
const isKeyboardEvent = /*@__PURE__*/ makeMap(`onkeyup,onkeydown,onkeypress`)
const resolveModifiers = (
key: ExpressionNode,
export const resolveModifiers = (
key: ExpressionNode | string,
modifiers: SimpleExpressionNode[],
context: TransformContext,
context: TransformContext | null,
loc: SourceLocation,
) => {
): {
keyModifiers: string[]
nonKeyModifiers: string[]
eventOptionModifiers: string[]
} => {
const keyModifiers = []
const nonKeyModifiers = []
const eventOptionModifiers = []
@ -46,6 +50,7 @@ const resolveModifiers = (
if (
__COMPAT__ &&
modifier === 'native' &&
context &&
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_ON_NATIVE,
context,
@ -58,12 +63,16 @@ const resolveModifiers = (
// e.g. .passive & .capture
eventOptionModifiers.push(modifier)
} else {
const keyString = isString(key)
? key
: isStaticExp(key)
? key.content
: null
// runtimeModifiers: modifiers that needs runtime guards
if (maybeKeyModifier(modifier)) {
if (isStaticExp(key)) {
if (
isKeyboardEvent((key as SimpleExpressionNode).content.toLowerCase())
) {
if (keyString) {
if (isKeyboardEvent(keyString.toLowerCase())) {
keyModifiers.push(modifier)
} else {
nonKeyModifiers.push(modifier)

View File

@ -117,6 +117,8 @@ return () => {}
exports[`sfc reactive props destructure > default values w/ type declaration & key is string 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
import { toDisplayString as _toDisplayString } from "vue"
export default /*@__PURE__*/_defineComponent({
props: {
@ -129,7 +131,9 @@ export default /*@__PURE__*/_defineComponent({
return () => {}
return (_ctx: any,_cache: any) => {
return _toDisplayString(__props.foo)
}
}
})"

View File

@ -155,6 +155,7 @@ describe('sfc reactive props destructure', () => {
"onUpdate:modelValue": (val: number) => void // double-quoted string containing symbols
}>()
</script>
<template>{{ foo }}</template>
`)
expect(bindings).toStrictEqual({
__propsAliases: {
@ -173,6 +174,7 @@ describe('sfc reactive props destructure', () => {
"foo:bar": { type: String, required: true, default: 'foo-bar' },
"onUpdate:modelValue": { type: Function, required: true }
},`)
expect(content).toMatch(`__props.foo`)
assertCode(content)
})

View File

@ -381,6 +381,17 @@ h1 { color: red }
})
})
describe('vapor mode', () => {
test('on empty script', () => {
const { descriptor } = parse(`<script vapor></script>`)
expect(descriptor.vapor).toBe(true)
})
test('on template', () => {
const { descriptor } = parse(`<template vapor><div/></template>`)
expect(descriptor.vapor).toBe(true)
})
})
describe('warnings', () => {
function assertWarning(errors: Error[], msg: string) {
expect(errors.some(e => e.message.match(msg))).toBe(true)

View File

@ -46,6 +46,7 @@
"@vue/compiler-core": "workspace:*",
"@vue/compiler-dom": "workspace:*",
"@vue/compiler-ssr": "workspace:*",
"@vue/compiler-vapor": "workspace:*",
"@vue/shared": "workspace:*",
"estree-walker": "catalog:",
"magic-string": "catalog:",

View File

@ -2,6 +2,7 @@ import {
BindingTypes,
UNREF,
isFunctionType,
isStaticNode,
unwrapTSNode,
walkIdentifiers,
} from '@vue/compiler-dom'
@ -125,6 +126,10 @@ export interface SFCScriptCompileOptions {
* Transform Vue SFCs into custom elements.
*/
customElement?: boolean | ((filename: string) => boolean)
/**
* Force to use of Vapor mode.
*/
vapor?: boolean
}
export interface ImportBinding {
@ -169,6 +174,8 @@ export function compileScript(
const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
const scriptLang = script && script.lang
const scriptSetupLang = scriptSetup && scriptSetup.lang
const vapor = sfc.vapor || options.vapor
const ssr = options.templateOptions?.ssr
if (!scriptSetup) {
if (!script) {
@ -743,7 +750,7 @@ export function compileScript(
if (
sfc.cssVars.length &&
// no need to do this when targeting SSR
!options.templateOptions?.ssr
!ssr
) {
ctx.helperImports.add(CSS_VARS_HELPER)
ctx.helperImports.add('unref')
@ -853,12 +860,12 @@ export function compileScript(
} else {
// inline mode
if (sfc.template && !sfc.template.src) {
if (options.templateOptions && options.templateOptions.ssr) {
if (ssr) {
hasInlinedSsrRenderFn = true
}
// inline render function mode - we are going to compile the template and
// inline it right here
const { code, ast, preamble, tips, errors } = compileTemplate({
const { code, preamble, tips, errors, helpers } = compileTemplate({
filename,
ast: sfc.template.ast,
source: sfc.template.content,
@ -868,6 +875,7 @@ export function compileScript(
scoped: sfc.styles.some(s => s.scoped),
isProd: options.isProd,
ssrCssVars: sfc.cssVars,
vapor,
compilerOptions: {
...(options.templateOptions &&
options.templateOptions.compilerOptions),
@ -903,7 +911,7 @@ export function compileScript(
// avoid duplicated unref import
// as this may get injected by the render function preamble OR the
// css vars codegen
if (ast && ast.helpers.has(UNREF)) {
if (helpers && helpers.has(UNREF)) {
ctx.helperImports.delete('unref')
}
returned = code
@ -923,7 +931,11 @@ export function compileScript(
`\n}\n\n`,
)
} else {
ctx.s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
ctx.s.appendRight(
endOffset,
// vapor mode generates its own return when inlined
`\n${vapor && !ssr ? `` : `return `}${returned}\n}\n\n`,
)
}
// 10. finalize default export
@ -972,13 +984,17 @@ export function compileScript(
ctx.s.prependLeft(
startOffset,
`\n${genDefaultAs} /*@__PURE__*/${ctx.helper(
`defineComponent`,
vapor && !ssr ? `defineVaporComponent` : `defineComponent`,
)}({${def}${runtimeOptions}\n ${
hasAwait ? `async ` : ``
}setup(${args}) {\n${exposeCall}`,
)
ctx.s.appendRight(endOffset, `})`)
} else {
// in TS, defineVaporComponent adds the option already
if (vapor) {
runtimeOptions += `\n __vapor: true,`
}
if (defaultExport || definedOptions) {
// without TS, can't rely on rest spread, so we use Object.assign
// export default Object.assign(__default__, { ... })
@ -1247,40 +1263,3 @@ function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
return false
}
}
function isStaticNode(node: Node): boolean {
node = unwrapTSNode(node)
switch (node.type) {
case 'UnaryExpression': // void 0, !true
return isStaticNode(node.argument)
case 'LogicalExpression': // 1 > 2
case 'BinaryExpression': // 1 + 2
return isStaticNode(node.left) && isStaticNode(node.right)
case 'ConditionalExpression': {
// 1 ? 2 : 3
return (
isStaticNode(node.test) &&
isStaticNode(node.consequent) &&
isStaticNode(node.alternate)
)
}
case 'SequenceExpression': // (1, 2)
case 'TemplateLiteral': // `foo${1}`
return node.expressions.every(expr => isStaticNode(expr))
case 'ParenthesizedExpression': // (1)
return isStaticNode(node.expression)
case 'StringLiteral':
case 'NumericLiteral':
case 'BooleanLiteral':
case 'NullLiteral':
case 'BigIntLiteral':
return true
}
return false
}

View File

@ -1,5 +1,5 @@
import {
type CodegenResult,
type BaseCodegenResult,
type CompilerError,
type CompilerOptions,
type ElementNode,
@ -24,24 +24,29 @@ import {
} from './template/transformSrcset'
import { generateCodeFrame, isObject } from '@vue/shared'
import * as CompilerDOM from '@vue/compiler-dom'
import * as CompilerVapor from '@vue/compiler-vapor'
import * as CompilerSSR from '@vue/compiler-ssr'
import consolidate from '@vue/consolidate'
import { warnOnce } from './warn'
import { genCssVarsFromList } from './style/cssVars'
export interface TemplateCompiler {
compile(source: string | RootNode, options: CompilerOptions): CodegenResult
compile(
source: string | RootNode,
options: CompilerOptions,
): BaseCodegenResult
parse(template: string, options: ParserOptions): RootNode
}
export interface SFCTemplateCompileResults {
code: string
ast?: RootNode
ast?: unknown
preamble?: string
source: string
tips: string[]
errors: (string | CompilerError)[]
map?: RawSourceMap
helpers?: Set<string | symbol>
}
export interface SFCTemplateCompileOptions {
@ -52,6 +57,7 @@ export interface SFCTemplateCompileOptions {
scoped?: boolean
slotted?: boolean
isProd?: boolean
vapor?: boolean
ssr?: boolean
ssrCssVars?: string[]
inMap?: RawSourceMap
@ -168,6 +174,7 @@ function doCompileTemplate({
source,
ast: inAST,
ssr = false,
vapor = false,
ssrCssVars,
isProd = false,
compiler,
@ -202,7 +209,11 @@ function doCompileTemplate({
const shortId = id.replace(/^data-v-/, '')
const longId = `data-v-${shortId}`
const defaultCompiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM
const defaultCompiler = ssr
? (CompilerSSR as TemplateCompiler)
: vapor
? (CompilerVapor as TemplateCompiler)
: CompilerDOM
compiler = compiler || defaultCompiler
if (compiler !== defaultCompiler) {
@ -227,25 +238,30 @@ function doCompileTemplate({
inAST = createRoot(template.children, inAST.source)
}
let { code, ast, preamble, map } = compiler.compile(inAST || source, {
mode: 'module',
prefixIdentifiers: true,
hoistStatic: true,
cacheHandlers: true,
ssrCssVars:
ssr && ssrCssVars && ssrCssVars.length
? genCssVarsFromList(ssrCssVars, shortId, isProd, true)
: '',
scopeId: scoped ? longId : undefined,
slotted,
sourceMap: true,
...compilerOptions,
hmr: !isProd,
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
filename,
onError: e => errors.push(e),
onWarn: w => warnings.push(w),
})
let { code, ast, preamble, map, helpers } = compiler.compile(
inAST || source,
{
mode: 'module',
prefixIdentifiers: true,
hoistStatic: true,
cacheHandlers: true,
ssrCssVars:
ssr && ssrCssVars && ssrCssVars.length
? genCssVarsFromList(ssrCssVars, shortId, isProd, true)
: '',
scopeId: scoped ? longId : undefined,
slotted,
sourceMap: true,
...compilerOptions,
hmr: !isProd,
nodeTransforms: nodeTransforms.concat(
compilerOptions.nodeTransforms || [],
),
filename,
onError: e => errors.push(e),
onWarn: w => warnings.push(w),
},
)
// inMap should be the map produced by ./parse.ts which is a simple line-only
// mapping. If it is present, we need to adjust the final map and errors to
@ -271,7 +287,16 @@ function doCompileTemplate({
return msg
})
return { code, ast, preamble, source, errors, tips, map }
return {
code,
ast,
preamble,
source,
errors,
tips,
map,
helpers,
}
}
function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {

View File

@ -84,6 +84,8 @@ export interface SFCDescriptor {
*/
slotted: boolean
vapor: boolean
/**
* compare with an existing descriptor to determine whether HMR should perform
* a reload vs. re-render.
@ -137,6 +139,7 @@ export function parse(
customBlocks: [],
cssVars: [],
slotted: false,
vapor: false,
shouldForceReload: prevImports => hmrShouldReload(prevImports, descriptor),
}
@ -159,8 +162,9 @@ export function parse(
ignoreEmpty &&
node.tag !== 'template' &&
isEmpty(node) &&
!hasSrc(node)
!hasAttr(node, 'src')
) {
descriptor.vapor ||= hasAttr(node, 'vapor')
return
}
switch (node.tag) {
@ -171,6 +175,7 @@ export function parse(
source,
false,
) as SFCTemplateBlock)
descriptor.vapor ||= !!templateBlock.attrs.vapor
if (!templateBlock.attrs.src) {
templateBlock.ast = createRoot(node.children, source)
@ -195,7 +200,8 @@ export function parse(
break
case 'script':
const scriptBlock = createBlock(node, source, pad) as SFCScriptBlock
const isSetup = !!scriptBlock.attrs.setup
descriptor.vapor ||= !!scriptBlock.attrs.vapor
const isSetup = !!(scriptBlock.attrs.setup || scriptBlock.attrs.vapor)
if (isSetup && !descriptor.scriptSetup) {
descriptor.scriptSetup = scriptBlock
break
@ -404,13 +410,8 @@ function padContent(
}
}
function hasSrc(node: ElementNode) {
return node.props.some(p => {
if (p.type !== NodeTypes.ATTRIBUTE) {
return false
}
return p.name === 'src'
})
function hasAttr(node: ElementNode, name: string) {
return node.props.some(p => p.type === NodeTypes.ATTRIBUTE && p.name === name)
}
/**

View File

@ -79,6 +79,15 @@ export function processDefineProps(
)
}
ctx.propsTypeDecl = node.typeParameters.params[0]
// register bindings
const { props } = resolveTypeElements(ctx, ctx.propsTypeDecl)
if (props) {
for (const key in props) {
if (!(key in ctx.bindingMetadata)) {
ctx.bindingMetadata[key] = BindingTypes.PROPS
}
}
}
}
// handle props destructure
@ -190,10 +199,6 @@ export function extractRuntimeProps(
for (const prop of props) {
propStrings.push(genRuntimePropFromType(ctx, prop, hasStaticDefaults))
// register bindings
if ('bindingMetadata' in ctx && !(prop.key in ctx.bindingMetadata)) {
ctx.bindingMetadata[prop.key] = BindingTypes.PROPS
}
}
let propsDecls = `{

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018-present, Yuxi (Evan) You
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.

View File

@ -0,0 +1 @@
# @vue/compiler-vapor

View File

@ -0,0 +1,310 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compile > bindings 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
const x0 = _child(n0)
_renderEffect(() => _setText(x0, "count is " + _toDisplayString(_ctx.count) + "."))
return n0
}"
`;
exports[`compile > custom directive > basic 1`] = `
"import { resolveDirective as _resolveDirective, withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const _directive_test = _resolveDirective("test")
const _directive_hello = _resolveDirective("hello")
const n0 = t0()
_withVaporDirectives(n0, [[_directive_test], [_directive_hello, void 0, void 0, { world: true }]])
return n0
}"
`;
exports[`compile > custom directive > component 1`] = `
"import { resolveComponent as _resolveComponent, resolveDirective as _resolveDirective, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, withVaporDirectives as _withVaporDirectives, createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const _component_Bar = _resolveComponent("Bar")
const _component_Comp = _resolveComponent("Comp")
const _directive_hello = _resolveDirective("hello")
const _directive_test = _resolveDirective("test")
const n4 = _createComponentWithFallback(_component_Comp, null, {
"default": () => {
const n0 = _createIf(() => (true), () => {
const n3 = t0()
_setInsertionState(n3)
const n2 = _createComponentWithFallback(_component_Bar)
_withVaporDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]])
return n3
})
return n0
}
}, true)
_withVaporDirectives(n4, [[_directive_test]])
return n4
}"
`;
exports[`compile > directives > custom directive > basic 1`] = `
"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
_withVaporDirectives(n0, [[_ctx.vExample]])
return n0
}"
`;
exports[`compile > directives > custom directive > binding value 1`] = `
"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
_withVaporDirectives(n0, [[_ctx.vExample, () => _ctx.msg]])
return n0
}"
`;
exports[`compile > directives > custom directive > dynamic parameters 1`] = `
"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
_withVaporDirectives(n0, [[_ctx.vExample, () => _ctx.msg, _ctx.foo]])
return n0
}"
`;
exports[`compile > directives > custom directive > modifiers 1`] = `
"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
_withVaporDirectives(n0, [[_ctx.vExample, () => _ctx.msg, void 0, { bar: true }]])
return n0
}"
`;
exports[`compile > directives > custom directive > modifiers w/o binding 1`] = `
"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
_withVaporDirectives(n0, [[_ctx.vExample, void 0, void 0, { "foo-bar": true }]])
return n0
}"
`;
exports[`compile > directives > custom directive > static parameters 1`] = `
"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
_withVaporDirectives(n0, [[_ctx.vExample, () => _ctx.msg, "foo"]])
return n0
}"
`;
exports[`compile > directives > custom directive > static parameters and modifiers 1`] = `
"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
_withVaporDirectives(n0, [[_ctx.vExample, () => _ctx.msg, "foo", { bar: true }]])
return n0
}"
`;
exports[`compile > directives > v-cloak > basic 1`] = `
"import { template as _template } from 'vue';
const t0 = _template("<div>test</div>", true)
export function render(_ctx) {
const n0 = t0()
return n0
}"
`;
exports[`compile > directives > v-pre > basic 1`] = `
"import { template as _template } from 'vue';
const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
return n0
}"
`;
exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div>")
const t1 = _template("<div> </div>")
export function render(_ctx, $props, $emit, $attrs, $slots) {
const _component_Comp = _resolveComponent("Comp")
const n0 = t0()
const n3 = t1()
const n2 = _child(n3)
_setInsertionState(n3, 0)
const n1 = _createComponentWithFallback(_component_Comp)
_renderEffect(() => {
_setProp(n3, "id", _ctx.foo)
_setText(n2, _toDisplayString(_ctx.bar))
})
return [n0, n3]
}"
`;
exports[`compile > dynamic root 1`] = `
"import { toDisplayString as _toDisplayString, setText as _setText, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const n0 = t0()
_setText(n0, _toDisplayString(1) + _toDisplayString(2))
return n0
}"
`;
exports[`compile > dynamic root nodes and interpolation 1`] = `
"import { child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<button> </button>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
const x0 = _child(n0)
n0.$evtclick = e => _ctx.handleClick(e)
_renderEffect(() => {
const _count = _ctx.count
_setProp(n0, "id", _count)
_setText(x0, _toDisplayString(_count) + "foo" + _toDisplayString(_count) + "foo" + _toDisplayString(_count))
})
return n0
}"
`;
exports[`compile > execution order > basic 1`] = `
"import { child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
export function render(_ctx) {
const n0 = t0()
const x0 = _child(n0)
_renderEffect(() => {
_setProp(n0, "id", _ctx.foo)
_setText(x0, _toDisplayString(_ctx.bar))
})
return n0
}"
`;
exports[`compile > execution order > with v-once 1`] = `
"import { child as _child, next as _next, nthChild as _nthChild, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div><span> </span> <br> </div>", true)
export function render(_ctx) {
const n3 = t0()
const n0 = _child(n3)
const n1 = _next(n0)
const n2 = _nthChild(n3, 3)
const x0 = _child(n0)
_setText(x0, _toDisplayString(_ctx.foo))
_renderEffect(() => {
_setText(n1, " " + _toDisplayString(_ctx.bar))
_setText(n2, " " + _toDisplayString(_ctx.baz))
})
return n3
}"
`;
exports[`compile > expression parsing > interpolation 1`] = `
"
const n0 = t0()
_renderEffect(() => _setText(n0, _toDisplayString(a + b.value)))
return n0
"
`;
exports[`compile > expression parsing > v-bind 1`] = `
"
const n0 = t0()
_renderEffect(() => {
const _key = key.value
_setDynamicProps(n0, [{ [_key+1]: _unref(foo)[_key+1]() }], true)
})
return n0
"
`;
exports[`compile > fragment 1`] = `
"import { template as _template } from 'vue';
const t0 = _template("<p></p>")
const t1 = _template("<span></span>")
const t2 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
const n1 = t1()
const n2 = t2()
return [n0, n1, n2]
}"
`;
exports[`compile > setInsertionState > next, child and nthChild should be above the setInsertionState 1`] = `
"import { resolveComponent as _resolveComponent, child as _child, next as _next, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, nthChild as _nthChild, createIf as _createIf, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")
const t1 = _template("<div><div></div><!><div></div><!><div><button></button></div></div>", true)
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n6 = t1()
const n5 = _next(_child(n6))
const n7 = _nthChild(n6, 3)
const p0 = _next(n7)
const n4 = _child(p0)
_setInsertionState(n6, n5)
const n0 = _createComponentWithFallback(_component_Comp)
_setInsertionState(n6, n7)
const n1 = _createIf(() => (true), () => {
const n3 = t0()
return n3
})
_renderEffect(() => _setProp(n4, "disabled", _ctx.foo))
return n6
}"
`;
exports[`compile > static + dynamic root 1`] = `
"import { toDisplayString as _toDisplayString, setText as _setText, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const n0 = t0()
_setText(n0, _toDisplayString(1) + _toDisplayString(2) + "3" + _toDisplayString(4) + _toDisplayString(5) + "6" + _toDisplayString(7) + _toDisplayString(8) + "9" + 'A' + 'B')
return n0
}"
`;
exports[`compile > static template 1`] = `
"import { template as _template } from 'vue';
const t0 = _template("<div><p>hello</p><input><span></span></div>", true)
export function render(_ctx) {
const n0 = t0()
return n0
}"
`;

View File

@ -0,0 +1,44 @@
/**
* @vitest-environment jsdom
*/
const parser: DOMParser = new DOMParser()
function parseHTML(html: string): string {
return parser.parseFromString(html, 'text/html').body.innerHTML
}
function checkAbbr(
template: string,
abbrevation: string,
expected: string,
): void {
// TODO do some optimzations to make sure template === abbrevation
expect(parseHTML(abbrevation)).toBe(expected)
}
test('template abbreviation', () => {
checkAbbr('<div>hello</div>', '<div>hello', '<div>hello</div>')
checkAbbr(
'<div><div>hello</div></div>',
'<div><div>hello',
'<div><div>hello</div></div>',
)
checkAbbr(
'<div><span>foo</span><span/></div>',
'<div><span>foo</span><span>',
'<div><span>foo</span><span></span></div>',
)
checkAbbr(
'<div><hr/><div/></div>',
'<div><hr><div>',
'<div><hr><div></div></div>',
)
checkAbbr(
'<div><div/><hr/></div>',
'<div><div></div><hr>',
'<div><div></div><hr></div>',
)
checkAbbr('<span/>hello', '<span></span>hello', '<span></span>hello')
})

View File

@ -0,0 +1,265 @@
import { BindingTypes, type RootNode } from '@vue/compiler-dom'
import { type CompilerOptions, compile as _compile } from '../src'
// TODO This is a temporary test case for initial implementation.
// Remove it once we have more comprehensive tests.
// DO NOT ADD MORE TESTS HERE.
function compile(template: string | RootNode, options: CompilerOptions = {}) {
let { code } = _compile(template, {
...options,
mode: 'module',
prefixIdentifiers: true,
})
return code
}
describe('compile', () => {
test('static template', () => {
const code = compile(
`<div>
<p>hello</p>
<input />
<span />
</div>`,
)
expect(code).matchSnapshot()
})
test('dynamic root', () => {
const code = compile(`{{ 1 }}{{ 2 }}`)
expect(code).matchSnapshot()
})
test('dynamic root nodes and interpolation', () => {
const code = compile(
`<button @click="handleClick" :id="count">{{count}}foo{{count}}foo{{count}} </button>`,
)
expect(code).matchSnapshot()
})
test('static + dynamic root', () => {
const code = compile(
`{{ 1 }}{{ 2 }}3{{ 4 }}{{ 5 }}6{{ 7 }}{{ 8 }}9{{ 'A' }}{{ 'B' }}`,
)
expect(code).matchSnapshot()
})
test('fragment', () => {
const code = compile(`<p/><span/><div/>`)
expect(code).matchSnapshot()
})
test('bindings', () => {
const code = compile(`<div>count is {{ count }}.</div>`, {
bindingMetadata: {
count: BindingTypes.SETUP_REF,
},
})
expect(code).matchSnapshot()
})
describe('directives', () => {
describe('v-pre', () => {
test('basic', () => {
const code = compile(`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n`, {
bindingMetadata: {
foo: BindingTypes.SETUP_REF,
bar: BindingTypes.SETUP_REF,
},
})
expect(code).toMatchSnapshot()
expect(code).contains(
JSON.stringify('<div :id="foo"><Comp></Comp>{{ bar }}</div>'),
)
expect(code).not.contains('effect')
})
test('should not affect siblings after it', () => {
const code = compile(
`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
`<div :id="foo"><Comp/>{{ bar }}</div>`,
{
bindingMetadata: {
foo: BindingTypes.SETUP_REF,
bar: BindingTypes.SETUP_REF,
},
},
)
expect(code).toMatchSnapshot()
// Waiting for TODO, There should be more here.
})
})
describe('v-cloak', () => {
test('basic', () => {
const code = compile(`<div v-cloak>test</div>`)
expect(code).toMatchSnapshot()
expect(code).not.contains('v-cloak')
})
})
describe('custom directive', () => {
test('basic', () => {
const code = compile(`<div v-example></div>`, {
bindingMetadata: {
vExample: BindingTypes.SETUP_CONST,
},
})
expect(code).matchSnapshot()
})
test('binding value', () => {
const code = compile(`<div v-example="msg"></div>`, {
bindingMetadata: {
msg: BindingTypes.SETUP_REF,
vExample: BindingTypes.SETUP_CONST,
},
})
expect(code).matchSnapshot()
})
test('static parameters', () => {
const code = compile(`<div v-example:foo="msg"></div>`, {
bindingMetadata: {
msg: BindingTypes.SETUP_REF,
vExample: BindingTypes.SETUP_CONST,
},
})
expect(code).matchSnapshot()
})
test('modifiers', () => {
const code = compile(`<div v-example.bar="msg"></div>`, {
bindingMetadata: {
msg: BindingTypes.SETUP_REF,
vExample: BindingTypes.SETUP_CONST,
},
})
expect(code).matchSnapshot()
})
test('modifiers w/o binding', () => {
const code = compile(`<div v-example.foo-bar></div>`, {
bindingMetadata: {
vExample: BindingTypes.SETUP_CONST,
},
})
expect(code).matchSnapshot()
})
test('static parameters and modifiers', () => {
const code = compile(`<div v-example:foo.bar="msg"></div>`, {
bindingMetadata: {
msg: BindingTypes.SETUP_REF,
vExample: BindingTypes.SETUP_CONST,
},
})
expect(code).matchSnapshot()
})
test('dynamic parameters', () => {
const code = compile(`<div v-example:[foo]="msg"></div>`, {
bindingMetadata: {
foo: BindingTypes.SETUP_REF,
vExample: BindingTypes.SETUP_CONST,
},
})
expect(code).matchSnapshot()
})
})
})
describe('expression parsing', () => {
test('interpolation', () => {
const code = compile(`{{ a + b }}`, {
inline: true,
bindingMetadata: {
b: BindingTypes.SETUP_REF,
},
})
expect(code).matchSnapshot()
expect(code).contains('a + b.value')
})
test('v-bind', () => {
const code = compile(`<div :[key+1]="foo[key+1]()" />`, {
inline: true,
bindingMetadata: {
key: BindingTypes.SETUP_REF,
foo: BindingTypes.SETUP_MAYBE_REF,
},
})
expect(code).matchSnapshot()
expect(code).contains('const _key = key.value')
expect(code).contains('_key+1')
expect(code).contains(
'_setDynamicProps(n0, [{ [_key+1]: _unref(foo)[_key+1]() }], true)',
)
})
// TODO: add more test for expression parsing (v-on, v-slot, v-for)
})
describe('custom directive', () => {
test('basic', () => {
const code = compile(`<div v-test v-hello.world />`)
expect(code).matchSnapshot()
})
test('component', () => {
const code = compile(`
<Comp v-test>
<div v-if="true">
<Bar v-hello.world />
</div>
</Comp>
`)
expect(code).matchSnapshot()
})
})
describe('setInsertionState', () => {
test('next, child and nthChild should be above the setInsertionState', () => {
const code = compile(`
<div>
<div />
<Comp />
<div />
<div v-if="true" />
<div>
<button :disabled="foo" />
</div>
</div>
`)
expect(code).toMatchSnapshot()
})
})
describe('execution order', () => {
test('basic', () => {
const code = compile(`<div :id="foo">{{ bar }}</div>`)
expect(code).matchSnapshot()
expect(code).contains(
`_setProp(n0, "id", _ctx.foo)
_setText(x0, _toDisplayString(_ctx.bar))`,
)
})
test('with v-once', () => {
const code = compile(
`<div>
<span v-once>{{ foo }}</span>
{{ bar }}<br>
{{ baz }}
</div>`,
)
expect(code).matchSnapshot()
expect(code).contains(
`_setText(n1, " " + _toDisplayString(_ctx.bar))
_setText(n2, " " + _toDisplayString(_ctx.baz))`,
)
})
})
})

View File

@ -0,0 +1,3 @@
// import { compile } from '../src/compile'
describe.todo('scopeId compiler support', () => {})

View File

@ -0,0 +1,55 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: expression > basic 1`] = `
"import { toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setText(n0, _toDisplayString(_ctx.a)))
return n0
}"
`;
exports[`compiler: expression > props 1`] = `
"import { toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
_renderEffect(() => _setText(n0, _toDisplayString($props.foo)))
return n0
}"
`;
exports[`compiler: expression > props aliased 1`] = `
"import { toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
_renderEffect(() => _setText(n0, _toDisplayString($props['bar'])))
return n0
}"
`;
exports[`compiler: expression > update expression 1`] = `
"import { child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
export function render(_ctx) {
const n1 = t0()
const n0 = _child(n1)
const x1 = _child(n1)
_renderEffect(() => {
const _String = String
const _foo = _ctx.foo
_setProp(n1, "id", _String(_foo.id++))
_setProp(n1, "foo", _foo)
_setProp(n1, "bar", _ctx.bar++)
_setText(n0, _toDisplayString(_String(_foo.id++)) + " " + _toDisplayString(_foo) + " " + _toDisplayString(_ctx.bar))
_setText(x1, _toDisplayString(_String(_foo.id++)) + " " + _toDisplayString(_foo) + " " + _toDisplayString(_ctx.bar))
})
return n1
}"
`;

View File

@ -0,0 +1,76 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: children transform > anchor insertion in middle 1`] = `
"import { child as _child, next as _next, setInsertionState as _setInsertionState, createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>")
const t1 = _template("<div><div></div><!><div></div></div>", true)
export function render(_ctx) {
const n4 = t1()
const n3 = _next(_child(n4))
_setInsertionState(n4, n3)
const n0 = _createIf(() => (1), () => {
const n2 = t0()
return n2
}, null, true)
return n4
}"
`;
exports[`compiler: children transform > children & sibling references 1`] = `
"import { child as _child, next as _next, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div><p> </p> <p> </p></div>", true)
export function render(_ctx) {
const n3 = t0()
const n0 = _child(n3)
const n1 = _next(n0)
const n2 = _next(n1)
const x0 = _child(n0)
const x2 = _child(n2)
_renderEffect(() => {
_setText(x0, _toDisplayString(_ctx.first))
_setText(n1, " " + _toDisplayString(_ctx.second) + " " + _toDisplayString(_ctx.third) + " ")
_setText(x2, _toDisplayString(_ctx.forth))
})
return n3
}"
`;
exports[`compiler: children transform > efficient find 1`] = `
"import { child as _child, nthChild as _nthChild, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div><div>x</div><div>x</div><div> </div></div>", true)
export function render(_ctx) {
const n1 = t0()
const n0 = _nthChild(n1, 2)
const x0 = _child(n0)
_renderEffect(() => _setText(x0, _toDisplayString(_ctx.msg)))
return n1
}"
`;
exports[`compiler: children transform > efficient traversal 1`] = `
"import { child as _child, next as _next, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div><div>x</div><div><span> </span></div><div><span> </span></div><div><span> </span></div></div>", true)
export function render(_ctx) {
const n3 = t0()
const p0 = _next(_child(n3))
const p1 = _next(p0)
const p2 = _next(p1)
const n0 = _child(p0)
const n1 = _child(p1)
const n2 = _child(p2)
const x0 = _child(n0)
const x1 = _child(n1)
const x2 = _child(n2)
_renderEffect(() => {
const _msg = _ctx.msg
_setText(x0, _toDisplayString(_msg))
_setText(x1, _toDisplayString(_msg))
_setText(x2, _toDisplayString(_msg))
})
return n3
}"
`;

View File

@ -0,0 +1,463 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: element transform > component > cache v-on expression with unique handler name 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const _component_Bar = _resolveComponent("Bar")
const _on_bar = $event => (_ctx.handleBar($event))
const n0 = _createComponentWithFallback(_component_Foo, { onBar: () => _on_bar })
const _on_bar1 = () => _ctx.handler
const n1 = _createComponentWithFallback(_component_Bar, { onBar: () => _on_bar1 })
return [n0, n1]
}"
`;
exports[`compiler: element transform > component > do not resolve component from non-script-setup bindings 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx, $props, $emit, $attrs, $slots) {
const _component_Example = _resolveComponent("Example")
const n0 = _createComponentWithFallback(_component_Example, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > generate multi root component 1`] = `
"import { createComponent as _createComponent, template as _template } from 'vue';
const t0 = _template("123")
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = _createComponent(_ctx.Comp)
const n1 = t0()
return [n0, n1]
}"
`;
exports[`compiler: element transform > component > generate single root component 1`] = `
"import { createComponent as _createComponent } from 'vue';
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = _createComponent(_ctx.Comp, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > import + resolve component 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > resolve component from setup bindings (inline const) 1`] = `
"
const n0 = _createComponent(Example, null, null, true)
return n0
"
`;
exports[`compiler: element transform > component > resolve component from setup bindings (inline) 1`] = `
"
const n0 = _createComponent(_unref(Example), null, null, true)
return n0
"
`;
exports[`compiler: element transform > component > resolve component from setup bindings 1`] = `
"import { createComponent as _createComponent } from 'vue';
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = _createComponent(_ctx.Example, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > resolve implicitly self-referencing component 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Example__self = _resolveComponent("Example", true)
const n0 = _createComponentWithFallback(_component_Example__self, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > resolve namespaced component from props bindings (inline) 1`] = `
"
const n0 = _createComponent(Foo.Example, null, null, true)
return n0
"
`;
exports[`compiler: element transform > component > resolve namespaced component from props bindings (non-inline) 1`] = `
"import { createComponent as _createComponent } from 'vue';
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = _createComponent(_ctx.Foo.Example, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > resolve namespaced component from setup bindings (inline const) 1`] = `
"
const n0 = _createComponent(Foo.Example, null, null, true)
return n0
"
`;
exports[`compiler: element transform > component > resolve namespaced component from setup bindings 1`] = `
"import { createComponent as _createComponent } from 'vue';
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = _createComponent(_ctx.Foo.Example, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > static props 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, {
id: () => ("foo"),
class: () => ("bar")
}, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > v-bind="obj" 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, { $: [
() => (_ctx.obj)
] }, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > v-bind="obj" after static prop 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, {
id: () => ("foo"),
$: [
() => (_ctx.obj)
]
}, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > v-bind="obj" before static prop 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, { $: [
() => (_ctx.obj),
{ id: () => ("foo") }
] }, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > v-bind="obj" between static props 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, {
id: () => ("foo"),
$: [
() => (_ctx.obj),
{ class: () => ("bar") }
]
}, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > v-for on component should not mark as single root 1`] = `
"import { createComponent as _createComponent, createFor as _createFor } from 'vue';
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = _createFor(() => (_ctx.items), (_for_item0) => {
const n2 = _createComponent(_ctx.Comp)
return n2
}, (item) => (item), 2)
return n0
}"
`;
exports[`compiler: element transform > component > v-on expression is a function call 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const _on_bar = $event => (_ctx.handleBar($event))
const n0 = _createComponentWithFallback(_component_Foo, { onBar: () => _on_bar }, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > v-on expression is inline statement 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const _on_bar = () => _ctx.handler
const n0 = _createComponentWithFallback(_component_Foo, { onBar: () => _on_bar }, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > v-on="obj" 1`] = `
"import { resolveComponent as _resolveComponent, toHandlers as _toHandlers, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, { $: [
() => (_toHandlers(_ctx.obj))
] }, null, true)
return n0
}"
`;
exports[`compiler: element transform > component dynamic event with once modifier 1`] = `
"import { resolveComponent as _resolveComponent, toHandlerKey as _toHandlerKey, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, { $: [
() => ({ [_toHandlerKey(_ctx.foo) + "Once"]: () => _ctx.bar })
] }, null, true)
return n0
}"
`;
exports[`compiler: element transform > component event with once modifier 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, { onFooOnce: () => _ctx.bar }, null, true)
return n0
}"
`;
exports[`compiler: element transform > component with dynamic event arguments 1`] = `
"import { resolveComponent as _resolveComponent, toHandlerKey as _toHandlerKey, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, { $: [
() => ({ [_toHandlerKey(_ctx.foo-_ctx.bar)]: () => _ctx.bar }),
() => ({ [_toHandlerKey(_ctx.baz)]: () => _ctx.qux })
] }, null, true)
return n0
}"
`;
exports[`compiler: element transform > component with dynamic prop arguments 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, { $: [
() => ({ [_ctx.foo-_ctx.bar]: _ctx.bar }),
() => ({ [_ctx.baz]: _ctx.qux })
] }, null, true)
return n0
}"
`;
exports[`compiler: element transform > dynamic component > capitalized version w/ static binding 1`] = `
"import { resolveDynamicComponent as _resolveDynamicComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const n0 = _createComponentWithFallback(_resolveDynamicComponent("foo"), null, null, true)
return n0
}"
`;
exports[`compiler: element transform > dynamic component > dynamic binding 1`] = `
"import { createDynamicComponent as _createDynamicComponent } from 'vue';
export function render(_ctx) {
const n0 = _createDynamicComponent(() => (_ctx.foo), null, null, true)
return n0
}"
`;
exports[`compiler: element transform > dynamic component > dynamic binding shorthand 1`] = `
"import { createDynamicComponent as _createDynamicComponent } from 'vue';
export function render(_ctx) {
const n0 = _createDynamicComponent(() => (_ctx.is), null, null, true)
return n0
}"
`;
exports[`compiler: element transform > dynamic component > normal component with is prop 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_custom_input = _resolveComponent("custom-input")
const n0 = _createComponentWithFallback(_component_custom_input, { is: () => ("foo") }, null, true)
return n0
}"
`;
exports[`compiler: element transform > dynamic component > static binding 1`] = `
"import { resolveDynamicComponent as _resolveDynamicComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const n0 = _createComponentWithFallback(_resolveDynamicComponent("foo"), null, null, true)
return n0
}"
`;
exports[`compiler: element transform > empty template 1`] = `
"
export function render(_ctx) {
return null
}"
`;
exports[`compiler: element transform > invalid html nesting 1`] = `
"import { insert as _insert, template as _template } from 'vue';
const t0 = _template("<div>123</div>")
const t1 = _template("<p></p>")
const t2 = _template("<form></form>")
export function render(_ctx) {
const n1 = t1()
const n3 = t2()
const n0 = t0()
const n2 = t2()
_insert(n0, n1)
_insert(n2, n3)
return [n1, n3]
}"
`;
exports[`compiler: element transform > props + children 1`] = `
"import { template as _template } from 'vue';
const t0 = _template("<div id=\\"foo\\"><span></span></div>", true)
export function render(_ctx) {
const n0 = t0()
return n0
}"
`;
exports[`compiler: element transform > props merging: class 1`] = `
"import { setClass as _setClass, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setClass(n0, ["foo", { bar: _ctx.isBar }]))
return n0
}"
`;
exports[`compiler: element transform > props merging: event handlers 1`] = `
"import { withKeys as _withKeys, delegate as _delegate, delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
_delegate(n0, "click", _withKeys(e => _ctx.a(e), ["foo"]))
_delegate(n0, "click", _withKeys(e => _ctx.b(e), ["bar"]))
return n0
}"
`;
exports[`compiler: element transform > props merging: style 1`] = `
"import { setStyle as _setStyle, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_setStyle(n0, ["color: green", { color: 'red' }])
return n0
}"
`;
exports[`compiler: element transform > static props 1`] = `
"import { template as _template } from 'vue';
const t0 = _template("<div id=\\"foo\\" class=\\"bar\\"></div>", true)
export function render(_ctx) {
const n0 = t0()
return n0
}"
`;
exports[`compiler: element transform > v-bind="obj" 1`] = `
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, [_ctx.obj], true))
return n0
}"
`;
exports[`compiler: element transform > v-bind="obj" after static prop 1`] = `
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, [{ id: "foo" }, _ctx.obj], true))
return n0
}"
`;
exports[`compiler: element transform > v-bind="obj" before static prop 1`] = `
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, [_ctx.obj, { id: "foo" }], true))
return n0
}"
`;
exports[`compiler: element transform > v-bind="obj" between static props 1`] = `
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, [{ id: "foo" }, _ctx.obj, { class: "bar" }], true))
return n0
}"
`;
exports[`compiler: element transform > v-on="obj" 1`] = `
"import { setDynamicEvents as _setDynamicEvents, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicEvents(n0, _ctx.obj))
return n0
}"
`;

View File

@ -0,0 +1,52 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: template ref transform > dynamic ref 1`] = `
"import { renderEffect as _renderEffect, setRef as _setRef, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
let r0
_renderEffect(() => r0 = _setRef(n0, _ctx.foo, r0))
return n0
}"
`
exports[`compiler: template ref transform > ref + v-for 1`] = `
"import { setRef as _setRef, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = _createFor(() => ([1,2,3]), (_block) => {
const n2 = t0()
_setRef(n2, "foo", void 0, true)
return [n2, () => {}]
})
return n0
}"
`
exports[`compiler: template ref transform > ref + v-if 1`] = `
"import { setRef as _setRef, createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = _createIf(() => (true), () => {
const n2 = t0()
_setRef(n2, "foo")
return n2
})
return n0
}"
`
exports[`compiler: template ref transform > static ref 1`] = `
"import { setRef as _setRef, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setRef(n0, "foo")
return n0
}"
`

View File

@ -0,0 +1,163 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: transform <slot> outlets > default slot outlet 1`] = `
"import { createSlot as _createSlot } from 'vue';
export function render(_ctx) {
const n0 = _createSlot("default", null)
return n0
}"
`;
exports[`compiler: transform <slot> outlets > default slot outlet with fallback 1`] = `
"import { createSlot as _createSlot, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = _createSlot("default", null, () => {
const n2 = t0()
return n2
})
return n0
}"
`;
exports[`compiler: transform <slot> outlets > default slot outlet with props & fallback 1`] = `
"import { createSlot as _createSlot, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = _createSlot("default", { foo: () => (_ctx.bar) }, () => {
const n2 = t0()
return n2
})
return n0
}"
`;
exports[`compiler: transform <slot> outlets > default slot outlet with props 1`] = `
"import { createSlot as _createSlot } from 'vue';
export function render(_ctx) {
const n0 = _createSlot("default", {
foo: () => ("bar"),
baz: () => (_ctx.qux),
fooBar: () => (_ctx.foo-_ctx.bar)
})
return n0
}"
`;
exports[`compiler: transform <slot> outlets > dynamically named slot outlet 1`] = `
"import { createSlot as _createSlot } from 'vue';
export function render(_ctx) {
const n0 = _createSlot(() => (_ctx.foo + _ctx.bar), null)
return n0
}"
`;
exports[`compiler: transform <slot> outlets > dynamically named slot outlet with v-bind shorthand 1`] = `
"import { createSlot as _createSlot } from 'vue';
export function render(_ctx) {
const n0 = _createSlot(() => (_ctx.name), null)
return n0
}"
`;
exports[`compiler: transform <slot> outlets > error on unexpected custom directive on <slot> 1`] = `
"import { resolveDirective as _resolveDirective, createSlot as _createSlot } from 'vue';
export function render(_ctx) {
const _directive_foo = _resolveDirective("foo")
const n0 = _createSlot("default", null)
return n0
}"
`;
exports[`compiler: transform <slot> outlets > error on unexpected custom directive with v-show on <slot> 1`] = `
"import { createSlot as _createSlot } from 'vue';
export function render(_ctx) {
const n0 = _createSlot("default", null)
return n0
}"
`;
exports[`compiler: transform <slot> outlets > named slot outlet with fallback 1`] = `
"import { createSlot as _createSlot, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = _createSlot("foo", null, () => {
const n2 = t0()
return n2
})
return n0
}"
`;
exports[`compiler: transform <slot> outlets > named slot outlet with props & fallback 1`] = `
"import { createSlot as _createSlot, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = _createSlot("foo", { foo: () => (_ctx.bar) }, () => {
const n2 = t0()
return n2
})
return n0
}"
`;
exports[`compiler: transform <slot> outlets > statically named slot outlet 1`] = `
"import { createSlot as _createSlot } from 'vue';
export function render(_ctx) {
const n0 = _createSlot("foo", null)
return n0
}"
`;
exports[`compiler: transform <slot> outlets > statically named slot outlet with props 1`] = `
"import { createSlot as _createSlot } from 'vue';
export function render(_ctx) {
const n0 = _createSlot("foo", {
foo: () => ("bar"),
baz: () => (_ctx.qux)
})
return n0
}"
`;
exports[`compiler: transform <slot> outlets > statically named slot outlet with v-bind="obj" 1`] = `
"import { createSlot as _createSlot } from 'vue';
export function render(_ctx) {
const n0 = _createSlot("foo", {
foo: () => ("bar"),
$: [
() => (_ctx.obj),
{ baz: () => (_ctx.qux) }
]
})
return n0
}"
`;
exports[`compiler: transform <slot> outlets > statically named slot outlet with v-on 1`] = `
"import { createSlot as _createSlot, toHandlers as _toHandlers } from 'vue';
export function render(_ctx) {
const n0 = _createSlot("default", {
onClick: () => _ctx.foo,
$: [
() => (_toHandlers(_ctx.bar)),
{ baz: () => (_ctx.qux) }
]
})
return n0
}"
`;

View File

@ -0,0 +1,85 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: template ref transform > dynamic ref 1`] = `
"import { createTemplateRefSetter as _createTemplateRefSetter, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const _setTemplateRef = _createTemplateRefSetter()
const n0 = t0()
let r0
_renderEffect(() => r0 = _setTemplateRef(n0, _ctx.foo, r0))
return n0
}"
`;
exports[`compiler: template ref transform > function ref 1`] = `
"import { createTemplateRefSetter as _createTemplateRefSetter, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const _setTemplateRef = _createTemplateRefSetter()
const n0 = t0()
let r0
_renderEffect(() => {
const _foo = _ctx.foo
r0 = _setTemplateRef(n0, bar => {
_foo.value = bar
;({ baz: _ctx.baz } = bar)
console.log(_foo.value, _ctx.baz)
}, r0)
})
return n0
}"
`;
exports[`compiler: template ref transform > ref + v-for 1`] = `
"import { createTemplateRefSetter as _createTemplateRefSetter, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const _setTemplateRef = _createTemplateRefSetter()
const n0 = _createFor(() => ([1,2,3]), (_for_item0) => {
const n2 = t0()
_setTemplateRef(n2, "foo", void 0, true)
return n2
}, null, 4)
return n0
}"
`;
exports[`compiler: template ref transform > ref + v-if 1`] = `
"import { createTemplateRefSetter as _createTemplateRefSetter, createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const _setTemplateRef = _createTemplateRefSetter()
const n0 = _createIf(() => (true), () => {
const n2 = t0()
_setTemplateRef(n2, "foo")
return n2
})
return n0
}"
`;
exports[`compiler: template ref transform > static ref (inline mode) 1`] = `
"
const _setTemplateRef = _createTemplateRefSetter()
const n0 = t0()
_setTemplateRef(n0, foo)
return n0
"
`;
exports[`compiler: template ref transform > static ref 1`] = `
"import { createTemplateRefSetter as _createTemplateRefSetter, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const _setTemplateRef = _createTemplateRefSetter()
const n0 = t0()
_setTemplateRef(n0, "foo")
return n0
}"
`;

View File

@ -0,0 +1,23 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: text transform > consecutive text 1`] = `
"import { toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setText(n0, _toDisplayString(_ctx.msg)))
return n0
}"
`;
exports[`compiler: text transform > no consecutive text 1`] = `
"import { setText as _setText, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const n0 = t0()
_setText(n0, "hello world")
return n0
}"
`;

View File

@ -0,0 +1,658 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`cache multiple access > cache variable used in both property shorthand and normal binding 1`] = `
"import { setStyle as _setStyle, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
const _color = _ctx.color
_setStyle(n0, {color: _color})
_setProp(n0, "id", _color)
})
return n0
}"
`;
exports[`cache multiple access > dynamic key bindings with expressions 1`] = `
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
const _key = _ctx.key
_setDynamicProps(n0, [{ [_key+1]: _ctx.foo[_key+1]() }], true)
})
return n0
}"
`;
exports[`cache multiple access > dynamic property access 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
const _obj = _ctx.obj
_setProp(n0, "id", _obj[1][_ctx.baz] + _obj.bar)
})
return n0
}"
`;
exports[`cache multiple access > function calls with arguments 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
const n1 = t0()
const n2 = t0()
_renderEffect(() => {
const _foo = _ctx.foo
const _bar = _ctx.bar
const _foo_bar_baz = _foo[_bar(_ctx.baz)]
_setProp(n0, "id", _foo_bar_baz)
_setProp(n1, "id", _foo_bar_baz)
_setProp(n2, "id", _bar() + _foo)
})
return [n0, n1, n2]
}"
`;
exports[`cache multiple access > not cache variable and member expression with the same name 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setProp(n0, "id", _ctx.bar + _ctx.obj.bar))
return n0
}"
`;
exports[`cache multiple access > not cache variable in function expression 1`] = `
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, [{ foo: bar => _ctx.foo = bar }], true))
return n0
}"
`;
exports[`cache multiple access > not cache variable only used in property shorthand 1`] = `
"import { setStyle as _setStyle, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setStyle(n0, {color: _ctx.color}))
return n0
}"
`;
exports[`cache multiple access > object property chain access 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
const n1 = t0()
_renderEffect(() => {
const _obj = _ctx.obj
const _obj_foo_baz_obj_bar = _obj['foo']['baz'] + _obj.bar
_setProp(n0, "id", _obj_foo_baz_obj_bar)
_setProp(n1, "id", _obj_foo_baz_obj_bar)
})
return [n0, n1]
}"
`;
exports[`cache multiple access > object property name substring cases 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
const _p = _ctx.p
const _p_title = _p.title
_setProp(n0, "id", _p_title + _p.titles + _p_title)
})
return n0
}"
`;
exports[`cache multiple access > optional chaining 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
const _obj = _ctx.obj
_setProp(n0, "id", _obj?.foo + _obj?.bar)
})
return n0
}"
`;
exports[`cache multiple access > repeated expression in expressions 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
const n1 = t0()
const n2 = t0()
_renderEffect(() => {
const _foo = _ctx.foo
const _foo_bar = _foo + _ctx.bar
_setProp(n0, "id", _foo_bar)
_setProp(n1, "id", _foo_bar)
_setProp(n2, "id", _foo + _foo_bar)
})
return [n0, n1, n2]
}"
`;
exports[`cache multiple access > repeated expressions 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
const n1 = t0()
_renderEffect(() => {
const _foo_bar = _ctx.foo + _ctx.bar
_setProp(n0, "id", _foo_bar)
_setProp(n1, "id", _foo_bar)
})
return [n0, n1]
}"
`;
exports[`cache multiple access > repeated variable in expressions 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
const n1 = t0()
_renderEffect(() => {
const _foo = _ctx.foo
_setProp(n0, "id", _foo + _foo + _ctx.bar)
_setProp(n1, "id", _foo)
})
return [n0, n1]
}"
`;
exports[`cache multiple access > repeated variables 1`] = `
"import { setClass as _setClass, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
const n1 = t0()
_renderEffect(() => {
const _foo = _ctx.foo
_setClass(n0, _foo)
_setClass(n1, _foo)
})
return [n0, n1]
}"
`;
exports[`cache multiple access > variable name substring edge cases 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
const _title = _ctx.title
_setProp(n0, "id", _title + _ctx.titles + _title)
})
return n0
}"
`;
exports[`compiler v-bind > .attr modifier 1`] = `
"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setAttr(n0, "foo-bar", _ctx.id))
return n0
}"
`;
exports[`compiler v-bind > .attr modifier w/ innerHTML 1`] = `
"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setAttr(n0, "innerHTML", _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .attr modifier w/ no expression 1`] = `
"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setAttr(n0, "foo-bar", _ctx.fooBar))
return n0
}"
`;
exports[`compiler v-bind > .attr modifier w/ progress value 1`] = `
"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<progress></progress>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setAttr(n0, "value", _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .attr modifier w/ textContent 1`] = `
"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setAttr(n0, "textContent", _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .attr modifier w/ value 1`] = `
"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setAttr(n0, "value", _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .camel modifier 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setProp(n0, "fooBar", _ctx.id))
return n0
}"
`;
exports[`compiler v-bind > .camel modifier w/ dynamic arg 1`] = `
"import { camelize as _camelize, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, [{ [_camelize(_ctx.foo)]: _ctx.id }], true))
return n0
}"
`;
exports[`compiler v-bind > .camel modifier w/ no expression 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setProp(n0, "fooBar", _ctx.fooBar))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) 1`] = `
"import { setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) w/ innerHTML 1`] = `
"import { setHtml as _setHtml, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setHtml(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) w/ no expression 1`] = `
"import { setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) w/ progress value 1`] = `
"import { setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<progress></progress>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDOMProp(n0, "value", _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) w/ textContent 1`] = `
"import { setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setText(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) w/ value 1`] = `
"import { setValue as _setValue, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setValue(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier 1`] = `
"import { setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ dynamic arg 1`] = `
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, [{ ["." + _ctx.fooBar]: _ctx.id }], true))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ innerHTML 1`] = `
"import { setHtml as _setHtml, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setHtml(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ no expression 1`] = `
"import { setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ progress value 1`] = `
"import { setDOMProp as _setDOMProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<progress></progress>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDOMProp(n0, "value", _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ textContent 1`] = `
"import { setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setText(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ value 1`] = `
"import { setValue as _setValue, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setValue(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > :innerHTML 1`] = `
"import { setHtml as _setHtml, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setHtml(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > :textContext 1`] = `
"import { setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setText(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > :value 1`] = `
"import { setValue as _setValue, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<input>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setValue(n0, _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > :value w/ progress 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<progress></progress>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setProp(n0, "value", _ctx.foo))
return n0
}"
`;
exports[`compiler v-bind > attributes must be set as attribute 1`] = `
"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")
const t1 = _template("<input>")
const t2 = _template("<textarea></textarea>")
const t3 = _template("<img>")
const t4 = _template("<video></video>")
const t5 = _template("<canvas></canvas>")
const t6 = _template("<source>")
export function render(_ctx) {
const n0 = t0()
const n1 = t1()
const n2 = t2()
const n3 = t3()
const n4 = t4()
const n5 = t5()
const n6 = t6()
_renderEffect(() => {
const _width = _ctx.width
const _height = _ctx.height
_setAttr(n0, "spellcheck", _ctx.spellcheck)
_setAttr(n0, "draggable", _ctx.draggable)
_setAttr(n0, "translate", _ctx.translate)
_setAttr(n0, "form", _ctx.form)
_setAttr(n1, "list", _ctx.list)
_setAttr(n2, "type", _ctx.type)
_setAttr(n3, "width", _width)
_setAttr(n3, "height", _height)
_setAttr(n4, "width", _width)
_setAttr(n4, "height", _height)
_setAttr(n5, "width", _width)
_setAttr(n5, "height", _height)
_setAttr(n6, "width", _width)
_setAttr(n6, "height", _height)
})
return [n0, n1, n2, n3, n4, n5, n6]
}"
`;
exports[`compiler v-bind > basic 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setProp(n0, "id", _ctx.id))
return n0
}"
`;
exports[`compiler v-bind > dynamic arg 1`] = `
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
const _id = _ctx.id
const _title = _ctx.title
_setDynamicProps(n0, [{ [_id]: _id, [_title]: _title }], true)
})
return n0
}"
`;
exports[`compiler v-bind > dynamic arg w/ static attribute 1`] = `
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
const _id = _ctx.id
_setDynamicProps(n0, [{ [_id]: _id, foo: "bar", checked: "" }], true)
})
return n0
}"
`;
exports[`compiler v-bind > no expression (shorthand) 1`] = `
"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setAttr(n0, "camel-case", _ctx.camelCase))
return n0
}"
`;
exports[`compiler v-bind > no expression 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setProp(n0, "id", _ctx.id))
return n0
}"
`;
exports[`compiler v-bind > number value 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponentWithFallback(_component_Comp, { depth: () => (0) }, null, true)
return n0
}"
`;
exports[`compiler v-bind > should error if empty expression 1`] = `
"import { template as _template } from 'vue';
const t0 = _template("<div arg></div>", true)
export function render(_ctx) {
const n0 = t0()
return n0
}"
`;
exports[`compiler v-bind > with constant value 1`] = `
"import { setProp as _setProp, template as _template } from 'vue';
const t0 = _template("<div f=\\"foo1\\" h=\\"1\\"></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
_setProp(n0, "a", void 0)
_setProp(n0, "b", 1 > 2)
_setProp(n0, "c", 1 + 2)
_setProp(n0, "d", 1 ? 2 : 3)
_setProp(n0, "e", (2))
_setProp(n0, "g", 1)
_setProp(n0, "i", true)
_setProp(n0, "j", null)
_setProp(n0, "k", _ctx.x)
_setProp(n0, "l", { foo: 1 })
_setProp(n0, "m", { [_ctx.x]: 1 })
_setProp(n0, "n", { ...{ foo: 1 } })
_setProp(n0, "o", [1, , 3])
_setProp(n0, "p", [1, ...[2, 3]])
_setProp(n0, "q", [1, 2])
_setProp(n0, "r", /\\s+/)
return n0
}"
`;

View File

@ -0,0 +1,159 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: v-for > array de-structured value (with rest) 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => {
const n2 = t0()
const x2 = _child(n2)
_renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value[0] + _for_item0.value.slice(1) + _for_key0.value)))
return n2
}, ([id, ...other], index) => (id))
return n0
}"
`;
exports[`compiler: v-for > array de-structured value 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => {
const n2 = t0()
const x2 = _child(n2)
_renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value[0] + _for_item0.value[1] + _for_key0.value)))
return n2
}, ([id, other], index) => (id))
return n0
}"
`;
exports[`compiler: v-for > basic v-for 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.items), (_for_item0) => {
const n2 = t0()
const x2 = _child(n2)
n2.$evtclick = () => (_ctx.remove(_for_item0.value))
_renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value)))
return n2
}, (item) => (item.id))
return n0
}"
`;
exports[`compiler: v-for > multi effect 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.items), (_for_item0, _for_key0) => {
const n2 = t0()
_renderEffect(() => {
_setProp(n2, "item", _for_item0.value)
_setProp(n2, "index", _for_key0.value)
})
return n2
})
return n0
}"
`;
exports[`compiler: v-for > nested v-for 1`] = `
"import { setInsertionState as _setInsertionState, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<span> </span>")
const t1 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
const n5 = t1()
_setInsertionState(n5)
const n2 = _createFor(() => (_for_item0.value), (_for_item1) => {
const n4 = t0()
const x4 = _child(n4)
_renderEffect(() => _setText(x4, _toDisplayString(_for_item1.value+_for_item0.value)))
return n4
}, null, 1)
return n5
})
return n0
}"
`;
exports[`compiler: v-for > object de-structured value (with rest) 1`] = `
"import { getRestElement as _getRestElement, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => {
const n2 = t0()
const x2 = _child(n2)
_renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value.id + _getRestElement(_for_item0.value, ["id"]) + _for_key0.value)))
return n2
}, ({ id, ...other }, index) => (id))
return n0
}"
`;
exports[`compiler: v-for > object de-structured value 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<span> </span>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.items), (_for_item0) => {
const n2 = t0()
const x2 = _child(n2)
_renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value.id) + _toDisplayString(_for_item0.value.value)))
return n2
}, ({ id, value }) => (id))
return n0
}"
`;
exports[`compiler: v-for > object value, key and index 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0, _for_index0) => {
const n2 = t0()
const x2 = _child(n2)
_renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value + _for_key0.value + _for_index0.value)))
return n2
}, (value, key, index) => (key))
return n0
}"
`;
exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = `
"import { getDefaultValue as _getDefaultValue, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
const n2 = t0()
const x2 = _child(n2)
_renderEffect(() => _setText(x2, _toDisplayString(_getDefaultValue(_for_item0.value.foo, _ctx.bar) + _ctx.bar + _ctx.baz + _getDefaultValue(_for_item0.value.baz[0], _ctx.quux) + _ctx.quux)))
return n2
})
return n0
}"
`;
exports[`compiler: v-for > w/o value 1`] = `
"import { createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div>item</div>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.items), (_for_item0) => {
const n2 = t0()
return n2
})
return n0
}"
`;

View File

@ -0,0 +1,34 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`v-html > should convert v-html to innerHTML 1`] = `
"import { setHtml as _setHtml, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
_renderEffect(() => _setHtml(n0, _ctx.code))
return n0
}"
`;
exports[`v-html > should raise error and ignore children when v-html is present 1`] = `
"import { setHtml as _setHtml, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setHtml(n0, _ctx.test))
return n0
}"
`;
exports[`v-html > should raise error if has no expression 1`] = `
"import { setHtml as _setHtml, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_setHtml(n0, "")
return n0
}"
`;

View File

@ -0,0 +1,162 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: v-if > basic v-if 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.ok), () => {
const n2 = t0()
const x2 = _child(n2)
_renderEffect(() => _setText(x2, _toDisplayString(_ctx.msg)))
return n2
})
return n0
}"
`;
exports[`compiler: v-if > comment between branches 1`] = `
"import { createIf as _createIf, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")
const t1 = _template("<!--foo-->")
const t2 = _template("<p></p>")
const t3 = _template("<!--bar-->")
const t4 = _template("fine")
const t5 = _template("<div> </div>")
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.ok), () => {
const n2 = t0()
return n2
}, () => _createIf(() => (_ctx.orNot), () => {
const n5 = t1()
const n6 = t2()
return [n5, n6]
}, () => {
const n10 = t3()
const n11 = t4()
return [n10, n11]
}))
const n13 = t5()
const x13 = _child(n13)
_renderEffect(() => _setText(x13, _toDisplayString(_ctx.text)))
return [n0, n13]
}"
`;
exports[`compiler: v-if > dedupe same template 1`] = `
"import { createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div>hello</div>")
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.ok), () => {
const n2 = t0()
return n2
})
const n3 = _createIf(() => (_ctx.ok), () => {
const n5 = t0()
return n5
})
return [n0, n3]
}"
`;
exports[`compiler: v-if > template v-if 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>")
const t1 = _template("hello")
const t2 = _template("<p> </p>", true)
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.ok), () => {
const n2 = t0()
const n3 = t1()
const n4 = t2()
const x4 = _child(n4)
_renderEffect(() => _setText(x4, _toDisplayString(_ctx.msg)))
return [n2, n3, n4]
})
return n0
}"
`;
exports[`compiler: v-if > v-if + v-else 1`] = `
"import { createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>")
const t1 = _template("<p></p>")
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.ok), () => {
const n2 = t0()
return n2
}, () => {
const n4 = t1()
return n4
})
return n0
}"
`;
exports[`compiler: v-if > v-if + v-else-if + v-else 1`] = `
"import { createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>")
const t1 = _template("<p></p>")
const t2 = _template("fine")
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.ok), () => {
const n2 = t0()
return n2
}, () => _createIf(() => (_ctx.orNot), () => {
const n4 = t1()
return n4
}, () => {
const n7 = t2()
return n7
}))
return n0
}"
`;
exports[`compiler: v-if > v-if + v-else-if 1`] = `
"import { createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>")
const t1 = _template("<p></p>")
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.ok), () => {
const n2 = t0()
return n2
}, () => _createIf(() => (_ctx.orNot), () => {
const n4 = t1()
return n4
}))
return n0
}"
`;
exports[`compiler: v-if > v-if + v-if / v-else[-if] 1`] = `
"import { setInsertionState as _setInsertionState, createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<span>foo</span>")
const t1 = _template("<span>bar</span>")
const t2 = _template("<span>baz</span>")
const t3 = _template("<div></div>", true)
export function render(_ctx) {
const n8 = t3()
_setInsertionState(n8)
const n0 = _createIf(() => (_ctx.foo), () => {
const n2 = t0()
return n2
})
_setInsertionState(n8)
const n3 = _createIf(() => (_ctx.bar), () => {
const n5 = t1()
return n5
}, () => {
const n7 = t2()
return n7
})
return n8
}"
`;

View File

@ -0,0 +1,242 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: vModel transform > component > v-model for component should generate modelModifiers 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponentWithFallback(_component_Comp, { modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => _value => (_ctx.foo = _value),
modelModifiers: () => ({ trim: true, "bar-baz": true }) }, null, true)
return n0
}"
`;
exports[`compiler: vModel transform > component > v-model for component should work 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponentWithFallback(_component_Comp, { modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => _value => (_ctx.foo = _value) }, null, true)
return n0
}"
`;
exports[`compiler: vModel transform > component > v-model with arguments for component should generate modelModifiers 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponentWithFallback(_component_Comp, {
foo: () => (_ctx.foo),
"onUpdate:foo": () => _value => (_ctx.foo = _value),
fooModifiers: () => ({ trim: true }),
bar: () => (_ctx.bar),
"onUpdate:bar": () => _value => (_ctx.bar = _value),
barModifiers: () => ({ number: true })
}, null, true)
return n0
}"
`;
exports[`compiler: vModel transform > component > v-model with arguments for component should work 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponentWithFallback(_component_Comp, { bar: () => (_ctx.foo),
"onUpdate:bar": () => _value => (_ctx.foo = _value) }, null, true)
return n0
}"
`;
exports[`compiler: vModel transform > component > v-model with dynamic arguments for component should generate modelModifiers 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponentWithFallback(_component_Comp, { $: [
() => ({ [_ctx.foo]: _ctx.foo,
["onUpdate:" + _ctx.foo]: () => _value => (_ctx.foo = _value),
[_ctx.foo + "Modifiers"]: () => ({ trim: true }) }),
() => ({ [_ctx.bar]: _ctx.bar,
["onUpdate:" + _ctx.bar]: () => _value => (_ctx.bar = _value),
[_ctx.bar + "Modifiers"]: () => ({ number: true }) })
] }, null, true)
return n0
}"
`;
exports[`compiler: vModel transform > component > v-model with dynamic arguments for component should work 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponentWithFallback(_component_Comp, { $: [
() => ({ [_ctx.arg]: _ctx.foo,
["onUpdate:" + _ctx.arg]: () => _value => (_ctx.foo = _value) })
] }, null, true)
return n0
}"
`;
exports[`compiler: vModel transform > modifiers > .lazy 1`] = `
"import { applyTextModel as _applyTextModel, template as _template } from 'vue';
const t0 = _template("<input>", true)
export function render(_ctx) {
const n0 = t0()
_applyTextModel(n0, () => (_ctx.model), _value => (_ctx.model = _value), { lazy: true })
return n0
}"
`;
exports[`compiler: vModel transform > modifiers > .number 1`] = `
"import { applyTextModel as _applyTextModel, template as _template } from 'vue';
const t0 = _template("<input>", true)
export function render(_ctx) {
const n0 = t0()
_applyTextModel(n0, () => (_ctx.model), _value => (_ctx.model = _value), { number: true })
return n0
}"
`;
exports[`compiler: vModel transform > modifiers > .trim 1`] = `
"import { applyTextModel as _applyTextModel, template as _template } from 'vue';
const t0 = _template("<input>", true)
export function render(_ctx) {
const n0 = t0()
_applyTextModel(n0, () => (_ctx.model), _value => (_ctx.model = _value), { trim: true })
return n0
}"
`;
exports[`compiler: vModel transform > should support input (checkbox) 1`] = `
"import { applyCheckboxModel as _applyCheckboxModel, template as _template } from 'vue';
const t0 = _template("<input type=\\"checkbox\\">", true)
export function render(_ctx) {
const n0 = t0()
_applyCheckboxModel(n0, () => (_ctx.model), _value => (_ctx.model = _value))
return n0
}"
`;
exports[`compiler: vModel transform > should support input (dynamic type) 1`] = `
"import { applyDynamicModel as _applyDynamicModel, template as _template } from 'vue';
const t0 = _template("<input>", true)
export function render(_ctx) {
const n0 = t0()
_applyDynamicModel(n0, () => (_ctx.model), _value => (_ctx.model = _value))
return n0
}"
`;
exports[`compiler: vModel transform > should support input (radio) 1`] = `
"import { applyRadioModel as _applyRadioModel, template as _template } from 'vue';
const t0 = _template("<input type=\\"radio\\">", true)
export function render(_ctx) {
const n0 = t0()
_applyRadioModel(n0, () => (_ctx.model), _value => (_ctx.model = _value))
return n0
}"
`;
exports[`compiler: vModel transform > should support input (text) 1`] = `
"import { applyTextModel as _applyTextModel, template as _template } from 'vue';
const t0 = _template("<input type=\\"text\\">", true)
export function render(_ctx) {
const n0 = t0()
_applyTextModel(n0, () => (_ctx.model), _value => (_ctx.model = _value))
return n0
}"
`;
exports[`compiler: vModel transform > should support member expression 1`] = `
"import { applyTextModel as _applyTextModel, template as _template } from 'vue';
const t0 = _template("<input>")
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
const n1 = t0()
const n2 = t0()
_applyTextModel(n0, () => (_ctx.setupRef.child), _value => (_ctx.setupRef.child = _value))
_applyTextModel(n1, () => (_ctx.setupLet.child), _value => (_ctx.setupLet.child = _value))
_applyTextModel(n2, () => (_ctx.setupMaybeRef.child), _value => (_ctx.setupMaybeRef.child = _value))
return [n0, n1, n2]
}"
`;
exports[`compiler: vModel transform > should support member expression w/ inline 1`] = `
"
const n0 = t0()
const n1 = t0()
const n2 = t0()
_applyTextModel(n0, () => (setupRef.value.child), _value => (setupRef.value.child = _value))
_applyTextModel(n1, () => (_unref(setupLet).child), _value => (_unref(setupLet).child = _value))
_applyTextModel(n2, () => (_unref(setupMaybeRef).child), _value => (_unref(setupMaybeRef).child = _value))
return [n0, n1, n2]
"
`;
exports[`compiler: vModel transform > should support select 1`] = `
"import { applySelectModel as _applySelectModel, template as _template } from 'vue';
const t0 = _template("<select></select>", true)
export function render(_ctx) {
const n0 = t0()
_applySelectModel(n0, () => (_ctx.model), _value => (_ctx.model = _value))
return n0
}"
`;
exports[`compiler: vModel transform > should support simple expression 1`] = `
"import { applyTextModel as _applyTextModel, template as _template } from 'vue';
const t0 = _template("<input>", true)
export function render(_ctx) {
const n0 = t0()
_applyTextModel(n0, () => (_ctx.model), _value => (_ctx.model = _value))
return n0
}"
`;
exports[`compiler: vModel transform > should support textarea 1`] = `
"import { applyTextModel as _applyTextModel, template as _template } from 'vue';
const t0 = _template("<textarea></textarea>", true)
export function render(_ctx) {
const n0 = t0()
_applyTextModel(n0, () => (_ctx.model), _value => (_ctx.model = _value))
return n0
}"
`;
exports[`compiler: vModel transform > should support w/ dynamic v-bind 1`] = `
"import { applyDynamicModel as _applyDynamicModel, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<input>", true)
export function render(_ctx) {
const n0 = t0()
_applyDynamicModel(n0, () => (_ctx.model), _value => (_ctx.model = _value))
_renderEffect(() => _setDynamicProps(n0, [_ctx.obj], true))
return n0
}"
`;
exports[`compiler: vModel transform > should support w/ dynamic v-bind 2`] = `
"import { applyDynamicModel as _applyDynamicModel, template as _template } from 'vue';
const t0 = _template("<input>", true)
export function render(_ctx) {
const n0 = t0()
_applyDynamicModel(n0, () => (_ctx.model), _value => (_ctx.model = _value))
return n0
}"
`;

View File

@ -0,0 +1,472 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`v-on > complex member expression w/ prefixIdentifiers: true 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = e => _ctx.a['b' + _ctx.c](e)
return n0
}"
`;
exports[`v-on > dynamic arg 1`] = `
"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
_on(n0, _ctx.event, e => _ctx.handler(e), {
effect: true
})
})
return n0
}"
`;
exports[`v-on > dynamic arg with complex exp prefixing 1`] = `
"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
_on(n0, _ctx.event(_ctx.foo), e => _ctx.handler(e), {
effect: true
})
})
return n0
}"
`;
exports[`v-on > dynamic arg with prefixing 1`] = `
"import { on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
_on(n0, _ctx.event, e => _ctx.handler(e), {
effect: true
})
})
return n0
}"
`;
exports[`v-on > event modifier 1`] = `
"import { withModifiers as _withModifiers, on as _on, withKeys as _withKeys, delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<a></a>")
const t1 = _template("<form></form>")
const t2 = _template("<div></div>")
const t3 = _template("<input>")
_delegateEvents("click", "contextmenu", "mouseup", "keyup")
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
const n1 = t1()
const n2 = t0()
const n3 = t2()
const n4 = t2()
const n5 = t0()
const n6 = t2()
const n7 = t3()
const n8 = t3()
const n9 = t3()
const n10 = t3()
const n11 = t3()
const n12 = t3()
const n13 = t3()
const n14 = t3()
const n15 = t3()
const n16 = t3()
const n17 = t3()
const n18 = t3()
const n19 = t3()
const n20 = t3()
const n21 = t3()
n0.$evtclick = _withModifiers(_ctx.handleEvent, ["stop"])
_on(n1, "submit", _withModifiers(_ctx.handleEvent, ["prevent"]))
n2.$evtclick = _withModifiers(_ctx.handleEvent, ["stop","prevent"])
n3.$evtclick = _withModifiers(_ctx.handleEvent, ["self"])
_on(n4, "click", _ctx.handleEvent, {
capture: true
})
_on(n5, "click", _ctx.handleEvent, {
once: true
})
_on(n6, "scroll", _ctx.handleEvent, {
passive: true
})
n7.$evtcontextmenu = _withModifiers(_ctx.handleEvent, ["right"])
n8.$evtclick = _withModifiers(_ctx.handleEvent, ["left"])
n9.$evtmouseup = _withModifiers(_ctx.handleEvent, ["middle"])
n10.$evtcontextmenu = _withKeys(_withModifiers(_ctx.handleEvent, ["right"]), ["enter"])
n11.$evtkeyup = _withKeys(_ctx.handleEvent, ["enter"])
n12.$evtkeyup = _withKeys(_ctx.handleEvent, ["tab"])
n13.$evtkeyup = _withKeys(_ctx.handleEvent, ["delete"])
n14.$evtkeyup = _withKeys(_ctx.handleEvent, ["esc"])
n15.$evtkeyup = _withKeys(_ctx.handleEvent, ["space"])
n16.$evtkeyup = _withKeys(_ctx.handleEvent, ["up"])
n17.$evtkeyup = _withKeys(_ctx.handleEvent, ["down"])
n18.$evtkeyup = _withKeys(_ctx.handleEvent, ["left"])
n19.$evtkeyup = _withModifiers(e => _ctx.submit(e), ["middle"])
n20.$evtkeyup = _withModifiers(e => _ctx.submit(e), ["middle","self"])
n21.$evtkeyup = _withKeys(_withModifiers(_ctx.handleEvent, ["self"]), ["enter"])
return [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, n17, n18, n19, n20, n21]
}"
`;
exports[`v-on > expression with type 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
n0.$evtclick = e => _ctx.handleClick(e)
return n0
}"
`;
exports[`v-on > function expression w/ prefixIdentifiers: true 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = e => _ctx.foo(e)
return n0
}"
`;
exports[`v-on > inline statement w/ prefixIdentifiers: true 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = $event => (_ctx.foo($event))
return n0
}"
`;
exports[`v-on > multiple inline statements w/ prefixIdentifiers: true 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = $event => {_ctx.foo($event);_ctx.bar()}
return n0
}"
`;
exports[`v-on > should NOT add a prefix to $event if the expression is a function expression 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = $event => {_ctx.i++;_ctx.foo($event)}
return n0
}"
`;
exports[`v-on > should NOT wrap as function if expression is already function expression (with Typescript) 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = (e: any): any => _ctx.foo(e)
return n0
}"
`;
exports[`v-on > should NOT wrap as function if expression is already function expression (with newlines) 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick =
$event => {
_ctx.foo($event)
}
return n0
}"
`;
exports[`v-on > should NOT wrap as function if expression is already function expression 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = $event => _ctx.foo($event)
return n0
}"
`;
exports[`v-on > should NOT wrap as function if expression is complex member expression 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = e => _ctx.a['b' + _ctx.c](e)
return n0
}"
`;
exports[`v-on > should delegate event 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = e => _ctx.test(e)
return n0
}"
`;
exports[`v-on > should handle multi-line statement 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = () => {
_ctx.foo();
_ctx.bar()
}
return n0
}"
`;
exports[`v-on > should handle multiple inline statement 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = () => {_ctx.foo();_ctx.bar()}
return n0
}"
`;
exports[`v-on > should not prefix member expression 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = e => _ctx.foo.bar(e)
return n0
}"
`;
exports[`v-on > should not wrap keys guard if no key modifier is present 1`] = `
"import { withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("keyup")
export function render(_ctx) {
const n0 = t0()
n0.$evtkeyup = _withModifiers(e => _ctx.test(e), ["exact"])
return n0
}"
`;
exports[`v-on > should support multiple events and modifiers options w/ prefixIdentifiers: true 1`] = `
"import { withModifiers as _withModifiers, withKeys as _withKeys, delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click", "keyup")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = _withModifiers(e => _ctx.test(e), ["stop"])
n0.$evtkeyup = _withKeys(e => _ctx.test(e), ["enter"])
return n0
}"
`;
exports[`v-on > should support multiple modifiers and event options w/ prefixIdentifiers: true 1`] = `
"import { withModifiers as _withModifiers, on as _on, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_on(n0, "click", _withModifiers(e => _ctx.test(e), ["stop","prevent"]), {
capture: true,
once: true
})
return n0
}"
`;
exports[`v-on > should transform click.middle 1`] = `
"import { withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("mouseup")
export function render(_ctx) {
const n0 = t0()
n0.$evtmouseup = _withModifiers(e => _ctx.test(e), ["middle"])
return n0
}"
`;
exports[`v-on > should transform click.middle 2`] = `
"import { withModifiers as _withModifiers, on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
_on(n0, (_ctx.event) === "click" ? "mouseup" : (_ctx.event), _withModifiers(e => _ctx.test(e), ["middle"]), {
effect: true
})
})
return n0
}"
`;
exports[`v-on > should transform click.right 1`] = `
"import { withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("contextmenu")
export function render(_ctx) {
const n0 = t0()
n0.$evtcontextmenu = _withModifiers(e => _ctx.test(e), ["right"])
return n0
}"
`;
exports[`v-on > should transform click.right 2`] = `
"import { withModifiers as _withModifiers, withKeys as _withKeys, on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
_on(n0, (_ctx.event) === "click" ? "contextmenu" : (_ctx.event), _withKeys(_withModifiers(e => _ctx.test(e), ["right"]), ["right"]), {
effect: true
})
})
return n0
}"
`;
exports[`v-on > should use delegate helper when have multiple events of same name 1`] = `
"import { delegate as _delegate, withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
_delegate(n0, "click", e => _ctx.test(e))
_delegate(n0, "click", _withModifiers(e => _ctx.test(e), ["stop"]))
return n0
}"
`;
exports[`v-on > should wrap as function if expression is inline statement 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
n0.$evtclick = () => (_ctx.i++)
return n0
}"
`;
exports[`v-on > should wrap both for dynamic key event w/ left/right modifiers 1`] = `
"import { withModifiers as _withModifiers, withKeys as _withKeys, on as _on, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
_on(n0, _ctx.e, _withKeys(_withModifiers(e => _ctx.test(e), ["left"]), ["left"]), {
effect: true
})
})
return n0
}"
`;
exports[`v-on > should wrap in unref if identifier is setup-maybe-ref w/ inline: true 1`] = `
"
const n0 = t0()
const n1 = t0()
const n2 = t0()
n0.$evtclick = () => (x.value=_unref(y))
n1.$evtclick = () => (x.value++)
n2.$evtclick = () => ({ x: x.value } = _unref(y))
return [n0, n1, n2]
"
`;
exports[`v-on > should wrap keys guard for keyboard events or dynamic events 1`] = `
"import { withModifiers as _withModifiers, withKeys as _withKeys, on as _on, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_on(n0, "keydown", _withKeys(_withModifiers(e => _ctx.test(e), ["stop","ctrl"]), ["a"]), {
capture: true
})
return n0
}"
`;
exports[`v-on > should wrap keys guard for static key event w/ left/right modifiers 1`] = `
"import { withKeys as _withKeys, delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("keyup")
export function render(_ctx) {
const n0 = t0()
n0.$evtkeyup = _withKeys(e => _ctx.test(e), ["left"])
return n0
}"
`;
exports[`v-on > simple expression 1`] = `
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
_delegateEvents("click")
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
n0.$evtclick = _ctx.handleClick
return n0
}"
`;

View File

@ -0,0 +1,104 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: v-once > as root node 1`] = `
"import { setProp as _setProp, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_setProp(n0, "id", _ctx.foo)
return n0
}"
`;
exports[`compiler: v-once > basic 1`] = `
"import { child as _child, next as _next, toDisplayString as _toDisplayString, setText as _setText, setClass as _setClass, template as _template } from 'vue';
const t0 = _template("<div> <span></span></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n2 = t0()
const n0 = _child(n2)
const n1 = _next(n0)
_setText(n0, _toDisplayString(_ctx.msg) + " ")
_setClass(n1, _ctx.clz)
return n2
}"
`;
exports[`compiler: v-once > inside v-once 1`] = `
"import { template as _template } from 'vue';
const t0 = _template("<div><div></div></div>", true)
export function render(_ctx) {
const n0 = t0()
return n0
}"
`;
exports[`compiler: v-once > on component 1`] = `
"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n1 = t0()
_setInsertionState(n1)
const n0 = _createComponentWithFallback(_component_Comp, { id: () => (_ctx.foo) }, null, null, true)
return n1
}"
`;
exports[`compiler: v-once > on nested plain element 1`] = `
"import { child as _child, setProp as _setProp, template as _template } from 'vue';
const t0 = _template("<div><div></div></div>", true)
export function render(_ctx) {
const n1 = t0()
const n0 = _child(n1)
_setProp(n0, "id", _ctx.foo)
return n1
}"
`;
exports[`compiler: v-once > with v-for 1`] = `
"import { createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
const n2 = t0()
return n2
}, null, 4)
return n0
}"
`;
exports[`compiler: v-once > with v-if 1`] = `
"import { createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.expr), () => {
const n2 = t0()
return n2
}, null, true)
return n0
}"
`;
exports[`compiler: v-once > with v-if/else 1`] = `
"import { createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>")
const t1 = _template("<p></p>")
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.expr), () => {
const n2 = t0()
return n2
}, () => {
const n4 = t1()
return n4
}, true)
return n0
}"
`;

View File

@ -0,0 +1,12 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: v-show transform > simple expression 1`] = `
"import { applyVShow as _applyVShow, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_applyVShow(n0, () => (_ctx.foo))
return n0
}"
`;

View File

@ -0,0 +1,341 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: transform slot > dynamic slots name 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template("foo")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponentWithFallback(_component_Comp, null, {
$: [
() => ({
name: _ctx.name,
fn: () => {
const n0 = t0()
return n0
}
})
]
}, true)
return n2
}"
`;
exports[`compiler: transform slot > dynamic slots name w/ v-for 1`] = `
"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createForSlots as _createForSlots, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponentWithFallback(_component_Comp, null, {
$: [
() => (_createForSlots(_ctx.list, (item) => ({
name: item,
fn: (_slotProps0) => {
const n0 = t0()
_renderEffect(() => _setText(n0, _toDisplayString(_slotProps0["bar"])))
return n0
}
})))
]
}, true)
return n2
}"
`;
exports[`compiler: transform slot > dynamic slots name w/ v-for and provide absent key 1`] = `
"import { resolveComponent as _resolveComponent, createForSlots as _createForSlots, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template("foo")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponentWithFallback(_component_Comp, null, {
$: [
() => (_createForSlots(_ctx.list, (_, __, index) => ({
name: index,
fn: () => {
const n0 = t0()
return n0
}
})))
]
}, true)
return n2
}"
`;
exports[`compiler: transform slot > dynamic slots name w/ v-if / v-else[-if] 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template("condition slot")
const t1 = _template("another condition")
const t2 = _template("else condition")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n6 = _createComponentWithFallback(_component_Comp, null, {
$: [
() => (_ctx.condition
? {
name: "condition",
fn: () => {
const n0 = t0()
return n0
}
}
: _ctx.anotherCondition
? {
name: "condition",
fn: (_slotProps0) => {
const n2 = t1()
return n2
}
}
: {
name: "condition",
fn: () => {
const n4 = t2()
return n4
}
})
]
}, true)
return n6
}"
`;
exports[`compiler: transform slot > implicit default slot 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponentWithFallback(_component_Comp, null, {
"default": () => {
const n0 = t0()
return n0
}
}, true)
return n1
}"
`;
exports[`compiler: transform slot > named slots w/ implicit default slot 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template("foo")
const t1 = _template("bar")
const t2 = _template("<span></span>")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n4 = _createComponentWithFallback(_component_Comp, null, {
"one": () => {
const n0 = t0()
return n0
},
"default": () => {
const n2 = t1()
const n3 = t2()
return [n2, n3]
}
}, true)
return n4
}"
`;
exports[`compiler: transform slot > nested component slot 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_B = _resolveComponent("B")
const _component_A = _resolveComponent("A")
const n1 = _createComponentWithFallback(_component_A, null, {
"default": () => {
const n0 = _createComponentWithFallback(_component_B)
return n0
}
}, true)
return n1
}"
`;
exports[`compiler: transform slot > nested slots scoping 1`] = `
"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const _component_Inner = _resolveComponent("Inner")
const _component_Comp = _resolveComponent("Comp")
const n5 = _createComponentWithFallback(_component_Comp, null, {
"default": (_slotProps0) => {
const n1 = _createComponentWithFallback(_component_Inner, null, {
"default": (_slotProps1) => {
const n0 = t0()
_renderEffect(() => _setText(n0, _toDisplayString(_slotProps0["foo"] + _slotProps1["bar"] + _ctx.baz)))
return n0
}
})
const n3 = t0()
_renderEffect(() => _setText(n3, " " + _toDisplayString(_slotProps0["foo"] + _ctx.bar + _ctx.baz)))
return [n1, n3]
}
}, true)
return n5
}"
`;
exports[`compiler: transform slot > on component dynamically named slot 1`] = `
"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponentWithFallback(_component_Comp, null, {
$: [
() => ({
name: _ctx.named,
fn: (_slotProps0) => {
const n0 = t0()
_renderEffect(() => _setText(n0, _toDisplayString(_slotProps0["foo"] + _ctx.bar)))
return n0
}
})
]
}, true)
return n1
}"
`;
exports[`compiler: transform slot > on component named slot 1`] = `
"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponentWithFallback(_component_Comp, null, {
"named": (_slotProps0) => {
const n0 = t0()
_renderEffect(() => _setText(n0, _toDisplayString(_slotProps0["foo"] + _ctx.bar)))
return n0
}
}, true)
return n1
}"
`;
exports[`compiler: transform slot > on-component default slot 1`] = `
"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponentWithFallback(_component_Comp, null, {
"default": (_slotProps0) => {
const n0 = t0()
_renderEffect(() => _setText(n0, _toDisplayString(_slotProps0["foo"] + _ctx.bar)))
return n0
}
}, true)
return n1
}"
`;
exports[`compiler: transform slot > quote slot name 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponentWithFallback(_component_Comp, null, {
"nav-bar-title-before": () => {
return null
}
}, true)
return n1
}"
`;
exports[`compiler: transform slot > slot + v-if / v-else[-if] should not cause error 1`] = `
"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createSlot as _createSlot, createComponentWithFallback as _createComponentWithFallback, createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const _component_Bar = _resolveComponent("Bar")
const n6 = t0()
_setInsertionState(n6)
const n0 = _createSlot("foo", null)
_setInsertionState(n6)
const n1 = _createIf(() => (true), () => {
const n3 = _createComponentWithFallback(_component_Foo)
return n3
}, () => {
const n5 = _createComponentWithFallback(_component_Bar)
return n5
})
return n6
}"
`;
exports[`compiler: transform slot > with whitespace: 'preserve' > implicit default slot 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template(" Header ")
const t1 = _template(" ")
const t2 = _template("<p></p>")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n4 = _createComponentWithFallback(_component_Comp, null, {
"header": () => {
const n0 = t0()
return n0
},
"default": () => {
const n2 = t1()
const n3 = t2()
return [n2, n3]
}
}, true)
return n4
}"
`;
exports[`compiler: transform slot > with whitespace: 'preserve' > named default slot + implicit whitespace content 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template(" Header ")
const t1 = _template(" Default ")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n5 = _createComponentWithFallback(_component_Comp, null, {
"header": () => {
const n0 = t0()
return n0
},
"default": () => {
const n3 = t1()
return n3
}
}, true)
return n5
}"
`;
exports[`compiler: transform slot > with whitespace: 'preserve' > should not generate whitespace only default slot 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
const t0 = _template(" Header ")
const t1 = _template(" Footer ")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n5 = _createComponentWithFallback(_component_Comp, null, {
"header": () => {
const n0 = t0()
return n0
},
"footer": () => {
const n3 = t1()
return n3
}
}, true)
return n5
}"
`;

View File

@ -0,0 +1,35 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`v-text > should convert v-text to setText 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
const x0 = _child(n0)
_renderEffect(() => _setText(x0, _toDisplayString(_ctx.str)))
return n0
}"
`;
exports[`v-text > should raise error and ignore children when v-text is present 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
export function render(_ctx) {
const n0 = t0()
const x0 = _child(n0)
_renderEffect(() => _setText(x0, _toDisplayString(_ctx.test)))
return n0
}"
`;
exports[`v-text > should raise error if has no expression 1`] = `
"import { template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
return n0
}"
`;

View File

@ -0,0 +1,37 @@
import type { RootNode } from '@vue/compiler-dom'
import {
type CompilerOptions,
type RootIRNode,
generate,
parse,
transform,
} from '../../src'
export function makeCompile(options: CompilerOptions = {}) {
return (
template: string,
overrideOptions: CompilerOptions = {},
): {
ast: RootNode
ir: RootIRNode
code: string
helpers: Set<string>
} => {
const ast = parse(template, {
prefixIdentifiers: true,
...options,
...overrideOptions,
})
const ir = transform(ast, {
prefixIdentifiers: true,
...options,
...overrideOptions,
})
const { code, helpers } = generate(ir, {
prefixIdentifiers: true,
...options,
...overrideOptions,
})
return { ast, ir, code, helpers }
}
}

View File

@ -0,0 +1,50 @@
import { BindingTypes } from '@vue/compiler-dom'
import {
transformChildren,
transformElement,
transformText,
transformVBind,
} from '../../src'
import { makeCompile } from './_utils'
const compileWithExpression = makeCompile({
nodeTransforms: [transformElement, transformChildren, transformText],
directiveTransforms: { bind: transformVBind },
})
describe('compiler: expression', () => {
test('basic', () => {
const { code } = compileWithExpression(`{{ a }}`)
expect(code).toMatchSnapshot()
expect(code).contains(`ctx.a`)
})
test('props', () => {
const { code } = compileWithExpression(`{{ foo }}`, {
bindingMetadata: { foo: BindingTypes.PROPS },
})
expect(code).toMatchSnapshot()
expect(code).contains(`$props.foo`)
})
test('props aliased', () => {
const { code } = compileWithExpression(`{{ foo }}`, {
bindingMetadata: {
foo: BindingTypes.PROPS_ALIASED,
__propsAliases: { foo: 'bar' } as any,
},
})
expect(code).toMatchSnapshot()
expect(code).contains(`$props['bar']`)
})
test('update expression', () => {
const { code } = compileWithExpression(`
<div :id="String(foo.id++)" :foo="foo" :bar="bar++">
{{ String(foo.id++) }} {{ foo }} {{ bar }}
</div>
`)
expect(code).toMatchSnapshot()
expect(code).contains(`_String(_foo.id++)`)
})
})

View File

@ -0,0 +1,76 @@
import { makeCompile } from './_utils'
import {
transformChildren,
transformElement,
transformText,
transformVIf,
} from '../../src'
const compileWithElementTransform = makeCompile({
nodeTransforms: [
transformText,
transformVIf,
transformElement,
transformChildren,
],
})
describe('compiler: children transform', () => {
test('children & sibling references', () => {
const { code, helpers } = compileWithElementTransform(
`<div>
<p>{{ first }}</p>
{{ second }}
{{ third }}
<p>{{ forth }}</p>
</div>`,
)
expect(code).toMatchSnapshot()
expect(Array.from(helpers)).containSubset([
'child',
'toDisplayString',
'renderEffect',
'next',
'setText',
'template',
])
})
test('efficient traversal', () => {
const { code } = compileWithElementTransform(
`<div>
<div>x</div>
<div><span>{{ msg }}</span></div>
<div><span>{{ msg }}</span></div>
<div><span>{{ msg }}</span></div>
</div>`,
)
expect(code).toMatchSnapshot()
})
test('efficient find', () => {
const { code } = compileWithElementTransform(
`<div>
<div>x</div>
<div>x</div>
<div>{{ msg }}</div>
</div>`,
)
expect(code).contains(`const n0 = _nthChild(n1, 2)`)
expect(code).toMatchSnapshot()
})
test('anchor insertion in middle', () => {
const { code } = compileWithElementTransform(
`<div>
<div></div>
<div v-if="1"></div>
<div></div>
</div>`,
)
// ensure the insertion anchor is generated before the insertion statement
expect(code).toMatch(`const n3 = _next(_child(n4))`)
expect(code).toMatch(`_setInsertionState(n4, n3)`)
expect(code).toMatchSnapshot()
})
})

View File

@ -0,0 +1,959 @@
import { makeCompile } from './_utils'
import {
IRDynamicPropsKind,
IRNodeTypes,
transformChildren,
transformElement,
transformText,
transformVBind,
transformVFor,
transformVOn,
} from '../../src'
import {
type BindingMetadata,
BindingTypes,
NodeTypes,
} from '@vue/compiler-dom'
const compileWithElementTransform = makeCompile({
nodeTransforms: [
transformVFor,
transformElement,
transformChildren,
transformText,
],
directiveTransforms: {
bind: transformVBind,
on: transformVOn,
},
})
describe('compiler: element transform', () => {
describe('component', () => {
test('import + resolve component', () => {
const { code, ir, helpers } = compileWithElementTransform(`<Foo/>`)
expect(code).toMatchSnapshot()
expect(helpers).contains.all.keys('resolveComponent')
expect(helpers).contains.all.keys('createComponentWithFallback')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 0,
tag: 'Foo',
asset: true,
root: true,
props: [[]],
})
})
test('resolve implicitly self-referencing component', () => {
const { code, helpers } = compileWithElementTransform(`<Example/>`, {
filename: `/foo/bar/Example.vue?vue&type=template`,
})
expect(code).toMatchSnapshot()
expect(code).toContain('_resolveComponent("Example", true)')
expect(helpers).toContain('resolveComponent')
})
test('resolve component from setup bindings', () => {
const { code, ir, helpers } = compileWithElementTransform(`<Example/>`, {
bindingMetadata: {
Example: BindingTypes.SETUP_MAYBE_REF,
},
})
expect(code).toMatchSnapshot()
expect(helpers).not.toContain('resolveComponent')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Example',
asset: false,
})
})
test('resolve component from setup bindings (inline)', () => {
const { code, helpers } = compileWithElementTransform(`<Example/>`, {
inline: true,
bindingMetadata: {
Example: BindingTypes.SETUP_MAYBE_REF,
},
})
expect(code).toMatchSnapshot()
expect(code).contains(`unref(Example)`)
expect(helpers).not.toContain('resolveComponent')
expect(helpers).toContain('unref')
})
test('resolve component from setup bindings (inline const)', () => {
const { code, helpers } = compileWithElementTransform(`<Example/>`, {
inline: true,
bindingMetadata: {
Example: BindingTypes.SETUP_CONST,
},
})
expect(code).toMatchSnapshot()
expect(helpers).not.toContain('resolveComponent')
})
test('resolve namespaced component from setup bindings', () => {
const { code, helpers } = compileWithElementTransform(`<Foo.Example/>`, {
bindingMetadata: {
Foo: BindingTypes.SETUP_MAYBE_REF,
},
})
expect(code).toMatchSnapshot()
expect(code).contains(`_ctx.Foo.Example`)
expect(helpers).not.toContain('resolveComponent')
})
test('resolve namespaced component from setup bindings (inline const)', () => {
const { code, helpers } = compileWithElementTransform(`<Foo.Example/>`, {
inline: true,
bindingMetadata: {
Foo: BindingTypes.SETUP_CONST,
},
})
expect(code).toMatchSnapshot()
expect(code).contains(`Foo.Example`)
expect(helpers).not.toContain('resolveComponent')
})
test('resolve namespaced component from props bindings (inline)', () => {
const { code, helpers } = compileWithElementTransform(`<Foo.Example/>`, {
inline: true,
bindingMetadata: {
Foo: BindingTypes.PROPS,
},
})
expect(code).toMatchSnapshot()
expect(code).contains(`Foo.Example`)
expect(helpers).not.toContain('resolveComponent')
})
test('resolve namespaced component from props bindings (non-inline)', () => {
const { code, helpers } = compileWithElementTransform(`<Foo.Example/>`, {
inline: false,
bindingMetadata: {
Foo: BindingTypes.PROPS,
},
})
expect(code).toMatchSnapshot()
expect(code).contains('_ctx.Foo.Example')
expect(helpers).not.toContain('resolveComponent')
})
test('do not resolve component from non-script-setup bindings', () => {
const bindingMetadata: BindingMetadata = {
Example: BindingTypes.SETUP_MAYBE_REF,
}
Object.defineProperty(bindingMetadata, '__isScriptSetup', {
value: false,
})
const { code, ir, helpers } = compileWithElementTransform(`<Example/>`, {
bindingMetadata,
})
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveComponent')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 0,
tag: 'Example',
asset: true,
})
})
test('generate single root component', () => {
const { code } = compileWithElementTransform(`<Comp/>`, {
bindingMetadata: { Comp: BindingTypes.SETUP_CONST },
})
expect(code).toMatchSnapshot()
expect(code).contains('_createComponent(_ctx.Comp, null, null, true)')
})
test('generate multi root component', () => {
const { code } = compileWithElementTransform(`<Comp/>123`, {
bindingMetadata: { Comp: BindingTypes.SETUP_CONST },
})
expect(code).toMatchSnapshot()
expect(code).contains('_createComponent(_ctx.Comp)')
})
test('v-for on component should not mark as single root', () => {
const { code } = compileWithElementTransform(
`<Comp v-for="item in items" :key="item"/>`,
{
bindingMetadata: { Comp: BindingTypes.SETUP_CONST },
},
)
expect(code).toMatchSnapshot()
expect(code).contains('_createComponent(_ctx.Comp)')
})
test('static props', () => {
const { code, ir } = compileWithElementTransform(
`<Foo id="foo" class="bar" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains(`{
id: () => ("foo"),
class: () => ("bar")
}`)
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
asset: true,
root: true,
props: [
[
{
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'id',
isStatic: true,
},
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
],
},
{
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'class',
isStatic: true,
},
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'bar',
isStatic: true,
},
],
},
],
],
})
})
test('v-bind="obj"', () => {
const { code, ir } = compileWithElementTransform(`<Foo v-bind="obj" />`)
expect(code).toMatchSnapshot()
expect(code).contains(`[
() => (_ctx.obj)
]`)
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj', isStatic: false },
},
],
})
})
test('v-bind="obj" after static prop', () => {
const { code, ir } = compileWithElementTransform(
`<Foo id="foo" v-bind="obj" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains(`{
id: () => ("foo"),
$: [
() => (_ctx.obj)
]
}`)
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
],
})
})
test('v-bind="obj" before static prop', () => {
const { code, ir } = compileWithElementTransform(
`<Foo v-bind="obj" id="foo" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains(`[
() => (_ctx.obj),
{ id: () => ("foo") }
]`)
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
],
})
})
test('v-bind="obj" between static props', () => {
const { code, ir } = compileWithElementTransform(
`<Foo id="foo" v-bind="obj" class="bar" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains(`{
id: () => ("foo"),
$: [
() => (_ctx.obj),
{ class: () => ("bar") }
]
}`)
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'class' }, values: [{ content: 'bar' }] }],
],
})
})
test.todo('props merging: event handlers', () => {
const { code, ir } = compileWithElementTransform(
`<Foo @click.foo="a" @click.bar="b" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains('onClick: () => [_ctx.a, _ctx.b]')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'onClick', isStatic: true },
values: [{ content: 'a' }, { content: 'b' }],
},
],
],
},
])
})
test.todo('props merging: style', () => {
const { code } = compileWithElementTransform(
`<Foo style="color: green" :style="{ color: 'red' }" />`,
)
expect(code).toMatchSnapshot()
})
test.todo('props merging: class', () => {
const { code } = compileWithElementTransform(
`<Foo class="foo" :class="{ bar: isBar }" />`,
)
expect(code).toMatchSnapshot()
})
test('v-on="obj"', () => {
const { code, ir } = compileWithElementTransform(`<Foo v-on="obj" />`)
expect(code).toMatchSnapshot()
expect(code).contains(`[
() => (_toHandlers(_ctx.obj))
]`)
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
handler: true,
},
],
})
})
test('v-on expression is inline statement', () => {
const { code, ir } = compileWithElementTransform(
`<Foo v-on:bar="() => handler" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains(`onBar: () => _on_bar`)
expect(code).contains(`const _on_bar = () => _ctx.handler`)
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar' }],
},
],
],
})
})
test('v-on expression is a function call', () => {
const { code, ir } = compileWithElementTransform(
`<Foo v-on:bar="handleBar($event)" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains(`onBar: () => _on_bar`)
expect(code).contains(
`const _on_bar = $event => (_ctx.handleBar($event))`,
)
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar' }],
},
],
],
})
})
test('cache v-on expression with unique handler name', () => {
const { code, ir } = compileWithElementTransform(
`<Foo v-on:bar="handleBar($event)" /><Bar v-on:bar="() => handler" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains(`onBar: () => _on_bar`)
expect(code).contains(
`const _on_bar = $event => (_ctx.handleBar($event))`,
)
expect(code).contains(`onBar: () => _on_bar1`)
expect(code).contains(`const _on_bar1 = () => _ctx.handler`)
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar' }],
},
],
],
})
expect(ir.block.dynamic.children[1].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Bar',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar1' }],
},
],
],
})
})
})
describe('dynamic component', () => {
test('static binding', () => {
const { code, ir, helpers } = compileWithElementTransform(
`<component is="foo" />`,
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveDynamicComponent')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
})
})
test('capitalized version w/ static binding', () => {
const { code, ir, helpers } = compileWithElementTransform(
`<Component is="foo" />`,
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveDynamicComponent')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
})
})
test('dynamic binding', () => {
const { code, ir, helpers } = compileWithElementTransform(
`<component :is="foo" />`,
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('createDynamicComponent')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: false,
},
})
})
test('dynamic binding shorthand', () => {
const { code, ir, helpers } =
compileWithElementTransform(`<component :is />`)
expect(code).toMatchSnapshot()
expect(helpers).toContain('createDynamicComponent')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'is',
isStatic: false,
},
})
})
// #3934
test('normal component with is prop', () => {
const { code, ir, helpers } = compileWithElementTransform(
`<custom-input is="foo" />`,
{
isNativeTag: () => false,
},
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveComponent')
expect(helpers).not.toContain('resolveDynamicComponent')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'custom-input',
asset: true,
root: true,
props: [[{ key: { content: 'is' }, values: [{ content: 'foo' }] }]],
})
})
})
test('static props', () => {
const { code, ir } = compileWithElementTransform(
`<div id="foo" class="bar" />`,
)
const template = '<div id="foo" class="bar"></div>'
expect(code).toMatchSnapshot()
expect(code).contains(JSON.stringify(template))
expect(ir.template).toMatchObject([template])
expect(ir.block.effect).lengthOf(0)
})
test('props + children', () => {
const { code, ir } = compileWithElementTransform(
`<div id="foo"><span/></div>`,
)
const template = '<div id="foo"><span></span></div>'
expect(code).toMatchSnapshot()
expect(code).contains(JSON.stringify(template))
expect(ir.template).toMatchObject([template])
expect(ir.block.effect).lengthOf(0)
})
test('v-bind="obj"', () => {
const { code, ir } = compileWithElementTransform(`<div v-bind="obj" />`)
expect(code).toMatchSnapshot()
expect(ir.block.effect).toMatchObject([
{
expressions: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
],
operations: [
{
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: 0,
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
},
],
},
],
},
])
expect(code).contains('_setDynamicProps(n0, [_ctx.obj], true)')
})
test('v-bind="obj" after static prop', () => {
const { code, ir } = compileWithElementTransform(
`<div id="foo" v-bind="obj" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.effect).toMatchObject([
{
expressions: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
],
operations: [
{
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: 0,
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{
kind: IRDynamicPropsKind.EXPRESSION,
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
},
],
},
],
},
])
expect(code).contains(
'_setDynamicProps(n0, [{ id: "foo" }, _ctx.obj], true)',
)
})
test('v-bind="obj" before static prop', () => {
const { code, ir } = compileWithElementTransform(
`<div v-bind="obj" id="foo" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.effect).toMatchObject([
{
expressions: [{ content: 'obj' }],
operations: [
{
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: 0,
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
],
},
],
},
])
expect(code).contains(
'_setDynamicProps(n0, [_ctx.obj, { id: "foo" }], true)',
)
})
test('v-bind="obj" between static props', () => {
const { code, ir } = compileWithElementTransform(
`<div id="foo" v-bind="obj" class="bar" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.effect).toMatchObject([
{
expressions: [{ content: 'obj' }],
operations: [
{
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: 0,
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'class' }, values: [{ content: 'bar' }] }],
],
},
],
},
])
expect(code).contains(
'_setDynamicProps(n0, [{ id: "foo" }, _ctx.obj, { class: "bar" }], true)',
)
})
test('props merging: event handlers', () => {
const { code, ir } = compileWithElementTransform(
`<div @click.foo="a" @click.bar="b" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SET_EVENT,
element: 0,
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'click',
isStatic: true,
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: false,
},
keyOverride: undefined,
delegate: true,
effect: false,
},
{
type: IRNodeTypes.SET_EVENT,
element: 0,
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'click',
isStatic: true,
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
isStatic: false,
},
keyOverride: undefined,
delegate: true,
effect: false,
},
])
})
test('props merging: style', () => {
const { code, ir } = compileWithElementTransform(
`<div style="color: green" :style="{ color: 'red' }" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SET_PROP,
element: 0,
prop: {
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'style',
isStatic: true,
},
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'color: green',
isStatic: true,
},
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{ color: 'red' }`,
isStatic: false,
},
],
},
},
])
})
test('props merging: class', () => {
const { code, ir } = compileWithElementTransform(
`<div class="foo" :class="{ bar: isBar }" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.effect).toMatchObject([
{
expressions: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{ bar: isBar }`,
isStatic: false,
},
],
operations: [
{
type: IRNodeTypes.SET_PROP,
element: 0,
prop: {
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'class',
isStatic: true,
},
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`,
isStatic: true,
},
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{ bar: isBar }`,
isStatic: false,
},
],
},
},
],
},
])
})
test('v-on="obj"', () => {
const { code, ir } = compileWithElementTransform(`<div v-on="obj" />`)
expect(code).toMatchSnapshot()
expect(ir.block.effect).toMatchObject([
{
expressions: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
],
operations: [
{
type: IRNodeTypes.SET_DYNAMIC_EVENTS,
element: 0,
event: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
},
],
},
])
expect(code).contains('_setDynamicEvents(n0, _ctx.obj)')
})
test('component with dynamic prop arguments', () => {
const { code, ir } = compileWithElementTransform(
`<Foo :[foo-bar]="bar" :[baz]="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'foo-bar' },
values: [{ content: 'bar' }],
},
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'baz' },
values: [{ content: 'qux' }],
},
],
})
})
test('component with dynamic event arguments', () => {
const { code, ir } = compileWithElementTransform(
`<Foo @[foo-bar]="bar" @[baz]="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'foo-bar' },
values: [{ content: 'bar' }],
handler: true,
},
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'baz' },
values: [{ content: 'qux' }],
handler: true,
},
],
})
})
test('component event with once modifier', () => {
const { code } = compileWithElementTransform(`<Foo @foo.once="bar" />`)
expect(code).toMatchSnapshot()
})
test('component dynamic event with once modifier', () => {
const { code } = compileWithElementTransform(`<Foo @[foo].once="bar" />`)
expect(code).toMatchSnapshot()
})
test('invalid html nesting', () => {
const { code, ir } = compileWithElementTransform(
`<p><div>123</div></p>
<form><form/></form>`,
)
expect(code).toMatchSnapshot()
expect(ir.template).toEqual(['<div>123</div>', '<p></p>', '<form></form>'])
expect(ir.block.dynamic).toMatchObject({
children: [
{ id: 1, template: 1, children: [{ id: 0, template: 0 }] },
{ id: 3, template: 2, children: [{ id: 2, template: 2 }] },
],
})
expect(ir.block.operation).toMatchObject([
{ type: IRNodeTypes.INSERT_NODE, parent: 1, elements: [0] },
{ type: IRNodeTypes.INSERT_NODE, parent: 3, elements: [2] },
])
})
test('empty template', () => {
const { code } = compileWithElementTransform('')
expect(code).toMatchSnapshot()
expect(code).contain('return null')
})
})

View File

@ -0,0 +1,280 @@
import { ErrorCodes, NodeTypes } from '@vue/compiler-dom'
import {
IRNodeTypes,
transformChildren,
transformElement,
transformSlotOutlet,
transformText,
transformVBind,
transformVOn,
transformVShow,
} from '../../src'
import { makeCompile } from './_utils'
const compileWithSlotsOutlet = makeCompile({
nodeTransforms: [
transformText,
transformSlotOutlet,
transformElement,
transformChildren,
],
directiveTransforms: {
bind: transformVBind,
on: transformVOn,
show: transformVShow,
},
})
describe('compiler: transform <slot> outlets', () => {
test('default slot outlet', () => {
const { ir, code, helpers } = compileWithSlotsOutlet(`<slot />`)
expect(code).toMatchSnapshot()
expect(helpers).toContain('createSlot')
expect(ir.block.effect).toEqual([])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'default',
isStatic: true,
},
props: [],
fallback: undefined,
})
})
test('statically named slot outlet', () => {
const { ir, code } = compileWithSlotsOutlet(`<slot name="foo" />`)
expect(code).toMatchSnapshot()
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
})
})
test('dynamically named slot outlet', () => {
const { ir, code } = compileWithSlotsOutlet(`<slot :name="foo + bar" />`)
expect(code).toMatchSnapshot()
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo + bar',
isStatic: false,
},
})
})
test('dynamically named slot outlet with v-bind shorthand', () => {
const { ir, code } = compileWithSlotsOutlet(`<slot :name />`)
expect(code).toMatchSnapshot()
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'name',
isStatic: false,
},
})
})
test('default slot outlet with props', () => {
const { ir, code } = compileWithSlotsOutlet(
`<slot foo="bar" :baz="qux" :foo-bar="foo-bar" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'default' },
props: [
[
{ key: { content: 'foo' }, values: [{ content: 'bar' }] },
{ key: { content: 'baz' }, values: [{ content: 'qux' }] },
{ key: { content: 'fooBar' }, values: [{ content: 'foo-bar' }] },
],
],
})
})
test('statically named slot outlet with props', () => {
const { ir, code } = compileWithSlotsOutlet(
`<slot name="foo" foo="bar" :baz="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'foo' },
props: [
[
{ key: { content: 'foo' }, values: [{ content: 'bar' }] },
{ key: { content: 'baz' }, values: [{ content: 'qux' }] },
],
],
})
})
test('statically named slot outlet with v-bind="obj"', () => {
const { ir, code } = compileWithSlotsOutlet(
`<slot name="foo" foo="bar" v-bind="obj" :baz="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'foo' },
props: [
[{ key: { content: 'foo' }, values: [{ content: 'bar' }] }],
{ value: { content: 'obj', isStatic: false } },
[{ key: { content: 'baz' }, values: [{ content: 'qux' }] }],
],
})
})
test('statically named slot outlet with v-on', () => {
const { ir, code } = compileWithSlotsOutlet(
`<slot @click="foo" v-on="bar" :baz="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
props: [
[{ key: { content: 'click' }, values: [{ content: 'foo' }] }],
{ value: { content: 'bar' }, handler: true },
[{ key: { content: 'baz' }, values: [{ content: 'qux' }] }],
],
})
})
test('default slot outlet with fallback', () => {
const { ir, code } = compileWithSlotsOutlet(`<slot><div/></slot>`)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'default' },
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
returns: [2],
},
})
})
test('named slot outlet with fallback', () => {
const { ir, code } = compileWithSlotsOutlet(
`<slot name="foo"><div/></slot>`,
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'foo' },
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
returns: [2],
},
})
})
test('default slot outlet with props & fallback', () => {
const { ir, code } = compileWithSlotsOutlet(
`<slot :foo="bar"><div/></slot>`,
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'default' },
props: [[{ key: { content: 'foo' }, values: [{ content: 'bar' }] }]],
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
returns: [2],
},
})
})
test('named slot outlet with props & fallback', () => {
const { ir, code } = compileWithSlotsOutlet(
`<slot name="foo" :foo="bar"><div/></slot>`,
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'foo' },
props: [[{ key: { content: 'foo' }, values: [{ content: 'bar' }] }]],
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
returns: [2],
},
})
})
test('error on unexpected custom directive on <slot>', () => {
const onError = vi.fn()
const source = `<slot v-foo />`
const index = source.indexOf('v-foo')
const { code } = compileWithSlotsOutlet(source, { onError })
expect(code).toMatchSnapshot()
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
loc: {
start: {
offset: index,
line: 1,
column: index + 1,
},
end: {
offset: index + 5,
line: 1,
column: index + 6,
},
},
})
})
test('error on unexpected custom directive with v-show on <slot>', () => {
const onError = vi.fn()
const source = `<slot v-show="ok" />`
const index = source.indexOf('v-show="ok"')
const { code } = compileWithSlotsOutlet(source, { onError })
expect(code).toMatchSnapshot()
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
loc: {
start: {
offset: index,
line: 1,
column: index + 1,
},
end: {
offset: index + 11,
line: 1,
column: index + 12,
},
},
})
})
})

Some files were not shown because too many files have changed in this diff Show More