mirror of https://github.com/vuejs/core.git
chore: Merge branch 'main' into minor
This commit is contained in:
commit
1ea775633d
|
@ -17,7 +17,11 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
|
||||||
|
|
||||||
## Pull Request Guidelines
|
## Pull Request Guidelines
|
||||||
|
|
||||||
- Checkout a topic branch from a base branch, e.g. `main`, and merge back against that branch.
|
- 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.
|
- [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.
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 200 KiB |
Binary file not shown.
After Width: | Height: | Size: 262 KiB |
|
@ -0,0 +1,122 @@
|
||||||
|
# Vue Core Maintenance Handbook
|
||||||
|
|
||||||
|
Unlike [contributing.md](./contributing.md), which targets external contributors, this document is mainly intended for team members responsible for maintaining the project. It provides guidelines on how to triage issues, review & merge PRs, and publish releases. However, it should also be valuable to external contributors even if you are not a maintainer, as it gives you a better idea of how the maintainers operate, and how you can better collaborate with them. And who knows - maybe one day you will join as a maintainer as well!
|
||||||
|
|
||||||
|
- [Issue Triage Workflow](#issue-triage-workflow)
|
||||||
|
- [Pull Request Review Guidelines](#pull-request-review-guidelines)
|
||||||
|
- [Reviewing a Fix](#reviewing-a-fix)
|
||||||
|
- [Reviewing a Refactor](#reviewing-a-refactor)
|
||||||
|
- [Reviewing a Feature](#reviewing-a-feature)
|
||||||
|
- [Common Considerations for All PRs](#common-considerations-for-all-prs)
|
||||||
|
- [PR Merge Rules for Team Members](#pr-merge-rules-for-team-members)
|
||||||
|
- [Git Branch and Release Workflow](#git-branch-and-release-workflow)
|
||||||
|
|
||||||
|
## Issue Triage Workflow
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Pull Request Review Guidelines
|
||||||
|
|
||||||
|
The first step of reviewing a PR is to identify its purpose. We can usually put a PR in one of these categories:
|
||||||
|
|
||||||
|
- **Fix**: fixes some wrong behavior. Usually associated with an issue that has a reproduction of the behavior being fixed.
|
||||||
|
- **Refactor**: improves performance or code quality, but does not affect behavior.
|
||||||
|
- **Feature**: implements something that increases the public API surface.
|
||||||
|
|
||||||
|
Depending on the type of the PR, different considerations need to be taken into account.
|
||||||
|
|
||||||
|
### Reviewing a Fix
|
||||||
|
|
||||||
|
- Is the PR fixing a well defined issue / bug report?
|
||||||
|
- If not, ask to clarify context / provide reproduction or failing test case
|
||||||
|
- In most cases, a fix PR should include a test case that fails without the fix.
|
||||||
|
- Is it the right fix?
|
||||||
|
- If not, guide user to rework the PR.
|
||||||
|
- If the needed change is small and obvious, can directly push to the PR or add inline suggestions to reduce the back-and-forth.
|
||||||
|
- Is the cost justified?
|
||||||
|
- Sometimes the fix for a rare edge case might be introducing disproportionately large overhead (perf or code size). We should try our best to reduce the overhead to make the fix a reasonable tradeoff.
|
||||||
|
- If the reviewer is not sure about a fix, try to leave a comment explaining the concerns / reservations so the contributor at least gets some feedback.
|
||||||
|
|
||||||
|
#### Verifying a Fix
|
||||||
|
|
||||||
|
- **Always locally verify that the fix indeed fixes the original behavior, either through a reproduction or a failing test case.**
|
||||||
|
- We will run [ecosystem-ci](https://github.com/vuejs/ecosystem-ci) before every release, but if you are concerned about the potential impact of a change, it never hurts to manually run ecosystem-ci by leaving a `/ecosystem-ci run` comment (only works for team members).
|
||||||
|
- Take extra caution with snapshot tests! The CI can be "passing" even if the code generated in the snapshot contains bugs. It's best to always accompany a snapshot test with extra `expect(code).toMatch(...)` assertions.
|
||||||
|
|
||||||
|
### Reviewing a Refactor
|
||||||
|
|
||||||
|
- Performance: if a refactor PR claims to improve performance, there should be benchmarks showcasing said performance unless the improvement is self-explanatory.
|
||||||
|
|
||||||
|
- Code quality / stylistic PRs: we should be conservative on merging this type PRs because (1) they can be subjective in many cases, and (2) they often come with large git diffs, causing merge conflicts with other pending PRs, and leading to unwanted noise when tracing changes through git history. Use your best judgement on this type of PRs on whether they are worth it.
|
||||||
|
|
||||||
|
- For PRs in this category that are approved, do not merge immediately. Group them before releasing a new minor, after all feature-oriented PRs are merged.
|
||||||
|
|
||||||
|
### Reviewing a Feature
|
||||||
|
|
||||||
|
- Feature PRs should always have clear context and explanation on why the feature should be added, ideally in the form of an RFC. If the PR doesn't explain what real-world problem it is solving, ask the contributor to clarify.
|
||||||
|
|
||||||
|
- Decide if the feature should require an RFC process. The line isn't always clear, but a rough criteria is whether it is augmenting an existing API vs. adding a new API. Some examples:
|
||||||
|
|
||||||
|
- Adding a new built-in component or directive is "significant" and definitely requires an RFC.
|
||||||
|
- Template syntax additions like adding a new `v-on` modifier or a new `v-bind` syntax sugar are "substantial". It would be nice to have an RFC for it, but a detailed explanation on the use case and reasoning behind the design directly in the PR itself can be acceptable.
|
||||||
|
- Small, low-impact additions like exposing a new utility type or adding a new app config option can be self-explanatory, but should still provide enough context in the PR.
|
||||||
|
|
||||||
|
- Always ask if the use case can be solved with existing APIs. Vue already has a pretty large API surface, so we want to make sure every new addition either solves something that wasn't possible before, or significantly improves the DX of a common task.
|
||||||
|
|
||||||
|
### Common Considerations for All PRs
|
||||||
|
|
||||||
|
- Scope: a PR should only contain changes directly related to the problem being addressed. It should not contain unnecessary code style changes.
|
||||||
|
|
||||||
|
- Implementation: code style should be consistent with the rest of the codebase, follow common best practices. Prefer code that is boring but easy to understand over "clever" code.
|
||||||
|
|
||||||
|
- Size: bundle size matters. We have a GitHub action that compares the size change for every PR. We should always aim to realize the desired changes with the smallest amount of code size increase.
|
||||||
|
|
||||||
|
- Sometimes we need to compare the size increase vs. perceived benefits to decide whether a change is justifiable. Also take extra care to make sure added code can be tree-shaken if not needed.
|
||||||
|
|
||||||
|
- Make sure to put dev-only code in `__DEV__` branches so they are tree-shakable.
|
||||||
|
|
||||||
|
- Runtime code is more sensitive to size increase than compiler code.
|
||||||
|
|
||||||
|
- Make sure it doesn't accidentally cause dev-only or compiler-only code branches to be included in the runtime build. Notable case is that some functions in @vue/shared are compiler-only and should not be used in runtime code, e.g. `isHTMLTag` and `isSVGTag`.
|
||||||
|
|
||||||
|
- Performance
|
||||||
|
- Be careful about code changes in "hot paths", in particular the Virtual DOM renderer (`runtime-core/src/renderer.ts`) and component instantiation code.
|
||||||
|
|
||||||
|
- Potential Breakage
|
||||||
|
- avoiding runtime behavior breakage is the highest priority
|
||||||
|
- if not sure, use `ecosystem-ci` to verify!
|
||||||
|
- some fix inevitably cause behavior change, these must be discussed case-by-case
|
||||||
|
- type level breakage (e.g upgrading TS) is possible between minors
|
||||||
|
|
||||||
|
## PR Merge Rules for Team Members
|
||||||
|
|
||||||
|
Given that the PR meets the review requirements:
|
||||||
|
|
||||||
|
- Chore / dependencies bumps: can merge directly.
|
||||||
|
- Fixes / refactors: can merge with two or more approvals from team members.
|
||||||
|
- If you believe a PR looks good but you are not 100% confident to merge, label with "ready for merge" and Evan will provide a final review before merging.
|
||||||
|
- Features: if approved by two or more team members, label with "ready to merge". Evan will review periodically, or they can be raised and discussed at team meetings.
|
||||||
|
|
||||||
|
## Git Branch and Release Workflow
|
||||||
|
|
||||||
|
We use two primary work branches: `main` and `minor`.
|
||||||
|
|
||||||
|
- The `main` branch is for stable releases. Changes that are bug fixes or refactors that do not affect the public API surface should land in this branch. We periodically release patch releases from the `main` branch.
|
||||||
|
|
||||||
|
- The `minor` branch is the WIP branch for the next minor release. Changes that are new features or those that affect public API behavior should land in this branch. We will periodically release pre-releases (alpha / beta) for the next minor from this branch.
|
||||||
|
|
||||||
|
Before each release, we merge latest `main` into `minor` so it would include the latest bug fixes.
|
||||||
|
|
||||||
|
When the minor is ready, we do a final merge of `main` into `minor`, and then release a stable minor from this branch (e.g. `3.4.0`). After that, the `main` branch is fast-forwarded to the release commit, so the two branches are synced at each stable minor release.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Reasoning Behind the Workflow
|
||||||
|
|
||||||
|
The reason behind this workflow is to allow merging and releasing of fixes and features in parallel. In the past, we used a linear trunk-based development model. While the linear model results in a clean git history, the downside is that we need to be careful about when to merge patches vs. features.
|
||||||
|
|
||||||
|
Vue typically groups a number of features with the same scope in a minor release. We don't want to release a minor just because we happened to merge a feature PR along with a bunch of small bug fixes. So we usually "wait" until we feel we are ready to start working on a minor release before merging feature PRs.
|
||||||
|
|
||||||
|
But in reality, there are always bugs to fix and patch release to work on - this caused the intervals between minors to drag on longer than we had hoped, and many feature PRs were left waiting for a long period of time.
|
||||||
|
|
||||||
|
This is why we decided to separate bug fixes and feature PRs into separate branches. With this two-branch model, we are able to merge and release both types of changes in parallel.
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Set node version to 18
|
- name: Set node version to 18
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
@ -30,4 +30,4 @@ jobs:
|
||||||
- name: Run prettier
|
- name: Run prettier
|
||||||
run: pnpm run format
|
run: pnpm run format
|
||||||
|
|
||||||
- uses: autofix-ci/action@d3e591514b99d0fca6779455ff8338516663f7cc
|
- uses: autofix-ci/action@bee19d72e71787c12ca0f29de72f2833e437e4c9
|
||||||
|
|
|
@ -20,7 +20,7 @@ jobs:
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Set node version to 18
|
- name: Set node version to 18
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -45,7 +45,7 @@ jobs:
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -74,7 +74,7 @@ jobs:
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -97,7 +97,7 @@ jobs:
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -9,7 +9,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'vuejs/core' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run')
|
if: github.repository == 'vuejs/core' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const user = context.payload.sender.login
|
const user = context.payload.sender.login
|
||||||
|
@ -43,7 +43,7 @@ jobs:
|
||||||
})
|
})
|
||||||
throw new Error('not allowed')
|
throw new Error('not allowed')
|
||||||
}
|
}
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
id: get-pr-data
|
id: get-pr-data
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
|
@ -58,7 +58,7 @@ jobs:
|
||||||
branchName: pr.head.ref,
|
branchName: pr.head.ref,
|
||||||
repo: pr.head.repo.full_name
|
repo: pr.head.repo.full_name
|
||||||
}
|
}
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
id: trigger
|
id: trigger
|
||||||
env:
|
env:
|
||||||
COMMENT: ${{ github.event.comment.body }}
|
COMMENT: ${{ github.event.comment.body }}
|
||||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
if: github.repository == 'vuejs/core'
|
if: github.repository == 'vuejs/core'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v4
|
- uses: dessant/lock-threads@v5
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-inactive-days: '14'
|
issue-inactive-days: '14'
|
||||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
|
@ -27,7 +27,7 @@ jobs:
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -1,3 +1,19 @@
|
||||||
|
## [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 eeror 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.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28)
|
# [3.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ Vue.js is an MIT-licensed open source project with its ongoing development made
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a target="_blank" href="https://vuejs.org/sponsor/#current-sponsors">
|
<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>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
32
package.json
32
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.4.0-alpha.1",
|
"version": "3.4.0-alpha.1",
|
||||||
"packageManager": "pnpm@8.9.2",
|
"packageManager": "pnpm@8.10.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
|
@ -27,9 +27,9 @@
|
||||||
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
||||||
"dev-compiler": "run-p \"dev template-explorer\" serve",
|
"dev-compiler": "run-p \"dev template-explorer\" serve",
|
||||||
"dev-sfc": "run-s dev-sfc-prepare dev-sfc-run",
|
"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-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",
|
"serve": "serve",
|
||||||
"open": "open http://localhost:3000/packages/template-explorer/local.html",
|
"open": "open http://localhost:3000/packages/template-explorer/local.html",
|
||||||
"build-sfc-playground": "run-s build-all-cjs build-runtime-esm build-ssr-esm build-sfc-playground-self",
|
"build-sfc-playground": "run-s build-all-cjs build-runtime-esm build-ssr-esm build-sfc-playground-self",
|
||||||
|
@ -57,40 +57,40 @@
|
||||||
"node": ">=18.12.0"
|
"node": ">=18.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/parser": "^7.23.0",
|
"@babel/parser": "^7.23.3",
|
||||||
"@babel/types": "^7.23.0",
|
"@babel/types": "^7.23.3",
|
||||||
"@rollup/plugin-alias": "^5.0.1",
|
"@rollup/plugin-alias": "^5.0.1",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@rollup/plugin-json": "^6.0.1",
|
"@rollup/plugin-json": "^6.0.1",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-replace": "^5.0.4",
|
"@rollup/plugin-replace": "^5.0.4",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@types/hash-sum": "^1.0.1",
|
"@types/hash-sum": "^1.0.2",
|
||||||
"@types/node": "^20.8.7",
|
"@types/node": "^20.9.2",
|
||||||
"@typescript-eslint/parser": "^6.8.0",
|
"@typescript-eslint/parser": "^6.11.0",
|
||||||
"@vitest/coverage-istanbul": "^0.34.6",
|
"@vitest/coverage-istanbul": "^0.34.6",
|
||||||
"@vue/consolidate": "0.17.3",
|
"@vue/consolidate": "0.17.3",
|
||||||
"conventional-changelog-cli": "^4.1.0",
|
"conventional-changelog-cli": "^4.1.0",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"esbuild": "^0.19.5",
|
"esbuild": "^0.19.5",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"eslint": "^8.52.0",
|
"eslint": "^8.54.0",
|
||||||
"eslint-plugin-jest": "^27.4.3",
|
"eslint-plugin-jest": "^27.6.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"execa": "^8.0.1",
|
"execa": "^8.0.1",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"lint-staged": "^15.0.2",
|
"lint-staged": "^15.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"magic-string": "^0.30.5",
|
"magic-string": "^0.30.5",
|
||||||
"markdown-table": "^3.0.3",
|
"markdown-table": "^3.0.3",
|
||||||
"marked": "^9.1.2",
|
"marked": "^9.1.6",
|
||||||
"minimist": "^1.2.8",
|
"minimist": "^1.2.8",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.1.0",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.2",
|
||||||
"puppeteer": "~21.4.0",
|
"puppeteer": "~21.5.1",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"rollup": "^4.1.4",
|
"rollup": "^4.1.4",
|
||||||
"rollup-plugin-dts": "^6.1.0",
|
"rollup-plugin-dts": "^6.1.0",
|
||||||
|
@ -102,9 +102,9 @@
|
||||||
"terser": "^5.22.0",
|
"terser": "^5.22.0",
|
||||||
"todomvc-app-css": "^2.4.3",
|
"todomvc-app-css": "^2.4.3",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"tsx": "^3.14.0",
|
"tsx": "^4.1.4",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^4.5.0",
|
"vite": "^5.0.0",
|
||||||
"vitest": "^0.34.6"
|
"vitest": "^0.34.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
exports[`compiler: expression transform > bindingMetadata > inline mode 1`] = `
|
exports[`compiler: expression transform > bindingMetadata > inline mode 1`] = `
|
||||||
"(_ctx, _cache) => {
|
"(_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,48 @@ exports[`compiler: expression transform > bindingMetadata > non-inline mode 1`]
|
||||||
"const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache, $props, $setup, $data, $options) {
|
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 > bindingMetadata > should not prefix temp variable of for loop 1`] = `
|
||||||
|
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||||
|
return (_openBlock(), _createElementBlock(\\"div\\", {
|
||||||
|
onClick: () => {
|
||||||
|
for (let i = 0; i < _ctx.list.length; i++) {
|
||||||
|
_ctx.log(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null, 8 /* PROPS */, [\\"onClick\\"]))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for...in 1`] = `
|
||||||
|
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||||
|
return (_openBlock(), _createElementBlock(\\"div\\", {
|
||||||
|
onClick: () => {
|
||||||
|
for (const x in _ctx.list) {
|
||||||
|
_ctx.log(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null, 8 /* PROPS */, [\\"onClick\\"]))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for...of 1`] = `
|
||||||
|
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||||
|
return (_openBlock(), _createElementBlock(\\"div\\", {
|
||||||
|
onClick: () => {
|
||||||
|
for (const x of _ctx.list) {
|
||||||
|
_ctx.log(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null, 8 /* PROPS */, [\\"onClick\\"]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -85,7 +85,7 @@ return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock(\\"input\\", {
|
return (_openBlock(), _createElementBlock(\\"input\\", {
|
||||||
\\"foo-value\\": model,
|
\\"foo-value\\": model,
|
||||||
\\"onUpdate:fooValue\\": $event => ((model) = $event)
|
\\"onUpdate:fooValue\\": $event => ((model) = $event)
|
||||||
}, null, 40 /* PROPS, HYDRATE_EVENTS */, [\\"foo-value\\", \\"onUpdate:fooValue\\"]))
|
}, null, 40 /* PROPS, NEED_HYDRATION */, [\\"foo-value\\", \\"onUpdate:fooValue\\"]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1089,7 +1089,7 @@ describe('compiler: element transform', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('HYDRATE_EVENTS', () => {
|
test('NEED_HYDRATION for v-on', () => {
|
||||||
// ignore click events (has dedicated fast path)
|
// ignore click events (has dedicated fast path)
|
||||||
const { node } = parseWithElementTransform(`<div @click="foo" />`, {
|
const { node } = parseWithElementTransform(`<div @click="foo" />`, {
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
|
@ -1108,12 +1108,24 @@ describe('compiler: element transform', () => {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(node2.patchFlag).toBe(
|
expect(node2.patchFlag).toBe(
|
||||||
genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
|
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('NEED_HYDRATION for v-bind.prop', () => {
|
||||||
|
const { node } = parseWithBind(`<div v-bind:id.prop="id" />`)
|
||||||
|
expect(node.patchFlag).toBe(
|
||||||
|
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
|
||||||
|
)
|
||||||
|
|
||||||
|
const { node: node2 } = parseWithBind(`<div .id="id" />`)
|
||||||
|
expect(node2.patchFlag).toBe(
|
||||||
|
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// #5870
|
// #5870
|
||||||
test('HYDRATE_EVENTS on dynamic component', () => {
|
test('NEED_HYDRATION on dynamic component', () => {
|
||||||
const { node } = parseWithElementTransform(
|
const { node } = parseWithElementTransform(
|
||||||
`<component :is="foo" @input="foo" />`,
|
`<component :is="foo" @input="foo" />`,
|
||||||
{
|
{
|
||||||
|
@ -1123,7 +1135,7 @@ describe('compiler: element transform', () => {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(node.patchFlag).toBe(
|
expect(node.patchFlag).toBe(
|
||||||
genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
|
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -506,7 +506,8 @@ describe('compiler: expression transform', () => {
|
||||||
data: BindingTypes.DATA,
|
data: BindingTypes.DATA,
|
||||||
options: BindingTypes.OPTIONS,
|
options: BindingTypes.OPTIONS,
|
||||||
reactive: BindingTypes.SETUP_REACTIVE_CONST,
|
reactive: BindingTypes.SETUP_REACTIVE_CONST,
|
||||||
literal: BindingTypes.LITERAL_CONST
|
literal: BindingTypes.LITERAL_CONST,
|
||||||
|
isNaN: BindingTypes.SETUP_REF
|
||||||
}
|
}
|
||||||
|
|
||||||
function compileWithBindingMetadata(
|
function compileWithBindingMetadata(
|
||||||
|
@ -522,19 +523,56 @@ describe('compiler: expression transform', () => {
|
||||||
|
|
||||||
test('non-inline mode', () => {
|
test('non-inline mode', () => {
|
||||||
const { code } = compileWithBindingMetadata(
|
const { code } = compileWithBindingMetadata(
|
||||||
`<div>{{ props }} {{ setup }} {{ data }} {{ options }}</div>`
|
`<div>{{ props }} {{ setup }} {{ data }} {{ options }} {{ isNaN }}</div>`
|
||||||
)
|
)
|
||||||
expect(code).toMatch(`$props.props`)
|
expect(code).toMatch(`$props.props`)
|
||||||
expect(code).toMatch(`$setup.setup`)
|
expect(code).toMatch(`$setup.setup`)
|
||||||
|
expect(code).toMatch(`$setup.isNaN`)
|
||||||
expect(code).toMatch(`$data.data`)
|
expect(code).toMatch(`$data.data`)
|
||||||
expect(code).toMatch(`$options.options`)
|
expect(code).toMatch(`$options.options`)
|
||||||
expect(code).toMatch(`_ctx, _cache, $props, $setup, $data, $options`)
|
expect(code).toMatch(`_ctx, _cache, $props, $setup, $data, $options`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should not prefix temp variable of for...in', () => {
|
||||||
|
const { code } = compileWithBindingMetadata(
|
||||||
|
`<div @click="() => {
|
||||||
|
for (const x in list) {
|
||||||
|
log(x)
|
||||||
|
}
|
||||||
|
}"/>`
|
||||||
|
)
|
||||||
|
expect(code).not.toMatch(`_ctx.x`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not prefix temp variable of for...of', () => {
|
||||||
|
const { code } = compileWithBindingMetadata(
|
||||||
|
`<div @click="() => {
|
||||||
|
for (const x of list) {
|
||||||
|
log(x)
|
||||||
|
}
|
||||||
|
}"/>`
|
||||||
|
)
|
||||||
|
expect(code).not.toMatch(`_ctx.x`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not prefix temp variable of for loop', () => {
|
||||||
|
const { code } = compileWithBindingMetadata(
|
||||||
|
`<div @click="() => {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
log(i)
|
||||||
|
}
|
||||||
|
}"/>`
|
||||||
|
)
|
||||||
|
expect(code).not.toMatch(`_ctx.i`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('inline mode', () => {
|
test('inline mode', () => {
|
||||||
const { code } = compileWithBindingMetadata(
|
const { code } = compileWithBindingMetadata(
|
||||||
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }}</div>`,
|
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}</div>`,
|
||||||
{ inline: true }
|
{ inline: true }
|
||||||
)
|
)
|
||||||
expect(code).toMatch(`__props.props`)
|
expect(code).toMatch(`__props.props`)
|
||||||
|
@ -542,6 +580,7 @@ describe('compiler: expression transform', () => {
|
||||||
expect(code).toMatch(`_toDisplayString(setupConst)`)
|
expect(code).toMatch(`_toDisplayString(setupConst)`)
|
||||||
expect(code).toMatch(`_ctx.data`)
|
expect(code).toMatch(`_ctx.data`)
|
||||||
expect(code).toMatch(`_ctx.options`)
|
expect(code).toMatch(`_ctx.options`)
|
||||||
|
expect(code).toMatch(`isNaN.value`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -674,8 +674,8 @@ describe('compiler: v-for', () => {
|
||||||
patchFlag: !disableTracking
|
patchFlag: !disableTracking
|
||||||
? genFlagText(PatchFlags.STABLE_FRAGMENT)
|
? genFlagText(PatchFlags.STABLE_FRAGMENT)
|
||||||
: keyed
|
: keyed
|
||||||
? genFlagText(PatchFlags.KEYED_FRAGMENT)
|
? genFlagText(PatchFlags.KEYED_FRAGMENT)
|
||||||
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||||
children: {
|
children: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: RENDER_LIST,
|
callee: RENDER_LIST,
|
||||||
|
|
|
@ -32,12 +32,12 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.23.0",
|
"@babel/parser": "^7.23.3",
|
||||||
"@vue/shared": "3.4.0-alpha.1",
|
"@vue/shared": "workspace:*",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.23.0"
|
"@babel/types": "^7.23.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,19 @@ export function walkBlockDeclarations(
|
||||||
) {
|
) {
|
||||||
if (stmt.declare || !stmt.id) continue
|
if (stmt.declare || !stmt.id) continue
|
||||||
onIdent(stmt.id)
|
onIdent(stmt.id)
|
||||||
|
} else if (
|
||||||
|
stmt.type === 'ForOfStatement' ||
|
||||||
|
stmt.type === 'ForInStatement' ||
|
||||||
|
stmt.type === 'ForStatement'
|
||||||
|
) {
|
||||||
|
const variable = stmt.type === 'ForStatement' ? stmt.init : stmt.left
|
||||||
|
if (variable && variable.type === 'VariableDeclaration') {
|
||||||
|
for (const decl of variable.declarations) {
|
||||||
|
for (const id of extractIdentifiers(decl.id)) {
|
||||||
|
onIdent(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -448,8 +448,8 @@ function genAssets(
|
||||||
__COMPAT__ && type === 'filter'
|
__COMPAT__ && type === 'filter'
|
||||||
? RESOLVE_FILTER
|
? RESOLVE_FILTER
|
||||||
: type === 'component'
|
: type === 'component'
|
||||||
? RESOLVE_COMPONENT
|
? RESOLVE_COMPONENT
|
||||||
: RESOLVE_DIRECTIVE
|
: RESOLVE_DIRECTIVE
|
||||||
)
|
)
|
||||||
for (let i = 0; i < assets.length; i++) {
|
for (let i = 0; i < assets.length; i++) {
|
||||||
let id = assets[i]
|
let id = assets[i]
|
||||||
|
|
|
@ -41,8 +41,8 @@ export function getBaseTransformPreset(
|
||||||
transformExpression
|
transformExpression
|
||||||
]
|
]
|
||||||
: __BROWSER__ && __DEV__
|
: __BROWSER__ && __DEV__
|
||||||
? [transformExpression]
|
? [transformExpression]
|
||||||
: []),
|
: []),
|
||||||
transformSlotOutlet,
|
transformSlotOutlet,
|
||||||
transformElement,
|
transformElement,
|
||||||
trackSlotScopes,
|
trackSlotScopes,
|
||||||
|
|
|
@ -811,8 +811,8 @@ function parseAttribute(
|
||||||
(isPropShorthand || startsWith(name, ':')
|
(isPropShorthand || startsWith(name, ':')
|
||||||
? 'bind'
|
? 'bind'
|
||||||
: startsWith(name, '@')
|
: startsWith(name, '@')
|
||||||
? 'on'
|
? 'on'
|
||||||
: 'slot')
|
: 'slot')
|
||||||
let arg: ExpressionNode | undefined
|
let arg: ExpressionNode | undefined
|
||||||
|
|
||||||
if (match[2]) {
|
if (match[2]) {
|
||||||
|
@ -1063,7 +1063,7 @@ function parseTextData(
|
||||||
) {
|
) {
|
||||||
return rawText
|
return rawText
|
||||||
} else {
|
} else {
|
||||||
// DATA or RCDATA containing "&"". Entity decoding required.
|
// DATA or RCDATA containing "&". Entity decoding is required.
|
||||||
return context.options.decodeEntities(
|
return context.options.decodeEntities(
|
||||||
rawText,
|
rawText,
|
||||||
mode === TextModes.ATTRIBUTE_VALUE
|
mode === TextModes.ATTRIBUTE_VALUE
|
||||||
|
|
|
@ -238,8 +238,8 @@ export function createTransformContext(
|
||||||
const removalIndex = node
|
const removalIndex = node
|
||||||
? list.indexOf(node)
|
? list.indexOf(node)
|
||||||
: context.currentNode
|
: context.currentNode
|
||||||
? context.childIndex
|
? context.childIndex
|
||||||
: -1
|
: -1
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
if (__DEV__ && removalIndex < 0) {
|
if (__DEV__ && removalIndex < 0) {
|
||||||
throw new Error(`node being removed is not a child of current parent`)
|
throw new Error(`node being removed is not a child of current parent`)
|
||||||
|
|
|
@ -550,7 +550,7 @@ export function buildProps(
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// directives
|
// directives
|
||||||
const { name, arg, exp, loc } = prop
|
const { name, arg, exp, loc, modifiers } = prop
|
||||||
const isVBind = name === 'bind'
|
const isVBind = name === 'bind'
|
||||||
const isVOn = name === 'on'
|
const isVOn = name === 'on'
|
||||||
|
|
||||||
|
@ -678,6 +678,11 @@ export function buildProps(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// force hydration for v-bind with .prop modifier
|
||||||
|
if (isVBind && modifiers.includes('prop')) {
|
||||||
|
patchFlag |= PatchFlags.NEED_HYDRATION
|
||||||
|
}
|
||||||
|
|
||||||
const directiveTransform = context.directiveTransforms[name]
|
const directiveTransform = context.directiveTransforms[name]
|
||||||
if (directiveTransform) {
|
if (directiveTransform) {
|
||||||
// has built-in directive transform.
|
// has built-in directive transform.
|
||||||
|
@ -743,12 +748,12 @@ export function buildProps(
|
||||||
patchFlag |= PatchFlags.PROPS
|
patchFlag |= PatchFlags.PROPS
|
||||||
}
|
}
|
||||||
if (hasHydrationEventBinding) {
|
if (hasHydrationEventBinding) {
|
||||||
patchFlag |= PatchFlags.HYDRATE_EVENTS
|
patchFlag |= PatchFlags.NEED_HYDRATION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!shouldUseBlock &&
|
!shouldUseBlock &&
|
||||||
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
|
(patchFlag === 0 || patchFlag === PatchFlags.NEED_HYDRATION) &&
|
||||||
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
|
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
|
||||||
) {
|
) {
|
||||||
patchFlag |= PatchFlags.NEED_PATCH
|
patchFlag |= PatchFlags.NEED_PATCH
|
||||||
|
|
|
@ -227,10 +227,15 @@ export function processExpression(
|
||||||
const isScopeVarReference = context.identifiers[rawExp]
|
const isScopeVarReference = context.identifiers[rawExp]
|
||||||
const isAllowedGlobal = isGloballyAllowed(rawExp)
|
const isAllowedGlobal = isGloballyAllowed(rawExp)
|
||||||
const isLiteral = isLiteralWhitelisted(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
|
// const bindings exposed from setup can be skipped for patching but
|
||||||
// cannot be hoisted to module scope
|
// cannot be hoisted to module scope
|
||||||
if (isConst(bindingMetadata[node.content])) {
|
if (isConst(bindingMetadata[rawExp])) {
|
||||||
node.constType = ConstantTypes.CAN_SKIP_PATCH
|
node.constType = ConstantTypes.CAN_SKIP_PATCH
|
||||||
}
|
}
|
||||||
node.content = rewriteIdentifier(rawExp)
|
node.content = rewriteIdentifier(rawExp)
|
||||||
|
|
|
@ -37,7 +37,8 @@ import {
|
||||||
isTemplateNode,
|
isTemplateNode,
|
||||||
isSlotOutlet,
|
isSlotOutlet,
|
||||||
injectProp,
|
injectProp,
|
||||||
findDir
|
findDir,
|
||||||
|
forAliasRE
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import {
|
import {
|
||||||
RENDER_LIST,
|
RENDER_LIST,
|
||||||
|
@ -94,8 +95,8 @@ export const transformFor = createStructuralDirectiveTransform(
|
||||||
const fragmentFlag = isStableFragment
|
const fragmentFlag = isStableFragment
|
||||||
? PatchFlags.STABLE_FRAGMENT
|
? PatchFlags.STABLE_FRAGMENT
|
||||||
: keyProp
|
: keyProp
|
||||||
? PatchFlags.KEYED_FRAGMENT
|
? PatchFlags.KEYED_FRAGMENT
|
||||||
: PatchFlags.UNKEYED_FRAGMENT
|
: PatchFlags.UNKEYED_FRAGMENT
|
||||||
|
|
||||||
forNode.codegenNode = createVNodeCall(
|
forNode.codegenNode = createVNodeCall(
|
||||||
context,
|
context,
|
||||||
|
@ -140,10 +141,10 @@ export const transformFor = createStructuralDirectiveTransform(
|
||||||
const slotOutlet = isSlotOutlet(node)
|
const slotOutlet = isSlotOutlet(node)
|
||||||
? node
|
? node
|
||||||
: isTemplate &&
|
: isTemplate &&
|
||||||
node.children.length === 1 &&
|
node.children.length === 1 &&
|
||||||
isSlotOutlet(node.children[0])
|
isSlotOutlet(node.children[0])
|
||||||
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
|
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
|
||||||
: null
|
: null
|
||||||
|
|
||||||
if (slotOutlet) {
|
if (slotOutlet) {
|
||||||
// <slot v-for="..."> or <template v-for="..."><slot/></template>
|
// <slot v-for="..."> or <template v-for="..."><slot/></template>
|
||||||
|
@ -308,7 +309,6 @@ 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,
|
// 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.
|
// but those do not make sense in the first place, so this works in practice.
|
||||||
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
||||||
|
|
|
@ -349,8 +349,8 @@ export function buildSlots(
|
||||||
const slotFlag = hasDynamicSlots
|
const slotFlag = hasDynamicSlots
|
||||||
? SlotFlags.DYNAMIC
|
? SlotFlags.DYNAMIC
|
||||||
: hasForwardedSlots(node.children)
|
: hasForwardedSlots(node.children)
|
||||||
? SlotFlags.FORWARDED
|
? SlotFlags.FORWARDED
|
||||||
: SlotFlags.STABLE
|
: SlotFlags.STABLE
|
||||||
|
|
||||||
let slots = createObjectExpression(
|
let slots = createObjectExpression(
|
||||||
slotsProperties.concat(
|
slotsProperties.concat(
|
||||||
|
|
|
@ -519,3 +519,5 @@ export function getMemoedVNodeCall(node: BlockCodegenNode | MemoExpression) {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
||||||
|
|
|
@ -20,10 +20,7 @@ describe('stringify static html', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function repeat(code: string, n: number): string {
|
function repeat(code: string, n: number): string {
|
||||||
return new Array(n)
|
return code.repeat(n)
|
||||||
.fill(0)
|
|
||||||
.map(() => code)
|
|
||||||
.join('')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test('should bail on non-eligible static trees', () => {
|
test('should bail on non-eligible static trees', () => {
|
||||||
|
|
|
@ -137,6 +137,27 @@ describe('compiler: transform v-model', () => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should error on dynamic value binding alongside v-model', () => {
|
||||||
|
const onError = vi.fn()
|
||||||
|
transformWithModel(`<input v-model="test" :value="test" />`, {
|
||||||
|
onError
|
||||||
|
})
|
||||||
|
expect(onError).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
code: DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// #3596
|
||||||
|
test('should NOT error on static value binding alongside v-model', () => {
|
||||||
|
const onError = vi.fn()
|
||||||
|
transformWithModel(`<input v-model="test" value="test" />`, {
|
||||||
|
onError
|
||||||
|
})
|
||||||
|
expect(onError).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('modifiers', () => {
|
describe('modifiers', () => {
|
||||||
|
|
|
@ -272,7 +272,7 @@ describe('compiler-dom: transform v-on', () => {
|
||||||
// should not treat cached handler as dynamicProp, so it should have no
|
// should not treat cached handler as dynamicProp, so it should have no
|
||||||
// dynamicProps flags and only the hydration flag
|
// dynamicProps flags and only the hydration flag
|
||||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||||
genFlagText(PatchFlags.HYDRATE_EVENTS)
|
genFlagText(PatchFlags.NEED_HYDRATION)
|
||||||
)
|
)
|
||||||
expect(prop).toMatchObject({
|
expect(prop).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme",
|
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.4.0-alpha.1",
|
"@vue/shared": "workspace:*",
|
||||||
"@vue/compiler-core": "3.4.0-alpha.1"
|
"@vue/compiler-core": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,8 +153,8 @@ const isStringifiableAttr = (name: string, ns: DOMNamespaces) => {
|
||||||
(ns === DOMNamespaces.HTML
|
(ns === DOMNamespaces.HTML
|
||||||
? isKnownHtmlAttr(name)
|
? isKnownHtmlAttr(name)
|
||||||
: ns === DOMNamespaces.SVG
|
: ns === DOMNamespaces.SVG
|
||||||
? isKnownSvgAttr(name)
|
? isKnownSvgAttr(name)
|
||||||
: false) || dataAriaRE.test(name)
|
: false) || dataAriaRE.test(name)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,9 @@ import {
|
||||||
ElementTypes,
|
ElementTypes,
|
||||||
findProp,
|
findProp,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
hasDynamicKeyVBind
|
hasDynamicKeyVBind,
|
||||||
|
findDir,
|
||||||
|
isStaticArgOf
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
|
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
|
||||||
import {
|
import {
|
||||||
|
@ -32,8 +34,8 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkDuplicatedValue() {
|
function checkDuplicatedValue() {
|
||||||
const value = findProp(node, 'value')
|
const value = findDir(node, 'bind')
|
||||||
if (value) {
|
if (value && isStaticArgOf(value.arg, 'value')) {
|
||||||
context.onError(
|
context.onError(
|
||||||
createDOMCompilerError(
|
createDOMCompilerError(
|
||||||
DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
|
DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
|
||||||
|
|
|
@ -96,14 +96,14 @@ const transformClick = (key: ExpressionNode, event: string) => {
|
||||||
return isStaticClick
|
return isStaticClick
|
||||||
? createSimpleExpression(event, true)
|
? createSimpleExpression(event, true)
|
||||||
: key.type !== NodeTypes.SIMPLE_EXPRESSION
|
: key.type !== NodeTypes.SIMPLE_EXPRESSION
|
||||||
? createCompoundExpression([
|
? createCompoundExpression([
|
||||||
`(`,
|
`(`,
|
||||||
key,
|
key,
|
||||||
`) === "onClick" ? "${event}" : (`,
|
`) === "onClick" ? "${event}" : (`,
|
||||||
key,
|
key,
|
||||||
`)`
|
`)`
|
||||||
])
|
])
|
||||||
: key
|
: key
|
||||||
}
|
}
|
||||||
|
|
||||||
export const transformOn: DirectiveTransform = (dir, node, context) => {
|
export const transformOn: DirectiveTransform = (dir, node, context) => {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
**Note: as of 3.2.13+, this package is included as a dependency of the main `vue` package and can be accessed as `vue/compiler-sfc`. This means you no longer need to explicitly install this package and ensure its version match that of `vue`'s. Just use the main `vue/compiler-sfc` deep import instead.**
|
**Note: as of 3.2.13+, this package is included as a dependency of the main `vue` package and can be accessed as `vue/compiler-sfc`. This means you no longer need to explicitly install this package and ensure its version match that of `vue`'s. Just use the main `vue/compiler-sfc` deep import instead.**
|
||||||
|
|
||||||
This package contains lower level utilities that you can use if you are writing a plugin / transform for a bundler or module system that compiles Vue Single File Components (SFCs) into JavaScript. It is used in [vue-loader](https://github.com/vuejs/vue-loader), [rollup-plugin-vue](https://github.com/vuejs/rollup-plugin-vue) and [vite](https://github.com/vitejs/vite).
|
This package contains lower level utilities that you can use if you are writing a plugin / transform for a bundler or module system that compiles Vue Single File Components (SFCs) into JavaScript. It is used in [vue-loader](https://github.com/vuejs/vue-loader) and [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue).
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
|
@ -77,4 +77,4 @@ export default script
|
||||||
|
|
||||||
Options needed for these APIs can be passed via the query string.
|
Options needed for these APIs can be passed via the query string.
|
||||||
|
|
||||||
For detailed API references and options, check out the source type definitions. For actual usage of these APIs, check out [rollup-plugin-vue](https://github.com/vuejs/rollup-plugin-vue/tree/next) or [vue-loader](https://github.com/vuejs/vue-loader/tree/next).
|
For detailed API references and options, check out the source type definitions. For actual usage of these APIs, check out [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue) or [vue-loader](https://github.com/vuejs/vue-loader/tree/next).
|
||||||
|
|
|
@ -696,14 +696,14 @@ return { get vMyDir() { return vMyDir } }
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > dev mode import usage check > dynamic arguments 1`] = `
|
exports[`SFC compile <script setup> > dev mode import usage check > dynamic arguments 1`] = `
|
||||||
"import { defineComponent as _defineComponent } from 'vue'
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
import { FooBar, foo, bar, unused } from './x'
|
import { FooBar, foo, bar, unused, baz } from './x'
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
export default /*#__PURE__*/_defineComponent({
|
||||||
setup(__props, { expose: __expose }) {
|
setup(__props, { expose: __expose }) {
|
||||||
__expose();
|
__expose();
|
||||||
|
|
||||||
|
|
||||||
return { get FooBar() { return FooBar }, get foo() { return foo }, get bar() { return bar } }
|
return { get FooBar() { return FooBar }, get foo() { return foo }, get bar() { return bar }, get baz() { return baz } }
|
||||||
}
|
}
|
||||||
|
|
||||||
})"
|
})"
|
||||||
|
|
|
@ -376,18 +376,19 @@ describe('SFC compile <script setup>', () => {
|
||||||
test('dynamic arguments', () => {
|
test('dynamic arguments', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FooBar, foo, bar, unused } from './x'
|
import { FooBar, foo, bar, unused, baz } from './x'
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<FooBar #[foo.slotName] />
|
<FooBar #[foo.slotName] />
|
||||||
<FooBar #unused />
|
<FooBar #unused />
|
||||||
<div :[bar.attrName]="15"></div>
|
<div :[bar.attrName]="15"></div>
|
||||||
<div unused="unused"></div>
|
<div unused="unused"></div>
|
||||||
|
<div #[\`item:\${baz.key}\`]="{ value }"></div>
|
||||||
</template>
|
</template>
|
||||||
`)
|
`)
|
||||||
expect(content).toMatch(
|
expect(content).toMatch(
|
||||||
`return { get FooBar() { return FooBar }, get foo() { return foo }, ` +
|
`return { get FooBar() { return FooBar }, get foo() { return foo }, ` +
|
||||||
`get bar() { return bar } }`
|
`get bar() { return bar }, get baz() { return baz } }`
|
||||||
)
|
)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
})
|
})
|
||||||
|
|
|
@ -81,6 +81,24 @@ return { emit }
|
||||||
})"
|
})"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`defineEmits > w/ type (interface w/ extends) 1`] = `
|
||||||
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
|
interface Base { (e: 'foo'): void }
|
||||||
|
interface Emits extends Base { (e: 'bar'): void }
|
||||||
|
|
||||||
|
export default /*#__PURE__*/_defineComponent({
|
||||||
|
emits: [\\"bar\\", \\"foo\\"],
|
||||||
|
setup(__props, { expose: __expose, emit: __emit }) {
|
||||||
|
__expose();
|
||||||
|
|
||||||
|
const emit = __emit
|
||||||
|
|
||||||
|
return { emit }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`defineEmits > w/ type (interface) 1`] = `
|
exports[`defineEmits > w/ type (interface) 1`] = `
|
||||||
"import { defineComponent as _defineComponent } from 'vue'
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
interface Emits { (e: 'foo' | 'bar'): void }
|
interface Emits { (e: 'foo' | 'bar'): void }
|
||||||
|
|
|
@ -46,6 +46,51 @@ export default /*#__PURE__*/_defineComponent({
|
||||||
|
|
||||||
const { foo } = __props
|
const { foo } = __props
|
||||||
|
|
||||||
|
return { }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`defineProps > should escape names w/ special symbols 1`] = `
|
||||||
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default /*#__PURE__*/_defineComponent({
|
||||||
|
props: {
|
||||||
|
\\"spa ce\\": { type: null, required: true },
|
||||||
|
\\"exclamation!mark\\": { type: null, required: true },
|
||||||
|
\\"double\\\\\\"quote\\": { type: null, required: true },
|
||||||
|
\\"hash#tag\\": { type: null, required: true },
|
||||||
|
\\"dollar$sign\\": { type: null, required: true },
|
||||||
|
\\"percentage%sign\\": { type: null, required: true },
|
||||||
|
\\"amper&sand\\": { type: null, required: true },
|
||||||
|
\\"single'quote\\": { type: null, required: true },
|
||||||
|
\\"round(brack)ets\\": { type: null, required: true },
|
||||||
|
\\"aste*risk\\": { type: null, required: true },
|
||||||
|
\\"pl+us\\": { type: null, required: true },
|
||||||
|
\\"com,ma\\": { type: null, required: true },
|
||||||
|
\\"do.t\\": { type: null, required: true },
|
||||||
|
\\"sla/sh\\": { type: null, required: true },
|
||||||
|
\\"co:lon\\": { type: null, required: true },
|
||||||
|
\\"semi;colon\\": { type: null, required: true },
|
||||||
|
\\"angle<brack>ets\\": { type: null, required: true },
|
||||||
|
\\"equal=sign\\": { type: null, required: true },
|
||||||
|
\\"question?mark\\": { type: null, required: true },
|
||||||
|
\\"at@sign\\": { type: null, required: true },
|
||||||
|
\\"square[brack]ets\\": { type: null, required: true },
|
||||||
|
\\"back\\\\\\\\slash\\": { type: null, required: true },
|
||||||
|
\\"ca^ret\\": { type: null, required: true },
|
||||||
|
\\"back\`tick\\": { type: null, required: true },
|
||||||
|
\\"curly{bra}ces\\": { type: null, required: true },
|
||||||
|
\\"pi|pe\\": { type: null, required: true },
|
||||||
|
\\"til~de\\": { type: null, required: true },
|
||||||
|
\\"da-sh\\": { type: null, required: true }
|
||||||
|
},
|
||||||
|
setup(__props: any, { expose: __expose }) {
|
||||||
|
__expose();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { }
|
return { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,6 +277,7 @@ export default /*#__PURE__*/_defineComponent({
|
||||||
alias: { type: Array, required: true },
|
alias: { type: Array, required: true },
|
||||||
method: { type: Function, required: true },
|
method: { type: Function, required: true },
|
||||||
symbol: { type: Symbol, required: true },
|
symbol: { type: Symbol, required: true },
|
||||||
|
error: { type: Error, required: true },
|
||||||
extract: { type: Number, required: true },
|
extract: { type: Number, required: true },
|
||||||
exclude: { type: [Number, Boolean], required: true },
|
exclude: { type: [Number, Boolean], required: true },
|
||||||
uppercase: { type: String, required: true },
|
uppercase: { type: String, required: true },
|
||||||
|
|
|
@ -80,6 +80,18 @@ const emit = defineEmits(['a', 'b'])
|
||||||
expect(content).toMatch(`emits: ["foo", "bar"]`)
|
expect(content).toMatch(`emits: ["foo", "bar"]`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('w/ type (interface w/ extends)', () => {
|
||||||
|
const { content } = compile(`
|
||||||
|
<script setup lang="ts">
|
||||||
|
interface Base { (e: 'foo'): void }
|
||||||
|
interface Emits extends Base { (e: 'bar'): void }
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(`emits: ["bar", "foo"]`)
|
||||||
|
})
|
||||||
|
|
||||||
test('w/ type (exported interface)', () => {
|
test('w/ type (exported interface)', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
|
@ -97,6 +97,7 @@ const props = defineProps({ foo: String })
|
||||||
alias: Alias
|
alias: Alias
|
||||||
method(): void
|
method(): void
|
||||||
symbol: symbol
|
symbol: symbol
|
||||||
|
error: Error
|
||||||
extract: Extract<1 | 2 | boolean, 2>
|
extract: Extract<1 | 2 | boolean, 2>
|
||||||
exclude: Exclude<1 | 2 | boolean, 2>
|
exclude: Exclude<1 | 2 | boolean, 2>
|
||||||
uppercase: Uppercase<'foo'>
|
uppercase: Uppercase<'foo'>
|
||||||
|
@ -143,6 +144,7 @@ const props = defineProps({ foo: String })
|
||||||
expect(content).toMatch(`alias: { type: Array, required: true }`)
|
expect(content).toMatch(`alias: { type: Array, required: true }`)
|
||||||
expect(content).toMatch(`method: { type: Function, required: true }`)
|
expect(content).toMatch(`method: { type: Function, required: true }`)
|
||||||
expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
|
expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
|
||||||
|
expect(content).toMatch(`error: { type: Error, required: true }`)
|
||||||
expect(content).toMatch(
|
expect(content).toMatch(
|
||||||
`objectOrFn: { type: [Function, Object], required: true },`
|
`objectOrFn: { type: [Function, Object], required: true },`
|
||||||
)
|
)
|
||||||
|
@ -198,6 +200,7 @@ const props = defineProps({ foo: String })
|
||||||
alias: BindingTypes.PROPS,
|
alias: BindingTypes.PROPS,
|
||||||
method: BindingTypes.PROPS,
|
method: BindingTypes.PROPS,
|
||||||
symbol: BindingTypes.PROPS,
|
symbol: BindingTypes.PROPS,
|
||||||
|
error: BindingTypes.PROPS,
|
||||||
objectOrFn: BindingTypes.PROPS,
|
objectOrFn: BindingTypes.PROPS,
|
||||||
extract: BindingTypes.PROPS,
|
extract: BindingTypes.PROPS,
|
||||||
exclude: BindingTypes.PROPS,
|
exclude: BindingTypes.PROPS,
|
||||||
|
@ -608,4 +611,103 @@ const props = defineProps({ foo: String })
|
||||||
}).toThrow(`cannot accept both type and non-type arguments`)
|
}).toThrow(`cannot accept both type and non-type arguments`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should escape names w/ special symbols', () => {
|
||||||
|
const { content, bindings } = compile(`
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
'spa ce': unknown
|
||||||
|
'exclamation!mark': unknown
|
||||||
|
'double"quote': unknown
|
||||||
|
'hash#tag': unknown
|
||||||
|
'dollar$sign': unknown
|
||||||
|
'percentage%sign': unknown
|
||||||
|
'amper&sand': unknown
|
||||||
|
"single'quote": unknown
|
||||||
|
'round(brack)ets': unknown
|
||||||
|
'aste*risk': unknown
|
||||||
|
'pl+us': unknown
|
||||||
|
'com,ma': unknown
|
||||||
|
'do.t': unknown
|
||||||
|
'sla/sh': unknown
|
||||||
|
'co:lon': unknown
|
||||||
|
'semi;colon': unknown
|
||||||
|
'angle<brack>ets': unknown
|
||||||
|
'equal=sign': unknown
|
||||||
|
'question?mark': unknown
|
||||||
|
'at@sign': unknown
|
||||||
|
'square[brack]ets': unknown
|
||||||
|
'back\\\\slash': unknown
|
||||||
|
'ca^ret': unknown
|
||||||
|
'back\`tick': unknown
|
||||||
|
'curly{bra}ces': unknown
|
||||||
|
'pi|pe': unknown
|
||||||
|
'til~de': unknown
|
||||||
|
'da-sh': unknown
|
||||||
|
}>()
|
||||||
|
</script>`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(`"spa ce": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(
|
||||||
|
`"exclamation!mark": { type: null, required: true }`
|
||||||
|
)
|
||||||
|
expect(content).toMatch(`"double\\"quote": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"hash#tag": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"dollar$sign": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"percentage%sign": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"amper&sand": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"single'quote": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"round(brack)ets": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"aste*risk": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"pl+us": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"com,ma": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"do.t": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"sla/sh": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"co:lon": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"semi;colon": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"angle<brack>ets": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"equal=sign": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"question?mark": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"at@sign": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(
|
||||||
|
`"square[brack]ets": { type: null, required: true }`
|
||||||
|
)
|
||||||
|
expect(content).toMatch(`"back\\\\slash": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"ca^ret": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"back\`tick": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"curly{bra}ces": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"pi|pe": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"til~de": { type: null, required: true }`)
|
||||||
|
expect(content).toMatch(`"da-sh": { type: null, required: true }`)
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
'spa ce': BindingTypes.PROPS,
|
||||||
|
'exclamation!mark': BindingTypes.PROPS,
|
||||||
|
'double"quote': BindingTypes.PROPS,
|
||||||
|
'hash#tag': BindingTypes.PROPS,
|
||||||
|
dollar$sign: BindingTypes.PROPS,
|
||||||
|
'percentage%sign': BindingTypes.PROPS,
|
||||||
|
'amper&sand': BindingTypes.PROPS,
|
||||||
|
"single'quote": BindingTypes.PROPS,
|
||||||
|
'round(brack)ets': BindingTypes.PROPS,
|
||||||
|
'aste*risk': BindingTypes.PROPS,
|
||||||
|
'pl+us': BindingTypes.PROPS,
|
||||||
|
'com,ma': BindingTypes.PROPS,
|
||||||
|
'do.t': BindingTypes.PROPS,
|
||||||
|
'sla/sh': BindingTypes.PROPS,
|
||||||
|
'co:lon': BindingTypes.PROPS,
|
||||||
|
'semi;colon': BindingTypes.PROPS,
|
||||||
|
'angle<brack>ets': BindingTypes.PROPS,
|
||||||
|
'equal=sign': BindingTypes.PROPS,
|
||||||
|
'question?mark': BindingTypes.PROPS,
|
||||||
|
'at@sign': BindingTypes.PROPS,
|
||||||
|
'square[brack]ets': BindingTypes.PROPS,
|
||||||
|
'back\\slash': BindingTypes.PROPS,
|
||||||
|
'ca^ret': BindingTypes.PROPS,
|
||||||
|
'back`tick': BindingTypes.PROPS,
|
||||||
|
'curly{bra}ces': BindingTypes.PROPS,
|
||||||
|
'pi|pe': BindingTypes.PROPS,
|
||||||
|
'til~de': BindingTypes.PROPS,
|
||||||
|
'da-sh': BindingTypes.PROPS
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -481,25 +481,28 @@ describe('resolveType', () => {
|
||||||
|
|
||||||
test.runIf(process.platform === 'win32')('relative ts on Windows', () => {
|
test.runIf(process.platform === 'win32')('relative ts on Windows', () => {
|
||||||
const files = {
|
const files = {
|
||||||
'C:\\Test\\foo.ts': 'export type P = { foo: number }',
|
'C:\\Test\\FolderA\\foo.ts': 'export type P = { foo: number }',
|
||||||
'C:\\Test\\bar.d.ts':
|
'C:\\Test\\FolderA\\bar.d.ts':
|
||||||
'type X = { bar: string }; export { X as Y };' +
|
'type X = { bar: string }; export { X as Y };' +
|
||||||
// verify that we can parse syntax that is only valid in d.ts
|
// verify that we can parse syntax that is only valid in d.ts
|
||||||
'export const baz: boolean'
|
'export const baz: boolean',
|
||||||
|
'C:\\Test\\FolderB\\buz.ts': 'export type Z = { buz: string }'
|
||||||
}
|
}
|
||||||
const { props, deps } = resolve(
|
const { props, deps } = resolve(
|
||||||
`
|
`
|
||||||
import { P } from './foo'
|
import { P } from './foo'
|
||||||
import { Y as PP } from './bar'
|
import { Y as PP } from './bar'
|
||||||
defineProps<P & PP>()
|
import { Z as PPP } from '../FolderB/buz'
|
||||||
|
defineProps<P & PP & PPP>()
|
||||||
`,
|
`,
|
||||||
files,
|
files,
|
||||||
{},
|
{},
|
||||||
'C:\\Test\\Test.vue'
|
'C:\\Test\\FolderA\\Test.vue'
|
||||||
)
|
)
|
||||||
expect(props).toStrictEqual({
|
expect(props).toStrictEqual({
|
||||||
foo: ['Number'],
|
foo: ['Number'],
|
||||||
bar: ['String']
|
bar: ['String'],
|
||||||
|
buz: ['String']
|
||||||
})
|
})
|
||||||
expect(deps && [...deps].map(normalize)).toStrictEqual(
|
expect(deps && [...deps].map(normalize)).toStrictEqual(
|
||||||
Object.keys(files).map(normalize)
|
Object.keys(files).map(normalize)
|
||||||
|
|
|
@ -32,27 +32,27 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.23.0",
|
"@babel/parser": "^7.23.3",
|
||||||
"@vue/compiler-core": "3.4.0-alpha.1",
|
"@vue/compiler-core": "workspace:*",
|
||||||
"@vue/compiler-dom": "3.4.0-alpha.1",
|
"@vue/compiler-dom": "workspace:*",
|
||||||
"@vue/compiler-ssr": "3.4.0-alpha.1",
|
"@vue/compiler-ssr": "workspace:*",
|
||||||
"@vue/reactivity-transform": "3.4.0-alpha.1",
|
"@vue/reactivity-transform": "workspace:*",
|
||||||
"@vue/shared": "3.4.0-alpha.1",
|
"@vue/shared": "workspace:*",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.30.5",
|
"magic-string": "^0.30.5",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.23.0",
|
"@babel/types": "^7.23.3",
|
||||||
"@vue/consolidate": "^0.17.3",
|
"@vue/consolidate": "^0.17.3",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"lru-cache": "^10.0.1",
|
"lru-cache": "^10.0.3",
|
||||||
"merge-source-map": "^1.1.0",
|
"merge-source-map": "^1.1.0",
|
||||||
"minimatch": "^9.0.3",
|
"minimatch": "^9.0.3",
|
||||||
"postcss-modules": "^4.3.1",
|
"postcss-modules": "^4.3.1",
|
||||||
"postcss-selector-parser": "^6.0.13",
|
"postcss-selector-parser": "^6.0.13",
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.2",
|
||||||
"sass": "^1.69.4"
|
"sass": "^1.69.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1172,8 +1172,8 @@ function walkObjectPattern(
|
||||||
const type = isDefineCall
|
const type = isDefineCall
|
||||||
? BindingTypes.SETUP_CONST
|
? BindingTypes.SETUP_CONST
|
||||||
: isConst
|
: isConst
|
||||||
? BindingTypes.SETUP_MAYBE_REF
|
? BindingTypes.SETUP_MAYBE_REF
|
||||||
: BindingTypes.SETUP_LET
|
: BindingTypes.SETUP_LET
|
||||||
registerBinding(bindings, p.key, type)
|
registerBinding(bindings, p.key, type)
|
||||||
} else {
|
} else {
|
||||||
walkPattern(p.value, bindings, isConst, isDefineCall)
|
walkPattern(p.value, bindings, isConst, isDefineCall)
|
||||||
|
@ -1208,8 +1208,8 @@ function walkPattern(
|
||||||
const type = isDefineCall
|
const type = isDefineCall
|
||||||
? BindingTypes.SETUP_CONST
|
? BindingTypes.SETUP_CONST
|
||||||
: isConst
|
: isConst
|
||||||
? BindingTypes.SETUP_MAYBE_REF
|
? BindingTypes.SETUP_MAYBE_REF
|
||||||
: BindingTypes.SETUP_LET
|
: BindingTypes.SETUP_LET
|
||||||
registerBinding(bindings, node, type)
|
registerBinding(bindings, node, type)
|
||||||
} else if (node.type === 'RestElement') {
|
} else if (node.type === 'RestElement') {
|
||||||
// argument can only be identifier when destructuring
|
// argument can only be identifier when destructuring
|
||||||
|
@ -1224,8 +1224,8 @@ function walkPattern(
|
||||||
const type = isDefineCall
|
const type = isDefineCall
|
||||||
? BindingTypes.SETUP_CONST
|
? BindingTypes.SETUP_CONST
|
||||||
: isConst
|
: isConst
|
||||||
? BindingTypes.SETUP_MAYBE_REF
|
? BindingTypes.SETUP_MAYBE_REF
|
||||||
: BindingTypes.SETUP_LET
|
: BindingTypes.SETUP_LET
|
||||||
registerBinding(bindings, node.left, type)
|
registerBinding(bindings, node.left, type)
|
||||||
} else {
|
} else {
|
||||||
walkPattern(node.left, bindings, isConst)
|
walkPattern(node.left, bindings, isConst)
|
||||||
|
|
|
@ -124,8 +124,8 @@ export function compileTemplate(
|
||||||
? preprocessCustomRequire
|
? preprocessCustomRequire
|
||||||
? preprocessCustomRequire(preprocessLang)
|
? preprocessCustomRequire(preprocessLang)
|
||||||
: __ESM_BROWSER__
|
: __ESM_BROWSER__
|
||||||
? undefined
|
? undefined
|
||||||
: consolidate[preprocessLang as keyof typeof consolidate]
|
: consolidate[preprocessLang as keyof typeof consolidate]
|
||||||
: false
|
: false
|
||||||
if (preprocessor) {
|
if (preprocessor) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
export const version = __VERSION__
|
export const version = __VERSION__
|
||||||
|
|
||||||
// API
|
// API
|
||||||
export { parse, parseCache } from './parse'
|
export { parse } from './parse'
|
||||||
export { compileTemplate } from './compileTemplate'
|
export { compileTemplate } from './compileTemplate'
|
||||||
export { compileStyle, compileStyleAsync } from './compileStyle'
|
export { compileStyle, compileStyleAsync } from './compileStyle'
|
||||||
export { compileScript } from './compileScript'
|
export { compileScript } from './compileScript'
|
||||||
export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault'
|
export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault'
|
||||||
export { resolveTypeElements, inferRuntimeType } from './script/resolveType'
|
export { resolveTypeElements, inferRuntimeType } from './script/resolveType'
|
||||||
|
|
||||||
|
import { SFCParseResult, parseCache as _parseCache } from './parse'
|
||||||
|
// #9521 export parseCache as a simple map to avoid exposing LRU types
|
||||||
|
export const parseCache = _parseCache as Map<string, SFCParseResult>
|
||||||
|
|
||||||
// TODO remove in 3.4
|
// TODO remove in 3.4
|
||||||
export {
|
export {
|
||||||
shouldTransform as shouldTransformRef,
|
shouldTransform as shouldTransformRef,
|
||||||
|
|
|
@ -164,7 +164,7 @@ export function resolveParserPlugins(
|
||||||
}
|
}
|
||||||
if (lang === 'ts' || lang === 'tsx') {
|
if (lang === 'ts' || lang === 'tsx') {
|
||||||
plugins.push(['typescript', { dts }])
|
plugins.push(['typescript', { dts }])
|
||||||
if (!plugins.includes('decorators')) {
|
if (!userPlugins || !userPlugins.includes('decorators')) {
|
||||||
plugins.push('decorators-legacy')
|
plugins.push('decorators-legacy')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {
|
||||||
isCallOf,
|
isCallOf,
|
||||||
unwrapTSNode,
|
unwrapTSNode,
|
||||||
toRuntimeTypeString,
|
toRuntimeTypeString,
|
||||||
getEscapedKey
|
getEscapedPropName
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import { genModelProps } from './defineModel'
|
import { genModelProps } from './defineModel'
|
||||||
import { getObjectOrArrayExpressionKeys } from './analyzeScriptBindings'
|
import { getObjectOrArrayExpressionKeys } from './analyzeScriptBindings'
|
||||||
|
@ -139,7 +139,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
|
||||||
const defaults: string[] = []
|
const defaults: string[] = []
|
||||||
for (const key in ctx.propsDestructuredBindings) {
|
for (const key in ctx.propsDestructuredBindings) {
|
||||||
const d = genDestructuredDefaultValue(ctx, key)
|
const d = genDestructuredDefaultValue(ctx, key)
|
||||||
const finalKey = getEscapedKey(key)
|
const finalKey = getEscapedPropName(key)
|
||||||
if (d)
|
if (d)
|
||||||
defaults.push(
|
defaults.push(
|
||||||
`${finalKey}: ${d.valueString}${
|
`${finalKey}: ${d.valueString}${
|
||||||
|
@ -257,7 +257,7 @@ function genRuntimePropFromType(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalKey = getEscapedKey(key)
|
const finalKey = getEscapedPropName(key)
|
||||||
if (!ctx.options.isProd) {
|
if (!ctx.options.isProd) {
|
||||||
return `${finalKey}: { ${concatStrings([
|
return `${finalKey}: { ${concatStrings([
|
||||||
`type: ${toRuntimeTypeString(type)}`,
|
`type: ${toRuntimeTypeString(type)}`,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
createRoot,
|
createRoot,
|
||||||
|
forAliasRE,
|
||||||
parserOptions,
|
parserOptions,
|
||||||
transform,
|
transform,
|
||||||
walkIdentifiers
|
walkIdentifiers
|
||||||
|
@ -50,12 +51,14 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
||||||
if (!isBuiltInDirective(prop.name)) {
|
if (!isBuiltInDirective(prop.name)) {
|
||||||
code += `,v${capitalize(camelize(prop.name))}`
|
code += `,v${capitalize(camelize(prop.name))}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// process dynamic directive arguments
|
||||||
if (prop.arg && !(prop.arg as SimpleExpressionNode).isStatic) {
|
if (prop.arg && !(prop.arg as SimpleExpressionNode).isStatic) {
|
||||||
code += `,${processExp(
|
code += `,${stripStrings(
|
||||||
(prop.arg as SimpleExpressionNode).content,
|
(prop.arg as SimpleExpressionNode).content
|
||||||
prop.name
|
|
||||||
)}`
|
)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prop.exp) {
|
if (prop.exp) {
|
||||||
code += `,${processExp(
|
code += `,${processExp(
|
||||||
(prop.exp as SimpleExpressionNode).content,
|
(prop.exp as SimpleExpressionNode).content,
|
||||||
|
@ -85,8 +88,6 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
|
||||||
|
|
||||||
function processExp(exp: string, dir?: string): string {
|
function processExp(exp: string, dir?: string): string {
|
||||||
if (/ as\s+\w|<.*>|:/.test(exp)) {
|
if (/ as\s+\w|<.*>|:/.test(exp)) {
|
||||||
if (dir === 'slot') {
|
if (dir === 'slot') {
|
||||||
|
|
|
@ -39,8 +39,9 @@ import { parse as babelParse } from '@babel/parser'
|
||||||
import { parse } from '../parse'
|
import { parse } from '../parse'
|
||||||
import { createCache } from '../cache'
|
import { createCache } from '../cache'
|
||||||
import type TS from 'typescript'
|
import type TS from 'typescript'
|
||||||
import { extname, dirname } from 'path'
|
import { extname, dirname, join } from 'path'
|
||||||
import { minimatch as isMatch } from 'minimatch'
|
import { minimatch as isMatch } from 'minimatch'
|
||||||
|
import * as process from 'process'
|
||||||
|
|
||||||
export type SimpleTypeResolveOptions = Partial<
|
export type SimpleTypeResolveOptions = Partial<
|
||||||
Pick<
|
Pick<
|
||||||
|
@ -356,12 +357,15 @@ function resolveInterfaceMembers(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { props } = resolveTypeElements(ctx, ext, scope)
|
const { props, calls } = resolveTypeElements(ctx, ext, scope)
|
||||||
for (const key in props) {
|
for (const key in props) {
|
||||||
if (!hasOwn(base.props, key)) {
|
if (!hasOwn(base.props, key)) {
|
||||||
base.props[key] = props[key]
|
base.props[key] = props[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (calls) {
|
||||||
|
;(base.calls || (base.calls = [])).push(...calls)
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ctx.error(
|
ctx.error(
|
||||||
`Failed to resolve extends base type.\nIf this previously worked in 3.2, ` +
|
`Failed to resolve extends base type.\nIf this previously worked in 3.2, ` +
|
||||||
|
@ -653,8 +657,8 @@ function innerResolveTypeReference(
|
||||||
? scope.exportedDeclares
|
? scope.exportedDeclares
|
||||||
: scope.declares
|
: scope.declares
|
||||||
: onlyExported
|
: onlyExported
|
||||||
? scope.exportedTypes
|
? scope.exportedTypes
|
||||||
: scope.types
|
: scope.types
|
||||||
if (lookupSource[name]) {
|
if (lookupSource[name]) {
|
||||||
return lookupSource[name]
|
return lookupSource[name]
|
||||||
} else {
|
} else {
|
||||||
|
@ -697,10 +701,10 @@ function getReferenceName(node: ReferenceTypes): string | string[] {
|
||||||
node.type === 'TSTypeReference'
|
node.type === 'TSTypeReference'
|
||||||
? node.typeName
|
? node.typeName
|
||||||
: node.type === 'TSExpressionWithTypeArguments'
|
: node.type === 'TSExpressionWithTypeArguments'
|
||||||
? node.expression
|
? node.expression
|
||||||
: node.type === 'TSImportType'
|
: node.type === 'TSImportType'
|
||||||
? node.qualifier
|
? node.qualifier
|
||||||
: node.exprName
|
: node.exprName
|
||||||
if (ref?.type === 'Identifier') {
|
if (ref?.type === 'Identifier') {
|
||||||
return ref.name
|
return ref.name
|
||||||
} else if (ref?.type === 'TSQualifiedName') {
|
} else if (ref?.type === 'TSQualifiedName') {
|
||||||
|
@ -798,7 +802,12 @@ function importSourceToScope(
|
||||||
|
|
||||||
let resolved: string | undefined = scope.resolvedImportSources[source]
|
let resolved: string | undefined = scope.resolvedImportSources[source]
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
if (source.startsWith('.')) {
|
if (source.startsWith('..')) {
|
||||||
|
const osSpecificJoinFn = process.platform === 'win32' ? join : joinPaths
|
||||||
|
|
||||||
|
const filename = osSpecificJoinFn(dirname(scope.filename), source)
|
||||||
|
resolved = resolveExt(filename, fs)
|
||||||
|
} else if (source.startsWith('.')) {
|
||||||
// relative import - fast path
|
// relative import - fast path
|
||||||
const filename = joinPaths(dirname(scope.filename), source)
|
const filename = joinPaths(dirname(scope.filename), source)
|
||||||
resolved = resolveExt(filename, fs)
|
resolved = resolveExt(filename, fs)
|
||||||
|
@ -1069,8 +1078,8 @@ function ctxToScope(ctx: TypeResolveContext): TypeScope {
|
||||||
'ast' in ctx
|
'ast' in ctx
|
||||||
? ctx.ast
|
? ctx.ast
|
||||||
: ctx.scriptAst
|
: ctx.scriptAst
|
||||||
? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body]
|
? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body]
|
||||||
: ctx.scriptSetupAst!.body
|
: ctx.scriptSetupAst!.body
|
||||||
|
|
||||||
const scope = new TypeScope(
|
const scope = new TypeScope(
|
||||||
ctx.filename,
|
ctx.filename,
|
||||||
|
@ -1413,6 +1422,7 @@ export function inferRuntimeType(
|
||||||
case 'WeakMap':
|
case 'WeakMap':
|
||||||
case 'Date':
|
case 'Date':
|
||||||
case 'Promise':
|
case 'Promise':
|
||||||
|
case 'Error':
|
||||||
return [node.typeName.name]
|
return [node.typeName.name]
|
||||||
|
|
||||||
// TS built-in utility types
|
// TS built-in utility types
|
||||||
|
|
|
@ -76,8 +76,8 @@ export function getId(node: Expression) {
|
||||||
return node.type === 'Identifier'
|
return node.type === 'Identifier'
|
||||||
? node.name
|
? node.name
|
||||||
: node.type === 'StringLiteral'
|
: node.type === 'StringLiteral'
|
||||||
? node.value
|
? node.value
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
const identity = (str: string) => str
|
const identity = (str: string) => str
|
||||||
|
@ -113,8 +113,14 @@ export const joinPaths = (path.posix || path).join
|
||||||
* key may contain symbols
|
* key may contain symbols
|
||||||
* e.g. onUpdate:modelValue -> "onUpdate:modelValue"
|
* e.g. onUpdate:modelValue -> "onUpdate:modelValue"
|
||||||
*/
|
*/
|
||||||
export const escapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
|
export const propNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~\-]/
|
||||||
|
|
||||||
export function getEscapedKey(key: string) {
|
export function getEscapedPropName(key: string) {
|
||||||
return escapeSymbolsRE.test(key) ? JSON.stringify(key) : key
|
return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
|
||||||
|
|
||||||
|
export function getEscapedCssVarName(key: string) {
|
||||||
|
return key.replace(cssVarNameEscapeSymbolsRE, s => `\\${s}`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
BindingMetadata
|
BindingMetadata
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { SFCDescriptor } from '../parse'
|
import { SFCDescriptor } from '../parse'
|
||||||
import { escapeSymbolsRE } from '../script/utils'
|
import { getEscapedCssVarName } from '../script/utils'
|
||||||
import { PluginCreator } from 'postcss'
|
import { PluginCreator } from 'postcss'
|
||||||
import hash from 'hash-sum'
|
import hash from 'hash-sum'
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ function genVarName(id: string, raw: string, isProd: boolean): string {
|
||||||
return hash(id + raw)
|
return hash(id + raw)
|
||||||
} else {
|
} else {
|
||||||
// escape ASCII Punctuation & Symbols
|
// escape ASCII Punctuation & Symbols
|
||||||
return `${id}-${raw.replace(escapeSymbolsRE, s => `\\${s}`)}`
|
return `${id}-${getEscapedCssVarName(raw)}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme",
|
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.4.0-alpha.1",
|
"@vue/shared": "workspace:*",
|
||||||
"@vue/compiler-dom": "3.4.0-alpha.1"
|
"@vue/compiler-dom": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ export function compile(
|
||||||
// reusing core v-bind
|
// reusing core v-bind
|
||||||
bind: transformBind,
|
bind: transformBind,
|
||||||
on: transformOn,
|
on: transformOn,
|
||||||
// model and show has dedicated SSR handling
|
// model and show have dedicated SSR handling
|
||||||
model: ssrTransformModel,
|
model: ssrTransformModel,
|
||||||
show: ssrTransformShow,
|
show: ssrTransformShow,
|
||||||
// the following are ignored during SSR
|
// the following are ignored during SSR
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/dts-built-test",
|
"name": "@vue/dts-built-test",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
"types": "dist/dts-built-test.d.ts",
|
"types": "dist/dts-built-test.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "workspace:*",
|
"@vue/shared": "workspace:*",
|
||||||
"@vue/reactivity": "workspace:*",
|
"@vue/reactivity": "workspace:*",
|
||||||
"vue": "workspace:*"
|
"vue": "workspace:*"
|
||||||
},
|
}
|
||||||
"version": "3.4.0-alpha.1"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1472,6 +1472,31 @@ describe('slots', () => {
|
||||||
expectType<Slots | undefined>(new comp2().$slots)
|
expectType<Slots | undefined>(new comp2().$slots)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #5885
|
||||||
|
describe('should work when props type is incompatible with setup returned type ', () => {
|
||||||
|
type SizeType = 'small' | 'big'
|
||||||
|
const Comp = defineComponent({
|
||||||
|
props: {
|
||||||
|
size: {
|
||||||
|
type: String as PropType<SizeType>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
expectType<SizeType>(props.size)
|
||||||
|
return {
|
||||||
|
size: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
type CompInstance = InstanceType<typeof Comp>
|
||||||
|
|
||||||
|
const CompA = {} as CompInstance
|
||||||
|
expectType<ComponentPublicInstance>(CompA)
|
||||||
|
expectType<number>(CompA.size)
|
||||||
|
expectType<SizeType>(CompA.$props.size)
|
||||||
|
})
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DefineComponent,
|
DefineComponent,
|
||||||
ComponentOptionsMixin,
|
ComponentOptionsMixin,
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { defineCustomElement } from 'vue'
|
import {
|
||||||
import { expectType, describe } from './utils'
|
defineCustomElement,
|
||||||
|
defineComponent,
|
||||||
|
type VueElementConstructor
|
||||||
|
} from 'vue'
|
||||||
|
import { expectType, describe, test } from './utils'
|
||||||
|
|
||||||
describe('inject', () => {
|
describe('inject', () => {
|
||||||
// with object inject
|
// with object inject
|
||||||
|
@ -62,3 +66,20 @@ describe('inject', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('defineCustomElement using defineComponent return type', () => {
|
||||||
|
test('with emits', () => {
|
||||||
|
const Comp1Vue = defineComponent({
|
||||||
|
props: {
|
||||||
|
a: String
|
||||||
|
},
|
||||||
|
emits: {
|
||||||
|
click: () => true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const Comp = defineCustomElement(Comp1Vue)
|
||||||
|
expectType<VueElementConstructor>(Comp)
|
||||||
|
|
||||||
|
expectType<string | undefined>(new Comp().a)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "dts-test",
|
"name": "dts-test",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "workspace:*",
|
"vue": "workspace:*",
|
||||||
"@vue/dts-built-test": "workspace:*"
|
"@vue/dts-built-test": "workspace:*"
|
||||||
},
|
}
|
||||||
"version": "3.4.0-alpha.1"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,10 @@ import {
|
||||||
MaybeRef,
|
MaybeRef,
|
||||||
MaybeRefOrGetter,
|
MaybeRefOrGetter,
|
||||||
ComputedRef,
|
ComputedRef,
|
||||||
computed
|
computed,
|
||||||
|
ShallowRef
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { expectType, describe } from './utils'
|
import { expectType, describe, IsUnion } from './utils'
|
||||||
|
|
||||||
function plainType(arg: number | Ref<number>) {
|
function plainType(arg: number | Ref<number>) {
|
||||||
// ref coercing
|
// ref coercing
|
||||||
|
@ -174,6 +175,27 @@ if (refStatus.value === 'initial') {
|
||||||
refStatus.value = 'invalidating'
|
refStatus.value = 'invalidating'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const shallow = shallowRef(1)
|
||||||
|
expectType<Ref<number>>(shallow)
|
||||||
|
expectType<ShallowRef<number>>(shallow)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
//#7852
|
||||||
|
type Steps = { step: '1' } | { step: '2' }
|
||||||
|
const shallowUnionGenParam = shallowRef<Steps>({ step: '1' })
|
||||||
|
const shallowUnionAsCast = shallowRef({ step: '1' } as Steps)
|
||||||
|
|
||||||
|
expectType<IsUnion<typeof shallowUnionGenParam>>(false)
|
||||||
|
expectType<IsUnion<typeof shallowUnionAsCast>>(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('shallowRef with generic', <T>() => {
|
||||||
|
const r = ref({}) as MaybeRef<T>
|
||||||
|
expectType<ShallowRef<T> | Ref<T>>(shallowRef(r))
|
||||||
|
})
|
||||||
|
|
||||||
// proxyRefs: should return `reactive` directly
|
// proxyRefs: should return `reactive` directly
|
||||||
const r1 = reactive({
|
const r1 = reactive({
|
||||||
k: 'v'
|
k: 'v'
|
||||||
|
|
|
@ -8,7 +8,8 @@ import {
|
||||||
defineSlots,
|
defineSlots,
|
||||||
VNode,
|
VNode,
|
||||||
Ref,
|
Ref,
|
||||||
defineModel
|
defineModel,
|
||||||
|
toRefs
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { describe, expectType } from './utils'
|
import { describe, expectType } from './utils'
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
|
@ -20,6 +21,7 @@ describe('defineProps w/ type declaration', () => {
|
||||||
foo: string
|
foo: string
|
||||||
bool?: boolean
|
bool?: boolean
|
||||||
boolAndUndefined: boolean | undefined
|
boolAndUndefined: boolean | undefined
|
||||||
|
file?: File | File[]
|
||||||
}>()
|
}>()
|
||||||
// explicitly declared type should be refined
|
// explicitly declared type should be refined
|
||||||
expectType<string>(props.foo)
|
expectType<string>(props.foo)
|
||||||
|
@ -108,6 +110,7 @@ describe('defineProps w/ generic type declaration + withDefaults', <T extends
|
||||||
defineProps<{
|
defineProps<{
|
||||||
n?: number
|
n?: number
|
||||||
bool?: boolean
|
bool?: boolean
|
||||||
|
s?: string
|
||||||
|
|
||||||
generic1?: T[] | { x: T }
|
generic1?: T[] | { x: T }
|
||||||
generic2?: { x: T }
|
generic2?: { x: T }
|
||||||
|
@ -126,6 +129,10 @@ describe('defineProps w/ generic type declaration + withDefaults', <T extends
|
||||||
)
|
)
|
||||||
|
|
||||||
res.n + 1
|
res.n + 1
|
||||||
|
// @ts-expect-error should be readonly
|
||||||
|
res.n++
|
||||||
|
// @ts-expect-error should be readonly
|
||||||
|
res.s = ''
|
||||||
|
|
||||||
expectType<T[] | { x: T }>(res.generic1)
|
expectType<T[] | { x: T }>(res.generic1)
|
||||||
expectType<{ x: T }>(res.generic2)
|
expectType<{ x: T }>(res.generic2)
|
||||||
|
@ -328,3 +335,11 @@ describe('useSlots', () => {
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
expectType<Slots>(slots)
|
expectType<Slots>(slots)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #6420
|
||||||
|
describe('toRefs w/ type declaration', () => {
|
||||||
|
const props = defineProps<{
|
||||||
|
file?: File | File[]
|
||||||
|
}>()
|
||||||
|
expectType<Ref<File | File[] | undefined>>(toRefs(props).file)
|
||||||
|
})
|
||||||
|
|
|
@ -17,6 +17,59 @@ expectType<JSX.Element>(
|
||||||
<div style={[{ color: 'red' }, [{ fontSize: '1em' }]]} />
|
<div style={[{ color: 'red' }, [{ fontSize: '1em' }]]} />
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// allow undefined, string, object, array and nested array classes
|
||||||
|
expectType<JSX.Element>(<div class={undefined} />)
|
||||||
|
expectType<JSX.Element>(<div class={'foo'} />)
|
||||||
|
expectType<JSX.Element>(<div class={['foo', undefined, 'bar']} />)
|
||||||
|
expectType<JSX.Element>(<div class={[]} />)
|
||||||
|
expectType<JSX.Element>(<div class={['foo', ['bar'], [['baz']]]} />)
|
||||||
|
expectType<JSX.Element>(<div class={{ foo: true, bar: false, baz: true }} />)
|
||||||
|
expectType<JSX.Element>(<div class={{}} />)
|
||||||
|
expectType<JSX.Element>(
|
||||||
|
<div class={['foo', ['bar'], { baz: true }, [{ qux: true }]]} />
|
||||||
|
)
|
||||||
|
expectType<JSX.Element>(
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
{ foo: false },
|
||||||
|
{ bar: 0 },
|
||||||
|
{ baz: -0 },
|
||||||
|
{ qux: '' },
|
||||||
|
{ quux: null },
|
||||||
|
{ corge: undefined },
|
||||||
|
{ grault: NaN }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
expectType<JSX.Element>(
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
{ foo: true },
|
||||||
|
{ bar: 'not-empty' },
|
||||||
|
{ baz: 1 },
|
||||||
|
{ qux: {} },
|
||||||
|
{ quux: [] }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
// #7955
|
||||||
|
expectType<JSX.Element>(<div style={[undefined, '', null, false]} />)
|
||||||
|
|
||||||
|
expectType<JSX.Element>(<div style={undefined} />)
|
||||||
|
|
||||||
|
expectType<JSX.Element>(<div style={null} />)
|
||||||
|
|
||||||
|
expectType<JSX.Element>(<div style={''} />)
|
||||||
|
|
||||||
|
expectType<JSX.Element>(<div style={false} />)
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
;<div style={[0]} />
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
;<div style={0} />
|
||||||
|
|
||||||
// @ts-expect-error unknown prop
|
// @ts-expect-error unknown prop
|
||||||
;<div foo="bar" />
|
;<div foo="bar" />
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ref, computed, watch, defineComponent } from 'vue'
|
import { ref, computed, watch, defineComponent, shallowRef } from 'vue'
|
||||||
import { expectType } from './utils'
|
import { expectType } from './utils'
|
||||||
|
|
||||||
const source = ref('foo')
|
const source = ref('foo')
|
||||||
|
@ -92,3 +92,17 @@ defineComponent({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
{
|
||||||
|
//#7852
|
||||||
|
type Steps = { step: '1' } | { step: '2' }
|
||||||
|
const shallowUnionGenParam = shallowRef<Steps>({ step: '1' })
|
||||||
|
const shallowUnionAsCast = shallowRef({ step: '1' } as Steps)
|
||||||
|
|
||||||
|
watch(shallowUnionGenParam, value => {
|
||||||
|
expectType<Steps>(value)
|
||||||
|
})
|
||||||
|
watch(shallowUnionAsCast, value => {
|
||||||
|
expectType<Steps>(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -28,14 +28,14 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme",
|
"homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.23.0",
|
"@babel/parser": "^7.23.3",
|
||||||
"@vue/compiler-core": "3.4.0-alpha.1",
|
"@vue/compiler-core": "workspace:*",
|
||||||
"@vue/shared": "3.4.0-alpha.1",
|
"@vue/shared": "workspace:*",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.30.5"
|
"magic-string": "^0.30.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.23.2",
|
"@babel/core": "^7.23.3",
|
||||||
"@babel/types": "^7.23.0"
|
"@babel/types": "^7.23.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -448,8 +448,8 @@ export function transformAST(
|
||||||
const keyStr = isString(key)
|
const keyStr = isString(key)
|
||||||
? `'${key}'`
|
? `'${key}'`
|
||||||
: key
|
: key
|
||||||
? snip(key)
|
? snip(key)
|
||||||
: `'${nameId.name}'`
|
: `'${nameId.name}'`
|
||||||
const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``
|
const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``
|
||||||
s.appendLeft(
|
s.appendLeft(
|
||||||
call.end! + offset,
|
call.end! + offset,
|
||||||
|
|
|
@ -275,6 +275,14 @@ describe('reactivity/readonly', () => {
|
||||||
expect(isReactive(value)).toBe(true)
|
expect(isReactive(value)).toBe(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should return undefined from Map.clear() call', () => {
|
||||||
|
const wrapped = readonly(new Collection())
|
||||||
|
expect(wrapped.clear()).toBeUndefined()
|
||||||
|
expect(
|
||||||
|
`Clear operation failed: target is readonly.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -332,6 +340,14 @@ describe('reactivity/readonly', () => {
|
||||||
expect(isReadonly(v2)).toBe(true)
|
expect(isReadonly(v2)).toBe(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should return undefined from Set.clear() call', () => {
|
||||||
|
const wrapped = readonly(new Collection())
|
||||||
|
expect(wrapped.clear()).toBeUndefined()
|
||||||
|
expect(
|
||||||
|
`Clear operation failed: target is readonly.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -113,6 +113,12 @@ describe('reactivity/shallowReadonly', () => {
|
||||||
).not.toHaveBeenWarned()
|
).not.toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should return undefined from Map.clear() call', () => {
|
||||||
|
const sroMap = shallowReadonly(new Map())
|
||||||
|
expect(sroMap.clear()).toBeUndefined()
|
||||||
|
expect(`Clear operation failed: target is readonly.`).toHaveBeenWarned()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('collection/Set', () => {
|
describe('collection/Set', () => {
|
||||||
|
@ -197,5 +203,11 @@ describe('reactivity/shallowReadonly', () => {
|
||||||
).not.toHaveBeenWarned()
|
).not.toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should return undefined from Set.clear() call', () => {
|
||||||
|
const sroSet = shallowReadonly(new Set())
|
||||||
|
expect(sroSet.clear()).toBeUndefined()
|
||||||
|
expect(`Clear operation failed: target is readonly.`).toHaveBeenWarned()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -36,6 +36,6 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme",
|
"homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.4.0-alpha.1"
|
"@vue/shared": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,8 +109,8 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
||||||
? shallowReadonlyMap
|
? shallowReadonlyMap
|
||||||
: readonlyMap
|
: readonlyMap
|
||||||
: shallow
|
: shallow
|
||||||
? shallowReactiveMap
|
? shallowReactiveMap
|
||||||
: reactiveMap
|
: reactiveMap
|
||||||
).get(target)
|
).get(target)
|
||||||
) {
|
) {
|
||||||
return target
|
return target
|
||||||
|
|
|
@ -228,7 +228,11 @@ function createReadonlyMethod(type: TriggerOpTypes): Function {
|
||||||
toRaw(this)
|
toRaw(this)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return type === TriggerOpTypes.DELETE ? false : this
|
return type === TriggerOpTypes.DELETE
|
||||||
|
? false
|
||||||
|
: type === TriggerOpTypes.CLEAR
|
||||||
|
? undefined
|
||||||
|
: this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,8 +346,8 @@ function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
|
||||||
? shallowReadonlyInstrumentations
|
? shallowReadonlyInstrumentations
|
||||||
: shallowInstrumentations
|
: shallowInstrumentations
|
||||||
: isReadonly
|
: isReadonly
|
||||||
? readonlyInstrumentations
|
? readonlyInstrumentations
|
||||||
: mutableInstrumentations
|
: mutableInstrumentations
|
||||||
|
|
||||||
return (
|
return (
|
||||||
target: CollectionTypes,
|
target: CollectionTypes,
|
||||||
|
|
|
@ -138,24 +138,24 @@ type Builtin = Primitive | Function | Date | Error | RegExp
|
||||||
export type DeepReadonly<T> = T extends Builtin
|
export type DeepReadonly<T> = T extends Builtin
|
||||||
? T
|
? T
|
||||||
: T extends Map<infer K, infer V>
|
: T extends Map<infer K, infer V>
|
||||||
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
||||||
: T extends ReadonlyMap<infer K, infer V>
|
: T extends ReadonlyMap<infer K, infer V>
|
||||||
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
||||||
: T extends WeakMap<infer K, infer V>
|
: T extends WeakMap<infer K, infer V>
|
||||||
? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
|
? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
|
||||||
: T extends Set<infer U>
|
: T extends Set<infer U>
|
||||||
? ReadonlySet<DeepReadonly<U>>
|
? ReadonlySet<DeepReadonly<U>>
|
||||||
: T extends ReadonlySet<infer U>
|
: T extends ReadonlySet<infer U>
|
||||||
? ReadonlySet<DeepReadonly<U>>
|
? ReadonlySet<DeepReadonly<U>>
|
||||||
: T extends WeakSet<infer U>
|
: T extends WeakSet<infer U>
|
||||||
? WeakSet<DeepReadonly<U>>
|
? WeakSet<DeepReadonly<U>>
|
||||||
: T extends Promise<infer U>
|
: T extends Promise<infer U>
|
||||||
? Promise<DeepReadonly<U>>
|
? Promise<DeepReadonly<U>>
|
||||||
: T extends Ref<infer U>
|
: T extends Ref<infer U>
|
||||||
? Readonly<Ref<DeepReadonly<U>>>
|
? Readonly<Ref<DeepReadonly<U>>>
|
||||||
: T extends {}
|
: T extends {}
|
||||||
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
||||||
: Readonly<T>
|
: Readonly<T>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes an object (reactive or plain) or a ref and returns a readonly proxy to
|
* Takes an object (reactive or plain) or a ref and returns a readonly proxy to
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { ComputedRef } from './computed'
|
||||||
import {
|
import {
|
||||||
activeEffect,
|
activeEffect,
|
||||||
shouldTrack,
|
shouldTrack,
|
||||||
|
@ -128,9 +129,8 @@ export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }
|
||||||
* @param value - The "inner value" for the shallow ref.
|
* @param value - The "inner value" for the shallow ref.
|
||||||
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref}
|
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref}
|
||||||
*/
|
*/
|
||||||
export function shallowRef<T extends object>(
|
export function shallowRef<T>(value: MaybeRef<T>): Ref<T> | ShallowRef<T>
|
||||||
value: T
|
export function shallowRef<T extends Ref>(value: T): T
|
||||||
): T extends Ref ? T : ShallowRef<T>
|
|
||||||
export function shallowRef<T>(value: T): ShallowRef<T>
|
export function shallowRef<T>(value: T): ShallowRef<T>
|
||||||
export function shallowRef<T = any>(): ShallowRef<T | undefined>
|
export function shallowRef<T = any>(): ShallowRef<T | undefined>
|
||||||
export function shallowRef(value?: unknown) {
|
export function shallowRef(value?: unknown) {
|
||||||
|
@ -224,7 +224,7 @@ export type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T)
|
||||||
* @param ref - Ref or plain value to be converted into the plain value.
|
* @param ref - Ref or plain value to be converted into the plain value.
|
||||||
* @see {@link https://vuejs.org/api/reactivity-utilities.html#unref}
|
* @see {@link https://vuejs.org/api/reactivity-utilities.html#unref}
|
||||||
*/
|
*/
|
||||||
export function unref<T>(ref: MaybeRef<T>): T {
|
export function unref<T>(ref: MaybeRef<T> | ComputedRef<T>): T {
|
||||||
return isRef(ref) ? ref.value : ref
|
return isRef(ref) ? ref.value : ref
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ export function unref<T>(ref: MaybeRef<T>): T {
|
||||||
* @param source - A getter, an existing ref, or a non-function value.
|
* @param source - A getter, an existing ref, or a non-function value.
|
||||||
* @see {@link https://vuejs.org/api/reactivity-utilities.html#tovalue}
|
* @see {@link https://vuejs.org/api/reactivity-utilities.html#tovalue}
|
||||||
*/
|
*/
|
||||||
export function toValue<T>(source: MaybeRefOrGetter<T>): T {
|
export function toValue<T>(source: MaybeRefOrGetter<T> | ComputedRef<T>): T {
|
||||||
return isFunction(source) ? source() : unref(source)
|
return isFunction(source) ? source() : unref(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,8 +429,8 @@ export function toRef<T>(
|
||||||
): T extends () => infer R
|
): T extends () => infer R
|
||||||
? Readonly<Ref<R>>
|
? Readonly<Ref<R>>
|
||||||
: T extends Ref
|
: T extends Ref
|
||||||
? T
|
? T
|
||||||
: Ref<UnwrapRef<T>>
|
: Ref<UnwrapRef<T>>
|
||||||
export function toRef<T extends object, K extends keyof T>(
|
export function toRef<T extends object, K extends keyof T>(
|
||||||
object: T,
|
object: T,
|
||||||
key: K
|
key: K
|
||||||
|
@ -491,17 +491,17 @@ export type ShallowUnwrapRef<T> = {
|
||||||
[K in keyof T]: T[K] extends Ref<infer V>
|
[K in keyof T]: T[K] extends Ref<infer V>
|
||||||
? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined
|
? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined
|
||||||
: T[K] extends Ref<infer V> | undefined
|
: T[K] extends Ref<infer V> | undefined
|
||||||
? unknown extends V
|
? unknown extends V
|
||||||
? undefined
|
? undefined
|
||||||
: V | undefined
|
: V | undefined
|
||||||
: T[K]
|
: T[K]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UnwrapRef<T> = T extends ShallowRef<infer V>
|
export type UnwrapRef<T> = T extends ShallowRef<infer V>
|
||||||
? V
|
? V
|
||||||
: T extends Ref<infer V>
|
: T extends Ref<infer V>
|
||||||
? UnwrapRefSimple<V>
|
? UnwrapRefSimple<V>
|
||||||
: UnwrapRefSimple<T>
|
: UnwrapRefSimple<T>
|
||||||
|
|
||||||
export type UnwrapRefSimple<T> = T extends
|
export type UnwrapRefSimple<T> = T extends
|
||||||
| Function
|
| Function
|
||||||
|
@ -512,9 +512,9 @@ export type UnwrapRefSimple<T> = T extends
|
||||||
| { [RawSymbol]?: true }
|
| { [RawSymbol]?: true }
|
||||||
? T
|
? T
|
||||||
: T extends ReadonlyArray<any>
|
: T extends ReadonlyArray<any>
|
||||||
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
|
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
|
||||||
: T extends object & { [ShallowReactiveMarker]?: never }
|
: T extends object & { [ShallowReactiveMarker]?: never }
|
||||||
? {
|
? {
|
||||||
[P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>
|
[P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>
|
||||||
}
|
}
|
||||||
: T
|
: T
|
||||||
|
|
|
@ -91,7 +91,7 @@ describe('api: watch', () => {
|
||||||
array.push(1)
|
array.push(1)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(spy).toBeCalledTimes(1)
|
expect(spy).toBeCalledTimes(1)
|
||||||
expect(spy).toBeCalledWith([1], expect.anything(), expect.anything())
|
expect(spy).toBeCalledWith([1], [1], expect.anything())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not fire if watched getter result did not change', async () => {
|
it('should not fire if watched getter result did not change', async () => {
|
||||||
|
@ -1243,4 +1243,39 @@ describe('api: watch', () => {
|
||||||
expect(count.value).toBe(2)
|
expect(count.value).toBe(2)
|
||||||
expect(cb).toHaveBeenCalledTimes(1)
|
expect(cb).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #5151
|
||||||
|
test('OnCleanup also needs to be cleaned,', async () => {
|
||||||
|
const spy1 = vi.fn()
|
||||||
|
const spy2 = vi.fn()
|
||||||
|
const num = ref(0)
|
||||||
|
|
||||||
|
watch(num, (value, oldValue, onCleanup) => {
|
||||||
|
if (value > 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
spy1()
|
||||||
|
onCleanup(() => {
|
||||||
|
// OnCleanup also needs to be cleaned
|
||||||
|
spy2()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
num.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(spy1).toHaveBeenCalledTimes(1)
|
||||||
|
expect(spy2).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
num.value++
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(spy1).toHaveBeenCalledTimes(1)
|
||||||
|
expect(spy2).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
num.value++
|
||||||
|
await nextTick()
|
||||||
|
// would not be calld when value>1
|
||||||
|
expect(spy1).toHaveBeenCalledTimes(1)
|
||||||
|
expect(spy2).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -336,7 +336,8 @@ describe('component props', () => {
|
||||||
obj: { type: Object },
|
obj: { type: Object },
|
||||||
cls: { type: MyClass },
|
cls: { type: MyClass },
|
||||||
fn: { type: Function },
|
fn: { type: Function },
|
||||||
skipCheck: { type: [Boolean, Function], skipCheck: true }
|
skipCheck: { type: [Boolean, Function], skipCheck: true },
|
||||||
|
empty: { type: [] }
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
return () => null
|
return () => null
|
||||||
|
@ -351,7 +352,8 @@ describe('component props', () => {
|
||||||
obj: 'false',
|
obj: 'false',
|
||||||
cls: {},
|
cls: {},
|
||||||
fn: true,
|
fn: true,
|
||||||
skipCheck: 'foo'
|
skipCheck: 'foo',
|
||||||
|
empty: [1, 2, 3]
|
||||||
}),
|
}),
|
||||||
nodeOps.createElement('div')
|
nodeOps.createElement('div')
|
||||||
)
|
)
|
||||||
|
@ -379,6 +381,9 @@ describe('component props', () => {
|
||||||
expect(
|
expect(
|
||||||
`Invalid prop: type check failed for prop "skipCheck". Expected Boolean | Function, got String with value "foo".`
|
`Invalid prop: type check failed for prop "skipCheck". Expected Boolean | Function, got String with value "foo".`
|
||||||
).not.toHaveBeenWarned()
|
).not.toHaveBeenWarned()
|
||||||
|
expect(
|
||||||
|
`Prop type [] for prop "empty" won't match anything. Did you mean to use type Array instead?`
|
||||||
|
).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
// #3495
|
// #3495
|
||||||
|
|
|
@ -17,9 +17,12 @@ import {
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
onErrorCaptured,
|
onErrorCaptured,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
|
SuspenseProps,
|
||||||
|
resolveDynamicComponent,
|
||||||
Fragment
|
Fragment
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { createApp, defineComponent } from 'vue'
|
import { createApp, defineComponent } from 'vue'
|
||||||
|
import { type RawSlots } from 'packages/runtime-core/src/componentSlots'
|
||||||
|
|
||||||
describe('Suspense', () => {
|
describe('Suspense', () => {
|
||||||
const deps: Promise<any>[] = []
|
const deps: Promise<any>[] = []
|
||||||
|
@ -1523,4 +1526,75 @@ describe('Suspense', () => {
|
||||||
expected = `<div>outerB</div><div>innerB</div>`
|
expected = `<div>outerB</div><div>innerB</div>`
|
||||||
expect(serializeInner(root)).toBe(expected)
|
expect(serializeInner(root)).toBe(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('warnings', () => {
|
||||||
|
// base function to check if a combination of slots warns or not
|
||||||
|
function baseCheckWarn(
|
||||||
|
shouldWarn: boolean,
|
||||||
|
children: RawSlots,
|
||||||
|
props: SuspenseProps | null = null
|
||||||
|
) {
|
||||||
|
const Comp = {
|
||||||
|
setup() {
|
||||||
|
return () => h(Suspense, props, children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Comp), root)
|
||||||
|
|
||||||
|
if (shouldWarn) {
|
||||||
|
expect(`<Suspense> slots expect a single root node.`).toHaveBeenWarned()
|
||||||
|
} else {
|
||||||
|
expect(
|
||||||
|
`<Suspense> slots expect a single root node.`
|
||||||
|
).not.toHaveBeenWarned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// actual function that we use in tests
|
||||||
|
const checkWarn = baseCheckWarn.bind(null, true)
|
||||||
|
const checkNoWarn = baseCheckWarn.bind(null, false)
|
||||||
|
|
||||||
|
test('does not warn on single child', async () => {
|
||||||
|
checkNoWarn({
|
||||||
|
default: h('div'),
|
||||||
|
fallback: h('div')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not warn on null', async () => {
|
||||||
|
checkNoWarn({
|
||||||
|
default: null,
|
||||||
|
fallback: null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not warn on <component :is="null" />', async () => {
|
||||||
|
checkNoWarn({
|
||||||
|
default: () => [resolveDynamicComponent(null)],
|
||||||
|
fallback: () => null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not warn on empty array', async () => {
|
||||||
|
checkNoWarn({
|
||||||
|
default: [],
|
||||||
|
fallback: () => []
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('warns on multiple children in default', async () => {
|
||||||
|
checkWarn({
|
||||||
|
default: [h('div'), h('div')]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('warns on multiple children in fallback', async () => {
|
||||||
|
checkWarn({
|
||||||
|
default: h('div'),
|
||||||
|
fallback: [h('div'), h('div')]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -218,6 +218,75 @@ describe('hot module replacement', () => {
|
||||||
expect(deactiveSpy).toHaveBeenCalledTimes(1)
|
expect(deactiveSpy).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #7121
|
||||||
|
test('reload KeepAlive slot in Transition', async () => {
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
const childId = 'test-transition-keep-alive-reload'
|
||||||
|
const unmountSpy = vi.fn()
|
||||||
|
const mountSpy = vi.fn()
|
||||||
|
const activeSpy = vi.fn()
|
||||||
|
const deactiveSpy = vi.fn()
|
||||||
|
|
||||||
|
const Child: ComponentOptions = {
|
||||||
|
__hmrId: childId,
|
||||||
|
data() {
|
||||||
|
return { count: 0 }
|
||||||
|
},
|
||||||
|
unmounted: unmountSpy,
|
||||||
|
render: compileToFunction(`<div>{{ count }}</div>`)
|
||||||
|
}
|
||||||
|
createRecord(childId, Child)
|
||||||
|
|
||||||
|
const Parent: ComponentOptions = {
|
||||||
|
components: { Child },
|
||||||
|
data() {
|
||||||
|
return { toggle: true }
|
||||||
|
},
|
||||||
|
render: compileToFunction(
|
||||||
|
`<button @click="toggle = !toggle"></button><BaseTransition mode="out-in"><KeepAlive><Child v-if="toggle" /></KeepAlive></BaseTransition>`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render(h(Parent), root)
|
||||||
|
expect(serializeInner(root)).toBe(`<button></button><div>0</div>`)
|
||||||
|
|
||||||
|
reload(childId, {
|
||||||
|
__hmrId: childId,
|
||||||
|
data() {
|
||||||
|
return { count: 1 }
|
||||||
|
},
|
||||||
|
mounted: mountSpy,
|
||||||
|
unmounted: unmountSpy,
|
||||||
|
activated: activeSpy,
|
||||||
|
deactivated: deactiveSpy,
|
||||||
|
render: compileToFunction(`<div>{{ count }}</div>`)
|
||||||
|
})
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<button></button><div>1</div>`)
|
||||||
|
expect(unmountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(mountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(activeSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(deactiveSpy).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
// should not unmount when toggling
|
||||||
|
triggerEvent(root.children[1] as TestElement, 'click')
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<button></button><!---->`)
|
||||||
|
expect(unmountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(mountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(activeSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(deactiveSpy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
// should not mount when toggling
|
||||||
|
triggerEvent(root.children[1] as TestElement, 'click')
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<button></button><div>1</div>`)
|
||||||
|
expect(unmountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(mountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(activeSpy).toHaveBeenCalledTimes(2)
|
||||||
|
expect(deactiveSpy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('reload class component', async () => {
|
test('reload class component', async () => {
|
||||||
const root = nodeOps.createElement('div')
|
const root = nodeOps.createElement('div')
|
||||||
const childId = 'test4-child'
|
const childId = 'test4-child'
|
||||||
|
|
|
@ -935,6 +935,18 @@ describe('SSR hydration', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('force hydrate prop with `.prop` modifier', () => {
|
||||||
|
const { container } = mountWithHydration(
|
||||||
|
'<input type="checkbox" :indeterminate.prop="true">',
|
||||||
|
() =>
|
||||||
|
h('input', {
|
||||||
|
type: 'checkbox',
|
||||||
|
'.indeterminate': true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect((container.firstChild! as any).indeterminate).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
test('force hydrate input v-model with non-string value bindings', () => {
|
test('force hydrate input v-model with non-string value bindings', () => {
|
||||||
const { container } = mountWithHydration(
|
const { container } = mountWithHydration(
|
||||||
'<input type="checkbox" value="true">',
|
'<input type="checkbox" value="true">',
|
||||||
|
@ -953,6 +965,20 @@ describe('SSR hydration', () => {
|
||||||
expect((container.firstChild as any)._trueValue).toBe(true)
|
expect((container.firstChild as any)._trueValue).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('force hydrate checkbox with indeterminate', () => {
|
||||||
|
const { container } = mountWithHydration(
|
||||||
|
'<input type="checkbox" indeterminate>',
|
||||||
|
() =>
|
||||||
|
createVNode(
|
||||||
|
'input',
|
||||||
|
{ type: 'checkbox', indeterminate: '' },
|
||||||
|
null,
|
||||||
|
PatchFlags.HOISTED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
expect((container.firstChild as any).indeterminate).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
test('force hydrate select option with non-string value bindings', () => {
|
test('force hydrate select option with non-string value bindings', () => {
|
||||||
const { container } = mountWithHydration(
|
const { container } = mountWithHydration(
|
||||||
'<select><option :value="true">ok</option></select>',
|
'<select><option :value="true">ok</option></select>',
|
||||||
|
@ -1177,5 +1203,21 @@ describe('SSR hydration', () => {
|
||||||
expect(teleportContainer.innerHTML).toBe(`<span>value</span>`)
|
expect(teleportContainer.innerHTML).toBe(`<span>value</span>`)
|
||||||
expect(`Hydration children mismatch`).toHaveBeenWarned()
|
expect(`Hydration children mismatch`).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('comment mismatch (element)', () => {
|
||||||
|
const { container } = mountWithHydration(`<div><span></span></div>`, () =>
|
||||||
|
h('div', [createCommentVNode('hi')])
|
||||||
|
)
|
||||||
|
expect(container.innerHTML).toBe('<div><!--hi--></div>')
|
||||||
|
expect(`Hydration node mismatch`).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('comment mismatch (text)', () => {
|
||||||
|
const { container } = mountWithHydration(`<div>foobar</div>`, () =>
|
||||||
|
h('div', [createCommentVNode('hi')])
|
||||||
|
)
|
||||||
|
expect(container.innerHTML).toBe('<div><!--hi--></div>')
|
||||||
|
expect(`Hydration node mismatch`).toHaveBeenWarned()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -354,4 +354,25 @@ describe('renderer: component', () => {
|
||||||
expect(serializeInner(root)).toBe(`<h1>1</h1>`)
|
expect(serializeInner(root)).toBe(`<h1>1</h1>`)
|
||||||
expect(spy).toHaveBeenCalledTimes(2)
|
expect(spy).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should warn accessing `this` in a <script setup> template', () => {
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
__isScriptSetup: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render(this: any) {
|
||||||
|
return this.$attrs.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(App), root)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
`Property '$attrs' was accessed via 'this'. Avoid using 'this' in templates.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -477,13 +477,13 @@ describe('vnode', () => {
|
||||||
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
|
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should not track vnodes with only HYDRATE_EVENTS flag', () => {
|
test('should not track vnodes with only NEED_HYDRATION flag', () => {
|
||||||
const hoist = createVNode('div')
|
const hoist = createVNode('div')
|
||||||
const vnode =
|
const vnode =
|
||||||
(openBlock(),
|
(openBlock(),
|
||||||
createBlock('div', null, [
|
createBlock('div', null, [
|
||||||
hoist,
|
hoist,
|
||||||
createVNode('div', null, 'text', PatchFlags.HYDRATE_EVENTS)
|
createVNode('div', null, 'text', PatchFlags.NEED_HYDRATION)
|
||||||
]))
|
]))
|
||||||
expect(vnode.dynamicChildren).toStrictEqual([])
|
expect(vnode.dynamicChildren).toStrictEqual([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme",
|
"homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.4.0-alpha.1",
|
"@vue/shared": "workspace:*",
|
||||||
"@vue/reactivity": "3.4.0-alpha.1"
|
"@vue/reactivity": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,8 +70,7 @@ export type DefineComponent<
|
||||||
true,
|
true,
|
||||||
{},
|
{},
|
||||||
S
|
S
|
||||||
> &
|
>
|
||||||
Props
|
|
||||||
> &
|
> &
|
||||||
ComponentOptionsBase<
|
ComponentOptionsBase<
|
||||||
Props,
|
Props,
|
||||||
|
|
|
@ -4,7 +4,8 @@ import {
|
||||||
isFunction,
|
isFunction,
|
||||||
Prettify,
|
Prettify,
|
||||||
UnionToIntersection,
|
UnionToIntersection,
|
||||||
extend
|
extend,
|
||||||
|
LooseRequired
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
|
@ -82,7 +83,7 @@ export function defineProps<
|
||||||
>(props: PP): Prettify<Readonly<ExtractPropTypes<PP>>>
|
>(props: PP): Prettify<Readonly<ExtractPropTypes<PP>>>
|
||||||
// overload 3: typed-based declaration
|
// overload 3: typed-based declaration
|
||||||
export function defineProps<TypeProps>(): DefineProps<
|
export function defineProps<TypeProps>(): DefineProps<
|
||||||
TypeProps,
|
LooseRequired<TypeProps>,
|
||||||
BooleanKey<TypeProps>
|
BooleanKey<TypeProps>
|
||||||
>
|
>
|
||||||
// implementation
|
// implementation
|
||||||
|
@ -297,8 +298,8 @@ type PropsWithDefaults<
|
||||||
T,
|
T,
|
||||||
Defaults extends InferDefaults<T>,
|
Defaults extends InferDefaults<T>,
|
||||||
BKeys extends keyof T
|
BKeys extends keyof T
|
||||||
> = Omit<T, keyof Defaults> & {
|
> = Readonly<Omit<T, keyof Defaults>> & {
|
||||||
[K in keyof Defaults]-?: K extends keyof T
|
readonly [K in keyof Defaults]-?: K extends keyof T
|
||||||
? Defaults[K] extends undefined
|
? Defaults[K] extends undefined
|
||||||
? T[K]
|
? T[K]
|
||||||
: NotUndefined<T[K]>
|
: NotUndefined<T[K]>
|
||||||
|
|
|
@ -60,10 +60,10 @@ type MapSources<T, Immediate> = {
|
||||||
? V | undefined
|
? V | undefined
|
||||||
: V
|
: V
|
||||||
: T[K] extends object
|
: T[K] extends object
|
||||||
? Immediate extends true
|
? Immediate extends true
|
||||||
? T[K] | undefined
|
? T[K] | undefined
|
||||||
: T[K]
|
: T[K]
|
||||||
: never
|
: never
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnCleanup = (cleanupFn: () => void) => void
|
type OnCleanup = (cleanupFn: () => void) => void
|
||||||
|
@ -288,10 +288,11 @@ function doWatch(
|
||||||
getter = () => traverse(baseGetter())
|
getter = () => traverse(baseGetter())
|
||||||
}
|
}
|
||||||
|
|
||||||
let cleanup: () => void
|
let cleanup: (() => void) | undefined
|
||||||
let onCleanup: OnCleanup = (fn: () => void) => {
|
let onCleanup: OnCleanup = (fn: () => void) => {
|
||||||
cleanup = effect.onStop = () => {
|
cleanup = effect.onStop = () => {
|
||||||
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
|
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
|
||||||
|
cleanup = effect.onStop = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,8 +349,8 @@ function doWatch(
|
||||||
oldValue === INITIAL_WATCHER_VALUE
|
oldValue === INITIAL_WATCHER_VALUE
|
||||||
? undefined
|
? undefined
|
||||||
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
|
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
|
||||||
? []
|
? []
|
||||||
: oldValue,
|
: oldValue,
|
||||||
onCleanup
|
onCleanup
|
||||||
])
|
])
|
||||||
oldValue = newValue
|
oldValue = newValue
|
||||||
|
|
|
@ -251,8 +251,8 @@ export function createCompatVue(
|
||||||
mergeBase[key] = isArray(superValue)
|
mergeBase[key] = isArray(superValue)
|
||||||
? superValue.slice()
|
? superValue.slice()
|
||||||
: isObject(superValue)
|
: isObject(superValue)
|
||||||
? extend(Object.create(null), superValue)
|
? extend(Object.create(null), superValue)
|
||||||
: superValue
|
: superValue
|
||||||
}
|
}
|
||||||
|
|
||||||
SubVue.options = mergeOptions(
|
SubVue.options = mergeOptions(
|
||||||
|
|
|
@ -922,10 +922,10 @@ export function finishComponentSetup(
|
||||||
(__ESM_BUNDLER__
|
(__ESM_BUNDLER__
|
||||||
? ` Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".`
|
? ` Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".`
|
||||||
: __ESM_BROWSER__
|
: __ESM_BROWSER__
|
||||||
? ` Use "vue.esm-browser.js" instead.`
|
? ` Use "vue.esm-browser.js" instead.`
|
||||||
: __GLOBAL__
|
: __GLOBAL__
|
||||||
? ` Use "vue.global.js" instead.`
|
? ` Use "vue.global.js" instead.`
|
||||||
: ``) /* should not happen */
|
: ``) /* should not happen */
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
warn(`Component is missing template or render function.`)
|
warn(`Component is missing template or render function.`)
|
||||||
|
|
|
@ -41,19 +41,19 @@ export type EmitsToProps<T extends EmitsOptions> = T extends string[]
|
||||||
[K in string & `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
|
[K in string & `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
|
||||||
}
|
}
|
||||||
: T extends ObjectEmitsOptions
|
: T extends ObjectEmitsOptions
|
||||||
? {
|
? {
|
||||||
[K in string &
|
[K in string &
|
||||||
`on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
|
`on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
|
||||||
? T[Uncapitalize<C>] extends null
|
? T[Uncapitalize<C>] extends null
|
||||||
? (...args: any[]) => any
|
? (...args: any[]) => any
|
||||||
: (
|
: (
|
||||||
...args: T[Uncapitalize<C>] extends (...args: infer P) => any
|
...args: T[Uncapitalize<C>] extends (...args: infer P) => any
|
||||||
? P
|
? P
|
||||||
: never
|
: never
|
||||||
) => any
|
) => any
|
||||||
: never
|
: never
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
export type EmitFn<
|
export type EmitFn<
|
||||||
Options = ObjectEmitsOptions,
|
Options = ObjectEmitsOptions,
|
||||||
|
@ -61,14 +61,14 @@ export type EmitFn<
|
||||||
> = Options extends Array<infer V>
|
> = Options extends Array<infer V>
|
||||||
? (event: V, ...args: any[]) => void
|
? (event: V, ...args: any[]) => void
|
||||||
: {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
|
: {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
|
||||||
? (event: string, ...args: any[]) => void
|
? (event: string, ...args: any[]) => void
|
||||||
: UnionToIntersection<
|
: UnionToIntersection<
|
||||||
{
|
{
|
||||||
[key in Event]: Options[key] extends (...args: infer Args) => any
|
[key in Event]: Options[key] extends (...args: infer Args) => any
|
||||||
? (event: key, ...args: Args) => void
|
? (event: key, ...args: Args) => void
|
||||||
: (event: key, ...args: any[]) => void
|
: (event: key, ...args: any[]) => void
|
||||||
}[Event]
|
}[Event]
|
||||||
>
|
>
|
||||||
|
|
||||||
export function emit(
|
export function emit(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
|
|
|
@ -419,8 +419,8 @@ export type ExtractComputedReturns<T extends any> = {
|
||||||
[key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }
|
[key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }
|
||||||
? TReturn
|
? TReturn
|
||||||
: T[key] extends (...args: any[]) => infer TReturn
|
: T[key] extends (...args: any[]) => infer TReturn
|
||||||
? TReturn
|
? TReturn
|
||||||
: never
|
: never
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ObjectWatchOptionItem = {
|
export type ObjectWatchOptionItem = {
|
||||||
|
@ -450,10 +450,10 @@ export type InjectToObject<T extends ComponentInjectOptions> =
|
||||||
[K in T[number]]?: unknown
|
[K in T[number]]?: unknown
|
||||||
}
|
}
|
||||||
: T extends ObjectInjectOptions
|
: T extends ObjectInjectOptions
|
||||||
? {
|
? {
|
||||||
[K in keyof T]?: unknown
|
[K in keyof T]?: unknown
|
||||||
}
|
}
|
||||||
: never
|
: never
|
||||||
|
|
||||||
interface LegacyOptions<
|
interface LegacyOptions<
|
||||||
Props,
|
Props,
|
||||||
|
@ -752,8 +752,8 @@ export function applyOptions(instance: ComponentInternalInstance) {
|
||||||
const get = isFunction(opt)
|
const get = isFunction(opt)
|
||||||
? opt.bind(publicThis, publicThis)
|
? opt.bind(publicThis, publicThis)
|
||||||
: isFunction(opt.get)
|
: isFunction(opt.get)
|
||||||
? opt.get.bind(publicThis, publicThis)
|
? opt.get.bind(publicThis, publicThis)
|
||||||
: NOOP
|
: NOOP
|
||||||
if (__DEV__ && get === NOOP) {
|
if (__DEV__ && get === NOOP) {
|
||||||
warn(`Computed property "${key}" has no getter.`)
|
warn(`Computed property "${key}" has no getter.`)
|
||||||
}
|
}
|
||||||
|
@ -761,12 +761,12 @@ export function applyOptions(instance: ComponentInternalInstance) {
|
||||||
!isFunction(opt) && isFunction(opt.set)
|
!isFunction(opt) && isFunction(opt.set)
|
||||||
? opt.set.bind(publicThis)
|
? opt.set.bind(publicThis)
|
||||||
: __DEV__
|
: __DEV__
|
||||||
? () => {
|
? () => {
|
||||||
warn(
|
warn(
|
||||||
`Write operation failed: computed property "${key}" is readonly.`
|
`Write operation failed: computed property "${key}" is readonly.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
: NOOP
|
: NOOP
|
||||||
const c = computed({
|
const c = computed({
|
||||||
get,
|
get,
|
||||||
set
|
set
|
||||||
|
|
|
@ -111,22 +111,22 @@ type DefaultKeys<T> = {
|
||||||
type InferPropType<T> = [T] extends [null]
|
type InferPropType<T> = [T] extends [null]
|
||||||
? any // null & true would fail to infer
|
? any // null & true would fail to infer
|
||||||
: [T] extends [{ type: null | true }]
|
: [T] extends [{ type: null | true }]
|
||||||
? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
|
? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
|
||||||
: [T] extends [ObjectConstructor | { type: ObjectConstructor }]
|
: [T] extends [ObjectConstructor | { type: ObjectConstructor }]
|
||||||
? Record<string, any>
|
? Record<string, any>
|
||||||
: [T] extends [BooleanConstructor | { type: BooleanConstructor }]
|
: [T] extends [BooleanConstructor | { type: BooleanConstructor }]
|
||||||
? boolean
|
? boolean
|
||||||
: [T] extends [DateConstructor | { type: DateConstructor }]
|
: [T] extends [DateConstructor | { type: DateConstructor }]
|
||||||
? Date
|
? Date
|
||||||
: [T] extends [(infer U)[] | { type: (infer U)[] }]
|
: [T] extends [(infer U)[] | { type: (infer U)[] }]
|
||||||
? U extends DateConstructor
|
? U extends DateConstructor
|
||||||
? Date | InferPropType<U>
|
? Date | InferPropType<U>
|
||||||
: InferPropType<U>
|
: InferPropType<U>
|
||||||
: [T] extends [Prop<infer V, infer D>]
|
: [T] extends [Prop<infer V, infer D>]
|
||||||
? unknown extends V
|
? unknown extends V
|
||||||
? IfAny<V, V, D>
|
? IfAny<V, V, D>
|
||||||
: V
|
: V
|
||||||
: T
|
: T
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract prop types from a runtime props options object.
|
* Extract prop types from a runtime props options object.
|
||||||
|
@ -725,6 +725,12 @@ function getInvalidTypeMessage(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
expectedTypes: string[]
|
expectedTypes: string[]
|
||||||
): string {
|
): string {
|
||||||
|
if (expectedTypes.length === 0) {
|
||||||
|
return (
|
||||||
|
`Prop type [] for prop "${name}" won't match anything.` +
|
||||||
|
` Did you mean to use type Array instead?`
|
||||||
|
)
|
||||||
|
}
|
||||||
let message =
|
let message =
|
||||||
`Invalid prop: type check failed for prop "${name}".` +
|
`Invalid prop: type check failed for prop "${name}".` +
|
||||||
` Expected ${expectedTypes.map(capitalize).join(' | ')}`
|
` Expected ${expectedTypes.map(capitalize).join(' | ')}`
|
||||||
|
|
|
@ -15,7 +15,8 @@ import {
|
||||||
isString,
|
isString,
|
||||||
isFunction,
|
isFunction,
|
||||||
UnionToIntersection,
|
UnionToIntersection,
|
||||||
Prettify
|
Prettify,
|
||||||
|
IfAny
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
toRaw,
|
toRaw,
|
||||||
|
@ -187,7 +188,6 @@ export type CreateComponentPublicInstance<
|
||||||
I,
|
I,
|
||||||
S
|
S
|
||||||
>
|
>
|
||||||
|
|
||||||
// public properties exposed on the proxy, which is used as the render context
|
// public properties exposed on the proxy, which is used as the render context
|
||||||
// in templates (as `this` in the render option)
|
// in templates (as `this` in the render option)
|
||||||
export type ComponentPublicInstance<
|
export type ComponentPublicInstance<
|
||||||
|
@ -226,7 +226,7 @@ export type ComponentPublicInstance<
|
||||||
: (...args: any) => any,
|
: (...args: any) => any,
|
||||||
options?: WatchOptions
|
options?: WatchOptions
|
||||||
): WatchStopHandle
|
): WatchStopHandle
|
||||||
} & P &
|
} & IfAny<P, P, Omit<P, keyof ShallowUnwrapRef<B>>> &
|
||||||
ShallowUnwrapRef<B> &
|
ShallowUnwrapRef<B> &
|
||||||
UnwrapNestedRefs<D> &
|
UnwrapNestedRefs<D> &
|
||||||
ExtractComputedReturns<C> &
|
ExtractComputedReturns<C> &
|
||||||
|
|
|
@ -73,9 +73,24 @@ export function renderComponentRoot(
|
||||||
// withProxy is a proxy with a different `has` trap only for
|
// withProxy is a proxy with a different `has` trap only for
|
||||||
// runtime-compiled render functions using `with` block.
|
// runtime-compiled render functions using `with` block.
|
||||||
const proxyToUse = withProxy || proxy
|
const proxyToUse = withProxy || proxy
|
||||||
|
// 'this' isn't available in production builds with `<script setup>`,
|
||||||
|
// so warn if it's used in dev.
|
||||||
|
const thisProxy =
|
||||||
|
__DEV__ && setupState.__isScriptSetup
|
||||||
|
? new Proxy(proxyToUse!, {
|
||||||
|
get(target, key, receiver) {
|
||||||
|
warn(
|
||||||
|
`Property '${String(
|
||||||
|
key
|
||||||
|
)}' was accessed via 'this'. Avoid using 'this' in templates.`
|
||||||
|
)
|
||||||
|
return Reflect.get(target, key, receiver)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: proxyToUse
|
||||||
result = normalizeVNode(
|
result = normalizeVNode(
|
||||||
render!.call(
|
render!.call(
|
||||||
proxyToUse,
|
thisProxy,
|
||||||
proxyToUse!,
|
proxyToUse!,
|
||||||
renderCache,
|
renderCache,
|
||||||
props,
|
props,
|
||||||
|
|
|
@ -474,9 +474,13 @@ function emptyPlaceholder(vnode: VNode): VNode | undefined {
|
||||||
|
|
||||||
function getKeepAliveChild(vnode: VNode): VNode | undefined {
|
function getKeepAliveChild(vnode: VNode): VNode | undefined {
|
||||||
return isKeepAlive(vnode)
|
return isKeepAlive(vnode)
|
||||||
? vnode.children
|
? // #7121 ensure get the child component subtree in case
|
||||||
? ((vnode.children as VNodeArrayChildren)[0] as VNode)
|
// it's been replaced during HMR
|
||||||
: undefined
|
__DEV__ && vnode.component
|
||||||
|
? vnode.component.subTree
|
||||||
|
: vnode.children
|
||||||
|
? ((vnode.children as VNodeArrayChildren)[0] as VNode)
|
||||||
|
: undefined
|
||||||
: vnode
|
: vnode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
assertNumber
|
assertNumber
|
||||||
} from '../warning'
|
} from '../warning'
|
||||||
import { handleError, ErrorCodes } from '../errorHandling'
|
import { handleError, ErrorCodes } from '../errorHandling'
|
||||||
|
import { NULL_DYNAMIC_COMPONENT } from '../helpers/resolveAssets'
|
||||||
|
|
||||||
export interface SuspenseProps {
|
export interface SuspenseProps {
|
||||||
onResolve?: () => void
|
onResolve?: () => void
|
||||||
|
@ -795,7 +796,11 @@ function normalizeSuspenseSlot(s: any) {
|
||||||
}
|
}
|
||||||
if (isArray(s)) {
|
if (isArray(s)) {
|
||||||
const singleChild = filterSingleRoot(s)
|
const singleChild = filterSingleRoot(s)
|
||||||
if (__DEV__ && !singleChild) {
|
if (
|
||||||
|
__DEV__ &&
|
||||||
|
!singleChild &&
|
||||||
|
s.filter(child => child !== NULL_DYNAMIC_COMPONENT).length > 0
|
||||||
|
) {
|
||||||
warn(`<Suspense> slots expect a single root node.`)
|
warn(`<Suspense> slots expect a single root node.`)
|
||||||
}
|
}
|
||||||
s = singleChild
|
s = singleChild
|
||||||
|
|
|
@ -63,6 +63,7 @@ const resolveTarget = <T = RendererElement>(
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TeleportImpl = {
|
export const TeleportImpl = {
|
||||||
|
name: 'Teleport',
|
||||||
__isTeleport: true,
|
__isTeleport: true,
|
||||||
process(
|
process(
|
||||||
n1: TeleportVNode | null,
|
n1: TeleportVNode | null,
|
||||||
|
|
|
@ -111,6 +111,21 @@ export function createHydrationFunctions(
|
||||||
let domType = node.nodeType
|
let domType = node.nodeType
|
||||||
vnode.el = node
|
vnode.el = node
|
||||||
|
|
||||||
|
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||||
|
if (!('__vnode' in node)) {
|
||||||
|
Object.defineProperty(node, '__vnode', {
|
||||||
|
value: vnode,
|
||||||
|
enumerable: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!('__vueParentComponent' in node)) {
|
||||||
|
Object.defineProperty(node, '__vueParentComponent', {
|
||||||
|
value: parentComponent,
|
||||||
|
enumerable: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (patchFlag === PatchFlags.BAIL) {
|
if (patchFlag === PatchFlags.BAIL) {
|
||||||
optimized = false
|
optimized = false
|
||||||
vnode.dynamicChildren = null
|
vnode.dynamicChildren = null
|
||||||
|
@ -145,18 +160,17 @@ export function createHydrationFunctions(
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case Comment:
|
case Comment:
|
||||||
if (domType !== DOMNodeTypes.COMMENT || isFragmentStart) {
|
if (isTemplateNode(node)) {
|
||||||
if ((node as Element).tagName.toLowerCase() === 'template') {
|
nextNode = nextSibling(node)
|
||||||
const content = (vnode.el! as HTMLTemplateElement).content
|
// wrapped <transition appear>
|
||||||
.firstChild!
|
// replace <template> node with inner child
|
||||||
|
replaceNode(
|
||||||
// replace <template> node with inner children
|
(vnode.el = node.content.firstChild!),
|
||||||
replaceNode(content, node, parentComponent)
|
node,
|
||||||
vnode.el = node = content
|
parentComponent
|
||||||
nextNode = nextSibling(node)
|
)
|
||||||
} else {
|
} else if (domType !== DOMNodeTypes.COMMENT || isFragmentStart) {
|
||||||
nextNode = onMismatch()
|
nextNode = onMismatch()
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
nextNode = nextSibling(node)
|
nextNode = nextSibling(node)
|
||||||
}
|
}
|
||||||
|
@ -209,7 +223,7 @@ export function createHydrationFunctions(
|
||||||
(domType !== DOMNodeTypes.ELEMENT ||
|
(domType !== DOMNodeTypes.ELEMENT ||
|
||||||
(vnode.type as string).toLowerCase() !==
|
(vnode.type as string).toLowerCase() !==
|
||||||
(node as Element).tagName.toLowerCase()) &&
|
(node as Element).tagName.toLowerCase()) &&
|
||||||
!isTemplateNode(node as Element)
|
!isTemplateNode(node)
|
||||||
) {
|
) {
|
||||||
nextNode = onMismatch()
|
nextNode = onMismatch()
|
||||||
} else {
|
} else {
|
||||||
|
@ -322,24 +336,28 @@ export function createHydrationFunctions(
|
||||||
const { type, props, patchFlag, shapeFlag, dirs, transition } = vnode
|
const { type, props, patchFlag, shapeFlag, dirs, transition } = vnode
|
||||||
// #4006 for form elements with non-string v-model value bindings
|
// #4006 for form elements with non-string v-model value bindings
|
||||||
// e.g. <option :value="obj">, <input type="checkbox" :true-value="1">
|
// e.g. <option :value="obj">, <input type="checkbox" :true-value="1">
|
||||||
const forcePatchValue = (type === 'input' && dirs) || type === 'option'
|
// #7476 <input indeterminate>
|
||||||
|
const forcePatch = type === 'input' || type === 'option'
|
||||||
// skip props & children if this is hoisted static nodes
|
// skip props & children if this is hoisted static nodes
|
||||||
// #5405 in dev, always hydrate children for HMR
|
// #5405 in dev, always hydrate children for HMR
|
||||||
if (__DEV__ || forcePatchValue || patchFlag !== PatchFlags.HOISTED) {
|
if (__DEV__ || forcePatch || patchFlag !== PatchFlags.HOISTED) {
|
||||||
if (dirs) {
|
if (dirs) {
|
||||||
invokeDirectiveHook(vnode, null, parentComponent, 'created')
|
invokeDirectiveHook(vnode, null, parentComponent, 'created')
|
||||||
}
|
}
|
||||||
// props
|
// props
|
||||||
if (props) {
|
if (props) {
|
||||||
if (
|
if (
|
||||||
forcePatchValue ||
|
forcePatch ||
|
||||||
!optimized ||
|
!optimized ||
|
||||||
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.HYDRATE_EVENTS)
|
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
|
||||||
) {
|
) {
|
||||||
for (const key in props) {
|
for (const key in props) {
|
||||||
if (
|
if (
|
||||||
(forcePatchValue && key.endsWith('value')) ||
|
(forcePatch &&
|
||||||
(isOn(key) && !isReservedProp(key))
|
(key.endsWith('value') || key === 'indeterminate')) ||
|
||||||
|
(isOn(key) && !isReservedProp(key)) ||
|
||||||
|
// force hydrate v-bind with .prop modifiers
|
||||||
|
key[0] === '.'
|
||||||
) {
|
) {
|
||||||
patchProp(
|
patchProp(
|
||||||
el,
|
el,
|
||||||
|
@ -564,8 +582,8 @@ export function createHydrationFunctions(
|
||||||
node.nodeType === DOMNodeTypes.TEXT
|
node.nodeType === DOMNodeTypes.TEXT
|
||||||
? `(text)`
|
? `(text)`
|
||||||
: isComment(node) && node.data === '['
|
: isComment(node) && node.data === '['
|
||||||
? `(start of fragment)`
|
? `(start of fragment)`
|
||||||
: ``
|
: ``
|
||||||
)
|
)
|
||||||
vnode.el = null
|
vnode.el = null
|
||||||
|
|
||||||
|
@ -637,17 +655,16 @@ export function createHydrationFunctions(
|
||||||
let parent = parentComponent
|
let parent = parentComponent
|
||||||
while (parent) {
|
while (parent) {
|
||||||
if (parent.vnode.el === oldNode) {
|
if (parent.vnode.el === oldNode) {
|
||||||
parent.vnode.el = newNode
|
parent.vnode.el = parent.subTree.el = newNode
|
||||||
parent.subTree.el = newNode
|
|
||||||
}
|
}
|
||||||
parent = parent.parent
|
parent = parent.parent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTemplateNode = (node: Element): boolean => {
|
const isTemplateNode = (node: Node): node is HTMLTemplateElement => {
|
||||||
return (
|
return (
|
||||||
node.nodeType === DOMNodeTypes.ELEMENT &&
|
node.nodeType === DOMNodeTypes.ELEMENT &&
|
||||||
node.tagName.toLowerCase() === 'template'
|
(node as Element).tagName.toLowerCase() === 'template'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2401,7 +2401,7 @@ export function traverseStaticChildren(n1: VNode, n2: VNode, shallow = false) {
|
||||||
const c1 = ch1[i] as VNode
|
const c1 = ch1[i] as VNode
|
||||||
let c2 = ch2[i] as VNode
|
let c2 = ch2[i] as VNode
|
||||||
if (c2.shapeFlag & ShapeFlags.ELEMENT && !c2.dynamicChildren) {
|
if (c2.shapeFlag & ShapeFlags.ELEMENT && !c2.dynamicChildren) {
|
||||||
if (c2.patchFlag <= 0 || c2.patchFlag === PatchFlags.HYDRATE_EVENTS) {
|
if (c2.patchFlag <= 0 || c2.patchFlag === PatchFlags.NEED_HYDRATION) {
|
||||||
c2 = ch2[i] = cloneIfMounted(ch2[i] as VNode)
|
c2 = ch2[i] = cloneIfMounted(ch2[i] as VNode)
|
||||||
c2.el = c1.el
|
c2.el = c1.el
|
||||||
}
|
}
|
||||||
|
|
|
@ -488,7 +488,7 @@ function createBaseVNode(
|
||||||
(vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
|
(vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
|
||||||
// the EVENTS flag is only for hydration and if it is the only flag, the
|
// the EVENTS flag is only for hydration and if it is the only flag, the
|
||||||
// vnode should not be considered dynamic due to handler caching.
|
// vnode should not be considered dynamic due to handler caching.
|
||||||
vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
|
vnode.patchFlag !== PatchFlags.NEED_HYDRATION
|
||||||
) {
|
) {
|
||||||
currentBlock.push(vnode)
|
currentBlock.push(vnode)
|
||||||
}
|
}
|
||||||
|
@ -573,20 +573,20 @@ function _createVNode(
|
||||||
const shapeFlag = isString(type)
|
const shapeFlag = isString(type)
|
||||||
? ShapeFlags.ELEMENT
|
? ShapeFlags.ELEMENT
|
||||||
: __FEATURE_SUSPENSE__ && isSuspense(type)
|
: __FEATURE_SUSPENSE__ && isSuspense(type)
|
||||||
? ShapeFlags.SUSPENSE
|
? ShapeFlags.SUSPENSE
|
||||||
: isTeleport(type)
|
: isTeleport(type)
|
||||||
? ShapeFlags.TELEPORT
|
? ShapeFlags.TELEPORT
|
||||||
: isObject(type)
|
: isObject(type)
|
||||||
? ShapeFlags.STATEFUL_COMPONENT
|
? ShapeFlags.STATEFUL_COMPONENT
|
||||||
: isFunction(type)
|
: isFunction(type)
|
||||||
? ShapeFlags.FUNCTIONAL_COMPONENT
|
? ShapeFlags.FUNCTIONAL_COMPONENT
|
||||||
: 0
|
: 0
|
||||||
|
|
||||||
if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
|
if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
|
||||||
type = toRaw(type)
|
type = toRaw(type)
|
||||||
warn(
|
warn(
|
||||||
`Vue received a Component which was made a reactive object. This can ` +
|
`Vue received a Component that was made a reactive object. This can ` +
|
||||||
`lead to unnecessary performance overhead, and should be avoided by ` +
|
`lead to unnecessary performance overhead and should be avoided by ` +
|
||||||
`marking the component with \`markRaw\` or using \`shallowRef\` ` +
|
`marking the component with \`markRaw\` or using \`shallowRef\` ` +
|
||||||
`instead of \`ref\`.`,
|
`instead of \`ref\`.`,
|
||||||
`\nComponent that was made reactive: `,
|
`\nComponent that was made reactive: `,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue