Merge branch 'main' into fix-7276

This commit is contained in:
edison 2024-08-08 16:09:05 +08:00 committed by GitHub
commit e245fb7502
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
622 changed files with 47506 additions and 34047 deletions

View File

@ -1,89 +0,0 @@
const DOMGlobals = ['window', 'document']
const NodeGlobals = ['module', 'require']
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module'
},
plugins: ['jest'],
rules: {
'no-debugger': 'error',
'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],
'no-restricted-syntax': [
'error',
// since we target ES2015 for baseline support, we need to forbid object
// rest spread usage in destructure as it compiles into a verbose helper.
'ObjectPattern > RestElement',
// tsc compiles assignment spread into Object.assign() calls, but esbuild
// still generates verbose helpers, so spread assignment is also prohiboted
'ObjectExpression > SpreadElement',
'AwaitExpression'
]
},
overrides: [
// tests, no restrictions (runs in Node / jest with jsdom)
{
files: ['**/__tests__/**', 'packages/dts-test/**'],
rules: {
'no-restricted-globals': 'off',
'no-restricted-syntax': 'off',
'jest/no-disabled-tests': 'error',
'jest/no-focused-tests': 'error'
}
},
// 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'
}
},
// Node scripts
{
files: [
'scripts/**',
'*.{js,ts}',
'packages/**/index.js',
'packages/size-check/**'
],
rules: {
'no-restricted-globals': 'off',
'no-restricted-syntax': 'off'
}
}
]
}

2
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,2 @@
# update prettier & eslint config (#9162)
bfe6b459d3a0ce6168611ee1ac7e6e789709df9d

View File

@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- 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.

View File

@ -1,39 +0,0 @@
name: "\U0001F680 New feature proposal"
description: Suggest an idea for this project
labels: [":sparkles: feature request"]
body:
- type: markdown
attributes:
value: |
**Before You Start...**
This form is only for submitting feature requests. 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 - another user may have already requested something similar!
- type: textarea
id: problem-description
attributes:
label: What problem does this feature solve?
description: |
Explain your use case, context, and rationale behind this feature request. More importantly, what is the **end user experience** you are trying to build that led to the need for this feature?
An important design goal of Vue is keeping the API surface small and straightforward. In general, we only consider adding new features that solve a problem that cannot be easily dealt with using existing APIs (i.e. not just an alternative way of doing things that can already be done). The problem should also be common enough to justify the addition.
placeholder: Problem description
validations:
required: true
- type: textarea
id: proposed-API
attributes:
label: What does the proposed API look like?
description: |
Describe how you propose to solve the problem and provide code samples of how the API would work once implemented. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format your code blocks.
placeholder: Assumed API
validations:
required: true

View File

@ -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}/
```
@ -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

View File

@ -17,7 +17,31 @@ 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. `main`, 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.
@ -57,9 +81,9 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
## Development Setup
You will need [Node.js](https://nodejs.org) **version 16+**, and [PNPM](https://pnpm.io) **version 7+**.
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:
@ -82,11 +106,11 @@ The project uses [simple-git-hooks](https://github.com/toplenboren/simple-git-ho
- Type check the entire project
- Automatically format changed files using Prettier
- Verify commit message format (logic in `scripts/verifyCommit.js`)
- 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".
@ -181,11 +205,11 @@ Shortcut for starting the SFC Playground in local dev mode. This provides the fa
### `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 reproductions that require real build setups: link `packages/vue` globally, then link it into the project being debugged.
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 useful when working on pure compiler issues.
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`
@ -248,8 +272,6 @@ This repository employs a [monorepo](https://en.wikipedia.org/wiki/Monorepo) set
- `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).
- `size-check`: Used for checking built bundle sizes on CI.
### 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:

View File

@ -1,70 +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
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 10
versioning-strategy: lockfile-only

1746
.github/git-branch-workflow.excalidraw vendored Normal file

File diff suppressed because it is too large Load Diff

BIN
.github/git-branch-workflow.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

BIN
.github/issue-workflow.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

123
.github/maintenance.md vendored Normal file
View File

@ -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
![Workflow](./issue-workflow.png)
## 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.
![Workflow](./git-branch-workflow.png)
### 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.

58
.github/renovate.json5 vendored Normal file
View File

@ -0,0 +1,58 @@
{
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
extends: ['config:base', 'schedule:weekly', 'group:allNonMajor'],
labels: ['dependencies'],
ignorePaths: ['**/__tests__/**'],
rangeStrategy: 'bump',
packageRules: [
{
depTypeList: ['peerDependencies'],
enabled: false,
},
{
groupName: 'test',
matchPackageNames: ['vitest', 'jsdom', 'puppeteer'],
matchPackagePrefixes: ['@vitest'],
},
{
groupName: 'playground',
matchFileNames: [
'packages/sfc-playground/package.json',
'packages/template-explorer/package.json',
],
},
{
groupName: 'compiler',
matchPackageNames: ['magic-string'],
matchPackagePrefixes: ['@babel', 'postcss'],
},
{
groupName: 'build',
matchPackageNames: ['vite', '@swc/core'],
matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'],
},
{
groupName: 'lint',
matchPackageNames: ['simple-git-hooks', 'lint-staged'],
matchPackagePrefixes: ['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',
],
}

34
.github/workflows/autofix.yml vendored Normal file
View File

@ -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.0.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@ff86a557419858bb967097bfc916833f5647fa8c

33
.github/workflows/canary-minor.yml vendored Normal file
View File

@ -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.0.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 --tag minor
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -12,15 +12,15 @@ jobs:
runs-on: ubuntu-latest
environment: Release
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4.0.0
- name: Set node version to 18
uses: actions/setup-node@v3
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
node-version-file: '.node-version'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'

View File

@ -6,6 +6,7 @@ on:
pull_request:
branches:
- main
- minor
permissions:
contents: read # to fetch code (actions/checkout)
@ -14,21 +15,20 @@ jobs:
unit-test:
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
env:
PUPPETEER_SKIP_DOWNLOAD: 'true'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4.0.0
- name: Set node version to 18
uses: actions/setup-node@v3
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
node-version-file: '.node-version'
cache: 'pnpm'
- name: Skip Puppeteer download
run: echo "PUPPETEER_SKIP_DOWNLOAD=1" >> $GITHUB_ENV
- run: pnpm install
- name: Run unit tests
@ -37,21 +37,20 @@ jobs:
unit-test-windows:
runs-on: windows-latest
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
env:
PUPPETEER_SKIP_DOWNLOAD: 'true'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4.0.0
- name: Set node version to 18
uses: actions/setup-node@v3
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
node-version-file: '.node-version'
cache: 'pnpm'
- name: Skip Puppeteer download
run: echo "PUPPETEER_SKIP_DOWNLOAD=1" >> $env:GITHUB_ENV
- run: pnpm install
- name: Run compiler unit tests
@ -64,73 +63,81 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup cache for Chromium binary
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.cache/puppeteer/chrome
path: ~/.cache/puppeteer
key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4.0.0
- name: Set node version to 18
uses: actions/setup-node@v3
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
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
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
env:
PUPPETEER_SKIP_DOWNLOAD: 'true'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4.0.0
- name: Set node version to 18
uses: actions/setup-node@v3
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
node-version-file: '.node-version'
cache: 'pnpm'
- name: Skip Puppeteer download
run: echo "PUPPETEER_SKIP_DOWNLOAD=1" >> $GITHUB_ENV
- run: pnpm install
- name: Run eslint
run: pnpm run lint
# - name: Run prettier
# run: pnpm run format-check
- name: Run prettier
run: pnpm run format-check
- name: Run type declaration tests
run: pnpm run test-dts
size:
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
env:
CI_JOB_NUMBER: 1
steps:
- uses: actions/checkout@v3
# benchmarks:
# runs-on: ubuntu-latest
# if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
# env:
# PUPPETEER_SKIP_DOWNLOAD: 'true'
# steps:
# - uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
# - name: Install pnpm
# uses: pnpm/action-setup@v3.0.0
- name: Set node version to 18
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
# - name: Install Node.js
# uses: actions/setup-node@v4
# with:
# node-version-file: '.node-version'
# cache: 'pnpm'
- run: PUPPETEER_SKIP_DOWNLOAD=1 pnpm install
- run: pnpm run size
# - run: pnpm install
# - name: Run benchmarks
# uses: CodSpeedHQ/action@v2
# with:
# run: pnpm vitest bench --run
# token: ${{ secrets.CODSPEED_TOKEN }}

View File

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'vuejs/core' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run')
steps:
- uses: actions/github-script@v6
- uses: actions/github-script@v7
with:
script: |
const user = context.payload.sender.login
@ -43,7 +43,7 @@ jobs:
})
throw new Error('not allowed')
}
- uses: actions/github-script@v6
- uses: actions/github-script@v7
id: get-pr-data
with:
script: |
@ -58,7 +58,7 @@ jobs:
branchName: pr.head.ref,
repo: pr.head.repo.full_name
}
- uses: actions/github-script@v6
- uses: actions/github-script@v7
id: trigger
env:
COMMENT: ${{ github.event.comment.body }}

View File

@ -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'

View File

@ -3,7 +3,7 @@ on:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
name: Create Release
name: Create GH Release for Tag
permissions: {}
jobs:
@ -24,4 +24,5 @@ jobs:
with:
tag_name: ${{ github.ref }}
body: |
Please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details.
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.

95
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,95 @@
name: Release
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch to publish'
required: true
default: 'main'
type: choice
options:
- main
- minor
bump:
description: 'Bump version'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- prepatch
- preminor
- custom
custom_version:
description: 'Custom version'
required: false
default: ''
type: string
jobs:
release:
# prevents this action from running on forks
if: github.repository == 'vuejs/core'
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
with:
ref: ${{ inputs.branch }}
fetch-depth: 0 # need this to get tags for changelog generation
- 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: Configure git user as vue bot
run: |
git config user.name "vue-bot"
git config user.email "<bot@vuejs.org>"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: Run release script
id: release
run: |
pnpm release ${{ inputs.bump != 'custom' && inputs.bump || inputs.custom_version }} --skipPrompts
RELEASE_TAG=$(git describe --tags --abbrev=0)
echo "tag=$RELEASE_TAG" >> $GITHUB_OUTPUT
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Push tags
run: git push -u origin ${{ inputs.branch }} --follow-tags
- name: Create Release for Tag
id: release_tag
uses: yyx990803/release-tag@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.release.outputs.tag }}
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.

54
.github/workflows/size-data.yml vendored Normal file
View File

@ -0,0 +1,54 @@
name: size data
on:
push:
branches:
- main
- minor
pull_request:
branches:
- main
- minor
permissions:
contents: read
env:
PUPPETEER_SKIP_DOWNLOAD: 'true'
jobs:
upload:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4.0.0
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
cache: pnpm
- name: Install dependencies
run: pnpm install
- run: pnpm run size
- name: Upload Size Data
uses: actions/upload-artifact@v4
with:
name: size-data
path: temp/size
- name: Save PR number
if: ${{github.event_name == 'pull_request'}}
run: echo ${{ github.event.number }} > ./pr.txt
- uses: actions/upload-artifact@v4
if: ${{github.event_name == 'pull_request'}}
with:
name: pr-number
path: pr.txt

85
.github/workflows/size-report.yml vendored Normal file
View File

@ -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.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.0.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 PR number
uses: dawidd6/action-download-artifact@v6
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id }}
path: /tmp/pr-number
- name: Read PR Number
id: pr-number
uses: juliangruber/read-file-action@v1
with:
path: /tmp/pr-number/pr.txt
- name: Download Size Data
uses: dawidd6/action-download-artifact@v6
with:
name: size-data
run_id: ${{ github.event.workflow_run.id }}
path: temp/size
- name: Download Previous Size Data
uses: dawidd6/action-download-artifact@v6
with:
branch: ${{ github.base_ref }}
workflow: size-data.yml
event: push
name: size-data
path: temp/size-prev
if_no_artifact_found: warn
- name: Prepare report
run: pnpm tsx scripts/size-report.ts > 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 }}
<!-- VUE_CORE_SIZE -->
body-include: '<!-- VUE_CORE_SIZE -->'

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ TODOs.md
.eslintcache
dts-build/packages
*.tsbuildinfo
*.tgz

1
.node-version Normal file
View File

@ -0,0 +1 @@
20

View File

@ -1 +1,3 @@
dist
pnpm-lock.yaml
CHANGELOG*.md

View File

@ -1,5 +1,5 @@
semi: false
singleQuote: true
printWidth: 80
trailingComma: 'none'
arrowParens: 'avoid'
{
"semi": false,
"singleQuote": true,
"arrowParens": "avoid"
}

23
.vscode/launch.json vendored
View File

