diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index 4c667f90216..00000000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,69 +0,0 @@
-const DOMGlobals = ['window', 'document']
-const NodeGlobals = ['module', 'require']
-
-module.exports = {
- parser: '@typescript-eslint/parser',
- parserOptions: {
- sourceType: 'module'
- },
- rules: {
- 'no-unused-vars': [
- 'error',
- // we are only using this rule to check for unused arguments since TS
- // catches unused variables but not args.
- { varsIgnorePattern: '.*', args: 'none' }
- ],
- // most of the codebase are expected to be env agnostic
- 'no-restricted-globals': ['error', ...DOMGlobals, ...NodeGlobals],
- // since we target ES2015 for baseline support, we need to forbid object
- // rest spread usage (both assign and destructure)
- 'no-restricted-syntax': [
- 'error',
- 'ObjectExpression > SpreadElement',
- 'ObjectPattern > RestElement',
- 'AwaitExpression'
- ]
- },
- overrides: [
- // tests, no restrictions (runs in Node / jest with jsdom)
- {
- files: ['**/__tests__/**', 'test-dts/**'],
- rules: {
- 'no-restricted-globals': 'off',
- 'no-restricted-syntax': 'off'
- }
- },
- // shared, may be used in any env
- {
- files: ['packages/shared/**'],
- rules: {
- 'no-restricted-globals': 'off'
- }
- },
- // Packages targeting DOM
- {
- files: ['packages/{vue,vue-compat,runtime-dom}/**'],
- rules: {
- 'no-restricted-globals': ['error', ...NodeGlobals]
- }
- },
- // Packages targeting Node
- {
- files: [
- 'packages/{compiler-sfc,compiler-ssr,server-renderer,reactivity-transform}/**'
- ],
- rules: {
- 'no-restricted-globals': ['error', ...DOMGlobals],
- 'no-restricted-syntax': 'off'
- }
- },
- // Private package, browser only + no syntax restrictions
- {
- files: ['packages/template-explorer/**', 'packages/sfc-playground/**'],
- rules: {
- 'no-restricted-globals': ['error', ...NodeGlobals],
- 'no-restricted-syntax': 'off'
- }
- }
- ]
-}
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 00000000000..d06b03a3f89
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,2 @@
+# update prettier & eslint config (#9162)
+bfe6b459d3a0ce6168611ee1ac7e6e789709df9d
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 00000000000..95e0ca79c07
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,74 @@
+name: "\U0001F41E Bug report"
+description: Create a report to help us improve
+body:
+ - type: markdown
+ attributes:
+ value: |
+ **Before You Start...**
+
+ This form is only for submitting bug reports. If you have a usage question
+ or are unsure if this is really a bug, make sure to:
+
+ - Read the [docs](https://vuejs.org/)
+ - Ask on [Discord Chat](https://chat.vuejs.org/)
+ - Ask on [GitHub Discussions](https://github.com/vuejs/core/discussions)
+ - Look for / ask questions on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=vue.js)
+
+ Also try to search for your issue - it may have already been answered or even fixed in the development branch.
+ However, if you find that an old, closed issue still persists in the latest version,
+ you should open a new issue using the form below instead of commenting on the old issue.
+ - type: input
+ id: version
+ attributes:
+ label: Vue version
+ validations:
+ required: true
+ - type: input
+ id: reproduction-link
+ attributes:
+ label: Link to minimal reproduction
+ description: |
+ The easiest way to provide a reproduction is by showing the bug in [The SFC Playground](https://play.vuejs.org/).
+ If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://vite.new/vue).
+ If neither of these are suitable, you can always provide a GitHub repository.
+
+ The reproduction should be **minimal** - i.e. it should contain only the bare minimum amount of code needed
+ to show the bug. See [Bug Reproduction Guidelines](https://github.com/vuejs/core/blob/main/.github/bug-repro-guidelines.md) for more details.
+
+ Please do not just fill in a random link. The issue will be closed if no valid reproduction is provided.
+ placeholder: Reproduction Link
+ validations:
+ required: true
+ - type: textarea
+ id: steps-to-reproduce
+ attributes:
+ label: Steps to reproduce
+ description: |
+ What do we need to do after opening your repro in order to make the bug happen? Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code.
+ placeholder: Steps to reproduce
+ validations:
+ required: true
+ - type: textarea
+ id: expected
+ attributes:
+ label: What is expected?
+ validations:
+ required: true
+ - type: textarea
+ id: actually-happening
+ attributes:
+ label: What is actually happening?
+ validations:
+ required: true
+ - type: textarea
+ id: system-info
+ attributes:
+ label: System Info
+ description: Output of `npx envinfo --system --npmPackages vue --binaries --browsers`
+ render: shell
+ placeholder: System, Binaries, Browsers
+ - type: textarea
+ id: additional-comments
+ attributes:
+ label: Any additional comments?
+ description: e.g. some background/context of how you ran into this bug.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index d331b5312a9..02f99c6bfbb 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,8 +1,14 @@
blank_issues_enabled: false
contact_links:
- - name: Create new issue
- url: https://new-issue.vuejs.org/?repo=vuejs/core
- about: Please use the following link to create a new issue.
+ - name: Feature Request
+ url: https://github.com/vuejs/rfcs/discussions
+ about: Suggest new features for consideration
+ - name: Discord Chat
+ url: https://chat.vuejs.org
+ about: Ask questions and discuss with other Vue users in real time.
+ - name: Questions & Discussions
+ url: https://github.com/vuejs/core/discussions
+ about: Use GitHub discussions for message-board style questions and discussions.
- name: Patreon
url: https://www.patreon.com/evanyou
about: Love Vue.js? Please consider supporting us via Patreon.
diff --git a/.github/bug-repro-guidelines.md b/.github/bug-repro-guidelines.md
new file mode 100644
index 00000000000..90458b30741
--- /dev/null
+++ b/.github/bug-repro-guidelines.md
@@ -0,0 +1,29 @@
+## About Bug Reproductions
+
+A bug reproduction is a piece of code that can run and demonstrate how a bug can happen.
+
+### Text is not enough
+
+It's impossible to fix a bug from mere text descriptions. First, it's very difficult to precisely describe a technical problem while keeping it easy to follow; Second, the real cause may very well be something that you forgot to even mention. A reproduction is the only way that can reliably help us understand what is going on, so please provide one.
+
+### A repro must be runnable
+
+Screenshots or videos are NOT reproductions! They only show that the bug exists, but do not provide enough information on why it happens. Only runnable code provides the most complete context and allows us to properly debug the scenario. That said, in some cases videos/gifs can help explain interaction issues that are hard to describe in text.
+
+### A repro should be minimal
+
+Some users would give us a link to a real project and hope we can help them figure out what is wrong. We generally do not accept such requests because:
+
+You are already familiar with your codebase, but we are not. It is extremely time-consuming to hunt a bug in a big and unfamiliar codebase.
+
+The problematic behavior may very well be caused by your code rather than by a bug in Vue.
+
+A minimal reproduction means it demonstrates the bug, and the bug only. It should only contain the bare minimum amount of code that can reliably cause the bug. Try your best to get rid of anything that aren't directly related to the problem.
+
+### How to create a repro
+
+For Vue 3 core reproductions, try reproducing it in [The SFC Playground](https://play.vuejs.org/).
+
+If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://vite.new/vue).
+
+If neither of these are suitable, you can always provide a GitHub repository.
diff --git a/.github/commit-convention.md b/.github/commit-convention.md
index a8522fa210a..11a64576a24 100644
--- a/.github/commit-convention.md
+++ b/.github/commit-convention.md
@@ -6,7 +6,7 @@
Messages must be matched by the following regex:
-``` js
+```regexp
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,50}/
```
@@ -44,7 +44,7 @@ This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
### Full Message Format
-A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
+A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
```
():
@@ -74,9 +74,9 @@ The scope could be anything specifying the place of the commit change. For examp
The subject contains a succinct description of the change:
-* use the imperative, present tense: "change" not "changed" nor "changes"
-* don't capitalize the first letter
-* no dot (.) at the end
+- use the imperative, present tense: "change" not "changed" nor "changes"
+- don't capitalize the first letter
+- no dot (.) at the end
### Body
diff --git a/.github/contributing.md b/.github/contributing.md
index 33395947855..2554582b887 100644
--- a/.github/contributing.md
+++ b/.github/contributing.md
@@ -17,7 +17,33 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
## Pull Request Guidelines
-- Checkout a topic branch from a base branch, e.g. `master`, and merge back against that branch.
+### What kinds of Pull Requests are accepted?
+
+- Bug fix that addresses a clearly identified bug. **"Clearly identified bug"** means the bug has a proper reproduction either from a related open issue, or is included in the PR itself. Avoid submitting PRs that claim to fix something but do not sufficiently explain what is being fixed.
+
+- New feature that addresses a clearly explained and widely applicable use case. **"Widely applicable"** means the new feature should provide non-trivial improvements to the majority of the user base. Vue already has a large API surface so we are quite cautious about adding new features - if the use case is niche and can be addressed via userland implementations, it likely isn't suitable to go into core.
+
+ The feature implementation should also consider the trade-off between the added complexity vs. the benefits gained. For example, if a small feature requires significant changes that spreads across the codebase, it is likely not worth it, or the approach should be reconsidered.
+
+ If the feature has a non-trivial API surface addition, or significantly affects the way a common use case is approached by the users, it should go through a discussion first in the [RFC repo](https://github.com/vuejs/rfcs/discussions). PRs of such features without prior discussion make it really difficult to steer / adjust the API design due to coupling with concrete implementations, and can lead to wasted work.
+
+- Chore: typos, comment improvements, build config, CI config, etc. For typos and comment changes, try to combine multiple of them into a single PR.
+
+- **It should be noted that we discourage contributors from submitting code refactors that are largely stylistic.** Code refactors are only accepted if it improves performance, or comes with sufficient explanations on why it objectively improves the code quality (e.g. makes a related feature implementation easier).
+
+ The reason is that code readability is subjective. The maintainers of this project have chosen to write the code in its current style based on our preferences, and we do not want to spend time explaining our stylistic preferences. Contributors should just respect the established conventions when contributing code.
+
+ Another aspect of it is that large scale stylistic changes result in massive diffs that touch multiple files, adding noise to the git history and makes tracing behavior changes across commits more cumbersome.
+
+### 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.
+
+- [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:
@@ -28,21 +54,36 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
- 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 `npm test -- --coverage`.
+ - Add appropriate test coverage if applicable. You can check the coverage of your code addition by running `nr test-coverage`.
- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.
- Make sure tests pass!
-- Commit messages must follow the [commit message convention](./commit-convention.md) so that changelogs can be automatically generated. Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [yorkie](https://github.com/yyx990803/yorkie)).
+- Commit messages must follow the [commit message convention](./commit-convention.md) so that changelogs can be automatically generated. Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)).
+
+- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)).
+
+### Advanced Pull Request Tips
-- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [yorkie](https://github.com/yyx990803/yorkie)).
+- 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.
+ - Make sure to put dev-only code in `__DEV__` branches so they are tree-shakable.
+ - Runtime code is more sensitive to size increase than compiler code.
+ - 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`.
## Development Setup
-You will need [Node.js](https://nodejs.org) **version 16+**, and [PNPM](https://pnpm.io).
+You will need [Node.js](https://nodejs.org) with minimum version as specified in the [`.node-version`](https://github.com/vuejs/core/blob/main/.node-version) file, and [PNPM](https://pnpm.io) with minimum version as specified in the [`"packageManager"` field in `package.json`](https://github.com/vuejs/core/blob/main/package.json#L4).
-We also recommend installing [ni](https://github.com/antfu/ni) to help switching between repos using different package managers. `ni` also provides the handy `nr` command which running npm scripts easier.
+We also recommend installing [@antfu/ni](https://github.com/antfu/ni) to help switching between repos using different package managers. `ni` also provides the handy `nr` command which running npm scripts easier.
After cloning the repo, run:
@@ -53,13 +94,35 @@ $ pnpm i # install the dependencies of the project
A high level overview of tools used:
- [TypeScript](https://www.typescriptlang.org/) as the development language
-- [Rollup](https://rollupjs.org) for bundling
-- [Jest](https://jestjs.io/) for unit testing
+- [Vite](https://vitejs.dev/) and [ESBuild](https://esbuild.github.io/) for development bundling
+- [Rollup](https://rollupjs.org) for production bundling
+- [Vitest](https://vitest.dev/) for unit testing
- [Prettier](https://prettier.io/) for code formatting
+- [ESLint](https://eslint.org/) for static error prevention (outside of types)
+
+## Git Hooks
+
+The project uses [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks) to enforce the following on each commit:
+
+- Type check the entire project
+- Automatically format changed files using Prettier
+- Verify commit message format (logic in `scripts/verify-commit.js`)
## Scripts
-**The examples below will be using the `nr` command from the [ni](https://github.com/antfu/ni) package.** You can also use plain `npm run`, but you will need to pass all additional arguments after the command after an extra `--`. For example, `nr build runtime --all` is equivalent to `npm run build -- runtime --all`.
+**The examples below will be using the `nr` command from the [@antfu/ni](https://github.com/antfu/ni) package.** You can also use plain `npm run`, but you will need to pass all additional arguments after the command after an extra `--`. For example, `nr build runtime --all` is equivalent to `npm run build -- runtime --all`.
+
+The `run-s` and `run-p` commands found in some scripts are from [npm-run-all](https://github.com/mysticatea/npm-run-all) for orchestrating multiple scripts. `run-s` means "run in sequence" while `run-p` means "run in parallel".
+
+- [`nr build`](#nr-build)
+- [`nr build-dts`](#nr-build-dts)
+- [`nr check`](#nr-check)
+- [`nr dev`](#nr-dev)
+- [`nr dev-sfc`](#nr-dev-sfc)
+- [`nr dev-esm`](#nr-dev-esm)
+- [`nr dev-compiler`](#nr-dev-compiler)
+- [`nr test`](#nr-test)
+- [`nr test-dts`](#nr-test-dts)
### `nr build`
@@ -75,6 +138,8 @@ nr build runtime-core
nr build runtime --all
```
+Note that `nr build` uses `rollup-plugin-esbuild` for transpiling typescript and **does not perform type checking**. To run type check on the entire codebase, run `nr check`. Type checks are also automatically run on each commit.
+
#### Build Formats
By default, each package will be built in multiple distribution formats as specified in the `buildOptions.formats` field in its `package.json`. These can be overwritten via the `-f` flag. The following formats are supported:
@@ -108,13 +173,11 @@ nr build runtime-core -f esm-browser,cjs
Use the `--sourcemap` or `-s` flag to build with source maps. Note this will make the build much slower.
-#### Build with Type Declarations
+### `nr build-dts`
-The `--types` or `-t` flag will generate type declarations during the build and in addition:
+This command builds the type declarations for all packages. It first generates the raw `.d.ts` files in the `temp` directory, then uses [rollup-plugin-dts](https://github.com/Swatinem/rollup-plugin-dts) to roll the types into a single `.d.ts` file for each package.
-- Roll the declarations into a single `.d.ts` file for each package;
-- Generate an API report in `/temp/.api.md`. This report contains potential warnings emitted by [api-extractor](https://api-extractor.com/).
-- Generate an API model json in `/temp/.api.json`. This file can be used to generate a Markdown version of the exported APIs.
+### `nr check`
### `nr dev`
@@ -123,7 +186,7 @@ The `dev` script bundles a target package (default: `vue`) in a specified format
```bash
$ nr dev
-> watching: packages/vue/dist/vue.global.js
+> built: packages/vue/dist/vue.global.js
```
- **Important:** output of the `dev` script is for development and debugging only. While it has the same runtime behavior, the generated code should never be published to npm.
@@ -136,29 +199,44 @@ $ nr dev
- The `dev` script supports the `-i` flag for inlining all deps. This is useful when debugging `esm-bundler` builds which externalizes deps by default.
+### `nr dev-sfc`
+
+Shortcut for starting the SFC Playground in local dev mode. This provides the fastest feedback loop when debugging issues that can be reproduced in the SFC Playground.
+
+### `nr dev-esm`
+
+Builds and watches `vue/dist/vue-runtime.esm-bundler.js` with all deps inlined using esbuild. This is useful when debugging the ESM build in a reproduction that requires real build setups: link `packages/vue` globally, then link it into the project being debugged.
+
### `nr dev-compiler`
-The `dev-compiler` script builds, watches and serves the [Template Explorer](https://github.com/vuejs/core/tree/main/packages/template-explorer) at `http://localhost:5000`. This is extremely useful when working on the compiler.
+The `dev-compiler` script builds, watches and serves the [Template Explorer](https://github.com/vuejs/core/tree/main/packages/template-explorer) at `http://localhost:3000`. This is useful when working on pure compiler issues.
### `nr test`
-The `test` script simply calls the `jest` binary, so all [Jest CLI Options](https://jestjs.io/docs/en/cli) can be used. Some examples:
+The `test` script simply calls the `vitest` binary, so all [Vitest CLI Options](https://vitest.dev/guide/cli.html#options) can be used. Some examples:
```bash
-# run all tests
+# run all tests in watch mode
$ nr test
+# run once and exit (equivalent to `vitest run`)
+$ nr test run
+
# run all tests under the runtime-core package
$ nr test runtime-core
-# run tests in a specific file
-$ nr test fileName
+# run tests in files matching the pattern
+$ nr test
-# run a specific test in a specific file
-$ nr test fileName -t 'test name'
+# run a specific test in specific files
+$ nr test -t 'test name'
```
-The default `test` script includes the `--runInBand` jest flag to improve test stability, especially for the CSS transition related tests. When you are testing specific test specs, you can also run `npx jest` with flags directly to speed up tests (jest runs them in parallel by default).
+Tests that test against source code are grouped under `nr test-unit`, while tests that test against built files that run in real browsers are grouped under `nr test-e2e`.
+
+### `nr test-dts`
+
+Runs `nr build-dts` first, then verify the type tests in `packages-private/dts-test` are working correctly against the actual built type declarations.
## Project Structure
@@ -182,14 +260,18 @@ This repository employs a [monorepo](https://en.wikipedia.org/wiki/Monorepo) set
- `compiler-ssr`: Compiler that produces render functions optimized for server-side rendering.
-- `template-explorer`: A development tool for debugging compiler output. You can run `nr dev template-explorer` and open its `index.html` to get a repl of template compilation based on current source code.
-
- A [live version](https://vue-next-template-explorer.netlify.com) of the template explorer is also available, which can be used for providing reproductions for compiler bugs. You can also pick the deployment for a specific commit from the [deploy logs](https://app.netlify.com/sites/vue-next-template-explorer/deploys).
-
- `shared`: Internal utilities shared across multiple packages (especially environment-agnostic utils used by both runtime and compiler packages).
- `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).
+
+ - `template-explorer`: A development tool for debugging compiler output, continuously deployed at https://template-explorer.vuejs.org/. To run it locally, run [`nr dev-compiler`](#nr-dev-compiler).
+
### Importing Packages
The packages can import each other directly using their package names. Note that when importing a package, the name listed in its `package.json` should be used. Most of the time the `@vue/` prefix is needed:
@@ -201,32 +283,34 @@ import { h } from '@vue/runtime-core'
This is made possible via several configurations:
- For TypeScript, `compilerOptions.paths` in `tsconfig.json`
-- For Jest, `moduleNameMapper` in `jest.config.js`
+- Vitest and Rollup share the same set of aliases from `scripts/aliases.js`
- For plain Node.js, they are linked using [PNPM Workspaces](https://pnpm.io/workspaces).
### Package Dependencies
-```
- +---------------------+
- | |
- | @vue/compiler-sfc |
- | |
- +-----+--------+------+
- | |
- v v
- +---------------------+ +----------------------+
- | | | |
- +------------>| @vue/compiler-dom +--->| @vue/compiler-core |
- | | | | |
- +----+----+ +---------------------+ +----------------------+
- | |
- | vue |
- | |
- +----+----+ +---------------------+ +----------------------+ +-------------------+
- | | | | | | |
- +------------>| @vue/runtime-dom +--->| @vue/runtime-core +--->| @vue/reactivity |
- | | | | | |
- +---------------------+ +----------------------+ +-------------------+
+```mermaid
+ flowchart LR
+ compiler-sfc["@vue/compiler-sfc"]
+ compiler-dom["@vue/compiler-dom"]
+ compiler-core["@vue/compiler-core"]
+ vue["vue"]
+ runtime-dom["@vue/runtime-dom"]
+ runtime-core["@vue/runtime-core"]
+ reactivity["@vue/reactivity"]
+
+ subgraph "Runtime Packages"
+ runtime-dom --> runtime-core
+ runtime-core --> reactivity
+ end
+
+ subgraph "Compiler Packages"
+ compiler-sfc --> compiler-core
+ compiler-sfc --> compiler-dom
+ compiler-dom --> compiler-core
+ end
+
+ vue ---> compiler-dom
+ vue --> runtime-dom
```
There are some rules to follow when importing across package boundaries:
@@ -239,7 +323,7 @@ There are some rules to follow when importing across package boundaries:
## Contributing Tests
-Unit tests are collocated with the code being tested in each package, inside directories named `__tests__`. Consult the [Jest docs](https://jestjs.io/docs/en/using-matchers) and existing test cases for how to write new test specs. Here are some additional guidelines:
+Unit tests are collocated with the code being tested in each package, inside directories named `__tests__`. Consult the [Vitest docs](https://vitest.dev/api/) and existing test cases for how to write new test specs. Here are some additional guidelines:
- Use the minimal API needed for a test case. For example, if a test can be written without involving the reactivity system or a component, it should be written so. This limits the test's exposure to changes in unrelated parts and makes it more stable.
@@ -247,13 +331,11 @@ Unit tests are collocated with the code being tested in each package, inside dir
- Only use platform-specific runtimes if the test is asserting platform-specific behavior.
-Test coverage is continuously deployed at https://vue-next-coverage.netlify.app/. PRs that improve test coverage are welcome, but in general the test coverage should be used as a guidance for finding API use cases that are not covered by tests. We don't recommend adding tests that only improve coverage but not actually test a meaning use case.
+Test coverage is continuously deployed at https://coverage.vuejs.org. PRs that improve test coverage are welcome, but in general the test coverage should be used as a guidance for finding API use cases that are not covered by tests. We don't recommend adding tests that only improve coverage but not actually test a meaning use case.
### Testing Type Definition Correctness
-This project uses [tsd](https://github.com/SamVerschueren/tsd) to test the built definition files (`*.d.ts`).
-
-Type tests are located in the `test-dts` directory. To run the dts tests, run `nr test-dts`. Note that the type test requires all relevant `*.d.ts` files to be built first (and the script does it for you). Once the `d.ts` files are built and up-to-date, the tests can be re-run by simply running `nr test-dts`.
+Type tests are located in the `packages-private/dts-test` directory. To run the dts tests, run `nr test-dts`. Note that the type test requires all relevant `*.d.ts` files to be built first (and the script does it for you). Once the `d.ts` files are built and up-to-date, the tests can be re-run by running `nr test-dts-only`.
## Financial Contribution
@@ -270,4 +352,4 @@ Funds donated via Patreon go directly to support Evan You's full-time work on Vu
Thank you to all the people who have already contributed to Vue.js!
-
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index af713d27a13..00000000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-version: 2
-updates:
-- package-ecosystem: npm
- directory: "/"
- schedule:
- interval: monthly
- open-pull-requests-limit: 10
- versioning-strategy: lockfile-only
- ignore:
- - dependency-name: "@types/node"
- versions:
- - 14.14.24
- - 14.14.37
- - dependency-name: "@babel/parser"
- versions:
- - 7.12.11
- - 7.12.13
- - 7.12.14
- - 7.12.15
- - 7.12.16
- - 7.12.17
- - 7.13.0
- - 7.13.10
- - 7.13.11
- - 7.13.13
- - 7.13.4
- - 7.13.9
- - dependency-name: eslint
- versions:
- - 7.23.0
- - dependency-name: postcss
- versions:
- - 8.2.4
- - 8.2.5
- - 8.2.7
- - 8.2.8
- - dependency-name: typescript
- versions:
- - 4.2.2
- - dependency-name: "@babel/types"
- versions:
- - 7.12.12
- - 7.12.13
- - 7.12.17
- - 7.13.0
- - dependency-name: pug-code-gen
- versions:
- - 2.0.3
- - dependency-name: estree-walker
- versions:
- - 2.0.2
- - dependency-name: "@typescript-eslint/parser"
- versions:
- - 4.14.2
- - 4.15.0
- - dependency-name: "@microsoft/api-extractor"
- versions:
- - 7.13.1
- - dependency-name: rollup
- versions:
- - 2.38.5
- - dependency-name: node-notifier
- versions:
- - 8.0.1
diff --git a/.github/git-branch-workflow.excalidraw b/.github/git-branch-workflow.excalidraw
new file mode 100644
index 00000000000..dd9127938da
--- /dev/null
+++ b/.github/git-branch-workflow.excalidraw
@@ -0,0 +1,1746 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "arrow",
+ "version": 799,
+ "versionNonce": 529220601,
+ "isDeleted": false,
+ "id": "Gao2krnDddLMCj468JSWD",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 860.0129225738813,
+ "y": 663.9911710635109,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#ffc9c9",
+ "width": 133.75296854079784,
+ "height": 149.58016791936518,
+ "seed": 1415631543,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "hDC6an14QljktaZCUhcPF",
+ "focus": 0.09950793234484598,
+ "gap": 1.2432497743127229
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 25.209039386719837,
+ 85.96948921803892
+ ],
+ [
+ 133.75296854079784,
+ 149.58016791936518
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 563,
+ "versionNonce": 290881303,
+ "isDeleted": false,
+ "id": "N3wyyEU7TQ8BsOQgxCmlR",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 292.88008929085873,
+ "y": 660.7027503334302,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#b2f2bb",
+ "width": 936.9972134376155,
+ "height": 1.3184243543457796,
+ "seed": 534235417,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 936.9972134376155,
+ -1.3184243543457796
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 302,
+ "versionNonce": 883286489,
+ "isDeleted": false,
+ "id": "nRDWQs5nQa37yzCWTBiXC",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 293.1231624544633,
+ "y": 820.6017661012943,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#b2f2bb",
+ "width": 790.7091601354882,
+ "height": 0.35284814071621895,
+ "seed": 515907671,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "ggogfJT7E_bbfEog7Hjnp",
+ "focus": -0.14000162237652433,
+ "gap": 1
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 790.7091601354882,
+ -0.35284814071621895
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 36,
+ "versionNonce": 981763127,
+ "isDeleted": false,
+ "id": "ZPdMAnEUq5Jgj1W07Zqiw",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 292.0450153578305,
+ "y": 619.3959946602608,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#b2f2bb",
+ "width": 46.875,
+ "height": 24,
+ "seed": 1311694519,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 3,
+ "text": "main",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "main",
+ "lineHeight": 1.2,
+ "baseline": 20
+ },
+ {
+ "type": "text",
+ "version": 94,
+ "versionNonce": 18759353,
+ "isDeleted": false,
+ "id": "g9IkEIfu4vA8Qkwtw01Hi",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 290.88990199912035,
+ "y": 779.1760596323645,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#b2f2bb",
+ "width": 58.59375,
+ "height": 24,
+ "seed": 329886135,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 3,
+ "text": "minor",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "minor",
+ "lineHeight": 1.2,
+ "baseline": 20
+ },
+ {
+ "type": "ellipse",
+ "version": 50,
+ "versionNonce": 1442112855,
+ "isDeleted": false,
+ "id": "RrdEQ7hwgGGDPhzDnuZj1",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 361.55609907891005,
+ "y": 649.8742329483416,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 2077639991,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 79,
+ "versionNonce": 1547173785,
+ "isDeleted": false,
+ "id": "Zmp49FKWxGSzKnVKomjQc",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 427.3015090315691,
+ "y": 650.256485100784,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 372652121,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 76,
+ "versionNonce": 586949239,
+ "isDeleted": false,
+ "id": "UOl9nLBksM7RPdH9mzjJa",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 490.9435520120701,
+ "y": 651.2601420343765,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 508667545,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 120,
+ "versionNonce": 874947705,
+ "isDeleted": false,
+ "id": "oMC55V0VO_hOXoZ1se8Kl",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 555.4481126698772,
+ "y": 650.7975189165487,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 1914963513,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 66,
+ "versionNonce": 39762839,
+ "isDeleted": false,
+ "id": "DZY5DC5uVP7-U5c3ngIZ4",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 622.5167031502219,
+ "y": 649.3743647489936,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 165914713,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 107,
+ "versionNonce": 1689103705,
+ "isDeleted": false,
+ "id": "Vsw6oIiTM3fQypkiCic3f",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 690.330195260967,
+ "y": 650.6681412649529,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 280044345,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [
+ {
+ "id": "lwYvAs-7FTjcwxKjcx0KV",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 148,
+ "versionNonce": 1986194201,
+ "isDeleted": false,
+ "id": "D14w9erv_2l53mINe2nSt",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 361.004283792179,
+ "y": 810.2809579853473,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#ffc9c9",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 1203257975,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 179,
+ "versionNonce": 1172811511,
+ "isDeleted": false,
+ "id": "6WO8xOpG0rf673b_bT0m7",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 426.74969374483805,
+ "y": 810.6632101377896,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 2056706967,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [
+ {
+ "id": "mE8Mu0qKfFaWPCC5vmF_f",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 173,
+ "versionNonce": 820518905,
+ "isDeleted": false,
+ "id": "VB9U8oH-78hf530hIb_mG",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 490.391736725339,
+ "y": 811.6668670713822,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 1149587639,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 218,
+ "versionNonce": 1227143191,
+ "isDeleted": false,
+ "id": "Bxv1hcS0VmxUwI0JLFH97",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 554.8962973831461,
+ "y": 811.2042439535543,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 1864901079,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [
+ {
+ "id": "M14Q0Uo1DBy2Ss2SOFSgW",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 167,
+ "versionNonce": 1387509977,
+ "isDeleted": false,
+ "id": "4v23gkfhy-hzk18YdkfLz",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 621.9648878634908,
+ "y": 809.7810897859994,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#ffc9c9",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 462671607,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [
+ {
+ "id": "vEF1cIIYYWKm84KLKqEz3",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 200,
+ "versionNonce": 774085943,
+ "isDeleted": false,
+ "id": "AtEf7o4WZQn4Zxq8EN5fH",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 689.7783799742359,
+ "y": 811.0748663019584,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 1414322199,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [
+ {
+ "id": "3heKY3vfe3-6ni4dX7Uqo",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 199,
+ "versionNonce": 1834563001,
+ "isDeleted": false,
+ "id": "ugDby5sBv4NKdNt8eC1sg",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 762.6179978227377,
+ "y": 810.2986003923828,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 1598537015,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 211,
+ "versionNonce": 407428695,
+ "isDeleted": false,
+ "id": "Fwe4F2sB_0jptOZGYsusj",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 837.1081608628116,
+ "y": 810.859236882632,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 1340669527,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [
+ {
+ "id": "M14Q0Uo1DBy2Ss2SOFSgW",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "arrow",
+ "version": 57,
+ "versionNonce": 335287961,
+ "isDeleted": false,
+ "id": "mE8Mu0qKfFaWPCC5vmF_f",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 437.60867586595543,
+ "y": 830.4227236701945,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#ffc9c9",
+ "width": 0.5232394659406623,
+ "height": 33.25787987764363,
+ "seed": 482155929,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "6WO8xOpG0rf673b_bT0m7",
+ "focus": -0.1727591064041787,
+ "gap": 1.046152088903881
+ },
+ "endBinding": {
+ "elementId": "JALHBtowuh3_a86loej2x",
+ "focus": 0.015156451076917701,
+ "gap": 15.586906139714472
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.5232394659406623,
+ 33.25787987764363
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 59,
+ "versionNonce": 1248394103,
+ "isDeleted": false,
+ "id": "AI-_jSAuzesxTqwRvpk0s",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 501.2878833373983,
+ "y": 652.3088851192829,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#ffc9c9",
+ "width": 0,
+ "height": 40.40111211199792,
+ "seed": 1052632343,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ -40.40111211199792
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 261,
+ "versionNonce": 693099385,
+ "isDeleted": false,
+ "id": "lwYvAs-7FTjcwxKjcx0KV",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 786.7392304423553,
+ "y": 649.6016935672433,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#ffc9c9",
+ "width": 0,
+ "height": 40.40111211199792,
+ "seed": 1233043511,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "s0PKxsWTJSDbQeEl_WI-C",
+ "focus": 0.016372633695398757,
+ "gap": 1
+ },
+ "endBinding": {
+ "elementId": "9ia1Uwc5X0fRw5iaahmcT",
+ "focus": 0.025318405829282714,
+ "gap": 14.862364635333904
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ -40.40111211199792
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 121,
+ "versionNonce": 952661143,
+ "isDeleted": false,
+ "id": "qWW8uxDIcV3Bkj28uvRLr",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 454.32425448306674,
+ "y": 537.8854189061962,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffc9c9",
+ "width": 93.75,
+ "height": 57.599999999999994,
+ "seed": 809847769,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "patch\nrelease\ne.g. 3.3.8",
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "patch\nrelease\ne.g. 3.3.8",
+ "lineHeight": 1.2,
+ "baseline": 53
+ },
+ {
+ "type": "text",
+ "version": 257,
+ "versionNonce": 1838679129,
+ "isDeleted": false,
+ "id": "9ia1Uwc5X0fRw5iaahmcT",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 741.0510307156029,
+ "y": 536.7382168199114,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffc9c9",
+ "width": 93.75,
+ "height": 57.599999999999994,
+ "seed": 213765431,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [
+ {
+ "id": "lwYvAs-7FTjcwxKjcx0KV",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "patch\nrelease\ne.g. 3.3.9",
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "patch\nrelease\ne.g. 3.3.9",
+ "lineHeight": 1.2,
+ "baseline": 53
+ },
+ {
+ "type": "text",
+ "version": 222,
+ "versionNonce": 1528547767,
+ "isDeleted": false,
+ "id": "JALHBtowuh3_a86loej2x",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 350.7264132088442,
+ "y": 879.2675096875524,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffc9c9",
+ "width": 168.75,
+ "height": 57.599999999999994,
+ "seed": 41180921,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [
+ {
+ "id": "mE8Mu0qKfFaWPCC5vmF_f",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "pre minor\nrelease\ne.g. 3.4.0-alpha.1",
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "pre minor\nrelease\ne.g. 3.4.0-alpha.1",
+ "lineHeight": 1.2,
+ "baseline": 53
+ },
+ {
+ "type": "arrow",
+ "version": 345,
+ "versionNonce": 1286082873,
+ "isDeleted": false,
+ "id": "3heKY3vfe3-6ni4dX7Uqo",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 699.5281288163526,
+ "y": 831.0290882554708,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#ffc9c9",
+ "width": 0.5502191262773977,
+ "height": 33.25154356841597,
+ "seed": 627698359,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "AtEf7o4WZQn4Zxq8EN5fH",
+ "focus": -0.05612657009295625,
+ "gap": 1.1451322685712295
+ },
+ "endBinding": {
+ "elementId": "9t6qH-tAxVUexkHHi2pd2",
+ "focus": 0.015156451076917755,
+ "gap": 15.586906139714358
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.5502191262773977,
+ 33.25154356841597
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 365,
+ "versionNonce": 1049066199,
+ "isDeleted": false,
+ "id": "9t6qH-tAxVUexkHHi2pd2",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 617.3409291322284,
+ "y": 879.8675379636011,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffc9c9",
+ "width": 159.375,
+ "height": 57.599999999999994,
+ "seed": 1013545943,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [
+ {
+ "id": "3heKY3vfe3-6ni4dX7Uqo",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613071,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "pre minor\nrelease\ne.g. 3.4.0-beta.1",
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "pre minor\nrelease\ne.g. 3.4.0-beta.1",
+ "lineHeight": 1.2,
+ "baseline": 53
+ },
+ {
+ "type": "arrow",
+ "version": 788,
+ "versionNonce": 1810072089,
+ "isDeleted": false,
+ "id": "vEF1cIIYYWKm84KLKqEz3",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 630.3597332113623,
+ "y": 667.2735668205443,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#ffc9c9",
+ "width": 2.258228100583324,
+ "height": 140.75112333166828,
+ "seed": 2091697367,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "4v23gkfhy-hzk18YdkfLz",
+ "focus": 0.13930391883256707,
+ "gap": 1.8256906627890626
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 1.8426514015177418,
+ 69.09942755691065
+ ],
+ [
+ 2.258228100583324,
+ 140.75112333166828
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 687,
+ "versionNonce": 2017318649,
+ "isDeleted": false,
+ "id": "M14Q0Uo1DBy2Ss2SOFSgW",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 370.5976915356099,
+ "y": 667.5155013947814,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#ffc9c9",
+ "width": 1.5329291446666957,
+ "height": 145.39303664953377,
+ "seed": 361678233,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.34892760581925586,
+ 83.56228079137543
+ ],
+ [
+ 1.1840015388474399,
+ 145.39303664953377
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 537,
+ "versionNonce": 342487319,
+ "isDeleted": false,
+ "id": "CHAOOJMz7tNaG1VsG_uzT",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 384.81046417498214,
+ "y": 725.4677076298137,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffc9c9",
+ "width": 131.25,
+ "height": 57.599999999999994,
+ "seed": 1656007289,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "merge main\ninto minor\nbefore release",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "merge main\ninto minor\nbefore release",
+ "lineHeight": 1.2,
+ "baseline": 53
+ },
+ {
+ "type": "ellipse",
+ "version": 202,
+ "versionNonce": 876253145,
+ "isDeleted": false,
+ "id": "hDC6an14QljktaZCUhcPF",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 993.0386151813434,
+ "y": 810.335845473903,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffc9c9",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 1433430105,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [
+ {
+ "id": "Gao2krnDddLMCj468JSWD",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "arrow",
+ "version": 1525,
+ "versionNonce": 777631287,
+ "isDeleted": false,
+ "id": "ces8IwHCpQlTnELpjFDIn",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1092.5386800881793,
+ "y": 827.5114796878765,
+ "strokeColor": "#f08c00",
+ "backgroundColor": "#ffc9c9",
+ "width": 0.3315362017829102,
+ "height": 49.45191086419197,
+ "seed": 225867737,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "8rWUxp-jRNGrGRmhHHfm4",
+ "focus": -0.2047594653982401,
+ "gap": 10.392197401393389
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.3315362017829102,
+ 49.45191086419197
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 894,
+ "versionNonce": 1173171385,
+ "isDeleted": false,
+ "id": "8rWUxp-jRNGrGRmhHHfm4",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1047.251646167428,
+ "y": 887.3555879534618,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffc9c9",
+ "width": 112.5,
+ "height": 57.599999999999994,
+ "seed": 1600918713,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [
+ {
+ "id": "ces8IwHCpQlTnELpjFDIn",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "stable minor\nrelease\ne.g. 3.4.0",
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "stable minor\nrelease\ne.g. 3.4.0",
+ "lineHeight": 1.2,
+ "baseline": 53
+ },
+ {
+ "type": "ellipse",
+ "version": 201,
+ "versionNonce": 78435447,
+ "isDeleted": false,
+ "id": "3RHuRn_evSK0YUe02B4MY",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 909.9742423218671,
+ "y": 810.4142561718397,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 1199705047,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 371,
+ "versionNonce": 2093872087,
+ "isDeleted": false,
+ "id": "9h2Cu__8owLUgUGjGcWDe",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 848.4414471158692,
+ "y": 650.826922928275,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 603147257,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 361,
+ "versionNonce": 1981618457,
+ "isDeleted": false,
+ "id": "s0PKxsWTJSDbQeEl_WI-C",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 777.1778842958995,
+ "y": 650.2466837635417,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#b2f2bb",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 326722777,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [
+ {
+ "id": "lwYvAs-7FTjcwxKjcx0KV",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 871,
+ "versionNonce": 1528156247,
+ "isDeleted": false,
+ "id": "3JAdSa7kqqSDSom5ZFDoE",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 904.3603861670398,
+ "y": 707.2413714353705,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffc9c9",
+ "width": 140.625,
+ "height": 57.599999999999994,
+ "seed": 1011049431,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "final merge\nmain into minor\nbefore release",
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "final merge\nmain into minor\nbefore release",
+ "lineHeight": 1.2,
+ "baseline": 53
+ },
+ {
+ "type": "arrow",
+ "version": 591,
+ "versionNonce": 1714373785,
+ "isDeleted": false,
+ "id": "7kFBLq2Iczmj0lVnVk8Ad",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1100.7141458557703,
+ "y": 814.2034531496416,
+ "strokeColor": "#2f9e44",
+ "backgroundColor": "#ffffff",
+ "width": 127.38209933342364,
+ "height": 144.5383600420214,
+ "seed": 25829591,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "Y7VXnuc9QEz2N2l9i0xrc",
+ "focus": 0.3932764551319699,
+ "gap": 5.928572790502042
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 88.94909573964219,
+ -43.721805169626464
+ ],
+ [
+ 127.38209933342364,
+ -144.5383600420214
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1208,
+ "versionNonce": 1254600055,
+ "isDeleted": false,
+ "id": "gwFWlPLabuYhxCOweJjWz",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1223.0464288187204,
+ "y": 725.1565933898091,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffc9c9",
+ "width": 150,
+ "height": 38.4,
+ "seed": 51102743,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "main merge minor\n(fast forward)",
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "main merge minor\n(fast forward)",
+ "lineHeight": 1.2,
+ "baseline": 34
+ },
+ {
+ "type": "ellipse",
+ "version": 597,
+ "versionNonce": 1760381305,
+ "isDeleted": false,
+ "id": "Y7VXnuc9QEz2N2l9i0xrc",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1227.4473966637659,
+ "y": 647.6689320688656,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#a5d8ff",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 412038615,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [
+ {
+ "id": "7kFBLq2Iczmj0lVnVk8Ad",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 547,
+ "versionNonce": 1585505943,
+ "isDeleted": false,
+ "id": "ggogfJT7E_bbfEog7Hjnp",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1083.7911569735343,
+ "y": 809.5203742153592,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#a5d8ff",
+ "width": 18.814646969963974,
+ "height": 18.814646969963974,
+ "seed": 741463161,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [
+ {
+ "id": "nRDWQs5nQa37yzCWTBiXC",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 229,
+ "versionNonce": 1935127129,
+ "isDeleted": false,
+ "id": "eU-EgpwDD42CLYUEIDLaD",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 305.8405004265049,
+ "y": 389.31989430571576,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#a5d8ff",
+ "width": 581.25,
+ "height": 19.2,
+ "seed": 1086231577,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "- merge feature PRs into, and release minors from minor branch",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "- merge feature PRs into, and release minors from minor branch",
+ "lineHeight": 1.2,
+ "baseline": 15
+ },
+ {
+ "type": "text",
+ "version": 397,
+ "versionNonce": 116088535,
+ "isDeleted": false,
+ "id": "Kt6VBAVD4sLM4IexsRGoX",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 305.4136207977353,
+ "y": 358.61173442109686,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#a5d8ff",
+ "width": 618.75,
+ "height": 19.2,
+ "seed": 273353945,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1698927617946,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "- merge fix / chore PRs into, and release patches from main branch",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "- merge fix / chore PRs into, and release patches from main branch",
+ "lineHeight": 1.2,
+ "baseline": 15
+ },
+ {
+ "type": "text",
+ "version": 459,
+ "versionNonce": 440532793,
+ "isDeleted": false,
+ "id": "JwKEdnU6H_Nu74WbEAX5M",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 305.6723761009271,
+ "y": 418.3724478537203,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#a5d8ff",
+ "width": 459.375,
+ "height": 19.2,
+ "seed": 1001222329,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "- merge main into minor before each minor release",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "- merge main into minor before each minor release",
+ "lineHeight": 1.2,
+ "baseline": 15
+ },
+ {
+ "type": "text",
+ "version": 602,
+ "versionNonce": 1108720119,
+ "isDeleted": false,
+ "id": "mb9ZoP803MiH7MTO8wH-2",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 305.0895924262568,
+ "y": 447.44321411383333,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#a5d8ff",
+ "width": 534.375,
+ "height": 19.2,
+ "seed": 264651479,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "- fast forward main to minor after a stable minor release",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "- fast forward main to minor after a stable minor release",
+ "lineHeight": 1.2,
+ "baseline": 15
+ },
+ {
+ "type": "text",
+ "version": 612,
+ "versionNonce": 1588872441,
+ "isDeleted": false,
+ "id": "IfJPOFiwrCibpaBQqc5g-",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 2,
+ "opacity": 100,
+ "angle": 0,
+ "x": 646.7131179044119,
+ "y": 724.4984335940012,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffc9c9",
+ "width": 131.25,
+ "height": 57.599999999999994,
+ "seed": 1301100087,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1698927613072,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 3,
+ "text": "merge main\ninto minor\nbefore release",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "merge main\ninto minor\nbefore release",
+ "lineHeight": 1.2,
+ "baseline": 53
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/.github/git-branch-workflow.png b/.github/git-branch-workflow.png
new file mode 100644
index 00000000000..6c8ee07d484
Binary files /dev/null and b/.github/git-branch-workflow.png differ
diff --git a/.github/issue-workflow.png b/.github/issue-workflow.png
new file mode 100644
index 00000000000..92b1de0633c
Binary files /dev/null and b/.github/issue-workflow.png differ
diff --git a/.github/maintenance.md b/.github/maintenance.md
new file mode 100644
index 00000000000..b1fb550dd7a
--- /dev/null
+++ b/.github/maintenance.md
@@ -0,0 +1,123 @@
+# Vue Core Maintenance Handbook
+
+Unlike [contributing.md](./contributing.md), which targets external contributors, this document is mainly intended for team members responsible for maintaining the project. It provides guidelines on how to triage issues, review & merge PRs, and publish releases. However, it should also be valuable to external contributors even if you are not a maintainer, as it gives you a better idea of how the maintainers operate, and how you can better collaborate with them. And who knows - maybe one day you will join as a maintainer as well!
+
+- [Issue Triage Workflow](#issue-triage-workflow)
+- [Pull Request Review Guidelines](#pull-request-review-guidelines)
+ - [Reviewing a Fix](#reviewing-a-fix)
+ - [Reviewing a Refactor](#reviewing-a-refactor)
+ - [Reviewing a Feature](#reviewing-a-feature)
+ - [Common Considerations for All PRs](#common-considerations-for-all-prs)
+- [PR Merge Rules for Team Members](#pr-merge-rules-for-team-members)
+- [Git Branch and Release Workflow](#git-branch-and-release-workflow)
+
+## Issue Triage Workflow
+
+
+
+## Pull Request Review Guidelines
+
+The first step of reviewing a PR is to identify its purpose. We can usually put a PR in one of these categories:
+
+- **Fix**: fixes some wrong behavior. Usually associated with an issue that has a reproduction of the behavior being fixed.
+- **Refactor**: improves performance or code quality, but does not affect behavior.
+- **Feature**: implements something that increases the public API surface.
+
+Depending on the type of the PR, different considerations need to be taken into account.
+
+### Reviewing a Fix
+
+- Is the PR fixing a well defined issue / bug report?
+ - If not, ask to clarify context / provide reproduction or failing test case
+- In most cases, a fix PR should include a test case that fails without the fix.
+- Is it the right fix?
+ - If not, guide user to rework the PR.
+ - If the needed change is small and obvious, can directly push to the PR or add inline suggestions to reduce the back-and-forth.
+- Is the cost justified?
+ - Sometimes the fix for a rare edge case might be introducing disproportionately large overhead (perf or code size). We should try our best to reduce the overhead to make the fix a reasonable tradeoff.
+- If the reviewer is not sure about a fix, try to leave a comment explaining the concerns / reservations so the contributor at least gets some feedback.
+
+#### Verifying a Fix
+
+- **Always locally verify that the fix indeed fixes the original behavior, either through a reproduction or a failing test case.**
+- We will run [ecosystem-ci](https://github.com/vuejs/ecosystem-ci) before every release, but if you are concerned about the potential impact of a change, it never hurts to manually run ecosystem-ci by leaving a `/ecosystem-ci run` comment (only works for team members).
+- Take extra caution with snapshot tests! The CI can be "passing" even if the code generated in the snapshot contains bugs. It's best to always accompany a snapshot test with extra `expect(code).toMatch(...)` assertions.
+
+### Reviewing a Refactor
+
+- 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
+
+- 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.
+
+- Always ask if the use case can be solved with existing APIs. Vue already has a pretty large API surface, so we want to make sure every new addition either solves something that wasn't possible before, or significantly improves the DX of a common task.
+
+### Common Considerations for All PRs
+
+- Scope: a PR should only contain changes directly related to the problem being addressed. It should not contain unnecessary code style changes.
+
+- 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.
+
+ - Runtime code is more sensitive to size increase than compiler code.
+
+ - 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
+ - avoiding runtime behavior breakage is the highest priority
+ - if not sure, use `ecosystem-ci` to verify!
+ - some fix inevitably cause behavior change, these must be discussed case-by-case
+ - type level breakage (e.g upgrading TS) is possible between minors
+
+## PR Merge Rules for Team Members
+
+Given that the PR meets the review requirements:
+
+- Chore / dependencies bumps: can merge directly.
+- Fixes / refactors: can merge with two or more approvals from team members.
+ - If you believe a PR looks good but you are not 100% confident to merge, label with "ready for merge" and Evan will provide a final review before merging.
+- Features: if approved by two or more team members, label with "ready to merge". Evan will review periodically, or they can be raised and discussed at team meetings.
+
+## Git Branch and Release Workflow
+
+We use two primary work branches: `main` and `minor`.
+
+- The `main` branch is for stable releases. Changes that are bug fixes or refactors that do not affect the public API surface should land in this branch. We periodically release patch releases from the `main` branch.
+
+- The `minor` branch is the WIP branch for the next minor release. Changes that are new features or those that affect public API behavior should land in this branch. We will periodically release pre-releases (alpha / beta) for the next minor from this branch.
+
+Before each release, we merge latest `main` into `minor` so it would include the latest bug fixes.
+
+When the minor is ready, we do a final merge of `main` into `minor`, and then release a stable minor from this branch (e.g. `3.4.0`). After that, the `main` branch is fast-forwarded to the release commit, so the two branches are synced at each stable minor release.
+
+
+
+### Reasoning Behind the Workflow
+
+The reason behind this workflow is to allow merging and releasing of fixes and features in parallel. In the past, we used a linear trunk-based development model. While the linear model results in a clean git history, the downside is that we need to be careful about when to merge patches vs. features.
+
+Vue typically groups a number of features with the same scope in a minor release. We don't want to release a minor just because we happened to merge a feature PR along with a bunch of small bug fixes. So we usually "wait" until we feel we are ready to start working on a minor release before merging feature PRs.
+
+But in reality, there are always bugs to fix and patch release to work on - this caused the intervals between minors to drag on longer than we had hoped, and many feature PRs were left waiting for a long period of time.
+
+This is why we decided to separate bug fixes and feature PRs into separate branches. With this two-branch model, we are able to merge and release both types of changes in parallel.
diff --git a/.github/renovate.json5 b/.github/renovate.json5
new file mode 100644
index 00000000000..aad4afa132a
--- /dev/null
+++ b/.github/renovate.json5
@@ -0,0 +1,75 @@
+{
+ $schema: 'https://docs.renovatebot.com/renovate-schema.json',
+ extends: ['config:recommended', 'schedule:weekly', 'group:allNonMajor'],
+ labels: ['dependencies'],
+ ignorePaths: ['**/__tests__/**'],
+ rangeStrategy: 'bump',
+ packageRules: [
+ {
+ matchDepTypes: ['peerDependencies'],
+ enabled: false,
+ },
+ {
+ groupName: 'test',
+ matchPackageNames: ['vitest', 'jsdom', 'puppeteer', '@vitest{/,}**'],
+ },
+ {
+ groupName: 'playground',
+ matchFileNames: [
+ 'packages-private/sfc-playground/package.json',
+ 'packages-private/template-explorer/package.json',
+ ],
+ },
+ {
+ groupName: 'compiler',
+ matchPackageNames: ['magic-string', '@babel{/,}**', 'postcss{/,}**'],
+ },
+ {
+ groupName: 'build',
+ matchPackageNames: [
+ 'vite',
+ '@swc/core',
+ 'rollup{/,}**',
+ 'esbuild{/,}**',
+ '@rollup{/,}**',
+ '@vitejs{/,}**',
+ ],
+ },
+ {
+ groupName: 'lint',
+ matchPackageNames: [
+ 'simple-git-hooks',
+ 'lint-staged',
+ 'typescript-eslint{/,}**',
+ 'eslint{/,}**',
+ 'prettier{/,}**',
+ ],
+ },
+ ],
+ ignoreDeps: [
+ 'vue',
+
+ // manually bumping
+ 'node',
+ 'typescript',
+
+ // ESM only
+ 'estree-walker',
+
+ // pinned
+ // https://github.com/vuejs/core/issues/10300#issuecomment-1940855364
+ 'lru-cache',
+
+ // pinned
+ // https://github.com/vuejs/core/commit/a012e39b373f1b6918e5c89856e8f902e1bfa14d
+ '@rollup/plugin-replace',
+
+ // pinned
+ // only used in example for e2e tests
+ 'marked',
+
+ // pinned, 5.0+ has exports issues
+ // https://github.com/vuejs/core/issues/11603
+ 'entities',
+ ],
+}
diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml
new file mode 100644
index 00000000000..95bc65f357b
--- /dev/null
+++ b/.github/workflows/autofix.yml
@@ -0,0 +1,34 @@
+name: autofix.ci
+
+on:
+ pull_request:
+permissions:
+ contents: read
+
+jobs:
+ autofix:
+ runs-on: ubuntu-latest
+ env:
+ PUPPETEER_SKIP_DOWNLOAD: 'true'
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4.1.0
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ registry-url: 'https://registry.npmjs.org'
+ cache: 'pnpm'
+
+ - run: pnpm install
+
+ - name: Run eslint
+ run: pnpm run lint --fix
+
+ - name: Run prettier
+ run: pnpm run format
+
+ - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27
diff --git a/.github/workflows/canary-minor.yml b/.github/workflows/canary-minor.yml
new file mode 100644
index 00000000000..0b6401b8ce4
--- /dev/null
+++ b/.github/workflows/canary-minor.yml
@@ -0,0 +1,33 @@
+name: canary minor release
+on:
+ # Runs every Monday at 1 AM UTC (9:00 AM in Singapore)
+ schedule:
+ - cron: 0 1 * * MON
+ workflow_dispatch:
+
+jobs:
+ canary:
+ # prevents this action from running on forks
+ if: github.repository == 'vuejs/core'
+ runs-on: ubuntu-latest
+ environment: Release
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: minor
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4.1.0
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ registry-url: 'https://registry.npmjs.org'
+ cache: 'pnpm'
+
+ - run: pnpm install
+
+ - run: pnpm release --canary --publish --tag minor
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml
new file mode 100644
index 00000000000..71c794c7078
--- /dev/null
+++ b/.github/workflows/canary.yml
@@ -0,0 +1,31 @@
+name: canary release
+on:
+ # Runs every Monday at 1 AM UTC (9:00 AM in Singapore)
+ schedule:
+ - cron: 0 1 * * MON
+ workflow_dispatch:
+
+jobs:
+ canary:
+ # prevents this action from running on forks
+ if: github.repository == 'vuejs/core'
+ runs-on: ubuntu-latest
+ environment: Release
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4.1.0
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ registry-url: 'https://registry.npmjs.org'
+ cache: 'pnpm'
+
+ - run: pnpm install
+
+ - run: pnpm release --canary --publish
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0e02bfc6627..c8c217f62c4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,76 +3,40 @@ on:
push:
branches:
- '**'
+ tags:
+ - '!**'
pull_request:
branches:
- main
+ - minor
+
jobs:
test:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
-
- - name: Install pnpm
- uses: pnpm/action-setup@v2.0.1
- with:
- version: 6.15.1
-
- - name: Set node version to 16
- uses: actions/setup-node@v2
- with:
- node-version: 16
- cache: 'pnpm'
-
- - run: pnpm install
-
- - name: Run unit tests
- run: pnpm run test
+ if: ${{ ! startsWith(github.event.head_commit.message, 'release:') && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository) }}
+ uses: ./.github/workflows/test.yml
- test-dts:
+ continuous-release:
+ if: github.repository == 'vuejs/core'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - name: Checkout
+ uses: actions/checkout@v4
- name: Install pnpm
- uses: pnpm/action-setup@v2.0.1
- with:
- version: 6.15.1
+ uses: pnpm/action-setup@v4
- - name: Set node version to 16
- uses: actions/setup-node@v2
+ - name: Install Node.js
+ uses: actions/setup-node@v4
with:
- node-version: 16
+ node-version-file: '.node-version'
+ registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
- - run: pnpm install
-
- - name: Run type declaration tests
- run: pnpm run test-dts
-
- size:
- runs-on: ubuntu-latest
- env:
- CI_JOB_NUMBER: 1
- steps:
- - uses: actions/checkout@v2
-
- - name: Install pnpm
- uses: pnpm/action-setup@v2.0.1
- with:
- version: 6.15.1
-
- - name: Set node version to 16
- uses: actions/setup-node@v2
- with:
- node-version: 16
- cache: 'pnpm'
+ - name: Install deps
+ run: pnpm install
- - run: pnpm install
- - run: pnpm run size
+ - name: Build
+ run: pnpm build --withTypes
- # - name: Check build size
- # uses: posva/size-check-action@v1.1.2
- # with:
- # github_token: ${{ secrets.GITHUB_TOKEN }}
- # build_script: size
- # files: packages/vue/dist/vue.global.prod.js packages/runtime-dom/dist/runtime-dom.global.prod.js packages/size-check/dist/index.js
+ - name: Release
+ run: pnpx pkg-pr-new publish --compact --pnpm './packages/*'
diff --git a/.github/workflows/close-cant-reproduce-issues.yml b/.github/workflows/close-cant-reproduce-issues.yml
new file mode 100644
index 00000000000..8fb48f842d8
--- /dev/null
+++ b/.github/workflows/close-cant-reproduce-issues.yml
@@ -0,0 +1,21 @@
+name: Auto close issues with "can't reproduce" label
+
+on:
+ schedule:
+ - cron: '0 0 * * *'
+
+permissions:
+ issues: write
+
+jobs:
+ close-issues:
+ if: github.repository == 'vuejs/core'
+ runs-on: ubuntu-latest
+ steps:
+ - name: can't reproduce
+ uses: actions-cool/issues-helper@v3
+ with:
+ actions: 'close-issues'
+ token: ${{ secrets.GITHUB_TOKEN }}
+ labels: "can't reproduce"
+ inactive-day: 3
diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml
new file mode 100644
index 00000000000..b3e963ececa
--- /dev/null
+++ b/.github/workflows/ecosystem-ci-trigger.yml
@@ -0,0 +1,90 @@
+name: ecosystem-ci trigger
+
+on:
+ issue_comment:
+ types: [created]
+
+jobs:
+ trigger:
+ runs-on: ubuntu-latest
+ if: github.repository == 'vuejs/core' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run')
+ steps:
+ - name: Check user permission
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const user = context.payload.sender.login
+ console.log(`Validate user: ${user}`)
+
+ let isVuejsMember = false
+ try {
+ const { status } = await github.rest.orgs.checkMembershipForUser({
+ org: 'vuejs',
+ username: user
+ });
+
+ isVuejsMember = (status === 204)
+ } catch (e) {}
+
+ if (isVuejsMember) {
+ console.log('Allowed')
+ await github.rest.reactions.createForIssueComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: context.payload.comment.id,
+ content: '+1',
+ })
+ } else {
+ console.log('Not allowed')
+ await github.rest.reactions.createForIssueComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: context.payload.comment.id,
+ content: '-1',
+ })
+ throw new Error('not allowed')
+ }
+ - name: Get PR info
+ uses: actions/github-script@v7
+ id: get-pr-data
+ with:
+ script: |
+ console.log(`Get PR info: ${context.repo.owner}/${context.repo.repo}#${context.issue.number}`)
+ const { data: pr } = await github.rest.pulls.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.issue.number
+ })
+ return {
+ num: context.issue.number,
+ branchName: pr.head.ref,
+ repo: pr.head.repo.full_name,
+ commit: pr.head.sha
+ }
+ - name: Trigger run
+ uses: actions/github-script@v7
+ id: trigger
+ env:
+ COMMENT: ${{ github.event.comment.body }}
+ with:
+ github-token: ${{ secrets.ECOSYSTEM_CI_ACCESS_TOKEN }}
+ result-encoding: string
+ script: |
+ const comment = process.env.COMMENT.trim()
+ const prData = ${{ steps.get-pr-data.outputs.result }}
+
+ const suite = comment.replace(/^\/ecosystem-ci run/, '').trim()
+
+ await github.rest.actions.createWorkflowDispatch({
+ owner: context.repo.owner,
+ repo: 'ecosystem-ci',
+ workflow_id: 'ecosystem-ci-from-pr.yml',
+ ref: 'main',
+ inputs: {
+ prNumber: '' + prData.num,
+ branchName: prData.branchName,
+ repo: prData.repo,
+ suite: suite === '' ? '-' : suite,
+ commit: prData.commit
+ }
+ })
diff --git a/.github/workflows/lock-closed-issues.yml b/.github/workflows/lock-closed-issues.yml
new file mode 100644
index 00000000000..68a7d6c7a15
--- /dev/null
+++ b/.github/workflows/lock-closed-issues.yml
@@ -0,0 +1,20 @@
+name: Lock Closed Issues
+
+on:
+ schedule:
+ - cron: '0 0 * * *'
+
+permissions:
+ issues: write
+
+jobs:
+ action:
+ if: github.repository == 'vuejs/core'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: dessant/lock-threads@v5
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ issue-inactive-days: '14'
+ issue-lock-reason: ''
+ process-only: 'issues'
diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml
deleted file mode 100644
index d9ea7a07f72..00000000000
--- a/.github/workflows/release-tag.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-on:
- push:
- tags:
- - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
-
-name: Create Release
-
-jobs:
- build:
- name: Create Release
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@master
- - name: Create Release for Tag
- id: release_tag
- uses: yyx990803/release-tag@master
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tag_name: ${{ github.ref }}
- body: |
- Please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details.
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000000..c260a728e71
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,55 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
+
+jobs:
+ test:
+ uses: ./.github/workflows/test.yml
+
+ release:
+ # prevents this action from running on forks
+ if: github.repository == 'vuejs/core'
+ needs: [test]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ id-token: write
+ # Use Release environment for deployment protection
+ environment: Release
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ registry-url: 'https://registry.npmjs.org'
+ cache: 'pnpm'
+
+ - name: Install deps
+ run: pnpm install
+
+ - name: Build and publish
+ id: publish
+ run: |
+ pnpm release --publishOnly
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+
+ - name: Create GitHub release
+ id: release_tag
+ uses: yyx990803/release-tag@master
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: ${{ github.ref }}
+ body: |
+ For stable releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details.
+ For pre-releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/minor/CHANGELOG.md) of the `minor` branch.
diff --git a/.github/workflows/size-data.yml b/.github/workflows/size-data.yml
new file mode 100644
index 00000000000..5a370b8b92f
--- /dev/null
+++ b/.github/workflows/size-data.yml
@@ -0,0 +1,51 @@
+name: size data
+
+on:
+ push:
+ branches:
+ - main
+ - minor
+ pull_request:
+ branches:
+ - main
+ - minor
+
+permissions:
+ contents: read
+
+env:
+ PUPPETEER_SKIP_DOWNLOAD: 'true'
+
+jobs:
+ upload:
+ if: github.repository == 'vuejs/core'
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4.1.0
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ cache: pnpm
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - run: pnpm run size
+
+ - name: Save PR number & base branch
+ if: ${{github.event_name == 'pull_request'}}
+ run: |
+ echo ${{ github.event.number }} > ./temp/size/number.txt
+ echo ${{ github.base_ref }} > ./temp/size/base.txt
+
+ - name: Upload Size Data
+ uses: actions/upload-artifact@v4
+ with:
+ name: size-data
+ path: temp/size
diff --git a/.github/workflows/size-report.yml b/.github/workflows/size-report.yml
new file mode 100644
index 00000000000..66b5ad0ef29
--- /dev/null
+++ b/.github/workflows/size-report.yml
@@ -0,0 +1,85 @@
+name: size report
+
+on:
+ workflow_run:
+ workflows: ['size data']
+ types:
+ - completed
+
+permissions:
+ contents: read
+ pull-requests: write
+ issues: write
+
+env:
+ PUPPETEER_SKIP_DOWNLOAD: 'true'
+
+jobs:
+ size-report:
+ runs-on: ubuntu-latest
+ if: >
+ github.repository == 'vuejs/core' &&
+ github.event.workflow_run.event == 'pull_request' &&
+ github.event.workflow_run.conclusion == 'success'
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4.1.0
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ cache: pnpm
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Download Size Data
+ uses: dawidd6/action-download-artifact@v9
+ with:
+ name: size-data
+ run_id: ${{ github.event.workflow_run.id }}
+ path: temp/size
+
+ - name: Read PR Number
+ id: pr-number
+ uses: juliangruber/read-file-action@v1
+ with:
+ path: temp/size/number.txt
+
+ - name: Read base branch
+ id: pr-base
+ uses: juliangruber/read-file-action@v1
+ with:
+ path: temp/size/base.txt
+
+ - name: Download Previous Size Data
+ uses: dawidd6/action-download-artifact@v9
+ with:
+ branch: ${{ steps.pr-base.outputs.content }}
+ workflow: size-data.yml
+ event: push
+ name: size-data
+ path: temp/size-prev
+ if_no_artifact_found: warn
+
+ - name: Prepare report
+ run: node scripts/size-report.js > size-report.md
+
+ - name: Read Size Report
+ id: size-report
+ uses: juliangruber/read-file-action@v1
+ with:
+ path: ./size-report.md
+
+ - name: Create Comment
+ uses: actions-cool/maintain-one-comment@v3
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ number: ${{ steps.pr-number.outputs.content }}
+ body: |
+ ${{ steps.size-report.outputs.content }}
+
+ body-include: ''
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000000..1122eb35573
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,111 @@
+name: 'test'
+
+on: workflow_call
+
+permissions:
+ contents: read # to fetch code (actions/checkout)
+
+jobs:
+ unit-test:
+ runs-on: ubuntu-latest
+ env:
+ PUPPETEER_SKIP_DOWNLOAD: 'true'
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4.1.0
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ cache: 'pnpm'
+
+ - run: pnpm install
+
+ - name: Run unit tests
+ run: pnpm run test-unit
+
+ unit-test-windows:
+ runs-on: windows-latest
+ env:
+ PUPPETEER_SKIP_DOWNLOAD: 'true'
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4.1.0
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ cache: 'pnpm'
+
+ - run: pnpm install
+
+ - name: Run compiler unit tests
+ run: pnpm run test-unit compiler
+
+ - name: Run ssr unit tests
+ run: pnpm run test-unit server-renderer
+
+ e2e-test:
+ 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.1.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
+
+ - name: verify treeshaking
+ run: node scripts/verify-treeshaking.js
+
+ lint-and-test-dts:
+ runs-on: ubuntu-latest
+ env:
+ PUPPETEER_SKIP_DOWNLOAD: 'true'
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4.1.0
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ cache: 'pnpm'
+
+ - run: pnpm install
+
+ - name: Run eslint
+ run: pnpm run lint
+
+ - name: Run prettier
+ run: pnpm run format-check
+
+ - name: Run tsc
+ run: pnpm run check
+
+ - name: Run type declaration tests
+ run: pnpm run test-dts
diff --git a/.gitignore b/.gitignore
index 1487e3b7c21..9dd21f59bf6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,7 @@ explorations
TODOs.md
*.log
.idea
+.eslintcache
+dts-build/packages
+*.tsbuildinfo
+*.tgz
diff --git a/.node-version b/.node-version
new file mode 100644
index 00000000000..7d41c735d71
--- /dev/null
+++ b/.node-version
@@ -0,0 +1 @@
+22.14.0
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000000..ca3c40849fd
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,3 @@
+dist
+pnpm-lock.yaml
+CHANGELOG*.md
diff --git a/.prettierrc b/.prettierrc
index ef93d94821a..759232e7cf6 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,5 +1,5 @@
-semi: false
-singleQuote: true
-printWidth: 80
-trailingComma: 'none'
-arrowParens: 'avoid'
+{
+ "semi": false,
+ "singleQuote": true,
+ "arrowParens": "avoid"
+}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 00000000000..91ebd56925c
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["vitest.explorer"]
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index b63ffc79b80..9fc03aa9bc4 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,24 +5,15 @@
"version": "0.2.0",
"configurations": [
{
- "name": "Jest",
"type": "node",
"request": "launch",
- "program": "${workspaceFolder}/node_modules/.bin/jest",
- "stopOnEntry": false,
- "args": ["${fileBasename}", "--runInBand", "--detectOpenHandles"],
- "cwd": "${workspaceFolder}",
- "preLaunchTask": null,
- "runtimeExecutable": null,
- "runtimeArgs": ["--nolazy"],
- "env": {
- "NODE_ENV": "development"
- },
- "console": "integratedTerminal",
- "sourceMaps": true,
- "windows": {
- "program": "${workspaceFolder}/node_modules/jest/bin/jest",
- }
+ "name": "Vitest - Debug Current Test File",
+ "autoAttachChildProcesses": true,
+ "skipFiles": ["/**", "**/node_modules/**"],
+ "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
+ "args": ["run", "${relativeFile}"],
+ "smartStep": true,
+ "console": "integratedTerminal"
}
]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1dcc2819c28..302428290b9 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -13,5 +13,6 @@
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
- }
+ },
+ "editor.formatOnSave": true
}
diff --git a/.well-known/funding-manifest-urls b/.well-known/funding-manifest-urls
new file mode 100644
index 00000000000..f26079d4138
--- /dev/null
+++ b/.well-known/funding-manifest-urls
@@ -0,0 +1 @@
+https://vuejs.org/funding.json
diff --git a/BACKERS.md b/BACKERS.md
index fa66d206698..d8eb697f957 100644
--- a/BACKERS.md
+++ b/BACKERS.md
@@ -1,9 +1,9 @@
Sponsors & Backers
-Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of the awesome sponsors and backers listed in this file. If you'd like to join them, please consider [ sponsor Vue's development](https://vuejs.org/sponsor/).
+Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of the awesome sponsors and backers listed in this file. If you'd like to join them, please consider [ sponsoring Vue's development](https://vuejs.org/sponsor/).
-
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 111f3e200fa..51e718af1e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,2619 +1,595 @@
-## [3.2.31](https://github.com/vuejs/core/compare/v3.2.30...v3.2.31) (2022-02-12)
+## [3.5.16](https://github.com/vuejs/core/compare/v3.5.15...v3.5.16) (2025-05-29)
-### Bug Fixes
-
-* **compiler-ssr:** no need to inject resolveDirective calls for setup custom directives ([436c500](https://github.com/vuejs/core/commit/436c500d2c418930652fededc4882540dcd0c987))
-* **runtime-core:** allow spying on proxy methods ([#4216](https://github.com/vuejs/core/issues/4216)) ([8457d8b](https://github.com/vuejs/core/commit/8457d8b980674b09547edb2dae28091306fe6aa8))
-* **ssr:** always hydrate children for HMR ([#5406](https://github.com/vuejs/core/issues/5406)) ([0342fae](https://github.com/vuejs/core/commit/0342fae8ad0e71866e9b9725a1f9c471db775c76)), closes [#5405](https://github.com/vuejs/core/issues/5405)
-
-
-
-## [3.2.30](https://github.com/vuejs/core/compare/v3.2.29...v3.2.30) (2022-02-07)
-
-
-### Features
-
-* **ssr:** support custom directive getSSRProps in optimized compilation ([60cf175](https://github.com/vuejs/core/commit/60cf175d88236db2c2a4a02900c92e26ceea0073)), closes [#5304](https://github.com/vuejs/core/issues/5304)
-
-
-### Performance Improvements
-
-* **reactivity:** optimize effect/effectScope active state tracking ([2993a24](https://github.com/vuejs/core/commit/2993a246181df12e367b7abdfce0954244e8f7ec))
-
-
-
-## [3.2.29](https://github.com/vuejs/vue-next/compare/v3.2.28...v3.2.29) (2022-01-23)
-
-
-### Bug Fixes
-
-* **compiler-sfc:** fix css v-bind inside other css functions ([16fa18d](https://github.com/vuejs/vue-next/commit/16fa18da6dbbc52c89f9ea729816e1e70ab0d388)), closes [#5302](https://github.com/vuejs/vue-next/issues/5302) [#5306](https://github.com/vuejs/vue-next/issues/5306)
-* **reactivity:** ensure readonly refs can be replaced with new refs in reactive objects ([#5310](https://github.com/vuejs/vue-next/issues/5310)) ([4be1037](https://github.com/vuejs/vue-next/commit/4be1037f31e169d667059c44364fc3e43803accb)), closes [#5307](https://github.com/vuejs/vue-next/issues/5307)
-* **runtime-dom:** fix static content re-insertion ([9aa5dfd](https://github.com/vuejs/vue-next/commit/9aa5dfd4bb8efac0041e33ef5fdbebab59cc6516)), closes [#5308](https://github.com/vuejs/vue-next/issues/5308)
-
-
-
-## 3.2.28 (2022-01-21)
-
-* build: fix build script ([3d80b15](https://github.com/vuejs/vue-next/commit/3d80b15))
-* fix(compat): convertLegacyVModelProps should merge model option in mixins (#5251) ([72130ac](https://github.com/vuejs/vue-next/commit/72130ac)), closes [#5251](https://github.com/vuejs/vue-next/issues/5251)
-* fix(compat): ensure fallthrough *Native events are not dropped during props update (#5228) ([97f6bd9](https://github.com/vuejs/vue-next/commit/97f6bd9)), closes [#5228](https://github.com/vuejs/vue-next/issues/5228)
-* fix(compat): simulate Vue 2.6.14 version in compat build (#5293) ([d0b9708](https://github.com/vuejs/vue-next/commit/d0b9708)), closes [#5293](https://github.com/vuejs/vue-next/issues/5293)
-* fix(compiler-core): handle v-memo in template v-for (#5291) ([9f55e6f](https://github.com/vuejs/vue-next/commit/9f55e6f)), closes [#5291](https://github.com/vuejs/vue-next/issues/5291) [#5288](https://github.com/vuejs/vue-next/issues/5288)
-* fix(compiler-sfc): support complex expression in CSS v-bind() (#5114) ([95d49bf](https://github.com/vuejs/vue-next/commit/95d49bf)), closes [#5114](https://github.com/vuejs/vue-next/issues/5114) [#5109](https://github.com/vuejs/vue-next/issues/5109)
-* fix(compiler-sfc/reactivity-transform): fix edge case where normal script has ref macros but script ([4768f26](https://github.com/vuejs/vue-next/commit/4768f26))
-* fix(reactivity-transform): apply transform for labelled variable declarations ([a05b000](https://github.com/vuejs/vue-next/commit/a05b000)), closes [/github.com/vuejs/core/issues/5298#issuecomment-1017970061](https://github.com//github.com/vuejs/core/issues/5298/issues/issuecomment-1017970061)
-* fix(reactivity-transform): apply transform on exported variable declarations ([a81a992](https://github.com/vuejs/vue-next/commit/a81a992)), closes [#5298](https://github.com/vuejs/vue-next/issues/5298)
-* fix(reactivity): differentiate shallow/deep proxies of same target when nested in reactive ([9c304bf](https://github.com/vuejs/vue-next/commit/9c304bf)), closes [#5271](https://github.com/vuejs/vue-next/issues/5271)
-* fix(reactivity): mutating a readonly ref nested in a reactive object should fail. (#5048) ([171f5e9](https://github.com/vuejs/vue-next/commit/171f5e9)), closes [#5048](https://github.com/vuejs/vue-next/issues/5048) [#5042](https://github.com/vuejs/vue-next/issues/5042)
-* fix(runtime-core): ensure mergeProps skips undefined event handlers (#5299) ([c35ec47](https://github.com/vuejs/vue-next/commit/c35ec47)), closes [#5299](https://github.com/vuejs/vue-next/issues/5299) [#5296](https://github.com/vuejs/vue-next/issues/5296)
-* fix(ssr): only cache computed getters during render phase ([2f91872](https://github.com/vuejs/vue-next/commit/2f91872)), closes [#5300](https://github.com/vuejs/vue-next/issues/5300)
-* fix(types): calling readonly() with ref() should return Readonly[> (#5212) ([c64907d](https://github.com/vuejs/vue-next/commit/c64907d)), closes [#5212](https://github.com/vuejs/vue-next/issues/5212)
-* refactor: includes instead of indexOf (#5117) ([63210fe](https://github.com/vuejs/vue-next/commit/63210fe)), closes [#5117](https://github.com/vuejs/vue-next/issues/5117)
-* chore: bump marked ([0c06c74](https://github.com/vuejs/vue-next/commit/0c06c74))
-* chore: comment dom tag config usage [ci skip] ([b2bac9f](https://github.com/vuejs/vue-next/commit/b2bac9f))
-* chore: fix typo (#5261) [ci skip] ([e603fd2](https://github.com/vuejs/vue-next/commit/e603fd2)), closes [#5261](https://github.com/vuejs/vue-next/issues/5261)
-* chore: fix typo (#5282) [ci skip] ([e802275](https://github.com/vuejs/vue-next/commit/e802275)), closes [#5282](https://github.com/vuejs/vue-next/issues/5282)
-* chore: type improvements (#5264) ([92e04a6](https://github.com/vuejs/vue-next/commit/92e04a6)), closes [#5264](https://github.com/vuejs/vue-next/issues/5264)
-* chore: update repo references ([ae4b078](https://github.com/vuejs/vue-next/commit/ae4b078))
-* perf(reactivity): optimize effect run condition ([25bc654](https://github.com/vuejs/vue-next/commit/25bc654))
-* feat(reactivity): add isShallow api ([9fda941](https://github.com/vuejs/vue-next/commit/9fda941))
-* docs(contributing): missing structure info for compiler-sfc (#3559) [ci skip] ([8cbfe09](https://github.com/vuejs/vue-next/commit/8cbfe09)), closes [#3559](https://github.com/vuejs/vue-next/issues/3559)
-
-
-
-## [3.2.27](https://github.com/vuejs/core/compare/v3.2.26...v3.2.27) (2022-01-16)
-
-
-### Bug Fixes
-
-* **KeepAlive:** remove cached VNode properly ([#5260](https://github.com/vuejs/core/issues/5260)) ([2e3e183](https://github.com/vuejs/core/commit/2e3e183b4f19c9e25865e35438653cbc9bf01afc)), closes [#5258](https://github.com/vuejs/core/issues/5258)
-* **reactivity-transform:** should not rewrite for...in / for...of scope variables ([7007ffb](https://github.com/vuejs/core/commit/7007ffb2c796d6d56b9c8e278c54dc1cefd7b58f))
-* **sfc-playground:** hide title to avoid overlap ([#5099](https://github.com/vuejs/core/issues/5099)) ([44b9527](https://github.com/vuejs/core/commit/44b95276f5c086e1d88fa3c686a5f39eb5bb7821))
-* **ssr:** make computed inactive during ssr, fix memory leak ([f4f0966](https://github.com/vuejs/core/commit/f4f0966b33863ac0fca6a20cf9e8ddfbb311ae87)), closes [#5208](https://github.com/vuejs/core/issues/5208)
-* **ssr:** remove missing ssr directive transform error ([55cc4af](https://github.com/vuejs/core/commit/55cc4af25e6f4924b267620bd965e496f260d41a))
-* **types/tsx:** allow ref_for type on tsx elements ([78df8c7](https://github.com/vuejs/core/commit/78df8c78c4539d2408278d1a11612b6bbc47d22f))
-* **types:** fix shallowReadonly type ([92f11d6](https://github.com/vuejs/core/commit/92f11d6740929f5b591740e30ae5fba50940ec82))
-* **types:** handle ToRef] ([5ac7030](https://github.com/vuejs/core/commit/5ac703055fa83cb1e8a173bbd6a4d6c33707a3c3)), closes [#5188](https://github.com/vuejs/core/issues/5188)
-* **types:** KeepAlive match pattern should allow mixed array ([3007d5b](https://github.com/vuejs/core/commit/3007d5b4cafed1da445bc498f771bd2c79eda6fc))
-
-
-### Features
-
-* **types:** simplify `ExtractPropTypes` to avoid props JSDocs being removed ([#5166](https://github.com/vuejs/core/issues/5166)) ([a570b38](https://github.com/vuejs/core/commit/a570b38741a7dc259772c5ccce7ea8a1638eb0bd))
-
-
-### Performance Improvements
-
-* improve memory usage for static vnodes ([ed9eb62](https://github.com/vuejs/core/commit/ed9eb62e5992bd575d999c4197330d8bad622cfb))
-
-
-
-## [3.2.26](https://github.com/vuejs/core/compare/v3.2.25...v3.2.26) (2021-12-12)
-
-
-
-## [3.2.25](https://github.com/vuejs/core/compare/v3.2.24...v3.2.25) (2021-12-12)
-
-
-### Bug Fixes
-
-* **compiler-sfc:** generate valid TS in script and script setup co-usage with TS ([7e4f0a8](https://github.com/vuejs/core/commit/7e4f0a869498e7dce601e7c150f402045ea2e79b)), closes [#5094](https://github.com/vuejs/core/issues/5094)
-* **compiler:** force block for custom dirs and inline beforeUpdate hooks ([1c9a481](https://github.com/vuejs/core/commit/1c9a4810fcdd2b6c1c6c3be077aebbecbfcbcf1e))
-* **runtime-core:** disallow recurse in vnode/directive beforeUpdate hooks ([a1167c5](https://github.com/vuejs/core/commit/a1167c57e5514be57505f4bce8d163aa1f92cf14))
-
-
-### Features
-
-* **compiler-core:** support aliasing vue: prefixed events to inline vnode hooks ([4b0ca87](https://github.com/vuejs/core/commit/4b0ca8709a7e2652f4b02665f378d47ba4dbe969))
-* **experimental:** allow const for ref sugar declarations ([9823bd9](https://github.com/vuejs/core/commit/9823bd95d11f22f0ae53f5e0b705a21b6e6e8859))
-* **reactivity-transform/types:** restructure macro types + export types for all shorthand methods ([db729ce](https://github.com/vuejs/core/commit/db729ce99eb13cd18dad600055239c63edd9cfb8))
-* **reactivity-transform:** $$() escape for destructured prop bindings ([198ca14](https://github.com/vuejs/core/commit/198ca14f192f9eb80028153f3d36600e636de3f0))
-* **reactivity-transform:** rename @vue/ref-transform to @vue/reactivity-transform ([d70fd8d](https://github.com/vuejs/core/commit/d70fd8d36b23c987f2ebe3280da785f4d2e7d2ef))
-* **reactivity-transform:** support $-shorthands for all ref-creating APIs ([179fc05](https://github.com/vuejs/core/commit/179fc05a8406eac525c8450153b42fcb5af7d6bb))
-* **reactivity-transform:** support optionally importing macros ([fbd0fe9](https://github.com/vuejs/core/commit/fbd0fe97595f759e12e445c713b732775589fabf))
-* **reactivity-transform:** use toRef() for $() destructure codegen ([93ba6b9](https://github.com/vuejs/core/commit/93ba6b974e4a2ff4ba004fef47ef69cfe980c654))
-* **reactivity:** support default value in toRef() ([2db9c90](https://github.com/vuejs/core/commit/2db9c909c2cf3845f57b2c930c05cd6c17abe3b0))
-* **sfc-playground:** add github link ([#5067](https://github.com/vuejs/core/issues/5067)) ([9ac0dde](https://github.com/vuejs/core/commit/9ac0ddea4beec1a1c4471463d3476ccd019bd84e))
-* **sfc-playground:** prevent ctrl+s default behavior ([#5066](https://github.com/vuejs/core/issues/5066)) ([b027507](https://github.com/vuejs/core/commit/b0275070e4824c5efa868528f610eaced83d8fbc))
-* support ref in v-for, remove compat deprecation warnings ([41c18ef](https://github.com/vuejs/core/commit/41c18effea9dd32ab899b5de3bb0513abdb52ee4))
-
-
-
-## [3.2.24](https://github.com/vuejs/core/compare/v3.2.23...v3.2.24) (2021-12-06)
-
-
-### Bug Fixes
-
-* **compat:** maintain compatConfig option in legacy functional comp ([#4974](https://github.com/vuejs/core/issues/4974)) ([ee97cf5](https://github.com/vuejs/core/commit/ee97cf5a4db9e4f135d8eb25aff725eb37363675))
-* **compiler-dom:** avoid bailing stringification on setup const bindings ([29beda7](https://github.com/vuejs/core/commit/29beda7c6f69f79e65f0111cb2d2b8d57d8257bb))
-* **compiler-sfc:** make asset url imports stringifiable ([87c73e9](https://github.com/vuejs/core/commit/87c73e99d6aed0771f8c955ca9d5188ec22c90e7))
-* **package:** ensure ref-macros export is recognized by vue-tsc ([#5003](https://github.com/vuejs/core/issues/5003)) ([f855269](https://github.com/vuejs/core/commit/f8552697fbbdbd444d8322c6b6adeb48cc0b5617))
-* **runtime-core:** handle initial undefined attrs ([#5017](https://github.com/vuejs/core/issues/5017)) ([6d887aa](https://github.com/vuejs/core/commit/6d887aaf591cfa05d5fea978bbd87e3e502bfa86)), closes [#5016](https://github.com/vuejs/core/issues/5016)
-* **types/reactivity:** export ShallowRef type ([#5026](https://github.com/vuejs/core/issues/5026)) ([523b4b7](https://github.com/vuejs/core/commit/523b4b78f5d2e11f1822e09c324a854c790a7863)), closes [#5205](https://github.com/vuejs/core/issues/5205)
-
-
-### Features
-
-* **types/script-setup:** add generic type to defineExpose ([#5035](https://github.com/vuejs/core/issues/5035)) ([34985fe](https://github.com/vuejs/core/commit/34985fee6b23018b6eb6322239db6165c1b0e273))
-
-
-
-## [3.2.23](https://github.com/vuejs/core/compare/v3.2.22...v3.2.23) (2021-11-26)
-
-
-### Bug Fixes
-
-* **reactivity:** retain readonly proxies when setting as reactive property ([d145128](https://github.com/vuejs/core/commit/d145128ab400f4563eb3727626d0942ea5f4980a)), closes [#4986](https://github.com/vuejs/core/issues/4986)
-* **runtime-core:** fix component public instance has check for accessed non-existent properties ([aac0466](https://github.com/vuejs/core/commit/aac0466cb8819fd132fbcc9c4d3e1014c14e2ad8)), closes [#4962](https://github.com/vuejs/core/issues/4962)
-* **runtime-core:** handle error in async KeepAlive hooks ([#4978](https://github.com/vuejs/core/issues/4978)) ([820a143](https://github.com/vuejs/core/commit/820a14345798edc0ab673bae8ce3181e479d9cca))
-* **runtime-dom:** fix option element value patching edge case ([#4959](https://github.com/vuejs/core/issues/4959)) ([89b2f92](https://github.com/vuejs/core/commit/89b2f924fc82d7f71dcb8ffbacb386fd5cf9ade2)), closes [#4956](https://github.com/vuejs/core/issues/4956)
-* **runtime-dom:** patchDOMProps should not set _value if element is custom element ([#4839](https://github.com/vuejs/core/issues/4839)) ([1701bf3](https://github.com/vuejs/core/commit/1701bf3968f001dd3a2bc9f41e3e7e0f1b13e922))
-* **types:** export ref-macros.d.ts ([1245709](https://github.com/vuejs/core/commit/124570973df4ddfdd38e43bf1e92b9710321e5d9))
-* **types:** fix propType type inference ([#4985](https://github.com/vuejs/core/issues/4985)) ([3c449cd](https://github.com/vuejs/core/commit/3c449cd408840d35987fb32b39737fbf093809d6)), closes [#4983](https://github.com/vuejs/core/issues/4983)
-* **types:** scrip-setup+ts: ensure proper handling of `null` as default prop value. ([#4979](https://github.com/vuejs/core/issues/4979)) ([f2d2d7b](https://github.com/vuejs/core/commit/f2d2d7b2d236f256531ae9ad2048bd939c92d834)), closes [#4868](https://github.com/vuejs/core/issues/4868)
-
-
-### Features
-
-* **compiler-sfc:** export resolveTemplateUsageCheckString for HMR plugin use ([#4908](https://github.com/vuejs/core/issues/4908)) ([c61baac](https://github.com/vuejs/core/commit/c61baac75a03b938bc728a8de961ba93736a0ff6))
-* **compiler-sfc:** expose properties for more accurate HMR ([68c45e7](https://github.com/vuejs/core/commit/68c45e73da902e715df9614800a7ab43d6579198)), closes [#4358](https://github.com/vuejs/core/issues/4358) [#4908](https://github.com/vuejs/core/issues/4908)
-
-
-
-## [3.2.22](https://github.com/vuejs/core/compare/v3.2.21...v3.2.22) (2021-11-15)
-
-
-### Bug Fixes
-
-* **compiler-sfc:** add type for props include Function in prod mode ([#4938](https://github.com/vuejs/core/issues/4938)) ([9c42a1e](https://github.com/vuejs/core/commit/9c42a1e2a3385f3b33faed5cdcc430bf8c1fc4b2))
-* **compiler-sfc:** add type for props's properties in prod mode ([#4790](https://github.com/vuejs/core/issues/4790)) ([090df08](https://github.com/vuejs/core/commit/090df0837eb0aedd8a02fd0107b7668ca5c136a1)), closes [#4783](https://github.com/vuejs/core/issues/4783)
-* **compiler-sfc:** externalRE support automatic http/https prefix url pattern ([#4922](https://github.com/vuejs/core/issues/4922)) ([574070f](https://github.com/vuejs/core/commit/574070f43f804fd855f4ee319936ec770a56cef0)), closes [#4920](https://github.com/vuejs/core/issues/4920)
-* **compiler-sfc:** fix expose codegen edge case ([#4919](https://github.com/vuejs/core/issues/4919)) ([31fd590](https://github.com/vuejs/core/commit/31fd590fd47e2dc89b84687ffe26a5c6f05fea34)), closes [#4917](https://github.com/vuejs/core/issues/4917)
-* **devtool:** improve devtools late injection browser env detection ([#4890](https://github.com/vuejs/core/issues/4890)) ([fa2237f](https://github.com/vuejs/core/commit/fa2237f1d824eac511c4246135318594c48dc121))
-* **runtime-core:** improve dedupe listeners when attr fallthrough ([#4912](https://github.com/vuejs/core/issues/4912)) ([b4eb7e3](https://github.com/vuejs/core/commit/b4eb7e3866d7dc722d93a48f4faae1696d4e7023)), closes [#4859](https://github.com/vuejs/core/issues/4859)
-* **types/sfc:** fix withDefaults type inference when using union types ([#4925](https://github.com/vuejs/core/issues/4925)) ([04e5835](https://github.com/vuejs/core/commit/04e58351965caf489ac68e4961ef70448d954912))
-
-
-
-## [3.2.21](https://github.com/vuejs/core/compare/v3.2.20...v3.2.21) (2021-11-02)
-
-
-### Bug Fixes
-
-* **custom-element:** fix custom element props access on initial render ([4b7f76e](https://github.com/vuejs/core/commit/4b7f76e36a7fc650986a20eca258f7a5d912424f)), closes [#4792](https://github.com/vuejs/core/issues/4792)
-* **custom-element:** fix initial attr type casting for programmtically created elements ([3ca8317](https://github.com/vuejs/core/commit/3ca83179d1a798f65e4e70215c511e2f1b64adb6)), closes [#4772](https://github.com/vuejs/core/issues/4772)
-* **devtools:** avoid open handle in non-browser env ([6916d72](https://github.com/vuejs/core/commit/6916d725a06a57e92ff9d046ccf132c305cd0a51)), closes [#4815](https://github.com/vuejs/core/issues/4815)
-* **devtools:** fix memory leak when devtools is not installed ([#4833](https://github.com/vuejs/core/issues/4833)) ([6b32f0d](https://github.com/vuejs/core/commit/6b32f0d976c0aac8bb2c1b78fedd03e76fb391eb)), closes [#4829](https://github.com/vuejs/core/issues/4829)
-* **runtime-core:** add `v-memo` to built-in directives check ([#4787](https://github.com/vuejs/core/issues/4787)) ([5eb7263](https://github.com/vuejs/core/commit/5eb72630a53a8dd82c2b8a9705c21a8075161a3d))
-* **runtime-dom:** fix behavior regression for v-show + style display binding ([3f38d59](https://github.com/vuejs/core/commit/3f38d599f5aacdd3eeaa9475251a24f74e7ae3b4)), closes [#4768](https://github.com/vuejs/core/issues/4768)
-* **types:** fix ref unwrapping type inference for nested shallowReactive & shallowRef ([20a3615](https://github.com/vuejs/core/commit/20a361541cc5faffa82cbf3f2d49639a97b3b678)), closes [#4771](https://github.com/vuejs/core/issues/4771)
-
-
-
-## [3.2.20](https://github.com/vuejs/core/compare/v3.2.19...v3.2.20) (2021-10-08)
-
-
-### Bug Fixes
-
-* **compiler-sfc:** fix props codegen w/ leading import ([d4c04e9](https://github.com/vuejs/core/commit/d4c04e979934b81a30467aa4b1e717175b9b2d80)), closes [#4764](https://github.com/vuejs/core/issues/4764)
-* **compiler-sfc:** support runtime Enum in normal script ([#4698](https://github.com/vuejs/core/issues/4698)) ([f66d456](https://github.com/vuejs/core/commit/f66d456b7a39db9dae7e70c28bb431ff293d8fef))
-* **devtools:** clear devtools buffer after timeout ([f4639e0](https://github.com/vuejs/core/commit/f4639e0a36abe16828b202d7297e1486653b1217)), closes [#4738](https://github.com/vuejs/core/issues/4738)
-* **hmr:** fix hmr for components with no active instance yet ([9e3d773](https://github.com/vuejs/core/commit/9e3d7731c7839638f49157123c6b372fec9e4d46)), closes [#4757](https://github.com/vuejs/core/issues/4757)
-* **types:** ensure that DeepReadonly handles Ref type properly ([#4714](https://github.com/vuejs/core/issues/4714)) ([ed0071a](https://github.com/vuejs/core/commit/ed0071ac1a6d18439f3212711c6901fbb7193288))
-* **types:** make `toRef` return correct type(fix [#4732](https://github.com/vuejs/core/issues/4732)) ([#4734](https://github.com/vuejs/core/issues/4734)) ([925bc34](https://github.com/vuejs/core/commit/925bc346fe85091467fcd2e40d6c1ff07f3b51c4))
-
-
-### Features
-
-* **compiler-sfc:** `
+
+
+
+
+
+
+
diff --git a/packages-private/sfc-playground/src/Header.vue b/packages-private/sfc-playground/src/Header.vue
new file mode 100644
index 00000000000..bf1c9bad6eb
--- /dev/null
+++ b/packages-private/sfc-playground/src/Header.vue
@@ -0,0 +1,318 @@
+
+
+
+
+
+
+ Vue SFC Playground
+
+
+
+
+
+
diff --git a/packages-private/sfc-playground/src/VersionSelect.vue b/packages-private/sfc-playground/src/VersionSelect.vue
new file mode 100644
index 00000000000..3a30e497f97
--- /dev/null
+++ b/packages-private/sfc-playground/src/VersionSelect.vue
@@ -0,0 +1,151 @@
+
+
+
+
+
+ {{ label }}
+ {{ version }}
+
+
+
+
+
+
+
diff --git a/packages/sfc-playground/src/download/download.ts b/packages-private/sfc-playground/src/download/download.ts
similarity index 67%
rename from packages/sfc-playground/src/download/download.ts
rename to packages-private/sfc-playground/src/download/download.ts
index dd7e3761d67..6b051abae19 100644
--- a/packages/sfc-playground/src/download/download.ts
+++ b/packages-private/sfc-playground/src/download/download.ts
@@ -5,8 +5,9 @@ import main from './template/main.js?raw'
import pkg from './template/package.json?raw'
import config from './template/vite.config.js?raw'
import readme from './template/README.md?raw'
+import type { ReplStore } from '@vue/repl'
-export async function downloadProject(store: any) {
+export async function downloadProject(store: ReplStore) {
if (!confirm('Download project files?')) {
return
}
@@ -16,7 +17,10 @@ export async function downloadProject(store: any) {
// basic structure
zip.file('index.html', index)
- zip.file('package.json', pkg)
+ zip.file(
+ 'package.json',
+ pkg.replace(`"vue": "latest"`, `"vue": "${store.vueVersion || 'latest'}"`),
+ )
zip.file('vite.config.js', config)
zip.file('README.md', readme)
@@ -26,7 +30,11 @@ export async function downloadProject(store: any) {
const files = store.getFiles()
for (const file in files) {
- src.file(file, files[file])
+ if (file !== 'import-map.json' && file !== 'tsconfig.json') {
+ src.file(file, files[file])
+ } else {
+ zip.file(file, files[file])
+ }
}
const blob = await zip.generateAsync({ type: 'blob' })
diff --git a/packages/sfc-playground/src/download/template/README.md b/packages-private/sfc-playground/src/download/template/README.md
similarity index 60%
rename from packages/sfc-playground/src/download/template/README.md
rename to packages-private/sfc-playground/src/download/template/README.md
index 39c47d255ae..91b21489fd5 100644
--- a/packages/sfc-playground/src/download/template/README.md
+++ b/packages-private/sfc-playground/src/download/template/README.md
@@ -1,6 +1,6 @@
# Vite Vue Starter
-This is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.org) v12+.
+This is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.org) version 18+ or 20+.
To start:
@@ -11,4 +11,8 @@ npm run dev
# if using yarn:
yarn
yarn dev
+
+# if using pnpm:
+pnpm install
+pnpm run dev
```
diff --git a/packages/sfc-playground/src/download/template/index.html b/packages-private/sfc-playground/src/download/template/index.html
similarity index 95%
rename from packages/sfc-playground/src/download/template/index.html
rename to packages-private/sfc-playground/src/download/template/index.html
index 030a6ff51bf..e631329c1b0 100644
--- a/packages/sfc-playground/src/download/template/index.html
+++ b/packages-private/sfc-playground/src/download/template/index.html
@@ -1,4 +1,4 @@
-
+
diff --git a/packages/sfc-playground/src/download/template/main.js b/packages-private/sfc-playground/src/download/template/main.js
similarity index 100%
rename from packages/sfc-playground/src/download/template/main.js
rename to packages-private/sfc-playground/src/download/template/main.js
diff --git a/packages/sfc-playground/src/download/template/package.json b/packages-private/sfc-playground/src/download/template/package.json
similarity index 63%
rename from packages/sfc-playground/src/download/template/package.json
rename to packages-private/sfc-playground/src/download/template/package.json
index 4f6bdd4a42c..b9bb278edf3 100644
--- a/packages/sfc-playground/src/download/template/package.json
+++ b/packages-private/sfc-playground/src/download/template/package.json
@@ -1,17 +1,17 @@
{
"name": "vite-vue-starter",
"version": "0.0.0",
+ "type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
- "vue": "^3.2.0"
+ "vue": "latest"
},
"devDependencies": {
- "@vitejs/plugin-vue": "^1.4.0",
- "@vue/compiler-sfc": "^3.2.0",
- "vite": "^2.4.4"
+ "@vitejs/plugin-vue": "^5.2.4",
+ "vite": "^6.3.5"
}
-}
\ No newline at end of file
+}
diff --git a/packages/sfc-playground/src/download/template/vite.config.js b/packages-private/sfc-playground/src/download/template/vite.config.js
similarity index 87%
rename from packages/sfc-playground/src/download/template/vite.config.js
rename to packages-private/sfc-playground/src/download/template/vite.config.js
index 315212d69a7..05c17402a4a 100644
--- a/packages/sfc-playground/src/download/template/vite.config.js
+++ b/packages-private/sfc-playground/src/download/template/vite.config.js
@@ -3,5 +3,5 @@ import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [vue()]
+ plugins: [vue()],
})
diff --git a/packages-private/sfc-playground/src/icons/Copy.vue b/packages-private/sfc-playground/src/icons/Copy.vue
new file mode 100644
index 00000000000..f3851da63cc
--- /dev/null
+++ b/packages-private/sfc-playground/src/icons/Copy.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/packages/sfc-playground/src/icons/Download.vue b/packages-private/sfc-playground/src/icons/Download.vue
similarity index 88%
rename from packages/sfc-playground/src/icons/Download.vue
rename to packages-private/sfc-playground/src/icons/Download.vue
index a7c4ed18488..c5caec7098c 100644
--- a/packages/sfc-playground/src/icons/Download.vue
+++ b/packages-private/sfc-playground/src/icons/Download.vue
@@ -1,6 +1,6 @@
-
-
+
+
+
+
+
+
diff --git a/packages-private/sfc-playground/src/icons/Moon.vue b/packages-private/sfc-playground/src/icons/Moon.vue
new file mode 100644
index 00000000000..21f393d4d6e
--- /dev/null
+++ b/packages-private/sfc-playground/src/icons/Moon.vue
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/packages-private/sfc-playground/src/icons/Reload.vue b/packages-private/sfc-playground/src/icons/Reload.vue
new file mode 100644
index 00000000000..5ec5be80889
--- /dev/null
+++ b/packages-private/sfc-playground/src/icons/Reload.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/packages/sfc-playground/src/icons/Share.vue b/packages-private/sfc-playground/src/icons/Share.vue
similarity index 93%
rename from packages/sfc-playground/src/icons/Share.vue
rename to packages-private/sfc-playground/src/icons/Share.vue
index 829fcbe2373..8dd9ccd84b3 100644
--- a/packages/sfc-playground/src/icons/Share.vue
+++ b/packages-private/sfc-playground/src/icons/Share.vue
@@ -2,7 +2,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/sfc-playground/src/main.ts b/packages-private/sfc-playground/src/main.ts
similarity index 72%
rename from packages/sfc-playground/src/main.ts
rename to packages-private/sfc-playground/src/main.ts
index e645bb2bd0f..7be63408035 100644
--- a/packages/sfc-playground/src/main.ts
+++ b/packages-private/sfc-playground/src/main.ts
@@ -1,10 +1,9 @@
import { createApp } from 'vue'
import App from './App.vue'
-import '@vue/repl/style.css'
// @ts-expect-error Custom window property
window.VUE_DEVTOOLS_CONFIG = {
- defaultSelectedAppId: 'id:repl'
+ defaultSelectedAppId: 'repl',
}
createApp(App).mount('#app')
diff --git a/packages-private/sfc-playground/src/vue-dev-proxy-prod.ts b/packages-private/sfc-playground/src/vue-dev-proxy-prod.ts
new file mode 100644
index 00000000000..3b2faf19533
--- /dev/null
+++ b/packages-private/sfc-playground/src/vue-dev-proxy-prod.ts
@@ -0,0 +1,2 @@
+// serve vue to the iframe sandbox during dev.
+export * from 'vue/dist/vue.runtime.esm-browser.prod.js'
diff --git a/packages/sfc-playground/src/vue-dev-proxy.ts b/packages-private/sfc-playground/src/vue-dev-proxy.ts
similarity index 100%
rename from packages/sfc-playground/src/vue-dev-proxy.ts
rename to packages-private/sfc-playground/src/vue-dev-proxy.ts
diff --git a/packages-private/sfc-playground/src/vue-server-renderer-dev-proxy.ts b/packages-private/sfc-playground/src/vue-server-renderer-dev-proxy.ts
new file mode 100644
index 00000000000..f2ceb265609
--- /dev/null
+++ b/packages-private/sfc-playground/src/vue-server-renderer-dev-proxy.ts
@@ -0,0 +1,2 @@
+// serve vue/server-renderer to the iframe sandbox during dev.
+export * from 'vue/server-renderer'
diff --git a/packages-private/sfc-playground/vercel.json b/packages-private/sfc-playground/vercel.json
new file mode 100644
index 00000000000..4511eb79d49
--- /dev/null
+++ b/packages-private/sfc-playground/vercel.json
@@ -0,0 +1,16 @@
+{
+ "github": {
+ "silent": true
+ },
+ "headers": [
+ {
+ "source": "/assets/(.*)",
+ "headers": [
+ {
+ "key": "Cache-Control",
+ "value": "max-age=31536000, immutable"
+ }
+ ]
+ }
+ ]
+}
diff --git a/packages-private/sfc-playground/vite.config.ts b/packages-private/sfc-playground/vite.config.ts
new file mode 100644
index 00000000000..2e77f1970a7
--- /dev/null
+++ b/packages-private/sfc-playground/vite.config.ts
@@ -0,0 +1,59 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { type Plugin, defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import { spawnSync } from 'node:child_process'
+
+const commit = spawnSync('git', ['rev-parse', '--short=7', 'HEAD'])
+ .stdout.toString()
+ .trim()
+
+export default defineConfig({
+ plugins: [
+ vue({
+ script: {
+ fs: {
+ fileExists: fs.existsSync,
+ readFile: file => fs.readFileSync(file, 'utf-8'),
+ },
+ },
+ }),
+ copyVuePlugin(),
+ ],
+ define: {
+ __COMMIT__: JSON.stringify(commit),
+ __VUE_PROD_DEVTOOLS__: JSON.stringify(true),
+ },
+ optimizeDeps: {
+ exclude: ['@vue/repl'],
+ },
+})
+
+function copyVuePlugin(): Plugin {
+ return {
+ name: 'copy-vue',
+ generateBundle() {
+ const copyFile = (file: string) => {
+ const filePath = path.resolve(__dirname, '../../packages', file)
+ const basename = path.basename(file)
+ if (!fs.existsSync(filePath)) {
+ throw new Error(
+ `${basename} not built. ` +
+ `Run "nr build vue -f esm-browser" first.`,
+ )
+ }
+ this.emitFile({
+ type: 'asset',
+ fileName: basename,
+ source: fs.readFileSync(filePath, 'utf-8'),
+ })
+ }
+
+ copyFile(`vue/dist/vue.esm-browser.js`)
+ copyFile(`vue/dist/vue.esm-browser.prod.js`)
+ copyFile(`vue/dist/vue.runtime.esm-browser.js`)
+ copyFile(`vue/dist/vue.runtime.esm-browser.prod.js`)
+ copyFile(`server-renderer/dist/server-renderer.esm-browser.js`)
+ },
+ }
+}
diff --git a/packages/template-explorer/README.md b/packages-private/template-explorer/README.md
similarity index 100%
rename from packages/template-explorer/README.md
rename to packages-private/template-explorer/README.md
diff --git a/packages-private/template-explorer/_redirects b/packages-private/template-explorer/_redirects
new file mode 100644
index 00000000000..9d570fb8259
--- /dev/null
+++ b/packages-private/template-explorer/_redirects
@@ -0,0 +1,2 @@
+https://vue-next-template-explorer.netlify.app https://template-explorer.vuejs.org 301!
+https://vue-next-template-explorer.netlify.app/* https://template-explorer.vuejs.org/:splat 301!
diff --git a/packages-private/template-explorer/index.html b/packages-private/template-explorer/index.html
new file mode 100644
index 00000000000..d1db969f01b
--- /dev/null
+++ b/packages-private/template-explorer/index.html
@@ -0,0 +1,24 @@
+Vue Template Explorer
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages-private/template-explorer/local.html b/packages-private/template-explorer/local.html
new file mode 100644
index 00000000000..c86cdb6b34c
--- /dev/null
+++ b/packages-private/template-explorer/local.html
@@ -0,0 +1,24 @@
+Vue Template Explorer
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/template-explorer/package.json b/packages-private/template-explorer/package.json
similarity index 73%
rename from packages/template-explorer/package.json
rename to packages-private/template-explorer/package.json
index 35eadc58af7..08da34b173e 100644
--- a/packages/template-explorer/package.json
+++ b/packages-private/template-explorer/package.json
@@ -1,7 +1,7 @@
{
"name": "@vue/template-explorer",
- "version": "3.2.31",
"private": true,
+ "version": "0.0.0",
"buildOptions": {
"formats": [
"global"
@@ -11,7 +11,7 @@
"enableNonBrowserBranches": true
},
"dependencies": {
- "monaco-editor": "^0.20.0",
- "source-map": "^0.6.1"
+ "monaco-editor": "^0.52.2",
+ "source-map-js": "^1.2.1"
}
}
diff --git a/packages/template-explorer/src/index.ts b/packages-private/template-explorer/src/index.ts
similarity index 88%
rename from packages/template-explorer/src/index.ts
rename to packages-private/template-explorer/src/index.ts
index d676c717373..988712d623c 100644
--- a/packages/template-explorer/src/index.ts
+++ b/packages-private/template-explorer/src/index.ts
@@ -1,14 +1,18 @@
-import * as m from 'monaco-editor'
-import { compile, CompilerError, CompilerOptions } from '@vue/compiler-dom'
+import type * as m from 'monaco-editor'
+import {
+ type CompilerError,
+ type CompilerOptions,
+ compile,
+} from '@vue/compiler-dom'
import { compile as ssrCompile } from '@vue/compiler-ssr'
import {
- defaultOptions,
compilerOptions,
+ defaultOptions,
initOptions,
- ssrMode
+ ssrMode,
} from './options'
import { toRaw, watchEffect } from '@vue/runtime-dom'
-import { SourceMapConsumer } from 'source-map'
+import { SourceMapConsumer } from 'source-map-js'
import theme from './theme'
declare global {
@@ -30,8 +34,8 @@ const sharedEditorOptions: m.editor.IStandaloneEditorConstructionOptions = {
scrollBeyondLastLine: false,
renderWhitespace: 'selection',
minimap: {
- enabled: false
- }
+ enabled: false,
+ },
}
window.init = () => {
@@ -48,13 +52,13 @@ window.init = () => {
hash = escape(atob(hash))
} catch (e) {}
persistedState = JSON.parse(
- decodeURIComponent(hash) || localStorage.getItem('state') || `{}`
+ decodeURIComponent(hash) || localStorage.getItem('state') || `{}`,
)
} catch (e: any) {
// bad stored state, clear it
console.warn(
'Persisted state in localStorage seems to be corrupted, please reload.\n' +
- e.message
+ e.message,
)
localStorage.clear()
}
@@ -76,18 +80,18 @@ window.init = () => {
const compileFn = ssrMode.value ? ssrCompile : compile
const start = performance.now()
const { code, ast, map } = compileFn(source, {
- filename: 'ExampleTemplate.vue',
...compilerOptions,
+ filename: 'ExampleTemplate.vue',
sourceMap: true,
onError: err => {
errors.push(err)
- }
+ },
})
console.log(`Compiled in ${(performance.now() - start).toFixed(2)}ms.`)
monaco.editor.setModelMarkers(
editor.getModel()!,
`@vue/compiler-dom`,
- errors.filter(e => e.loc).map(formatError)
+ errors.filter(e => e.loc).map(formatError),
)
console.log(`AST: `, ast)
console.log(`Options: `, toRaw(compilerOptions))
@@ -110,7 +114,7 @@ window.init = () => {
endLineNumber: loc.end.line,
endColumn: loc.end.column,
message: `Vue template compilation error: ${err.message}`,
- code: String(err.code)
+ code: String(err.code),
}
}
@@ -123,7 +127,7 @@ window.init = () => {
for (key in compilerOptions) {
const val = compilerOptions[key]
if (typeof val !== 'object' && val !== defaultOptions[key]) {
- // @ts-ignore
+ // @ts-expect-error
optionsToSave[key] = val
}
}
@@ -131,7 +135,7 @@ window.init = () => {
const state = JSON.stringify({
src,
ssr: ssrMode.value,
- options: optionsToSave
+ options: optionsToSave,
} as PersistedState)
localStorage.setItem('state', state)
window.location.hash = btoa(unescape(encodeURIComponent(state)))
@@ -145,21 +149,21 @@ window.init = () => {
value: persistedState?.src || `Hello World
`,
language: 'html',
...sharedEditorOptions,
- wordWrap: 'bounded'
+ wordWrap: 'bounded',
})
editor.getModel()!.updateOptions({
- tabSize: 2
+ tabSize: 2,
})
const output = monaco.editor.create(document.getElementById('output')!, {
value: '',
language: 'javascript',
readOnly: true,
- ...sharedEditorOptions
+ ...sharedEditorOptions,
})
output.getModel()!.updateOptions({
- tabSize: 2
+ tabSize: 2,
})
// handle resize
@@ -184,7 +188,7 @@ window.init = () => {
const pos = lastSuccessfulMap.generatedPositionFor({
source: 'ExampleTemplate.vue',
line: e.position.lineNumber,
- column: e.position.column - 1
+ column: e.position.column - 1,
})
if (pos.line != null && pos.column != null) {
prevOutputDecos = output.deltaDecorations(prevOutputDecos, [
@@ -193,22 +197,22 @@ window.init = () => {
pos.line,
pos.column + 1,
pos.line,
- pos.lastColumn ? pos.lastColumn + 2 : pos.column + 2
+ pos.lastColumn ? pos.lastColumn + 2 : pos.column + 2,
),
options: {
- inlineClassName: `highlight`
- }
- }
+ inlineClassName: `highlight`,
+ },
+ },
])
output.revealPositionInCenter({
lineNumber: pos.line,
- column: pos.column + 1
+ column: pos.column + 1,
})
} else {
clearOutputDecos()
}
}
- }, 100)
+ }, 100),
)
let previousEditorDecos: string[] = []
@@ -222,7 +226,7 @@ window.init = () => {
if (lastSuccessfulMap) {
const pos = lastSuccessfulMap.originalPositionFor({
line: e.position.lineNumber,
- column: e.position.column - 1
+ column: e.position.column - 1,
})
if (
pos.line != null &&
@@ -234,7 +238,7 @@ window.init = () => {
) {
const translatedPos = {
column: pos.column + 1,
- lineNumber: pos.line
+ lineNumber: pos.line,
}
previousEditorDecos = editor.deltaDecorations(previousEditorDecos, [
{
@@ -242,20 +246,20 @@ window.init = () => {
pos.line,
pos.column + 1,
pos.line,
- pos.column + 1
+ pos.column + 1,
),
options: {
isWholeLine: true,
- className: `highlight`
- }
- }
+ className: `highlight`,
+ },
+ },
])
editor.revealPositionInCenter(translatedPos)
} else {
clearEditorDecos()
}
}
- }, 100)
+ }, 100),
)
initOptions()
@@ -264,7 +268,7 @@ window.init = () => {
function debounce any>(
fn: T,
- delay: number = 300
+ delay: number = 300,
): T {
let prevTimer: number | null = null
return ((...args: any[]) => {
@@ -275,5 +279,5 @@ function debounce any>(
fn(...args)
prevTimer = null
}, delay)
- }) as any
+ }) as T
}
diff --git a/packages/template-explorer/src/options.ts b/packages-private/template-explorer/src/options.ts
similarity index 86%
rename from packages/template-explorer/src/options.ts
rename to packages-private/template-explorer/src/options.ts
index 73e0a959f79..e3cc6173a8a 100644
--- a/packages/template-explorer/src/options.ts
+++ b/packages-private/template-explorer/src/options.ts
@@ -1,5 +1,5 @@
-import { h, reactive, createApp, ref } from 'vue'
-import { CompilerOptions } from '@vue/compiler-dom'
+import { createApp, h, reactive, ref } from 'vue'
+import type { CompilerOptions } from '@vue/compiler-dom'
import { BindingTypes } from '@vue/compiler-core'
export const ssrMode = ref(false)
@@ -22,12 +22,12 @@ export const defaultOptions: CompilerOptions = {
setupLet: BindingTypes.SETUP_LET,
setupMaybeRef: BindingTypes.SETUP_MAYBE_REF,
setupProp: BindingTypes.PROPS,
- vMySetupDir: BindingTypes.SETUP_CONST
- }
+ vMySetupDir: BindingTypes.SETUP_CONST,
+ },
}
export const compilerOptions: CompilerOptions = reactive(
- Object.assign({}, defaultOptions)
+ Object.assign({}, defaultOptions),
)
const App = {
@@ -44,18 +44,18 @@ const App = {
'a',
{
href: `https://github.com/vuejs/core/tree/${__COMMIT__}`,
- target: `_blank`
+ target: `_blank`,
},
- `@${__COMMIT__}`
+ `@${__COMMIT__}`,
),
' | ',
h(
'a',
{
href: 'https://app.netlify.com/sites/vue-next-template-explorer/deploys',
- target: `_blank`
+ target: `_blank`,
},
- 'History'
+ 'History',
),
h('div', { id: 'options-wrapper' }, [
@@ -71,7 +71,7 @@ const App = {
checked: isModule,
onChange() {
compilerOptions.mode = 'module'
- }
+ },
}),
h('label', { for: 'mode-module' }, 'module'),
' ',
@@ -82,9 +82,9 @@ const App = {
checked: !isModule,
onChange() {
compilerOptions.mode = 'function'
- }
+ },
}),
- h('label', { for: 'mode-function' }, 'function')
+ h('label', { for: 'mode-function' }, 'function'),
]),
// whitespace handling
@@ -97,7 +97,7 @@ const App = {
checked: compilerOptions.whitespace === 'condense',
onChange() {
compilerOptions.whitespace = 'condense'
- }
+ },
}),
h('label', { for: 'whitespace-condense' }, 'condense'),
' ',
@@ -108,9 +108,9 @@ const App = {
checked: compilerOptions.whitespace === 'preserve',
onChange() {
compilerOptions.whitespace = 'preserve'
- }
+ },
}),
- h('label', { for: 'whitespace-preserve' }, 'preserve')
+ h('label', { for: 'whitespace-preserve' }, 'preserve'),
]),
// SSR
@@ -122,9 +122,9 @@ const App = {
checked: ssrMode.value,
onChange(e: Event) {
ssrMode.value = (e.target as HTMLInputElement).checked
- }
+ },
}),
- h('label', { for: 'ssr' }, 'SSR')
+ h('label', { for: 'ssr' }, 'SSR'),
]),
// toggle prefixIdentifiers
@@ -137,9 +137,9 @@ const App = {
onChange(e: Event) {
compilerOptions.prefixIdentifiers =
(e.target as HTMLInputElement).checked || isModule
- }
+ },
}),
- h('label', { for: 'prefix' }, 'prefixIdentifiers')
+ h('label', { for: 'prefix' }, 'prefixIdentifiers'),
]),
// toggle hoistStatic
@@ -153,9 +153,9 @@ const App = {
compilerOptions.hoistStatic = (
e.target as HTMLInputElement
).checked
- }
+ },
}),
- h('label', { for: 'hoist' }, 'hoistStatic')
+ h('label', { for: 'hoist' }, 'hoistStatic'),
]),
// toggle cacheHandlers
@@ -169,9 +169,9 @@ const App = {
compilerOptions.cacheHandlers = (
e.target as HTMLInputElement
).checked
- }
+ },
}),
- h('label', { for: 'cache' }, 'cacheHandlers')
+ h('label', { for: 'cache' }, 'cacheHandlers'),
]),
// toggle scopeId
@@ -186,9 +186,9 @@ const App = {
isModule && (e.target as HTMLInputElement).checked
? 'scope-id'
: null
- }
+ },
}),
- h('label', { for: 'scope-id' }, 'scopeId')
+ h('label', { for: 'scope-id' }, 'scopeId'),
]),
// inline mode
@@ -201,9 +201,9 @@ const App = {
compilerOptions.inline = (
e.target as HTMLInputElement
).checked
- }
+ },
}),
- h('label', { for: 'inline' }, 'inline')
+ h('label', { for: 'inline' }, 'inline'),
]),
// compat mode
@@ -218,15 +218,15 @@ const App = {
).checked
? 2
: 3
- }
+ },
}),
- h('label', { for: 'compat' }, 'v2 compat mode')
- ])
- ])
- ])
+ h('label', { for: 'compat' }, 'v2 compat mode'),
+ ]),
+ ]),
+ ]),
]
}
- }
+ },
}
export function initOptions() {
diff --git a/packages/template-explorer/src/theme.ts b/packages-private/template-explorer/src/theme.ts
similarity index 57%
rename from packages/template-explorer/src/theme.ts
rename to packages-private/template-explorer/src/theme.ts
index 99da1081ae9..9027cd0c011 100644
--- a/packages/template-explorer/src/theme.ts
+++ b/packages-private/template-explorer/src/theme.ts
@@ -4,234 +4,234 @@ export default {
rules: [
{
foreground: 'de935f',
- token: 'number'
+ token: 'number',
},
{
foreground: '969896',
- token: 'comment'
+ token: 'comment',
},
{
foreground: 'ced1cf',
- token: 'keyword.operator.class'
+ token: 'keyword.operator.class',
},
{
foreground: 'ced1cf',
- token: 'constant.other'
+ token: 'constant.other',
},
{
foreground: 'ced1cf',
- token: 'source.php.embedded.line'
+ token: 'source.php.embedded.line',
},
{
foreground: 'cc6666',
- token: 'variable'
+ token: 'variable',
},
{
foreground: 'cc6666',
- token: 'support.other.variable'
+ token: 'support.other.variable',
},
{
foreground: 'cc6666',
- token: 'string.other.link'
+ token: 'string.other.link',
},
{
foreground: 'cc6666',
- token: 'string.regexp'
+ token: 'string.regexp',
},
{
foreground: 'cc6666',
- token: 'entity.name.tag'
+ token: 'entity.name.tag',
},
{
foreground: 'cc6666',
- token: 'entity.other.attribute-name'
+ token: 'entity.other.attribute-name',
},
{
foreground: 'cc6666',
- token: 'meta.tag'
+ token: 'meta.tag',
},
{
foreground: 'cc6666',
- token: 'declaration.tag'
+ token: 'declaration.tag',
},
{
foreground: 'cc6666',
- token: 'markup.deleted.git_gutter'
+ token: 'markup.deleted.git_gutter',
},
{
foreground: 'de935f',
- token: 'constant.numeric'
+ token: 'constant.numeric',
},
{
foreground: 'de935f',
- token: 'constant.language'
+ token: 'constant.language',
},
{
foreground: 'de935f',
- token: 'support.constant'
+ token: 'support.constant',
},
{
foreground: 'de935f',
- token: 'constant.character'
+ token: 'constant.character',
},
{
foreground: 'de935f',
- token: 'variable.parameter'
+ token: 'variable.parameter',
},
{
foreground: 'de935f',
- token: 'punctuation.section.embedded'
+ token: 'punctuation.section.embedded',
},
{
foreground: 'de935f',
- token: 'keyword.other.unit'
+ token: 'keyword.other.unit',
},
{
foreground: 'f0c674',
- token: 'entity.name.class'
+ token: 'entity.name.class',
},
{
foreground: 'f0c674',
- token: 'entity.name.type.class'
+ token: 'entity.name.type.class',
},
{
foreground: 'f0c674',
- token: 'support.type'
+ token: 'support.type',
},
{
foreground: 'f0c674',
- token: 'support.class'
+ token: 'support.class',
},
{
foreground: 'b5bd68',
- token: 'string'
+ token: 'string',
},
{
foreground: 'b5bd68',
- token: 'constant.other.symbol'
+ token: 'constant.other.symbol',
},
{
foreground: 'b5bd68',
- token: 'entity.other.inherited-class'
+ token: 'entity.other.inherited-class',
},
{
foreground: 'b5bd68',
- token: 'markup.heading'
+ token: 'markup.heading',
},
{
foreground: 'b5bd68',
- token: 'markup.inserted.git_gutter'
+ token: 'markup.inserted.git_gutter',
},
{
foreground: '8abeb7',
- token: 'keyword.operator'
+ token: 'keyword.operator',
},
{
foreground: '8abeb7',
- token: 'constant.other.color'
+ token: 'constant.other.color',
},
{
foreground: '81a2be',
- token: 'entity.name.function'
+ token: 'entity.name.function',
},
{
foreground: '81a2be',
- token: 'meta.function-call'
+ token: 'meta.function-call',
},
{
foreground: '81a2be',
- token: 'support.function'
+ token: 'support.function',
},
{
foreground: '81a2be',
- token: 'keyword.other.special-method'
+ token: 'keyword.other.special-method',
},
{
foreground: '81a2be',
- token: 'meta.block-level'
+ token: 'meta.block-level',
},
{
foreground: '81a2be',
- token: 'markup.changed.git_gutter'
+ token: 'markup.changed.git_gutter',
},
{
foreground: 'b294bb',
- token: 'keyword'
+ token: 'keyword',
},
{
foreground: 'b294bb',
- token: 'storage'
+ token: 'storage',
},
{
foreground: 'b294bb',
- token: 'storage.type'
+ token: 'storage.type',
},
{
foreground: 'b294bb',
- token: 'entity.name.tag.css'
+ token: 'entity.name.tag.css',
},
{
foreground: 'ced2cf',
background: 'df5f5f',
- token: 'invalid'
+ token: 'invalid',
},
{
foreground: 'ced2cf',
background: '82a3bf',
- token: 'meta.separator'
+ token: 'meta.separator',
},
{
foreground: 'ced2cf',
background: 'b798bf',
- token: 'invalid.deprecated'
+ token: 'invalid.deprecated',
},
{
foreground: 'ffffff',
- token: 'markup.inserted.diff'
+ token: 'markup.inserted.diff',
},
{
foreground: 'ffffff',
- token: 'markup.deleted.diff'
+ token: 'markup.deleted.diff',
},
{
foreground: 'ffffff',
- token: 'meta.diff.header.to-file'
+ token: 'meta.diff.header.to-file',
},
{
foreground: 'ffffff',
- token: 'meta.diff.header.from-file'
+ token: 'meta.diff.header.from-file',
},
{
foreground: '718c00',
- token: 'markup.inserted.diff'
+ token: 'markup.inserted.diff',
},
{
foreground: '718c00',
- token: 'meta.diff.header.to-file'
+ token: 'meta.diff.header.to-file',
},
{
foreground: 'c82829',
- token: 'markup.deleted.diff'
+ token: 'markup.deleted.diff',
},
{
foreground: 'c82829',
- token: 'meta.diff.header.from-file'
+ token: 'meta.diff.header.from-file',
},
{
foreground: 'ffffff',
background: '4271ae',
- token: 'meta.diff.header.from-file'
+ token: 'meta.diff.header.from-file',
},
{
foreground: 'ffffff',
background: '4271ae',
- token: 'meta.diff.header.to-file'
+ token: 'meta.diff.header.to-file',
},
{
foreground: '3e999f',
fontStyle: 'italic',
- token: 'meta.diff.range'
- }
+ token: 'meta.diff.range',
+ },
],
colors: {
'editor.foreground': '#C5C8C6',
@@ -239,6 +239,6 @@ export default {
'editor.selectionBackground': '#373B41',
'editor.lineHighlightBackground': '#282A2E',
'editorCursor.foreground': '#AEAFAD',
- 'editorWhitespace.foreground': '#4B4E55'
- }
+ 'editorWhitespace.foreground': '#4B4E55',
+ },
}
diff --git a/packages/template-explorer/style.css b/packages-private/template-explorer/style.css
similarity index 86%
rename from packages/template-explorer/style.css
rename to packages-private/template-explorer/style.css
index 01a3e8b550b..eed9e18a009 100644
--- a/packages/template-explorer/style.css
+++ b/packages-private/template-explorer/style.css
@@ -1,7 +1,10 @@
body {
margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- --bg: #1D1F21;
+ overflow: hidden;
+ font-family:
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
+ Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ --bg: #1d1f21;
--border: #333;
}
diff --git a/packages-private/tsconfig.json b/packages-private/tsconfig.json
new file mode 100644
index 00000000000..1c287a7500c
--- /dev/null
+++ b/packages-private/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "isolatedDeclarations": false
+ },
+ "include": ["."]
+}
diff --git a/packages-private/vite-debug/App.vue b/packages-private/vite-debug/App.vue
new file mode 100644
index 00000000000..95b3be8eee5
--- /dev/null
+++ b/packages-private/vite-debug/App.vue
@@ -0,0 +1,15 @@
+
+
+
+ {{ count }}
+
+
+
diff --git a/packages-private/vite-debug/README.md b/packages-private/vite-debug/README.md
new file mode 100644
index 00000000000..4f035ae6f8d
--- /dev/null
+++ b/packages-private/vite-debug/README.md
@@ -0,0 +1 @@
+This package is used for debugging issues that are related to `@vitejs/plugin-vue`, or can only be reproduced in a Vite-based setup. It aims to be as close to production as possible so Vue packages are resolved to the dist files instead of source.
diff --git a/packages-private/vite-debug/index.html b/packages-private/vite-debug/index.html
new file mode 100644
index 00000000000..79052a023ba
--- /dev/null
+++ b/packages-private/vite-debug/index.html
@@ -0,0 +1,2 @@
+
+
diff --git a/packages-private/vite-debug/main.ts b/packages-private/vite-debug/main.ts
new file mode 100644
index 00000000000..52668a0a545
--- /dev/null
+++ b/packages-private/vite-debug/main.ts
@@ -0,0 +1,6 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+
+const app = createApp(App)
+
+app.mount('#app')
diff --git a/packages-private/vite-debug/package.json b/packages-private/vite-debug/package.json
new file mode 100644
index 00000000000..b0f2bad2b2d
--- /dev/null
+++ b/packages-private/vite-debug/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "vite-debug",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "serve": "vite preview"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "catalog:",
+ "vite": "catalog:",
+ "vue": "workspace:*"
+ }
+}
diff --git a/packages-private/vite-debug/tsconfig.json b/packages-private/vite-debug/tsconfig.json
new file mode 100644
index 00000000000..ceecb1cde14
--- /dev/null
+++ b/packages-private/vite-debug/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "compilerOptions": {
+ "module": "esnext",
+ "moduleResolution": "bundler"
+ },
+ "include": ["./*"]
+}
diff --git a/packages-private/vite-debug/vite.config.ts b/packages-private/vite-debug/vite.config.ts
new file mode 100644
index 00000000000..c40aa3c361b
--- /dev/null
+++ b/packages-private/vite-debug/vite.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+ plugins: [vue()],
+})
diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap
index c292b032934..db268af4f9b 100644
--- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap
+++ b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap
@@ -1,6 +1,6 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: codegen ArrayExpression 1`] = `
+exports[`compiler: codegen > ArrayExpression 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
@@ -12,26 +12,26 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: codegen CacheExpression 1`] = `
+exports[`compiler: codegen > CacheExpression 1`] = `
"
export function render(_ctx, _cache) {
return _cache[1] || (_cache[1] = foo)
}"
`;
-exports[`compiler: codegen CacheExpression w/ isVNode: true 1`] = `
+exports[`compiler: codegen > CacheExpression w/ isVOnce: true 1`] = `
"
export function render(_ctx, _cache) {
return _cache[1] || (
_setBlockTracking(-1),
- _cache[1] = foo,
+ (_cache[1] = foo).cacheIndex = 1,
_setBlockTracking(1),
_cache[1]
)
}"
`;
-exports[`compiler: codegen ConditionalExpression 1`] = `
+exports[`compiler: codegen > ConditionalExpression 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
@@ -44,31 +44,31 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: codegen Element (callExpression + objectExpression + TemplateChildNode[]) 1`] = `
+exports[`compiler: codegen > Element (callExpression + objectExpression + TemplateChildNode[]) 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
- return _createElementVNode(\\"div\\", {
- id: \\"foo\\",
+ return _createElementVNode("div", {
+ id: "foo",
[prop]: bar,
[foo + bar]: bar
}, [
- _createElementVNode(\\"p\\", { \\"some-key\\": \\"foo\\" })
- ], 16)
+ _createElementVNode("p", { "some-key": "foo" })
+ ], 16 /* FULL_PROPS */)
}
}"
`;
-exports[`compiler: codegen assets + temps 1`] = `
+exports[`compiler: codegen > assets + temps 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
- const _component_Foo = _resolveComponent(\\"Foo\\")
- const _component_bar_baz = _resolveComponent(\\"bar-baz\\")
- const _component_barbaz = _resolveComponent(\\"barbaz\\")
- const _component_Qux = _resolveComponent(\\"Qux\\", true)
- const _directive_my_dir_0 = _resolveDirective(\\"my_dir_0\\")
- const _directive_my_dir_1 = _resolveDirective(\\"my_dir_1\\")
+ const _component_Foo = _resolveComponent("Foo")
+ const _component_bar_baz = _resolveComponent("bar-baz")
+ const _component_barbaz = _resolveComponent("barbaz")
+ const _component_Qux = _resolveComponent("Qux", true)
+ const _directive_my_dir_0 = _resolveDirective("my_dir_0")
+ const _directive_my_dir_1 = _resolveDirective("my_dir_1")
let _temp0, _temp1, _temp2
return null
@@ -76,16 +76,16 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: codegen comment 1`] = `
+exports[`compiler: codegen > comment 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
- return _createCommentVNode(\\"foo\\")
+ return _createCommentVNode("foo")
}
}"
`;
-exports[`compiler: codegen compound expression 1`] = `
+exports[`compiler: codegen > compound expression 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
@@ -94,16 +94,16 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: codegen forNode 1`] = `
+exports[`compiler: codegen > forNode 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
- return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(), 1))
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(), 1 /* TEXT */))
}
}"
`;
-exports[`compiler: codegen forNode with constant expression 1`] = `
+exports[`compiler: codegen > forNode with constant expression 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
@@ -112,7 +112,7 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: codegen function mode preamble 1`] = `
+exports[`compiler: codegen > function mode preamble 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -124,7 +124,7 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: codegen function mode preamble w/ prefixIdentifiers: true 1`] = `
+exports[`compiler: codegen > function mode preamble w/ prefixIdentifiers: true 1`] = `
"const { createVNode: _createVNode, resolveDirective: _resolveDirective } = Vue
return function render(_ctx, _cache) {
@@ -132,10 +132,10 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: codegen hoists 1`] = `
+exports[`compiler: codegen > hoists 1`] = `
"
const _hoisted_1 = hello
-const _hoisted_2 = { id: \\"foo\\" }
+const _hoisted_2 = { id: "foo" }
return function render(_ctx, _cache) {
with (_ctx) {
@@ -144,7 +144,7 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: codegen ifNode 1`] = `
+exports[`compiler: codegen > ifNode 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
@@ -155,7 +155,7 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: codegen interpolation 1`] = `
+exports[`compiler: codegen > interpolation 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
@@ -164,16 +164,16 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: codegen module mode preamble 1`] = `
-"import { createVNode as _createVNode, resolveDirective as _resolveDirective } from \\"vue\\"
+exports[`compiler: codegen > module mode preamble 1`] = `
+"import { createVNode as _createVNode, resolveDirective as _resolveDirective } from "vue"
export function render(_ctx, _cache) {
return null
}"
`;
-exports[`compiler: codegen module mode preamble w/ optimizeImports: true 1`] = `
-"import { createVNode, resolveDirective } from \\"vue\\"
+exports[`compiler: codegen > module mode preamble w/ optimizeImports: true 1`] = `
+"import { createVNode, resolveDirective } from "vue"
// Binding optimization for webpack code-split
const _createVNode = createVNode, _resolveDirective = resolveDirective
@@ -183,16 +183,16 @@ export function render(_ctx, _cache) {
}"
`;
-exports[`compiler: codegen static text 1`] = `
+exports[`compiler: codegen > static text 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
- return \\"hello\\"
+ return "hello"
}
}"
`;
-exports[`compiler: codegen temps 1`] = `
+exports[`compiler: codegen > temps 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap
index a72a43782ce..625485719cb 100644
--- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap
+++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap
@@ -1,25 +1,25 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: integration tests function mode 1`] = `
+exports[`compiler: integration tests > function mode 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList, createElementVNode: _createElementVNode, normalizeClass: _normalizeClass } = _Vue
- return (_openBlock(), _createElementBlock(\\"div\\", {
- id: \\"foo\\",
+ return (_openBlock(), _createElementBlock("div", {
+ id: "foo",
class: _normalizeClass(bar.baz)
}, [
- _createTextVNode(_toDisplayString(world.burn()) + \\" \\", 1 /* TEXT */),
+ _createTextVNode(_toDisplayString(world.burn()) + " ", 1 /* TEXT */),
ok
- ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }, \\"yes\\"))
+ ? (_openBlock(), _createElementBlock("div", { key: 0 }, "yes"))
: (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [
- _createTextVNode(\\"no\\")
- ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)),
+ _createTextVNode("no")
+ ], 64 /* STABLE_FRAGMENT */)),
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (value, index) => {
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _createElementVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */)
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createElementVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
], 2 /* CLASS */))
@@ -27,46 +27,46 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = `
+exports[`compiler: integration tests > function mode w/ prefixIdentifiers: true 1`] = `
"const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList, createElementVNode: _createElementVNode, normalizeClass: _normalizeClass } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock(\\"div\\", {
- id: \\"foo\\",
+ return (_openBlock(), _createElementBlock("div", {
+ id: "foo",
class: _normalizeClass(_ctx.bar.baz)
}, [
- _createTextVNode(_toDisplayString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
+ _createTextVNode(_toDisplayString(_ctx.world.burn()) + " ", 1 /* TEXT */),
(_ctx.ok)
- ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }, \\"yes\\"))
+ ? (_openBlock(), _createElementBlock("div", { key: 0 }, "yes"))
: (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [
- _createTextVNode(\\"no\\")
- ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)),
+ _createTextVNode("no")
+ ], 64 /* STABLE_FRAGMENT */)),
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, (value, index) => {
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _createElementVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */)
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createElementVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
], 2 /* CLASS */))
}"
`;
-exports[`compiler: integration tests module mode 1`] = `
-"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createTextVNode as _createTextVNode, Fragment as _Fragment, renderList as _renderList, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass } from \\"vue\\"
+exports[`compiler: integration tests > module mode 1`] = `
+"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createTextVNode as _createTextVNode, Fragment as _Fragment, renderList as _renderList, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass } from "vue"
export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock(\\"div\\", {
- id: \\"foo\\",
+ return (_openBlock(), _createElementBlock("div", {
+ id: "foo",
class: _normalizeClass(_ctx.bar.baz)
}, [
- _createTextVNode(_toDisplayString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
+ _createTextVNode(_toDisplayString(_ctx.world.burn()) + " ", 1 /* TEXT */),
(_ctx.ok)
- ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }, \\"yes\\"))
+ ? (_openBlock(), _createElementBlock("div", { key: 0 }, "yes"))
: (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [
- _createTextVNode(\\"no\\")
- ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)),
+ _createTextVNode("no")
+ ], 64 /* STABLE_FRAGMENT */)),
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, (value, index) => {
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _createElementVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */)
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createElementVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
], 2 /* CLASS */))
diff --git a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap
index d7ac9888a59..942eed4c4dc 100644
--- a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap
+++ b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap
@@ -1,430 +1,709 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: parse Errors ABRUPT_CLOSING_OF_EMPTY_COMMENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "",
- "loc": Object {
- "end": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
+exports[`compiler: parse > Edge Cases > invalid html 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 1,
+ "line": 3,
+ "offset": 13,
},
- "source": "",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
+ "source": "
+",
+ "start": {
+ "column": 1,
+ "line": 2,
+ "offset": 6,
},
},
- "type": 3,
+ "ns": 0,
+ "props": [],
+ "tag": "span",
+ "tagType": 0,
+ "type": 1,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 27,
- "line": 1,
- "offset": 26,
+ "loc": {
+ "end": {
+ "column": 7,
+ "line": 3,
+ "offset": 19,
},
- "source": " ",
- "start": Object {
+ "source": "
+
+
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
- "tag": "template",
+ "props": [],
+ "tag": "div",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 27,
- "line": 1,
- "offset": 26,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 8,
+ "line": 4,
+ "offset": 27,
},
- "source": " ",
- "start": Object {
+ "source": "
+
+
+ ",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
+
+
+",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors ABRUPT_CLOSING_OF_EMPTY_COMMENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "",
- "loc": Object {
- "end": Object {
- "column": 17,
- "line": 1,
- "offset": 16,
- },
- "source": "",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "type": 3,
- },
- ],
+exports[`compiler: parse > Edge Cases > self closing multiple tag 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 28,
+ "isSelfClosing": true,
+ "loc": {
+ "end": {
+ "column": 37,
"line": 1,
- "offset": 27,
+ "offset": 36,
},
- "source": " ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 28,
- "line": 1,
- "offset": 27,
- },
- "source": " ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors ABRUPT_CLOSING_OF_EMPTY_COMMENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "",
- "loc": Object {
- "end": Object {
- "column": 18,
+ "props": [
+ {
+ "arg": {
+ "constType": 3,
+ "content": "class",
+ "isStatic": true,
+ "loc": {
+ "end": {
+ "column": 12,
+ "line": 1,
+ "offset": 11,
+ },
+ "source": "class",
+ "start": {
+ "column": 7,
+ "line": 1,
+ "offset": 6,
+ },
+ },
+ "type": 4,
+ },
+ "exp": {
+ "constType": 0,
+ "content": "{ some: condition }",
+ "isStatic": false,
+ "loc": {
+ "end": {
+ "column": 33,
+ "line": 1,
+ "offset": 32,
+ },
+ "source": "{ some: condition }",
+ "start": {
+ "column": 14,
+ "line": 1,
+ "offset": 13,
+ },
+ },
+ "type": 4,
+ },
+ "loc": {
+ "end": {
+ "column": 34,
"line": 1,
- "offset": 17,
+ "offset": 33,
},
- "source": "",
- "start": Object {
- "column": 11,
+ "source": ":class="{ some: condition }"",
+ "start": {
+ "column": 6,
"line": 1,
- "offset": 10,
+ "offset": 5,
},
},
- "type": 3,
+ "modifiers": [],
+ "name": "bind",
+ "rawName": ":class",
+ "type": 7,
},
],
+ "tag": "div",
+ "tagType": 0,
+ "type": 1,
+ },
+ {
+ "children": [],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 29,
- "line": 1,
- "offset": 28,
+ "isSelfClosing": true,
+ "loc": {
+ "end": {
+ "column": 37,
+ "line": 2,
+ "offset": 73,
},
- "source": " ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
- "line": 1,
- "offset": 0,
+ "line": 2,
+ "offset": 37,
},
},
"ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 29,
- "line": 1,
- "offset": 28,
- },
- "source": " ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors CDATA_IN_HTML_CONTENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "[CDATA[cdata]]",
- "loc": Object {
- "end": Object {
- "column": 28,
- "line": 1,
- "offset": 27,
+ "props": [
+ {
+ "arg": {
+ "constType": 3,
+ "content": "style",
+ "isStatic": true,
+ "loc": {
+ "end": {
+ "column": 16,
+ "line": 2,
+ "offset": 52,
+ },
+ "source": "style",
+ "start": {
+ "column": 11,
+ "line": 2,
+ "offset": 47,
+ },
},
- "source": "",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
+ "type": 4,
+ },
+ "exp": {
+ "constType": 0,
+ "content": "{ color: 'red' }",
+ "isStatic": false,
+ "loc": {
+ "end": {
+ "column": 34,
+ "line": 2,
+ "offset": 70,
+ },
+ "source": "{ color: 'red' }",
+ "start": {
+ "column": 18,
+ "line": 2,
+ "offset": 54,
+ },
},
+ "type": 4,
},
- "type": 3,
+ "loc": {
+ "end": {
+ "column": 35,
+ "line": 2,
+ "offset": 71,
+ },
+ "source": "v-bind:style="{ color: 'red' }"",
+ "start": {
+ "column": 4,
+ "line": 2,
+ "offset": 40,
+ },
+ },
+ "modifiers": [],
+ "name": "bind",
+ "rawName": "v-bind:style",
+ "type": 7,
},
],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 39,
- "line": 1,
- "offset": 38,
- },
- "source": " ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
+ "tag": "p",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 39,
- "line": 1,
- "offset": 38,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 37,
+ "line": 2,
+ "offset": 73,
},
- "source": " ",
- "start": Object {
+ "source": "
+
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
+
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors CDATA_IN_HTML_CONTENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "cdata",
- "loc": Object {
- "end": Object {
- "column": 30,
- "line": 1,
- "offset": 29,
- },
- "source": "cdata",
- "start": Object {
- "column": 25,
- "line": 1,
- "offset": 24,
- },
- },
- "type": 2,
- },
- ],
+exports[`compiler: parse > Edge Cases > valid html 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
+ "isSelfClosing": true,
+ "loc": {
+ "end": {
"column": 39,
- "line": 1,
- "offset": 38,
+ "line": 2,
+ "offset": 73,
},
- "source": " ",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
+ "source": "
",
+ "start": {
+ "column": 3,
+ "line": 2,
+ "offset": 37,
},
},
- "ns": 1,
- "props": Array [],
- "tag": "svg",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
+ "ns": 0,
+ "props": [
+ {
+ "arg": {
+ "constType": 3,
+ "content": "style",
+ "isStatic": true,
+ "loc": {
+ "end": {
+ "column": 18,
+ "line": 2,
+ "offset": 52,
+ },
+ "source": "style",
+ "start": {
+ "column": 13,
+ "line": 2,
+ "offset": 47,
+ },
+ },
+ "type": 4,
+ },
+ "exp": {
+ "constType": 0,
+ "content": "{ color: 'red' }",
+ "isStatic": false,
+ "loc": {
+ "end": {
+ "column": 36,
+ "line": 2,
+ "offset": 70,
+ },
+ "source": "{ color: 'red' }",
+ "start": {
+ "column": 20,
+ "line": 2,
+ "offset": 54,
+ },
+ },
+ "type": 4,
+ },
+ "loc": {
+ "end": {
+ "column": 37,
+ "line": 2,
+ "offset": 71,
+ },
+ "source": "v-bind:style="{ color: 'red' }"",
+ "start": {
+ "column": 6,
+ "line": 2,
+ "offset": 40,
+ },
+ },
+ "modifiers": [],
+ "name": "bind",
+ "rawName": "v-bind:style",
+ "type": 7,
+ },
+ ],
+ "tag": "p",
+ "tagType": 0,
+ "type": 1,
+ },
+ {
+ "content": " a comment with inside it ",
+ "loc": {
+ "end": {
+ "column": 43,
+ "line": 3,
+ "offset": 116,
+ },
+ "source": "",
+ "start": {
+ "column": 3,
+ "line": 3,
+ "offset": 76,
+ },
+ },
+ "type": 3,
+ },
+ ],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 7,
+ "line": 4,
+ "offset": 123,
+ },
+ "source": "",
+ "start": {
+ "column": 1,
+ "line": 1,
+ "offset": 0,
+ },
+ },
+ "ns": 0,
+ "props": [
+ {
+ "arg": {
+ "constType": 3,
+ "content": "class",
+ "isStatic": true,
+ "loc": {
+ "end": {
+ "column": 12,
+ "line": 1,
+ "offset": 11,
+ },
+ "source": "class",
+ "start": {
+ "column": 7,
+ "line": 1,
+ "offset": 6,
+ },
+ },
+ "type": 4,
+ },
+ "exp": {
+ "constType": 0,
+ "content": "{ some: condition }",
+ "isStatic": false,
+ "loc": {
+ "end": {
+ "column": 33,
+ "line": 1,
+ "offset": 32,
+ },
+ "source": "{ some: condition }",
+ "start": {
+ "column": 14,
+ "line": 1,
+ "offset": 13,
+ },
+ },
+ "type": 4,
+ },
+ "loc": {
+ "end": {
+ "column": 34,
+ "line": 1,
+ "offset": 33,
+ },
+ "source": ":class="{ some: condition }"",
+ "start": {
+ "column": 6,
+ "line": 1,
+ "offset": 5,
+ },
+ },
+ "modifiers": [],
+ "name": "bind",
+ "rawName": ":class",
+ "type": 7,
+ },
+ ],
+ "tag": "div",
+ "tagType": 0,
+ "type": 1,
+ },
+ ],
+ "codegenNode": undefined,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 7,
+ "line": 4,
+ "offset": 123,
+ },
+ "source": "",
+ "start": {
+ "column": 1,
+ "line": 1,
+ "offset": 0,
+ },
+ },
+ "source": "",
+ "temps": 0,
+ "type": 0,
+}
+`;
+
+exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 39,
+ "line": 1,
+ "offset": 38,
+ },
+ "source": " ",
+ "start": {
+ "column": 1,
+ "line": 1,
+ "offset": 0,
+ },
+ },
+ "ns": 0,
+ "props": [],
+ "tag": "template",
+ "tagType": 0,
+ "type": 1,
+ },
+ ],
+ "codegenNode": undefined,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 39,
+ "line": 1,
+ "offset": 38,
+ },
+ "source": " ",
+ "start": {
+ "column": 1,
+ "line": 1,
+ "offset": 0,
+ },
+ },
+ "source": " ",
+ "temps": 0,
+ "type": 0,
+}
+`;
+
+exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [
+ {
+ "content": "cdata",
+ "loc": {
+ "end": {
+ "column": 30,
+ "line": 1,
+ "offset": 29,
+ },
+ "source": "cdata",
+ "start": {
+ "column": 25,
+ "line": 1,
+ "offset": 24,
+ },
+ },
+ "type": 2,
+ },
+ ],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 39,
+ "line": 1,
+ "offset": 38,
+ },
+ "source": " ",
+ "start": {
+ "column": 11,
+ "line": 1,
+ "offset": 10,
+ },
+ },
+ "ns": 1,
+ "props": [],
+ "tag": "svg",
+ "tagType": 0,
+ "type": 1,
+ },
+ ],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
"column": 50,
"line": 1,
"offset": 49,
},
"source": " ",
- "start": Object {
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
"column": 50,
"line": 1,
"offset": 49,
},
"source": " ",
- "start": Object {
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": " ",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors DUPLICATE_ATTRIBUTE
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
+exports[`compiler: parse > Errors > DUPLICATE_ATTRIBUTE >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
+ "loc": {
+ "end": {
"column": 34,
"line": 1,
"offset": 33,
},
- "source": "
",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
"ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
+ "props": [
+ {
+ "loc": {
+ "end": {
"column": 21,
"line": 1,
"offset": 20,
},
- "source": "id=\\"\\"",
- "start": Object {
+ "source": "id=""",
+ "start": {
"column": 16,
"line": 1,
"offset": 15,
},
},
"name": "id",
+ "nameLoc": {
+ "end": {
+ "column": 18,
+ "line": 1,
+ "offset": 17,
+ },
+ "source": "id",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
"type": 6,
- "value": Object {
+ "value": {
"content": "",
- "loc": Object {
- "end": Object {
+ "loc": {
+ "end": {
"column": 21,
"line": 1,
"offset": 20,
},
- "source": "\\"\\"",
- "start": Object {
+ "source": """",
+ "start": {
"column": 19,
"line": 1,
"offset": 18,
@@ -433,32 +712,45 @@ Object {
"type": 2,
},
},
- Object {
- "loc": Object {
- "end": Object {
+ {
+ "loc": {
+ "end": {
"column": 27,
"line": 1,
"offset": 26,
},
- "source": "id=\\"\\"",
- "start": Object {
+ "source": "id=""",
+ "start": {
"column": 22,
"line": 1,
"offset": 21,
},
},
"name": "id",
+ "nameLoc": {
+ "end": {
+ "column": 24,
+ "line": 1,
+ "offset": 23,
+ },
+ "source": "id",
+ "start": {
+ "column": 22,
+ "line": 1,
+ "offset": 21,
+ },
+ },
"type": 6,
- "value": Object {
+ "value": {
"content": "",
- "loc": Object {
- "end": Object {
+ "loc": {
+ "end": {
"column": 27,
"line": 1,
"offset": 26,
},
- "source": "\\"\\"",
- "start": Object {
+ "source": """",
+ "start": {
"column": 25,
"line": 1,
"offset": 24,
@@ -474,289 +766,137 @@ Object {
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
+ "loc": {
+ "end": {
"column": 45,
"line": 1,
"offset": 44,
},
- "source": "
",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
"column": 45,
"line": 1,
"offset": 44,
},
- "source": "
",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors END_TAG_WITH_ATTRIBUTES
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 28,
+exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > < 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "content": "<",
+ "loc": {
+ "end": {
+ "column": 12,
"line": 1,
- "offset": 27,
+ "offset": 11,
},
- "source": "
",
- "start": Object {
+ "source": "<",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "ns": 0,
- "props": Array [],
- "tag": "div",
- "tagType": 0,
- "type": 1,
+ "type": 2,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 39,
+ "loc": {
+ "end": {
+ "column": 12,
"line": 1,
- "offset": 38,
+ "offset": 11,
},
- "source": "
",
- "start": Object {
+ "source": "<",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 39,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 12,
"line": 1,
- "offset": 38,
+ "offset": 11,
},
- "source": "
",
- "start": Object {
+ "source": "<",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "<",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors END_TAG_WITH_TRAILING_SOLIDUS
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 23,
+exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "content": "",
+ "loc": {
+ "end": {
+ "column": 13,
"line": 1,
- "offset": 22,
+ "offset": 12,
},
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 34,
- "line": 1,
- "offset": 33,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 34,
- "line": 1,
- "offset": 33,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors EOF_BEFORE_TAG_NAME < 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "<",
- "loc": Object {
- "end": Object {
- "column": 12,
- "line": 1,
- "offset": 11,
- },
- "source": "<",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "type": 2,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 12,
- "line": 1,
- "offset": 11,
- },
- "source": "<",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 12,
- "line": 1,
- "offset": 11,
- },
- "source": "<",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors EOF_BEFORE_TAG_NAME 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "",
- "loc": Object {
- "end": Object {
- "column": 13,
- "line": 1,
- "offset": 12,
- },
- "source": "",
- "start": Object {
+ "source": "",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
@@ -766,145 +906,144 @@ Object {
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
+ "loc": {
+ "end": {
"column": 13,
"line": 1,
"offset": 12,
},
"source": "",
- "start": Object {
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
"column": 13,
"line": 1,
"offset": 12,
},
"source": "",
- "start": Object {
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors EOF_IN_CDATA Errors > EOF_IN_CDATA > Errors > EOF_IN_CDATA > Errors > EOF_IN_COMMENT > 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "comment",
- "loc": Object {
- "end": Object {
- "column": 26,
+exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 30,
"line": 1,
- "offset": 25,
+ "offset": 29,
},
- "source": "",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "type": 3,
+ "ns": 0,
+ "props": [
+ {
+ "loc": {
+ "end": {
+ "column": 23,
+ "line": 1,
+ "offset": 22,
+ },
+ "source": "a ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 37,
- "line": 1,
- "offset": 36,
- },
- "source": " ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors INCORRECTLY_OPENED_COMMENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "content": "DOCTYPE html",
- "loc": Object {
- "end": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- "source": "",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "type": 3,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 16,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 41,
"line": 1,
- "offset": 15,
+ "offset": 40,
},
- "source": "",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors INCORRECTLY_OPENED_COMMENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "",
- "loc": Object {
- "end": Object {
- "column": 14,
+exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 31,
"line": 1,
- "offset": 13,
+ "offset": 30,
},
- "source": "",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "type": 3,
+ "ns": 0,
+ "props": [
+ {
+ "loc": {
+ "end": {
+ "column": 24,
+ "line": 1,
+ "offset": 23,
+ },
+ "source": "foo=bar"",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "name": "foo",
+ "nameLoc": {
+ "end": {
+ "column": 19,
+ "line": 1,
+ "offset": 18,
+ },
+ "source": "foo",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "type": 6,
+ "value": {
+ "content": "bar"",
+ "loc": {
+ "end": {
+ "column": 24,
+ "line": 1,
+ "offset": 23,
+ },
+ "source": "bar"",
+ "start": {
+ "column": 20,
+ "line": 1,
+ "offset": 19,
+ },
+ },
+ "type": 2,
+ },
+ },
+ ],
+ "tag": "div",
+ "tagType": 0,
+ "type": 1,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 25,
+ "loc": {
+ "end": {
+ "column": 42,
"line": 1,
- "offset": 24,
+ "offset": 41,
},
- "source": " ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 25,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 42,
"line": 1,
- "offset": 24,
+ "offset": 41,
},
- "source": " ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors INCORRECTLY_OPENED_COMMENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "-",
- "loc": Object {
- "end": Object {
- "column": 15,
+exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 31,
"line": 1,
- "offset": 14,
+ "offset": 30,
},
- "source": "",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "type": 3,
+ "ns": 0,
+ "props": [
+ {
+ "loc": {
+ "end": {
+ "column": 24,
+ "line": 1,
+ "offset": 23,
+ },
+ "source": "foo=bar'",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "name": "foo",
+ "nameLoc": {
+ "end": {
+ "column": 19,
+ "line": 1,
+ "offset": 18,
+ },
+ "source": "foo",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "type": 6,
+ "value": {
+ "content": "bar'",
+ "loc": {
+ "end": {
+ "column": 24,
+ "line": 1,
+ "offset": 23,
+ },
+ "source": "bar'",
+ "start": {
+ "column": 20,
+ "line": 1,
+ "offset": 19,
+ },
+ },
+ "type": 2,
+ },
+ },
+ ],
+ "tag": "div",
+ "tagType": 0,
+ "type": 1,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 26,
+ "loc": {
+ "end": {
+ "column": 42,
"line": 1,
- "offset": 25,
+ "offset": 41,
},
- "source": " ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 26,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 42,
"line": 1,
- "offset": 25,
+ "offset": 41,
},
- "source": " ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors INCORRECTLY_OPENED_COMMENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "ELEMENT br EMPTY",
- "loc": Object {
- "end": Object {
- "column": 30,
+exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 34,
"line": 1,
- "offset": 29,
+ "offset": 33,
},
- "source": "",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "type": 3,
+ "ns": 0,
+ "props": [
+ {
+ "loc": {
+ "end": {
+ "column": 27,
+ "line": 1,
+ "offset": 26,
+ },
+ "source": "foo=bar",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 41,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 45,
"line": 1,
- "offset": 40,
+ "offset": 44,
},
- "source": "
",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors INVALID_FIRST_CHARACTER_OF_TAG_NAME
�> 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "�",
- "loc": Object {
- "end": Object {
- "column": 15,
+exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 34,
"line": 1,
- "offset": 14,
+ "offset": 33,
},
- "source": "�>",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "type": 3,
+ "ns": 0,
+ "props": [
+ {
+ "loc": {
+ "end": {
+ "column": 27,
+ "line": 1,
+ "offset": 26,
+ },
+ "source": "foo=bar=baz",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "name": "foo",
+ "nameLoc": {
+ "end": {
+ "column": 19,
+ "line": 1,
+ "offset": 18,
+ },
+ "source": "foo",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "type": 6,
+ "value": {
+ "content": "bar=baz",
+ "loc": {
+ "end": {
+ "column": 27,
+ "line": 1,
+ "offset": 26,
+ },
+ "source": "bar=baz",
+ "start": {
+ "column": 20,
+ "line": 1,
+ "offset": 19,
+ },
+ },
+ "type": 2,
+ },
+ },
+ ],
+ "tag": "div",
+ "tagType": 0,
+ "type": 1,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 26,
+ "loc": {
+ "end": {
+ "column": 45,
"line": 1,
- "offset": 25,
+ "offset": 44,
},
- "source": "
�> ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 26,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 45,
"line": 1,
- "offset": 25,
+ "offset": 44,
},
- "source": "
�> ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors INVALID_FIRST_CHARACTER_OF_TAG_NAME
<�> 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "<�>",
- "loc": Object {
- "end": Object {
- "column": 14,
+exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 31,
"line": 1,
- "offset": 13,
+ "offset": 30,
},
- "source": "<�>",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "type": 2,
+ "ns": 0,
+ "props": [
+ {
+ "loc": {
+ "end": {
+ "column": 24,
+ "line": 1,
+ "offset": 23,
+ },
+ "source": "foo=bar\`",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "name": "foo",
+ "nameLoc": {
+ "end": {
+ "column": 19,
+ "line": 1,
+ "offset": 18,
+ },
+ "source": "foo",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "type": 6,
+ "value": {
+ "content": "bar\`",
+ "loc": {
+ "end": {
+ "column": 24,
+ "line": 1,
+ "offset": 23,
+ },
+ "source": "bar\`",
+ "start": {
+ "column": 20,
+ "line": 1,
+ "offset": 19,
+ },
+ },
+ "type": 2,
+ },
+ },
+ ],
+ "tag": "div",
+ "tagType": 0,
+ "type": 1,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 25,
+ "loc": {
+ "end": {
+ "column": 42,
"line": 1,
- "offset": 24,
+ "offset": 41,
},
- "source": "
<�> ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 25,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 42,
"line": 1,
- "offset": 24,
+ "offset": 41,
},
- "source": "
<�> ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors INVALID_FIRST_CHARACTER_OF_TAG_NAME
{{a < b}} 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": Object {
- "constType": 0,
- "content": "a < b",
- "isStatic": false,
- "loc": Object {
- "end": Object {
- "column": 18,
- "line": 1,
- "offset": 17,
- },
- "source": "a < b",
- "start": Object {
- "column": 13,
- "line": 1,
- "offset": 12,
- },
- },
- "type": 4,
- },
- "loc": Object {
- "end": Object {
- "column": 20,
+exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 24,
"line": 1,
- "offset": 19,
+ "offset": 23,
},
- "source": "{{a < b}}",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "type": 5,
+ "ns": 0,
+ "props": [
+ {
+ "loc": {
+ "end": {
+ "column": 17,
+ "line": 1,
+ "offset": 16,
+ },
+ "source": "=",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "name": "=",
+ "nameLoc": {
+ "end": {
+ "column": 17,
+ "line": 1,
+ "offset": 16,
+ },
+ "source": "=",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "type": 6,
+ "value": undefined,
+ },
+ ],
+ "tag": "div",
+ "tagType": 0,
+ "type": 1,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 31,
+ "loc": {
+ "end": {
+ "column": 35,
"line": 1,
- "offset": 30,
+ "offset": 34,
},
- "source": "
{{a < b}} ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 31,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 35,
"line": 1,
- "offset": 30,
+ "offset": 34,
},
- "source": "
{{a < b}} ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors INVALID_FIRST_CHARACTER_OF_TAG_NAME
a < b 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "a < b",
- "loc": Object {
- "end": Object {
- "column": 16,
+exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
+ "codegenNode": undefined,
+ "loc": {
+ "end": {
+ "column": 31,
"line": 1,
- "offset": 15,
+ "offset": 30,
},
- "source": "a < b",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "type": 2,
+ "ns": 0,
+ "props": [
+ {
+ "loc": {
+ "end": {
+ "column": 24,
+ "line": 1,
+ "offset": 23,
+ },
+ "source": "=foo=bar",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "name": "=foo",
+ "nameLoc": {
+ "end": {
+ "column": 20,
+ "line": 1,
+ "offset": 19,
+ },
+ "source": "=foo",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "type": 6,
+ "value": {
+ "content": "bar",
+ "loc": {
+ "end": {
+ "column": 24,
+ "line": 1,
+ "offset": 23,
+ },
+ "source": "bar",
+ "start": {
+ "column": 21,
+ "line": 1,
+ "offset": 20,
+ },
+ },
+ "type": 2,
+ },
+ },
+ ],
+ "tag": "div",
+ "tagType": 0,
+ "type": 1,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 27,
+ "loc": {
+ "end": {
+ "column": 42,
"line": 1,
- "offset": 26,
+ "offset": 41,
},
- "source": "
a < b ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 27,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 42,
"line": 1,
- "offset": 26,
+ "offset": 41,
},
- "source": "
a < b ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors INVALID_FIRST_CHARACTER_OF_TAG_NAME
a b 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "a ",
- "loc": Object {
- "end": Object {
- "column": 13,
- "line": 1,
- "offset": 12,
- },
- "source": "a ",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "type": 2,
- },
- Object {
- "content": " b",
- "start": Object {
- "column": 13,
- "line": 1,
- "offset": 12,
- },
- },
- "type": 3,
- },
- ],
+exports[`compiler: parse > Errors > UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 28,
+ "loc": {
+ "end": {
+ "column": 29,
"line": 1,
- "offset": 27,
+ "offset": 28,
},
- "source": "
a b ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 28,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 29,
"line": 1,
- "offset": 27,
+ "offset": 28,
},
- "source": "
a b ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors MISSING_ATTRIBUTE_VALUE
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
+exports[`compiler: parse > Errors > UNEXPECTED_SOLIDUS_IN_TAG >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 28,
+ "loc": {
+ "end": {
+ "column": 26,
"line": 1,
- "offset": 27,
+ "offset": 25,
},
- "source": "
",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
"ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 21,
+ "props": [
+ {
+ "loc": {
+ "end": {
+ "column": 17,
"line": 1,
- "offset": 20,
+ "offset": 16,
},
- "source": "id= /",
- "start": Object {
+ "source": "a",
+ "start": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ },
+ "name": "a",
+ "nameLoc": {
+ "end": {
+ "column": 17,
+ "line": 1,
+ "offset": 16,
+ },
+ "source": "a",
+ "start": {
"column": 16,
"line": 1,
"offset": 15,
},
},
- "name": "id",
"type": 6,
- "value": Object {
- "content": "/",
- "loc": Object {
- "end": Object {
- "column": 21,
- "line": 1,
- "offset": 20,
- },
- "source": "/",
- "start": Object {
- "column": 20,
- "line": 1,
- "offset": 19,
- },
+ "value": undefined,
+ },
+ {
+ "loc": {
+ "end": {
+ "column": 19,
+ "line": 1,
+ "offset": 18,
+ },
+ "source": "b",
+ "start": {
+ "column": 18,
+ "line": 1,
+ "offset": 17,
+ },
+ },
+ "name": "b",
+ "nameLoc": {
+ "end": {
+ "column": 19,
+ "line": 1,
+ "offset": 18,
+ },
+ "source": "b",
+ "start": {
+ "column": 18,
+ "line": 1,
+ "offset": 17,
},
- "type": 2,
},
+ "type": 6,
+ "value": undefined,
},
],
"tag": "div",
@@ -3625,3512 +3803,1021 @@ Object {
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 39,
+ "loc": {
+ "end": {
+ "column": 37,
"line": 1,
- "offset": 38,
+ "offset": 36,
},
- "source": "
",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 39,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 37,
"line": 1,
- "offset": 38,
+ "offset": 36,
},
- "source": "
",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors MISSING_ATTRIBUTE_VALUE
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 27,
+exports[`compiler: parse > Errors > X_INVALID_END_TAG >
]]> 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "content": "
",
+ "loc": {
+ "end": {
+ "column": 21,
"line": 1,
- "offset": 26,
+ "offset": 20,
},
- "source": "
",
- "start": Object {
- "column": 11,
+ "source": "",
+ "start": {
+ "column": 15,
"line": 1,
- "offset": 10,
+ "offset": 14,
},
},
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 20,
- "line": 1,
- "offset": 19,
- },
- "source": "id= ",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "id",
- "type": 6,
- "value": undefined,
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
+ "type": 2,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 38,
+ "loc": {
+ "end": {
+ "column": 30,
"line": 1,
- "offset": 37,
+ "offset": 29,
},
- "source": "
",
- "start": Object {
+ "source": "]]> ",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
- "ns": 0,
- "props": Array [],
- "tag": "template",
+ "ns": 1,
+ "props": [],
+ "tag": "svg",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 38,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 30,
"line": 1,
- "offset": 37,
+ "offset": 29,
},
- "source": "
",
- "start": Object {
+ "source": "]]> ",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "]]> ",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors MISSING_ATTRIBUTE_VALUE
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 26,
+exports[`compiler: parse > Errors > X_INVALID_END_TAG > 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "content": "",
+ "loc": {
+ "end": {
+ "column": 19,
"line": 1,
- "offset": 25,
+ "offset": 18,
},
- "source": "
",
- "start": Object {
- "column": 11,
+ "source": "",
+ "start": {
+ "column": 6,
"line": 1,
- "offset": 10,
+ "offset": 5,
},
},
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 19,
- "line": 1,
- "offset": 18,
- },
- "source": "id=",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "id",
- "type": 6,
- "value": undefined,
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
+ "type": 3,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 37,
+ "loc": {
+ "end": {
+ "column": 25,
"line": 1,
- "offset": 36,
+ "offset": 24,
},
- "source": "
",
- "start": Object {
+ "source": " ",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
- "ns": 0,
- "props": Array [],
- "tag": "template",
+ "ns": 1,
+ "props": [],
+ "tag": "svg",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 37,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 25,
"line": 1,
- "offset": 36,
+ "offset": 24,
},
- "source": "
",
- "start": Object {
+ "source": " ",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": " ",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors MISSING_END_TAG_NAME > 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [],
+exports[`compiler: parse > Errors > X_INVALID_END_TAG > 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 25,
+ "loc": {
+ "end": {
+ "column": 34,
"line": 1,
- "offset": 24,
+ "offset": 33,
},
- "source": "> ",
- "start": Object {
+ "source": " ",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 25,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 34,
"line": 1,
- "offset": 24,
+ "offset": 33,
},
- "source": "> ",
- "start": Object {
+ "source": " ",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": " ",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors MISSING_WHITESPACE_BETWEEN_ATTRIBUTES
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 19,
- "line": 2,
- "offset": 43,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "id=\\"foo\\"",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "id",
- "type": 6,
- "value": Object {
- "content": "foo",
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "\\"foo\\"",
- "start": Object {
- "column": 19,
- "line": 1,
- "offset": 18,
- },
- },
- "type": 2,
- },
- },
- Object {
- "loc": Object {
- "end": Object {
- "column": 12,
- "line": 2,
- "offset": 36,
- },
- "source": "class=\\"bar\\"",
- "start": Object {
- "column": 1,
- "line": 2,
- "offset": 25,
- },
- },
- "name": "class",
- "type": 6,
- "value": Object {
- "content": "bar",
- "loc": Object {
- "end": Object {
- "column": 12,
- "line": 2,
- "offset": 36,
- },
- "source": "\\"bar\\"",
- "start": Object {
- "column": 7,
- "line": 2,
- "offset": 31,
- },
- },
- "type": 2,
- },
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
+exports[`compiler: parse > Errors > X_INVALID_END_TAG > 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 30,
- "line": 2,
- "offset": 54,
+ "loc": {
+ "end": {
+ "column": 28,
+ "line": 1,
+ "offset": 27,
},
- "source": "
",
- "start": Object {
+ "source": " ",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 30,
- "line": 2,
- "offset": 54,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 28,
+ "line": 1,
+ "offset": 27,
},
- "source": "
",
- "start": Object {
+ "source": " ",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": " ",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors MISSING_WHITESPACE_BETWEEN_ATTRIBUTES
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 42,
+exports[`compiler: parse > Errors > X_INVALID_END_TAG > {{''}} 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "content": {
+ "constType": 0,
+ "content": "''",
+ "isStatic": false,
+ "loc": {
+ "end": {
+ "column": 21,
+ "line": 1,
+ "offset": 20,
+ },
+ "source": "''",
+ "start": {
+ "column": 13,
+ "line": 1,
+ "offset": 12,
+ },
+ },
+ "type": 4,
+ },
+ "loc": {
+ "end": {
+ "column": 23,
"line": 1,
- "offset": 41,
+ "offset": 22,
},
- "source": "
",
- "start": Object {
+ "source": "{{''}}",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "id=\\"foo\\"",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "id",
- "type": 6,
- "value": Object {
- "content": "foo",
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "\\"foo\\"",
- "start": Object {
- "column": 19,
- "line": 1,
- "offset": 18,
- },
- },
- "type": 2,
- },
- },
- Object {
- "loc": Object {
- "end": Object {
- "column": 35,
- "line": 1,
- "offset": 34,
- },
- "source": "class=\\"bar\\"",
- "start": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- },
- "name": "class",
- "type": 6,
- "value": Object {
- "content": "bar",
- "loc": Object {
- "end": Object {
- "column": 35,
- "line": 1,
- "offset": 34,
- },
- "source": "\\"bar\\"",
- "start": Object {
- "column": 30,
- "line": 1,
- "offset": 29,
- },
- },
- "type": 2,
- },
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
+ "type": 5,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 53,
+ "loc": {
+ "end": {
+ "column": 34,
"line": 1,
- "offset": 52,
+ "offset": 33,
},
- "source": "
",
- "start": Object {
+ "source": "{{''}} ",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 53,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 34,
"line": 1,
- "offset": 52,
+ "offset": 33,
},
- "source": "
",
- "start": Object {
+ "source": "{{''}} ",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "{{''}} ",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors NESTED_COMMENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "a Errors > X_INVALID_END_TAG > 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "content": "",
+ "loc": {
+ "end": {
+ "column": 17,
"line": 1,
- "offset": 20,
+ "offset": 16,
},
- "source": "",
- "start": Object {
+ "source": "",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "type": 3,
+ "type": 2,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 32,
+ "loc": {
+ "end": {
+ "column": 28,
"line": 1,
- "offset": 31,
+ "offset": 27,
},
- "source": " ",
- "start": Object {
+ "source": "",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
- "tag": "template",
+ "props": [],
+ "tag": "textarea",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 32,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 28,
"line": 1,
- "offset": 31,
+ "offset": 27,
},
- "source": " ",
- "start": Object {
+ "source": "",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors NESTED_COMMENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "a",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "type": 3,
- },
- ],
+exports[`compiler: parse > Errors > X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END >
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 39,
+ "isSelfClosing": true,
+ "loc": {
+ "end": {
+ "column": 25,
"line": 1,
- "offset": 38,
+ "offset": 24,
},
- "source": " ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
- "tag": "template",
+ "props": [
+ {
+ "arg": {
+ "constType": 0,
+ "content": "sef",
+ "isStatic": false,
+ "loc": {
+ "end": {
+ "column": 17,
+ "line": 1,
+ "offset": 16,
+ },
+ "source": "[sef ",
+ "start": {
+ "column": 12,
+ "line": 1,
+ "offset": 11,
+ },
+ },
+ "type": 4,
+ },
+ "exp": undefined,
+ "loc": {
+ "end": {
+ "column": 16,
+ "line": 1,
+ "offset": 15,
+ },
+ "source": "v-foo:[sef",
+ "start": {
+ "column": 6,
+ "line": 1,
+ "offset": 5,
+ },
+ },
+ "modifiers": [],
+ "name": "foo",
+ "rawName": "v-foo:[sef",
+ "type": 7,
+ },
+ {
+ "loc": {
+ "end": {
+ "column": 22,
+ "line": 1,
+ "offset": 21,
+ },
+ "source": "fsef]",
+ "start": {
+ "column": 17,
+ "line": 1,
+ "offset": 16,
+ },
+ },
+ "name": "fsef]",
+ "nameLoc": {
+ "end": {
+ "column": 22,
+ "line": 1,
+ "offset": 21,
+ },
+ "source": "fsef]",
+ "start": {
+ "column": 17,
+ "line": 1,
+ "offset": 16,
+ },
+ },
+ "type": 6,
+ "value": undefined,
+ },
+ ],
+ "tag": "div",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 39,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 25,
"line": 1,
- "offset": 38,
+ "offset": 24,
},
- "source": " ",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors NESTED_COMMENT 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "a",
- "start": Object {
+ "source": "",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "type": 3,
+ "ns": 0,
+ "props": [],
+ "tag": "div",
+ "tagType": 0,
+ "type": 1,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 40,
+ "loc": {
+ "end": {
+ "column": 16,
"line": 1,
- "offset": 39,
+ "offset": 15,
},
- "source": "
",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 40,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 16,
"line": 1,
- "offset": 39,
+ "offset": 15,
},
- "source": "
",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors NESTED_COMMENT
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "a",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 11,
"line": 1,
"offset": 10,
},
},
- "type": 3,
+ "ns": 0,
+ "props": [],
+ "tag": "div",
+ "tagType": 0,
+ "type": 1,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 35,
+ "loc": {
+ "end": {
+ "column": 27,
"line": 1,
- "offset": 34,
+ "offset": 26,
},
- "source": "
",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
+ "props": [],
"tag": "template",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 35,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 27,
"line": 1,
- "offset": 34,
+ "offset": 26,
},
- "source": "
",
- "start": Object {
+ "source": "
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 30,
+exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END >
{{ foo
1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "children": [
+ {
+ "content": "{{ foo
",
+ "loc": {
+ "end": {
+ "column": 18,
"line": 1,
- "offset": 29,
+ "offset": 17,
},
- "source": "
",
- "start": Object {
- "column": 11,
+ "source": "{{ foo ",
+ "start": {
+ "column": 6,
"line": 1,
- "offset": 10,
+ "offset": 5,
},
},
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 23,
- "line": 1,
- "offset": 22,
- },
- "source": "a\\"bc=''",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "a\\"bc",
- "type": 6,
- "value": Object {
- "content": "",
- "loc": Object {
- "end": Object {
- "column": 23,
- "line": 1,
- "offset": 22,
- },
- "source": "''",
- "start": Object {
- "column": 21,
- "line": 1,
- "offset": 20,
- },
- },
- "type": 2,
- },
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
+ "type": 2,
},
],
"codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 41,
+ "loc": {
+ "end": {
+ "column": 18,
"line": 1,
- "offset": 40,
+ "offset": 17,
},
- "source": "
",
- "start": Object {
+ "source": "{{ foo
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
- "props": Array [],
- "tag": "template",
+ "props": [],
+ "tag": "div",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 41,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 18,
"line": 1,
- "offset": 40,
+ "offset": 17,
},
- "source": "
",
- "start": Object {
+ "source": "{{ foo
",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "{{ foo
",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 30,
- "line": 1,
- "offset": 29,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 23,
- "line": 1,
- "offset": 22,
- },
- "source": "a'bc=''",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "a'bc",
- "type": 6,
- "value": Object {
- "content": "",
- "loc": Object {
- "end": Object {
- "column": 23,
- "line": 1,
- "offset": 22,
- },
- "source": "''",
- "start": Object {
- "column": 21,
- "line": 1,
- "offset": 20,
- },
- },
- "type": 2,
- },
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 41,
+exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{ 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "content": "{{",
+ "loc": {
+ "end": {
+ "column": 3,
"line": 1,
- "offset": 40,
+ "offset": 2,
},
- "source": "
",
- "start": Object {
+ "source": "{{",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
+ "type": 2,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 41,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 3,
"line": 1,
- "offset": 40,
+ "offset": 2,
},
- "source": "
",
- "start": Object {
+ "source": "{{",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "{{",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 30,
- "line": 1,
- "offset": 29,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 23,
- "line": 1,
- "offset": 22,
- },
- "source": "a Errors > X_MISSING_INTERPOLATION_END > {{ foo 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "content": "{{ foo",
+ "loc": {
+ "end": {
+ "column": 7,
"line": 1,
- "offset": 40,
+ "offset": 6,
},
- "source": "
",
- "start": Object {
+ "source": "{{ foo",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
+ "type": 2,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 41,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 7,
"line": 1,
- "offset": 40,
+ "offset": 6,
},
- "source": "
",
- "start": Object {
+ "source": "{{ foo",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "{{ foo",
"temps": 0,
"type": 0,
}
`;
-exports[`compiler: parse Errors UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 31,
- "line": 1,
- "offset": 30,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
+exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{}} 1`] = `
+{
+ "cached": [],
+ "children": [
+ {
+ "content": {
+ "constType": 0,
+ "content": "",
+ "isStatic": false,
+ "loc": {
+ "end": {
+ "column": 3,
+ "line": 1,
+ "offset": 2,
},
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "foo=bar\\"",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "foo",
- "type": 6,
- "value": Object {
- "content": "bar\\"",
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "bar\\"",
- "start": Object {
- "column": 20,
- "line": 1,
- "offset": 19,
- },
- },
- "type": 2,
- },
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 42,
- "line": 1,
- "offset": 41,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 42,
- "line": 1,
- "offset": 41,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 31,
- "line": 1,
- "offset": 30,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
+ "source": "",
+ "start": {
+ "column": 3,
+ "line": 1,
+ "offset": 2,
},
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "foo=bar'",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "foo",
- "type": 6,
- "value": Object {
- "content": "bar'",
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "bar'",
- "start": Object {
- "column": 20,
- "line": 1,
- "offset": 19,
- },
- },
- "type": 2,
- },
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
},
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 42,
+ "type": 4,
+ },
+ "loc": {
+ "end": {
+ "column": 5,
"line": 1,
- "offset": 41,
+ "offset": 4,
},
- "source": "
",
- "start": Object {
+ "source": "{{}}",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
+ "type": 5,
},
],
"codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 42,
+ "components": [],
+ "directives": [],
+ "helpers": Set {},
+ "hoists": [],
+ "imports": [],
+ "loc": {
+ "end": {
+ "column": 5,
"line": 1,
- "offset": 41,
+ "offset": 4,
},
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 34,
- "line": 1,
- "offset": 33,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 27,
- "line": 1,
- "offset": 26,
- },
- "source": "foo=bar
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 45,
- "line": 1,
- "offset": 44,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 34,
- "line": 1,
- "offset": 33,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 27,
- "line": 1,
- "offset": 26,
- },
- "source": "foo=bar=baz",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "foo",
- "type": 6,
- "value": Object {
- "content": "bar=baz",
- "loc": Object {
- "end": Object {
- "column": 27,
- "line": 1,
- "offset": 26,
- },
- "source": "bar=baz",
- "start": Object {
- "column": 20,
- "line": 1,
- "offset": 19,
- },
- },
- "type": 2,
- },
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 45,
- "line": 1,
- "offset": 44,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 45,
- "line": 1,
- "offset": 44,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 31,
- "line": 1,
- "offset": 30,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "foo=bar\`",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "foo",
- "type": 6,
- "value": Object {
- "content": "bar\`",
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "bar\`",
- "start": Object {
- "column": 20,
- "line": 1,
- "offset": 19,
- },
- },
- "type": 2,
- },
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 42,
- "line": 1,
- "offset": 41,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 42,
- "line": 1,
- "offset": 41,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 17,
- "line": 1,
- "offset": 16,
- },
- "source": "=",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "=",
- "type": 6,
- "value": undefined,
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 35,
- "line": 1,
- "offset": 34,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 35,
- "line": 1,
- "offset": 34,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 31,
- "line": 1,
- "offset": 30,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "=foo=bar",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "=foo",
- "type": 6,
- "value": Object {
- "content": "bar",
- "loc": Object {
- "end": Object {
- "column": 24,
- "line": 1,
- "offset": 23,
- },
- "source": "bar",
- "start": Object {
- "column": 21,
- "line": 1,
- "offset": 20,
- },
- },
- "type": 2,
- },
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 42,
- "line": 1,
- "offset": 41,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 42,
- "line": 1,
- "offset": 41,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "?xml?",
- "loc": Object {
- "end": Object {
- "column": 18,
- "line": 1,
- "offset": 17,
- },
- "source": "",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "type": 3,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 29,
- "line": 1,
- "offset": 28,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 29,
- "line": 1,
- "offset": 28,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors UNEXPECTED_SOLIDUS_IN_TAG
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 26,
- "line": 1,
- "offset": 25,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "loc": Object {
- "end": Object {
- "column": 17,
- "line": 1,
- "offset": 16,
- },
- "source": "a",
- "start": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- },
- "name": "a",
- "type": 6,
- "value": undefined,
- },
- Object {
- "loc": Object {
- "end": Object {
- "column": 19,
- "line": 1,
- "offset": 18,
- },
- "source": "b",
- "start": Object {
- "column": 18,
- "line": 1,
- "offset": 17,
- },
- },
- "name": "b",
- "type": 6,
- "value": undefined,
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 37,
- "line": 1,
- "offset": 36,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 37,
- "line": 1,
- "offset": 36,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_INVALID_END_TAG
]]> 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "
",
- "loc": Object {
- "end": Object {
- "column": 21,
- "line": 1,
- "offset": 20,
- },
- "source": " ",
- "start": Object {
- "column": 15,
- "line": 1,
- "offset": 14,
- },
- },
- "type": 2,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 30,
- "line": 1,
- "offset": 29,
- },
- "source": "]]> ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 1,
- "props": Array [],
- "tag": "svg",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 30,
- "line": 1,
- "offset": 29,
- },
- "source": "]]> ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_INVALID_END_TAG 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "",
- "loc": Object {
- "end": Object {
- "column": 19,
- "line": 1,
- "offset": 18,
- },
- "source": "",
- "start": Object {
- "column": 6,
- "line": 1,
- "offset": 5,
- },
- },
- "type": 3,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 25,
- "line": 1,
- "offset": 24,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 1,
- "props": Array [],
- "tag": "svg",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 25,
- "line": 1,
- "offset": 24,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_INVALID_END_TAG
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 34,
- "line": 1,
- "offset": 33,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 34,
- "line": 1,
- "offset": 33,
- },
- "source": " ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_INVALID_END_TAG 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 28,
- "line": 1,
- "offset": 27,
- },
- "source": " ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 28,
- "line": 1,
- "offset": 27,
- },
- "source": " ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_INVALID_END_TAG {{''}} 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": Object {
- "constType": 0,
- "content": "''",
- "isStatic": false,
- "loc": Object {
- "end": Object {
- "column": 21,
- "line": 1,
- "offset": 20,
- },
- "source": "''",
- "start": Object {
- "column": 13,
- "line": 1,
- "offset": 12,
- },
- },
- "type": 4,
- },
- "loc": Object {
- "end": Object {
- "column": 23,
- "line": 1,
- "offset": 22,
- },
- "source": "{{''}}",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "type": 5,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 34,
- "line": 1,
- "offset": 33,
- },
- "source": "{{''}} ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 34,
- "line": 1,
- "offset": 33,
- },
- "source": "{{''}} ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_INVALID_END_TAG 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "content": "",
- "loc": Object {
- "end": Object {
- "column": 17,
- "line": 1,
- "offset": 16,
- },
- "source": "",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "type": 2,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 28,
- "line": 1,
- "offset": 27,
- },
- "source": "",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "textarea",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 28,
- "line": 1,
- "offset": 27,
- },
- "source": "",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": true,
- "loc": Object {
- "end": Object {
- "column": 25,
- "line": 1,
- "offset": 24,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "arg": Object {
- "constType": 0,
- "content": "sef",
- "isStatic": false,
- "loc": Object {
- "end": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- "source": "[sef",
- "start": Object {
- "column": 12,
- "line": 1,
- "offset": 11,
- },
- },
- "type": 4,
- },
- "exp": undefined,
- "loc": Object {
- "end": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- "source": "v-foo:[sef",
- "start": Object {
- "column": 6,
- "line": 1,
- "offset": 5,
- },
- },
- "modifiers": Array [],
- "name": "foo",
- "type": 7,
- },
- Object {
- "loc": Object {
- "end": Object {
- "column": 22,
- "line": 1,
- "offset": 21,
- },
- "source": "fsef]",
- "start": Object {
- "column": 17,
- "line": 1,
- "offset": 16,
- },
- },
- "name": "fsef]",
- "type": 6,
- "value": undefined,
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 25,
- "line": 1,
- "offset": 24,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_MISSING_END_TAG 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_MISSING_END_TAG
1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 16,
- "line": 1,
- "offset": 15,
- },
- "source": "
",
- "start": Object {
- "column": 11,
- "line": 1,
- "offset": 10,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 27,
- "line": 1,
- "offset": 26,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "template",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 27,
- "line": 1,
- "offset": 26,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_MISSING_INTERPOLATION_END {{ 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "content": "{{",
- "loc": Object {
- "end": Object {
- "column": 3,
- "line": 1,
- "offset": 2,
- },
- "source": "{{",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "type": 2,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 3,
- "line": 1,
- "offset": 2,
- },
- "source": "{{",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_MISSING_INTERPOLATION_END {{ foo 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "content": "{{ foo",
- "loc": Object {
- "end": Object {
- "column": 7,
- "line": 1,
- "offset": 6,
- },
- "source": "{{ foo",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "type": 2,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 7,
- "line": 1,
- "offset": 6,
- },
- "source": "{{ foo",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse Errors X_MISSING_INTERPOLATION_END {{}} 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "content": Object {
- "constType": 0,
- "content": "",
- "isStatic": false,
- "loc": Object {
- "end": Object {
- "column": 3,
- "line": 1,
- "offset": 2,
- },
- "source": "",
- "start": Object {
- "column": 3,
- "line": 1,
- "offset": 2,
- },
- },
- "type": 4,
- },
- "loc": Object {
- "end": Object {
- "column": 5,
- "line": 1,
- "offset": 4,
- },
- "source": "{{}}",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "type": 5,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 5,
- "line": 1,
- "offset": 4,
- },
- "source": "{{}}",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse invalid html 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 1,
- "line": 3,
- "offset": 13,
- },
- "source": "
-",
- "start": Object {
- "column": 1,
- "line": 2,
- "offset": 6,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "span",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 7,
- "line": 3,
- "offset": 19,
- },
- "source": "
-
-
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 8,
- "line": 4,
- "offset": 27,
- },
- "source": "
-
-
- ",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse self closing multiple tag 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": true,
- "loc": Object {
- "end": Object {
- "column": 37,
- "line": 1,
- "offset": 36,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "arg": Object {
- "constType": 3,
- "content": "class",
- "isStatic": true,
- "loc": Object {
- "end": Object {
- "column": 12,
- "line": 1,
- "offset": 11,
- },
- "source": "class",
- "start": Object {
- "column": 7,
- "line": 1,
- "offset": 6,
- },
- },
- "type": 4,
- },
- "exp": Object {
- "constType": 0,
- "content": "{ some: condition }",
- "isStatic": false,
- "loc": Object {
- "end": Object {
- "column": 33,
- "line": 1,
- "offset": 32,
- },
- "source": "{ some: condition }",
- "start": Object {
- "column": 14,
- "line": 1,
- "offset": 13,
- },
- },
- "type": 4,
- },
- "loc": Object {
- "end": Object {
- "column": 34,
- "line": 1,
- "offset": 33,
- },
- "source": ":class=\\"{ some: condition }\\"",
- "start": Object {
- "column": 6,
- "line": 1,
- "offset": 5,
- },
- },
- "modifiers": Array [],
- "name": "bind",
- "type": 7,
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": true,
- "loc": Object {
- "end": Object {
- "column": 37,
- "line": 2,
- "offset": 73,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 2,
- "offset": 37,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "arg": Object {
- "constType": 3,
- "content": "style",
- "isStatic": true,
- "loc": Object {
- "end": Object {
- "column": 16,
- "line": 2,
- "offset": 52,
- },
- "source": "style",
- "start": Object {
- "column": 11,
- "line": 2,
- "offset": 47,
- },
- },
- "type": 4,
- },
- "exp": Object {
- "constType": 0,
- "content": "{ color: 'red' }",
- "isStatic": false,
- "loc": Object {
- "end": Object {
- "column": 34,
- "line": 2,
- "offset": 70,
- },
- "source": "{ color: 'red' }",
- "start": Object {
- "column": 18,
- "line": 2,
- "offset": 54,
- },
- },
- "type": 4,
- },
- "loc": Object {
- "end": Object {
- "column": 35,
- "line": 2,
- "offset": 71,
- },
- "source": "v-bind:style=\\"{ color: 'red' }\\"",
- "start": Object {
- "column": 4,
- "line": 2,
- "offset": 40,
- },
- },
- "modifiers": Array [],
- "name": "bind",
- "type": 7,
- },
- ],
- "tag": "p",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 37,
- "line": 2,
- "offset": 73,
- },
- "source": "
-
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "temps": 0,
- "type": 0,
-}
-`;
-
-exports[`compiler: parse valid html 1`] = `
-Object {
- "cached": 0,
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [],
- "codegenNode": undefined,
- "isSelfClosing": true,
- "loc": Object {
- "end": Object {
- "column": 39,
- "line": 2,
- "offset": 73,
- },
- "source": "
",
- "start": Object {
- "column": 3,
- "line": 2,
- "offset": 37,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "arg": Object {
- "constType": 3,
- "content": "style",
- "isStatic": true,
- "loc": Object {
- "end": Object {
- "column": 18,
- "line": 2,
- "offset": 52,
- },
- "source": "style",
- "start": Object {
- "column": 13,
- "line": 2,
- "offset": 47,
- },
- },
- "type": 4,
- },
- "exp": Object {
- "constType": 0,
- "content": "{ color: 'red' }",
- "isStatic": false,
- "loc": Object {
- "end": Object {
- "column": 36,
- "line": 2,
- "offset": 70,
- },
- "source": "{ color: 'red' }",
- "start": Object {
- "column": 20,
- "line": 2,
- "offset": 54,
- },
- },
- "type": 4,
- },
- "loc": Object {
- "end": Object {
- "column": 37,
- "line": 2,
- "offset": 71,
- },
- "source": "v-bind:style=\\"{ color: 'red' }\\"",
- "start": Object {
- "column": 6,
- "line": 2,
- "offset": 40,
- },
- },
- "modifiers": Array [],
- "name": "bind",
- "type": 7,
- },
- ],
- "tag": "p",
- "tagType": 0,
- "type": 1,
- },
- Object {
- "content": " a comment with inside it ",
- "loc": Object {
- "end": Object {
- "column": 43,
- "line": 3,
- "offset": 116,
- },
- "source": "",
- "start": Object {
- "column": 3,
- "line": 3,
- "offset": 76,
- },
- },
- "type": 3,
- },
- ],
- "codegenNode": undefined,
- "isSelfClosing": false,
- "loc": Object {
- "end": Object {
- "column": 7,
- "line": 4,
- "offset": 123,
- },
- "source": "
",
- "start": Object {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "ns": 0,
- "props": Array [
- Object {
- "arg": Object {
- "constType": 3,
- "content": "class",
- "isStatic": true,
- "loc": Object {
- "end": Object {
- "column": 12,
- "line": 1,
- "offset": 11,
- },
- "source": "class",
- "start": Object {
- "column": 7,
- "line": 1,
- "offset": 6,
- },
- },
- "type": 4,
- },
- "exp": Object {
- "constType": 0,
- "content": "{ some: condition }",
- "isStatic": false,
- "loc": Object {
- "end": Object {
- "column": 33,
- "line": 1,
- "offset": 32,
- },
- "source": "{ some: condition }",
- "start": Object {
- "column": 14,
- "line": 1,
- "offset": 13,
- },
- },
- "type": 4,
- },
- "loc": Object {
- "end": Object {
- "column": 34,
- "line": 1,
- "offset": 33,
- },
- "source": ":class=\\"{ some: condition }\\"",
- "start": Object {
- "column": 6,
- "line": 1,
- "offset": 5,
- },
- },
- "modifiers": Array [],
- "name": "bind",
- "type": 7,
- },
- ],
- "tag": "div",
- "tagType": 0,
- "type": 1,
- },
- ],
- "codegenNode": undefined,
- "components": Array [],
- "directives": Array [],
- "helpers": Array [],
- "hoists": Array [],
- "imports": Array [],
- "loc": Object {
- "end": Object {
- "column": 7,
- "line": 4,
- "offset": 123,
- },
- "source": "
",
- "start": Object {
+ "source": "{{}}",
+ "start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
+ "source": "{{}}",
"temps": 0,
"type": 0,
}
diff --git a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
index a250f325d0a..97aff163ecb 100644
--- a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
+++ b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
@@ -1,56 +1,41 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`scopeId compiler support should push scopeId for hoisted nodes 1`] = `
-"import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \\"vue\\"
-
-const _withScopeId = n => (_pushScopeId(\\"test\\"),n=n(),_popScopeId(),n)
-const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\\"div\\", null, \\"hello\\", -1 /* HOISTED */))
-const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\\"div\\", null, \\"world\\", -1 /* HOISTED */))
-
-export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _hoisted_1,
- _createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
- _hoisted_2
- ]))
-}"
-`;
-
-exports[`scopeId compiler support should wrap default slot 1`] = `
-"import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
+exports[`scopeId compiler support > should wrap default slot 1`] = `
+"import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
- const _component_Child = _resolveComponent(\\"Child\\")
+ const _component_Child = _resolveComponent("Child")
return (_openBlock(), _createBlock(_component_Child, null, {
default: _withCtx(() => [
- _createElementVNode(\\"div\\")
+ _createElementVNode("div")
]),
_: 1 /* STABLE */
}))
}"
`;
-exports[`scopeId compiler support should wrap dynamic slots 1`] = `
-"import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
+exports[`scopeId compiler support > should wrap dynamic slots 1`] = `
+"import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
- const _component_Child = _resolveComponent(\\"Child\\")
+ const _component_Child = _resolveComponent("Child")
return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
(_ctx.ok)
? {
- name: \\"foo\\",
+ name: "foo",
fn: _withCtx(() => [
- _createElementVNode(\\"div\\")
- ])
+ _createElementVNode("div")
+ ]),
+ key: "0"
}
: undefined,
_renderList(_ctx.list, (i) => {
return {
name: i,
fn: _withCtx(() => [
- _createElementVNode(\\"div\\")
+ _createElementVNode("div")
])
}
})
@@ -58,18 +43,18 @@ export function render(_ctx, _cache) {
}"
`;
-exports[`scopeId compiler support should wrap named slots 1`] = `
-"import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
+exports[`scopeId compiler support > should wrap named slots 1`] = `
+"import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
- const _component_Child = _resolveComponent(\\"Child\\")
+ const _component_Child = _resolveComponent("Child")
return (_openBlock(), _createBlock(_component_Child, null, {
foo: _withCtx(({ msg }) => [
_createTextVNode(_toDisplayString(msg), 1 /* TEXT */)
]),
bar: _withCtx(() => [
- _createElementVNode(\\"div\\")
+ _createElementVNode("div")
]),
_: 1 /* STABLE */
}))
diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts
index b51ca132836..34386ce6930 100644
--- a/packages/compiler-core/__tests__/codegen.spec.ts
+++ b/packages/compiler-core/__tests__/codegen.spec.ts
@@ -1,38 +1,38 @@
import {
- locStub,
- generate,
+ ConstantTypes,
+ type DirectiveArguments,
+ type ForCodegenNode,
+ type IfConditionalExpression,
NodeTypes,
- RootNode,
- createSimpleExpression,
- createObjectExpression,
- createObjectProperty,
+ type RootNode,
+ type VNodeCall,
createArrayExpression,
- createCompoundExpression,
- createInterpolation,
+ createAssignmentExpression,
+ createBlockStatement,
+ createCacheExpression,
createCallExpression,
+ createCompoundExpression,
createConditionalExpression,
- ForCodegenNode,
- createCacheExpression,
- createTemplateLiteral,
- createBlockStatement,
createIfStatement,
- createAssignmentExpression,
- IfConditionalExpression,
+ createInterpolation,
+ createObjectExpression,
+ createObjectProperty,
+ createSimpleExpression,
+ createTemplateLiteral,
createVNodeCall,
- VNodeCall,
- DirectiveArguments,
- ConstantTypes
+ generate,
+ locStub,
} from '../src'
import {
- CREATE_VNODE,
- TO_DISPLAY_STRING,
- RESOLVE_DIRECTIVE,
- helperNameMap,
- RESOLVE_COMPONENT,
CREATE_COMMENT,
+ CREATE_ELEMENT_VNODE,
+ CREATE_VNODE,
FRAGMENT,
RENDER_LIST,
- CREATE_ELEMENT_VNODE
+ RESOLVE_COMPONENT,
+ RESOLVE_DIRECTIVE,
+ TO_DISPLAY_STRING,
+ helperNameMap,
} from '../src/runtimeHelpers'
import { createElementWithCodegen, genFlagText } from './testUtils'
import { PatchFlags } from '@vue/shared'
@@ -40,69 +40,70 @@ import { PatchFlags } from '@vue/shared'
function createRoot(options: Partial
= {}): RootNode {
return {
type: NodeTypes.ROOT,
+ source: '',
children: [],
- helpers: [],
+ helpers: new Set(),
components: [],
directives: [],
imports: [],
hoists: [],
- cached: 0,
+ cached: [],
temps: 0,
codegenNode: createSimpleExpression(`null`, false),
loc: locStub,
- ...options
+ ...options,
}
}
describe('compiler: codegen', () => {
test('module mode preamble', () => {
const root = createRoot({
- helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
+ helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
})
const { code } = generate(root, { mode: 'module' })
expect(code).toMatch(
- `import { ${helperNameMap[CREATE_VNODE]} as _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} as _${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"`
+ `import { ${helperNameMap[CREATE_VNODE]} as _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} as _${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"`,
)
expect(code).toMatchSnapshot()
})
test('module mode preamble w/ optimizeImports: true', () => {
const root = createRoot({
- helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
+ helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
})
const { code } = generate(root, { mode: 'module', optimizeImports: true })
expect(code).toMatch(
- `import { ${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"`
+ `import { ${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"`,
)
expect(code).toMatch(
- `const _${helperNameMap[CREATE_VNODE]} = ${helperNameMap[CREATE_VNODE]}, _${helperNameMap[RESOLVE_DIRECTIVE]} = ${helperNameMap[RESOLVE_DIRECTIVE]}`
+ `const _${helperNameMap[CREATE_VNODE]} = ${helperNameMap[CREATE_VNODE]}, _${helperNameMap[RESOLVE_DIRECTIVE]} = ${helperNameMap[RESOLVE_DIRECTIVE]}`,
)
expect(code).toMatchSnapshot()
})
test('function mode preamble', () => {
const root = createRoot({
- helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
+ helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
})
const { code } = generate(root, { mode: 'function' })
expect(code).toMatch(`const _Vue = Vue`)
expect(code).toMatch(
- `const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = _Vue`
+ `const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = _Vue`,
)
expect(code).toMatchSnapshot()
})
test('function mode preamble w/ prefixIdentifiers: true', () => {
const root = createRoot({
- helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
+ helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
})
const { code } = generate(root, {
mode: 'function',
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(code).not.toMatch(`const _Vue = Vue`)
expect(code).toMatch(
- `const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = Vue`
+ `const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = Vue`,
)
expect(code).toMatchSnapshot()
})
@@ -111,27 +112,27 @@ describe('compiler: codegen', () => {
const root = createRoot({
components: [`Foo`, `bar-baz`, `barbaz`, `Qux__self`],
directives: [`my_dir_0`, `my_dir_1`],
- temps: 3
+ temps: 3,
})
const { code } = generate(root, { mode: 'function' })
expect(code).toMatch(
- `const _component_Foo = _${helperNameMap[RESOLVE_COMPONENT]}("Foo")\n`
+ `const _component_Foo = _${helperNameMap[RESOLVE_COMPONENT]}("Foo")\n`,
)
expect(code).toMatch(
- `const _component_bar_baz = _${helperNameMap[RESOLVE_COMPONENT]}("bar-baz")\n`
+ `const _component_bar_baz = _${helperNameMap[RESOLVE_COMPONENT]}("bar-baz")\n`,
)
expect(code).toMatch(
- `const _component_barbaz = _${helperNameMap[RESOLVE_COMPONENT]}("barbaz")\n`
+ `const _component_barbaz = _${helperNameMap[RESOLVE_COMPONENT]}("barbaz")\n`,
)
// implicit self reference from SFC filename
expect(code).toMatch(
- `const _component_Qux = _${helperNameMap[RESOLVE_COMPONENT]}("Qux", true)\n`
+ `const _component_Qux = _${helperNameMap[RESOLVE_COMPONENT]}("Qux", true)\n`,
)
expect(code).toMatch(
- `const _directive_my_dir_0 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_0")\n`
+ `const _directive_my_dir_0 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_0")\n`,
)
expect(code).toMatch(
- `const _directive_my_dir_1 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_1")\n`
+ `const _directive_my_dir_1 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_1")\n`,
)
expect(code).toMatch(`let _temp0, _temp1, _temp2`)
expect(code).toMatchSnapshot()
@@ -145,12 +146,12 @@ describe('compiler: codegen', () => {
[
createObjectProperty(
createSimpleExpression(`id`, true, locStub),
- createSimpleExpression(`foo`, true, locStub)
- )
+ createSimpleExpression(`foo`, true, locStub),
+ ),
],
- locStub
- )
- ]
+ locStub,
+ ),
+ ],
})
const { code } = generate(root)
expect(code).toMatch(`const _hoisted_1 = hello`)
@@ -160,7 +161,7 @@ describe('compiler: codegen', () => {
test('temps', () => {
const root = createRoot({
- temps: 3
+ temps: 3,
})
const { code } = generate(root)
expect(code).toMatch(`let _temp0, _temp1, _temp2`)
@@ -173,9 +174,9 @@ describe('compiler: codegen', () => {
codegenNode: {
type: NodeTypes.TEXT,
content: 'hello',
- loc: locStub
- }
- })
+ loc: locStub,
+ },
+ }),
)
expect(code).toMatch(`return "hello"`)
expect(code).toMatchSnapshot()
@@ -184,8 +185,8 @@ describe('compiler: codegen', () => {
test('interpolation', () => {
const { code } = generate(
createRoot({
- codegenNode: createInterpolation(`hello`, locStub)
- })
+ codegenNode: createInterpolation(`hello`, locStub),
+ }),
)
expect(code).toMatch(`return _${helperNameMap[TO_DISPLAY_STRING]}(hello)`)
expect(code).toMatchSnapshot()
@@ -197,9 +198,9 @@ describe('compiler: codegen', () => {
codegenNode: {
type: NodeTypes.COMMENT,
content: 'foo',
- loc: locStub
- }
- })
+ loc: locStub,
+ },
+ }),
)
expect(code).toMatch(`return _${helperNameMap[CREATE_COMMENT]}("foo")`)
expect(code).toMatchSnapshot()
@@ -215,15 +216,15 @@ describe('compiler: codegen', () => {
{
type: NodeTypes.INTERPOLATION,
loc: locStub,
- content: createSimpleExpression(`bar`, false, locStub)
+ content: createSimpleExpression(`bar`, false, locStub),
},
// nested compound
- createCompoundExpression([` + `, `nested`])
- ])
- })
+ createCompoundExpression([` + `, `nested`]),
+ ]),
+ }),
)
expect(code).toMatch(
- `return _ctx.foo + _${helperNameMap[TO_DISPLAY_STRING]}(bar) + nested`
+ `return _ctx.foo + _${helperNameMap[TO_DISPLAY_STRING]}(bar) + nested`,
)
expect(code).toMatchSnapshot()
})
@@ -238,10 +239,10 @@ describe('compiler: codegen', () => {
codegenNode: createConditionalExpression(
createSimpleExpression('foo', false),
createSimpleExpression('bar', false),
- createSimpleExpression('baz', false)
- ) as IfConditionalExpression
- }
- })
+ createSimpleExpression('baz', false),
+ ) as IfConditionalExpression,
+ },
+ }),
)
expect(code).toMatch(/return foo\s+\? bar\s+: baz/)
expect(code).toMatchSnapshot()
@@ -266,13 +267,13 @@ describe('compiler: codegen', () => {
disableTracking: true,
props: undefined,
children: createCallExpression(RENDER_LIST),
- patchFlag: '1',
+ patchFlag: PatchFlags.TEXT,
dynamicProps: undefined,
directives: undefined,
- loc: locStub
- } as ForCodegenNode
- }
- })
+ loc: locStub,
+ } as ForCodegenNode,
+ },
+ }),
)
expect(code).toMatch(`openBlock(true)`)
expect(code).toMatchSnapshot()
@@ -288,7 +289,7 @@ describe('compiler: codegen', () => {
'1 + 2',
false,
locStub,
- ConstantTypes.CAN_STRINGIFY
+ ConstantTypes.CAN_STRINGIFY,
),
valueAlias: undefined,
keyAlias: undefined,
@@ -302,13 +303,13 @@ describe('compiler: codegen', () => {
disableTracking: false,
props: undefined,
children: createCallExpression(RENDER_LIST),
- patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
+ patchFlag: PatchFlags.STABLE_FRAGMENT,
dynamicProps: undefined,
directives: undefined,
- loc: locStub
- } as ForCodegenNode
- }
- })
+ loc: locStub,
+ } as ForCodegenNode,
+ },
+ }),
)
expect(code).toMatch(`openBlock()`)
expect(code).toMatchSnapshot()
@@ -325,11 +326,11 @@ describe('compiler: codegen', () => {
[
createObjectProperty(
createSimpleExpression(`id`, true, locStub),
- createSimpleExpression(`foo`, true, locStub)
+ createSimpleExpression(`foo`, true, locStub),
),
createObjectProperty(
createSimpleExpression(`prop`, false, locStub),
- createSimpleExpression(`bar`, false, locStub)
+ createSimpleExpression(`bar`, false, locStub),
),
// compound expression as computed key
createObjectProperty(
@@ -338,13 +339,13 @@ describe('compiler: codegen', () => {
loc: locStub,
children: [
`foo + `,
- createSimpleExpression(`bar`, false, locStub)
- ]
+ createSimpleExpression(`bar`, false, locStub),
+ ],
},
- createSimpleExpression(`bar`, false, locStub)
- )
+ createSimpleExpression(`bar`, false, locStub),
+ ),
],
- locStub
+ locStub,
),
// ChildNode[]
[
@@ -355,17 +356,17 @@ describe('compiler: codegen', () => {
createObjectProperty(
// should quote the key!
createSimpleExpression(`some-key`, true, locStub),
- createSimpleExpression(`foo`, true, locStub)
- )
+ createSimpleExpression(`foo`, true, locStub),
+ ),
],
- locStub
- )
- )
+ locStub,
+ ),
+ ),
],
// flag
- PatchFlags.FULL_PROPS + ''
- )
- })
+ PatchFlags.FULL_PROPS,
+ ),
+ }),
)
expect(code).toMatch(`
return _${helperNameMap[CREATE_ELEMENT_VNODE]}("div", {
@@ -374,7 +375,7 @@ describe('compiler: codegen', () => {
[foo + bar]: bar
}, [
_${helperNameMap[CREATE_ELEMENT_VNODE]}("p", { "some-key": "foo" })
- ], ${PatchFlags.FULL_PROPS})`)
+ ], ${genFlagText(PatchFlags.FULL_PROPS)})`)
expect(code).toMatchSnapshot()
})
@@ -383,9 +384,9 @@ describe('compiler: codegen', () => {
createRoot({
codegenNode: createArrayExpression([
createSimpleExpression(`foo`, false),
- createCallExpression(`bar`, [`baz`])
- ])
- })
+ createCallExpression(`bar`, [`baz`]),
+ ]),
+ }),
)
expect(code).toMatch(`return [
foo,
@@ -403,17 +404,17 @@ describe('compiler: codegen', () => {
createConditionalExpression(
createSimpleExpression(`orNot`, false),
createCallExpression(`bar`),
- createCallExpression(`baz`)
- )
- )
- })
+ createCallExpression(`baz`),
+ ),
+ ),
+ }),
)
expect(code).toMatch(
`return ok
? foo()
: orNot
? bar()
- : baz()`
+ : baz()`,
)
expect(code).toMatchSnapshot()
})
@@ -421,45 +422,45 @@ describe('compiler: codegen', () => {
test('CacheExpression', () => {
const { code } = generate(
createRoot({
- cached: 1,
+ cached: [],
codegenNode: createCacheExpression(
1,
- createSimpleExpression(`foo`, false)
- )
+ createSimpleExpression(`foo`, false),
+ ),
}),
{
mode: 'module',
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`)
expect(code).toMatchSnapshot()
})
- test('CacheExpression w/ isVNode: true', () => {
+ test('CacheExpression w/ isVOnce: true', () => {
const { code } = generate(
createRoot({
- cached: 1,
+ cached: [],
codegenNode: createCacheExpression(
1,
createSimpleExpression(`foo`, false),
- true
- )
+ true,
+ ),
}),
{
mode: 'module',
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(code).toMatch(
`
_cache[1] || (
_setBlockTracking(-1),
- _cache[1] = foo,
+ (_cache[1] = foo).cacheIndex = 1,
_setBlockTracking(1),
_cache[1]
)
- `.trim()
+ `.trim(),
)
expect(code).toMatchSnapshot()
})
@@ -471,11 +472,11 @@ describe('compiler: codegen', () => {
createTemplateLiteral([
`foo`,
createCallExpression(`_renderAttr`, ['id', 'foo']),
- `bar`
- ])
- ])
+ `bar`,
+ ]),
+ ]),
}),
- { ssr: true, mode: 'module' }
+ { ssr: true, mode: 'module' },
)
expect(code).toMatchInlineSnapshot(`
"
@@ -492,11 +493,11 @@ describe('compiler: codegen', () => {
codegenNode: createBlockStatement([
createIfStatement(
createSimpleExpression('foo', false),
- createBlockStatement([createCallExpression(`ok`)])
- )
- ])
+ createBlockStatement([createCallExpression(`ok`)]),
+ ),
+ ]),
}),
- { ssr: true, mode: 'module' }
+ { ssr: true, mode: 'module' },
)
expect(code).toMatchInlineSnapshot(`
"
@@ -515,11 +516,11 @@ describe('compiler: codegen', () => {
createIfStatement(
createSimpleExpression('foo', false),
createBlockStatement([createCallExpression(`foo`)]),
- createBlockStatement([createCallExpression('bar')])
- )
- ])
+ createBlockStatement([createCallExpression('bar')]),
+ ),
+ ]),
}),
- { ssr: true, mode: 'module' }
+ { ssr: true, mode: 'module' },
)
expect(code).toMatchInlineSnapshot(`
"
@@ -542,12 +543,12 @@ describe('compiler: codegen', () => {
createBlockStatement([createCallExpression(`foo`)]),
createIfStatement(
createSimpleExpression('bar', false),
- createBlockStatement([createCallExpression(`bar`)])
- )
- )
- ])
+ createBlockStatement([createCallExpression(`bar`)]),
+ ),
+ ),
+ ]),
}),
- { ssr: true, mode: 'module' }
+ { ssr: true, mode: 'module' },
)
expect(code).toMatchInlineSnapshot(`
"
@@ -571,12 +572,12 @@ describe('compiler: codegen', () => {
createIfStatement(
createSimpleExpression('bar', false),
createBlockStatement([createCallExpression(`bar`)]),
- createBlockStatement([createCallExpression('baz')])
- )
- )
- ])
+ createBlockStatement([createCallExpression('baz')]),
+ ),
+ ),
+ ]),
}),
- { ssr: true, mode: 'module' }
+ { ssr: true, mode: 'module' },
)
expect(code).toMatchInlineSnapshot(`
"
@@ -598,9 +599,9 @@ describe('compiler: codegen', () => {
createRoot({
codegenNode: createAssignmentExpression(
createSimpleExpression(`foo`, false),
- createSimpleExpression(`bar`, false)
- )
- })
+ createSimpleExpression(`bar`, false),
+ ),
+ }),
)
expect(code).toMatchInlineSnapshot(`
"
@@ -616,24 +617,24 @@ describe('compiler: codegen', () => {
function genCode(node: VNodeCall) {
return generate(
createRoot({
- codegenNode: node
- })
+ codegenNode: node,
+ }),
).code.match(/with \(_ctx\) \{\s+([^]+)\s+\}\s+\}$/)![1]
}
const mockProps = createObjectExpression([
- createObjectProperty(`foo`, createSimpleExpression(`bar`, true))
+ createObjectProperty(`foo`, createSimpleExpression(`bar`, true)),
])
const mockChildren = createCompoundExpression(['children'])
const mockDirs = createArrayExpression([
- createArrayExpression([`foo`, createSimpleExpression(`bar`, false)])
+ createArrayExpression([`foo`, createSimpleExpression(`bar`, false)]),
]) as DirectiveArguments
test('tag only', () => {
expect(genCode(createVNodeCall(null, `"div"`))).toMatchInlineSnapshot(`
- "return _createElementVNode(\\"div\\")
- "
- `)
+ "return _createElementVNode("div")
+ "
+ `)
expect(genCode(createVNodeCall(null, FRAGMENT))).toMatchInlineSnapshot(`
"return _createElementVNode(_Fragment)
"
@@ -643,31 +644,34 @@ describe('compiler: codegen', () => {
test('with props', () => {
expect(genCode(createVNodeCall(null, `"div"`, mockProps)))
.toMatchInlineSnapshot(`
- "return _createElementVNode(\\"div\\", { foo: \\"bar\\" })
- "
- `)
+ "return _createElementVNode("div", { foo: "bar" })
+ "
+ `)
})
test('with children, no props', () => {
expect(genCode(createVNodeCall(null, `"div"`, undefined, mockChildren)))
.toMatchInlineSnapshot(`
- "return _createElementVNode(\\"div\\", null, children)
- "
- `)
+ "return _createElementVNode("div", null, children)
+ "
+ `)
})
test('with children + props', () => {
expect(genCode(createVNodeCall(null, `"div"`, mockProps, mockChildren)))
.toMatchInlineSnapshot(`
- "return _createElementVNode(\\"div\\", { foo: \\"bar\\" }, children)
- "
- `)
+ "return _createElementVNode("div", { foo: "bar" }, children)
+ "
+ `)
})
test('with patchFlag and no children/props', () => {
- expect(genCode(createVNodeCall(null, `"div"`, undefined, undefined, '1')))
- .toMatchInlineSnapshot(`
- "return _createElementVNode(\\"div\\", null, null, 1)
+ expect(
+ genCode(
+ createVNodeCall(null, `"div"`, undefined, undefined, PatchFlags.TEXT),
+ ),
+ ).toMatchInlineSnapshot(`
+ "return _createElementVNode("div", null, null, 1 /* TEXT */)
"
`)
})
@@ -683,11 +687,11 @@ describe('compiler: codegen', () => {
undefined,
undefined,
undefined,
- true
- )
- )
+ true,
+ ),
+ ),
).toMatchInlineSnapshot(`
- "return (_openBlock(), _createElementBlock(\\"div\\", { foo: \\"bar\\" }, children))
+ "return (_openBlock(), _createElementBlock("div", { foo: "bar" }, children))
"
`)
})
@@ -704,11 +708,11 @@ describe('compiler: codegen', () => {
undefined,
undefined,
true,
- true
- )
- )
+ true,
+ ),
+ ),
).toMatchInlineSnapshot(`
- "return (_openBlock(true), _createElementBlock(\\"div\\", { foo: \\"bar\\" }, children))
+ "return (_openBlock(true), _createElementBlock("div", { foo: "bar" }, children))
"
`)
})
@@ -723,11 +727,11 @@ describe('compiler: codegen', () => {
mockChildren,
undefined,
undefined,
- mockDirs
- )
- )
+ mockDirs,
+ ),
+ ),
).toMatchInlineSnapshot(`
- "return _withDirectives(_createElementVNode(\\"div\\", { foo: \\"bar\\" }, children), [
+ "return _withDirectives(_createElementVNode("div", { foo: "bar" }, children), [
[foo, bar]
])
"
@@ -745,11 +749,11 @@ describe('compiler: codegen', () => {
undefined,
undefined,
mockDirs,
- true
- )
- )
+ true,
+ ),
+ ),
).toMatchInlineSnapshot(`
- "return _withDirectives((_openBlock(), _createElementBlock(\\"div\\", { foo: \\"bar\\" }, children)), [
+ "return _withDirectives((_openBlock(), _createElementBlock("div", { foo: "bar" }, children)), [
[foo, bar]
])
"
diff --git a/packages/compiler-core/__tests__/compile.spec.ts b/packages/compiler-core/__tests__/compile.spec.ts
index 33766f71b3b..995741091df 100644
--- a/packages/compiler-core/__tests__/compile.spec.ts
+++ b/packages/compiler-core/__tests__/compile.spec.ts
@@ -1,5 +1,5 @@
import { baseCompile as compile } from '../src'
-import { SourceMapConsumer, RawSourceMap } from 'source-map'
+import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
describe('compiler: integration tests', () => {
const source = `
@@ -20,7 +20,7 @@ describe('compiler: integration tests', () => {
function getPositionInCode(
code: string,
token: string,
- expectName: string | boolean = false
+ expectName: string | boolean = false,
): Pos {
const generatedOffset = code.indexOf(token)
let line = 1
@@ -36,7 +36,7 @@ describe('compiler: integration tests', () => {
column:
lastNewLinePos === -1
? generatedOffset
- : generatedOffset - lastNewLinePos - 1
+ : generatedOffset - lastNewLinePos - 1,
}
if (expectName) {
res.name = typeof expectName === 'string' ? expectName : token
@@ -47,7 +47,7 @@ describe('compiler: integration tests', () => {
test('function mode', () => {
const { code, map } = compile(source, {
sourceMap: true,
- filename: `foo.vue`
+ filename: `foo.vue`,
})
expect(code).toMatchSnapshot()
@@ -57,55 +57,55 @@ describe('compiler: integration tests', () => {
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
- consumer.originalPositionFor(getPositionInCode(code, `id`))
+ consumer.originalPositionFor(getPositionInCode(code, `id`)),
).toMatchObject(getPositionInCode(source, `id`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `"foo"`))
+ consumer.originalPositionFor(getPositionInCode(code, `"foo"`)),
).toMatchObject(getPositionInCode(source, `"foo"`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `class:`))
+ consumer.originalPositionFor(getPositionInCode(code, `class:`)),
).toMatchObject(getPositionInCode(source, `class=`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `bar`))
+ consumer.originalPositionFor(getPositionInCode(code, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`))
// without prefixIdentifiers: true, identifiers inside compound expressions
// are mapped to closest parent expression.
expect(
- consumer.originalPositionFor(getPositionInCode(code, `baz`))
+ consumer.originalPositionFor(getPositionInCode(code, `baz`)),
).toMatchObject(getPositionInCode(source, `bar`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `world`))
+ consumer.originalPositionFor(getPositionInCode(code, `world`)),
).toMatchObject(getPositionInCode(source, `world`))
// without prefixIdentifiers: true, identifiers inside compound expressions
// are mapped to closest parent expression.
expect(
- consumer.originalPositionFor(getPositionInCode(code, `burn()`))
+ consumer.originalPositionFor(getPositionInCode(code, `burn()`)),
).toMatchObject(getPositionInCode(source, `world`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `ok`))
+ consumer.originalPositionFor(getPositionInCode(code, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `list`))
+ consumer.originalPositionFor(getPositionInCode(code, `list`)),
).toMatchObject(getPositionInCode(source, `list`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value`))
+ consumer.originalPositionFor(getPositionInCode(code, `value`)),
).toMatchObject(getPositionInCode(source, `value`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `index`))
+ consumer.originalPositionFor(getPositionInCode(code, `index`)),
).toMatchObject(getPositionInCode(source, `index`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value + index`))
+ consumer.originalPositionFor(getPositionInCode(code, `value + index`)),
).toMatchObject(getPositionInCode(source, `value + index`))
})
@@ -113,7 +113,7 @@ describe('compiler: integration tests', () => {
const { code, map } = compile(source, {
sourceMap: true,
filename: `foo.vue`,
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(code).toMatchSnapshot()
@@ -123,64 +123,66 @@ describe('compiler: integration tests', () => {
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
- consumer.originalPositionFor(getPositionInCode(code, `id`))
+ consumer.originalPositionFor(getPositionInCode(code, `id`)),
).toMatchObject(getPositionInCode(source, `id`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `"foo"`))
+ consumer.originalPositionFor(getPositionInCode(code, `"foo"`)),
).toMatchObject(getPositionInCode(source, `"foo"`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `class:`))
+ consumer.originalPositionFor(getPositionInCode(code, `class:`)),
).toMatchObject(getPositionInCode(source, `class=`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `bar`))
+ consumer.originalPositionFor(getPositionInCode(code, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`))
+ consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `baz`))
+ consumer.originalPositionFor(getPositionInCode(code, `baz`)),
).toMatchObject(getPositionInCode(source, `baz`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `world`, true))
+ consumer.originalPositionFor(getPositionInCode(code, `world`, true)),
).toMatchObject(getPositionInCode(source, `world`, `world`))
expect(
consumer.originalPositionFor(
- getPositionInCode(code, `_ctx.world`, `world`)
- )
+ getPositionInCode(code, `_ctx.world`, `world`),
+ ),
).toMatchObject(getPositionInCode(source, `world`, `world`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `burn()`))
+ consumer.originalPositionFor(getPositionInCode(code, `burn()`)),
).toMatchObject(getPositionInCode(source, `burn()`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `ok`))
+ consumer.originalPositionFor(getPositionInCode(code, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`))
+ consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `list`))
+ consumer.originalPositionFor(getPositionInCode(code, `list`)),
).toMatchObject(getPositionInCode(source, `list`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.list`, `list`))
+ consumer.originalPositionFor(
+ getPositionInCode(code, `_ctx.list`, `list`),
+ ),
).toMatchObject(getPositionInCode(source, `list`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value`))
+ consumer.originalPositionFor(getPositionInCode(code, `value`)),
).toMatchObject(getPositionInCode(source, `value`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `index`))
+ consumer.originalPositionFor(getPositionInCode(code, `index`)),
).toMatchObject(getPositionInCode(source, `index`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value + index`))
+ consumer.originalPositionFor(getPositionInCode(code, `value + index`)),
).toMatchObject(getPositionInCode(source, `value + index`))
})
@@ -188,7 +190,7 @@ describe('compiler: integration tests', () => {
const { code, map } = compile(source, {
mode: 'module',
sourceMap: true,
- filename: `foo.vue`
+ filename: `foo.vue`,
})
expect(code).toMatchSnapshot()
@@ -198,64 +200,66 @@ describe('compiler: integration tests', () => {
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
- consumer.originalPositionFor(getPositionInCode(code, `id`))
+ consumer.originalPositionFor(getPositionInCode(code, `id`)),
).toMatchObject(getPositionInCode(source, `id`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `"foo"`))
+ consumer.originalPositionFor(getPositionInCode(code, `"foo"`)),
).toMatchObject(getPositionInCode(source, `"foo"`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `class:`))
+ consumer.originalPositionFor(getPositionInCode(code, `class:`)),
).toMatchObject(getPositionInCode(source, `class=`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `bar`))
+ consumer.originalPositionFor(getPositionInCode(code, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`))
+ consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `baz`))
+ consumer.originalPositionFor(getPositionInCode(code, `baz`)),
).toMatchObject(getPositionInCode(source, `baz`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `world`, true))
+ consumer.originalPositionFor(getPositionInCode(code, `world`, true)),
).toMatchObject(getPositionInCode(source, `world`, `world`))
expect(
consumer.originalPositionFor(
- getPositionInCode(code, `_ctx.world`, `world`)
- )
+ getPositionInCode(code, `_ctx.world`, `world`),
+ ),
).toMatchObject(getPositionInCode(source, `world`, `world`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `burn()`))
+ consumer.originalPositionFor(getPositionInCode(code, `burn()`)),
).toMatchObject(getPositionInCode(source, `burn()`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `ok`))
+ consumer.originalPositionFor(getPositionInCode(code, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`))
+ consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `list`))
+ consumer.originalPositionFor(getPositionInCode(code, `list`)),
).toMatchObject(getPositionInCode(source, `list`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `_ctx.list`, `list`))
+ consumer.originalPositionFor(
+ getPositionInCode(code, `_ctx.list`, `list`),
+ ),
).toMatchObject(getPositionInCode(source, `list`, true))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value`))
+ consumer.originalPositionFor(getPositionInCode(code, `value`)),
).toMatchObject(getPositionInCode(source, `value`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `index`))
+ consumer.originalPositionFor(getPositionInCode(code, `index`)),
).toMatchObject(getPositionInCode(source, `index`))
expect(
- consumer.originalPositionFor(getPositionInCode(code, `value + index`))
+ consumer.originalPositionFor(getPositionInCode(code, `value + index`)),
).toMatchObject(getPositionInCode(source, `value + index`))
})
})
diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts
index f1869739cf4..cdc2b09fd48 100644
--- a/packages/compiler-core/__tests__/parse.spec.ts
+++ b/packages/compiler-core/__tests__/parse.spec.ts
@@ -1,18 +1,21 @@
-import { ParserOptions } from '../src/options'
-import { baseParse, TextModes } from '../src/parse'
+import type { ParserOptions } from '../src/options'
import { ErrorCodes } from '../src/errors'
import {
- CommentNode,
- ElementNode,
+ type CommentNode,
+ ConstantTypes,
+ type DirectiveNode,
+ type ElementNode,
ElementTypes,
+ type InterpolationNode,
Namespaces,
NodeTypes,
- Position,
- TextNode,
- InterpolationNode,
- ConstantTypes
+ type Position,
+ type TextNode,
} from '../src/ast'
+import { baseParse } from '../src/parser'
+import type { Program } from '@babel/types'
+
describe('compiler: parse', () => {
describe('Text', () => {
test('simple text', () => {
@@ -25,27 +28,36 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 9, line: 1, column: 10 },
- source: 'some text'
- }
+ source: 'some text',
+ },
})
})
test('simple text with invalid end tag', () => {
- const onError = jest.fn()
- const ast = baseParse('some text ', {
- onError
- })
+ const onError = vi.fn()
+ const ast = baseParse('some text ', { onError })
const text = ast.children[0] as TextNode
- expect(onError).toBeCalled()
+ expect(onError.mock.calls).toMatchObject([
+ [
+ {
+ code: ErrorCodes.X_INVALID_END_TAG,
+ loc: {
+ start: { column: 10, line: 1, offset: 9 },
+ end: { column: 10, line: 1, offset: 9 },
+ },
+ },
+ ],
+ ])
+
expect(text).toStrictEqual({
type: NodeTypes.TEXT,
content: 'some text',
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 9, line: 1, column: 10 },
- source: 'some text'
- }
+ source: 'some text',
+ },
})
})
@@ -60,8 +72,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 },
- source: 'some '
- }
+ source: 'some ',
+ },
})
expect(text2).toStrictEqual({
type: NodeTypes.TEXT,
@@ -69,8 +81,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 20, line: 1, column: 21 },
end: { offset: 25, line: 1, column: 26 },
- source: ' text'
- }
+ source: ' text',
+ },
})
})
@@ -85,8 +97,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 },
- source: 'some '
- }
+ source: 'some ',
+ },
})
expect(text2).toStrictEqual({
type: NodeTypes.TEXT,
@@ -94,8 +106,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 21, line: 1, column: 22 },
end: { offset: 26, line: 1, column: 27 },
- source: ' text'
- }
+ source: ' text',
+ },
})
})
@@ -110,8 +122,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 },
- source: 'some '
- }
+ source: 'some ',
+ },
})
expect(text2).toStrictEqual({
type: NodeTypes.TEXT,
@@ -119,8 +131,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 32, line: 1, column: 33 },
end: { offset: 37, line: 1, column: 38 },
- source: ' text'
- }
+ source: ' text',
+ },
})
})
@@ -130,7 +142,7 @@ describe('compiler: parse', () => {
if (err.code !== ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME) {
throw err
}
- }
+ },
})
const text = ast.children[0] as TextNode
@@ -140,8 +152,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 },
- source: 'a < b'
- }
+ source: 'a < b',
+ },
})
})
@@ -151,7 +163,7 @@ describe('compiler: parse', () => {
if (error.code !== ErrorCodes.X_MISSING_INTERPOLATION_END) {
throw error
}
- }
+ },
})
const text = ast.children[0] as TextNode
@@ -161,8 +173,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 6, line: 1, column: 7 },
- source: 'a {{ b'
- }
+ source: 'a {{ b',
+ },
})
})
})
@@ -182,14 +194,14 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 2, line: 1, column: 3 },
end: { offset: 9, line: 1, column: 10 },
- source: `message`
- }
+ source: 'message',
+ },
},
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 11, line: 1, column: 12 },
- source: '{{message}}'
- }
+ source: '{{message}}',
+ },
})
})
@@ -207,14 +219,14 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 3, line: 1, column: 4 },
end: { offset: 6, line: 1, column: 7 },
- source: 'a {
loc: {
start: { offset: 3, line: 1, column: 4 },
end: { offset: 6, line: 1, column: 7 },
- source: 'a {
loc: {
start: { offset: 12, line: 1, column: 13 },
end: { offset: 15, line: 1, column: 16 },
- source: 'c>d'
- }
+ source: 'c>d',
+ },
},
loc: {
start: { offset: 9, line: 1, column: 10 },
end: { offset: 18, line: 1, column: 19 },
- source: '{{ c>d }}'
- }
+ source: '{{ c>d }}',
+ },
})
})
@@ -280,20 +292,20 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 16, line: 1, column: 17 },
- source: '" "'
- }
+ source: '"
"',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 19, line: 1, column: 20 },
- source: '{{ " " }}'
- }
+ source: '{{ " " }}',
+ },
})
})
test('custom delimiters', () => {
const ast = baseParse('{msg}
', {
- delimiters: ['{', '}']
+ delimiters: ['{', '}'],
})
const element = ast.children[0] as ElementNode
const interpolation = element.children[0] as InterpolationNode
@@ -308,14 +320,14 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 4, line: 1, column: 5 },
end: { offset: 7, line: 1, column: 8 },
- source: 'msg'
- }
+ source: 'msg',
+ },
},
loc: {
start: { offset: 3, line: 1, column: 4 },
end: { offset: 8, line: 1, column: 9 },
- source: '{msg}'
- }
+ source: '{msg}',
+ },
})
})
})
@@ -331,8 +343,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 7, line: 1, column: 8 },
- source: ''
- }
+ source: '',
+ },
})
})
@@ -346,8 +358,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 10, line: 1, column: 11 },
- source: ''
- }
+ source: '',
+ },
})
})
@@ -362,8 +374,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 10, line: 1, column: 11 },
- source: ''
- }
+ source: '',
+ },
})
expect(comment2).toStrictEqual({
type: NodeTypes.COMMENT,
@@ -371,8 +383,8 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 20, line: 1, column: 21 },
- source: ''
- }
+ source: '',
+ },
})
})
@@ -389,38 +401,38 @@ describe('compiler: parse', () => {
const rawText = `
`
const astWithComments = baseParse(`${rawText} `, {
- comments: true
+ comments: true,
})
expect(
- (astWithComments.children[0] as ElementNode).children
+ (astWithComments.children[0] as ElementNode).children,
).toMatchObject([
{
type: NodeTypes.ELEMENT,
- tag: 'p'
+ tag: 'p',
},
{
- type: NodeTypes.COMMENT
+ type: NodeTypes.COMMENT,
},
{
type: NodeTypes.ELEMENT,
- tag: 'p'
- }
+ tag: 'p',
+ },
])
const astWithoutComments = baseParse(`${rawText} `, {
- comments: false
+ comments: false,
})
expect(
- (astWithoutComments.children[0] as ElementNode).children
+ (astWithoutComments.children[0] as ElementNode).children,
).toMatchObject([
{
type: NodeTypes.ELEMENT,
- tag: 'p'
+ tag: 'p',
},
{
type: NodeTypes.ELEMENT,
- tag: 'p'
- }
+ tag: 'p',
+ },
])
})
})
@@ -437,7 +449,6 @@ describe('compiler: parse', () => {
tagType: ElementTypes.ELEMENT,
codegenNode: undefined,
props: [],
- isSelfClosing: false,
children: [
{
type: NodeTypes.TEXT,
@@ -445,15 +456,15 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 10, line: 1, column: 11 },
- source: 'hello'
- }
- }
+ source: 'hello',
+ },
+ },
],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 16, line: 1, column: 17 },
- source: 'hello
'
- }
+ source: 'hello
',
+ },
})
})
@@ -468,13 +479,12 @@ describe('compiler: parse', () => {
tagType: ElementTypes.ELEMENT,
codegenNode: undefined,
props: [],
- isSelfClosing: false,
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 11, line: 1, column: 12 },
- source: '
'
- }
+ source: '
',
+ },
})
})
@@ -489,20 +499,19 @@ describe('compiler: parse', () => {
tagType: ElementTypes.ELEMENT,
codegenNode: undefined,
props: [],
-
- isSelfClosing: true,
children: [],
+ isSelfClosing: true,
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 6, line: 1, column: 7 },
- source: '
'
- }
+ source: '
',
+ },
})
})
test('void element', () => {
const ast = baseParse(' after', {
- isVoidTag: tag => tag === 'img'
+ isVoidTag: tag => tag === 'img',
})
const element = ast.children[0] as ElementNode
@@ -513,14 +522,35 @@ describe('compiler: parse', () => {
tagType: ElementTypes.ELEMENT,
codegenNode: undefined,
props: [],
-
- isSelfClosing: false,
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 },
- source: ' '
- }
+ source: ' ',
+ },
+ })
+ })
+
+ test('self-closing void element', () => {
+ const ast = baseParse(' after', {
+ isVoidTag: tag => tag === 'img',
+ })
+ const element = ast.children[0] as ElementNode
+
+ expect(element).toStrictEqual({
+ type: NodeTypes.ELEMENT,
+ ns: Namespaces.HTML,
+ tag: 'img',
+ tagType: ElementTypes.ELEMENT,
+ codegenNode: undefined,
+ props: [],
+ children: [],
+ isSelfClosing: true,
+ loc: {
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 6, line: 1, column: 7 },
+ source: ' ',
+ },
})
})
@@ -529,7 +559,7 @@ describe('compiler: parse', () => {
const element = ast.children[0]
expect(element).toMatchObject({
type: NodeTypes.ELEMENT,
- tagType: ElementTypes.TEMPLATE
+ tagType: ElementTypes.TEMPLATE,
})
})
@@ -538,31 +568,31 @@ describe('compiler: parse', () => {
const element = ast.children[0]
expect(element).toMatchObject({
type: NodeTypes.ELEMENT,
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
})
test('native element with `isNativeTag`', () => {
const ast = baseParse('
', {
- isNativeTag: tag => tag === 'div'
+ isNativeTag: tag => tag === 'div',
})
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
@@ -572,105 +602,105 @@ describe('compiler: parse', () => {
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'comp',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
- test('v-is with `isNativeTag`', () => {
+ test('is casting with `isNativeTag`', () => {
const ast = baseParse(
- `
`,
+ `
`,
{
- isNativeTag: tag => tag === 'div'
- }
+ isNativeTag: tag => tag === 'div',
+ },
)
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
- test('v-is without `isNativeTag`', () => {
- const ast = baseParse(`
`)
+ test('is casting without `isNativeTag`', () => {
+ const ast = baseParse(`
`)
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
test('custom element', () => {
const ast = baseParse('
', {
isNativeTag: tag => tag === 'div',
- isCustomElement: tag => tag === 'comp'
+ isCustomElement: tag => tag === 'comp',
})
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'comp',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
})
test('built-in component', () => {
const ast = baseParse('
', {
- isBuiltInComponent: tag => (tag === 'comp' ? Symbol() : void 0)
+ isBuiltInComponent: tag => (tag === 'comp' ? Symbol() : void 0),
})
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
- tagType: ElementTypes.ELEMENT
+ tagType: ElementTypes.ELEMENT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
@@ -680,13 +710,13 @@ describe('compiler: parse', () => {
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'slot',
- tagType: ElementTypes.SLOT
+ tagType: ElementTypes.SLOT,
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
- tagType: ElementTypes.COMPONENT
+ tagType: ElementTypes.COMPONENT,
})
})
@@ -704,22 +734,26 @@ describe('compiler: parse', () => {
{
type: NodeTypes.ATTRIBUTE,
name: 'id',
+ nameLoc: {
+ start: { offset: 5, line: 1, column: 6 },
+ end: { offset: 7, line: 1, column: 8 },
+ source: 'id',
+ },
value: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'id'
- }
- }
+ source: 'id',
+ },
+ },
],
- isSelfClosing: false,
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 14, line: 1, column: 15 },
- source: '
'
- }
+ source: '
',
+ },
})
})
@@ -737,30 +771,34 @@ describe('compiler: parse', () => {
{
type: NodeTypes.ATTRIBUTE,
name: 'id',
+ nameLoc: {
+ start: { offset: 5, line: 1, column: 6 },
+ end: { offset: 7, line: 1, column: 8 },
+ source: 'id',
+ },
value: {
type: NodeTypes.TEXT,
content: '',
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 10, line: 1, column: 11 },
- source: '""'
- }
+ source: '""',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 10, line: 1, column: 11 },
- source: 'id=""'
- }
- }
+ source: 'id=""',
+ },
+ },
],
- isSelfClosing: false,
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 17, line: 1, column: 18 },
- source: '
'
- }
+ source: '
',
+ },
})
})
@@ -778,30 +816,34 @@ describe('compiler: parse', () => {
{
type: NodeTypes.ATTRIBUTE,
name: 'id',
+ nameLoc: {
+ start: { offset: 5, line: 1, column: 6 },
+ end: { offset: 7, line: 1, column: 8 },
+ source: 'id',
+ },
value: {
type: NodeTypes.TEXT,
content: '',
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 10, line: 1, column: 11 },
- source: "''"
- }
+ source: "''",
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 10, line: 1, column: 11 },
- source: "id=''"
- }
- }
+ source: "id=''",
+ },
+ },
],
- isSelfClosing: false,
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 17, line: 1, column: 18 },
- source: "
"
- }
+ source: "
",
+ },
})
})
@@ -819,30 +861,34 @@ describe('compiler: parse', () => {
{
type: NodeTypes.ATTRIBUTE,
name: 'id',
+ nameLoc: {
+ start: { offset: 5, line: 1, column: 6 },
+ end: { offset: 7, line: 1, column: 8 },
+ source: 'id',
+ },
value: {
type: NodeTypes.TEXT,
content: ">'",
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 12, line: 1, column: 13 },
- source: '">\'"'
- }
+ source: '">\'"',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 12, line: 1, column: 13 },
- source: 'id=">\'"'
- }
- }
+ source: 'id=">\'"',
+ },
+ },
],
- isSelfClosing: false,
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 19, line: 1, column: 20 },
- source: '
'
- }
+ source: '
',
+ },
})
})
@@ -860,30 +906,34 @@ describe('compiler: parse', () => {
{
type: NodeTypes.ATTRIBUTE,
name: 'id',
+ nameLoc: {
+ start: { offset: 5, line: 1, column: 6 },
+ end: { offset: 7, line: 1, column: 8 },
+ source: 'id',
+ },
value: {
type: NodeTypes.TEXT,
content: '>"',
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 12, line: 1, column: 13 },
- source: "'>\"'"
- }
+ source: "'>\"'",
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 12, line: 1, column: 13 },
- source: "id='>\"'"
- }
- }
+ source: "id='>\"'",
+ },
+ },
],
- isSelfClosing: false,
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 19, line: 1, column: 20 },
- source: "
"
- }
+ source: "
",
+ },
})
})
@@ -901,30 +951,117 @@ describe('compiler: parse', () => {
{
type: NodeTypes.ATTRIBUTE,
name: 'id',
+ nameLoc: {
+ start: { offset: 5, line: 1, column: 6 },
+ end: { offset: 7, line: 1, column: 8 },
+ source: 'id',
+ },
value: {
type: NodeTypes.TEXT,
content: 'a/',
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 10, line: 1, column: 11 },
- source: 'a/'
- }
+ source: 'a/',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 10, line: 1, column: 11 },
- source: 'id=a/'
- }
- }
+ source: 'id=a/',
+ },
+ },
],
- isSelfClosing: false,
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 17, line: 1, column: 18 },
- source: '
'
- }
+ source: '
',
+ },
+ })
+ })
+
+ test('attribute value with >', () => {
+ const ast = baseParse(
+ '',
+ { parseMode: 'sfc' },
+ )
+ const element = ast.children[0] as ElementNode
+ expect(element).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ ns: Namespaces.HTML,
+ tag: 'script',
+ tagType: ElementTypes.ELEMENT,
+ codegenNode: undefined,
+ children: [],
+ innerLoc: {
+ start: { column: 67, line: 1, offset: 66 },
+ end: { column: 67, line: 1, offset: 66 },
+ },
+ props: [
+ {
+ loc: {
+ source: 'setup',
+ end: { column: 14, line: 1, offset: 13 },
+ start: { column: 9, line: 1, offset: 8 },
+ },
+ name: 'setup',
+ nameLoc: {
+ source: 'setup',
+ end: { column: 14, line: 1, offset: 13 },
+ start: { column: 9, line: 1, offset: 8 },
+ },
+ type: NodeTypes.ATTRIBUTE,
+ value: undefined,
+ },
+ {
+ loc: {
+ source: 'lang="ts"',
+ end: { column: 24, line: 1, offset: 23 },
+ start: { column: 15, line: 1, offset: 14 },
+ },
+ name: 'lang',
+ nameLoc: {
+ source: 'lang',
+ end: { column: 19, line: 1, offset: 18 },
+ start: { column: 15, line: 1, offset: 14 },
+ },
+ type: NodeTypes.ATTRIBUTE,
+ value: {
+ content: 'ts',
+ loc: {
+ source: '"ts"',
+ end: { column: 24, line: 1, offset: 23 },
+ start: { column: 20, line: 1, offset: 19 },
+ },
+ type: NodeTypes.TEXT,
+ },
+ },
+ {
+ loc: {
+ source: 'generic="T extends Record"',
+ end: { column: 66, line: 1, offset: 65 },
+ start: { column: 25, line: 1, offset: 24 },
+ },
+ name: 'generic',
+ nameLoc: {
+ source: 'generic',
+ end: { column: 32, line: 1, offset: 31 },
+ start: { column: 25, line: 1, offset: 24 },
+ },
+ type: NodeTypes.ATTRIBUTE,
+ value: {
+ content: 'T extends Record',
+ loc: {
+ source: '"T extends Record"',
+ end: { column: 66, line: 1, offset: 65 },
+ start: { column: 33, line: 1, offset: 32 },
+ },
+ type: NodeTypes.TEXT,
+ },
+ },
+ ],
})
})
@@ -942,76 +1079,95 @@ describe('compiler: parse', () => {
{
type: NodeTypes.ATTRIBUTE,
name: 'id',
+ nameLoc: {
+ start: { offset: 5, line: 1, column: 6 },
+ end: { offset: 7, line: 1, column: 8 },
+ source: 'id',
+ },
value: {
type: NodeTypes.TEXT,
content: 'a',
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
- source: 'a'
- }
+ source: 'a',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 9, line: 1, column: 10 },
- source: 'id=a'
- }
+ source: 'id=a',
+ },
},
{
type: NodeTypes.ATTRIBUTE,
name: 'class',
+ nameLoc: {
+ start: { offset: 10, line: 1, column: 11 },
+ end: { offset: 15, line: 1, column: 16 },
+ source: 'class',
+ },
value: {
type: NodeTypes.TEXT,
content: 'c',
loc: {
start: { offset: 16, line: 1, column: 17 },
end: { offset: 19, line: 1, column: 20 },
- source: '"c"'
- }
+ source: '"c"',
+ },
},
loc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 19, line: 1, column: 20 },
- source: 'class="c"'
- }
+ source: 'class="c"',
+ },
},
{
type: NodeTypes.ATTRIBUTE,
name: 'inert',
+ nameLoc: {
+ start: { offset: 20, line: 1, column: 21 },
+ end: { offset: 25, line: 1, column: 26 },
+ source: 'inert',
+ },
value: undefined,
loc: {
start: { offset: 20, line: 1, column: 21 },
end: { offset: 25, line: 1, column: 26 },
- source: 'inert'
- }
+ source: 'inert',
+ },
},
{
type: NodeTypes.ATTRIBUTE,
name: 'style',
+ nameLoc: {
+ start: { offset: 26, line: 1, column: 27 },
+ end: { offset: 31, line: 1, column: 32 },
+ source: 'style',
+ },
value: {
type: NodeTypes.TEXT,
content: '',
loc: {
start: { offset: 32, line: 1, column: 33 },
end: { offset: 34, line: 1, column: 35 },
- source: "''"
- }
+ source: "''",
+ },
},
loc: {
start: { offset: 26, line: 1, column: 27 },
end: { offset: 34, line: 1, column: 35 },
- source: "style=''"
- }
- }
+ source: "style=''",
+ },
+ },
],
- isSelfClosing: false,
children: [],
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 41, line: 1, column: 42 },
- source: '
'
- }
+ source: '
',
+ },
})
})
@@ -1023,60 +1179,40 @@ describe('compiler: parse', () => {
expect(element).toStrictEqual({
children: [],
codegenNode: undefined,
- isSelfClosing: false,
loc: {
- end: {
- column: 10,
- line: 3,
- offset: 29
- },
+ start: { column: 1, line: 1, offset: 0 },
+ end: { column: 10, line: 3, offset: 29 },
source: '
',
- start: {
- column: 1,
- line: 1,
- offset: 0
- }
},
- ns: 0,
+ ns: Namespaces.HTML,
props: [
{
- loc: {
- end: {
- column: 3,
- line: 3,
- offset: 22
- },
- source: 'class=" \n\t c \t\n "',
- start: {
- column: 6,
- line: 1,
- offset: 5
- }
- },
name: 'class',
- type: 6,
+ nameLoc: {
+ start: { column: 6, line: 1, offset: 5 },
+ end: { column: 11, line: 1, offset: 10 },
+ source: 'class',
+ },
+ type: NodeTypes.ATTRIBUTE,
value: {
content: 'c',
loc: {
- end: {
- column: 3,
- line: 3,
- offset: 22
- },
+ start: { column: 12, line: 1, offset: 11 },
+ end: { column: 3, line: 3, offset: 22 },
source: '" \n\t c \t\n "',
- start: {
- column: 12,
- line: 1,
- offset: 11
- }
},
- type: 2
- }
- }
- ],
+ type: NodeTypes.TEXT,
+ },
+ loc: {
+ start: { column: 6, line: 1, offset: 5 },
+ end: { column: 3, line: 3, offset: 22 },
+ source: 'class=" \n\t c \t\n "',
+ },
+ },
+ ],
tag: 'div',
- tagType: 0,
- type: 1
+ tagType: ElementTypes.ELEMENT,
+ type: NodeTypes.ELEMENT,
})
})
@@ -1087,14 +1223,15 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'if',
+ rawName: 'v-if',
arg: undefined,
modifiers: [],
exp: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 9, line: 1, column: 10 },
- source: 'v-if'
- }
+ source: 'v-if',
+ },
})
})
@@ -1105,6 +1242,7 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'if',
+ rawName: 'v-if',
arg: undefined,
modifiers: [],
exp: {
@@ -1115,14 +1253,14 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 11, line: 1, column: 12 },
end: { offset: 12, line: 1, column: 13 },
- source: 'a'
- }
+ source: 'a',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 13, line: 1, column: 14 },
- source: 'v-if="a"'
- }
+ source: 'v-if="a"',
+ },
})
})
@@ -1133,33 +1271,52 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'on',
+ rawName: 'v-on:click',
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'click',
isStatic: true,
constType: ConstantTypes.CAN_STRINGIFY,
-
loc: {
+ start: { column: 11, line: 1, offset: 10 },
+ end: { column: 16, line: 1, offset: 15 },
source: 'click',
- start: {
- column: 11,
- line: 1,
- offset: 10
- },
- end: {
- column: 16,
- line: 1,
- offset: 15
- }
- }
+ },
},
modifiers: [],
exp: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 15, line: 1, column: 16 },
- source: 'v-on:click'
- }
+ source: 'v-on:click',
+ },
+ })
+ })
+
+ // #3494
+ test('directive argument edge case', () => {
+ const ast = baseParse('
')
+ const directive = (ast.children[0] as ElementNode)
+ .props[0] as DirectiveNode
+ expect(directive.arg).toMatchObject({
+ loc: {
+ start: { offset: 12, line: 1, column: 13 },
+ end: { offset: 16, line: 1, column: 17 },
+ },
+ })
+ })
+
+ // https://github.com/vuejs/language-tools/issues/2710
+ test('directive argument edge case (2)', () => {
+ const ast = baseParse('
')
+ const directive = (ast.children[0] as ElementNode)
+ .props[0] as DirectiveNode
+ expect(directive.arg).toMatchObject({
+ content: 'item.item',
+ loc: {
+ start: { offset: 6, line: 1, column: 7 },
+ end: { offset: 15, line: 1, column: 16 },
+ },
})
})
@@ -1170,33 +1327,25 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'on',
+ rawName: 'v-on:[event]',
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'event',
isStatic: false,
constType: ConstantTypes.NOT_CONSTANT,
-
loc: {
+ start: { column: 11, line: 1, offset: 10 },
+ end: { column: 18, line: 1, offset: 17 },
source: '[event]',
- start: {
- column: 11,
- line: 1,
- offset: 10
- },
- end: {
- column: 18,
- line: 1,
- offset: 17
- }
- }
+ },
},
modifiers: [],
exp: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 17, line: 1, column: 18 },
- source: 'v-on:[event]'
- }
+ source: 'v-on:[event]',
+ },
})
})
@@ -1207,14 +1356,35 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'on',
+ rawName: 'v-on.enter',
arg: undefined,
- modifiers: ['enter'],
+ modifiers: [
+ {
+ constType: 3,
+ content: 'enter',
+ isStatic: true,
+ loc: {
+ end: {
+ column: 16,
+ line: 1,
+ offset: 15,
+ },
+ source: 'enter',
+ start: {
+ column: 11,
+ line: 1,
+ offset: 10,
+ },
+ },
+ type: 4,
+ },
+ ],
exp: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 15, line: 1, column: 16 },
- source: 'v-on.enter'
- }
+ source: 'v-on.enter',
+ },
})
})
@@ -1225,14 +1395,54 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'on',
+ rawName: 'v-on.enter.exact',
arg: undefined,
- modifiers: ['enter', 'exact'],
+ modifiers: [
+ {
+ constType: 3,
+ content: 'enter',
+ isStatic: true,
+ loc: {
+ end: {
+ column: 16,
+ line: 1,
+ offset: 15,
+ },
+ source: 'enter',
+ start: {
+ column: 11,
+ line: 1,
+ offset: 10,
+ },
+ },
+ type: 4,
+ },
+ {
+ constType: 3,
+ content: 'exact',
+ isStatic: true,
+ loc: {
+ end: {
+ column: 22,
+ line: 1,
+ offset: 21,
+ },
+ source: 'exact',
+ start: {
+ column: 17,
+ line: 1,
+ offset: 16,
+ },
+ },
+ type: 4,
+ },
+ ],
exp: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 21, line: 1, column: 22 },
- source: 'v-on.enter.exact'
- }
+ source: 'v-on.enter.exact',
+ },
})
})
@@ -1243,33 +1453,64 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'on',
+ rawName: 'v-on:click.enter.exact',
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'click',
isStatic: true,
constType: ConstantTypes.CAN_STRINGIFY,
-
loc: {
+ start: { column: 11, line: 1, offset: 10 },
+ end: { column: 16, line: 1, offset: 15 },
source: 'click',
- start: {
- column: 11,
- line: 1,
- offset: 10
- },
- end: {
- column: 16,
- line: 1,
- offset: 15
- }
- }
+ },
},
- modifiers: ['enter', 'exact'],
+ modifiers: [
+ {
+ constType: 3,
+ content: 'enter',
+ isStatic: true,
+ loc: {
+ end: {
+ column: 22,
+ line: 1,
+ offset: 21,
+ },
+ source: 'enter',
+ start: {
+ column: 17,
+ line: 1,
+ offset: 16,
+ },
+ },
+ type: 4,
+ },
+ {
+ constType: 3,
+ content: 'exact',
+ isStatic: true,
+ loc: {
+ end: {
+ column: 28,
+ line: 1,
+ offset: 27,
+ },
+ source: 'exact',
+ start: {
+ column: 23,
+ line: 1,
+ offset: 22,
+ },
+ },
+ type: 4,
+ },
+ ],
exp: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 27, line: 1, column: 28 },
- source: 'v-on:click.enter.exact'
- }
+ source: 'v-on:click.enter.exact',
+ },
})
})
@@ -1280,41 +1521,54 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'on',
+ rawName: 'v-on:[a.b].camel',
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a.b',
isStatic: false,
constType: ConstantTypes.NOT_CONSTANT,
-
loc: {
+ start: { column: 11, line: 1, offset: 10 },
+ end: { column: 16, line: 1, offset: 15 },
source: '[a.b]',
- start: {
- column: 11,
- line: 1,
- offset: 10
- },
- end: {
- column: 16,
- line: 1,
- offset: 15
- }
- }
+ },
},
- modifiers: ['camel'],
+ modifiers: [
+ {
+ constType: 3,
+ content: 'camel',
+ isStatic: true,
+ loc: {
+ end: {
+ column: 22,
+ line: 1,
+ offset: 21,
+ },
+ source: 'camel',
+ start: {
+ column: 17,
+ line: 1,
+ offset: 16,
+ },
+ },
+ type: 4,
+ },
+ ],
exp: undefined,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 21, line: 1, column: 22 },
- source: 'v-on:[a.b].camel'
- }
+ source: 'v-on:[a.b].camel',
+ },
})
})
+
test('directive with no name', () => {
let errorCode = -1
const ast = baseParse('
', {
onError: err => {
errorCode = err.code as number
- }
+ },
})
const directive = (ast.children[0] as ElementNode).props[0]
@@ -1326,8 +1580,13 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 7, line: 1, column: 8 },
- source: 'v-'
- }
+ source: 'v-',
+ },
+ nameLoc: {
+ start: { offset: 5, line: 1, column: 6 },
+ end: { offset: 7, line: 1, column: 8 },
+ source: 'v-',
+ },
})
})
@@ -1338,25 +1597,17 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'bind',
+ rawName: ':a',
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
constType: ConstantTypes.CAN_STRINGIFY,
-
loc: {
+ start: { column: 7, line: 1, offset: 6 },
+ end: { column: 8, line: 1, offset: 7 },
source: 'a',
- start: {
- column: 7,
- line: 1,
- offset: 6
- },
- end: {
- column: 8,
- line: 1,
- offset: 7
- }
- }
+ },
},
modifiers: [],
exp: {
@@ -1364,18 +1615,17 @@ describe('compiler: parse', () => {
content: 'b',
isStatic: false,
constType: ConstantTypes.NOT_CONSTANT,
-
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
- source: 'b'
- }
+ source: 'b',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 9, line: 1, column: 10 },
- source: ':a=b'
- }
+ source: ':a=b',
+ },
})
})
@@ -1386,44 +1636,55 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'bind',
+ rawName: '.a',
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
constType: ConstantTypes.CAN_STRINGIFY,
-
loc: {
+ start: { column: 7, line: 1, offset: 6 },
+ end: { column: 8, line: 1, offset: 7 },
source: 'a',
- start: {
- column: 7,
- line: 1,
- offset: 6
- },
- end: {
- column: 8,
- line: 1,
- offset: 7
- }
- }
+ },
},
- modifiers: ['prop'],
+ modifiers: [
+ {
+ constType: 0,
+ content: 'prop',
+ isStatic: false,
+ loc: {
+ end: {
+ column: 1,
+ line: 1,
+ offset: 0,
+ },
+ source: '',
+ start: {
+ column: 1,
+ line: 1,
+ offset: 0,
+ },
+ },
+ type: 4,
+ },
+ ],
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
isStatic: false,
constType: ConstantTypes.NOT_CONSTANT,
-
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
- source: 'b'
- }
+ source: 'b',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 9, line: 1, column: 10 },
- source: '.a=b'
- }
+ source: '.a=b',
+ },
})
})
@@ -1434,27 +1695,39 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'bind',
+ rawName: ':a.sync',
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
constType: ConstantTypes.CAN_STRINGIFY,
-
loc: {
+ start: { column: 7, line: 1, offset: 6 },
+ end: { column: 8, line: 1, offset: 7 },
source: 'a',
- start: {
- column: 7,
- line: 1,
- offset: 6
- },
- end: {
- column: 8,
- line: 1,
- offset: 7
- }
- }
+ },
},
- modifiers: ['sync'],
+ modifiers: [
+ {
+ constType: 3,
+ content: 'sync',
+ isStatic: true,
+ loc: {
+ end: {
+ column: 13,
+ line: 1,
+ offset: 12,
+ },
+ source: 'sync',
+ start: {
+ column: 9,
+ line: 1,
+ offset: 8,
+ },
+ },
+ type: 4,
+ },
+ ],
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
@@ -1464,14 +1737,14 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 13, line: 1, column: 14 },
end: { offset: 14, line: 1, column: 15 },
- source: 'b'
- }
+ source: 'b',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 14, line: 1, column: 15 },
- source: ':a.sync=b'
- }
+ source: ':a.sync=b',
+ },
})
})
@@ -1482,25 +1755,17 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'on',
+ rawName: '@a',
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
constType: ConstantTypes.CAN_STRINGIFY,
-
loc: {
+ start: { column: 7, line: 1, offset: 6 },
+ end: { column: 8, line: 1, offset: 7 },
source: 'a',
- start: {
- column: 7,
- line: 1,
- offset: 6
- },
- end: {
- column: 8,
- line: 1,
- offset: 7
- }
- }
+ },
},
modifiers: [],
exp: {
@@ -1512,14 +1777,14 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
- source: 'b'
- }
+ source: 'b',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 9, line: 1, column: 10 },
- source: '@a=b'
- }
+ source: '@a=b',
+ },
})
})
@@ -1530,27 +1795,39 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'on',
+ rawName: '@a.enter',
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
constType: ConstantTypes.CAN_STRINGIFY,
-
loc: {
+ start: { column: 7, line: 1, offset: 6 },
+ end: { column: 8, line: 1, offset: 7 },
source: 'a',
- start: {
- column: 7,
- line: 1,
- offset: 6
- },
- end: {
- column: 8,
- line: 1,
- offset: 7
- }
- }
+ },
},
- modifiers: ['enter'],
+ modifiers: [
+ {
+ constType: 3,
+ content: 'enter',
+ isStatic: true,
+ loc: {
+ end: {
+ column: 14,
+ line: 1,
+ offset: 13,
+ },
+ source: 'enter',
+ start: {
+ column: 9,
+ line: 1,
+ offset: 8,
+ },
+ },
+ type: 4,
+ },
+ ],
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
@@ -1560,14 +1837,14 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 14, line: 1, column: 15 },
end: { offset: 15, line: 1, column: 16 },
- source: 'b'
- }
+ source: 'b',
+ },
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 15, line: 1, column: 16 },
- source: '@a.enter=b'
- }
+ source: '@a.enter=b',
+ },
})
})
@@ -1578,24 +1855,17 @@ describe('compiler: parse', () => {
expect(directive).toStrictEqual({
type: NodeTypes.DIRECTIVE,
name: 'slot',
+ rawName: '#a',
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
constType: ConstantTypes.CAN_STRINGIFY,
loc: {
+ start: { column: 8, line: 1, offset: 7 },
+ end: { column: 9, line: 1, offset: 8 },
source: 'a',
- start: {
- column: 8,
- line: 1,
- offset: 7
- },
- end: {
- column: 9,
- line: 1,
- offset: 8
- }
- }
+ },
},
modifiers: [],
exp: {
@@ -1607,14 +1877,14 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 15, line: 1, column: 16 },
- source: '{ b }'
- }
+ source: '{ b }',
+ },
},
loc: {
start: { offset: 6, line: 1, column: 7 },
end: { offset: 16, line: 1, column: 17 },
- source: '#a="{ b }"'
- }
+ source: '#a="{ b }"',
+ },
})
})
@@ -1626,32 +1896,32 @@ describe('compiler: parse', () => {
expect(directive).toMatchObject({
type: NodeTypes.DIRECTIVE,
name: 'slot',
+ rawName: 'v-slot:foo.bar',
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo.bar',
isStatic: true,
constType: ConstantTypes.CAN_STRINGIFY,
loc: {
- source: 'foo.bar',
start: {
column: 14,
line: 1,
- offset: 13
+ offset: 13,
},
end: {
column: 21,
line: 1,
- offset: 20
- }
- }
- }
+ offset: 20,
+ },
+ },
+ },
})
})
test('v-pre', () => {
const ast = baseParse(
` {{ bar }}
\n` +
- ` {{ bar }}
`
+ ` {{ bar }}
`,
)
const divWithPre = ast.children[0] as ElementNode
@@ -1661,29 +1931,22 @@ describe('compiler: parse', () => {
name: `:id`,
value: {
type: NodeTypes.TEXT,
- content: `foo`
+ content: `foo`,
},
loc: {
- source: `:id="foo"`,
- start: {
- line: 1,
- column: 12
- },
- end: {
- line: 1,
- column: 21
- }
- }
- }
+ start: { line: 1, column: 12 },
+ end: { line: 1, column: 21 },
+ },
+ },
])
expect(divWithPre.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tagType: ElementTypes.ELEMENT,
- tag: `Comp`
+ tag: `Comp`,
})
expect(divWithPre.children[1]).toMatchObject({
type: NodeTypes.TEXT,
- content: `{{ bar }}`
+ content: `{{ bar }}`,
})
// should not affect siblings after it
@@ -1695,44 +1958,87 @@ describe('compiler: parse', () => {
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: true,
- content: `id`
+ content: `id`,
},
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
- content: `foo`
+ content: `foo`,
},
loc: {
- source: `:id="foo"`,
start: {
line: 2,
- column: 6
+ column: 6,
},
end: {
line: 2,
- column: 15
- }
- }
- }
+ column: 15,
+ },
+ },
+ },
])
expect(divWithoutPre.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tagType: ElementTypes.COMPONENT,
- tag: `Comp`
+ tag: `Comp`,
})
expect(divWithoutPre.children[1]).toMatchObject({
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `bar`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
+ // https://github.com/vuejs/docs/issues/2586
+ test('v-pre with half-open interpolation', () => {
+ const ast = baseParse(
+ `
+ {{ number
+ }}
+
+ `,
+ )
+ expect((ast.children[0] as ElementNode).children).toMatchObject([
+ {
+ type: NodeTypes.ELEMENT,
+ children: [{ type: NodeTypes.TEXT, content: `{{ number ` }],
+ },
+ {
+ type: NodeTypes.ELEMENT,
+ children: [{ type: NodeTypes.TEXT, content: `}}` }],
+ },
+ ])
+
+ const ast2 = baseParse(`{{ number
`)
+ expect((ast2.children[0] as ElementNode).children).toMatchObject([
+ {
+ type: NodeTypes.ELEMENT,
+ children: [{ type: NodeTypes.TEXT, content: `{{ number ` }],
+ },
+ ])
+
+ const ast3 = baseParse(`
`, {
+ parseMode: 'html',
+ })
+ expect((ast3.children[0] as ElementNode).children).toMatchObject([
+ {
+ type: NodeTypes.ELEMENT,
+ children: [
+ {
+ type: NodeTypes.TEXT,
+ content: `{{ foo `,
+ },
+ ],
+ },
+ ])
+ })
+
test('self-closing v-pre', () => {
const ast = baseParse(
- `
\n {{ bar }}
`
+ `
\n {{ bar }}
`,
)
// should not affect siblings after it
const divWithoutPre = ast.children[1] as ElementNode
@@ -1743,38 +2049,37 @@ describe('compiler: parse', () => {
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: true,
- content: `id`
+ content: `id`,
},
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
- content: `foo`
+ content: `foo`,
},
loc: {
- source: `:id="foo"`,
start: {
line: 2,
- column: 6
+ column: 6,
},
end: {
line: 2,
- column: 15
- }
- }
- }
+ column: 15,
+ },
+ },
+ },
])
expect(divWithoutPre.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tagType: ElementTypes.COMPONENT,
- tag: `Comp`
+ tag: `Comp`,
})
expect(divWithoutPre.children[1]).toMatchObject({
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `bar`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
@@ -1789,133 +2094,192 @@ describe('compiler: parse', () => {
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 10, line: 1, column: 11 },
- source: 'hello'
- }
+ source: 'hello',
+ },
})
})
})
- test('self closing single tag', () => {
- const ast = baseParse('
')
+ describe('Edge Cases', () => {
+ test('self closing single tag', () => {
+ const ast = baseParse('
')
- expect(ast.children).toHaveLength(1)
- expect(ast.children[0]).toMatchObject({ tag: 'div' })
- })
+ expect(ast.children).toHaveLength(1)
+ expect(ast.children[0]).toMatchObject({ tag: 'div' })
+ })
- test('self closing multiple tag', () => {
- const ast = baseParse(
- `
\n` +
- `
`
- )
+ test('self closing multiple tag', () => {
+ const ast = baseParse(
+ `
\n` +
+ `
`,
+ )
- expect(ast).toMatchSnapshot()
+ expect(ast).toMatchSnapshot()
- expect(ast.children).toHaveLength(2)
- expect(ast.children[0]).toMatchObject({ tag: 'div' })
- expect(ast.children[1]).toMatchObject({ tag: 'p' })
- })
+ expect(ast.children).toHaveLength(2)
+ expect(ast.children[0]).toMatchObject({ tag: 'div' })
+ expect(ast.children[1]).toMatchObject({ tag: 'p' })
+ })
- test('valid html', () => {
- const ast = baseParse(
- `\n` +
- `
\n` +
- ` \n` +
- `
`
- )
+ test('valid html', () => {
+ const ast = baseParse(
+ `\n` +
+ `
\n` +
+ ` \n` +
+ `
`,
+ )
- expect(ast).toMatchSnapshot()
+ expect(ast).toMatchSnapshot()
- expect(ast.children).toHaveLength(1)
- const el = ast.children[0] as any
- expect(el).toMatchObject({
- tag: 'div'
- })
- expect(el.children).toHaveLength(2)
- expect(el.children[0]).toMatchObject({
- tag: 'p'
+ expect(ast.children).toHaveLength(1)
+ const el = ast.children[0] as any
+ expect(el).toMatchObject({
+ tag: 'div',
+ })
+ expect(el.children).toHaveLength(2)
+ expect(el.children[0]).toMatchObject({
+ tag: 'p',
+ })
+ expect(el.children[1]).toMatchObject({
+ type: NodeTypes.COMMENT,
+ })
})
- expect(el.children[1]).toMatchObject({
- type: NodeTypes.COMMENT
+
+ test('invalid html', () => {
+ expect(() => {
+ baseParse(`\n\n
\n`)
+ }).toThrow('Element is missing end tag.')
+
+ const spy = vi.fn()
+ const ast = baseParse(`\n\n
\n`, {
+ onError: spy,
+ })
+
+ expect(spy.mock.calls).toMatchObject([
+ [
+ {
+ code: ErrorCodes.X_MISSING_END_TAG,
+ loc: {
+ start: {
+ offset: 6,
+ line: 2,
+ column: 1,
+ },
+ },
+ },
+ ],
+ [
+ {
+ code: ErrorCodes.X_INVALID_END_TAG,
+ loc: {
+ start: {
+ offset: 20,
+ line: 4,
+ column: 1,
+ },
+ },
+ },
+ ],
+ ])
+
+ expect(ast).toMatchSnapshot()
+ })
+
+ test('parse with correct location info', () => {
+ const fooSrc = `foo\n is `
+ const barSrc = `{{ bar }}`
+ const butSrc = ` but `
+ const bazSrc = `{{ baz }}`
+ const [foo, bar, but, baz] = baseParse(
+ fooSrc + barSrc + butSrc + bazSrc,
+ ).children
+
+ let offset = 0
+ expect(foo.loc.start).toEqual({ line: 1, column: 1, offset })
+ offset += fooSrc.length
+ expect(foo.loc.end).toEqual({ line: 2, column: 5, offset })
+
+ expect(bar.loc.start).toEqual({ line: 2, column: 5, offset })
+ const barInner = (bar as InterpolationNode).content
+ offset += 3
+ expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset })
+ offset += 3
+ expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset })
+ offset += 3
+ expect(bar.loc.end).toEqual({ line: 2, column: 14, offset })
+
+ expect(but.loc.start).toEqual({ line: 2, column: 14, offset })
+ offset += butSrc.length
+ expect(but.loc.end).toEqual({ line: 2, column: 19, offset })
+
+ expect(baz.loc.start).toEqual({ line: 2, column: 19, offset })
+ const bazInner = (baz as InterpolationNode).content
+ offset += 3
+ expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset })
+ offset += 3
+ expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset })
+ offset += 3
+ expect(baz.loc.end).toEqual({ line: 2, column: 28, offset })
+ })
+
+ // With standard HTML parsing, the following input would ignore the slash
+ // and treat "<" and "template" as attributes on the open tag of "Hello",
+ // causing `` to fail to close, and ``,
+ {
+ onError: spy,
+ },
+ )
+ //
+ expect(ast.children.length).toBe(2)
+ expect(ast.children[1]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'script',
+ })
})
- })
- test('invalid html', () => {
- expect(() => {
- baseParse(`\n\n
\n`)
- }).toThrow('Element is missing end tag.')
+ test('arg should be undefined on shorthand dirs with no arg', () => {
+ const ast = baseParse(` `)
+ const el = ast.children[0] as ElementNode
+ expect(el.props[0]).toMatchObject({
+ type: NodeTypes.DIRECTIVE,
+ name: 'slot',
+ exp: undefined,
+ arg: undefined,
+ })
+ })
- const spy = jest.fn()
- const ast = baseParse(`\n\n
\n`, {
- onError: spy
+ // edge case found in vue-macros where the input is TS or JSX
+ test('should reset inRCDATA state', () => {
+ baseParse(``, { parseMode: 'sfc', onError() {} })
+ expect(() => baseParse(`{ foo }`)).not.toThrow()
})
- expect(spy.mock.calls).toMatchObject([
- [
- {
- code: ErrorCodes.X_MISSING_END_TAG,
- loc: {
- start: {
- offset: 6,
- line: 2,
- column: 1
- }
- }
- }
- ],
- [
- {
- code: ErrorCodes.X_INVALID_END_TAG,
- loc: {
- start: {
- offset: 20,
- line: 4,
- column: 1
- }
- }
- }
- ]
- ])
+ test('correct loc when the closing > is foarmatted', () => {
+ const [span] = baseParse(` `).children
- expect(ast).toMatchSnapshot()
- })
+ expect(span.loc.source).toBe(' ')
+ expect(span.loc.start.offset).toBe(0)
+ expect(span.loc.end.offset).toBe(27)
+ })
- test('parse with correct location info', () => {
- const [foo, bar, but, baz] = baseParse(
- `
-foo
- is {{ bar }} but {{ baz }}`.trim()
- ).children
-
- let offset = 0
- expect(foo.loc.start).toEqual({ line: 1, column: 1, offset })
- offset += foo.loc.source.length
- expect(foo.loc.end).toEqual({ line: 2, column: 5, offset })
-
- expect(bar.loc.start).toEqual({ line: 2, column: 5, offset })
- const barInner = (bar as InterpolationNode).content
- offset += 3
- expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset })
- offset += barInner.loc.source.length
- expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset })
- offset += 3
- expect(bar.loc.end).toEqual({ line: 2, column: 14, offset })
-
- expect(but.loc.start).toEqual({ line: 2, column: 14, offset })
- offset += but.loc.source.length
- expect(but.loc.end).toEqual({ line: 2, column: 19, offset })
-
- expect(baz.loc.start).toEqual({ line: 2, column: 19, offset })
- const bazInner = (baz as InterpolationNode).content
- offset += 3
- expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset })
- offset += bazInner.loc.source.length
- expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset })
- offset += 3
- expect(baz.loc.end).toEqual({ line: 2, column: 28, offset })
+ test('correct loc when a line in attribute value ends with &', () => {
+ const [span] = baseParse(` `).children
+ expect(span.loc.end.line).toBe(2)
+ })
})
describe('decodeEntities option', () => {
- test('use default map', () => {
+ test('use decode by default', () => {
const ast: any = baseParse('><&'"&foo;')
expect(ast.children.length).toBe(1)
@@ -1923,15 +2287,14 @@ foo
expect(ast.children[0].content).toBe('><&\'"&foo;')
})
- test('use the given map', () => {
- const ast: any = baseParse('&∪︀', {
+ test('should warn in non-browser build', () => {
+ baseParse('&∪︀', {
decodeEntities: text => text.replace('∪︀', '\u222A\uFE00'),
- onError: () => {} // Ignore errors
+ onError: () => {}, // Ignore errors
})
-
- expect(ast.children.length).toBe(1)
- expect(ast.children[0].type).toBe(NodeTypes.TEXT)
- expect(ast.children[0].content).toBe('&\u222A\uFE00')
+ expect(
+ `decodeEntities option is passed but will be ignored`,
+ ).toHaveBeenWarned()
})
})
@@ -1939,21 +2302,21 @@ foo
const parse = (content: string, options?: ParserOptions) =>
baseParse(content, {
whitespace: 'condense',
- ...options
+ ...options,
})
- it('should remove whitespaces at start/end inside an element', () => {
+ test('should remove whitespaces at start/end inside an element', () => {
const ast = parse(`
`)
expect((ast.children[0] as ElementNode).children.length).toBe(1)
})
- it('should remove whitespaces w/ newline between elements', () => {
+ test('should remove whitespaces w/ newline between elements', () => {
const ast = parse(`
\n
\n
`)
expect(ast.children.length).toBe(3)
expect(ast.children.every(c => c.type === NodeTypes.ELEMENT)).toBe(true)
})
- it('should remove whitespaces adjacent to comments', () => {
+ test('should remove whitespaces adjacent to comments', () => {
const ast = parse(`
\n
`)
expect(ast.children.length).toBe(3)
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
@@ -1961,7 +2324,7 @@ foo
expect(ast.children[2].type).toBe(NodeTypes.ELEMENT)
})
- it('should remove whitespaces w/ newline between comments and elements', () => {
+ test('should remove whitespaces w/ newline between comments and elements', () => {
const ast = parse(`
\n \n
`)
expect(ast.children.length).toBe(3)
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
@@ -1969,18 +2332,29 @@ foo
expect(ast.children[2].type).toBe(NodeTypes.ELEMENT)
})
- it('should NOT remove whitespaces w/ newline between interpolations', () => {
+ test('should NOT remove whitespaces w/ newline between interpolations', () => {
const ast = parse(`{{ foo }} \n {{ bar }}`)
expect(ast.children.length).toBe(3)
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
expect(ast.children[1]).toMatchObject({
type: NodeTypes.TEXT,
- content: ' '
+ content: ' ',
+ })
+ expect(ast.children[2].type).toBe(NodeTypes.INTERPOLATION)
+ })
+
+ test('should NOT remove whitespaces w/ newline between interpolation and comment', () => {
+ const ast = parse(` \n {{msg}}`)
+ expect(ast.children.length).toBe(3)
+ expect(ast.children[0].type).toBe(NodeTypes.COMMENT)
+ expect(ast.children[1]).toMatchObject({
+ type: NodeTypes.TEXT,
+ content: ' ',
})
expect(ast.children[2].type).toBe(NodeTypes.INTERPOLATION)
})
- it('should NOT remove whitespaces w/o newline between elements', () => {
+ test('should NOT remove whitespaces w/o newline between elements', () => {
const ast = parse(`
`)
expect(ast.children.length).toBe(5)
expect(ast.children.map(c => c.type)).toMatchObject([
@@ -1988,18 +2362,19 @@ foo
NodeTypes.TEXT,
NodeTypes.ELEMENT,
NodeTypes.TEXT,
- NodeTypes.ELEMENT
+ NodeTypes.ELEMENT,
])
})
- it('should condense consecutive whitespaces in text', () => {
+ test('should condense consecutive whitespaces in text', () => {
const ast = parse(` foo \n bar baz `)
expect((ast.children[0] as TextNode).content).toBe(` foo bar baz `)
})
- it('should remove leading newline character immediately following the pre element start tag', () => {
- const ast = baseParse(`\n foo bar `, {
- isPreTag: tag => tag === 'pre'
+ test('should remove leading newline character immediately following the pre element start tag', () => {
+ const ast = parse(`\n foo bar `, {
+ isPreTag: tag => tag === 'pre',
+ isIgnoreNewlineTag: tag => tag === 'pre',
})
expect(ast.children).toHaveLength(1)
const preElement = ast.children[0] as ElementNode
@@ -2007,30 +2382,29 @@ foo
expect((preElement.children[0] as TextNode).content).toBe(` foo bar `)
})
- it('should NOT remove leading newline character immediately following child-tag of pre element', () => {
- const ast = baseParse(` \n foo bar `, {
- isPreTag: tag => tag === 'pre'
+ test('should NOT remove leading newline character immediately following child-tag of pre element', () => {
+ const ast = parse(` \n foo bar `, {
+ isPreTag: tag => tag === 'pre',
})
const preElement = ast.children[0] as ElementNode
expect(preElement.children).toHaveLength(2)
expect((preElement.children[1] as TextNode).content).toBe(
- `\n foo bar `
+ `\n foo bar `,
)
})
- it('self-closing pre tag', () => {
- const ast = baseParse(`\n foo bar `, {
- isPreTag: tag => tag === 'pre'
+ test('self-closing pre tag', () => {
+ const ast = parse(`\n foo bar `, {
+ isPreTag: tag => tag === 'pre',
})
const elementAfterPre = ast.children[1] as ElementNode
- // should not affect the and condense its whitepsace inside
+ // should not affect the and condense its whitespace inside
expect((elementAfterPre.children[0] as TextNode).content).toBe(` foo bar`)
})
- it('should NOT condense whitespaces in RCDATA text mode', () => {
- const ast = baseParse(``, {
- getTextMode: ({ tag }) =>
- tag === 'textarea' ? TextModes.RCDATA : TextModes.DATA
+ test('should NOT condense whitespaces in RCDATA text mode', () => {
+ const ast = parse(``, {
+ parseMode: 'html',
})
const preElement = ast.children[0] as ElementNode
expect(preElement.children).toHaveLength(1)
@@ -2042,15 +2416,15 @@ foo
const parse = (content: string, options?: ParserOptions) =>
baseParse(content, {
whitespace: 'preserve',
- ...options
+ ...options,
})
- it('should still remove whitespaces at start/end inside an element', () => {
+ test('should still remove whitespaces at start/end inside an element', () => {
const ast = parse(`
`)
expect((ast.children[0] as ElementNode).children.length).toBe(1)
})
- it('should preserve whitespaces w/ newline between elements', () => {
+ test('should preserve whitespaces w/ newline between elements', () => {
const ast = parse(`
\n
\n
`)
expect(ast.children.length).toBe(5)
expect(ast.children.map(c => c.type)).toMatchObject([
@@ -2058,11 +2432,11 @@ foo
NodeTypes.TEXT,
NodeTypes.ELEMENT,
NodeTypes.TEXT,
- NodeTypes.ELEMENT
+ NodeTypes.ELEMENT,
])
})
- it('should preserve whitespaces adjacent to comments', () => {
+ test('should preserve whitespaces adjacent to comments', () => {
const ast = parse(`
\n
`)
expect(ast.children.length).toBe(5)
expect(ast.children.map(c => c.type)).toMatchObject([
@@ -2070,11 +2444,11 @@ foo
NodeTypes.TEXT,
NodeTypes.COMMENT,
NodeTypes.TEXT,
- NodeTypes.ELEMENT
+ NodeTypes.ELEMENT,
])
})
- it('should preserve whitespaces w/ newline between comments and elements', () => {
+ test('should preserve whitespaces w/ newline between comments and elements', () => {
const ast = parse(`
\n \n
`)
expect(ast.children.length).toBe(5)
expect(ast.children.map(c => c.type)).toMatchObject([
@@ -2082,22 +2456,22 @@ foo
NodeTypes.TEXT,
NodeTypes.COMMENT,
NodeTypes.TEXT,
- NodeTypes.ELEMENT
+ NodeTypes.ELEMENT,
])
})
- it('should preserve whitespaces w/ newline between interpolations', () => {
+ test('should preserve whitespaces w/ newline between interpolations', () => {
const ast = parse(`{{ foo }} \n {{ bar }}`)
expect(ast.children.length).toBe(3)
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
expect(ast.children[1]).toMatchObject({
type: NodeTypes.TEXT,
- content: ' '
+ content: ' ',
})
expect(ast.children[2].type).toBe(NodeTypes.INTERPOLATION)
})
- it('should preserve whitespaces w/o newline between elements', () => {
+ test('should preserve whitespaces w/o newline between elements', () => {
const ast = parse(`
`)
expect(ast.children.length).toBe(5)
expect(ast.children.map(c => c.type)).toMatchObject([
@@ -2105,18 +2479,79 @@ foo
NodeTypes.TEXT,
NodeTypes.ELEMENT,
NodeTypes.TEXT,
- NodeTypes.ELEMENT
+ NodeTypes.ELEMENT,
])
})
- it('should preserve consecutive whitespaces in text', () => {
+ test('should preserve consecutive whitespaces in text', () => {
const content = ` foo \n bar baz `
const ast = parse(content)
expect((ast.children[0] as TextNode).content).toBe(content)
})
})
+ describe('expression parsing', () => {
+ test('interpolation', () => {
+ const ast = baseParse(`{{ a + b }}`, { prefixIdentifiers: true })
+ // @ts-expect-error
+ expect((ast.children[0] as InterpolationNode).content.ast?.type).toBe(
+ 'BinaryExpression',
+ )
+ })
+
+ test('v-bind', () => {
+ const ast = baseParse(`
`, {
+ prefixIdentifiers: true,
+ })
+ const dir = (ast.children[0] as ElementNode).props[0] as DirectiveNode
+ // @ts-expect-error
+ expect(dir.arg?.ast?.type).toBe('BinaryExpression')
+ // @ts-expect-error
+ expect(dir.exp?.ast?.type).toBe('CallExpression')
+ })
+
+ test('v-on multi statements', () => {
+ const ast = baseParse(`
`, {
+ prefixIdentifiers: true,
+ })
+ const dir = (ast.children[0] as ElementNode).props[0] as DirectiveNode
+ // @ts-expect-error
+ expect(dir.exp?.ast?.type).toBe('Program')
+ expect((dir.exp?.ast as Program).body).toMatchObject([
+ { type: 'ExpressionStatement' },
+ { type: 'ExpressionStatement' },
+ ])
+ })
+
+ test('v-slot', () => {
+ const ast = baseParse(` `, {
+ prefixIdentifiers: true,
+ })
+ const dir = (ast.children[0] as ElementNode).props[0] as DirectiveNode
+ // @ts-expect-error
+ expect(dir.exp?.ast?.type).toBe('ArrowFunctionExpression')
+ })
+
+ test('v-for', () => {
+ const ast = baseParse(`
`, {
+ prefixIdentifiers: true,
+ })
+ const dir = (ast.children[0] as ElementNode).props[0] as DirectiveNode
+ const { source, value, key, index } = dir.forParseResult!
+ // @ts-expect-error
+ expect(source.ast?.type).toBe('MemberExpression')
+ // @ts-expect-error
+ expect(value?.ast?.type).toBe('ArrowFunctionExpression')
+ expect(key?.ast).toBeNull() // simple ident
+ expect(index?.ast).toBeNull() // simple ident
+ })
+ })
+
describe('Errors', () => {
+ // HTML parsing errors as specified at
+ // https://html.spec.whatwg.org/multipage/parsing.html#parse-errors
+ // We ignore some errors that do NOT affect parse result in meaningful ways
+ // but have non-trivial implementation cost.
const patterns: {
[key: string]: Array<{
code: string
@@ -2124,44 +2559,44 @@ foo
options?: Partial
}>
} = {
- ABRUPT_CLOSING_OF_EMPTY_COMMENT: [
- {
- code: ' ',
- errors: [
- {
- type: ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT,
- loc: { offset: 10, line: 1, column: 11 }
- }
- ]
- },
- {
- code: ' ',
- errors: [
- {
- type: ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT,
- loc: { offset: 10, line: 1, column: 11 }
- }
- ]
- },
- {
- code: ' ',
- errors: []
- }
- ],
+ // ABRUPT_CLOSING_OF_EMPTY_COMMENT: [
+ // {
+ // code: ' ',
+ // errors: [
+ // {
+ // type: ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT,
+ // loc: { offset: 10, line: 1, column: 11 }
+ // }
+ // ]
+ // },
+ // {
+ // code: ' ',
+ // errors: [
+ // {
+ // type: ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT,
+ // loc: { offset: 10, line: 1, column: 11 }
+ // }
+ // ]
+ // },
+ // {
+ // code: ' ',
+ // errors: []
+ // }
+ // ],
CDATA_IN_HTML_CONTENT: [
{
code: ' ',
errors: [
{
type: ErrorCodes.CDATA_IN_HTML_CONTENT,
- loc: { offset: 10, line: 1, column: 11 }
- }
- ]
+ loc: { offset: 10, line: 1, column: 11 },
+ },
+ ],
},
{
code: ' ',
- errors: []
- }
+ errors: [],
+ },
],
DUPLICATE_ATTRIBUTE: [
{
@@ -2169,60 +2604,60 @@ foo
errors: [
{
type: ErrorCodes.DUPLICATE_ATTRIBUTE,
- loc: { offset: 21, line: 1, column: 22 }
- }
- ]
- }
- ],
- END_TAG_WITH_ATTRIBUTES: [
- {
- code: '
',
- errors: [
- {
- type: ErrorCodes.END_TAG_WITH_ATTRIBUTES,
- loc: { offset: 21, line: 1, column: 22 }
- }
- ]
- }
- ],
- END_TAG_WITH_TRAILING_SOLIDUS: [
- {
- code: '
',
- errors: [
- {
- type: ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS,
- loc: { offset: 20, line: 1, column: 21 }
- }
- ]
- }
+ loc: { offset: 21, line: 1, column: 22 },
+ },
+ ],
+ },
],
+ // END_TAG_WITH_ATTRIBUTES: [
+ // {
+ // code: '
',
+ // errors: [
+ // {
+ // type: ErrorCodes.END_TAG_WITH_ATTRIBUTES,
+ // loc: { offset: 21, line: 1, column: 22 }
+ // }
+ // ]
+ // }
+ // ],
+ // END_TAG_WITH_TRAILING_SOLIDUS: [
+ // {
+ // code: '
',
+ // errors: [
+ // {
+ // type: ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS,
+ // loc: { offset: 20, line: 1, column: 21 }
+ // }
+ // ]
+ // }
+ // ],
EOF_BEFORE_TAG_NAME: [
{
code: '<',
errors: [
{
type: ErrorCodes.EOF_BEFORE_TAG_NAME,
- loc: { offset: 11, line: 1, column: 12 }
+ loc: { offset: 11, line: 1, column: 12 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '',
errors: [
{
type: ErrorCodes.EOF_BEFORE_TAG_NAME,
- loc: { offset: 12, line: 1, column: 13 }
+ loc: { offset: 12, line: 1, column: 13 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
- }
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
+ },
],
EOF_IN_CDATA: [
{
@@ -2230,35 +2665,35 @@ foo
errors: [
{
type: ErrorCodes.EOF_IN_CDATA,
- loc: { offset: 29, line: 1, column: 30 }
+ loc: { offset: 29, line: 1, column: 30 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 10, line: 1, column: 11 }
+ loc: { offset: 10, line: 1, column: 11 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: ' ',
- errors: [
- {
- type: ErrorCodes.INCORRECTLY_CLOSED_COMMENT,
- loc: { offset: 10, line: 1, column: 11 }
- }
- ]
- }
- ],
- INCORRECTLY_OPENED_COMMENT: [
- {
- code: ' ',
- errors: [
- {
- type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
- loc: { offset: 10, line: 1, column: 11 }
- }
- ]
- },
- {
- code: ' ',
- errors: [
- {
- type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
- loc: { offset: 10, line: 1, column: 11 }
- }
- ]
- },
- {
- code: ' ',
- errors: [
- {
- type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
- loc: { offset: 10, line: 1, column: 11 }
- }
- ]
- },
- // Just ignore doctype.
- {
- code: '',
- errors: []
- }
- ],
- INVALID_FIRST_CHARACTER_OF_TAG_NAME: [
- {
- code: 'a < b ',
- errors: [
- {
- type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
- loc: { offset: 13, line: 1, column: 14 }
- }
- ]
- },
- {
- code: '<�> ',
- errors: [
- {
- type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
- loc: { offset: 11, line: 1, column: 12 }
- }
- ]
- },
- {
- code: 'a b ',
- errors: [
- {
- type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
- loc: { offset: 14, line: 1, column: 15 }
+ loc: { offset: 10, line: 1, column: 11 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
- },
- {
- code: '�> ',
- errors: [
- {
- type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
- loc: { offset: 12, line: 1, column: 13 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
- // Don't throw invalid-first-character-of-tag-name in interpolation
- {
- code: '{{a < b}} ',
- errors: []
- }
],
+ // INCORRECTLY_CLOSED_COMMENT: [
+ // {
+ // code: ' ',
+ // errors: [
+ // {
+ // type: ErrorCodes.INCORRECTLY_CLOSED_COMMENT,
+ // loc: { offset: 10, line: 1, column: 11 }
+ // }
+ // ]
+ // }
+ // ],
+ // INCORRECTLY_OPENED_COMMENT: [
+ // {
+ // code: ' ',
+ // errors: [
+ // {
+ // type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
+ // loc: { offset: 10, line: 1, column: 11 }
+ // }
+ // ]
+ // },
+ // {
+ // code: ' ',
+ // errors: [
+ // {
+ // type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
+ // loc: { offset: 10, line: 1, column: 11 }
+ // }
+ // ]
+ // },
+ // {
+ // code: ' ',
+ // errors: [
+ // {
+ // type: ErrorCodes.INCORRECTLY_OPENED_COMMENT,
+ // loc: { offset: 10, line: 1, column: 11 }
+ // }
+ // ]
+ // },
+ // // Just ignore doctype.
+ // {
+ // code: '',
+ // errors: []
+ // }
+ // ],
+ // INVALID_FIRST_CHARACTER_OF_TAG_NAME: [
+ // {
+ // code: 'a < b ',
+ // errors: [
+ // {
+ // type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
+ // loc: { offset: 13, line: 1, column: 14 }
+ // }
+ // ]
+ // },
+ // {
+ // code: '<�> ',
+ // errors: [
+ // {
+ // type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
+ // loc: { offset: 11, line: 1, column: 12 }
+ // }
+ // ]
+ // },
+ // {
+ // code: 'a b ',
+ // errors: [
+ // {
+ // type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
+ // loc: { offset: 14, line: 1, column: 15 }
+ // },
+ // {
+ // type: ErrorCodes.X_MISSING_END_TAG,
+ // loc: { offset: 0, line: 1, column: 1 }
+ // }
+ // ]
+ // },
+ // {
+ // code: '�> ',
+ // errors: [
+ // {
+ // type: ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
+ // loc: { offset: 12, line: 1, column: 13 }
+ // }
+ // ]
+ // },
+ // // Don't throw invalid-first-character-of-tag-name in interpolation
+ // {
+ // code: '{{a < b}} ',
+ // errors: []
+ // }
+ // ],
MISSING_ATTRIBUTE_VALUE: [
{
code: '
',
errors: [
{
type: ErrorCodes.MISSING_ATTRIBUTE_VALUE,
- loc: { offset: 18, line: 1, column: 19 }
- }
- ]
+ loc: { offset: 18, line: 1, column: 19 },
+ },
+ ],
},
{
code: '
',
errors: [
{
type: ErrorCodes.MISSING_ATTRIBUTE_VALUE,
- loc: { offset: 19, line: 1, column: 20 }
- }
- ]
+ loc: { offset: 19, line: 1, column: 20 },
+ },
+ ],
},
{
code: '
',
- errors: []
- }
+ errors: [],
+ },
],
MISSING_END_TAG_NAME: [
{
@@ -2723,106 +3110,106 @@ foo
errors: [
{
type: ErrorCodes.MISSING_END_TAG_NAME,
- loc: { offset: 12, line: 1, column: 13 }
- }
- ]
- }
- ],
- MISSING_WHITESPACE_BETWEEN_ATTRIBUTES: [
- {
- code: '
',
- errors: [
- {
- type: ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES,
- loc: { offset: 23, line: 1, column: 24 }
- }
- ]
- },
- // CR doesn't appear in tokenization phase, but all CR are removed in preprocessing.
- // https://html.spec.whatwg.org/multipage/parsing.html#preprocessing-the-input-stream
- {
- code: '
',
- errors: []
- }
- ],
- NESTED_COMMENT: [
- {
- code: ' ',
- errors: [
- {
- type: ErrorCodes.NESTED_COMMENT,
- loc: { offset: 15, line: 1, column: 16 }
- }
- ]
- },
- {
- code: ' ',
- errors: [
- {
- type: ErrorCodes.NESTED_COMMENT,
- loc: { offset: 15, line: 1, column: 16 }
+ loc: { offset: 12, line: 1, column: 13 },
},
- {
- type: ErrorCodes.NESTED_COMMENT,
- loc: { offset: 20, line: 1, column: 21 }
- }
- ]
+ ],
},
- {
- code: ' ',
- errors: [
- {
- type: ErrorCodes.NESTED_COMMENT,
- loc: { offset: 15, line: 1, column: 16 }
- }
- ]
- },
- {
- code: ' ',
- errors: []
- },
- {
- code: ' ',
+ // errors: [
+ // {
+ // type: ErrorCodes.NESTED_COMMENT,
+ // loc: { offset: 15, line: 1, column: 16 }
+ // }
+ // ]
+ // },
+ // {
+ // code: ' ',
+ // errors: [
+ // {
+ // type: ErrorCodes.NESTED_COMMENT,
+ // loc: { offset: 15, line: 1, column: 16 }
+ // },
+ // {
+ // type: ErrorCodes.NESTED_COMMENT,
+ // loc: { offset: 20, line: 1, column: 21 }
+ // }
+ // ]
+ // },
+ // {
+ // code: ' ',
+ // errors: [
+ // {
+ // type: ErrorCodes.NESTED_COMMENT,
+ // loc: { offset: 15, line: 1, column: 16 }
+ // }
+ // ]
+ // },
+ // {
+ // code: ' ',
+ // errors: []
+ // },
+ // {
+ // code: '',
- errors: []
- }
+ errors: [],
+ },
],
X_MISSING_END_TAG: [
{
@@ -2959,23 +3359,23 @@ foo
errors: [
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 10, line: 1, column: 11 }
- }
- ]
+ loc: { offset: 10, line: 1, column: 11 },
+ },
+ ],
},
{
code: '',
errors: [
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 10, line: 1, column: 11 }
+ loc: { offset: 10, line: 1, column: 11 },
},
{
type: ErrorCodes.X_MISSING_END_TAG,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
- }
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
+ },
],
X_MISSING_INTERPOLATION_END: [
{
@@ -2983,23 +3383,36 @@ foo
errors: [
{
type: ErrorCodes.X_MISSING_INTERPOLATION_END,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '{{',
errors: [
{
type: ErrorCodes.X_MISSING_INTERPOLATION_END,
- loc: { offset: 0, line: 1, column: 1 }
- }
- ]
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
+ },
+ {
+ code: '
{{ foo
',
+ errors: [
+ {
+ type: ErrorCodes.X_MISSING_INTERPOLATION_END,
+ loc: { offset: 5, line: 1, column: 6 },
+ },
+ {
+ type: ErrorCodes.X_MISSING_END_TAG,
+ loc: { offset: 0, line: 1, column: 1 },
+ },
+ ],
},
{
code: '{{}}',
- errors: []
- }
+ errors: [],
+ },
],
X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END: [
{
@@ -3007,54 +3420,39 @@ foo
errors: [
{
type: ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END,
- loc: { offset: 15, line: 1, column: 16 }
- }
- ]
- }
- ]
+ loc: { offset: 15, line: 1, column: 16 },
+ },
+ ],
+ },
+ ],
}
- for (const key of Object.keys(patterns) as (keyof typeof patterns)[]) {
+ for (const key of Object.keys(patterns)) {
describe(key, () => {
for (const { code, errors, options } of patterns[key]) {
test(
code.replace(
/[\r\n]/g,
- c => `\\x0${c.codePointAt(0)!.toString(16)};`
+ c => `\\x0${c.codePointAt(0)!.toString(16)};`,
),
() => {
- const spy = jest.fn()
+ const spy = vi.fn()
const ast = baseParse(code, {
- getNamespace: (tag, parent) => {
- const ns = parent ? parent.ns : Namespaces.HTML
- if (ns === Namespaces.HTML) {
- if (tag === 'svg') {
- return (Namespaces.HTML + 1) as any
- }
- }
- return ns
- },
- getTextMode: ({ tag }) => {
- if (tag === 'textarea') {
- return TextModes.RCDATA
- }
- if (tag === 'script') {
- return TextModes.RAWTEXT
- }
- return TextModes.DATA
- },
+ parseMode: 'html',
+ getNamespace: tag =>
+ tag === 'svg' ? Namespaces.SVG : Namespaces.HTML,
...options,
- onError: spy
+ onError: spy,
})
expect(
spy.mock.calls.map(([err]) => ({
type: err.code,
- loc: err.loc.start
- }))
+ loc: err.loc.start,
+ })),
).toMatchObject(errors)
expect(ast).toMatchSnapshot()
- }
+ },
)
}
})
diff --git a/packages/compiler-core/__tests__/scopeId.spec.ts b/packages/compiler-core/__tests__/scopeId.spec.ts
index 5f7ea0d2e6a..7d1d27e115f 100644
--- a/packages/compiler-core/__tests__/scopeId.spec.ts
+++ b/packages/compiler-core/__tests__/scopeId.spec.ts
@@ -1,7 +1,4 @@
import { baseCompile } from '../src/compile'
-import { PUSH_SCOPE_ID, POP_SCOPE_ID } from '../src/runtimeHelpers'
-import { PatchFlags } from '@vue/shared'
-import { genFlagText } from './testUtils'
/**
* Ensure all slot functions are wrapped with _withCtx
@@ -18,7 +15,7 @@ describe('scopeId compiler support', () => {
test('should wrap default slot', () => {
const { code } = baseCompile(`
`, {
mode: 'module',
- scopeId: 'test'
+ scopeId: 'test',
})
expect(code).toMatch(`default: _withCtx(() => [`)
expect(code).toMatchSnapshot()
@@ -33,8 +30,8 @@ describe('scopeId compiler support', () => {
`,
{
mode: 'module',
- scopeId: 'test'
- }
+ scopeId: 'test',
+ },
)
expect(code).toMatch(`foo: _withCtx(({ msg }) => [`)
expect(code).toMatch(`bar: _withCtx(() => [`)
@@ -50,35 +47,11 @@ describe('scopeId compiler support', () => {
`,
{
mode: 'module',
- scopeId: 'test'
- }
+ scopeId: 'test',
+ },
)
expect(code).toMatch(/name: "foo",\s+fn: _withCtx\(/)
expect(code).toMatch(/name: i,\s+fn: _withCtx\(/)
expect(code).toMatchSnapshot()
})
-
- test('should push scopeId for hoisted nodes', () => {
- const { ast, code } = baseCompile(
- `
`,
- {
- mode: 'module',
- scopeId: 'test',
- hoistStatic: true
- }
- )
- expect(ast.helpers).toContain(PUSH_SCOPE_ID)
- expect(ast.helpers).toContain(POP_SCOPE_ID)
- expect(ast.hoists.length).toBe(2)
- ;[
- `const _withScopeId = n => (_pushScopeId("test"),n=n(),_popScopeId(),n)`,
- `const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", ${genFlagText(
- PatchFlags.HOISTED
- )}))`,
- `const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", ${genFlagText(
- PatchFlags.HOISTED
- )}))`
- ].forEach(c => expect(code).toMatch(c))
- expect(code).toMatchSnapshot()
- })
})
diff --git a/packages/compiler-core/__tests__/testUtils.ts b/packages/compiler-core/__tests__/testUtils.ts
index 23a29a777e3..a2525e0cab9 100644
--- a/packages/compiler-core/__tests__/testUtils.ts
+++ b/packages/compiler-core/__tests__/testUtils.ts
@@ -1,17 +1,19 @@
import {
+ type ElementNode,
+ ElementTypes,
+ Namespaces,
NodeTypes,
- ElementNode,
+ type Property,
+ type SimpleExpressionNode,
+ type VNodeCall,
locStub,
- Namespaces,
- ElementTypes,
- VNodeCall
} from '../src'
import {
- isString,
- PatchFlags,
PatchFlagNames,
+ type PatchFlags,
+ type ShapeFlags,
isArray,
- ShapeFlags
+ isString,
} from '@vue/shared'
const leadingBracketRE = /^\[/
@@ -22,7 +24,10 @@ const bracketsRE = /^\[|\]$/g
// e.g.
// - createObjectMatcher({ 'foo': '[bar]' }) matches { foo: bar }
// - createObjectMatcher({ '[foo]': 'bar' }) matches { [foo]: "bar" }
-export function createObjectMatcher(obj: Record
) {
+export function createObjectMatcher(obj: Record): {
+ type: NodeTypes
+ properties: Partial[]
+} {
return {
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: Object.keys(obj).map(key => ({
@@ -30,16 +35,16 @@ export function createObjectMatcher(obj: Record) {
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: key.replace(bracketsRE, ''),
- isStatic: !leadingBracketRE.test(key)
- },
+ isStatic: !leadingBracketRE.test(key),
+ } as SimpleExpressionNode,
value: isString(obj[key])
? {
type: NodeTypes.SIMPLE_EXPRESSION,
content: obj[key].replace(bracketsRE, ''),
- isStatic: !leadingBracketRE.test(obj[key])
+ isStatic: !leadingBracketRE.test(obj[key]),
}
- : obj[key]
- }))
+ : obj[key],
+ })),
}
}
@@ -48,7 +53,7 @@ export function createElementWithCodegen(
props?: VNodeCall['props'],
children?: VNodeCall['children'],
patchFlag?: VNodeCall['patchFlag'],
- dynamicProps?: VNodeCall['dynamicProps']
+ dynamicProps?: VNodeCall['dynamicProps'],
): ElementNode {
return {
type: NodeTypes.ELEMENT,
@@ -56,7 +61,6 @@ export function createElementWithCodegen(
ns: Namespaces.HTML,
tag: 'div',
tagType: ElementTypes.ELEMENT,
- isSelfClosing: false,
props: [],
children: [],
codegenNode: {
@@ -70,16 +74,16 @@ export function createElementWithCodegen(
isBlock: false,
disableTracking: false,
isComponent: false,
- loc: locStub
- }
+ loc: locStub,
+ },
}
}
type Flags = PatchFlags | ShapeFlags
export function genFlagText(
flag: Flags | Flags[],
- names: { [k: number]: string } = PatchFlagNames
-) {
+ names: { [k: number]: string } = PatchFlagNames,
+): string {
if (isArray(flag)) {
let f = 0
flag.forEach(ff => {
diff --git a/packages/compiler-core/__tests__/transform.spec.ts b/packages/compiler-core/__tests__/transform.spec.ts
index 89f0ecda56e..0946d364838 100644
--- a/packages/compiler-core/__tests__/transform.spec.ts
+++ b/packages/compiler-core/__tests__/transform.spec.ts
@@ -1,25 +1,24 @@
-import { baseParse } from '../src/parse'
-import { transform, NodeTransform } from '../src/transform'
+import { baseParse } from '../src/parser'
+import { type NodeTransform, transform } from '../src/transform'
import {
- ElementNode,
+ type DirectiveNode,
+ type ElementNode,
+ type ExpressionNode,
NodeTypes,
- DirectiveNode,
- ExpressionNode,
- VNodeCall
+ type VNodeCall,
} from '../src/ast'
import { ErrorCodes, createCompilerError } from '../src/errors'
import {
- TO_DISPLAY_STRING,
+ CREATE_COMMENT,
FRAGMENT,
RENDER_SLOT,
- CREATE_COMMENT
+ TO_DISPLAY_STRING,
} from '../src/runtimeHelpers'
import { transformIf } from '../src/transforms/vIf'
import { transformFor } from '../src/transforms/vFor'
import { transformElement } from '../src/transforms/transformElement'
import { transformSlotOutlet } from '../src/transforms/transformSlotOutlet'
import { transformText } from '../src/transforms/transformText'
-import { genFlagText } from './testUtils'
import { PatchFlags } from '@vue/shared'
describe('compiler: transform', () => {
@@ -34,7 +33,7 @@ describe('compiler: transform', () => {
}
transform(ast, {
- nodeTransforms: [plugin]
+ nodeTransforms: [plugin],
})
const div = ast.children[0] as ElementNode
@@ -43,29 +42,29 @@ describe('compiler: transform', () => {
ast,
{
parent: null,
- currentNode: ast
- }
+ currentNode: ast,
+ },
])
expect(calls[1]).toMatchObject([
div,
{
parent: ast,
- currentNode: div
- }
+ currentNode: div,
+ },
])
expect(calls[2]).toMatchObject([
div.children[0],
{
parent: div,
- currentNode: div.children[0]
- }
+ currentNode: div.children[0],
+ },
])
expect(calls[3]).toMatchObject([
div.children[1],
{
parent: div,
- currentNode: div.children[1]
- }
+ currentNode: div.children[1],
+ },
])
})
@@ -81,16 +80,16 @@ describe('compiler: transform', () => {
{
type: NodeTypes.TEXT,
content: 'hello',
- isEmpty: false
- }
- ]
- })
+ isEmpty: false,
+ },
+ ],
+ }),
)
}
}
- const spy = jest.fn(plugin)
+ const spy = vi.fn(plugin)
transform(ast, {
- nodeTransforms: [spy]
+ nodeTransforms: [spy],
})
expect(ast.children.length).toBe(2)
@@ -113,9 +112,9 @@ describe('compiler: transform', () => {
context.removeNode()
}
}
- const spy = jest.fn(plugin)
+ const spy = vi.fn(plugin)
transform(ast, {
- nodeTransforms: [spy]
+ nodeTransforms: [spy],
})
expect(ast.children.length).toBe(2)
@@ -141,9 +140,9 @@ describe('compiler: transform', () => {
context.removeNode(context.parent!.children[0])
}
}
- const spy = jest.fn(plugin)
+ const spy = vi.fn(plugin)
transform(ast, {
- nodeTransforms: [spy]
+ nodeTransforms: [spy],
})
expect(ast.children.length).toBe(1)
@@ -168,9 +167,9 @@ describe('compiler: transform', () => {
context.removeNode(context.parent!.children[1])
}
}
- const spy = jest.fn(plugin)
+ const spy = vi.fn(plugin)
transform(ast, {
- nodeTransforms: [spy]
+ nodeTransforms: [spy],
})
expect(ast.children.length).toBe(1)
@@ -194,31 +193,51 @@ describe('compiler: transform', () => {
}
}
transform(ast, {
- nodeTransforms: [mock]
+ nodeTransforms: [mock],
})
expect(ast.hoists).toMatchObject(hoisted)
expect((ast as any).children[0].props[0].exp.content).toBe(`_hoisted_1`)
expect((ast as any).children[1].props[0].exp.content).toBe(`_hoisted_2`)
})
+ test('context.filename and selfName', () => {
+ const ast = baseParse(`
`)
+
+ const calls: any[] = []
+ const plugin: NodeTransform = (node, context) => {
+ calls.push({ ...context })
+ }
+
+ transform(ast, {
+ filename: '/the/fileName.vue',
+ nodeTransforms: [plugin],
+ })
+
+ expect(calls.length).toBe(2)
+ expect(calls[1]).toMatchObject({
+ filename: '/the/fileName.vue',
+ selfName: 'FileName',
+ })
+ })
+
test('onError option', () => {
const ast = baseParse(`
`)
const loc = ast.children[0].loc
const plugin: NodeTransform = (node, context) => {
context.onError(
- createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc)
+ createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc),
)
}
- const spy = jest.fn()
+ const spy = vi.fn()
transform(ast, {
nodeTransforms: [plugin],
- onError: spy
+ onError: spy,
})
expect(spy.mock.calls[0]).toMatchObject([
{
code: ErrorCodes.X_INVALID_END_TAG,
- loc
- }
+ loc,
+ },
])
})
@@ -243,8 +262,8 @@ describe('compiler: transform', () => {
transformFor,
transformText,
transformSlotOutlet,
- transformElement
- ]
+ transformElement,
+ ],
})
return ast
}
@@ -253,7 +272,7 @@ describe('compiler: transform', () => {
tag: VNodeCall['tag'],
props?: VNodeCall['props'],
children?: VNodeCall['children'],
- patchFlag?: VNodeCall['patchFlag']
+ patchFlag?: VNodeCall['patchFlag'],
) {
return {
type: NodeTypes.VNODE_CALL,
@@ -261,7 +280,7 @@ describe('compiler: transform', () => {
tag,
props,
children,
- patchFlag
+ patchFlag,
}
}
@@ -275,8 +294,8 @@ describe('compiler: transform', () => {
expect(ast.codegenNode).toMatchObject({
codegenNode: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: RENDER_SLOT
- }
+ callee: RENDER_SLOT,
+ },
})
})
@@ -288,14 +307,14 @@ describe('compiler: transform', () => {
test('root v-if', () => {
const ast = transformWithCodegen(`
`)
expect(ast.codegenNode).toMatchObject({
- type: NodeTypes.IF
+ type: NodeTypes.IF,
})
})
test('root v-for', () => {
const ast = transformWithCodegen(`
`)
expect(ast.codegenNode).toMatchObject({
- type: NodeTypes.FOR
+ type: NodeTypes.FOR,
})
})
@@ -303,28 +322,28 @@ describe('compiler: transform', () => {
const ast = transformWithCodegen(`
`)
expect(ast.codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
- directives: { type: NodeTypes.JS_ARRAY_EXPRESSION }
+ directives: { type: NodeTypes.JS_ARRAY_EXPRESSION },
})
})
test('single text', () => {
const ast = transformWithCodegen(`hello`)
expect(ast.codegenNode).toMatchObject({
- type: NodeTypes.TEXT
+ type: NodeTypes.TEXT,
})
})
test('single interpolation', () => {
const ast = transformWithCodegen(`{{ foo }}`)
expect(ast.codegenNode).toMatchObject({
- type: NodeTypes.INTERPOLATION
+ type: NodeTypes.INTERPOLATION,
})
})
test('single CompoundExpression', () => {
const ast = transformWithCodegen(`{{ foo }} bar baz`)
expect(ast.codegenNode).toMatchObject({
- type: NodeTypes.COMPOUND_EXPRESSION
+ type: NodeTypes.COMPOUND_EXPRESSION,
})
})
@@ -336,10 +355,10 @@ describe('compiler: transform', () => {
undefined,
[
{ type: NodeTypes.ELEMENT, tag: `div` },
- { type: NodeTypes.ELEMENT, tag: `div` }
+ { type: NodeTypes.ELEMENT, tag: `div` },
] as any,
- genFlagText(PatchFlags.STABLE_FRAGMENT)
- )
+ PatchFlags.STABLE_FRAGMENT,
+ ),
)
})
@@ -352,13 +371,10 @@ describe('compiler: transform', () => {
[
{ type: NodeTypes.COMMENT },
{ type: NodeTypes.ELEMENT, tag: `div` },
- { type: NodeTypes.COMMENT }
+ { type: NodeTypes.COMMENT },
] as any,
- genFlagText([
- PatchFlags.STABLE_FRAGMENT,
- PatchFlags.DEV_ROOT_FRAGMENT
- ])
- )
+ PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
+ ),
)
})
})
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap
new file mode 100644
index 00000000000..b8bef22c478
--- /dev/null
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap
@@ -0,0 +1,458 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`compiler: cacheStatic transform > cache element with static key 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
+ ])))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > cache nested children array 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("p", null, [
+ _createElementVNode("span"),
+ _createElementVNode("span")
+ ], -1 /* CACHED */),
+ _createElementVNode("p", null, [
+ _createElementVNode("span"),
+ _createElementVNode("span")
+ ], -1 /* CACHED */)
+ ])))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > cache nested static tree with comments 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("div", null, [
+ _createCommentVNode("comment")
+ ], -1 /* CACHED */)
+ ])))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > cache siblings including text with common non-hoistable parent 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", null, null, -1 /* CACHED */),
+ _createTextVNode("foo"),
+ _createElementVNode("div", null, null, -1 /* CACHED */)
+ ])))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > cache single children array 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
+ ])))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > hoist static props for elements with directives 1`] = `
+"const _Vue = Vue
+const { createElementVNode: _createElementVNode } = _Vue
+
+const _hoisted_1 = { id: "foo" }
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { resolveDirective: _resolveDirective, createElementVNode: _createElementVNode, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ const _directive_foo = _resolveDirective("foo")
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ _withDirectives(_createElementVNode("div", _hoisted_1, null, 512 /* NEED_PATCH */), [
+ [_directive_foo]
+ ])
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > hoist static props for elements with dynamic text children 1`] = `
+"const _Vue = Vue
+const { createElementVNode: _createElementVNode } = _Vue
+
+const _hoisted_1 = { id: "foo" }
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createElementVNode("div", _hoisted_1, _toDisplayString(hello), 1 /* TEXT */)
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > hoist static props for elements with unhoistable children 1`] = `
+"const _Vue = Vue
+const { createVNode: _createVNode, createElementVNode: _createElementVNode } = _Vue
+
+const _hoisted_1 = { id: "foo" }
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { resolveComponent: _resolveComponent, createVNode: _createVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ const _component_Comp = _resolveComponent("Comp")
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createElementVNode("div", _hoisted_1, [
+ _createVNode(_component_Comp)
+ ])
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > cache nested static tree with static interpolation 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
+ ])))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > cache nested static tree with static prop value 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
+ ])))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > clone hoisted array children in v-for + HMR mode 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => {
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
+ _createElementVNode("span", { class: "hi" }, null, -1 /* CACHED */)
+ ]))]))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > hoist class with static object value 1`] = `
+"const _Vue = Vue
+const { createElementVNode: _createElementVNode } = _Vue
+
+const _hoisted_1 = {
+ class: /*@__PURE__*/_normalizeClass({ foo: true })
+}
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createElementVNode("span", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */)
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache SVG with directives 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createElementVNode: _createElementVNode, resolveDirective: _resolveDirective, openBlock: _openBlock, createElementBlock: _createElementBlock, withDirectives: _withDirectives } = _Vue
+
+ const _directive_foo = _resolveDirective("foo")
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ _withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
+ ]))), [
+ [_directive_foo]
+ ])
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache elements with cached handlers + other bindings 1`] = `
+"import { normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
+
+export function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createElementVNode("div", null, [
+ _createElementVNode("div", {
+ class: _normalizeClass({}),
+ onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.foo && _ctx.foo(...args)))
+ })
+ ])
+ ]))
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache elements with cached handlers 1`] = `
+"import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
+
+export function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createElementVNode("div", null, [
+ _createElementVNode("div", {
+ onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.foo && _ctx.foo(...args)))
+ })
+ ])
+ ]))
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables (2) 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
+ return (_openBlock(), _createElementBlock("p", null, [
+ _createElementVNode("span", null, _toDisplayString(o + 'foo'), 1 /* TEXT */)
+ ]))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables (v-slot) 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+
+ const _component_Comp = _resolveComponent("Comp")
+
+ return (_openBlock(), _createBlock(_component_Comp, null, {
+ default: _withCtx(({ foo }) => [
+ _createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */)
+ ]),
+ _: 1 /* STABLE */
+ }))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
+ return (_openBlock(), _createElementBlock("p", null, [
+ _createElementVNode("span", null, _toDisplayString(o), 1 /* TEXT */)
+ ]))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache keyed template v-for with plain element child 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+ return (_openBlock(), _createElementBlock("span", { key: item }))
+ }), 128 /* KEYED_FRAGMENT */))
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > should NOT cache components 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ const _component_Comp = _resolveComponent("Comp")
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createVNode(_component_Comp)
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > should NOT cache element with dynamic key 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ (_openBlock(), _createElementBlock("div", { key: foo }))
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > should NOT cache element with dynamic props (but hoist the props list) 1`] = `
+"const _Vue = Vue
+const { createElementVNode: _createElementVNode } = _Vue
+
+const _hoisted_1 = ["id"]
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, _hoisted_1)
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > should NOT cache element with dynamic ref 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createElementVNode("div", { ref: foo }, null, 512 /* NEED_PATCH */)
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > should cache v-if props/children if static 1`] = `
+"const _Vue = Vue
+const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
+
+const _hoisted_1 = {
+ key: 0,
+ id: "foo"
+}
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ ok
+ ? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", null, null, -1 /* CACHED */)
+ ])))
+ : _createCommentVNode("v-if", true)
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > should hoist props for root with single element excluding comments 1`] = `
+"const _Vue = Vue
+const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
+
+const _hoisted_1 = { id: "a" }
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock(_Fragment, null, [
+ _createCommentVNode("comment"),
+ _createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ _createElementVNode("div", { id: "b" }, [
+ _createElementVNode("div", { id: "c" }, [
+ _createElementVNode("div", { id: "d" }, [
+ _createElementVNode("div", { id: "e" }, "hello")
+ ])
+ ])
+ ], -1 /* CACHED */)
+ ]))
+ ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = `
+"const _Vue = Vue
+const { createElementVNode: _createElementVNode } = _Vue
+
+const _hoisted_1 = { id: "foo" }
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
+ return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", null, null, -1 /* CACHED */)
+ ])))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ ]))
+ }
+}"
+`;
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
deleted file mode 100644
index 73c714d837e..00000000000
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
+++ /dev/null
@@ -1,434 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`compiler: hoistStatic transform hoist element with static key 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"div\\", { key: \\"foo\\" }, null, -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform hoist nested static tree 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"p\\", null, [
- /*#__PURE__*/_createElementVNode(\\"span\\"),
- /*#__PURE__*/_createElementVNode(\\"span\\")
-], -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform hoist nested static tree with comments 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"div\\", null, [
- /*#__PURE__*/_createCommentVNode(\\"comment\\")
-], -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform hoist siblings with common non-hoistable parent 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", null, null, -1 /* HOISTED */)
-const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"div\\", null, null, -1 /* HOISTED */)
-const _hoisted_3 = [
- _hoisted_1,
- _hoisted_2
-]
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_3))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform hoist simple element 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\", -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform hoist static props for elements with directives 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = { id: \\"foo\\" }
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { resolveDirective: _resolveDirective, createElementVNode: _createElementVNode, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- const _directive_foo = _resolveDirective(\\"foo\\")
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _withDirectives(_createElementVNode(\\"div\\", _hoisted_1, null, 512 /* NEED_PATCH */), [
- [_directive_foo]
- ])
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform hoist static props for elements with dynamic text children 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = { id: \\"foo\\" }
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _createElementVNode(\\"div\\", _hoisted_1, _toDisplayString(hello), 1 /* TEXT */)
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform hoist static props for elements with unhoistable children 1`] = `
-"const _Vue = Vue
-const { createVNode: _createVNode, createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = { id: \\"foo\\" }
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { resolveComponent: _resolveComponent, createVNode: _createVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- const _component_Comp = _resolveComponent(\\"Comp\\")
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _createElementVNode(\\"div\\", _hoisted_1, [
- _createVNode(_component_Comp)
- ])
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform prefixIdentifiers hoist class with static object value 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = {
- class: /*#__PURE__*/_normalizeClass({ foo: true })
-}
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _createElementVNode(\\"span\\", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */)
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static interpolation 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", null, \\"foo \\" + /*#__PURE__*/_toDisplayString(1) + \\" \\" + /*#__PURE__*/_toDisplayString(true), -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static prop value 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", { foo: 0 }, /*#__PURE__*/_toDisplayString(1), -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist elements with cached handlers + other bindings 1`] = `
-"import { normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
-
-export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _createElementVNode(\\"div\\", null, [
- _createElementVNode(\\"div\\", {
- class: _normalizeClass({}),
- onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.foo && _ctx.foo(...args)))
- })
- ])
- ]))
-}"
-`;
-
-exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist elements with cached handlers 1`] = `
-"import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
-
-export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _createElementVNode(\\"div\\", null, [
- _createElementVNode(\\"div\\", {
- onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.foo && _ctx.foo(...args)))
- })
- ])
- ]))
-}"
-`;
-
-exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables (2) 1`] = `
-"const _Vue = Vue
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
- return (_openBlock(), _createElementBlock(\\"p\\", null, [
- _createElementVNode(\\"span\\", null, _toDisplayString(o + 'foo'), 1 /* TEXT */)
- ]))
- }), 256 /* UNKEYED_FRAGMENT */))
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables (v-slot) 1`] = `
-"const _Vue = Vue
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = _Vue
-
- const _component_Comp = _resolveComponent(\\"Comp\\")
-
- return (_openBlock(), _createBlock(_component_Comp, null, {
- default: _withCtx(({ foo }) => [
- _createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */)
- ]),
- _: 1 /* STABLE */
- }))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables 1`] = `
-"const _Vue = Vue
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
- return (_openBlock(), _createElementBlock(\\"p\\", null, [
- _createElementVNode(\\"span\\", null, _toDisplayString(o), 1 /* TEXT */)
- ]))
- }), 256 /* UNKEYED_FRAGMENT */))
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist keyed template v-for with plain element child 1`] = `
-"const _Vue = Vue
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
- return (_openBlock(), _createElementBlock(\\"span\\", { key: item }))
- }), 128 /* KEYED_FRAGMENT */))
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform should NOT hoist components 1`] = `
-"const _Vue = Vue
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- const _component_Comp = _resolveComponent(\\"Comp\\")
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _createVNode(_component_Comp)
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform should NOT hoist element with dynamic key 1`] = `
-"const _Vue = Vue
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- (_openBlock(), _createElementBlock(\\"div\\", { key: foo }))
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform should NOT hoist element with dynamic props (but hoist the props list) 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = [\\"id\\"]
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, _hoisted_1)
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform should NOT hoist element with dynamic ref 1`] = `
-"const _Vue = Vue
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _createElementVNode(\\"div\\", { ref: foo }, null, 512 /* NEED_PATCH */)
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform should NOT hoist root node 1`] = `
-"const _Vue = Vue
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\"))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform should hoist v-for children if static 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = { id: \\"foo\\" }
-const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"span\\", null, null, -1 /* HOISTED */)
-const _hoisted_3 = [
- _hoisted_2
-]
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
- return (_openBlock(), _createElementBlock(\\"div\\", _hoisted_1, _hoisted_3))
- }), 256 /* UNKEYED_FRAGMENT */))
- ]))
- }
-}"
-`;
-
-exports[`compiler: hoistStatic transform should hoist v-if props/children if static 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
-
-const _hoisted_1 = {
- key: 0,
- id: \\"foo\\"
-}
-const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"span\\", null, null, -1 /* HOISTED */)
-const _hoisted_3 = [
- _hoisted_2
-]
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
-
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- ok
- ? (_openBlock(), _createElementBlock(\\"div\\", _hoisted_1, _hoisted_3))
- : _createCommentVNode(\\"v-if\\", true)
- ]))
- }
-}"
-`;
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
new file mode 100644
index 00000000000..3da778eb675
--- /dev/null
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
@@ -0,0 +1,228 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`compiler: v-for > codegen > basic v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+ return (_openBlock(), _createElementBlock("span"))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > keyed template v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+ return (_openBlock(), _createElementBlock(_Fragment, { key: item }, [
+ "hello",
+ _createElementVNode("span")
+ ], 64 /* STABLE_FRAGMENT */))
+ }), 128 /* KEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > keyed v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+ return (_openBlock(), _createElementBlock("span", { key: item }))
+ }), 128 /* KEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > skipped key 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, __, index) => {
+ return (_openBlock(), _createElementBlock("span"))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > skipped value & key 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, __, index) => {
+ return (_openBlock(), _createElementBlock("span"))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > skipped value 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, key, index) => {
+ return (_openBlock(), _createElementBlock("span"))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > template v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+ return (_openBlock(), _createElementBlock(_Fragment, null, [
+ "hello",
+ _createElementVNode("span")
+ ], 64 /* STABLE_FRAGMENT */))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > template v-for key injection with single child 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+ return (_openBlock(), _createElementBlock("span", {
+ key: item.id,
+ id: item.id
+ }, null, 8 /* PROPS */, ["id"]))
+ }), 128 /* KEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > template v-for w/ 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+ return _renderSlot($slots, "default")
+ }), 256 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > v-for on 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+ return _renderSlot($slots, "default")
+ }), 256 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > v-for on element with custom directive 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, resolveDirective: _resolveDirective, withDirectives: _withDirectives } = _Vue
+
+ const _directive_foo = _resolveDirective("foo")
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
+ return _withDirectives((_openBlock(), _createElementBlock("div", null, null, 512 /* NEED_PATCH */)), [
+ [_directive_foo]
+ ])
+ }), 256 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > v-for with constant expression 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = _Vue
+
+ return (_openBlock(), _createElementBlock(_Fragment, null, _renderList(10, (item) => {
+ return _createElementVNode("p", null, _toDisplayString(item), 1 /* TEXT */)
+ }), 64 /* STABLE_FRAGMENT */))
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > v-if + v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
+
+ return ok
+ ? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
+ return (_openBlock(), _createElementBlock("div"))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ : _createCommentVNode("v-if", true)
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > v-if + v-for on 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
+
+ return ok
+ ? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
+ return (_openBlock(), _createElementBlock(_Fragment, null, [], 64 /* STABLE_FRAGMENT */))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ : _createCommentVNode("v-if", true)
+ }
+}"
+`;
+
+exports[`compiler: v-for > codegen > value + key + index 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, key, index) => {
+ return (_openBlock(), _createElementBlock("span"))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap
index 93c4277ffa3..5a94de5a68b 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap
@@ -1,15 +1,105 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: expression transform bindingMetadata inline mode 1`] = `
+exports[`compiler: expression transform > bindingMetadata > inline mode 1`] = `
"(_ctx, _cache) => {
- return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString(__props.props) + \\" \\" + _toDisplayString(_unref(setup)) + \\" \\" + _toDisplayString(setupConst) + \\" \\" + _toDisplayString(_ctx.data) + \\" \\" + _toDisplayString(_ctx.options), 1 /* TEXT */))
+ return (_openBlock(), _createElementBlock("div", null, _toDisplayString(__props.props) + " " + _toDisplayString(_unref(setup)) + " " + _toDisplayString(setupConst) + " " + _toDisplayString(_ctx.data) + " " + _toDisplayString(_ctx.options) + " " + _toDisplayString(isNaN.value), 1 /* TEXT */))
}"
`;
-exports[`compiler: expression transform bindingMetadata non-inline mode 1`] = `
+exports[`compiler: expression transform > bindingMetadata > non-inline mode 1`] = `
"const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) {
- return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString($props.props) + \\" \\" + _toDisplayString($setup.setup) + \\" \\" + _toDisplayString($data.data) + \\" \\" + _toDisplayString($options.options), 1 /* TEXT */))
+ return (_openBlock(), _createElementBlock("div", null, _toDisplayString($props.props) + " " + _toDisplayString($setup.setup) + " " + _toDisplayString($data.data) + " " + _toDisplayString($options.options) + " " + _toDisplayString($setup.isNaN), 1 /* TEXT */))
+}"
+`;
+
+exports[`compiler: expression transform > should allow leak of var declarations in for loop 1`] = `
+"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", {
+ onClick: () => {
+ for (var i = 0; i < _ctx.list.length; i++) {
+ _ctx.log(i)
+ }
+ _ctx.error(i)
+ }
+ }, null, 8 /* PROPS */, ["onClick"]))
+}"
+`;
+
+exports[`compiler: expression transform > should not prefix catch block param 1`] = `
+"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", {
+ onClick: () => {
+ try {} catch (err) { console.error(err) }
+ console.log(_ctx.err)
+ }
+ }, null, 8 /* PROPS */, ["onClick"]))
+}"
+`;
+
+exports[`compiler: expression transform > should not prefix destructured catch block param 1`] = `
+"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", {
+ onClick: () => {
+ try {
+ throw new Error('sup?')
+ } catch ({ message: { length } }) {
+ console.error(length)
+ }
+ console.log(_ctx.length)
+ }
+ }, null, 8 /* PROPS */, ["onClick"]))
+}"
+`;
+
+exports[`compiler: expression transform > should not prefix temp variable of for loop 1`] = `
+"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", {
+ onClick: () => {
+ for (let i = 0; i < _ctx.list.length; i++) {
+ _ctx.log(i)
+ }
+ _ctx.error(_ctx.i)
+ }
+ }, null, 8 /* PROPS */, ["onClick"]))
+}"
+`;
+
+exports[`compiler: expression transform > should not prefix temp variable of for...in 1`] = `
+"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", {
+ onClick: () => {
+ for (const x in _ctx.list) {
+ _ctx.log(x)
+ }
+ _ctx.error(_ctx.x)
+ }
+ }, null, 8 /* PROPS */, ["onClick"]))
+}"
+`;
+
+exports[`compiler: expression transform > should not prefix temp variable of for...of 1`] = `
+"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", {
+ onClick: () => {
+ for (const x of _ctx.list) {
+ _ctx.log(x)
+ }
+ _ctx.error(_ctx.x)
+ }
+ }, null, 8 /* PROPS */, ["onClick"]))
}"
`;
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/transformText.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/transformText.spec.ts.snap
index b17a825b7b8..7fb49ea7887 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/transformText.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/transformText.spec.ts.snap
@@ -1,6 +1,6 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: transform text 1`] = `
+exports[`compiler: transform text > 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -9,26 +9,26 @@ return function render(_ctx, _cache) {
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _createElementBlock(_Fragment, null, [
- _createTextVNode(\\"foo\\")
+ _createTextVNode("foo")
], 64 /* STABLE_FRAGMENT */))
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
`;
-exports[`compiler: transform text consecutive text 1`] = `
+exports[`compiler: transform text > consecutive text 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString } = _Vue
- return _toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz)
+ return _toDisplayString(foo) + " bar " + _toDisplayString(baz)
}
}"
`;
-exports[`compiler: transform text consecutive text between elements 1`] = `
+exports[`compiler: transform text > consecutive text between elements 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -36,15 +36,15 @@ return function render(_ctx, _cache) {
const { createElementVNode: _createElementVNode, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(_Fragment, null, [
- _createElementVNode(\\"div\\"),
- _createTextVNode(_toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz), 1 /* TEXT */),
- _createElementVNode(\\"div\\")
+ _createElementVNode("div"),
+ _createTextVNode(_toDisplayString(foo) + " bar " + _toDisplayString(baz), 1 /* TEXT */),
+ _createElementVNode("div")
], 64 /* STABLE_FRAGMENT */))
}
}"
`;
-exports[`compiler: transform text consecutive text mixed with elements 1`] = `
+exports[`compiler: transform text > consecutive text mixed with elements 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -52,26 +52,26 @@ return function render(_ctx, _cache) {
const { createElementVNode: _createElementVNode, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(_Fragment, null, [
- _createElementVNode(\\"div\\"),
- _createTextVNode(_toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz), 1 /* TEXT */),
- _createElementVNode(\\"div\\"),
- _createTextVNode(\\"hello\\"),
- _createElementVNode(\\"div\\")
+ _createElementVNode("div"),
+ _createTextVNode(_toDisplayString(foo) + " bar " + _toDisplayString(baz), 1 /* TEXT */),
+ _createElementVNode("div"),
+ _createTextVNode("hello"),
+ _createElementVNode("div")
], 64 /* STABLE_FRAGMENT */))
}
}"
`;
-exports[`compiler: transform text element with custom directives and only one text child node 1`] = `
+exports[`compiler: transform text > element with custom directives and only one text child node 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveDirective: _resolveDirective, openBlock: _openBlock, createElementBlock: _createElementBlock, withDirectives: _withDirectives } = _Vue
- const _directive_foo = _resolveDirective(\\"foo\\")
+ const _directive_foo = _resolveDirective("foo")
- return _withDirectives((_openBlock(), _createElementBlock(\\"p\\", null, [
+ return _withDirectives((_openBlock(), _createElementBlock("p", null, [
_createTextVNode(_toDisplayString(foo), 1 /* TEXT */)
])), [
[_directive_foo]
@@ -80,7 +80,7 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: transform text no consecutive text 1`] = `
+exports[`compiler: transform text > no consecutive text 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -92,7 +92,7 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: transform text text between elements (static) 1`] = `
+exports[`compiler: transform text > text between elements (static) 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -100,18 +100,18 @@ return function render(_ctx, _cache) {
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(_Fragment, null, [
- _createElementVNode(\\"div\\"),
- _createTextVNode(\\"hello\\"),
- _createElementVNode(\\"div\\")
+ _createElementVNode("div"),
+ _createTextVNode("hello"),
+ _createElementVNode("div")
], 64 /* STABLE_FRAGMENT */))
}
}"
`;
-exports[`compiler: transform text with prefixIdentifiers: true 1`] = `
+exports[`compiler: transform text > with prefixIdentifiers: true 1`] = `
"const { toDisplayString: _toDisplayString } = Vue
return function render(_ctx, _cache) {
- return _toDisplayString(_ctx.foo) + \\" bar \\" + _toDisplayString(_ctx.baz + _ctx.qux)
+ return _toDisplayString(_ctx.foo) + " bar " + _toDisplayString(_ctx.baz + _ctx.qux)
}"
`;
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
index cd53651daca..3da778eb675 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
@@ -1,6 +1,6 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: v-for codegen basic v-for 1`] = `
+exports[`compiler: v-for > codegen > basic v-for 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -8,13 +8,13 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
- return (_openBlock(), _createElementBlock(\\"span\\"))
+ return (_openBlock(), _createElementBlock("span"))
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-for codegen keyed template v-for 1`] = `
+exports[`compiler: v-for > codegen > keyed template v-for 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -23,15 +23,15 @@ return function render(_ctx, _cache) {
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
return (_openBlock(), _createElementBlock(_Fragment, { key: item }, [
- \\"hello\\",
- _createElementVNode(\\"span\\")
+ "hello",
+ _createElementVNode("span")
], 64 /* STABLE_FRAGMENT */))
}), 128 /* KEYED_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-for codegen keyed v-for 1`] = `
+exports[`compiler: v-for > codegen > keyed v-for 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -39,13 +39,13 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
- return (_openBlock(), _createElementBlock(\\"span\\", { key: item }))
+ return (_openBlock(), _createElementBlock("span", { key: item }))
}), 128 /* KEYED_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-for codegen skipped key 1`] = `
+exports[`compiler: v-for > codegen > skipped key 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -53,13 +53,13 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, __, index) => {
- return (_openBlock(), _createElementBlock(\\"span\\"))
+ return (_openBlock(), _createElementBlock("span"))
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-for codegen skipped value & key 1`] = `
+exports[`compiler: v-for > codegen > skipped value & key 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -67,13 +67,13 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, __, index) => {
- return (_openBlock(), _createElementBlock(\\"span\\"))
+ return (_openBlock(), _createElementBlock("span"))
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-for codegen skipped value 1`] = `
+exports[`compiler: v-for > codegen > skipped value 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -81,13 +81,13 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, key, index) => {
- return (_openBlock(), _createElementBlock(\\"span\\"))
+ return (_openBlock(), _createElementBlock("span"))
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-for codegen template v-for 1`] = `
+exports[`compiler: v-for > codegen > template v-for 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -96,15 +96,15 @@ return function render(_ctx, _cache) {
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
return (_openBlock(), _createElementBlock(_Fragment, null, [
- \\"hello\\",
- _createElementVNode(\\"span\\")
+ "hello",
+ _createElementVNode("span")
], 64 /* STABLE_FRAGMENT */))
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-for codegen template v-for key injection with single child 1`] = `
+exports[`compiler: v-for > codegen > template v-for key injection with single child 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -112,16 +112,16 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
- return (_openBlock(), _createElementBlock(\\"span\\", {
+ return (_openBlock(), _createElementBlock("span", {
key: item.id,
id: item.id
- }, null, 8 /* PROPS */, [\\"id\\"]))
+ }, null, 8 /* PROPS */, ["id"]))
}), 128 /* KEYED_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-for codegen template v-for w/ 1`] = `
+exports[`compiler: v-for > codegen > template v-for w/ 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -129,13 +129,13 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
- return _renderSlot($slots, \\"default\\")
+ return _renderSlot($slots, "default")
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-for codegen v-for on 1`] = `
+exports[`compiler: v-for > codegen > v-for on 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -143,23 +143,23 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
- return _renderSlot($slots, \\"default\\")
+ return _renderSlot($slots, "default")
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-for codegen v-for on element with custom directive 1`] = `
+exports[`compiler: v-for > codegen > v-for on element with custom directive 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, resolveDirective: _resolveDirective, withDirectives: _withDirectives } = _Vue
- const _directive_foo = _resolveDirective(\\"foo\\")
+ const _directive_foo = _resolveDirective("foo")
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
- return _withDirectives((_openBlock(), _createElementBlock(\\"div\\", null, null, 512 /* NEED_PATCH */)), [
+ return _withDirectives((_openBlock(), _createElementBlock("div", null, null, 512 /* NEED_PATCH */)), [
[_directive_foo]
])
}), 256 /* UNKEYED_FRAGMENT */))
@@ -167,7 +167,7 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: v-for codegen v-for with constant expression 1`] = `
+exports[`compiler: v-for > codegen > v-for with constant expression 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -175,13 +175,13 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = _Vue
return (_openBlock(), _createElementBlock(_Fragment, null, _renderList(10, (item) => {
- return _createElementVNode(\\"p\\", null, _toDisplayString(item), 1 /* TEXT */)
+ return _createElementVNode("p", null, _toDisplayString(item), 1 /* TEXT */)
}), 64 /* STABLE_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-for codegen v-if + v-for 1`] = `
+exports[`compiler: v-for > codegen > v-if + v-for 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -190,14 +190,14 @@ return function render(_ctx, _cache) {
return ok
? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
- return (_openBlock(), _createElementBlock(\\"div\\"))
+ return (_openBlock(), _createElementBlock("div"))
}), 256 /* UNKEYED_FRAGMENT */))
- : _createCommentVNode(\\"v-if\\", true)
+ : _createCommentVNode("v-if", true)
}
}"
`;
-exports[`compiler: v-for codegen v-if + v-for on 1`] = `
+exports[`compiler: v-for > codegen > v-if + v-for on 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -208,12 +208,12 @@ return function render(_ctx, _cache) {
? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
return (_openBlock(), _createElementBlock(_Fragment, null, [], 64 /* STABLE_FRAGMENT */))
}), 256 /* UNKEYED_FRAGMENT */))
- : _createCommentVNode(\\"v-if\\", true)
+ : _createCommentVNode("v-if", true)
}
}"
`;
-exports[`compiler: v-for codegen value + key + index 1`] = `
+exports[`compiler: v-for > codegen > value + key + index 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -221,7 +221,7 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, key, index) => {
- return (_openBlock(), _createElementBlock(\\"span\\"))
+ return (_openBlock(), _createElementBlock("span"))
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
index 54fc7cbe7b6..65ee73c46f4 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
@@ -1,6 +1,6 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: v-if codegen basic v-if 1`] = `
+exports[`compiler: v-if > codegen > basic v-if 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -8,13 +8,13 @@ return function render(_ctx, _cache) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
- ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }))
- : _createCommentVNode(\\"v-if\\", true)
+ ? (_openBlock(), _createElementBlock("div", { key: 0 }))
+ : _createCommentVNode("v-if", true)
}
}"
`;
-exports[`compiler: v-if codegen increasing key: v-if + v-else-if + v-else 1`] = `
+exports[`compiler: v-if > codegen > increasing key: v-if + v-else-if + v-else 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -23,19 +23,19 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
ok
- ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }))
- : (_openBlock(), _createElementBlock(\\"p\\", { key: 1 })),
+ ? (_openBlock(), _createElementBlock("div", { key: 0 }))
+ : (_openBlock(), _createElementBlock("p", { key: 1 })),
another
- ? (_openBlock(), _createElementBlock(\\"div\\", { key: 2 }))
+ ? (_openBlock(), _createElementBlock("div", { key: 2 }))
: orNot
- ? (_openBlock(), _createElementBlock(\\"p\\", { key: 3 }))
- : (_openBlock(), _createElementBlock(\\"p\\", { key: 4 }))
+ ? (_openBlock(), _createElementBlock("p", { key: 3 }))
+ : (_openBlock(), _createElementBlock("p", { key: 4 }))
], 64 /* STABLE_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-if codegen multiple v-if that are sibling nodes should have different keys 1`] = `
+exports[`compiler: v-if > codegen > multiple v-if that are sibling nodes should have different keys 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -44,17 +44,17 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
ok
- ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }))
- : _createCommentVNode(\\"v-if\\", true),
+ ? (_openBlock(), _createElementBlock("div", { key: 0 }))
+ : _createCommentVNode("v-if", true),
orNot
- ? (_openBlock(), _createElementBlock(\\"p\\", { key: 1 }))
- : _createCommentVNode(\\"v-if\\", true)
+ ? (_openBlock(), _createElementBlock("p", { key: 1 }))
+ : _createCommentVNode("v-if", true)
], 64 /* STABLE_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-if codegen template v-if 1`] = `
+exports[`compiler: v-if > codegen > template v-if 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -63,16 +63,16 @@ return function render(_ctx, _cache) {
return ok
? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [
- _createElementVNode(\\"div\\"),
- \\"hello\\",
- _createElementVNode(\\"p\\")
+ _createElementVNode("div"),
+ "hello",
+ _createElementVNode("p")
], 64 /* STABLE_FRAGMENT */))
- : _createCommentVNode(\\"v-if\\", true)
+ : _createCommentVNode("v-if", true)
}
}"
`;
-exports[`compiler: v-if codegen template v-if w/ single child 1`] = `
+exports[`compiler: v-if > codegen > template v-if w/ single child 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -80,13 +80,13 @@ return function render(_ctx, _cache) {
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return ok
- ? _renderSlot($slots, \\"default\\", { key: 0 })
- : _createCommentVNode(\\"v-if\\", true)
+ ? _renderSlot($slots, "default", { key: 0 })
+ : _createCommentVNode("v-if", true)
}
}"
`;
-exports[`compiler: v-if codegen v-if + v-else 1`] = `
+exports[`compiler: v-if > codegen > v-if + v-else 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -94,13 +94,13 @@ return function render(_ctx, _cache) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
- ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }))
- : (_openBlock(), _createElementBlock(\\"p\\", { key: 1 }))
+ ? (_openBlock(), _createElementBlock("div", { key: 0 }))
+ : (_openBlock(), _createElementBlock("p", { key: 1 }))
}
}"
`;
-exports[`compiler: v-if codegen v-if + v-else-if + v-else 1`] = `
+exports[`compiler: v-if > codegen > v-if + v-else-if + v-else 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -108,15 +108,15 @@ return function render(_ctx, _cache) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue
return ok
- ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }))
+ ? (_openBlock(), _createElementBlock("div", { key: 0 }))
: orNot
- ? (_openBlock(), _createElementBlock(\\"p\\", { key: 1 }))
- : (_openBlock(), _createElementBlock(_Fragment, { key: 2 }, [\\"fine\\"], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
+ ? (_openBlock(), _createElementBlock("p", { key: 1 }))
+ : (_openBlock(), _createElementBlock(_Fragment, { key: 2 }, ["fine"], 64 /* STABLE_FRAGMENT */))
}
}"
`;
-exports[`compiler: v-if codegen v-if + v-else-if 1`] = `
+exports[`compiler: v-if > codegen > v-if + v-else-if 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -124,15 +124,15 @@ return function render(_ctx, _cache) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
- ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }))
+ ? (_openBlock(), _createElementBlock("div", { key: 0 }))
: orNot
- ? (_openBlock(), _createElementBlock(\\"p\\", { key: 1 }))
- : _createCommentVNode(\\"v-if\\", true)
+ ? (_openBlock(), _createElementBlock("p", { key: 1 }))
+ : _createCommentVNode("v-if", true)
}
}"
`;
-exports[`compiler: v-if codegen v-if on 1`] = `
+exports[`compiler: v-if > codegen > v-if on 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -140,8 +140,8 @@ return function render(_ctx, _cache) {
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return ok
- ? _renderSlot($slots, \\"default\\", { key: 0 })
- : _createCommentVNode(\\"v-if\\", true)
+ ? _renderSlot($slots, "default", { key: 0 })
+ : _createCommentVNode("v-if", true)
}
}"
`;
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap
index 7627d8fd3da..86e0b3d2fd5 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap
@@ -1,44 +1,62 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: v-memo transform on component 1`] = `
-"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
+exports[`compiler: v-memo transform > element v-for key expression prefixing + v-memo 1`] = `
+"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue"
export function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ return (_openBlock(), _createElementBlock("div", null, [
+ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tableData, (data, __, ___, _cached) => {
+ const _memo = (_ctx.getLetter(data))
+ if (_cached && _cached.key === _ctx.getId(data) && _isMemoSame(_cached, _memo)) return _cached
+ const _item = (_openBlock(), _createElementBlock("span", {
+ key: _ctx.getId(data)
+ }))
+ _item.memo = _memo
+ return _item
+ }, _cache, 0), 128 /* KEYED_FRAGMENT */))
+ ]))
+}"
+`;
+
+exports[`compiler: v-memo transform > on component 1`] = `
+"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
+
+export function render(_ctx, _cache) {
+ const _component_Comp = _resolveComponent("Comp")
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
+ return (_openBlock(), _createElementBlock("div", null, [
_withMemo([_ctx.x], () => _createVNode(_component_Comp), _cache, 0)
]))
}"
`;
-exports[`compiler: v-memo transform on normal element 1`] = `
-"import { openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo } from \\"vue\\"
+exports[`compiler: v-memo transform > on normal element 1`] = `
+"import { openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo } from "vue"
export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
- _withMemo([_ctx.x], () => (_openBlock(), _createElementBlock(\\"div\\")), _cache, 0)
+ return (_openBlock(), _createElementBlock("div", null, [
+ _withMemo([_ctx.x], () => (_openBlock(), _createElementBlock("div")), _cache, 0)
]))
}"
`;
-exports[`compiler: v-memo transform on root element 1`] = `
-"import { openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo } from \\"vue\\"
+exports[`compiler: v-memo transform > on root element 1`] = `
+"import { openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo } from "vue"
export function render(_ctx, _cache) {
- return _withMemo([_ctx.x], () => (_openBlock(), _createElementBlock(\\"div\\")), _cache, 0)
+ return _withMemo([_ctx.x], () => (_openBlock(), _createElementBlock("div")), _cache, 0)
}"
`;
-exports[`compiler: v-memo transform on template v-for 1`] = `
-"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, isMemoSame as _isMemoSame, withMemo as _withMemo } from \\"vue\\"
+exports[`compiler: v-memo transform > on template v-for 1`] = `
+"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue"
export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
+ return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, ___, _cached) => {
const _memo = ([x, y === _ctx.z])
if (_cached && _cached.key === x && _isMemoSame(_cached, _memo)) return _cached
- const _item = (_openBlock(), _createElementBlock(\\"span\\", { key: x }, \\"foobar\\"))
+ const _item = (_openBlock(), _createElementBlock("span", { key: x }, "foobar"))
_item.memo = _memo
return _item
}, _cache, 0), 128 /* KEYED_FRAGMENT */))
@@ -46,16 +64,16 @@ export function render(_ctx, _cache) {
}"
`;
-exports[`compiler: v-memo transform on v-for 1`] = `
-"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createElementVNode as _createElementVNode, isMemoSame as _isMemoSame, withMemo as _withMemo } from \\"vue\\"
+exports[`compiler: v-memo transform > on v-for 1`] = `
+"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createElementVNode as _createElementVNode, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue"
export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
+ return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, ___, _cached) => {
const _memo = ([x, y === _ctx.z])
if (_cached && _cached.key === x && _isMemoSame(_cached, _memo)) return _cached
- const _item = (_openBlock(), _createElementBlock(\\"div\\", { key: x }, [
- _createElementVNode(\\"span\\", null, \\"foobar\\")
+ const _item = (_openBlock(), _createElementBlock("div", { key: x }, [
+ _createElementVNode("span", null, "foobar")
]))
_item.memo = _memo
return _item
@@ -64,17 +82,17 @@ export function render(_ctx, _cache) {
}"
`;
-exports[`compiler: v-memo transform on v-if 1`] = `
-"import { createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo, createCommentVNode as _createCommentVNode, resolveComponent as _resolveComponent, createBlock as _createBlock } from \\"vue\\"
+exports[`compiler: v-memo transform > on v-if 1`] = `
+"import { createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo, createCommentVNode as _createCommentVNode, resolveComponent as _resolveComponent, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
+ return (_openBlock(), _createElementBlock("div", null, [
(_ctx.ok)
- ? _withMemo([_ctx.x], () => (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }, [
- _createElementVNode(\\"span\\", null, \\"foo\\"),
- _createTextVNode(\\"bar\\")
+ ? _withMemo([_ctx.x], () => (_openBlock(), _createElementBlock("div", { key: 0 }, [
+ _createElementVNode("span", null, "foo"),
+ _createTextVNode("bar")
])), _cache, 0)
: _withMemo([_ctx.x], () => (_openBlock(), _createBlock(_component_Comp, { key: 1 })), _cache, 1)
]))
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
index 42974581f60..17c4e80b160 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
@@ -1,116 +1,116 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: transform v-model compound expression (with prefixIdentifiers) 1`] = `
-"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
+exports[`compiler: transform v-model > compound expression (with prefixIdentifiers) 1`] = `
+"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock(\\"input\\", {
+ return (_openBlock(), _createElementBlock("input", {
modelValue: _ctx.model[_ctx.index],
- \\"onUpdate:modelValue\\": $event => ((_ctx.model[_ctx.index]) = $event)
- }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
+ "onUpdate:modelValue": $event => ((_ctx.model[_ctx.index]) = $event)
+ }, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue"]))
}"
`;
-exports[`compiler: transform v-model compound expression 1`] = `
+exports[`compiler: transform v-model > compound expression 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock(\\"input\\", {
+ return (_openBlock(), _createElementBlock("input", {
modelValue: model[index],
- \\"onUpdate:modelValue\\": $event => ((model[index]) = $event)
- }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
+ "onUpdate:modelValue": $event => ((model[index]) = $event)
+ }, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue"]))
}
}"
`;
-exports[`compiler: transform v-model simple expression (with multilines) 1`] = `
+exports[`compiler: transform v-model > simple expression (with multilines) 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock(\\"input\\", {
+ return (_openBlock(), _createElementBlock("input", {
modelValue:
model
.
foo
,
- \\"onUpdate:modelValue\\": $event => ((
+ "onUpdate:modelValue": $event => ((
model
.
foo
) = $event)
- }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
+ }, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue"]))
}
}"
`;
-exports[`compiler: transform v-model simple expression (with prefixIdentifiers) 1`] = `
-"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
+exports[`compiler: transform v-model > simple expression (with prefixIdentifiers) 1`] = `
+"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock(\\"input\\", {
+ return (_openBlock(), _createElementBlock("input", {
modelValue: _ctx.model,
- \\"onUpdate:modelValue\\": $event => ((_ctx.model) = $event)
- }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
+ "onUpdate:modelValue": $event => ((_ctx.model) = $event)
+ }, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue"]))
}"
`;
-exports[`compiler: transform v-model simple expression 1`] = `
+exports[`compiler: transform v-model > simple expression 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock(\\"input\\", {
+ return (_openBlock(), _createElementBlock("input", {
modelValue: model,
- \\"onUpdate:modelValue\\": $event => ((model) = $event)
- }, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
+ "onUpdate:modelValue": $event => ((model) = $event)
+ }, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue"]))
}
}"
`;
-exports[`compiler: transform v-model with argument 1`] = `
+exports[`compiler: transform v-model > with argument 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock(\\"input\\", {
- value: model,
- \\"onUpdate:value\\": $event => ((model) = $event)
- }, null, 40 /* PROPS, HYDRATE_EVENTS */, [\\"value\\", \\"onUpdate:value\\"]))
+ return (_openBlock(), _createElementBlock("input", {
+ "foo-value": model,
+ "onUpdate:fooValue": $event => ((model) = $event)
+ }, null, 40 /* PROPS, NEED_HYDRATION */, ["foo-value", "onUpdate:fooValue"]))
}
}"
`;
-exports[`compiler: transform v-model with dynamic argument (with prefixIdentifiers) 1`] = `
-"import { normalizeProps as _normalizeProps, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
+exports[`compiler: transform v-model > with dynamic argument (with prefixIdentifiers) 1`] = `
+"import { normalizeProps as _normalizeProps, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock(\\"input\\", _normalizeProps({
+ return (_openBlock(), _createElementBlock("input", _normalizeProps({
[_ctx.value]: _ctx.model,
- [\\"onUpdate:\\" + _ctx.value]: $event => ((_ctx.model) = $event)
+ ["onUpdate:" + _ctx.value]: $event => ((_ctx.model) = $event)
}), null, 16 /* FULL_PROPS */))
}"
`;
-exports[`compiler: transform v-model with dynamic argument 1`] = `
+exports[`compiler: transform v-model > with dynamic argument 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { normalizeProps: _normalizeProps, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock(\\"input\\", _normalizeProps({
+ return (_openBlock(), _createElementBlock("input", _normalizeProps({
[value]: model,
- [\\"onUpdate:\\" + value]: $event => ((model) = $event)
+ ["onUpdate:" + value]: $event => ((model) = $event)
}), null, 16 /* FULL_PROPS */))
}
}"
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
index 575c59ebb59..6660865a523 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
@@ -1,6 +1,6 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: v-once transform as root node 1`] = `
+exports[`compiler: v-once transform > as root node 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
@@ -8,8 +8,8 @@ return function render(_ctx, _cache) {
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue
return _cache[0] || (
- _setBlockTracking(-1),
- _cache[0] = _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
+ _setBlockTracking(-1, true),
+ (_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
_setBlockTracking(1),
_cache[0]
)
@@ -17,19 +17,19 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: v-once transform on component 1`] = `
+exports[`compiler: v-once transform > on component 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
+ return (_openBlock(), _createElementBlock("div", null, [
_cache[0] || (
- _setBlockTracking(-1),
- _cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
+ _setBlockTracking(-1, true),
+ (_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
_setBlockTracking(1),
_cache[0]
)
@@ -38,17 +38,17 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: v-once transform on nested plain element 1`] = `
+exports[`compiler: v-once transform > on nested plain element 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
+ return (_openBlock(), _createElementBlock("div", null, [
_cache[0] || (
- _setBlockTracking(-1),
- _cache[0] = _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
+ _setBlockTracking(-1, true),
+ (_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
_setBlockTracking(1),
_cache[0]
)
@@ -57,17 +57,17 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: v-once transform on slot outlet 1`] = `
+exports[`compiler: v-once transform > on slot outlet 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, renderSlot: _renderSlot, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
+ return (_openBlock(), _createElementBlock("div", null, [
_cache[0] || (
- _setBlockTracking(-1),
- _cache[0] = _renderSlot($slots, \\"default\\"),
+ _setBlockTracking(-1, true),
+ (_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
_setBlockTracking(1),
_cache[0]
)
@@ -76,17 +76,17 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: v-once transform with hoistStatic: true 1`] = `
+exports[`compiler: v-once transform > with hoistStatic: true 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock(\\"div\\", null, [
+ return (_openBlock(), _createElementBlock("div", null, [
_cache[0] || (
- _setBlockTracking(-1),
- _cache[0] = _createElementVNode(\\"div\\"),
+ _setBlockTracking(-1, true),
+ (_cache[0] = _createElementVNode("div")).cacheIndex = 0,
_setBlockTracking(1),
_cache[0]
)
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
index 967f2b3f45e..2cd13bab036 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
@@ -1,10 +1,10 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: transform component slots dynamically named slots 1`] = `
+exports[`compiler: transform component slots > dynamically named slots 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
[_ctx.one]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
@@ -14,26 +14,26 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: transform component slots implicit default slot 1`] = `
+exports[`compiler: transform component slots > implicit default slot 1`] = `
"const { createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
default: _withCtx(() => [
- _createElementVNode(\\"div\\")
+ _createElementVNode("div")
]),
_: 1 /* STABLE */
}))
}"
`;
-exports[`compiler: transform component slots named slot with v-for w/ prefixIdentifiers: true 1`] = `
+exports[`compiler: transform component slots > named slot with v-for w/ prefixIdentifiers: true 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, renderList: _renderList, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
_renderList(_ctx.list, (name) => {
@@ -46,66 +46,71 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: transform component slots named slot with v-if + prefixIdentifiers: true 1`] = `
+exports[`compiler: transform component slots > named slot with v-if + prefixIdentifiers: true 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
(_ctx.ok)
? {
- name: \\"one\\",
- fn: _withCtx((props) => [_toDisplayString(props)])
+ name: "one",
+ fn: _withCtx((props) => [_toDisplayString(props)]),
+ key: "0"
}
: undefined
]), 1024 /* DYNAMIC_SLOTS */))
}"
`;
-exports[`compiler: transform component slots named slot with v-if + v-else-if + v-else 1`] = `
+exports[`compiler: transform component slots > named slot with v-if + v-else-if + v-else 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = _Vue
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
ok
? {
- name: \\"one\\",
- fn: _withCtx(() => [\\"foo\\"])
+ name: "one",
+ fn: _withCtx(() => ["foo"]),
+ key: "0"
}
: orNot
? {
- name: \\"two\\",
- fn: _withCtx((props) => [\\"bar\\"])
+ name: "two",
+ fn: _withCtx((props) => ["bar"]),
+ key: "1"
}
: {
- name: \\"one\\",
- fn: _withCtx(() => [\\"baz\\"])
+ name: "one",
+ fn: _withCtx(() => ["baz"]),
+ key: "2"
}
]), 1024 /* DYNAMIC_SLOTS */))
}
}"
`;
-exports[`compiler: transform component slots named slot with v-if 1`] = `
+exports[`compiler: transform component slots > named slot with v-if 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = _Vue
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
ok
? {
- name: \\"one\\",
- fn: _withCtx(() => [\\"hello\\"])
+ name: "one",
+ fn: _withCtx(() => ["hello"]),
+ key: "0"
}
: undefined
]), 1024 /* DYNAMIC_SLOTS */))
@@ -113,20 +118,20 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: transform component slots named slots w/ implicit default slot 1`] = `
+exports[`compiler: transform component slots > named slots w/ implicit default slot 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = _Vue
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
- one: _withCtx(() => [\\"foo\\"]),
+ one: _withCtx(() => ["foo"]),
default: _withCtx(() => [
- \\"bar\\",
- _createElementVNode(\\"span\\")
+ "bar",
+ _createElementVNode("span")
]),
_: 1 /* STABLE */
}))
@@ -134,12 +139,12 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: transform component slots nested slots scoping 1`] = `
+exports[`compiler: transform component slots > nested slots scoping 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Inner = _resolveComponent(\\"Inner\\")
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Inner = _resolveComponent("Inner")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
default: _withCtx(({ foo }) => [
@@ -147,7 +152,7 @@ return function render(_ctx, _cache) {
default: _withCtx(({ bar }) => [_toDisplayString(foo), _toDisplayString(bar), _toDisplayString(_ctx.baz)]),
_: 2 /* DYNAMIC */
}, 1024 /* DYNAMIC_SLOTS */),
- \\" \\",
+ " ",
_toDisplayString(foo),
_toDisplayString(_ctx.bar),
_toDisplayString(_ctx.baz)
@@ -157,11 +162,11 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: transform component slots on component dynamically named slot 1`] = `
+exports[`compiler: transform component slots > on component dynamically named slot 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
[_ctx.named]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
@@ -170,11 +175,11 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: transform component slots on component named slot 1`] = `
+exports[`compiler: transform component slots > on component named slot 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
named: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
@@ -183,11 +188,11 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: transform component slots on-component default slot 1`] = `
+exports[`compiler: transform component slots > on-component default slot 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
default: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
@@ -196,11 +201,11 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: transform component slots template named slots 1`] = `
+exports[`compiler: transform component slots > template named slots 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
one: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
@@ -210,46 +215,68 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: transform component slots with whitespace: 'preserve' implicit default slot 1`] = `
+exports[`compiler: transform component slots > with whitespace: 'preserve' > implicit default slot 1`] = `
"const { createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
- header: _withCtx(() => [\\" Header \\"]),
+ header: _withCtx(() => [" Header "]),
default: _withCtx(() => [
- \\" \\",
- _createElementVNode(\\"p\\")
+ " ",
+ _createElementVNode("p")
]),
_: 1 /* STABLE */
}))
}"
`;
-exports[`compiler: transform component slots with whitespace: 'preserve' named default slot + implicit whitespace content 1`] = `
+exports[`compiler: transform component slots > with whitespace: 'preserve' > named default slot + implicit whitespace content 1`] = `
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
- header: _withCtx(() => [\\" Header \\"]),
- default: _withCtx(() => [\\" Default \\"]),
+ header: _withCtx(() => [" Header "]),
+ default: _withCtx(() => [" Default "]),
_: 1 /* STABLE */
}))
}"
`;
-exports[`compiler: transform component slots with whitespace: 'preserve' should not generate whitespace only default slot 1`] = `
+exports[`compiler: transform component slots > with whitespace: 'preserve' > named slot with v-if + v-else 1`] = `
+"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+ const _component_Comp = _resolveComponent("Comp")
+
+ return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
+ ok
+ ? {
+ name: "one",
+ fn: _withCtx(() => ["foo"]),
+ key: "0"
+ }
+ : {
+ name: "two",
+ fn: _withCtx(() => ["baz"]),
+ key: "1"
+ }
+ ]), 1024 /* DYNAMIC_SLOTS */))
+}"
+`;
+
+exports[`compiler: transform component slots > with whitespace: 'preserve' > should not generate whitespace only default slot 1`] = `
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
- const _component_Comp = _resolveComponent(\\"Comp\\")
+ const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
- header: _withCtx(() => [\\" Header \\"]),
- footer: _withCtx(() => [\\" Footer \\"]),
+ header: _withCtx(() => [" Header "]),
+ footer: _withCtx(() => [" Footer "]),
_: 1 /* STABLE */
}))
}"
diff --git a/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
new file mode 100644
index 00000000000..74f6caca328
--- /dev/null
+++ b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
@@ -0,0 +1,803 @@
+import {
+ type CompilerOptions,
+ ConstantTypes,
+ type ElementNode,
+ type ForNode,
+ type IfNode,
+ NodeTypes,
+ type VNodeCall,
+ generate,
+ baseParse as parse,
+ transform,
+} from '../../src'
+import {
+ FRAGMENT,
+ NORMALIZE_CLASS,
+ RENDER_LIST,
+} from '../../src/runtimeHelpers'
+import { transformElement } from '../../src/transforms/transformElement'
+import { transformExpression } from '../../src/transforms/transformExpression'
+import { transformIf } from '../../src/transforms/vIf'
+import { transformFor } from '../../src/transforms/vFor'
+import { transformBind } from '../../src/transforms/vBind'
+import { transformOn } from '../../src/transforms/vOn'
+import { createObjectMatcher } from '../testUtils'
+import { transformText } from '../../src/transforms/transformText'
+import { PatchFlags } from '@vue/shared'
+
+const cachedChildrenArrayMatcher = (
+ tags: string[],
+ needArraySpread = false,
+) => ({
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ needArraySpread,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: tags.map(tag => {
+ if (tag === '') {
+ return {
+ type: NodeTypes.TEXT_CALL,
+ }
+ } else {
+ return {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: JSON.stringify(tag),
+ },
+ }
+ }
+ }),
+ },
+})
+
+function transformWithCache(template: string, options: CompilerOptions = {}) {
+ const ast = parse(template)
+ transform(ast, {
+ hoistStatic: true,
+ nodeTransforms: [
+ transformIf,
+ transformFor,
+ ...(options.prefixIdentifiers ? [transformExpression] : []),
+ transformElement,
+ transformText,
+ ],
+ directiveTransforms: {
+ on: transformOn,
+ bind: transformBind,
+ },
+ ...options,
+ })
+ expect(ast.codegenNode).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ isBlock: true,
+ })
+ return ast
+}
+
+describe('compiler: cacheStatic transform', () => {
+ test('should NOT cache root node', () => {
+ // if the whole tree is static, the root still needs to be a block
+ // so that it's patched in optimized mode to skip children
+ const root = transformWithCache(`
`)
+ expect(root.codegenNode).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ })
+ expect(root.cached.length).toBe(0)
+ })
+
+ test('cache root node children', () => {
+ // we don't have access to the root codegenNode during the transform
+ // so we only cache each child individually
+ const root = transformWithCache(
+ `hello hello `,
+ )
+ expect(root.codegenNode).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ children: [
+ { codegenNode: { type: NodeTypes.JS_CACHE_EXPRESSION } },
+ { codegenNode: { type: NodeTypes.JS_CACHE_EXPRESSION } },
+ ],
+ })
+ expect(root.cached.length).toBe(2)
+ })
+
+ test('cache single children array', () => {
+ const root = transformWithCache(
+ `hello
`,
+ )
+ expect(root.codegenNode).toMatchObject({
+ tag: `"div"`,
+ props: undefined,
+ children: cachedChildrenArrayMatcher(['span']),
+ })
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('cache nested children array', () => {
+ const root = transformWithCache(
+ ``,
+ )
+ expect((root.codegenNode as VNodeCall).children).toMatchObject(
+ cachedChildrenArrayMatcher(['p', 'p']),
+ )
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('cache nested static tree with comments', () => {
+ const root = transformWithCache(``)
+ expect((root.codegenNode as VNodeCall).children).toMatchObject(
+ cachedChildrenArrayMatcher(['div']),
+ )
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('cache siblings including text with common non-hoistable parent', () => {
+ const root = transformWithCache(``)
+ expect((root.codegenNode as VNodeCall).children).toMatchObject(
+ cachedChildrenArrayMatcher(['span', '', 'div']),
+ )
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('cache inside default slot', () => {
+ const root = transformWithCache(`{{x}} `)
+ expect((root.codegenNode as VNodeCall).children).toMatchObject({
+ properties: [
+ {
+ key: { content: 'default' },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: [
+ {
+ type: NodeTypes.TEXT_CALL,
+ },
+ // first slot child cached
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ },
+ },
+ ],
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ {
+ type: NodeTypes.JS_PROPERTY,
+ key: { content: '__' },
+ value: { content: '[0]' },
+ },
+ ],
+ })
+ })
+
+ test('cache default slot as a whole', () => {
+ const root = transformWithCache(` `)
+ expect((root.codegenNode as VNodeCall).children).toMatchObject({
+ properties: [
+ {
+ key: { content: 'default' },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ { type: NodeTypes.ELEMENT },
+ { type: NodeTypes.ELEMENT },
+ ],
+ },
+ },
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ {
+ type: NodeTypes.JS_PROPERTY,
+ key: { content: '__' },
+ value: { content: '[0]' },
+ },
+ ],
+ })
+ })
+
+ test('cache inside named slot', () => {
+ const root = transformWithCache(
+ `{{x}} `,
+ )
+ expect((root.codegenNode as VNodeCall).children).toMatchObject({
+ properties: [
+ {
+ key: { content: 'foo' },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: [
+ {
+ type: NodeTypes.TEXT_CALL,
+ },
+ // first slot child cached
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ },
+ },
+ ],
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ ],
+ })
+ })
+
+ test('cache named slot as a whole', () => {
+ const root = transformWithCache(
+ ` `,
+ )
+ expect((root.codegenNode as VNodeCall).children).toMatchObject({
+ properties: [
+ {
+ key: { content: 'foo' },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ { type: NodeTypes.ELEMENT },
+ { type: NodeTypes.ELEMENT },
+ ],
+ },
+ },
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ ],
+ })
+ })
+
+ test('cache dynamically named slot as a whole', () => {
+ const root = transformWithCache(
+ ` `,
+ )
+ expect((root.codegenNode as VNodeCall).children).toMatchObject({
+ properties: [
+ {
+ key: { content: 'foo', isStatic: false },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ { type: NodeTypes.ELEMENT },
+ { type: NodeTypes.ELEMENT },
+ ],
+ },
+ },
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ ],
+ })
+ })
+
+ test('cache dynamically named (expression) slot as a whole', () => {
+ const root = transformWithCache(
+ ` `,
+ { prefixIdentifiers: true },
+ )
+ expect((root.codegenNode as VNodeCall).children).toMatchObject({
+ properties: [
+ {
+ key: { type: NodeTypes.COMPOUND_EXPRESSION },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ { type: NodeTypes.ELEMENT },
+ { type: NodeTypes.ELEMENT },
+ ],
+ },
+ },
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ ],
+ })
+ })
+
+ test('should NOT cache components', () => {
+ const root = transformWithCache(`
`)
+ expect((root.codegenNode as VNodeCall).children).toMatchObject([
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `_component_Comp`,
+ },
+ },
+ ])
+ expect(root.cached.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('should NOT cache element with dynamic props (but hoist the props list)', () => {
+ const root = transformWithCache(``)
+ expect(root.hoists.length).toBe(1)
+ expect((root.codegenNode as VNodeCall).children).toMatchObject([
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: createObjectMatcher({
+ id: `[foo]`,
+ }),
+ children: undefined,
+ patchFlag: PatchFlags.PROPS,
+ dynamicProps: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_hoisted_1`,
+ isStatic: false,
+ },
+ },
+ },
+ ])
+ expect(root.cached.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('cache element with static key', () => {
+ const root = transformWithCache(``)
+ expect(root.codegenNode).toMatchObject({
+ tag: `"div"`,
+ props: undefined,
+ children: cachedChildrenArrayMatcher(['div']),
+ })
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('should NOT cache element with dynamic key', () => {
+ const root = transformWithCache(``)
+ expect((root.codegenNode as VNodeCall).children).toMatchObject([
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: createObjectMatcher({
+ key: `[foo]`,
+ }),
+ },
+ },
+ ])
+ expect(root.cached.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('should NOT cache element with dynamic ref', () => {
+ const root = transformWithCache(``)
+ expect((root.codegenNode as VNodeCall).children).toMatchObject([
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: createObjectMatcher({
+ ref: `[foo]`,
+ }),
+ children: undefined,
+ patchFlag: PatchFlags.NEED_PATCH,
+ },
+ },
+ ])
+ expect(root.cached.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('hoist static props for elements with directives', () => {
+ const root = transformWithCache(``)
+ expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
+ expect((root.codegenNode as VNodeCall).children).toMatchObject([
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_hoisted_1`,
+ },
+ children: undefined,
+ patchFlag: PatchFlags.NEED_PATCH,
+ directives: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ },
+ },
+ },
+ ])
+ expect(root.cached.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('hoist static props for elements with dynamic text children', () => {
+ const root = transformWithCache(
+ ``,
+ )
+ expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
+ expect((root.codegenNode as VNodeCall).children).toMatchObject([
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: { content: `_hoisted_1` },
+ children: { type: NodeTypes.INTERPOLATION },
+ patchFlag: PatchFlags.TEXT,
+ },
+ },
+ ])
+ expect(root.cached.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('hoist static props for elements with unhoistable children', () => {
+ const root = transformWithCache(``)
+ expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
+ expect((root.codegenNode as VNodeCall).children).toMatchObject([
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: { content: `_hoisted_1` },
+ children: [{ type: NodeTypes.ELEMENT, tag: `Comp` }],
+ },
+ },
+ ])
+ expect(root.cached.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('should cache v-if props/children if static', () => {
+ const root = transformWithCache(
+ ``,
+ )
+ expect(root.hoists).toMatchObject([
+ createObjectMatcher({
+ key: `[0]`, // key injected by v-if branch
+ id: 'foo',
+ }),
+ ])
+ expect(
+ ((root.children[0] as ElementNode).children[0] as IfNode).codegenNode,
+ ).toMatchObject({
+ type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
+ consequent: {
+ // blocks should NOT be cached
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: { content: `_hoisted_1` },
+ children: cachedChildrenArrayMatcher(['span']),
+ },
+ })
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('should hoist v-for children if static', () => {
+ const root = transformWithCache(
+ ``,
+ )
+ expect(root.hoists).toMatchObject([
+ createObjectMatcher({
+ id: 'foo',
+ }),
+ ])
+ const forBlockCodegen = (
+ (root.children[0] as ElementNode).children[0] as ForNode
+ ).codegenNode
+ expect(forBlockCodegen).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ tag: FRAGMENT,
+ props: undefined,
+ children: {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: RENDER_LIST,
+ },
+ patchFlag: PatchFlags.UNKEYED_FRAGMENT,
+ })
+ const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
+ expect(innerBlockCodegen.returns).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: { content: `_hoisted_1` },
+ children: cachedChildrenArrayMatcher(['span']),
+ })
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('should hoist props for root with single element excluding comments', () => {
+ // deeply nested div to trigger stringification condition
+ const root = transformWithCache(
+ ``,
+ )
+ expect(root.cached.length).toBe(1)
+ expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'a' })])
+
+ expect((root.codegenNode as VNodeCall).children).toMatchObject([
+ {
+ type: NodeTypes.COMMENT,
+ content: 'comment',
+ },
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: { content: `_hoisted_1` },
+ children: { type: NodeTypes.JS_CACHE_EXPRESSION },
+ },
+ },
+ ])
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ describe('prefixIdentifiers', () => {
+ test('cache nested static tree with static interpolation', () => {
+ const root = transformWithCache(
+ `foo {{ 1 }} {{ true }}
`,
+ {
+ prefixIdentifiers: true,
+ },
+ )
+ expect(root.codegenNode).toMatchObject({
+ tag: `"div"`,
+ props: undefined,
+ children: cachedChildrenArrayMatcher(['span']),
+ })
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('cache nested static tree with static prop value', () => {
+ const root = transformWithCache(
+ `{{ 1 }}
`,
+ {
+ prefixIdentifiers: true,
+ },
+ )
+ expect(root.codegenNode).toMatchObject({
+ tag: `"div"`,
+ props: undefined,
+ children: cachedChildrenArrayMatcher(['span']),
+ })
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('hoist class with static object value', () => {
+ const root = transformWithCache(
+ `{{ bar }}
`,
+ {
+ prefixIdentifiers: true,
+ },
+ )
+
+ expect(root.hoists).toMatchObject([
+ {
+ type: NodeTypes.JS_OBJECT_EXPRESSION,
+ properties: [
+ {
+ key: {
+ content: `class`,
+ isStatic: true,
+ constType: ConstantTypes.CAN_STRINGIFY,
+ },
+ value: {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: NORMALIZE_CLASS,
+ arguments: [
+ {
+ content: `{ foo: true }`,
+ isStatic: false,
+ constType: ConstantTypes.CAN_STRINGIFY,
+ },
+ ],
+ },
+ },
+ ],
+ },
+ ])
+ expect(root.codegenNode).toMatchObject({
+ tag: `"div"`,
+ props: undefined,
+ children: [
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"span"`,
+ props: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_hoisted_1`,
+ },
+ children: {
+ type: NodeTypes.INTERPOLATION,
+ content: {
+ content: `_ctx.bar`,
+ isStatic: false,
+ constType: ConstantTypes.NOT_CONSTANT,
+ },
+ },
+ patchFlag: PatchFlags.TEXT,
+ },
+ },
+ ],
+ })
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('should NOT cache expressions that refer scope variables', () => {
+ const root = transformWithCache(
+ ``,
+ {
+ prefixIdentifiers: true,
+ },
+ )
+
+ expect(root.cached.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('should NOT cache expressions that refer scope variables (2)', () => {
+ const root = transformWithCache(
+ ``,
+ {
+ prefixIdentifiers: true,
+ },
+ )
+
+ expect(root.cached.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('should NOT cache expressions that refer scope variables (v-slot)', () => {
+ const root = transformWithCache(
+ `{{ foo }} `,
+ {
+ prefixIdentifiers: true,
+ },
+ )
+
+ expect(root.cached.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('should NOT cache elements with cached handlers', () => {
+ const root = transformWithCache(
+ ``,
+ {
+ prefixIdentifiers: true,
+ cacheHandlers: true,
+ },
+ )
+
+ expect(root.cached.length).toBe(1)
+ expect(root.hoists.length).toBe(0)
+ expect(
+ generate(root, {
+ mode: 'module',
+ prefixIdentifiers: true,
+ }).code,
+ ).toMatchSnapshot()
+ })
+
+ test('should NOT cache elements with cached handlers + other bindings', () => {
+ const root = transformWithCache(
+ ``,
+ {
+ prefixIdentifiers: true,
+ cacheHandlers: true,
+ },
+ )
+
+ expect(root.cached.length).toBe(1)
+ expect(root.hoists.length).toBe(0)
+ expect(
+ generate(root, {
+ mode: 'module',
+ prefixIdentifiers: true,
+ }).code,
+ ).toMatchSnapshot()
+ })
+
+ test('should NOT cache keyed template v-for with plain element child', () => {
+ const root = transformWithCache(
+ `
`,
+ )
+ expect(root.hoists.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('should NOT cache SVG with directives', () => {
+ const root = transformWithCache(
+ ``,
+ )
+ expect(root.cached.length).toBe(1)
+ expect(root.codegenNode).toMatchObject({
+ children: [
+ {
+ tag: 'svg',
+ // only cache the children, not the svg tag itself
+ codegenNode: {
+ children: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ },
+ },
+ },
+ ],
+ })
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('clone hoisted array children in v-for + HMR mode', () => {
+ const root = transformWithCache(
+ ``,
+ {
+ hmr: true,
+ },
+ )
+ expect(root.cached.length).toBe(1)
+ const forBlockCodegen = (
+ (root.children[0] as ElementNode).children[0] as ForNode
+ ).codegenNode
+ expect(forBlockCodegen).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ tag: FRAGMENT,
+ props: undefined,
+ children: {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: RENDER_LIST,
+ },
+ patchFlag: PatchFlags.UNKEYED_FRAGMENT,
+ })
+ const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
+ expect(innerBlockCodegen.returns).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ children: cachedChildrenArrayMatcher(
+ ['span'],
+ true /* needArraySpread */,
+ ),
+ })
+ expect(generate(root).code).toMatchSnapshot()
+ })
+ })
+})
diff --git a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
deleted file mode 100644
index 0cbd4910a30..00000000000
--- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
+++ /dev/null
@@ -1,620 +0,0 @@
-import {
- baseParse as parse,
- transform,
- NodeTypes,
- generate,
- CompilerOptions,
- VNodeCall,
- IfNode,
- ElementNode,
- ForNode,
- ConstantTypes
-} from '../../src'
-import {
- FRAGMENT,
- RENDER_LIST,
- CREATE_TEXT,
- NORMALIZE_CLASS
-} from '../../src/runtimeHelpers'
-import { transformElement } from '../../src/transforms/transformElement'
-import { transformExpression } from '../../src/transforms/transformExpression'
-import { transformIf } from '../../src/transforms/vIf'
-import { transformFor } from '../../src/transforms/vFor'
-import { transformBind } from '../../src/transforms/vBind'
-import { transformOn } from '../../src/transforms/vOn'
-import { createObjectMatcher, genFlagText } from '../testUtils'
-import { transformText } from '../../src/transforms/transformText'
-import { PatchFlags } from '@vue/shared'
-
-const hoistedChildrenArrayMatcher = (startIndex = 1, length = 1) => ({
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- elements: new Array(length).fill(0).map((_, i) => ({
- type: NodeTypes.ELEMENT,
- codegenNode: {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_${startIndex + i}`
- }
- }))
-})
-
-function transformWithHoist(template: string, options: CompilerOptions = {}) {
- const ast = parse(template)
- transform(ast, {
- hoistStatic: true,
- nodeTransforms: [
- transformIf,
- transformFor,
- ...(options.prefixIdentifiers ? [transformExpression] : []),
- transformElement,
- transformText
- ],
- directiveTransforms: {
- on: transformOn,
- bind: transformBind
- },
- ...options
- })
- expect(ast.codegenNode).toMatchObject({
- type: NodeTypes.VNODE_CALL,
- isBlock: true
- })
- return ast
-}
-
-describe('compiler: hoistStatic transform', () => {
- test('should NOT hoist root node', () => {
- // if the whole tree is static, the root still needs to be a block
- // so that it's patched in optimized mode to skip children
- const root = transformWithHoist(`
`)
- expect(root.hoists.length).toBe(0)
- expect(root.codegenNode).toMatchObject({
- tag: `"div"`
- })
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('hoist simple element', () => {
- const root = transformWithHoist(
- `hello
`
- )
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`,
- props: createObjectMatcher({ class: 'inline' }),
- children: {
- type: NodeTypes.TEXT,
- content: `hello`
- }
- },
- hoistedChildrenArrayMatcher()
- ])
- expect(root.codegenNode).toMatchObject({
- tag: `"div"`,
- props: undefined,
- children: { content: `_hoisted_2` }
- })
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('hoist nested static tree', () => {
- const root = transformWithHoist(``)
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"p"`,
- props: undefined,
- children: [
- { type: NodeTypes.ELEMENT, tag: `span` },
- { type: NodeTypes.ELEMENT, tag: `span` }
- ]
- },
- hoistedChildrenArrayMatcher()
- ])
- expect((root.codegenNode as VNodeCall).children).toMatchObject({
- content: '_hoisted_2'
- })
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('hoist nested static tree with comments', () => {
- const root = transformWithHoist(``)
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: undefined,
- children: [{ type: NodeTypes.COMMENT, content: `comment` }]
- },
- hoistedChildrenArrayMatcher()
- ])
- expect((root.codegenNode as VNodeCall).children).toMatchObject({
- content: `_hoisted_2`
- })
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('hoist siblings with common non-hoistable parent', () => {
- const root = transformWithHoist(``)
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`
- },
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- },
- hoistedChildrenArrayMatcher(1, 2)
- ])
- expect((root.codegenNode as VNodeCall).children).toMatchObject({
- content: '_hoisted_3'
- })
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('should NOT hoist components', () => {
- const root = transformWithHoist(`
`)
- expect(root.hoists.length).toBe(0)
- expect((root.codegenNode as VNodeCall).children).toMatchObject([
- {
- type: NodeTypes.ELEMENT,
- codegenNode: {
- type: NodeTypes.VNODE_CALL,
- tag: `_component_Comp`
- }
- }
- ])
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('should NOT hoist element with dynamic props (but hoist the props list)', () => {
- const root = transformWithHoist(``)
- expect(root.hoists.length).toBe(1)
- expect((root.codegenNode as VNodeCall).children).toMatchObject([
- {
- type: NodeTypes.ELEMENT,
- codegenNode: {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: createObjectMatcher({
- id: `[foo]`
- }),
- children: undefined,
- patchFlag: genFlagText(PatchFlags.PROPS),
- dynamicProps: {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_1`,
- isStatic: false
- }
- }
- }
- ])
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('hoist element with static key', () => {
- const root = transformWithHoist(``)
- expect(root.hoists.length).toBe(2)
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: createObjectMatcher({ key: 'foo' })
- },
- hoistedChildrenArrayMatcher()
- ])
- expect(root.codegenNode).toMatchObject({
- tag: `"div"`,
- props: undefined,
- children: { content: `_hoisted_2` }
- })
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('should NOT hoist element with dynamic key', () => {
- const root = transformWithHoist(``)
- expect(root.hoists.length).toBe(0)
- expect((root.codegenNode as VNodeCall).children).toMatchObject([
- {
- type: NodeTypes.ELEMENT,
- codegenNode: {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: createObjectMatcher({
- key: `[foo]`
- })
- }
- }
- ])
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('should NOT hoist element with dynamic ref', () => {
- const root = transformWithHoist(``)
- expect(root.hoists.length).toBe(0)
- expect((root.codegenNode as VNodeCall).children).toMatchObject([
- {
- type: NodeTypes.ELEMENT,
- codegenNode: {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: createObjectMatcher({
- ref: `[foo]`
- }),
- children: undefined,
- patchFlag: genFlagText(PatchFlags.NEED_PATCH)
- }
- }
- ])
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('hoist static props for elements with directives', () => {
- const root = transformWithHoist(``)
- expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
- expect((root.codegenNode as VNodeCall).children).toMatchObject([
- {
- type: NodeTypes.ELEMENT,
- codegenNode: {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_1`
- },
- children: undefined,
- patchFlag: genFlagText(PatchFlags.NEED_PATCH),
- directives: {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
- }
- }
- ])
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('hoist static props for elements with dynamic text children', () => {
- const root = transformWithHoist(
- ``
- )
- expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
- expect((root.codegenNode as VNodeCall).children).toMatchObject([
- {
- type: NodeTypes.ELEMENT,
- codegenNode: {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: { content: `_hoisted_1` },
- children: { type: NodeTypes.INTERPOLATION },
- patchFlag: genFlagText(PatchFlags.TEXT)
- }
- }
- ])
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('hoist static props for elements with unhoistable children', () => {
- const root = transformWithHoist(``)
- expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
- expect((root.codegenNode as VNodeCall).children).toMatchObject([
- {
- type: NodeTypes.ELEMENT,
- codegenNode: {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: { content: `_hoisted_1` },
- children: [{ type: NodeTypes.ELEMENT, tag: `Comp` }]
- }
- }
- ])
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('should hoist v-if props/children if static', () => {
- const root = transformWithHoist(
- ``
- )
- expect(root.hoists).toMatchObject([
- createObjectMatcher({
- key: `[0]`, // key injected by v-if branch
- id: 'foo'
- }),
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`
- },
- hoistedChildrenArrayMatcher(2)
- ])
- expect(
- ((root.children[0] as ElementNode).children[0] as IfNode).codegenNode
- ).toMatchObject({
- type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
- consequent: {
- // blocks should NOT be hoisted
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: { content: `_hoisted_1` },
- children: { content: `_hoisted_3` }
- }
- })
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('should hoist v-for children if static', () => {
- const root = transformWithHoist(
- ``
- )
- expect(root.hoists).toMatchObject([
- createObjectMatcher({
- id: 'foo'
- }),
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`
- },
- hoistedChildrenArrayMatcher(2)
- ])
- const forBlockCodegen = (
- (root.children[0] as ElementNode).children[0] as ForNode
- ).codegenNode
- expect(forBlockCodegen).toMatchObject({
- type: NodeTypes.VNODE_CALL,
- tag: FRAGMENT,
- props: undefined,
- children: {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: RENDER_LIST
- },
- patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT)
- })
- const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
- expect(innerBlockCodegen.returns).toMatchObject({
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: { content: `_hoisted_1` },
- children: { content: `_hoisted_3` }
- })
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('hoist static text node between elements', () => {
- const root = transformWithHoist(``)
- expect(root.hoists).toMatchObject([
- {
- callee: CREATE_TEXT,
- arguments: [
- {
- type: NodeTypes.TEXT,
- content: `static`
- }
- ]
- },
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- elements: [
- {
- type: NodeTypes.TEXT_CALL
- },
- {
- type: NodeTypes.ELEMENT
- }
- ]
- }
- ])
- })
-
- describe('prefixIdentifiers', () => {
- test('hoist nested static tree with static interpolation', () => {
- const root = transformWithHoist(
- `foo {{ 1 }} {{ true }}
`,
- {
- prefixIdentifiers: true
- }
- )
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`,
- props: undefined,
- children: {
- type: NodeTypes.COMPOUND_EXPRESSION
- }
- },
- hoistedChildrenArrayMatcher()
- ])
- expect(root.codegenNode).toMatchObject({
- tag: `"div"`,
- props: undefined,
- children: {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_2`
- }
- })
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('hoist nested static tree with static prop value', () => {
- const root = transformWithHoist(
- `{{ 1 }}
`,
- {
- prefixIdentifiers: true
- }
- )
-
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`,
- props: createObjectMatcher({ foo: `[0]` }),
- children: {
- type: NodeTypes.INTERPOLATION,
- content: {
- content: `1`,
- isStatic: false,
- constType: ConstantTypes.CAN_STRINGIFY
- }
- }
- },
- hoistedChildrenArrayMatcher()
- ])
- expect(root.codegenNode).toMatchObject({
- tag: `"div"`,
- props: undefined,
- children: {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_2`
- }
- })
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('hoist class with static object value', () => {
- const root = transformWithHoist(
- `{{ bar }}
`,
- {
- prefixIdentifiers: true
- }
- )
-
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.JS_OBJECT_EXPRESSION,
- properties: [
- {
- key: {
- content: `class`,
- isStatic: true,
- constType: ConstantTypes.CAN_STRINGIFY
- },
- value: {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: NORMALIZE_CLASS,
- arguments: [
- {
- content: `{ foo: true }`,
- isStatic: false,
- constType: ConstantTypes.CAN_STRINGIFY
- }
- ]
- }
- }
- ]
- }
- ])
- expect(root.codegenNode).toMatchObject({
- tag: `"div"`,
- props: undefined,
- children: [
- {
- type: NodeTypes.ELEMENT,
- codegenNode: {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`,
- props: {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_1`
- },
- children: {
- type: NodeTypes.INTERPOLATION,
- content: {
- content: `_ctx.bar`,
- isStatic: false,
- constType: ConstantTypes.NOT_CONSTANT
- }
- },
- patchFlag: `1 /* TEXT */`
- }
- }
- ]
- })
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('should NOT hoist expressions that refer scope variables', () => {
- const root = transformWithHoist(
- ``,
- {
- prefixIdentifiers: true
- }
- )
-
- expect(root.hoists.length).toBe(0)
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('should NOT hoist expressions that refer scope variables (2)', () => {
- const root = transformWithHoist(
- ``,
- {
- prefixIdentifiers: true
- }
- )
-
- expect(root.hoists.length).toBe(0)
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('should NOT hoist expressions that refer scope variables (v-slot)', () => {
- const root = transformWithHoist(
- `{{ foo }} `,
- {
- prefixIdentifiers: true
- }
- )
-
- expect(root.hoists.length).toBe(0)
- expect(generate(root).code).toMatchSnapshot()
- })
-
- test('should NOT hoist elements with cached handlers', () => {
- const root = transformWithHoist(
- ``,
- {
- prefixIdentifiers: true,
- cacheHandlers: true
- }
- )
-
- expect(root.cached).toBe(1)
- expect(root.hoists.length).toBe(0)
- expect(
- generate(root, {
- mode: 'module',
- prefixIdentifiers: true
- }).code
- ).toMatchSnapshot()
- })
-
- test('should NOT hoist elements with cached handlers + other bindings', () => {
- const root = transformWithHoist(
- ``,
- {
- prefixIdentifiers: true,
- cacheHandlers: true
- }
- )
-
- expect(root.cached).toBe(1)
- expect(root.hoists.length).toBe(0)
- expect(
- generate(root, {
- mode: 'module',
- prefixIdentifiers: true
- }).code
- ).toMatchSnapshot()
- })
-
- test('should NOT hoist keyed template v-for with plain element child', () => {
- const root = transformWithHoist(
- `
`
- )
- expect(root.hoists.length).toBe(0)
- expect(generate(root).code).toMatchSnapshot()
- })
- })
-})
diff --git a/packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts b/packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts
index 96a60bd1d42..fa711051c18 100644
--- a/packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts
@@ -1,9 +1,9 @@
import {
+ type ElementNode,
+ type VNodeCall,
+ noopDirectiveTransform,
baseParse as parse,
transform,
- ElementNode,
- noopDirectiveTransform,
- VNodeCall
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
@@ -13,8 +13,8 @@ describe('compiler: noop directive transform', () => {
transform(ast, {
nodeTransforms: [transformElement],
directiveTransforms: {
- noop: noopDirectiveTransform
- }
+ noop: noopDirectiveTransform,
+ },
})
const node = ast.children[0] as ElementNode
// As v-noop adds no properties the codegen should be identical to
diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
index e7a95622d1a..bf3510a052d 100644
--- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
@@ -1,48 +1,49 @@
import {
- CompilerOptions,
+ BindingTypes,
+ type CompilerOptions,
+ ErrorCodes,
+ type NodeTransform,
+ baseCompile,
baseParse as parse,
transform,
- ErrorCodes,
- BindingTypes,
- NodeTransform,
transformExpression,
- baseCompile
} from '../../src'
import {
- RESOLVE_COMPONENT,
+ BASE_TRANSITION,
CREATE_VNODE,
+ GUARD_REACTIVE_PROPS,
+ KEEP_ALIVE,
MERGE_PROPS,
+ NORMALIZE_CLASS,
+ NORMALIZE_PROPS,
+ NORMALIZE_STYLE,
+ RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
- TO_HANDLERS,
- helperNameMap,
- TELEPORT,
RESOLVE_DYNAMIC_COMPONENT,
SUSPENSE,
- KEEP_ALIVE,
- BASE_TRANSITION,
- NORMALIZE_CLASS,
- NORMALIZE_STYLE,
- NORMALIZE_PROPS,
- GUARD_REACTIVE_PROPS
+ TELEPORT,
+ TO_HANDLERS,
+ helperNameMap,
} from '../../src/runtimeHelpers'
import {
+ type DirectiveNode,
NodeTypes,
+ type RootNode,
+ type VNodeCall,
createObjectProperty,
- DirectiveNode,
- RootNode,
- VNodeCall
} from '../../src/ast'
import { transformElement } from '../../src/transforms/transformElement'
import { transformStyle } from '../../../compiler-dom/src/transforms/transformStyle'
import { transformOn } from '../../src/transforms/vOn'
import { transformBind } from '../../src/transforms/vBind'
import { PatchFlags } from '@vue/shared'
-import { createObjectMatcher, genFlagText } from '../testUtils'
+import { createObjectMatcher } from '../testUtils'
import { transformText } from '../../src/transforms/transformText'
+import { parseWithForTransform } from './vFor.spec'
function parseWithElementTransform(
template: string,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
): {
root: RootNode
node: VNodeCall
@@ -52,14 +53,14 @@ function parseWithElementTransform(
const ast = parse(`${template}
`, options)
transform(ast, {
nodeTransforms: [transformElement, transformText],
- ...options
+ ...options,
})
const codegenNode = (ast as any).children[0].children[0]
.codegenNode as VNodeCall
expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL)
return {
root: ast,
- node: codegenNode
+ node: codegenNode,
}
}
@@ -68,8 +69,8 @@ function parseWithBind(template: string, options?: CompilerOptions) {
...options,
directiveTransforms: {
...options?.directiveTransforms,
- bind: transformBind
- }
+ bind: transformBind,
+ },
})
}
@@ -80,9 +81,9 @@ describe('compiler: element transform', () => {
expect(root.components).toContain(`Foo`)
})
- test('resolve implcitly self-referencing component', () => {
+ test('resolve implicitly self-referencing component', () => {
const { root } = parseWithElementTransform(` `, {
- filename: `/foo/bar/Example.vue?vue&type=template`
+ filename: `/foo/bar/Example.vue?vue&type=template`,
})
expect(root.helpers).toContain(RESOLVE_COMPONENT)
expect(root.components).toContain(`Example__self`)
@@ -91,8 +92,8 @@ describe('compiler: element transform', () => {
test('resolve component from setup bindings', () => {
const { root, node } = parseWithElementTransform(` `, {
bindingMetadata: {
- Example: BindingTypes.SETUP_MAYBE_REF
- }
+ Example: BindingTypes.SETUP_MAYBE_REF,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`$setup["Example"]`)
@@ -102,8 +103,8 @@ describe('compiler: element transform', () => {
const { root, node } = parseWithElementTransform(` `, {
inline: true,
bindingMetadata: {
- Example: BindingTypes.SETUP_MAYBE_REF
- }
+ Example: BindingTypes.SETUP_MAYBE_REF,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`_unref(Example)`)
@@ -113,8 +114,8 @@ describe('compiler: element transform', () => {
const { root, node } = parseWithElementTransform(` `, {
inline: true,
bindingMetadata: {
- Example: BindingTypes.SETUP_CONST
- }
+ Example: BindingTypes.SETUP_CONST,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`Example`)
@@ -123,8 +124,8 @@ describe('compiler: element transform', () => {
test('resolve namespaced component from setup bindings', () => {
const { root, node } = parseWithElementTransform(` `, {
bindingMetadata: {
- Foo: BindingTypes.SETUP_MAYBE_REF
- }
+ Foo: BindingTypes.SETUP_MAYBE_REF,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`$setup["Foo"].Example`)
@@ -134,8 +135,8 @@ describe('compiler: element transform', () => {
const { root, node } = parseWithElementTransform(` `, {
inline: true,
bindingMetadata: {
- Foo: BindingTypes.SETUP_MAYBE_REF
- }
+ Foo: BindingTypes.SETUP_MAYBE_REF,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`_unref(Foo).Example`)
@@ -145,20 +146,42 @@ describe('compiler: element transform', () => {
const { root, node } = parseWithElementTransform(` `, {
inline: true,
bindingMetadata: {
- Foo: BindingTypes.SETUP_CONST
- }
+ Foo: BindingTypes.SETUP_CONST,
+ },
})
expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
expect(node.tag).toBe(`Foo.Example`)
})
+ test('resolve namespaced component from props bindings (inline)', () => {
+ const { root, node } = parseWithElementTransform(` `, {
+ inline: true,
+ bindingMetadata: {
+ Foo: BindingTypes.PROPS,
+ },
+ })
+ expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
+ expect(node.tag).toBe(`_unref(__props["Foo"]).Example`)
+ })
+
+ test('resolve namespaced component from props bindings (non-inline)', () => {
+ const { root, node } = parseWithElementTransform(` `, {
+ inline: false,
+ bindingMetadata: {
+ Foo: BindingTypes.PROPS,
+ },
+ })
+ expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
+ expect(node.tag).toBe('_unref($props["Foo"]).Example')
+ })
+
test('do not resolve component from non-script-setup bindings', () => {
const bindingMetadata = {
- Example: BindingTypes.SETUP_MAYBE_REF
+ Example: BindingTypes.SETUP_MAYBE_REF,
}
Object.defineProperty(bindingMetadata, '__isScriptSetup', { value: false })
const { root } = parseWithElementTransform(` `, {
- bindingMetadata
+ bindingMetadata,
})
expect(root.helpers).toContain(RESOLVE_COMPONENT)
expect(root.components).toContain(`Example`)
@@ -170,9 +193,9 @@ describe('compiler: element transform', () => {
tag: `"div"`,
props: createObjectMatcher({
id: 'foo',
- class: 'bar'
+ class: 'bar',
}),
- children: undefined
+ children: undefined,
})
})
@@ -182,7 +205,7 @@ describe('compiler: element transform', () => {
expect(node).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
children: [
{
@@ -190,10 +213,10 @@ describe('compiler: element transform', () => {
tag: 'span',
codegenNode: {
type: NodeTypes.VNODE_CALL,
- tag: `"span"`
- }
- }
- ]
+ tag: `"span"`,
+ },
+ },
+ ],
})
})
@@ -209,10 +232,10 @@ describe('compiler: element transform', () => {
tag: 'span',
codegenNode: {
type: NodeTypes.VNODE_CALL,
- tag: `"span"`
- }
- }
- ]
+ tag: `"span"`,
+ },
+ },
+ ],
})
})
@@ -234,17 +257,17 @@ describe('compiler: element transform', () => {
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
- }
- ]
- }
- ]
+ content: `obj`,
+ },
+ ],
+ },
+ ],
})
})
test('v-bind="obj" after static prop', () => {
const { root, node } = parseWithElementTransform(
- `
`
+ `
`,
)
expect(root.helpers).toContain(MERGE_PROPS)
@@ -253,19 +276,19 @@ describe('compiler: element transform', () => {
callee: MERGE_PROPS,
arguments: [
createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
- }
- ]
+ content: `obj`,
+ },
+ ],
})
})
test('v-bind="obj" before static prop', () => {
const { root, node } = parseWithElementTransform(
- `
`
+ `
`,
)
expect(root.helpers).toContain(MERGE_PROPS)
@@ -275,18 +298,18 @@ describe('compiler: element transform', () => {
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
+ content: `obj`,
},
createObjectMatcher({
- id: 'foo'
- })
- ]
+ id: 'foo',
+ }),
+ ],
})
})
test('v-bind="obj" between static props', () => {
const { root, node } = parseWithElementTransform(
- `
`
+ `
`,
)
expect(root.helpers).toContain(MERGE_PROPS)
@@ -295,22 +318,22 @@ describe('compiler: element transform', () => {
callee: MERGE_PROPS,
arguments: [
createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
+ content: `obj`,
},
createObjectMatcher({
- class: 'bar'
- })
- ]
+ class: 'bar',
+ }),
+ ],
})
})
test('v-on="obj"', () => {
const { root, node } = parseWithElementTransform(
- `
`
+ `
`,
)
expect(root.helpers).toContain(MERGE_PROPS)
@@ -319,7 +342,7 @@ describe('compiler: element transform', () => {
callee: MERGE_PROPS,
arguments: [
createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
{
type: NodeTypes.JS_CALL_EXPRESSION,
@@ -327,20 +350,51 @@ describe('compiler: element transform', () => {
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
- }
- ]
+ content: `obj`,
+ },
+ `true`,
+ ],
},
createObjectMatcher({
- class: 'bar'
- })
- ]
+ class: 'bar',
+ }),
+ ],
+ })
+ })
+
+ test('v-on="obj" on component', () => {
+ const { root, node } = parseWithElementTransform(
+ ` `,
+ )
+ expect(root.helpers).toContain(MERGE_PROPS)
+
+ expect(node.props).toMatchObject({
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: MERGE_PROPS,
+ arguments: [
+ createObjectMatcher({
+ id: 'foo',
+ }),
+ {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: TO_HANDLERS,
+ arguments: [
+ {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `obj`,
+ },
+ ],
+ },
+ createObjectMatcher({
+ class: 'bar',
+ }),
+ ],
})
})
test('v-on="obj" + v-bind="obj"', () => {
const { root, node } = parseWithElementTransform(
- `
`
+ `
`,
)
expect(root.helpers).toContain(MERGE_PROPS)
@@ -349,7 +403,7 @@ describe('compiler: element transform', () => {
callee: MERGE_PROPS,
arguments: [
createObjectMatcher({
- id: 'foo'
+ id: 'foo',
}),
{
type: NodeTypes.JS_CALL_EXPRESSION,
@@ -357,15 +411,16 @@ describe('compiler: element transform', () => {
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `handlers`
- }
- ]
+ content: `handlers`,
+ },
+ `true`,
+ ],
},
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `obj`
- }
- ]
+ content: `obj`,
+ },
+ ],
})
})
@@ -375,15 +430,15 @@ describe('compiler: element transform', () => {
expect(node).toMatchObject({
tag: `"template"`,
props: createObjectMatcher({
- id: 'foo'
- })
+ id: 'foo',
+ }),
})
})
test('should handle with normal children', () => {
function assert(tag: string) {
const { root, node } = parseWithElementTransform(
- `<${tag} target="#foo"> ${tag}>`
+ `<${tag} target="#foo"> ${tag}>`,
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(TELEPORT)
@@ -391,7 +446,7 @@ describe('compiler: element transform', () => {
expect(node).toMatchObject({
tag: TELEPORT,
props: createObjectMatcher({
- target: '#foo'
+ target: '#foo',
}),
children: [
{
@@ -399,10 +454,10 @@ describe('compiler: element transform', () => {
tag: 'span',
codegenNode: {
type: NodeTypes.VNODE_CALL,
- tag: `"span"`
- }
- }
- ]
+ tag: `"span"`,
+ },
+ },
+ ],
})
}
@@ -413,7 +468,7 @@ describe('compiler: element transform', () => {
test('should handle ', () => {
function assert(tag: string, content: string, hasFallback?: boolean) {
const { root, node } = parseWithElementTransform(
- `<${tag}>${content}${tag}>`
+ `<${tag}>${content}${tag}>`,
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(SUSPENSE)
@@ -424,19 +479,19 @@ describe('compiler: element transform', () => {
children: hasFallback
? createObjectMatcher({
default: {
- type: NodeTypes.JS_FUNCTION_EXPRESSION
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
},
fallback: {
- type: NodeTypes.JS_FUNCTION_EXPRESSION
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
},
- _: `[1 /* STABLE */]`
+ _: `[1 /* STABLE */]`,
})
: createObjectMatcher({
default: {
- type: NodeTypes.JS_FUNCTION_EXPRESSION
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
},
- _: `[1 /* STABLE */]`
- })
+ _: `[1 /* STABLE */]`,
+ }),
})
}
@@ -445,7 +500,7 @@ describe('compiler: element transform', () => {
assert(
`suspense`,
`foo fallback `,
- true
+ true,
)
})
@@ -453,7 +508,7 @@ describe('compiler: element transform', () => {
function assert(tag: string) {
const root = parse(`<${tag}> ${tag}>
`)
transform(root, {
- nodeTransforms: [transformElement, transformText]
+ nodeTransforms: [transformElement, transformText],
})
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(KEEP_ALIVE)
@@ -466,7 +521,7 @@ describe('compiler: element transform', () => {
// keep-alive should not compile content to slots
children: [{ type: NodeTypes.ELEMENT, tag: 'span' }],
// should get a dynamic slots flag to force updates
- patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS)
+ patchFlag: PatchFlags.DYNAMIC_SLOTS,
})
}
@@ -477,7 +532,7 @@ describe('compiler: element transform', () => {
test('should handle ', () => {
function assert(tag: string) {
const { root, node } = parseWithElementTransform(
- `<${tag}> ${tag}>`
+ `<${tag}> ${tag}>`,
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(BASE_TRANSITION)
@@ -487,10 +542,10 @@ describe('compiler: element transform', () => {
props: undefined,
children: createObjectMatcher({
default: {
- type: NodeTypes.JS_FUNCTION_EXPRESSION
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
},
- _: `[1 /* STABLE */]`
- })
+ _: `[1 /* STABLE */]`,
+ }),
})
}
@@ -499,12 +554,12 @@ describe('compiler: element transform', () => {
})
test('error on v-bind with no argument', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithElementTransform(`
`, { onError })
expect(onError.mock.calls[0]).toMatchObject([
{
- code: ErrorCodes.X_V_BIND_NO_EXPRESSION
- }
+ code: ErrorCodes.X_V_BIND_NO_EXPRESSION,
+ },
])
})
@@ -515,10 +570,10 @@ describe('compiler: element transform', () => {
foo(dir) {
_dir = dir
return {
- props: [createObjectProperty(dir.arg!, dir.exp!)]
+ props: [createObjectProperty(dir.arg!, dir.exp!)],
}
- }
- }
+ },
+ },
})
expect(node.props).toMatchObject({
@@ -527,13 +582,13 @@ describe('compiler: element transform', () => {
{
type: NodeTypes.JS_PROPERTY,
key: _dir!.arg,
- value: _dir!.exp
- }
- ]
+ value: _dir!.exp,
+ },
+ ],
})
// should factor in props returned by custom directive transforms
// in patchFlag analysis
- expect(node.patchFlag).toMatch(PatchFlags.PROPS + '')
+ expect(node.patchFlag).toBe(PatchFlags.PROPS)
expect(node.dynamicProps).toMatch(`"bar"`)
})
@@ -545,11 +600,11 @@ describe('compiler: element transform', () => {
foo() {
return {
props: [],
- needRuntime: true
+ needRuntime: true,
}
- }
- }
- }
+ },
+ },
+ },
)
expect(root.helpers).toContain(RESOLVE_DIRECTIVE)
expect(root.directives).toContain(`foo`)
@@ -557,7 +612,7 @@ describe('compiler: element transform', () => {
tag: `"div"`,
props: undefined,
children: undefined,
- patchFlag: genFlagText(PatchFlags.NEED_PATCH), // should generate appropriate flag
+ patchFlag: PatchFlags.NEED_PATCH, // should generate appropriate flag
directives: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
@@ -569,18 +624,18 @@ describe('compiler: element transform', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `hello`,
- isStatic: false
+ isStatic: false,
},
// arg
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `bar`,
- isStatic: true
- }
- ]
- }
- ]
- }
+ isStatic: true,
+ },
+ ],
+ },
+ ],
+ },
})
})
@@ -592,24 +647,24 @@ describe('compiler: element transform', () => {
foo() {
return {
props: [],
- needRuntime: CREATE_VNODE
+ needRuntime: CREATE_VNODE,
}
- }
- }
- }
+ },
+ },
+ },
)
expect(root.helpers).toContain(CREATE_VNODE)
expect(root.helpers).not.toContain(RESOLVE_DIRECTIVE)
expect(root.directives.length).toBe(0)
expect(node.directives!.elements[0].elements[0]).toBe(
- `_${helperNameMap[CREATE_VNODE]}`
+ `_${helperNameMap[CREATE_VNODE]}`,
)
})
test('runtime directives', () => {
const { root, node } = parseWithElementTransform(
- `
`
+ `
`,
)
expect(root.helpers).toContain(RESOLVE_DIRECTIVE)
expect(root.directives).toContain(`foo`)
@@ -622,7 +677,7 @@ describe('compiler: element transform', () => {
elements: [
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
- elements: [`_directive_foo`]
+ elements: [`_directive_foo`],
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -631,9 +686,9 @@ describe('compiler: element transform', () => {
// exp
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `x`
- }
- ]
+ content: `x`,
+ },
+ ],
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -643,13 +698,13 @@ describe('compiler: element transform', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `y`,
- isStatic: false
+ isStatic: false,
},
// arg
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `arg`,
- isStatic: false
+ isStatic: false,
},
// modifiers
{
@@ -660,33 +715,33 @@ describe('compiler: element transform', () => {
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `mod`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `true`,
- isStatic: false
- }
+ isStatic: false,
+ },
},
{
type: NodeTypes.JS_PROPERTY,
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `mad`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `true`,
- isStatic: false
- }
- }
- ]
- }
- ]
- }
- ]
- }
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
})
})
@@ -695,9 +750,9 @@ describe('compiler: element transform', () => {
`
`,
{
directiveTransforms: {
- on: transformOn
- }
- }
+ on: transformOn,
+ },
+ },
)
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
@@ -707,7 +762,7 @@ describe('compiler: element transform', () => {
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `onClick`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -715,17 +770,17 @@ describe('compiler: element transform', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a`,
- isStatic: false
+ isStatic: false,
},
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `b`,
- isStatic: false
- }
- ]
- }
- }
- ]
+ isStatic: false,
+ },
+ ],
+ },
+ },
+ ],
})
})
@@ -735,9 +790,9 @@ describe('compiler: element transform', () => {
{
nodeTransforms: [transformStyle, transformElement],
directiveTransforms: {
- bind: transformBind
- }
- }
+ bind: transformBind,
+ },
+ },
)
expect(root.helpers).toContain(NORMALIZE_STYLE)
expect(node.props).toMatchObject({
@@ -748,7 +803,7 @@ describe('compiler: element transform', () => {
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
@@ -760,19 +815,19 @@ describe('compiler: element transform', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{"color":"green"}`,
- isStatic: false
+ isStatic: false,
},
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{ color: 'red' }`,
- isStatic: false
- }
- ]
- }
- ]
- }
- }
- ]
+ isStatic: false,
+ },
+ ],
+ },
+ ],
+ },
+ },
+ ],
})
})
@@ -782,10 +837,10 @@ describe('compiler: element transform', () => {
{
nodeTransforms: [transformExpression, transformStyle, transformElement],
directiveTransforms: {
- bind: transformBind
+ bind: transformBind,
},
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
expect(root.helpers).toContain(NORMALIZE_STYLE)
expect(node.props).toMatchObject({
@@ -796,14 +851,45 @@ describe('compiler: element transform', () => {
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: NORMALIZE_STYLE
- }
- }
- ]
+ callee: NORMALIZE_STYLE,
+ },
+ },
+ ],
+ })
+ })
+
+ test(':style with array literal', () => {
+ const { node, root } = parseWithElementTransform(
+ `
`,
+ {
+ nodeTransforms: [transformExpression, transformStyle, transformElement],
+ directiveTransforms: {
+ bind: transformBind,
+ },
+ prefixIdentifiers: true,
+ },
+ )
+ expect(root.helpers).toContain(NORMALIZE_STYLE)
+ expect(node.props).toMatchObject({
+ type: NodeTypes.JS_OBJECT_EXPRESSION,
+ properties: [
+ {
+ type: NodeTypes.JS_PROPERTY,
+ key: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `style`,
+ isStatic: true,
+ },
+ value: {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: NORMALIZE_STYLE,
+ },
+ },
+ ],
})
})
@@ -812,9 +898,9 @@ describe('compiler: element transform', () => {
`
`,
{
directiveTransforms: {
- bind: transformBind
- }
- }
+ bind: transformBind,
+ },
+ },
)
expect(root.helpers).toContain(NORMALIZE_CLASS)
expect(node.props).toMatchObject({
@@ -825,7 +911,7 @@ describe('compiler: element transform', () => {
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `class`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
@@ -837,19 +923,19 @@ describe('compiler: element transform', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`,
- isStatic: true
+ isStatic: true,
},
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{ bar: isBar }`,
- isStatic: false
- }
- ]
- }
- ]
- }
- }
- ]
+ isStatic: false,
+ },
+ ],
+ },
+ ],
+ },
+ },
+ ],
})
})
@@ -859,35 +945,35 @@ describe('compiler: element transform', () => {
expect(node.patchFlag).toBeUndefined()
const { node: node2 } = parseWithBind(`{{ foo }}
`)
- expect(node2.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
+ expect(node2.patchFlag).toBe(PatchFlags.TEXT)
// multiple nodes, merged with optimize text
const { node: node3 } = parseWithBind(`foo {{ bar }} baz
`)
- expect(node3.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
+ expect(node3.patchFlag).toBe(PatchFlags.TEXT)
})
test('CLASS', () => {
const { node } = parseWithBind(`
`)
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.CLASS))
+ expect(node.patchFlag).toBe(PatchFlags.CLASS)
})
test('STYLE', () => {
const { node } = parseWithBind(`
`)
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.STYLE))
+ expect(node.patchFlag).toBe(PatchFlags.STYLE)
})
test('PROPS', () => {
const { node } = parseWithBind(`
`)
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
+ expect(node.patchFlag).toBe(PatchFlags.PROPS)
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
})
test('CLASS + STYLE + PROPS', () => {
const { node } = parseWithBind(
- `
`
+ `
`,
)
expect(node.patchFlag).toBe(
- genFlagText([PatchFlags.CLASS, PatchFlags.STYLE, PatchFlags.PROPS])
+ PatchFlags.CLASS | PatchFlags.STYLE | PatchFlags.PROPS,
)
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
})
@@ -895,59 +981,59 @@ describe('compiler: element transform', () => {
// should treat `class` and `style` as PROPS
test('PROPS on component', () => {
const { node } = parseWithBind(
- ` `
+ ` `,
)
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
+ expect(node.patchFlag).toBe(PatchFlags.PROPS)
expect(node.dynamicProps).toBe(`["id", "class", "style"]`)
})
test('FULL_PROPS (v-bind)', () => {
const { node } = parseWithBind(`
`)
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
+ expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS)
})
test('FULL_PROPS (dynamic key)', () => {
const { node } = parseWithBind(`
`)
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
+ expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS)
})
test('FULL_PROPS (w/ others)', () => {
const { node } = parseWithBind(
- `
`
+ `
`,
)
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
+ expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS)
})
test('NEED_PATCH (static ref)', () => {
const { node } = parseWithBind(`
`)
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
+ expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
})
test('NEED_PATCH (dynamic ref)', () => {
const { node } = parseWithBind(`
`)
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
+ expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
})
test('NEED_PATCH (custom directives)', () => {
const { node } = parseWithBind(`
`)
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
+ expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
})
test('NEED_PATCH (vnode hooks)', () => {
- const root = baseCompile(`
`, {
+ const root = baseCompile(`
`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
}).ast
const node = (root as any).children[0].codegenNode
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
+ expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
})
test('script setup inline mode template ref (binding exists)', () => {
const { node } = parseWithElementTransform(` `, {
inline: true,
bindingMetadata: {
- input: BindingTypes.SETUP_REF
- }
+ input: BindingTypes.SETUP_REF,
+ },
})
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
@@ -956,31 +1042,31 @@ describe('compiler: element transform', () => {
type: NodeTypes.JS_PROPERTY,
key: {
content: 'ref_key',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'input',
- isStatic: true
- }
+ isStatic: true,
+ },
},
{
type: NodeTypes.JS_PROPERTY,
key: {
content: 'ref',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'input',
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
})
test('script setup inline mode template ref (binding does not exist)', () => {
const { node } = parseWithElementTransform(` `, {
- inline: true
+ inline: true,
})
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
@@ -989,38 +1075,97 @@ describe('compiler: element transform', () => {
type: NodeTypes.JS_PROPERTY,
key: {
content: 'ref',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'input',
- isStatic: true
- }
- }
- ]
+ isStatic: true,
+ },
+ },
+ ],
})
})
- test('HYDRATE_EVENTS', () => {
+ test('script setup inline mode template ref (binding does not exist but props with the same name exist)', () => {
+ const { node } = parseWithElementTransform(` `, {
+ inline: true,
+ bindingMetadata: {
+ msg: BindingTypes.PROPS,
+ ref: BindingTypes.SETUP_CONST,
+ },
+ })
+ expect(node.props).toMatchObject({
+ type: NodeTypes.JS_OBJECT_EXPRESSION,
+ properties: [
+ {
+ type: NodeTypes.JS_PROPERTY,
+ key: {
+ content: 'ref',
+ isStatic: true,
+ },
+ value: {
+ content: 'msg',
+ isStatic: true,
+ },
+ },
+ ],
+ })
+ })
+
+ test('NEED_HYDRATION for v-on', () => {
// ignore click events (has dedicated fast path)
const { node } = parseWithElementTransform(`
`, {
directiveTransforms: {
- on: transformOn
- }
+ on: transformOn,
+ },
})
// should only have props flag
- expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
+ expect(node.patchFlag).toBe(PatchFlags.PROPS)
const { node: node2 } = parseWithElementTransform(
`
`,
{
directiveTransforms: {
- on: transformOn
- }
- }
+ on: transformOn,
+ },
+ },
)
- expect(node2.patchFlag).toBe(
- genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
+ expect(node2.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION)
+ })
+
+ test('NEED_HYDRATION for v-bind.prop', () => {
+ const { node } = parseWithBind(`
`)
+ expect(node.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION)
+
+ const { node: node2 } = parseWithBind(`
`)
+ expect(node2.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION)
+ })
+
+ // #5870
+ test('NEED_HYDRATION on dynamic component', () => {
+ const { node } = parseWithElementTransform(
+ ` `,
+ {
+ directiveTransforms: {
+ on: transformOn,
+ },
+ },
)
+ expect(node.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION)
+ })
+
+ test('should not have PROPS patchflag for constant v-on handlers', () => {
+ const { node } = parseWithElementTransform(`
`, {
+ prefixIdentifiers: true,
+ bindingMetadata: {
+ foo: BindingTypes.SETUP_CONST,
+ },
+ directiveTransforms: {
+ on: transformOn,
+ },
+ })
+ // should only have hydration flag
+ expect(node.patchFlag).toBe(PatchFlags.NEED_HYDRATION)
})
})
@@ -1036,10 +1181,10 @@ describe('compiler: element transform', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
- isStatic: true
- }
- ]
- }
+ isStatic: true,
+ },
+ ],
+ },
})
})
@@ -1054,10 +1199,10 @@ describe('compiler: element transform', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
- isStatic: true
- }
- ]
- }
+ isStatic: true,
+ },
+ ],
+ },
})
})
@@ -1072,41 +1217,49 @@ describe('compiler: element transform', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
- isStatic: false
- }
- ]
- }
+ isStatic: false,
+ },
+ ],
+ },
})
})
- test('v-is', () => {
- const { node, root } = parseWithBind(`
`)
+ test('dynamic binding shorthand', () => {
+ const { node, root } = parseWithBind(` `)
expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
expect(node).toMatchObject({
+ isBlock: true,
tag: {
callee: RESOLVE_DYNAMIC_COMPONENT,
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `'foo'`,
- isStatic: false
- }
- ]
+ content: 'is',
+ isStatic: false,
+ },
+ ],
},
- // should skip v-is runtime check
- directives: undefined
+ })
+ })
+
+ test('is casting', () => {
+ const { node, root } = parseWithBind(`
`)
+ expect(root.helpers).toContain(RESOLVE_COMPONENT)
+ expect(node).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ tag: '_component_foo',
})
})
// #3934
test('normal component with is prop', () => {
const { node, root } = parseWithBind(` `, {
- isNativeTag: () => false
+ isNativeTag: () => false,
})
expect(root.helpers).toContain(RESOLVE_COMPONENT)
expect(root.helpers).not.toContain(RESOLVE_DYNAMIC_COMPONENT)
expect(node).toMatchObject({
- tag: '_component_custom_input'
+ tag: '_component_custom_input',
})
})
})
@@ -1114,12 +1267,24 @@ describe('compiler: element transform', () => {
test(' should be forced into blocks', () => {
const ast = parse(`
`)
transform(ast, {
- nodeTransforms: [transformElement]
+ nodeTransforms: [transformElement],
})
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: `"svg"`,
- isBlock: true
+ isBlock: true,
+ })
+ })
+
+ test(' should be forced into blocks', () => {
+ const ast = parse(`
`)
+ transform(ast, {
+ nodeTransforms: [transformElement],
+ })
+ expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ tag: `"math"`,
+ isBlock: true,
})
})
@@ -1131,7 +1296,7 @@ describe('compiler: element transform', () => {
test('force block for inline before-update handlers w/ children', () => {
expect(
parseWithElementTransform(`hello
`).node
- .isBlock
+ .isBlock,
).toBe(true)
})
@@ -1139,12 +1304,12 @@ describe('compiler: element transform', () => {
test('element with dynamic keys should be forced into blocks', () => {
const ast = parse(``)
transform(ast, {
- nodeTransforms: [transformElement]
+ nodeTransforms: [transformElement],
})
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
- isBlock: true
+ isBlock: true,
})
})
@@ -1159,23 +1324,61 @@ describe('compiler: element transform', () => {
prop.type === NodeTypes.ATTRIBUTE &&
prop.name === 'id' &&
prop.value &&
- prop.value.content === 'foo'
+ prop.value.content === 'foo',
)
) {
context.replaceNode({
...node,
- tag: 'span'
+ tag: 'span',
})
}
}
const ast = parse(``)
transform(ast, {
- nodeTransforms: [transformElement, transformText, customNodeTransform]
+ nodeTransforms: [transformElement, transformText, customNodeTransform],
})
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: '"span"',
- isBlock: false
+ isBlock: false,
+ })
+ })
+
+ test('ref_for marker on static ref', () => {
+ const { node } = parseWithForTransform(`
`)
+ expect((node.children[0] as any).codegenNode.props).toMatchObject(
+ createObjectMatcher({
+ ref_for: `[true]`,
+ ref: 'x',
+ }),
+ )
+ })
+
+ test('ref_for marker on dynamic ref', () => {
+ const { node } = parseWithForTransform(`
`)
+ expect((node.children[0] as any).codegenNode.props).toMatchObject(
+ createObjectMatcher({
+ ref_for: `[true]`,
+ ref: '[x]',
+ }),
+ )
+ })
+
+ test('ref_for marker on v-bind', () => {
+ const { node } = parseWithForTransform(`
`)
+ expect((node.children[0] as any).codegenNode.props).toMatchObject({
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: MERGE_PROPS,
+ arguments: [
+ createObjectMatcher({
+ ref_for: `[true]`,
+ }),
+ {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: 'x',
+ isStatic: false,
+ },
+ ],
})
})
})
diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts
index 4db7aa61903..c92814089ef 100644
--- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts
@@ -1,37 +1,42 @@
import {
+ BindingTypes,
+ type CompilerOptions,
+ ConstantTypes,
+ type DirectiveNode,
+ type ElementNode,
+ type InterpolationNode,
+ NodeTypes,
+ baseCompile,
baseParse as parse,
transform,
- ElementNode,
- DirectiveNode,
- NodeTypes,
- CompilerOptions,
- InterpolationNode,
- ConstantTypes,
- BindingTypes,
- baseCompile
} from '../../src'
import { transformIf } from '../../src/transforms/vIf'
import { transformExpression } from '../../src/transforms/transformExpression'
+import { PatchFlagNames, PatchFlags } from '../../../shared/src'
function parseWithExpressionTransform(
template: string,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
) {
- const ast = parse(template)
+ const ast = parse(template, options)
transform(ast, {
prefixIdentifiers: true,
nodeTransforms: [transformIf, transformExpression],
- ...options
+ ...options,
})
return ast.children[0]
}
+function compile(template: string) {
+ return baseCompile(template, { prefixIdentifiers: true })
+}
+
describe('compiler: expression transform', () => {
test('interpolation (root)', () => {
const node = parseWithExpressionTransform(`{{ foo }}`) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.foo`
+ content: `_ctx.foo`,
})
})
@@ -39,34 +44,34 @@ describe('compiler: expression transform', () => {
const node = parseWithExpressionTransform(`{{}}`) as InterpolationNode
const node2 = parseWithExpressionTransform(`{{ }}`) as InterpolationNode
const node3 = parseWithExpressionTransform(
- `{{ }}
`
+ `{{ }}
`,
) as ElementNode
const objectToBeMatched = {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: ``
+ content: ``,
}
expect(node.content).toMatchObject(objectToBeMatched)
expect(node2.content).toMatchObject(objectToBeMatched)
expect((node3.children[0] as InterpolationNode).content).toMatchObject(
- objectToBeMatched
+ objectToBeMatched,
)
})
test('interpolation (children)', () => {
const el = parseWithExpressionTransform(
- `{{ foo }}
`
+ `{{ foo }}
`,
) as ElementNode
const node = el.children[0] as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.foo`
+ content: `_ctx.foo`,
})
})
test('interpolation (complex)', () => {
const el = parseWithExpressionTransform(
- `{{ foo + bar(baz.qux) }}
`
+ `{{ foo + bar(baz.qux) }}
`,
) as ElementNode
const node = el.children[0] as InterpolationNode
expect(node.content).toMatchObject({
@@ -79,46 +84,46 @@ describe('compiler: expression transform', () => {
{ content: `_ctx.baz` },
`.`,
{ content: `qux` },
- `)`
- ]
+ `)`,
+ ],
})
})
test('directive value', () => {
const node = parseWithExpressionTransform(
- `
`
+ `
`,
) as ElementNode
const arg = (node.props[0] as DirectiveNode).arg!
expect(arg).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `arg`
+ content: `arg`,
})
const exp = (node.props[0] as DirectiveNode).exp!
expect(exp).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.baz`
+ content: `_ctx.baz`,
})
})
test('dynamic directive arg', () => {
const node = parseWithExpressionTransform(
- `
`
+ `
`,
) as ElementNode
const arg = (node.props[0] as DirectiveNode).arg!
expect(arg).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.arg`
+ content: `_ctx.arg`,
})
const exp = (node.props[0] as DirectiveNode).exp!
expect(exp).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.baz`
+ content: `_ctx.baz`,
})
})
test('should prefix complex expressions', () => {
const node = parseWithExpressionTransform(
- `{{ foo(baz + 1, { key: kuz }) }}`
+ `{{ foo(baz + 1, { key: kuz }) }}`,
) as InterpolationNode
// should parse into compound expression
expect(node.content).toMatchObject({
@@ -127,76 +132,57 @@ describe('compiler: expression transform', () => {
{
content: `_ctx.foo`,
loc: {
- source: `foo`,
- start: {
- offset: 3,
- line: 1,
- column: 4
- },
- end: {
- offset: 6,
- line: 1,
- column: 7
- }
- }
+ start: { offset: 3, line: 1, column: 4 },
+ end: { offset: 6, line: 1, column: 7 },
+ },
},
`(`,
{
content: `_ctx.baz`,
loc: {
- source: `baz`,
- start: {
- offset: 7,
- line: 1,
- column: 8
- },
- end: {
- offset: 10,
- line: 1,
- column: 11
- }
- }
+ start: { offset: 7, line: 1, column: 8 },
+ end: { offset: 10, line: 1, column: 11 },
+ },
},
` + 1, { key: `,
{
content: `_ctx.kuz`,
loc: {
- source: `kuz`,
- start: {
- offset: 23,
- line: 1,
- column: 24
- },
- end: {
- offset: 26,
- line: 1,
- column: 27
- }
- }
+ start: { offset: 23, line: 1, column: 24 },
+ end: { offset: 26, line: 1, column: 27 },
+ },
},
- ` })`
- ]
+ ` })`,
+ ],
})
})
test('should not prefix whitelisted globals', () => {
const node = parseWithExpressionTransform(
- `{{ Math.max(1, 2) }}`
+ `{{ Math.max(1, 2) }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: `Math` }, `.`, { content: `max` }, `(1, 2)`]
+ children: [{ content: `Math` }, `.`, { content: `max` }, `(1, 2)`],
+ })
+
+ expect(
+ (parseWithExpressionTransform(`{{ new Error() }}`) as InterpolationNode)
+ .content,
+ ).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: ['new ', { content: 'Error' }, '()'],
})
})
test('should not prefix reserved literals', () => {
function assert(exp: string) {
const node = parseWithExpressionTransform(
- `{{ ${exp} }}`
+ `{{ ${exp} }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: exp
+ content: exp,
})
}
assert(`true`)
@@ -207,7 +193,7 @@ describe('compiler: expression transform', () => {
test('should not prefix id of a function declaration', () => {
const node = parseWithExpressionTransform(
- `{{ function foo() { return bar } }}`
+ `{{ function foo() { return bar } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -216,14 +202,14 @@ describe('compiler: expression transform', () => {
{ content: `foo` },
`() { return `,
{ content: `_ctx.bar` },
- ` }`
- ]
+ ` }`,
+ ],
})
})
test('should not prefix params of a function expression', () => {
const node = parseWithExpressionTransform(
- `{{ foo => foo + bar }}`
+ `{{ foo => foo + bar }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -232,14 +218,14 @@ describe('compiler: expression transform', () => {
` => `,
{ content: `foo` },
` + `,
- { content: `_ctx.bar` }
- ]
+ { content: `_ctx.bar` },
+ ],
})
})
test('should prefix default value of a function expression param', () => {
const node = parseWithExpressionTransform(
- `{{ (foo = baz) => foo + bar }}`
+ `{{ (foo = baz) => foo + bar }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -251,14 +237,14 @@ describe('compiler: expression transform', () => {
`) => `,
{ content: `foo` },
` + `,
- { content: `_ctx.bar` }
- ]
+ { content: `_ctx.bar` },
+ ],
})
})
test('should not prefix function param destructuring', () => {
const node = parseWithExpressionTransform(
- `{{ ({ foo }) => foo + bar }}`
+ `{{ ({ foo }) => foo + bar }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -268,14 +254,14 @@ describe('compiler: expression transform', () => {
` }) => `,
{ content: `foo` },
` + `,
- { content: `_ctx.bar` }
- ]
+ { content: `_ctx.bar` },
+ ],
})
})
test('function params should not affect out of scope identifiers', () => {
const node = parseWithExpressionTransform(
- `{{ { a: foo => foo, b: foo } }}`
+ `{{ { a: foo => foo, b: foo } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -286,14 +272,14 @@ describe('compiler: expression transform', () => {
{ content: `foo` },
`, b: `,
{ content: `_ctx.foo` },
- ` }`
- ]
+ ` }`,
+ ],
})
})
test('should prefix default value of function param destructuring', () => {
const node = parseWithExpressionTransform(
- `{{ ({ foo = bar }) => foo + bar }}`
+ `{{ ({ foo = bar }) => foo + bar }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -305,13 +291,14 @@ describe('compiler: expression transform', () => {
` }) => `,
{ content: `foo` },
` + `,
- { content: `_ctx.bar` }
- ]
+ { content: `_ctx.bar` },
+ ],
})
})
+
test('should not prefix an object property key', () => {
const node = parseWithExpressionTransform(
- `{{ { foo() { baz() }, value: bar } }}`
+ `{{ { foo() { baz() }, value: bar } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -320,24 +307,24 @@ describe('compiler: expression transform', () => {
{ content: `_ctx.baz` },
`() }, value: `,
{ content: `_ctx.bar` },
- ` }`
- ]
+ ` }`,
+ ],
})
})
test('should not duplicate object key with same name as value', () => {
const node = parseWithExpressionTransform(
- `{{ { foo: foo } }}`
+ `{{ { foo: foo } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ foo: `, { content: `_ctx.foo` }, ` }`]
+ children: [`{ foo: `, { content: `_ctx.foo` }, ` }`],
})
})
test('should prefix a computed object property key', () => {
const node = parseWithExpressionTransform(
- `{{ { [foo]: bar } }}`
+ `{{ { [foo]: bar } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -346,24 +333,24 @@ describe('compiler: expression transform', () => {
{ content: `_ctx.foo` },
`]: `,
{ content: `_ctx.bar` },
- ` }`
- ]
+ ` }`,
+ ],
})
})
test('should prefix object property shorthand value', () => {
const node = parseWithExpressionTransform(
- `{{ { foo } }}`
+ `{{ { foo } }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ foo: `, { content: `_ctx.foo` }, ` }`]
+ children: [`{ foo: `, { content: `_ctx.foo` }, ` }`],
})
})
test('should not prefix id in a member expression', () => {
const node = parseWithExpressionTransform(
- `{{ foo.bar.baz }}`
+ `{{ foo.bar.baz }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -372,14 +359,14 @@ describe('compiler: expression transform', () => {
`.`,
{ content: `bar` },
`.`,
- { content: `baz` }
- ]
+ { content: `baz` },
+ ],
})
})
test('should prefix computed id in a member expression', () => {
const node = parseWithExpressionTransform(
- `{{ foo[bar][baz] }}`
+ `{{ foo[bar][baz] }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -389,32 +376,43 @@ describe('compiler: expression transform', () => {
{ content: `_ctx.bar` },
`][`,
{ content: '_ctx.baz' },
- `]`
- ]
+ `]`,
+ ],
})
})
test('should handle parse error', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithExpressionTransform(`{{ a( }}`, { onError })
expect(onError.mock.calls[0][0].message).toMatch(
- `Error parsing JavaScript expression: Unexpected token`
+ `Error parsing JavaScript expression: Unexpected token`,
+ )
+ })
+
+ test('should not error', () => {
+ const onError = vi.fn()
+ parseWithExpressionTransform(
+ `
`,
+ {
+ onError,
+ },
)
+ expect(onError).not.toHaveBeenCalled()
})
test('should prefix in assignment', () => {
const node = parseWithExpressionTransform(
- `{{ x = 1 }}`
+ `{{ x = 1 }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: `_ctx.x` }, ` = 1`]
+ children: [{ content: `_ctx.x` }, ` = 1`],
})
})
test('should prefix in assignment pattern', () => {
const node = parseWithExpressionTransform(
- `{{ { x, y: [z] } = obj }}`
+ `{{ { x, y: [z] } = obj }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -424,37 +422,156 @@ describe('compiler: expression transform', () => {
`, y: [`,
{ content: `_ctx.z` },
`] } = `,
- { content: `_ctx.obj` }
- ]
+ { content: `_ctx.obj` },
+ ],
})
})
+ // #8295
+ test('should treat floating point number literals as constant', () => {
+ const node = parseWithExpressionTransform(
+ `{{ [1, 2.1] }}`,
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ constType: ConstantTypes.CAN_STRINGIFY,
+ })
+ })
+
+ // #10807
+ test('should not bail constant on strings w/ ()', () => {
+ const node = parseWithExpressionTransform(
+ `{{ { foo: 'ok()' } }}`,
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ constType: ConstantTypes.CAN_STRINGIFY,
+ })
+ })
+
+ test('should bail constant for global identifiers w/ new or call expressions', () => {
+ const node = parseWithExpressionTransform(
+ `{{ new Date().getFullYear() }}`,
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ children: [
+ 'new ',
+ { constType: ConstantTypes.NOT_CONSTANT },
+ '().',
+ { constType: ConstantTypes.NOT_CONSTANT },
+ '()',
+ ],
+ })
+ })
+
+ test('should not prefix temp variable of for...in', () => {
+ const { code } = compile(
+ ` {
+ for (const x in list) {
+ log(x)
+ }
+ error(x)
+ }"/>`,
+ )
+ expect(code).not.toMatch(`log(_ctx.x)`)
+ expect(code).toMatch(`error(_ctx.x)`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should not prefix temp variable of for...of', () => {
+ const { code } = compile(
+ `
{
+ for (const x of list) {
+ log(x)
+ }
+ error(x)
+ }"/>`,
+ )
+ expect(code).not.toMatch(`log(_ctx.x)`)
+ expect(code).toMatch(`error(_ctx.x)`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should not prefix temp variable of for loop', () => {
+ const { code } = compile(
+ `
{
+ for (let i = 0; i < list.length; i++) {
+ log(i)
+ }
+ error(i)
+ }"/>`,
+ )
+ expect(code).not.toMatch(`log(_ctx.i)`)
+ expect(code).toMatch(`error(_ctx.i)`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should allow leak of var declarations in for loop', () => {
+ const { code } = compile(
+ `
{
+ for (var i = 0; i < list.length; i++) {
+ log(i)
+ }
+ error(i)
+ }"/>`,
+ )
+ expect(code).not.toMatch(`log(_ctx.i)`)
+ expect(code).not.toMatch(`error(_ctx.i)`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should not prefix catch block param', () => {
+ const { code } = compile(
+ `
{
+ try {} catch (err) { console.error(err) }
+ console.log(err)
+ }"/>`,
+ )
+ expect(code).not.toMatch(`console.error(_ctx.err)`)
+ expect(code).toMatch(`console.log(_ctx.err)`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should not prefix destructured catch block param', () => {
+ const { code } = compile(
+ `
{
+ try {
+ throw new Error('sup?')
+ } catch ({ message: { length } }) {
+ console.error(length)
+ }
+ console.log(length)
+ }"/>`,
+ )
+ expect(code).not.toMatch(`console.error(_ctx.length)`)
+ expect(code).toMatch(`console.log(_ctx.length)`)
+ expect(code).toMatchSnapshot()
+ })
+
describe('ES Proposals support', () => {
test('bigInt', () => {
const node = parseWithExpressionTransform(
- `{{ 13000n }}`
+ `{{ 13000n }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
content: `13000n`,
isStatic: false,
- constType: ConstantTypes.CAN_STRINGIFY
+ constType: ConstantTypes.CAN_STRINGIFY,
})
})
test('nullish coalescing', () => {
const node = parseWithExpressionTransform(
- `{{ a ?? b }}`
+ `{{ a ?? b }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: `_ctx.a` }, ` ?? `, { content: `_ctx.b` }]
+ children: [{ content: `_ctx.a` }, ` ?? `, { content: `_ctx.b` }],
})
})
test('optional chaining', () => {
const node = parseWithExpressionTransform(
- `{{ a?.b?.c }}`
+ `{{ a?.b?.c }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -463,8 +580,8 @@ describe('compiler: expression transform', () => {
`?.`,
{ content: `b` },
`?.`,
- { content: `c` }
- ]
+ { content: `c` },
+ ],
})
})
@@ -475,14 +592,18 @@ describe('compiler: expression transform', () => {
[
'pipelineOperator',
{
- proposal: 'minimal'
- }
- ]
- ]
+ proposal: 'minimal',
+ },
+ ],
+ ],
}) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: `_ctx.a` }, ` |> `, { content: `_ctx.uppercase` }]
+ children: [
+ { content: `_ctx.a` },
+ ` |> `,
+ { content: `_ctx.uppercase` },
+ ],
})
})
})
@@ -493,26 +614,30 @@ describe('compiler: expression transform', () => {
setup: BindingTypes.SETUP_MAYBE_REF,
setupConst: BindingTypes.SETUP_CONST,
data: BindingTypes.DATA,
- options: BindingTypes.OPTIONS
+ options: BindingTypes.OPTIONS,
+ reactive: BindingTypes.SETUP_REACTIVE_CONST,
+ literal: BindingTypes.LITERAL_CONST,
+ isNaN: BindingTypes.SETUP_REF,
}
function compileWithBindingMetadata(
template: string,
- options?: CompilerOptions
+ options?: CompilerOptions,
) {
return baseCompile(template, {
prefixIdentifiers: true,
bindingMetadata,
- ...options
+ ...options,
})
}
test('non-inline mode', () => {
const { code } = compileWithBindingMetadata(
- `
{{ props }} {{ setup }} {{ data }} {{ options }}
`
+ `
{{ props }} {{ setup }} {{ data }} {{ options }} {{ isNaN }}
`,
)
expect(code).toMatch(`$props.props`)
expect(code).toMatch(`$setup.setup`)
+ expect(code).toMatch(`$setup.isNaN`)
expect(code).toMatch(`$data.data`)
expect(code).toMatch(`$options.options`)
expect(code).toMatch(`_ctx, _cache, $props, $setup, $data, $options`)
@@ -521,15 +646,74 @@ describe('compiler: expression transform', () => {
test('inline mode', () => {
const { code } = compileWithBindingMetadata(
- `
{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }}
`,
- { inline: true }
+ `
{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}
`,
+ { inline: true },
)
expect(code).toMatch(`__props.props`)
expect(code).toMatch(`_unref(setup)`)
expect(code).toMatch(`_toDisplayString(setupConst)`)
expect(code).toMatch(`_ctx.data`)
expect(code).toMatch(`_ctx.options`)
+ expect(code).toMatch(`isNaN.value`)
expect(code).toMatchSnapshot()
})
+
+ test('literal const handling', () => {
+ const { code } = compileWithBindingMetadata(`
{{ literal }}
`, {
+ inline: true,
+ })
+ expect(code).toMatch(`toDisplayString(literal)`)
+ // #7973 should skip patch for literal const
+ expect(code).not.toMatch(
+ `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
+ )
+ })
+
+ test('literal const handling, non-inline mode', () => {
+ const { code } = compileWithBindingMetadata(`
{{ literal }}
`)
+ expect(code).toMatch(`toDisplayString($setup.literal)`)
+ // #7973 should skip patch for literal const
+ expect(code).not.toMatch(
+ `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
+ )
+ })
+
+ test('reactive const handling', () => {
+ const { code } = compileWithBindingMetadata(`
{{ reactive }}
`, {
+ inline: true,
+ })
+ // #7973 should not skip patch for reactive const
+ expect(code).toMatch(
+ `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
+ )
+ })
+
+ // #10754
+ test('await expression in right hand of assignment, inline mode', () => {
+ const node = parseWithExpressionTransform(
+ `{{ (async () => { x = await bar })() }}`,
+ {
+ inline: true,
+ bindingMetadata: {
+ x: BindingTypes.SETUP_LET,
+ bar: BindingTypes.SETUP_CONST,
+ },
+ },
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [
+ `(async () => { `,
+ {
+ content: `_isRef(x) ? x.value = await bar : x`,
+ },
+ ` = await `,
+ {
+ content: `bar`,
+ },
+ ` })()`,
+ ],
+ })
+ })
})
})
diff --git a/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts b/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts
index fd5bd09c0f1..6420bdbbdac 100644
--- a/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts
@@ -1,10 +1,10 @@
import {
- CompilerOptions,
+ type CompilerOptions,
+ type ElementNode,
+ ErrorCodes,
+ NodeTypes,
baseParse as parse,
transform,
- ElementNode,
- NodeTypes,
- ErrorCodes
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
@@ -19,13 +19,13 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
nodeTransforms: [
...(options.prefixIdentifiers ? [transformExpression] : []),
transformSlotOutlet,
- transformElement
+ transformElement,
],
directiveTransforms: {
on: transformOn,
- bind: transformBind
+ bind: transformBind,
},
- ...options
+ ...options,
})
return ast
}
@@ -36,7 +36,7 @@ describe('compiler: transform
outlets', () => {
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: [`$slots`, `"default"`]
+ arguments: [`$slots`, `"default"`],
})
})
@@ -45,7 +45,7 @@ describe('compiler: transform outlets', () => {
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: [`$slots`, `"foo"`]
+ arguments: [`$slots`, `"foo"`],
})
})
@@ -59,15 +59,15 @@ describe('compiler: transform outlets', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`,
- isStatic: false
- }
- ]
+ isStatic: false,
+ },
+ ],
})
})
test('dynamically named slot outlet w/ prefixIdentifiers: true', () => {
const ast = parseWithSlots(` `, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
@@ -80,23 +80,23 @@ describe('compiler: transform outlets', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.foo`,
- isStatic: false
+ isStatic: false,
},
` + `,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.bar`,
- isStatic: false
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ ],
+ },
+ ],
})
})
test('default slot outlet with props', () => {
const ast = parseWithSlots(
- ` `
+ ` `,
)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
@@ -110,36 +110,36 @@ describe('compiler: transform outlets', () => {
{
key: {
content: `foo`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `bar`,
- isStatic: true
- }
+ isStatic: true,
+ },
},
{
key: {
content: `baz`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `qux`,
- isStatic: false
- }
+ isStatic: false,
+ },
},
{
key: {
content: `fooBar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `foo-bar`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
@@ -158,26 +158,26 @@ describe('compiler: transform outlets', () => {
{
key: {
content: `foo`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `bar`,
- isStatic: true
- }
+ isStatic: true,
+ },
},
{
key: {
content: `baz`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `qux`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
@@ -196,26 +196,26 @@ describe('compiler: transform outlets', () => {
{
key: {
content: `foo`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `bar`,
- isStatic: true
- }
+ isStatic: true,
+ },
},
{
key: {
content: `baz`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `qux`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
@@ -234,11 +234,11 @@ describe('compiler: transform outlets', () => {
returns: [
{
type: NodeTypes.ELEMENT,
- tag: `div`
- }
- ]
- }
- ]
+ tag: `div`,
+ },
+ ],
+ },
+ ],
})
})
@@ -257,11 +257,11 @@ describe('compiler: transform outlets', () => {
returns: [
{
type: NodeTypes.ELEMENT,
- tag: `div`
- }
- ]
- }
- ]
+ tag: `div`,
+ },
+ ],
+ },
+ ],
})
})
@@ -279,14 +279,14 @@ describe('compiler: transform outlets', () => {
{
key: {
content: `foo`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `bar`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
},
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
@@ -294,11 +294,11 @@ describe('compiler: transform outlets', () => {
returns: [
{
type: NodeTypes.ELEMENT,
- tag: `div`
- }
- ]
- }
- ]
+ tag: `div`,
+ },
+ ],
+ },
+ ],
})
})
@@ -316,14 +316,14 @@ describe('compiler: transform outlets', () => {
{
key: {
content: `foo`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `bar`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
},
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
@@ -331,11 +331,11 @@ describe('compiler: transform outlets', () => {
returns: [
{
type: NodeTypes.ELEMENT,
- tag: `div`
- }
- ]
- }
- ]
+ tag: `div`,
+ },
+ ],
+ },
+ ],
})
})
@@ -344,11 +344,11 @@ describe('compiler: transform outlets', () => {
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: [`$slots`, `"default"`, `{}`, `undefined`, `true`]
+ arguments: [`$slots`, `"default"`, `{}`, `undefined`, `true`],
})
const fallback = parseWithSlots(`fallback `, {
slotted: false,
- scopeId: 'foo'
+ scopeId: 'foo',
})
const child = {
@@ -357,37 +357,52 @@ describe('compiler: transform outlets', () => {
returns: [
{
type: NodeTypes.TEXT,
- content: `fallback`
- }
- ]
+ content: `fallback`,
+ },
+ ],
}
expect((fallback.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: [`$slots`, `"default"`, `{}`, child, `true`]
+ arguments: [`$slots`, `"default"`, `{}`, child, `true`],
})
})
test(`error on unexpected custom directive on `, () => {
- const onError = jest.fn()
+ const onError = vi.fn()
const source = ` `
parseWithSlots(source, { onError })
const index = source.indexOf('v-foo')
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
loc: {
- source: `v-foo`,
start: {
offset: index,
line: 1,
- column: index + 1
+ column: index + 1,
},
end: {
offset: index + 5,
line: 1,
- column: index + 6
- }
- }
+ column: index + 6,
+ },
+ },
+ })
+ })
+
+ test('dynamically named slot outlet with v-bind shorthand', () => {
+ const ast = parseWithSlots(` `)
+ expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: RENDER_SLOT,
+ arguments: [
+ `$slots`,
+ {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `name`,
+ isStatic: false,
+ },
+ ],
})
})
})
diff --git a/packages/compiler-core/__tests__/transforms/transformText.spec.ts b/packages/compiler-core/__tests__/transforms/transformText.spec.ts
index 6e321d145e5..1a6d6916a73 100644
--- a/packages/compiler-core/__tests__/transforms/transformText.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/transformText.spec.ts
@@ -1,11 +1,11 @@
import {
- CompilerOptions,
- baseParse as parse,
- transform,
+ type CompilerOptions,
+ type ElementNode,
+ type ForNode,
NodeTypes,
generate,
- ForNode,
- ElementNode
+ baseParse as parse,
+ transform,
} from '../../src'
import { transformFor } from '../../src/transforms/vFor'
import { transformText } from '../../src/transforms/transformText'
@@ -22,9 +22,9 @@ function transformWithTextOpt(template: string, options: CompilerOptions = {}) {
transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement,
- transformText
+ transformText,
],
- ...options
+ ...options,
})
return ast
}
@@ -35,8 +35,8 @@ describe('compiler: transform text', () => {
expect(root.children[0]).toMatchObject({
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -51,8 +51,8 @@ describe('compiler: transform text', () => {
` + `,
{ type: NodeTypes.TEXT, content: ` bar ` },
` + `,
- { type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
- ]
+ { type: NodeTypes.INTERPOLATION, content: { content: `baz` } },
+ ],
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -75,12 +75,12 @@ describe('compiler: transform text', () => {
` + `,
{ type: NodeTypes.TEXT, content: ` bar ` },
` + `,
- { type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
- ]
+ { type: NodeTypes.INTERPOLATION, content: { content: `baz` } },
+ ],
},
- genFlagText(PatchFlags.TEXT)
- ]
- }
+ genFlagText(PatchFlags.TEXT),
+ ],
+ },
})
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
expect(generate(root).code).toMatchSnapshot()
@@ -99,11 +99,11 @@ describe('compiler: transform text', () => {
arguments: [
{
type: NodeTypes.TEXT,
- content: `hello`
- }
+ content: `hello`,
+ },
// should have no flag
- ]
- }
+ ],
+ },
})
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
expect(generate(root).code).toMatchSnapshot()
@@ -111,7 +111,7 @@ describe('compiler: transform text', () => {
test('consecutive text mixed with elements', () => {
const root = transformWithTextOpt(
- `
{{ foo }} bar {{ baz }}
hello
`
+ `
{{ foo }} bar {{ baz }}
hello
`,
)
expect(root.children.length).toBe(5)
expect(root.children[0].type).toBe(NodeTypes.ELEMENT)
@@ -128,12 +128,12 @@ describe('compiler: transform text', () => {
` + `,
{ type: NodeTypes.TEXT, content: ` bar ` },
` + `,
- { type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
- ]
+ { type: NodeTypes.INTERPOLATION, content: { content: `baz` } },
+ ],
},
- genFlagText(PatchFlags.TEXT)
- ]
- }
+ genFlagText(PatchFlags.TEXT),
+ ],
+ },
})
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
expect(root.children[3]).toMatchObject({
@@ -144,10 +144,10 @@ describe('compiler: transform text', () => {
arguments: [
{
type: NodeTypes.TEXT,
- content: `hello`
- }
- ]
- }
+ content: `hello`,
+ },
+ ],
+ },
})
expect(root.children[4].type).toBe(NodeTypes.ELEMENT)
expect(generate(root).code).toMatchSnapshot()
@@ -155,21 +155,21 @@ describe('compiler: transform text', () => {
test('', () => {
const root = transformWithTextOpt(
- `foo `
+ `foo `,
)
expect(root.children[0].type).toBe(NodeTypes.FOR)
const forNode = root.children[0] as ForNode
// should convert template v-for text children because they are inside
// fragments
expect(forNode.children[0]).toMatchObject({
- type: NodeTypes.TEXT_CALL
+ type: NodeTypes.TEXT_CALL,
})
expect(generate(root).code).toMatchSnapshot()
})
test('with prefixIdentifiers: true', () => {
const root = transformWithTextOpt(`{{ foo }} bar {{ baz + qux }}`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(root.children.length).toBe(1)
expect(root.children[0]).toMatchObject({
@@ -183,15 +183,15 @@ describe('compiler: transform text', () => {
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: `_ctx.baz` }, ` + `, { content: `_ctx.qux` }]
- }
- }
- ]
+ children: [{ content: `_ctx.baz` }, ` + `, { content: `_ctx.qux` }],
+ },
+ },
+ ],
})
expect(
generate(root, {
- prefixIdentifiers: true
- }).code
+ prefixIdentifiers: true,
+ }).code,
).toMatchSnapshot()
})
@@ -210,12 +210,12 @@ describe('compiler: transform text', () => {
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: 'foo'
- }
+ content: 'foo',
+ },
},
- genFlagText(PatchFlags.TEXT)
- ]
- }
+ genFlagText(PatchFlags.TEXT),
+ ],
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
diff --git a/packages/compiler-core/__tests__/transforms/vBind.spec.ts b/packages/compiler-core/__tests__/transforms/vBind.spec.ts
index 27e0ae10c81..be063b8a9d5 100644
--- a/packages/compiler-core/__tests__/transforms/vBind.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vBind.spec.ts
@@ -1,37 +1,37 @@
import {
- baseParse as parse,
- transform,
- ElementNode,
- ObjectExpression,
- CompilerOptions,
+ type CallExpression,
+ type CompilerOptions,
+ type ElementNode,
ErrorCodes,
- VNodeCall,
NodeTypes,
- CallExpression
+ type ObjectExpression,
+ type VNodeCall,
+ baseParse as parse,
+ transform,
} from '../../src'
import { transformBind } from '../../src/transforms/vBind'
import { transformElement } from '../../src/transforms/transformElement'
import {
CAMELIZE,
+ NORMALIZE_PROPS,
helperNameMap,
- NORMALIZE_PROPS
} from '../../src/runtimeHelpers'
import { transformExpression } from '../../src/transforms/transformExpression'
function parseWithVBind(
template: string,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
): ElementNode {
const ast = parse(template)
transform(ast, {
nodeTransforms: [
...(options.prefixIdentifiers ? [transformExpression] : []),
- transformElement
+ transformElement,
],
directiveTransforms: {
- bind: transformBind
+ bind: transformBind,
},
- ...options
+ ...options,
})
return ast.children[0] as ElementNode
}
@@ -47,13 +47,13 @@ describe('compiler: transform v-bind', () => {
loc: {
start: {
line: 1,
- column: 13
+ column: 13,
},
end: {
line: 1,
- column: 15
- }
- }
+ column: 15,
+ },
+ },
},
value: {
content: `id`,
@@ -61,14 +61,52 @@ describe('compiler: transform v-bind', () => {
loc: {
start: {
line: 1,
- column: 17
+ column: 17,
},
end: {
line: 1,
- column: 19
- }
- }
- }
+ column: 19,
+ },
+ },
+ },
+ })
+ })
+
+ test('no expression', () => {
+ const node = parseWithVBind(`
`)
+ const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+ expect(props.properties[0]).toMatchObject({
+ key: {
+ content: `id`,
+ isStatic: true,
+ loc: {
+ start: { line: 1, column: 13, offset: 12 },
+ end: { line: 1, column: 15, offset: 14 },
+ },
+ },
+ value: {
+ content: `id`,
+ isStatic: false,
+ loc: {
+ start: { line: 1, column: 13, offset: 12 },
+ end: { line: 1, column: 15, offset: 14 },
+ },
+ },
+ })
+ })
+
+ test('no expression (shorthand)', () => {
+ const node = parseWithVBind(`
`)
+ const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+ expect(props.properties[0]).toMatchObject({
+ key: {
+ content: `id`,
+ isStatic: true,
+ },
+ value: {
+ content: `id`,
+ isStatic: false,
+ },
})
})
@@ -85,45 +123,45 @@ describe('compiler: transform v-bind', () => {
{
key: {
content: `id || ""`,
- isStatic: false
+ isStatic: false,
},
value: {
content: `id`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
- test('should error if no expression', () => {
- const onError = jest.fn()
- const node = parseWithVBind(`
`, { onError })
+ test('should error if empty expression', () => {
+ const onError = vi.fn()
+ const node = parseWithVBind(`
`, { onError })
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_BIND_NO_EXPRESSION,
loc: {
start: {
line: 1,
- column: 6
+ column: 6,
},
end: {
line: 1,
- column: 16
- }
- }
+ column: 19,
+ },
+ },
})
expect(props.properties[0]).toMatchObject({
key: {
content: `arg`,
- isStatic: true
+ isStatic: true,
},
value: {
content: ``,
- isStatic: true
- }
+ isStatic: true,
+ },
})
})
@@ -133,12 +171,27 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({
key: {
content: `fooBar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `id`,
- isStatic: false
- }
+ isStatic: false,
+ },
+ })
+ })
+
+ test('.camel modifier w/ no expression', () => {
+ const node = parseWithVBind(`
`)
+ const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+ expect(props.properties[0]).toMatchObject({
+ key: {
+ content: `fooBar`,
+ isStatic: true,
+ },
+ value: {
+ content: `fooBar`,
+ isStatic: false,
+ },
})
})
@@ -155,22 +208,22 @@ describe('compiler: transform v-bind', () => {
{
key: {
content: `_${helperNameMap[CAMELIZE]}(foo || "")`,
- isStatic: false
+ isStatic: false,
},
value: {
content: `id`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
test('.camel modifier w/ dynamic arg + prefixIdentifiers', () => {
const node = parseWithVBind(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const props = (node.codegenNode as VNodeCall).props as CallExpression
expect(props).toMatchObject({
@@ -190,17 +243,17 @@ describe('compiler: transform v-bind', () => {
{ content: `_ctx.bar` },
`)`,
`) || ""`,
- `)`
- ]
+ `)`,
+ ],
},
value: {
content: `_ctx.id`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
@@ -210,12 +263,27 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({
key: {
content: `.fooBar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `id`,
- isStatic: false
- }
+ isStatic: false,
+ },
+ })
+ })
+
+ test('.prop modifier w/ no expression', () => {
+ const node = parseWithVBind(`
`)
+ const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+ expect(props.properties[0]).toMatchObject({
+ key: {
+ content: `.fooBar`,
+ isStatic: true,
+ },
+ value: {
+ content: `fooBar`,
+ isStatic: false,
+ },
})
})
@@ -232,22 +300,22 @@ describe('compiler: transform v-bind', () => {
{
key: {
content: '`.${fooBar || ""}`',
- isStatic: false
+ isStatic: false,
},
value: {
content: `id`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
test('.prop modifier w/ dynamic arg + prefixIdentifiers', () => {
const node = parseWithVBind(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const props = (node.codegenNode as VNodeCall).props as CallExpression
expect(props).toMatchObject({
@@ -267,17 +335,17 @@ describe('compiler: transform v-bind', () => {
{ content: `_ctx.bar` },
`)`,
`) || ""`,
- `)`
- ]
+ `)`,
+ ],
},
value: {
content: `_ctx.id`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
})
@@ -287,12 +355,27 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({
key: {
content: `.fooBar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `id`,
- isStatic: false
- }
+ isStatic: false,
+ },
+ })
+ })
+
+ test('.prop modifier (shortband) w/ no expression', () => {
+ const node = parseWithVBind(`
`)
+ const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+ expect(props.properties[0]).toMatchObject({
+ key: {
+ content: `.fooBar`,
+ isStatic: true,
+ },
+ value: {
+ content: `fooBar`,
+ isStatic: false,
+ },
})
})
@@ -302,12 +385,45 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({
key: {
content: `^foo-bar`,
- isStatic: true
+ isStatic: true,
},
value: {
content: `id`,
- isStatic: false
- }
+ isStatic: false,
+ },
+ })
+ })
+
+ test('.attr modifier w/ no expression', () => {
+ const node = parseWithVBind(`
`)
+ const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+ expect(props.properties[0]).toMatchObject({
+ key: {
+ content: `^foo-bar`,
+ isStatic: true,
+ },
+ value: {
+ content: `fooBar`,
+ isStatic: false,
+ },
+ })
+ })
+
+ test('error on invalid argument for same-name shorthand', () => {
+ const onError = vi.fn()
+ parseWithVBind(`
`, { 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,
+ },
+ },
})
})
})
diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts
index c22b364aa61..fead2476ac5 100644
--- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts
@@ -1,4 +1,4 @@
-import { baseParse as parse } from '../../src/parse'
+import { baseParse as parse } from '../../src/parser'
import { transform } from '../../src/transform'
import { transformIf } from '../../src/transforms/vIf'
import { transformFor } from '../../src/transforms/vFor'
@@ -7,24 +7,28 @@ import { transformElement } from '../../src/transforms/transformElement'
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
import { transformExpression } from '../../src/transforms/transformExpression'
import {
- ForNode,
+ ConstantTypes,
+ type ElementNode,
+ type ForCodegenNode,
+ type ForNode,
+ type InterpolationNode,
NodeTypes,
- SimpleExpressionNode,
- ElementNode,
- InterpolationNode,
- ForCodegenNode,
- ConstantTypes
+ type RootNode,
+ type SimpleExpressionNode,
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
-import { CompilerOptions, generate } from '../../src'
+import { type CompilerOptions, generate } from '../../src'
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared'
-import { createObjectMatcher, genFlagText } from '../testUtils'
+import { createObjectMatcher } from '../testUtils'
-function parseWithForTransform(
+export function parseWithForTransform(
template: string,
- options: CompilerOptions = {}
-) {
+ options: CompilerOptions = {},
+): {
+ root: RootNode
+ node: ForNode & { codegenNode: ForCodegenNode }
+} {
const ast = parse(template, options)
transform(ast, {
nodeTransforms: [
@@ -32,16 +36,16 @@ function parseWithForTransform(
transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []),
transformSlotOutlet,
- transformElement
+ transformElement,
],
directiveTransforms: {
- bind: transformBind
+ bind: transformBind,
},
- ...options
+ ...options,
})
return {
root: ast,
- node: ast.children[0] as ForNode & { codegenNode: ForCodegenNode }
+ node: ast.children[0] as ForNode & { codegenNode: ForCodegenNode },
}
}
@@ -49,7 +53,7 @@ describe('compiler: v-for', () => {
describe('transform', () => {
test('number expression', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
@@ -59,7 +63,7 @@ describe('compiler: v-for', () => {
test('value', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
@@ -69,31 +73,31 @@ describe('compiler: v-for', () => {
test('object de-structured value', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe(
- '{ id, value }'
+ '{ id, value }',
)
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('array de-structured value', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe(
- '[ id, value ]'
+ '[ id, value ]',
)
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('value and key', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
@@ -104,13 +108,13 @@ describe('compiler: v-for', () => {
test('value, key and index', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
@@ -118,12 +122,12 @@ describe('compiler: v-for', () => {
test('skipped key', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
@@ -131,12 +135,12 @@ describe('compiler: v-for', () => {
test('skipped value and key', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect(forNode.valueAlias).toBeUndefined()
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
@@ -144,7 +148,7 @@ describe('compiler: v-for', () => {
test('unbracketed value', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
@@ -154,7 +158,7 @@ describe('compiler: v-for', () => {
test('unbracketed value and key', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
@@ -165,13 +169,13 @@ describe('compiler: v-for', () => {
test('unbracketed value, key and index', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
@@ -179,12 +183,12 @@ describe('compiler: v-for', () => {
test('unbracketed skipped key', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
@@ -192,94 +196,118 @@ describe('compiler: v-for', () => {
test('unbracketed skipped value and key', () => {
const { node: forNode } = parseWithForTransform(
- ' '
+ ' ',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
- 'index'
+ 'index',
)
expect(forNode.valueAlias).toBeUndefined()
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
+
+ test('source containing string expression with spaces', () => {
+ const { node: forNode } = parseWithForTransform(
+ ` `,
+ )
+ expect(forNode.keyAlias).toBeUndefined()
+ expect(forNode.objectIndexAlias).toBeUndefined()
+ expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('item')
+ expect((forNode.source as SimpleExpressionNode).content).toBe(
+ "state ['my items']",
+ )
+ })
})
describe('errors', () => {
test('missing expression', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithForTransform(' ', { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_NO_EXPRESSION
- })
+ code: ErrorCodes.X_V_FOR_NO_EXPRESSION,
+ }),
)
})
test('empty expression', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithForTransform(' ', { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ }),
)
})
test('invalid expression', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithForTransform(' ', { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ }),
)
})
test('missing source', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithForTransform(' ', { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ }),
+ )
+ })
+
+ test('missing source and have multiple spaces with', () => {
+ const onError = vi.fn()
+ parseWithForTransform(' ', { onError })
+
+ expect(onError).toHaveBeenCalledTimes(1)
+ expect(onError).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ }),
)
})
test('missing value', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithForTransform(' ', { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ }),
)
})
test(' key placement', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithForTransform(
`
`,
- { onError }
+ { onError },
)
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT
- })
+ code: ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT,
+ }),
)
// should not warn on nested v-for keys
@@ -288,7 +316,7 @@ describe('compiler: v-for', () => {
`,
- { onError }
+ { onError },
)
expect(onError).toHaveBeenCalledTimes(1)
})
@@ -315,7 +343,7 @@ describe('compiler: v-for', () => {
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(
- itemsOffset + 1 + `items`.length
+ itemsOffset + 1 + `items`.length,
)
})
@@ -339,7 +367,7 @@ describe('compiler: v-for', () => {
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(
- itemsOffset + 1 + `items`.length
+ itemsOffset + 1 + `items`.length,
)
})
@@ -363,7 +391,7 @@ describe('compiler: v-for', () => {
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(
- itemsOffset + 1 + `items`.length
+ itemsOffset + 1 + `items`.length,
)
})
@@ -405,7 +433,7 @@ describe('compiler: v-for', () => {
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(
- itemsOffset + 1 + `items`.length
+ itemsOffset + 1 + `items`.length,
)
})
@@ -438,7 +466,7 @@ describe('compiler: v-for', () => {
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(
- itemsOffset + 1 + `items`.length
+ itemsOffset + 1 + `items`.length,
)
})
})
@@ -446,18 +474,18 @@ describe('compiler: v-for', () => {
describe('prefixIdentifiers: true', () => {
test('should prefix v-for source', () => {
const { node } = parseWithForTransform(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(node.source).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.list`
+ content: `_ctx.list`,
})
})
test('should prefix v-for source w/ complex expression', () => {
const { node } = parseWithForTransform(
`
`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(node.source).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -467,31 +495,31 @@ describe('compiler: v-for', () => {
{ content: `concat` },
`([`,
{ content: `_ctx.foo` },
- `])`
- ]
+ `])`,
+ ],
})
})
test('should not prefix v-for alias', () => {
const { node } = parseWithForTransform(
`{{ i }}{{ j }}
`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `i`
+ content: `i`,
})
expect((div.children[1] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.j`
+ content: `_ctx.j`,
})
})
test('should not prefix v-for aliases (multiple)', () => {
const { node } = parseWithForTransform(
`{{ i + j + k }}{{ l }}
`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({
@@ -501,23 +529,23 @@ describe('compiler: v-for', () => {
` + `,
{ content: `j` },
` + `,
- { content: `k` }
- ]
+ { content: `k` },
+ ],
})
expect((div.children[1] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.l`
+ content: `_ctx.l`,
})
})
test('should prefix id outside of v-for', () => {
const { node } = parseWithForTransform(
``,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect((node.children[1] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.i`
+ content: `_ctx.i`,
})
})
@@ -526,7 +554,7 @@ describe('compiler: v-for', () => {
``,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const outerDiv = node.children[0] as ElementNode
const innerFor = outerDiv.children[0] as ForNode
@@ -534,7 +562,7 @@ describe('compiler: v-for', () => {
.children[0] as InterpolationNode
expect(innerExp.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [{ content: 'i' }, ` + `, { content: `_ctx.j` }]
+ children: [{ content: 'i' }, ` + `, { content: `_ctx.j` }],
})
// when an inner v-for shadows a variable of an outer v-for and exit,
@@ -542,7 +570,7 @@ describe('compiler: v-for', () => {
const outerExp = outerDiv.children[1] as InterpolationNode
expect(outerExp.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `i`
+ content: `i`,
})
})
@@ -551,7 +579,7 @@ describe('compiler: v-for', () => {
`
{{ foo + bar + baz + qux + quux }}
`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(node.valueAlias!).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@@ -564,8 +592,8 @@ describe('compiler: v-for', () => {
{ content: `qux` },
` = `,
{ content: `_ctx.quux` },
- `] }`
- ]
+ `] }`,
+ ],
})
const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({
@@ -579,17 +607,17 @@ describe('compiler: v-for', () => {
` + `,
{ content: `qux` },
` + `,
- { content: `_ctx.quux` }
- ]
+ { content: `_ctx.quux` },
+ ],
})
})
test('element v-for key expression prefixing', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
'test
',
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({
@@ -604,20 +632,20 @@ describe('compiler: v-for', () => {
`(`,
// should NOT prefix in scope variables
{ content: `item` },
- `)`
- ]
- }
- })
+ `)`,
+ ],
+ },
+ }),
})
})
// #2085
test('template v-for key expression prefixing', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
'test ',
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({
@@ -632,19 +660,19 @@ describe('compiler: v-for', () => {
`(`,
// should NOT prefix in scope variables
{ content: `item` },
- `)`
- ]
- }
- })
+ `)`,
+ ],
+ },
+ }),
})
})
test('template v-for key no prefixing on attribute key', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
'test ',
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({
@@ -653,9 +681,9 @@ describe('compiler: v-for', () => {
props: createObjectMatcher({
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: 'key'
- }
- })
+ content: 'key',
+ },
+ }),
})
})
})
@@ -665,17 +693,17 @@ describe('compiler: v-for', () => {
node: ForCodegenNode,
keyed: boolean = false,
customReturn: boolean = false,
- disableTracking: boolean = true
+ disableTracking: boolean = true,
) {
expect(node).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: FRAGMENT,
disableTracking,
patchFlag: !disableTracking
- ? genFlagText(PatchFlags.STABLE_FRAGMENT)
+ ? PatchFlags.STABLE_FRAGMENT
: keyed
- ? genFlagText(PatchFlags.KEYED_FRAGMENT)
- : genFlagText(PatchFlags.UNKEYED_FRAGMENT),
+ ? PatchFlags.KEYED_FRAGMENT
+ : PatchFlags.UNKEYED_FRAGMENT,
children: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST,
@@ -687,32 +715,34 @@ describe('compiler: v-for', () => {
? {}
: {
type: NodeTypes.VNODE_CALL,
- isBlock: disableTracking
- }
- }
- ]
- }
+ isBlock: disableTracking,
+ },
+ },
+ ],
+ },
})
const renderListArgs = node.children.arguments
return {
source: renderListArgs[0] as SimpleExpressionNode,
params: (renderListArgs[1] as any).params,
returns: (renderListArgs[1] as any).returns,
- innerVNodeCall: customReturn ? null : (renderListArgs[1] as any).returns
+ innerVNodeCall: customReturn
+ ? null
+ : (renderListArgs[1] as any).returns,
}
}
test('basic v-for', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(' ')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
innerVNodeCall: {
- tag: `"span"`
- }
+ tag: `"span"`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -720,11 +750,11 @@ describe('compiler: v-for', () => {
test('value + key + index', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(' ')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
- params: [{ content: `item` }, { content: `key` }, { content: `index` }]
+ params: [{ content: `item` }, { content: `key` }, { content: `index` }],
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -732,11 +762,11 @@ describe('compiler: v-for', () => {
test('skipped value', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(' ')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
- params: [{ content: `_` }, { content: `key` }, { content: `index` }]
+ params: [{ content: `_` }, { content: `key` }, { content: `index` }],
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -744,11 +774,11 @@ describe('compiler: v-for', () => {
test('skipped key', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(' ')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
- params: [{ content: `item` }, { content: `__` }, { content: `index` }]
+ params: [{ content: `item` }, { content: `__` }, { content: `index` }],
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -756,11 +786,11 @@ describe('compiler: v-for', () => {
test('skipped value & key', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(' ')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
- params: [{ content: `_` }, { content: `__` }, { content: `index` }]
+ params: [{ content: `_` }, { content: `__` }, { content: `index` }],
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -768,9 +798,9 @@ describe('compiler: v-for', () => {
test('v-for with constant expression', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform('{{item}}
', {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(
@@ -778,8 +808,8 @@ describe('compiler: v-for', () => {
codegenNode,
false /* keyed */,
false /* customReturn */,
- false /* disableTracking */
- )
+ false /* disableTracking */,
+ ),
).toMatchObject({
source: { content: `10`, constType: ConstantTypes.CAN_STRINGIFY },
params: [{ content: `item` }],
@@ -793,11 +823,11 @@ describe('compiler: v-for', () => {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item',
isStatic: false,
- constType: ConstantTypes.NOT_CONSTANT
- }
+ constType: ConstantTypes.NOT_CONSTANT,
+ },
},
- patchFlag: genFlagText(PatchFlags.TEXT)
- }
+ patchFlag: PatchFlags.TEXT,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -805,9 +835,9 @@ describe('compiler: v-for', () => {
test('template v-for', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
- 'hello '
+ 'hello ',
)
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
@@ -818,10 +848,10 @@ describe('compiler: v-for', () => {
isBlock: true,
children: [
{ type: NodeTypes.TEXT, content: `hello` },
- { type: NodeTypes.ELEMENT, tag: `span` }
+ { type: NodeTypes.ELEMENT, tag: `span` },
],
- patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT)
- }
+ patchFlag: PatchFlags.STABLE_FRAGMENT,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -829,19 +859,19 @@ describe('compiler: v-for', () => {
test('template v-for w/ ', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
- ' '
+ ' ',
)
expect(
- assertSharedCodegen(codegenNode, false, true /* custom return */)
+ assertSharedCodegen(codegenNode, false, true /* custom return */),
).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
returns: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: RENDER_SLOT
- }
+ callee: RENDER_SLOT,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -850,9 +880,9 @@ describe('compiler: v-for', () => {
test('template v-for key injection with single child', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
- ' '
+ ' ',
)
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
@@ -862,9 +892,9 @@ describe('compiler: v-for', () => {
tag: `"span"`,
props: createObjectMatcher({
key: '[item.id]',
- id: '[item.id]'
- })
- }
+ id: '[item.id]',
+ }),
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -872,17 +902,17 @@ describe('compiler: v-for', () => {
test('v-for on ', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(' ')
expect(
- assertSharedCodegen(codegenNode, false, true /* custom return */)
+ assertSharedCodegen(codegenNode, false, true /* custom return */),
).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
returns: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: RENDER_SLOT
- }
+ callee: RENDER_SLOT,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -890,7 +920,7 @@ describe('compiler: v-for', () => {
test('keyed v-for', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(' ')
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
@@ -898,9 +928,9 @@ describe('compiler: v-for', () => {
innerVNodeCall: {
tag: `"span"`,
props: createObjectMatcher({
- key: `[item]`
- })
- }
+ key: `[item]`,
+ }),
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -908,9 +938,9 @@ describe('compiler: v-for', () => {
test('keyed template v-for', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(
- 'hello '
+ 'hello ',
)
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
@@ -918,14 +948,14 @@ describe('compiler: v-for', () => {
innerVNodeCall: {
tag: FRAGMENT,
props: createObjectMatcher({
- key: `[item]`
+ key: `[item]`,
}),
children: [
{ type: NodeTypes.TEXT, content: `hello` },
- { type: NodeTypes.ELEMENT, tag: `span` }
+ { type: NodeTypes.ELEMENT, tag: `span` },
],
- patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT)
- }
+ patchFlag: PatchFlags.STABLE_FRAGMENT,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -933,7 +963,7 @@ describe('compiler: v-for', () => {
test('v-if + v-for', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(`
`)
expect(codegenNode).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@@ -941,11 +971,11 @@ describe('compiler: v-for', () => {
consequent: {
type: NodeTypes.VNODE_CALL,
props: createObjectMatcher({
- key: `[0]`
+ key: `[0]`,
}),
isBlock: true,
disableTracking: true,
- patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
+ patchFlag: PatchFlags.UNKEYED_FRAGMENT,
children: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST,
@@ -957,12 +987,12 @@ describe('compiler: v-for', () => {
returns: {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
- isBlock: true
- }
- }
- ]
- }
- }
+ isBlock: true,
+ },
+ },
+ ],
+ },
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -971,7 +1001,7 @@ describe('compiler: v-for', () => {
test('v-if + v-for on ', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform(` `)
expect(codegenNode).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@@ -979,11 +1009,11 @@ describe('compiler: v-for', () => {
consequent: {
type: NodeTypes.VNODE_CALL,
props: createObjectMatcher({
- key: `[0]`
+ key: `[0]`,
}),
isBlock: true,
disableTracking: true,
- patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
+ patchFlag: PatchFlags.UNKEYED_FRAGMENT,
children: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST,
@@ -995,12 +1025,12 @@ describe('compiler: v-for', () => {
returns: {
type: NodeTypes.VNODE_CALL,
tag: FRAGMENT,
- isBlock: true
- }
- }
- ]
- }
- }
+ isBlock: true,
+ },
+ },
+ ],
+ },
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -1008,14 +1038,40 @@ describe('compiler: v-for', () => {
test('v-for on element with custom directive', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithForTransform('
')
const { returns } = assertSharedCodegen(codegenNode, false, true)
expect(returns).toMatchObject({
type: NodeTypes.VNODE_CALL,
- directives: { type: NodeTypes.JS_ARRAY_EXPRESSION }
+ directives: { type: NodeTypes.JS_ARRAY_EXPRESSION },
})
expect(generate(root).code).toMatchSnapshot()
})
+
+ test('template v-for key w/ :key shorthand on div', () => {
+ const {
+ node: { codegenNode },
+ } = parseWithForTransform('test
')
+ expect(codegenNode.patchFlag).toBe(PatchFlags.KEYED_FRAGMENT)
+ })
+
+ test('template v-for key w/ :key shorthand on template injected to the child', () => {
+ const {
+ node: { codegenNode },
+ } = parseWithForTransform(
+ 'test
',
+ )
+ expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
+ source: { content: `keys` },
+ params: [{ content: `key` }],
+ innerVNodeCall: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: createObjectMatcher({
+ key: '[key]',
+ }),
+ },
+ })
+ })
})
})
diff --git a/packages/compiler-core/__tests__/transforms/vIf.spec.ts b/packages/compiler-core/__tests__/transforms/vIf.spec.ts
index 5c36885670f..2c2fedab0d5 100644
--- a/packages/compiler-core/__tests__/transforms/vIf.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vIf.spec.ts
@@ -1,29 +1,29 @@
-import { baseParse as parse } from '../../src/parse'
+import { baseParse as parse } from '../../src/parser'
import { transform } from '../../src/transform'
import { transformIf } from '../../src/transforms/vIf'
import { transformElement } from '../../src/transforms/transformElement'
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
import {
- CommentNode,
- ConditionalExpression,
- ElementNode,
+ type CommentNode,
+ type ConditionalExpression,
+ type ElementNode,
ElementTypes,
- IfBranchNode,
- IfConditionalExpression,
- IfNode,
+ type IfBranchNode,
+ type IfConditionalExpression,
+ type IfNode,
NodeTypes,
- SimpleExpressionNode,
- TextNode,
- VNodeCall
+ type SimpleExpressionNode,
+ type TextNode,
+ type VNodeCall,
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
-import { CompilerOptions, generate, TO_HANDLERS } from '../../src'
+import { type CompilerOptions, TO_HANDLERS, generate } from '../../src'
import {
CREATE_COMMENT,
FRAGMENT,
MERGE_PROPS,
NORMALIZE_PROPS,
- RENDER_SLOT
+ RENDER_SLOT,
} from '../../src/runtimeHelpers'
import { createObjectMatcher } from '../testUtils'
@@ -31,12 +31,12 @@ function parseWithIfTransform(
template: string,
options: CompilerOptions = {},
returnIndex: number = 0,
- childrenLen: number = 1
+ childrenLen: number = 1,
) {
const ast = parse(template, options)
transform(ast, {
nodeTransforms: [transformIf, transformSlotOutlet, transformElement],
- ...options
+ ...options,
})
if (!options.onError) {
expect(ast.children.length).toBe(childrenLen)
@@ -48,7 +48,7 @@ function parseWithIfTransform(
root: ast,
node: ast.children[returnIndex] as IfNode & {
codegenNode: IfConditionalExpression
- }
+ },
}
}
@@ -59,7 +59,7 @@ describe('compiler: v-if', () => {
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1)
expect((node.branches[0].condition as SimpleExpressionNode).content).toBe(
- `ok`
+ `ok`,
)
expect(node.branches[0].children.length).toBe(1)
expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT)
@@ -68,12 +68,12 @@ describe('compiler: v-if', () => {
test('template v-if', () => {
const { node } = parseWithIfTransform(
- `
hello
`
+ `
hello
`,
)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1)
expect((node.branches[0].condition as SimpleExpressionNode).content).toBe(
- `ok`
+ `ok`,
)
expect(node.branches[0].children.length).toBe(3)
expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT)
@@ -89,16 +89,16 @@ describe('compiler: v-if', () => {
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1)
expect((node.branches[0].children[0] as ElementNode).tag).toBe(
- `Component`
+ `Component`,
)
expect((node.branches[0].children[0] as ElementNode).tagType).toBe(
- ElementTypes.COMPONENT
+ ElementTypes.COMPONENT,
)
// #2058 since a component may fail to resolve and fallback to a plain
// element, it still needs to be made a block
expect(
((node.branches[0].children[0] as ElementNode)!
- .codegenNode as VNodeCall)!.isBlock
+ .codegenNode as VNodeCall)!.isBlock,
).toBe(true)
})
@@ -122,7 +122,7 @@ describe('compiler: v-if', () => {
test('v-if + v-else-if', () => {
const { node } = parseWithIfTransform(
- `
`
+ `
`,
)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(2)
@@ -142,7 +142,7 @@ describe('compiler: v-if', () => {
test('v-if + v-else-if + v-else', () => {
const { node } = parseWithIfTransform(
- `
fine `
+ `
fine `,
)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(3)
@@ -202,125 +202,125 @@ describe('compiler: v-if', () => {
test('should prefix v-if condition', () => {
const { node } = parseWithIfTransform(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(node.branches[0].condition).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_ctx.ok`
+ content: `_ctx.ok`,
})
})
})
describe('errors', () => {
test('error on v-else missing adjacent v-if', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
const { node: node1 } = parseWithIfTransform(`
`, { onError })
expect(onError.mock.calls[0]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node1.loc
- }
+ loc: node1.loc,
+ },
])
const { node: node2 } = parseWithIfTransform(
`
`,
{ onError },
- 1
+ 1,
)
expect(onError.mock.calls[1]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node2.loc
- }
+ loc: node2.loc,
+ },
])
const { node: node3 } = parseWithIfTransform(
`
foo
`,
{ onError },
- 2
+ 2,
)
expect(onError.mock.calls[2]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node3.loc
- }
+ loc: node3.loc,
+ },
])
})
test('error on v-else-if missing adjacent v-if or v-else-if', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
const { node: node1 } = parseWithIfTransform(`
`, {
- onError
+ onError,
})
expect(onError.mock.calls[0]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node1.loc
- }
+ loc: node1.loc,
+ },
])
const { node: node2 } = parseWithIfTransform(
`
`,
{ onError },
- 1
+ 1,
)
expect(onError.mock.calls[1]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node2.loc
- }
+ loc: node2.loc,
+ },
])
const { node: node3 } = parseWithIfTransform(
`
foo
`,
{ onError },
- 2
+ 2,
)
expect(onError.mock.calls[2]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: node3.loc
- }
+ loc: node3.loc,
+ },
])
const {
- node: { branches }
+ node: { branches },
} = parseWithIfTransform(
`
`,
{ onError },
- 0
+ 0,
)
expect(onError.mock.calls[3]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
- loc: branches[branches.length - 1].loc
- }
+ loc: branches[branches.length - 1].loc,
+ },
])
})
test('error on user key', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
// dynamic
parseWithIfTransform(
`
`,
- { onError }
+ { onError },
)
expect(onError.mock.calls[0]).toMatchObject([
{
- code: ErrorCodes.X_V_IF_SAME_KEY
- }
+ code: ErrorCodes.X_V_IF_SAME_KEY,
+ },
])
// static
parseWithIfTransform(`
`, {
- onError
+ onError,
})
expect(onError.mock.calls[1]).toMatchObject([
{
- code: ErrorCodes.X_V_IF_SAME_KEY
- }
+ code: ErrorCodes.X_V_IF_SAME_KEY,
+ },
])
})
})
@@ -329,63 +329,63 @@ describe('compiler: v-if', () => {
function assertSharedCodegen(
node: IfConditionalExpression,
depth: number = 0,
- hasElse: boolean = false
+ hasElse: boolean = false,
) {
expect(node).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
- content: `ok`
+ content: `ok`,
},
consequent: {
type: NodeTypes.VNODE_CALL,
- isBlock: true
+ isBlock: true,
},
alternate:
depth < 1
? hasElse
? {
type: NodeTypes.VNODE_CALL,
- isBlock: true
+ isBlock: true,
}
: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_COMMENT
+ callee: CREATE_COMMENT,
}
: {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
- content: `orNot`
+ content: `orNot`,
},
consequent: {
type: NodeTypes.VNODE_CALL,
- isBlock: true
+ isBlock: true,
},
alternate: hasElse
? {
type: NodeTypes.VNODE_CALL,
- isBlock: true
+ isBlock: true,
}
: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_COMMENT
- }
- }
+ callee: CREATE_COMMENT,
+ },
+ },
})
}
test('basic v-if', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`
`)
assertSharedCodegen(codegenNode)
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
expect(codegenNode.alternate).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_COMMENT
+ callee: CREATE_COMMENT,
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -393,7 +393,7 @@ describe('compiler: v-if', () => {
test('template v-if', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`
hello
`)
assertSharedCodegen(codegenNode)
expect(codegenNode.consequent).toMatchObject({
@@ -402,12 +402,12 @@ describe('compiler: v-if', () => {
children: [
{ type: NodeTypes.ELEMENT, tag: 'div' },
{ type: NodeTypes.TEXT, content: `hello` },
- { type: NodeTypes.ELEMENT, tag: 'p' }
- ]
+ { type: NodeTypes.ELEMENT, tag: 'p' },
+ ],
})
expect(codegenNode.alternate).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_COMMENT
+ callee: CREATE_COMMENT,
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -415,12 +415,12 @@ describe('compiler: v-if', () => {
test('template v-if w/ single child', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(` `)
expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
+ arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })],
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -428,12 +428,12 @@ describe('compiler: v-if', () => {
test('v-if on ', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(` `)
expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
- arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
+ arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })],
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -441,16 +441,16 @@ describe('compiler: v-if', () => {
test('v-if + v-else', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`
`)
assertSharedCodegen(codegenNode, 0, true)
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
expect(codegenNode.alternate).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -458,17 +458,17 @@ describe('compiler: v-if', () => {
test('v-if + v-else-if', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`
`)
assertSharedCodegen(codegenNode, 1)
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
const branch2 = codegenNode.alternate as ConditionalExpression
expect(branch2.consequent).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -476,19 +476,19 @@ describe('compiler: v-if', () => {
test('v-if + v-else-if + v-else', () => {
const {
root,
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(
- `
fine `
+ `
fine `,
)
assertSharedCodegen(codegenNode, 1, true)
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
const branch2 = codegenNode.alternate as ConditionalExpression
expect(branch2.consequent).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
expect(branch2.alternate).toMatchObject({
tag: FRAGMENT,
@@ -496,9 +496,9 @@ describe('compiler: v-if', () => {
children: [
{
type: NodeTypes.TEXT,
- content: `fine`
- }
- ]
+ content: `fine`,
+ },
+ ],
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -508,7 +508,7 @@ describe('compiler: v-if', () => {
`
`,
{},
0 /* returnIndex, just give the default value */,
- 2 /* childrenLen */
+ 2 /* childrenLen */,
)
const ifNode = root.children[0] as IfNode & {
@@ -516,14 +516,14 @@ describe('compiler: v-if', () => {
}
expect(ifNode.codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
const ifNode2 = root.children[1] as IfNode & {
codegenNode: IfConditionalExpression
}
expect(ifNode2.codegenNode.consequent).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -533,41 +533,41 @@ describe('compiler: v-if', () => {
`
`,
{},
0 /* returnIndex, just give the default value */,
- 2 /* childrenLen */
+ 2 /* childrenLen */,
)
const ifNode = root.children[0] as IfNode & {
codegenNode: IfConditionalExpression
}
expect(ifNode.codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
expect(ifNode.codegenNode.alternate).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
const ifNode2 = root.children[1] as IfNode & {
codegenNode: IfConditionalExpression
}
expect(ifNode2.codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[2]` })
+ props: createObjectMatcher({ key: `[2]` }),
})
const branch = ifNode2.codegenNode.alternate as IfConditionalExpression
expect(branch.consequent).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[3]` })
+ props: createObjectMatcher({ key: `[3]` }),
})
expect(branch.alternate).toMatchObject({
tag: `"p"`,
- props: createObjectMatcher({ key: `[4]` })
+ props: createObjectMatcher({ key: `[4]` }),
})
expect(generate(root).code).toMatchSnapshot()
})
test('key injection (only v-bind)', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`
`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
@@ -577,15 +577,18 @@ describe('compiler: v-if', () => {
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
- arguments: [createObjectMatcher({ key: `[0]` }), { content: `obj` }]
- }
- ]
+ arguments: [
+ createObjectMatcher({ key: `[0]` }),
+ { content: `obj` },
+ ],
+ },
+ ],
})
})
test('key injection (before v-bind)', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`
`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
@@ -594,16 +597,16 @@ describe('compiler: v-if', () => {
arguments: [
createObjectMatcher({
key: '[0]',
- id: 'foo'
+ id: 'foo',
}),
- { content: `obj` }
- ]
+ { content: `obj` },
+ ],
})
})
test('key injection (after v-bind)', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`
`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
@@ -613,39 +616,57 @@ describe('compiler: v-if', () => {
createObjectMatcher({ key: `[0]` }),
{ content: `obj` },
createObjectMatcher({
- id: 'foo'
- })
- ]
+ id: 'foo',
+ }),
+ ],
})
})
test('key injection (w/ custom directive)', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(`
`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.directives).not.toBeUndefined()
expect(branch1.props).toMatchObject(createObjectMatcher({ key: `[0]` }))
})
+ // #6631
+ test('avoid duplicate keys', () => {
+ const {
+ node: { codegenNode },
+ } = parseWithIfTransform(`
`)
+ const branch1 = codegenNode.consequent as VNodeCall
+ expect(branch1.props).toMatchObject({
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: MERGE_PROPS,
+ arguments: [
+ createObjectMatcher({
+ key: 'custom_key',
+ }),
+ { content: `obj` },
+ ],
+ })
+ })
+
test('with spaces between branches', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(
- `
`
+ `
`,
)
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[0]` })
+ props: createObjectMatcher({ key: `[0]` }),
})
const branch = codegenNode.alternate as ConditionalExpression
expect(branch.consequent).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[1]` })
+ props: createObjectMatcher({ key: `[1]` }),
})
expect(branch.alternate).toMatchObject({
tag: `"div"`,
- props: createObjectMatcher({ key: `[2]` })
+ props: createObjectMatcher({ key: `[2]` }),
})
})
@@ -694,25 +715,46 @@ describe('compiler: v-if', () => {
expect(b1.children[3].type).toBe(NodeTypes.ELEMENT)
expect((b1.children[3] as ElementNode).tag).toBe(`p`)
})
+
+ // #6843
+ test('should parse correctly with comments: true in prod', () => {
+ __DEV__ = false
+ parseWithIfTransform(
+ `
+
+
+
+
+
+
+
+
+
+
+ `,
+ { comments: true },
+ )
+ __DEV__ = true
+ })
})
test('v-on with v-if', () => {
const {
- node: { codegenNode }
+ node: { codegenNode },
} = parseWithIfTransform(
- `w/ v-if `
+ `w/ v-if `,
)
expect((codegenNode.consequent as any).props.type).toBe(
- NodeTypes.JS_CALL_EXPRESSION
+ NodeTypes.JS_CALL_EXPRESSION,
)
expect((codegenNode.consequent as any).props.callee).toBe(MERGE_PROPS)
expect(
(codegenNode.consequent as any).props.arguments[0].properties[0].value
- .content
+ .content,
).toBe('0')
expect((codegenNode.consequent as any).props.arguments[1].callee).toBe(
- TO_HANDLERS
+ TO_HANDLERS,
)
})
})
diff --git a/packages/compiler-core/__tests__/transforms/vMemo.spec.ts b/packages/compiler-core/__tests__/transforms/vMemo.spec.ts
index 1b259f7ca68..41e7d922ebe 100644
--- a/packages/compiler-core/__tests__/transforms/vMemo.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vMemo.spec.ts
@@ -4,7 +4,7 @@ describe('compiler: v-memo transform', () => {
function compile(content: string) {
return baseCompile(`${content}
`, {
mode: 'module',
- prefixIdentifiers: true
+ prefixIdentifiers: true,
}).code
}
@@ -12,8 +12,8 @@ describe('compiler: v-memo transform', () => {
expect(
baseCompile(`
`, {
mode: 'module',
- prefixIdentifiers: true
- }).code
+ prefixIdentifiers: true,
+ }).code,
).toMatchSnapshot()
})
@@ -29,8 +29,8 @@ describe('compiler: v-memo transform', () => {
expect(
compile(
`foo bar
- `
- )
+ `,
+ ),
).toMatchSnapshot()
})
@@ -39,8 +39,8 @@ describe('compiler: v-memo transform', () => {
compile(
`
foobar
-
`
- )
+ `,
+ ),
).toMatchSnapshot()
})
@@ -49,8 +49,16 @@ describe('compiler: v-memo transform', () => {
compile(
`
foobar
- `
- )
+ `,
+ ),
+ ).toMatchSnapshot()
+ })
+
+ test('element v-for key expression prefixing + v-memo', () => {
+ expect(
+ compile(
+ `
`,
+ ),
).toMatchSnapshot()
})
})
diff --git a/packages/compiler-core/__tests__/transforms/vModel.spec.ts b/packages/compiler-core/__tests__/transforms/vModel.spec.ts
index 96dbe2cb5fb..82dd4909fd6 100644
--- a/packages/compiler-core/__tests__/transforms/vModel.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vModel.spec.ts
@@ -1,16 +1,17 @@
import {
+ BindingTypes,
+ type CompilerOptions,
+ type ComponentNode,
+ type ElementNode,
+ type ForNode,
+ NORMALIZE_PROPS,
+ NodeTypes,
+ type ObjectExpression,
+ type PlainElementNode,
+ type VNodeCall,
+ generate,
baseParse as parse,
transform,
- generate,
- ElementNode,
- ObjectExpression,
- CompilerOptions,
- ForNode,
- PlainElementNode,
- ComponentNode,
- NodeTypes,
- VNodeCall,
- NORMALIZE_PROPS
} from '../../src'
import { ErrorCodes } from '../../src/errors'
import { transformModel } from '../../src/transforms/vModel'
@@ -18,7 +19,7 @@ import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression'
import { transformFor } from '../../src/transforms/vFor'
import { trackSlotScopes } from '../../src/transforms/vSlot'
-import { CallExpression } from '@babel/types'
+import type { CallExpression } from '@babel/types'
function parseWithVModel(template: string, options: CompilerOptions = {}) {
const ast = parse(template)
@@ -28,13 +29,13 @@ function parseWithVModel(template: string, options: CompilerOptions = {}) {
transformFor,
transformExpression,
transformElement,
- trackSlotScopes
+ trackSlotScopes,
],
directiveTransforms: {
...options.directiveTransforms,
- model: transformModel
+ model: transformModel,
},
- ...options
+ ...options,
})
return ast
@@ -50,29 +51,29 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({
key: {
content: 'modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'model',
- isStatic: false
- }
+ isStatic: false,
+ },
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
'$event => ((',
{
content: 'model',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root).code).toMatchSnapshot()
@@ -80,7 +81,7 @@ describe('compiler: transform v-model', () => {
test('simple expression (with prefixIdentifiers)', () => {
const root = parseWithVModel('
', {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
@@ -89,29 +90,29 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({
key: {
content: 'modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
content: '_ctx.model',
- isStatic: false
- }
+ isStatic: false,
+ },
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
'$event => ((',
{
content: '_ctx.model',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
@@ -127,29 +128,29 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({
key: {
content: 'modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
content: '\n model\n.\nfoo \n',
- isStatic: false
- }
+ isStatic: false,
+ },
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
'$event => ((',
{
content: '\n model\n.\nfoo \n',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root).code).toMatchSnapshot()
@@ -164,29 +165,29 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({
key: {
content: 'modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
content: 'model[index]',
- isStatic: false
- }
+ isStatic: false,
+ },
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
'$event => ((',
{
content: 'model[index]',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root).code).toMatchSnapshot()
@@ -194,7 +195,7 @@ describe('compiler: transform v-model', () => {
test('compound expression (with prefixIdentifiers)', () => {
const root = parseWithVModel('
', {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
@@ -203,28 +204,28 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({
key: {
content: 'modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
{
content: '_ctx.model',
- isStatic: false
+ isStatic: false,
},
'[',
{
content: '_ctx.index',
- isStatic: false
+ isStatic: false,
},
- ']'
- ]
- }
+ ']',
+ ],
+ },
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:modelValue',
- isStatic: true
+ isStatic: true,
},
value: {
children: [
@@ -233,55 +234,55 @@ describe('compiler: transform v-model', () => {
children: [
{
content: '_ctx.model',
- isStatic: false
+ isStatic: false,
},
'[',
{
content: '_ctx.index',
- isStatic: false
+ isStatic: false,
},
- ']'
- ]
+ ']',
+ ],
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
})
test('with argument', () => {
- const root = parseWithVModel('
')
+ const root = parseWithVModel('
')
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
- content: 'value',
- isStatic: true
+ content: 'foo-value',
+ isStatic: true,
},
value: {
content: 'model',
- isStatic: false
- }
+ isStatic: false,
+ },
})
expect(props[1]).toMatchObject({
key: {
- content: 'onUpdate:value',
- isStatic: true
+ content: 'onUpdate:fooValue',
+ isStatic: true,
},
value: {
children: [
'$event => ((',
{
content: 'model',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
+ ') = $event)',
+ ],
+ },
})
expect(generate(root).code).toMatchSnapshot()
@@ -303,12 +304,12 @@ describe('compiler: transform v-model', () => {
{
key: {
content: 'value',
- isStatic: false
+ isStatic: false,
},
value: {
content: 'model',
- isStatic: false
- }
+ isStatic: false,
+ },
},
{
key: {
@@ -316,24 +317,24 @@ describe('compiler: transform v-model', () => {
'"onUpdate:" + ',
{
content: 'value',
- isStatic: false
- }
- ]
+ isStatic: false,
+ },
+ ],
},
value: {
children: [
'$event => ((',
{
content: 'model',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
- }
- ]
- }
- ]
+ ') = $event)',
+ ],
+ },
+ },
+ ],
+ },
+ ],
})
expect(generate(root).code).toMatchSnapshot()
@@ -341,7 +342,7 @@ describe('compiler: transform v-model', () => {
test('with dynamic argument (with prefixIdentifiers)', () => {
const root = parseWithVModel('
', {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const node = root.children[0] as ElementNode
const props = (node.codegenNode as VNodeCall)
@@ -357,12 +358,12 @@ describe('compiler: transform v-model', () => {
{
key: {
content: '_ctx.value',
- isStatic: false
+ isStatic: false,
},
value: {
content: '_ctx.model',
- isStatic: false
- }
+ isStatic: false,
+ },
},
{
key: {
@@ -370,24 +371,24 @@ describe('compiler: transform v-model', () => {
'"onUpdate:" + ',
{
content: '_ctx.value',
- isStatic: false
- }
- ]
+ isStatic: false,
+ },
+ ],
},
value: {
children: [
'$event => ((',
{
content: '_ctx.model',
- isStatic: false
+ isStatic: false,
},
- ') = $event)'
- ]
- }
- }
- ]
- }
- ]
+ ') = $event)',
+ ],
+ },
+ },
+ ],
+ },
+ ],
})
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
@@ -396,15 +397,15 @@ describe('compiler: transform v-model', () => {
test('should cache update handler w/ cacheHandlers: true', () => {
const root = parseWithVModel('
', {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const codegen = (root.children[0] as PlainElementNode)
.codegenNode as VNodeCall
// should not list cached prop in dynamicProps
expect(codegen.dynamicProps).toBe(`["modelValue"]`)
expect((codegen.props as ObjectExpression).properties[1].value.type).toBe(
- NodeTypes.JS_CACHE_EXPRESSION
+ NodeTypes.JS_CACHE_EXPRESSION,
)
})
@@ -413,34 +414,34 @@ describe('compiler: transform v-model', () => {
'
',
{
prefixIdentifiers: true,
- cacheHandlers: true
- }
+ cacheHandlers: true,
+ },
)
- expect(root.cached).toBe(0)
+ expect(root.cached.length).toBe(0)
const codegen = (
(root.children[0] as ForNode).children[0] as PlainElementNode
).codegenNode as VNodeCall
expect(codegen.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
expect(
- (codegen.props as ObjectExpression).properties[1].value.type
+ (codegen.props as ObjectExpression).properties[1].value.type,
).not.toBe(NodeTypes.JS_CACHE_EXPRESSION)
})
test('should not cache update handler if it inside v-once', () => {
const root = parseWithVModel('
', {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
expect(root.cached).not.toBe(2)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
})
test('should mark update handler dynamic if it refers slot scope variables', () => {
const root = parseWithVModel(
'
',
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
const codegen = (
(root.children[0] as ComponentNode).children[0] as PlainElementNode
@@ -450,7 +451,7 @@ describe('compiler: transform v-model', () => {
test('should generate modelModifiers for component v-model', () => {
const root = parseWithVModel('
', {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall
@@ -461,9 +462,12 @@ describe('compiler: transform v-model', () => {
{ key: { content: `onUpdate:modelValue` } },
{
key: { content: 'modelModifiers' },
- value: { content: `{ trim: true, "bar-baz": true }`, isStatic: false }
- }
- ]
+ value: {
+ content: `{ trim: true, "bar-baz": true }`,
+ isStatic: false,
+ },
+ },
+ ],
})
// should NOT include modelModifiers in dynamicPropNames because it's never
// gonna change
@@ -474,8 +478,8 @@ describe('compiler: transform v-model', () => {
const root = parseWithVModel(
'
',
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall
@@ -486,79 +490,96 @@ describe('compiler: transform v-model', () => {
{ key: { content: `onUpdate:foo` } },
{
key: { content: 'fooModifiers' },
- value: { content: `{ trim: true }`, isStatic: false }
+ value: { content: `{ trim: true }`, isStatic: false },
},
{ key: { content: `bar` } },
{ key: { content: `onUpdate:bar` } },
{
key: { content: 'barModifiers' },
- value: { content: `{ number: true }`, isStatic: false }
- }
- ]
+ value: { content: `{ number: true }`, isStatic: false },
+ },
+ ],
})
// should NOT include modelModifiers in dynamicPropNames because it's never
// gonna change
expect(vnodeCall.dynamicProps).toBe(
- `["foo", "onUpdate:foo", "bar", "onUpdate:bar"]`
+ `["foo", "onUpdate:foo", "bar", "onUpdate:bar"]`,
)
})
describe('errors', () => {
test('missing expression', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithVModel('
', { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_MODEL_NO_EXPRESSION
- })
+ code: ErrorCodes.X_V_MODEL_NO_EXPRESSION,
+ }),
)
})
test('empty expression', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithVModel('
', { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION,
+ }),
)
})
test('mal-formed expression', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithVModel('
', { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION
- })
+ code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION,
+ }),
)
})
test('allow unicode', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithVModel('
', { onError })
expect(onError).toHaveBeenCalledTimes(0)
})
test('used on scope variable', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithVModel('
', {
onError,
- prefixIdentifiers: true
+ prefixIdentifiers: true,
+ })
+
+ expect(onError).toHaveBeenCalledTimes(1)
+ expect(onError).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE,
+ }),
+ )
+ })
+
+ test('used on props', () => {
+ const onError = vi.fn()
+ parseWithVModel('
', {
+ onError,
+ bindingMetadata: {
+ p: BindingTypes.PROPS,
+ },
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE
- })
+ code: ErrorCodes.X_V_MODEL_ON_PROPS,
+ }),
)
})
})
diff --git a/packages/compiler-core/__tests__/transforms/vOn.spec.ts b/packages/compiler-core/__tests__/transforms/vOn.spec.ts
index 8f943a7491f..9fda0259585 100644
--- a/packages/compiler-core/__tests__/transforms/vOn.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vOn.spec.ts
@@ -1,15 +1,16 @@
import {
- baseParse as parse,
- CompilerOptions,
- ElementNode,
+ type CompilerOptions,
+ type ElementNode,
ErrorCodes,
+ NodeTypes,
+ type ObjectExpression,
TO_HANDLER_KEY,
+ type VNodeCall,
helperNameMap,
- NodeTypes,
- ObjectExpression,
+ baseParse as parse,
transform,
- VNodeCall
} from '../../src'
+import { transformFor } from '../../src/transforms/vFor'
import { transformOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression'
@@ -17,15 +18,15 @@ import { transformExpression } from '../../src/transforms/transformExpression'
function parseWithVOn(template: string, options: CompilerOptions = {}) {
const ast = parse(template, options)
transform(ast, {
- nodeTransforms: [transformExpression, transformElement],
+ nodeTransforms: [transformExpression, transformElement, transformFor],
directiveTransforms: {
- on: transformOn
+ on: transformOn,
},
- ...options
+ ...options,
})
return {
root: ast,
- node: ast.children[0] as ElementNode
+ node: ast.children[0] as ElementNode,
}
}
@@ -41,13 +42,13 @@ describe('compiler: transform v-on', () => {
loc: {
start: {
line: 1,
- column: 11
+ column: 11,
},
end: {
line: 1,
- column: 16
- }
- }
+ column: 16,
+ },
+ },
},
value: {
content: `onClick`,
@@ -55,16 +56,16 @@ describe('compiler: transform v-on', () => {
loc: {
start: {
line: 1,
- column: 18
+ column: 18,
},
end: {
line: 1,
- column: 25
- }
- }
- }
- }
- ]
+ column: 25,
+ },
+ },
+ },
+ },
+ ],
})
})
@@ -78,22 +79,22 @@ describe('compiler: transform v-on', () => {
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: `event` },
- `)`
- ]
+ `)`,
+ ],
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
})
test('dynamic arg with prefixing', () => {
const { node } = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@@ -103,22 +104,22 @@ describe('compiler: transform v-on', () => {
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: `_ctx.event` },
- `)`
- ]
+ `)`,
+ ],
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
})
test('dynamic arg with complex exp prefixing', () => {
const { node } = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@@ -131,16 +132,16 @@ describe('compiler: transform v-on', () => {
`(`,
{ content: `_ctx.foo` },
`)`,
- `)`
- ]
+ `)`,
+ ],
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
})
@@ -152,10 +153,10 @@ describe('compiler: transform v-on', () => {
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`$event => (`, { content: `i++` }, `)`]
- }
- }
- ]
+ children: [`$event => (`, { content: `i++` }, `)`],
+ },
+ },
+ ],
})
})
@@ -170,10 +171,10 @@ describe('compiler: transform v-on', () => {
// should wrap with `{` for multiple statements
// in this case the return value is discarded and the behavior is
// consistent with 2.x
- children: [`$event => {`, { content: `foo();bar()` }, `}`]
- }
- }
- ]
+ children: [`$event => {`, { content: `foo();bar()` }, `}`],
+ },
+ },
+ ],
})
})
@@ -188,16 +189,16 @@ describe('compiler: transform v-on', () => {
// should wrap with `{` for multiple statements
// in this case the return value is discarded and the behavior is
// consistent with 2.x
- children: [`$event => {`, { content: `\nfoo();\nbar()\n` }, `}`]
- }
- }
- ]
+ children: [`$event => {`, { content: `\nfoo();\nbar()\n` }, `}`],
+ },
+ },
+ ],
})
})
test('inline statement w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@@ -214,20 +215,20 @@ describe('compiler: transform v-on', () => {
`(`,
// should NOT prefix $event
{ content: `$event` },
- `)`
- ]
+ `)`,
+ ],
},
- `)`
- ]
- }
- }
- ]
+ `)`,
+ ],
+ },
+ },
+ ],
})
})
test('multiple inline statements w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@@ -245,14 +246,14 @@ describe('compiler: transform v-on', () => {
{ content: `$event` },
`);`,
{ content: `_ctx.bar` },
- `()`
- ]
+ `()`,
+ ],
},
- `}`
- ]
- }
- }
- ]
+ `}`,
+ ],
+ },
+ },
+ ],
})
})
@@ -264,10 +265,57 @@ describe('compiler: transform v-on', () => {
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `$event => foo($event)`
- }
- }
- ]
+ content: `$event => foo($event)`,
+ },
+ },
+ ],
+ })
+ })
+
+ test('should NOT wrap as function if expression is already function expression (with TypeScript)', () => {
+ const { node } = parseWithVOn(`
foo(e)"/>`)
+ expect((node.codegenNode as VNodeCall).props).toMatchObject({
+ properties: [
+ {
+ key: { content: `onClick` },
+ value: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `(e: any): any => foo(e)`,
+ },
+ },
+ ],
+ })
+
+ const { node: node2 } = parseWithVOn(
+ `
foo(e)"/>`,
+ )
+ expect((node2.codegenNode as VNodeCall).props).toMatchObject({
+ properties: [
+ {
+ key: { content: `onClick` },
+ value: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `(e: (number | string)[]) => foo(e)`,
+ },
+ },
+ ],
+ })
+ })
+
+ test('should NOT wrap as function if expression is already function expression (async)', () => {
+ const { node } = parseWithVOn(
+ `
await foo($event)"/>`,
+ )
+ expect((node.codegenNode as VNodeCall).props).toMatchObject({
+ properties: [
+ {
+ key: { content: `onClick` },
+ value: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `async $event => await foo($event)`,
+ },
+ },
+ ],
})
})
@@ -277,7 +325,7 @@ describe('compiler: transform v-on', () => {
$event => {
foo($event)
}
- "/>`
+ "/>`,
)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@@ -289,10 +337,10 @@ describe('compiler: transform v-on', () => {
$event => {
foo($event)
}
- `
- }
- }
- ]
+ `,
+ },
+ },
+ ],
})
})
@@ -302,7 +350,7 @@ describe('compiler: transform v-on', () => {
function($event) {
foo($event)
}
- "/>`
+ "/>`,
)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@@ -314,10 +362,10 @@ describe('compiler: transform v-on', () => {
function($event) {
foo($event)
}
- `
- }
- }
- ]
+ `,
+ },
+ },
+ ],
})
})
@@ -329,16 +377,16 @@ describe('compiler: transform v-on', () => {
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `a['b' + c]`
- }
- }
- ]
+ content: `a['b' + c]`,
+ },
+ },
+ ],
})
})
test('complex member expression w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@@ -350,17 +398,17 @@ describe('compiler: transform v-on', () => {
{ content: `_ctx.a` },
`['b' + `,
{ content: `_ctx.c` },
- `]`
- ]
- }
- }
- ]
+ `]`,
+ ],
+ },
+ },
+ ],
})
})
test('function expression w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`
foo(e)"/>`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@@ -374,34 +422,34 @@ describe('compiler: transform v-on', () => {
{ content: `_ctx.foo` },
`(`,
{ content: `e` },
- `)`
- ]
- }
- }
- ]
+ `)`,
+ ],
+ },
+ },
+ ],
})
})
test('should error if no expression AND no modifier', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithVOn(`
`, { onError })
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_ON_NO_EXPRESSION,
loc: {
start: {
line: 1,
- column: 6
+ column: 6,
},
end: {
line: 1,
- column: 16
- }
- }
+ column: 16,
+ },
+ },
})
})
test('should NOT error if no expression but has modifier', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
parseWithVOn(`
`, { onError })
expect(onError).not.toHaveBeenCalled()
})
@@ -412,55 +460,57 @@ describe('compiler: transform v-on', () => {
properties: [
{
key: {
- content: `onFooBar`
+ content: `onFooBar`,
},
value: {
- content: `onMount`
- }
- }
- ]
+ content: `onMount`,
+ },
+ },
+ ],
})
})
- test('case conversion for vnode hooks', () => {
- const { node } = parseWithVOn(`
`)
- expect((node.codegenNode as VNodeCall).props).toMatchObject({
- properties: [
- {
- key: {
- content: `onVnodeMounted`
- },
- value: {
- content: `onMount`
- }
- }
- ]
+ test('error for vnode hooks', () => {
+ const onError = vi.fn()
+ parseWithVOn(`
`, { onError })
+ expect(onError.mock.calls[0][0]).toMatchObject({
+ code: ErrorCodes.X_VNODE_HOOKS,
+ loc: {
+ start: {
+ line: 1,
+ column: 11,
+ },
+ end: {
+ line: 1,
+ column: 24,
+ },
+ },
})
})
test('vue: prefixed events', () => {
const { node } = parseWithVOn(
- `
`
+ `
`,
)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
- content: `onVnodeMounted`
+ content: `onVnodeMounted`,
},
value: {
- content: `onMount`
- }
+ content: `onMount`,
+ },
},
{
key: {
- content: `onVnodeBeforeUpdate`
+ content: `onVnodeBeforeUpdate`,
},
value: {
- content: `onBeforeUpdate`
- }
- }
- ]
+ content: `onBeforeUpdate`,
+ },
+ },
+ ],
})
})
@@ -468,35 +518,35 @@ describe('compiler: transform v-on', () => {
test('empty handler', () => {
const { root, node } = parseWithVOn(`
`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `() => {}`
- }
+ content: `() => {}`,
+ },
})
})
test('member expression handler', () => {
const { root, node } = parseWithVOn(`
`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
@@ -505,23 +555,23 @@ describe('compiler: transform v-on', () => {
children: [
`(...args) => (`,
{ content: `_ctx.foo && _ctx.foo(...args)` },
- `)`
- ]
- }
+ `)`,
+ ],
+ },
})
})
test('compound member expression handler', () => {
const { root, node } = parseWithVOn(`
`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
@@ -538,12 +588,12 @@ describe('compiler: transform v-on', () => {
{ content: `_ctx.foo` },
`.`,
{ content: `bar` },
- `(...args)`
- ]
+ `(...args)`,
+ ],
},
- `)`
- ]
- }
+ `)`,
+ ],
+ },
})
})
@@ -551,9 +601,9 @@ describe('compiler: transform v-on', () => {
const { root } = parseWithVOn(`
`, {
prefixIdentifiers: true,
cacheHandlers: true,
- isNativeTag: tag => tag === 'div'
+ isNativeTag: tag => tag === 'div',
})
- expect(root.cached).toBe(0)
+ expect(root.cached.length).toBe(0)
})
test('should not be cached inside v-once', () => {
@@ -561,31 +611,42 @@ describe('compiler: transform v-on', () => {
`
`,
{
prefixIdentifiers: true,
- cacheHandlers: true
- }
+ cacheHandlers: true,
+ },
+ )
+ expect(root.cached.length).not.toBe(2)
+ expect(root.cached.length).toBe(1)
+ })
+
+ test('unicode identifier should not be cached (v-for)', () => {
+ const { root } = parseWithVOn(
+ `
`,
+ {
+ prefixIdentifiers: true,
+ cacheHandlers: true,
+ },
)
- expect(root.cached).not.toBe(2)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(0)
})
test('inline function expression handler', () => {
const { root, node } = parseWithVOn(`
`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`() => `, { content: `_ctx.foo` }, `()`]
- }
+ children: [`() => `, { content: `_ctx.foo` }, `()`],
+ },
})
})
@@ -594,22 +655,55 @@ describe('compiler: transform v-on', () => {
`
`,
{
prefixIdentifiers: true,
- cacheHandlers: true
- }
+ cacheHandlers: true,
+ },
+ )
+ expect(root.cached.length).toBe(1)
+ const vnodeCall = node.codegenNode as VNodeCall
+ // should not treat cached handler as dynamicProp, so no flags
+ expect(vnodeCall.patchFlag).toBeUndefined()
+ expect(
+ (vnodeCall.props as ObjectExpression).properties[0].value,
+ ).toMatchObject({
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ index: 0,
+ value: {
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [`async () => await `, { content: `_ctx.foo` }, `()`],
+ },
+ })
+ })
+
+ test('inline async arrow function with no bracket expression handler', () => {
+ const { root, node } = parseWithVOn(
+ `
`,
+ {
+ prefixIdentifiers: true,
+ cacheHandlers: true,
+ },
)
- expect(root.cached).toBe(1)
+
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`async () => await `, { content: `_ctx.foo` }, `()`]
- }
+ children: [
+ `async `,
+ { content: `e` },
+ ` => await `,
+ { content: `_ctx.foo` },
+ `(`,
+ { content: `e` },
+ `)`,
+ ],
+ },
})
})
@@ -618,15 +712,15 @@ describe('compiler: transform v-on', () => {
`
`,
{
prefixIdentifiers: true,
- cacheHandlers: true
- }
+ cacheHandlers: true,
+ },
)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
@@ -635,24 +729,24 @@ describe('compiler: transform v-on', () => {
children: [
`async function () { await `,
{ content: `_ctx.foo` },
- `() } `
- ]
- }
+ `() } `,
+ ],
+ },
})
})
test('inline statement handler', () => {
const { root, node } = parseWithVOn(`
`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
- expect(root.cached).toBe(1)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
- (vnodeCall.props as ObjectExpression).properties[0].value
+ (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
@@ -661,9 +755,9 @@ describe('compiler: transform v-on', () => {
children: [
`$event => (`,
{ children: [{ content: `_ctx.foo` }, `++`] },
- `)`
- ]
- }
+ `)`,
+ ],
+ },
})
})
})
diff --git a/packages/compiler-core/__tests__/transforms/vOnce.spec.ts b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts
index 553ac1dfac4..5f47a0fdbd9 100644
--- a/packages/compiler-core/__tests__/transforms/vOnce.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts
@@ -1,10 +1,10 @@
import {
- baseParse as parse,
- transform,
+ type CompilerOptions,
NodeTypes,
generate,
- CompilerOptions,
- getBaseTransformPreset
+ getBaseTransformPreset,
+ baseParse as parse,
+ transform,
} from '../../src'
import { RENDER_SLOT, SET_BLOCK_TRACKING } from '../../src/runtimeHelpers'
@@ -14,7 +14,7 @@ function transformWithOnce(template: string, options: CompilerOptions = {}) {
transform(ast, {
nodeTransforms,
directiveTransforms,
- ...options
+ ...options,
})
return ast
}
@@ -22,60 +22,60 @@ function transformWithOnce(template: string, options: CompilerOptions = {}) {
describe('compiler: v-once transform', () => {
test('as root node', () => {
const root = transformWithOnce(`
`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- }
+ tag: `"div"`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('on nested plain element', () => {
const root = transformWithOnce(`
`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- }
+ tag: `"div"`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('on component', () => {
const root = transformWithOnce(`
`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
- tag: `_component_Comp`
- }
+ tag: `_component_Comp`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('on slot outlet', () => {
const root = transformWithOnce(`
`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: RENDER_SLOT
- }
+ callee: RENDER_SLOT,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -84,15 +84,15 @@ describe('compiler: v-once transform', () => {
test('inside v-once', () => {
const root = transformWithOnce(`
`)
expect(root.cached).not.toBe(2)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
})
// cached nodes should be ignored by hoistStatic transform
test('with hoistStatic: true', () => {
const root = transformWithOnce(`
`, {
- hoistStatic: true
+ hoistStatic: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.hoists.length).toBe(0)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
@@ -100,15 +100,15 @@ describe('compiler: v-once transform', () => {
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
- tag: `"div"`
- }
+ tag: `"div"`,
+ },
})
expect(generate(root).code).toMatchSnapshot()
})
test('with v-if/else', () => {
const root = transformWithOnce(`
`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.children[0]).toMatchObject({
type: NodeTypes.IF,
@@ -119,27 +119,27 @@ describe('compiler: v-once transform', () => {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
type: NodeTypes.VNODE_CALL,
- tag: `"div"`
+ tag: `"div"`,
},
alternate: {
type: NodeTypes.VNODE_CALL,
- tag: `"p"`
- }
- }
- }
+ tag: `"p"`,
+ },
+ },
+ },
})
})
test('with v-for', () => {
const root = transformWithOnce(`
`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.children[0]).toMatchObject({
type: NodeTypes.FOR,
// should cache the entire v-for expression, not just a single branch
codegenNode: {
- type: NodeTypes.JS_CACHE_EXPRESSION
- }
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ },
})
})
})
diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
index 687c4d8b358..e0f44a064fb 100644
--- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
@@ -1,17 +1,18 @@
import {
- CompilerOptions,
+ type CompilerOptions,
+ type ComponentNode,
+ type ElementNode,
+ ErrorCodes,
+ type ForNode,
+ NodeTypes,
+ type ObjectExpression,
+ type RenderSlotCall,
+ type SimpleExpressionNode,
+ type SlotsExpression,
+ type VNodeCall,
+ generate,
baseParse as parse,
transform,
- generate,
- ElementNode,
- NodeTypes,
- ErrorCodes,
- ForNode,
- ComponentNode,
- VNodeCall,
- SlotsExpression,
- ObjectExpression,
- SimpleExpressionNode
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
@@ -20,17 +21,17 @@ import { transformExpression } from '../../src/transforms/transformExpression'
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
import {
trackSlotScopes,
- trackVForSlotScopes
+ trackVForSlotScopes,
} from '../../src/transforms/vSlot'
import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeHelpers'
-import { createObjectMatcher, genFlagText } from '../testUtils'
+import { createObjectMatcher } from '../testUtils'
import { PatchFlags } from '@vue/shared'
import { transformFor } from '../../src/transforms/vFor'
import { transformIf } from '../../src/transforms/vIf'
function parseWithSlots(template: string, options: CompilerOptions = {}) {
const ast = parse(template, {
- whitespace: options.whitespace
+ whitespace: options.whitespace,
})
transform(ast, {
nodeTransforms: [
@@ -41,13 +42,13 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
: []),
transformSlotOutlet,
transformElement,
- trackSlotScopes
+ trackSlotScopes,
],
directiveTransforms: {
on: transformOn,
- bind: transformBind
+ bind: transformBind,
},
- ...options
+ ...options,
})
return {
root: ast,
@@ -55,7 +56,7 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
ast.children[0].type === NodeTypes.ELEMENT
? ((ast.children[0].codegenNode as VNodeCall)
.children as SlotsExpression)
- : null
+ : null,
}
}
@@ -69,25 +70,25 @@ function createSlotMatcher(obj: Record
, isDynamic = false) {
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: !/^\[/.test(key),
- content: key.replace(/^\[|\]$/g, '')
+ content: key.replace(/^\[|\]$/g, ''),
},
- value: obj[key]
+ value: obj[key],
} as any
})
.concat({
key: { content: `_` },
value: {
content: isDynamic ? `2 /* DYNAMIC */` : `1 /* STABLE */`,
- isStatic: false
- }
- })
+ isStatic: false,
+ },
+ }),
}
}
describe('compiler: transform component slots', () => {
test('implicit default slot', () => {
const { root, slots } = parseWithSlots(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(slots).toMatchObject(
createSlotMatcher({
@@ -97,11 +98,11 @@ describe('compiler: transform component slots', () => {
returns: [
{
type: NodeTypes.ELEMENT,
- tag: `div`
- }
- ]
- }
- })
+ tag: `div`,
+ },
+ ],
+ },
+ }),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
@@ -109,7 +110,7 @@ describe('compiler: transform component slots', () => {
test('on-component default slot', () => {
const { root, slots } = parseWithSlots(
`{{ foo }}{{ bar }} `,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher({
@@ -117,24 +118,24 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
- }
- ]
- }
- })
+ content: `_ctx.bar`,
+ },
+ },
+ ],
+ },
+ }),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
@@ -142,7 +143,7 @@ describe('compiler: transform component slots', () => {
test('on component named slot', () => {
const { root, slots } = parseWithSlots(
`{{ foo }}{{ bar }} `,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher({
@@ -150,24 +151,24 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
- }
- ]
- }
- })
+ content: `_ctx.bar`,
+ },
+ },
+ ],
+ },
+ }),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
@@ -182,7 +183,7 @@ describe('compiler: transform component slots', () => {
{{ foo }}{{ bar }}
`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher({
@@ -190,45 +191,45 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
- }
- ]
+ content: `_ctx.bar`,
+ },
+ },
+ ],
},
two: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `bar` }, ` }`]
+ children: [`{ `, { content: `bar` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.foo`
- }
+ content: `_ctx.foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `bar`
- }
- }
- ]
- }
- })
+ content: `bar`,
+ },
+ },
+ ],
+ },
+ }),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
@@ -236,7 +237,7 @@ describe('compiler: transform component slots', () => {
test('on component dynamically named slot', () => {
const { root, slots } = parseWithSlots(
`{{ foo }}{{ bar }} `,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher(
@@ -245,26 +246,26 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
- }
- ]
- }
+ content: `_ctx.bar`,
+ },
+ },
+ ],
+ },
},
- true
- )
+ true,
+ ),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
@@ -273,7 +274,7 @@ describe('compiler: transform component slots', () => {
const { root, slots } = parseWithSlots(
`
foo bar
- `
+ `,
)
expect(slots).toMatchObject(
createSlotMatcher({
@@ -283,9 +284,9 @@ describe('compiler: transform component slots', () => {
returns: [
{
type: NodeTypes.TEXT,
- content: `foo`
- }
- ]
+ content: `foo`,
+ },
+ ],
},
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
@@ -293,15 +294,15 @@ describe('compiler: transform component slots', () => {
returns: [
{
type: NodeTypes.TEXT,
- content: `bar`
+ content: `bar`,
},
{
type: NodeTypes.ELEMENT,
- tag: `span`
- }
- ]
- }
- })
+ tag: `span`,
+ },
+ ],
+ },
+ }),
)
expect(generate(root).code).toMatchSnapshot()
})
@@ -316,7 +317,7 @@ describe('compiler: transform component slots', () => {
{{ foo }}{{ bar }}
`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher(
@@ -325,47 +326,47 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
- }
- ]
+ content: `_ctx.bar`,
+ },
+ },
+ ],
},
'[_ctx.two]': {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `bar` }, ` }`]
+ children: [`{ `, { content: `bar` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.foo`
- }
+ content: `_ctx.foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `bar`
- }
- }
- ]
- }
+ content: `bar`,
+ },
+ },
+ ],
+ },
},
- true
- )
+ true,
+ ),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
@@ -380,7 +381,7 @@ describe('compiler: transform component slots', () => {
{{ foo }}{{ bar }}{{ baz }}
`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject(
createSlotMatcher({
@@ -388,7 +389,7 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `foo` }, ` }`]
+ children: [`{ `, { content: `foo` }, ` }`],
},
returns: [
{
@@ -403,63 +404,63 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`{ `, { content: `bar` }, ` }`]
+ children: [`{ `, { content: `bar` }, ` }`],
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `bar`
- }
+ content: `bar`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.baz`
- }
- }
- ]
- }
+ content: `_ctx.baz`,
+ },
+ },
+ ],
+ },
},
- true
+ true,
),
// nested slot should be forced dynamic, since scope variables
// are not tracked as dependencies of the slot.
- patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS)
- }
+ patchFlag: PatchFlags.DYNAMIC_SLOTS,
+ },
},
// test scope
{
type: NodeTypes.TEXT,
- content: ` `
+ content: ` `,
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `foo`
- }
+ content: `foo`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.bar`
- }
+ content: `_ctx.bar`,
+ },
},
{
type: NodeTypes.INTERPOLATION,
content: {
- content: `_ctx.baz`
- }
- }
- ]
- }
- })
+ content: `_ctx.baz`,
+ },
+ },
+ ],
+ },
+ }),
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
@@ -468,14 +469,12 @@ describe('compiler: transform component slots', () => {
const { root } = parseWithSlots(
`
foo
-
`
+ `,
)
const div = ((root.children[0] as ForNode).children[0] as ElementNode)
.codegenNode as any
const comp = div.children[0]
- expect(comp.codegenNode.patchFlag).toBe(
- genFlagText(PatchFlags.DYNAMIC_SLOTS)
- )
+ expect(comp.codegenNode.patchFlag).toBe(PatchFlags.DYNAMIC_SLOTS)
})
test('should only force dynamic slots when actually using scope vars w/ prefixIdentifiers: true', () => {
@@ -493,7 +492,7 @@ describe('compiler: transform component slots', () => {
flag = (innerComp.codegenNode as VNodeCall).patchFlag
}
if (shouldForce) {
- expect(flag).toBe(genFlagText(PatchFlags.DYNAMIC_SLOTS))
+ expect(flag).toBe(PatchFlags.DYNAMIC_SLOTS)
} else {
expect(flag).toBeUndefined()
}
@@ -503,14 +502,14 @@ describe('compiler: transform component slots', () => {
`
foo
`,
- false
+ false,
)
assertDynamicSlots(
`
{{ i }}
`,
- true
+ true,
)
// reference the component's own slot variable should not force dynamic slots
@@ -518,14 +517,14 @@ describe('compiler: transform component slots', () => {
`
{{ bar }}
`,
- false
+ false,
)
assertDynamicSlots(
`
{{ foo }}
`,
- true
+ true,
)
// #2564
@@ -533,14 +532,14 @@ describe('compiler: transform component slots', () => {
`
`,
- true
+ true,
)
assertDynamicSlots(
`
`,
- false
+ false,
)
})
@@ -548,14 +547,14 @@ describe('compiler: transform component slots', () => {
const { root, slots } = parseWithSlots(
`
hello
- `
+ `,
)
expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
- _: `[2 /* DYNAMIC */]`
+ _: `[2 /* DYNAMIC */]`,
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -567,20 +566,21 @@ describe('compiler: transform component slots', () => {
name: `one`,
fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
- returns: [{ type: NodeTypes.TEXT, content: `hello` }]
- }
+ returns: [{ type: NodeTypes.TEXT, content: `hello` }],
+ },
+ key: `0`,
}),
alternate: {
content: `undefined`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
- expect((root as any).children[0].codegenNode.patchFlag).toMatch(
- PatchFlags.DYNAMIC_SLOTS + ''
+ expect((root as any).children[0].codegenNode.patchFlag).toBe(
+ PatchFlags.DYNAMIC_SLOTS,
)
expect(generate(root).code).toMatchSnapshot()
})
@@ -590,14 +590,14 @@ describe('compiler: transform component slots', () => {
`
{{ props }}
`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
- _: `[2 /* DYNAMIC */]`
+ _: `[2 /* DYNAMIC */]`,
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -613,22 +613,23 @@ describe('compiler: transform component slots', () => {
returns: [
{
type: NodeTypes.INTERPOLATION,
- content: { content: `props` }
- }
- ]
- }
+ content: { content: `props` },
+ },
+ ],
+ },
+ key: `0`,
}),
alternate: {
content: `undefined`,
- isStatic: false
- }
- }
- ]
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
+ },
+ ],
})
- expect((root as any).children[0].codegenNode.patchFlag).toMatch(
- PatchFlags.DYNAMIC_SLOTS + ''
+ expect((root as any).children[0].codegenNode.patchFlag).toBe(
+ PatchFlags.DYNAMIC_SLOTS,
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
@@ -639,14 +640,14 @@ describe('compiler: transform component slots', () => {
foo
bar
baz
- `
+ `,
)
expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
- _: `[2 /* DYNAMIC */]`
+ _: `[2 /* DYNAMIC */]`,
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -659,8 +660,9 @@ describe('compiler: transform component slots', () => {
fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: undefined,
- returns: [{ type: NodeTypes.TEXT, content: `foo` }]
- }
+ returns: [{ type: NodeTypes.TEXT, content: `foo` }],
+ },
+ key: `0`,
}),
alternate: {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@@ -670,26 +672,29 @@ describe('compiler: transform component slots', () => {
fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { content: `props` },
- returns: [{ type: NodeTypes.TEXT, content: `bar` }]
- }
+ returns: [{ type: NodeTypes.TEXT, content: `bar` }],
+ },
+ key: `1`,
}),
alternate: createObjectMatcher({
name: `one`,
fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: undefined,
- returns: [{ type: NodeTypes.TEXT, content: `baz` }]
- }
- })
- }
- }
- ]
- }
- ]
+ returns: [{ type: NodeTypes.TEXT, content: `baz` }],
+ },
+ key: `2`,
+ }),
+ },
+ },
+ ],
+ },
+ ],
})
- expect((root as any).children[0].codegenNode.patchFlag).toMatch(
- PatchFlags.DYNAMIC_SLOTS + ''
+ expect((root as any).children[0].codegenNode.patchFlag).toBe(
+ PatchFlags.DYNAMIC_SLOTS,
)
+ expect((root as any).children[0].children.length).toBe(3)
expect(generate(root).code).toMatchSnapshot()
})
@@ -698,14 +703,14 @@ describe('compiler: transform component slots', () => {
`
{{ name }}
`,
- { prefixIdentifiers: true }
+ { prefixIdentifiers: true },
)
expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
- _: `[2 /* DYNAMIC */]`
+ _: `[2 /* DYNAMIC */]`,
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -725,20 +730,20 @@ describe('compiler: transform component slots', () => {
returns: [
{
type: NodeTypes.INTERPOLATION,
- content: { content: `name`, isStatic: false }
- }
- ]
- }
- })
- }
- ]
- }
- ]
- }
- ]
+ content: { content: `name`, isStatic: false },
+ },
+ ],
+ },
+ }),
+ },
+ ],
+ },
+ ],
+ },
+ ],
})
- expect((root as any).children[0].codegenNode.patchFlag).toMatch(
- PatchFlags.DYNAMIC_SLOTS + ''
+ expect((root as any).children[0].codegenNode.patchFlag).toBe(
+ PatchFlags.DYNAMIC_SLOTS,
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
@@ -749,13 +754,13 @@ describe('compiler: transform component slots', () => {
properties: [
{
key: { content: `default` },
- value: { type: NodeTypes.JS_FUNCTION_EXPRESSION }
+ value: { type: NodeTypes.JS_FUNCTION_EXPRESSION },
},
{
key: { content: `_` },
- value: { content: `3 /* FORWARDED */` }
- }
- ]
+ value: { content: `3 /* FORWARDED */` },
+ },
+ ],
}
test('
tag only', () => {
const { slots } = parseWithSlots(` `)
@@ -774,7 +779,7 @@ describe('compiler: transform component slots', () => {
test(' tag w/ template', () => {
const { slots } = parseWithSlots(
- ` `
+ ` `,
)
expect(slots).toMatchObject(toMatch)
})
@@ -783,98 +788,144 @@ describe('compiler: transform component slots', () => {
const { slots } = parseWithSlots(` `)
expect(slots).toMatchObject(toMatch)
})
+
+ // # fix: #6900
+ test('consistent behavior of @xxx:modelValue and @xxx:model-value', () => {
+ const { root: rootUpper } = parseWithSlots(
+ `
`,
+ )
+ const slotNodeUpper = (rootUpper.codegenNode! as VNodeCall)
+ .children as ElementNode[]
+ const propertiesObjUpper = (
+ slotNodeUpper[0].codegenNode! as RenderSlotCall
+ ).arguments[2]
+ expect(propertiesObjUpper).toMatchObject({
+ properties: [
+ {
+ key: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: 'onFoo:modelValue',
+ },
+ value: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `handler`,
+ isStatic: false,
+ },
+ },
+ ],
+ })
+
+ const { root } = parseWithSlots(
+ `
`,
+ )
+ const slotNode = (root.codegenNode! as VNodeCall)
+ .children as ElementNode[]
+ const propertiesObj = (slotNode[0].codegenNode! as RenderSlotCall)
+ .arguments[2]
+ expect(propertiesObj).toMatchObject({
+ properties: [
+ {
+ key: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: 'onFoo:modelValue',
+ },
+ value: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `handler`,
+ isStatic: false,
+ },
+ },
+ ],
+ })
+ })
})
describe('errors', () => {
test('error on extraneous children w/ named default slot', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
const source = `foo bar `
parseWithSlots(source, { onError })
const index = source.indexOf('bar')
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
loc: {
- source: `bar`,
start: {
offset: index,
line: 1,
- column: index + 1
+ column: index + 1,
},
end: {
offset: index + 3,
line: 1,
- column: index + 4
- }
- }
+ column: index + 4,
+ },
+ },
})
})
test('error on duplicated slot names', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
const source = ` `
parseWithSlots(source, { onError })
const index = source.lastIndexOf('#foo')
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
loc: {
- source: `#foo`,
start: {
offset: index,
line: 1,
- column: index + 1
+ column: index + 1,
},
end: {
offset: index + 4,
line: 1,
- column: index + 5
- }
- }
+ column: index + 5,
+ },
+ },
})
})
test('error on invalid mixed slot usage', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
const source = ` `
parseWithSlots(source, { onError })
const index = source.lastIndexOf('#foo')
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE,
loc: {
- source: `#foo`,
start: {
offset: index,
line: 1,
- column: index + 1
+ column: index + 1,
},
end: {
offset: index + 4,
line: 1,
- column: index + 5
- }
- }
+ column: index + 5,
+ },
+ },
})
})
test('error on v-slot usage on plain elements', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
const source = `
`
parseWithSlots(source, { onError })
const index = source.indexOf('v-slot')
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_SLOT_MISPLACED,
loc: {
- source: `v-slot`,
start: {
offset: index,
line: 1,
- column: index + 1
+ column: index + 1,
},
end: {
offset: index + 6,
line: 1,
- column: index + 7
- }
- }
+ column: index + 7,
+ },
+ },
})
})
})
@@ -888,11 +939,11 @@ describe('compiler: transform component slots', () => {
`
const { root } = parseWithSlots(source, {
- whitespace: 'preserve'
+ whitespace: 'preserve',
})
expect(
- `Extraneous children found when component already has explicitly named default slot.`
+ `Extraneous children found when component already has explicitly named default slot.`,
).not.toHaveBeenWarned()
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
@@ -905,11 +956,11 @@ describe('compiler: transform component slots', () => {
`
const { root } = parseWithSlots(source, {
- whitespace: 'preserve'
+ whitespace: 'preserve',
})
expect(
- `Extraneous children found when component already has explicitly named default slot.`
+ `Extraneous children found when component already has explicitly named default slot.`,
).not.toHaveBeenWarned()
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
@@ -922,7 +973,7 @@ describe('compiler: transform component slots', () => {
`
const { root } = parseWithSlots(source, {
- whitespace: 'preserve'
+ whitespace: 'preserve',
})
// slots is vnodeCall's children as an ObjectExpression
@@ -932,10 +983,24 @@ describe('compiler: transform component slots', () => {
// should be: header, footer, _ (no default)
expect(slots.length).toBe(3)
expect(
- slots.some(p => (p.key as SimpleExpressionNode).content === 'default')
+ slots.some(p => (p.key as SimpleExpressionNode).content === 'default'),
).toBe(false)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
+
+ test('named slot with v-if + v-else', () => {
+ const source = `
+
+ foo
+ baz
+
+ `
+ const { root } = parseWithSlots(source, {
+ whitespace: 'preserve',
+ })
+
+ expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
+ })
})
})
diff --git a/packages/compiler-core/__tests__/utils.spec.ts b/packages/compiler-core/__tests__/utils.spec.ts
index 45fa46fea7a..2d377a271ac 100644
--- a/packages/compiler-core/__tests__/utils.spec.ts
+++ b/packages/compiler-core/__tests__/utils.spec.ts
@@ -1,11 +1,10 @@
-import { TransformContext } from '../src'
-import { Position } from '../src/ast'
+import type { ExpressionNode, TransformContext } from '../src'
+import { type Position, createSimpleExpression } from '../src/ast'
import {
- getInnerRange,
advancePositionWithClone,
- isMemberExpressionNode,
isMemberExpressionBrowser,
- toValidAssetId
+ isMemberExpressionNode,
+ toValidAssetId,
} from '../src/utils'
function p(line: number, column: number, offset: number): Position {
@@ -41,34 +40,9 @@ describe('advancePositionWithClone', () => {
})
})
-describe('getInnerRange', () => {
- const loc1 = {
- source: 'foo\nbar\nbaz',
- start: p(1, 1, 0),
- end: p(3, 3, 11)
- }
-
- test('at start', () => {
- const loc2 = getInnerRange(loc1, 0, 4)
- expect(loc2.start).toEqual(loc1.start)
- expect(loc2.end.column).toBe(1)
- expect(loc2.end.line).toBe(2)
- expect(loc2.end.offset).toBe(4)
- })
-
- test('in between', () => {
- const loc2 = getInnerRange(loc1, 4, 3)
- expect(loc2.start.column).toBe(1)
- expect(loc2.start.line).toBe(2)
- expect(loc2.start.offset).toBe(4)
- expect(loc2.end.column).toBe(4)
- expect(loc2.end.line).toBe(2)
- expect(loc2.end.offset).toBe(7)
- })
-})
-
describe('isMemberExpression', () => {
- function commonAssertions(fn: (str: string) => boolean) {
+ function commonAssertions(raw: (exp: ExpressionNode) => boolean) {
+ const fn = (str: string) => raw(createSimpleExpression(str))
// should work
expect(fn('obj.foo')).toBe(true)
expect(fn('obj[foo]')).toBe(true)
@@ -105,13 +79,16 @@ describe('isMemberExpression', () => {
test('browser', () => {
commonAssertions(isMemberExpressionBrowser)
- expect(isMemberExpressionBrowser('123[a]')).toBe(false)
+ expect(isMemberExpressionBrowser(createSimpleExpression('123[a]'))).toBe(
+ false,
+ )
})
test('node', () => {
const ctx = { expressionPlugins: ['typescript'] } as any as TransformContext
- const fn = (str: string) => isMemberExpressionNode(str, ctx)
- commonAssertions(fn)
+ const fn = (str: string) =>
+ isMemberExpressionNode(createSimpleExpression(str), ctx)
+ commonAssertions(exp => isMemberExpressionNode(exp, ctx))
// TS-specific checks
expect(fn('foo as string')).toBe(true)
@@ -122,6 +99,10 @@ describe('isMemberExpression', () => {
expect(fn(`123[a]`)).toBe(true)
expect(fn(`foo() as string`)).toBe(false)
expect(fn(`a + b as string`)).toBe(false)
+ // #9865
+ expect(fn('""')).toBe(false)
+ expect(fn('undefined')).toBe(false)
+ expect(fn('null')).toBe(false)
})
})
@@ -131,6 +112,6 @@ test('toValidAssetId', () => {
expect(toValidAssetId('div', 'filter')).toBe('_filter_div')
expect(toValidAssetId('foo-bar', 'component')).toBe('_component_foo_bar')
expect(toValidAssetId('test-测试-1', 'component')).toBe(
- '_component_test_2797935797_1'
+ '_component_test_2797935797_1',
)
})
diff --git a/packages/compiler-core/api-extractor.json b/packages/compiler-core/api-extractor.json
deleted file mode 100644
index b677d51cd8d..00000000000
--- a/packages/compiler-core/api-extractor.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extends": "../../api-extractor.json",
- "mainEntryPointFilePath": "./dist/packages//src/index.d.ts",
- "dtsRollup": {
- "publicTrimmedFilePath": "./dist/.d.ts"
- }
-}
diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json
index 224d4ed0b13..48f33e4de60 100644
--- a/packages/compiler-core/package.json
+++ b/packages/compiler-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-core",
- "version": "3.2.31",
+ "version": "3.5.16",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
@@ -9,6 +9,20 @@
"index.js",
"dist"
],
+ "exports": {
+ ".": {
+ "types": "./dist/compiler-core.d.ts",
+ "node": {
+ "production": "./dist/compiler-core.cjs.prod.js",
+ "development": "./dist/compiler-core.cjs.js",
+ "default": "./index.js"
+ },
+ "module": "./dist/compiler-core.esm-bundler.js",
+ "import": "./dist/compiler-core.esm-bundler.js",
+ "require": "./index.js"
+ },
+ "./*": "./*"
+ },
"buildOptions": {
"name": "VueCompilerCore",
"compat": true,
@@ -32,12 +46,13 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
"dependencies": {
- "@vue/shared": "3.2.31",
- "@babel/parser": "^7.16.4",
- "estree-walker": "^2.0.2",
- "source-map": "^0.6.1"
+ "@babel/parser": "catalog:",
+ "@vue/shared": "workspace:*",
+ "entities": "^4.5.0",
+ "estree-walker": "catalog:",
+ "source-map-js": "catalog:"
},
"devDependencies": {
- "@babel/types": "^7.16.0"
+ "@babel/types": "catalog:"
}
}
diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts
index 58fa76abe7f..2d6df9d9010 100644
--- a/packages/compiler-core/src/ast.ts
+++ b/packages/compiler-core/src/ast.ts
@@ -1,28 +1,32 @@
-import { isString } from '@vue/shared'
-import { ForParseResult } from './transforms/vFor'
+import { type PatchFlags, isString } from '@vue/shared'
import {
- RENDER_SLOT,
- CREATE_SLOTS,
- RENDER_LIST,
+ CREATE_BLOCK,
+ CREATE_ELEMENT_BLOCK,
+ CREATE_ELEMENT_VNODE,
+ type CREATE_SLOTS,
+ CREATE_VNODE,
+ type FRAGMENT,
OPEN_BLOCK,
- FRAGMENT,
+ type RENDER_LIST,
+ type RENDER_SLOT,
WITH_DIRECTIVES,
- WITH_MEMO
+ type WITH_MEMO,
} from './runtimeHelpers'
-import { PropsExpression } from './transforms/transformElement'
-import { ImportItem, TransformContext } from './transform'
-import { getVNodeBlockHelper, getVNodeHelper } from './utils'
+import type { PropsExpression } from './transforms/transformElement'
+import type { ImportItem, TransformContext } from './transform'
+import type { Node as BabelNode } from '@babel/types'
// Vue template is a platform-agnostic superset of HTML (syntax only).
-// More namespaces like SVG and MathML are declared by platform specific
-// compilers.
+// More namespaces can be declared by platform specific compilers.
export type Namespace = number
-export const enum Namespaces {
- HTML
+export enum Namespaces {
+ HTML,
+ SVG,
+ MATH_ML,
}
-export const enum NodeTypes {
+export enum NodeTypes {
ROOT,
ELEMENT,
TEXT,
@@ -53,14 +57,14 @@ export const enum NodeTypes {
JS_IF_STATEMENT,
JS_ASSIGNMENT_EXPRESSION,
JS_SEQUENCE_EXPRESSION,
- JS_RETURN_STATEMENT
+ JS_RETURN_STATEMENT,
}
-export const enum ElementTypes {
+export enum ElementTypes {
ELEMENT,
COMPONENT,
SLOT,
- TEMPLATE
+ TEMPLATE,
}
export interface Node {
@@ -99,16 +103,18 @@ export type TemplateChildNode =
export interface RootNode extends Node {
type: NodeTypes.ROOT
+ source: string
children: TemplateChildNode[]
- helpers: symbol[]
+ helpers: Set
components: string[]
directives: string[]
hoists: (JSChildNode | null)[]
imports: ImportItem[]
- cached: number
+ cached: (CacheExpression | null)[]
temps: number
ssrHelpers?: symbol[]
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
+ transformed?: boolean
// v2 compat only
filters?: string[]
@@ -125,9 +131,10 @@ export interface BaseElementNode extends Node {
ns: Namespace
tag: string
tagType: ElementTypes
- isSelfClosing: boolean
props: Array
children: TemplateChildNode[]
+ isSelfClosing?: boolean
+ innerLoc?: SourceLocation // only for SFC root level elements
}
export interface PlainElementNode extends BaseElementNode {
@@ -179,19 +186,28 @@ export interface CommentNode extends Node {
export interface AttributeNode extends Node {
type: NodeTypes.ATTRIBUTE
name: string
+ nameLoc: SourceLocation
value: TextNode | undefined
}
export interface DirectiveNode extends Node {
type: NodeTypes.DIRECTIVE
+ /**
+ * the normalized name without prefix or shorthands, e.g. "bind", "on"
+ */
name: string
+ /**
+ * the raw attribute name, preserving shorthand, and including arg & modifiers
+ * this is only used during parse.
+ */
+ rawName?: string
exp: ExpressionNode | undefined
arg: ExpressionNode | undefined
- modifiers: string[]
+ modifiers: SimpleExpressionNode[]
/**
* optional property to cache the expression parse result for v-for
*/
- parseResult?: ForParseResult
+ forParseResult?: ForParseResult
}
/**
@@ -199,11 +215,11 @@ export interface DirectiveNode extends Node {
* Higher levels implies lower levels. e.g. a node that can be stringified
* can always be hoisted and skipped for patch.
*/
-export const enum ConstantTypes {
+export enum ConstantTypes {
NOT_CONSTANT = 0,
CAN_SKIP_PATCH,
- CAN_HOIST,
- CAN_STRINGIFY
+ CAN_CACHE,
+ CAN_STRINGIFY,
}
export interface SimpleExpressionNode extends Node {
@@ -211,6 +227,12 @@ export interface SimpleExpressionNode extends Node {
content: string
isStatic: boolean
constType: ConstantTypes
+ /**
+ * - `null` means the expression is a simple identifier that doesn't need
+ * parsing
+ * - `false` means there was a parsing error
+ */
+ ast?: BabelNode | null | false
/**
* Indicates this is an identifier for a hoist vnode call and points to the
* hoisted node.
@@ -231,6 +253,12 @@ export interface InterpolationNode extends Node {
export interface CompoundExpressionNode extends Node {
type: NodeTypes.COMPOUND_EXPRESSION
+ /**
+ * - `null` means the expression is a simple identifier that doesn't need
+ * parsing
+ * - `false` means there was a parsing error
+ */
+ ast?: BabelNode | null | false
children: (
| SimpleExpressionNode
| CompoundExpressionNode
@@ -259,6 +287,7 @@ export interface IfBranchNode extends Node {
condition: ExpressionNode | undefined // else
children: TemplateChildNode[]
userKey?: AttributeNode | DirectiveNode
+ isTemplateIf?: boolean
}
export interface ForNode extends Node {
@@ -272,6 +301,14 @@ export interface ForNode extends Node {
codegenNode?: ForCodegenNode
}
+export interface ForParseResult {
+ source: ExpressionNode
+ value: ExpressionNode | undefined
+ key: ExpressionNode | undefined
+ index: ExpressionNode | undefined
+ finalized: boolean
+}
+
export interface TextCallNode extends Node {
type: NodeTypes.TEXT_CALL
content: TextNode | InterpolationNode | CompoundExpressionNode
@@ -293,8 +330,9 @@ export interface VNodeCall extends Node {
| SlotsExpression // component slots
| ForRenderListExpression // v-for fragment call
| SimpleExpressionNode // hoisted
+ | CacheExpression // cached
| undefined
- patchFlag: string | undefined
+ patchFlag: PatchFlags | undefined
dynamicProps: string | SimpleExpressionNode | undefined
directives: DirectiveArguments | undefined
isBlock: boolean
@@ -379,7 +417,9 @@ export interface CacheExpression extends Node {
type: NodeTypes.JS_CACHE_EXPRESSION
index: number
value: JSChildNode
- isVNode: boolean
+ needPauseTracking: boolean
+ inVOnce: boolean
+ needArraySpread: boolean
}
export interface MemoExpression extends CallExpression {
@@ -458,7 +498,7 @@ export interface RenderSlotCall extends CallExpression {
string,
string | ExpressionNode,
PropsExpression | '{}',
- TemplateChildNode[]
+ TemplateChildNode[],
]
}
@@ -474,7 +514,7 @@ export interface SlotsObjectProperty extends Property {
}
export interface SlotFunctionExpression extends FunctionExpression {
- returns: TemplateChildNode[]
+ returns: TemplateChildNode[] | CacheExpression
}
// createSlots({ ... }, [
@@ -524,7 +564,7 @@ export interface ForCodegenNode extends VNodeCall {
tag: typeof FRAGMENT
props: undefined
children: ForRenderListExpression
- patchFlag: string
+ patchFlag: PatchFlags
disableTracking: boolean
}
@@ -534,7 +574,7 @@ export interface ForRenderListExpression extends CallExpression {
}
export interface ForIteratorExpression extends FunctionExpression {
- returns: BlockCodegenNode
+ returns?: BlockCodegenNode
}
// AST Utilities ---------------------------------------------------------------
@@ -543,27 +583,28 @@ export interface ForIteratorExpression extends FunctionExpression {
// associated with template nodes, so their source locations are just a stub.
// Container types like CompoundExpression also don't need a real location.
export const locStub: SourceLocation = {
- source: '',
start: { line: 1, column: 1, offset: 0 },
- end: { line: 1, column: 1, offset: 0 }
+ end: { line: 1, column: 1, offset: 0 },
+ source: '',
}
export function createRoot(
children: TemplateChildNode[],
- loc = locStub
+ source = '',
): RootNode {
return {
type: NodeTypes.ROOT,
+ source,
children,
- helpers: [],
+ helpers: new Set(),
components: [],
directives: [],
hoists: [],
imports: [],
- cached: 0,
+ cached: [],
temps: 0,
codegenNode: undefined,
- loc
+ loc: locStub,
}
}
@@ -578,7 +619,7 @@ export function createVNodeCall(
isBlock: VNodeCall['isBlock'] = false,
disableTracking: VNodeCall['disableTracking'] = false,
isComponent: VNodeCall['isComponent'] = false,
- loc = locStub
+ loc: SourceLocation = locStub,
): VNodeCall {
if (context) {
if (isBlock) {
@@ -603,41 +644,41 @@ export function createVNodeCall(
isBlock,
disableTracking,
isComponent,
- loc
+ loc,
}
}
export function createArrayExpression(
elements: ArrayExpression['elements'],
- loc: SourceLocation = locStub
+ loc: SourceLocation = locStub,
): ArrayExpression {
return {
type: NodeTypes.JS_ARRAY_EXPRESSION,
loc,
- elements
+ elements,
}
}
export function createObjectExpression(
properties: ObjectExpression['properties'],
- loc: SourceLocation = locStub
+ loc: SourceLocation = locStub,
): ObjectExpression {
return {
type: NodeTypes.JS_OBJECT_EXPRESSION,
loc,
- properties
+ properties,
}
}
export function createObjectProperty(
key: Property['key'] | string,
- value: Property['value']
+ value: Property['value'],
): Property {
return {
type: NodeTypes.JS_PROPERTY,
loc: locStub,
key: isString(key) ? createSimpleExpression(key, true) : key,
- value
+ value,
}
}
@@ -645,38 +686,38 @@ export function createSimpleExpression(
content: SimpleExpressionNode['content'],
isStatic: SimpleExpressionNode['isStatic'] = false,
loc: SourceLocation = locStub,
- constType: ConstantTypes = ConstantTypes.NOT_CONSTANT
+ constType: ConstantTypes = ConstantTypes.NOT_CONSTANT,
): SimpleExpressionNode {
return {
type: NodeTypes.SIMPLE_EXPRESSION,
loc,
content,
isStatic,
- constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType
+ constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType,
}
}
export function createInterpolation(
content: InterpolationNode['content'] | string,
- loc: SourceLocation
+ loc: SourceLocation,
): InterpolationNode {
return {
type: NodeTypes.INTERPOLATION,
loc,
content: isString(content)
? createSimpleExpression(content, false, loc)
- : content
+ : content,
}
}
export function createCompoundExpression(
children: CompoundExpressionNode['children'],
- loc: SourceLocation = locStub
+ loc: SourceLocation = locStub,
): CompoundExpressionNode {
return {
type: NodeTypes.COMPOUND_EXPRESSION,
loc,
- children
+ children,
}
}
@@ -687,13 +728,13 @@ type InferCodegenNodeType = T extends typeof RENDER_SLOT
export function createCallExpression(
callee: T,
args: CallExpression['arguments'] = [],
- loc: SourceLocation = locStub
+ loc: SourceLocation = locStub,
): InferCodegenNodeType {
return {
type: NodeTypes.JS_CALL_EXPRESSION,
loc,
callee,
- arguments: args
+ arguments: args,
} as InferCodegenNodeType
}
@@ -702,7 +743,7 @@ export function createFunctionExpression(
returns: FunctionExpression['returns'] = undefined,
newline: boolean = false,
isSlot: boolean = false,
- loc: SourceLocation = locStub
+ loc: SourceLocation = locStub,
): FunctionExpression {
return {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
@@ -710,7 +751,7 @@ export function createFunctionExpression(
returns,
newline,
isSlot,
- loc
+ loc,
}
}
@@ -718,7 +759,7 @@ export function createConditionalExpression(
test: ConditionalExpression['test'],
consequent: ConditionalExpression['consequent'],
alternate: ConditionalExpression['alternate'],
- newline = true
+ newline = true,
): ConditionalExpression {
return {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@@ -726,86 +767,115 @@ export function createConditionalExpression(
consequent,
alternate,
newline,
- loc: locStub
+ loc: locStub,
}
}
export function createCacheExpression(
index: number,
value: JSChildNode,
- isVNode: boolean = false
+ needPauseTracking: boolean = false,
+ inVOnce: boolean = false,
): CacheExpression {
return {
type: NodeTypes.JS_CACHE_EXPRESSION,
index,
value,
- isVNode,
- loc: locStub
+ needPauseTracking: needPauseTracking,
+ inVOnce,
+ needArraySpread: false,
+ loc: locStub,
}
}
export function createBlockStatement(
- body: BlockStatement['body']
+ body: BlockStatement['body'],
): BlockStatement {
return {
type: NodeTypes.JS_BLOCK_STATEMENT,
body,
- loc: locStub
+ loc: locStub,
}
}
export function createTemplateLiteral(
- elements: TemplateLiteral['elements']
+ elements: TemplateLiteral['elements'],
): TemplateLiteral {
return {
type: NodeTypes.JS_TEMPLATE_LITERAL,
elements,
- loc: locStub
+ loc: locStub,
}
}
export function createIfStatement(
test: IfStatement['test'],
consequent: IfStatement['consequent'],
- alternate?: IfStatement['alternate']
+ alternate?: IfStatement['alternate'],
): IfStatement {
return {
type: NodeTypes.JS_IF_STATEMENT,
test,
consequent,
alternate,
- loc: locStub
+ loc: locStub,
}
}
export function createAssignmentExpression(
left: AssignmentExpression['left'],
- right: AssignmentExpression['right']
+ right: AssignmentExpression['right'],
): AssignmentExpression {
return {
type: NodeTypes.JS_ASSIGNMENT_EXPRESSION,
left,
right,
- loc: locStub
+ loc: locStub,
}
}
export function createSequenceExpression(
- expressions: SequenceExpression['expressions']
+ expressions: SequenceExpression['expressions'],
): SequenceExpression {
return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions,
- loc: locStub
+ loc: locStub,
}
}
export function createReturnStatement(
- returns: ReturnStatement['returns']
+ returns: ReturnStatement['returns'],
): ReturnStatement {
return {
type: NodeTypes.JS_RETURN_STATEMENT,
returns,
- loc: locStub
+ loc: locStub,
+ }
+}
+
+export function getVNodeHelper(
+ ssr: boolean,
+ isComponent: boolean,
+): typeof CREATE_VNODE | typeof CREATE_ELEMENT_VNODE {
+ return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE
+}
+
+export function getVNodeBlockHelper(
+ ssr: boolean,
+ isComponent: boolean,
+): typeof CREATE_BLOCK | typeof CREATE_ELEMENT_BLOCK {
+ return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK
+}
+
+export function convertToBlock(
+ node: VNodeCall,
+ { helper, removeHelper, inSSR }: TransformContext,
+): void {
+ if (!node.isBlock) {
+ node.isBlock = true
+ removeHelper(getVNodeHelper(inSSR, node.isComponent))
+ helper(OPEN_BLOCK)
+ helper(getVNodeBlockHelper(inSSR, node.isComponent))
}
}
diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts
index e773015f90f..52fabeea896 100644
--- a/packages/compiler-core/src/babelUtils.ts
+++ b/packages/compiler-core/src/babelUtils.ts
@@ -1,73 +1,96 @@
// should only use types from @babel/types
// do not import runtime methods
import type {
+ BlockStatement,
+ ForInStatement,
+ ForOfStatement,
+ ForStatement,
+ Function,
Identifier,
Node,
- Function,
ObjectProperty,
- BlockStatement,
- Program
+ Program,
} from '@babel/types'
import { walk } from 'estree-walker'
+/**
+ * Return value indicates whether the AST walked can be a constant
+ */
export function walkIdentifiers(
root: Node,
onIdentifier: (
node: Identifier,
- parent: Node,
+ parent: Node | null,
parentStack: Node[],
isReference: boolean,
- isLocal: boolean
+ isLocal: boolean,
) => void,
includeAll = false,
parentStack: Node[] = [],
- knownIds: Record = Object.create(null)
-) {
+ knownIds: Record = Object.create(null),
+): void {
if (__BROWSER__) {
return
}
const rootExp =
- root.type === 'Program' &&
- root.body[0].type === 'ExpressionStatement' &&
- root.body[0].expression
+ root.type === 'Program'
+ ? root.body[0].type === 'ExpressionStatement' && root.body[0].expression
+ : root
- ;(walk as any)(root, {
- enter(node: Node & { scopeIds?: Set }, parent: Node | undefined) {
+ walk(root, {
+ enter(node: Node & { scopeIds?: Set }, parent: Node | null) {
parent && parentStack.push(parent)
if (
parent &&
parent.type.startsWith('TS') &&
- parent.type !== 'TSAsExpression' &&
- parent.type !== 'TSNonNullExpression' &&
- parent.type !== 'TSTypeAssertion'
+ !TS_NODE_TYPES.includes(parent.type)
) {
return this.skip()
}
if (node.type === 'Identifier') {
const isLocal = !!knownIds[node.name]
- const isRefed = isReferencedIdentifier(node, parent!, parentStack)
+ const isRefed = isReferencedIdentifier(node, parent, parentStack)
if (includeAll || (isRefed && !isLocal)) {
- onIdentifier(node, parent!, parentStack, isRefed, isLocal)
+ onIdentifier(node, parent, parentStack, isRefed, isLocal)
}
} else if (
node.type === 'ObjectProperty' &&
- parent!.type === 'ObjectPattern'
+ // eslint-disable-next-line no-restricted-syntax
+ parent?.type === 'ObjectPattern'
) {
// mark property in destructure pattern
;(node as any).inPattern = true
} else if (isFunctionType(node)) {
- // walk function expressions and add its arguments to known identifiers
- // so that we don't prefix them
- walkFunctionParams(node, id => markScopeIdentifier(node, id, knownIds))
+ if (node.scopeIds) {
+ node.scopeIds.forEach(id => markKnownIds(id, knownIds))
+ } else {
+ // walk function expressions and add its arguments to known identifiers
+ // so that we don't prefix them
+ walkFunctionParams(node, id =>
+ markScopeIdentifier(node, id, knownIds),
+ )
+ }
} else if (node.type === 'BlockStatement') {
- // #3445 record block-level local variables
- walkBlockDeclarations(node, id =>
+ if (node.scopeIds) {
+ node.scopeIds.forEach(id => markKnownIds(id, knownIds))
+ } else {
+ // #3445 record block-level local variables
+ walkBlockDeclarations(node, id =>
+ markScopeIdentifier(node, id, knownIds),
+ )
+ }
+ } else if (node.type === 'CatchClause' && node.param) {
+ for (const id of extractIdentifiers(node.param)) {
markScopeIdentifier(node, id, knownIds)
+ }
+ } else if (isForStatement(node)) {
+ walkForStatement(node, false, id =>
+ markScopeIdentifier(node, id, knownIds),
)
}
},
- leave(node: Node & { scopeIds?: Set }, parent: Node | undefined) {
+ leave(node: Node & { scopeIds?: Set }, parent: Node | null) {
parent && parentStack.pop()
if (node !== rootExp && node.scopeIds) {
for (const id of node.scopeIds) {
@@ -77,15 +100,15 @@ export function walkIdentifiers(
}
}
}
- }
+ },
})
}
export function isReferencedIdentifier(
id: Identifier,
parent: Node | null,
- parentStack: Node[]
-) {
+ parentStack: Node[],
+): boolean {
if (__BROWSER__) {
return false
}
@@ -119,7 +142,7 @@ export function isReferencedIdentifier(
export function isInDestructureAssignment(
parent: Node,
- parentStack: Node[]
+ parentStack: Node[],
): boolean {
if (
parent &&
@@ -138,10 +161,23 @@ export function isInDestructureAssignment(
return false
}
+export function isInNewExpression(parentStack: Node[]): boolean {
+ let i = parentStack.length
+ while (i--) {
+ const p = parentStack[i]
+ if (p.type === 'NewExpression') {
+ return true
+ } else if (p.type !== 'MemberExpression') {
+ break
+ }
+ }
+ return false
+}
+
export function walkFunctionParams(
node: Function,
- onIdent: (id: Identifier) => void
-) {
+ onIdent: (id: Identifier) => void,
+): void {
for (const p of node.params) {
for (const id of extractIdentifiers(p)) {
onIdent(id)
@@ -151,8 +187,8 @@ export function walkFunctionParams(
export function walkBlockDeclarations(
block: BlockStatement | Program,
- onIdent: (node: Identifier) => void
-) {
+ onIdent: (node: Identifier) => void,
+): void {
for (const stmt of block.body) {
if (stmt.type === 'VariableDeclaration') {
if (stmt.declare) continue
@@ -167,13 +203,44 @@ export function walkBlockDeclarations(
) {
if (stmt.declare || !stmt.id) continue
onIdent(stmt.id)
+ } else if (isForStatement(stmt)) {
+ walkForStatement(stmt, true, onIdent)
+ }
+ }
+}
+
+function isForStatement(
+ stmt: Node,
+): stmt is ForStatement | ForOfStatement | ForInStatement {
+ return (
+ stmt.type === 'ForOfStatement' ||
+ stmt.type === 'ForInStatement' ||
+ stmt.type === 'ForStatement'
+ )
+}
+
+function walkForStatement(
+ stmt: ForStatement | ForOfStatement | ForInStatement,
+ isVar: boolean,
+ onIdent: (id: Identifier) => void,
+) {
+ const variable = stmt.type === 'ForStatement' ? stmt.init : stmt.left
+ if (
+ variable &&
+ variable.type === 'VariableDeclaration' &&
+ (variable.kind === 'var' ? isVar : !isVar)
+ ) {
+ for (const decl of variable.declarations) {
+ for (const id of extractIdentifiers(decl.id)) {
+ onIdent(id)
+ }
}
}
}
export function extractIdentifiers(
param: Node,
- nodes: Identifier[] = []
+ nodes: Identifier[] = [],
): Identifier[] {
switch (param.type) {
case 'Identifier':
@@ -216,20 +283,24 @@ export function extractIdentifiers(
return nodes
}
+function markKnownIds(name: string, knownIds: Record) {
+ if (name in knownIds) {
+ knownIds[name]++
+ } else {
+ knownIds[name] = 1
+ }
+}
+
function markScopeIdentifier(
node: Node & { scopeIds?: Set },
child: Identifier,
- knownIds: Record
+ knownIds: Record,
) {
const { name } = child
if (node.scopeIds && node.scopeIds.has(name)) {
return
}
- if (name in knownIds) {
- knownIds[name]++
- } else {
- knownIds[name] = 1
- }
+ markKnownIds(name, knownIds)
;(node.scopeIds || (node.scopeIds = new Set())).add(name)
}
@@ -242,7 +313,7 @@ export const isStaticProperty = (node: Node): node is ObjectProperty =>
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
!node.computed
-export const isStaticPropertyKey = (node: Node, parent: Node) =>
+export const isStaticPropertyKey = (node: Node, parent: Node): boolean =>
isStaticProperty(parent) && parent.key === node
/**
@@ -366,6 +437,7 @@ function isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {
// no: export { NODE as foo } from "foo";
case 'ExportSpecifier':
// @ts-expect-error
+ // eslint-disable-next-line no-restricted-syntax
if (grandparent?.source) {
return false
}
@@ -422,3 +494,19 @@ function isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {
return true
}
+
+export const TS_NODE_TYPES: string[] = [
+ 'TSAsExpression', // foo as number
+ 'TSTypeAssertion', // (foo)
+ 'TSNonNullExpression', // foo!
+ 'TSInstantiationExpression', // foo
+ 'TSSatisfiesExpression', // foo satisfies T
+]
+
+export function unwrapTSNode(node: Node): Node {
+ if (TS_NODE_TYPES.includes(node.type)) {
+ return unwrapTSNode((node as any).expression)
+ } else {
+ return node
+ }
+}
diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index 2b641e9ad69..6b4559fabb2 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -1,62 +1,107 @@
-import { CodegenOptions } from './options'
+import type { CodegenOptions } from './options'
import {
- RootNode,
- TemplateChildNode,
- TextNode,
- CommentNode,
- ExpressionNode,
+ type ArrayExpression,
+ type AssignmentExpression,
+ type CacheExpression,
+ type CallExpression,
+ type CommentNode,
+ type CompoundExpressionNode,
+ type ConditionalExpression,
+ type ExpressionNode,
+ type FunctionExpression,
+ type IfStatement,
+ type InterpolationNode,
+ type JSChildNode,
NodeTypes,
- JSChildNode,
- CallExpression,
- ArrayExpression,
- ObjectExpression,
- Position,
- InterpolationNode,
- CompoundExpressionNode,
- SimpleExpressionNode,
- FunctionExpression,
- ConditionalExpression,
- CacheExpression,
+ type ObjectExpression,
+ type Position,
+ type ReturnStatement,
+ type RootNode,
+ type SSRCodegenNode,
+ type SequenceExpression,
+ type SimpleExpressionNode,
+ type TemplateChildNode,
+ type TemplateLiteral,
+ type TextNode,
+ type VNodeCall,
+ getVNodeBlockHelper,
+ getVNodeHelper,
locStub,
- SSRCodegenNode,
- TemplateLiteral,
- IfStatement,
- AssignmentExpression,
- ReturnStatement,
- VNodeCall,
- SequenceExpression
} from './ast'
-import { SourceMapGenerator, RawSourceMap } from 'source-map'
+import { SourceMapGenerator } from 'source-map-js'
import {
advancePositionWithMutation,
assert,
- getVNodeBlockHelper,
- getVNodeHelper,
isSimpleIdentifier,
- toValidAssetId
+ toValidAssetId,
} from './utils'
-import { isString, isArray, isSymbol } from '@vue/shared'
import {
- helperNameMap,
- TO_DISPLAY_STRING,
+ PatchFlagNames,
+ type PatchFlags,
+ isArray,
+ isString,
+ isSymbol,
+} from '@vue/shared'
+import {
+ CREATE_COMMENT,
+ CREATE_ELEMENT_VNODE,
+ CREATE_STATIC,
+ CREATE_TEXT,
CREATE_VNODE,
+ OPEN_BLOCK,
RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
+ RESOLVE_FILTER,
SET_BLOCK_TRACKING,
- CREATE_COMMENT,
- CREATE_TEXT,
- PUSH_SCOPE_ID,
- POP_SCOPE_ID,
- WITH_DIRECTIVES,
- CREATE_ELEMENT_VNODE,
- OPEN_BLOCK,
- CREATE_STATIC,
+ TO_DISPLAY_STRING,
WITH_CTX,
- RESOLVE_FILTER
+ WITH_DIRECTIVES,
+ helperNameMap,
} from './runtimeHelpers'
-import { ImportItem } from './transform'
+import type { ImportItem } from './transform'
+
+/**
+ * The `SourceMapGenerator` type from `source-map-js` is a bit incomplete as it
+ * misses `toJSON()`. We also need to add types for internal properties which we
+ * need to access for better performance.
+ *
+ * Since TS 5.3, dts generation starts to strangely include broken triple slash
+ * references for source-map-js, so we are inlining all source map related types
+ * here to to workaround that.
+ */
+export interface CodegenSourceMapGenerator {
+ setSourceContent(sourceFile: string, sourceContent: string): void
+ // SourceMapGenerator has this method but the types do not include it
+ toJSON(): RawSourceMap
+ _sources: Set
+ _names: Set
+ _mappings: {
+ add(mapping: MappingItem): void
+ }
+}
+
+export interface RawSourceMap {
+ file?: string
+ sourceRoot?: string
+ version: string
+ sources: string[]
+ names: string[]
+ sourcesContent?: string[]
+ mappings: string
+}
+
+interface MappingItem {
+ source: string
+ generatedLine: number
+ generatedColumn: number
+ originalLine: number
+ originalColumn: number
+ name: string | null
+}
-const PURE_ANNOTATION = `/*#__PURE__*/`
+const PURE_ANNOTATION = `/*@__PURE__*/`
+
+const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
@@ -67,6 +112,13 @@ export interface CodegenResult {
map?: RawSourceMap
}
+enum NewlineType {
+ Start = 0,
+ End = -1,
+ None = -2,
+ Unknown = -3,
+}
+
export interface CodegenContext
extends Omit, 'bindingMetadata' | 'inline'> {
source: string
@@ -76,9 +128,9 @@ export interface CodegenContext
offset: number
indentLevel: number
pure: boolean
- map?: SourceMapGenerator
+ map?: CodegenSourceMapGenerator
helper(key: symbol): string
- push(code: string, node?: CodegenNode): void
+ push(code: string, newlineIndex?: number, node?: CodegenNode): void
indent(): void
deindent(withoutNewLine?: boolean): void
newline(): void
@@ -98,8 +150,8 @@ function createCodegenContext(
ssrRuntimeModuleName = 'vue/server-renderer',
ssr = false,
isTS = false,
- inSSR = false
- }: CodegenOptions
+ inSSR = false,
+ }: CodegenOptions,
): CodegenContext {
const context: CodegenContext = {
mode,
@@ -114,7 +166,7 @@ function createCodegenContext(
ssr,
isTS,
inSSR,
- source: ast.loc.source,
+ source: ast.source,
code: ``,
column: 1,
line: 1,
@@ -125,7 +177,7 @@ function createCodegenContext(
helper(key) {
return `_${helperNameMap[key]}`
},
- push(code, node) {
+ push(code, newlineIndex = NewlineType.None, node) {
context.code += code
if (!__BROWSER__ && context.map) {
if (node) {
@@ -136,10 +188,46 @@ function createCodegenContext(
name = content
}
}
- addMapping(node.loc.start, name)
+ if (node.loc.source) {
+ addMapping(node.loc.start, name)
+ }
+ }
+ if (newlineIndex === NewlineType.Unknown) {
+ // multiple newlines, full iteration
+ advancePositionWithMutation(context, code)
+ } else {
+ // fast paths
+ context.offset += code.length
+ if (newlineIndex === NewlineType.None) {
+ // no newlines; fast path to avoid newline detection
+ if (__TEST__ && code.includes('\n')) {
+ throw new Error(
+ `CodegenContext.push() called newlineIndex: none, but contains` +
+ `newlines: ${code.replace(/\n/g, '\\n')}`,
+ )
+ }
+ context.column += code.length
+ } else {
+ // single newline at known index
+ if (newlineIndex === NewlineType.End) {
+ newlineIndex = code.length - 1
+ }
+ if (
+ __TEST__ &&
+ (code.charAt(newlineIndex) !== '\n' ||
+ code.slice(0, newlineIndex).includes('\n') ||
+ code.slice(newlineIndex + 1).includes('\n'))
+ ) {
+ throw new Error(
+ `CodegenContext.push() called with newlineIndex: ${newlineIndex} ` +
+ `but does not conform: ${code.replace(/\n/g, '\\n')}`,
+ )
+ }
+ context.line++
+ context.column = code.length - newlineIndex
+ }
}
- advancePositionWithMutation(context, code)
- if (node && node.loc !== locStub) {
+ if (node && node.loc !== locStub && node.loc.source) {
addMapping(node.loc.end)
}
}
@@ -156,32 +244,35 @@ function createCodegenContext(
},
newline() {
newline(context.indentLevel)
- }
+ },
}
function newline(n: number) {
- context.push('\n' + ` `.repeat(n))
+ context.push('\n' + ` `.repeat(n), NewlineType.Start)
}
- function addMapping(loc: Position, name?: string) {
- context.map!.addMapping({
+ function addMapping(loc: Position, name: string | null = null) {
+ // we use the private property to directly add the mapping
+ // because the addMapping() implementation in source-map-js has a bunch of
+ // unnecessary arg and validation checks that are pure overhead in our case.
+ const { _names, _mappings } = context.map!
+ if (name !== null && !_names.has(name)) _names.add(name)
+ _mappings.add({
+ originalLine: loc.line,
+ originalColumn: loc.column - 1, // source-map column is 0 based
+ generatedLine: context.line,
+ generatedColumn: context.column - 1,
+ source: filename,
name,
- source: context.filename,
- original: {
- line: loc.line,
- column: loc.column - 1 // source-map column is 0 based
- },
- generated: {
- line: context.line,
- column: context.column - 1
- }
})
}
if (!__BROWSER__ && sourceMap) {
// lazy require source-map implementation, only in non-browser builds
- context.map = new SourceMapGenerator()
- context.map!.setSourceContent(filename, context.source)
+ context.map =
+ new SourceMapGenerator() as unknown as CodegenSourceMapGenerator
+ context.map.setSourceContent(filename, context.source)
+ context.map._sources.add(filename)
}
return context
@@ -191,7 +282,7 @@ export function generate(
ast: RootNode,
options: CodegenOptions & {
onContextCreated?: (context: CodegenContext) => void
- } = {}
+ } = {},
): CodegenResult {
const context = createCodegenContext(ast, options)
if (options.onContextCreated) options.onContextCreated(context)
@@ -203,10 +294,11 @@ export function generate(
deindent,
newline,
scopeId,
- ssr
+ ssr,
} = context
- const hasHelpers = ast.helpers.length > 0
+ const helpers = Array.from(ast.helpers)
+ const hasHelpers = helpers.length > 0
const useWithBlock = !prefixIdentifiers && mode !== 'module'
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
const isSetupInlined = !__BROWSER__ && !!options.inline
@@ -248,11 +340,9 @@ export function generate(
// also they should be renamed to avoid collision with user properties
if (hasHelpers) {
push(
- `const { ${ast.helpers
- .map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
- .join(', ')} } = _Vue`
+ `const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`,
+ NewlineType.End,
)
- push(`\n`)
newline()
}
}
@@ -283,7 +373,7 @@ export function generate(
}
}
if (ast.components.length || ast.directives.length || ast.temps) {
- push(`\n`)
+ push(`\n`, NewlineType.Start)
newline()
}
@@ -309,8 +399,7 @@ export function generate(
ast,
code: context.code,
preamble: isSetupInlined ? preambleContext.code : ``,
- // SourceMapGenerator does have toJSON() method but it's not in the types
- map: context.map ? (context.map as any).toJSON() : undefined
+ map: context.map ? context.map.toJSON() : undefined,
}
}
@@ -322,26 +411,27 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
newline,
runtimeModuleName,
runtimeGlobalName,
- ssrRuntimeModuleName
+ ssrRuntimeModuleName,
} = context
const VueBinding =
!__BROWSER__ && ssr
? `require(${JSON.stringify(runtimeModuleName)})`
: runtimeGlobalName
- const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
// Generate const declaration for helpers
// In prefix mode, we place the const declaration at top so it's done
// only once; But if we not prefixing, we place the declaration inside the
// with block so it doesn't incur the `in` check cost for every helper access.
- if (ast.helpers.length > 0) {
+ const helpers = Array.from(ast.helpers)
+ if (helpers.length > 0) {
if (!__BROWSER__ && prefixIdentifiers) {
push(
- `const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`
+ `const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`,
+ NewlineType.End,
)
} else {
// "with" mode.
// save Vue in a separate variable to avoid collision
- push(`const _Vue = ${VueBinding}\n`)
+ push(`const _Vue = ${VueBinding}\n`, NewlineType.End)
// in "with" mode, helpers are declared inside the with block to avoid
// has check cost, but hoists are lifted out of the function - we need
// to provide the helper here.
@@ -351,12 +441,12 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
CREATE_ELEMENT_VNODE,
CREATE_COMMENT,
CREATE_TEXT,
- CREATE_STATIC
+ CREATE_STATIC,
]
- .filter(helper => ast.helpers.includes(helper))
+ .filter(helper => helpers.includes(helper))
.map(aliasHelper)
.join(', ')
- push(`const { ${staticHelpers} } = _Vue\n`)
+ push(`const { ${staticHelpers} } = _Vue\n`, NewlineType.End)
}
}
}
@@ -366,7 +456,8 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
push(
`const { ${ast.ssrHelpers
.map(aliasHelper)
- .join(', ')} } = require("${ssrRuntimeModuleName}")\n`
+ .join(', ')} } = require("${ssrRuntimeModuleName}")\n`,
+ NewlineType.End,
)
}
genHoists(ast.hoists, context)
@@ -378,22 +469,19 @@ function genModulePreamble(
ast: RootNode,
context: CodegenContext,
genScopeId: boolean,
- inline?: boolean
+ inline?: boolean,
) {
const {
push,
newline,
optimizeImports,
runtimeModuleName,
- ssrRuntimeModuleName
+ ssrRuntimeModuleName,
} = context
- if (genScopeId && ast.hoists.length) {
- ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
- }
-
// generate import statements for helpers
- if (ast.helpers.length) {
+ if (ast.helpers.size) {
+ const helpers = Array.from(ast.helpers)
if (optimizeImports) {
// when bundled with webpack with code-split, calling an import binding
// as a function leads to it being wrapped with `Object(a.b)` or `(0,a.b)`,
@@ -401,20 +489,23 @@ function genModulePreamble(
// therefore we assign the imports to variables (which is a constant ~50b
// cost per-component instead of scaling with template size)
push(
- `import { ${ast.helpers
+ `import { ${helpers
.map(s => helperNameMap[s])
- .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
+ .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
+ NewlineType.End,
)
push(
- `\n// Binding optimization for webpack code-split\nconst ${ast.helpers
+ `\n// Binding optimization for webpack code-split\nconst ${helpers
.map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
- .join(', ')}\n`
+ .join(', ')}\n`,
+ NewlineType.End,
)
} else {
push(
- `import { ${ast.helpers
+ `import { ${helpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
- .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
+ .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
+ NewlineType.End,
)
}
}
@@ -423,7 +514,8 @@ function genModulePreamble(
push(
`import { ${ast.ssrHelpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
- .join(', ')} } from "${ssrRuntimeModuleName}"\n`
+ .join(', ')} } from "${ssrRuntimeModuleName}"\n`,
+ NewlineType.End,
)
}
@@ -443,14 +535,14 @@ function genModulePreamble(
function genAssets(
assets: string[],
type: 'component' | 'directive' | 'filter',
- { helper, push, newline, isTS }: CodegenContext
+ { helper, push, newline, isTS }: CodegenContext,
) {
const resolver = helper(
__COMPAT__ && type === 'filter'
? RESOLVE_FILTER
: type === 'component'
- ? RESOLVE_COMPONENT
- : RESOLVE_DIRECTIVE
+ ? RESOLVE_COMPONENT
+ : RESOLVE_DIRECTIVE,
)
for (let i = 0; i < assets.length; i++) {
let id = assets[i]
@@ -462,7 +554,7 @@ function genAssets(
push(
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
maybeSelfReference ? `, true` : ``
- })${isTS ? `!` : ``}`
+ })${isTS ? `!` : ``}`,
)
if (i < assets.length - 1) {
newline()
@@ -475,33 +567,14 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
return
}
context.pure = true
- const { push, newline, helper, scopeId, mode } = context
- const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
+ const { push, newline } = context
newline()
- // generate inlined withScopeId helper
- if (genScopeId) {
- push(
- `const _withScopeId = n => (${helper(
- PUSH_SCOPE_ID
- )}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`
- )
- newline()
- }
-
for (let i = 0; i < hoists.length; i++) {
const exp = hoists[i]
if (exp) {
- const needScopeIdWrapper = genScopeId && exp.type === NodeTypes.VNODE_CALL
- push(
- `const _hoisted_${i + 1} = ${
- needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : ``
- }`
- )
+ push(`const _hoisted_${i + 1} = `)
genNode(exp, context)
- if (needScopeIdWrapper) {
- push(`)`)
- }
newline()
}
}
@@ -533,7 +606,7 @@ function isText(n: string | CodegenNode) {
function genNodeListAsArray(
nodes: (string | CodegenNode | TemplateChildNode[])[],
- context: CodegenContext
+ context: CodegenContext,
) {
const multilines =
nodes.length > 3 ||
@@ -549,13 +622,13 @@ function genNodeList(
nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
context: CodegenContext,
multilines: boolean = false,
- comma: boolean = true
+ comma: boolean = true,
) {
const { push, newline } = context
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (isString(node)) {
- push(node)
+ push(node, NewlineType.Unknown)
} else if (isArray(node)) {
genNodeListAsArray(node, context)
} else {
@@ -574,7 +647,7 @@ function genNodeList(
function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
if (isString(node)) {
- context.push(node)
+ context.push(node, NewlineType.Unknown)
return
}
if (isSymbol(node)) {
@@ -589,7 +662,7 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
assert(
node.codegenNode != null,
`Codegen node is missing for element/if/for node. ` +
- `Apply appropriate transforms first.`
+ `Apply appropriate transforms first.`,
)
genNode(node.codegenNode!, context)
break
@@ -654,7 +727,7 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
!__BROWSER__ && genReturnStatement(node, context)
break
- /* istanbul ignore next */
+ /* v8 ignore start */
case NodeTypes.IF_BRANCH:
// noop
break
@@ -665,19 +738,24 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
const exhaustiveCheck: never = node
return exhaustiveCheck
}
+ /* v8 ignore stop */
}
}
function genText(
node: TextNode | SimpleExpressionNode,
- context: CodegenContext
+ context: CodegenContext,
) {
- context.push(JSON.stringify(node.content), node)
+ context.push(JSON.stringify(node.content), NewlineType.Unknown, node)
}
function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
const { content, isStatic } = node
- context.push(isStatic ? JSON.stringify(content) : content, node)
+ context.push(
+ isStatic ? JSON.stringify(content) : content,
+ NewlineType.Unknown,
+ node,
+ )
}
function genInterpolation(node: InterpolationNode, context: CodegenContext) {
@@ -690,12 +768,12 @@ function genInterpolation(node: InterpolationNode, context: CodegenContext) {
function genCompoundExpression(
node: CompoundExpressionNode,
- context: CodegenContext
+ context: CodegenContext,
) {
for (let i = 0; i < node.children!.length; i++) {
const child = node.children![i]
if (isString(child)) {
- context.push(child)
+ context.push(child, NewlineType.Unknown)
} else {
genNode(child, context)
}
@@ -704,7 +782,7 @@ function genCompoundExpression(
function genExpressionAsPropertyKey(
node: ExpressionNode,
- context: CodegenContext
+ context: CodegenContext,
) {
const { push } = context
if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
@@ -716,9 +794,9 @@ function genExpressionAsPropertyKey(
const text = isSimpleIdentifier(node.content)
? node.content
: JSON.stringify(node.content)
- push(text, node)
+ push(text, NewlineType.None, node)
} else {
- push(`[${node.content}]`, node)
+ push(`[${node.content}]`, NewlineType.Unknown, node)
}
}
@@ -727,7 +805,11 @@ function genComment(node: CommentNode, context: CodegenContext) {
if (pure) {
push(PURE_ANNOTATION)
}
- push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)
+ push(
+ `${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`,
+ NewlineType.Unknown,
+ node,
+ )
}
function genVNodeCall(node: VNodeCall, context: CodegenContext) {
@@ -741,8 +823,30 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
directives,
isBlock,
disableTracking,
- isComponent
+ isComponent,
} = node
+
+ // add dev annotations to patch flags
+ let patchFlagString
+ if (patchFlag) {
+ if (__DEV__) {
+ if (patchFlag < 0) {
+ // special flags (negative and mutually exclusive)
+ patchFlagString = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
+ } else {
+ // bitwise flags
+ const flagNames = Object.keys(PatchFlagNames)
+ .map(Number)
+ .filter(n => n > 0 && patchFlag & n)
+ .map(n => PatchFlagNames[n as PatchFlags])
+ .join(`, `)
+ patchFlagString = patchFlag + ` /* ${flagNames} */`
+ }
+ } else {
+ patchFlagString = String(patchFlag)
+ }
+ }
+
if (directives) {
push(helper(WITH_DIRECTIVES) + `(`)
}
@@ -755,10 +859,10 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
const callHelper: symbol = isBlock
? getVNodeBlockHelper(context.inSSR, isComponent)
: getVNodeHelper(context.inSSR, isComponent)
- push(helper(callHelper) + `(`, node)
+ push(helper(callHelper) + `(`, NewlineType.None, node)
genNodeList(
- genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
- context
+ genNullableArgs([tag, props, children, patchFlagString, dynamicProps]),
+ context,
)
push(`)`)
if (isBlock) {
@@ -786,7 +890,7 @@ function genCallExpression(node: CallExpression, context: CodegenContext) {
if (pure) {
push(PURE_ANNOTATION)
}
- push(callee + `(`, node)
+ push(callee + `(`, NewlineType.None, node)
genNodeList(node.arguments, context)
push(`)`)
}
@@ -795,7 +899,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
const { push, indent, deindent, newline } = context
const { properties } = node
if (!properties.length) {
- push(`{}`, node)
+ push(`{}`, NewlineType.None, node)
return
}
const multilines =
@@ -827,7 +931,7 @@ function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
function genFunctionExpression(
node: FunctionExpression,
- context: CodegenContext
+ context: CodegenContext,
) {
const { push, indent, deindent } = context
const { params, returns, body, newline, isSlot } = node
@@ -835,7 +939,7 @@ function genFunctionExpression(
// wrap slot functions with owner context
push(`_${helperNameMap[WITH_CTX]}(`)
}
- push(`(`, node)
+ push(`(`, NewlineType.None, node)
if (isArray(params)) {
genNodeList(params, context)
} else if (params) {
@@ -872,7 +976,7 @@ function genFunctionExpression(
function genConditionalExpression(
node: ConditionalExpression,
- context: CodegenContext
+ context: CodegenContext,
) {
const { test, consequent, alternate, newline: needNewline } = node
const { push, indent, deindent, newline } = context
@@ -908,16 +1012,23 @@ function genConditionalExpression(
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
const { push, helper, indent, deindent, newline } = context
+ const { needPauseTracking, needArraySpread } = node
+ if (needArraySpread) {
+ push(`[...(`)
+ }
push(`_cache[${node.index}] || (`)
- if (node.isVNode) {
+ if (needPauseTracking) {
indent()
- push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
+ push(`${helper(SET_BLOCK_TRACKING)}(-1`)
+ if (node.inVOnce) push(`, true`)
+ push(`),`)
newline()
+ push(`(`)
}
push(`_cache[${node.index}] = `)
genNode(node.value, context)
- if (node.isVNode) {
- push(`,`)
+ if (needPauseTracking) {
+ push(`).cacheIndex = ${node.index},`)
newline()
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
newline()
@@ -925,6 +1036,9 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
deindent()
}
push(`)`)
+ if (needArraySpread) {
+ push(`)]`)
+ }
}
function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
@@ -935,7 +1049,7 @@ function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
for (let i = 0; i < l; i++) {
const e = node.elements[i]
if (isString(e)) {
- push(e.replace(/(`|\$|\\)/g, '\\$1'))
+ push(e.replace(/(`|\$|\\)/g, '\\$1'), NewlineType.Unknown)
} else {
push('${')
if (multilines) indent()
@@ -973,7 +1087,7 @@ function genIfStatement(node: IfStatement, context: CodegenContext) {
function genAssignmentExpression(
node: AssignmentExpression,
- context: CodegenContext
+ context: CodegenContext,
) {
genNode(node.left, context)
context.push(` = `)
@@ -982,7 +1096,7 @@ function genAssignmentExpression(
function genSequenceExpression(
node: SequenceExpression,
- context: CodegenContext
+ context: CodegenContext,
) {
context.push(`(`)
genNodeList(node.expressions, context)
@@ -991,7 +1105,7 @@ function genSequenceExpression(
function genReturnStatement(
{ returns }: ReturnStatement,
- context: CodegenContext
+ context: CodegenContext,
) {
context.push(`return `)
if (isArray(returns)) {
diff --git a/packages/compiler-core/src/compat/compatConfig.ts b/packages/compiler-core/src/compat/compatConfig.ts
index dcb304263b4..58c5d9611f2 100644
--- a/packages/compiler-core/src/compat/compatConfig.ts
+++ b/packages/compiler-core/src/compat/compatConfig.ts
@@ -1,7 +1,7 @@
-import { SourceLocation } from '../ast'
-import { CompilerError } from '../errors'
-import { ParserContext } from '../parse'
-import { TransformContext } from '../transform'
+import type { SourceLocation } from '../ast'
+import type { CompilerError } from '../errors'
+import type { MergedParserOptions } from '../parser'
+import type { TransformContext } from '../transform'
export type CompilerCompatConfig = Partial<
Record
@@ -13,16 +13,15 @@ export interface CompilerCompatOptions {
compatConfig?: CompilerCompatConfig
}
-export const enum CompilerDeprecationTypes {
+export enum CompilerDeprecationTypes {
COMPILER_IS_ON_ELEMENT = 'COMPILER_IS_ON_ELEMENT',
COMPILER_V_BIND_SYNC = 'COMPILER_V_BIND_SYNC',
- COMPILER_V_BIND_PROP = 'COMPILER_V_BIND_PROP',
COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER',
COMPILER_V_ON_NATIVE = 'COMPILER_V_ON_NATIVE',
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
COMPILER_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE',
COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE',
- COMPILER_FILTERS = 'COMPILER_FILTER'
+ COMPILER_FILTERS = 'COMPILER_FILTERS',
}
type DeprecationData = {
@@ -36,7 +35,7 @@ const deprecationData: Record = {
`Platform-native elements with "is" prop will no longer be ` +
`treated as components in Vue 3 unless the "is" value is explicitly ` +
`prefixed with "vue:".`,
- link: `https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop.html`,
},
[CompilerDeprecationTypes.COMPILER_V_BIND_SYNC]: {
@@ -44,13 +43,7 @@ const deprecationData: Record = {
`.sync modifier for v-bind has been removed. Use v-model with ` +
`argument instead. \`v-bind:${key}.sync\` should be changed to ` +
`\`v-model:${key}\`.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/v-model.html`
- },
-
- [CompilerDeprecationTypes.COMPILER_V_BIND_PROP]: {
- message:
- `.prop modifier for v-bind has been removed and no longer necessary. ` +
- `Vue 3 will automatically set a binding as DOM property when appropriate.`
+ link: `https://v3-migration.vuejs.org/breaking-changes/v-model.html`,
},
[CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER]: {
@@ -60,12 +53,12 @@ const deprecationData: Record = {
`that appears before v-bind in the case of conflict. ` +
`To retain 2.x behavior, move v-bind to make it the first attribute. ` +
`You can also suppress this warning if the usage is intended.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/v-bind.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/v-bind.html`,
},
[CompilerDeprecationTypes.COMPILER_V_ON_NATIVE]: {
message: `.native modifier for v-on has been removed as is no longer necessary.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/v-on-native-modifier-removed.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/v-on-native-modifier-removed.html`,
},
[CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE]: {
@@ -75,18 +68,18 @@ const deprecationData: Record = {
`access to v-for scope variables. It is best to avoid the ambiguity ` +
`with tags or use a computed property that filters v-for ` +
`data source.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/v-if-v-for.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/v-if-v-for.html`,
},
[CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE]: {
message:
` with no special directives will render as a native template ` +
- `element instead of its inner content in Vue 3.`
+ `element instead of its inner content in Vue 3.`,
},
[CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE]: {
message: `"inline-template" has been removed in Vue 3.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/inline-template-attribute.html`
+ link: `https://v3-migration.vuejs.org/breaking-changes/inline-template-attribute.html`,
},
[CompilerDeprecationTypes.COMPILER_FILTERS]: {
@@ -94,18 +87,15 @@ const deprecationData: Record = {
`filters have been removed in Vue 3. ` +
`The "|" symbol will be treated as native JavaScript bitwise OR operator. ` +
`Use method calls or computed properties instead.`,
- link: `https://v3-migration.vuejs.org/breaking-changes/filters.html`
- }
+ link: `https://v3-migration.vuejs.org/breaking-changes/filters.html`,
+ },
}
function getCompatValue(
key: CompilerDeprecationTypes | 'MODE',
- context: ParserContext | TransformContext
+ { compatConfig }: MergedParserOptions | TransformContext,
) {
- const config = (context as ParserContext).options
- ? (context as ParserContext).options.compatConfig
- : (context as TransformContext).compatConfig
- const value = config && config[key]
+ const value = compatConfig && compatConfig[key]
if (key === 'MODE') {
return value || 3 // compiler defaults to v3 behavior
} else {
@@ -115,8 +105,8 @@ function getCompatValue(
export function isCompatEnabled(
key: CompilerDeprecationTypes,
- context: ParserContext | TransformContext
-) {
+ context: MergedParserOptions | TransformContext,
+): boolean {
const mode = getCompatValue('MODE', context)
const value = getCompatValue(key, context)
// in v3 mode, only enable if explicitly set to true
@@ -126,7 +116,7 @@ export function isCompatEnabled(
export function checkCompatEnabled(
key: CompilerDeprecationTypes,
- context: ParserContext | TransformContext,
+ context: MergedParserOptions | TransformContext,
loc: SourceLocation | null,
...args: any[]
): boolean {
@@ -139,10 +129,10 @@ export function checkCompatEnabled(
export function warnDeprecation(
key: CompilerDeprecationTypes,
- context: ParserContext | TransformContext,
+ context: MergedParserOptions | TransformContext,
loc: SourceLocation | null,
...args: any[]
-) {
+): void {
const val = getCompatValue(key, context)
if (val === 'suppress-warning') {
return
diff --git a/packages/compiler-core/src/compat/transformFilter.ts b/packages/compiler-core/src/compat/transformFilter.ts
index 8eaa81eb3f1..4791e67543e 100644
--- a/packages/compiler-core/src/compat/transformFilter.ts
+++ b/packages/compiler-core/src/compat/transformFilter.ts
@@ -1,19 +1,18 @@
import { RESOLVE_FILTER } from '../runtimeHelpers'
import {
- AttributeNode,
- DirectiveNode,
- NodeTransform,
+ type AttributeNode,
+ type DirectiveNode,
+ type ExpressionNode,
NodeTypes,
- SimpleExpressionNode,
- toValidAssetId,
- TransformContext
-} from '@vue/compiler-core'
+ type SimpleExpressionNode,
+} from '../ast'
import {
CompilerDeprecationTypes,
isCompatEnabled,
- warnDeprecation
+ warnDeprecation,
} from './compatConfig'
-import { ExpressionNode } from '../ast'
+import type { NodeTransform, TransformContext } from '../transform'
+import { toValidAssetId } from '../utils'
const validDivisionCharRE = /[\w).+\-_$\]]/
@@ -26,9 +25,7 @@ export const transformFilter: NodeTransform = (node, context) => {
// filter rewrite is applied before expression transform so only
// simple expressions are possible at this stage
rewriteFilter(node.content, context)
- }
-
- if (node.type === NodeTypes.ELEMENT) {
+ } else if (node.type === NodeTypes.ELEMENT) {
node.props.forEach((prop: AttributeNode | DirectiveNode) => {
if (
prop.type === NodeTypes.DIRECTIVE &&
@@ -163,19 +160,21 @@ function parseFilter(node: SimpleExpressionNode, context: TransformContext) {
warnDeprecation(
CompilerDeprecationTypes.COMPILER_FILTERS,
context,
- node.loc
+ node.loc,
)
for (i = 0; i < filters.length; i++) {
expression = wrapFilter(expression, filters[i], context)
}
node.content = expression
+ // reset ast since the content is replaced
+ node.ast = undefined
}
}
function wrapFilter(
exp: string,
filter: string,
- context: TransformContext
+ context: TransformContext,
): string {
context.helper(RESOLVE_FILTER)
const i = filter.indexOf('(')
diff --git a/packages/compiler-core/src/compile.ts b/packages/compiler-core/src/compile.ts
index 95e3718964a..a697c9d22e6 100644
--- a/packages/compiler-core/src/compile.ts
+++ b/packages/compiler-core/src/compile.ts
@@ -1,9 +1,13 @@
-import { CompilerOptions } from './options'
-import { baseParse } from './parse'
-import { transform, NodeTransform, DirectiveTransform } from './transform'
-import { generate, CodegenResult } from './codegen'
-import { RootNode } from './ast'
-import { isString, extend } from '@vue/shared'
+import type { CompilerOptions } from './options'
+import { baseParse } from './parser'
+import {
+ type DirectiveTransform,
+ type NodeTransform,
+ transform,
+} from './transform'
+import { type CodegenResult, generate } from './codegen'
+import type { RootNode } from './ast'
+import { extend, isString } from '@vue/shared'
import { transformIf } from './transforms/vIf'
import { transformFor } from './transforms/vFor'
import { transformExpression } from './transforms/transformExpression'
@@ -16,16 +20,16 @@ import { transformText } from './transforms/transformText'
import { transformOnce } from './transforms/vOnce'
import { transformModel } from './transforms/vModel'
import { transformFilter } from './compat/transformFilter'
-import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
+import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
import { transformMemo } from './transforms/vMemo'
export type TransformPreset = [
NodeTransform[],
- Record
+ Record,
]
export function getBaseTransformPreset(
- prefixIdentifiers?: boolean
+ prefixIdentifiers?: boolean,
): TransformPreset {
return [
[
@@ -38,33 +42,33 @@ export function getBaseTransformPreset(
? [
// order is important
trackVForSlotScopes,
- transformExpression
+ transformExpression,
]
: __BROWSER__ && __DEV__
- ? [transformExpression]
- : []),
+ ? [transformExpression]
+ : []),
transformSlotOutlet,
transformElement,
trackSlotScopes,
- transformText
+ transformText,
],
{
on: transformOn,
bind: transformBind,
- model: transformModel
- }
+ model: transformModel,
+ },
]
}
// we name it `baseCompile` so that higher order compilers like
// @vue/compiler-dom can export `compile` while re-exporting everything else.
export function baseCompile(
- template: string | RootNode,
- options: CompilerOptions = {}
+ source: string | RootNode,
+ options: CompilerOptions = {},
): CodegenResult {
const onError = options.onError || defaultOnError
const isModuleMode = options.mode === 'module'
- /* istanbul ignore if */
+ /* v8 ignore start */
if (__BROWSER__) {
if (options.prefixIdentifiers === true) {
onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED))
@@ -72,6 +76,7 @@ export function baseCompile(
onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
}
}
+ /* v8 ignore stop */
const prefixIdentifiers =
!__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode)
@@ -82,7 +87,10 @@ export function baseCompile(
onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
}
- const ast = isString(template) ? baseParse(template, options) : template
+ const resolvedOptions = extend({}, options, {
+ prefixIdentifiers,
+ })
+ const ast = isString(source) ? baseParse(source, resolvedOptions) : source
const [nodeTransforms, directiveTransforms] =
getBaseTransformPreset(prefixIdentifiers)
@@ -95,24 +103,18 @@ export function baseCompile(
transform(
ast,
- extend({}, options, {
- prefixIdentifiers,
+ extend({}, resolvedOptions, {
nodeTransforms: [
...nodeTransforms,
- ...(options.nodeTransforms || []) // user transforms
+ ...(options.nodeTransforms || []), // user transforms
],
directiveTransforms: extend(
{},
directiveTransforms,
- options.directiveTransforms || {} // user transforms
- )
- })
+ options.directiveTransforms || {}, // user transforms
+ ),
+ }),
)
- return generate(
- ast,
- extend({}, options, {
- prefixIdentifiers
- })
- )
+ return generate(ast, resolvedOptions)
}
diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts
index f0af09dda31..58e113ab19e 100644
--- a/packages/compiler-core/src/errors.ts
+++ b/packages/compiler-core/src/errors.ts
@@ -1,4 +1,4 @@
-import { SourceLocation } from './ast'
+import type { SourceLocation } from './ast'
export interface CompilerError extends SyntaxError {
code: number | string
@@ -9,11 +9,11 @@ export interface CoreCompilerError extends CompilerError {
code: ErrorCodes
}
-export function defaultOnError(error: CompilerError) {
+export function defaultOnError(error: CompilerError): never {
throw error
}
-export function defaultOnWarn(msg: CompilerError) {
+export function defaultOnWarn(msg: CompilerError): void {
__DEV__ && console.warn(`[Vue warn] ${msg.message}`)
}
@@ -25,19 +25,19 @@ export function createCompilerError(
code: T,
loc?: SourceLocation,
messages?: { [code: number]: string },
- additionalMessage?: string
+ additionalMessage?: string,
): InferCompilerError {
const msg =
__DEV__ || !__BROWSER__
? (messages || errorMessages)[code] + (additionalMessage || ``)
- : code
+ : `https://vuejs.org/error-reference/#compiler-${code}`
const error = new SyntaxError(String(msg)) as InferCompilerError
error.code = code
error.loc = loc
return error
}
-export const enum ErrorCodes {
+export enum ErrorCodes {
// parse errors
ABRUPT_CLOSING_OF_EMPTY_COMMENT,
CDATA_IN_HTML_CONTENT,
@@ -87,6 +87,7 @@ export const enum ErrorCodes {
X_V_MODEL_NO_EXPRESSION,
X_V_MODEL_MALFORMED_EXPRESSION,
X_V_MODEL_ON_SCOPE_VARIABLE,
+ X_V_MODEL_ON_PROPS,
X_INVALID_EXPRESSION,
X_KEEP_ALIVE_INVALID_CHILDREN,
@@ -95,11 +96,16 @@ export const enum ErrorCodes {
X_MODULE_MODE_NOT_SUPPORTED,
X_CACHE_HANDLER_NOT_SUPPORTED,
X_SCOPE_ID_NOT_SUPPORTED,
+ X_VNODE_HOOKS,
+
+ // placed here to preserve order for the current minor
+ // TODO adjust order in 3.5
+ X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
// Special value for higher-order compilers to pick up the last code
// to avoid collision of error codes. This should always be kept as the last
// item.
- __EXTEND_POINT__
+ __EXTEND_POINT__,
}
export const errorMessages: Record = {
@@ -154,10 +160,11 @@ export const errorMessages: Record = {
[ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
[ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT]: ` key should be placed on the tag.`,
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
+ [ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT]: `v-bind with same-name shorthand only allows static argument.`,
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on outlet.`,
[ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE]:
- `Mixed v-slot usage on both the component and nested .` +
+ `Mixed v-slot usage on both the component and nested . ` +
`When there are multiple named slots, all slots should use ` +
`syntax to avoid scope ambiguity.`,
[ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES]: `Duplicate slot names found. `,
@@ -168,8 +175,10 @@ export const errorMessages: Record = {
[ErrorCodes.X_V_MODEL_NO_EXPRESSION]: `v-model is missing expression.`,
[ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]: `v-model value must be a valid JavaScript member expression.`,
[ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE]: `v-model cannot be used on v-for or v-slot scope variables because they are not writable.`,
+ [ErrorCodes.X_V_MODEL_ON_PROPS]: `v-model cannot be used on a prop, because local prop bindings are not writable.\nUse a v-bind binding combined with a v-on listener that emits update:x event instead.`,
[ErrorCodes.X_INVALID_EXPRESSION]: `Error parsing JavaScript expression: `,
[ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN]: ` expects exactly one child component.`,
+ [ErrorCodes.X_VNODE_HOOKS]: `@vnode-* hooks in templates are no longer supported. Use the vue: prefix instead. For example, @vnode-mounted should be changed to @vue:mounted. @vnode-* hooks support has been removed in 3.4.`,
// generic errors
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
@@ -178,5 +187,5 @@ export const errorMessages: Record = {
[ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED]: `"scopeId" option is only supported in module mode.`,
// just to fulfill types
- [ErrorCodes.__EXTEND_POINT__]: ``
+ [ErrorCodes.__EXTEND_POINT__]: ``,
}
diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts
index a68d2395815..29e5f681300 100644
--- a/packages/compiler-core/src/index.ts
+++ b/packages/compiler-core/src/index.ts
@@ -2,31 +2,38 @@ export { baseCompile } from './compile'
// Also expose lower level APIs & types
export {
- CompilerOptions,
- ParserOptions,
- TransformOptions,
- CodegenOptions,
- HoistTransform,
- BindingMetadata,
- BindingTypes
+ type CompilerOptions,
+ type ParserOptions,
+ type TransformOptions,
+ type CodegenOptions,
+ type HoistTransform,
+ type BindingMetadata,
+ BindingTypes,
} from './options'
-export { baseParse, TextModes } from './parse'
+export { baseParse } from './parser'
export {
transform,
- TransformContext,
+ type TransformContext,
createTransformContext,
traverseNode,
createStructuralDirectiveTransform,
- NodeTransform,
- StructuralDirectiveTransform,
- DirectiveTransform
+ type NodeTransform,
+ type StructuralDirectiveTransform,
+ type DirectiveTransform,
} from './transform'
-export { generate, CodegenContext, CodegenResult } from './codegen'
+export {
+ generate,
+ type CodegenContext,
+ type CodegenResult,
+ type CodegenSourceMapGenerator,
+ type RawSourceMap,
+} from './codegen'
export {
ErrorCodes,
- CoreCompilerError,
- CompilerError,
- createCompilerError
+ errorMessages,
+ createCompilerError,
+ type CoreCompilerError,
+ type CompilerError,
} from './errors'
export * from './ast'
@@ -34,7 +41,7 @@ export * from './utils'
export * from './babelUtils'
export * from './runtimeHelpers'
-export { getBaseTransformPreset, TransformPreset } from './compile'
+export { getBaseTransformPreset, type TransformPreset } from './compile'
export { transformModel } from './transforms/vModel'
export { transformOn } from './transforms/vOn'
export { transformBind } from './transforms/vBind'
@@ -43,27 +50,29 @@ export { processIf } from './transforms/vIf'
export { processFor, createForLoopParams } from './transforms/vFor'
export {
transformExpression,
- processExpression
+ processExpression,
+ stringifyExpression,
} from './transforms/transformExpression'
export {
buildSlots,
- SlotFnBuilder,
+ type SlotFnBuilder,
trackVForSlotScopes,
- trackSlotScopes
+ trackSlotScopes,
} from './transforms/vSlot'
export {
transformElement,
resolveComponentType,
buildProps,
buildDirectiveArgs,
- PropsExpression
+ type PropsExpression,
} from './transforms/transformElement'
export { processSlotOutlet } from './transforms/transformSlotOutlet'
+export { getConstantType } from './transforms/cacheStatic'
export { generateCodeFrame } from '@vue/shared'
// v2 compat only
export {
checkCompatEnabled,
warnDeprecation,
- CompilerDeprecationTypes
+ CompilerDeprecationTypes,
} from './compat/compatConfig'
diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts
index a0658b4d75d..1de865f42eb 100644
--- a/packages/compiler-core/src/options.ts
+++ b/packages/compiler-core/src/options.ts
@@ -1,13 +1,18 @@
-import { ElementNode, Namespace, TemplateChildNode, ParentNode } from './ast'
-import { TextModes } from './parse'
-import { CompilerError } from './errors'
-import {
- NodeTransform,
+import type {
+ ElementNode,
+ Namespace,
+ Namespaces,
+ ParentNode,
+ TemplateChildNode,
+} from './ast'
+import type { CompilerError } from './errors'
+import type {
DirectiveTransform,
- TransformContext
+ NodeTransform,
+ TransformContext,
} from './transform'
-import { CompilerCompatOptions } from './compat/compatConfig'
-import { ParserPlugin } from '@babel/parser'
+import type { CompilerCompatOptions } from './compat/compatConfig'
+import type { ParserPlugin } from '@babel/parser'
export interface ErrorHandlingOptions {
onWarn?: (warning: CompilerError) => void
@@ -17,6 +22,24 @@ export interface ErrorHandlingOptions {
export interface ParserOptions
extends ErrorHandlingOptions,
CompilerCompatOptions {
+ /**
+ * Base mode is platform agnostic and only parses HTML-like template syntax,
+ * treating all tags the same way. Specific tag parsing behavior can be
+ * configured by higher-level compilers.
+ *
+ * HTML mode adds additional logic for handling special parsing behavior in
+ * `
+ this.emitCodePoint(cp, consumed),
+ )
+ }
+ }
+
+ public reset(): void {
+ this.state = State.Text
+ this.mode = ParseMode.BASE
+ this.buffer = ''
+ this.sectionStart = 0
+ this.index = 0
+ this.baseState = State.Text
+ this.inRCDATA = false
+ this.currentSequence = undefined!
+ this.newlines.length = 0
+ this.delimiterOpen = defaultDelimitersOpen
+ this.delimiterClose = defaultDelimitersClose
+ }
+
+ /**
+ * Generate Position object with line / column information using recorded
+ * newline positions. We know the index is always going to be an already
+ * processed index, so all the newlines up to this index should have been
+ * recorded.
+ */
+ public getPos(index: number): Position {
+ let line = 1
+ let column = index + 1
+ for (let i = this.newlines.length - 1; i >= 0; i--) {
+ const newlineIndex = this.newlines[i]
+ if (index > newlineIndex) {
+ line = i + 2
+ column = index - newlineIndex
+ break
+ }
+ }
+ return {
+ column,
+ line,
+ offset: index,
+ }
+ }
+
+ private peek() {
+ return this.buffer.charCodeAt(this.index + 1)
+ }
+
+ private stateText(c: number): void {
+ if (c === CharCodes.Lt) {
+ if (this.index > this.sectionStart) {
+ this.cbs.ontext(this.sectionStart, this.index)
+ }
+ this.state = State.BeforeTagName
+ this.sectionStart = this.index
+ } else if (!__BROWSER__ && c === CharCodes.Amp) {
+ this.startEntity()
+ } else if (!this.inVPre && c === this.delimiterOpen[0]) {
+ this.state = State.InterpolationOpen
+ this.delimiterIndex = 0
+ this.stateInterpolationOpen(c)
+ }
+ }
+
+ public delimiterOpen: Uint8Array = defaultDelimitersOpen
+ public delimiterClose: Uint8Array = defaultDelimitersClose
+ private delimiterIndex = -1
+
+ private stateInterpolationOpen(c: number): void {
+ if (c === this.delimiterOpen[this.delimiterIndex]) {
+ if (this.delimiterIndex === this.delimiterOpen.length - 1) {
+ const start = this.index + 1 - this.delimiterOpen.length
+ if (start > this.sectionStart) {
+ this.cbs.ontext(this.sectionStart, start)
+ }
+ this.state = State.Interpolation
+ this.sectionStart = start
+ } else {
+ this.delimiterIndex++
+ }
+ } else if (this.inRCDATA) {
+ this.state = State.InRCDATA
+ this.stateInRCDATA(c)
+ } else {
+ this.state = State.Text
+ this.stateText(c)
+ }
+ }
+
+ private stateInterpolation(c: number): void {
+ if (c === this.delimiterClose[0]) {
+ this.state = State.InterpolationClose
+ this.delimiterIndex = 0
+ this.stateInterpolationClose(c)
+ }
+ }
+
+ private stateInterpolationClose(c: number) {
+ if (c === this.delimiterClose[this.delimiterIndex]) {
+ if (this.delimiterIndex === this.delimiterClose.length - 1) {
+ this.cbs.oninterpolation(this.sectionStart, this.index + 1)
+ if (this.inRCDATA) {
+ this.state = State.InRCDATA
+ } else {
+ this.state = State.Text
+ }
+ this.sectionStart = this.index + 1
+ } else {
+ this.delimiterIndex++
+ }
+ } else {
+ this.state = State.Interpolation
+ this.stateInterpolation(c)
+ }
+ }
+
+ public currentSequence: Uint8Array = undefined!
+ private sequenceIndex = 0
+ private stateSpecialStartSequence(c: number): void {
+ const isEnd = this.sequenceIndex === this.currentSequence.length
+ const isMatch = isEnd
+ ? // If we are at the end of the sequence, make sure the tag name has ended
+ isEndOfTagSection(c)
+ : // Otherwise, do a case-insensitive comparison
+ (c | 0x20) === this.currentSequence[this.sequenceIndex]
+
+ if (!isMatch) {
+ this.inRCDATA = false
+ } else if (!isEnd) {
+ this.sequenceIndex++
+ return
+ }
+
+ this.sequenceIndex = 0
+ this.state = State.InTagName
+ this.stateInTagName(c)
+ }
+
+ /** Look for an end tag. For and `,
)
+
// should be optimized now
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `
${repeat(
- ` `,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}
`
- ),
- '1'
- ]
- }, // the children array is hoisted as well
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `
${repeat(
+ ` `,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
+ 1,
+ ),
])
+
+ expect(code).toMatchSnapshot()
})
test('should work on eligible content (elements > 20)', () => {
- const { ast } = compileWithStringify(
+ const { code, ast } = compileWithStringify(
`
${repeat(
` `,
- StringifyThresholds.NODE_COUNT
- )}
`
+ StringifyThresholds.NODE_COUNT,
+ )}
`,
)
// should be optimized now
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `
${repeat(
- ` `,
- StringifyThresholds.NODE_COUNT
- )}
`
- ),
- '1'
- ]
- },
- // the children array is hoisted as well
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `
${repeat(` `, StringifyThresholds.NODE_COUNT)}
`,
+ 1,
+ ),
])
+
+ expect(code).toMatchSnapshot()
})
test('should work for multiple adjacent nodes', () => {
- const { ast } = compileWithStringify(
+ const { ast, code } = compileWithStringify(
`
${repeat(
` `,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}
`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
)
- // should have 6 hoisted nodes (including the entire array),
- // but 2~5 should be null because they are merged into 1
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- repeat(
- `
`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )
- ),
- '5'
- ]
- },
- null,
- null,
- null,
- null,
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ repeat(
+ `
`,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ ),
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ ),
])
+
+ expect(code).toMatchSnapshot()
})
test('serializing constant bindings', () => {
- const { ast } = compileWithStringify(
+ const { ast, code } = compileWithStringify(
`
${repeat(
`{{ 1 }} + {{ false }} `,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}
`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
)
// should be optimized now
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `
${repeat(
- `1 + false `,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}
`
- ),
- '1'
- ]
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `
${repeat(
+ `1 + false `,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
+ 1,
+ ),
])
+ expect(code).toMatchSnapshot()
+ })
+
+ // #12391
+ test('serializing template string style', () => {
+ const { ast, code } = compileWithStringify(
+ `
${repeat(
+ `{{ 1 }} + {{ false }} `,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
+ )
+ // should be optimized now
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `
${repeat(
+ `1 + false `,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
+ 1,
+ ),
+ ])
+ expect(code).toMatchSnapshot()
})
test('escape', () => {
- const { ast } = compileWithStringify(
+ const { ast, code } = compileWithStringify(
`
${repeat(
`{{ 1 }} + {{ '<' }} ` +
`& `,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}
`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
)
// should be optimized now
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `${repeat(
- `1 + < ` + `& `,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}
`
- ),
- '1'
- ]
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `${repeat(
+ `1 + < ` + `& `,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
+ 1,
+ ),
])
+ expect(code).toMatchSnapshot()
})
- test('should bail on bindings that are hoisted but not stringifiable', () => {
+ test('should bail on bindings that are cached but not stringifiable', () => {
const { ast, code } = compile(
`${repeat(
`
foo `,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}
`,
{
hoistStatic: true,
@@ -198,7 +221,7 @@ describe('stringify static html', () => {
'_imports_0_',
false,
node.loc,
- ConstantTypes.CAN_HOIST
+ ConstantTypes.CAN_CACHE,
)
node.props[0] = {
type: NodeTypes.DIRECTIVE,
@@ -206,24 +229,14 @@ describe('stringify static html', () => {
arg: createSimpleExpression('src', true),
exp,
modifiers: [],
- loc: node.loc
+ loc: node.loc,
}
}
- }
- ]
- }
- )
- expect(ast.hoists).toMatchObject([
- {
- // the expression and the tree are still hoistable
- // but should stay NodeTypes.VNODE_CALL
- // if it's stringified it will be NodeTypes.JS_CALL_EXPRESSION
- type: NodeTypes.VNODE_CALL
+ },
+ ],
},
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
- ])
+ )
+ expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
expect(code).toMatchSnapshot()
})
@@ -233,7 +246,7 @@ describe('stringify static html', () => {
const { ast, code } = compile(
`${repeat(
`
foo `,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}
`,
{
hoistStatic: true,
@@ -246,7 +259,7 @@ describe('stringify static html', () => {
'_imports_0_',
false,
node.loc,
- ConstantTypes.CAN_STRINGIFY
+ ConstantTypes.CAN_STRINGIFY,
)
node.props[0] = {
type: NodeTypes.DIRECTIVE,
@@ -254,160 +267,126 @@ describe('stringify static html', () => {
arg: createSimpleExpression('src', true),
exp,
modifiers: [],
- loc: node.loc
+ loc: node.loc,
}
}
- }
- ]
- }
- )
- expect(ast.hoists).toMatchObject([
- {
- // the hoisted node should be NodeTypes.JS_CALL_EXPRESSION
- // of `createStaticVNode()` instead of dynamic NodeTypes.VNODE_CALL
- type: NodeTypes.JS_CALL_EXPRESSION
+ },
+ ],
},
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
- ])
+ )
+ expect(ast.cached).toMatchObject([cachedArraySuccessMatcher()])
expect(code).toMatchSnapshot()
})
// #1128
- test('should bail on non attribute bindings', () => {
+ test('should bail on non-attribute bindings', () => {
const { ast } = compileWithStringify(
``
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )} `,
)
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
- ])
+ expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
const { ast: ast2 } = compileWithStringify(
``
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}`,
)
- expect(ast2.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
- ])
- })
+ expect(ast2.cached).toMatchObject([cachedArrayBailedMatcher()])
- test('should bail on non attribute bindings', () => {
- const { ast } = compileWithStringify(
+ const { ast: ast3 } = compileWithStringify(
``
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )} `,
)
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
- ])
+ expect(ast3.cached).toMatchObject([cachedArrayBailedMatcher()])
- const { ast: ast2 } = compileWithStringify(
+ const { ast: ast4 } = compileWithStringify(
``
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )} `,
)
- expect(ast2.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
- ])
+ expect(ast4.cached).toMatchObject([cachedArrayBailedMatcher()])
})
test('should bail on tags that has placement constraints (eg.tables related tags)', () => {
const { ast } = compileWithStringify(
`${repeat(
`foo `,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}
`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}`,
)
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION
- }
- ])
+ expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
})
test('should bail inside slots', () => {
const { ast } = compileWithStringify(
`${repeat(
`
`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )} `
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}`,
)
- expect(ast.hoists.length).toBe(
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )
- ast.hoists.forEach(node => {
- expect(node).toMatchObject({
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
- })
- })
+ expect(ast.cached).toMatchObject([
+ cachedArrayBailedMatcher(StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
+ ])
const { ast: ast2 } = compileWithStringify(
`${repeat(
`
`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )} `
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )} `,
)
- expect(ast2.hoists.length).toBe(
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )
- ast2.hoists.forEach(node => {
- expect(node).toMatchObject({
- type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION
- })
- })
+ expect(ast2.cached).toMatchObject([
+ cachedArrayBailedMatcher(StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
+ ])
})
test('should remove attribute for `null`', () => {
const { ast } = compileWithStringify(
`${repeat(
` `,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}
`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}`,
)
- expect(ast.hoists[0]).toMatchObject({
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `${repeat(
- ` `,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}`
- ),
- '5'
- ]
- })
+
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ repeat(` `, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ ),
+ ])
+ })
+
+ // #6617
+ test('should remove boolean attribute for `false`', () => {
+ const { ast } = compileWithStringify(
+ `enable ${repeat(
+ `
`,
+ StringifyThresholds.NODE_COUNT,
+ )}`,
+ )
+ expect(ast.cached).toMatchObject([
+ {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: CREATE_STATIC,
+ arguments: [
+ JSON.stringify(
+ `enable ${repeat(
+ `
`,
+ StringifyThresholds.NODE_COUNT,
+ )}`,
+ ),
+ '21',
+ ],
+ },
+ },
+ ])
})
test('should stringify svg', () => {
@@ -416,21 +395,134 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify(
`${svg}${repeat(
repeated,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )}
`
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )} `,
)
- expect(ast.hoists[0]).toMatchObject({
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `${svg}${repeat(
- repeated,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
- )} `
- ),
- '1'
- ]
- })
+
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `${svg}${repeat(
+ repeated,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}`,
+ 1,
+ ),
+ ])
+ })
+
+ test('should stringify mathML', () => {
+ const math = ``
+ const repeated = `1 `
+ const { ast } = compileWithStringify(
+ `${math}${repeat(
+ repeated,
+ StringifyThresholds.NODE_COUNT,
+ )}
`,
+ )
+
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `${math}${repeat(repeated, StringifyThresholds.NODE_COUNT)} `,
+ 1,
+ ),
+ ])
+ })
+
+ // #5439
+ test('stringify v-html', () => {
+ const { code } = compileWithStringify(`
+
+
+ 1 2
+
`)
+ expect(code).toMatch(`show-it
`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('stringify v-text', () => {
+ const { code } = compileWithStringify(`
+
+
+ 1 2
+
`)
+ expect(code).toMatch(`<span>show-it </span>
`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('stringify v-text with escape', () => {
+ const { code } = compileWithStringify(`
+
+
+ 1 2
+
`)
+ expect(code).toMatch(`text1
`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should work for elements with string values', () => {
+ const { ast, code } = compileWithStringify(
+ `${repeat(
+ ` `,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
+ )
+ // should be optimized now
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `${repeat(
+ ` `,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )} `,
+ 1,
+ ),
+ ])
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should bail for elements with number values', () => {
+ const { ast, code } = compileWithStringify(
+ `${repeat(
+ ` `,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
+ )
+ expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should bail for comments', () => {
+ const { code } = compileWithStringify(
+ `${repeat(
+ ` `,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
+ )
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should bail for elements with null values', () => {
+ const { ast, code } = compileWithStringify(
+ ` ${repeat(
+ ` `,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}
`,
+ )
+ expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
+ expect(code).toMatchSnapshot()
+ })
+
+ test('eligible content (elements > 20) + non-eligible content', () => {
+ const { code } = compileWithStringify(
+ `${repeat(
+ `
`,
+ StringifyThresholds.NODE_COUNT,
+ )}
1
${repeat(
+ `
`,
+ StringifyThresholds.NODE_COUNT,
+ )}
`,
+ )
+
+ expect(code).toMatchSnapshot()
})
})
diff --git a/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts b/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
index 6eee9f8ba38..51c40c45d98 100644
--- a/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
+++ b/packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
@@ -1,10 +1,10 @@
import {
+ type CompilerOptions,
+ type ElementNode,
+ NodeTypes,
+ type VNodeCall,
baseParse as parse,
transform,
- CompilerOptions,
- ElementNode,
- NodeTypes,
- VNodeCall
} from '@vue/compiler-core'
import { transformBind } from '../../../compiler-core/src/transforms/vBind'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
@@ -12,16 +12,16 @@ import { transformStyle } from '../../src/transforms/transformStyle'
function transformWithStyleTransform(
template: string,
- options: CompilerOptions = {}
+ options: CompilerOptions = {},
) {
const ast = parse(template)
transform(ast, {
nodeTransforms: [transformStyle],
- ...options
+ ...options,
})
return {
root: ast,
- node: ast.children[0] as ElementNode
+ node: ast.children[0] as ElementNode,
}
}
@@ -34,13 +34,13 @@ describe('compiler: style transform', () => {
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
- isStatic: true
+ isStatic: true,
},
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{"color":"red"}`,
- isStatic: false
- }
+ isStatic: false,
+ },
})
})
@@ -48,8 +48,8 @@ describe('compiler: style transform', () => {
const { node } = transformWithStyleTransform(`
`, {
nodeTransforms: [transformStyle, transformElement],
directiveTransforms: {
- bind: transformBind
- }
+ bind: transformBind,
+ },
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
@@ -58,15 +58,15 @@ describe('compiler: style transform', () => {
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
- isStatic: true
+ isStatic: true,
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{"color":"red"}`,
- isStatic: false
- }
- }
- ]
+ isStatic: false,
+ },
+ },
+ ],
})
// should not cause the STYLE patchFlag to be attached
expect((node.codegenNode as VNodeCall).patchFlag).toBeUndefined()
diff --git a/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts b/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts
index 8c441cef0e4..bca2fdf9558 100644
--- a/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts
+++ b/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts
@@ -1,15 +1,12 @@
import {
+ type CompilerOptions,
+ type PlainElementNode,
baseParse as parse,
transform,
- PlainElementNode,
- CompilerOptions
} from '@vue/compiler-core'
import { transformVHtml } from '../../src/transforms/vHtml'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
-import {
- createObjectMatcher,
- genFlagText
-} from '../../../compiler-core/__tests__/testUtils'
+import { createObjectMatcher } from '../../../compiler-core/__tests__/testUtils'
import { PatchFlags } from '@vue/shared'
import { DOMErrorCodes } from '../../src/errors'
@@ -18,9 +15,9 @@ function transformWithVHtml(template: string, options: CompilerOptions = {}) {
transform(ast, {
nodeTransforms: [transformElement],
directiveTransforms: {
- html: transformVHtml
+ html: transformVHtml,
},
- ...options
+ ...options,
})
return ast
}
@@ -31,40 +28,40 @@ describe('compiler: v-html transform', () => {
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
- innerHTML: `[test]`
+ innerHTML: `[test]`,
}),
children: undefined,
- patchFlag: genFlagText(PatchFlags.PROPS),
- dynamicProps: `["innerHTML"]`
+ patchFlag: PatchFlags.PROPS,
+ dynamicProps: `["innerHTML"]`,
})
})
it('should raise error and ignore children when v-html is present', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
const ast = transformWithVHtml(`hello
`, {
- onError
+ onError,
})
expect(onError.mock.calls).toMatchObject([
- [{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }]
+ [{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }],
])
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
- innerHTML: `[test]`
+ innerHTML: `[test]`,
}),
children: undefined, // <-- children should have been removed
- patchFlag: genFlagText(PatchFlags.PROPS),
- dynamicProps: `["innerHTML"]`
+ patchFlag: PatchFlags.PROPS,
+ dynamicProps: `["innerHTML"]`,
})
})
it('should raise error if has no expression', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
transformWithVHtml(`
`, {
- onError
+ onError,
})
expect(onError.mock.calls).toMatchObject([
- [{ code: DOMErrorCodes.X_V_HTML_NO_EXPRESSION }]
+ [{ code: DOMErrorCodes.X_V_HTML_NO_EXPRESSION }],
])
})
})
diff --git a/packages/compiler-dom/__tests__/transforms/vModel.spec.ts b/packages/compiler-dom/__tests__/transforms/vModel.spec.ts
index 3da0b30085f..02d188f01b9 100644
--- a/packages/compiler-dom/__tests__/transforms/vModel.spec.ts
+++ b/packages/compiler-dom/__tests__/transforms/vModel.spec.ts
@@ -1,8 +1,8 @@
import {
+ type CompilerOptions,
+ generate,
baseParse as parse,
transform,
- CompilerOptions,
- generate
} from '@vue/compiler-core'
import { transformModel } from '../../src/transforms/vModel'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
@@ -12,7 +12,7 @@ import {
V_MODEL_DYNAMIC,
V_MODEL_RADIO,
V_MODEL_SELECT,
- V_MODEL_TEXT
+ V_MODEL_TEXT,
} from '../../src/runtimeHelpers'
function transformWithModel(template: string, options: CompilerOptions = {}) {
@@ -20,9 +20,9 @@ function transformWithModel(template: string, options: CompilerOptions = {}) {
transform(ast, {
nodeTransforms: [transformElement],
directiveTransforms: {
- model: transformModel
+ model: transformModel,
},
- ...options
+ ...options,
})
return ast
}
@@ -70,7 +70,7 @@ describe('compiler: transform v-model', () => {
expect(generate(root).code).toMatchSnapshot()
const root2 = transformWithModel(
- ' '
+ ' ',
)
expect(root2.helpers).toContain(V_MODEL_DYNAMIC)
expect(generate(root2).code).toMatchSnapshot()
@@ -92,34 +92,34 @@ describe('compiler: transform v-model', () => {
describe('errors', () => {
test('plain elements with argument', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
transformWithModel(' ', { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT
- })
+ code: DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
+ }),
)
})
test('invalid element', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
transformWithModel(' ', { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT
- })
+ code: DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
+ }),
)
})
test('should allow usage on custom element', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
const root = transformWithModel(' ', {
onError,
- isCustomElement: tag => tag.startsWith('my-')
+ isCustomElement: tag => tag.startsWith('my-'),
})
expect(root.helpers).toContain(V_MODEL_TEXT)
expect(onError).not.toHaveBeenCalled()
@@ -127,16 +127,37 @@ describe('compiler: transform v-model', () => {
})
test('should raise error if used file input element', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
transformWithModel(` `, {
- onError
+ onError,
})
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT
- })
+ code: DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
+ }),
)
})
+
+ test('should error on dynamic value binding alongside v-model', () => {
+ const onError = vi.fn()
+ transformWithModel(` `, {
+ onError,
+ })
+ expect(onError).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
+ }),
+ )
+ })
+
+ // #3596
+ test('should NOT error on static value binding alongside v-model', () => {
+ const onError = vi.fn()
+ transformWithModel(` `, {
+ onError,
+ })
+ expect(onError).not.toHaveBeenCalled()
+ })
})
describe('modifiers', () => {
diff --git a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts
index efc7fee374f..ee9ea93c33d 100644
--- a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts
+++ b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts
@@ -1,19 +1,19 @@
import {
- baseParse as parse,
- CompilerOptions,
- ElementNode,
+ BindingTypes,
+ type CompilerOptions,
+ type ElementNode,
+ NodeTypes,
+ type ObjectExpression,
TO_HANDLER_KEY,
+ type VNodeCall,
helperNameMap,
- NodeTypes,
- ObjectExpression,
+ baseParse as parse,
transform,
- VNodeCall
} from '@vue/compiler-core'
import { transformOn } from '../../src/transforms/vOn'
import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../../src/runtimeHelpers'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
import { transformExpression } from '../../../compiler-core/src/transforms/transformExpression'
-import { genFlagText } from '../../../compiler-core/__tests__/testUtils'
import { PatchFlags } from '@vue/shared'
function parseWithVOn(template: string, options: CompilerOptions = {}) {
@@ -21,32 +21,31 @@ function parseWithVOn(template: string, options: CompilerOptions = {}) {
transform(ast, {
nodeTransforms: [transformExpression, transformElement],
directiveTransforms: {
- on: transformOn
+ on: transformOn,
},
- ...options
+ ...options,
})
+ const node = (ast.children[0] as ElementNode).codegenNode as VNodeCall
return {
root: ast,
- props: (
- ((ast.children[0] as ElementNode).codegenNode as VNodeCall)
- .props as ObjectExpression
- ).properties
+ node,
+ props: (node.props as ObjectExpression).properties,
}
}
describe('compiler-dom: transform v-on', () => {
it('should support multiple modifiers w/ prefixIdentifiers: true', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: '_ctx.test' }, '["stop","prevent"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["stop","prevent"]'],
+ },
})
})
@@ -54,8 +53,8 @@ describe('compiler-dom: transform v-on', () => {
const { props } = parseWithVOn(
`
`,
{
- prefixIdentifiers: true
- }
+ prefixIdentifiers: true,
+ },
)
const [clickProp, keyUpProp] = props
@@ -64,95 +63,95 @@ describe('compiler-dom: transform v-on', () => {
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: '_ctx.test' }, '["stop"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["stop"]'],
+ },
})
expect(keyUpProp).toMatchObject({
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_KEYS,
- arguments: [{ content: '_ctx.test' }, '["enter"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["enter"]'],
+ },
})
})
it('should support multiple modifiers and event options w/ prefixIdentifiers: true', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
key: {
- content: `onClickCaptureOnce`
+ content: `onClickCaptureOnce`,
},
value: {
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: '_ctx.test' }, '["stop"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["stop"]'],
+ },
})
})
it('should wrap keys guard for keyboard events or dynamic events', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
key: {
- content: `onKeydownCapture`
+ content: `onKeydownCapture`,
},
value: {
callee: V_ON_WITH_KEYS,
arguments: [
{
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: '_ctx.test' }, '["stop","ctrl"]']
+ arguments: [{ content: '_ctx.test' }, '["stop","ctrl"]'],
},
- '["a"]'
- ]
- }
+ '["a"]',
+ ],
+ },
})
})
it('should not wrap keys guard if no key modifier is present', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: '_ctx.test' }, '["exact"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["exact"]'],
+ },
})
})
it('should wrap keys guard for static key event w/ left/right modifiers', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_KEYS,
- arguments: [{ content: '_ctx.test' }, '["left"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["left"]'],
+ },
})
})
it('should wrap both for dynamic key event w/ left/right modifiers', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
@@ -161,41 +160,41 @@ describe('compiler-dom: transform v-on', () => {
arguments: [
{
callee: V_ON_WITH_MODIFIERS,
- arguments: [{ content: `_ctx.test` }, `["left"]`]
+ arguments: [{ content: `_ctx.test` }, `["left"]`],
},
- '["left"]'
- ]
- }
+ '["left"]',
+ ],
+ },
})
})
it('should not wrap normal guard if there is only keys guard', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`
`, {
- prefixIdentifiers: true
+ prefixIdentifiers: true,
})
expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY,
value: {
callee: V_ON_WITH_KEYS,
- arguments: [{ content: '_ctx.test' }, '["enter"]']
- }
+ arguments: [{ content: '_ctx.test' }, '["enter"]'],
+ },
})
})
test('should transform click.right', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`
`)
expect(prop.key).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `onContextmenu`
+ content: `onContextmenu`,
})
// dynamic
const {
- props: [prop2]
+ props: [prop2],
} = parseWithVOn(`
`)
// (_toHandlerKey(event)).toLowerCase() === "onclick" ? "onContextmenu" : (_toHandlerKey(event))
expect(prop2.key).toMatchObject({
@@ -206,34 +205,34 @@ describe('compiler-dom: transform v-on', () => {
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
- `)`
- ]
+ `)`,
+ ],
},
`) === "onClick" ? "onContextmenu" : (`,
{
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
- `)`
- ]
+ `)`,
+ ],
},
- `)`
- ]
+ `)`,
+ ],
})
})
test('should transform click.middle', () => {
const {
- props: [prop]
+ props: [prop],
} = parseWithVOn(`
`)
expect(prop.key).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
- content: `onMouseup`
+ content: `onMouseup`,
})
// dynamic
const {
- props: [prop2]
+ props: [prop2],
} = parseWithVOn(`
`)
// (_eventNaming(event)).toLowerCase() === "onclick" ? "onMouseup" : (_eventNaming(event))
expect(prop2.key).toMatchObject({
@@ -244,48 +243,62 @@ describe('compiler-dom: transform v-on', () => {
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
- `)`
- ]
+ `)`,
+ ],
},
`) === "onClick" ? "onMouseup" : (`,
{
children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
- `)`
- ]
+ `)`,
+ ],
},
- `)`
- ]
+ `)`,
+ ],
})
})
test('cache handler w/ modifiers', () => {
const {
root,
- props: [prop]
+ props: [prop],
} = parseWithVOn(`
`, {
prefixIdentifiers: true,
- cacheHandlers: true
+ cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
// should not treat cached handler as dynamicProp, so it should have no
// dynamicProps flags and only the hydration flag
expect((root as any).children[0].codegenNode.patchFlag).toBe(
- genFlagText(PatchFlags.HYDRATE_EVENTS)
+ PatchFlags.NEED_HYDRATION,
)
expect(prop).toMatchObject({
key: {
- content: `onKeyupCapture`
+ content: `onKeyupCapture`,
},
value: {
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: V_ON_WITH_KEYS
- }
- }
+ callee: V_ON_WITH_KEYS,
+ },
+ },
+ })
+ })
+
+ test('should not have PROPS patchFlag for constant v-on handlers with modifiers', () => {
+ const { node } = parseWithVOn(`
`, {
+ prefixIdentifiers: true,
+ bindingMetadata: {
+ foo: BindingTypes.SETUP_CONST,
+ },
+ directiveTransforms: {
+ on: transformOn,
+ },
})
+ // should only have hydration flag
+ expect(node.patchFlag).toBe(PatchFlags.NEED_HYDRATION)
})
})
diff --git a/packages/compiler-dom/__tests__/transforms/vShow.spec.ts b/packages/compiler-dom/__tests__/transforms/vShow.spec.ts
index 3c70741cb82..11aa62464d3 100644
--- a/packages/compiler-dom/__tests__/transforms/vShow.spec.ts
+++ b/packages/compiler-dom/__tests__/transforms/vShow.spec.ts
@@ -1,8 +1,8 @@
import {
+ type CompilerOptions,
+ generate,
baseParse as parse,
transform,
- generate,
- CompilerOptions
} from '@vue/compiler-core'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
import { transformShow } from '../../src/transforms/vShow'
@@ -13,9 +13,9 @@ function transformWithShow(template: string, options: CompilerOptions = {}) {
transform(ast, {
nodeTransforms: [transformElement],
directiveTransforms: {
- show: transformShow
+ show: transformShow,
},
- ...options
+ ...options,
})
return ast
}
@@ -28,14 +28,14 @@ describe('compiler: v-show transform', () => {
})
test('should raise error if has no expression', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
transformWithShow(`
`, { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
- code: DOMErrorCodes.X_V_SHOW_NO_EXPRESSION
- })
+ code: DOMErrorCodes.X_V_SHOW_NO_EXPRESSION,
+ }),
)
})
})
diff --git a/packages/compiler-dom/__tests__/transforms/vText.spec.ts b/packages/compiler-dom/__tests__/transforms/vText.spec.ts
index 75dbda31fd1..e96ab297298 100644
--- a/packages/compiler-dom/__tests__/transforms/vText.spec.ts
+++ b/packages/compiler-dom/__tests__/transforms/vText.spec.ts
@@ -1,15 +1,12 @@
import {
+ type CompilerOptions,
+ type PlainElementNode,
baseParse as parse,
transform,
- PlainElementNode,
- CompilerOptions
} from '@vue/compiler-core'
import { transformVText } from '../../src/transforms/vText'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
-import {
- createObjectMatcher,
- genFlagText
-} from '../../../compiler-core/__tests__/testUtils'
+import { createObjectMatcher } from '../../../compiler-core/__tests__/testUtils'
import { PatchFlags } from '@vue/shared'
import { DOMErrorCodes } from '../../src/errors'
@@ -18,9 +15,9 @@ function transformWithVText(template: string, options: CompilerOptions = {}) {
transform(ast, {
nodeTransforms: [transformElement],
directiveTransforms: {
- text: transformVText
+ text: transformVText,
},
- ...options
+ ...options,
})
return ast
}
@@ -32,43 +29,43 @@ describe('compiler: v-text transform', () => {
tag: `"div"`,
props: createObjectMatcher({
textContent: {
- arguments: [{ content: 'test' }]
- }
+ arguments: [{ content: 'test' }],
+ },
}),
children: undefined,
- patchFlag: genFlagText(PatchFlags.PROPS),
- dynamicProps: `["textContent"]`
+ patchFlag: PatchFlags.PROPS,
+ dynamicProps: `["textContent"]`,
})
})
it('should raise error and ignore children when v-text is present', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
const ast = transformWithVText(`hello
`, {
- onError
+ onError,
})
expect(onError.mock.calls).toMatchObject([
- [{ code: DOMErrorCodes.X_V_TEXT_WITH_CHILDREN }]
+ [{ code: DOMErrorCodes.X_V_TEXT_WITH_CHILDREN }],
])
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
textContent: {
- arguments: [{ content: 'test' }]
- }
+ arguments: [{ content: 'test' }],
+ },
}),
children: undefined, // <-- children should have been removed
- patchFlag: genFlagText(PatchFlags.PROPS),
- dynamicProps: `["textContent"]`
+ patchFlag: PatchFlags.PROPS,
+ dynamicProps: `["textContent"]`,
})
})
it('should raise error if has no expression', () => {
- const onError = jest.fn()
+ const onError = vi.fn()
transformWithVText(`
`, {
- onError
+ onError,
})
expect(onError.mock.calls).toMatchObject([
- [{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }]
+ [{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }],
])
})
})
diff --git a/packages/compiler-dom/__tests__/transforms/validateHtmlNesting.spec.ts b/packages/compiler-dom/__tests__/transforms/validateHtmlNesting.spec.ts
new file mode 100644
index 00000000000..46d69846bae
--- /dev/null
+++ b/packages/compiler-dom/__tests__/transforms/validateHtmlNesting.spec.ts
@@ -0,0 +1,202 @@
+import { type CompilerError, compile } from '../../src'
+import { isValidHTMLNesting } from '../../src/htmlNesting'
+
+describe('validate html nesting', () => {
+ it('should warn with p > div', () => {
+ let err: CompilerError | undefined
+ compile(`
`, {
+ onWarn: e => (err = e),
+ })
+ expect(err).toBeDefined()
+ expect(err!.message).toMatch(` cannot be child of
`)
+ })
+
+ it('should not warn with select > hr', () => {
+ let err: CompilerError | undefined
+ compile(` `, {
+ onWarn: e => (err = e),
+ })
+ expect(err).toBeUndefined()
+ })
+
+ // #13318
+ it('should not warn when parent tag is template', () => {
+ let err: CompilerError | undefined
+ compile(` `, {
+ onWarn: e => (err = e),
+ })
+ expect(err).toBeUndefined()
+ })
+})
+
+/**
+ * Copied from https://github.com/MananTank/validate-html-nesting
+ * with ISC license
+ */
+describe('isValidHTMLNesting', () => {
+ test('form', () => {
+ // invalid
+ expect(isValidHTMLNesting('form', 'form')).toBe(false)
+
+ // valid
+ expect(isValidHTMLNesting('form', 'div')).toBe(true)
+ expect(isValidHTMLNesting('form', 'input')).toBe(true)
+ expect(isValidHTMLNesting('form', 'select')).toBe(true)
+ expect(isValidHTMLNesting('form', 'button')).toBe(true)
+ expect(isValidHTMLNesting('form', 'label')).toBe(true)
+ expect(isValidHTMLNesting('form', 'h1')).toBe(true)
+ })
+
+ test('p', () => {
+ // invalid
+ expect(isValidHTMLNesting('p', 'p')).toBe(false)
+ expect(isValidHTMLNesting('p', 'div')).toBe(false)
+ expect(isValidHTMLNesting('p', 'hr')).toBe(false)
+ expect(isValidHTMLNesting('p', 'blockquote')).toBe(false)
+ expect(isValidHTMLNesting('p', 'pre')).toBe(false)
+
+ // valid
+ expect(isValidHTMLNesting('p', 'a')).toBe(true)
+ expect(isValidHTMLNesting('p', 'span')).toBe(true)
+ expect(isValidHTMLNesting('p', 'abbr')).toBe(true)
+ expect(isValidHTMLNesting('p', 'button')).toBe(true)
+ expect(isValidHTMLNesting('p', 'b')).toBe(true)
+ expect(isValidHTMLNesting('p', 'i')).toBe(true)
+ expect(isValidHTMLNesting('p', 'input')).toBe(true)
+ expect(isValidHTMLNesting('p', 'label')).toBe(true)
+ })
+
+ test('a', () => {
+ // invalid
+ expect(isValidHTMLNesting('a', 'a')).toBe(false)
+
+ // valid
+ expect(isValidHTMLNesting('a', 'div')).toBe(true)
+ expect(isValidHTMLNesting('a', 'span')).toBe(true)
+ })
+
+ test('button', () => {
+ // invalid
+ expect(isValidHTMLNesting('button', 'button')).toBe(false)
+
+ // valid
+ expect(isValidHTMLNesting('button', 'div')).toBe(true)
+ expect(isValidHTMLNesting('button', 'span')).toBe(true)
+ })
+
+ test('table', () => {
+ // invalid
+ expect(isValidHTMLNesting('table', 'tr')).toBe(false)
+ expect(isValidHTMLNesting('table', 'table')).toBe(false)
+ expect(isValidHTMLNesting('table', 'td')).toBe(false)
+
+ // valid
+ expect(isValidHTMLNesting('table', 'thead')).toBe(true)
+ expect(isValidHTMLNesting('table', 'tbody')).toBe(true)
+ expect(isValidHTMLNesting('table', 'tfoot')).toBe(true)
+ expect(isValidHTMLNesting('table', 'caption')).toBe(true)
+ expect(isValidHTMLNesting('table', 'colgroup')).toBe(true)
+ })
+
+ test('td', () => {
+ // valid
+ expect(isValidHTMLNesting('td', 'span')).toBe(true)
+ expect(isValidHTMLNesting('tr', 'td')).toBe(true)
+
+ // invalid
+ expect(isValidHTMLNesting('td', 'td')).toBe(false)
+ expect(isValidHTMLNesting('div', 'td')).toBe(false)
+ })
+
+ test('tbody', () => {
+ // invalid
+ expect(isValidHTMLNesting('tbody', 'td')).toBe(false)
+
+ // valid
+ expect(isValidHTMLNesting('tbody', 'tr')).toBe(true)
+ })
+
+ test('tr', () => {
+ // invalid
+ expect(isValidHTMLNesting('tr', 'tr')).toBe(false)
+ expect(isValidHTMLNesting('table', 'tr')).toBe(false)
+
+ // valid
+ expect(isValidHTMLNesting('tbody', 'tr')).toBe(true)
+ expect(isValidHTMLNesting('thead', 'tr')).toBe(true)
+ expect(isValidHTMLNesting('tfoot', 'tr')).toBe(true)
+ expect(isValidHTMLNesting('tr', 'td')).toBe(true)
+ expect(isValidHTMLNesting('tr', 'th')).toBe(true)
+ })
+
+ test('li', () => {
+ // invalid
+ expect(isValidHTMLNesting('li', 'li')).toBe(false)
+ // valid
+ expect(isValidHTMLNesting('li', 'div')).toBe(true)
+ expect(isValidHTMLNesting('li', 'ul')).toBe(true)
+ })
+
+ test('headings', () => {
+ // invalid
+ expect(isValidHTMLNesting('h1', 'h1')).toBe(false)
+ expect(isValidHTMLNesting('h2', 'h1')).toBe(false)
+ expect(isValidHTMLNesting('h3', 'h1')).toBe(false)
+ expect(isValidHTMLNesting('h1', 'h6')).toBe(false)
+
+ // valid
+ expect(isValidHTMLNesting('h1', 'div')).toBe(true)
+ })
+
+ describe('SVG', () => {
+ test('svg', () => {
+ // invalid non-svg tags as children
+ expect(isValidHTMLNesting('svg', 'div')).toBe(false)
+ expect(isValidHTMLNesting('svg', 'img')).toBe(false)
+ expect(isValidHTMLNesting('svg', 'p')).toBe(false)
+ expect(isValidHTMLNesting('svg', 'h2')).toBe(false)
+ expect(isValidHTMLNesting('svg', 'span')).toBe(false)
+
+ // valid non-svg tags as children
+ expect(isValidHTMLNesting('svg', 'a')).toBe(true)
+ expect(isValidHTMLNesting('svg', 'textarea')).toBe(true)
+ expect(isValidHTMLNesting('svg', 'input')).toBe(true)
+ expect(isValidHTMLNesting('svg', 'select')).toBe(true)
+
+ // valid svg tags as children
+ expect(isValidHTMLNesting('svg', 'g')).toBe(true)
+ expect(isValidHTMLNesting('svg', 'ellipse')).toBe(true)
+ expect(isValidHTMLNesting('svg', 'feOffset')).toBe(true)
+ })
+
+ test('foreignObject', () => {
+ // valid
+ expect(isValidHTMLNesting('foreignObject', 'g')).toBe(true)
+ expect(isValidHTMLNesting('foreignObject', 'div')).toBe(true)
+ expect(isValidHTMLNesting('foreignObject', 'a')).toBe(true)
+ expect(isValidHTMLNesting('foreignObject', 'textarea')).toBe(true)
+ })
+
+ test('g', () => {
+ // valid
+ expect(isValidHTMLNesting('g', 'div')).toBe(true)
+ expect(isValidHTMLNesting('g', 'p')).toBe(true)
+ expect(isValidHTMLNesting('g', 'a')).toBe(true)
+ expect(isValidHTMLNesting('g', 'textarea')).toBe(true)
+ expect(isValidHTMLNesting('g', 'g')).toBe(true)
+ })
+
+ test('dl', () => {
+ // valid
+ expect(isValidHTMLNesting('dl', 'dt')).toBe(true)
+ expect(isValidHTMLNesting('dl', 'dd')).toBe(true)
+ expect(isValidHTMLNesting('dl', 'div')).toBe(true)
+ expect(isValidHTMLNesting('div', 'dt')).toBe(true)
+ expect(isValidHTMLNesting('div', 'dd')).toBe(true)
+
+ // invalid
+ expect(isValidHTMLNesting('span', 'dt')).toBe(false)
+ expect(isValidHTMLNesting('span', 'dd')).toBe(false)
+ })
+ })
+})
diff --git a/packages/compiler-dom/__tests__/transforms/warnTransitionChildren.spec.ts b/packages/compiler-dom/__tests__/transforms/warnTransitionChildren.spec.ts
deleted file mode 100644
index 3cccdfffc88..00000000000
--- a/packages/compiler-dom/__tests__/transforms/warnTransitionChildren.spec.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { compile } from '../../src'
-
-describe('compiler warnings', () => {
- describe('Transition', () => {
- function checkWarning(
- template: string,
- shouldWarn: boolean,
- message = ` expects exactly one child element or component.`
- ) {
- const spy = jest.fn()
- compile(template.trim(), {
- hoistStatic: true,
- transformHoist: null,
- onError: err => {
- spy(err.message)
- }
- })
-
- if (shouldWarn) expect(spy).toHaveBeenCalledWith(message)
- else expect(spy).not.toHaveBeenCalled()
- }
-
- test('warns if multiple children', () => {
- checkWarning(
- `
-
- hey
- hey
-
- `,
- true
- )
- })
-
- test('warns with v-for', () => {
- checkWarning(
- `
-
- hey
-
- `,
- true
- )
- })
-
- test('warns with multiple v-if + v-for', () => {
- checkWarning(
- `
-
- hey
- hey
-
- `,
- true
- )
- })
-
- test('warns with template v-if', () => {
- checkWarning(
- `
-
-
-
- `,
- true
- )
- })
-
- test('warns with multiple templates', () => {
- checkWarning(
- `
-
-
-
-
- `,
- true
- )
- })
-
- test('warns if multiple children with v-if', () => {
- checkWarning(
- `
-
- hey
- hey
-
- `,
- true
- )
- })
-
- test('does not warn with regular element', () => {
- checkWarning(
- `
-
- hey
-
- `,
- false
- )
- })
-
- test('does not warn with one single v-if', () => {
- checkWarning(
- `
-
- hey
-
- `,
- false
- )
- })
-
- test('does not warn with v-if v-else-if v-else', () => {
- checkWarning(
- `
-
- hey
- hey
- hey
-
- `,
- false
- )
- })
-
- test('does not warn with v-if v-else', () => {
- checkWarning(
- `
-
- hey
- hey
-
- `,
- false
- )
- })
- })
-})
-
-test('the v-if/else-if/else branchs in Transition should ignore comments', () => {
- expect(
- compile(`
-
- hey
-
- hey
-
-
-
- `).code
- ).toMatchSnapshot()
-})
diff --git a/packages/compiler-dom/api-extractor.json b/packages/compiler-dom/api-extractor.json
deleted file mode 100644
index 5602b3a6fd2..00000000000
--- a/packages/compiler-dom/api-extractor.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extends": "../../api-extractor.json",
- "mainEntryPointFilePath": "./dist/packages//src/index.d.ts",
- "dtsRollup": {
- "publicTrimmedFilePath": "./dist/.d.ts"
- }
-}
diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json
index 6bd44b95fd3..08ecbbe921b 100644
--- a/packages/compiler-dom/package.json
+++ b/packages/compiler-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-dom",
- "version": "3.2.31",
+ "version": "3.5.16",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",
@@ -11,6 +11,20 @@
"index.js",
"dist"
],
+ "exports": {
+ ".": {
+ "types": "./dist/compiler-dom.d.ts",
+ "node": {
+ "production": "./dist/compiler-dom.cjs.prod.js",
+ "development": "./dist/compiler-dom.cjs.js",
+ "default": "./index.js"
+ },
+ "module": "./dist/compiler-dom.esm-bundler.js",
+ "import": "./dist/compiler-dom.esm-bundler.js",
+ "require": "./index.js"
+ },
+ "./*": "./*"
+ },
"sideEffects": false,
"buildOptions": {
"name": "VueCompilerDOM",
@@ -37,7 +51,7 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme",
"dependencies": {
- "@vue/shared": "3.2.31",
- "@vue/compiler-core": "3.2.31"
+ "@vue/shared": "workspace:*",
+ "@vue/compiler-core": "workspace:*"
}
}
diff --git a/packages/compiler-dom/src/decodeHtml.ts b/packages/compiler-dom/src/decodeHtml.ts
deleted file mode 100644
index 68b0277a434..00000000000
--- a/packages/compiler-dom/src/decodeHtml.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import { ParserOptions } from '@vue/compiler-core'
-import namedCharacterReferences from './namedChars.json'
-
-// lazy compute this to make this file tree-shakable for browser
-let maxCRNameLength: number
-
-export const decodeHtml: ParserOptions['decodeEntities'] = (
- rawText,
- asAttr
-) => {
- let offset = 0
- const end = rawText.length
- let decodedText = ''
-
- function advance(length: number) {
- offset += length
- rawText = rawText.slice(length)
- }
-
- while (offset < end) {
- const head = /&(?:#x?)?/i.exec(rawText)
- if (!head || offset + head.index >= end) {
- const remaining = end - offset
- decodedText += rawText.slice(0, remaining)
- advance(remaining)
- break
- }
-
- // Advance to the "&".
- decodedText += rawText.slice(0, head.index)
- advance(head.index)
-
- if (head[0] === '&') {
- // Named character reference.
- let name = ''
- let value: string | undefined = undefined
- if (/[0-9a-z]/i.test(rawText[1])) {
- if (!maxCRNameLength) {
- maxCRNameLength = Object.keys(namedCharacterReferences).reduce(
- (max, name) => Math.max(max, name.length),
- 0
- )
- }
- for (let length = maxCRNameLength; !value && length > 0; --length) {
- name = rawText.slice(1, 1 + length)
- value = (namedCharacterReferences as Record)[name]
- }
- if (value) {
- const semi = name.endsWith(';')
- if (
- asAttr &&
- !semi &&
- /[=a-z0-9]/i.test(rawText[name.length + 1] || '')
- ) {
- decodedText += '&' + name
- advance(1 + name.length)
- } else {
- decodedText += value
- advance(1 + name.length)
- }
- } else {
- decodedText += '&' + name
- advance(1 + name.length)
- }
- } else {
- decodedText += '&'
- advance(1)
- }
- } else {
- // Numeric character reference.
- const hex = head[0] === ''
- const pattern = hex ? /^([0-9a-f]+);?/i : /^([0-9]+);?/
- const body = pattern.exec(rawText)
- if (!body) {
- decodedText += head[0]
- advance(head[0].length)
- } else {
- // https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state
- let cp = Number.parseInt(body[1], hex ? 16 : 10)
- if (cp === 0) {
- cp = 0xfffd
- } else if (cp > 0x10ffff) {
- cp = 0xfffd
- } else if (cp >= 0xd800 && cp <= 0xdfff) {
- cp = 0xfffd
- } else if ((cp >= 0xfdd0 && cp <= 0xfdef) || (cp & 0xfffe) === 0xfffe) {
- // noop
- } else if (
- (cp >= 0x01 && cp <= 0x08) ||
- cp === 0x0b ||
- (cp >= 0x0d && cp <= 0x1f) ||
- (cp >= 0x7f && cp <= 0x9f)
- ) {
- cp = CCR_REPLACEMENTS[cp] || cp
- }
- decodedText += String.fromCodePoint(cp)
- advance(body[0].length)
- }
- }
- }
- return decodedText
-}
-
-// https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state
-const CCR_REPLACEMENTS: Record = {
- 0x80: 0x20ac,
- 0x82: 0x201a,
- 0x83: 0x0192,
- 0x84: 0x201e,
- 0x85: 0x2026,
- 0x86: 0x2020,
- 0x87: 0x2021,
- 0x88: 0x02c6,
- 0x89: 0x2030,
- 0x8a: 0x0160,
- 0x8b: 0x2039,
- 0x8c: 0x0152,
- 0x8e: 0x017d,
- 0x91: 0x2018,
- 0x92: 0x2019,
- 0x93: 0x201c,
- 0x94: 0x201d,
- 0x95: 0x2022,
- 0x96: 0x2013,
- 0x97: 0x2014,
- 0x98: 0x02dc,
- 0x99: 0x2122,
- 0x9a: 0x0161,
- 0x9b: 0x203a,
- 0x9c: 0x0153,
- 0x9e: 0x017e,
- 0x9f: 0x0178
-}
diff --git a/packages/compiler-dom/src/decodeHtmlBrowser.ts b/packages/compiler-dom/src/decodeHtmlBrowser.ts
index cca3bb12a6e..2e72d0fd1f7 100644
--- a/packages/compiler-dom/src/decodeHtmlBrowser.ts
+++ b/packages/compiler-dom/src/decodeHtmlBrowser.ts
@@ -8,9 +8,9 @@ export function decodeHtmlBrowser(raw: string, asAttr = false): string {
}
if (asAttr) {
decoder.innerHTML = ``
- return decoder.children[0].getAttribute('foo') as string
+ return decoder.children[0].getAttribute('foo')!
} else {
decoder.innerHTML = raw
- return decoder.textContent as string
+ return decoder.textContent!
}
}
diff --git a/packages/compiler-dom/src/errors.ts b/packages/compiler-dom/src/errors.ts
index 159bd4ba3b0..b47624840ab 100644
--- a/packages/compiler-dom/src/errors.ts
+++ b/packages/compiler-dom/src/errors.ts
@@ -1,8 +1,8 @@
import {
- SourceLocation,
- CompilerError,
+ type CompilerError,
+ ErrorCodes,
+ type SourceLocation,
createCompilerError,
- ErrorCodes
} from '@vue/compiler-core'
export interface DOMCompilerError extends CompilerError {
@@ -11,17 +11,17 @@ export interface DOMCompilerError extends CompilerError {
export function createDOMCompilerError(
code: DOMErrorCodes,
- loc?: SourceLocation
+ loc?: SourceLocation,
) {
return createCompilerError(
code,
loc,
- __DEV__ || !__BROWSER__ ? DOMErrorMessages : undefined
+ __DEV__ || !__BROWSER__ ? DOMErrorMessages : undefined,
) as DOMCompilerError
}
-export const enum DOMErrorCodes {
- X_V_HTML_NO_EXPRESSION = ErrorCodes.__EXTEND_POINT__,
+export enum DOMErrorCodes {
+ X_V_HTML_NO_EXPRESSION = 53 /* ErrorCodes.__EXTEND_POINT__ */,
X_V_HTML_WITH_CHILDREN,
X_V_TEXT_NO_EXPRESSION,
X_V_TEXT_WITH_CHILDREN,
@@ -32,7 +32,20 @@ export const enum DOMErrorCodes {
X_V_SHOW_NO_EXPRESSION,
X_TRANSITION_INVALID_CHILDREN,
X_IGNORED_SIDE_EFFECT_TAG,
- __EXTEND_POINT__
+ __EXTEND_POINT__,
+}
+
+if (__TEST__) {
+ // esbuild cannot infer enum increments if first value is from another
+ // file, so we have to manually keep them in sync. this check ensures it
+ // errors out if there are collisions.
+ if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {
+ throw new Error(
+ `DOMErrorCodes need to be updated to ${
+ ErrorCodes.__EXTEND_POINT__ + 1
+ } to match extension point from core ErrorCodes.`,
+ )
+ }
}
export const DOMErrorMessages: { [code: number]: string } = {
@@ -46,5 +59,5 @@ export const DOMErrorMessages: { [code: number]: string } = {
[DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE]: `Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior.`,
[DOMErrorCodes.X_V_SHOW_NO_EXPRESSION]: `v-show is missing expression.`,
[DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN]: `
expects exactly one child element or component.`,
- [DOMErrorCodes.X_IGNORED_SIDE_EFFECT_TAG]: `Tags with side effect (
+ `)
+ expect(content).toMatch(`return { a, b }`)
+ assertCode(content)
+ })
+
test('should expose top level declarations', () => {
const { content, bindings } = compile(`
`)
- expect(content).toMatch('return { aa, bb, cc, dd, a, b, c, d, xx, x }')
+ expect(content).toMatch(
+ `return { get aa() { return aa }, set aa(v) { aa = v }, ` +
+ `bb, cc, dd, get a() { return a }, set a(v) { a = v }, b, c, d, ` +
+ `get xx() { return xx }, get x() { return x } }`,
+ )
expect(bindings).toStrictEqual({
x: BindingTypes.SETUP_MAYBE_REF,
a: BindingTypes.SETUP_LET,
@@ -29,9 +50,9 @@ describe('SFC compile
- `)
- // should generate working code
- assertCode(content)
- // should anayze bindings
- expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS,
- bar: BindingTypes.SETUP_CONST,
- props: BindingTypes.SETUP_CONST
- })
-
- // should remove defineOptions import and call
- expect(content).not.toMatch('defineProps')
- // should generate correct setup signature
- expect(content).toMatch(`setup(__props, { expose }) {`)
- // should assign user identifier to it
- expect(content).toMatch(`const props = __props`)
- // should include context options in default export
- expect(content).toMatch(`export default {
- props: {
- foo: String
-},`)
- })
-
- test('defineProps w/ external definition', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`export default {
- props: propsModel,`)
- })
-
- // #4764
- test('defineProps w/ leading code', () => {
- const { content } = compile(`
-
- `)
- // props declaration should be inside setup, not moved along with the import
- expect(content).not.toMatch(`const props = __props\nimport`)
- assertCode(content)
- })
-
- test('defineEmits()', () => {
- const { content, bindings } = compile(`
-
- `)
- assertCode(content)
- expect(bindings).toStrictEqual({
- myEmit: BindingTypes.SETUP_CONST
- })
- // should remove defineOptions import and call
- expect(content).not.toMatch('defineEmits')
- // should generate correct setup signature
- expect(content).toMatch(`setup(__props, { expose, emit: myEmit }) {`)
- // should include context options in default export
- expect(content).toMatch(`export default {
- emits: ['foo', 'bar'],`)
- })
-
- test('defineProps/defineEmits in multi-variable declaration', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`const a = 1;`) // test correct removal
- expect(content).toMatch(`props: ['item'],`)
- expect(content).toMatch(`emits: ['a'],`)
- })
-
- test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`props: ['item'],`)
- expect(content).toMatch(`emits: ['a'],`)
- })
-
- test('defineExpose()', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- // should remove defineOptions import and call
- expect(content).not.toMatch('defineExpose')
- // should generate correct setup signature
- expect(content).toMatch(`setup(__props, { expose }) {`)
- // should replace callee
- expect(content).toMatch(/\bexpose\(\{ foo: 123 \}\)/)
- })
-
describe('
+ `)
+ assertCode(content)
+
+ expect(content).toMatch(`console.log('test')`)
+ expect(content).toMatch(`const props = __props;`)
+ expect(content).toMatch(`const emit = __emit;`)
+ expect(content).toMatch(`(function () {})()`)
+ })
+
test('script setup first, named default export', () => {
const { content } = compile(`
+
+
+ `)
+ assertCode(content)
+ })
})
describe('imports', () => {
@@ -275,7 +215,7 @@ defineExpose({ foo: 123 })
compile(``).content
+ `).content,
)
})
@@ -286,7 +226,7 @@ defineExpose({ foo: 123 })
import a from 'a' // comment
import b from 'b'
- `).content
+ `).content,
)
})
@@ -298,7 +238,7 @@ defineExpose({ foo: 123 })
defineProps(['foo'])
defineEmits(['bar'])
const r = ref(0)
- `).content
+ `).content,
)
})
@@ -306,14 +246,22 @@ defineExpose({ foo: 123 })
const { content } = compile(
`
+
+
`,
- { reactivityTransform: true }
)
assertCode(content)
- expect(content).toMatch(`import { ref } from 'vue'`)
+ expect(content).toMatch(
+ `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`,
+ )
+ expect(content).toMatch(`import { useCssVars, ref } from 'vue'`)
})
test('import dedupe between
-
-
-
-
- FooBar
-
- `)
- // FooBar: should not be matched by plain text or incorrect case
- // FooBaz: used as PascalCase component
- // FooQux: used as kebab-case component
- // foo: lowercase component
- expect(content).toMatch(`return { fooBar, FooBaz, FooQux, foo }`)
- assertCode(content)
- })
+ describe('import ref/reactive function from other place', () => {
+ test('import directly', () => {
+ const { bindings } = compile(`
+
-
-
-
- `)
- expect(content).toMatch(`return { vMyDir }`)
- assertCode(content)
- })
+ `)
+ expect(bindings).toStrictEqual({
+ ref: BindingTypes.SETUP_MAYBE_REF,
+ reactive: BindingTypes.SETUP_MAYBE_REF,
+ foo: BindingTypes.SETUP_MAYBE_REF,
+ bar: BindingTypes.SETUP_MAYBE_REF,
+ })
+ })
- // https://github.com/vuejs/core/issues/4599
- test('attribute expressions', () => {
- const { content } = compile(`
-
-
-
-
- `)
- expect(content).toMatch(`return { cond, bar, baz }`)
- assertCode(content)
- })
+ test('import w/ alias', () => {
+ const { bindings } = compile(`
+
-
- {{ x }} {{ yy }} {{ x$y }}
-
+ const foo = ref(1)
+ const bar = reactive(1)
+
`)
- // x: used in interpolation
- // y: should not be matched by {{ yy }} or 'y' in binding exps
- // x$y: #4274 should escape special chars when creating Regex
- expect(content).toMatch(`return { x, z, x$y }`)
- assertCode(content)
- })
+ expect(bindings).toStrictEqual({
+ _reactive: BindingTypes.SETUP_MAYBE_REF,
+ _ref: BindingTypes.SETUP_MAYBE_REF,
+ foo: BindingTypes.SETUP_MAYBE_REF,
+ bar: BindingTypes.SETUP_MAYBE_REF,
+ })
+ })
- // #4340 interpolations in tempalte strings
- test('js template string interpolations', () => {
- const { content } = compile(`
-
-
- {{ \`\${VAR}VAR2\${VAR3}\` }}
-
- `)
- // VAR2 should not be matched
- expect(content).toMatch(`return { VAR, VAR3 }`)
- assertCode(content)
+ `)
+ expect(bindings).toStrictEqual({
+ bar: BindingTypes.SETUP_REACTIVE_CONST,
+ x: BindingTypes.SETUP_CONST,
+ })
+ })
})
- // edge case: last tag in template
- test('last tag', () => {
- const { content } = compile(`
-
-
-
-
-
- `)
- expect(content).toMatch(`return { FooBaz, Last }`)
+ test('should support module string names syntax', () => {
+ const { content, bindings } = compile(`
+
+
+ `)
assertCode(content)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.SETUP_MAYBE_REF,
+ })
})
})
@@ -447,7 +358,7 @@ defineExpose({ foo: 123 })
static
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
// check snapshot and make sure helper imports and
// hoists are placed correctly.
@@ -465,10 +376,10 @@ defineExpose({ foo: 123 })
defineExpose({ count })
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
assertCode(content)
- expect(content).toMatch(`setup(__props, { expose })`)
+ expect(content).toMatch(`setup(__props, { expose: __expose })`)
expect(content).toMatch(`expose({ count })`)
})
@@ -486,7 +397,7 @@ defineExpose({ foo: 123 })
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
expect(content).toMatch('[_unref(vMyDir)]')
expect(content).toMatch('_createVNode(ChildComp)')
@@ -502,6 +413,7 @@ defineExpose({ foo: 123 })
import { ref } from 'vue'
import Foo, { bar } from './Foo.vue'
import other from './util'
+ import * as tree from './tree'
const count = ref(0)
const constant = {}
const maybe = foo()
@@ -511,9 +423,10 @@ defineExpose({ foo: 123 })
{{ bar }}
{{ count }} {{ constant }} {{ maybe }} {{ lett }} {{ other }}
+ {{ tree.foo() }}
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
// no need to unref vue component import
expect(content).toMatch(`createVNode(Foo,`)
@@ -529,6 +442,8 @@ defineExpose({ foo: 123 })
expect(content).toMatch(`unref(maybe)`)
// should unref() on let bindings
expect(content).toMatch(`unref(lett)`)
+ // no need to unref namespace import (this also preserves tree-shaking)
+ expect(content).toMatch(`tree.foo()`)
// no need to unref function declarations
expect(content).toMatch(`{ onClick: fn }`)
// no need to mark constant fns in patch flag
@@ -550,7 +465,7 @@ defineExpose({ foo: 123 })
`,
- { inlineTemplate: true }
+ { inlineTemplate: true },
)
// known const ref: set value
expect(content).toMatch(`(count).value = $event`)
@@ -558,11 +473,48 @@ defineExpose({ foo: 123 })
expect(content).toMatch(`_isRef(maybe) ? (maybe).value = $event : null`)
// let: handle both cases
expect(content).toMatch(
- `_isRef(lett) ? (lett).value = $event : lett = $event`
+ `_isRef(lett) ? (lett).value = $event : lett = $event`,
+ )
+ assertCode(content)
+ })
+
+ test('v-model w/ newlines codegen', () => {
+ const { content } = compile(
+ `
+
+
+
+ `,
+ { inlineTemplate: true },
)
+ expect(content).toMatch(`_isRef(count) ? (count).value = $event : null`)
assertCode(content)
})
+ test('v-model should not generate ref assignment code for non-setup bindings', () => {
+ const { content } = compile(
+ `
+
+
+
+
+ `,
+ { inlineTemplate: true },
+ )
+ expect(content).not.toMatch(`_isRef(foo)`)
+ })
+
test('template assignment expression codegen', () => {
const { content } = compile(
`
{{ count }}
@@ -683,392 +637,107 @@ defineExpose({ foo: 123 })
`,
{
inlineTemplate: true,
templateOptions: {
- ssr: true
- }
- }
+ ssr: true,
+ },
+ },
)
expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
expect(content).toMatch(`return (_ctx, _push`)
expect(content).toMatch(`ssrInterpolate`)
- assertCode(content)
- })
- })
-
- describe('with TypeScript', () => {
- test('hoist type declarations', () => {
- const { content } = compile(`
- `)
- assertCode(content)
- })
-
- test('defineProps/Emit w/ runtime options', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
- props: { foo: String },
- emits: ['a', 'b'],
- setup(__props, { expose, emit }) {`)
- })
-
- test('defineProps w/ type', () => {
- const { content, bindings } = compile(`
- `)
- assertCode(content)
- expect(content).toMatch(`string: { type: String, required: true }`)
- expect(content).toMatch(`number: { type: Number, required: true }`)
- expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
- expect(content).toMatch(`object: { type: Object, required: true }`)
- expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
- expect(content).toMatch(`fn: { type: Function, required: true }`)
- expect(content).toMatch(`functionRef: { type: Function, required: true }`)
- expect(content).toMatch(`objectRef: { type: Object, required: true }`)
- expect(content).toMatch(`dateTime: { type: Date, required: true }`)
- expect(content).toMatch(`array: { type: Array, required: true }`)
- expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
- expect(content).toMatch(`tuple: { type: Array, required: true }`)
- expect(content).toMatch(`set: { type: Set, required: true }`)
- expect(content).toMatch(`literal: { type: String, required: true }`)
- expect(content).toMatch(`optional: { type: null, required: false }`)
- expect(content).toMatch(`recordRef: { type: Object, required: true }`)
- expect(content).toMatch(`interface: { type: Object, required: true }`)
- expect(content).toMatch(`alias: { type: Array, required: true }`)
- expect(content).toMatch(`method: { type: Function, required: true }`)
- expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
- expect(content).toMatch(
- `union: { type: [String, Number], required: true }`
- )
- expect(content).toMatch(`literalUnion: { type: String, required: true }`)
- expect(content).toMatch(
- `literalUnionNumber: { type: Number, required: true }`
- )
+ expect(content).not.toMatch(`useCssVars`)
+ expect(content).toMatch(`"--${mockId}-count": (count.value)`)
+ expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`)
expect(content).toMatch(
- `literalUnionMixed: { type: [String, Number, Boolean], required: true }`
+ `"--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
)
- expect(content).toMatch(`intersection: { type: Object, required: true }`)
- expect(content).toMatch(`foo: { type: [Function, null], required: true }`)
- expect(bindings).toStrictEqual({
- string: BindingTypes.PROPS,
- number: BindingTypes.PROPS,
- boolean: BindingTypes.PROPS,
- object: BindingTypes.PROPS,
- objectLiteral: BindingTypes.PROPS,
- fn: BindingTypes.PROPS,
- functionRef: BindingTypes.PROPS,
- objectRef: BindingTypes.PROPS,
- dateTime: BindingTypes.PROPS,
- array: BindingTypes.PROPS,
- arrayRef: BindingTypes.PROPS,
- tuple: BindingTypes.PROPS,
- set: BindingTypes.PROPS,
- literal: BindingTypes.PROPS,
- optional: BindingTypes.PROPS,
- recordRef: BindingTypes.PROPS,
- interface: BindingTypes.PROPS,
- alias: BindingTypes.PROPS,
- method: BindingTypes.PROPS,
- symbol: BindingTypes.PROPS,
- union: BindingTypes.PROPS,
- literalUnion: BindingTypes.PROPS,
- literalUnionNumber: BindingTypes.PROPS,
- literalUnionMixed: BindingTypes.PROPS,
- intersection: BindingTypes.PROPS,
- foo: BindingTypes.PROPS
- })
- })
-
- test('defineProps w/ interface', () => {
- const { content, bindings } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`x: { type: Number, required: false }`)
- expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
- })
- })
-
- test('defineProps w/ exported interface', () => {
- const { content, bindings } = compile(`
-
- `)
assertCode(content)
- expect(content).toMatch(`x: { type: Number, required: false }`)
- expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
- })
})
- test('defineProps w/ exported interface in normal script', () => {
- const { content, bindings } = compile(`
-
-
- `)
- assertCode(content)
- expect(content).toMatch(`x: { type: Number, required: false }`)
- expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
- })
- })
-
- test('defineProps w/ type alias', () => {
- const { content, bindings } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`x: { type: Number, required: false }`)
- expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
- })
- })
-
- test('defineProps w/ exported type alias', () => {
- const { content, bindings } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`x: { type: Number, required: false }`)
- expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
- })
- })
-
- test('withDefaults (static)', () => {
- const { content, bindings } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(
- `foo: { type: String, required: false, default: 'hi' }`
- )
- expect(content).toMatch(`bar: { type: Number, required: false }`)
- expect(content).toMatch(`baz: { type: Boolean, required: true }`)
- expect(content).toMatch(
- `qux: { type: Function, required: false, default() { return 1 } }`
- )
- expect(content).toMatch(
- `{ foo: string, bar?: number, baz: boolean, qux(): number }`
- )
- expect(content).toMatch(`const props = __props`)
- expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS,
- bar: BindingTypes.PROPS,
- baz: BindingTypes.PROPS,
- qux: BindingTypes.PROPS,
- props: BindingTypes.SETUP_CONST
- })
+ test('the v-for wrapped in parentheses can be correctly parsed & inline is false', () => {
+ expect(() =>
+ compile(
+ `
+
+
+
+
+ `,
+ {
+ inlineTemplate: false,
+ },
+ ),
+ ).not.toThrowError()
})
- test('withDefaults (dynamic)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
- expect(content).toMatch(
+ test('unref + new expression', () => {
+ const { content } = compile(
`
- _mergeDefaults({
- foo: { type: String, required: false },
- bar: { type: Number, required: false },
- baz: { type: Boolean, required: true }
- }, { ...defaults })`.trim()
+
+
+ {{ new Foo() }}
+ {{ new Foo.Bar() }}
+
+ `,
+ { inlineTemplate: true },
)
- })
-
- test('defineEmits w/ type', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type (union)', () => {
- const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
- expect(() =>
- compile(`
-
- `)
- ).toThrow()
- })
-
- test('defineEmits w/ type (type literal w/ call signatures)', () => {
- const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
- const { content } = compile(`
-
- `)
+ expect(content).toMatch(`new (_unref(Foo))()`)
+ expect(content).toMatch(`new (_unref(Foo)).Bar()`)
assertCode(content)
- expect(content).toMatch(`emit: (${type}),`)
- expect(content).toMatch(`emits: ["foo", "bar", "baz"]`)
})
- test('defineEmits w/ type (interface)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type (exported interface)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type (type alias)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type (exported type alias)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type (referenced function type)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
+
+
+
+ `
+ const { content, map } = compile(source, { inlineTemplate: true })
+ expect(map).not.toBeUndefined()
+ const consumer = new SourceMapConsumer(map as RawSourceMap)
+ expect(
+ consumer.originalPositionFor(getPositionInCode(content, 'count')),
+ ).toMatchObject(getPositionInCode(source, `count`))
+ expect(
+ consumer.originalPositionFor(getPositionInCode(content, 'Error')),
+ ).toMatchObject(getPositionInCode(source, `Error`))
})
+ })
- test('defineEmits w/ type (referenced exported function type)', () => {
+ describe('with TypeScript', () => {
+ test('hoist type declarations', () => {
const { content } = compile(`
- `)
+ export interface Foo {}
+ type Bar = {}
+ `)
assertCode(content)
- expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
})
test('runtime Enum', () => {
const { content, bindings } = compile(
``
+ `,
)
assertCode(content)
expect(bindings).toStrictEqual({
- Foo: BindingTypes.SETUP_CONST
+ Foo: BindingTypes.LITERAL_CONST,
})
})
@@ -1081,14 +750,14 @@ const emit = defineEmits(['a', 'b'])
`
+ `,
)
assertCode(content)
expect(bindings).toStrictEqual({
- D: BindingTypes.SETUP_CONST,
- C: BindingTypes.SETUP_CONST,
- B: BindingTypes.SETUP_CONST,
- Foo: BindingTypes.SETUP_CONST
+ D: BindingTypes.LITERAL_CONST,
+ C: BindingTypes.LITERAL_CONST,
+ B: BindingTypes.LITERAL_CONST,
+ Foo: BindingTypes.LITERAL_CONST,
})
})
@@ -1096,20 +765,38 @@ const emit = defineEmits(['a', 'b'])
const { content, bindings } = compile(
``
+ `,
+ { hoistStatic: true },
)
assertCode(content)
expect(bindings).toStrictEqual({
- Foo: BindingTypes.SETUP_CONST
+ Foo: BindingTypes.LITERAL_CONST,
})
})
+
+ test('import type', () => {
+ const { content } = compile(
+ ``,
+ )
+ expect(content).toMatch(`return { get Baz() { return Baz } }`)
+ assertCode(content)
+ })
+
+ test('with generic attribute', () => {
+ const { content } = compile(`
+ `)
+ assertCode(content)
+ })
})
describe('async/await detection', () => {
function assertAwaitDetection(code: string, shouldAsync = true) {
- const { content } = compile(``, {
- reactivityTransform: true
- })
+ const { content } = compile(``)
if (shouldAsync) {
expect(content).toMatch(`let __temp, __restore`)
}
@@ -1127,7 +814,7 @@ const emit = defineEmits(['a', 'b'])
})
test('ref', () => {
- assertAwaitDetection(`let a = $ref(1 + (await foo))`)
+ assertAwaitDetection(`let a = ref(1 + (await foo))`)
})
// #4448
@@ -1153,6 +840,59 @@ const emit = defineEmits(['a', 'b'])
assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
})
+ test('multiple `if` nested statements', () => {
+ assertAwaitDetection(`if (ok) {
+ let a = 'foo'
+ await 0 + await 1
+ await 2
+ } else if (a) {
+ await 10
+ if (b) {
+ await 0 + await 1
+ } else {
+ let a = 'foo'
+ await 2
+ }
+ if (b) {
+ await 3
+ await 4
+ }
+ } else {
+ await 5
+ }`)
+ })
+
+ test('multiple `if while` nested statements', () => {
+ assertAwaitDetection(`if (ok) {
+ while (d) {
+ await 5
+ }
+ while (d) {
+ await 5
+ await 6
+ if (c) {
+ let f = 10
+ 10 + await 7
+ } else {
+ await 8
+ await 9
+ }
+ }
+ }`)
+ })
+
+ test('multiple `if for` nested statements', () => {
+ assertAwaitDetection(`if (ok) {
+ for (let a of [1,2,3]) {
+ await a
+ }
+ for (let a of [1,2,3]) {
+ await a
+ await a
+ }
+ }`)
+ })
+
test('should ignore await inside functions', () => {
// function declaration
assertAwaitDetection(`async function foo() { await bar }`, false)
@@ -1163,7 +903,7 @@ const emit = defineEmits(['a', 'b'])
// class method
assertAwaitDetection(
`const cls = class Foo { async method() { await bar }}`,
- false
+ false,
)
})
})
@@ -1171,7 +911,7 @@ const emit = defineEmits(['a', 'b'])
describe('errors', () => {
test('`)
+ compile(``),
).toThrow(``)
+ `),
).toThrow(moduleErrorMsg)
expect(() =>
compile(``)
+ `),
).toThrow(moduleErrorMsg)
expect(() =>
compile(``)
+ `),
).toThrow(moduleErrorMsg)
})
- test('defineProps/Emit() w/ both type and non-type args', () => {
- expect(() => {
- compile(``)
- }).toThrow(`cannot accept both type and non-type arguments`)
-
- expect(() => {
- compile(``)
- }).toThrow(`cannot accept both type and non-type arguments`)
- })
-
test('defineProps/Emit() referencing local var', () => {
expect(() =>
compile(``)
+ `),
).toThrow(`cannot reference locally declared variables`)
expect(() =>
compile(``)
+ `),
).toThrow(`cannot reference locally declared variables`)
// #4644
@@ -1241,7 +967,7 @@ const emit = defineEmits(['a', 'b'])
default: () => bar
}
})
- `)
+ `),
).not.toThrow(`cannot reference locally declared variables`)
})
@@ -1257,7 +983,7 @@ const emit = defineEmits(['a', 'b'])
defineEmits({
foo: bar => bar > 1
})
- `).content
+ `).content,
)
})
@@ -1273,9 +999,41 @@ const emit = defineEmits(['a', 'b'])
defineEmits({
foo: () => bar > 1
})
- `).content
+ `).content,
)
})
+
+ test('defineModel() referencing local var', () => {
+ expect(() =>
+ compile(``),
+ ).toThrow(`cannot reference locally declared variables`)
+
+ // allow const
+ expect(() =>
+ compile(``),
+ ).not.toThrow(`cannot reference locally declared variables`)
+
+ // allow in get/set
+ expect(() =>
+ compile(``),
+ ).not.toThrow(`cannot reference locally declared variables`)
+ })
})
})
@@ -1307,7 +1065,7 @@ describe('SFC analyze
`)
expect(bindings).toStrictEqual({
- foo: BindingTypes.SETUP_CONST
+ foo: BindingTypes.LITERAL_CONST,
})
})
@@ -1387,7 +1145,7 @@ describe('SFC analyze
+ {{ a }} `,
+ undefined,
+ {
+ filename: 'FooBar.vue',
+ },
+ )
+ expect(content).toMatch(`export default {
+ __name: 'FooBar'`)
+ assertCode(content)
+ })
+
+ test('do not overwrite manual name (object)', () => {
+ const { content } = compile(
+ `
+
+ {{ a }} `,
+ undefined,
+ {
+ filename: 'FooBar.vue',
+ },
+ )
+ expect(content).not.toMatch(`name: 'FooBar'`)
+ expect(content).toMatch(`name: 'Baz'`)
+ assertCode(content)
+ })
+
+ test('do not overwrite manual name (call)', () => {
+ const { content } = compile(
+ `
+
+ {{ a }} `,
+ undefined,
+ {
+ filename: 'FooBar.vue',
+ },
+ )
+ expect(content).not.toMatch(`name: 'FooBar'`)
+ expect(content).toMatch(`name: 'Baz'`)
+ assertCode(content)
})
})
})
+
+describe('SFC genDefaultAs', () => {
+ test('normal `,
+ {
+ genDefaultAs: '_sfc_',
+ },
+ )
+ expect(content).not.toMatch('export default')
+ expect(content).toMatch(`const _sfc_ = {}`)
+ assertCode(content)
+ })
+
+ test('normal
+ `,
+ {
+ genDefaultAs: '_sfc_',
+ },
+ )
+ expect(content).not.toMatch('export default')
+ expect(content).not.toMatch('__default__')
+ expect(content).toMatch(`const _sfc_ = {}`)
+ assertCode(content)
+ })
+
+ test('
+ `,
+ {
+ genDefaultAs: '_sfc_',
+ },
+ )
+ expect(content).not.toMatch('export default')
+ expect(content).toMatch(
+ `const _sfc_ = /*@__PURE__*/Object.assign(__default__`,
+ )
+ assertCode(content)
+ })
+
+ test('
+ `,
+ {
+ genDefaultAs: '_sfc_',
+ },
+ )
+ expect(content).not.toMatch('export default')
+ expect(content).toMatch(
+ `const _sfc_ = /*@__PURE__*/Object.assign(__default__`,
+ )
+ assertCode(content)
+ })
+
+ test('`,
+ {
+ genDefaultAs: '_sfc_',
+ },
+ )
+ expect(content).not.toMatch('export default')
+ expect(content).toMatch(`const _sfc_ = {\n setup`)
+ assertCode(content)
+ })
+
+ test('`,
+ {
+ genDefaultAs: '_sfc_',
+ },
+ )
+ expect(content).not.toMatch('export default')
+ expect(content).toMatch(`const _sfc_ = /*@__PURE__*/_defineComponent(`)
+ assertCode(content)
+ })
+
+ test('
+ `,
+ {
+ genDefaultAs: '_sfc_',
+ },
+ )
+ expect(content).not.toMatch('export default')
+ expect(content).toMatch(
+ `const _sfc_ = /*@__PURE__*/_defineComponent({\n ...__default__`,
+ )
+ assertCode(content)
+ })
+
+ test('binding type for edge cases', () => {
+ const { bindings } = compile(
+ ``,
+ )
+ expect(bindings).toStrictEqual({
+ toRef: BindingTypes.SETUP_CONST,
+ props: BindingTypes.SETUP_REACTIVE_CONST,
+ foo: BindingTypes.SETUP_REF,
+ })
+ })
+
+ describe('parser plugins', () => {
+ test('import attributes', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+
+ expect(() =>
+ compile(`
+ `),
+ ).toThrow()
+ })
+
+ test('import attributes (user override for deprecated syntax)', () => {
+ const { content } = compile(
+ `
+
+ `,
+ {
+ babelParserPlugins: [
+ ['importAttributes', { deprecatedAssertSyntax: true }],
+ ],
+ },
+ )
+ assertCode(content)
+ })
+ })
+})
+
+describe('compileScript', () => {
+ test('should care about runtimeModuleName', () => {
+ const { content } = compile(
+ `
+
+ `,
+ {
+ templateOptions: {
+ compilerOptions: {
+ runtimeModuleName: 'npm:vue',
+ },
+ },
+ },
+ )
+ expect(content).toMatch(
+ `import { withAsyncContext as _withAsyncContext } from "npm:vue"\n`,
+ )
+ assertCode(content)
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap
new file mode 100644
index 00000000000..a25a384a979
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap
@@ -0,0 +1,285 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`defineEmits > basic usage 1`] = `
+"
+export default {
+ emits: ['foo', 'bar'],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+const myEmit = __emit
+
+return { myEmit }
+}
+
+}"
+`;
+
+exports[`defineEmits > w/ runtime options 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ['a', 'b'],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (exported interface) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+export interface Emits { (e: 'foo' | 'bar'): void }
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo", "bar"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (exported type alias) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+export type Emits = { (e: 'foo' | 'bar'): void }
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo", "bar"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (interface ts type) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+interface Emits { (e: 'foo'): void }
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ['foo'],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit: Emits = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (interface w/ extends) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+interface Base { (e: 'foo'): void }
+ interface Emits extends Base { (e: 'bar'): void }
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["bar", "foo"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (interface) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+interface Emits { (e: 'foo' | 'bar'): void }
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo", "bar"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (property syntax string literal) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo:bar"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (property syntax) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo", "bar"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (referenced exported function type) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+export type Emits = (e: 'foo' | 'bar') => void
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo", "bar"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (referenced function type) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+type Emits = (e: 'foo' | 'bar') => void
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo", "bar"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (type alias) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+type Emits = { (e: 'foo' | 'bar'): void }
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo", "bar"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (type literal w/ call signatures) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo", "bar", "baz"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (type references in union) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+type BaseEmit = "change"
+ type Emit = "some" | "emit" | BaseEmit
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["some", "emit", "change", "another"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit;
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (union) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo", "bar", "baz"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo", "bar"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type from normal script 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+ export interface Emits { (e: 'foo' | 'bar'): void }
+
+export default /*@__PURE__*/_defineComponent({
+ emits: ["foo", "bar"],
+ setup(__props, { expose: __expose, emit: __emit }) {
+ __expose();
+
+ const emit = __emit
+
+return { emit }
+}
+
+})"
+`;
diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineExpose.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineExpose.spec.ts.snap
new file mode 100644
index 00000000000..f8754f8a9af
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineExpose.spec.ts.snap
@@ -0,0 +1,29 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`
+ `)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ myEmit: BindingTypes.SETUP_CONST,
+ })
+ // should remove defineEmits import and call
+ expect(content).not.toMatch('defineEmits')
+ // should generate correct setup signature
+ expect(content).toMatch(
+ `setup(__props, { expose: __expose, emit: __emit }) {`,
+ )
+ expect(content).toMatch('const myEmit = __emit')
+ // should include context options in default export
+ expect(content).toMatch(`export default {
+ emits: ['foo', 'bar'],`)
+ })
+
+ test('w/ runtime options', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`export default /*@__PURE__*/_defineComponent({
+ emits: ['a', 'b'],
+ setup(__props, { expose: __expose, emit: __emit }) {`)
+ expect(content).toMatch('const emit = __emit')
+ })
+
+ test('w/ type', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (union)', () => {
+ const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar", "baz"]`)
+ })
+
+ test('w/ type (type literal w/ call signatures)', () => {
+ const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar", "baz"]`)
+ })
+
+ test('w/ type (interface)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (interface w/ extends)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["bar", "foo"]`)
+ })
+
+ test('w/ type (exported interface)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type from normal script', () => {
+ const { content } = compile(`
+
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (type alias)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (exported type alias)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (referenced function type)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (referenced exported function type)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ // #5393
+ test('w/ type (interface ts type)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ['foo']`)
+ })
+
+ test('w/ type (property syntax)', () => {
+ const { content } = compile(`
+
+ `)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ assertCode(content)
+ })
+
+ // #8040
+ test('w/ type (property syntax string literal)', () => {
+ const { content } = compile(`
+
+ `)
+ expect(content).toMatch(`emits: ["foo:bar"]`)
+ assertCode(content)
+ })
+
+ // #7943
+ test('w/ type (type references in union)', () => {
+ const { content } = compile(`
+
+ `)
+
+ expect(content).toMatch(`emits: ["some", "emit", "change", "another"]`)
+ assertCode(content)
+ })
+
+ describe('errors', () => {
+ test('w/ both type and non-type args', () => {
+ expect(() => {
+ compile(``)
+ }).toThrow(`cannot accept both type and non-type arguments`)
+ })
+
+ test('mixed usage of property / call signature', () => {
+ expect(() =>
+ compile(``),
+ ).toThrow(
+ `defineEmits() type cannot mixed call signature and property syntax.`,
+ )
+ })
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineExpose.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineExpose.spec.ts
new file mode 100644
index 00000000000..7b2a9f7cbe4
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/defineExpose.spec.ts
@@ -0,0 +1,26 @@
+import { assertCode, compileSFCScript as compile } from '../utils'
+
+test('defineExpose()', () => {
+ const { content } = compile(`
+
+`)
+ assertCode(content)
+ // should remove defineOptions import and call
+ expect(content).not.toMatch('defineExpose')
+ // should generate correct setup signature
+ expect(content).toMatch(`setup(__props, { expose: __expose }) {`)
+ // should replace callee
+ expect(content).toMatch(/\b__expose\(\{ foo: 123 \}\)/)
+})
+
+test('
+
+ `)
+ assertCode(content)
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts
new file mode 100644
index 00000000000..5d696a95d88
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts
@@ -0,0 +1,272 @@
+import { BindingTypes } from '@vue/compiler-core'
+import { assertCode, compileSFCScript as compile } from '../utils'
+
+describe('defineModel()', () => {
+ test('basic usage', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ )
+ assertCode(content)
+ expect(content).toMatch('props: {')
+ expect(content).toMatch('"modelValue": { required: true },')
+ expect(content).toMatch('"count": {},')
+ expect(content).toMatch('"toString": { type: Function },')
+ expect(content).toMatch(
+ 'emits: ["update:modelValue", "update:count", "update:toString"],',
+ )
+ expect(content).toMatch(
+ `const modelValue = _useModel(__props, "modelValue")`,
+ )
+ expect(content).toMatch(`const c = _useModel(__props, 'count')`)
+ expect(content).toMatch(`const toString = _useModel(__props, 'toString')`)
+ expect(content).toMatch(`return { modelValue, c, toString }`)
+ expect(content).not.toMatch('defineModel')
+
+ expect(bindings).toStrictEqual({
+ modelValue: BindingTypes.SETUP_REF,
+ count: BindingTypes.PROPS,
+ c: BindingTypes.SETUP_REF,
+ toString: BindingTypes.SETUP_REF,
+ })
+ })
+
+ test('w/ defineProps and defineEmits', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ )
+ assertCode(content)
+ expect(content).toMatch(`props: /*@__PURE__*/_mergeModels({ foo: String }`)
+ expect(content).toMatch(`"modelValue": { default: 0 }`)
+ expect(content).toMatch(`const count = _useModel(__props, "modelValue")`)
+ expect(content).not.toMatch('defineModel')
+ expect(bindings).toStrictEqual({
+ count: BindingTypes.SETUP_REF,
+ foo: BindingTypes.PROPS,
+ modelValue: BindingTypes.PROPS,
+ })
+ })
+
+ test('w/ array props', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ )
+ assertCode(content)
+ expect(content).toMatch(`props: /*@__PURE__*/_mergeModels(['foo', 'bar'], {
+ "count": {},
+ "countModifiers": {},
+ })`)
+ expect(content).toMatch(`const count = _useModel(__props, 'count')`)
+ expect(content).not.toMatch('defineModel')
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.PROPS,
+ count: BindingTypes.SETUP_REF,
+ })
+ })
+
+ test('w/ types, basic usage', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ )
+ assertCode(content)
+ expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
+ expect(content).toMatch('"modelModifiers": {}')
+ expect(content).toMatch('"count": { type: Number }')
+ expect(content).toMatch(
+ '"disabled": { type: Number, ...{ required: false } }',
+ )
+ expect(content).toMatch('"any": { type: Boolean, skipCheck: true }')
+ expect(content).toMatch(
+ 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]',
+ )
+
+ expect(content).toMatch(
+ `const modelValue = _useModel(__props, "modelValue")`,
+ )
+ expect(content).toMatch(`const count = _useModel(__props, 'count')`)
+ expect(content).toMatch(
+ `const disabled = _useModel(__props, 'disabled')`,
+ )
+ expect(content).toMatch(
+ `const any = _useModel(__props, 'any')`,
+ )
+
+ expect(bindings).toStrictEqual({
+ modelValue: BindingTypes.SETUP_REF,
+ count: BindingTypes.SETUP_REF,
+ disabled: BindingTypes.SETUP_REF,
+ any: BindingTypes.SETUP_REF,
+ })
+ })
+
+ test('w/ types, production mode', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ { isProd: true },
+ )
+ assertCode(content)
+ expect(content).toMatch('"modelValue": { type: Boolean }')
+ expect(content).toMatch('"fn": {}')
+ expect(content).toMatch(
+ '"fnWithDefault": { type: Function, ...{ default: () => null } },',
+ )
+ expect(content).toMatch('"str": {}')
+ expect(content).toMatch('"optional": { required: false }')
+ expect(content).toMatch(
+ 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]',
+ )
+ expect(content).toMatch(
+ `const modelValue = _useModel(__props, "modelValue")`,
+ )
+ expect(content).toMatch(`const fn = _useModel<() => void>(__props, 'fn')`)
+ expect(content).toMatch(`const str = _useModel(__props, 'str')`)
+ expect(bindings).toStrictEqual({
+ modelValue: BindingTypes.SETUP_REF,
+ fn: BindingTypes.SETUP_REF,
+ fnWithDefault: BindingTypes.SETUP_REF,
+ str: BindingTypes.SETUP_REF,
+ optional: BindingTypes.SETUP_REF,
+ })
+ })
+
+ test('w/ types, production mode, boolean + multiple types', () => {
+ const { content } = compile(
+ `
+
+ `,
+ { isProd: true },
+ )
+ assertCode(content)
+ expect(content).toMatch('"modelValue": { type: [Boolean, String, Object] }')
+ })
+
+ test('w/ types, production mode, function + runtime opts + multiple types', () => {
+ const { content } = compile(
+ `
+
+ `,
+ { isProd: true },
+ )
+ assertCode(content)
+ expect(content).toMatch(
+ '"modelValue": { type: [Number, Function], ...{ default: () => 1 } }',
+ )
+ })
+
+ test('get / set transformers', () => {
+ const { content } = compile(
+ `
+
+ `,
+ )
+ assertCode(content)
+ expect(content).toMatch(/"modelValue": {\s+required: true,?\s+}/m)
+ expect(content).toMatch(
+ `_useModel(__props, "modelValue", {
+ get(v) { return v - 1 },
+ set: (v) => { return v + 1 },
+ })`,
+ )
+
+ const { content: content2 } = compile(
+ `
+
+ `,
+ )
+ assertCode(content2)
+ expect(content2).toMatch(
+ /"modelValue": {\s+default: 0,\s+required: true,?\s+}/m,
+ )
+ expect(content2).toMatch(
+ `_useModel(__props, "modelValue", {
+ get(v) { return v - 1 },
+ set: (v) => { return v + 1 },
+ })`,
+ )
+ })
+
+ test('usage w/ props destructure', () => {
+ const { content } = compile(
+ `
+
+ `,
+ { propsDestructure: true },
+ )
+ assertCode(content)
+ expect(content).toMatch(`set: (v) => { return v + __props.x }`)
+ })
+
+ test('w/ Boolean And Function types, production mode', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ { isProd: true },
+ )
+ assertCode(content)
+ expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
+ expect(content).toMatch('emits: ["update:modelValue"]')
+ expect(content).toMatch(
+ `const modelValue = _useModel(__props, "modelValue")`,
+ )
+ expect(bindings).toStrictEqual({
+ modelValue: BindingTypes.SETUP_REF,
+ })
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts
new file mode 100644
index 00000000000..286f1e11bfd
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts
@@ -0,0 +1,149 @@
+import { assertCode, compileSFCScript as compile } from '../utils'
+
+describe('defineOptions()', () => {
+ test('basic usage', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ // should remove defineOptions import and call
+ expect(content).not.toMatch('defineOptions')
+ // should include context options in default export
+ expect(content).toMatch(
+ `export default /*@__PURE__*/Object.assign({ name: 'FooApp' }, `,
+ )
+ })
+
+ test('empty argument', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`export default {`)
+ // should remove defineOptions import and call
+ expect(content).not.toMatch('defineOptions')
+ })
+
+ it('should emit an error with two defineOptions', () => {
+ expect(() =>
+ compile(`
+
+ `),
+ ).toThrowError('[@vue/compiler-sfc] duplicate defineOptions() call')
+ })
+
+ it('should emit an error with props or emits property', () => {
+ expect(() =>
+ compile(`
+
+ `),
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.',
+ )
+
+ expect(() =>
+ compile(`
+
+ `),
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.',
+ )
+
+ expect(() =>
+ compile(`
+
+ `),
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead.',
+ )
+
+ expect(() =>
+ compile(`
+
+ `),
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead.',
+ )
+ })
+
+ it('should emit an error with type generic', () => {
+ expect(() =>
+ compile(`
+
+ `),
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot accept type arguments',
+ )
+ })
+
+ it('should emit an error with type assertion', () => {
+ expect(() =>
+ compile(`
+
+ `),
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.',
+ )
+ })
+
+ it('should emit an error with declaring props/emits/slots/expose', () => {
+ expect(() =>
+ compile(`
+
+ `),
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead',
+ )
+
+ expect(() =>
+ compile(`
+
+ `),
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead',
+ )
+
+ expect(() =>
+ compile(`
+
+ `),
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead',
+ )
+
+ expect(() =>
+ compile(`
+
+ `),
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead',
+ )
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
new file mode 100644
index 00000000000..dcf6341a9fc
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
@@ -0,0 +1,837 @@
+import { BindingTypes } from '@vue/compiler-core'
+import { assertCode, compileSFCScript as compile } from '../utils'
+
+describe('defineProps', () => {
+ test('basic usage', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ // should generate working code
+ assertCode(content)
+ // should analyze bindings
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.LITERAL_CONST,
+ props: BindingTypes.SETUP_REACTIVE_CONST,
+ })
+
+ // should remove defineOptions import and call
+ expect(content).not.toMatch('defineProps')
+ // should generate correct setup signature
+ expect(content).toMatch(`setup(__props, { expose: __expose }) {`)
+ // should assign user identifier to it
+ expect(content).toMatch(`const props = __props`)
+ // should include context options in default export
+ expect(content).toMatch(`export default {
+ props: {
+ foo: String
+},`)
+ })
+
+ test('w/ external definition', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`export default {
+ props: propsModel,`)
+ })
+
+ // #4764
+ test('w/ leading code', () => {
+ const { content } = compile(`
+
+ `)
+ // props declaration should be inside setup, not moved along with the import
+ expect(content).not.toMatch(`const props = __props\nimport`)
+ assertCode(content)
+ })
+
+ test('defineProps w/ runtime options', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`export default /*@__PURE__*/_defineComponent({
+ props: { foo: String },
+ setup(__props, { expose: __expose }) {`)
+ })
+
+ test('w/ type', () => {
+ const { content, bindings } = compile(`
+ `)
+ assertCode(content)
+ expect(content).toMatch(`string: { type: String, required: true }`)
+ expect(content).toMatch(`number: { type: Number, required: true }`)
+ expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
+ expect(content).toMatch(`object: { type: Object, required: true }`)
+ expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
+ expect(content).toMatch(`fn: { type: Function, required: true }`)
+ expect(content).toMatch(`functionRef: { type: Function, required: true }`)
+ expect(content).toMatch(`objectRef: { type: Object, required: true }`)
+ expect(content).toMatch(`dateTime: { type: Date, required: true }`)
+ expect(content).toMatch(`array: { type: Array, required: true }`)
+ expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
+ expect(content).toMatch(`tuple: { type: Array, required: true }`)
+ expect(content).toMatch(`set: { type: Set, required: true }`)
+ expect(content).toMatch(`literal: { type: String, required: true }`)
+ expect(content).toMatch(`optional: { type: null, required: false }`)
+ expect(content).toMatch(`recordRef: { type: Object, required: true }`)
+ expect(content).toMatch(`interface: { type: Object, required: true }`)
+ expect(content).toMatch(`alias: { type: Array, required: true }`)
+ expect(content).toMatch(`method: { type: Function, required: true }`)
+ expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
+ expect(content).toMatch(`error: { type: Error, required: true }`)
+ expect(content).toMatch(
+ `objectOrFn: { type: [Function, Object], required: true },`,
+ )
+ expect(content).toMatch(`extract: { type: Number, required: true }`)
+ expect(content).toMatch(
+ `exclude: { type: [Number, Boolean], required: true }`,
+ )
+ expect(content).toMatch(`uppercase: { type: String, required: true }`)
+ expect(content).toMatch(`params: { type: Array, required: true }`)
+ expect(content).toMatch(`nonNull: { type: String, required: true }`)
+ expect(content).toMatch(`union: { type: [String, Number], required: true }`)
+ expect(content).toMatch(`literalUnion: { type: String, required: true }`)
+ expect(content).toMatch(
+ `literalUnionNumber: { type: Number, required: true }`,
+ )
+ expect(content).toMatch(
+ `literalUnionMixed: { type: [String, Number, Boolean], required: true }`,
+ )
+ expect(content).toMatch(`intersection: { type: Object, required: true }`)
+ expect(content).toMatch(`intersection2: { type: String, required: true }`)
+ expect(content).toMatch(`foo: { type: [Function, null], required: true }`)
+ expect(content).toMatch(`unknown: { type: null, required: true }`)
+ // uninon containing unknown type: skip check
+ expect(content).toMatch(`unknownUnion: { type: null, required: true }`)
+ // intersection containing unknown type: narrow to the known types
+ expect(content).toMatch(
+ `unknownIntersection: { type: Object, required: true },`,
+ )
+ expect(content).toMatch(
+ `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },`,
+ )
+ expect(content).toMatch(
+ `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }`,
+ )
+ expect(bindings).toStrictEqual({
+ string: BindingTypes.PROPS,
+ number: BindingTypes.PROPS,
+ boolean: BindingTypes.PROPS,
+ object: BindingTypes.PROPS,
+ objectLiteral: BindingTypes.PROPS,
+ fn: BindingTypes.PROPS,
+ functionRef: BindingTypes.PROPS,
+ objectRef: BindingTypes.PROPS,
+ dateTime: BindingTypes.PROPS,
+ array: BindingTypes.PROPS,
+ arrayRef: BindingTypes.PROPS,
+ tuple: BindingTypes.PROPS,
+ set: BindingTypes.PROPS,
+ literal: BindingTypes.PROPS,
+ optional: BindingTypes.PROPS,
+ recordRef: BindingTypes.PROPS,
+ interface: BindingTypes.PROPS,
+ alias: BindingTypes.PROPS,
+ method: BindingTypes.PROPS,
+ symbol: BindingTypes.PROPS,
+ error: BindingTypes.PROPS,
+ objectOrFn: BindingTypes.PROPS,
+ extract: BindingTypes.PROPS,
+ exclude: BindingTypes.PROPS,
+ union: BindingTypes.PROPS,
+ literalUnion: BindingTypes.PROPS,
+ literalUnionNumber: BindingTypes.PROPS,
+ literalUnionMixed: BindingTypes.PROPS,
+ intersection: BindingTypes.PROPS,
+ intersection2: BindingTypes.PROPS,
+ foo: BindingTypes.PROPS,
+ uppercase: BindingTypes.PROPS,
+ params: BindingTypes.PROPS,
+ nonNull: BindingTypes.PROPS,
+ unknown: BindingTypes.PROPS,
+ unknownUnion: BindingTypes.PROPS,
+ unknownIntersection: BindingTypes.PROPS,
+ unknownUnionWithBoolean: BindingTypes.PROPS,
+ unknownUnionWithFunction: BindingTypes.PROPS,
+ })
+ })
+
+ test('w/ interface', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS,
+ })
+ })
+
+ test('w/ extends interface', () => {
+ const { content, bindings } = compile(`
+
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`z: { type: Number, required: true }`)
+ expect(content).toMatch(`y: { type: String, required: true }`)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS,
+ y: BindingTypes.PROPS,
+ z: BindingTypes.PROPS,
+ })
+ })
+
+ test('w/ extends intersection type', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`z: { type: Number, required: true }`)
+ expect(content).toMatch(`y: { type: String, required: true }`)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS,
+ y: BindingTypes.PROPS,
+ z: BindingTypes.PROPS,
+ })
+ })
+
+ test('w/ intersection type', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`y: { type: String, required: true }`)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS,
+ y: BindingTypes.PROPS,
+ })
+ })
+
+ test('w/ exported interface', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS,
+ })
+ })
+
+ test('w/ exported interface in normal script', () => {
+ const { content, bindings } = compile(`
+
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS,
+ })
+ })
+
+ test('w/ type alias', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS,
+ })
+ })
+
+ test('w/ exported type alias', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS,
+ })
+ })
+
+ test('w/ TS assertion', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ expect(content).toMatch(`props: ['foo']`)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ })
+ })
+
+ test('withDefaults (static)', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(
+ `foo: { type: String, required: false, default: 'hi' }`,
+ )
+ expect(content).toMatch(`bar: { type: Number, required: false }`)
+ expect(content).toMatch(`baz: { type: Boolean, required: true }`)
+ expect(content).toMatch(
+ `qux: { type: Function, required: false, default() { return 1 } }`,
+ )
+ expect(content).toMatch(
+ `quux: { type: Function, required: false, default() { } }`,
+ )
+ expect(content).toMatch(
+ `quuxx: { type: Promise, required: false, async default() { return await Promise.resolve('hi') } }`,
+ )
+ expect(content).toMatch(
+ `fred: { type: String, required: false, get default() { return 'fred' } }`,
+ )
+ expect(content).toMatch(`const props = __props`)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.PROPS,
+ baz: BindingTypes.PROPS,
+ qux: BindingTypes.PROPS,
+ quux: BindingTypes.PROPS,
+ quuxx: BindingTypes.PROPS,
+ fred: BindingTypes.PROPS,
+ props: BindingTypes.SETUP_CONST,
+ })
+ })
+
+ test('withDefaults (static) + normal script', () => {
+ const { content } = compile(`
+
+
+ `)
+ assertCode(content)
+ })
+
+ // #7111
+ test('withDefaults (static) w/ production mode', () => {
+ const { content } = compile(
+ `
+
+ `,
+ { isProd: true },
+ )
+ assertCode(content)
+ expect(content).toMatch(`const props = __props`)
+
+ // foo has no default value, the Function can be dropped
+ expect(content).toMatch(`foo: {}`)
+ expect(content).toMatch(`bar: { type: Boolean }`)
+ expect(content).toMatch(`baz: { type: [Boolean, Function], default: true }`)
+ expect(content).toMatch(`qux: { default: 'hi' }`)
+ })
+
+ test('withDefaults (dynamic)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
+ expect(content).toMatch(
+ `
+ _mergeDefaults({
+ foo: { type: String, required: false },
+ bar: { type: Number, required: false },
+ baz: { type: Boolean, required: true }
+ }, { ...defaults })`.trim(),
+ )
+ })
+
+ test('withDefaults (reference)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
+ expect(content).toMatch(
+ `
+ _mergeDefaults({
+ foo: { type: String, required: false },
+ bar: { type: Number, required: false },
+ baz: { type: Boolean, required: true }
+ }, defaults)`.trim(),
+ )
+ })
+
+ // #7111
+ test('withDefaults (dynamic) w/ production mode', () => {
+ const { content } = compile(
+ `
+
+ `,
+ { isProd: true },
+ )
+ assertCode(content)
+ expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
+ expect(content).toMatch(
+ `
+ _mergeDefaults({
+ foo: { type: Function },
+ bar: { type: Boolean },
+ baz: { type: [Boolean, Function] },
+ qux: {}
+ }, { ...defaults })`.trim(),
+ )
+ })
+
+ test('withDefaults w/ dynamic object method', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
+ expect(content).toMatch(
+ `
+ _mergeDefaults({
+ foo: { type: Function, required: false }
+ }, {
+ ['fo' + 'o']() { return 'foo' }
+ })`.trim(),
+ )
+ })
+
+ test('runtime inference for Enum', () => {
+ expect(
+ compile(
+ ``,
+ { hoistStatic: true },
+ ).content,
+ ).toMatch(`foo: { type: Number`)
+
+ expect(
+ compile(
+ ``,
+ { hoistStatic: true },
+ ).content,
+ ).toMatch(`foo: { type: String`)
+
+ expect(
+ compile(
+ ``,
+ { hoistStatic: true },
+ ).content,
+ ).toMatch(`foo: { type: [String, Number]`)
+
+ expect(
+ compile(
+ ``,
+ { hoistStatic: true },
+ ).content,
+ ).toMatch(`foo: { type: Number`)
+ })
+
+ // #8148
+ test('should not override local bindings', () => {
+ const { bindings } = compile(`
+
+ `)
+ expect(bindings).toStrictEqual({
+ bar: BindingTypes.SETUP_REF,
+ computed: BindingTypes.SETUP_CONST,
+ })
+ })
+
+ // #8289
+ test('destructure without enabling reactive destructure', () => {
+ const { content, bindings } = compile(
+ ``,
+ {
+ propsDestructure: false,
+ },
+ )
+ expect(content).toMatch(`const { foo } = __props`)
+ expect(content).toMatch(`return { foo }`)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.SETUP_CONST,
+ })
+ assertCode(content)
+ })
+
+ test('prohibiting reactive destructure', () => {
+ expect(() =>
+ compile(
+ ``,
+ {
+ propsDestructure: 'error',
+ },
+ ),
+ ).toThrow()
+ })
+
+ describe('errors', () => {
+ test('w/ both type and non-type args', () => {
+ expect(() => {
+ compile(``)
+ }).toThrow(`cannot accept both type and non-type arguments`)
+ })
+ })
+
+ test('should escape names w/ special symbols', () => {
+ const { content, bindings } = compile(`
+ `)
+ assertCode(content)
+ expect(content).toMatch(`"spa ce": { type: null, required: true }`)
+ expect(content).toMatch(
+ `"exclamation!mark": { type: null, required: true }`,
+ )
+ expect(content).toMatch(`"double\\"quote": { type: null, required: true }`)
+ expect(content).toMatch(`"hash#tag": { type: null, required: true }`)
+ expect(content).toMatch(`"dollar$sign": { type: null, required: true }`)
+ expect(content).toMatch(`"percentage%sign": { type: null, required: true }`)
+ expect(content).toMatch(`"amper&sand": { type: null, required: true }`)
+ expect(content).toMatch(`"single'quote": { type: null, required: true }`)
+ expect(content).toMatch(`"round(brack)ets": { type: null, required: true }`)
+ expect(content).toMatch(`"aste*risk": { type: null, required: true }`)
+ expect(content).toMatch(`"pl+us": { type: null, required: true }`)
+ expect(content).toMatch(`"com,ma": { type: null, required: true }`)
+ expect(content).toMatch(`"do.t": { type: null, required: true }`)
+ expect(content).toMatch(`"sla/sh": { type: null, required: true }`)
+ expect(content).toMatch(`"co:lon": { type: null, required: true }`)
+ expect(content).toMatch(`"semi;colon": { type: null, required: true }`)
+ expect(content).toMatch(`"angleets": { type: null, required: true }`)
+ expect(content).toMatch(`"equal=sign": { type: null, required: true }`)
+ expect(content).toMatch(`"question?mark": { type: null, required: true }`)
+ expect(content).toMatch(`"at@sign": { type: null, required: true }`)
+ expect(content).toMatch(
+ `"square[brack]ets": { type: null, required: true }`,
+ )
+ expect(content).toMatch(`"back\\\\slash": { type: null, required: true }`)
+ expect(content).toMatch(`"ca^ret": { type: null, required: true }`)
+ expect(content).toMatch(`"back\`tick": { type: null, required: true }`)
+ expect(content).toMatch(`"curly{bra}ces": { type: null, required: true }`)
+ expect(content).toMatch(`"pi|pe": { type: null, required: true }`)
+ expect(content).toMatch(`"til~de": { type: null, required: true }`)
+ expect(content).toMatch(`"da-sh": { type: null, required: true }`)
+ expect(bindings).toStrictEqual({
+ 'spa ce': BindingTypes.PROPS,
+ 'exclamation!mark': BindingTypes.PROPS,
+ 'double"quote': BindingTypes.PROPS,
+ 'hash#tag': BindingTypes.PROPS,
+ dollar$sign: BindingTypes.PROPS,
+ 'percentage%sign': BindingTypes.PROPS,
+ 'amper&sand': BindingTypes.PROPS,
+ "single'quote": BindingTypes.PROPS,
+ 'round(brack)ets': BindingTypes.PROPS,
+ 'aste*risk': BindingTypes.PROPS,
+ 'pl+us': BindingTypes.PROPS,
+ 'com,ma': BindingTypes.PROPS,
+ 'do.t': BindingTypes.PROPS,
+ 'sla/sh': BindingTypes.PROPS,
+ 'co:lon': BindingTypes.PROPS,
+ 'semi;colon': BindingTypes.PROPS,
+ 'angleets': BindingTypes.PROPS,
+ 'equal=sign': BindingTypes.PROPS,
+ 'question?mark': BindingTypes.PROPS,
+ 'at@sign': BindingTypes.PROPS,
+ 'square[brack]ets': BindingTypes.PROPS,
+ 'back\\slash': BindingTypes.PROPS,
+ 'ca^ret': BindingTypes.PROPS,
+ 'back`tick': BindingTypes.PROPS,
+ 'curly{bra}ces': BindingTypes.PROPS,
+ 'pi|pe': BindingTypes.PROPS,
+ 'til~de': BindingTypes.PROPS,
+ 'da-sh': BindingTypes.PROPS,
+ })
+ })
+
+ // #8989
+ test('custom element retains the props type & production mode', () => {
+ const { content } = compile(
+ ``,
+ { isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
+ { filename: 'app.ce.vue' },
+ )
+
+ expect(content).toMatch(`foo: {type: Number}`)
+ assertCode(content)
+ })
+
+ test('custom element retains the props type & default value & production mode', () => {
+ const { content } = compile(
+ ``,
+ { isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
+ { filename: 'app.ce.vue' },
+ )
+ expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
+ assertCode(content)
+ })
+
+ test('w/ TSTypeAliasDeclaration', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(
+ `foo: { type: Function, required: false, default: () => true }`,
+ )
+ expect(content).toMatch(
+ `bar: { type: Function, required: false, default: () => true }`,
+ )
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
new file mode 100644
index 00000000000..25dd817bbe5
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
@@ -0,0 +1,532 @@
+import { BindingTypes } from '@vue/compiler-core'
+import type { SFCScriptCompileOptions } from '../../src'
+import { assertCode, compileSFCScript } from '../utils'
+
+describe('sfc reactive props destructure', () => {
+ function compile(src: string, options?: Partial) {
+ return compileSFCScript(src, {
+ inlineTemplate: true,
+ ...options,
+ })
+ }
+
+ test('basic usage', () => {
+ const { content, bindings } = compile(`
+
+ {{ foo }}
+ `)
+ expect(content).not.toMatch(`const { foo } =`)
+ expect(content).toMatch(`console.log(__props.foo)`)
+ expect(content).toMatch(`_toDisplayString(__props.foo)`)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ })
+ })
+
+ test('multiple variable declarations', () => {
+ const { content, bindings } = compile(`
+
+ {{ foo }} {{ hello }} {{ bar }}
+ `)
+ expect(content).not.toMatch(`const { foo } =`)
+ expect(content).toMatch(`const bar = 'fish', hello = 'world'`)
+ expect(content).toMatch(`_toDisplayString(hello)`)
+ expect(content).toMatch(`_toDisplayString(bar)`)
+ expect(content).toMatch(`_toDisplayString(__props.foo)`)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.LITERAL_CONST,
+ hello: BindingTypes.LITERAL_CONST,
+ })
+ })
+
+ test('nested scope', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ expect(content).not.toMatch(`const { foo, bar } =`)
+ expect(content).toMatch(`console.log(foo)`)
+ expect(content).toMatch(`console.log(__props.bar)`)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.PROPS,
+ test: BindingTypes.SETUP_CONST,
+ })
+ })
+
+ test('default values w/ array runtime declaration', () => {
+ const { content } = compile(`
+
+ `)
+ // literals can be used as-is, non-literals are always returned from a
+ // function
+ // functions need to be marked with a skip marker
+ expect(content)
+ .toMatch(`props: /*@__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], {
+ foo: 1,
+ bar: () => ({}),
+ func: () => {}, __skip_func: true
+})`)
+ assertCode(content)
+ })
+
+ test('default values w/ object runtime declaration', () => {
+ const { content } = compile(`
+
+ `)
+ // literals can be used as-is, non-literals are always returned from a
+ // function
+ // functions need to be marked with a skip marker since we cannot always
+ // safely infer whether runtime type is Function (e.g. if the runtime decl
+ // is imported, or spreads another object)
+ expect(content)
+ .toMatch(`props: /*@__PURE__*/_mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
+ foo: 1,
+ bar: () => ({}),
+ func: () => {}, __skip_func: true,
+ ext: x, __skip_ext: true
+})`)
+ assertCode(content)
+ })
+ test('default values w/ runtime declaration & key is string', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ expect(bindings).toStrictEqual({
+ __propsAliases: {
+ fooBar: 'foo:bar',
+ },
+ foo: BindingTypes.PROPS,
+ 'foo:bar': BindingTypes.PROPS,
+ fooBar: BindingTypes.PROPS_ALIASED,
+ })
+
+ expect(content).toMatch(`
+ props: /*@__PURE__*/_mergeDefaults(['foo', 'foo:bar'], {
+ foo: 1,
+ "foo:bar": 'foo-bar'
+}),`)
+ assertCode(content)
+ })
+
+ test('default values w/ type declaration', () => {
+ const { content } = compile(`
+
+ `)
+ // literals can be used as-is, non-literals are always returned from a
+ // function
+ expect(content).toMatch(`props: {
+ foo: { type: Number, required: false, default: 1 },
+ bar: { type: Object, required: false, default: () => ({}) },
+ func: { type: Function, required: false, default: () => {} }
+ }`)
+ assertCode(content)
+ })
+
+ test('default values w/ type declaration & key is string', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ expect(bindings).toStrictEqual({
+ __propsAliases: {
+ fooBar: 'foo:bar',
+ },
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.PROPS,
+ 'foo:bar': BindingTypes.PROPS,
+ fooBar: BindingTypes.PROPS_ALIASED,
+ 'onUpdate:modelValue': BindingTypes.PROPS,
+ })
+ expect(content).toMatch(`
+ props: {
+ foo: { type: Number, required: true, default: 1 },
+ bar: { type: Number, required: true, default: 2 },
+ "foo:bar": { type: String, required: true, default: 'foo-bar' },
+ "onUpdate:modelValue": { type: Function, required: true }
+ },`)
+ assertCode(content)
+ })
+
+ test('default values w/ type declaration, prod mode', () => {
+ const { content } = compile(
+ `
+
+ `,
+ { isProd: true },
+ )
+ assertCode(content)
+ // literals can be used as-is, non-literals are always returned from a
+ // function
+ expect(content).toMatch(`props: {
+ foo: { default: 1 },
+ bar: { default: () => ({}) },
+ baz: {},
+ boola: { type: Boolean },
+ boolb: { type: [Boolean, Number] },
+ func: { type: Function, default: () => {} }
+ }`)
+ })
+
+ test('with TSInstantiationExpression', () => {
+ const { content } = compile(
+ `
+
+ `,
+ { isProd: true },
+ )
+ assertCode(content)
+ expect(content).toMatch(`const foo = __props.value<123>`)
+ })
+
+ test('aliasing', () => {
+ const { content, bindings } = compile(`
+
+ {{ foo + bar }}
+ `)
+ expect(content).not.toMatch(`const { foo: bar } =`)
+ expect(content).toMatch(`let x = foo`) // should not process
+ expect(content).toMatch(`let y = __props.foo`)
+ // should convert bar to __props.foo in template expressions
+ expect(content).toMatch(`_toDisplayString(__props.foo + __props.foo)`)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.SETUP_LET,
+ y: BindingTypes.SETUP_LET,
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.PROPS_ALIASED,
+ __propsAliases: {
+ bar: 'foo',
+ },
+ })
+ })
+
+ // #5425
+ test('non-identifier prop names', () => {
+ const { content, bindings } = compile(`
+
+ {{ fooBar }}
+ `)
+ expect(content).toMatch(`x = __props["foo.bar"]`)
+ expect(content).toMatch(`toDisplayString(__props["foo.bar"])`)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.SETUP_LET,
+ 'foo.bar': BindingTypes.PROPS,
+ fooBar: BindingTypes.PROPS_ALIASED,
+ __propsAliases: {
+ fooBar: 'foo.bar',
+ },
+ })
+ })
+
+ test('rest spread', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ expect(content).toMatch(
+ `const rest = _createPropsRestProxy(__props, ["foo","bar"])`,
+ )
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.PROPS,
+ baz: BindingTypes.PROPS,
+ rest: BindingTypes.SETUP_REACTIVE_CONST,
+ })
+ })
+
+ test('rest spread non-inline', () => {
+ const { content, bindings } = compile(
+ `
+
+ {{ rest.bar }}
+ `,
+ { inlineTemplate: false },
+ )
+ expect(content).toMatch(
+ `const rest = _createPropsRestProxy(__props, ["foo"])`,
+ )
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.PROPS,
+ rest: BindingTypes.SETUP_REACTIVE_CONST,
+ })
+ })
+
+ // #6960
+ test('computed static key', () => {
+ const { content, bindings } = compile(`
+
+ {{ foo }}
+ `)
+ expect(content).not.toMatch(`const { foo } =`)
+ expect(content).toMatch(`console.log(__props.foo)`)
+ expect(content).toMatch(`_toDisplayString(__props.foo)`)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ })
+ })
+
+ test('multi-variable declaration', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`const a = 1;`)
+ expect(content).toMatch(`props: ['item'],`)
+ })
+
+ // #6757
+ test('multi-variable declaration fix #6757 ', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`const a = 1;`)
+ expect(content).toMatch(`props: ['item'],`)
+ })
+
+ // #7422
+ test('multi-variable declaration fix #7422', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`const a = 0,`)
+ expect(content).toMatch(`b = 0;`)
+ expect(content).toMatch(`props: ['item'],`)
+ })
+
+ test('handle function parameters with same name as destructured props', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`console.log(__props.value)`)
+ })
+
+ test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`props: ['item'],`)
+ expect(content).toMatch(`emits: ['a'],`)
+ })
+
+ describe('errors', () => {
+ test('should error on deep destructure', () => {
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).toThrow(`destructure does not support nested patterns`)
+
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).toThrow(`destructure does not support nested patterns`)
+ })
+
+ test('should error on computed key', () => {
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).toThrow(`destructure cannot use computed key`)
+ })
+
+ test('should warn when used with withDefaults', () => {
+ compile(
+ ``,
+ )
+ expect(
+ `withDefaults() is unnecessary when using destructure`,
+ ).toHaveBeenWarned()
+ })
+
+ test('should error if destructure reference local vars', () => {
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).toThrow(`cannot reference locally declared variables`)
+ })
+
+ test('should error if assignment to destructured prop binding', () => {
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).toThrow(`Cannot assign to destructured props`)
+
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).toThrow(`Cannot assign to destructured props`)
+ })
+
+ test('should error when passing destructured prop into certain methods', () => {
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).toThrow(
+ `"foo" is a destructured prop and should not be passed directly to watch().`,
+ )
+
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).toThrow(
+ `"foo" is a destructured prop and should not be passed directly to watch().`,
+ )
+
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).toThrow(
+ `"foo" is a destructured prop and should not be passed directly to toRef().`,
+ )
+
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).toThrow(
+ `"foo" is a destructured prop and should not be passed directly to toRef().`,
+ )
+ })
+
+ // not comprehensive, but should help for most common cases
+ test('should error if default value type does not match declared type', () => {
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).toThrow(`Default value of prop "foo" does not match declared type.`)
+ })
+
+ // #8017
+ test('should not throw an error if the variable is not a props', () => {
+ expect(() =>
+ compile(
+ ``,
+ ),
+ ).not.toThrowError()
+ })
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineSlots.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineSlots.spec.ts
new file mode 100644
index 00000000000..357709afdf3
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/defineSlots.spec.ts
@@ -0,0 +1,40 @@
+import { assertCode, compileSFCScript as compile } from '../utils'
+
+describe('defineSlots()', () => {
+ test('basic usage', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`const slots = _useSlots()`)
+ expect(content).not.toMatch('defineSlots')
+ })
+
+ test('w/o return value', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).not.toMatch('defineSlots')
+ expect(content).not.toMatch(`_useSlots`)
+ })
+
+ test('w/o generic params', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`const slots = _useSlots()`)
+ expect(content).not.toMatch('defineSlots')
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts b/packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts
new file mode 100644
index 00000000000..ce6191777cc
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts
@@ -0,0 +1,219 @@
+import { BindingTypes } from '@vue/compiler-core'
+import type { SFCScriptCompileOptions } from '../../src'
+import { assertCode, compileSFCScript } from '../utils'
+
+describe('sfc hoist static', () => {
+ function compile(src: string, options?: Partial) {
+ return compileSFCScript(src, {
+ inlineTemplate: true,
+ hoistStatic: true,
+ ...options,
+ })
+ }
+
+ test('should hoist literal value', () => {
+ const code = `
+ const string = 'default value'
+ const number = 123
+ const boolean = false
+ const nil = null
+ const bigint = 100n
+ const template = \`str\`
+ `.trim()
+ const { content, bindings } = compile(`
+
+ `)
+
+ // should hoist to first line
+ expect(content.startsWith(code)).toBe(true)
+ expect(bindings).toStrictEqual({
+ string: BindingTypes.LITERAL_CONST,
+ number: BindingTypes.LITERAL_CONST,
+ boolean: BindingTypes.LITERAL_CONST,
+ nil: BindingTypes.LITERAL_CONST,
+ bigint: BindingTypes.LITERAL_CONST,
+ template: BindingTypes.LITERAL_CONST,
+ })
+ assertCode(content)
+ })
+
+ test('should hoist expressions', () => {
+ const code = `
+ const unary = !false
+ const binary = 1 + 2
+ const conditional = 1 ? 2 : 3
+ const sequence = (1, true, 'foo', 1)
+ `.trim()
+ const { content, bindings } = compile(`
+
+ `)
+ // should hoist to first line
+ expect(content.startsWith(code)).toBe(true)
+ expect(bindings).toStrictEqual({
+ binary: BindingTypes.LITERAL_CONST,
+ conditional: BindingTypes.LITERAL_CONST,
+ unary: BindingTypes.LITERAL_CONST,
+ sequence: BindingTypes.LITERAL_CONST,
+ })
+ assertCode(content)
+ })
+
+ test('should hoist w/ defineProps/Emits', () => {
+ const hoistCode = `const defaultValue = 'default value'`
+ const { content, bindings } = compile(`
+
+ `)
+
+ // should hoist to first line
+ expect(content.startsWith(hoistCode)).toBe(true)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ defaultValue: BindingTypes.LITERAL_CONST,
+ })
+ assertCode(content)
+ })
+
+ test('should not hoist a variable', () => {
+ const code = `
+ let KEY1 = 'default value'
+ var KEY2 = 123
+ const regex = /.*/g
+ const undef = undefined
+ `.trim()
+ const { content, bindings } = compile(`
+
+ `)
+ expect(bindings).toStrictEqual({
+ KEY1: BindingTypes.SETUP_LET,
+ KEY2: BindingTypes.SETUP_LET,
+ regex: BindingTypes.SETUP_CONST,
+ undef: BindingTypes.SETUP_MAYBE_REF,
+ })
+ expect(content).toMatch(`setup(__props) {\n\n ${code}`)
+ assertCode(content)
+ })
+
+ test('should not hoist a constant initialized to a reference value', () => {
+ const code = `
+ const KEY1 = Boolean
+ const KEY2 = [Boolean]
+ const KEY3 = [getCurrentInstance()]
+ let i = 0;
+ const KEY4 = (i++, 'foo')
+ enum KEY5 {
+ FOO = 1,
+ BAR = getCurrentInstance(),
+ }
+ const KEY6 = \`template\${i}\`
+ `.trim()
+ const { content, bindings } = compile(`
+
+ `)
+ expect(bindings).toStrictEqual({
+ KEY1: BindingTypes.SETUP_MAYBE_REF,
+ KEY2: BindingTypes.SETUP_CONST,
+ KEY3: BindingTypes.SETUP_CONST,
+ KEY4: BindingTypes.SETUP_CONST,
+ KEY5: BindingTypes.SETUP_CONST,
+ KEY6: BindingTypes.SETUP_CONST,
+ i: BindingTypes.SETUP_LET,
+ })
+ expect(content).toMatch(`setup(__props) {\n\n ${code}`)
+ assertCode(content)
+ })
+
+ test('should not hoist a object or array', () => {
+ const code = `
+ const obj = { foo: 'bar' }
+ const arr = [1, 2, 3]
+ `.trim()
+ const { content, bindings } = compile(`
+
+ `)
+ expect(bindings).toStrictEqual({
+ arr: BindingTypes.SETUP_CONST,
+ obj: BindingTypes.SETUP_CONST,
+ })
+ expect(content).toMatch(`setup(__props) {\n\n ${code}`)
+ assertCode(content)
+ })
+
+ test('should not hoist a function or class', () => {
+ const code = `
+ const fn = () => {}
+ function fn2() {}
+ class Foo {}
+ `.trim()
+ const { content, bindings } = compile(`
+
+ `)
+ expect(bindings).toStrictEqual({
+ Foo: BindingTypes.SETUP_CONST,
+ fn: BindingTypes.SETUP_CONST,
+ fn2: BindingTypes.SETUP_CONST,
+ })
+ expect(content).toMatch(`setup(__props) {\n\n ${code}`)
+ assertCode(content)
+ })
+
+ test('should enable when only script setup', () => {
+ const { content, bindings } = compile(`
+
+
+ `)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.SETUP_CONST,
+ })
+ assertCode(content)
+ })
+
+ test('should not hoist when disabled', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ { hoistStatic: false },
+ )
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.SETUP_CONST,
+ })
+ assertCode(content)
+ })
+
+ test('template binding access in inline mode', () => {
+ const { content } = compile(
+ `
+
+ {{ foo }}
+ `,
+ )
+ expect(content).toMatch('_toDisplayString(foo)')
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts b/packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts
new file mode 100644
index 00000000000..210fa09688a
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts
@@ -0,0 +1,267 @@
+import { assertCode, compileSFCScript as compile } from '../utils'
+
+// in dev mode, declared bindings are returned as an object from setup()
+// when using TS, users may import types which should not be returned as
+// values, so we need to check import usage in the template to determine
+// what to be returned.
+
+test('components', () => {
+ const { content } = compile(`
+
+
+
+
+
+ FooBar
+
+ `)
+ // FooBar: should not be matched by plain text or incorrect case
+ // FooBaz: used as PascalCase component
+ // FooQux: used as kebab-case component
+ // foo: lowercase component
+ expect(content).toMatch(
+ `return { fooBar, get FooBaz() { return FooBaz }, ` +
+ `get FooQux() { return FooQux }, get foo() { return foo } }`,
+ )
+ assertCode(content)
+})
+
+test('directive', () => {
+ const { content } = compile(`
+
+
+
+
+ `)
+ expect(content).toMatch(`return { get vMyDir() { return vMyDir } }`)
+ assertCode(content)
+})
+
+test('dynamic arguments', () => {
+ const { content } = compile(`
+
+
+
+
+
+
+
+
+
+ `)
+ expect(content).toMatch(
+ `return { get FooBar() { return FooBar }, get foo() { return foo }, ` +
+ `get bar() { return bar }, get baz() { return baz }, get msg() { return msg } }`,
+ )
+ assertCode(content)
+})
+
+// https://github.com/vuejs/core/issues/4599
+test('attribute expressions', () => {
+ const { content } = compile(`
+
+
+
+
+ `)
+ expect(content).toMatch(
+ `return { cond, get bar() { return bar }, get baz() { return baz } }`,
+ )
+ assertCode(content)
+})
+
+test('vue interpolations', () => {
+ const { content } = compile(`
+
+
+ {{ x }} {{ yy }} {{ x$y }}
+
+ `)
+ // x: used in interpolation
+ // y: should not be matched by {{ yy }} or 'y' in binding exps
+ // x$y: #4274 should escape special chars when creating Regex
+ expect(content).toMatch(
+ `return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }`,
+ )
+ assertCode(content)
+})
+
+// #4340 interpolations in template strings
+test('js template string interpolations', () => {
+ const { content } = compile(`
+
+
+ {{ \`\${VAR}VAR2\${VAR3}\` }}
+
+ `)
+ // VAR2 should not be matched
+ expect(content).toMatch(
+ `return { get VAR() { return VAR }, get VAR3() { return VAR3 } }`,
+ )
+ assertCode(content)
+})
+
+// edge case: last tag in template
+test('last tag', () => {
+ const { content } = compile(`
+
+
+
+
+
+ `)
+ expect(content).toMatch(
+ `return { get FooBaz() { return FooBaz }, get Last() { return Last } }`,
+ )
+ assertCode(content)
+})
+
+test('TS annotations', () => {
+ const { content } = compile(`
+
+
+ {{ a as Foo }}
+ {{ b() }}
+ {{ Baz }}
+ {{ data }}
+
+
+ `)
+ expect(content).toMatch(`return { a, b, get Baz() { return Baz } }`)
+ assertCode(content)
+})
+
+// vuejs/vue#12591
+test('v-on inline statement', () => {
+ // should not error
+ compile(`
+
+
+
+
+ `)
+})
+
+test('template ref', () => {
+ const { content } = compile(`
+
+
+
+
+
+
+ `)
+ expect(content).toMatch(
+ 'return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }',
+ )
+ assertCode(content)
+})
+
+// https://github.com/nuxt/nuxt/issues/22416
+test('property access', () => {
+ const { content } = compile(`
+
+
+ {{ Foo.Bar.Baz }}
+
+ `)
+ expect(content).toMatch('return { get Foo() { return Foo } }')
+ assertCode(content)
+})
+
+test('spread operator', () => {
+ const { content } = compile(`
+
+
+
+
+ `)
+ expect(content).toMatch('return { get Foo() { return Foo } }')
+ assertCode(content)
+})
+
+test('property access (whitespace)', () => {
+ const { content } = compile(`
+
+
+ {{ Foo . Bar . Baz }}
+
+ `)
+ expect(content).toMatch('return { get Foo() { return Foo } }')
+ assertCode(content)
+})
+
+// #9974
+test('namespace / dot component usage', () => {
+ const { content } = compile(`
+
+
+
+
+ `)
+ expect(content).toMatch('return { get Foo() { return Foo } }')
+ assertCode(content)
+})
+
+test('check when has explicit parse options', () => {
+ const { content } = compile(
+ `
+
+
+ {{ x }}
+
+ `,
+ undefined,
+ { templateParseOptions: {} },
+ )
+ expect(content).toMatch('return { get x() { return x } }')
+})
+
+// #11745
+test('shorthand binding w/ kebab-case', () => {
+ const { content } = compile(
+ `
+
+
+
+
+ `,
+ )
+ expect(content).toMatch('return { get fooBar() { return fooBar }')
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
new file mode 100644
index 00000000000..68fc5cc3159
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
@@ -0,0 +1,1534 @@
+import { normalize } from 'node:path'
+import type { Identifier } from '@babel/types'
+import { type SFCScriptCompileOptions, parse } from '../../src'
+import { ScriptCompileContext } from '../../src/script/context'
+import {
+ inferRuntimeType,
+ invalidateTypeCache,
+ recordImports,
+ registerTS,
+ resolveTypeElements,
+} from '../../src/script/resolveType'
+import { UNKNOWN_TYPE } from '../../src/script/utils'
+import ts from 'typescript'
+
+registerTS(() => ts)
+
+describe('resolveType', () => {
+ test('type literal', () => {
+ const { props, calls } = resolve(`defineProps<{
+ foo: number // property
+ bar(): void // method
+ 'baz': string // string literal key
+ (e: 'foo'): void // call signature
+ (e: 'bar'): void
+ }>()`)
+ expect(props).toStrictEqual({
+ foo: ['Number'],
+ bar: ['Function'],
+ baz: ['String'],
+ })
+ expect(calls?.length).toBe(2)
+ })
+
+ test('reference type', () => {
+ expect(
+ resolve(`
+ type Aliased = { foo: number }
+ defineProps()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Number'],
+ })
+ })
+
+ test('reference exported type', () => {
+ expect(
+ resolve(`
+ export type Aliased = { foo: number }
+ defineProps()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Number'],
+ })
+ })
+
+ test('reference interface', () => {
+ expect(
+ resolve(`
+ interface Aliased { foo: number }
+ defineProps()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Number'],
+ })
+ })
+
+ test('reference exported interface', () => {
+ expect(
+ resolve(`
+ export interface Aliased { foo: number }
+ defineProps()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Number'],
+ })
+ })
+
+ test('reference interface extends', () => {
+ expect(
+ resolve(`
+ export interface A { a(): void }
+ export interface B extends A { b: boolean }
+ interface C { c: string }
+ interface Aliased extends B, C { foo: number }
+ defineProps()
+ `).props,
+ ).toStrictEqual({
+ a: ['Function'],
+ b: ['Boolean'],
+ c: ['String'],
+ foo: ['Number'],
+ })
+ })
+
+ test('reference class', () => {
+ expect(
+ resolve(`
+ class Foo {}
+ defineProps<{ foo: Foo }>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Object'],
+ })
+ })
+
+ test('function type', () => {
+ expect(
+ resolve(`
+ defineProps<(e: 'foo') => void>()
+ `).calls?.length,
+ ).toBe(1)
+ })
+
+ test('reference function type', () => {
+ expect(
+ resolve(`
+ type Fn = (e: 'foo') => void
+ defineProps()
+ `).calls?.length,
+ ).toBe(1)
+ })
+
+ test('intersection type', () => {
+ expect(
+ resolve(`
+ type Foo = { foo: number }
+ type Bar = { bar: string }
+ type Baz = { bar: string | boolean }
+ defineProps<{ self: any } & Foo & Bar & Baz>()
+ `).props,
+ ).toStrictEqual({
+ self: [UNKNOWN_TYPE],
+ foo: ['Number'],
+ // both Bar & Baz has 'bar', but Baz['bar] is wider so it should be
+ // preferred
+ bar: ['String', 'Boolean'],
+ })
+ })
+
+ test('intersection type with ignore', () => {
+ expect(
+ resolve(`
+ type Foo = { foo: number }
+ type Bar = { bar: string }
+ defineProps()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Number'],
+ })
+ })
+
+ // #7553
+ test('union type', () => {
+ expect(
+ resolve(`
+ interface CommonProps {
+ size?: 'xl' | 'l' | 'm' | 's' | 'xs'
+ }
+
+ type ConditionalProps =
+ | {
+ color: 'normal' | 'primary' | 'secondary'
+ appearance: 'normal' | 'outline' | 'text'
+ }
+ | {
+ color: number
+ appearance: 'outline'
+ note: string
+ }
+
+ defineProps()
+ `).props,
+ ).toStrictEqual({
+ size: ['String'],
+ color: ['String', 'Number'],
+ appearance: ['String'],
+ note: ['String'],
+ })
+ })
+
+ test('template string type', () => {
+ expect(
+ resolve(`
+ type T = 'foo' | 'bar'
+ type S = 'x' | 'y'
+ defineProps<{
+ [\`_\${T}_\${S}_\`]: string
+ }>()
+ `).props,
+ ).toStrictEqual({
+ _foo_x_: ['String'],
+ _foo_y_: ['String'],
+ _bar_x_: ['String'],
+ _bar_y_: ['String'],
+ })
+ })
+
+ test('mapped types w/ string manipulation', () => {
+ expect(
+ resolve(`
+ type T = 'foo' | 'bar'
+ defineProps<{ [K in T]: string | number } & {
+ [K in 'optional']?: boolean
+ } & {
+ [K in Capitalize]: string
+ } & {
+ [K in Uppercase>]: string
+ } & {
+ [K in \`x\${T}\`]: string
+ }>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String', 'Number'],
+ bar: ['String', 'Number'],
+ Foo: ['String'],
+ Bar: ['String'],
+ FOO: ['String'],
+ xfoo: ['String'],
+ xbar: ['String'],
+ optional: ['Boolean'],
+ })
+ })
+
+ test('utility type: Partial', () => {
+ expect(
+ resolve(`
+ type T = { foo: number, bar: string }
+ defineProps>()
+ `).raw.props,
+ ).toMatchObject({
+ foo: {
+ optional: true,
+ },
+ bar: {
+ optional: true,
+ },
+ })
+ })
+
+ test('utility type: Required', () => {
+ expect(
+ resolve(`
+ type T = { foo?: number, bar?: string }
+ defineProps>()
+ `).raw.props,
+ ).toMatchObject({
+ foo: {
+ optional: false,
+ },
+ bar: {
+ optional: false,
+ },
+ })
+ })
+
+ test('utility type: Pick', () => {
+ expect(
+ resolve(`
+ type T = { foo: number, bar: string, baz: boolean }
+ type K = 'foo' | 'bar'
+ defineProps>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Number'],
+ bar: ['String'],
+ })
+ })
+
+ test('utility type: Omit', () => {
+ expect(
+ resolve(`
+ type T = { foo: number, bar: string, baz: boolean }
+ type K = 'foo' | 'bar'
+ defineProps>()
+ `).props,
+ ).toStrictEqual({
+ baz: ['Boolean'],
+ })
+ })
+
+ test('utility type: mapped type with Omit and Pick', () => {
+ expect(
+ resolve(`
+ type Optional = Omit & Partial>
+ interface Test {
+ foo: string;
+ bar?: string;
+ }
+ type OptionalTest = Optional
+ defineProps()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String'],
+ bar: ['String'],
+ })
+ })
+
+ test('utility type: ReadonlyArray', () => {
+ expect(
+ resolve(`
+ defineProps<{ foo: ReadonlyArray }>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Array'],
+ })
+ })
+
+ test('utility type: ReadonlyMap & Readonly Set', () => {
+ expect(
+ resolve(`
+ defineProps<{ foo: ReadonlyMap, bar: ReadonlySet }>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Map'],
+ bar: ['Set'],
+ })
+ })
+
+ test('indexed access type (literal)', () => {
+ expect(
+ resolve(`
+ type T = { bar: number }
+ type S = { nested: { foo: T['bar'] }}
+ defineProps()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Number'],
+ })
+ })
+
+ test('indexed access type (advanced)', () => {
+ expect(
+ resolve(`
+ type K = 'foo' | 'bar'
+ type T = { foo: string, bar: number }
+ type S = { foo: { foo: T[string] }, bar: { bar: string } }
+ defineProps()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String', 'Number'],
+ bar: ['String'],
+ })
+ })
+
+ test('indexed access type (number)', () => {
+ expect(
+ resolve(`
+ type A = (string | number)[]
+ type AA = Array
+ type T = [1, 'foo']
+ type TT = [foo: 1, bar: 'foo']
+ defineProps<{ foo: A[number], bar: AA[number], tuple: T[number], namedTuple: TT[number] }>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String', 'Number'],
+ bar: ['String'],
+ tuple: ['Number', 'String'],
+ namedTuple: ['Number', 'String'],
+ })
+ })
+
+ test('namespace', () => {
+ expect(
+ resolve(`
+ type X = string
+ namespace Foo {
+ type X = number
+ export namespace Bar {
+ export type A = {
+ foo: X
+ }
+ }
+ }
+ defineProps()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Number'],
+ })
+ })
+
+ test('interface merging', () => {
+ expect(
+ resolve(`
+ interface Foo {
+ a: string
+ }
+ interface Foo {
+ b: number
+ }
+ defineProps<{
+ foo: Foo['a'],
+ bar: Foo['b']
+ }>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String'],
+ bar: ['Number'],
+ })
+ })
+
+ test('namespace merging', () => {
+ expect(
+ resolve(`
+ namespace Foo {
+ export type A = string
+ }
+ namespace Foo {
+ export type B = number
+ }
+ defineProps<{
+ foo: Foo.A,
+ bar: Foo.B
+ }>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String'],
+ bar: ['Number'],
+ })
+ })
+
+ test('namespace merging with other types', () => {
+ expect(
+ resolve(`
+ namespace Foo {
+ export type A = string
+ }
+ interface Foo {
+ b: number
+ }
+ defineProps<{
+ foo: Foo.A,
+ bar: Foo['b']
+ }>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String'],
+ bar: ['Number'],
+ })
+ })
+
+ test('enum merging', () => {
+ expect(
+ resolve(`
+ enum Foo {
+ A = 1
+ }
+ enum Foo {
+ B = 'hi'
+ }
+ defineProps<{
+ foo: Foo
+ }>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Number', 'String'],
+ })
+ })
+
+ test('typeof', () => {
+ expect(
+ resolve(`
+ declare const a: string
+ defineProps<{ foo: typeof a }>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String'],
+ })
+ })
+
+ test('readonly', () => {
+ expect(
+ resolve(`
+ defineProps<{ foo: readonly unknown[] }>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['Array'],
+ })
+ })
+
+ test('keyof', () => {
+ const files = {
+ '/foo.ts': `export type IMP = { ${1}: 1 };`,
+ }
+
+ const { props } = resolve(
+ `
+ import { IMP } from './foo'
+ interface Foo { foo: 1, ${1}: 1 }
+ type Bar = { bar: 1 }
+ declare const obj: Bar
+ declare const set: Set
+ declare const arr: Array
+
+ defineProps<{
+ imp: keyof IMP,
+ foo: keyof Foo,
+ bar: keyof Bar,
+ obj: keyof typeof obj,
+ set: keyof typeof set,
+ arr: keyof typeof arr
+ }>()
+ `,
+ files,
+ )
+
+ expect(props).toStrictEqual({
+ imp: ['Number'],
+ foo: ['String', 'Number'],
+ bar: ['String'],
+ obj: ['String'],
+ set: ['String'],
+ arr: ['String', 'Number'],
+ })
+ })
+
+ test('keyof: index signature', () => {
+ const { props } = resolve(
+ `
+ declare const num: number;
+ interface Foo {
+ [key: symbol]: 1
+ [key: string]: 1
+ [key: typeof num]: 1,
+ }
+
+ type Test = T
+ type Bar = {
+ [key: string]: 1
+ [key: Test]: 1
+ }
+
+ defineProps<{
+ foo: keyof Foo
+ bar: keyof Bar
+ }>()
+ `,
+ )
+
+ expect(props).toStrictEqual({
+ foo: ['Symbol', 'String', 'Number'],
+ bar: [UNKNOWN_TYPE],
+ })
+ })
+
+ // #11129
+ test('keyof: intersection type', () => {
+ const { props } = resolve(`
+ type A = { name: string }
+ type B = A & { [key: number]: string }
+ defineProps<{
+ foo: keyof B
+ }>()`)
+ expect(props).toStrictEqual({
+ foo: ['String', 'Number'],
+ })
+ })
+
+ test('keyof: union type', () => {
+ const { props } = resolve(`
+ type A = { name: string }
+ type B = A | { [key: number]: string }
+ defineProps<{
+ foo: keyof B
+ }>()`)
+ expect(props).toStrictEqual({
+ foo: ['String', 'Number'],
+ })
+ })
+
+ test('keyof: utility type', () => {
+ const { props } = resolve(
+ `
+ type Foo = Record
+ type Bar = { [key: string]: any }
+ type AnyRecord = Record
+ type Baz = { a: 1, ${1}: 2, b: 3}
+
+ defineProps<{
+ record: keyof Foo,
+ anyRecord: keyof AnyRecord
+ partial: keyof Partial,
+ required: keyof Required,
+ readonly: keyof Readonly,
+ pick: keyof Pick
+ extract: keyof Extract
+ }>()
+ `,
+ )
+
+ expect(props).toStrictEqual({
+ record: ['Symbol', 'String'],
+ anyRecord: ['String', 'Number', 'Symbol'],
+ partial: ['String'],
+ required: ['String'],
+ readonly: ['String'],
+ pick: ['String', 'Number'],
+ extract: ['String', 'Number'],
+ })
+ })
+
+ test('keyof: fallback to Unknown', () => {
+ const { props } = resolve(
+ `
+ interface Barr {}
+ interface Bar extends Barr {}
+ type Foo = keyof Bar
+ defineProps<{ foo: Foo }>()
+ `,
+ )
+
+ expect(props).toStrictEqual({
+ foo: [UNKNOWN_TYPE],
+ })
+ })
+
+ test('keyof: nested object with number', () => {
+ const { props } = resolve(
+ `
+ interface Type {
+ deep: {
+ 1: any
+ }
+ }
+
+ defineProps<{
+ route: keyof Type['deep']
+ }>()`,
+ )
+
+ expect(props).toStrictEqual({
+ route: ['Number'],
+ })
+ })
+
+ test('keyof: nested object with string', () => {
+ const { props } = resolve(
+ `
+ interface Type {
+ deep: {
+ foo: any
+ }
+ }
+
+ defineProps<{
+ route: keyof Type['deep']
+ }>()`,
+ )
+
+ expect(props).toStrictEqual({
+ route: ['String'],
+ })
+ })
+
+ test('keyof: nested object with intermediate', () => {
+ const { props } = resolve(
+ `
+ interface Type {
+ deep: {
+ foo: any
+ }
+ }
+
+ type Foo = Type['deep']
+
+ defineProps<{
+ route: keyof Foo
+ }>()`,
+ )
+
+ expect(props).toStrictEqual({
+ route: ['String'],
+ })
+ })
+
+ test('ExtractPropTypes (element-plus)', () => {
+ const { props, raw } = resolve(
+ `
+ import { ExtractPropTypes } from 'vue'
+ declare const props: {
+ foo: StringConstructor,
+ bar: {
+ type: import('foo').EpPropFinalized,
+ required: true
+ }
+ }
+ type Props = ExtractPropTypes
+ defineProps()
+ `,
+ )
+ expect(props).toStrictEqual({
+ foo: ['String'],
+ bar: ['Boolean'],
+ })
+ expect(raw.props.bar.optional).toBe(false)
+ })
+
+ test('ExtractPropTypes (antd)', () => {
+ const { props } = resolve(
+ `
+ declare const props: () => {
+ foo: StringConstructor,
+ bar: { type: PropType }
+ }
+ type Props = Partial>>
+ defineProps()
+ `,
+ )
+ expect(props).toStrictEqual({
+ foo: ['String'],
+ bar: ['Boolean'],
+ })
+ })
+
+ // #11266
+ test('correctly parse type annotation for declared function', () => {
+ const { props } = resolve(`
+ import { ExtractPropTypes } from 'vue'
+ interface UploadFile {
+ xhr?: T
+ }
+ declare function uploadProps(): {
+ fileList: {
+ type: PropType[]>
+ default: UploadFile[]
+ }
+ }
+ type UploadProps = ExtractPropTypes>
+ defineProps()`)
+ expect(props).toStrictEqual({
+ fileList: ['Array'],
+ })
+ })
+
+ describe('generics', () => {
+ test('generic with type literal', () => {
+ expect(
+ resolve(`
+ type Props = T
+ defineProps>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String'],
+ })
+ })
+
+ test('generic used in intersection', () => {
+ expect(
+ resolve(`
+ type Foo = { foo: string; }
+ type Bar = { bar: number; }
+ type Props = T & U & { baz: boolean }
+ defineProps>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String'],
+ bar: ['Number'],
+ baz: ['Boolean'],
+ })
+ })
+
+ test('generic type /w generic type alias', () => {
+ expect(
+ resolve(`
+ type Aliased = Readonly>
+ type Props = Aliased
+ type Foo = { foo: string; }
+ defineProps>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String'],
+ })
+ })
+
+ test('generic type /w aliased type literal', () => {
+ expect(
+ resolve(`
+ type Aliased = { foo: T }
+ defineProps>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String'],
+ })
+ })
+
+ test('generic type /w interface', () => {
+ expect(
+ resolve(`
+ interface Props {
+ foo: T
+ }
+ type Foo = string
+ defineProps>()
+ `).props,
+ ).toStrictEqual({
+ foo: ['String'],
+ })
+ })
+
+ test('generic from external-file', () => {
+ const files = {
+ '/foo.ts': 'export type P = { foo: T }',
+ }
+ const { props } = resolve(
+ `
+ import { P } from './foo'
+ defineProps>()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ foo: ['String'],
+ })
+ })
+ })
+
+ describe('external type imports', () => {
+ test('relative ts', () => {
+ const files = {
+ '/foo.ts': 'export type P = { foo: number }',
+ '/bar.d.ts':
+ 'type X = { bar: string }; export { X as Y };' +
+ // verify that we can parse syntax that is only valid in d.ts
+ 'export const baz: boolean',
+ }
+ const { props, deps } = resolve(
+ `
+ import { P } from './foo'
+ import { Y as PP } from './bar'
+ defineProps
()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ foo: ['Number'],
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(Object.keys(files))
+ })
+
+ // #10635
+ test('relative tsx', () => {
+ const files = {
+ '/foo.tsx': 'export type P = { foo: number }',
+ '/bar/index.tsx': 'export type PP = { bar: string }',
+ }
+ const { props, deps } = resolve(
+ `
+ import { P } from './foo'
+ import { PP } from './bar'
+ defineProps
()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ foo: ['Number'],
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(Object.keys(files))
+ })
+
+ test.runIf(process.platform === 'win32')('relative ts on Windows', () => {
+ const files = {
+ 'C:\\Test\\FolderA\\foo.ts': 'export type P = { foo: number }',
+ 'C:\\Test\\FolderA\\bar.d.ts':
+ 'type X = { bar: string }; export { X as Y };' +
+ // verify that we can parse syntax that is only valid in d.ts
+ 'export const baz: boolean',
+ 'C:\\Test\\FolderB\\buz.ts': 'export type Z = { buz: string }',
+ }
+ const { props, deps } = resolve(
+ `
+ import { P } from './foo'
+ import { Y as PP } from './bar'
+ import { Z as PPP } from '../FolderB/buz'
+ defineProps
()
+ `,
+ files,
+ {},
+ 'C:\\Test\\FolderA\\Test.vue',
+ )
+ expect(props).toStrictEqual({
+ foo: ['Number'],
+ bar: ['String'],
+ buz: ['String'],
+ })
+ expect(deps && [...deps].map(normalize)).toStrictEqual(
+ Object.keys(files).map(normalize),
+ )
+ })
+
+ // #8244
+ test('utility type in external file', () => {
+ const files = {
+ '/foo.ts': 'type A = { n?: number }; export type B = Required',
+ }
+ const { props } = resolve(
+ `
+ import { B } from './foo'
+ defineProps()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ n: ['Number'],
+ })
+ })
+
+ test('relative vue', () => {
+ const files = {
+ '/foo.vue':
+ '',
+ '/bar.vue':
+ '',
+ }
+ const { props, deps } = resolve(
+ `
+ import { P } from './foo.vue'
+ import { P as PP } from './bar.vue'
+ defineProps ()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ foo: ['Number'],
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(Object.keys(files))
+ })
+
+ test('relative (chained)', () => {
+ const files = {
+ '/foo.ts': `import type { P as PP } from './nested/bar.vue'
+ export type P = { foo: number } & PP`,
+ '/nested/bar.vue':
+ '',
+ }
+ const { props, deps } = resolve(
+ `
+ import { P } from './foo'
+ defineProps
()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ foo: ['Number'],
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(Object.keys(files))
+ })
+
+ test('relative (chained, re-export)', () => {
+ const files = {
+ '/foo.ts': `export { P as PP } from './bar'`,
+ '/bar.ts': 'export type P = { bar: string }',
+ }
+ const { props, deps } = resolve(
+ `
+ import { PP as P } from './foo'
+ defineProps
()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(Object.keys(files))
+ })
+
+ test('relative (chained, export *)', () => {
+ const files = {
+ '/foo.ts': `export * from './bar'`,
+ '/bar.ts': 'export type P = { bar: string }',
+ }
+ const { props, deps } = resolve(
+ `
+ import { P } from './foo'
+ defineProps
()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(Object.keys(files))
+ })
+
+ test('relative (default export)', () => {
+ const files = {
+ '/foo.ts': `export default interface P { foo: string }`,
+ '/bar.ts': `type X = { bar: string }; export default X`,
+ }
+ const { props, deps } = resolve(
+ `
+ import P from './foo'
+ import X from './bar'
+ defineProps
()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ foo: ['String'],
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(Object.keys(files))
+ })
+
+ test('relative (default re-export)', () => {
+ const files = {
+ '/bar.ts': `export { default } from './foo'`,
+ '/foo.ts': `export default interface P { foo: string }; export interface PP { bar: number }`,
+ '/baz.ts': `export { PP as default } from './foo'`,
+ }
+ const { props, deps } = resolve(
+ `
+ import P from './bar'
+ import PP from './baz'
+ defineProps
()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ foo: ['String'],
+ bar: ['Number'],
+ })
+ expect(deps && [...deps]).toStrictEqual(Object.keys(files))
+ })
+
+ test('relative (re-export /w same source type name)', () => {
+ const files = {
+ '/foo.ts': `export default interface P { foo: string }`,
+ '/bar.ts': `export default interface PP { bar: number }`,
+ '/baz.ts': `export { default as X } from './foo'; export { default as XX } from './bar'; `,
+ }
+ const { props, deps } = resolve(
+ `import { X, XX } from './baz'
+ defineProps()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ foo: ['String'],
+ bar: ['Number'],
+ })
+ expect(deps && [...deps]).toStrictEqual(['/baz.ts', '/foo.ts', '/bar.ts'])
+ })
+
+ test('relative (dynamic import)', () => {
+ const files = {
+ '/foo.ts': `export type P = { foo: string, bar: import('./bar').N }`,
+ '/bar.ts': 'export type N = number',
+ }
+ const { props, deps } = resolve(
+ `
+ defineProps()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ foo: ['String'],
+ bar: ['Number'],
+ })
+ expect(deps && [...deps]).toStrictEqual(Object.keys(files))
+ })
+
+ // #8339
+ test('relative, .js import', () => {
+ const files = {
+ '/foo.d.ts':
+ 'import { PP } from "./bar.js"; export type P = { foo: PP }',
+ '/bar.d.ts': 'export type PP = "foo" | "bar"',
+ }
+ const { props, deps } = resolve(
+ `
+ import { P } from './foo'
+ defineProps()
+ `,
+ files,
+ )
+ expect(props).toStrictEqual({
+ foo: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(Object.keys(files))
+ })
+
+ test('ts module resolve', () => {
+ const files = {
+ '/node_modules/foo/package.json': JSON.stringify({
+ types: 'index.d.ts',
+ }),
+ '/node_modules/foo/index.d.ts': 'export type P = { foo: number }',
+ '/tsconfig.json': JSON.stringify({
+ compilerOptions: {
+ paths: {
+ bar: ['./pp.ts'],
+ },
+ },
+ }),
+ '/pp.ts': 'export type PP = { bar: string }',
+ }
+
+ const { props, deps } = resolve(
+ `
+ import { P } from 'foo'
+ import { PP } from 'bar'
+ defineProps
()
+ `,
+ files,
+ )
+
+ expect(props).toStrictEqual({
+ foo: ['Number'],
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual([
+ '/node_modules/foo/index.d.ts',
+ '/pp.ts',
+ ])
+ })
+
+ test('ts module resolve w/ project reference & extends', () => {
+ const files = {
+ '/tsconfig.json': JSON.stringify({
+ references: [
+ {
+ path: './tsconfig.app.json',
+ },
+ ],
+ }),
+ '/tsconfig.app.json': JSON.stringify({
+ include: ['**/*.ts', '**/*.vue'],
+ extends: './tsconfig.web.json',
+ }),
+ '/tsconfig.web.json': JSON.stringify({
+ compilerOptions: {
+ composite: true,
+ paths: {
+ bar: ['./user.ts'],
+ },
+ },
+ }),
+ '/user.ts': 'export type User = { bar: string }',
+ }
+
+ const { props, deps } = resolve(
+ `
+ import { User } from 'bar'
+ defineProps()
+ `,
+ files,
+ )
+
+ expect(props).toStrictEqual({
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(['/user.ts'])
+ })
+
+ test('ts module resolve w/ project reference folder', () => {
+ const files = {
+ '/tsconfig.json': JSON.stringify({
+ references: [
+ {
+ path: './web',
+ },
+ {
+ path: './empty',
+ },
+ {
+ path: './noexists-should-ignore',
+ },
+ ],
+ }),
+ '/web/tsconfig.json': JSON.stringify({
+ include: ['../**/*.ts', '../**/*.vue'],
+ compilerOptions: {
+ composite: true,
+ paths: {
+ bar: ['../user.ts'],
+ },
+ },
+ }),
+ // tsconfig with no include / paths defined, should match nothing
+ '/empty/tsconfig.json': JSON.stringify({
+ compilerOptions: {
+ composite: true,
+ },
+ }),
+ '/user.ts': 'export type User = { bar: string }',
+ }
+
+ const { props, deps } = resolve(
+ `
+ import { User } from 'bar'
+ defineProps()
+ `,
+ files,
+ )
+
+ expect(props).toStrictEqual({
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(['/user.ts'])
+ })
+
+ // #11382
+ test('ts module resolve circular project reference', () => {
+ const files = {
+ '/tsconfig.json': JSON.stringify({
+ exclude: ['**/*.ts', '**/*.vue'],
+ references: [
+ {
+ path: './tsconfig.web.json',
+ },
+ ],
+ }),
+ '/tsconfig.web.json': JSON.stringify({
+ include: ['**/*.ts', '**/*.vue'],
+ compilerOptions: {
+ composite: true,
+ paths: {
+ user: ['./user.ts'],
+ },
+ },
+ references: [
+ {
+ // circular reference
+ path: './tsconfig.json',
+ },
+ ],
+ }),
+ '/user.ts': 'export type User = { bar: string }',
+ }
+
+ const { props, deps } = resolve(
+ `
+ import { User } from 'user'
+ defineProps()
+ `,
+ files,
+ )
+
+ expect(props).toStrictEqual({
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(['/user.ts'])
+ })
+
+ test('ts module resolve w/ path aliased vue file', () => {
+ const files = {
+ '/tsconfig.json': JSON.stringify({
+ compilerOptions: {
+ include: ['**/*.ts', '**/*.vue'],
+ paths: {
+ '@/*': ['./src/*'],
+ },
+ },
+ }),
+ '/src/Foo.vue':
+ '',
+ }
+
+ const { props, deps } = resolve(
+ `
+ import { P } from '@/Foo.vue'
+ defineProps()
+ `,
+ files,
+ )
+
+ expect(props).toStrictEqual({
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(['/src/Foo.vue'])
+ })
+
+ test('global types', () => {
+ const files = {
+ // ambient
+ '/app.d.ts':
+ 'declare namespace App { interface User { name: string } }',
+ // module - should only respect the declare global block
+ '/global.d.ts': `
+ declare type PP = { bar: number }
+ declare global {
+ type PP = { bar: string }
+ }
+ export {}
+ `,
+ }
+
+ const { props, deps } = resolve(`defineProps()`, files, {
+ globalTypeFiles: Object.keys(files),
+ })
+
+ expect(props).toStrictEqual({
+ name: ['String'],
+ bar: ['String'],
+ })
+ expect(deps && [...deps]).toStrictEqual(Object.keys(files))
+ })
+
+ test('global types with ambient references', () => {
+ const files = {
+ // with references
+ '/backend.d.ts': `
+ declare namespace App.Data {
+ export type AircraftData = {
+ id: string
+ manufacturer: App.Data.Listings.ManufacturerData
+ }
+ }
+ declare namespace App.Data.Listings {
+ export type ManufacturerData = {
+ id: string
+ }
+ }
+ `,
+ }
+
+ const { props } = resolve(`defineProps()`, files, {
+ globalTypeFiles: Object.keys(files),
+ })
+
+ expect(props).toStrictEqual({
+ id: ['String'],
+ manufacturer: ['Object'],
+ })
+ })
+
+ // #9871
+ test('shared generics with different args', () => {
+ const files = {
+ '/foo.ts': `export interface Foo { value: T }`,
+ }
+ const { props } = resolve(
+ `import type { Foo } from './foo'
+ defineProps>()`,
+ files,
+ undefined,
+ `/One.vue`,
+ )
+ expect(props).toStrictEqual({
+ value: ['String'],
+ })
+ const { props: props2 } = resolve(
+ `import type { Foo } from './foo'
+ defineProps>()`,
+ files,
+ undefined,
+ `/Two.vue`,
+ false /* do not invalidate cache */,
+ )
+ expect(props2).toStrictEqual({
+ value: ['Number'],
+ })
+ })
+ })
+
+ describe('errors', () => {
+ test('failed type reference', () => {
+ expect(() => resolve(`defineProps()`)).toThrow(
+ `Unresolvable type reference`,
+ )
+ })
+
+ test('unsupported computed keys', () => {
+ expect(() => resolve(`defineProps<{ [Foo]: string }>()`)).toThrow(
+ `Unsupported computed key in type referenced by a macro`,
+ )
+ })
+
+ test('unsupported index type', () => {
+ expect(() => resolve(`defineProps()`)).toThrow(
+ `Unsupported type when resolving index type`,
+ )
+ })
+
+ test('failed import source resolve', () => {
+ expect(() =>
+ resolve(`import { X } from './foo'; defineProps()`),
+ ).toThrow(`Failed to resolve import source "./foo"`)
+ })
+
+ test('should not error on unresolved type when inferring runtime type', () => {
+ expect(() => resolve(`defineProps<{ foo: T }>()`)).not.toThrow()
+ expect(() => resolve(`defineProps<{ foo: T['bar'] }>()`)).not.toThrow()
+ expect(() =>
+ resolve(`
+ import type P from 'unknown'
+ defineProps<{ foo: P }>()
+ `),
+ ).not.toThrow()
+ })
+
+ test('error against failed extends', () => {
+ expect(() =>
+ resolve(`
+ import type Base from 'unknown'
+ interface Props extends Base {}
+ defineProps()
+ `),
+ ).toThrow(`@vue-ignore`)
+ })
+
+ test('allow ignoring failed extends', () => {
+ let res: any
+
+ expect(
+ () =>
+ (res = resolve(`
+ import type Base from 'unknown'
+ interface Props extends /*@vue-ignore*/ Base {
+ foo: string
+ }
+ defineProps()
+ `)),
+ ).not.toThrow(`@vue-ignore`)
+
+ expect(res.props).toStrictEqual({
+ foo: ['String'],
+ })
+ })
+ })
+
+ describe('template literals', () => {
+ test('mapped types with string type', () => {
+ expect(
+ resolve(`
+ type X = 'a' | 'b'
+ defineProps<{[K in X as \`\${K}_foo\`]: string}>()
+ `).props,
+ ).toStrictEqual({
+ a_foo: ['String'],
+ b_foo: ['String'],
+ })
+ })
+
+ // #10962
+ test('mapped types with generic parameters', () => {
+ const { props } = resolve(`
+ type Breakpoints = 'sm' | 'md' | 'lg'
+ type BreakpointFactory = {
+ [K in Breakpoints as \`\${T}\${Capitalize}\`]: V
+ }
+ type ColsBreakpoints = BreakpointFactory<'cols', number>
+ defineProps()
+ `)
+ expect(props).toStrictEqual({
+ colsSm: ['Number'],
+ colsMd: ['Number'],
+ colsLg: ['Number'],
+ })
+ })
+
+ test('allowArbitraryExtensions', () => {
+ const files = {
+ '/foo.d.vue.ts': 'export type Foo = number;',
+ '/foo.vue': '
',
+ '/bar.d.css.ts': 'export type Bar = string;',
+ '/bar.css': ':root { --color: red; }',
+ }
+
+ const { props } = resolve(
+ `
+ import { Foo } from './foo.vue'
+ import { Bar } from './bar.css'
+ defineProps<{ foo: Foo; bar: Bar }>()
+ `,
+ files,
+ )
+
+ expect(props).toStrictEqual({
+ foo: ['Number'],
+ bar: ['String'],
+ })
+ })
+ })
+})
+
+function resolve(
+ code: string,
+ files: Record = {},
+ options?: Partial,
+ sourceFileName: string = '/Test.vue',
+ invalidateCache = true,
+) {
+ const { descriptor } = parse(``, {
+ filename: sourceFileName,
+ })
+ const ctx = new ScriptCompileContext(descriptor, {
+ id: 'test',
+ fs: {
+ fileExists(file) {
+ return !!(files[file] ?? files[normalize(file)])
+ },
+ readFile(file) {
+ return files[file] ?? files[normalize(file)]
+ },
+ },
+ ...options,
+ })
+
+ if (invalidateCache) {
+ for (const file in files) {
+ invalidateTypeCache(file)
+ }
+ }
+
+ // ctx.userImports is collected when calling compileScript(), but we are
+ // skipping that here, so need to manually register imports
+ ctx.userImports = recordImports(ctx.scriptSetupAst!.body) as any
+
+ let target: any
+ for (const s of ctx.scriptSetupAst!.body) {
+ if (
+ s.type === 'ExpressionStatement' &&
+ s.expression.type === 'CallExpression' &&
+ (s.expression.callee as Identifier).name === 'defineProps'
+ ) {
+ target = s.expression.typeParameters!.params[0]
+ }
+ }
+ const raw = resolveTypeElements(ctx, target)
+ const props: Record = {}
+ for (const key in raw.props) {
+ props[key] = inferRuntimeType(ctx, raw.props[key])
+ }
+ return {
+ props,
+ calls: raw.calls,
+ deps: ctx.deps,
+ raw,
+ }
+}
diff --git a/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts b/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts
deleted file mode 100644
index 140dbec2e6b..00000000000
--- a/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts
+++ /dev/null
@@ -1,211 +0,0 @@
-import { BindingTypes } from '@vue/compiler-core'
-import { SFCScriptCompileOptions } from '../src'
-import { compileSFCScript, assertCode } from './utils'
-
-describe('sfc props transform', () => {
- function compile(src: string, options?: Partial) {
- return compileSFCScript(src, {
- inlineTemplate: true,
- reactivityTransform: true,
- ...options
- })
- }
-
- test('basic usage', () => {
- const { content, bindings } = compile(`
-
- {{ foo }}
- `)
- expect(content).not.toMatch(`const { foo } =`)
- expect(content).toMatch(`console.log(__props.foo)`)
- expect(content).toMatch(`_toDisplayString(__props.foo)`)
- assertCode(content)
- expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS
- })
- })
-
- test('nested scope', () => {
- const { content, bindings } = compile(`
-
- `)
- expect(content).not.toMatch(`const { foo, bar } =`)
- expect(content).toMatch(`console.log(foo)`)
- expect(content).toMatch(`console.log(__props.bar)`)
- assertCode(content)
- expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS,
- bar: BindingTypes.PROPS,
- test: BindingTypes.SETUP_CONST
- })
- })
-
- test('default values w/ runtime declaration', () => {
- const { content } = compile(`
-
- `)
- // literals can be used as-is, non-literals are always returned from a
- // function
- expect(content).toMatch(`props: _mergeDefaults(['foo', 'bar'], {
- foo: 1,
- bar: () => {}
-})`)
- assertCode(content)
- })
-
- test('default values w/ type declaration', () => {
- const { content } = compile(`
-
- `)
- // literals can be used as-is, non-literals are always returned from a
- // function
- expect(content).toMatch(`props: {
- foo: { type: Number, required: false, default: 1 },
- bar: { type: Object, required: false, default: () => {} }
- }`)
- assertCode(content)
- })
-
- test('default values w/ type declaration, prod mode', () => {
- const { content } = compile(
- `
-
- `,
- { isProd: true }
- )
- // literals can be used as-is, non-literals are always returned from a
- // function
- expect(content).toMatch(`props: {
- foo: { default: 1 },
- bar: { default: () => {} },
- baz: null,
- boola: { type: Boolean },
- boolb: { type: [Boolean, Number] },
- func: { type: Function, default: () => () => {} }
- }`)
- assertCode(content)
- })
-
- test('aliasing', () => {
- const { content, bindings } = compile(`
-
- {{ foo + bar }}
- `)
- expect(content).not.toMatch(`const { foo: bar } =`)
- expect(content).toMatch(`let x = foo`) // should not process
- expect(content).toMatch(`let y = __props.foo`)
- // should convert bar to __props.foo in template expressions
- expect(content).toMatch(`_toDisplayString(__props.foo + __props.foo)`)
- assertCode(content)
- expect(bindings).toStrictEqual({
- x: BindingTypes.SETUP_LET,
- y: BindingTypes.SETUP_LET,
- foo: BindingTypes.PROPS,
- bar: BindingTypes.PROPS_ALIASED,
- __propsAliases: {
- bar: 'foo'
- }
- })
- })
-
- test('rest spread', () => {
- const { content, bindings } = compile(`
-
- `)
- expect(content).toMatch(
- `const rest = _createPropsRestProxy(__props, ["foo","bar"])`
- )
- assertCode(content)
- expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS,
- bar: BindingTypes.PROPS,
- baz: BindingTypes.PROPS,
- rest: BindingTypes.SETUP_CONST
- })
- })
-
- test('$$() escape', () => {
- const { content } = compile(`
-
- `)
- expect(content).toMatch(`const __props_foo = _toRef(__props, 'foo')`)
- expect(content).toMatch(`const __props_bar = _toRef(__props, 'bar')`)
- expect(content).toMatch(`console.log((__props_foo))`)
- expect(content).toMatch(`console.log((__props_bar))`)
- expect(content).toMatch(`({ foo: __props_foo, baz: __props_bar })`)
- assertCode(content)
- })
-
- describe('errors', () => {
- test('should error on deep destructure', () => {
- expect(() =>
- compile(
- ``
- )
- ).toThrow(`destructure does not support nested patterns`)
-
- expect(() =>
- compile(
- ``
- )
- ).toThrow(`destructure does not support nested patterns`)
- })
-
- test('should error on computed key', () => {
- expect(() =>
- compile(
- ``
- )
- ).toThrow(`destructure cannot use computed key`)
- })
-
- test('should error when used with withDefaults', () => {
- expect(() =>
- compile(
- ``
- )
- ).toThrow(`withDefaults() is unnecessary when using destructure`)
- })
-
- test('should error if destructure reference local vars', () => {
- expect(() =>
- compile(
- ``
- )
- ).toThrow(`cannot reference locally declared variables`)
- })
- })
-})
diff --git a/packages/compiler-sfc/__tests__/compileScriptRefTransform.spec.ts b/packages/compiler-sfc/__tests__/compileScriptRefTransform.spec.ts
deleted file mode 100644
index 88d62f2b478..00000000000
--- a/packages/compiler-sfc/__tests__/compileScriptRefTransform.spec.ts
+++ /dev/null
@@ -1,189 +0,0 @@
-import { BindingTypes } from '@vue/compiler-core'
-import { compileSFCScript as compile, assertCode } from './utils'
-
-// this file only tests integration with SFC - main test case for the ref
-// transform can be found in /packages/reactivity-transform/__tests__
-describe('sfc ref transform', () => {
- function compileWithReactivityTransform(src: string) {
- return compile(src, { reactivityTransform: true })
- }
-
- test('$ unwrapping', () => {
- const { content, bindings } = compileWithReactivityTransform(``)
- expect(content).not.toMatch(`$(ref())`)
- expect(content).not.toMatch(`$(ref(1))`)
- expect(content).not.toMatch(`$(shallowRef({`)
- expect(content).toMatch(`let foo = (ref())`)
- expect(content).toMatch(`let a = (ref(1))`)
- expect(content).toMatch(`
- let b = (shallowRef({
- count: 0
- }))
- `)
- // normal declarations left untouched
- expect(content).toMatch(`let c = () => {}`)
- expect(content).toMatch(`let d`)
- expect(content).toMatch(`return { foo, a, b, c, d, ref, shallowRef }`)
- assertCode(content)
- expect(bindings).toStrictEqual({
- foo: BindingTypes.SETUP_REF,
- a: BindingTypes.SETUP_REF,
- b: BindingTypes.SETUP_REF,
- c: BindingTypes.SETUP_LET,
- d: BindingTypes.SETUP_LET,
- ref: BindingTypes.SETUP_CONST,
- shallowRef: BindingTypes.SETUP_CONST
- })
- })
-
- test('$ref & $shallowRef declarations', () => {
- const { content, bindings } = compileWithReactivityTransform(``)
- expect(content).toMatch(
- `import { ref as _ref, shallowRef as _shallowRef } from 'vue'`
- )
- expect(content).not.toMatch(`$ref()`)
- expect(content).not.toMatch(`$ref(1)`)
- expect(content).not.toMatch(`$shallowRef({`)
- expect(content).toMatch(`let foo = _ref()`)
- expect(content).toMatch(`let a = _ref(1)`)
- expect(content).toMatch(`
- let b = _shallowRef({
- count: 0
- })
- `)
- // normal declarations left untouched
- expect(content).toMatch(`let c = () => {}`)
- expect(content).toMatch(`let d`)
- assertCode(content)
- expect(bindings).toStrictEqual({
- foo: BindingTypes.SETUP_REF,
- a: BindingTypes.SETUP_REF,
- b: BindingTypes.SETUP_REF,
- c: BindingTypes.SETUP_LET,
- d: BindingTypes.SETUP_LET
- })
- })
-
- test('usage in normal `)
- expect(content).not.toMatch(`$ref(0)`)
- expect(content).toMatch(`import { ref as _ref } from 'vue'`)
- expect(content).toMatch(`let count = _ref(0)`)
- expect(content).toMatch(`count.value++`)
- expect(content).toMatch(`return ({ count })`)
- assertCode(content)
- })
-
- test('usage /w typescript', () => {
- const { content } = compileWithReactivityTransform(`
-
- `)
- expect(content).toMatch(`import { ref as _ref`)
- expect(content).toMatch(`let msg = _ref('foo')`)
- expect(content).toMatch(`let bar = _ref ('bar')`)
- assertCode(content)
- })
-
- test('usage with normal
- `)
- // should dedupe helper imports
- expect(content).toMatch(`import { ref as _ref } from 'vue'`)
-
- expect(content).toMatch(`let a = _ref(0)`)
- expect(content).toMatch(`let b = _ref(0)`)
-
- // root level ref binding declared in
-
- `)
- expect(content).toMatch(`console.log(data.value)`)
- assertCode(content)
- })
-
- describe('errors', () => {
- test('defineProps/Emit() referencing ref declarations', () => {
- expect(() =>
- compile(
- ``,
- { reactivityTransform: true }
- )
- ).toThrow(`cannot reference locally declared variables`)
-
- expect(() =>
- compile(
- ``,
- { reactivityTransform: true }
- )
- ).toThrow(`cannot reference locally declared variables`)
- })
- })
-})
diff --git a/packages/compiler-sfc/__tests__/compileStyle.spec.ts b/packages/compiler-sfc/__tests__/compileStyle.spec.ts
index a343fe6b6fa..9b7e7c53710 100644
--- a/packages/compiler-sfc/__tests__/compileStyle.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileStyle.spec.ts
@@ -1,24 +1,20 @@
-/**
- * @jest-environment node
- */
-
import {
+ type SFCStyleCompileOptions,
compileStyle,
compileStyleAsync,
- SFCStyleCompileOptions
} from '../src/compileStyle'
-import path from 'path'
+import path from 'node:path'
export function compileScoped(
source: string,
- options?: Partial
+ options?: Partial,
): string {
const res = compileStyle({
source,
filename: 'test.css',
id: 'data-v-test',
scoped: true,
- ...options
+ ...options,
})
if (res.errors.length) {
res.errors.forEach(err => {
@@ -32,34 +28,90 @@ export function compileScoped(
describe('SFC scoped CSS', () => {
test('simple selectors', () => {
expect(compileScoped(`h1 { color: red; }`)).toMatch(
- `h1[data-v-test] { color: red;`
+ `h1[data-v-test] { color: red;`,
)
expect(compileScoped(`.foo { color: red; }`)).toMatch(
- `.foo[data-v-test] { color: red;`
+ `.foo[data-v-test] { color: red;`,
)
})
test('descendent selector', () => {
expect(compileScoped(`h1 .foo { color: red; }`)).toMatch(
- `h1 .foo[data-v-test] { color: red;`
+ `h1 .foo[data-v-test] { color: red;`,
+ )
+
+ // #13387
+ expect(
+ compileScoped(`main {
+ width: 100%;
+ > * {
+ max-width: 200px;
+ }
+}`),
+ ).toMatchInlineSnapshot(`
+ "main {
+&[data-v-test] {
+ width: 100%;
+}
+> *[data-v-test] {
+ max-width: 200px;
+}
+}"`)
+ })
+
+ test('nesting selector', () => {
+ expect(compileScoped(`h1 { color: red; .foo { color: red; } }`)).toMatch(
+ `h1 {\n&[data-v-test] { color: red;\n}\n.foo[data-v-test] { color: red;`,
+ )
+ })
+
+ test('nesting selector with atrule and comment', () => {
+ expect(
+ compileScoped(
+ `h1 {
+color: red;
+/*background-color: pink;*/
+@media only screen and (max-width: 800px) {
+ background-color: green;
+ .bar { color: white }
+}
+.foo { color: red; }
+}`,
+ ),
+ ).toMatch(
+ `h1 {
+&[data-v-test] {
+color: red
+/*background-color: pink;*/
+}
+@media only screen and (max-width: 800px) {
+&[data-v-test] {
+ background-color: green
+}
+.bar[data-v-test] { color: white
+}
+}
+.foo[data-v-test] { color: red;
+}
+}`,
)
})
test('multiple selectors', () => {
expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
- `h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`
+ `h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`,
)
})
test('pseudo class', () => {
expect(compileScoped(`.foo:after { color: red; }`)).toMatch(
- `.foo[data-v-test]:after { color: red;`
+ `.foo[data-v-test]:after { color: red;`,
)
})
test('pseudo element', () => {
expect(compileScoped(`::selection { display: none; }`)).toMatch(
- '[data-v-test]::selection {'
+ '[data-v-test]::selection {',
)
})
@@ -89,6 +141,23 @@ describe('SFC scoped CSS', () => {
".baz .qux[data-v-test] .foo .bar { color: red;
}"
`)
+ expect(compileScoped(`:is(.foo :deep(.bar)) { color: red; }`))
+ .toMatchInlineSnapshot(`
+ ":is(.foo[data-v-test] .bar) { color: red;
+ }"
+ `)
+ expect(compileScoped(`:where(.foo :deep(.bar)) { color: red; }`))
+ .toMatchInlineSnapshot(`
+ ":where(.foo[data-v-test] .bar) { color: red;
+ }"
+ `)
+ expect(compileScoped(`:deep(.foo) { color: red; .bar { color: red; } }`))
+ .toMatchInlineSnapshot(`
+ "[data-v-test] .foo { color: red;
+ .bar { color: red;
+ }
+ }"
+ `)
})
test('::v-slotted', () => {
@@ -138,6 +207,66 @@ describe('SFC scoped CSS', () => {
`)
})
+ test(':is() and :where() with multiple selectors', () => {
+ expect(compileScoped(`:is(.foo) { color: red; }`)).toMatchInlineSnapshot(`
+ ":is(.foo[data-v-test]) { color: red;
+ }"
+ `)
+ expect(compileScoped(`:where(.foo, .bar) { color: red; }`))
+ .toMatchInlineSnapshot(`
+ ":where(.foo[data-v-test], .bar[data-v-test]) { color: red;
+ }"
+ `)
+ expect(compileScoped(`:is(.foo, .bar) div { color: red; }`))
+ .toMatchInlineSnapshot(`
+ ":is(.foo, .bar) div[data-v-test] { color: red;
+ }"
+ `)
+ })
+
+ // #10511
+ test(':is() and :where() in compound selectors', () => {
+ expect(
+ compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
+ ).toMatchInlineSnapshot(`
+ ".div[data-v-test] { color: red;
+ }
+ .div[data-v-test]:where(:hover) { color: blue;
+ }"
+ `)
+
+ expect(
+ compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
+ ).toMatchInlineSnapshot(`
+ ".div[data-v-test] { color: red;
+ }
+ .div[data-v-test]:is(:hover) { color: blue;
+ }"
+ `)
+
+ expect(
+ compileScoped(
+ `.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
+ ),
+ ).toMatchInlineSnapshot(`
+ ".div[data-v-test] { color: red;
+ }
+ .div[data-v-test]:where(.foo:hover) { color: blue;
+ }"
+ `)
+
+ expect(
+ compileScoped(
+ `.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
+ ),
+ ).toMatchInlineSnapshot(`
+ ".div[data-v-test] { color: red;
+ }
+ .div[data-v-test]:is(.foo:hover) { color: blue;
+ }"
+ `)
+ })
+
test('media query', () => {
expect(compileScoped(`@media print { .foo { color: red }}`))
.toMatchInlineSnapshot(`
@@ -194,30 +323,30 @@ describe('SFC scoped CSS', () => {
to { opacity: 1; }
}
`,
- { id: 'data-v-test' }
+ { id: 'data-v-test' },
)
expect(style).toContain(
- `.anim[data-v-test] {\n animation: color-test 5s infinite, other 5s;`
+ `.anim[data-v-test] {\n animation: color-test 5s infinite, other 5s;`,
)
expect(style).toContain(
- `.anim-2[data-v-test] {\n animation-name: color-test`
+ `.anim-2[data-v-test] {\n animation-name: color-test`,
)
expect(style).toContain(
- `.anim-3[data-v-test] {\n animation: 5s color-test infinite, 5s other;`
+ `.anim-3[data-v-test] {\n animation: 5s color-test infinite, 5s other;`,
)
expect(style).toContain(`@keyframes color-test {`)
expect(style).toContain(`@-webkit-keyframes color-test {`)
expect(style).toContain(
- `.anim-multiple[data-v-test] {\n animation: color-test 5s infinite,opacity-test 2s;`
+ `.anim-multiple[data-v-test] {\n animation: color-test 5s infinite,opacity-test 2s;`,
)
expect(style).toContain(
- `.anim-multiple-2[data-v-test] {\n animation-name: color-test,opacity-test;`
+ `.anim-multiple-2[data-v-test] {\n animation-name: color-test,opacity-test;`,
)
expect(style).toContain(`@keyframes opacity-test {\nfrom { opacity: 0;`)
expect(style).toContain(
- `@-webkit-keyframes opacity-test {\nfrom { opacity: 0;`
+ `@-webkit-keyframes opacity-test {\nfrom { opacity: 0;`,
)
})
@@ -242,7 +371,7 @@ describe('SFC scoped CSS', () => {
}"
`)
expect(
- `::v-deep usage as a combinator has been deprecated.`
+ `::v-deep usage as a combinator has been deprecated.`,
).toHaveBeenWarned()
})
@@ -253,7 +382,7 @@ describe('SFC scoped CSS', () => {
}"
`)
expect(
- `the >>> and /deep/ combinators have been deprecated.`
+ `the >>> and /deep/ combinators have been deprecated.`,
).toHaveBeenWarned()
})
@@ -264,7 +393,7 @@ describe('SFC scoped CSS', () => {
}"
`)
expect(
- `the >>> and /deep/ combinators have been deprecated.`
+ `the >>> and /deep/ combinators have been deprecated.`,
).toHaveBeenWarned()
})
})
@@ -276,7 +405,7 @@ describe('SFC CSS modules', () => {
source: `.red { color: red }\n.green { color: green }\n:global(.blue) { color: blue }`,
filename: `test.css`,
id: 'test',
- modules: true
+ modules: true,
})
expect(result.modules).toBeDefined()
expect(result.modules!.red).toMatch('_red_')
@@ -293,8 +422,8 @@ describe('SFC CSS modules', () => {
modulesOptions: {
scopeBehaviour: 'global',
generateScopedName: `[name]__[local]__[hash:base64:5]`,
- localsConvention: 'camelCaseOnly'
- }
+ localsConvention: 'camelCaseOnly',
+ },
})
expect(result.modules).toBeDefined()
expect(result.modules!.fooBar).toMatch('__foo-bar__')
@@ -310,11 +439,11 @@ describe('SFC style preprocessors', () => {
`,
filename: path.resolve(__dirname, './fixture/test.scss'),
id: '',
- preprocessLang: 'scss'
+ preprocessLang: 'scss',
})
expect([...res.dependencies]).toStrictEqual([
- path.join(__dirname, './fixture/import.scss')
+ path.join(__dirname, './fixture/import.scss'),
])
})
@@ -325,7 +454,7 @@ describe('SFC style preprocessors', () => {
@mixin square($size) {
width: $size;
height: $size;
- }`
+ }`,
},
source: `
.square {
@@ -334,7 +463,7 @@ describe('SFC style preprocessors', () => {
`,
filename: path.resolve(__dirname, './fixture/test.scss'),
id: '',
- preprocessLang: 'scss'
+ preprocessLang: 'scss',
})
expect(res.errors.length).toBe(0)
@@ -357,14 +486,33 @@ describe('SFC style preprocessors', () => {
width: $size;
height: $size;
}`
- }
+ },
},
source,
filename,
id: '',
- preprocessLang: 'scss'
+ preprocessLang: 'scss',
})
expect(res.errors.length).toBe(0)
})
+
+ test('should mount scope on correct selector when have universal selector', () => {
+ expect(compileScoped(`* { color: red; }`)).toMatchInlineSnapshot(`
+ "[data-v-test] { color: red;
+ }"
+ `)
+ expect(compileScoped('* .foo { color: red; }')).toMatchInlineSnapshot(`
+ ".foo[data-v-test] { color: red;
+ }"
+ `)
+ expect(compileScoped(`*.foo { color: red; }`)).toMatchInlineSnapshot(`
+ ".foo[data-v-test] { color: red;
+ }"
+ `)
+ expect(compileScoped(`.foo * { color: red; }`)).toMatchInlineSnapshot(`
+ ".foo[data-v-test] * { color: red;
+ }"
+ `)
+ })
})
diff --git a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
index af0f66007c7..81cf75a912d 100644
--- a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
@@ -1,13 +1,17 @@
+import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
+import { parse as babelParse } from '@babel/parser'
import {
+ type SFCTemplateCompileOptions,
compileTemplate,
- SFCTemplateCompileOptions
} from '../src/compileTemplate'
-import { parse, SFCTemplateBlock } from '../src/parse'
+import { type SFCTemplateBlock, parse } from '../src/parse'
+import { compileScript } from '../src'
+import { getPositionInCode } from './utils'
function compile(opts: Omit) {
return compileTemplate({
...opts,
- id: ''
+ id: '',
})
}
@@ -22,6 +26,22 @@ test('should work', () => {
expect(result.code).toMatch(`export function render(`)
})
+// #6807
+test('should work with style comment', () => {
+ const source = `
+ {{ render }}
+ `
+
+ const result = compile({ filename: 'example.vue', source })
+ expect(result.errors.length).toBe(0)
+ expect(result.source).toBe(source)
+ expect(result.code).toMatch(`{"width":"300px","height":"100px"}`)
+})
+
test('preprocess pug', () => {
const template = parse(
`
@@ -32,28 +52,55 @@ body
p Cool Pug example!
`,
- { filename: 'example.vue', sourceMap: true }
+ { filename: 'example.vue', sourceMap: true },
+ ).descriptor.template as SFCTemplateBlock
+
+ const result = compile({
+ filename: 'example.vue',
+ source: template.content,
+ preprocessLang: template.lang,
+ })
+
+ expect(result.errors.length).toBe(0)
+})
+
+test('preprocess pug with indents and blank lines', () => {
+ const template = parse(
+ `
+
+ body
+ h1 The next line contains four spaces.
+
+ div.container
+ p The next line is empty.
+ p This is the last line.
+
+`,
+ { filename: 'example.vue', sourceMap: true },
).descriptor.template as SFCTemplateBlock
const result = compile({
filename: 'example.vue',
source: template.content,
- preprocessLang: template.lang
+ preprocessLang: template.lang,
})
expect(result.errors.length).toBe(0)
+ expect(result.source).toBe(
+ 'The next line contains four spaces. This is the last line.
',
+ )
})
test('warn missing preprocessor', () => {
const template = parse(`hi \n`, {
filename: 'example.vue',
- sourceMap: true
+ sourceMap: true,
}).descriptor.template as SFCTemplateBlock
const result = compile({
filename: 'example.vue',
source: template.content,
- preprocessLang: template.lang
+ preprocessLang: template.lang,
})
expect(result.errors.length).toBe(1)
@@ -65,8 +112,8 @@ test('transform asset url options', () => {
const { code: code1 } = compile({
...input,
transformAssetUrls: {
- tags: { foo: ['bar'] }
- }
+ tags: { foo: ['bar'] },
+ },
})
expect(code1).toMatch(`import _imports_0 from 'baz'\n`)
@@ -74,15 +121,15 @@ test('transform asset url options', () => {
const { code: code2 } = compile({
...input,
transformAssetUrls: {
- foo: ['bar']
- }
+ foo: ['bar'],
+ },
})
expect(code2).toMatch(`import _imports_0 from 'baz'\n`)
// false option
const { code: code3 } = compile({
...input,
- transformAssetUrls: false
+ transformAssetUrls: false,
})
expect(code3).not.toMatch(`import _imports_0 from 'baz'\n`)
})
@@ -91,25 +138,223 @@ test('source map', () => {
const template = parse(
`
-
+
`,
- { filename: 'example.vue', sourceMap: true }
- ).descriptor.template as SFCTemplateBlock
+ { filename: 'example.vue', sourceMap: true },
+ ).descriptor.template!
- const result = compile({
+ const { code, map } = compile({
+ filename: 'example.vue',
+ source: template.content,
+ })
+
+ expect(map!.sources).toEqual([`example.vue`])
+ expect(map!.sourcesContent).toEqual([template.content])
+
+ const consumer = new SourceMapConsumer(map as RawSourceMap)
+ expect(
+ consumer.originalPositionFor(getPositionInCode(code, 'foobar')),
+ ).toMatchObject(getPositionInCode(template.content, `foobar`))
+})
+
+test('source map: v-if generated comment should not have original position', () => {
+ const template = parse(
+ `
+
+
+
+ `,
+ { filename: 'example.vue', sourceMap: true },
+ ).descriptor.template!
+
+ const { code, map } = compile({
+ filename: 'example.vue',
+ source: template.content,
+ })
+
+ expect(map!.sources).toEqual([`example.vue`])
+ expect(map!.sourcesContent).toEqual([template.content])
+
+ const consumer = new SourceMapConsumer(map as RawSourceMap)
+ const commentNode = code.match(/_createCommentVNode\("v-if", true\)/)
+ expect(commentNode).not.toBeNull()
+ const commentPosition = getPositionInCode(code, commentNode![0])
+ const originalPosition = consumer.originalPositionFor(commentPosition)
+ // the comment node should not be mapped to the original source
+ expect(originalPosition.column).toBeNull()
+ expect(originalPosition.line).toBeNull()
+ expect(originalPosition.source).toBeNull()
+})
+
+test('should work w/ AST from descriptor', () => {
+ const source = `
+
+
+
+ `
+ const template = parse(source, {
+ filename: 'example.vue',
+ sourceMap: true,
+ }).descriptor.template!
+
+ expect(template.ast!.source).toBe(source)
+
+ const { code, map } = compile({
+ filename: 'example.vue',
+ source: template.content,
+ ast: template.ast,
+ })
+
+ expect(map!.sources).toEqual([`example.vue`])
+ // when reusing AST from SFC parse for template compile,
+ // the source corresponds to the entire SFC
+ expect(map!.sourcesContent).toEqual([source])
+
+ const consumer = new SourceMapConsumer(map as RawSourceMap)
+ expect(
+ consumer.originalPositionFor(getPositionInCode(code, 'foobar')),
+ ).toMatchObject(getPositionInCode(source, `foobar`))
+
+ expect(code).toBe(
+ compile({
+ filename: 'example.vue',
+ source: template.content,
+ }).code,
+ )
+})
+
+test('should work w/ AST from descriptor in SSR mode', () => {
+ const source = `
+
+
+
+ `
+ const template = parse(source, {
+ filename: 'example.vue',
+ sourceMap: true,
+ }).descriptor.template!
+
+ expect(template.ast!.source).toBe(source)
+
+ const { code, map } = compile({
+ filename: 'example.vue',
+ source: '', // make sure it's actually using the AST instead of source
+ ast: template.ast,
+ ssr: true,
+ })
+
+ expect(map!.sources).toEqual([`example.vue`])
+ // when reusing AST from SFC parse for template compile,
+ // the source corresponds to the entire SFC
+ expect(map!.sourcesContent).toEqual([source])
+
+ const consumer = new SourceMapConsumer(map as RawSourceMap)
+ expect(
+ consumer.originalPositionFor(getPositionInCode(code, 'foobar')),
+ ).toMatchObject(getPositionInCode(source, `foobar`))
+
+ expect(code).toBe(
+ compile({
+ filename: 'example.vue',
+ source: template.content,
+ ssr: true,
+ }).code,
+ )
+})
+
+test('should not reuse AST if using custom compiler', () => {
+ const source = `
+
+
+
+ `
+ const template = parse(source, {
+ filename: 'example.vue',
+ sourceMap: true,
+ }).descriptor.template!
+
+ const { code } = compile({
+ filename: 'example.vue',
+ source: template.content,
+ ast: template.ast,
+ compiler: {
+ parse: () => null as any,
+ // @ts-expect-error
+ compile: input => ({ code: input }),
+ },
+ })
+
+ // what we really want to assert is that the `input` received by the custom
+ // compiler is the source string, not the AST.
+ expect(code).toBe(template.content)
+})
+
+test('should force re-parse on already transformed AST', () => {
+ const source = `
+
+
+
+ `
+ const template = parse(source, {
+ filename: 'example.vue',
+ sourceMap: true,
+ }).descriptor.template!
+
+ // force set to empty, if this is reused then it won't generate proper code
+ template.ast!.children = []
+ template.ast!.transformed = true
+
+ const { code } = compile({
+ filename: 'example.vue',
+ source: '',
+ ast: template.ast,
+ })
+
+ expect(code).toBe(
+ compile({
+ filename: 'example.vue',
+ source: template.content,
+ }).code,
+ )
+})
+
+test('should force re-parse with correct compiler in SSR mode', () => {
+ const source = `
+
+
+
+ `
+ const template = parse(source, {
filename: 'example.vue',
- source: template.content
+ sourceMap: true,
+ }).descriptor.template!
+
+ // force set to empty, if this is reused then it won't generate proper code
+ template.ast!.children = []
+ template.ast!.transformed = true
+
+ const { code } = compile({
+ filename: 'example.vue',
+ source: '',
+ ast: template.ast,
+ ssr: true,
})
- expect(result.map).toMatchSnapshot()
+ expect(code).toBe(
+ compile({
+ filename: 'example.vue',
+ source: template.content,
+ ssr: true,
+ }).code,
+ )
})
test('template errors', () => {
const result = compile({
filename: 'example.vue',
- source: `
`
+ source: `
`,
})
expect(result.errors).toMatchSnapshot()
})
@@ -121,20 +366,20 @@ test('preprocessor errors', () => {
div(class='class)
`,
- { filename: 'example.vue', sourceMap: true }
+ { filename: 'example.vue', sourceMap: true },
).descriptor.template as SFCTemplateBlock
const result = compile({
filename: 'example.vue',
source: template.content,
- preprocessLang: template.lang
+ preprocessLang: template.lang,
})
expect(result.errors.length).toBe(1)
const message = result.errors[0].toString()
expect(message).toMatch(`Error: example.vue:3:1`)
expect(message).toMatch(
- `The end of the string reached with no closing bracket ) found.`
+ `The end of the string reached with no closing bracket ) found.`,
)
})
@@ -148,8 +393,122 @@ test('should generate the correct imports expression', () => {
`,
- ssr: true
+ ssr: true,
})
expect(code).toMatch(`_ssrRenderAttr(\"src\", _imports_1)`)
expect(code).toMatch(`_createVNode(\"img\", { src: _imports_1 })`)
})
+
+// #3874
+test('should not hoist srcset URLs in SSR mode', () => {
+ const { code } = compile({
+ filename: 'example.vue',
+ source: `
+
+
+
+
+
+
+
+
+
+
+ `,
+ ssr: true,
+ })
+ expect(code).toMatchSnapshot()
+})
+
+// #6742
+test('dynamic v-on + static v-on should merged', () => {
+ const source = ` `
+
+ const result = compile({ filename: 'example.vue', source })
+
+ expect(result.code).toMatchSnapshot()
+})
+
+// #9853 regression found in Nuxt tests
+// walkIdentifiers can get called multiple times on the same node
+// due to #9729 calling it during SFC template usage check.
+// conditions needed:
+// 1. `
+
+ {{ list.map((t, index) => ({ t: t })) }}
+
+ `
+ const { descriptor } = parse(src)
+ // compileScript triggers importUsageCheck
+ compileScript(descriptor, { id: 'xxx' })
+ const { code } = compileTemplate({
+ id: 'xxx',
+ filename: 'test.vue',
+ ast: descriptor.template!.ast,
+ source: descriptor.template!.content,
+ })
+ expect(code).not.toMatch(`_ctx.t`)
+})
+
+test('prefixing edge case for reused AST ssr mode', () => {
+ const src = `
+
+
+
+
+
+
+ `
+ const { descriptor } = parse(src)
+ // compileScript triggers importUsageCheck
+ compileScript(descriptor, { id: 'xxx' })
+ expect(() =>
+ compileTemplate({
+ id: 'xxx',
+ filename: 'test.vue',
+ ast: descriptor.template!.ast,
+ source: descriptor.template!.content,
+ ssr: true,
+ }),
+ ).not.toThrowError()
+})
+
+// #10852
+test('non-identifier expression in legacy filter syntax', () => {
+ const src = `
+
+
+ Today is
+ {{ new Date() | formatDate }}
+
+
+ `
+
+ const { descriptor } = parse(src)
+ const compilationResult = compileTemplate({
+ id: 'xxx',
+ filename: 'test.vue',
+ ast: descriptor.template!.ast,
+ source: descriptor.template!.content,
+ ssr: false,
+ compilerOptions: {
+ compatConfig: {
+ MODE: 2,
+ },
+ },
+ })
+
+ expect(() => {
+ babelParse(compilationResult.code, { sourceType: 'module' })
+ }).not.toThrow()
+})
diff --git a/packages/compiler-sfc/__tests__/cssVars.spec.ts b/packages/compiler-sfc/__tests__/cssVars.spec.ts
index d9912f44b51..323c9c7a599 100644
--- a/packages/compiler-sfc/__tests__/cssVars.spec.ts
+++ b/packages/compiler-sfc/__tests__/cssVars.spec.ts
@@ -1,5 +1,5 @@
-import { compileStyle } from '../src'
-import { mockId, compileSFCScript, assertCode } from './utils'
+import { compileStyle, parse } from '../src'
+import { assertCode, compileSFCScript, mockId } from './utils'
describe('CSS vars injection', () => {
test('generating correct code for nested paths', () => {
@@ -8,11 +8,11 @@ describe('CSS vars injection', () => {
``
+ }`,
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-color": (_ctx.color),
- "${mockId}-font_size": (_ctx.font.size)
+ "${mockId}-font\\.size": (_ctx.font.size)
})`)
assertCode(content)
})
@@ -32,7 +32,7 @@ describe('CSS vars injection', () => {
div {
font-size: v-bind(size);
}
- `
+ `,
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-size": (_ctx.size)
@@ -57,7 +57,7 @@ describe('CSS vars injection', () => {
font-size: v-bind(size);
border: v-bind(foo);
}
- `
+ `,
)
// should handle:
// 1. local const bindings
@@ -69,7 +69,7 @@ describe('CSS vars injection', () => {
"${mockId}-foo": (__props.foo)
})`)
expect(content).toMatch(
- `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`
+ `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`,
)
assertCode(content)
})
@@ -79,14 +79,22 @@ describe('CSS vars injection', () => {
source: `.foo {
color: v-bind(color);
font-size: v-bind('font.size');
+
+ font-weight: v-bind(_φ);
+ font-size: v-bind(1-字号);
+ font-family: v-bind(フォント);
}`,
filename: 'test.css',
- id: 'data-v-test'
+ id: 'data-v-test',
})
expect(code).toMatchInlineSnapshot(`
".foo {
color: var(--test-color);
- font-size: var(--test-font_size);
+ font-size: var(--test-font\\.size);
+
+ font-weight: var(--test-_φ);
+ font-size: var(--test-1-字号);
+ font-family: var(--test-フォント);
}"
`)
})
@@ -98,7 +106,7 @@ describe('CSS vars injection', () => {
color: v-bind(color);
font-size: v-bind('font.size');
}`,
- { isProd: true }
+ { isProd: true },
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"4003f1a6": (_ctx.color),
@@ -112,7 +120,7 @@ describe('CSS vars injection', () => {
}`,
filename: 'test.css',
id: mockId,
- isProd: true
+ isProd: true,
})
expect(code).toMatchInlineSnapshot(`
".foo {
@@ -127,8 +135,8 @@ describe('CSS vars injection', () => {
assertCode(
compileSFCScript(
`\n` +
- ``
- ).content
+ ``,
+ ).content,
)
})
@@ -136,8 +144,8 @@ describe('CSS vars injection', () => {
assertCode(
compileSFCScript(
`\n` +
- ``
- ).content
+ ``,
+ ).content,
)
})
@@ -147,8 +155,8 @@ describe('CSS vars injection', () => {
`\n` + ``
- ).content
+ \n` + ``,
+ ).content,
)
})
@@ -156,8 +164,8 @@ describe('CSS vars injection', () => {
assertCode(
compileSFCScript(
`\n` +
- ``
- ).content
+ ``,
+ ).content,
)
})
@@ -170,7 +178,7 @@ describe('CSS vars injection', () => {
div{ /* color: v-bind(color); */ width:20; }
div{ width: v-bind(width); }
/* comment */
- `
+ `,
)
expect(content).not.toMatch(`"${mockId}-color": (color)`)
@@ -190,7 +198,7 @@ describe('CSS vars injection', () => {
p {
color: v-bind(color);
}
- `
+ `,
)
// color should only be injected once, even if it is twice in style
expect(content).toMatch(`_useCssVars(_ctx => ({
@@ -221,15 +229,116 @@ describe('CSS vars injection', () => {
p {
color: v-bind(((a + b)) / (2 * a));
}
- `
+ `,
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-foo": (_unref(foo)),
- "${mockId}-foo____px_": (_unref(foo) + 'px'),
- "${mockId}-_a___b____2____px_": ((_unref(a) + _unref(b)) / 2 + 'px'),
- "${mockId}-__a___b______2___a_": (((_unref(a) + _unref(b))) / (2 * _unref(a)))
-})`)
+ "${mockId}-foo\\ \\+\\ \\'px\\'": (_unref(foo) + 'px'),
+ "${mockId}-\\(a\\ \\+\\ b\\)\\ \\/\\ 2\\ \\+\\ \\'px\\'": ((_unref(a) + _unref(b)) / 2 + 'px'),
+ "${mockId}-\\(\\(a\\ \\+\\ b\\)\\)\\ \\/\\ \\(2\\ \\*\\ a\\)": (((_unref(a) + _unref(b))) / (2 * _unref(a)))
+}))`)
assertCode(content)
})
+
+ // #6022
+ test('should be able to parse incomplete expressions', () => {
+ const {
+ descriptor: { cssVars },
+ } = parse(
+ `
+ `,
+ )
+ expect(cssVars).toMatchObject([`count.toString(`, `xxx`])
+ })
+
+ // #7759
+ test('It should correctly parse the case where there is no space after the script tag', () => {
+ const { content } = compileSFCScript(
+ `
+ `,
+ )
+ expect(content).toMatch(
+ `export default {\n setup(__props, { expose: __expose }) {\n __expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))`,
+ )
+ })
+
+ describe('skip codegen in SSR', () => {
+ test('script setup, inline', () => {
+ const { content } = compileSFCScript(
+ `\n` +
+ ``,
+ {
+ inlineTemplate: true,
+ templateOptions: {
+ ssr: true,
+ },
+ },
+ )
+ expect(content).not.toMatch(`_useCssVars`)
+ })
+
+ // #6926
+ test('script, non-inline', () => {
+ const { content } = compileSFCScript(
+ `\n` +
+ ``,
+ {
+ inlineTemplate: false,
+ templateOptions: {
+ ssr: true,
+ },
+ },
+ )
+ expect(content).not.toMatch(`_useCssVars`)
+ })
+
+ test('normal script', () => {
+ const { content } = compileSFCScript(
+ `\n` +
+ ``,
+ {
+ templateOptions: {
+ ssr: true,
+ },
+ },
+ )
+ expect(content).not.toMatch(`_useCssVars`)
+ })
+ })
})
})
diff --git a/packages/compiler-sfc/__tests__/parse.spec.ts b/packages/compiler-sfc/__tests__/parse.spec.ts
index 9db9bb7f3e6..265655e47ef 100644
--- a/packages/compiler-sfc/__tests__/parse.spec.ts
+++ b/packages/compiler-sfc/__tests__/parse.spec.ts
@@ -1,21 +1,72 @@
import { parse } from '../src'
-import { baseParse, baseCompile } from '@vue/compiler-core'
-import { SourceMapConsumer } from 'source-map'
+import {
+ ElementTypes,
+ NodeTypes,
+ baseCompile,
+ createRoot,
+} from '@vue/compiler-core'
+import { SourceMapConsumer } from 'source-map-js'
describe('compiler:sfc', () => {
describe('source map', () => {
test('style block', () => {
// Padding determines how many blank lines will there be before the style block
const padding = Math.round(Math.random() * 10)
- const style = parse(
- `${'\n'.repeat(padding)}\n`
- ).descriptor.styles[0]
+ const src =
+ `${'\n'.repeat(padding)}` +
+ `
- expect(style.map).not.toBeUndefined()
+
- const consumer = new SourceMapConsumer(style.map!)
+
+
+`
+ const {
+ descriptor: { styles },
+ } = parse(src)
+
+ expect(styles[0].map).not.toBeUndefined()
+ const consumer = new SourceMapConsumer(styles[0].map!)
+ const lineOffset =
+ src.slice(0, src.indexOf(`\n`.replace(
/./g,
- ' '
- ) + '\n{ "greeting": "hello" }\n'
+ ' ',
+ ) + '\n{ "greeting": "hello" }\n',
)
})
@@ -121,9 +192,8 @@ h1 { color: red }
end: {
line: 3,
column: 1,
- offset: 10 + content.length
+ offset: 10 + content.length,
},
- source: content
})
})
@@ -132,9 +202,8 @@ h1 { color: red }
expect(descriptor.template).toBeTruthy()
expect(descriptor.template!.content).toBeFalsy()
expect(descriptor.template!.loc).toMatchObject({
- start: { line: 1, column: 1, offset: 0 },
- end: { line: 1, column: 1, offset: 0 },
- source: ''
+ start: { line: 1, column: 12, offset: 11 },
+ end: { line: 1, column: 12, offset: 11 },
})
})
@@ -145,7 +214,6 @@ h1 { color: red }
expect(descriptor.template!.loc).toMatchObject({
start: { line: 1, column: 11, offset: 10 },
end: { line: 1, column: 11, offset: 10 },
- source: ''
})
})
@@ -156,7 +224,7 @@ h1 { color: red }
expect(parse(``).descriptor.styles.length).toBe(0)
expect(parse(` `).descriptor.customBlocks.length).toBe(0)
expect(
- parse(` \n\t `).descriptor.customBlocks.length
+ parse(` \n\t `).descriptor.customBlocks.length,
).toBe(0)
})
@@ -167,25 +235,28 @@ h1 { color: red }
expect(descriptor.script!.attrs['src']).toBe('com')
})
+ test('should not expose ast on template node if has src import', () => {
+ const { descriptor } = parse(` `)
+ expect(descriptor.template!.ast).toBeUndefined()
+ })
+
test('ignoreEmpty: false', () => {
const { descriptor } = parse(
`\n`,
{
- ignoreEmpty: false
- }
+ ignoreEmpty: false,
+ },
)
expect(descriptor.script).toBeTruthy()
expect(descriptor.script!.loc).toMatchObject({
- source: '',
start: { line: 1, column: 9, offset: 8 },
- end: { line: 1, column: 9, offset: 8 }
+ end: { line: 1, column: 9, offset: 8 },
})
expect(descriptor.scriptSetup).toBeTruthy()
expect(descriptor.scriptSetup!.loc).toMatchObject({
- source: '\n',
start: { line: 2, column: 15, offset: 32 },
- end: { line: 3, column: 1, offset: 33 }
+ end: { line: 3, column: 1, offset: 33 },
})
})
@@ -201,20 +272,22 @@ h1 { color: red }
test('treat empty lang attribute as the html', () => {
const content = `ok
`
const { descriptor, errors } = parse(
- `${content} `
+ `${content} `,
)
expect(descriptor.template!.content).toBe(content)
expect(errors.length).toBe(0)
})
// #1120
- test('alternative template lang should be treated as plain text', () => {
- const content = `p(v-if="1 < 2") test`
+ test('template with preprocessor lang should be treated as plain text', () => {
+ const content = `p(v-if="1 < 2") test
`
const { descriptor, errors } = parse(
- `` + content + ` `
+ `` + content + ` `,
)
expect(errors.length).toBe(0)
expect(descriptor.template!.content).toBe(content)
+ // should not attempt to parse the content
+ expect(descriptor.template!.ast!.children.length).toBe(1)
})
//#2566
@@ -233,17 +306,17 @@ h1 { color: red }
expect(parse(`hi `).descriptor.slotted).toBe(false)
expect(
parse(`hi `).descriptor
- .slotted
+ .slotted,
).toBe(false)
expect(
parse(
- `hi `
- ).descriptor.slotted
+ `hi `,
+ ).descriptor.slotted,
).toBe(true)
expect(
parse(
- `hi `
- ).descriptor.slotted
+ `hi `,
+ ).descriptor.slotted,
).toBe(true)
})
@@ -260,19 +333,54 @@ h1 { color: red }
test('custom compiler', () => {
const { errors } = parse(` `, {
compiler: {
- parse: baseParse,
- compile: baseCompile
- }
+ parse: (_, options) => {
+ options.onError!(new Error('foo') as any)
+ return createRoot([])
+ },
+ compile: baseCompile,
+ },
})
- expect(errors.length).toBe(1)
+ expect(errors.length).toBe(2)
+ // error thrown by the custom parse
+ expect(errors[0].message).toBe('foo')
+ // error thrown based on the returned root
+ expect(errors[1].message).toMatch('At least one')
})
test('treat custom blocks as raw text', () => {
- const { errors, descriptor } = parse(` <-& `)
+ const { errors, descriptor } = parse(
+ ` <-& `,
+ )
expect(errors.length).toBe(0)
expect(descriptor.customBlocks[0].content).toBe(` <-& `)
})
+ test('should accept parser options', () => {
+ const { errors, descriptor } = parse(` `, {
+ templateParseOptions: {
+ isCustomElement: t => t === 'hello',
+ },
+ })
+ expect(errors.length).toBe(0)
+ expect(descriptor.template!.ast!.children[0]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'hello',
+ tagType: ElementTypes.ELEMENT,
+ })
+
+ // test cache invalidation on different options
+ const { descriptor: d2 } = parse(` `, {
+ templateParseOptions: {
+ isCustomElement: t => t !== 'hello',
+ },
+ })
+ expect(d2.template!.ast!.children[0]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'hello',
+ tagType: ElementTypes.COMPONENT,
+ })
+ })
+
describe('warnings', () => {
function assertWarning(errors: Error[], msg: string) {
expect(errors.some(e => e.message.match(msg))).toBe(true)
@@ -281,7 +389,7 @@ h1 { color: red }
test('should only allow single template element', () => {
assertWarning(
parse(`
`).errors,
- `Single file component can contain only one element`
+ `Single file component can contain only one element`,
)
})
@@ -289,25 +397,43 @@ h1 { color: red }
assertWarning(
parse(``)
.errors,
- `Single file component can contain only one `
+ ``,
).errors,
- `Single file component can contain only one `
- ).errors.length
+ ``,
+ ).errors.length,
).toBe(0)
})
+
+ // # 6676
+ test('should throw error if no or
-
-
-
-
-
-
-
diff --git a/packages/sfc-playground/src/Header.vue b/packages/sfc-playground/src/Header.vue
deleted file mode 100644
index fe65d8af7ae..00000000000
--- a/packages/sfc-playground/src/Header.vue
+++ /dev/null
@@ -1,291 +0,0 @@
-
-
-
-
-
-
- Vue SFC Playground
-
-
-
-
- Version: {{ activeVersion }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/sfc-playground/src/icons/GitHub.vue b/packages/sfc-playground/src/icons/GitHub.vue
deleted file mode 100644
index a20522f1e0a..00000000000
--- a/packages/sfc-playground/src/icons/GitHub.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/packages/sfc-playground/src/icons/Moon.vue b/packages/sfc-playground/src/icons/Moon.vue
deleted file mode 100644
index 2bfef28a9f3..00000000000
--- a/packages/sfc-playground/src/icons/Moon.vue
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/packages/sfc-playground/src/icons/Sun.vue b/packages/sfc-playground/src/icons/Sun.vue
deleted file mode 100644
index 529ad57b7f7..00000000000
--- a/packages/sfc-playground/src/icons/Sun.vue
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/sfc-playground/vite.config.ts b/packages/sfc-playground/vite.config.ts
deleted file mode 100644
index 4184c791817..00000000000
--- a/packages/sfc-playground/vite.config.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import fs from 'fs'
-import path from 'path'
-import { defineConfig, Plugin } from 'vite'
-import vue from '@vitejs/plugin-vue'
-import execa from 'execa'
-
-const commit = execa.sync('git', ['rev-parse', 'HEAD']).stdout.slice(0, 7)
-
-export default defineConfig({
- plugins: [vue(), copyVuePlugin()],
- define: {
- __COMMIT__: JSON.stringify(commit),
- __VUE_PROD_DEVTOOLS__: JSON.stringify(true)
- },
- optimizeDeps: {
- exclude: ['@vue/repl']
- }
-})
-
-function copyVuePlugin(): Plugin {
- return {
- name: 'copy-vue',
- generateBundle() {
- const filePath = path.resolve(
- __dirname,
- '../vue/dist/vue.runtime.esm-browser.js'
- )
- if (!fs.existsSync(filePath)) {
- throw new Error(
- `vue.runtime.esm-browser.js not built. ` +
- `Run "nr build vue -f esm-browser" first.`
- )
- }
- this.emitFile({
- type: 'asset',
- fileName: 'vue.runtime.esm-browser.js',
- source: fs.readFileSync(filePath, 'utf-8')
- })
- }
- }
-}
diff --git a/packages/shared/__tests__/__snapshots__/codeframe.spec.ts.snap b/packages/shared/__tests__/__snapshots__/codeframe.spec.ts.snap
index 579a4507d6e..28486d71723 100644
--- a/packages/shared/__tests__/__snapshots__/codeframe.spec.ts.snap
+++ b/packages/shared/__tests__/__snapshots__/codeframe.spec.ts.snap
@@ -1,62 +1,88 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: codeframe line in middle 1`] = `
-"2 |
+exports[`compiler: codeframe > invalid start and end 1`] = `
+"1 |
+ | ^
+2 |
+3 |
-6 |
+6 |
| ^^^^^^^^^
7 |
"
`;
-exports[`compiler: codeframe line near top 1`] = `
+exports[`compiler: codeframe > line near top 1`] = `
"1 |
-2 |
+2 |
| ^^^^^^^^^
3 |