-
+
-
+ + + + ++ +
+
diff --git a/.github/contributing.md b/.github/contributing.md
index 2554582b887..f0ec46ee709 100644
--- a/.github/contributing.md
+++ b/.github/contributing.md
@@ -38,7 +38,6 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
### Pull Request Checklist
- Vue core has two primary work branches: `main` and `minor`.
-
- If your pull request is a feature that adds new API surface, it should be submitted against the `minor` branch.
- Otherwise, it should be submitted against the `main` branch.
@@ -46,12 +45,10 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
- [Make sure to tick the "Allow edits from maintainers" box](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). This allows us to directly make minor edits / refactors and saves a lot of time.
- If adding a new feature:
-
- Add accompanying test case.
- Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.
- If fixing a bug:
-
- If you are resolving a special issue, add `(fix #xxxx[,#xxxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `update entities encoding/decoding (fix #3899)`.
- Provide a detailed description of the bug in the PR. Live demo preferred.
- Add appropriate test coverage if applicable. You can check the coverage of your code addition by running `nr test-coverage`.
@@ -69,9 +66,7 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
- The PR should fix the intended bug **only** and not introduce unrelated changes. This includes unnecessary refactors - a PR should focus on the fix and not code style, this makes it easier to trace changes in the future.
- Consider the performance / size impact of the changes, and whether the bug being fixes justifies the cost. If the bug being fixed is a very niche edge case, we should try to minimize the size / perf cost to make it worthwhile.
-
- Is the code perf-sensitive (e.g. in "hot paths" like component updates or the vdom patch function?)
-
- If the branch is dev-only, performance is less of a concern.
- Check how much extra bundle size the change introduces.
@@ -265,7 +260,6 @@ This repository employs a [monorepo](https://en.wikipedia.org/wiki/Monorepo) set
- `vue`: The public facing "full build" which includes both the runtime AND the compiler.
- Private utility packages:
-
- `dts-test`: Contains type-only tests against generated dts files.
- `sfc-playground`: The playground continuously deployed at https://play.vuejs.org. To run the playground locally, use [`nr dev-sfc`](#nr-dev-sfc).
@@ -290,27 +284,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:
diff --git a/.github/maintenance.md b/.github/maintenance.md
index b1fb550dd7a..7b0c2a33626 100644
--- a/.github/maintenance.md
+++ b/.github/maintenance.md
@@ -48,7 +48,6 @@ Depending on the type of the PR, different considerations need to be taken into
- Performance: if a refactor PR claims to improve performance, there should be benchmarks showcasing said performance unless the improvement is self-explanatory.
- Code quality / stylistic PRs: we should be conservative on merging this type PRs because (1) they can be subjective in many cases, and (2) they often come with large git diffs, causing merge conflicts with other pending PRs, and leading to unwanted noise when tracing changes through git history. Use your best judgement on this type of PRs on whether they are worth it.
-
- For PRs in this category that are approved, do not merge immediately. Group them before releasing a new minor, after all feature-oriented PRs are merged.
### Reviewing a Feature
@@ -56,7 +55,6 @@ Depending on the type of the PR, different considerations need to be taken into
- Feature PRs should always have clear context and explanation on why the feature should be added, ideally in the form of an RFC. If the PR doesn't explain what real-world problem it is solving, ask the contributor to clarify.
- Decide if the feature should require an RFC process. The line isn't always clear, but a rough criteria is whether it is augmenting an existing API vs. adding a new API. Some examples:
-
- Adding a new built-in component or directive is "significant" and definitely requires an RFC.
- Template syntax additions like adding a new `v-on` modifier or a new `v-bind` syntax sugar are "substantial". It would be nice to have an RFC for it, but a detailed explanation on the use case and reasoning behind the design directly in the PR itself can be acceptable.
- Small, low-impact additions like exposing a new utility type or adding a new app config option can be self-explanatory, but should still provide enough context in the PR.
@@ -70,7 +68,6 @@ Depending on the type of the PR, different considerations need to be taken into
- Implementation: code style should be consistent with the rest of the codebase, follow common best practices. Prefer code that is boring but easy to understand over "clever" code.
- Size: bundle size matters. We have a GitHub action that compares the size change for every PR. We should always aim to realize the desired changes with the smallest amount of code size increase.
-
- Sometimes we need to compare the size increase vs. perceived benefits to decide whether a change is justifiable. Also take extra care to make sure added code can be tree-shaken if not needed.
- Make sure to put dev-only code in `__DEV__` branches so they are tree-shakable.
@@ -80,7 +77,6 @@ Depending on the type of the PR, different considerations need to be taken into
- Make sure it doesn't accidentally cause dev-only or compiler-only code branches to be included in the runtime build. Notable case is that some functions in @vue/shared are compiler-only and should not be used in runtime code, e.g. `isHTMLTag` and `isSVGTag`.
- Performance
-
- Be careful about code changes in "hot paths", in particular the Virtual DOM renderer (`runtime-core/src/renderer.ts`) and component instantiation code.
- Potential Breakage
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c8c217f62c4..6b69e4727e4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 1122eb35573..1202ef9c8b4 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -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:
diff --git a/.gitignore b/.gitignore
index 9dd21f59bf6..973c062daf7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ TODOs.md
dts-build/packages
*.tsbuildinfo
*.tgz
+packages-private/benchmark/reference
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 302428290b9..7907859bb86 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -14,5 +14,6 @@
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
- "editor.formatOnSave": true
+ "editor.formatOnSave": true,
+ "vitest.disableWorkspaceWarning": true
}
diff --git a/eslint.config.js b/eslint.config.js
index b752b2e19f1..1f5128ec72f 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -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',
diff --git a/package.json b/package.json
index 08bdc5ac908..e05a475cb50 100644
--- a/package.json
+++ b/package.json
@@ -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.16.0",
"@types/semver": "^7.7.0",
"@types/serve-handler": "^6.1.4",
+ "@vitest/ui": "^3.0.2",
"@vitest/coverage-v8": "^3.1.4",
"@vitest/eslint-plugin": "^1.2.1",
"@vue/consolidate": "1.0.0",
diff --git a/packages-private/benchmark/.gitignore b/packages-private/benchmark/.gitignore
new file mode 100644
index 00000000000..484ab7e5c61
--- /dev/null
+++ b/packages-private/benchmark/.gitignore
@@ -0,0 +1 @@
+results/*
diff --git a/packages-private/benchmark/client/App.vue b/packages-private/benchmark/client/App.vue
new file mode 100644
index 00000000000..c85deea53ea
--- /dev/null
+++ b/packages-private/benchmark/client/App.vue
@@ -0,0 +1,136 @@
+
+
+
+ Vue.js (VDOM) Benchmark
+
+
+
+
+
+
+
+
diff --git a/packages-private/benchmark/client/AppVapor.vue b/packages-private/benchmark/client/AppVapor.vue
new file mode 100644
index 00000000000..0fd284da3f4
--- /dev/null
+++ b/packages-private/benchmark/client/AppVapor.vue
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+ {{ row.id }}
+
+ {{ row.label.value }}
+
+
+
+
+
+
+
+ Vue.js (Vapor) Benchmark
+
+
+
+
+
+
+
+
diff --git a/packages-private/benchmark/client/data.ts b/packages-private/benchmark/client/data.ts
new file mode 100644
index 00000000000..ea5de1451d8
--- /dev/null
+++ b/packages-private/benchmark/client/data.ts
@@ -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
+}
diff --git a/packages-private/benchmark/client/index.html b/packages-private/benchmark/client/index.html
new file mode 100644
index 00000000000..c3ca4c53590
--- /dev/null
+++ b/packages-private/benchmark/client/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+ {{ row.id }}
+
+ {{ row.label.value }}
+
+
+
+
+
+
+
+
props.msg: {{ msg }}
+ + + +props.msg: {{ msg }}
+hello
hello
+ + +
", 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("") +const t1 = _template("") + +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("") +const t1 = _template("") +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("") +const t1 = _template("") + +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("foo") +const t1 = _template("bar") +const t2 = _template("baz") +const t3 = _template("", 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 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap new file mode 100644 index 00000000000..5ef064974c0 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap @@ -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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("") + +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("", 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("", 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("", 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("", 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("", true) + +export function render(_ctx) { + const n0 = t0() + _applyDynamicModel(n0, () => (_ctx.model), _value => (_ctx.model = _value)) + return n0 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap new file mode 100644 index 00000000000..dd00e552675 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap @@ -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("", 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("", 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("", 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("", 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("") +const t1 = _template("") +const t2 = _template("") +const t3 = _template("") +_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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", true) +_delegateEvents("click") + +export function render(_ctx, $props, $emit, $attrs, $slots) { + const n0 = t0() + n0.$evtclick = _ctx.handleClick + return n0 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap new file mode 100644 index 00000000000..ab3ade45b60 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap @@ -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("", 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("
{{ first }}
+ {{ second }} + {{ third }} +{{ forth }}
+']) + expect(ir.block.effect).toEqual([]) + const op = ir.block.dynamic.children[0].operation as IfIRNode + expect(op.positive.effect).toMatchObject([ + { + operations: [ + { + type: IRNodeTypes.SET_TEXT, + element: 4, + values: [ + { + content: 'msg', + type: NodeTypes.SIMPLE_EXPRESSION, + isStatic: false, + }, + ], + }, + ], + }, + ]) + expect(op.positive.dynamic).toMatchObject({ + id: 1, + children: { + 2: { + id: 4, + }, + }, + }) + }) + + test('dedupe same template', () => { + const { code, ir } = compileWithVIf( + `
bar