@ -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_internals>/**", "**/node_modules/**"],
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
"args": ["run", "${relativeFile}"],
"smartStep": true,
"console": "integratedTerminal"
}
]
}

File diff suppressed because it is too large Load Diff

7
FUNDING.json Normal file
View File

@ -0,0 +1,7 @@
{
"drips": {
"ethereum": {
"ownedBy": "0x5393BdeA2a020769256d9f337B0fc81a2F64850A"
}
}
}

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2018-present, Yuxi (Evan) You
Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,4 +1,4 @@
# vuejs/core [![npm](https://img.shields.io/npm/v/vue.svg)](https://www.npmjs.com/package/vue) [![build status](https://github.com/vuejs/core/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/vuejs/core/actions/workflows/ci.yml)
# vuejs/core [![npm](https://img.shields.io/npm/v/vue.svg)](https://www.npmjs.com/package/vue) [![build status](https://github.com/vuejs/core/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/vuejs/core/actions/workflows/ci.yml) [![Download](https://img.shields.io/npm/dm/vue)](https://www.npmjs.com/package/vue)
## Getting Started
@ -20,7 +20,7 @@ Vue.js is an MIT-licensed open source project with its ongoing development made
<p align="center">
<a target="_blank" href="https://vuejs.org/sponsor/#current-sponsors">
<img alt="sponsors" src="https://sponsors.vuejs.org/sponsors.svg?v2">
<img alt="sponsors" src="https://sponsors.vuejs.org/sponsors.svg?v3">
</a>
</p>

View File

@ -5,3 +5,11 @@ To report a vulnerability, please email security@vuejs.org.
While the discovery of new vulnerabilities is rare, we also recommend always using the latest versions of Vue and its official companion libraries to ensure your application remains as secure as possible.
Please note that we do not consider XSS via template expressions a valid attack vector, because it can only happen if the user intentionally uses untrusted content as template compilation source. This is similar to knowingly pasting untrusted scripts into a browser console. We explicitly warn users against using untrusted content as template compilation source in our documentation.
## Security Hall of Fame
We would like to thank the following security researchers for responsibly disclosing security issues to us.
- Jeet Pal - [@jeetpal2007](https://github.com/jeetpal2007) | [Email](jeetpal2007@gmail.com) | [LinkedIn](https://in.linkedin.com/in/jeet-pal-22601a290)
- Mix - [@mnixry](https://github.com/mnixry)
- Aviv Keller - [@RedYetiDev](https://github.com/redyetidev) | [LinkedIn](https://www.linkedin.com/in/redyetidev) <redyetidev@gmail.com>

View File

@ -773,7 +773,7 @@ may cause build issues in projects still using TS 3.x.
- **types:** adjust type exports for manual render function and tooling usage ([e4dc03a](https://github.com/vuejs/core/commit/e4dc03a8b17d5e9f167de6a62a645878ac7ef3e2)), closes [#1329](https://github.com/vuejs/core/issues/1329)
- **types:** mixins/extends support in TypeScript ([#626](https://github.com/vuejs/core/issues/626)) ([d3c436a](https://github.com/vuejs/core/commit/d3c436ae2e66b75b7f2ed574dadda3f0e1fdce73))
- **types:** support typing directive value via generic argument ([#1007](https://github.com/vuejs/core/issues/1007)) ([419b86d](https://github.com/vuejs/core/commit/419b86d1908f2a0521e6a7eafcbee764e9ee59a0)), closes [#998](https://github.com/vuejs/core/issues/998)
- **types:** update to Typescript 3.9 ([#1106](https://github.com/vuejs/core/issues/1106)) ([97dedeb](https://github.com/vuejs/core/commit/97dedebd8097116a16209664a1ca38392b964da3))
- **types:** update to TypeScript 3.9 ([#1106](https://github.com/vuejs/core/issues/1106)) ([97dedeb](https://github.com/vuejs/core/commit/97dedebd8097116a16209664a1ca38392b964da3))
### Performance Improvements

598
changelogs/CHANGELOG-3.3.md Normal file
View File

@ -0,0 +1,598 @@
## [3.3.13](https://github.com/vuejs/core/compare/v3.3.12...v3.3.13) (2023-12-19)
### Bug Fixes
* **compiler-core:** fix v-on with modifiers on inline expression of undefined ([#9866](https://github.com/vuejs/core/issues/9866)) ([bae79dd](https://github.com/vuejs/core/commit/bae79ddf8564a2da4a5365cfeb8d811990f42335)), closes [#9865](https://github.com/vuejs/core/issues/9865)
* **runtime-dom:** cache event handlers by key/modifiers ([#9851](https://github.com/vuejs/core/issues/9851)) ([04d2c05](https://github.com/vuejs/core/commit/04d2c05054c26b02fbc1d84839b0ed5cd36455b6)), closes [#9849](https://github.com/vuejs/core/issues/9849)
* **types:** extract properties from extended collections ([#9854](https://github.com/vuejs/core/issues/9854)) ([24b1c1d](https://github.com/vuejs/core/commit/24b1c1dd57fd55d998aa231a147500e010b10219)), closes [#9852](https://github.com/vuejs/core/issues/9852)
## [3.3.12](https://github.com/vuejs/core/compare/v3.3.11...v3.3.12) (2023-12-16)
### Bug Fixes
* **hydration:** handle appear transition before patch props ([#9837](https://github.com/vuejs/core/issues/9837)) ([e70f4c4](https://github.com/vuejs/core/commit/e70f4c47c553b6e16d8fad70743271ca23802fe7)), closes [#9832](https://github.com/vuejs/core/issues/9832)
* **sfc/cssVars:** fix loss of CSS v-bind variables when setting inline style with string value ([#9824](https://github.com/vuejs/core/issues/9824)) ([0a387df](https://github.com/vuejs/core/commit/0a387dfb1d04afb6eae4296b6da76dfdaca77af4)), closes [#9821](https://github.com/vuejs/core/issues/9821)
* **ssr:** fix suspense hydration of fallback content ([#7188](https://github.com/vuejs/core/issues/7188)) ([60415b5](https://github.com/vuejs/core/commit/60415b5d67df55f1fd6b176615299c08640fa142))
* **types:** add `xmlns:xlink` to `SVGAttributes` ([#9300](https://github.com/vuejs/core/issues/9300)) ([0d61b42](https://github.com/vuejs/core/commit/0d61b429ecf63591d31e09702058fa4c7132e1a7)), closes [#9299](https://github.com/vuejs/core/issues/9299)
* **types:** fix `shallowRef` type error ([#9839](https://github.com/vuejs/core/issues/9839)) ([9a57158](https://github.com/vuejs/core/commit/9a571582b53220270e498d8712ea59312c0bef3a))
* **types:** support for generic keyof slots ([#8374](https://github.com/vuejs/core/issues/8374)) ([213eba4](https://github.com/vuejs/core/commit/213eba479ce080efc1053fe636f6be4a4c889b44))
## [3.3.11](https://github.com/vuejs/core/compare/v3.3.10...v3.3.11) (2023-12-08)
### Bug Fixes
* **custom-element:** correctly handle number type props in prod ([#8989](https://github.com/vuejs/core/issues/8989)) ([d74d364](https://github.com/vuejs/core/commit/d74d364d62db8e48881af6b5a75ce4fb5f36cc35))
* **reactivity:** fix mutation on user proxy of reactive Array ([6ecbd5c](https://github.com/vuejs/core/commit/6ecbd5ce2a7f59314a8326a1d193874b87f4d8c8)), closes [#9742](https://github.com/vuejs/core/issues/9742) [#9751](https://github.com/vuejs/core/issues/9751) [#9750](https://github.com/vuejs/core/issues/9750)
* **runtime-dom:** fix width and height prop check condition ([5b00286](https://github.com/vuejs/core/commit/5b002869c533220706f9788b496b8ca8d8e98609)), closes [#9762](https://github.com/vuejs/core/issues/9762)
* **shared:** handle Map with symbol keys in toDisplayString ([#9731](https://github.com/vuejs/core/issues/9731)) ([364821d](https://github.com/vuejs/core/commit/364821d6bdb1775e2f55a69bcfb9f40f7acf1506)), closes [#9727](https://github.com/vuejs/core/issues/9727)
* **shared:** handle more Symbol cases in toDisplayString ([983d45d](https://github.com/vuejs/core/commit/983d45d4f8eb766b5a16b7ea93b86d3c51618fa6))
* **Suspense:** properly get anchor when mount fallback vnode ([#9770](https://github.com/vuejs/core/issues/9770)) ([b700328](https://github.com/vuejs/core/commit/b700328342e17dc16b19316c2e134a26107139d2)), closes [#9769](https://github.com/vuejs/core/issues/9769)
* **types:** ref() return type should not be any when initial value is any ([#9768](https://github.com/vuejs/core/issues/9768)) ([cdac121](https://github.com/vuejs/core/commit/cdac12161ec27b45ded48854c3d749664b6d4a6d))
* **watch:** should not fire pre watcher on child component unmount ([#7181](https://github.com/vuejs/core/issues/7181)) ([6784f0b](https://github.com/vuejs/core/commit/6784f0b1f8501746ea70d87d18ed63a62cf6b76d)), closes [#7030](https://github.com/vuejs/core/issues/7030)
## [3.3.10](https://github.com/vuejs/core/compare/v3.3.9...v3.3.10) (2023-12-04)
### Bug Fixes
* **app:** prevent template from being cached between apps with different options ([#9724](https://github.com/vuejs/core/issues/9724)) ([ec71585](https://github.com/vuejs/core/commit/ec715854ca12520b2afc9e9b3981cbae05ae5206)), closes [#9618](https://github.com/vuejs/core/issues/9618)
* **compiler-sfc:** avoid passing forEach index to genMap ([f12db7f](https://github.com/vuejs/core/commit/f12db7fb564a534cef2e5805cc9f54afe5d72fbf))
* **compiler-sfc:** deindent pug/jade templates ([6345197](https://github.com/vuejs/core/commit/634519720a21fb5a6871454e1cadad7053a568b8)), closes [#3231](https://github.com/vuejs/core/issues/3231) [#3842](https://github.com/vuejs/core/issues/3842) [#7723](https://github.com/vuejs/core/issues/7723)
* **compiler-sfc:** fix :where and :is selector in scoped mode with multiple selectors ([#9735](https://github.com/vuejs/core/issues/9735)) ([c3e2c55](https://github.com/vuejs/core/commit/c3e2c556b532656b50b8ab5cd2d9eabc26622d63)), closes [#9707](https://github.com/vuejs/core/issues/9707)
* **compiler-sfc:** generate more treeshaking friendly code ([#9507](https://github.com/vuejs/core/issues/9507)) ([8d74ca0](https://github.com/vuejs/core/commit/8d74ca0e6fa2738ca6854b7e879ff59419f948c7)), closes [#9500](https://github.com/vuejs/core/issues/9500)
* **compiler-sfc:** support inferring generic types ([#8511](https://github.com/vuejs/core/issues/8511)) ([eb5e307](https://github.com/vuejs/core/commit/eb5e307c0be62002e62c4c800d0dfacb39b0d4ca)), closes [#8482](https://github.com/vuejs/core/issues/8482)
* **compiler-sfc:** support resolving components from props ([#8785](https://github.com/vuejs/core/issues/8785)) ([7cbcee3](https://github.com/vuejs/core/commit/7cbcee3d831241a8bd3588ae92d3f27e3641e25f))
* **compiler-sfc:** throw error when failing to load TS during type resolution ([#8883](https://github.com/vuejs/core/issues/8883)) ([4936d2e](https://github.com/vuejs/core/commit/4936d2e11a8d0ca3704bfe408548cb26bb3fd5e9))
* **cssVars:** cssVar names should be double-escaped when generating code for ssr ([#8824](https://github.com/vuejs/core/issues/8824)) ([5199a12](https://github.com/vuejs/core/commit/5199a12f8855cd06f24bf355708b5a2134f63176)), closes [#7823](https://github.com/vuejs/core/issues/7823)
* **deps:** update compiler to ^7.23.4 ([#9681](https://github.com/vuejs/core/issues/9681)) ([31f6ebc](https://github.com/vuejs/core/commit/31f6ebc4df84490ed29fb75e7bf4259200eb51f0))
* **runtime-core:** Suspense get anchor properly in Transition ([#9309](https://github.com/vuejs/core/issues/9309)) ([65f3fe2](https://github.com/vuejs/core/commit/65f3fe273127a8b68e1222fbb306d28d85f01757)), closes [#8105](https://github.com/vuejs/core/issues/8105)
* **runtime-dom:** set width/height with units as attribute ([#8781](https://github.com/vuejs/core/issues/8781)) ([bfc1838](https://github.com/vuejs/core/commit/bfc1838f31199de3f189198a3c234fa7bae91386))
* **ssr:** avoid computed being accidentally cached before server render ([#9688](https://github.com/vuejs/core/issues/9688)) ([30d5d93](https://github.com/vuejs/core/commit/30d5d93a92b2154406ec04f8aca6b217fa01177c)), closes [#5300](https://github.com/vuejs/core/issues/5300)
* **types:** expose emits as props in functional components ([#9234](https://github.com/vuejs/core/issues/9234)) ([887e54c](https://github.com/vuejs/core/commit/887e54c347ea9eac4c721b5e2288f054873d1d30))
* **types:** fix reactive collection types ([#8960](https://github.com/vuejs/core/issues/8960)) ([ad27473](https://github.com/vuejs/core/commit/ad274737015c36906d76f3189203093fa3a2e4e7)), closes [#8904](https://github.com/vuejs/core/issues/8904)
* **types:** improve return type withKeys and withModifiers ([#9734](https://github.com/vuejs/core/issues/9734)) ([43c3cfd](https://github.com/vuejs/core/commit/43c3cfdec5ae5d70fa2a21e857abc2d73f1a0d07))
### Performance Improvements
* optimize on* prop check ([38aaa8c](https://github.com/vuejs/core/commit/38aaa8c88648c54fe2616ad9c0961288092fcb44))
* **runtime-dom:** cache modifier wrapper functions ([da4a4fb](https://github.com/vuejs/core/commit/da4a4fb5e8eee3c6d31f24ebd79a9d0feca56cb2)), closes [#8882](https://github.com/vuejs/core/issues/8882)
* **v-on:** constant handlers with modifiers should not be treated as dynamic ([4d94ebf](https://github.com/vuejs/core/commit/4d94ebfe75174b340d2b794e699cad1add3600a9))
## [3.3.9](https://github.com/vuejs/core/compare/v3.3.8...v3.3.9) (2023-11-25)
### Bug Fixes
* **compiler-core:** avoid rewriting scope variables in inline for loops ([#7245](https://github.com/vuejs/core/issues/7245)) ([a2d810e](https://github.com/vuejs/core/commit/a2d810eb40cef631f61991ca68b426ee9546aba0)), closes [#7238](https://github.com/vuejs/core/issues/7238)
* **compiler-core:** fix `resolveParserPlugins` decorators check ([#9566](https://github.com/vuejs/core/issues/9566)) ([9d0eba9](https://github.com/vuejs/core/commit/9d0eba916f3bf6fb5c03222400edae1a2db7444f)), closes [#9560](https://github.com/vuejs/core/issues/9560)
* **compiler-sfc:** consistently escape type-only prop names ([#8654](https://github.com/vuejs/core/issues/8654)) ([3e08d24](https://github.com/vuejs/core/commit/3e08d246dfd8523c54fb8e7a4a6fd5506ffb1bcc)), closes [#8635](https://github.com/vuejs/core/issues/8635) [#8910](https://github.com/vuejs/core/issues/8910) [vitejs/vite-plugin-vue#184](https://github.com/vitejs/vite-plugin-vue/issues/184)
* **compiler-sfc:** malformed filename on windows using path.posix.join() ([#9478](https://github.com/vuejs/core/issues/9478)) ([f18a174](https://github.com/vuejs/core/commit/f18a174979626b3429db93c5d5b7ae5448917c70)), closes [#8671](https://github.com/vuejs/core/issues/8671) [#9583](https://github.com/vuejs/core/issues/9583) [#9446](https://github.com/vuejs/core/issues/9446) [#9473](https://github.com/vuejs/core/issues/9473)
* **compiler-sfc:** support `:is` and `:where` selector in scoped css rewrite ([#8929](https://github.com/vuejs/core/issues/8929)) ([3227e50](https://github.com/vuejs/core/commit/3227e50b32105f8893f7dff2f29278c5b3a9f621))
* **compiler-sfc:** support resolve extends interface for defineEmits ([#8470](https://github.com/vuejs/core/issues/8470)) ([9e1b74b](https://github.com/vuejs/core/commit/9e1b74bcd5fa4151f5d1bc02c69fbbfa4762f577)), closes [#8465](https://github.com/vuejs/core/issues/8465)
* **hmr/transition:** fix kept-alive component inside transition disappearing after hmr ([#7126](https://github.com/vuejs/core/issues/7126)) ([d11e978](https://github.com/vuejs/core/commit/d11e978fc98dcc83526c167e603b8308f317f786)), closes [#7121](https://github.com/vuejs/core/issues/7121)
* **hydration:** force hydration for v-bind with .prop modifier ([364f319](https://github.com/vuejs/core/commit/364f319d214226770d97c98d8fcada80c9e8dde3)), closes [#7490](https://github.com/vuejs/core/issues/7490)
* **hydration:** properly hydrate indeterminate prop ([34b5a5d](https://github.com/vuejs/core/commit/34b5a5da4ae9c9faccac237acd7acc8e7e017571)), closes [#7476](https://github.com/vuejs/core/issues/7476)
* **reactivity:** clear method on readonly collections should return undefined ([#7316](https://github.com/vuejs/core/issues/7316)) ([657476d](https://github.com/vuejs/core/commit/657476dcdb964be4fbb1277c215c073f3275728e))
* **reactivity:** onCleanup also needs to be cleaned ([#8655](https://github.com/vuejs/core/issues/8655)) ([73fd810](https://github.com/vuejs/core/commit/73fd810eebdd383a2b4629f67736c4db1f428abd)), closes [#5151](https://github.com/vuejs/core/issues/5151) [#7695](https://github.com/vuejs/core/issues/7695)
* **ssr:** hydration `__vnode` missing for devtools ([#9328](https://github.com/vuejs/core/issues/9328)) ([5156ac5](https://github.com/vuejs/core/commit/5156ac5b38cfa80d3db26f2c9bf40cb22a7521cb))
* **types:** allow falsy value types in `StyleValue` ([#7954](https://github.com/vuejs/core/issues/7954)) ([17aa92b](https://github.com/vuejs/core/commit/17aa92b79b31d8bb8b5873ddc599420cb9806db8)), closes [#7955](https://github.com/vuejs/core/issues/7955)
* **types:** defineCustomElement using defineComponent return type with emits ([#7937](https://github.com/vuejs/core/issues/7937)) ([5d932a8](https://github.com/vuejs/core/commit/5d932a8e6d14343c9d7fc7c2ecb58ac618b2f938)), closes [#7782](https://github.com/vuejs/core/issues/7782)
* **types:** fix `unref` and `toValue` when input union type contains ComputedRef ([#8748](https://github.com/vuejs/core/issues/8748)) ([176d476](https://github.com/vuejs/core/commit/176d47671271b1abc21b1508e9a493c7efca6451)), closes [#8747](https://github.com/vuejs/core/issues/8747) [#8857](https://github.com/vuejs/core/issues/8857)
* **types:** fix instance type when props type is incompatible with setup returned type ([#7338](https://github.com/vuejs/core/issues/7338)) ([0e1e8f9](https://github.com/vuejs/core/commit/0e1e8f919e5a74cdaadf9c80ee135088b25e7fa3)), closes [#5885](https://github.com/vuejs/core/issues/5885)
* **types:** fix shallowRef return type with union value type ([#7853](https://github.com/vuejs/core/issues/7853)) ([7c44800](https://github.com/vuejs/core/commit/7c448000b0def910c2cfabfdf7ff20a3d6bc844f)), closes [#7852](https://github.com/vuejs/core/issues/7852)
* **types:** more precise types for class bindings ([#8012](https://github.com/vuejs/core/issues/8012)) ([46e3374](https://github.com/vuejs/core/commit/46e33744c890bd49482c5e5c5cdea44e00ec84d5))
* **types:** remove optional properties from defineProps return type ([#6421](https://github.com/vuejs/core/issues/6421)) ([94c049d](https://github.com/vuejs/core/commit/94c049d930d922069e38ea8700d7ff0970f71e61)), closes [#6420](https://github.com/vuejs/core/issues/6420)
* **types:** return type of withDefaults should be readonly ([#8601](https://github.com/vuejs/core/issues/8601)) ([f15debc](https://github.com/vuejs/core/commit/f15debc01acb22d23f5acee97e6f02db88cef11a))
* **types:** revert class type restrictions ([5d077c8](https://github.com/vuejs/core/commit/5d077c8754cc14f85d2d6d386df70cf8c0d93842)), closes [#8012](https://github.com/vuejs/core/issues/8012)
* **types:** update jsx type definitions ([#8607](https://github.com/vuejs/core/issues/8607)) ([58e2a94](https://github.com/vuejs/core/commit/58e2a94871ae06a909c5f8bad07fb401193e6a38))
* **types:** widen ClassValue type ([2424013](https://github.com/vuejs/core/commit/242401305944422d0c361b16101a4d18908927af))
* **v-model:** avoid overwriting number input with same value ([#7004](https://github.com/vuejs/core/issues/7004)) ([40f4b77](https://github.com/vuejs/core/commit/40f4b77bb570868cb6e47791078767797e465989)), closes [#7003](https://github.com/vuejs/core/issues/7003)
* **v-model:** unnecessary value binding error should apply to dynamic instead of static binding ([2859b65](https://github.com/vuejs/core/commit/2859b653c9a22460e60233cac10fe139e359b046)), closes [#3596](https://github.com/vuejs/core/issues/3596)
## [3.3.8](https://github.com/vuejs/core/compare/v3.3.7...v3.3.8) (2023-11-06)
### Bug Fixes
* **compile-sfc:** support `Error` type in `defineProps` ([#5955](https://github.com/vuejs/core/issues/5955)) ([a989345](https://github.com/vuejs/core/commit/a9893458ec519aae442e1b99e64e6d74685cd22c))
* **compiler-core:** known global should be shadowed by local variables in expression rewrite ([#9492](https://github.com/vuejs/core/issues/9492)) ([a75d1c5](https://github.com/vuejs/core/commit/a75d1c5c6242e91a73cc5ba01e6da620dea0b3d9)), closes [#9482](https://github.com/vuejs/core/issues/9482)
* **compiler-sfc:** fix dynamic directive arguments usage check for slots ([#9495](https://github.com/vuejs/core/issues/9495)) ([b39fa1f](https://github.com/vuejs/core/commit/b39fa1f8157647859331ce439c42ae016a49b415)), closes [#9493](https://github.com/vuejs/core/issues/9493)
* **deps:** update dependency @vue/repl to ^2.6.2 ([#9536](https://github.com/vuejs/core/issues/9536)) ([5cef325](https://github.com/vuejs/core/commit/5cef325f41e3b38657c72fa1a38dedeee1c7a60a))
* **deps:** update dependency @vue/repl to ^2.6.3 ([#9540](https://github.com/vuejs/core/issues/9540)) ([176d590](https://github.com/vuejs/core/commit/176d59058c9aecffe9da4d4311e98496684f06d4))
* **hydration:** fix tagName access error on comment/text node hydration mismatch ([dd8a0cf](https://github.com/vuejs/core/commit/dd8a0cf5dcde13d2cbd899262a0e07f16e14e489)), closes [#9531](https://github.com/vuejs/core/issues/9531)
* **types:** avoid exposing lru-cache types in generated dts ([462aeb3](https://github.com/vuejs/core/commit/462aeb3b600765e219ded2ee9a0ed1e74df61de0)), closes [#9521](https://github.com/vuejs/core/issues/9521)
* **warn:** avoid warning on empty children with Suspense ([#3962](https://github.com/vuejs/core/issues/3962)) ([405f345](https://github.com/vuejs/core/commit/405f34587a63a5f1e3d147b9848219ea98acc22d))
## [3.3.7](https://github.com/vuejs/core/compare/v3.3.6...v3.3.7) (2023-10-24)
### Bug Fixes
* **compiler-sfc:** avoid gen useCssVars when targeting SSR ([#6979](https://github.com/vuejs/core/issues/6979)) ([c568778](https://github.com/vuejs/core/commit/c568778ea3265d8e57f788b00864c9509bf88a4e)), closes [#6926](https://github.com/vuejs/core/issues/6926)
* **compiler-ssr:** proper scope analysis for ssr vnode slot fallback ([#7184](https://github.com/vuejs/core/issues/7184)) ([e09c26b](https://github.com/vuejs/core/commit/e09c26bc9bc4394c2c2d928806d382515c2676f3)), closes [#7095](https://github.com/vuejs/core/issues/7095)
* correctly resolve types from relative paths on Windows ([#9446](https://github.com/vuejs/core/issues/9446)) ([089d36d](https://github.com/vuejs/core/commit/089d36d167dc7834065b03ca689f9b6a44eead8a)), closes [#8671](https://github.com/vuejs/core/issues/8671)
* **hmr:** fix hmr error for hoisted children array in v-for ([7334376](https://github.com/vuejs/core/commit/733437691f70ebca8dd6cc3bc8356f5b57d4d5d8)), closes [#6978](https://github.com/vuejs/core/issues/6978) [#7114](https://github.com/vuejs/core/issues/7114)
* **reactivity:** assigning array.length while observing a symbol property ([#7568](https://github.com/vuejs/core/issues/7568)) ([e9e2778](https://github.com/vuejs/core/commit/e9e2778e9ec5cca07c1df5f0c9b7b3595a1a3244))
* **scheduler:** ensure jobs are in the correct order ([#7748](https://github.com/vuejs/core/issues/7748)) ([a8f6638](https://github.com/vuejs/core/commit/a8f663867b8cd2736b82204bc58756ef02441276)), closes [#7576](https://github.com/vuejs/core/issues/7576)
* **ssr:** fix hydration mismatch for disabled teleport at component root ([#9399](https://github.com/vuejs/core/issues/9399)) ([d8990fc](https://github.com/vuejs/core/commit/d8990fc6182d1c2cf0a8eab7b35a9d04df668507)), closes [#6152](https://github.com/vuejs/core/issues/6152)
* **Suspense:** calling hooks before the transition finishes ([#9388](https://github.com/vuejs/core/issues/9388)) ([00de3e6](https://github.com/vuejs/core/commit/00de3e61ed7a55e7d6c2e1987551d66ad0f909ff)), closes [#5844](https://github.com/vuejs/core/issues/5844) [#5952](https://github.com/vuejs/core/issues/5952)
* **transition/ssr:** make transition appear work with SSR ([#8859](https://github.com/vuejs/core/issues/8859)) ([5ea8a8a](https://github.com/vuejs/core/commit/5ea8a8a4fab4e19a71e123e4d27d051f5e927172)), closes [#6951](https://github.com/vuejs/core/issues/6951)
* **types:** fix ComponentCustomProps augmentation ([#9468](https://github.com/vuejs/core/issues/9468)) ([7374e93](https://github.com/vuejs/core/commit/7374e93f0281f273b90ab5a6724cc47332a01d6c)), closes [#8376](https://github.com/vuejs/core/issues/8376)
* **types:** improve `h` overload to support union of string and component ([#5432](https://github.com/vuejs/core/issues/5432)) ([16ecb44](https://github.com/vuejs/core/commit/16ecb44c89cd8299a3b8de33cccc2e2cc36f065b)), closes [#5431](https://github.com/vuejs/core/issues/5431)
## [3.3.6](https://github.com/vuejs/core/compare/v3.3.5...v3.3.6) (2023-10-20)
### Bug Fixes
* **compiler-sfc:** model name conflict ([#8798](https://github.com/vuejs/core/issues/8798)) ([df81da8](https://github.com/vuejs/core/commit/df81da8be97c8a1366563c7e3e01076ef02eb8f7))
* **compiler-sfc:** support asset paths containing spaces ([#8752](https://github.com/vuejs/core/issues/8752)) ([36c99a9](https://github.com/vuejs/core/commit/36c99a9c6bb6bc306be054c3c8a85ff8ce50605a))
* **compiler-ssr:** fix missing scopeId on server-rendered TransitionGroup ([#7557](https://github.com/vuejs/core/issues/7557)) ([61c1357](https://github.com/vuejs/core/commit/61c135742795aa5e3189a79c7dec6afa21bbc8d9)), closes [#7554](https://github.com/vuejs/core/issues/7554)
* **compiler-ssr:** fix ssr compile error for select with non-option children ([#9442](https://github.com/vuejs/core/issues/9442)) ([cdb2e72](https://github.com/vuejs/core/commit/cdb2e725e7ea297f1f4180fb04889a3b757bc84e)), closes [#9440](https://github.com/vuejs/core/issues/9440)
* **runtime-core:** delete stale slots which are present but undefined ([#6484](https://github.com/vuejs/core/issues/6484)) ([75b8722](https://github.com/vuejs/core/commit/75b872213574cb37e2c9e8a15f65613f867ca9a6)), closes [#9109](https://github.com/vuejs/core/issues/9109)
* **runtime-core:** fix error when using cssvars with disabled teleport ([#7341](https://github.com/vuejs/core/issues/7341)) ([8f0472c](https://github.com/vuejs/core/commit/8f0472c9abedb337dc256143b69d8ab8759dbf5c)), closes [#7342](https://github.com/vuejs/core/issues/7342)
* **teleport:** ensure descendent component would be unmounted correctly ([#6529](https://github.com/vuejs/core/issues/6529)) ([4162311](https://github.com/vuejs/core/commit/4162311efdb0db5ca458542e1604b19efa2fae0e)), closes [#6347](https://github.com/vuejs/core/issues/6347)
* **types:** support contenteditable="plaintext-only" ([#8796](https://github.com/vuejs/core/issues/8796)) ([26ca89e](https://github.com/vuejs/core/commit/26ca89e5cf734fbef81e182050d2a215ec8a437b))
### Performance Improvements
* replace Map/Set with WeakMap/WeakSet ([#8549](https://github.com/vuejs/core/issues/8549)) ([712f96d](https://github.com/vuejs/core/commit/712f96d6ac4d3d984732cba448cb84624daba850))
## [3.3.5](https://github.com/vuejs/core/compare/v3.3.4...v3.3.5) (2023-10-20)
### Bug Fixes
* add isGloballyWhitelisted back, but deprecated ([#8556](https://github.com/vuejs/core/issues/8556)) ([63dfe8e](https://github.com/vuejs/core/commit/63dfe8eab499979bcc2f7829e82464e13899c895)), closes [#8416](https://github.com/vuejs/core/issues/8416)
* **build:** disable useDefineForClassFields in esbuild ([#9252](https://github.com/vuejs/core/issues/9252)) ([6d14fa8](https://github.com/vuejs/core/commit/6d14fa88e85d4c9e264be394ddb37a54ca6738a8))
* **compat:** return value of vue compat set() ([#9377](https://github.com/vuejs/core/issues/9377)) ([e3c2d69](https://github.com/vuejs/core/commit/e3c2d699f694d9500ddee78571172a24f0e3b17a))
* **compiler-sfc:** don't hoist props and emit ([#8535](https://github.com/vuejs/core/issues/8535)) ([24db951](https://github.com/vuejs/core/commit/24db9516d8b4857182ec1a3af86cb7346691679b)), closes [#7805](https://github.com/vuejs/core/issues/7805) [#7812](https://github.com/vuejs/core/issues/7812)
* **compiler-sfc:** don't registerTS when bundling for browsers ([#8582](https://github.com/vuejs/core/issues/8582)) ([6f45f76](https://github.com/vuejs/core/commit/6f45f76df2c43796b35067ef8f8b9a7bca454040))
* **compiler-sfc:** fix using imported ref as template ref during dev ([#7593](https://github.com/vuejs/core/issues/7593)) ([776ebf2](https://github.com/vuejs/core/commit/776ebf25b2e7570e78ac1c148fc45c823c21a542)), closes [#7567](https://github.com/vuejs/core/issues/7567)
* **compiler-sfc:** handle dynamic directive arguments in template usage check ([#8538](https://github.com/vuejs/core/issues/8538)) ([e404a69](https://github.com/vuejs/core/commit/e404a699f48ae5c5a5da947f42679343192158c7)), closes [#8537](https://github.com/vuejs/core/issues/8537)
* **compiler-sfc:** ignore style v-bind in double slash comments ([#5409](https://github.com/vuejs/core/issues/5409)) ([381b497](https://github.com/vuejs/core/commit/381b4977af25ba5392704f72ec6b3f2394d87ae7))
* **compiler-sfc:** pass options directly to stylus ([#3848](https://github.com/vuejs/core/issues/3848)) ([d6446a6](https://github.com/vuejs/core/commit/d6446a6d40774b79045a9ddba7b5fd5201d51450))
* **compiler-sfc:** support resolve multiple re-export /w same source type name ([#8365](https://github.com/vuejs/core/issues/8365)) ([4fa8da8](https://github.com/vuejs/core/commit/4fa8da8576717c619e1e8c04d19038488c75fbea)), closes [#8364](https://github.com/vuejs/core/issues/8364)
* **compiler-sfc:** typo in experimental feature warnings ([#8513](https://github.com/vuejs/core/issues/8513)) ([fd1a3f9](https://github.com/vuejs/core/commit/fd1a3f95990d7c372fa1c0c40c55caca761a33a4))
* **deps:** update dependency monaco-editor to ^0.44.0 ([#9237](https://github.com/vuejs/core/issues/9237)) ([8611874](https://github.com/vuejs/core/commit/8611874e09a827b6491173836c8942284d5de22c))
* **deps:** update playground ([#9154](https://github.com/vuejs/core/issues/9154)) ([c8566a2](https://github.com/vuejs/core/commit/c8566a22b7cf37e6aefab7bad7b97ce2db9fae4c))
* **playground:** fix github button style ([#7722](https://github.com/vuejs/core/issues/7722)) ([5ee992c](https://github.com/vuejs/core/commit/5ee992cfeabc6c4b871980c6057d0ac7140ad2fa))
* **runtime-core:** swap client/server debug labels ([#9089](https://github.com/vuejs/core/issues/9089)) ([8f311c6](https://github.com/vuejs/core/commit/8f311c6f823f6776ca1c49bfbbbf8c7d9dea9cf1))
* **ssr:** render correct initial selected state for select with v-model ([#7432](https://github.com/vuejs/core/issues/7432)) ([201c46d](https://github.com/vuejs/core/commit/201c46df07a38f3c2b73f384e8e6846dc62f224e)), closes [#7392](https://github.com/vuejs/core/issues/7392)
* **ssr:** reset current instance if setting up options component errors ([#7743](https://github.com/vuejs/core/issues/7743)) ([020851e](https://github.com/vuejs/core/commit/020851e57d9a9f727c6ea07e9c1575430af02b73)), closes [#7733](https://github.com/vuejs/core/issues/7733)
* **teleport:** handle target change while disabled ([#7837](https://github.com/vuejs/core/issues/7837)) ([140a89b](https://github.com/vuejs/core/commit/140a89b833bceed60838182b875d2953c70af114)), closes [#7835](https://github.com/vuejs/core/issues/7835)
* **transition:** handle possible auto value for transition/animation durations ([96c76fa](https://github.com/vuejs/core/commit/96c76facb7de37fc241ccd55e121fd60a49a1452)), closes [#8409](https://github.com/vuejs/core/issues/8409)
* **types/jsx:** add `inert` attribute and missing `hidden` values ([#8090](https://github.com/vuejs/core/issues/8090)) ([ceb0732](https://github.com/vuejs/core/commit/ceb0732e0b1bb4c8c505d80e97ff6fc89035fa90))
* **types/jsx:** add missing loading attr for img element ([#6160](https://github.com/vuejs/core/issues/6160)) ([68d6b43](https://github.com/vuejs/core/commit/68d6b43f7e29b76aab2c6c1882885380a43fa3e3))
* **types:** correct withDefaults return type for boolean prop with undefined default value ([#8602](https://github.com/vuejs/core/issues/8602)) ([f07cb18](https://github.com/vuejs/core/commit/f07cb18fedf9a446545aadf76bcdfb957c7ebcbd))
* **types:** ensure nextTick return type reflect correct Promise value ([#8406](https://github.com/vuejs/core/issues/8406)) ([6a22b1f](https://github.com/vuejs/core/commit/6a22b1f6c287b60eda385df8a514335af8e040ea))
* **types:** support correct types for style on svg elements ([#6322](https://github.com/vuejs/core/issues/6322)) ([364dc53](https://github.com/vuejs/core/commit/364dc53c7cc6f97d812ad175199c698faa92538e))
### Performance Improvements
* **compiler-sfc:** lazy require typescript ([d2c3d8b](https://github.com/vuejs/core/commit/d2c3d8b70b2df6e16f053a7ac58e6b04e7b2078f))
* **custom-element:** cancel `MutationObserver` listener when disconnected ([#8666](https://github.com/vuejs/core/issues/8666)) ([24d98f0](https://github.com/vuejs/core/commit/24d98f03276de5b0fbced5a4c9d61b24e7d9d084))
* mark `defineComponent` as side-effects-free ([#8512](https://github.com/vuejs/core/issues/8512)) ([438027c](https://github.com/vuejs/core/commit/438027cf9ecb63260f59d3027e0b188717694795))
## [3.3.4](https://github.com/vuejs/core/compare/v3.3.3...v3.3.4) (2023-05-18)
### Bug Fixes
* **build:** ensure correct typing for node esm ([d621d4c](https://github.com/vuejs/core/commit/d621d4c646b2d7b190fbd44ad1fd04512b3de300))
* **build:** fix __DEV__ flag replacement edge case ([8b7c04b](https://github.com/vuejs/core/commit/8b7c04b18f73aad9a08dd57eba90101b5b2aef28)), closes [#8353](https://github.com/vuejs/core/issues/8353)
* **compiler-sfc:** handle imported types from default exports ([5aec717](https://github.com/vuejs/core/commit/5aec717a2402652306085f58432ba3ab91848a74)), closes [#8355](https://github.com/vuejs/core/issues/8355)
## [3.3.3](https://github.com/vuejs/core/compare/v3.3.2...v3.3.3) (2023-05-18)
### Bug Fixes
* avoid regex s flag for old browsers ([91f1c62](https://github.com/vuejs/core/commit/91f1c62e6384a8b09f90e7e43b8d347901e529a0)), closes [#8316](https://github.com/vuejs/core/issues/8316)
* **build:** fix dev flag replacement in esm-builder builds ([#8314](https://github.com/vuejs/core/issues/8314)) ([003836f](https://github.com/vuejs/core/commit/003836f90e1f00ebd04b77ec07ccfa4e649a2ff4)), closes [#8312](https://github.com/vuejs/core/issues/8312)
* **compiler-sfc:** don't hoist regexp literial ([#8300](https://github.com/vuejs/core/issues/8300)) ([8ec73a3](https://github.com/vuejs/core/commit/8ec73a3aea7a52e9479f107ae5737761166ddae6))
* **compiler-sfc:** fix props destructing default value type checking with unresolved type ([#8340](https://github.com/vuejs/core/issues/8340)) ([f69dbab](https://github.com/vuejs/core/commit/f69dbabf8794426c3e9ed33ae77dd8ce655eafd2)), closes [#8326](https://github.com/vuejs/core/issues/8326)
* **compiler-sfc:** fix type import from path aliased vue file ([fab9c72](https://github.com/vuejs/core/commit/fab9c727805c6186c490f99023e8cf5401b0b5a9)), closes [#8348](https://github.com/vuejs/core/issues/8348)
* **compiler-sfc:** handle ts files with relative imports with .js extension ([b36addd](https://github.com/vuejs/core/commit/b36addd3bde07467e9ff5641bd1c2bdc3085944c)), closes [#8339](https://github.com/vuejs/core/issues/8339)
* **compiler-sfc:** parses correctly when inline mode is off ([#8337](https://github.com/vuejs/core/issues/8337)) ([ecbd42a](https://github.com/vuejs/core/commit/ecbd42a1444e3c599e464dec002e43d548d99669)), closes [#6088](https://github.com/vuejs/core/issues/6088)
* **compiler-sfc:** support defineEmits type reference with unions ([#8299](https://github.com/vuejs/core/issues/8299)) ([b133e0f](https://github.com/vuejs/core/commit/b133e0fd97b0b4fabbb43151c19031b8fb47c05b)), closes [#7943](https://github.com/vuejs/core/issues/7943)
* **types:** support generic usage with withDefaults + defineProps ([#8335](https://github.com/vuejs/core/issues/8335)) ([216f269](https://github.com/vuejs/core/commit/216f26995b63c2df26ca0f39f390fe8d59cdabfa)), closes [#8310](https://github.com/vuejs/core/issues/8310) [#8331](https://github.com/vuejs/core/issues/8331) [#8325](https://github.com/vuejs/core/issues/8325)
## [3.3.2](https://github.com/vuejs/core/compare/v3.3.1...v3.3.2) (2023-05-12)
### Bug Fixes
* **compiler-core:** treat floating point numbers as constants ([8dc8cf8](https://github.com/vuejs/core/commit/8dc8cf852bf8057aa5c4b5670f09e8c28a168b73)), closes [#8295](https://github.com/vuejs/core/issues/8295)
* **compiler-dom:** do not throw in production on side effect tags ([c454b9d](https://github.com/vuejs/core/commit/c454b9d7f431d57abedb7184d1e4059914c4463f)), closes [#8287](https://github.com/vuejs/core/issues/8287) [#8292](https://github.com/vuejs/core/issues/8292)
* **compiler-sfc:** fix regression on props destructure when transform is not enabled ([f25bd37](https://github.com/vuejs/core/commit/f25bd37c6707fde19d164d90a38de41168941f4b)), closes [#8289](https://github.com/vuejs/core/issues/8289)
* **compiler-sfc:** handle prop keys that need escaping ([#7803](https://github.com/vuejs/core/issues/7803)) ([690ef29](https://github.com/vuejs/core/commit/690ef296357c7fc09f66ba9408df548e117f686f)), closes [#8291](https://github.com/vuejs/core/issues/8291)
* **compiler-sfc:** properly parse d.ts files when resolving types ([aa1e77d](https://github.com/vuejs/core/commit/aa1e77d532b951ea5d3a5e26214a8b0c9c02fb6f)), closes [#8285](https://github.com/vuejs/core/issues/8285)
* **compiler-sfc:** raise specific warning for failed extends and allow ignoring extends ([8235072](https://github.com/vuejs/core/commit/82350721a408e1f552c613c05971439d6c218d87)), closes [#8286](https://github.com/vuejs/core/issues/8286)
## [3.3.1](https://github.com/vuejs/core/compare/v3.3.0...v3.3.1) (2023-05-11)
### Bug Fixes
* **suspense:** handle nested sync suspense for hydration ([a3f5485](https://github.com/vuejs/core/commit/a3f54857858c8ca0e6b9f12618d151ab255fb040))
# [3.3.0 Rurouni Kenshin](https://github.com/vuejs/core/compare/v3.3.0-beta.5...v3.3.0) (2023-05-11)
- For a detailed walkthrough of the new features in 3.3, please read the [release blog post](https://blog.vuejs.org/posts/vue-3-3).
- Features and deprecations listed here are aggregated from the beta and alpha releases. For full chronological history, bug fixes, and other minor features, please consult the individual logs of the 3.3 beta and alpha releases.
## Features
* **sfc:** support imported types in SFC macros ([#8083](https://github.com/vuejs/core/pull/8083))
* **types/slots:** support slot presence / props type checks via `defineSlots` macro and `slots` option ([#7982](https://github.com/vuejs/core/issues/7982)) ([5a2f5d5](https://github.com/vuejs/core/commit/5a2f5d59cffa36a99e6f2feab6b3ba7958b7362f))
* **sfc:** support more ergnomic defineEmits type syntax ([#7992](https://github.com/vuejs/core/issues/7992)) ([8876dcc](https://github.com/vuejs/core/commit/8876dccf42a7f05375d97cb18c1afdfd0fc51c94))
* **sfc:** introduce `defineModel` macro and `useModel` helper ([#8018](https://github.com/vuejs/core/issues/8018)) ([14f3d74](https://github.com/vuejs/core/commit/14f3d747a34d45415b0036b274517d70a27ec0d3))
* **reactivity:** improve support of getter usage in reactivity APIs ([#7997](https://github.com/vuejs/core/issues/7997)) ([59e8284](https://github.com/vuejs/core/commit/59e828448e7f37643cd0eaea924a764e9d314448))
* **compiler-sfc:** add defineOptions macro ([#5738](https://github.com/vuejs/core/issues/5738)) ([bcf5841](https://github.com/vuejs/core/commit/bcf5841ddecc64d0bdbd56ce1463eb8ebf01bb9d))
* **types/jsx:** support jsxImportSource, avoid global JSX conflict ([#7958](https://github.com/vuejs/core/issues/7958)) ([d0b7ef3](https://github.com/vuejs/core/commit/d0b7ef3b61d5f83e35e5854b3c2c874e23463102))
* **dx:** improve readability of displayed types for props ([4c9bfd2](https://github.com/vuejs/core/commit/4c9bfd2b999ce472f7481aae4f9dc5bb9f76628e))
* **app:** app.runWithContext() ([#7451](https://github.com/vuejs/core/issues/7451)) ([869f3fb](https://github.com/vuejs/core/commit/869f3fb93e61400be4fd925e0850c2b1564749e2))
* hasInjectionContext() for libraries ([#8111](https://github.com/vuejs/core/issues/8111)) ([5510ce3](https://github.com/vuejs/core/commit/5510ce385abfa151c07a5253cccf4abccabdd01d))
* allow accessing console in template ([#6508](https://github.com/vuejs/core/issues/6508)) ([fe76224](https://github.com/vuejs/core/commit/fe762247f8035d28d543bc5602ad01b0c258f6d6)), closes [#7939](https://github.com/vuejs/core/issues/7939)
* **suspense:** introduce suspensible option for `<Suspense>` ([#6736](https://github.com/vuejs/core/issues/6736)) ([cb37d0b](https://github.com/vuejs/core/commit/cb37d0b9ffb5d4bb81a0367d84295dec8dd4448c)), closes [#5513](https://github.com/vuejs/core/issues/5513)
* **compiler-dom:** treat inert as boolean attribute ([#8209](https://github.com/vuejs/core/issues/8209)) ([918ec8a](https://github.com/vuejs/core/commit/918ec8a5cbc825a3947cd35fe966671c245af087)), closes [#8208](https://github.com/vuejs/core/issues/8208)
* **types:** add slots types for built-in components ([#6033](https://github.com/vuejs/core/issues/6033)) ([3cb4dc9](https://github.com/vuejs/core/commit/3cb4dc9e5538e1c2bde9fa691b001615a848c546))
* **types:** provide ExtractPublicPropTypes utility type ([bff63c5](https://github.com/vuejs/core/commit/bff63c5498f5fa098689c18defe48ae08d47eadb)), closes [#5272](https://github.com/vuejs/core/issues/5272) [#8168](https://github.com/vuejs/core/issues/8168)
* **compiler-sfc:** expose parseCache ([4576548](https://github.com/vuejs/core/commit/45765488d498d94f8760c9e82f1177070057b17c)), closes [#8202](https://github.com/vuejs/core/issues/8202)
## Deprecations
* **deprecation:** deprecate [@vnode](https://github.com/vnode) hooks in favor of vue: prefix ([5f0394a](https://github.com/vuejs/core/commit/5f0394a5ab88c82c74e240161499721f63d5462e))
* **deprecation:** deprecate v-is directive ([bbd8301](https://github.com/vuejs/core/commit/bbd8301a1344b02de635ea16d4822db1c343bd12))
* **deprecation:** unwrap injected refs in Options API by default, deprecate app.config.unwrapInjectedRefs ([526fa3b](https://github.com/vuejs/core/commit/526fa3b2ccf038375e76f8af2f1ddf79a7388878))
# [3.3.0-beta.5](https://github.com/vuejs/core/compare/v3.3.0-beta.4...v3.3.0-beta.5) (2023-05-08)
### Bug Fixes
* **build:** retain defineComponent() treeshakability in Rollup ([c2172f3](https://github.com/vuejs/core/commit/c2172f3a0ebbd7153e209dd8df6d9724bc524d9a)), closes [#8236](https://github.com/vuejs/core/issues/8236)
* **compiler-sfc:** enable props destructure when reactivity transform option is enabled ([862edfd](https://github.com/vuejs/core/commit/862edfd91a2c2f6b75f943cb1a9682c4be5d7fa8))
* **compiler-sfc:** fix built-in type resolving in external files ([6b194bc](https://github.com/vuejs/core/commit/6b194bcf3b8143895c2a472cd87998ebf9856146)), closes [#8244](https://github.com/vuejs/core/issues/8244)
* **compiler-sfc:** transform destructured props when reactivity transform option is enabled ([#8252](https://github.com/vuejs/core/issues/8252)) ([287bd99](https://github.com/vuejs/core/commit/287bd999942e58925377f50540c7134cff2a9279))
* **runtime-core:** ensure defineComponent name in extraOptions takes higher priority ([b2be75b](https://github.com/vuejs/core/commit/b2be75bad4ba70da1da6930eb914e51ce2c630b2))
* **runtime-dom:** check attribute value when setting option value ([#8246](https://github.com/vuejs/core/issues/8246)) ([4495373](https://github.com/vuejs/core/commit/4495373d28d9fa4479eedd224adb16248ae0b9f4)), closes [#8227](https://github.com/vuejs/core/issues/8227)
* **suspense:** fix nested suspensible suspense with no asyn deps ([e147512](https://github.com/vuejs/core/commit/e1475129fc6f8c086c2ec667476900b8c8f46774)), closes [#8206](https://github.com/vuejs/core/issues/8206)
* **types:** remove short syntax support in defineSlots() ([1279b17](https://github.com/vuejs/core/commit/1279b1730079f77692a0817d51bbba57eb2b871b))
# [3.3.0-beta.4](https://github.com/vuejs/core/compare/v3.3.0-beta.3...v3.3.0-beta.4) (2023-05-05)
### Bug Fixes
* **runtime-core:** handle template ref with number values ([#8233](https://github.com/vuejs/core/issues/8233)) ([1b1242f](https://github.com/vuejs/core/commit/1b1242f4d1349e361335b2815f41742d41283a94)), closes [#8230](https://github.com/vuejs/core/issues/8230)
* **types:** retain compatibility for provide() usage with explicit type parameter ([038cd83](https://github.com/vuejs/core/commit/038cd830d5b34b47d7e7e1c61f0973d27cd8b915))
### Features
* **compiler-dom:** treat inert as boolean attribute ([#8209](https://github.com/vuejs/core/issues/8209)) ([918ec8a](https://github.com/vuejs/core/commit/918ec8a5cbc825a3947cd35fe966671c245af087)), closes [#8208](https://github.com/vuejs/core/issues/8208)
* **types:** add slots types for built-in components ([#6033](https://github.com/vuejs/core/issues/6033)) ([3cb4dc9](https://github.com/vuejs/core/commit/3cb4dc9e5538e1c2bde9fa691b001615a848c546))
* **types:** provide ExtractPublicPropTypes utility type ([bff63c5](https://github.com/vuejs/core/commit/bff63c5498f5fa098689c18defe48ae08d47eadb)), closes [#5272](https://github.com/vuejs/core/issues/5272) [#8168](https://github.com/vuejs/core/issues/8168)
# [3.3.0-beta.3](https://github.com/vuejs/core/compare/v3.3.0-beta.2...v3.3.0-beta.3) (2023-05-01)
### Bug Fixes
* **compiler-core:** handle slot argument parsing edge case ([b434d12](https://github.com/vuejs/core/commit/b434d12bf6cbd49a7c99b1646d9517d8393ea49f))
* **hmr:** keep slots proxy mutable for hmr ([c117d9c](https://github.com/vuejs/core/commit/c117d9c257820481b85304db26ce5c77af5d050c)), closes [#8188](https://github.com/vuejs/core/issues/8188)
* **types:** fix provide type checking for ref value ([de87e6e](https://github.com/vuejs/core/commit/de87e6e405dfaf9a917d7eb423fcee35237c2020)), closes [#8201](https://github.com/vuejs/core/issues/8201)
### Features
* **compiler-sfc:** expose parseCache ([4576548](https://github.com/vuejs/core/commit/45765488d498d94f8760c9e82f1177070057b17c)), closes [#8202](https://github.com/vuejs/core/issues/8202)
# [3.3.0-beta.2](https://github.com/vuejs/core/compare/v3.3.0-beta.1...v3.3.0-beta.2) (2023-04-25)
### Bug Fixes
* **compiler-sfc:** avoid all hard errors when inferring runtime type ([2d9f6f9](https://github.com/vuejs/core/commit/2d9f6f926453c46f542789927bcd30d15da9c24b))
* **compiler-sfc:** normalize windows paths when resolving types ([#8136](https://github.com/vuejs/core/issues/8136)) ([29da504](https://github.com/vuejs/core/commit/29da50468770fcee16ba5d5bec7166dd5bc120ee))
* **compiler-sfc:** props bindings should not override user declared bindings ([433a58c](https://github.com/vuejs/core/commit/433a58ccb61c25512dcc3df155b8e285256917ef)), closes [#8148](https://github.com/vuejs/core/issues/8148)
### Features
* **compiler-sfc:** support project references when resolving types ([1c0be5c](https://github.com/vuejs/core/commit/1c0be5c7444966fa444460e87633cf44ec60292a)), closes [#8140](https://github.com/vuejs/core/issues/8140)
### Performance Improvements
* **compiler-sfc:** infer ref binding type for more built-in methods ([a370e80](https://github.com/vuejs/core/commit/a370e8006a70ea49a7d04c8c1a42d0947eba5dea))
# [3.3.0-beta.1](https://github.com/vuejs/core/compare/v3.3.0-alpha.13...v3.3.0-beta.1) (2023-04-21)
### Features
* allow accessing console in template ([#6508](https://github.com/vuejs/core/issues/6508)) ([fe76224](https://github.com/vuejs/core/commit/fe762247f8035d28d543bc5602ad01b0c258f6d6)), closes [#7939](https://github.com/vuejs/core/issues/7939)
* **compiler-sfc:** improve utility type Partial and Required ([#8103](https://github.com/vuejs/core/issues/8103)) ([1d1d728](https://github.com/vuejs/core/commit/1d1d72894995fde14bd09e2990462c19d5176bf9))
* **deprecation:** deprecate [@vnode](https://github.com/vnode) hooks in favor of vue: prefix ([5f0394a](https://github.com/vuejs/core/commit/5f0394a5ab88c82c74e240161499721f63d5462e))
* **deprecation:** deprecate v-is directive ([bbd8301](https://github.com/vuejs/core/commit/bbd8301a1344b02de635ea16d4822db1c343bd12))
* **deprecation:** unwrap injected refs in Options API by default, deprecate app.config.unwrapInjectedRefs ([526fa3b](https://github.com/vuejs/core/commit/526fa3b2ccf038375e76f8af2f1ddf79a7388878))
* **suspense:** introduce suspensible option for `<Suspense>` ([#6736](https://github.com/vuejs/core/issues/6736)) ([cb37d0b](https://github.com/vuejs/core/commit/cb37d0b9ffb5d4bb81a0367d84295dec8dd4448c)), closes [#5513](https://github.com/vuejs/core/issues/5513)
# [3.3.0-alpha.13](https://github.com/vuejs/core/compare/v3.3.0-alpha.12...v3.3.0-alpha.13) (2023-04-20)
### Bug Fixes
* **compiler-sfc:** handle type merging + fix namespace access when inferring type ([d53e157](https://github.com/vuejs/core/commit/d53e157805678db7a3b9ca2fccc74530e1dfbc48)), closes [#8102](https://github.com/vuejs/core/issues/8102)
* **compiler-sfc:** normalize filename when invalidating cache ([9b5a34b](https://github.com/vuejs/core/commit/9b5a34bf8c0d1b4c6ec3cf1434076b7e25065f84))
* **hmr:** always traverse static children in dev ([f17a82c](https://github.com/vuejs/core/commit/f17a82c769cfb60ee6785ef5d34d91191d153542)), closes [#7921](https://github.com/vuejs/core/issues/7921) [#8100](https://github.com/vuejs/core/issues/8100)
* **hmr:** force update cached slots during HMR ([94fa67a](https://github.com/vuejs/core/commit/94fa67a4f73b3646c8c1e29512a71b17bd56efc3)), closes [#7155](https://github.com/vuejs/core/issues/7155) [#7158](https://github.com/vuejs/core/issues/7158)
### Features
* **compiler-sfc:** support dynamic imports when resolving types ([4496456](https://github.com/vuejs/core/commit/4496456d7d9947560ef1e35ccb176b97a27bd8f4))
* **compiler-sfc:** support export * when resolving types ([7c3ca3c](https://github.com/vuejs/core/commit/7c3ca3cc3e122fe273e80a950c57d492a7f0bf4a))
* **compiler-sfc:** support ExtractPropTypes when resolving types ([50c0bbe](https://github.com/vuejs/core/commit/50c0bbe5221dbc1dc353d4960e69ec7c8ba12905)), closes [#8104](https://github.com/vuejs/core/issues/8104)
* hasInjectionContext() for libraries ([#8111](https://github.com/vuejs/core/issues/8111)) ([5510ce3](https://github.com/vuejs/core/commit/5510ce385abfa151c07a5253cccf4abccabdd01d))
# [3.3.0-alpha.12](https://github.com/vuejs/core/compare/v3.3.0-alpha.11...v3.3.0-alpha.12) (2023-04-18)
### Bug Fixes
* **compiler:** fix expression codegen for literal const bindings in non-inline mode ([0f77a2b](https://github.com/vuejs/core/commit/0f77a2b1d1047d66ccdfda70382d1a223886130c))
# [3.3.0-alpha.11](https://github.com/vuejs/core/compare/v3.3.0-alpha.10...v3.3.0-alpha.11) (2023-04-17)
### Bug Fixes
* **compiler-sfc:** normalize windows paths when resolving types ([271df09](https://github.com/vuejs/core/commit/271df09470c61d073185ba6cf3cf50358713c500))
# [3.3.0-alpha.10](https://github.com/vuejs/core/compare/v3.3.0-alpha.9...v3.3.0-alpha.10) (2023-04-17)
### Bug Fixes
* **hmr:** invalidate cached props/emits options on hmr ([4b5b384](https://github.com/vuejs/core/commit/4b5b384485cf8f6124f6738b89e3d047358f3a11))
* **runtime-core:** properly merge props and emits options from mixins ([#8052](https://github.com/vuejs/core/issues/8052)) ([c94ef02](https://github.com/vuejs/core/commit/c94ef02421d7422bc59d10cf2eee9f4e7dcea6c8)), closes [#7989](https://github.com/vuejs/core/issues/7989)
### Features
* **compiler-sfc:** expose type import deps on compiled script block ([8d8ddd6](https://github.com/vuejs/core/commit/8d8ddd686c832b2ea29b87ef47666b13c4ad5d4c))
* **compiler-sfc:** expose type resolve APIs ([f22e32e](https://github.com/vuejs/core/commit/f22e32e365bf6292cb606cb7289609e82da8b790))
* **compiler-sfc:** mark props destructure as experimental and require explicit opt-in ([6b13e04](https://github.com/vuejs/core/commit/6b13e04b4c83fcdbb180dc1d59f536a1309c2960))
* **compiler-sfc:** support intersection and union types in macros ([d1f973b](https://github.com/vuejs/core/commit/d1f973bff82581fb335d6fc05623d1ad3d84fb7c)), closes [#7553](https://github.com/vuejs/core/issues/7553)
* **compiler-sfc:** support limited built-in utility types in macros ([1cfab4c](https://github.com/vuejs/core/commit/1cfab4c695b0c28f549f8c97faee5099581792a7))
* **compiler-sfc:** support mapped types, string types & template type in macros ([fb8ecc8](https://github.com/vuejs/core/commit/fb8ecc803e58bfef0971346c63fefc529812daa7))
* **compiler-sfc:** support namespace members type in macros ([5ff40bb](https://github.com/vuejs/core/commit/5ff40bb0dc2918b7db15fe9f49db2a135a925572))
* **compiler-sfc:** support relative imported types in macros ([8aa4ea8](https://github.com/vuejs/core/commit/8aa4ea81d6e4d3110aa1619cca594543da4c9b63))
* **compiler-sfc:** support resolving type imports from modules ([3982bef](https://github.com/vuejs/core/commit/3982bef533b451d1b59fa243560184a13fe8c18c))
* **compiler-sfc:** support specifying global types for sfc macros ([4e028b9](https://github.com/vuejs/core/commit/4e028b966991937c83fb2529973fd3d41080bb61)), closes [/github.com/vuejs/core/pull/8083#issuecomment-1508468713](https://github.com//github.com/vuejs/core/pull/8083/issues/issuecomment-1508468713)
* **compiler-sfc:** support string indexed type in macros ([3f779dd](https://github.com/vuejs/core/commit/3f779ddbf85054c8915fa4537f8a79baab392d5c))
* **compiler-sfc:** support string/number indexed types in macros ([760755f](https://github.com/vuejs/core/commit/760755f4f83680bee13ad546cdab2e48ade38dff))
### Performance Improvements
* **compiler:** use source-map-js ([19e17a9](https://github.com/vuejs/core/commit/19e17a951c3387cbd6a1597e6cd9048a4aad4528))
# [3.3.0-alpha.9](https://github.com/vuejs/core/compare/v3.3.0-alpha.8...v3.3.0-alpha.9) (2023-04-08)
### Bug Fixes
* **compiler-sfc:** accept `StringLiteral` node in `defineEmit` tuple syntax ([#8041](https://github.com/vuejs/core/issues/8041)) ([3ccbea0](https://github.com/vuejs/core/commit/3ccbea08e09217b50a410d7b49ebb138e0c4c1e7)), closes [#8040](https://github.com/vuejs/core/issues/8040)
* **compiler-sfc:** fix binding type for constants when hoistStatic is disabled ([#8029](https://github.com/vuejs/core/issues/8029)) ([f7f4624](https://github.com/vuejs/core/commit/f7f4624191bbdc09600dbb0eb048b947c3a4f761))
* **compiler-sfc:** skip empty `defineOptions` and support TypeScript type assertions ([#8028](https://github.com/vuejs/core/issues/8028)) ([9557529](https://github.com/vuejs/core/commit/955752951e1d31b90d817bd20830fe3f89018771))
* **compiler-ssr:** disable v-once transform in ssr vdom fallback branch ([05f94cf](https://github.com/vuejs/core/commit/05f94cf7b01dd05ed7d3170916a38b175d5df292)), closes [#7644](https://github.com/vuejs/core/issues/7644)
* **types:** improve defineProps return type with generic arguments ([91a931a](https://github.com/vuejs/core/commit/91a931ae8707b8d43f10216e1ce8e18b12158f99))
* **types:** more public type argument order fix ([af563bf](https://github.com/vuejs/core/commit/af563bf428200367b6f5bb7944f690c85d810202))
* **types:** retain type parameters order for public types ([bdf557f](https://github.com/vuejs/core/commit/bdf557f6f233c039fff8007b1b16aec00c4e68aa))
### Features
* **app:** app.runWithContext() ([#7451](https://github.com/vuejs/core/issues/7451)) ([869f3fb](https://github.com/vuejs/core/commit/869f3fb93e61400be4fd925e0850c2b1564749e2))
* **sfc:** introduce `defineModel` macro and `useModel` helper ([#8018](https://github.com/vuejs/core/issues/8018)) ([14f3d74](https://github.com/vuejs/core/commit/14f3d747a34d45415b0036b274517d70a27ec0d3))
### Reverts
* Revert "chore: remove unused args passed to ssrRender" ([b117b88](https://github.com/vuejs/core/commit/b117b8844881a732a021432066230ff2215049ea))
# [3.3.0-alpha.8](https://github.com/vuejs/core/compare/v3.3.0-alpha.7...v3.3.0-alpha.8) (2023-04-04)
### Bug Fixes
* **compiler-sfc:** check binding is prop before erroring ([f3145a9](https://github.com/vuejs/core/commit/f3145a915aaec11c915f1df258c5209ae4782bcc)), closes [#8017](https://github.com/vuejs/core/issues/8017)
# [3.3.0-alpha.7](https://github.com/vuejs/core/compare/v3.3.0-alpha.6...v3.3.0-alpha.7) (2023-04-03)
### Bug Fixes
* **compiler-dom:** handle newlines when evaluating constants during stringification ([#7995](https://github.com/vuejs/core/issues/7995)) ([5261085](https://github.com/vuejs/core/commit/52610851137b9c5f6f57d771fd604fba309b3c97)), closes [#7994](https://github.com/vuejs/core/issues/7994)
* **compiler-sfc:** use dynamic defaults merging for methods with computed keys ([482f2e3](https://github.com/vuejs/core/commit/482f2e3434a1edc47a181890354838e206d08922)), closes [#7113](https://github.com/vuejs/core/issues/7113)
### Features
* **compiler-sfc:** codegen support for defineEmits() short syntax (followup of [#7992](https://github.com/vuejs/core/issues/7992)) ([ef73ea5](https://github.com/vuejs/core/commit/ef73ea53eaf853d43e70946d2d448ae8c0a83e4f))
* **compiler-sfc:** support arbitrary expression as withDefaults argument ([fe61944](https://github.com/vuejs/core/commit/fe619443d2e99301975de120685dbae8d66c03a6)), closes [#6459](https://github.com/vuejs/core/issues/6459)
* **reactivity:** improve support of getter usage in reactivity APIs ([#7997](https://github.com/vuejs/core/issues/7997)) ([59e8284](https://github.com/vuejs/core/commit/59e828448e7f37643cd0eaea924a764e9d314448))
* **sfc:** revert withDefaults() deprecation ([4af5d1b](https://github.com/vuejs/core/commit/4af5d1b0754035058436f9e4e5c12aedef199177))
* **sfc:** support more ergnomic defineEmits type syntax ([#7992](https://github.com/vuejs/core/issues/7992)) ([8876dcc](https://github.com/vuejs/core/commit/8876dccf42a7f05375d97cb18c1afdfd0fc51c94))
* **types/slots:** support slot presence / props type checks via `defineSlots` macro and `slots` option ([#7982](https://github.com/vuejs/core/issues/7982)) ([5a2f5d5](https://github.com/vuejs/core/commit/5a2f5d59cffa36a99e6f2feab6b3ba7958b7362f))
# [3.3.0-alpha.6](https://github.com/vuejs/core/compare/v3.3.0-alpha.5...v3.3.0-alpha.6) (2023-03-30)
### Bug Fixes
* **compiler-core:** check if expression is constant ([#7974](https://github.com/vuejs/core/issues/7974)) ([77686cf](https://github.com/vuejs/core/commit/77686cf4765e7e345bef364c0b03739e3c2da91d)), closes [#7973](https://github.com/vuejs/core/issues/7973)
* **compiler-core:** fix codegen for literal const in non-inline mode ([6bda4b6](https://github.com/vuejs/core/commit/6bda4b66886240b28993c88b7ecd4640a199be65))
* **compiler-sfc:** allow `<script>` with lang='js' ([#7398](https://github.com/vuejs/core/issues/7398)) ([9f5e20c](https://github.com/vuejs/core/commit/9f5e20ccff235946159f0e50519f5be09bc89d5b))
* **compiler-sfc:** avoid codegen conflict with user variable named `expose` ([#7949](https://github.com/vuejs/core/issues/7949)) ([c839129](https://github.com/vuejs/core/commit/c839129ab9d46f56a019b0ff234b35f27cac1e35)), closes [#7890](https://github.com/vuejs/core/issues/7890)
* **compiler-sfc:** disallow `expose` property in `defineOptions` ([#7967](https://github.com/vuejs/core/issues/7967)) ([93f7729](https://github.com/vuejs/core/commit/93f77292c9f2c4dcc83aae6b5943a8b1d7443092))
* **compiler-sfc:** fix defineExpose() codegen regression from [#7949](https://github.com/vuejs/core/issues/7949) ([a94072d](https://github.com/vuejs/core/commit/a94072dd2ca1aca4ce1fbe5da51ca2a9a07a4637))
* **compiler-sfc:** fix edge case of default export call with no args ([#7536](https://github.com/vuejs/core/issues/7536)) ([d60e58c](https://github.com/vuejs/core/commit/d60e58c9f62ae7bd1f9c888cd2e55982a64d9e96)), closes [#7534](https://github.com/vuejs/core/issues/7534)
* **compiler-sfc:** fix function default value handling w/ props destructure ([e10a89e](https://github.com/vuejs/core/commit/e10a89e608d3486c0c9a0457ee7c56d208e0aa91))
* **compiler-sfc:** handle more TS built-in utilities in defineProps inference ([4355d24](https://github.com/vuejs/core/commit/4355d2492dccdb175b18d083e20f3ec39a52801f))
* **compiler-sfc:** infer function prop type from type literal w/ callable signature ([#7119](https://github.com/vuejs/core/issues/7119)) ([3a7572c](https://github.com/vuejs/core/commit/3a7572cdb2074c5cac2231e4525296104141411c))
* **compiler-sfc:** infer object type for empty type literal ([1a04fba](https://github.com/vuejs/core/commit/1a04fba10b6462303c65f1095da86ce05c14f1f4))
* **compiler-sfc:** infer runtime type in defineProps ([#7972](https://github.com/vuejs/core/issues/7972)) ([ba4cec3](https://github.com/vuejs/core/commit/ba4cec31b91da60555892c381b00c2fa5b3e0e39))
* **compiler-sfc:** infer TS Extract&Exclude runtime type ([#7339](https://github.com/vuejs/core/issues/7339)) ([6391daf](https://github.com/vuejs/core/commit/6391daf6586e49e165b3195863b602c3cbb92ace)), closes [#7337](https://github.com/vuejs/core/issues/7337) [#6252](https://github.com/vuejs/core/issues/6252)
* **compiler-sfc:** infer TSIntersectionType in defineProps ([#7394](https://github.com/vuejs/core/issues/7394)) ([151a8ad](https://github.com/vuejs/core/commit/151a8ad6b9288784c3b2514820a112b93ecf2dca))
* **compiler-sfc:** properly handle unknown types in runtime prop inference ([5fb406e](https://github.com/vuejs/core/commit/5fb406e3e1d585076779e9ce4a588a4f9a61ef23)), closes [#7511](https://github.com/vuejs/core/issues/7511)
* **compiler-sfc:** properly remove comma of multiple macros in the same declaration ([#7423](https://github.com/vuejs/core/issues/7423)) ([336a3d7](https://github.com/vuejs/core/commit/336a3d7b9199aeda19672832ce173de298b1105a)), closes [#7422](https://github.com/vuejs/core/issues/7422) [#6778](https://github.com/vuejs/core/issues/6778)
* **compiler-sfc:** rewrite default export with AST analysis instead of regex ([#7068](https://github.com/vuejs/core/issues/7068)) ([701b95f](https://github.com/vuejs/core/commit/701b95ff3d2bad0284c03865a0b052fc8324beec)), closes [#7038](https://github.com/vuejs/core/issues/7038) [#7041](https://github.com/vuejs/core/issues/7041) [#7078](https://github.com/vuejs/core/issues/7078)
* **compiler-sfc:** unwrap TS node for defineProps ([#7340](https://github.com/vuejs/core/issues/7340)) ([1b69d5f](https://github.com/vuejs/core/commit/1b69d5f2f49ee60af4b10137c7833a76420dbba1))
* **compiler-sfc:** use prependLeft to handle CSSVars ([#7760](https://github.com/vuejs/core/issues/7760)) ([139104b](https://github.com/vuejs/core/commit/139104ba2695eecae672db98f978de78e17ab7e1))
* **jsx-runtime:** fix automatic runtime implementation ([#7959](https://github.com/vuejs/core/issues/7959)) ([5838950](https://github.com/vuejs/core/commit/5838950ecf7e4e17dd5a23acd621077390666b76))
* **jsx-runtime:** handle keys ([#7976](https://github.com/vuejs/core/issues/7976)) ([ff60b93](https://github.com/vuejs/core/commit/ff60b933ae4e02422393664ee7818cffadf9b58b))
* **types/jsx:** jsx-runtime types for global JSX namespace registration ([#7978](https://github.com/vuejs/core/issues/7978)) ([0f73f39](https://github.com/vuejs/core/commit/0f73f394dafd709298bd8c71107a323bf322a1d2))
* **types/jsx:** move JSX DOM types back to `@vue/runtime-dom` ([#7979](https://github.com/vuejs/core/issues/7979)) ([ffe679c](https://github.com/vuejs/core/commit/ffe679c490986b69956daec7166f1ab6d9f23073))
* **types/jsx:** remove $slots children override ([28e30c8](https://github.com/vuejs/core/commit/28e30c819df5e4fc301c98f7be938fa13e8be3bc))
* **types:** revert jsx global removal (to be removed in 3.4) ([e224922](https://github.com/vuejs/core/commit/e224922e972e78b4fb6cbd129d685c2317018ad1))
### Features
* **compiler-core:** support parsing `const` modifier in type parameters ([#7912](https://github.com/vuejs/core/issues/7912)) ([b7bd50f](https://github.com/vuejs/core/commit/b7bd50f5059e8755c0204f01a8c55b1724688e7e))
* **compiler-sfc:** add defineOptions macro ([#5738](https://github.com/vuejs/core/issues/5738)) ([bcf5841](https://github.com/vuejs/core/commit/bcf5841ddecc64d0bdbd56ce1463eb8ebf01bb9d))
* **compiler-sfc:** enable reactive props destructure by default ([#7986](https://github.com/vuejs/core/issues/7986)) ([ba9c2ae](https://github.com/vuejs/core/commit/ba9c2ae247fcc3960b238a04cb635158daa82004))
* **compiler-sfc:** improve runtime props inference for enum ([eded947](https://github.com/vuejs/core/commit/eded94712e37856f258dc8c85f98a26fa41ae05f))
* **compiler-sfc:** support generating variable instead of default export in compileScript ([71635be](https://github.com/vuejs/core/commit/71635be68d25887f91d624bb7f78281a851bc0cb))
* **compiler-sfc:** support module string names syntax ([#7428](https://github.com/vuejs/core/issues/7428)) ([0002567](https://github.com/vuejs/core/commit/000256772816d54976e462330a7be342c49c7304))
* **complier-sfc:** hoist literal constants for script ([#5752](https://github.com/vuejs/core/issues/5752)) ([7def8b1](https://github.com/vuejs/core/commit/7def8b15b89aa78accd9a00927db91e8091a12b7)), closes [#5750](https://github.com/vuejs/core/issues/5750)
* **runtime-core:** add skipCheck for prop ([#7548](https://github.com/vuejs/core/issues/7548)) ([63ad77f](https://github.com/vuejs/core/commit/63ad77f6f65751780aa52f817387165b4773cfe4))
* **sfc:** deprecate reactivity transform ([efb54e7](https://github.com/vuejs/core/commit/efb54e7315e93f4be7004d1c0a4de8c523dab334))
* **types:** `defineComponent()` with generics support ([#7963](https://github.com/vuejs/core/issues/7963)) ([d77557c](https://github.com/vuejs/core/commit/d77557c4038f88a676903b379505b280a88cc774)), closes [#3102](https://github.com/vuejs/core/issues/3102)
### BREAKING CHANGES
* **types:** The type of `defineComponent()` when passing in a function has changed. This overload signature is rarely used in practice and the breakage will be minimal, so repurposing it to something more useful should be worth it.
# [3.3.0-alpha.5](https://github.com/vuejs/core/compare/v3.3.0-alpha.4...v3.3.0-alpha.5) (2023-03-26)
### Bug Fixes
* **runtime-core:** support `getCurrentInstance` across mutiple builds of Vue ([8d2d5bf](https://github.com/vuejs/core/commit/8d2d5bf48a24dab44e5b03cb8fa0c5faa4b696e3))
* **types:** ensure defineProps with generics return correct types ([c288c7b](https://github.com/vuejs/core/commit/c288c7b0bd6077d690f42153c3fc49a45454a66a))
### Features
* **dx:** improve readability of displayed types for props ([4c9bfd2](https://github.com/vuejs/core/commit/4c9bfd2b999ce472f7481aae4f9dc5bb9f76628e))
* **types/jsx:** support jsxImportSource, avoid global JSX conflict ([#7958](https://github.com/vuejs/core/issues/7958)) ([d0b7ef3](https://github.com/vuejs/core/commit/d0b7ef3b61d5f83e35e5854b3c2c874e23463102))
### Note on JSX Types Change
* In the next minor (3.4), Vue no longer registers the global `JSX` namespace by default. This is necessary to avoid global namespace collision with React so that TSX of both libs can co-exist in the same project. This should not affect SFC-only users with latest version of Volar.
For TSX users, it is suggested to set [jsxImportSource](https://www.typescriptlang.org/tsconfig#jsxImportSource) to `'vue'` in `tsconfig.json` after upgrading to 3.3, or opt-in per file with `/* @jsxImportSource vue */`. This will allow you to opt-in to the new behavior now and upgrade seamlessly when 3.4 releases.
If there is code that depends on the presence of the global `JSX` namespace, you can retain the exact pre-3.4 global behavior by explicitly referencing `vue/jsx`, which registers the global `JSX` namespace.
Note that the planned change in 3.4 is a type-only breaking change in a minor release, which adheres to our [release policy](https://vuejs.org/about/releases.html#semantic-versioning-edge-cases).
# [3.3.0-alpha.4](https://github.com/vuejs/core/compare/v3.3.0-alpha.3...v3.3.0-alpha.4) (2023-02-06)
### Bug Fixes
* **build:** fix const enum w/ number values ([92bb189](https://github.com/vuejs/core/commit/92bb189ca7ecae221ebf5411da87c715780d2de3))
# [3.3.0-alpha.3](https://github.com/vuejs/core/compare/v3.3.0-alpha.2...v3.3.0-alpha.3) (2023-02-06)
### Bug Fixes
* **build:** avoid const enum conflicts ([d1181ad](https://github.com/vuejs/core/commit/d1181ad692861c140e687372efbc67a1d65d642a))
# [3.3.0-alpha.2](https://github.com/vuejs/core/compare/v3.3.0-alpha.1...v3.3.0-alpha.2) (2023-02-05)
### Bug Fixes
* **build:** fix dev flag replacement in esm-bundler builds ([5851eaa](https://github.com/vuejs/core/commit/5851eaa9339364d41860a277a99f2352de2a3834))
# [3.3.0-alpha.1](https://github.com/vuejs/core/compare/v3.2.47...v3.3.0-alpha.1) (2023-02-05)
### Bug Fixes
* **build:** ensure BaseTransition functions can be tree-shaken ([3a6f5eb](https://github.com/vuejs/core/commit/3a6f5eb0f7d60dc87d17a69c66e88ae5688b11a5))

174
eslint.config.js Normal file
View File

@ -0,0 +1,174 @@
import importX from 'eslint-plugin-import-x'
import tseslint from 'typescript-eslint'
import vitest from 'eslint-plugin-vitest'
import { builtinModules } from 'node:module'
const DOMGlobals = ['window', 'document']
const NodeGlobals = ['module', 'require']
const banConstEnum = {
selector: 'TSEnumDeclaration[const=true]',
message:
'Please use non-const enums. This project automatically inlines enums.',
}
export default tseslint.config(
{
files: ['**/*.js', '**/*.ts', '**/*.tsx'],
extends: [tseslint.configs.base],
plugins: {
'import-x': importX,
},
rules: {
'no-debugger': 'error',
'no-console': ['error', { allow: ['warn', 'error', 'info'] }],
// most of the codebase are expected to be env agnostic
'no-restricted-globals': ['error', ...DOMGlobals, ...NodeGlobals],
'no-restricted-syntax': [
'error',
banConstEnum,
{
selector: 'ObjectPattern > RestElement',
message:
'Our output target is ES2016, and object rest spread results in ' +
'verbose helpers and should be avoided.',
},
{
selector: 'ObjectExpression > SpreadElement',
message:
'esbuild transpiles object spread into very verbose inline helpers.\n' +
'Please use the `extend` helper from @vue/shared instead.',
},
{
selector: 'AwaitExpression',
message:
'Our output target is ES2016, so async/await syntax should be avoided.',
},
{
selector: 'ChainExpression',
message:
'Our output target is ES2016, and optional chaining results in ' +
'verbose helpers and should be avoided.',
},
],
'sort-imports': ['error', { ignoreDeclarationSort: true }],
'import-x/no-nodejs-modules': [
'error',
{ allow: builtinModules.map(mod => `node:${mod}`) },
],
// This rule enforces the preference for using '@ts-expect-error' comments in TypeScript
// code to indicate intentional type errors, improving code clarity and maintainability.
'@typescript-eslint/prefer-ts-expect-error': 'error',
// Enforce the use of 'import type' for importing types
'@typescript-eslint/consistent-type-imports': [
'error',
{
fixStyle: 'inline-type-imports',
disallowTypeAnnotations: false,
},
],
// Enforce the use of top-level import type qualifier when an import only has specifiers with inline type qualifiers
'@typescript-eslint/no-import-type-side-effects': 'error',
},
},
// tests, no restrictions (runs in Node / Vitest with jsdom)
{
files: ['**/__tests__/**', 'packages/dts-test/**'],
plugins: { vitest },
languageOptions: {
globals: {
...vitest.environments.env.globals,
},
},
rules: {
'no-console': 'off',
'no-restricted-globals': 'off',
'no-restricted-syntax': 'off',
'vitest/no-disabled-tests': 'error',
'vitest/no-focused-tests': 'error',
},
},
// shared, may be used in any env
{
files: ['packages/shared/**', 'eslint.config.js'],
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}/**'],
rules: {
'no-restricted-globals': ['error', ...DOMGlobals],
'no-restricted-syntax': ['error', banConstEnum],
},
},
// Private package, browser only + no syntax restrictions
{
files: ['packages/template-explorer/**', 'packages/sfc-playground/**'],
rules: {
'no-restricted-globals': ['error', ...NodeGlobals],
'no-restricted-syntax': ['error', banConstEnum],
'no-console': 'off',
},
},
// JavaScript files
{
files: ['*.js'],
rules: {
// We only do `no-unused-vars` checks for js files, TS files are checked by TypeScript itself.
'no-unused-vars': ['error', { vars: 'all', args: 'none' }],
},
},
// Node scripts
{
files: [
'eslint.config.js',
'rollup*.config.js',
'scripts/**',
'./*.{js,ts}',
'packages/*/*.js',
'packages/vue/*/*.js',
],
rules: {
'no-restricted-globals': 'off',
'no-restricted-syntax': ['error', banConstEnum],
'no-console': 'off',
},
},
// Import nodejs modules in compiler-sfc
{
files: ['packages/compiler-sfc/src/**'],
rules: {
'import-x/no-nodejs-modules': ['error', { allow: builtinModules }],
},
},
{
ignores: [
'**/dist/',
'**/temp/',
'**/coverage/',
'.idea/',
'explorations/',
'dts-build/packages',
],
},
)

View File

@ -1,38 +1,42 @@
{
"private": true,
"version": "3.3.4",
"packageManager": "pnpm@8.6.2",
"version": "3.4.36",
"packageManager": "pnpm@9.6.0",
"type": "module",
"scripts": {
"dev": "node scripts/dev.js",
"build": "node scripts/build.js",
"build-dts": "tsc -p tsconfig.build.json && rollup -c rollup.dts.config.js",
"size": "run-s size-global size-baseline",
"size-global": "node scripts/build.js vue runtime-dom -f global -p",
"size-baseline": "node scripts/build.js vue -f esm-bundler-runtime && node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler && cd packages/size-check && vite build && node brotli",
"build-dts": "tsc -p tsconfig.build-browser.json && tsc -p tsconfig.build-node.json && rollup -c rollup.dts.config.js",
"clean": "rimraf packages/*/dist temp .eslintcache",
"size": "run-s \"size-*\" && tsx scripts/usage-size.ts",
"size-global": "node scripts/build.js vue runtime-dom -f global -p --size",
"size-esm-runtime": "node scripts/build.js vue -f esm-bundler-runtime",
"size-esm": "node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler",
"check": "tsc --incremental --noEmit",
"lint": "eslint --cache --ext .ts packages/*/{src,__tests__}/**.ts",
"format": "prettier --write --cache --parser typescript \"**/*.[tj]s?(x)\"",
"format-check": "prettier --check --cache --parser typescript \"**/*.[tj]s?(x)\"",
"lint": "eslint --cache .",
"format": "prettier --write --cache .",
"format-check": "prettier --check --cache .",
"test": "vitest",
"test-unit": "vitest -c vitest.unit.config.ts",
"test-e2e": "node scripts/build.js vue -f global -d && vitest -c vitest.e2e.config.ts",
"test-dts": "run-s build-dts test-dts-only",
"test-dts-only": "tsc -p ./packages/dts-test/tsconfig.test.json",
"test-dts-only": "tsc -p packages/dts-built-test/tsconfig.json && tsc -p ./packages/dts-test/tsconfig.test.json",
"test-coverage": "vitest -c vitest.unit.config.ts --coverage",
"test-bench": "vitest bench",
"release": "node scripts/release.js",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
"dev-compiler": "run-p \"dev template-explorer\" serve",
"dev-sfc": "run-s dev-sfc-prepare dev-sfc-run",
"dev-sfc-prepare": "node scripts/pre-dev-sfc.js || npm run build-compiler-cjs",
"dev-sfc-prepare": "node scripts/pre-dev-sfc.js || npm run build-all-cjs",
"dev-sfc-serve": "vite packages/sfc-playground --host",
"dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
"dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev vue -ipf esm-browser-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
"serve": "serve",
"open": "open http://localhost:5000/packages/template-explorer/local.html",
"build-sfc-playground": "run-s build-compiler-cjs build-runtime-esm build-ssr-esm build-sfc-playground-self",
"build-compiler-cjs": "node scripts/build.js compiler reactivity-transform shared -af cjs",
"open": "open http://localhost:3000/packages/template-explorer/local.html",
"build-sfc-playground": "run-s build-all-cjs build-runtime-esm build-browser-esm build-ssr-esm build-sfc-playground-self",
"build-all-cjs": "node scripts/build.js vue runtime compiler reactivity shared -af cjs",
"build-runtime-esm": "node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler-runtime && node scripts/build.js vue -f esm-browser-runtime",
"build-browser-esm": "node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler && node scripts/build.js vue -f esm-browser",
"build-ssr-esm": "node scripts/build.js compiler-sfc server-renderer -f esm-browser",
"build-sfc-playground-self": "cd packages/sfc-playground && npm run build",
"preinstall": "npx only-allow pnpm",
@ -40,65 +44,79 @@
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged && pnpm check",
"commit-msg": "node scripts/verifyCommit.js"
"commit-msg": "node scripts/verify-commit.js"
},
"lint-staged": {
"*.{js,json}": [
"prettier --write"
],
"*.ts?(x)": [
"eslint",
"eslint --fix",
"prettier --parser=typescript --write"
]
},
"engines": {
"node": ">=16.11.0"
"node": ">=18.12.0"
},
"devDependencies": {
"@babel/parser": "^7.21.3",
"@babel/types": "^7.21.3",
"@rollup/plugin-alias": "^4.0.3",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.4.0",
"@types/hash-sum": "^1.0.0",
"@types/node": "^16.4.7",
"@typescript-eslint/parser": "^5.56.0",
"@vitest/coverage-istanbul": "^0.29.7",
"@vue/consolidate": "0.17.3",
"chalk": "^4.1.0",
"conventional-changelog-cli": "^2.0.31",
"enquirer": "^2.3.2",
"esbuild": "^0.17.4",
"esbuild-plugin-polyfill-node": "^0.2.0",
"eslint": "^8.33.0",
"eslint-plugin-jest": "^27.2.1",
"estree-walker": "^2.0.2",
"execa": "^4.0.2",
"jsdom": "^21.1.0",
"lint-staged": "^10.2.10",
"lodash": "^4.17.15",
"magic-string": "^0.30.0",
"marked": "^4.0.10",
"minimist": "^1.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"pug": "^3.0.1",
"puppeteer": "~19.6.0",
"rollup": "^3.26.0",
"rollup-plugin-dts": "^5.3.0",
"rollup-plugin-esbuild": "^5.0.0",
"rollup-plugin-polyfill-node": "^0.12.0",
"semver": "^7.3.2",
"serve": "^12.0.0",
"simple-git-hooks": "^2.8.1",
"terser": "^5.15.1",
"todomvc-app-css": "^2.3.0",
"tslib": "^2.5.0",
"typescript": "^5.1.6",
"vite": "^4.3.0",
"vitest": "^0.30.1"
"@babel/parser": "catalog:",
"@babel/types": "catalog:",
"@rollup/plugin-alias": "^5.1.0",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "5.0.4",
"@swc/core": "^1.7.6",
"@types/hash-sum": "^1.0.2",
"@types/node": "^20.14.14",
"@types/semver": "^7.5.8",
"@vitest/coverage-istanbul": "^1.6.0",
"@vue/consolidate": "1.0.0",
"conventional-changelog-cli": "^5.0.0",
"enquirer": "^2.4.1",
"esbuild": "^0.23.0",
"esbuild-plugin-polyfill-node": "^0.3.0",
"eslint": "^9.8.0",
"eslint-plugin-import-x": "^3.1.0",
"eslint-plugin-vitest": "^0.5.4",
"estree-walker": "catalog:",
"jsdom": "^24.1.1",
"lint-staged": "^15.2.8",
"lodash": "^4.17.21",
"magic-string": "^0.30.11",
"markdown-table": "^3.0.3",
"marked": "^13.0.3",
"npm-run-all2": "^6.2.2",
"picocolors": "^1.0.1",
"prettier": "^3.3.3",
"pretty-bytes": "^6.1.1",
"pug": "^3.0.3",
"puppeteer": "~22.15.0",
"rimraf": "^6.0.1",
"rollup": "^4.20.0",
"rollup-plugin-dts": "^6.1.1",
"rollup-plugin-esbuild": "^6.1.1",
"rollup-plugin-polyfill-node": "^0.13.0",
"semver": "^7.6.3",
"serve": "^14.2.3",
"simple-git-hooks": "^2.11.1",
"todomvc-app-css": "^2.4.3",
"tslib": "^2.6.3",
"tsx": "^4.16.5",
"typescript": "~5.4.5",
"typescript-eslint": "^8.0.0",
"vite": "catalog:",
"vitest": "^1.6.0"
},
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {
"typescript-eslint>eslint": "^9.0.0",
"@typescript-eslint/eslint-plugin>eslint": "^9.0.0",
"@typescript-eslint/parser>eslint": "^9.0.0",
"@typescript-eslint/type-utils>eslint": "^9.0.0",
"@typescript-eslint/utils>eslint": "^9.0.0"
}
}
}
}

View File

@ -19,12 +19,12 @@ export function render(_ctx, _cache) {
}"
`;
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]
)
@ -48,13 +48,13 @@ exports[`compiler: codegen > Element (callExpression + objectExpression + Templa
"
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 */)
}
}"
`;
@ -63,12 +63,12 @@ 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
@ -80,7 +80,7 @@ exports[`compiler: codegen > comment 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
return _createCommentVNode(\\"foo\\")
return _createCommentVNode("foo")
}
}"
`;
@ -98,7 +98,7 @@ 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 */))
}
}"
`;
@ -135,7 +135,7 @@ return function render(_ctx, _cache) {
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) {
@ -165,7 +165,7 @@ return function render(_ctx, _cache) {
`;
exports[`compiler: codegen > module mode preamble 1`] = `
"import { createVNode as _createVNode, resolveDirective as _resolveDirective } from \\"vue\\"
"import { createVNode as _createVNode, resolveDirective as _resolveDirective } from "vue"
export function render(_ctx, _cache) {
return null
@ -173,7 +173,7 @@ export function render(_ctx, _cache) {
`;
exports[`compiler: codegen > module mode preamble w/ optimizeImports: true 1`] = `
"import { createVNode, resolveDirective } from \\"vue\\"
"import { createVNode, resolveDirective } from "vue"
// Binding optimization for webpack code-split
const _createVNode = createVNode, _resolveDirective = resolveDirective
@ -187,7 +187,7 @@ exports[`compiler: codegen > static text 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
return \\"hello\\"
return "hello"
}
}"
`;

View File

@ -7,19 +7,19 @@ 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\\")
_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 */))
@ -31,19 +31,19 @@ exports[`compiler: integration tests > function mode w/ prefixIdentifiers: true
"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\\")
_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 */))
@ -51,22 +51,22 @@ return function render(_ctx, _cache) {
`;
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\\"
"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\\")
_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 */))

View File

@ -1,14 +1,30 @@
// 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\\"
"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 */))
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, [
return (_openBlock(), _createElementBlock("div", null, [
_hoisted_1,
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
_hoisted_2
]))
}"
`;
exports[`scopeId compiler support > should push typescript-compatible 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: any) => (_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: any,_cache: any) {
return (_openBlock(), _createElementBlock("div", null, [
_hoisted_1,
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
_hoisted_2
@ -17,14 +33,14 @@ export function render(_ctx, _cache) {
`;
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\\"
"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 */
}))
@ -32,26 +48,26 @@ export function render(_ctx, _cache) {
`;
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\\"
"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\\"
key: "0"
}
: undefined,
_renderList(_ctx.list, (i) => {
return {
name: i,
fn: _withCtx(() => [
_createElementVNode(\\"div\\")
_createElementVNode("div")
])
}
})
@ -60,17 +76,17 @@ 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\\"
"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 */
}))

View File

@ -1,38 +1,38 @@
import {
locStub,
generate,
ConstantTypes,
type DirectiveArguments,
type ForCodegenNode,
type IfConditionalExpression,
NodeTypes,
RootNode,
createSimpleExpression,
type RootNode,
type VNodeCall,
createArrayExpression,
createAssignmentExpression,
createBlockStatement,
createCacheExpression,
createCallExpression,
createCompoundExpression,
createConditionalExpression,
createIfStatement,
createInterpolation,
createObjectExpression,
createObjectProperty,
createArrayExpression,
createCompoundExpression,
createInterpolation,
createCallExpression,
createConditionalExpression,
ForCodegenNode,
createCacheExpression,
createSimpleExpression,
createTemplateLiteral,
createBlockStatement,
createIfStatement,
createAssignmentExpression,
IfConditionalExpression,
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,6 +40,7 @@ import { PatchFlags } from '@vue/shared'
function createRoot(options: Partial<RootNode> = {}): RootNode {
return {
type: NodeTypes.ROOT,
source: '',
children: [],
helpers: new Set(),
components: [],
@ -50,59 +51,59 @@ function createRoot(options: Partial<RootNode> = {}): RootNode {
temps: 0,
codegenNode: createSimpleExpression(`null`, false),
loc: locStub,
...options
...options,
}
}
describe('compiler: codegen', () => {
test('module mode preamble', () => {
const root = createRoot({
helpers: new Set([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: new Set([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: new Set([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: new Set([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),
],
locStub
},
createSimpleExpression(`bar`, false, 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()
})
@ -424,42 +425,42 @@ describe('compiler: codegen', () => {
cached: 1,
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,
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,22 +617,22 @@ 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(`
@ -643,7 +644,7 @@ describe('compiler: codegen', () => {
test('with props', () => {
expect(genCode(createVNodeCall(null, `"div"`, mockProps)))
.toMatchInlineSnapshot(`
"return _createElementVNode(\\"div\\", { foo: \\"bar\\" })
"return _createElementVNode("div", { foo: "bar" })
"
`)
})
@ -651,7 +652,7 @@ describe('compiler: codegen', () => {
test('with children, no props', () => {
expect(genCode(createVNodeCall(null, `"div"`, undefined, mockChildren)))
.toMatchInlineSnapshot(`
"return _createElementVNode(\\"div\\", null, children)
"return _createElementVNode("div", null, children)
"
`)
})
@ -659,15 +660,18 @@ describe('compiler: codegen', () => {
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]
])
"

View File

@ -1,5 +1,5 @@
import { baseCompile as compile } from '../src'
import { SourceMapConsumer, RawSourceMap } from 'source-map-js'
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`))
})
})

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import { baseCompile } from '../src/compile'
import { PUSH_SCOPE_ID, POP_SCOPE_ID } from '../src/runtimeHelpers'
import { POP_SCOPE_ID, PUSH_SCOPE_ID } from '../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared'
import { genFlagText } from './testUtils'
@ -18,7 +18,7 @@ describe('scopeId compiler support', () => {
test('should wrap default slot', () => {
const { code } = baseCompile(`<Child><div/></Child>`, {
mode: 'module',
scopeId: 'test'
scopeId: 'test',
})
expect(code).toMatch(`default: _withCtx(() => [`)
expect(code).toMatchSnapshot()
@ -33,8 +33,8 @@ describe('scopeId compiler support', () => {
`,
{
mode: 'module',
scopeId: 'test'
}
scopeId: 'test',
},
)
expect(code).toMatch(`foo: _withCtx(({ msg }) => [`)
expect(code).toMatch(`bar: _withCtx(() => [`)
@ -50,8 +50,8 @@ 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\(/)
@ -64,8 +64,8 @@ describe('scopeId compiler support', () => {
{
mode: 'module',
scopeId: 'test',
hoistStatic: true
}
hoistStatic: true,
},
)
expect(ast.helpers).toContain(PUSH_SCOPE_ID)
expect(ast.helpers).toContain(POP_SCOPE_ID)
@ -73,11 +73,36 @@ describe('scopeId compiler support', () => {
;[
`const _withScopeId = n => (_pushScopeId("test"),n=n(),_popScopeId(),n)`,
`const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", ${genFlagText(
PatchFlags.HOISTED
PatchFlags.HOISTED,
)}))`,
`const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", ${genFlagText(
PatchFlags.HOISTED
)}))`
PatchFlags.HOISTED,
)}))`,
].forEach(c => expect(code).toMatch(c))
expect(code).toMatchSnapshot()
})
test('should push typescript-compatible scopeId for hoisted nodes', () => {
const { ast, code } = baseCompile(
`<div><div>hello</div>{{ foo }}<div>world</div></div>`,
{
mode: 'module',
scopeId: 'test',
hoistStatic: true,
isTS: true,
},
)
expect(ast.helpers).toContain(PUSH_SCOPE_ID)
expect(ast.helpers).toContain(POP_SCOPE_ID)
expect(ast.hoists.length).toBe(2)
;[
`const _withScopeId = (n: any) => (_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()
})

View File

@ -1,17 +1,17 @@
import {
NodeTypes,
ElementNode,
locStub,
Namespaces,
type ElementNode,
ElementTypes,
VNodeCall
Namespaces,
NodeTypes,
type VNodeCall,
locStub,
} from '../src'
import {
isString,
PatchFlags,
PatchFlagNames,
type PatchFlags,
type ShapeFlags,
isArray,
ShapeFlags
isString,
} from '@vue/shared'
const leadingBracketRE = /^\[/
@ -30,16 +30,16 @@ export function createObjectMatcher(obj: Record<string, any>) {
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: key.replace(bracketsRE, ''),
isStatic: !leadingBracketRE.test(key)
isStatic: !leadingBracketRE.test(key),
},
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 +48,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 +56,6 @@ export function createElementWithCodegen(
ns: Namespaces.HTML,
tag: 'div',
tagType: ElementTypes.ELEMENT,
isSelfClosing: false,
props: [],
children: [],
codegenNode: {
@ -70,15 +69,15 @@ 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,
) {
if (isArray(flag)) {
let f = 0

View File

@ -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 = vi.fn(plugin)
transform(ast, {
nodeTransforms: [spy]
nodeTransforms: [spy],
})
expect(ast.children.length).toBe(2)
@ -115,7 +114,7 @@ describe('compiler: transform', () => {
}
const spy = vi.fn(plugin)
transform(ast, {
nodeTransforms: [spy]
nodeTransforms: [spy],
})
expect(ast.children.length).toBe(2)
@ -143,7 +142,7 @@ describe('compiler: transform', () => {
}
const spy = vi.fn(plugin)
transform(ast, {
nodeTransforms: [spy]
nodeTransforms: [spy],
})
expect(ast.children.length).toBe(1)
@ -170,7 +169,7 @@ describe('compiler: transform', () => {
}
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(`<div />`)
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(`<div/>`)
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 = 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(`<div v-if="ok" />`)
expect(ast.codegenNode).toMatchObject({
type: NodeTypes.IF
type: NodeTypes.IF,
})
})
test('root v-for', () => {
const ast = transformWithCodegen(`<div v-for="i in list" />`)
expect(ast.codegenNode).toMatchObject({
type: NodeTypes.FOR
type: NodeTypes.FOR,
})
})
@ -303,28 +322,28 @@ describe('compiler: transform', () => {
const ast = transformWithCodegen(`<div v-foo/>`)
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,
),
)
})
})

View File

@ -4,7 +4,7 @@ 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_1 = /*#__PURE__*/_createElementVNode("div", { key: "foo" }, null, -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
@ -13,7 +13,7 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
}
}"
`;
@ -22,9 +22,9 @@ 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\\")
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, [
/*#__PURE__*/_createElementVNode("span"),
/*#__PURE__*/_createElementVNode("span")
], -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
@ -34,7 +34,7 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
}
}"
`;
@ -43,8 +43,8 @@ exports[`compiler: hoistStatic transform > hoist nested static tree with comment
"const _Vue = Vue
const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"div\\", null, [
/*#__PURE__*/_createCommentVNode(\\"comment\\")
const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, [
/*#__PURE__*/_createCommentVNode("comment")
], -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
@ -54,7 +54,7 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
}
}"
`;
@ -63,8 +63,8 @@ exports[`compiler: hoistStatic transform > hoist siblings with common non-hoista
"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_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
@ -74,7 +74,7 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_3))
return (_openBlock(), _createElementBlock("div", null, _hoisted_3))
}
}"
`;
@ -83,7 +83,7 @@ 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_1 = /*#__PURE__*/_createElementVNode("span", { class: "inline" }, "hello", -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
@ -92,7 +92,7 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
}
}"
`;
@ -101,16 +101,16 @@ exports[`compiler: hoistStatic transform > hoist static props for elements with
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
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\\")
const _directive_foo = _resolveDirective("foo")
return (_openBlock(), _createElementBlock(\\"div\\", null, [
_withDirectives(_createElementVNode(\\"div\\", _hoisted_1, null, 512 /* NEED_PATCH */), [
return (_openBlock(), _createElementBlock("div", null, [
_withDirectives(_createElementVNode("div", _hoisted_1, null, 512 /* NEED_PATCH */), [
[_directive_foo]
])
]))
@ -122,14 +122,14 @@ exports[`compiler: hoistStatic transform > hoist static props for elements with
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
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 */)
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("div", _hoisted_1, _toDisplayString(hello), 1 /* TEXT */)
]))
}
}"
@ -139,16 +139,16 @@ exports[`compiler: hoistStatic transform > hoist static props for elements with
"const _Vue = Vue
const { createVNode: _createVNode, createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
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\\")
const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createElementBlock(\\"div\\", null, [
_createElementVNode(\\"div\\", _hoisted_1, [
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("div", _hoisted_1, [
_createVNode(_component_Comp)
])
]))
@ -168,8 +168,8 @@ 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 */)
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("span", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */)
]))
}
}"
@ -179,7 +179,7 @@ exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested stat
"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_1 = /*#__PURE__*/_createElementVNode("span", null, "foo " + /*#__PURE__*/_toDisplayString(1) + " " + /*#__PURE__*/_toDisplayString(true), -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
@ -188,7 +188,7 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
}
}"
`;
@ -197,7 +197,7 @@ exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested stat
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"span\\", { foo: 0 }, /*#__PURE__*/_toDisplayString(1), -1 /* HOISTED */)
const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", { foo: 0 }, /*#__PURE__*/_toDisplayString(1), -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
@ -206,7 +206,7 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
}
}"
`;
@ -215,7 +215,7 @@ exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"path\\", { d: \\"M2,3H5.5L12\\" }, null, -1 /* HOISTED */)
const _hoisted_1 = /*#__PURE__*/_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
@ -224,10 +224,10 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, resolveDirective: _resolveDirective, openBlock: _openBlock, createElementBlock: _createElementBlock, withDirectives: _withDirectives } = _Vue
const _directive_foo = _resolveDirective(\\"foo\\")
const _directive_foo = _resolveDirective("foo")
return (_openBlock(), _createElementBlock(\\"div\\", null, [
_withDirectives((_openBlock(), _createElementBlock(\\"svg\\", null, _hoisted_2)), [
return (_openBlock(), _createElementBlock("div", null, [
_withDirectives((_openBlock(), _createElementBlock("svg", null, _hoisted_2)), [
[_directive_foo]
])
]))
@ -236,12 +236,12 @@ return function render(_ctx, _cache) {
`;
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\\"
"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\\", {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("div", null, [
_createElementVNode("div", {
class: _normalizeClass({}),
onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.foo && _ctx.foo(...args)))
})
@ -251,12 +251,12 @@ export function render(_ctx, _cache) {
`;
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\\"
"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\\", {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("div", null, [
_createElementVNode("div", {
onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.foo && _ctx.foo(...args)))
})
])
@ -271,10 +271,10 @@ 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, [
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 */)
return (_openBlock(), _createElementBlock("p", null, [
_createElementVNode("span", null, _toDisplayString(o + 'foo'), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
]))
@ -289,7 +289,7 @@ 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\\")
const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, {
default: _withCtx(({ foo }) => [
@ -308,10 +308,10 @@ 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, [
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 */)
return (_openBlock(), _createElementBlock("p", null, [
_createElementVNode("span", null, _toDisplayString(o), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
]))
@ -326,9 +326,9 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, [
return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
return (_openBlock(), _createElementBlock(\\"span\\", { key: item }))
return (_openBlock(), _createElementBlock("span", { key: item }))
}), 128 /* KEYED_FRAGMENT */))
]))
}
@ -342,9 +342,9 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { 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, [
_createVNode(_component_Comp)
]))
}
@ -358,8 +358,8 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, [
(_openBlock(), _createElementBlock(\\"div\\", { key: foo }))
return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(), _createElementBlock("div", { key: foo }))
]))
}
}"
@ -369,14 +369,14 @@ exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = [\\"id\\"]
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)
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("div", { id: foo }, null, 8 /* PROPS */, _hoisted_1)
]))
}
}"
@ -389,8 +389,8 @@ 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 */)
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("div", { ref: foo }, null, 512 /* NEED_PATCH */)
]))
}
}"
@ -403,7 +403,7 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\"))
return (_openBlock(), _createElementBlock("div"))
}
}"
`;
@ -412,8 +412,8 @@ exports[`compiler: hoistStatic transform > should hoist v-for children if static
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"span\\", null, null, -1 /* HOISTED */)
const _hoisted_1 = { id: "foo" }
const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */)
const _hoisted_3 = [
_hoisted_2
]
@ -422,9 +422,9 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, [
return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _createElementBlock(\\"div\\", _hoisted_1, _hoisted_3))
return (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3))
}), 256 /* UNKEYED_FRAGMENT */))
]))
}
@ -437,9 +437,9 @@ const { createElementVNode: _createElementVNode, createCommentVNode: _createComm
const _hoisted_1 = {
key: 0,
id: \\"foo\\"
id: "foo"
}
const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"span\\", null, null, -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */)
const _hoisted_3 = [
_hoisted_2
]
@ -448,10 +448,10 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), _createElementBlock(\\"div\\", null, [
return (_openBlock(), _createElementBlock("div", null, [
ok
? (_openBlock(), _createElementBlock(\\"div\\", _hoisted_1, _hoisted_3))
: _createCommentVNode(\\"v-if\\", true)
? (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3))
: _createCommentVNode("v-if", true)
]))
}
}"

