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/CHANGELOG.md b/CHANGELOG.md index d490aa19d95..6e787f1a982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,599 +1,144 @@ -## [3.5.17](https://github.com/vuejs/core/compare/v3.5.16...v3.5.17) (2025-06-18) +# [3.6.0-alpha.2](https://github.com/vuejs/core/compare/v3.6.0-alpha.1...v3.6.0-alpha.2) (2025-07-18) ### Bug Fixes -* **compat:** allow v-model built in modifiers on component ([#12654](https://github.com/vuejs/core/issues/12654)) ([cb14b86](https://github.com/vuejs/core/commit/cb14b860f150c4a83bcd52cd26096b7a5aa3a2bf)), closes [#12652](https://github.com/vuejs/core/issues/12652) -* **compile-sfc:** handle mapped types work with omit and pick ([#12648](https://github.com/vuejs/core/issues/12648)) ([4eb46e4](https://github.com/vuejs/core/commit/4eb46e443f1878199755cb73d481d318a9714392)), closes [#12647](https://github.com/vuejs/core/issues/12647) -* **compiler-core:** do not increase newlines in `InEntity` state ([#13362](https://github.com/vuejs/core/issues/13362)) ([f05a8d6](https://github.com/vuejs/core/commit/f05a8d613bd873b811cfdb9979ccac8382dba322)) -* **compiler-core:** ignore whitespace when matching adjacent v-if ([#12321](https://github.com/vuejs/core/issues/12321)) ([10ebcef](https://github.com/vuejs/core/commit/10ebcef8c870dbc042b0ea49b1424b2e8f692145)), closes [#9173](https://github.com/vuejs/core/issues/9173) -* **compiler-core:** prevent comments from blocking static node hoisting ([#13345](https://github.com/vuejs/core/issues/13345)) ([55dad62](https://github.com/vuejs/core/commit/55dad625acd9e9ddd5a933d5e323ecfdec1a612f)), closes [#13344](https://github.com/vuejs/core/issues/13344) -* **compiler-sfc:** improved type resolution for function type aliases ([#13452](https://github.com/vuejs/core/issues/13452)) ([f3479aa](https://github.com/vuejs/core/commit/f3479aac9625f4459e650d1c0a70e73863147903)), closes [#13444](https://github.com/vuejs/core/issues/13444) -* **custom-element:** ensure configureApp is applied to async component ([#12607](https://github.com/vuejs/core/issues/12607)) ([5ba1afb](https://github.com/vuejs/core/commit/5ba1afba09c3ea56c1c17484f5d8aeae210ce52a)), closes [#12448](https://github.com/vuejs/core/issues/12448) -* **custom-element:** prevent injecting child styles if shadowRoot is false ([#12769](https://github.com/vuejs/core/issues/12769)) ([73055d8](https://github.com/vuejs/core/commit/73055d8d9578d485e3fe846726b50666e1aa56f5)), closes [#12630](https://github.com/vuejs/core/issues/12630) -* **reactivity:** add `__v_skip` flag to `Dep` to prevent reactive conversion ([#12804](https://github.com/vuejs/core/issues/12804)) ([e8d8f5f](https://github.com/vuejs/core/commit/e8d8f5f604e821acc46b4200d5b06979c05af1c2)), closes [#12803](https://github.com/vuejs/core/issues/12803) -* **runtime-core:** unset old ref during patching when new ref is absent ([#12900](https://github.com/vuejs/core/issues/12900)) ([47ddf98](https://github.com/vuejs/core/commit/47ddf986021dff8de68b0da72787e53a6c19de4c)), closes [#12898](https://github.com/vuejs/core/issues/12898) -* **slots:** make cache indexes marker non-enumerable ([#13469](https://github.com/vuejs/core/issues/13469)) ([919c447](https://github.com/vuejs/core/commit/919c44744bba1f0c661c87d2059c3b429611aa7e)), closes [#13468](https://github.com/vuejs/core/issues/13468) -* **ssr:** handle initial selected state for select with v-model + v-for/v-if option ([#13487](https://github.com/vuejs/core/issues/13487)) ([1552095](https://github.com/vuejs/core/commit/15520954f9f1c7f834175938a50dba5d4be0e6c4)), closes [#13486](https://github.com/vuejs/core/issues/13486) -* **types:** typo of `vOnce` and `vSlot` ([#13343](https://github.com/vuejs/core/issues/13343)) ([762fae4](https://github.com/vuejs/core/commit/762fae4b57ad60602e5c84465a3bff562785b314)) +* **compiler-vapor:** handle empty interpolation ([#13592](https://github.com/vuejs/core/issues/13592)) ([d1f2915](https://github.com/vuejs/core/commit/d1f2915cfe7915fa73624485ff3dd443176a31a9)) +* **compiler-vapor:** handle special characters in cached variable names ([#13626](https://github.com/vuejs/core/issues/13626)) ([a5e106d](https://github.com/vuejs/core/commit/a5e106d96eb17d73c8673e826393c910d5594a2f)) +* **compiler-vapor:** selectors was not initialized in time when the initial value of createFor source was not empty ([#13642](https://github.com/vuejs/core/issues/13642)) ([f04c9c3](https://github.com/vuejs/core/commit/f04c9c342d398c11111c873143dc437f588578ee)) +* **reactivity:** allow collect effects in EffectScope ([#13657](https://github.com/vuejs/core/issues/13657)) ([b9fb79a](https://github.com/vuejs/core/commit/b9fb79a1fd099b67e01c5fe5941551c0da3a0cae)), closes [#13656](https://github.com/vuejs/core/issues/13656) +* **reactivity:** remove link check to align with 3.5 ([#13654](https://github.com/vuejs/core/issues/13654)) ([3cb27d1](https://github.com/vuejs/core/commit/3cb27d156f6a30e8f950616a53a3726519eaf216)), closes [#13620](https://github.com/vuejs/core/issues/13620) +* **runtime-core:** use __vapor instead of vapor to identify Vapor components ([#13652](https://github.com/vuejs/core/issues/13652)) ([ad21b1b](https://github.com/vuejs/core/commit/ad21b1b7e96bc894f5df0d95fbd77c9ba6b15c2e)) +* **runtime-vapor:** component emits vdom interop ([#13498](https://github.com/vuejs/core/issues/13498)) ([d95fc18](https://github.com/vuejs/core/commit/d95fc186c26e81345cd75037c3c1304b0eae13b4)) +* **runtime-vapor:** handle v-model vdom interop error ([#13643](https://github.com/vuejs/core/issues/13643)) ([2be828a](https://github.com/vuejs/core/commit/2be828a0c165c7f1533ace0bd81fba43a2af16d6)) +* **runtime-vapor:** remove access globalProperties warning ([#13609](https://github.com/vuejs/core/issues/13609)) ([fca74b0](https://github.com/vuejs/core/commit/fca74b00a86c6039aa05591618539a77aaa72daf)) -## [3.5.16](https://github.com/vuejs/core/compare/v3.5.15...v3.5.16) (2025-05-29) - - -### Reverts - -* Revert "fix(compiler-sfc): add scoping tag to trailing universal selector" (#13406) ([19f23b1](https://github.com/vuejs/core/commit/19f23b180bb679e38db95d6a10a420abeedc8e1c)), closes [#13406](https://github.com/vuejs/core/issues/13406) -* Revert "fix(compiler-sfc): add error handling for defineModel() without variable" (#13390) ([42f879f](https://github.com/vuejs/core/commit/42f879fcab48e0e1011967a771b4ad9e8838d760)), closes [#13390](https://github.com/vuejs/core/issues/13390) - - - -## [3.5.15](https://github.com/vuejs/core/compare/v3.5.14...v3.5.15) (2025-05-26) - - -### Bug Fixes - -* **compat:** ensure false value on input retains value attribute ([#13216](https://github.com/vuejs/core/issues/13216)) ([1a66474](https://github.com/vuejs/core/commit/1a664749d4d65a345589a6d78106ede7574cb2e1)), closes [#13205](https://github.com/vuejs/core/issues/13205) -* **compat:** should not warn COMPILER_V_BIND_OBJECT_ORDER when using v-bind together with v-for ([#12993](https://github.com/vuejs/core/issues/12993)) ([93949e6](https://github.com/vuejs/core/commit/93949e6587ee019bccd5b8b9d76f0e1ed6ea16fc)), closes [#12992](https://github.com/vuejs/core/issues/12992) -* **compile-sfc:** handle inline template source map in prod build ([#12701](https://github.com/vuejs/core/issues/12701)) ([89edc6c](https://github.com/vuejs/core/commit/89edc6cdcbd34ea6394927ecbfaa61dc4f871de7)), closes [#12682](https://github.com/vuejs/core/issues/12682) [vitejs/vite-plugin-vue#500](https://github.com/vitejs/vite-plugin-vue/issues/500) -* **compiler-core:** ensure mapping is added only if node source is available ([#13285](https://github.com/vuejs/core/issues/13285)) ([d37a2ac](https://github.com/vuejs/core/commit/d37a2ac59d904ac0e3257ba552b6c04920a363f0)), closes [#13261](https://github.com/vuejs/core/issues/13261) [vitejs/vite-plugin-vue#368](https://github.com/vitejs/vite-plugin-vue/issues/368) -* **compiler-dom:** improve HTML nesting validation to allow any child element within template tag ([#13320](https://github.com/vuejs/core/issues/13320)) ([163b365](https://github.com/vuejs/core/commit/163b3651d174321911648a164052effa9249a2aa)), closes [#13318](https://github.com/vuejs/core/issues/13318) -* **compiler-sfc:** add error handling for defineModel() without variable assignment ([#13352](https://github.com/vuejs/core/issues/13352)) ([00734af](https://github.com/vuejs/core/commit/00734afef5f7bddbdaee52aa5359a6ef989f32d3)), closes [#13280](https://github.com/vuejs/core/issues/13280) -* **compiler-sfc:** add scoping tag to trailing universal selector ([#12918](https://github.com/vuejs/core/issues/12918)) ([949df80](https://github.com/vuejs/core/commit/949df808809fd7cccf7718797beab0654aa68302)), closes [#12906](https://github.com/vuejs/core/issues/12906) -* **compiler-sfc:** improve type inference for TSTypeAliasDeclaration with better runtime type detection ([#13245](https://github.com/vuejs/core/issues/13245)) ([cf5a5e0](https://github.com/vuejs/core/commit/cf5a5e0edf0efcab25c27aa2d13eba91f7372d39)), closes [#13240](https://github.com/vuejs/core/issues/13240) -* **compiler-sfc:** simulate `allowArbitraryExtensions` on resolving type ([#13301](https://github.com/vuejs/core/issues/13301)) ([f7ce5ae](https://github.com/vuejs/core/commit/f7ce5ae666129339c006b339437c2dff6bceffe0)), closes [#13295](https://github.com/vuejs/core/issues/13295) -* **custom-element:** allow injecting values ​​from app context in nested elements ([#13219](https://github.com/vuejs/core/issues/13219)) ([b991075](https://github.com/vuejs/core/commit/b9910755a50c7d6c52b28c3aef20cf97810295c9)), closes [#13212](https://github.com/vuejs/core/issues/13212) -* **custom-element:** ensure proper remount and prevent redundant slot parsing with shadowRoot false ([#13201](https://github.com/vuejs/core/issues/13201)) ([1d41d4d](https://github.com/vuejs/core/commit/1d41d4de7f64a37160c8171d0137fd8d35c346c9)), closes [#13199](https://github.com/vuejs/core/issues/13199) -* **custom-element:** preserve appContext during update ([#12455](https://github.com/vuejs/core/issues/12455)) ([013749e](https://github.com/vuejs/core/commit/013749e75ef3b51762a86da379ea4ba4501b54ae)), closes [#12453](https://github.com/vuejs/core/issues/12453) -* **custom-element:** properly resolve props for sync component defs ([#12855](https://github.com/vuejs/core/issues/12855)) ([a683c80](https://github.com/vuejs/core/commit/a683c80cf44ecc482f8ac9c76bf2381443c1b0bb)), closes [#12854](https://github.com/vuejs/core/issues/12854) -* **hydration:** handle transition appear hydration edge case ([#13339](https://github.com/vuejs/core/issues/13339)) ([35aeae7](https://github.com/vuejs/core/commit/35aeae7fa3168adcf9ed95fd35495d17c8b93eeb)), closes [#13335](https://github.com/vuejs/core/issues/13335) -* **hydration:** skip lazy hydration for patched components ([#13283](https://github.com/vuejs/core/issues/13283)) ([80055fd](https://github.com/vuejs/core/commit/80055fddfb3ca1e2a44f19c7f0ffaeba00de5140)), closes [#13255](https://github.com/vuejs/core/issues/13255) -* **suspense:** handle edge case in patching list nodes within Suspense ([#13306](https://github.com/vuejs/core/issues/13306)) ([772b008](https://github.com/vuejs/core/commit/772b0087cb7be151c514a1d30365fb0f61a652ba)), closes [#13305](https://github.com/vuejs/core/issues/13305) -* **teleport:** handle deferred teleport updates before and after mount ([#13350](https://github.com/vuejs/core/issues/13350)) ([d15dce3](https://github.com/vuejs/core/commit/d15dce3142474f2ef9fffed38383acdadcb26c4c)), closes [#13349](https://github.com/vuejs/core/issues/13349) -* **types:** avoid merging component instance into `$props` in `ComponentInstance` ([#12870](https://github.com/vuejs/core/issues/12870)) ([f44feed](https://github.com/vuejs/core/commit/f44feed6fa461a9c4c724e9631c19e9e214c0a20)), closes [#12751](https://github.com/vuejs/core/issues/12751) -* **types:** exclude `undefined` from inferred prop types with default values ([#13007](https://github.com/vuejs/core/issues/13007)) ([5179d32](https://github.com/vuejs/core/commit/5179d328d950015e7fb2a74fe1a8518fd8d2c94e)), closes [#13006](https://github.com/vuejs/core/issues/13006) -* **watch:** update `oldValue` before running `cb` to prevent stale value ([#12296](https://github.com/vuejs/core/issues/12296)) ([c69c4bb](https://github.com/vuejs/core/commit/c69c4bb59c114f2b5e03733b55ef9ace3087b5c3)), closes [#12294](https://github.com/vuejs/core/issues/12294) - - - -## [3.5.14](https://github.com/vuejs/core/compare/v3.5.13...v3.5.14) (2025-05-15) - - -### Bug Fixes - -* **compat:** correct deprecation message for v-bind.sync usage ([#13137](https://github.com/vuejs/core/issues/13137)) ([466b30f](https://github.com/vuejs/core/commit/466b30f4049ec89fb282624ec17d1a93472ab93f)), closes [#13133](https://github.com/vuejs/core/issues/13133) -* **compiler-core:** remove slot cache from parent renderCache during unmounting ([#13215](https://github.com/vuejs/core/issues/13215)) ([5d166f3](https://github.com/vuejs/core/commit/5d166f3796a03a497435fc079c6a83a4e9c6cf52)) -* **compiler-sfc:** fix scope handling for props destructure in function parameters and catch clauses ([8e34357](https://github.com/vuejs/core/commit/8e3435779a667de485cf9efd78667d0ca14c5f84)), closes [#12790](https://github.com/vuejs/core/issues/12790) -* **compiler-sfc:** treat the return value of `useTemplateRef` as a definite ref ([#13197](https://github.com/vuejs/core/issues/13197)) ([8ae1122](https://github.com/vuejs/core/commit/8ae11226e8ee938615e17c7b81dc38ae3f7cefb9)) -* **compiler:** fix spelling error in domTagConfig ([#13043](https://github.com/vuejs/core/issues/13043)) ([388295b](https://github.com/vuejs/core/commit/388295b27f3cc69eba25d325bbe60a36a3df831a)) -* **customFormatter:** properly accessing ref value during debugger ([#12948](https://github.com/vuejs/core/issues/12948)) ([fdbd026](https://github.com/vuejs/core/commit/fdbd02658301dd794fe0c84f0018d080a07fca9f)) -* **hmr/teleport:** adjust static children traversal for HMR in dev mode ([#12819](https://github.com/vuejs/core/issues/12819)) ([5e37dd0](https://github.com/vuejs/core/commit/5e37dd009562bcd8080a200c32abde2d6e4f0305)), closes [#12816](https://github.com/vuejs/core/issues/12816) -* **hmr:** avoid hydration for hmr root reload ([#12450](https://github.com/vuejs/core/issues/12450)) ([1f98a9c](https://github.com/vuejs/core/commit/1f98a9c493d01c21befa90107f0593bc92a58932)), closes [vitejs/vite-plugin-vue#146](https://github.com/vitejs/vite-plugin-vue/issues/146) [vitejs/vite-plugin-vue#477](https://github.com/vitejs/vite-plugin-vue/issues/477) -* **hmr:** avoid hydration for hmr updating ([#12262](https://github.com/vuejs/core/issues/12262)) ([9c4dbbc](https://github.com/vuejs/core/commit/9c4dbbc5185125835ad3e49baba303bd54676111)), closes [#7706](https://github.com/vuejs/core/issues/7706) [#8170](https://github.com/vuejs/core/issues/8170) -* **reactivity:** ensure markRaw objects are not reactive ([#12824](https://github.com/vuejs/core/issues/12824)) ([295b5ec](https://github.com/vuejs/core/commit/295b5ec19b6a52c4a56652cc4d6e93a4ea7c14ed)), closes [#12807](https://github.com/vuejs/core/issues/12807) -* **reactivity:** ensure multiple effectScope on() and off() calls maintains correct active scope ([22dcbf3](https://github.com/vuejs/core/commit/22dcbf3e20eb84f69c8952f6f70d9990136a4a68)), closes [#12631](https://github.com/vuejs/core/issues/12631) [#12632](https://github.com/vuejs/core/issues/12632) [#12641](https://github.com/vuejs/core/issues/12641) -* **reactivity:** should not recompute if computed does not track reactive data ([#12341](https://github.com/vuejs/core/issues/12341)) ([0b23fd2](https://github.com/vuejs/core/commit/0b23fd23833cf085e7e112bf4435cfc9b360d072)), closes [#12337](https://github.com/vuejs/core/issues/12337) -* **runtime-core:** stop tracking deps in setRef during unmount ([#13210](https://github.com/vuejs/core/issues/13210)) ([016c472](https://github.com/vuejs/core/commit/016c472bd2e7604b21c69dee1da8545ce26e4d2f)) -* **runtime-core:** update __vnode of static nodes when patching along the optimized path ([#13223](https://github.com/vuejs/core/issues/13223)) ([b3ecee3](https://github.com/vuejs/core/commit/b3ecee3da8ed5c55dea89ce6b4b376b2b722b018)) -* **runtime-core:** inherit comment nodes during block patch in production build ([#10748](https://github.com/vuejs/core/issues/10748)) ([6264505](https://github.com/vuejs/core/commit/626450590d81f79117b34d2a73073b1dc8f551bd)), closes [#10747](https://github.com/vuejs/core/issues/10747) [#12650](https://github.com/vuejs/core/issues/12650) -* **runtime-core:** prevent unmounted vnode from being inserted during transition leave ([#12862](https://github.com/vuejs/core/issues/12862)) ([d6a6ec1](https://github.com/vuejs/core/commit/d6a6ec13ce521683bfb2a22932778ef7b51f8600)), closes [#12860](https://github.com/vuejs/core/issues/12860) -* **runtime-core:** respect immutability for readonly reactive arrays in `v-for` ([#13091](https://github.com/vuejs/core/issues/13091)) ([3f27c58](https://github.com/vuejs/core/commit/3f27c58ffbd4309df369bc89493fdc284dc540bb)), closes [#13087](https://github.com/vuejs/core/issues/13087) -* **runtime-dom:** always treat autocorrect as attribute ([#13001](https://github.com/vuejs/core/issues/13001)) ([1499135](https://github.com/vuejs/core/commit/1499135c227236e037bb746beeb777941b0b58ff)), closes [#5705](https://github.com/vuejs/core/issues/5705) -* **slots:** properly warn if slot invoked in setup ([#12195](https://github.com/vuejs/core/issues/12195)) ([9196222](https://github.com/vuejs/core/commit/9196222ae1d63b52b35ac5fbf5e71494587ccf05)), closes [#12194](https://github.com/vuejs/core/issues/12194) -* **ssr:** properly init slots during ssr rendering ([#12441](https://github.com/vuejs/core/issues/12441)) ([2206cd2](https://github.com/vuejs/core/commit/2206cd235a1627c540e795e378b7564a55b47313)), closes [#12438](https://github.com/vuejs/core/issues/12438) -* **transition:** fix KeepAlive with transition out-in mode behavior in production ([#12468](https://github.com/vuejs/core/issues/12468)) ([343c891](https://github.com/vuejs/core/commit/343c89122448719bd6ed6bd9de986dfb2721d6bf)), closes [#12465](https://github.com/vuejs/core/issues/12465) -* **TransitionGroup:** reset prevChildren to prevent memory leak ([#13183](https://github.com/vuejs/core/issues/13183)) ([8b848cb](https://github.com/vuejs/core/commit/8b848cbbd2af337d23e19e202f9ab433f8580855)), closes [#13181](https://github.com/vuejs/core/issues/13181) -* **types:** allow return any for Options API lifecycle hooks ([#5914](https://github.com/vuejs/core/issues/5914)) ([06310e8](https://github.com/vuejs/core/commit/06310e82f5bed62d1b9733dcb18cd8d6edc988de)) -* **types:** the directive's modifiers should be optional ([#12605](https://github.com/vuejs/core/issues/12605)) ([10e54dc](https://github.com/vuejs/core/commit/10e54dcc86a7967f3196d96200bcbd1d3d42082f)) -* **typos:** fix comments referencing transformElement.ts ([#12551](https://github.com/vuejs/core/issues/12551))[ci-skip] ([11c053a](https://github.com/vuejs/core/commit/11c053a5429ad0d27a0e2c78b6b026ea00ace116)) - +# [3.6.0-alpha.1](https://github.com/vuejs/core/compare/v3.5.17...v3.6.0-alpha.1) (2025-07-12) ### Features -* **types:** add type TemplateRef ([#12645](https://github.com/vuejs/core/issues/12645)) ([636a861](https://github.com/vuejs/core/commit/636a8619f06c71dfd79f7f6412fd130c4f84226f)) - - - -## [3.5.13](https://github.com/vuejs/core/compare/v3.5.12...v3.5.13) (2024-11-15) - - -### Bug Fixes - -* **compiler-core:** handle v-memo + v-for with functional key ([#12014](https://github.com/vuejs/core/issues/12014)) ([99009ee](https://github.com/vuejs/core/commit/99009eee0efc238392daba93792d478525b21afa)), closes [#12013](https://github.com/vuejs/core/issues/12013) -* **compiler-dom:** properly stringify template string style ([#12392](https://github.com/vuejs/core/issues/12392)) ([2d78539](https://github.com/vuejs/core/commit/2d78539da35322aea5f821b3cf9b02d006abac72)), closes [#12391](https://github.com/vuejs/core/issues/12391) -* **custom-element:** avoid triggering mutationObserver when relecting props ([352bc88](https://github.com/vuejs/core/commit/352bc88c1bd2fda09c61ab17ea1a5967ffcd7bc0)), closes [#12214](https://github.com/vuejs/core/issues/12214) [#12215](https://github.com/vuejs/core/issues/12215) -* **deps:** update dependency postcss to ^8.4.48 ([#12356](https://github.com/vuejs/core/issues/12356)) ([b5ff930](https://github.com/vuejs/core/commit/b5ff930089985a58c3553977ef999cec2a6708a4)) -* **hydration:** the component vnode's el should be updated when a mismatch occurs. ([#12255](https://github.com/vuejs/core/issues/12255)) ([a20a4cb](https://github.com/vuejs/core/commit/a20a4cb36a3e717d1f8f259d0d59f133f508ff0a)), closes [#12253](https://github.com/vuejs/core/issues/12253) -* **reactivity:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806) -* **reactivity:** release nested effects/scopes on effect scope stop ([#12373](https://github.com/vuejs/core/issues/12373)) ([bee2f5e](https://github.com/vuejs/core/commit/bee2f5ee62dc0cd04123b737779550726374dd0a)), closes [#12370](https://github.com/vuejs/core/issues/12370) -* **runtime-dom:** set css vars before user onMounted hooks ([2d5c5e2](https://github.com/vuejs/core/commit/2d5c5e25e9b7a56e883674fb434135ac514429b5)), closes [#11533](https://github.com/vuejs/core/issues/11533) -* **runtime-dom:** set css vars on update to handle child forcing reflow in onMount ([#11561](https://github.com/vuejs/core/issues/11561)) ([c4312f9](https://github.com/vuejs/core/commit/c4312f9c715c131a09e552ba46e9beb4b36d55e6)) -* **ssr:** avoid updating subtree of async component if it is resolved ([#12363](https://github.com/vuejs/core/issues/12363)) ([da7ad5e](https://github.com/vuejs/core/commit/da7ad5e3d24f3e108401188d909d27a4910da095)), closes [#12362](https://github.com/vuejs/core/issues/12362) -* **ssr:** ensure v-text updates correctly with custom directives in SSR output ([#12311](https://github.com/vuejs/core/issues/12311)) ([1f75d4e](https://github.com/vuejs/core/commit/1f75d4e6dfe18121ebe443cd3e8105d54f727893)), closes [#12309](https://github.com/vuejs/core/issues/12309) -* **ssr:** handle initial selected state for select with v-model + v-for option ([#12399](https://github.com/vuejs/core/issues/12399)) ([4f8d807](https://github.com/vuejs/core/commit/4f8d8078221ee52deed266677a227ad2a6d8dd22)), closes [#12395](https://github.com/vuejs/core/issues/12395) -* **teleport:** handle deferred teleport update before mounted ([#12168](https://github.com/vuejs/core/issues/12168)) ([8bff142](https://github.com/vuejs/core/commit/8bff142f99b646e9dd15897ec75368fbf34f1534)), closes [#12161](https://github.com/vuejs/core/issues/12161) -* **templateRef:** set ref on cached async component which wrapped in KeepAlive ([#12290](https://github.com/vuejs/core/issues/12290)) ([983eb50](https://github.com/vuejs/core/commit/983eb50a17eac76f1bba4394ad0316c62b72191d)), closes [#4999](https://github.com/vuejs/core/issues/4999) [#5004](https://github.com/vuejs/core/issues/5004) -* **test:** update snapshot ([#12169](https://github.com/vuejs/core/issues/12169)) ([828d4a4](https://github.com/vuejs/core/commit/828d4a443919fa2aa4e2e92fbd03a5f04b258eea)) -* **Transition:** fix transition memory leak edge case ([#12182](https://github.com/vuejs/core/issues/12182)) ([660132d](https://github.com/vuejs/core/commit/660132df6c6a8c14bf75e593dc47d2fdada30322)), closes [#12181](https://github.com/vuejs/core/issues/12181) -* **transition:** reflow before leave-active class after leave-from ([#12288](https://github.com/vuejs/core/issues/12288)) ([4b479db](https://github.com/vuejs/core/commit/4b479db61d233b054561402ae94ef08550073ea1)), closes [#2593](https://github.com/vuejs/core/issues/2593) -* **types:** defineEmits w/ interface declaration ([#12343](https://github.com/vuejs/core/issues/12343)) ([1022eab](https://github.com/vuejs/core/commit/1022eabaa1aaf8436876f5ec5573cb1e4b3959a6)), closes [#8457](https://github.com/vuejs/core/issues/8457) -* **v-once:** setting hasOnce to current block only when in v-once ([#12374](https://github.com/vuejs/core/issues/12374)) ([37300fc](https://github.com/vuejs/core/commit/37300fc26190a7299efddbf98800ffd96d5cad96)), closes [#12371](https://github.com/vuejs/core/issues/12371) - - -### Performance Improvements - -* **reactivity:** do not track inner key `__v_skip`` ([#11690](https://github.com/vuejs/core/issues/11690)) ([d637bd6](https://github.com/vuejs/core/commit/d637bd6c0164c2883e6eabd3c2f1f8c258dedfb1)) -* **runtime-core:** use feature flag for call to resolveMergedOptions ([#12163](https://github.com/vuejs/core/issues/12163)) ([1755ac0](https://github.com/vuejs/core/commit/1755ac0a108ba3486bd8397e56d3bdcd69196594)) - - - -## [3.5.12](https://github.com/vuejs/core/compare/v3.5.11...v3.5.12) (2024-10-11) - - -### Bug Fixes - -* **compiler-dom:** avoid stringify option with null value ([#12096](https://github.com/vuejs/core/issues/12096)) ([f6d9926](https://github.com/vuejs/core/commit/f6d99262364b7444ebab8742158599e8cdd79eaa)), closes [#12093](https://github.com/vuejs/core/issues/12093) -* **compiler-sfc:** do not skip TSInstantiationExpression when transforming props destructure ([#12064](https://github.com/vuejs/core/issues/12064)) ([d3ecde8](https://github.com/vuejs/core/commit/d3ecde8a696ff62c8d0ab067fd1d7ee0565b63c5)) -* **compiler-sfc:** use sass modern api if available and avoid deprecation warning ([#11992](https://github.com/vuejs/core/issues/11992)) ([4474c11](https://github.com/vuejs/core/commit/4474c113d1fb1c26298dd6794275d5b5c7cc4d93)) -* **compiler:** clone loc to `ifNode` ([#12131](https://github.com/vuejs/core/issues/12131)) ([cde2c06](https://github.com/vuejs/core/commit/cde2c0671b00d4f6111fcbd7aa76e45872f20b0c)), closes [vuejs/language-tools#4911](https://github.com/vuejs/language-tools/issues/4911) -* **custom-element:** properly remove hyphenated attribute ([#12143](https://github.com/vuejs/core/issues/12143)) ([e16e9a7](https://github.com/vuejs/core/commit/e16e9a7341e7cfb3c443da4e5e5b06e8158712c3)), closes [#12139](https://github.com/vuejs/core/issues/12139) -* **defineModel:** handle kebab-case model correctly ([#12063](https://github.com/vuejs/core/issues/12063)) ([c0418a3](https://github.com/vuejs/core/commit/c0418a3b8fa96a0b108ab71b7aab5d3388f90557)), closes [#12060](https://github.com/vuejs/core/issues/12060) -* **deps:** update dependency monaco-editor to ^0.52.0 ([#12119](https://github.com/vuejs/core/issues/12119)) ([f7cbea2](https://github.com/vuejs/core/commit/f7cbea2111c7770a180b640f36f6a5d4d6abc698)) -* **hydration:** provide compat fallback for idle callback hydration strategy ([#11935](https://github.com/vuejs/core/issues/11935)) ([1ae545a](https://github.com/vuejs/core/commit/1ae545a3786abef983be1c969726489685569c92)) -* **reactivity:** trigger reactivity for Map key `undefined` ([#12055](https://github.com/vuejs/core/issues/12055)) ([7ad289e](https://github.com/vuejs/core/commit/7ad289e1e7fea654524008ff91e43a8b8a55ef22)), closes [#12054](https://github.com/vuejs/core/issues/12054) -* **runtime-core:** allow symbol values for slot prop key ([#12069](https://github.com/vuejs/core/issues/12069)) ([d9d4d4e](https://github.com/vuejs/core/commit/d9d4d4e158cd51a9ddda249f29de8467f60b2792)), closes [#12068](https://github.com/vuejs/core/issues/12068) -* **runtime-core:** fix required prop check false positive for kebab-case edge cases ([#12034](https://github.com/vuejs/core/issues/12034)) ([9da1ac1](https://github.com/vuejs/core/commit/9da1ac156552ac449754e1373aac7e349841becb)), closes [#12011](https://github.com/vuejs/core/issues/12011) -* **runtime-dom:** prevent unnecessary updates in v-model checkbox when value is unchanged ([#12146](https://github.com/vuejs/core/issues/12146)) ([ea943af](https://github.com/vuejs/core/commit/ea943afe404c4ca4b729906c5e8daf7aa2ccde9b)), closes [#12144](https://github.com/vuejs/core/issues/12144) -* **teleport:** handle disabled teleport with updateCssVars ([#12113](https://github.com/vuejs/core/issues/12113)) ([76a8223](https://github.com/vuejs/core/commit/76a8223199c148b79a5c0ea19e235164809760cd)), closes [#12112](https://github.com/vuejs/core/issues/12112) -* **transition/ssr:** make transition appear work with Suspense in SSR ([#12047](https://github.com/vuejs/core/issues/12047)) ([f1a4f67](https://github.com/vuejs/core/commit/f1a4f67aedfe83e440c54222213f070774faa421)), closes [#12046](https://github.com/vuejs/core/issues/12046) -* **types:** ensure `this.$props` type does not include `string` ([#12123](https://github.com/vuejs/core/issues/12123)) ([704173e](https://github.com/vuejs/core/commit/704173e24276706de672cca6c9507e4dd9651197)), closes [#12122](https://github.com/vuejs/core/issues/12122) -* **types:** retain union type narrowing with defaults applied ([#12108](https://github.com/vuejs/core/issues/12108)) ([05685a9](https://github.com/vuejs/core/commit/05685a9d7c42d4cd37169b867833776b91154fed)), closes [#12106](https://github.com/vuejs/core/issues/12106) -* **useId:** ensure useId consistency when using serverPrefetch ([#12128](https://github.com/vuejs/core/issues/12128)) ([b4d3534](https://github.com/vuejs/core/commit/b4d35349d8bc39aa15bd3f1094d230e5928b177c)), closes [#12102](https://github.com/vuejs/core/issues/12102) -* **watch:** watchEffect clean-up with SSR ([#12097](https://github.com/vuejs/core/issues/12097)) ([b094c72](https://github.com/vuejs/core/commit/b094c72b3d40c52c7124f145a9db028509a11202)), closes [#11956](https://github.com/vuejs/core/issues/11956) +- **vapor mode** ([#12359](https://github.com/vuejs/core/issues/12359)) ([bfe5ce3](https://github.com/vuejs/core/commit/bfe5ce309c6fc16bb49cca78e141862bc12708ac)) + Please see [About Vapor Mode](#about-vapor-mode) section below for details. ### Performance Improvements -* **reactivity:** avoid unnecessary recursion in removeSub ([#12135](https://github.com/vuejs/core/issues/12135)) ([ec917cf](https://github.com/vuejs/core/commit/ec917cfdb9d0169cd0835d3a0e28244242657dc9)) - - - -## [3.5.11](https://github.com/vuejs/core/compare/v3.5.10...v3.5.11) (2024-10-03) - +- **reactivity:** refactor reactivity core by porting [alien-signals](https://github.com/stackblitz/alien-signals) ([#12349](https://github.com/vuejs/core/issues/12349)) ([313dc61](https://github.com/vuejs/core/commit/313dc61bef59e6869aaec9b5ea47c0bf9044a3fc)) ### Bug Fixes -* **compiler-sfc:** do not skip `TSSatisfiesExpression` when transforming props destructure ([#12062](https://github.com/vuejs/core/issues/12062)) ([2328b05](https://github.com/vuejs/core/commit/2328b051f4efa1f1394b7d4e73b7c3f76e430e7c)), closes [#12061](https://github.com/vuejs/core/issues/12061) -* **reactivity:** prevent overwriting `next` property during batch processing ([#12075](https://github.com/vuejs/core/issues/12075)) ([d3f5e6e](https://github.com/vuejs/core/commit/d3f5e6e5319b4ffaa55ca9a2ea3d95d78e76fa58)), closes [#12072](https://github.com/vuejs/core/issues/12072) -* **scheduler:** job ordering when the post queue is flushing ([#12090](https://github.com/vuejs/core/issues/12090)) ([577edca](https://github.com/vuejs/core/commit/577edca8e7795436efd710d1c289ea8ea2642b0e)) -* **types:** correctly infer `TypeProps` when it is `any` ([#12073](https://github.com/vuejs/core/issues/12073)) ([57315ab](https://github.com/vuejs/core/commit/57315ab9688c9741a271d1075bbd28cbe5f71e2f)), closes [#12058](https://github.com/vuejs/core/issues/12058) -* **types:** should not intersect `PublicProps` with `Props` ([#12077](https://github.com/vuejs/core/issues/12077)) ([6f85894](https://github.com/vuejs/core/commit/6f8589437635706f825ccec51800effba1d2bf5f)) -* **types:** infer the first generic type of `Ref` correctly ([#12094](https://github.com/vuejs/core/issues/12094)) ([c97bb84](https://github.com/vuejs/core/commit/c97bb84d0b0a16b012f886b6498e924415ed63e5)) - +- **css-vars:** nullish v-bind in style should not lead to unexpected inheritance ([#12461](https://github.com/vuejs/core/issues/12461)) ([c85f1b5](https://github.com/vuejs/core/commit/c85f1b5a132eb8ec25f71b250e25e65a5c20964f)), closes [#12434](https://github.com/vuejs/core/issues/12434) [#12439](https://github.com/vuejs/core/issues/12439) [#7474](https://github.com/vuejs/core/issues/7474) [#7475](https://github.com/vuejs/core/issues/7475) +- **reactivity:** ensure multiple effectScope `on()` and `off()` calls maintains correct active scope ([#12641](https://github.com/vuejs/core/issues/12641)) ([679cbdf](https://github.com/vuejs/core/commit/679cbdf4806cf8c325098f2b579abab60fffb1bb)) +- **reactivity:** queuing effects in an array ([#13078](https://github.com/vuejs/core/issues/13078)) ([826550c](https://github.com/vuejs/core/commit/826550cd629c59dd91aeb5abdbe101a483497358)) +- **reactivity:** toRefs should be allowed on plain objects ([ac43b11](https://github.com/vuejs/core/commit/ac43b118975b17d7ce7d9e6886f8806af11bee55)) +- **scheduler:** improve error handling in job flushing ([#13587](https://github.com/vuejs/core/issues/13587)) ([94b2ddc](https://github.com/vuejs/core/commit/94b2ddc6f97170f4169d9d81b963c6bcaab08be2)) +- **scheduler:** recover nextTick from error in post flush cb ([2bbb6d2](https://github.com/vuejs/core/commit/2bbb6d2fc56896e64a32b4421822d12bde2bb6e8)) +### About Vapor Mode -## [3.5.10](https://github.com/vuejs/core/compare/v3.5.9...v3.5.10) (2024-09-27) +Vapor Mode is a new compilation mode for Vue Single-File Components (SFC) with the goal of reducing baseline bundle size and improved performance. It is 100% opt-in, and supports a subset of existing Vue APIs with mostly identical behavior. +Vapor Mode has demonstrated the same level of performance with Solid and Svelte 5 in [3rd party benchmarks](https://github.com/krausest/js-framework-benchmark). -### Bug Fixes +#### General Stability Notes -* **custom-element:** properly set kebab-case props on Vue custom elements ([ea3efa0](https://github.com/vuejs/core/commit/ea3efa09e008918c1d9ba7226833a8b1a7a57244)), closes [#12030](https://github.com/vuejs/core/issues/12030) [#12032](https://github.com/vuejs/core/issues/12032) -* **reactivity:** fix nested batch edge case ([93c95dd](https://github.com/vuejs/core/commit/93c95dd4cd416503f43a98a1455f62658d22b0b2)) -* **reactivity:** only clear notified flags for computed in first batch iteration ([aa9ef23](https://github.com/vuejs/core/commit/aa9ef2386a0cd39a174e5a887ec2b1a3525034fc)), closes [#12045](https://github.com/vuejs/core/issues/12045) -* **types/ref:** handle nested refs in UnwrapRef ([#12049](https://github.com/vuejs/core/issues/12049)) ([e2c19c2](https://github.com/vuejs/core/commit/e2c19c20cfee9788519a80c0e53e216b78505994)), closes [#12044](https://github.com/vuejs/core/issues/12044) +Vapor Mode is available starting in Vue 3.6 alpha. Please note it is still incomplete and unstable during the alpha phase. The current focus is making it available for wider stability and compatibility testing. For now, we recommend using it for the following cases: +- Partial usage in existing apps, e.g. implementing a perf-sensitive sub page in Vapor Mode. +- Build small new apps entirely in Vapor Mode. +We do not recommend migrating existing components to Vapor Mode yet. -## [3.5.9](https://github.com/vuejs/core/compare/v3.5.8...v3.5.9) (2024-09-26) +#### Pending Features +Things that do not work in this version yet: -### Bug Fixes +- SSR hydration\* (which means it does not work with Nuxt yet) +- Async Component\* +- Transition\* +- KeepAlive\* +- Suspense -* **reactivity:** fix property dep removal regression ([6001e5c](https://github.com/vuejs/core/commit/6001e5c81a05c894586f9287fbd991677bdd0455)), closes [#12020](https://github.com/vuejs/core/issues/12020) [#12021](https://github.com/vuejs/core/issues/12021) -* **reactivity:** fix recursive sync watcher on computed edge case ([10ff159](https://github.com/vuejs/core/commit/10ff15924053d9bd95ad706f78ce09e288213fcf)), closes [#12033](https://github.com/vuejs/core/issues/12033) [#12037](https://github.com/vuejs/core/issues/12037) -* **runtime-core:** avoid rendering plain object as VNode ([#12038](https://github.com/vuejs/core/issues/12038)) ([cb34b28](https://github.com/vuejs/core/commit/cb34b28a4a9bf868be4785b001c526163eda342e)), closes [#12035](https://github.com/vuejs/core/issues/12035) [vitejs/vite-plugin-vue#353](https://github.com/vitejs/vite-plugin-vue/issues/353) -* **runtime-core:** make useId() always return a string ([a177092](https://github.com/vuejs/core/commit/a177092754642af2f98c33a4feffe8f198c3c950)) -* **types:** correct type inference of union event names ([#12022](https://github.com/vuejs/core/issues/12022)) ([4da6881](https://github.com/vuejs/core/commit/4da688141d9e7c15b622c289deaa81b11845b2c7)) -* **vue:** properly cache runtime compilation ([#12019](https://github.com/vuejs/core/issues/12019)) ([fa0ba24](https://github.com/vuejs/core/commit/fa0ba24b3ace02d7ecab65e57c2bea89a2550dcb)) +Features marked with \* have pending PRs which will be merged during the alpha phase. +#### Opting in to Vapor Mode +Vapor Mode only works for Single File Components using ` +``` -* **reactivity:** do not remove dep from depsMap when cleaning up deps of computed ([#11995](https://github.com/vuejs/core/issues/11995)) ([0267a58](https://github.com/vuejs/core/commit/0267a588017eee4951ac2a877fe1ccae84cad905)) - - - -## [3.5.7](https://github.com/vuejs/core/compare/v3.5.6...v3.5.7) (2024-09-20) - - -### Bug Fixes +Vapor Mode components are usable in two scenarios: -* **compile-core:** fix v-model with newlines edge case ([#11960](https://github.com/vuejs/core/issues/11960)) ([6224288](https://github.com/vuejs/core/commit/62242886d705ece88dbcad45bb78072ecccad0ca)), closes [#8306](https://github.com/vuejs/core/issues/8306) -* **compiler-sfc:** initialize scope with null prototype object ([#11963](https://github.com/vuejs/core/issues/11963)) ([215e154](https://github.com/vuejs/core/commit/215e15407294bf667261360218f975b88c99c2e5)) -* **hydration:** avoid observing non-Element node ([#11954](https://github.com/vuejs/core/issues/11954)) ([7257e6a](https://github.com/vuejs/core/commit/7257e6a34200409b3fc347d3bb807e11e2785974)), closes [#11952](https://github.com/vuejs/core/issues/11952) -* **reactivity:** do not remove dep from depsMap when unsubbed by computed ([960706e](https://github.com/vuejs/core/commit/960706eebf73f08ebc9d5dd853a05def05e2c153)) -* **reactivity:** fix dev-only memory leak by updating dep.subsHead on sub removal ([5c8b76e](https://github.com/vuejs/core/commit/5c8b76ed6cfbbcee4cbaac0b72beab7291044e4f)), closes [#11956](https://github.com/vuejs/core/issues/11956) -* **reactivity:** fix memory leak from dep instances of garbage collected objects ([235ea47](https://github.com/vuejs/core/commit/235ea4772ed2972914cf142da8b7ac1fb04f7585)), closes [#11979](https://github.com/vuejs/core/issues/11979) [#11971](https://github.com/vuejs/core/issues/11971) -* **reactivity:** fix triggerRef call on ObjectRefImpl returned by toRef ([#11986](https://github.com/vuejs/core/issues/11986)) ([b030c8b](https://github.com/vuejs/core/commit/b030c8bc7327877efb98aa3d9a58eb287a6ff07a)), closes [#11982](https://github.com/vuejs/core/issues/11982) -* **scheduler:** ensure recursive jobs can't be queued twice ([#11955](https://github.com/vuejs/core/issues/11955)) ([d18d6aa](https://github.com/vuejs/core/commit/d18d6aa1b20dc57a8103c51ec4d61e8e53ed936d)) -* **ssr:** don't render comments in TransitionGroup ([#11961](https://github.com/vuejs/core/issues/11961)) ([a2f6ede](https://github.com/vuejs/core/commit/a2f6edeb02faedbb673c4bc5c6a59d9a79a37d07)), closes [#11958](https://github.com/vuejs/core/issues/11958) -* **transition:** respect `duration` setting even when it is `0` ([#11967](https://github.com/vuejs/core/issues/11967)) ([f927a4a](https://github.com/vuejs/core/commit/f927a4ae6f7c453f70ba89498ee0c737dc9866fd)) -* **types:** correct type inference of all-optional props ([#11644](https://github.com/vuejs/core/issues/11644)) ([9eca65e](https://github.com/vuejs/core/commit/9eca65ee9871d1ac878755afa9a3eb1b02030350)), closes [#11733](https://github.com/vuejs/core/issues/11733) [vuejs/language-tools#4704](https://github.com/vuejs/language-tools/issues/4704) +1. Inside a Vapor app instance create via `createVaporApp`. Apps created this way avoids pulling in the Virtual DOM runtime code and allows bundle baseline size to be drastically reduced. +2. To use Vapor components in a VDOM app instance created via `createApp`, the `vaporInteropPlugin` must be installed: -### Performance Improvements - -* **hydration:** avoid observer if element is in viewport ([#11639](https://github.com/vuejs/core/issues/11639)) ([e075dfa](https://github.com/vuejs/core/commit/e075dfad5c7649c6045e3711687ec888e7aa1a39)) - - - -## [3.5.6](https://github.com/vuejs/core/compare/v3.5.5...v3.5.6) (2024-09-16) - - -### Bug Fixes - -* **compile-dom:** should be able to stringify mathML ([#11891](https://github.com/vuejs/core/issues/11891)) ([85c138c](https://github.com/vuejs/core/commit/85c138ced108268f7656b568dfd3036a1e0aae34)) -* **compiler-sfc:** preserve old behavior when using withDefaults with desutructure ([8492c3c](https://github.com/vuejs/core/commit/8492c3c49a922363d6c77ef192c133a8fbce6514)), closes [#11930](https://github.com/vuejs/core/issues/11930) -* **reactivity:** avoid exponential perf cost and reduce call stack depth for deeply chained computeds ([#11944](https://github.com/vuejs/core/issues/11944)) ([c74bb8c](https://github.com/vuejs/core/commit/c74bb8c2dd9e82aaabb0a2a2b368e900929b513b)), closes [#11928](https://github.com/vuejs/core/issues/11928) -* **reactivity:** rely on dirty check only when computed has deps ([#11931](https://github.com/vuejs/core/issues/11931)) ([aa5dafd](https://github.com/vuejs/core/commit/aa5dafd2b55d42d6a29316a3bc91aea85c676a0b)), closes [#11929](https://github.com/vuejs/core/issues/11929) -* **watch:** `once` option should be ignored by watchEffect ([#11884](https://github.com/vuejs/core/issues/11884)) ([49fa673](https://github.com/vuejs/core/commit/49fa673493d93b77ddba2165ab6545bae84fd1ae)) -* **watch:** unwatch should be callable during SSR ([#11925](https://github.com/vuejs/core/issues/11925)) ([2d6adf7](https://github.com/vuejs/core/commit/2d6adf78a047eed091db277ffbd9df0822fb0bdd)), closes [#11924](https://github.com/vuejs/core/issues/11924) - - - -## [3.5.5](https://github.com/vuejs/core/compare/v3.5.4...v3.5.5) (2024-09-13) - - -### Bug Fixes - -* **compiler-core:** fix handling of delimiterOpen in VPre ([#11915](https://github.com/vuejs/core/issues/11915)) ([706d4ac](https://github.com/vuejs/core/commit/706d4ac1d0210b2d9134b3228280187fe02fc971)), closes [#11913](https://github.com/vuejs/core/issues/11913) -* **compiler-dom:** fix stringify static edge for partially eligible chunks in cached parent ([1d99d61](https://github.com/vuejs/core/commit/1d99d61c1bd77f9ea6743f6214a82add8346a121)), closes [#11879](https://github.com/vuejs/core/issues/11879) [#11890](https://github.com/vuejs/core/issues/11890) -* **compiler-dom:** should ignore leading newline in ") +const t3 = _template("") +const t4 = _template("") +const t5 = _template("") +const t6 = _template("") + +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("
", 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("
", 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("
", 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("
", 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("
", 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("
", 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("
", 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 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap new file mode 100644 index 00000000000..f73bb18b9c6 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -0,0 +1,249 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`compiler: v-for > array de-structured value (with rest) 1`] = ` +"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("
", true) + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => { + const n2 = t0() + const x2 = _txt(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 { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("
", true) + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => { + const n2 = t0() + const x2 = _txt(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 { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, delegateEvents as _delegateEvents, template as _template } from 'vue'; +const t0 = _template("
", true) +_delegateEvents("click") + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.items), (_for_item0) => { + const n2 = t0() + const x2 = _txt(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 > key only binding pattern 1`] = ` +"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template(" ", true) + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.rows), (_for_item0) => { + const n2 = t0() + const x2 = _txt(n2) + _setText(x2, _toDisplayString(_for_item0.value.id + _for_item0.value.id)) + return n2 + }, (row) => (row.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("
", 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, txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template(" ") +const t1 = _template("
", 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 = _txt(n4) + _renderEffect(() => _setText(x4, _toDisplayString(_for_item1.value+_for_item0.value))) + return n4 + }, undefined, 1) + return n5 + }) + return n0 +}" +`; + +exports[`compiler: v-for > object de-structured value (with rest) 1`] = ` +"import { getRestElement as _getRestElement, txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("
", true) + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => { + const n2 = t0() + const x2 = _txt(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 { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template(" ", true) + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.items), (_for_item0) => { + const n2 = t0() + const x2 = _txt(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 { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("
", true) + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0, _for_index0) => { + const n2 = t0() + const x2 = _txt(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 > selector pattern 1`] = ` +"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template(" ", true) + +export function render(_ctx) { + let _selector0_0 + const n0 = _createFor(() => (_ctx.rows), (_for_item0) => { + const n2 = t0() + const x2 = _txt(n2) + _selector0_0(() => { + _setText(x2, _toDisplayString(_ctx.selected === _for_item0.value.id ? 'danger' : '')) + }) + return n2 + }, (row) => (row.id), undefined, ({ createSelector }) => { + _selector0_0 = createSelector(() => _ctx.selected) + }) + return n0 +}" +`; + +exports[`compiler: v-for > selector pattern 2`] = ` +"import { setClass as _setClass, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("", true) + +export function render(_ctx) { + let _selector0_0 + const n0 = _createFor(() => (_ctx.rows), (_for_item0) => { + const n2 = t0() + _selector0_0(() => { + _setClass(n2, _ctx.selected === _for_item0.value.id ? 'danger' : '') + }) + return n2 + }, (row) => (row.id), undefined, ({ createSelector }) => { + _selector0_0 = createSelector(() => _ctx.selected) + }) + return n0 +}" +`; + +exports[`compiler: v-for > selector pattern 3`] = ` +"import { setClass as _setClass, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("", true) + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.rows), (_for_item0) => { + const n2 = t0() + _renderEffect(() => { + const _row = _for_item0.value + _setClass(n2, _row.label === _row.id ? 'danger' : '') + }) + return n2 + }, (row) => (row.id)) + return n0 +}" +`; + +exports[`compiler: v-for > selector pattern 4`] = ` +"import { setClass as _setClass, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("", true) + +export function render(_ctx) { + let _selector0_0 + const n0 = _createFor(() => (_ctx.rows), (_for_item0) => { + const n2 = t0() + _selector0_0(() => { + _setClass(n2, { danger: _for_item0.value.id === _ctx.selected }) + }) + return n2 + }, (row) => (row.id), undefined, ({ createSelector }) => { + _selector0_0 = createSelector(() => _ctx.selected) + }) + return n0 +}" +`; + +exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = ` +"import { getDefaultValue as _getDefaultValue, txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("
", true) + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.list), (_for_item0) => { + const n2 = t0() + const x2 = _txt(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("
item
", true) + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.items), (_for_item0) => { + const n2 = t0() + return n2 + }) + return n0 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap new file mode 100644 index 00000000000..f78e395a75a --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap @@ -0,0 +1,44 @@ +// 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("
", 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("
", 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("
", true) + +export function render(_ctx) { + const n0 = t0() + _setHtml(n0, "") + return n0 +}" +`; + +exports[`v-html > work with dynamic component 1`] = ` +"import { createDynamicComponent as _createDynamicComponent, setHtml as _setHtml, renderEffect as _renderEffect } from 'vue'; + +export function render(_ctx) { + const n0 = _createDynamicComponent(() => ('button'), null, null, true) + _renderEffect(() => _setHtml(n0.nodes, _ctx.foo)) + return n0 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap new file mode 100644 index 00000000000..c60ae16be05 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap @@ -0,0 +1,162 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`compiler: v-if > basic v-if 1`] = ` +"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createIf as _createIf, template as _template } from 'vue'; +const t0 = _template("
", true) + +export function render(_ctx) { + const n0 = _createIf(() => (_ctx.ok), () => { + const n2 = t0() + const x2 = _txt(n2) + _renderEffect(() => _setText(x2, _toDisplayString(_ctx.msg))) + return n2 + }) + return n0 +}" +`; + +exports[`compiler: v-if > comment between branches 1`] = ` +"import { createIf as _createIf, txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; +const t0 = _template("
") +const t1 = _template("") +const t2 = _template("

") +const t3 = _template("") +const t4 = _template("fine") +const t5 = _template("
") + +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 = _txt(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("
hello
") + +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 { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createIf as _createIf, template as _template } from 'vue'; +const t0 = _template("
") +const t1 = _template("hello") +const t2 = _template("

", true) + +export function render(_ctx) { + const n0 = _createIf(() => (_ctx.ok), () => { + const n2 = t0() + const n3 = t1() + const n4 = t2() + const x4 = _txt(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, null) + const n0 = _createIf(() => (_ctx.foo), () => { + const n2 = t0() + return n2 + }) + _setInsertionState(n8, null) + 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..e7a2b30e69c --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap @@ -0,0 +1,487 @@ +// 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 > component event with special characters 1`] = ` +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _component_Foo = _resolveComponent("Foo") + const _on_update_model = () => {} + const _on_update_model1 = () => {} + const n0 = _createComponentWithFallback(_component_Foo, { + "onUpdate:model": () => _on_update_model, + "onUpdate-model": () => _on_update_model1 + }, null, true) + 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..b6107d5a1a1 --- /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("
", 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("
", 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("
", 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("
", 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("
", true) + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.list), (_for_item0) => { + const n2 = t0() + return n2 + }, undefined, 4) + return n0 +}" +`; + +exports[`compiler: v-once > with v-if 1`] = ` +"import { createIf as _createIf, template as _template } from 'vue'; +const t0 = _template("
", 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("
") +const t1 = _template("

") + +export function render(_ctx) { + const n0 = _createIf(() => (_ctx.expr), () => { + const n2 = t0() + return n2 + }, () => { + const n4 = t1() + return n4 + }, true) + return n0 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vShow.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vShow.spec.ts.snap new file mode 100644 index 00000000000..f595da5ef8f --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vShow.spec.ts.snap @@ -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("
", true) + +export function render(_ctx) { + const n0 = t0() + _applyVShow(n0, () => (_ctx.foo)) + return n0 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap new file mode 100644 index 00000000000..9b11f074c40 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -0,0 +1,432 @@ +// 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 > forwarded slots > 1`] = ` +"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _createForwardedSlot = _forwardedSlotCreator() + const _component_Comp = _resolveComponent("Comp") + const n2 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n1 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createForwardedSlot("default", null) + return n0 + } + }) + return n1 + } + }, true) + return n2 +}" +`; + +exports[`compiler: transform slot > forwarded slots > tag only 1`] = ` +"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _createForwardedSlot = _forwardedSlotCreator() + const _component_Comp = _resolveComponent("Comp") + const n1 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createForwardedSlot("default", null) + return n0 + } + }, true) + return n1 +}" +`; + +exports[`compiler: transform slot > forwarded slots > tag w/ template 1`] = ` +"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _createForwardedSlot = _forwardedSlotCreator() + const _component_Comp = _resolveComponent("Comp") + const n2 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createForwardedSlot("default", null) + return n0 + } + }, true) + return n2 +}" +`; + +exports[`compiler: transform slot > forwarded slots > tag w/ v-for 1`] = ` +"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createFor as _createFor, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _createForwardedSlot = _forwardedSlotCreator() + const _component_Comp = _resolveComponent("Comp") + const n3 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createFor(() => (_ctx.b), (_for_item0) => { + const n2 = _createForwardedSlot("default", null) + return n2 + }) + return n0 + } + }, true) + return n3 +}" +`; + +exports[`compiler: transform slot > forwarded slots > tag w/ v-if 1`] = ` +"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createIf as _createIf, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _createForwardedSlot = _forwardedSlotCreator() + const _component_Comp = _resolveComponent("Comp") + const n3 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createIf(() => (_ctx.ok), () => { + const n2 = _createForwardedSlot("default", null) + return n2 + }) + return n0 + } + }, true) + return n3 +}" +`; + +exports[`compiler: transform slot > implicit default slot 1`] = ` +"import { resolveComponent as _resolveComponent, 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": () => { + 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("") + +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("
", true) + +export function render(_ctx) { + const _component_Foo = _resolveComponent("Foo") + const _component_Bar = _resolveComponent("Bar") + const n6 = t0() + _setInsertionState(n6, null) + const n0 = _createSlot("foo", null) + _setInsertionState(n6, null) + 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("

") + +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 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap new file mode 100644 index 00000000000..cf5d79a186e --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap @@ -0,0 +1,45 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`v-text > should convert v-text to setText 1`] = ` +"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; +const t0 = _template("
", true) + +export function render(_ctx, $props, $emit, $attrs, $slots) { + const n0 = t0() + const x0 = _txt(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 { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue'; +const t0 = _template("
", true) + +export function render(_ctx) { + const n0 = t0() + const x0 = _txt(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("
", true) + +export function render(_ctx) { + const n0 = t0() + return n0 +}" +`; + +exports[`v-text > work with dynamic component 1`] = ` +"import { createDynamicComponent as _createDynamicComponent, toDisplayString as _toDisplayString, setElementText as _setElementText, renderEffect as _renderEffect } from 'vue'; + +export function render(_ctx) { + const n0 = _createDynamicComponent(() => ('button'), null, null, true) + _renderEffect(() => _setElementText(n0.nodes, _toDisplayString(_ctx.foo), true)) + return n0 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/_utils.ts b/packages/compiler-vapor/__tests__/transforms/_utils.ts new file mode 100644 index 00000000000..1b6e3f18ab1 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/_utils.ts @@ -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 + } => { + 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 } + } +} diff --git a/packages/compiler-vapor/__tests__/transforms/expression.spec.ts b/packages/compiler-vapor/__tests__/transforms/expression.spec.ts new file mode 100644 index 00000000000..9257a714b13 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/expression.spec.ts @@ -0,0 +1,71 @@ +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(` +
+ {{ String(foo.id++) }} {{ foo }} {{ bar }} +
+ `) + expect(code).toMatchSnapshot() + expect(code).contains(`_String(_foo.id++)`) + }) + + test('empty interpolation', () => { + const { code } = compileWithExpression(`{{}}`) + const { code: code2 } = compileWithExpression(`{{ }}`) + const { code: code3 } = compileWithExpression(`
{{ }}
`) + const { code: code4 } = compileWithExpression(`
{{ foo }}{{ }}
`) + + expect(code).toMatchSnapshot() + expect(code).not.toContain(`_toDisplayString`) + expect(code).not.toContain(`_setText`) + + expect(code2).toMatchSnapshot() + expect(code2).not.toContain(`_toDisplayString`) + expect(code2).not.toContain(`_setText`) + + expect(code3).toMatchSnapshot() + expect(code3).not.toContain(`_toDisplayString`) + expect(code3).not.toContain(`_setText`) + + expect(code4).toMatchSnapshot() + }) +}) diff --git a/packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts new file mode 100644 index 00000000000..2d8ae8c960d --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts @@ -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( + `
+

{{ first }}

+ {{ second }} + {{ third }} +

{{ forth }}

+
`, + ) + expect(code).toMatchSnapshot() + expect(Array.from(helpers)).containSubset([ + 'child', + 'toDisplayString', + 'renderEffect', + 'next', + 'setText', + 'template', + ]) + }) + + test('efficient traversal', () => { + const { code } = compileWithElementTransform( + `
+
x
+
{{ msg }}
+
{{ msg }}
+
{{ msg }}
+
`, + ) + expect(code).toMatchSnapshot() + }) + + test('efficient find', () => { + const { code } = compileWithElementTransform( + `
+
x
+
x
+
{{ msg }}
+
`, + ) + expect(code).contains(`const n0 = _nthChild(n1, 2)`) + expect(code).toMatchSnapshot() + }) + + test('anchor insertion in middle', () => { + const { code } = compileWithElementTransform( + `
+
+
+
+
`, + ) + // 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() + }) +}) diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts new file mode 100644 index 00000000000..a693db4ad39 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts @@ -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(``) + 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(``, { + 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(``, { + 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(``, { + 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(``, { + 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(``, { + 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(``, { + 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(``, { + 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(``, { + 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(``, { + 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(``, { + 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(`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( + ``, + { + bindingMetadata: { Comp: BindingTypes.SETUP_CONST }, + }, + ) + expect(code).toMatchSnapshot() + expect(code).contains('_createComponent(_ctx.Comp)') + }) + + test('static props', () => { + const { code, ir } = compileWithElementTransform( + ``, + ) + + 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(``) + 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( + ``, + ) + 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( + ``, + ) + 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( + ``, + ) + 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( + ``, + ) + 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( + ``, + ) + expect(code).toMatchSnapshot() + }) + + test.todo('props merging: class', () => { + const { code } = compileWithElementTransform( + ``, + ) + expect(code).toMatchSnapshot() + }) + + test('v-on="obj"', () => { + const { code, ir } = compileWithElementTransform(``) + 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( + ``, + ) + 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( + ``, + ) + 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( + ``, + ) + 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( + ``, + ) + 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( + ``, + ) + 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( + ``, + ) + 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(``) + 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( + ``, + { + 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( + `
`, + ) + + const template = '
' + 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( + `
`, + ) + + const template = '
' + 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(`
`) + 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( + `
`, + ) + 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( + `
`, + ) + 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( + `
`, + ) + 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( + `
`, + ) + 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( + `
`, + ) + 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( + `
`, + ) + + 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(`
`) + 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( + ``, + ) + 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( + ``, + ) + 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(``) + expect(code).toMatchSnapshot() + }) + + test('component dynamic event with once modifier', () => { + const { code } = compileWithElementTransform(``) + expect(code).toMatchSnapshot() + }) + + test('invalid html nesting', () => { + const { code, ir } = compileWithElementTransform( + `

123

+
`, + ) + expect(code).toMatchSnapshot() + expect(ir.template).toEqual(['
123
', '

', '
']) + 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') + }) +}) diff --git a/packages/compiler-vapor/__tests__/transforms/transformSlotOutlet.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformSlotOutlet.spec.ts new file mode 100644 index 00000000000..389c665a12f --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/transformSlotOutlet.spec.ts @@ -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 outlets', () => { + test('default slot outlet', () => { + const { ir, code, helpers } = compileWithSlotsOutlet(``) + 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(``) + 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(``) + 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(``) + 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( + ``, + ) + 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( + ``, + ) + 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( + ``, + ) + 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( + ``, + ) + 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(`
`) + expect(code).toMatchSnapshot() + expect(ir.template[0]).toBe('
') + 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( + `
`, + ) + expect(code).toMatchSnapshot() + expect(ir.template[0]).toBe('
') + 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( + `
`, + ) + expect(code).toMatchSnapshot() + expect(ir.template[0]).toBe('
') + 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( + `
`, + ) + expect(code).toMatchSnapshot() + expect(ir.template[0]).toBe('
') + 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 ', () => { + const onError = vi.fn() + const source = `` + 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 ', () => { + const onError = vi.fn() + const source = `` + 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, + }, + }, + }) + }) +}) diff --git a/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts new file mode 100644 index 00000000000..2c883d10cc6 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts @@ -0,0 +1,183 @@ +import { BindingTypes } from '@vue/compiler-dom' +import { + DynamicFlag, + type ForIRNode, + IRNodeTypes, + type IfIRNode, + transformChildren, + transformElement, + transformTemplateRef, + transformVFor, + transformVIf, +} from '../../src' +import { makeCompile } from './_utils' + +const compileWithTransformRef = makeCompile({ + nodeTransforms: [ + transformVIf, + transformVFor, + transformTemplateRef, + transformElement, + transformChildren, + ], +}) + +describe('compiler: template ref transform', () => { + test('static ref', () => { + const { ir, code } = compileWithTransformRef(`
`) + + expect(ir.block.dynamic.children[0]).toMatchObject({ + id: 0, + flags: DynamicFlag.REFERENCED, + }) + expect(ir.template).toEqual(['
']) + expect(ir.block.operation).lengthOf(1) + expect(ir.block.operation[0]).toMatchObject({ + type: IRNodeTypes.SET_TEMPLATE_REF, + element: 0, + value: { + content: 'foo', + isStatic: true, + loc: { + start: { line: 1, column: 10, offset: 9 }, + end: { line: 1, column: 15, offset: 14 }, + }, + }, + }) + expect(code).matchSnapshot() + expect(code).contains('const _setTemplateRef = _createTemplateRefSetter()') + expect(code).contains('_setTemplateRef(n0, "foo")') + }) + + test('static ref (inline mode)', () => { + const { code } = compileWithTransformRef(`
`, { + inline: true, + bindingMetadata: { foo: BindingTypes.SETUP_REF }, + }) + expect(code).matchSnapshot() + // pass the actual ref + expect(code).contains('_setTemplateRef(n0, foo)') + }) + + test('dynamic ref', () => { + const { ir, code } = compileWithTransformRef(`
`) + + expect(ir.block.dynamic.children[0]).toMatchObject({ + id: 0, + flags: DynamicFlag.REFERENCED, + }) + expect(ir.template).toEqual(['
']) + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.DECLARE_OLD_REF, + id: 0, + }, + ]) + expect(ir.block.effect).toMatchObject([ + { + operations: [ + { + type: IRNodeTypes.SET_TEMPLATE_REF, + element: 0, + value: { + content: 'foo', + isStatic: false, + }, + }, + ], + }, + ]) + expect(code).matchSnapshot() + expect(code).contains('const _setTemplateRef = _createTemplateRefSetter()') + expect(code).contains('_setTemplateRef(n0, _ctx.foo, r0)') + }) + + test('function ref', () => { + const { ir, code } = compileWithTransformRef( + `
`, + ) + expect(ir.block.dynamic.children[0]).toMatchObject({ + id: 0, + flags: DynamicFlag.REFERENCED, + }) + expect(ir.template).toEqual(['
']) + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.DECLARE_OLD_REF, + id: 0, + }, + ]) + expect(ir.block.effect).toMatchObject([ + { + operations: [ + { + type: IRNodeTypes.SET_TEMPLATE_REF, + element: 0, + value: { + isStatic: false, + }, + }, + ], + }, + ]) + expect(code).toMatchSnapshot() + expect(code).contains('const _setTemplateRef = _createTemplateRefSetter()') + expect(code).contains(`_setTemplateRef(n0, bar => { + _foo.value = bar + ;({ baz: _ctx.baz } = bar) + console.log(_foo.value, _ctx.baz) + }, r0)`) + }) + + test('ref + v-if', () => { + const { ir, code } = compileWithTransformRef( + `
`, + ) + + const op = ir.block.dynamic.children[0].operation as IfIRNode + expect(op.type).toBe(IRNodeTypes.IF) + + const { positive } = op + expect(positive.operation).toMatchObject([ + { + type: IRNodeTypes.SET_TEMPLATE_REF, + element: 2, + value: { + content: 'foo', + isStatic: true, + }, + effect: false, + }, + ]) + expect(code).matchSnapshot() + expect(code).contains('const _setTemplateRef = _createTemplateRefSetter()') + expect(code).contains('_setTemplateRef(n2, "foo")') + }) + + test('ref + v-for', () => { + const { ir, code } = compileWithTransformRef( + `
`, + ) + + const { render } = ir.block.dynamic.children[0].operation as ForIRNode + expect(render.operation).toMatchObject([ + { + type: IRNodeTypes.SET_TEMPLATE_REF, + element: 2, + value: { + content: 'foo', + isStatic: true, + }, + refFor: true, + effect: false, + }, + ]) + expect(code).matchSnapshot() + expect(code).contains('const _setTemplateRef = _createTemplateRefSetter()') + expect(code).contains('_setTemplateRef(n2, "foo", void 0, true)') + }) +}) diff --git a/packages/compiler-vapor/__tests__/transforms/transformText.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformText.spec.ts new file mode 100644 index 00000000000..20fa6d1fd00 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/transformText.spec.ts @@ -0,0 +1,51 @@ +// TODO: add tests for this transform +import { NodeTypes } from '@vue/compiler-dom' +import { + IRNodeTypes, + transformChildren, + transformElement, + transformText, + transformVBind, + transformVOn, +} from '../../src' + +import { makeCompile } from './_utils' + +const compileWithTextTransform = makeCompile({ + nodeTransforms: [transformElement, transformChildren, transformText], + directiveTransforms: { + bind: transformVBind, + on: transformVOn, + }, +}) + +describe('compiler: text transform', () => { + it('no consecutive text', () => { + const { code, ir, helpers } = compileWithTextTransform( + '{{ "hello world" }}', + ) + expect(code).toMatchSnapshot() + expect(helpers).contains.all.keys('setText', 'template') + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.SET_TEXT, + element: 0, + values: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: '"hello world"', + isStatic: false, + }, + ], + }, + ]) + }) + + it('consecutive text', () => { + const { code, ir, helpers } = compileWithTextTransform('{{ msg }}') + expect(code).toMatchSnapshot() + expect(helpers).contains.all.keys('setText', 'template') + expect(ir.block.operation).toMatchObject([]) + expect(ir.block.effect.length).toBe(1) + }) +}) diff --git a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts new file mode 100644 index 00000000000..e96186c275c --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts @@ -0,0 +1,846 @@ +import { BindingTypes, ErrorCodes, NodeTypes } from '@vue/compiler-dom' +import { + DynamicFlag, + IRNodeTypes, + transformChildren, + transformElement, + transformVBind, +} from '../../src' +import { makeCompile } from './_utils' + +const compileWithVBind = makeCompile({ + nodeTransforms: [transformElement, transformChildren], + directiveTransforms: { + bind: transformVBind, + }, +}) + +describe('compiler v-bind', () => { + test('basic', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(ir.block.dynamic.children[0]).toMatchObject({ + id: 0, + flags: DynamicFlag.REFERENCED, + }) + expect(ir.template).toEqual(['
']) + expect(ir.block.effect).lengthOf(1) + expect(ir.block.effect[0].expressions).lengthOf(1) + expect(ir.block.effect[0].operations).lengthOf(1) + expect(ir.block.effect[0]).toMatchObject({ + expressions: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'id', + isStatic: false, + }, + ], + operations: [ + { + type: IRNodeTypes.SET_PROP, + element: 0, + prop: { + key: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'id', + isStatic: true, + loc: { + start: { line: 1, column: 13, offset: 12 }, + end: { line: 1, column: 15, offset: 14 }, + source: 'id', + }, + }, + values: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'id', + isStatic: false, + loc: { + source: 'id', + start: { line: 1, column: 17, offset: 16 }, + end: { line: 1, column: 19, offset: 18 }, + }, + }, + ], + loc: { + start: { column: 6, line: 1, offset: 5 }, + end: { column: 20, line: 1, offset: 19 }, + source: 'v-bind:id="id"', + }, + runtimeCamelize: false, + }, + }, + ], + }) + + expect(code).matchSnapshot() + expect(code).contains('_setProp(n0, "id", _ctx.id') + }) + + test('no expression', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + type: IRNodeTypes.SET_PROP, + prop: { + key: { + content: `id`, + isStatic: true, + loc: { + start: { line: 1, column: 13, offset: 12 }, + end: { line: 1, column: 15, offset: 14 }, + }, + }, + values: [ + { + content: `id`, + isStatic: false, + loc: { + start: { line: 1, column: 13, offset: 12 }, + end: { line: 1, column: 15, offset: 14 }, + }, + }, + ], + }, + }) + expect(code).contains('_setProp(n0, "id", _ctx.id)') + }) + + test('no expression (shorthand)', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + type: IRNodeTypes.SET_PROP, + prop: { + key: { + content: `camel-case`, + isStatic: true, + }, + values: [ + { + content: `camelCase`, + isStatic: false, + }, + ], + }, + }) + expect(code).contains('_setAttr(n0, "camel-case", _ctx.camelCase)') + }) + + test('dynamic arg', () => { + const { ir, code } = compileWithVBind( + `
`, + ) + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + type: IRNodeTypes.SET_DYNAMIC_PROPS, + element: 0, + props: [ + [ + { + key: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'id', + isStatic: false, + }, + values: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'id', + isStatic: false, + }, + ], + }, + { + key: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'title', + isStatic: false, + }, + values: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'title', + isStatic: false, + }, + ], + }, + ], + ], + }) + expect(code).contains( + '_setDynamicProps(n0, [{ [_id]: _id, [_title]: _title }], true)', + ) + }) + + test('dynamic arg w/ static attribute', () => { + const { ir, code } = compileWithVBind( + `
`, + ) + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + type: IRNodeTypes.SET_DYNAMIC_PROPS, + element: 0, + props: [ + [ + { + key: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'id', + isStatic: false, + }, + values: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'id', + isStatic: false, + }, + ], + }, + { + key: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'foo', + isStatic: true, + }, + values: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'bar', + isStatic: true, + }, + ], + }, + { + key: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'checked', + isStatic: true, + }, + }, + ], + ], + }) + expect(code).contains( + '_setDynamicProps(n0, [{ [_id]: _id, foo: "bar", checked: "" }], true)', + ) + }) + + test('should error if empty expression', () => { + const onError = vi.fn() + const { ir, code } = compileWithVBind(`
`, { + onError, + }) + + expect(onError.mock.calls[0][0]).toMatchObject({ + code: ErrorCodes.X_V_BIND_NO_EXPRESSION, + loc: { + start: { line: 1, column: 6 }, + end: { line: 1, column: 19 }, + }, + }) + expect(ir.template).toEqual(['
']) + + expect(code).matchSnapshot() + expect(code).contains(JSON.stringify('
')) + }) + + test('error on invalid argument for same-name shorthand', () => { + const onError = vi.fn() + compileWithVBind(`
`, { onError }) + expect(onError.mock.calls[0][0]).toMatchObject({ + code: ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT, + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 18, + }, + }, + }) + }) + + test('.camel modifier', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(ir.block.effect[0].operations[0]).toMatchObject({ + prop: { + key: { + content: `fooBar`, + isStatic: true, + }, + values: [ + { + content: `id`, + isStatic: false, + }, + ], + runtimeCamelize: false, + modifier: undefined, + }, + }) + + expect(code).matchSnapshot() + expect(code).contains('_setProp(n0, "fooBar", _ctx.id)') + }) + + test('.camel modifier w/ no expression', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + prop: { + key: { + content: `fooBar`, + isStatic: true, + }, + values: [ + { + content: `fooBar`, + isStatic: false, + }, + ], + runtimeCamelize: false, + modifier: undefined, + }, + }) + expect(code).contains('renderEffect') + expect(code).contains('_setProp(n0, "fooBar", _ctx.fooBar)') + }) + + test('.camel modifier w/ dynamic arg', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(ir.block.effect[0].operations[0]).toMatchObject({ + type: IRNodeTypes.SET_DYNAMIC_PROPS, + props: [ + [ + { + key: { + content: `foo`, + isStatic: false, + }, + values: [ + { + content: `id`, + isStatic: false, + }, + ], + runtimeCamelize: true, + modifier: undefined, + }, + ], + ], + }) + + expect(code).matchSnapshot() + expect(code).contains('renderEffect') + expect(code).contains( + `_setDynamicProps(n0, [{ [_camelize(_ctx.foo)]: _ctx.id }], true)`, + ) + }) + + test.todo('.camel modifier w/ dynamic arg + prefixIdentifiers') + + test('.prop modifier', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + prop: { + key: { + content: `fooBar`, + isStatic: true, + }, + values: [ + { + content: `id`, + isStatic: false, + }, + ], + runtimeCamelize: false, + modifier: '.', + }, + }) + expect(code).contains('renderEffect') + expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.id)') + }) + + test('.prop modifier w/ no expression', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + prop: { + key: { + content: `fooBar`, + isStatic: true, + }, + values: [ + { + content: `fooBar`, + isStatic: false, + }, + ], + runtimeCamelize: false, + modifier: '.', + }, + }) + expect(code).contains('renderEffect') + expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar)') + }) + + test('.prop modifier w/ dynamic arg', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + type: IRNodeTypes.SET_DYNAMIC_PROPS, + props: [ + [ + { + key: { + content: `fooBar`, + isStatic: false, + }, + values: [ + { + content: `id`, + isStatic: false, + }, + ], + runtimeCamelize: false, + modifier: '.', + }, + ], + ], + }) + expect(code).contains('renderEffect') + expect(code).contains( + `_setDynamicProps(n0, [{ ["." + _ctx.fooBar]: _ctx.id }], true)`, + ) + }) + + test.todo('.prop modifier w/ dynamic arg + prefixIdentifiers') + + test('.prop modifier (shorthand)', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + prop: { + key: { + content: `fooBar`, + isStatic: true, + }, + values: [ + { + content: `id`, + isStatic: false, + }, + ], + runtimeCamelize: false, + modifier: '.', + }, + }) + expect(code).contains('renderEffect') + expect(code).contains(' _setDOMProp(n0, "fooBar", _ctx.id)') + }) + + test('.prop modifier (shorthand) w/ no expression', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + prop: { + key: { + content: `fooBar`, + isStatic: true, + }, + values: [ + { + content: `fooBar`, + isStatic: false, + }, + ], + runtimeCamelize: false, + modifier: '.', + }, + }) + expect(code).contains('renderEffect') + expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar)') + }) + + test('.prop modifier w/ innerHTML', () => { + const { code } = compileWithVBind(`
`) + expect(code).matchSnapshot() + expect(code).contains('_setHtml(n0, _ctx.foo)') + }) + + test('.prop modifier (shorthand) w/ innerHTML', () => { + const { code } = compileWithVBind(`
`) + expect(code).matchSnapshot() + expect(code).contains('_setHtml(n0, _ctx.foo)') + }) + + test('.prop modifier w/ textContent', () => { + const { code } = compileWithVBind(`
`) + expect(code).matchSnapshot() + expect(code).contains('_setText(n0, _ctx.foo)') + }) + + test('.prop modifier (shorthand) w/ textContent', () => { + const { code } = compileWithVBind(`
`) + expect(code).matchSnapshot() + expect(code).contains('_setText(n0, _ctx.foo)') + }) + + test('.prop modifier w/ value', () => { + const { code } = compileWithVBind(`
`) + expect(code).matchSnapshot() + expect(code).contains('_setValue(n0, _ctx.foo)') + }) + + test('.prop modifier (shorthand) w/ value', () => { + const { code } = compileWithVBind(`
`) + expect(code).matchSnapshot() + expect(code).contains('_setValue(n0, _ctx.foo)') + }) + + test('.prop modifier w/ progress value', () => { + const { code } = compileWithVBind(``) + expect(code).matchSnapshot() + expect(code).contains('_setDOMProp(n0, "value", _ctx.foo)') + }) + + test('.prop modifier (shorthand) w/ progress value', () => { + const { code } = compileWithVBind(``) + expect(code).matchSnapshot() + expect(code).contains('_setDOMProp(n0, "value", _ctx.foo)') + }) + + test('.attr modifier', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + prop: { + key: { + content: `foo-bar`, + isStatic: true, + }, + values: [ + { + content: `id`, + isStatic: false, + }, + ], + runtimeCamelize: false, + modifier: '^', + }, + }) + expect(code).contains('renderEffect') + expect(code).contains('_setAttr(n0, "foo-bar", _ctx.id)') + }) + + test('.attr modifier w/ no expression', () => { + const { ir, code } = compileWithVBind(`
`) + + expect(code).matchSnapshot() + expect(ir.block.effect[0].operations[0]).toMatchObject({ + prop: { + key: { + content: `foo-bar`, + isStatic: true, + }, + values: [ + { + content: `fooBar`, + isStatic: false, + }, + ], + runtimeCamelize: false, + modifier: '^', + }, + }) + + expect(code).contains('renderEffect') + expect(code).contains('_setAttr(n0, "foo-bar", _ctx.fooBar)') + }) + + test('.attr modifier w/ innerHTML', () => { + const { code } = compileWithVBind(`
`) + expect(code).matchSnapshot() + expect(code).contains('_setAttr(n0, "innerHTML", _ctx.foo)') + }) + + test('.attr modifier w/ textContent', () => { + const { code } = compileWithVBind(`
`) + expect(code).matchSnapshot() + expect(code).contains('_setAttr(n0, "textContent", _ctx.foo)') + }) + + test('.attr modifier w/ value', () => { + const { code } = compileWithVBind(`
`) + expect(code).matchSnapshot() + expect(code).contains('_setAttr(n0, "value", _ctx.foo)') + }) + + test('.attr modifier w/ progress value', () => { + const { code } = compileWithVBind(``) + expect(code).matchSnapshot() + expect(code).contains('_setAttr(n0, "value", _ctx.foo)') + }) + + test('attributes must be set as attribute', () => { + const { code } = compileWithVBind(` +
+ +