View File

@ -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/ <slot/> 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 <slot/> 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 <template> 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 */))
}
}"
`;

View File

@ -2,7 +2,7 @@
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 */))
}"
`;
@ -10,6 +10,96 @@ 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"]))
}"
`;

View File

@ -9,7 +9,7 @@ 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 */))
}
@ -23,7 +23,7 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString } = _Vue
return _toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz)
return _toDisplayString(foo) + " bar " + _toDisplayString(baz)
}
}"
`;
@ -36,9 +36,9 @@ 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 */))
}
}"
@ -52,11 +52,11 @@ 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 */))
}
}"
@ -69,9 +69,9 @@ 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]
@ -100,9 +100,9 @@ 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 */))
}
}"
@ -112,6 +112,6 @@ 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)
}"
`;

View File

@ -8,7 +8,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) => {
return (_openBlock(), _createElementBlock(\\"span\\"))
return (_openBlock(), _createElementBlock("span"))
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
@ -23,8 +23,8 @@ 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 */))
}
@ -39,7 +39,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) => {
return (_openBlock(), _createElementBlock(\\"span\\", { key: item }))
return (_openBlock(), _createElementBlock("span", { key: item }))
}), 128 /* KEYED_FRAGMENT */))
}
}"
@ -53,7 +53,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, __, index) => {
return (_openBlock(), _createElementBlock(\\"span\\"))
return (_openBlock(), _createElementBlock("span"))
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
@ -67,7 +67,7 @@ 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 */))
}
}"
@ -81,7 +81,7 @@ 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 */))
}
}"
@ -96,8 +96,8 @@ 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 */))
}
@ -112,10 +112,10 @@ 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 */))
}
}"
@ -129,7 +129,7 @@ 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 */))
}
}"
@ -143,7 +143,7 @@ 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 */))
}
}"
@ -156,10 +156,10 @@ 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 */))
@ -175,7 +175,7 @@ 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 */))
}
}"
@ -190,9 +190,9 @@ 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)
}
}"
`;
@ -208,7 +208,7 @@ 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)
}
}"
`;
@ -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 */))
}
}"

View File

@ -8,8 +8,8 @@ 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)
}
}"
`;
@ -23,13 +23,13 @@ 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 */))
}
}"
@ -44,11 +44,11 @@ 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 */))
}
}"
@ -63,11 +63,11 @@ 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)
}
}"
`;
@ -80,8 +80,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)
}
}"
`;
@ -94,8 +94,8 @@ 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 }))
}
}"
`;
@ -108,10 +108,10 @@ 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\\"], 64 /* STABLE_FRAGMENT */))
? (_openBlock(), _createElementBlock("p", { key: 1 }))
: (_openBlock(), _createElementBlock(_Fragment, { key: 2 }, ["fine"], 64 /* STABLE_FRAGMENT */))
}
}"
`;
@ -124,10 +124,10 @@ 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)
}
}"
`;
@ -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)
}
}"
`;

View File

@ -1,44 +1,44 @@
// 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\\"
"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\\")
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\\"
"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\\"
"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\\"
"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 */))
@ -47,15 +47,15 @@ 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\\"
"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
@ -65,16 +65,16 @@ 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\\"
"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)
]))

View File

@ -1,13 +1,13 @@
// 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\\"
"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"]))
}"
`;
@ -18,10 +18,10 @@ 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"]))
}
}"
`;
@ -33,30 +33,30 @@ 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\\"
"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"]))
}"
`;
@ -67,10 +67,10 @@ 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"]))
}
}"
`;
@ -82,21 +82,21 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(\\"input\\", {
\\"foo-value\\": model,
\\"onUpdate:fooValue\\": $event => ((model) = $event)
}, null, 40 /* PROPS, HYDRATE_EVENTS */, [\\"foo-value\\", \\"onUpdate:fooValue\\"]))
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\\"
"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 */))
}"
`;
@ -108,9 +108,9 @@ 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 */))
}
}"

View File

@ -9,7 +9,7 @@ return function render(_ctx, _cache) {
return _cache[0] || (
_setBlockTracking(-1),
_cache[0] = _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
_setBlockTracking(1),
_cache[0]
)
@ -24,12 +24,12 @@ 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\\"]),
(_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
_setBlockTracking(1),
_cache[0]
)
@ -45,10 +45,10 @@ 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\\"]),
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
_setBlockTracking(1),
_cache[0]
)
@ -64,10 +64,10 @@ 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\\"),
(_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
_setBlockTracking(1),
_cache[0]
)
@ -83,10 +83,10 @@ 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\\"),
(_cache[0] = _createElementVNode("div")).cacheIndex = 0,
_setBlockTracking(1),
_cache[0]
)

View File

@ -4,7 +4,7 @@ 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)]),
@ -18,11 +18,11 @@ 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 */
}))
@ -33,7 +33,7 @@ exports[`compiler: transform component slots > named slot with v-for w/ prefixId
"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) => {
@ -50,14 +50,14 @@ exports[`compiler: transform component slots > named slot with v-if + prefixIden
"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\\",
name: "one",
fn: _withCtx((props) => [_toDisplayString(props)]),
key: \\"0\\"
key: "0"
}
: undefined
]), 1024 /* DYNAMIC_SLOTS */))
@ -71,25 +71,25 @@ 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\\"]),
key: \\"0\\"
name: "one",
fn: _withCtx(() => ["foo"]),
key: "0"
}
: orNot
? {
name: \\"two\\",
fn: _withCtx((props) => [\\"bar\\"]),
key: \\"1\\"
name: "two",
fn: _withCtx((props) => ["bar"]),
key: "1"
}
: {
name: \\"one\\",
fn: _withCtx(() => [\\"baz\\"]),
key: \\"2\\"
name: "one",
fn: _withCtx(() => ["baz"]),
key: "2"
}
]), 1024 /* DYNAMIC_SLOTS */))
}
@ -103,14 +103,14 @@ 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\\"]),
key: \\"0\\"
name: "one",
fn: _withCtx(() => ["hello"]),
key: "0"
}
: undefined
]), 1024 /* DYNAMIC_SLOTS */))
@ -125,13 +125,13 @@ 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 */
}))
@ -143,8 +143,8 @@ 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 }) => [
@ -152,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)
@ -166,7 +166,7 @@ exports[`compiler: transform component slots > on component dynamically named sl
"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)]),
@ -179,7 +179,7 @@ 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)]),
@ -192,7 +192,7 @@ 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)]),
@ -205,7 +205,7 @@ 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)]),
@ -219,13 +219,13 @@ exports[`compiler: transform component slots > with whitespace: 'preserve' > imp
"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 */
}))
@ -236,11 +236,11 @@ exports[`compiler: transform component slots > with whitespace: 'preserve' > nam
"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 */
}))
}"
@ -250,11 +250,11 @@ exports[`compiler: transform component slots > with whitespace: 'preserve' > sho
"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 */
}))
}"

View File

@ -1,19 +1,19 @@
import {
type CompilerOptions,
ConstantTypes,
type ElementNode,
type ForNode,
type IfNode,
NodeTypes,
type VNodeCall,
generate,
baseParse as parse,
transform,
NodeTypes,
generate,
CompilerOptions,
VNodeCall,
IfNode,
ElementNode,
ForNode,
ConstantTypes
} from '../../src'
import {
FRAGMENT,
NORMALIZE_CLASS,
RENDER_LIST,
NORMALIZE_CLASS
} from '../../src/runtimeHelpers'
import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression'
@ -21,7 +21,7 @@ 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 { createObjectMatcher } from '../testUtils'
import { transformText } from '../../src/transforms/transformText'
import { PatchFlags } from '@vue/shared'
@ -31,9 +31,9 @@ const hoistedChildrenArrayMatcher = (startIndex = 1, length = 1) => ({
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_${startIndex + i}`
}
}))
content: `_hoisted_${startIndex + i}`,
},
})),
})
function transformWithHoist(template: string, options: CompilerOptions = {}) {
@ -45,17 +45,17 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement,
transformText
transformText,
],
directiveTransforms: {
on: transformOn,
bind: transformBind
bind: transformBind,
},
...options
...options,
})
expect(ast.codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
isBlock: true
isBlock: true,
})
return ast
}
@ -67,14 +67,14 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist(`<div/>`)
expect(root.hoists.length).toBe(0)
expect(root.codegenNode).toMatchObject({
tag: `"div"`
tag: `"div"`,
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist simple element', () => {
const root = transformWithHoist(
`<div><span class="inline">hello</span></div>`
`<div><span class="inline">hello</span></div>`,
)
expect(root.hoists).toMatchObject([
{
@ -83,15 +83,15 @@ describe('compiler: hoistStatic transform', () => {
props: createObjectMatcher({ class: 'inline' }),
children: {
type: NodeTypes.TEXT,
content: `hello`
}
content: `hello`,
},
hoistedChildrenArrayMatcher()
},
hoistedChildrenArrayMatcher(),
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: { content: `_hoisted_2` }
children: { content: `_hoisted_2` },
})
expect(generate(root).code).toMatchSnapshot()
})
@ -105,13 +105,13 @@ describe('compiler: hoistStatic transform', () => {
props: undefined,
children: [
{ type: NodeTypes.ELEMENT, tag: `span` },
{ type: NodeTypes.ELEMENT, tag: `span` }
]
{ type: NodeTypes.ELEMENT, tag: `span` },
],
},
hoistedChildrenArrayMatcher()
hoistedChildrenArrayMatcher(),
])
expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: '_hoisted_2'
content: '_hoisted_2',
})
expect(generate(root).code).toMatchSnapshot()
})
@ -123,12 +123,12 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: undefined,
children: [{ type: NodeTypes.COMMENT, content: `comment` }]
children: [{ type: NodeTypes.COMMENT, content: `comment` }],
},
hoistedChildrenArrayMatcher()
hoistedChildrenArrayMatcher(),
])
expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: `_hoisted_2`
content: `_hoisted_2`,
})
expect(generate(root).code).toMatchSnapshot()
})
@ -138,16 +138,16 @@ describe('compiler: hoistStatic transform', () => {
expect(root.hoists).toMatchObject([
{
type: NodeTypes.VNODE_CALL,
tag: `"span"`
tag: `"span"`,
},
{
type: NodeTypes.VNODE_CALL,
tag: `"div"`
tag: `"div"`,
},
hoistedChildrenArrayMatcher(1, 2)
hoistedChildrenArrayMatcher(1, 2),
])
expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: '_hoisted_3'
content: '_hoisted_3',
})
expect(generate(root).code).toMatchSnapshot()
})
@ -160,9 +160,9 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.VNODE_CALL,
tag: `_component_Comp`
}
}
tag: `_component_Comp`,
},
},
])
expect(generate(root).code).toMatchSnapshot()
})
@ -177,17 +177,17 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
id: `[foo]`
id: `[foo]`,
}),
children: undefined,
patchFlag: genFlagText(PatchFlags.PROPS),
patchFlag: PatchFlags.PROPS,
dynamicProps: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
isStatic: false
}
}
}
isStatic: false,
},
},
},
])
expect(generate(root).code).toMatchSnapshot()
})
@ -199,14 +199,14 @@ describe('compiler: hoistStatic transform', () => {
{
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({ key: 'foo' })
props: createObjectMatcher({ key: 'foo' }),
},
hoistedChildrenArrayMatcher()
hoistedChildrenArrayMatcher(),
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: { content: `_hoisted_2` }
children: { content: `_hoisted_2` },
})
expect(generate(root).code).toMatchSnapshot()
})
@ -221,10 +221,10 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
key: `[foo]`
})
}
}
key: `[foo]`,
}),
},
},
])
expect(generate(root).code).toMatchSnapshot()
})
@ -239,12 +239,12 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
ref: `[foo]`
ref: `[foo]`,
}),
children: undefined,
patchFlag: genFlagText(PatchFlags.NEED_PATCH)
}
}
patchFlag: PatchFlags.NEED_PATCH,
},
},
])
expect(generate(root).code).toMatchSnapshot()
})
@ -260,22 +260,22 @@ describe('compiler: hoistStatic transform', () => {
tag: `"div"`,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
content: `_hoisted_1`,
},
children: undefined,
patchFlag: genFlagText(PatchFlags.NEED_PATCH),
patchFlag: PatchFlags.NEED_PATCH,
directives: {
type: NodeTypes.JS_ARRAY_EXPRESSION
}
}
}
type: NodeTypes.JS_ARRAY_EXPRESSION,
},
},
},
])
expect(generate(root).code).toMatchSnapshot()
})
test('hoist static props for elements with dynamic text children', () => {
const root = transformWithHoist(
`<div><div id="foo">{{ hello }}</div></div>`
`<div><div id="foo">{{ hello }}</div></div>`,
)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect((root.codegenNode as VNodeCall).children).toMatchObject([
@ -286,9 +286,9 @@ describe('compiler: hoistStatic transform', () => {
tag: `"div"`,
props: { content: `_hoisted_1` },
children: { type: NodeTypes.INTERPOLATION },
patchFlag: genFlagText(PatchFlags.TEXT)
}
}
patchFlag: PatchFlags.TEXT,
},
},
])
expect(generate(root).code).toMatchSnapshot()
})
@ -303,30 +303,30 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: [{ type: NodeTypes.ELEMENT, tag: `Comp` }]
}
}
children: [{ type: NodeTypes.ELEMENT, tag: `Comp` }],
},
},
])
expect(generate(root).code).toMatchSnapshot()
})
test('should hoist v-if props/children if static', () => {
const root = transformWithHoist(
`<div><div v-if="ok" id="foo"><span/></div></div>`
`<div><div v-if="ok" id="foo"><span/></div></div>`,
)
expect(root.hoists).toMatchObject([
createObjectMatcher({
key: `[0]`, // key injected by v-if branch
id: 'foo'
id: 'foo',
}),
{
type: NodeTypes.VNODE_CALL,
tag: `"span"`
tag: `"span"`,
},
hoistedChildrenArrayMatcher(2)
hoistedChildrenArrayMatcher(2),
])
expect(
((root.children[0] as ElementNode).children[0] as IfNode).codegenNode
((root.children[0] as ElementNode).children[0] as IfNode).codegenNode,
).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
@ -334,25 +334,25 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: { content: `_hoisted_3` }
}
children: { content: `_hoisted_3` },
},
})
expect(generate(root).code).toMatchSnapshot()
})
test('should hoist v-for children if static', () => {
const root = transformWithHoist(
`<div><div v-for="i in list" id="foo"><span/></div></div>`
`<div><div v-for="i in list" id="foo"><span/></div></div>`,
)
expect(root.hoists).toMatchObject([
createObjectMatcher({
id: 'foo'
id: 'foo',
}),
{
type: NodeTypes.VNODE_CALL,
tag: `"span"`
tag: `"span"`,
},
hoistedChildrenArrayMatcher(2)
hoistedChildrenArrayMatcher(2),
])
const forBlockCodegen = (
(root.children[0] as ElementNode).children[0] as ForNode
@ -363,16 +363,16 @@ describe('compiler: hoistStatic transform', () => {
props: undefined,
children: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST
callee: RENDER_LIST,
},
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT)
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: { content: `_hoisted_3` }
children: { content: `_hoisted_3` },
})
expect(generate(root).code).toMatchSnapshot()
})
@ -382,8 +382,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist(
`<div><span>foo {{ 1 }} {{ true }}</span></div>`,
{
prefixIdentifiers: true
}
prefixIdentifiers: true,
},
)
expect(root.hoists).toMatchObject([
{
@ -391,18 +391,18 @@ describe('compiler: hoistStatic transform', () => {
tag: `"span"`,
props: undefined,
children: {
type: NodeTypes.COMPOUND_EXPRESSION
}
type: NodeTypes.COMPOUND_EXPRESSION,
},
hoistedChildrenArrayMatcher()
},
hoistedChildrenArrayMatcher(),
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_2`
}
content: `_hoisted_2`,
},
})
expect(generate(root).code).toMatchSnapshot()
})
@ -411,8 +411,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist(
`<div><span :foo="0">{{ 1 }}</span></div>`,
{
prefixIdentifiers: true
}
prefixIdentifiers: true,
},
)
expect(root.hoists).toMatchObject([
@ -425,19 +425,19 @@ describe('compiler: hoistStatic transform', () => {
content: {
content: `1`,
isStatic: false,
constType: ConstantTypes.CAN_STRINGIFY
}
}
constType: ConstantTypes.CAN_STRINGIFY,
},
hoistedChildrenArrayMatcher()
},
},
hoistedChildrenArrayMatcher(),
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_2`
}
content: `_hoisted_2`,
},
})
expect(generate(root).code).toMatchSnapshot()
})
@ -446,8 +446,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist(
`<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
{
prefixIdentifiers: true
}
prefixIdentifiers: true,
},
)
expect(root.hoists).toMatchObject([
@ -458,7 +458,7 @@ describe('compiler: hoistStatic transform', () => {
key: {
content: `class`,
isStatic: true,
constType: ConstantTypes.CAN_STRINGIFY
constType: ConstantTypes.CAN_STRINGIFY,
},
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
@ -467,13 +467,13 @@ describe('compiler: hoistStatic transform', () => {
{
content: `{ foo: true }`,
isStatic: false,
constType: ConstantTypes.CAN_STRINGIFY
}
]
}
}
]
}
constType: ConstantTypes.CAN_STRINGIFY,
},
],
},
},
],
},
])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
@ -486,20 +486,20 @@ describe('compiler: hoistStatic transform', () => {
tag: `"span"`,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
content: `_hoisted_1`,
},
children: {
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.bar`,
isStatic: false,
constType: ConstantTypes.NOT_CONSTANT
}
constType: ConstantTypes.NOT_CONSTANT,
},
patchFlag: `1 /* TEXT */`
}
}
]
},
patchFlag: PatchFlags.TEXT,
},
},
],
})
expect(generate(root).code).toMatchSnapshot()
})
@ -508,8 +508,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist(
`<div><p v-for="o in list"><span>{{ o }}</span></p></div>`,
{
prefixIdentifiers: true
}
prefixIdentifiers: true,
},
)
expect(root.hoists.length).toBe(0)
@ -520,8 +520,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist(
`<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`,
{
prefixIdentifiers: true
}
prefixIdentifiers: true,
},
)
expect(root.hoists.length).toBe(0)
@ -532,8 +532,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist(
`<Comp v-slot="{ foo }">{{ foo }}</Comp>`,
{
prefixIdentifiers: true
}
prefixIdentifiers: true,
},
)
expect(root.hoists.length).toBe(0)
@ -545,8 +545,8 @@ describe('compiler: hoistStatic transform', () => {
`<div><div><div @click="foo"/></div></div>`,
{
prefixIdentifiers: true,
cacheHandlers: true
}
cacheHandlers: true,
},
)
expect(root.cached).toBe(1)
@ -554,8 +554,8 @@ describe('compiler: hoistStatic transform', () => {
expect(
generate(root, {
mode: 'module',
prefixIdentifiers: true
}).code
prefixIdentifiers: true,
}).code,
).toMatchSnapshot()
})
@ -564,8 +564,8 @@ describe('compiler: hoistStatic transform', () => {
`<div><div><div :class="{}" @click="foo"/></div></div>`,
{
prefixIdentifiers: true,
cacheHandlers: true
}
cacheHandlers: true,
},
)
expect(root.cached).toBe(1)
@ -573,14 +573,14 @@ describe('compiler: hoistStatic transform', () => {
expect(
generate(root, {
mode: 'module',
prefixIdentifiers: true
}).code
prefixIdentifiers: true,
}).code,
).toMatchSnapshot()
})
test('should NOT hoist keyed template v-for with plain element child', () => {
const root = transformWithHoist(
`<div><template v-for="item in items" :key="item"><span/></template></div>`
`<div><template v-for="item in items" :key="item"><span/></template></div>`,
)
expect(root.hoists.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
@ -588,10 +588,22 @@ describe('compiler: hoistStatic transform', () => {
test('should NOT hoist SVG with directives', () => {
const root = transformWithHoist(
`<div><svg v-foo><path d="M2,3H5.5L12"/></svg></div>`
`<div><svg v-foo><path d="M2,3H5.5L12"/></svg></div>`,
)
expect(root.hoists.length).toBe(2)
expect(generate(root).code).toMatchSnapshot()
})
test('clone hoisted array children in HMR mode', () => {
const root = transformWithHoist(`<div><span class="hi"></span></div>`, {
hmr: true,
})
expect(root.hoists.length).toBe(2)
expect(root.codegenNode).toMatchObject({
children: {
content: '[..._hoisted_2]',
},
})
})
})
})

View File

@ -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

View File

@ -1,14 +1,14 @@
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'
@ -16,23 +16,27 @@ 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`,
})
})
@ -40,34 +44,34 @@ describe('compiler: expression transform', () => {
const node = parseWithExpressionTransform(`{{}}`) as InterpolationNode
const node2 = parseWithExpressionTransform(`{{ }}`) as InterpolationNode
const node3 = parseWithExpressionTransform(
`<div>{{ }}</div>`
`<div>{{ }}</div>`,
) 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(
`<div>{{ foo }}</div>`
`<div>{{ foo }}</div>`,
) 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(
`<div>{{ foo + bar(baz.qux) }}</div>`
`<div>{{ foo + bar(baz.qux) }}</div>`,
) as ElementNode
const node = el.children[0] as InterpolationNode
expect(node.content).toMatchObject({
@ -80,46 +84,46 @@ describe('compiler: expression transform', () => {
{ content: `_ctx.baz` },
`.`,
{ content: `qux` },
`)`
]
`)`,
],
})
})
test('directive value', () => {
const node = parseWithExpressionTransform(
`<div v-foo:arg="baz"/>`
`<div v-foo:arg="baz"/>`,
) 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(
`<div v-foo:[arg]="baz"/>`
`<div v-foo:[arg]="baz"/>`,
) 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({
@ -128,76 +132,57 @@ describe('compiler: expression transform', () => {
{
content: `_ctx.foo`,
loc: {
source: `foo`,
start: {
offset: 3,
line: 1,
column: 4
start: { offset: 3, line: 1, column: 4 },
end: { offset: 6, line: 1, column: 7 },
},
end: {
offset: 6,
line: 1,
column: 7
}
}
},
`(`,
{
content: `_ctx.baz`,
loc: {
source: `baz`,
start: {
offset: 7,
line: 1,
column: 8
start: { offset: 7, line: 1, column: 8 },
end: { offset: 10, line: 1, column: 11 },
},
end: {
offset: 10,
line: 1,
column: 11
}
}
},
` + 1, { key: `,
{
content: `_ctx.kuz`,
loc: {
source: `kuz`,
start: {
offset: 23,
line: 1,
column: 24
start: { offset: 23, line: 1, column: 24 },
end: { offset: 26, line: 1, column: 27 },
},
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`)
@ -208,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,
@ -217,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,
@ -233,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,
@ -252,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,
@ -269,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,
@ -287,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,
@ -306,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,
@ -321,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,
@ -347,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,
@ -373,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,
@ -390,8 +376,8 @@ describe('compiler: expression transform', () => {
{ content: `_ctx.bar` },
`][`,
{ content: '_ctx.baz' },
`]`
]
`]`,
],
})
})
@ -399,23 +385,34 @@ describe('compiler: expression transform', () => {
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(
`<p :id="undefined /* force override the id */"/>`,
{
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,
@ -425,47 +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] }}`
`{{ [1, 2.1] }}`,
) as InterpolationNode
expect(node.content).toMatchObject({
constType: ConstantTypes.CAN_STRINGIFY
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(
`<div @click="() => {
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(
`<div @click="() => {
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(
`<div @click="() => {
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(
`<div @click="() => {
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(
`<div @click="() => {
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(
`<div @click="() => {
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,
@ -474,8 +580,8 @@ describe('compiler: expression transform', () => {
`?.`,
{ content: `b` },
`?.`,
{ content: `c` }
]
{ content: `c` },
],
})
})
@ -486,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` },
],
})
})
})
@ -506,26 +616,28 @@ describe('compiler: expression transform', () => {
data: BindingTypes.DATA,
options: BindingTypes.OPTIONS,
reactive: BindingTypes.SETUP_REACTIVE_CONST,
literal: BindingTypes.LITERAL_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(
`<div>{{ props }} {{ setup }} {{ data }} {{ options }}</div>`
`<div>{{ props }} {{ setup }} {{ data }} {{ options }} {{ isNaN }}</div>`,
)
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`)
@ -534,25 +646,26 @@ describe('compiler: expression transform', () => {
test('inline mode', () => {
const { code } = compileWithBindingMetadata(
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }}</div>`,
{ inline: true }
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}</div>`,
{ 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(`<div>{{ literal }}</div>`, {
inline: true
inline: true,
})
expect(code).toMatch(`toDisplayString(literal)`)
// #7973 should skip patch for literal const
expect(code).not.toMatch(
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
)
})
@ -561,18 +674,46 @@ describe('compiler: expression transform', () => {
expect(code).toMatch(`toDisplayString($setup.literal)`)
// #7973 should skip patch for literal const
expect(code).not.toMatch(
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
)
})
test('reactive const handling', () => {
const { code } = compileWithBindingMetadata(`<div>{{ reactive }}</div>`, {
inline: true
inline: true,
})
// #7973 should not skip patch for reactive const
expect(code).toMatch(
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
`${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`,
},
` })()`,
],
})
})
})
})

View File

@ -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 <slot> 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 <slot> 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 <slot> outlets', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`,
isStatic: false
}
]
isStatic: false,
},
],
})
})
test('dynamically named slot outlet w/ prefixIdentifiers: true', () => {
const ast = parseWithSlots(`<slot :name="foo + bar" />`, {
prefixIdentifiers: true
prefixIdentifiers: true,
})
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
@ -80,23 +80,23 @@ describe('compiler: transform <slot> 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(
`<slot foo="bar" :baz="qux" :foo-bar="foo-bar" />`
`<slot foo="bar" :baz="qux" :foo-bar="foo-bar" />`,
)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
@ -110,36 +110,36 @@ describe('compiler: transform <slot> 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 <slot> 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 <slot> 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 <slot> outlets', () => {
returns: [
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
}
]
tag: `div`,
},
],
},
],
})
})
@ -257,11 +257,11 @@ describe('compiler: transform <slot> outlets', () => {
returns: [
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
}
]
tag: `div`,
},
],
},
],
})
})
@ -279,14 +279,14 @@ describe('compiler: transform <slot> 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 <slot> outlets', () => {
returns: [
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
}
]
tag: `div`,
},
],
},
],
})
})
@ -316,14 +316,14 @@ describe('compiler: transform <slot> 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 <slot> outlets', () => {
returns: [
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
}
]
tag: `div`,
},
],
},
],
})
})
@ -344,11 +344,11 @@ describe('compiler: transform <slot> 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(`<slot>fallback</slot>`, {
slotted: false,
scopeId: 'foo'
scopeId: 'foo',
})
const child = {
@ -357,14 +357,14 @@ describe('compiler: transform <slot> 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`],
})
})
@ -376,18 +376,33 @@ describe('compiler: transform <slot> outlets', () => {
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(`<slot :name />`)
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,
},
],
})
})
})

View File

@ -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(
`<div/>{{ foo }} bar {{ baz }}<div/>hello<div/>`
`<div/>{{ foo }} bar {{ baz }}<div/>hello<div/>`,
)
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('<template v-for>', () => {
const root = transformWithTextOpt(
`<template v-for="i in list">foo</template>`
`<template v-for="i in list">foo</template>`,
)
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()
})

View File

@ -1,37 +1,37 @@
import {
type CallExpression,
type CompilerOptions,
type ElementNode,
ErrorCodes,
NodeTypes,
type ObjectExpression,
type VNodeCall,
baseParse as parse,
transform,
ElementNode,
ObjectExpression,
CompilerOptions,
ErrorCodes,
VNodeCall,
NodeTypes,
CallExpression
} 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(`<div v-bind:id />`)
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(`<div :id />`)
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', () => {
test('should error if empty expression', () => {
const onError = vi.fn()
const node = parseWithVBind(`<div v-bind:arg />`, { onError })
const node = parseWithVBind(`<div v-bind:arg="" />`, { 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(`<div v-bind:foo-bar.camel />`)
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(`<div v-bind:[foo(bar)].camel="id"/>`, {
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(`<div v-bind:fooBar.prop />`)
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(`<div v-bind:[foo(bar)].prop="id"/>`, {
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(`<div .fooBar />`)
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(`<div v-bind:foo-bar.attr />`)
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(`<div v-bind:[arg] />`, { 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,
},
},
})
})
})

View File

@ -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,23 +7,23 @@ 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 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 = {},
) {
const ast = parse(template, options)
transform(ast, {
@ -32,16 +32,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 +49,7 @@ describe('compiler: v-for', () => {
describe('transform', () => {
test('number expression', () => {
const { node: forNode } = parseWithForTransform(
'<span v-for="index in 5" />'
'<span v-for="index in 5" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
@ -59,7 +59,7 @@ describe('compiler: v-for', () => {
test('value', () => {
const { node: forNode } = parseWithForTransform(
'<span v-for="(item) in items" />'
'<span v-for="(item) in items" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
@ -69,31 +69,31 @@ describe('compiler: v-for', () => {
test('object de-structured value', () => {
const { node: forNode } = parseWithForTransform(
'<span v-for="({ id, value }) in items" />'
'<span v-for="({ id, value }) in items" />',
)
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(
'<span v-for="([ id, value ]) in items" />'
'<span v-for="([ id, value ]) in items" />',
)
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(
'<span v-for="(item, key) in items" />'
'<span v-for="(item, key) in items" />',
)
expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
@ -104,13 +104,13 @@ describe('compiler: v-for', () => {
test('value, key and index', () => {
const { node: forNode } = parseWithForTransform(
'<span v-for="(value, key, index) in items" />'
'<span v-for="(value, key, index) in items" />',
)
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 +118,12 @@ describe('compiler: v-for', () => {
test('skipped key', () => {
const { node: forNode } = parseWithForTransform(
'<span v-for="(value,,index) in items" />'
'<span v-for="(value,,index) in items" />',
)
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 +131,12 @@ describe('compiler: v-for', () => {
test('skipped value and key', () => {
const { node: forNode } = parseWithForTransform(
'<span v-for="(,,index) in items" />'
'<span v-for="(,,index) in items" />',
)
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 +144,7 @@ describe('compiler: v-for', () => {
test('unbracketed value', () => {
const { node: forNode } = parseWithForTransform(
'<span v-for="item in items" />'
'<span v-for="item in items" />',
)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
@ -154,7 +154,7 @@ describe('compiler: v-for', () => {
test('unbracketed value and key', () => {
const { node: forNode } = parseWithForTransform(
'<span v-for="item, key in items" />'
'<span v-for="item, key in items" />',
)
expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
@ -165,13 +165,13 @@ describe('compiler: v-for', () => {
test('unbracketed value, key and index', () => {
const { node: forNode } = parseWithForTransform(
'<span v-for="value, key, index in items" />'
'<span v-for="value, key, index in items" />',
)
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 +179,12 @@ describe('compiler: v-for', () => {
test('unbracketed skipped key', () => {
const { node: forNode } = parseWithForTransform(
'<span v-for="value, , index in items" />'
'<span v-for="value, , index in items" />',
)
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,16 +192,28 @@ describe('compiler: v-for', () => {
test('unbracketed skipped value and key', () => {
const { node: forNode } = parseWithForTransform(
'<span v-for=", , index in items" />'
'<span v-for=", , index in items" />',
)
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(
`<span v-for="item in state ['my items']" />`,
)
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', () => {
@ -212,8 +224,8 @@ describe('compiler: v-for', () => {
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_FOR_NO_EXPRESSION
})
code: ErrorCodes.X_V_FOR_NO_EXPRESSION,
}),
)
})
@ -224,8 +236,8 @@ describe('compiler: v-for', () => {
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION
})
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
}),
)
})
@ -236,8 +248,8 @@ describe('compiler: v-for', () => {
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION
})
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
}),
)
})
@ -248,8 +260,20 @@ describe('compiler: v-for', () => {
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('<span v-for="item in " />', { onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
}),
)
})
@ -260,8 +284,8 @@ describe('compiler: v-for', () => {
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION
})
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
}),
)
})
@ -272,14 +296,14 @@ describe('compiler: v-for', () => {
<template v-for="item in items">
<div :key="item.id"/>
</template>`,
{ 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 +312,7 @@ describe('compiler: v-for', () => {
<template v-for="item in items">
<div v-for="c in item.children" :key="c.id"/>
</template>`,
{ onError }
{ onError },
)
expect(onError).toHaveBeenCalledTimes(1)
})
@ -315,7 +339,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 +363,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 +387,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 +429,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 +462,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 +470,18 @@ describe('compiler: v-for', () => {
describe('prefixIdentifiers: true', () => {
test('should prefix v-for source', () => {
const { node } = parseWithForTransform(`<div v-for="i in list"/>`, {
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(
`<div v-for="i in list.concat([foo])"/>`,
{ prefixIdentifiers: true }
{ prefixIdentifiers: true },
)
expect(node.source).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@ -467,31 +491,31 @@ describe('compiler: v-for', () => {
{ content: `concat` },
`([`,
{ content: `_ctx.foo` },
`])`
]
`])`,
],
})
})
test('should not prefix v-for alias', () => {
const { node } = parseWithForTransform(
`<div v-for="i in list">{{ i }}{{ j }}</div>`,
{ 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(
`<div v-for="(i, j, k) in list">{{ i + j + k }}{{ l }}</div>`,
{ prefixIdentifiers: true }
{ prefixIdentifiers: true },
)
const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({
@ -501,23 +525,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(
`<div><div v-for="i in list" />{{ i }}</div>`,
{ prefixIdentifiers: true }
{ prefixIdentifiers: true },
)
expect((node.children[1] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.i`
content: `_ctx.i`,
})
})
@ -526,7 +550,7 @@ describe('compiler: v-for', () => {
`<div v-for="i in list">
<div v-for="i in list">{{ i + j }}</div>{{ i }}
</div>`,
{ prefixIdentifiers: true }
{ prefixIdentifiers: true },
)
const outerDiv = node.children[0] as ElementNode
const innerFor = outerDiv.children[0] as ForNode
@ -534,7 +558,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 +566,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 +575,7 @@ describe('compiler: v-for', () => {
`<div v-for="({ foo = bar, baz: [qux = quux] }) in list">
{{ foo + bar + baz + qux + quux }}
</div>`,
{ prefixIdentifiers: true }
{ prefixIdentifiers: true },
)
expect(node.valueAlias!).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
@ -564,8 +588,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 +603,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(
'<div v-for="item in items" :key="itemKey(item)">test</div>',
{ prefixIdentifiers: true }
{ prefixIdentifiers: true },
)
const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({
@ -604,20 +628,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(
'<template v-for="item in items" :key="itemKey(item)">test</template>',
{ prefixIdentifiers: true }
{ prefixIdentifiers: true },
)
const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({
@ -632,19 +656,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(
'<template v-for="item in items" key="key">test</template>',
{ prefixIdentifiers: true }
{ prefixIdentifiers: true },
)
const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({
@ -653,9 +677,9 @@ describe('compiler: v-for', () => {
props: createObjectMatcher({
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'key'
}
})
content: 'key',
},
}),
})
})
})
@ -665,17 +689,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 +711,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('<span v-for="(item) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
innerVNodeCall: {
tag: `"span"`
}
tag: `"span"`,
},
})
expect(generate(root).code).toMatchSnapshot()
})
@ -720,11 +746,11 @@ describe('compiler: v-for', () => {
test('value + key + index', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform('<span v-for="(item, key, index) in items" />')
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 +758,11 @@ describe('compiler: v-for', () => {
test('skipped value', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform('<span v-for="(, key, index) in items" />')
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 +770,11 @@ describe('compiler: v-for', () => {
test('skipped key', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform('<span v-for="(item,,index) in items" />')
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 +782,11 @@ describe('compiler: v-for', () => {
test('skipped value & key', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform('<span v-for="(,,index) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
params: [{ content: `_` }, { content: `__` }, { content: `index` }]
params: [{ content: `_` }, { content: `__` }, { content: `index` }],
})
expect(generate(root).code).toMatchSnapshot()
})
@ -768,9 +794,9 @@ describe('compiler: v-for', () => {
test('v-for with constant expression', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform('<p v-for="item in 10">{{item}}</p>', {
prefixIdentifiers: true
prefixIdentifiers: true,
})
expect(
@ -778,8 +804,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 +819,11 @@ describe('compiler: v-for', () => {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item',
isStatic: false,
constType: ConstantTypes.NOT_CONSTANT
}
constType: ConstantTypes.NOT_CONSTANT,
},
},
patchFlag: PatchFlags.TEXT,
},
patchFlag: genFlagText(PatchFlags.TEXT)
}
})
expect(generate(root).code).toMatchSnapshot()
})
@ -805,9 +831,9 @@ describe('compiler: v-for', () => {
test('template v-for', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform(
'<template v-for="item in items">hello<span/></template>'
'<template v-for="item in items">hello<span/></template>',
)
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
@ -818,10 +844,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 +855,19 @@ describe('compiler: v-for', () => {
test('template v-for w/ <slot/>', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform(
'<template v-for="item in items"><slot/></template>'
'<template v-for="item in items"><slot/></template>',
)
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 +876,9 @@ describe('compiler: v-for', () => {
test('template v-for key injection with single child', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform(
'<template v-for="item in items" :key="item.id"><span :id="item.id" /></template>'
'<template v-for="item in items" :key="item.id"><span :id="item.id" /></template>',
)
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
@ -862,9 +888,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 +898,17 @@ describe('compiler: v-for', () => {
test('v-for on <slot/>', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform('<slot v-for="item in items"></slot>')
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 +916,7 @@ describe('compiler: v-for', () => {
test('keyed v-for', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform('<span v-for="(item) in items" :key="item" />')
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
@ -898,9 +924,9 @@ describe('compiler: v-for', () => {
innerVNodeCall: {
tag: `"span"`,
props: createObjectMatcher({
key: `[item]`
})
}
key: `[item]`,
}),
},
})
expect(generate(root).code).toMatchSnapshot()
})
@ -908,9 +934,9 @@ describe('compiler: v-for', () => {
test('keyed template v-for', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform(
'<template v-for="item in items" :key="item">hello<span/></template>'
'<template v-for="item in items" :key="item">hello<span/></template>',
)
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
@ -918,14 +944,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 +959,7 @@ describe('compiler: v-for', () => {
test('v-if + v-for', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform(`<div v-if="ok" v-for="i in list"/>`)
expect(codegenNode).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@ -941,11 +967,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 +983,12 @@ describe('compiler: v-for', () => {
returns: {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
isBlock: true
}
}
]
}
}
isBlock: true,
},
},
],
},
},
})
expect(generate(root).code).toMatchSnapshot()
})
@ -971,7 +997,7 @@ describe('compiler: v-for', () => {
test('v-if + v-for on <template>', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform(`<template v-if="ok" v-for="i in list"/>`)
expect(codegenNode).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@ -979,11 +1005,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 +1021,12 @@ describe('compiler: v-for', () => {
returns: {
type: NodeTypes.VNODE_CALL,
tag: FRAGMENT,
isBlock: true
}
}
]
}
}
isBlock: true,
},
},
],
},
},
})
expect(generate(root).code).toMatchSnapshot()
})
@ -1008,14 +1034,40 @@ describe('compiler: v-for', () => {
test('v-for on element with custom directive', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithForTransform('<div v-for="i in list" v-foo/>')
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('<div v-for="key in keys" :key>test</div>')
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(
'<template v-for="key in keys" :key><div>test</div></template>',
)
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `keys` },
params: [{ content: `key` }],
innerVNodeCall: {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
key: '[key]',
}),
},
})
})
})
})

View File

@ -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(
`<template v-if="ok"><div/>hello<p/></template>`
`<template v-if="ok"><div/>hello<p/></template>`,
)
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(
`<div v-if="ok"/><p v-else-if="orNot"/>`
`<div v-if="ok"/><p v-else-if="orNot"/>`,
)
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(
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`,
)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(3)
@ -202,11 +202,11 @@ describe('compiler: v-if', () => {
test('should prefix v-if condition', () => {
const { node } = parseWithIfTransform(`<div v-if="ok"/>`, {
prefixIdentifiers: true
prefixIdentifiers: true,
})
expect(node.branches[0].condition).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.ok`
content: `_ctx.ok`,
})
})
})
@ -219,32 +219,32 @@ describe('compiler: v-if', () => {
expect(onError.mock.calls[0]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node1.loc
}
loc: node1.loc,
},
])
const { node: node2 } = parseWithIfTransform(
`<div/><div v-else/>`,
{ 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(
`<div/>foo<div v-else/>`,
{ onError },
2
2,
)
expect(onError.mock.calls[2]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node3.loc
}
loc: node3.loc,
},
])
})
@ -252,52 +252,52 @@ describe('compiler: v-if', () => {
const onError = vi.fn()
const { node: node1 } = parseWithIfTransform(`<div v-else-if="foo"/>`, {
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(
`<div/><div v-else-if="foo"/>`,
{ 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(
`<div/>foo<div v-else-if="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(
`<div v-if="notOk"/><div v-else/><div v-else-if="ok"/>`,
{ 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,
},
])
})
@ -306,21 +306,21 @@ describe('compiler: v-if', () => {
// dynamic
parseWithIfTransform(
`<div v-if="ok" :key="a + 1" /><div v-else :key="a + 1" />`,
{ onError }
{ onError },
)
expect(onError.mock.calls[0]).toMatchObject([
{
code: ErrorCodes.X_V_IF_SAME_KEY
}
code: ErrorCodes.X_V_IF_SAME_KEY,
},
])
// static
parseWithIfTransform(`<div v-if="ok" key="1" /><div v-else key="1" />`, {
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(`<div v-if="ok"/>`)
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(`<template v-if="ok"><div/>hello<p/></template>`)
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 <slot/> child', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithIfTransform(`<template v-if="ok"><slot/></template>`)
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 <slot/>', () => {
const {
root,
node: { codegenNode }
node: { codegenNode },
} = parseWithIfTransform(`<slot v-if="ok"></slot>`)
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(`<div v-if="ok"/><p v-else/>`)
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(`<div v-if="ok"/><p v-else-if="orNot" />`)
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(
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`,
)
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', () => {
`<div v-if="ok"/><p v-if="orNot"/>`,
{},
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', () => {
`<div v-if="ok"/><p v-else/><div v-if="another"/><p v-else-if="orNot"/><p v-else/>`,
{},
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(`<div v-if="ok" v-bind="obj"/>`)
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(`<div v-if="ok" id="foo" v-bind="obj"/>`)
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(`<div v-if="ok" v-bind="obj" id="foo"/>`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
@ -613,15 +616,15 @@ 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(`<div v-if="ok" v-foo />`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.directives).not.toBeUndefined()
@ -631,7 +634,7 @@ describe('compiler: v-if', () => {
// #6631
test('avoid duplicate keys', () => {
const {
node: { codegenNode }
node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok" key="custom_key" v-bind="obj"/>`)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
@ -639,31 +642,31 @@ describe('compiler: v-if', () => {
callee: MERGE_PROPS,
arguments: [
createObjectMatcher({
key: 'custom_key'
key: 'custom_key',
}),
{ content: `obj` }
]
{ content: `obj` },
],
})
})
test('with spaces between branches', () => {
const {
node: { codegenNode }
node: { codegenNode },
} = parseWithIfTransform(
`<div v-if="ok"/> <div v-else-if="no"/> <div v-else/>`
`<div v-if="ok"/> <div v-else-if="no"/> <div v-else/>`,
)
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]` }),
})
})
@ -729,7 +732,7 @@ describe('compiler: v-if', () => {
<p/>
</template>
`,
{ comments: true }
{ comments: true },
)
__DEV__ = true
})
@ -737,21 +740,21 @@ describe('compiler: v-if', () => {
test('v-on with v-if', () => {
const {
node: { codegenNode }
node: { codegenNode },
} = parseWithIfTransform(
`<button v-on="{ click: clickEvent }" v-if="true">w/ v-if</button>`
`<button v-on="{ click: clickEvent }" v-if="true">w/ v-if</button>`,
)
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,
)
})
})

View File

@ -4,7 +4,7 @@ describe('compiler: v-memo transform', () => {
function compile(content: string) {
return baseCompile(`<div>${content}</div>`, {
mode: 'module',
prefixIdentifiers: true
prefixIdentifiers: true,
}).code
}
@ -12,8 +12,8 @@ describe('compiler: v-memo transform', () => {
expect(
baseCompile(`<div v-memo="[x]"></div>`, {
mode: 'module',
prefixIdentifiers: true
}).code
prefixIdentifiers: true,
}).code,
).toMatchSnapshot()
})
@ -29,8 +29,8 @@ describe('compiler: v-memo transform', () => {
expect(
compile(
`<div v-if="ok" v-memo="[x]"><span>foo</span>bar</div>
<Comp v-else v-memo="[x]"></Comp>`
)
<Comp v-else v-memo="[x]"></Comp>`,
),
).toMatchSnapshot()
})
@ -39,8 +39,8 @@ describe('compiler: v-memo transform', () => {
compile(
`<div v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]">
<span>foobar</span>
</div>`
)
</div>`,
),
).toMatchSnapshot()
})
@ -49,8 +49,8 @@ describe('compiler: v-memo transform', () => {
compile(
`<template v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]">
<span>foobar</span>
</template>`
)
</template>`,
),
).toMatchSnapshot()
})
})

View File

@ -1,17 +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,
BindingTypes
} from '../../src'
import { ErrorCodes } from '../../src/errors'
import { transformModel } from '../../src/transforms/vModel'
@ -19,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)
@ -29,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
@ -51,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()
@ -81,7 +81,7 @@ describe('compiler: transform v-model', () => {
test('simple expression (with prefixIdentifiers)', () => {
const root = parseWithVModel('<input v-model="model" />', {
prefixIdentifiers: true
prefixIdentifiers: true,
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
@ -90,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()
@ -128,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()
@ -165,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()
@ -195,7 +195,7 @@ describe('compiler: transform v-model', () => {
test('compound expression (with prefixIdentifiers)', () => {
const root = parseWithVModel('<input v-model="model[index]" />', {
prefixIdentifiers: true
prefixIdentifiers: true,
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
@ -204,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: [
@ -234,19 +234,19 @@ 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()
@ -260,29 +260,29 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({
key: {
content: 'foo-value',
isStatic: true
isStatic: true,
},
value: {
content: 'model',
isStatic: false
}
isStatic: false,
},
})
expect(props[1]).toMatchObject({
key: {
content: 'onUpdate:fooValue',
isStatic: true
isStatic: true,
},
value: {
children: [
'$event => ((',
{
content: 'model',
isStatic: false
isStatic: false,
},
') = $event)',
],
},
') = $event)'
]
}
})
expect(generate(root).code).toMatchSnapshot()
@ -304,12 +304,12 @@ describe('compiler: transform v-model', () => {
{
key: {
content: 'value',
isStatic: false
isStatic: false,
},
value: {
content: 'model',
isStatic: false
}
isStatic: false,
},
},
{
key: {
@ -317,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()
@ -342,7 +342,7 @@ describe('compiler: transform v-model', () => {
test('with dynamic argument (with prefixIdentifiers)', () => {
const root = parseWithVModel('<input v-model:[value]="model" />', {
prefixIdentifiers: true
prefixIdentifiers: true,
})
const node = root.children[0] as ElementNode
const props = (node.codegenNode as VNodeCall)
@ -358,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: {
@ -371,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()
@ -397,7 +397,7 @@ describe('compiler: transform v-model', () => {
test('should cache update handler w/ cacheHandlers: true', () => {
const root = parseWithVModel('<input v-model="foo" />', {
prefixIdentifiers: true,
cacheHandlers: true
cacheHandlers: true,
})
expect(root.cached).toBe(1)
const codegen = (root.children[0] as PlainElementNode)
@ -405,7 +405,7 @@ describe('compiler: transform v-model', () => {
// 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,
)
})
@ -414,8 +414,8 @@ describe('compiler: transform v-model', () => {
'<input v-for="i in list" v-model="foo[i]" />',
{
prefixIdentifiers: true,
cacheHandlers: true
}
cacheHandlers: true,
},
)
expect(root.cached).toBe(0)
const codegen = (
@ -423,14 +423,14 @@ describe('compiler: transform v-model', () => {
).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('<div v-once><input v-model="foo" /></div>', {
prefixIdentifiers: true,
cacheHandlers: true
cacheHandlers: true,
})
expect(root.cached).not.toBe(2)
expect(root.cached).toBe(1)
@ -440,8 +440,8 @@ describe('compiler: transform v-model', () => {
const root = parseWithVModel(
'<Comp v-slot="{ foo }"><input v-model="foo.bar"/></Comp>',
{
prefixIdentifiers: true
}
prefixIdentifiers: true,
},
)
const codegen = (
(root.children[0] as ComponentNode).children[0] as PlainElementNode
@ -451,7 +451,7 @@ describe('compiler: transform v-model', () => {
test('should generate modelModifiers for component v-model', () => {
const root = parseWithVModel('<Comp v-model.trim.bar-baz="foo" />', {
prefixIdentifiers: true
prefixIdentifiers: true,
})
const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall
@ -462,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
@ -475,8 +478,8 @@ describe('compiler: transform v-model', () => {
const root = parseWithVModel(
'<Comp v-model:foo.trim="foo" v-model:bar.number="bar" />',
{
prefixIdentifiers: true
}
prefixIdentifiers: true,
},
)
const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall
@ -487,20 +490,20 @@ 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"]`,
)
})
@ -512,8 +515,8 @@ describe('compiler: transform v-model', () => {
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_MODEL_NO_EXPRESSION
})
code: ErrorCodes.X_V_MODEL_NO_EXPRESSION,
}),
)
})
@ -524,8 +527,8 @@ describe('compiler: transform v-model', () => {
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION
})
code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION,
}),
)
})
@ -536,8 +539,8 @@ describe('compiler: transform v-model', () => {
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION
})
code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION,
}),
)
})
@ -552,14 +555,14 @@ describe('compiler: transform v-model', () => {
const onError = vi.fn()
parseWithVModel('<span v-for="i in list" v-model="i" />', {
onError,
prefixIdentifiers: true
prefixIdentifiers: true,
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE
})
code: ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE,
}),
)
})
@ -568,15 +571,15 @@ describe('compiler: transform v-model', () => {
parseWithVModel('<div v-model="p" />', {
onError,
bindingMetadata: {
p: BindingTypes.PROPS
}
p: BindingTypes.PROPS,
},
})
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_MODEL_ON_PROPS
})
code: ErrorCodes.X_V_MODEL_ON_PROPS,
}),
)
})
})

View File

@ -1,15 +1,16 @@
import {
baseParse as parse,
CompilerOptions,
ElementNode,
type CompilerOptions,
type ElementNode,
ErrorCodes,
TO_HANDLER_KEY,
helperNameMap,
NodeTypes,
ObjectExpression,
type ObjectExpression,
TO_HANDLER_KEY,
type VNodeCall,
helperNameMap,
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(`<div v-on:[event]="handler"/>`, {
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(`<div v-on:[event(foo)]="handler"/>`, {
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(`<div @click="foo($event)"/>`, {
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(`<div @click="foo($event);bar()"/>`, {
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,14 +265,14 @@ 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)', () => {
test('should NOT wrap as function if expression is already function expression (with TypeScript)', () => {
const { node } = parseWithVOn(`<div @click="(e: any): any => foo(e)"/>`)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@ -279,10 +280,27 @@ describe('compiler: transform v-on', () => {
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `(e: any): any => foo(e)`
}
}
]
content: `(e: any): any => foo(e)`,
},
},
],
})
})
test('should NOT wrap as function if expression is already function expression (async)', () => {
const { node } = parseWithVOn(
`<div @click="async $event => 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)`,
},
},
],
})
})
@ -292,7 +310,7 @@ describe('compiler: transform v-on', () => {
$event => {
foo($event)
}
"/>`
"/>`,
)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@ -304,10 +322,10 @@ describe('compiler: transform v-on', () => {
$event => {
foo($event)
}
`
}
}
]
`,
},
},
],
})
})
@ -317,7 +335,7 @@ describe('compiler: transform v-on', () => {
function($event) {
foo($event)
}
"/>`
"/>`,
)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@ -329,10 +347,10 @@ describe('compiler: transform v-on', () => {
function($event) {
foo($event)
}
`
}
}
]
`,
},
},
],
})
})
@ -344,16 +362,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(`<div @click="a['b' + c]"/>`, {
prefixIdentifiers: true
prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@ -365,17 +383,17 @@ describe('compiler: transform v-on', () => {
{ content: `_ctx.a` },
`['b' + `,
{ content: `_ctx.c` },
`]`
]
}
}
]
`]`,
],
},
},
],
})
})
test('function expression w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="e => foo(e)"/>`, {
prefixIdentifiers: true
prefixIdentifiers: true,
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
@ -389,11 +407,11 @@ describe('compiler: transform v-on', () => {
{ content: `_ctx.foo` },
`(`,
{ content: `e` },
`)`
]
}
}
]
`)`,
],
},
},
],
})
})
@ -405,13 +423,13 @@ describe('compiler: transform v-on', () => {
loc: {
start: {
line: 1,
column: 6
column: 6,
},
end: {
line: 1,
column: 16
}
}
column: 16,
},
},
})
})
@ -427,57 +445,57 @@ describe('compiler: transform v-on', () => {
properties: [
{
key: {
content: `onFooBar`
content: `onFooBar`,
},
value: {
content: `onMount`
}
}
]
content: `onMount`,
},
},
],
})
})
// TODO remove in 3.4
test('case conversion for vnode hooks', () => {
const { node } = parseWithVOn(`<div v-on:vnode-mounted="onMount"/>`)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
content: `onVnodeMounted`
test('error for vnode hooks', () => {
const onError = vi.fn()
parseWithVOn(`<div v-on:vnode-mounted="onMount"/>`, { onError })
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_VNODE_HOOKS,
loc: {
start: {
line: 1,
column: 11,
},
end: {
line: 1,
column: 24,
},
},
value: {
content: `onMount`
}
}
]
})
expect('@vnode-* hooks in templates are deprecated').toHaveBeenWarned()
})
test('vue: prefixed events', () => {
const { node } = parseWithVOn(
`<div v-on:vue:mounted="onMount" @vue:before-update="onBeforeUpdate" />`
`<div v-on:vue:mounted="onMount" @vue:before-update="onBeforeUpdate" />`,
)
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`,
},
},
],
})
})
@ -485,35 +503,35 @@ describe('compiler: transform v-on', () => {
test('empty handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click.prevent />`, {
prefixIdentifiers: true,
cacheHandlers: true
cacheHandlers: true,
})
expect(root.cached).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(`<div v-on:click="foo" />`, {
prefixIdentifiers: true,
cacheHandlers: true
cacheHandlers: true,
})
expect(root.cached).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,
@ -522,23 +540,23 @@ describe('compiler: transform v-on', () => {
children: [
`(...args) => (`,
{ content: `_ctx.foo && _ctx.foo(...args)` },
`)`
]
}
`)`,
],
},
})
})
test('compound member expression handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click="foo.bar" />`, {
prefixIdentifiers: true,
cacheHandlers: true
cacheHandlers: true,
})
expect(root.cached).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,
@ -555,12 +573,12 @@ describe('compiler: transform v-on', () => {
{ content: `_ctx.foo` },
`.`,
{ content: `bar` },
`(...args)`
]
`(...args)`,
],
},
`)`,
],
},
`)`
]
}
})
})
@ -568,7 +586,7 @@ describe('compiler: transform v-on', () => {
const { root } = parseWithVOn(`<comp v-on:click="foo" />`, {
prefixIdentifiers: true,
cacheHandlers: true,
isNativeTag: tag => tag === 'div'
isNativeTag: tag => tag === 'div',
})
expect(root.cached).toBe(0)
})
@ -578,31 +596,42 @@ describe('compiler: transform v-on', () => {
`<div v-once><div v-on:click="foo"/></div>`,
{
prefixIdentifiers: true,
cacheHandlers: true
}
cacheHandlers: true,
},
)
expect(root.cached).not.toBe(2)
expect(root.cached).toBe(1)
})
test('unicode identifier should not be cached (v-for)', () => {
const { root } = parseWithVOn(
`<div v-for="项 in items" :key="value"><div v-on:click="foo(项)"/></div>`,
{
prefixIdentifiers: true,
cacheHandlers: true,
},
)
expect(root.cached).toBe(0)
})
test('inline function expression handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click="() => foo()" />`, {
prefixIdentifiers: true,
cacheHandlers: true
cacheHandlers: true,
})
expect(root.cached).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` }, `()`],
},
})
})
@ -611,22 +640,55 @@ describe('compiler: transform v-on', () => {
`<div v-on:click="async () => await foo()" />`,
{
prefixIdentifiers: true,
cacheHandlers: true
}
cacheHandlers: true,
},
)
expect(root.cached).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 () => await `, { content: `_ctx.foo` }, `()`],
},
})
})
test('inline async arrow function with no bracket expression handler', () => {
const { root, node } = parseWithVOn(
`<div v-on:click="async e => await foo(e)" />`,
{
prefixIdentifiers: true,
cacheHandlers: true,
},
)
expect(root.cached).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 `,
{ content: `e` },
` => await `,
{ content: `_ctx.foo` },
`(`,
{ content: `e` },
`)`,
],
},
})
})
@ -635,15 +697,15 @@ describe('compiler: transform v-on', () => {
`<div v-on:click="async function () { await foo() } " />`,
{
prefixIdentifiers: true,
cacheHandlers: true
}
cacheHandlers: true,
},
)
expect(root.cached).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,
@ -652,16 +714,16 @@ describe('compiler: transform v-on', () => {
children: [
`async function () { await `,
{ content: `_ctx.foo` },
`() } `
]
}
`() } `,
],
},
})
})
test('inline statement handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click="foo++" />`, {
prefixIdentifiers: true,
cacheHandlers: true
cacheHandlers: true,
})
expect(root.cached).toBe(1)
expect(root.cached).toBe(1)
@ -669,7 +731,7 @@ describe('compiler: transform v-on', () => {
// 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,
@ -678,9 +740,9 @@ describe('compiler: transform v-on', () => {
children: [
`$event => (`,
{ children: [{ content: `_ctx.foo` }, `++`] },
`)`
]
}
`)`,
],
},
})
})
})

View File

@ -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
}
@ -29,8 +29,8 @@ describe('compiler: v-once transform', () => {
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
tag: `"div"`,
},
})
expect(generate(root).code).toMatchSnapshot()
})
@ -44,8 +44,8 @@ describe('compiler: v-once transform', () => {
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
tag: `"div"`,
},
})
expect(generate(root).code).toMatchSnapshot()
})
@ -59,8 +59,8 @@ describe('compiler: v-once transform', () => {
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
tag: `_component_Comp`
}
tag: `_component_Comp`,
},
})
expect(generate(root).code).toMatchSnapshot()
})
@ -74,8 +74,8 @@ describe('compiler: v-once transform', () => {
index: 0,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT
}
callee: RENDER_SLOT,
},
})
expect(generate(root).code).toMatchSnapshot()
})
@ -90,7 +90,7 @@ describe('compiler: v-once transform', () => {
// cached nodes should be ignored by hoistStatic transform
test('with hoistStatic: true', () => {
const root = transformWithOnce(`<div><div v-once /></div>`, {
hoistStatic: true
hoistStatic: true,
})
expect(root.cached).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
@ -100,8 +100,8 @@ describe('compiler: v-once transform', () => {
index: 0,
value: {
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
tag: `"div"`,
},
})
expect(generate(root).code).toMatchSnapshot()
})
@ -119,14 +119,14 @@ 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"`,
},
},
},
})
})
@ -138,8 +138,8 @@ describe('compiler: v-once transform', () => {
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,
},
})
})
})

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,10 @@
import { TransformContext } from '../src'
import { Position } from '../src/ast'
import type { TransformContext } from '../src'
import type { Position } from '../src/ast'
import {
getInnerRange,
advancePositionWithClone,
isMemberExpressionNode,
isMemberExpressionBrowser,
toValidAssetId
isMemberExpressionNode,
toValidAssetId,
} from '../src/utils'
function p(line: number, column: number, offset: number): Position {
@ -41,32 +40,6 @@ 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) {
// should work
@ -122,6 +95,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 +108,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',
)
})

View File

@ -1,6 +1,6 @@
{
"name": "@vue/compiler-core",
"version": "3.3.4",
"version": "3.4.36",
"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": {
"@babel/parser": "^7.21.3",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
"@babel/parser": "catalog:",
"@vue/shared": "workspace:*",
"entities": "^5.0.0",
"estree-walker": "catalog:",
"source-map-js": "catalog:"
},
"devDependencies": {
"@babel/types": "^7.21.3"
"@babel/types": "catalog:"
}
}

View File

@ -1,31 +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,
OPEN_BLOCK,
FRAGMENT,
WITH_DIRECTIVES,
WITH_MEMO,
CREATE_VNODE,
CREATE_ELEMENT_VNODE,
CREATE_BLOCK,
CREATE_ELEMENT_BLOCK
CREATE_ELEMENT_BLOCK,
CREATE_ELEMENT_VNODE,
type CREATE_SLOTS,
CREATE_VNODE,
type FRAGMENT,
OPEN_BLOCK,
type RENDER_LIST,
type RENDER_SLOT,
WITH_DIRECTIVES,
type WITH_MEMO,
} from './runtimeHelpers'
import { PropsExpression } from './transforms/transformElement'
import { ImportItem, TransformContext } from './transform'
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,
@ -56,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 {
@ -102,6 +103,7 @@ export type TemplateChildNode =
export interface RootNode extends Node {
type: NodeTypes.ROOT
source: string
children: TemplateChildNode[]
helpers: Set<symbol>
components: string[]
@ -112,6 +114,7 @@ export interface RootNode extends Node {
temps: number
ssrHelpers?: symbol[]
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
transformed?: boolean
// v2 compat only
filters?: string[]
@ -128,9 +131,10 @@ export interface BaseElementNode extends Node {
ns: Namespace
tag: string
tagType: ElementTypes
isSelfClosing: boolean
props: Array<AttributeNode | DirectiveNode>
children: TemplateChildNode[]
isSelfClosing?: boolean
innerLoc?: SourceLocation // only for SFC root level elements
}
export interface PlainElementNode extends BaseElementNode {
@ -182,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[]
/**
* optional property to cache the expression parse result for v-for
*/
parseResult?: ForParseResult
forParseResult?: ForParseResult
}
/**
@ -202,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_STRINGIFY,
}
export interface SimpleExpressionNode extends Node {
@ -214,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.
@ -234,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
@ -276,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
@ -298,7 +331,7 @@ export interface VNodeCall extends Node {
| ForRenderListExpression // v-for fragment call
| SimpleExpressionNode // hoisted
| undefined
patchFlag: string | undefined
patchFlag: PatchFlags | undefined
dynamicProps: string | SimpleExpressionNode | undefined
directives: DirectiveArguments | undefined
isBlock: boolean
@ -383,7 +416,7 @@ export interface CacheExpression extends Node {
type: NodeTypes.JS_CACHE_EXPRESSION
index: number
value: JSChildNode
isVNode: boolean
isVOnce: boolean
}
export interface MemoExpression extends CallExpression {
@ -462,7 +495,7 @@ export interface RenderSlotCall extends CallExpression {
string,
string | ExpressionNode,
PropsExpression | '{}',
TemplateChildNode[]
TemplateChildNode[],
]
}
@ -528,7 +561,7 @@ export interface ForCodegenNode extends VNodeCall {
tag: typeof FRAGMENT
props: undefined
children: ForRenderListExpression
patchFlag: string
patchFlag: PatchFlags
disableTracking: boolean
}
@ -538,7 +571,7 @@ export interface ForRenderListExpression extends CallExpression {
}
export interface ForIteratorExpression extends FunctionExpression {
returns: BlockCodegenNode
returns?: BlockCodegenNode
}
// AST Utilities ---------------------------------------------------------------
@ -547,17 +580,18 @@ 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: new Set(),
components: [],
@ -567,7 +601,7 @@ export function createRoot(
cached: 0,
temps: 0,
codegenNode: undefined,
loc
loc: locStub,
}
}
@ -582,7 +616,7 @@ export function createVNodeCall(
isBlock: VNodeCall['isBlock'] = false,
disableTracking: VNodeCall['disableTracking'] = false,
isComponent: VNodeCall['isComponent'] = false,
loc = locStub
loc = locStub,
): VNodeCall {
if (context) {
if (isBlock) {
@ -607,41 +641,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,
}
}
@ -649,38 +683,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,
}
}
@ -691,13 +725,13 @@ type InferCodegenNodeType<T> = T extends typeof RENDER_SLOT
export function createCallExpression<T extends CallExpression['callee']>(
callee: T,
args: CallExpression['arguments'] = [],
loc: SourceLocation = locStub
loc: SourceLocation = locStub,
): InferCodegenNodeType<T> {
return {
type: NodeTypes.JS_CALL_EXPRESSION,
loc,
callee,
arguments: args
arguments: args,
} as InferCodegenNodeType<T>
}
@ -706,7 +740,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,
@ -714,7 +748,7 @@ export function createFunctionExpression(
returns,
newline,
isSlot,
loc
loc,
}
}
@ -722,7 +756,7 @@ export function createConditionalExpression(
test: ConditionalExpression['test'],
consequent: ConditionalExpression['consequent'],
alternate: ConditionalExpression['alternate'],
newline = true
newline = true,
): ConditionalExpression {
return {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@ -730,87 +764,87 @@ export function createConditionalExpression(
consequent,
alternate,
newline,
loc: locStub
loc: locStub,
}
}
export function createCacheExpression(
index: number,
value: JSChildNode,
isVNode: boolean = false
isVOnce: boolean = false,
): CacheExpression {
return {
type: NodeTypes.JS_CACHE_EXPRESSION,
index,
value,
isVNode,
loc: locStub
isVOnce,
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,
}
}
@ -824,7 +858,7 @@ export function getVNodeBlockHelper(ssr: boolean, isComponent: boolean) {
export function convertToBlock(
node: VNodeCall,
{ helper, removeHelper, inSSR }: TransformContext
{ helper, removeHelper, inSSR }: TransformContext,
) {
if (!node.isBlock) {
node.isBlock = true

View File

@ -1,39 +1,45 @@
// 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<string, number> = Object.create(null)
knownIds: Record<string, number> = Object.create(null),
) {
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(root, {
enter(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
enter(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
parent && parentStack.push(parent)
if (
parent &&
@ -44,28 +50,47 @@ export function walkIdentifiers(
}
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)) {
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))
walkFunctionParams(node, id =>
markScopeIdentifier(node, id, knownIds),
)
}
} else if (node.type === 'BlockStatement') {
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<string> }, parent: Node | undefined) {
leave(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
parent && parentStack.pop()
if (node !== rootExp && node.scopeIds) {
for (const id of node.scopeIds) {
@ -75,14 +100,14 @@ export function walkIdentifiers(
}
}
}
}
},
})
}
export function isReferencedIdentifier(
id: Identifier,
parent: Node | null,
parentStack: Node[]
parentStack: Node[],
) {
if (__BROWSER__) {
return false
@ -117,7 +142,7 @@ export function isReferencedIdentifier(
export function isInDestructureAssignment(
parent: Node,
parentStack: Node[]
parentStack: Node[],
): boolean {
if (
parent &&
@ -136,9 +161,22 @@ 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,
) {
for (const p of node.params) {
for (const id of extractIdentifiers(p)) {
@ -149,7 +187,7 @@ export function walkFunctionParams(
export function walkBlockDeclarations(
block: BlockStatement | Program,
onIdent: (node: Identifier) => void
onIdent: (node: Identifier) => void,
) {
for (const stmt of block.body) {
if (stmt.type === 'VariableDeclaration') {
@ -165,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':
@ -214,20 +283,24 @@ export function extractIdentifiers(
return nodes
}
function markScopeIdentifier(
node: Node & { scopeIds?: Set<string> },
child: Identifier,
knownIds: Record<string, number>
) {
const { name } = child
if (node.scopeIds && node.scopeIds.has(name)) {
return
}
function markKnownIds(name: string, knownIds: Record<string, number>) {
if (name in knownIds) {
knownIds[name]++
} else {
knownIds[name] = 1
}
}
function markScopeIdentifier(
node: Node & { scopeIds?: Set<string> },
child: Identifier,
knownIds: Record<string, number>,
) {
const { name } = child
if (node.scopeIds && node.scopeIds.has(name)) {
return
}
markKnownIds(name, knownIds)
;(node.scopeIds || (node.scopeIds = new Set())).add(name)
}
@ -364,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
}
@ -426,5 +500,13 @@ export const TS_NODE_TYPES = [
'TSTypeAssertion', // (<number>foo)
'TSNonNullExpression', // foo!
'TSInstantiationExpression', // foo<string>
'TSSatisfiesExpression' // foo satisfies T
'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
}
}

View File

@ -1,60 +1,105 @@
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,
locStub,
SSRCodegenNode,
TemplateLiteral,
IfStatement,
AssignmentExpression,
ReturnStatement,
VNodeCall,
SequenceExpression,
type ObjectExpression,
type Position,
type ReturnStatement,
type RootNode,
type SSRCodegenNode,
type SequenceExpression,
type SimpleExpressionNode,
type TemplateChildNode,
type TemplateLiteral,
type TextNode,
type VNodeCall,
getVNodeBlockHelper,
getVNodeHelper
getVNodeHelper,
locStub,
} from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map-js'
import { SourceMapGenerator } from 'source-map-js'
import {
advancePositionWithMutation,
assert,
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,
POP_SCOPE_ID,
PUSH_SCOPE_ID,
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<string>
_names: Set<string>
_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__*/`
@ -69,6 +114,13 @@ export interface CodegenResult {
map?: RawSourceMap
}
enum NewlineType {
Start = 0,
End = -1,
None = -2,
Unknown = -3,
}
export interface CodegenContext
extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
source: string
@ -78,9 +130,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
@ -100,8 +152,8 @@ function createCodegenContext(
ssrRuntimeModuleName = 'vue/server-renderer',
ssr = false,
isTS = false,
inSSR = false
}: CodegenOptions
inSSR = false,
}: CodegenOptions,
): CodegenContext {
const context: CodegenContext = {
mode,
@ -116,7 +168,7 @@ function createCodegenContext(
ssr,
isTS,
inSSR,
source: ast.loc.source,
source: ast.source,
code: ``,
column: 1,
line: 1,
@ -127,7 +179,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) {
@ -140,7 +192,41 @@ function createCodegenContext(
}
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
}
}
if (node && node.loc !== locStub) {
addMapping(node.loc.end)
}
@ -158,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
@ -193,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)
@ -205,7 +294,7 @@ export function generate(
deindent,
newline,
scopeId,
ssr
ssr,
} = context
const helpers = Array.from(ast.helpers)
@ -250,8 +339,10 @@ export function generate(
// function mode const declarations should be inside with block
// also they should be renamed to avoid collision with user properties
if (hasHelpers) {
push(`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue`)
push(`\n`)
push(
`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`,
NewlineType.End,
)
newline()
}
}
@ -282,7 +373,7 @@ export function generate(
}
}
if (ast.components.length || ast.directives.length || ast.temps) {
push(`\n`)
push(`\n`, NewlineType.Start)
newline()
}
@ -308,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,
}
}
@ -321,7 +411,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
newline,
runtimeModuleName,
runtimeGlobalName,
ssrRuntimeModuleName
ssrRuntimeModuleName,
} = context
const VueBinding =
!__BROWSER__ && ssr
@ -334,11 +424,14 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
const helpers = Array.from(ast.helpers)
if (helpers.length > 0) {
if (!__BROWSER__ && prefixIdentifiers) {
push(`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`)
push(
`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.
@ -348,12 +441,12 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
CREATE_ELEMENT_VNODE,
CREATE_COMMENT,
CREATE_TEXT,
CREATE_STATIC
CREATE_STATIC,
]
.filter(helper => helpers.includes(helper))
.map(aliasHelper)
.join(', ')
push(`const { ${staticHelpers} } = _Vue\n`)
push(`const { ${staticHelpers} } = _Vue\n`, NewlineType.End)
}
}
}
@ -363,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)
@ -375,14 +469,14 @@ 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) {
@ -402,18 +496,21 @@ function genModulePreamble(
push(
`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 ${helpers
.map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
.join(', ')}\n`
.join(', ')}\n`,
NewlineType.End,
)
} else {
push(
`import { ${helpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
NewlineType.End,
)
}
}
@ -422,7 +519,8 @@ function genModulePreamble(
push(
`import { ${ast.ssrHelpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
.join(', ')} } from "${ssrRuntimeModuleName}"\n`
.join(', ')} } from "${ssrRuntimeModuleName}"\n`,
NewlineType.End,
)
}
@ -442,14 +540,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_DIRECTIVE,
)
for (let i = 0; i < assets.length; i++) {
let id = assets[i]
@ -461,7 +559,7 @@ function genAssets(
push(
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
maybeSelfReference ? `, true` : ``
})${isTS ? `!` : ``}`
})${isTS ? `!` : ``}`,
)
if (i < assets.length - 1) {
newline()
@ -480,10 +578,11 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
// generate inlined withScopeId helper
if (genScopeId) {
const param = context.isTS ? '(n: any)' : 'n'
push(
`const _withScopeId = n => (${helper(
PUSH_SCOPE_ID
)}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`
`const _withScopeId = ${param} => (${helper(
PUSH_SCOPE_ID,
)}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`,
)
newline()
}
@ -495,7 +594,7 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
push(
`const _hoisted_${i + 1} = ${
needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : ``
}`
}`,
)
genNode(exp, context)
if (needScopeIdWrapper) {
@ -532,7 +631,7 @@ function isText(n: string | CodegenNode) {
function genNodeListAsArray(
nodes: (string | CodegenNode | TemplateChildNode[])[],
context: CodegenContext
context: CodegenContext,
) {
const multilines =
nodes.length > 3 ||
@ -548,13 +647,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 {
@ -573,7 +672,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)) {
@ -588,7 +687,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
@ -669,14 +768,18 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
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) {
@ -689,12 +792,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)
}
@ -703,7 +806,7 @@ function genCompoundExpression(
function genExpressionAsPropertyKey(
node: ExpressionNode,
context: CodegenContext
context: CodegenContext,
) {
const { push } = context
if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
@ -715,9 +818,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)
}
}
@ -726,7 +829,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) {
@ -740,8 +847,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) + `(`)
}
@ -754,10 +883,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) {
@ -785,7 +914,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(`)`)
}
@ -794,7 +923,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 =
@ -826,7 +955,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
@ -834,7 +963,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) {
@ -871,7 +1000,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,15 +1037,16 @@ function genConditionalExpression(
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
const { push, helper, indent, deindent, newline } = context
push(`_cache[${node.index}] || (`)
if (node.isVNode) {
if (node.isVOnce) {
indent()
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
newline()
push(`(`)
}
push(`_cache[${node.index}] = `)
genNode(node.value, context)
if (node.isVNode) {
push(`,`)
if (node.isVOnce) {
push(`).cacheIndex = ${node.index},`)
newline()
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
newline()
@ -934,7 +1064,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()
@ -972,7 +1102,7 @@ function genIfStatement(node: IfStatement, context: CodegenContext) {
function genAssignmentExpression(
node: AssignmentExpression,
context: CodegenContext
context: CodegenContext,
) {
genNode(node.left, context)
context.push(` = `)
@ -981,7 +1111,7 @@ function genAssignmentExpression(
function genSequenceExpression(
node: SequenceExpression,
context: CodegenContext
context: CodegenContext,
) {
context.push(`(`)
genNodeList(node.expressions, context)
@ -990,7 +1120,7 @@ function genSequenceExpression(
function genReturnStatement(
{ returns }: ReturnStatement,
context: CodegenContext
context: CodegenContext,
) {
context.push(`return `)
if (isArray(returns)) {

View File

@ -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<CompilerDeprecationTypes, boolean | 'suppress-warning'>
@ -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<CompilerDeprecationTypes, DeprecationData> = {
`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<CompilerDeprecationTypes, DeprecationData> = {
`.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<CompilerDeprecationTypes, DeprecationData> = {
`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<CompilerDeprecationTypes, DeprecationData> = {
`access to v-for scope variables. It is best to avoid the ambiguity ` +
`with <template> 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:
`<template> 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<CompilerDeprecationTypes, DeprecationData> = {
`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,7 +105,7 @@ function getCompatValue(
export function isCompatEnabled(
key: CompilerDeprecationTypes,
context: ParserContext | TransformContext
context: MergedParserOptions | TransformContext,
) {
const mode = getCompatValue('MODE', context)
const value = getCompatValue(key, context)
@ -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,7 +129,7 @@ export function checkCompatEnabled(
export function warnDeprecation(
key: CompilerDeprecationTypes,
context: ParserContext | TransformContext,
context: MergedParserOptions | TransformContext,
loc: SourceLocation | null,
...args: any[]
) {

View File

@ -1,17 +1,17 @@
import { RESOLVE_FILTER } from '../runtimeHelpers'
import {
ExpressionNode,
AttributeNode,
DirectiveNode,
type AttributeNode,
type DirectiveNode,
type ExpressionNode,
NodeTypes,
SimpleExpressionNode
type SimpleExpressionNode,
} from '../ast'
import {
CompilerDeprecationTypes,
isCompatEnabled,
warnDeprecation
warnDeprecation,
} from './compatConfig'
import { NodeTransform, TransformContext } from '../transform'
import type { NodeTransform, TransformContext } from '../transform'
import { toValidAssetId } from '../utils'
const validDivisionCharRE = /[\w).+\-_$\]]/
@ -25,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 &&
@ -162,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('(')

View File

@ -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<string, DirectiveTransform>
Record<string, DirectiveTransform>,
]
export function getBaseTransformPreset(
prefixIdentifiers?: boolean
prefixIdentifiers?: boolean,
): TransformPreset {
return [
[
@ -38,7 +42,7 @@ export function getBaseTransformPreset(
? [
// order is important
trackVForSlotScopes,
transformExpression
transformExpression,
]
: __BROWSER__ && __DEV__
? [transformExpression]
@ -46,21 +50,21 @@ export function getBaseTransformPreset(
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'
@ -82,7 +86,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 +102,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)
}

View File

@ -1,4 +1,4 @@
import { SourceLocation } from './ast'
import type { SourceLocation } from './ast'
export interface CompilerError extends SyntaxError {
code: number | string
@ -25,19 +25,19 @@ export function createCompilerError<T extends number>(
code: T,
loc?: SourceLocation,
messages?: { [code: number]: string },
additionalMessage?: string
additionalMessage?: string,
): InferCompilerError<T> {
const msg =
__DEV__ || !__BROWSER__
? (messages || errorMessages)[code] + (additionalMessage || ``)
: code
: `https://vuejs.org/error-reference/#compiler-${code}`
const error = new SyntaxError(String(msg)) as InferCompilerError<T>
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,
@ -96,15 +96,16 @@ export const enum ErrorCodes {
X_MODULE_MODE_NOT_SUPPORTED,
X_CACHE_HANDLER_NOT_SUPPORTED,
X_SCOPE_ID_NOT_SUPPORTED,
X_VNODE_HOOKS,
// deprecations
DEPRECATION_VNODE_HOOKS,
DEPRECATION_V_IS,
// 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<ErrorCodes, string> = {
@ -159,6 +160,7 @@ export const errorMessages: Record<ErrorCodes, string> = {
[ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
[ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT]: `<template v-for> key should be placed on the <template> 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 <slot> outlet.`,
[ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE]:
@ -176,6 +178,7 @@ export const errorMessages: Record<ErrorCodes, string> = {
[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]: `<KeepAlive> 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.`,
@ -183,10 +186,6 @@ export const errorMessages: Record<ErrorCodes, string> = {
[ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED]: `"cacheHandlers" option is only supported when the "prefixIdentifiers" option is enabled.`,
[ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED]: `"scopeId" option is only supported in module mode.`,
// deprecations
[ErrorCodes.DEPRECATION_VNODE_HOOKS]: `@vnode-* hooks in templates are deprecated. Use the vue: prefix instead. For example, @vnode-mounted should be changed to @vue:mounted. @vnode-* hooks support will be removed in 3.4.`,
[ErrorCodes.DEPRECATION_V_IS]: `v-is="component-name" has been deprecated. Use is="vue:component-name" instead. v-is support will be removed in 3.4.`,
// just to fulfill types
[ErrorCodes.__EXTEND_POINT__]: ``
[ErrorCodes.__EXTEND_POINT__]: ``,
}

View File

@ -8,9 +8,9 @@ export {
type CodegenOptions,
type HoistTransform,
type BindingMetadata,
BindingTypes
BindingTypes,
} from './options'
export { baseParse, TextModes } from './parse'
export { baseParse } from './parser'
export {
transform,
type TransformContext,
@ -19,14 +19,21 @@ export {
createStructuralDirectiveTransform,
type NodeTransform,
type StructuralDirectiveTransform,
type DirectiveTransform
type DirectiveTransform,
} from './transform'
export { generate, type CodegenContext, type CodegenResult } from './codegen'
export {
generate,
type CodegenContext,
type CodegenResult,
type CodegenSourceMapGenerator,
type RawSourceMap,
} from './codegen'
export {
ErrorCodes,
errorMessages,
createCompilerError,
type CoreCompilerError,
type CompilerError
type CompilerError,
} from './errors'
export * from './ast'
@ -44,20 +51,20 @@ export { processFor, createForLoopParams } from './transforms/vFor'
export {
transformExpression,
processExpression,
stringifyExpression
stringifyExpression,
} from './transforms/transformExpression'
export {
buildSlots,
type SlotFnBuilder,
trackVForSlotScopes,
trackSlotScopes
trackSlotScopes,
} from './transforms/vSlot'
export {
transformElement,
resolveComponentType,
buildProps,
buildDirectiveArgs,
type PropsExpression
type PropsExpression,
} from './transforms/transformElement'
export { processSlotOutlet } from './transforms/transformSlotOutlet'
export { getConstantType } from './transforms/hoistStatic'
@ -67,5 +74,5 @@ export { generateCodeFrame } from '@vue/shared'
export {
checkCompatEnabled,
warnDeprecation,
CompilerDeprecationTypes
CompilerDeprecationTypes,
} from './compat/compatConfig'

View File

@ -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
* `<script>`, `<style>`,`<title>` and `<textarea>`.
* The logic is handled inside compiler-core for efficiency.
*
* SFC mode treats content of all root-level tags except `<template>` as plain
* text.
*/
parseMode?: 'base' | 'html' | 'sfc'
/**
* Specify the root namespace to use when parsing a template.
* Defaults to `Namespaces.HTML` (0).
*/
ns?: Namespaces
/**
* e.g. platform native elements, e.g. `<div>` for browsers
*/
@ -40,24 +63,23 @@ export interface ParserOptions
/**
* Get tag namespace
*/
getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace
/**
* Get text parsing mode for this element
*/
getTextMode?: (
node: ElementNode,
parent: ElementNode | undefined
) => TextModes
getNamespace?: (
tag: string,
parent: ElementNode | undefined,
rootNamespace: Namespace,
) => Namespace
/**
* @default ['{{', '}}']
*/
delimiters?: [string, string]
/**
* Whitespace handling strategy
* @default 'condense'
*/
whitespace?: 'preserve' | 'condense'
/**
* Only needed for DOM compilers
* Only used for DOM compilers that runs in the browser.
* In non-browser builds, this option is ignored.
*/
decodeEntities?: (rawText: string, asAttr: boolean) => string
/**
@ -65,15 +87,26 @@ export interface ParserOptions
* This defaults to `true` in development and `false` in production builds.
*/
comments?: boolean
/**
* Parse JavaScript expressions with Babel.
* @default false
*/
prefixIdentifiers?: boolean
/**
* A list of parser plugins to enable for `@babel/parser`, which is used to
* parse expressions in bindings and interpolations.
* https://babeljs.io/docs/en/next/babel-parser#plugins
*/
expressionPlugins?: ParserPlugin[]
}
export type HoistTransform = (
children: TemplateChildNode[],
context: TransformContext,
parent: ParentNode
parent: ParentNode,
) => void
export const enum BindingTypes {
export enum BindingTypes {
/**
* returned from data()
*/
@ -116,7 +149,7 @@ export const enum BindingTypes {
/**
* a literal constant, e.g. 'foo', 1, true
*/
LITERAL_CONST = 'literal-const'
LITERAL_CONST = 'literal-const',
}
export type BindingMetadata = {
@ -256,6 +289,12 @@ export interface TransformOptions
* needed to render inline CSS variables on component root
*/
ssrCssVars?: string
/**
* Whether to compile the template assuming it needs to handle HMR.
* Some edge cases may need to generate different code for HMR to work
* correctly, e.g. #6938, #7138
*/
hmr?: boolean
}
export interface CodegenOptions extends SharedTransformCodegenOptions {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``)
export const CREATE_STATIC = Symbol(__DEV__ ? `createStaticVNode` : ``)
export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``)
export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
__DEV__ ? `resolveDynamicComponent` : ``
__DEV__ ? `resolveDynamicComponent` : ``,
)
export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``)
export const RESOLVE_FILTER = Symbol(__DEV__ ? `resolveFilter` : ``)
@ -81,7 +81,7 @@ export const helperNameMap: Record<symbol, string> = {
[UNREF]: `unref`,
[IS_REF]: `isRef`,
[WITH_MEMO]: `withMemo`,
[IS_MEMO_SAME]: `isMemoSame`
[IS_MEMO_SAME]: `isMemoSame`,
}
export function registerRuntimeHelpers(helpers: Record<symbol, string>) {

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +1,45 @@
import { TransformOptions } from './options'
import type { TransformOptions } from './options'
import {
RootNode,
NodeTypes,
ParentNode,
TemplateChildNode,
ElementNode,
DirectiveNode,
Property,
ExpressionNode,
createSimpleExpression,
JSChildNode,
SimpleExpressionNode,
ElementTypes,
CacheExpression,
createCacheExpression,
TemplateLiteral,
createVNodeCall,
type ArrayExpression,
type CacheExpression,
ConstantTypes,
ArrayExpression,
convertToBlock
type DirectiveNode,
type ElementNode,
ElementTypes,
type ExpressionNode,
type JSChildNode,
NodeTypes,
type ParentNode,
type Property,
type RootNode,
type SimpleExpressionNode,
type TemplateChildNode,
type TemplateLiteral,
convertToBlock,
createCacheExpression,
createSimpleExpression,
createVNodeCall,
} from './ast'
import {
isString,
isArray,
NOOP,
PatchFlags,
PatchFlagNames,
EMPTY_OBJ,
NOOP,
PatchFlagNames,
PatchFlags,
camelize,
capitalize,
camelize
isArray,
isString,
} from '@vue/shared'
import { defaultOnError, defaultOnWarn } from './errors'
import {
TO_DISPLAY_STRING,
CREATE_COMMENT,
FRAGMENT,
TO_DISPLAY_STRING,
helperNameMap,
CREATE_COMMENT
} from './runtimeHelpers'
import { isVSlot } from './utils'
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
import { CompilerCompatOptions } from './compat/compatConfig'
import type { CompilerCompatOptions } from './compat/compatConfig'
// There are two types of transforms:
//
@ -48,7 +48,7 @@ import { CompilerCompatOptions } from './compat/compatConfig'
// replace or remove the node being processed.
export type NodeTransform = (
node: RootNode | TemplateChildNode,
context: TransformContext
context: TransformContext,
) => void | (() => void) | (() => void)[]
// - DirectiveTransform:
@ -60,7 +60,7 @@ export type DirectiveTransform = (
context: TransformContext,
// a platform specific compiler can import the base transform and augment
// it by passing in this optional argument.
augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult
augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,
) => DirectiveTransformResult
export interface DirectiveTransformResult {
@ -74,7 +74,7 @@ export interface DirectiveTransformResult {
export type StructuralDirectiveTransform = (
node: ElementNode,
dir: DirectiveNode,
context: TransformContext
context: TransformContext,
) => void | (() => void)
export interface ImportItem {
@ -83,9 +83,7 @@ export interface ImportItem {
}
export interface TransformContext
extends Required<
Omit<TransformOptions, 'filename' | keyof CompilerCompatOptions>
>,
extends Required<Omit<TransformOptions, keyof CompilerCompatOptions>>,
CompilerCompatOptions {
selfName: string | null
root: RootNode
@ -104,6 +102,9 @@ export interface TransformContext
vOnce: number
}
parent: ParentNode | null
// we could use a stack but in practice we've only ever needed two layers up
// so this is more efficient
grandParent: ParentNode | null
childIndex: number
currentNode: RootNode | TemplateChildNode | null
inVOnce: boolean
@ -117,7 +118,7 @@ export interface TransformContext
removeIdentifiers(exp: ExpressionNode | string): void
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
constantCache: Map<TemplateChildNode, ConstantTypes>
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
// 2.x Compat only
filters?: Set<string>
@ -129,6 +130,7 @@ export function createTransformContext(
filename = '',
prefixIdentifiers = false,
hoistStatic = false,
hmr = false,
cacheHandlers = false,
nodeTransforms = [],
directiveTransforms = {},
@ -146,15 +148,17 @@ export function createTransformContext(
isTS = false,
onError = defaultOnError,
onWarn = defaultOnWarn,
compatConfig
}: TransformOptions
compatConfig,
}: TransformOptions,
): TransformContext {
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
const context: TransformContext = {
// options
filename,
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
prefixIdentifiers,
hoistStatic,
hmr,
cacheHandlers,
nodeTransforms,
directiveTransforms,
@ -181,7 +185,7 @@ export function createTransformContext(
directives: new Set(),
hoists: [],
imports: [],
constantCache: new Map(),
constantCache: new WeakMap(),
temps: 0,
cached: 0,
identifiers: Object.create(null),
@ -189,9 +193,10 @@ export function createTransformContext(
vFor: 0,
vSlot: 0,
vPre: 0,
vOnce: 0
vOnce: 0,
},
parent: null,
grandParent: null,
currentNode: root,
childIndex: 0,
inVOnce: false,
@ -255,7 +260,7 @@ export function createTransformContext(
}
context.parent!.children.splice(removalIndex, 1)
},
onNodeRemoved: () => {},
onNodeRemoved: NOOP,
addIdentifiers(exp) {
// identifier tracking only happens in non-browser builds.
if (!__BROWSER__) {
@ -286,14 +291,14 @@ export function createTransformContext(
`_hoisted_${context.hoists.length}`,
false,
exp.loc,
ConstantTypes.CAN_HOIST
ConstantTypes.CAN_HOIST,
)
identifier.hoisted = exp
return identifier
},
cache(exp, isVNode = false) {
return createCacheExpression(context.cached++, exp, isVNode)
}
},
}
if (__COMPAT__) {
@ -332,6 +337,7 @@ export function transform(root: RootNode, options: TransformOptions) {
root.hoists = context.hoists
root.temps = context.temps
root.cached = context.cached
root.transformed = true
if (__COMPAT__) {
root.filters = [...context.filters!]
@ -376,12 +382,12 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
helper(FRAGMENT),
undefined,
root.children,
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
patchFlag,
undefined,
undefined,
true,
undefined,
false /* isComponent */
false /* isComponent */,
)
} else {
// no children = noop. codegen will return null.
@ -390,7 +396,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
export function traverseChildren(
parent: ParentNode,
context: TransformContext
context: TransformContext,
) {
let i = 0
const nodeRemoved = () => {
@ -399,6 +405,7 @@ export function traverseChildren(
for (; i < parent.children.length; i++) {
const child = parent.children[i]
if (isString(child)) continue
context.grandParent = context.parent
context.parent = parent
context.childIndex = i
context.onNodeRemoved = nodeRemoved
@ -408,7 +415,7 @@ export function traverseChildren(
export function traverseNode(
node: RootNode | TemplateChildNode,
context: TransformContext
context: TransformContext,
) {
context.currentNode = node
// apply transform plugins
@ -471,7 +478,7 @@ export function traverseNode(
export function createStructuralDirectiveTransform(
name: string | RegExp,
fn: StructuralDirectiveTransform
fn: StructuralDirectiveTransform,
): NodeTransform {
const matches = isString(name)
? (n: string) => n === name

View File

@ -1,30 +1,30 @@
import {
type CallExpression,
type ComponentNode,
ConstantTypes,
RootNode,
NodeTypes,
TemplateChildNode,
SimpleExpressionNode,
ElementTypes,
PlainElementNode,
ComponentNode,
TemplateNode,
VNodeCall,
ParentNode,
JSChildNode,
CallExpression,
type JSChildNode,
NodeTypes,
type ParentNode,
type PlainElementNode,
type RootNode,
type SimpleExpressionNode,
type TemplateChildNode,
type TemplateNode,
type VNodeCall,
createArrayExpression,
getVNodeBlockHelper,
getVNodeHelper
getVNodeHelper,
} from '../ast'
import { TransformContext } from '../transform'
import { PatchFlags, isString, isSymbol, isArray } from '@vue/shared'
import type { TransformContext } from '../transform'
import { PatchFlags, isArray, isString, isSymbol } from '@vue/shared'
import { isSlotOutlet } from '../utils'
import {
OPEN_BLOCK,
GUARD_REACTIVE_PROPS,
NORMALIZE_CLASS,
NORMALIZE_PROPS,
NORMALIZE_STYLE
NORMALIZE_STYLE,
OPEN_BLOCK,
} from '../runtimeHelpers'
export function hoistStatic(root: RootNode, context: TransformContext) {
@ -33,13 +33,13 @@ export function hoistStatic(root: RootNode, context: TransformContext) {
context,
// Root node is unfortunately non-hoistable due to potential parent
// fallthrough attributes.
isSingleElementRoot(root, root.children[0])
isSingleElementRoot(root, root.children[0]),
)
}
export function isSingleElementRoot(
root: RootNode,
child: TemplateChildNode
child: TemplateChildNode,
): child is PlainElementNode | ComponentNode | TemplateNode {
const { children } = root
return (
@ -52,7 +52,7 @@ export function isSingleElementRoot(
function walk(
node: ParentNode,
context: TransformContext,
doNotHoistNode: boolean = false
doNotHoistNode: boolean = false,
) {
const { children } = node
const originalCount = children.length
@ -70,8 +70,7 @@ function walk(
: getConstantType(child, context)
if (constantType > ConstantTypes.NOT_CONSTANT) {
if (constantType >= ConstantTypes.CAN_HOIST) {
;(child.codegenNode as VNodeCall).patchFlag =
PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
;(child.codegenNode as VNodeCall).patchFlag = PatchFlags.HOISTED
child.codegenNode = context.hoist(child.codegenNode!)
hoistedCount++
continue
@ -81,9 +80,9 @@ function walk(
// hoisting.
const codegenNode = child.codegenNode!
if (codegenNode.type === NodeTypes.VNODE_CALL) {
const flag = getPatchFlag(codegenNode)
const flag = codegenNode.patchFlag
if (
(!flag ||
(flag === undefined ||
flag === PatchFlags.NEED_PATCH ||
flag === PatchFlags.TEXT) &&
getGeneratedPropsConstantType(child, context) >=
@ -120,7 +119,7 @@ function walk(
walk(
child.branches[i],
context,
child.branches[i].children.length === 1
child.branches[i].children.length === 1,
)
}
}
@ -140,15 +139,22 @@ function walk(
node.codegenNode.type === NodeTypes.VNODE_CALL &&
isArray(node.codegenNode.children)
) {
node.codegenNode.children = context.hoist(
createArrayExpression(node.codegenNode.children)
const hoisted = context.hoist(
createArrayExpression(node.codegenNode.children),
)
// #6978, #7138, #7114
// a hoisted children array inside v-for can caused HMR errors since
// it might be mutated when mounting the v-for list
if (context.hmr) {
hoisted.content = `[...${hoisted.content}]`
}
node.codegenNode.children = hoisted
}
}
export function getConstantType(
node: TemplateChildNode | SimpleExpressionNode,
context: TransformContext
context: TransformContext,
): ConstantTypes {
const { constantCache } = context
switch (node.type) {
@ -167,12 +173,12 @@ export function getConstantType(
if (
codegenNode.isBlock &&
node.tag !== 'svg' &&
node.tag !== 'foreignObject'
node.tag !== 'foreignObject' &&
node.tag !== 'math'
) {
return ConstantTypes.NOT_CONSTANT
}
const flag = getPatchFlag(codegenNode)
if (!flag) {
if (codegenNode.patchFlag === undefined) {
let returnType = ConstantTypes.CAN_STRINGIFY
// Element itself has no patch flag. However we still need to check:
@ -237,7 +243,7 @@ export function getConstantType(
context.removeHelper(OPEN_BLOCK)
context.removeHelper(
getVNodeBlockHelper(context.inSSR, codegenNode.isComponent)
getVNodeBlockHelper(context.inSSR, codegenNode.isComponent),
)
codegenNode.isBlock = false
context.helper(getVNodeHelper(context.inSSR, codegenNode.isComponent))
@ -289,12 +295,12 @@ const allowHoistedHelperSet = new Set([
NORMALIZE_CLASS,
NORMALIZE_STYLE,
NORMALIZE_PROPS,
GUARD_REACTIVE_PROPS
GUARD_REACTIVE_PROPS,
])
function getConstantTypeOfHelperCall(
value: CallExpression,
context: TransformContext
context: TransformContext,
): ConstantTypes {
if (
value.type === NodeTypes.JS_CALL_EXPRESSION &&
@ -314,7 +320,7 @@ function getConstantTypeOfHelperCall(
function getGeneratedPropsConstantType(
node: PlainElementNode,
context: TransformContext
context: TransformContext,
): ConstantTypes {
let returnType = ConstantTypes.CAN_STRINGIFY
const props = getNodeProps(node)
@ -357,8 +363,3 @@ function getNodeProps(node: PlainElementNode) {
return codegenNode.props
}
}
function getPatchFlag(node: VNodeCall): number | undefined {
const flag = node.patchFlag
return flag ? parseInt(flag, 10) : undefined
}

View File

@ -1,3 +1,3 @@
import { DirectiveTransform } from '../transform'
import type { DirectiveTransform } from '../transform'
export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })

View File

@ -1,70 +1,69 @@
import { NodeTransform, TransformContext } from '../transform'
import type { NodeTransform, TransformContext } from '../transform'
import {
NodeTypes,
type ArrayExpression,
type CallExpression,
type ComponentNode,
ConstantTypes,
type DirectiveArguments,
type DirectiveNode,
type ElementNode,
ElementTypes,
CallExpression,
ObjectExpression,
ElementNode,
DirectiveNode,
ExpressionNode,
ArrayExpression,
createCallExpression,
type ExpressionNode,
type JSChildNode,
NodeTypes,
type ObjectExpression,
type Property,
type TemplateTextChildNode,
type VNodeCall,
createArrayExpression,
createCallExpression,
createObjectExpression,
createObjectProperty,
createSimpleExpression,
createObjectExpression,
Property,
ComponentNode,
VNodeCall,
TemplateTextChildNode,
DirectiveArguments,
createVNodeCall,
ConstantTypes
} from '../ast'
import {
PatchFlags,
PatchFlagNames,
isSymbol,
isOn,
isObject,
isReservedProp,
capitalize,
camelize,
isBuiltInDirective
capitalize,
isBuiltInDirective,
isObject,
isOn,
isReservedProp,
isSymbol,
} from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { ErrorCodes, createCompilerError } from '../errors'
import {
RESOLVE_DIRECTIVE,
RESOLVE_COMPONENT,
RESOLVE_DYNAMIC_COMPONENT,
GUARD_REACTIVE_PROPS,
KEEP_ALIVE,
MERGE_PROPS,
NORMALIZE_CLASS,
NORMALIZE_STYLE,
NORMALIZE_PROPS,
TO_HANDLERS,
TELEPORT,
KEEP_ALIVE,
NORMALIZE_STYLE,
RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
RESOLVE_DYNAMIC_COMPONENT,
SUSPENSE,
TELEPORT,
TO_HANDLERS,
UNREF,
GUARD_REACTIVE_PROPS
} from '../runtimeHelpers'
import {
getInnerRange,
toValidAssetId,
findProp,
isCoreComponent,
isStaticArgOf,
findDir,
isStaticExp
isStaticExp,
toValidAssetId,
} from '../utils'
import { buildSlots } from './vSlot'
import { getConstantType } from './hoistStatic'
import { BindingTypes } from '../options'
import {
checkCompatEnabled,
CompilerDeprecationTypes,
isCompatEnabled
checkCompatEnabled,
isCompatEnabled,
} from '../compat/compatConfig'
import { processExpression } from './transformExpression'
// some directive transforms (e.g. v-model) may return a symbol for runtime
// import, which should be used instead of a resolveDirective call.
@ -101,8 +100,7 @@ export const transformElement: NodeTransform = (node, context) => {
let vnodeProps: VNodeCall['props']
let vnodeChildren: VNodeCall['children']
let vnodePatchFlag: VNodeCall['patchFlag']
let patchFlag: number = 0
let patchFlag: VNodeCall['patchFlag'] | 0 = 0
let vnodeDynamicProps: VNodeCall['dynamicProps']
let dynamicPropNames: string[] | undefined
let vnodeDirectives: VNodeCall['directives']
@ -117,7 +115,7 @@ export const transformElement: NodeTransform = (node, context) => {
// updates inside get proper isSVG flag at runtime. (#639, #643)
// This is technically web-specific, but splitting the logic out of core
// leads to too much unnecessary complexity.
(tag === 'svg' || tag === 'foreignObject'))
(tag === 'svg' || tag === 'foreignObject' || tag === 'math'))
// props
if (props.length > 0) {
@ -126,7 +124,7 @@ export const transformElement: NodeTransform = (node, context) => {
context,
undefined,
isComponent,
isDynamicComponent
isDynamicComponent,
)
vnodeProps = propsBuildResult.props
patchFlag = propsBuildResult.patchFlag
@ -135,7 +133,7 @@ export const transformElement: NodeTransform = (node, context) => {
vnodeDirectives =
directives && directives.length
? (createArrayExpression(
directives.map(dir => buildDirectiveArgs(dir, context))
directives.map(dir => buildDirectiveArgs(dir, context)),
) as DirectiveArguments)
: undefined
@ -161,8 +159,8 @@ export const transformElement: NodeTransform = (node, context) => {
createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, {
start: node.children[0].loc.start,
end: node.children[node.children.length - 1].loc.end,
source: ''
})
source: '',
}),
)
}
}
@ -206,41 +204,22 @@ export const transformElement: NodeTransform = (node, context) => {
}
// patchFlag & dynamicPropNames
if (patchFlag !== 0) {
if (__DEV__) {
if (patchFlag < 0) {
// special flags (negative and mutually exclusive)
vnodePatchFlag =
patchFlag + ` /* ${PatchFlagNames[patchFlag as PatchFlags]} */`
} else {
// bitwise flags
const flagNames = Object.keys(PatchFlagNames)
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n as PatchFlags])
.join(`, `)
vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
}
} else {
vnodePatchFlag = String(patchFlag)
}
if (dynamicPropNames && dynamicPropNames.length) {
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
}
}
node.codegenNode = createVNodeCall(
context,
vnodeTag,
vnodeProps,
vnodeChildren,
vnodePatchFlag,
patchFlag === 0 ? undefined : patchFlag,
vnodeDynamicProps,
vnodeDirectives,
!!shouldUseBlock,
false /* disableTracking */,
isComponent,
node.loc
node.loc,
)
}
}
@ -248,29 +227,38 @@ export const transformElement: NodeTransform = (node, context) => {
export function resolveComponentType(
node: ComponentNode,
context: TransformContext,
ssr = false
ssr = false,
) {
let { tag } = node
// 1. dynamic component
const isExplicitDynamic = isComponentTag(tag)
const isProp = findProp(node, 'is')
const isProp = findProp(node, 'is', false, true /* allow empty */)
if (isProp) {
if (
isExplicitDynamic ||
(__COMPAT__ &&
isCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
context
context,
))
) {
const exp =
isProp.type === NodeTypes.ATTRIBUTE
? isProp.value && createSimpleExpression(isProp.value.content, true)
: isProp.exp
let exp: ExpressionNode | undefined
if (isProp.type === NodeTypes.ATTRIBUTE) {
exp = isProp.value && createSimpleExpression(isProp.value.content, true)
} else {
exp = isProp.exp
if (!exp) {
// #10469 handle :is shorthand
exp = createSimpleExpression(`is`, false, isProp.loc)
if (!__BROWSER__) {
exp = isProp.exp = processExpression(exp, context)
}
}
}
if (exp) {
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
exp
exp,
])
}
} else if (
@ -285,19 +273,6 @@ export function resolveComponentType(
}
}
// 1.5 v-is (TODO: remove in 3.4)
const isDir = !isExplicitDynamic && findDir(node, 'is')
if (isDir && isDir.exp) {
if (__DEV__) {
context.onWarn(
createCompilerError(ErrorCodes.DEPRECATION_V_IS, isDir.loc)
)
}
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
isDir.exp
])
}
// 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
if (builtIn) {
@ -385,6 +360,13 @@ function resolveSetupReference(name: string, context: TransformContext) {
`${context.helperString(UNREF)}(${fromMaybeRef})`
: `$setup[${JSON.stringify(fromMaybeRef)}]`
}
const fromProps = checkType(BindingTypes.PROPS)
if (fromProps) {
return `${context.helperString(UNREF)}(${
context.inline ? '__props' : '$props'
}[${JSON.stringify(fromProps)}])`
}
}
export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
@ -395,7 +377,7 @@ export function buildProps(
props: ElementNode['props'] = node.props,
isComponent: boolean,
isDynamicComponent: boolean,
ssr = false
ssr = false,
): {
props: PropsExpression | undefined
directives: DirectiveNode[]
@ -423,13 +405,25 @@ export function buildProps(
const pushMergeArg = (arg?: PropsExpression) => {
if (properties.length) {
mergeArgs.push(
createObjectExpression(dedupeProperties(properties), elementLoc)
createObjectExpression(dedupeProperties(properties), elementLoc),
)
properties = []
}
if (arg) mergeArgs.push(arg)
}
// mark template ref on v-for
const pushRefVForMarker = () => {
if (context.scopes.vFor > 0) {
properties.push(
createObjectProperty(
createSimpleExpression('ref_for', true),
createSimpleExpression('true'),
),
)
}
}
const analyzePatchFlag = ({ key, value }: Property) => {
if (isStaticExp(key)) {
const name = key.content
@ -452,6 +446,12 @@ export function buildProps(
hasVnodeHook = true
}
if (isEventHandler && value.type === NodeTypes.JS_CALL_EXPRESSION) {
// handler wrapped with internal helper e.g. withModifiers(fn)
// extract the actual expression
value = value.arguments[0] as JSChildNode
}
if (
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
@ -489,18 +489,11 @@ export function buildProps(
// static attribute
const prop = props[i]
if (prop.type === NodeTypes.ATTRIBUTE) {
const { loc, name, value } = prop
const { loc, name, nameLoc, value } = prop
let isStatic = true
if (name === 'ref') {
hasRef = true
if (context.scopes.vFor > 0) {
properties.push(
createObjectProperty(
createSimpleExpression('ref_for', true),
createSimpleExpression('true')
)
)
}
pushRefVForMarker()
// in inline mode there is no setupState object, so we can't use string
// keys to set the ref. Instead, we need to transform it to pass the
// actual ref instead.
@ -515,8 +508,8 @@ export function buildProps(
properties.push(
createObjectProperty(
createSimpleExpression('ref_key', true),
createSimpleExpression(value.content, true, value.loc)
)
createSimpleExpression(value.content, true, value.loc),
),
)
}
}
@ -529,28 +522,24 @@ export function buildProps(
(__COMPAT__ &&
isCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
context
context,
)))
) {
continue
}
properties.push(
createObjectProperty(
createSimpleExpression(
name,
true,
getInnerRange(loc, 0, name.length)
),
createSimpleExpression(name, true, nameLoc),
createSimpleExpression(
value ? value.content : '',
isStatic,
value ? value.loc : loc
)
)
value ? value.loc : loc,
),
),
)
} else {
// directives
const { name, arg, exp, loc } = prop
const { name, arg, exp, loc, modifiers } = prop
const isVBind = name === 'bind'
const isVOn = name === 'on'
@ -558,7 +547,7 @@ export function buildProps(
if (name === 'slot') {
if (!isComponent) {
context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, loc)
createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, loc),
)
}
continue
@ -576,7 +565,7 @@ export function buildProps(
(__COMPAT__ &&
isCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
context
context,
))))
) {
continue
@ -596,13 +585,8 @@ export function buildProps(
shouldUseBlock = true
}
if (isVBind && isStaticArgOf(arg, 'ref') && context.scopes.vFor > 0) {
properties.push(
createObjectProperty(
createSimpleExpression('ref_for', true),
createSimpleExpression('true')
)
)
if (isVBind && isStaticArgOf(arg, 'ref')) {
pushRefVForMarker()
}
// special case for v-bind and v-on with no argument
@ -610,6 +594,8 @@ export function buildProps(
hasDynamicKeys = true
if (exp) {
if (isVBind) {
// #10696 in case a v-bind object contains ref
pushRefVForMarker()
// have to merge early for compat build check
pushMergeArg()
if (__COMPAT__) {
@ -639,7 +625,7 @@ export function buildProps(
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
context,
loc
loc,
)
}
}
@ -647,7 +633,7 @@ export function buildProps(
if (
isCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
context
context,
)
) {
mergeArgs.unshift(exp)
@ -662,7 +648,7 @@ export function buildProps(
type: NodeTypes.JS_CALL_EXPRESSION,
loc,
callee: context.helper(TO_HANDLERS),
arguments: isComponent ? [exp] : [exp, `true`]
arguments: isComponent ? [exp] : [exp, `true`],
})
}
} else {
@ -671,13 +657,18 @@ export function buildProps(
isVBind
? ErrorCodes.X_V_BIND_NO_EXPRESSION
: ErrorCodes.X_V_ON_NO_EXPRESSION,
loc
)
loc,
),
)
}
continue
}
// force hydration for v-bind with .prop modifier
if (isVBind && modifiers.includes('prop')) {
patchFlag |= PatchFlags.NEED_HYDRATION
}
const directiveTransform = context.directiveTransforms[name]
if (directiveTransform) {
// has built-in directive transform.
@ -716,7 +707,7 @@ export function buildProps(
propsExpression = createCallExpression(
context.helper(MERGE_PROPS),
mergeArgs,
elementLoc
elementLoc,
)
} else {
// single v-bind with nothing else - no need for a mergeProps call
@ -725,7 +716,7 @@ export function buildProps(
} else if (properties.length) {
propsExpression = createObjectExpression(
dedupeProperties(properties),
elementLoc
elementLoc,
)
}
@ -743,12 +734,12 @@ export function buildProps(
patchFlag |= PatchFlags.PROPS
}
if (hasHydrationEventBinding) {
patchFlag |= PatchFlags.HYDRATE_EVENTS
patchFlag |= PatchFlags.NEED_HYDRATION
}
}
if (
!shouldUseBlock &&
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
(patchFlag === 0 || patchFlag === PatchFlags.NEED_HYDRATION) &&
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
) {
patchFlag |= PatchFlags.NEED_PATCH
@ -785,7 +776,7 @@ export function buildProps(
if (classProp && !isStaticExp(classProp.value)) {
classProp.value = createCallExpression(
context.helper(NORMALIZE_CLASS),
[classProp.value]
[classProp.value],
)
}
if (
@ -801,14 +792,14 @@ export function buildProps(
) {
styleProp.value = createCallExpression(
context.helper(NORMALIZE_STYLE),
[styleProp.value]
[styleProp.value],
)
}
} else {
// dynamic key binding, wrap with `normalizeProps`
propsExpression = createCallExpression(
context.helper(NORMALIZE_PROPS),
[propsExpression]
[propsExpression],
)
}
break
@ -821,9 +812,9 @@ export function buildProps(
context.helper(NORMALIZE_PROPS),
[
createCallExpression(context.helper(GUARD_REACTIVE_PROPS), [
propsExpression
])
]
propsExpression,
]),
],
)
break
}
@ -834,7 +825,7 @@ export function buildProps(
directives: runtimeDirectives,
patchFlag,
dynamicPropNames,
shouldUseBlock
shouldUseBlock,
}
}
@ -875,14 +866,14 @@ function mergeAsArray(existing: Property, incoming: Property) {
} else {
existing.value = createArrayExpression(
[existing.value, incoming.value],
existing.loc
existing.loc,
)
}
}
export function buildDirectiveArgs(
dir: DirectiveNode,
context: TransformContext
context: TransformContext,
): ArrayExpression {
const dirArgs: ArrayExpression['elements'] = []
const runtime = directiveImportMap.get(dir)
@ -922,10 +913,10 @@ export function buildDirectiveArgs(
dirArgs.push(
createObjectExpression(
dir.modifiers.map(modifier =>
createObjectProperty(modifier, trueExpression)
createObjectProperty(modifier, trueExpression),
),
loc,
),
loc
)
)
}
return createArrayExpression(dirArgs, dir.loc)

View File

@ -7,53 +7,50 @@
// - This transform is only applied in non-browser builds because it relies on
// an additional JavaScript parser. In the browser, there is no source-map
// support and the code is wrapped in `with (this) { ... }`.
import { NodeTransform, TransformContext } from '../transform'
import type { NodeTransform, TransformContext } from '../transform'
import {
type CompoundExpressionNode,
ConstantTypes,
type ExpressionNode,
NodeTypes,
createSimpleExpression,
ExpressionNode,
SimpleExpressionNode,
CompoundExpressionNode,
type SimpleExpressionNode,
createCompoundExpression,
ConstantTypes
createSimpleExpression,
} from '../ast'
import {
isInDestructureAssignment,
isInNewExpression,
isStaticProperty,
isStaticPropertyKey,
walkIdentifiers
walkIdentifiers,
} from '../babelUtils'
import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
import {
isGloballyAllowed,
makeMap,
genPropsAccessExp,
hasOwn,
isGloballyAllowed,
isString,
genPropsAccessExp
makeMap,
} from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import {
Node,
Identifier,
import { ErrorCodes, createCompilerError } from '../errors'
import type {
AssignmentExpression,
UpdateExpression
Identifier,
Node,
UpdateExpression,
} from '@babel/types'
import { validateBrowserExpression } from '../validateExpression'
import { parse } from '@babel/parser'
import { parseExpression } from '@babel/parser'
import { IS_REF, UNREF } from '../runtimeHelpers'
import { BindingTypes } from '../options'
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
// a heuristic safeguard to bail constant expressions on presence of
// likely function invocation and member access
const constantBailRE = /\w\s*\(|\.[^\d]/
export const transformExpression: NodeTransform = (node, context) => {
if (node.type === NodeTypes.INTERPOLATION) {
node.content = processExpression(
node.content as SimpleExpressionNode,
context
context,
)
} else if (node.type === NodeTypes.ELEMENT) {
// handle directives on element
@ -74,7 +71,7 @@ export const transformExpression: NodeTransform = (node, context) => {
exp,
context,
// slot args must be processed as function params
dir.name === 'slot'
dir.name === 'slot',
)
}
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
@ -104,7 +101,7 @@ export function processExpression(
asParams = false,
// v-on handler values may contain multiple statements
asRawStatements = false,
localVars: Record<string, number> = Object.create(context.identifiers)
localVars: Record<string, number> = Object.create(context.identifiers),
): ExpressionNode {
if (__BROWSER__) {
if (__DEV__) {
@ -119,7 +116,11 @@ export function processExpression(
}
const { inline, bindingMetadata } = context
const rewriteIdentifier = (raw: string, parent?: Node, id?: Identifier) => {
const rewriteIdentifier = (
raw: string,
parent?: Node | null,
id?: Identifier,
) => {
const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw]
if (inline) {
// x = y
@ -131,6 +132,11 @@ export function processExpression(
// ({ x } = y)
const isDestructureAssignment =
parent && isInDestructureAssignment(parent, parentStack)
const isNewExpression = parent && isInNewExpression(parentStack)
const wrapWithUnref = (raw: string) => {
const wrapped = `${context.helperString(UNREF)}(${raw})`
return isNewExpression ? `(${wrapped})` : wrapped
}
if (
isConst(type) ||
@ -147,7 +153,7 @@ export function processExpression(
// that assumes the value to be a ref for more efficiency
return isAssignmentLVal || isUpdateArg || isDestructureAssignment
? `${raw}.value`
: `${context.helperString(UNREF)}(${raw})`
: wrapWithUnref(raw)
} else if (type === BindingTypes.SETUP_LET) {
if (isAssignmentLVal) {
// let binding.
@ -163,8 +169,8 @@ export function processExpression(
context,
false,
false,
knownIds
)
knownIds,
),
)
return `${context.helperString(IS_REF)}(${raw})${
context.isTS ? ` //@ts-ignore\n` : ``
@ -190,7 +196,7 @@ export function processExpression(
// for now
return raw
} else {
return `${context.helperString(UNREF)}(${raw})`
return wrapWithUnref(raw)
}
} else if (type === BindingTypes.PROPS) {
// use __props which is generated by compileScript so in ts mode
@ -220,17 +226,27 @@ export function processExpression(
// fast path if expression is a simple identifier.
const rawExp = node.content
// bail constant on parens (function invocation) and dot (member access)
const bailConstant = constantBailRE.test(rawExp)
if (isSimpleIdentifier(rawExp)) {
let ast = node.ast
if (ast === false) {
// ast being false means it has caused an error already during parse phase
return node
}
if (ast === null || (!ast && isSimpleIdentifier(rawExp))) {
const isScopeVarReference = context.identifiers[rawExp]
const isAllowedGlobal = isGloballyAllowed(rawExp)
const isLiteral = isLiteralWhitelisted(rawExp)
if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) {
if (
!asParams &&
!isScopeVarReference &&
!isLiteral &&
(!isAllowedGlobal || bindingMetadata[rawExp])
) {
// const bindings exposed from setup can be skipped for patching but
// cannot be hoisted to module scope
if (isConst(bindingMetadata[node.content])) {
if (isConst(bindingMetadata[rawExp])) {
node.constType = ConstantTypes.CAN_SKIP_PATCH
}
node.content = rewriteIdentifier(rawExp)
@ -244,7 +260,7 @@ export function processExpression(
return node
}
let ast: any
if (!ast) {
// exp needs to be parsed differently:
// 1. Multiple inline statements (v-on, with presence of `;`): parse as raw
// exp, but make sure to pad with spaces for consistent ranges
@ -254,20 +270,22 @@ export function processExpression(
? ` ${rawExp} `
: `(${rawExp})${asParams ? `=>{}` : ``}`
try {
ast = parse(source, {
plugins: context.expressionPlugins
}).program
ast = parseExpression(source, {
sourceType: 'module',
plugins: context.expressionPlugins,
})
} catch (e: any) {
context.onError(
createCompilerError(
ErrorCodes.X_INVALID_EXPRESSION,
node.loc,
undefined,
e.message
)
e.message,
),
)
return node
}
}
type QualifiedId = Identifier & PrefixMeta
const ids: QualifiedId[] = []
@ -297,7 +315,13 @@ export function processExpression(
} else {
// The identifier is considered constant unless it's pointing to a
// local scope variable (a v-for alias, or a v-slot prop)
if (!(needPrefix && isLocal) && !bailConstant) {
if (
!(needPrefix && isLocal) &&
(!parent ||
(parent.type !== 'CallExpression' &&
parent.type !== 'NewExpression' &&
parent.type !== 'MemberExpression'))
) {
;(node as QualifiedId).isConstant = true
}
// also generate sub-expressions for other identifiers for better
@ -307,7 +331,7 @@ export function processExpression(
},
true, // invoke on ALL identifiers
parentStack,
knownIds
knownIds,
)
// We break up the compound expression into an array of strings and sub
@ -331,12 +355,14 @@ export function processExpression(
id.name,
false,
{
source,
start: advancePositionWithClone(node.loc.start, source, start),
end: advancePositionWithClone(node.loc.start, source, end)
end: advancePositionWithClone(node.loc.start, source, end),
source,
},
id.isConstant ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
)
id.isConstant
? ConstantTypes.CAN_STRINGIFY
: ConstantTypes.NOT_CONSTANT,
),
)
if (i === ids.length - 1 && end < rawExp.length) {
children.push(rawExp.slice(end))
@ -346,11 +372,10 @@ export function processExpression(
let ret
if (children.length) {
ret = createCompoundExpression(children, node.loc)
ret.ast = ast
} else {
ret = node
ret.constType = bailConstant
? ConstantTypes.NOT_CONSTANT
: ConstantTypes.CAN_STRINGIFY
ret.constType = ConstantTypes.CAN_STRINGIFY
}
ret.identifiers = Object.keys(knownIds)
return ret

View File

@ -1,17 +1,19 @@
import { NodeTransform, TransformContext } from '../transform'
import type { NodeTransform, TransformContext } from '../transform'
import {
type CallExpression,
type ExpressionNode,
NodeTypes,
CallExpression,
type SlotOutletNode,
createCallExpression,
ExpressionNode,
SlotOutletNode,
createFunctionExpression
createFunctionExpression,
createSimpleExpression,
} from '../ast'
import { isSlotOutlet, isStaticArgOf, isStaticExp } from '../utils'
import { buildProps, PropsExpression } from './transformElement'
import { createCompilerError, ErrorCodes } from '../errors'
import { type PropsExpression, buildProps } from './transformElement'
import { ErrorCodes, createCompilerError } from '../errors'
import { RENDER_SLOT } from '../runtimeHelpers'
import { camelize } from '@vue/shared'
import { processExpression } from './transformExpression'
export const transformSlotOutlet: NodeTransform = (node, context) => {
if (isSlotOutlet(node)) {
@ -23,7 +25,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
slotName,
'{}',
'undefined',
'true'
'true',
]
let expectedLen = 2
@ -45,7 +47,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
node.codegenNode = createCallExpression(
context.helper(RENDER_SLOT),
slotArgs,
loc
loc,
)
}
}
@ -57,7 +59,7 @@ interface SlotOutletProcessResult {
export function processSlotOutlet(
node: SlotOutletNode,
context: TransformContext
context: TransformContext,
): SlotOutletProcessResult {
let slotName: string | ExpressionNode = `"default"`
let slotProps: PropsExpression | undefined = undefined
@ -76,7 +78,15 @@ export function processSlotOutlet(
}
} else {
if (p.name === 'bind' && isStaticArgOf(p.arg, 'name')) {
if (p.exp) slotName = p.exp
if (p.exp) {
slotName = p.exp
} else if (p.arg && p.arg.type === NodeTypes.SIMPLE_EXPRESSION) {
const name = camelize(p.arg.content)
slotName = p.exp = createSimpleExpression(name, false, p.arg.loc)
if (!__BROWSER__) {
slotName = p.exp = processExpression(p.exp, context)
}
}
} else {
if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {
p.arg.content = camelize(p.arg.content)
@ -92,7 +102,7 @@ export function processSlotOutlet(
context,
nonNameProps,
false,
false
false,
)
slotProps = props
@ -100,14 +110,14 @@ export function processSlotOutlet(
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
directives[0].loc
)
directives[0].loc,
),
)
}
}
return {
slotName,
slotProps
slotProps,
}
}

View File

@ -1,16 +1,16 @@
import { NodeTransform } from '../transform'
import type { NodeTransform } from '../transform'
import {
NodeTypes,
CompoundExpressionNode,
createCallExpression,
CallExpression,
ElementTypes,
type CallExpression,
type CompoundExpressionNode,
ConstantTypes,
createCompoundExpression
ElementTypes,
NodeTypes,
createCallExpression,
createCompoundExpression,
} from '../ast'
import { isText } from '../utils'
import { CREATE_TEXT } from '../runtimeHelpers'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
import { PatchFlagNames, PatchFlags } from '@vue/shared'
import { getConstantType } from './hoistStatic'
// Merge adjacent text nodes and expressions into a single expression
@ -39,7 +39,7 @@ export const transformText: NodeTransform = (node, context) => {
if (!currentContainer) {
currentContainer = children[i] = createCompoundExpression(
[child],
child.loc
child.loc,
)
}
// merge adjacent text node into current
@ -72,7 +72,7 @@ export const transformText: NodeTransform = (node, context) => {
!node.props.find(
p =>
p.type === NodeTypes.DIRECTIVE &&
!context.directiveTransforms[p.name]
!context.directiveTransforms[p.name],
) &&
// in compat mode, <template> tags with no special directives
// will be rendered as a fragment so its children must be
@ -100,7 +100,7 @@ export const transformText: NodeTransform = (node, context) => {
) {
callArgs.push(
PatchFlags.TEXT +
(__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.TEXT]} */` : ``)
(__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.TEXT]} */` : ``),
)
}
children[i] = {
@ -109,8 +109,8 @@ export const transformText: NodeTransform = (node, context) => {
loc: child.loc,
codegenNode: createCallExpression(
context.helper(CREATE_TEXT),
callArgs
)
callArgs,
),
}
}
}

View File

@ -1,21 +1,66 @@
import { DirectiveTransform } from '../transform'
import type { DirectiveTransform, TransformContext } from '../transform'
import {
type DirectiveNode,
type ExpressionNode,
NodeTypes,
type SimpleExpressionNode,
createObjectProperty,
createSimpleExpression,
ExpressionNode,
NodeTypes
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { ErrorCodes, createCompilerError } from '../errors'
import { camelize } from '@vue/shared'
import { CAMELIZE } from '../runtimeHelpers'
import { processExpression } from './transformExpression'
// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-bind
// *with* args.
export const transformBind: DirectiveTransform = (dir, _node, context) => {
const { exp, modifiers, loc } = dir
const { modifiers, loc } = dir
const arg = dir.arg!
let { exp } = dir
// handle empty expression
if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim()) {
if (!__BROWSER__) {
// #10280 only error against empty expression in non-browser build
// because :foo in in-DOM templates will be parsed into :foo="" by the
// browser
context.onError(
createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc),
)
return {
props: [
createObjectProperty(arg, createSimpleExpression('', true, loc)),
],
}
} else {
exp = undefined
}
}
// same-name shorthand - :arg is expanded to :arg="arg"
if (!exp) {
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
// only simple expression is allowed for same-name shorthand
context.onError(
createCompilerError(
ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
arg.loc,
),
)
return {
props: [
createObjectProperty(arg, createSimpleExpression('', true, loc)),
],
}
}
transformBindShorthand(dir, context)
exp = dir.exp!
}
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
arg.children.unshift(`(`)
arg.children.push(`) || ""`)
@ -46,18 +91,21 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
}
}
if (
!exp ||
(exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
) {
context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc))
return {
props: [createObjectProperty(arg, createSimpleExpression('', true, loc))]
props: [createObjectProperty(arg, exp)],
}
}
return {
props: [createObjectProperty(arg, exp)]
export const transformBindShorthand = (
dir: DirectiveNode,
context: TransformContext,
) => {
const arg = dir.arg!
const propName = camelize((arg as SimpleExpressionNode).content)
dir.exp = createSimpleExpression(propName, false, arg.loc)
if (!__BROWSER__) {
dir.exp = processExpression(dir.exp, context)
}
}

View File

@ -1,53 +1,53 @@
import {
type TransformContext,
createStructuralDirectiveTransform,
TransformContext
} from '../transform'
import {
type BlockCodegenNode,
ConstantTypes,
type DirectiveNode,
type ElementNode,
type ExpressionNode,
type ForCodegenNode,
type ForIteratorExpression,
type ForNode,
type ForParseResult,
type ForRenderListExpression,
NodeTypes,
ExpressionNode,
createSimpleExpression,
SourceLocation,
SimpleExpressionNode,
type PlainElementNode,
type RenderSlotCall,
type SimpleExpressionNode,
type SlotOutletNode,
type VNodeCall,
createBlockStatement,
createCallExpression,
createCompoundExpression,
createFunctionExpression,
createObjectExpression,
createObjectProperty,
ForCodegenNode,
RenderSlotCall,
SlotOutletNode,
ElementNode,
DirectiveNode,
ForNode,
PlainElementNode,
createSimpleExpression,
createVNodeCall,
VNodeCall,
ForRenderListExpression,
BlockCodegenNode,
ForIteratorExpression,
ConstantTypes,
createBlockStatement,
createCompoundExpression,
getVNodeBlockHelper,
getVNodeHelper
getVNodeHelper,
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { ErrorCodes, createCompilerError } from '../errors'
import {
getInnerRange,
findDir,
findProp,
isTemplateNode,
isSlotOutlet,
injectProp,
findDir
isSlotOutlet,
isTemplateNode,
} from '../utils'
import {
RENDER_LIST,
OPEN_BLOCK,
FRAGMENT,
IS_MEMO_SAME
IS_MEMO_SAME,
OPEN_BLOCK,
RENDER_LIST,
} from '../runtimeHelpers'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
import { PatchFlags } from '@vue/shared'
import { transformBindShorthand } from './vBind'
export const transformFor = createStructuralDirectiveTransform(
'for',
@ -57,17 +57,24 @@ export const transformFor = createStructuralDirectiveTransform(
// create the loop render function expression now, and add the
// iterator on exit after all children have been traversed
const renderExp = createCallExpression(helper(RENDER_LIST), [
forNode.source
forNode.source,
]) as ForRenderListExpression
const isTemplate = isTemplateNode(node)
const memo = findDir(node, 'memo')
const keyProp = findProp(node, `key`)
const keyProp = findProp(node, `key`, false, true)
if (keyProp && keyProp.type === NodeTypes.DIRECTIVE && !keyProp.exp) {
// resolve :key shorthand #10882
transformBindShorthand(keyProp, context)
}
const keyExp =
keyProp &&
(keyProp.type === NodeTypes.ATTRIBUTE
? createSimpleExpression(keyProp.value!.content, true)
: keyProp.exp!)
const keyProperty = keyProp ? createObjectProperty(`key`, keyExp!) : null
? keyProp.value
? createSimpleExpression(keyProp.value.content, true)
: undefined
: keyProp.exp)
const keyProperty =
keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
if (!__BROWSER__ && isTemplate) {
// #2085 / #5288 process :key and v-memo expressions need to be
@ -77,13 +84,13 @@ export const transformFor = createStructuralDirectiveTransform(
if (memo) {
memo.exp = processExpression(
memo.exp! as SimpleExpressionNode,
context
context,
)
}
if (keyProperty && keyProp!.type !== NodeTypes.ATTRIBUTE) {
keyProperty.value = processExpression(
keyProperty.value as SimpleExpressionNode,
context
context,
)
}
}
@ -102,14 +109,13 @@ export const transformFor = createStructuralDirectiveTransform(
helper(FRAGMENT),
undefined,
renderExp,
fragmentFlag +
(__DEV__ ? ` /* ${PatchFlagNames[fragmentFlag]} */` : ``),
fragmentFlag,
undefined,
undefined,
true /* isBlock */,
!isStableFragment /* disableTracking */,
false /* isComponent */,
node.loc
node.loc,
) as ForCodegenNode
return () => {
@ -126,8 +132,8 @@ export const transformFor = createStructuralDirectiveTransform(
context.onError(
createCompilerError(
ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT,
key.loc
)
key.loc,
),
)
return true
}
@ -162,15 +168,12 @@ export const transformFor = createStructuralDirectiveTransform(
helper(FRAGMENT),
keyProperty ? createObjectExpression([keyProperty]) : undefined,
node.children,
PatchFlags.STABLE_FRAGMENT +
(__DEV__
? ` /* ${PatchFlagNames[PatchFlags.STABLE_FRAGMENT]} */`
: ``),
PatchFlags.STABLE_FRAGMENT,
undefined,
undefined,
true,
undefined,
false /* isComponent */
false /* isComponent */,
)
} else {
// Normal element v-for. Directly use the child's codegenNode
@ -185,12 +188,12 @@ export const transformFor = createStructuralDirectiveTransform(
// switch from block to vnode
removeHelper(OPEN_BLOCK)
removeHelper(
getVNodeBlockHelper(context.inSSR, childBlock.isComponent)
getVNodeBlockHelper(context.inSSR, childBlock.isComponent),
)
} else {
// switch from vnode to block
removeHelper(
getVNodeHelper(context.inSSR, childBlock.isComponent)
getVNodeHelper(context.inSSR, childBlock.isComponent),
)
}
}
@ -206,8 +209,8 @@ export const transformFor = createStructuralDirectiveTransform(
if (memo) {
const loop = createFunctionExpression(
createForLoopParams(forNode.parseResult, [
createSimpleExpression(`_cached`)
])
createSimpleExpression(`_cached`),
]),
)
loop.body = createBlockStatement([
createCompoundExpression([`const _memo = (`, memo.exp!, `)`]),
@ -215,30 +218,30 @@ export const transformFor = createStructuralDirectiveTransform(
`if (_cached`,
...(keyExp ? [` && _cached.key === `, keyExp] : []),
` && ${context.helperString(
IS_MEMO_SAME
)}(_cached, _memo)) return _cached`
IS_MEMO_SAME,
)}(_cached, _memo)) return _cached`,
]),
createCompoundExpression([`const _item = `, childBlock as any]),
createSimpleExpression(`_item.memo = _memo`),
createSimpleExpression(`return _item`)
createSimpleExpression(`return _item`),
])
renderExp.arguments.push(
loop as ForIteratorExpression,
createSimpleExpression(`_cache`),
createSimpleExpression(String(context.cached++))
createSimpleExpression(String(context.cached++)),
)
} else {
renderExp.arguments.push(
createFunctionExpression(
createForLoopParams(forNode.parseResult),
childBlock,
true /* force newline */
) as ForIteratorExpression
true /* force newline */,
) as ForIteratorExpression,
)
}
}
})
}
},
)
// target-agnostic transform used for both Client and SSR
@ -246,29 +249,26 @@ export function processFor(
node: ElementNode,
dir: DirectiveNode,
context: TransformContext,
processCodegen?: (forNode: ForNode) => (() => void) | undefined
processCodegen?: (forNode: ForNode) => (() => void) | undefined,
) {
if (!dir.exp) {
context.onError(
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc)
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc),
)
return
}
const parseResult = parseForExpression(
// can only be simple expression because vFor transform is applied
// before expression transform.
dir.exp as SimpleExpressionNode,
context
)
const parseResult = dir.forParseResult
if (!parseResult) {
context.onError(
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc)
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc),
)
return
}
finalizeForParseResult(parseResult, context)
const { addIdentifiers, removeIdentifiers, scopes } = context
const { source, value, key, index } = parseResult
@ -280,7 +280,7 @@ export function processFor(
keyAlias: key,
objectIndexAlias: index,
parseResult,
children: isTemplateNode(node) ? node.children : [node]
children: isTemplateNode(node) ? node.children : [node],
}
context.replaceNode(forNode)
@ -308,140 +308,75 @@ export function processFor(
}
}
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
// This regex doesn't cover the case if key or index aliases have destructuring,
// but those do not make sense in the first place, so this works in practice.
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const stripParensRE = /^\(|\)$/g
export function finalizeForParseResult(
result: ForParseResult,
context: TransformContext,
) {
if (result.finalized) return
export interface ForParseResult {
source: ExpressionNode
value: ExpressionNode | undefined
key: ExpressionNode | undefined
index: ExpressionNode | undefined
}
export function parseForExpression(
input: SimpleExpressionNode,
context: TransformContext
): ForParseResult | undefined {
const loc = input.loc
const exp = input.content
const inMatch = exp.match(forAliasRE)
if (!inMatch) return
const [, LHS, RHS] = inMatch
const result: ForParseResult = {
source: createAliasExpression(
loc,
RHS.trim(),
exp.indexOf(RHS, LHS.length)
),
value: undefined,
key: undefined,
index: undefined
}
if (!__BROWSER__ && context.prefixIdentifiers) {
result.source = processExpression(
result.source as SimpleExpressionNode,
context
context,
)
if (result.key) {
result.key = processExpression(
result.key as SimpleExpressionNode,
context,
true,
)
}
if (result.index) {
result.index = processExpression(
result.index as SimpleExpressionNode,
context,
true,
)
}
if (result.value) {
result.value = processExpression(
result.value as SimpleExpressionNode,
context,
true,
)
}
}
if (__DEV__ && __BROWSER__) {
validateBrowserExpression(result.source as SimpleExpressionNode, context)
}
let valueContent = LHS.trim().replace(stripParensRE, '').trim()
const trimmedOffset = LHS.indexOf(valueContent)
const iteratorMatch = valueContent.match(forIteratorRE)
if (iteratorMatch) {
valueContent = valueContent.replace(forIteratorRE, '').trim()
const keyContent = iteratorMatch[1].trim()
let keyOffset: number | undefined
if (keyContent) {
keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
result.key = createAliasExpression(loc, keyContent, keyOffset)
if (!__BROWSER__ && context.prefixIdentifiers) {
result.key = processExpression(result.key, context, true)
}
if (__DEV__ && __BROWSER__) {
if (result.key) {
validateBrowserExpression(
result.key as SimpleExpressionNode,
context,
true
true,
)
}
}
if (iteratorMatch[2]) {
const indexContent = iteratorMatch[2].trim()
if (indexContent) {
result.index = createAliasExpression(
loc,
indexContent,
exp.indexOf(
indexContent,
result.key
? keyOffset! + keyContent.length
: trimmedOffset + valueContent.length
)
)
if (!__BROWSER__ && context.prefixIdentifiers) {
result.index = processExpression(result.index, context, true)
}
if (__DEV__ && __BROWSER__) {
if (result.index) {
validateBrowserExpression(
result.index as SimpleExpressionNode,
context,
true
true,
)
}
}
}
}
if (valueContent) {
result.value = createAliasExpression(loc, valueContent, trimmedOffset)
if (!__BROWSER__ && context.prefixIdentifiers) {
result.value = processExpression(result.value, context, true)
}
if (__DEV__ && __BROWSER__) {
if (result.value) {
validateBrowserExpression(
result.value as SimpleExpressionNode,
context,
true
true,
)
}
}
return result
}
function createAliasExpression(
range: SourceLocation,
content: string,
offset: number
): SimpleExpressionNode {
return createSimpleExpression(
content,
false,
getInnerRange(range, offset, content.length)
)
result.finalized = true
}
export function createForLoopParams(
{ value, key, index }: ForParseResult,
memoArgs: ExpressionNode[] = []
memoArgs: ExpressionNode[] = [],
): ExpressionNode[] {
return createParamsList([value, key, index, ...memoArgs])
}
function createParamsList(
args: (ExpressionNode | undefined)[]
args: (ExpressionNode | undefined)[],
): ExpressionNode[] {
let i = args.length
while (i--) {

View File

@ -1,43 +1,37 @@
import {
type TransformContext,
createStructuralDirectiveTransform,
TransformContext,
traverseNode
traverseNode,
} from '../transform'
import {
NodeTypes,
type AttributeNode,
type BlockCodegenNode,
type CacheExpression,
ConstantTypes,
type DirectiveNode,
type ElementNode,
ElementTypes,
ElementNode,
DirectiveNode,
IfBranchNode,
SimpleExpressionNode,
type IfBranchNode,
type IfConditionalExpression,
type IfNode,
type MemoExpression,
NodeTypes,
type SimpleExpressionNode,
convertToBlock,
createCallExpression,
createConditionalExpression,
createSimpleExpression,
createObjectProperty,
createObjectExpression,
IfConditionalExpression,
BlockCodegenNode,
IfNode,
createObjectProperty,
createSimpleExpression,
createVNodeCall,
AttributeNode,
locStub,
CacheExpression,
ConstantTypes,
MemoExpression,
convertToBlock
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { ErrorCodes, createCompilerError } from '../errors'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import { FRAGMENT, CREATE_COMMENT } from '../runtimeHelpers'
import {
injectProp,
findDir,
findProp,
isBuiltInType,
getMemoedVNodeCall
} from '../utils'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
import { CREATE_COMMENT, FRAGMENT } from '../runtimeHelpers'
import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
import { PatchFlagNames, PatchFlags } from '@vue/shared'
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
@ -63,7 +57,7 @@ export const transformIf = createStructuralDirectiveTransform(
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
key,
context
context,
) as IfConditionalExpression
} else {
// attach this branch's codegen node to the v-if root.
@ -71,12 +65,12 @@ export const transformIf = createStructuralDirectiveTransform(
parentCondition.alternate = createCodegenNodeForBranch(
branch,
key + ifNode.branches.length - 1,
context
context,
)
}
}
})
}
},
)
// target-agnostic transform used for both Client and SSR
@ -87,8 +81,8 @@ export function processIf(
processCodegen?: (
node: IfNode,
branch: IfBranchNode,
isRoot: boolean
) => (() => void) | undefined
isRoot: boolean,
) => (() => void) | undefined,
) {
if (
dir.name !== 'else' &&
@ -96,7 +90,7 @@ export function processIf(
) {
const loc = dir.exp ? dir.exp.loc : node.loc
context.onError(
createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc),
)
dir.exp = createSimpleExpression(`true`, false, loc)
}
@ -116,7 +110,7 @@ export function processIf(
const ifNode: IfNode = {
type: NodeTypes.IF,
loc: node.loc,
branches: [branch]
branches: [branch],
}
context.replaceNode(ifNode)
if (processCodegen) {
@ -151,7 +145,7 @@ export function processIf(
sibling.branches[sibling.branches.length - 1].condition === undefined
) {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc),
)
}
@ -165,7 +159,8 @@ export function processIf(
!(
context.parent &&
context.parent.type === NodeTypes.ELEMENT &&
isBuiltInType(context.parent.tag, 'transition')
(context.parent.tag === 'transition' ||
context.parent.tag === 'Transition')
)
) {
branch.children = [...comments, ...branch.children]
@ -180,8 +175,8 @@ export function processIf(
context.onError(
createCompilerError(
ErrorCodes.X_V_IF_SAME_KEY,
branch.userKey!.loc
)
branch.userKey!.loc,
),
)
}
})
@ -200,7 +195,7 @@ export function processIf(
context.currentNode = null
} else {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc),
)
}
break
@ -216,14 +211,14 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
condition: dir.name === 'else' ? undefined : dir.exp,
children: isTemplateIf && !findDir(node, 'for') ? node.children : [node],
userKey: findProp(node, `key`),
isTemplateIf
isTemplateIf,
}
}
function createCodegenNodeForBranch(
branch: IfBranchNode,
keyIndex: number,
context: TransformContext
context: TransformContext,
): IfConditionalExpression | BlockCodegenNode | MemoExpression {
if (branch.condition) {
return createConditionalExpression(
@ -233,8 +228,8 @@ function createCodegenNodeForBranch(
// closes the current block.
createCallExpression(context.helper(CREATE_COMMENT), [
__DEV__ ? '"v-if"' : '""',
'true'
])
'true',
]),
) as IfConditionalExpression
} else {
return createChildrenCodegenNode(branch, keyIndex, context)
@ -244,7 +239,7 @@ function createCodegenNodeForBranch(
function createChildrenCodegenNode(
branch: IfBranchNode,
keyIndex: number,
context: TransformContext
context: TransformContext,
): BlockCodegenNode | MemoExpression {
const { helper } = context
const keyProperty = createObjectProperty(
@ -253,8 +248,8 @@ function createChildrenCodegenNode(
`${keyIndex}`,
false,
locStub,
ConstantTypes.CAN_HOIST
)
ConstantTypes.CAN_HOIST,
),
)
const { children } = branch
const firstChild = children[0]
@ -285,13 +280,13 @@ function createChildrenCodegenNode(
helper(FRAGMENT),
createObjectExpression([keyProperty]),
children,
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
patchFlag,
undefined,
undefined,
true,
false,
false /* isComponent */,
branch.loc
branch.loc,
)
}
} else {
@ -311,7 +306,7 @@ function createChildrenCodegenNode(
function isSameKey(
a: AttributeNode | DirectiveNode | undefined,
b: AttributeNode | DirectiveNode
b: AttributeNode | DirectiveNode,
): boolean {
if (!a || a.type !== b.type) {
return false
@ -339,7 +334,7 @@ function isSameKey(
}
function getParentCondition(
node: IfConditionalExpression | CacheExpression
node: IfConditionalExpression | CacheExpression,
): IfConditionalExpression {
while (true) {
if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {

View File

@ -1,13 +1,13 @@
import { NodeTransform } from '../transform'
import type { NodeTransform } from '../transform'
import { findDir } from '../utils'
import {
ElementTypes,
type MemoExpression,
NodeTypes,
type PlainElementNode,
convertToBlock,
createCallExpression,
createFunctionExpression,
ElementTypes,
MemoExpression,
NodeTypes,
PlainElementNode
} from '../ast'
import { WITH_MEMO } from '../runtimeHelpers'
@ -33,7 +33,7 @@ export const transformMemo: NodeTransform = (node, context) => {
dir.exp!,
createFunctionExpression(undefined, codegenNode),
`_cache`,
String(context.cached++)
String(context.cached++),
]) as MemoExpression
}
}

View File

@ -1,20 +1,20 @@
import { DirectiveTransform } from '../transform'
import type { DirectiveTransform } from '../transform'
import {
createSimpleExpression,
createObjectProperty,
createCompoundExpression,
NodeTypes,
Property,
ConstantTypes,
ElementTypes,
ExpressionNode,
ConstantTypes
type ExpressionNode,
NodeTypes,
type Property,
createCompoundExpression,
createObjectProperty,
createSimpleExpression,
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { ErrorCodes, createCompilerError } from '../errors'
import {
hasScopeRef,
isMemberExpression,
isSimpleIdentifier,
hasScopeRef,
isStaticExp
isStaticExp,
} from '../utils'
import { IS_REF } from '../runtimeHelpers'
import { BindingTypes } from '../options'
@ -24,11 +24,13 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
const { exp, arg } = dir
if (!exp) {
context.onError(
createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc)
createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc),
)
return createTransformProps()
}
// we assume v-model directives are always parsed
// (not artificially created by a transform)
const rawExp = exp.loc.source
const expString =
exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
@ -58,7 +60,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
(!isMemberExpression(expString, context) && !maybeRef)
) {
context.onError(
createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc)
createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc),
)
return createTransformProps()
}
@ -70,7 +72,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
context.identifiers[expString]
) {
context.onError(
createCompilerError(ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE, exp.loc)
createCompilerError(ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE, exp.loc),
)
return createTransformProps()
}
@ -90,7 +92,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
assignmentExp = createCompoundExpression([
`${eventArg} => ((`,
createSimpleExpression(rawExp, false, exp.loc),
`).value = $event)`
`).value = $event)`,
])
} else {
// v-model used on a potentially ref binding in <script setup> inline mode.
@ -100,14 +102,14 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
assignmentExp = createCompoundExpression([
`${eventArg} => (${context.helperString(IS_REF)}(${rawExp}) ? (`,
createSimpleExpression(rawExp, false, exp.loc),
`).value = $event : ${altAssignment})`
`).value = $event : ${altAssignment})`,
])
}
} else {
assignmentExp = createCompoundExpression([
`${eventArg} => ((`,
exp,
`) = $event)`
`) = $event)`,
])
}
@ -115,7 +117,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
// modelValue: foo
createObjectProperty(propName, dir.exp!),
// "onUpdate:modelValue": $event => (foo = $event)
createObjectProperty(eventName, assignmentExp)
createObjectProperty(eventName, assignmentExp),
]
// cache v-model handler if applicable (when it doesn't refer any scope vars)
@ -146,9 +148,9 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
`{ ${modifiers} }`,
false,
dir.loc,
ConstantTypes.CAN_HOIST
)
)
ConstantTypes.CAN_HOIST,
),
),
)
}

View File

@ -1,23 +1,23 @@
import { DirectiveTransform, DirectiveTransformResult } from '../transform'
import type { DirectiveTransform, DirectiveTransformResult } from '../transform'
import {
type DirectiveNode,
ElementTypes,
type ExpressionNode,
NodeTypes,
type SimpleExpressionNode,
createCompoundExpression,
createObjectProperty,
createSimpleExpression,
DirectiveNode,
ElementTypes,
ExpressionNode,
NodeTypes,
SimpleExpressionNode
} from '../ast'
import { camelize, toHandlerKey } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { ErrorCodes, createCompilerError } from '../errors'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import { hasScopeRef, isMemberExpression } from '../utils'
import { TO_HANDLER_KEY } from '../runtimeHelpers'
const fnExpRE =
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
/^\s*(async\s*)?(\([^)]*?\)|[\w$_]+)\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
export interface VOnDirectiveNode extends DirectiveNode {
// v-on without arg is handled directly in ./transformElements.ts due to it affecting
@ -33,7 +33,7 @@ export const transformOn: DirectiveTransform = (
dir,
node,
context,
augmentor
augmentor,
) => {
const { loc, modifiers, arg } = dir as VOnDirectiveNode
if (!dir.exp && !modifiers.length) {
@ -44,9 +44,7 @@ export const transformOn: DirectiveTransform = (
if (arg.isStatic) {
let rawName = arg.content
if (__DEV__ && rawName.startsWith('vnode')) {
context.onWarn(
createCompilerError(ErrorCodes.DEPRECATION_VNODE_HOOKS, arg.loc)
)
context.onError(createCompilerError(ErrorCodes.X_VNODE_HOOKS, arg.loc))
}
if (rawName.startsWith('vue:')) {
rawName = `vnode-${rawName.slice(4)}`
@ -67,7 +65,7 @@ export const transformOn: DirectiveTransform = (
eventName = createCompoundExpression([
`${context.helperString(TO_HANDLER_KEY)}(`,
arg,
`)`
`)`,
])
}
} else {
@ -97,7 +95,7 @@ export const transformOn: DirectiveTransform = (
exp,
context,
false,
hasMultipleStatements
hasMultipleStatements,
)
isInlineStatement && context.removeIdentifiers(`$event`)
// with scope analysis, the function is hoistable if it has no reference
@ -136,7 +134,7 @@ export const transformOn: DirectiveTransform = (
exp as SimpleExpressionNode,
context,
false,
hasMultipleStatements
hasMultipleStatements,
)
}
@ -153,7 +151,7 @@ export const transformOn: DirectiveTransform = (
}(...args)`
} => ${hasMultipleStatements ? `{` : `(`}`,
exp,
hasMultipleStatements ? `}` : `)`
hasMultipleStatements ? `}` : `)`,
])
}
}
@ -162,9 +160,9 @@ export const transformOn: DirectiveTransform = (
props: [
createObjectProperty(
eventName,
exp || createSimpleExpression(`() => {}`, false, loc)
)
]
exp || createSimpleExpression(`() => {}`, false, loc),
),
],
}
// apply extended compiler augmentor

View File

@ -1,6 +1,6 @@
import { NodeTransform } from '../transform'
import type { NodeTransform } from '../transform'
import { findDir } from '../utils'
import { ElementNode, ForNode, IfNode, NodeTypes } from '../ast'
import { type ElementNode, type ForNode, type IfNode, NodeTypes } from '../ast'
import { SET_BLOCK_TRACKING } from '../runtimeHelpers'
const seen = new WeakSet()

View File

@ -1,38 +1,37 @@
import {
ElementNode,
ObjectExpression,
createObjectExpression,
type CallExpression,
type ConditionalExpression,
type DirectiveNode,
type ElementNode,
ElementTypes,
type ExpressionNode,
type FunctionExpression,
NodeTypes,
type ObjectExpression,
type Property,
type SlotsExpression,
type SourceLocation,
type TemplateChildNode,
createArrayExpression,
createCallExpression,
createConditionalExpression,
createFunctionExpression,
createObjectExpression,
createObjectProperty,
createSimpleExpression,
createFunctionExpression,
DirectiveNode,
ElementTypes,
ExpressionNode,
Property,
TemplateChildNode,
SourceLocation,
createConditionalExpression,
ConditionalExpression,
SimpleExpressionNode,
FunctionExpression,
CallExpression,
createCallExpression,
createArrayExpression,
SlotsExpression
} from '../ast'
import { TransformContext, NodeTransform } from '../transform'
import { createCompilerError, ErrorCodes } from '../errors'
import type { NodeTransform, TransformContext } from '../transform'
import { ErrorCodes, createCompilerError } from '../errors'
import {
findDir,
isTemplateNode,
assert,
isVSlot,
findDir,
hasScopeRef,
isStaticExp
isStaticExp,
isTemplateNode,
isVSlot,
} from '../utils'
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
import { parseForExpression, createForLoopParams } from './vFor'
import { createForLoopParams, finalizeForParseResult } from './vFor'
import { SlotFlags, slotFlagsText } from '@vue/shared'
const defaultFallback = createSimpleExpression(`undefined`, false)
@ -78,11 +77,9 @@ export const trackVForSlotScopes: NodeTransform = (node, context) => {
node.props.some(isVSlot) &&
(vFor = findDir(node, 'for'))
) {
const result = (vFor.parseResult = parseForExpression(
vFor.exp as SimpleExpressionNode,
context
))
const result = vFor.forParseResult
if (result) {
finalizeForParseResult(result, context)
const { value, key, index } = result
const { addIdentifiers, removeIdentifiers } = context
value && addIdentifiers(value)
@ -100,17 +97,18 @@ export const trackVForSlotScopes: NodeTransform = (node, context) => {
export type SlotFnBuilder = (
slotProps: ExpressionNode | undefined,
vFor: DirectiveNode | undefined,
slotChildren: TemplateChildNode[],
loc: SourceLocation
loc: SourceLocation,
) => FunctionExpression
const buildClientSlotFn: SlotFnBuilder = (props, children, loc) =>
const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) =>
createFunctionExpression(
props,
children,
false /* newline */,
true /* isSlot */,
children.length ? children[0].loc : loc
children.length ? children[0].loc : loc,
)
// Instead of being a DirectiveTransform, v-slot processing is called during
@ -118,7 +116,7 @@ const buildClientSlotFn: SlotFnBuilder = (props, children, loc) =>
export function buildSlots(
node: ElementNode,
context: TransformContext,
buildSlotFn: SlotFnBuilder = buildClientSlotFn
buildSlotFn: SlotFnBuilder = buildClientSlotFn,
): {
slots: SlotsExpression
hasDynamicSlots: boolean
@ -149,8 +147,8 @@ export function buildSlots(
slotsProperties.push(
createObjectProperty(
arg || createSimpleExpression('default', true),
buildSlotFn(exp, children, loc)
)
buildSlotFn(exp, undefined, children, loc),
),
)
}
@ -180,7 +178,7 @@ export function buildSlots(
if (onComponentSlot) {
// already has on-component slot - this is incorrect usage.
context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc)
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc),
)
break
}
@ -190,7 +188,7 @@ export function buildSlots(
const {
arg: slotName = createSimpleExpression(`default`, true),
exp: slotProps,
loc: dirLoc
loc: dirLoc,
} = slotDir
// check if name is dynamic.
@ -201,19 +199,20 @@ export function buildSlots(
hasDynamicSlots = true
}
const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc)
const vFor = findDir(slotElement, 'for')
const slotFunction = buildSlotFn(slotProps, vFor, slotChildren, slotLoc)
// check if this slot is conditional (v-if/v-for)
let vIf: DirectiveNode | undefined
let vElse: DirectiveNode | undefined
let vFor: DirectiveNode | undefined
if ((vIf = findDir(slotElement, 'if'))) {
hasDynamicSlots = true
dynamicSlots.push(
createConditionalExpression(
vIf.exp!,
buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++),
defaultFallback
)
defaultFallback,
),
)
} else if (
(vElse = findDir(slotElement, /^else(-if)?$/, true /* allowEmpty */))
@ -227,10 +226,7 @@ export function buildSlots(
break
}
}
if (prev && isTemplateNode(prev) && findDir(prev, 'if')) {
// remove node
children.splice(i, 1)
i--
if (prev && isTemplateNode(prev) && findDir(prev, /^(else-)?if$/)) {
__TEST__ && assert(dynamicSlots.length > 0)
// attach this slot to previous conditional
let conditional = dynamicSlots[
@ -247,22 +243,21 @@ export function buildSlots(
buildDynamicSlot(
slotName,
slotFunction,
conditionalBranchIndex++
conditionalBranchIndex++,
),
defaultFallback
defaultFallback,
)
: buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++)
} else {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc)
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc),
)
}
} else if ((vFor = findDir(slotElement, 'for'))) {
} else if (vFor) {
hasDynamicSlots = true
const parseResult =
vFor.parseResult ||
parseForExpression(vFor.exp as SimpleExpressionNode, context)
const parseResult = vFor.forParseResult
if (parseResult) {
finalizeForParseResult(parseResult, context)
// Render the dynamic slots as an array and add it to the createSlot()
// args. The runtime knows how to handle it appropriately.
dynamicSlots.push(
@ -271,13 +266,16 @@ export function buildSlots(
createFunctionExpression(
createForLoopParams(parseResult),
buildDynamicSlot(slotName, slotFunction),
true /* force newline */
)
])
true /* force newline */,
),
]),
)
} else {
context.onError(
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, vFor.loc)
createCompilerError(
ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
vFor.loc,
),
)
}
} else {
@ -287,8 +285,8 @@ export function buildSlots(
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
dirLoc
)
dirLoc,
),
)
continue
}
@ -304,9 +302,9 @@ export function buildSlots(
if (!onComponentSlot) {
const buildDefaultSlotProperty = (
props: ExpressionNode | undefined,
children: TemplateChildNode[]
children: TemplateChildNode[],
) => {
const fn = buildSlotFn(props, children, loc)
const fn = buildSlotFn(props, undefined, children, loc)
if (__COMPAT__ && context.compatConfig) {
fn.isNonScopedSlot = true
}
@ -328,12 +326,12 @@ export function buildSlots(
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
implicitDefaultChildren[0].loc
)
implicitDefaultChildren[0].loc,
),
)
} else {
slotsProperties.push(
buildDefaultSlotProperty(undefined, implicitDefaultChildren)
buildDefaultSlotProperty(undefined, implicitDefaultChildren),
)
}
}
@ -353,37 +351,37 @@ export function buildSlots(
// 1 = compiled and static = can skip normalization AND diff as optimized
createSimpleExpression(
slotFlag + (__DEV__ ? ` /* ${slotFlagsText[slotFlag]} */` : ``),
false
)
)
false,
),
loc
),
),
loc,
) as SlotsExpression
if (dynamicSlots.length) {
slots = createCallExpression(context.helper(CREATE_SLOTS), [
slots,
createArrayExpression(dynamicSlots)
createArrayExpression(dynamicSlots),
]) as SlotsExpression
}
return {
slots,
hasDynamicSlots
hasDynamicSlots,
}
}
function buildDynamicSlot(
name: ExpressionNode,
fn: FunctionExpression,
index?: number
index?: number,
): ObjectExpression {
const props = [
createObjectProperty(`name`, name),
createObjectProperty(`fn`, fn)
createObjectProperty(`fn`, fn),
]
if (index != null) {
props.push(
createObjectProperty(`key`, createSimpleExpression(String(index), true))
createObjectProperty(`key`, createSimpleExpression(String(index), true)),
)
}
return createObjectExpression(props)

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