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
|
||||
|
||||
- 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.
|
||||
|
||||
|
|
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
|
||||
|
||||
- name: Set node version to 18
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
|
@ -30,4 +30,4 @@ jobs:
|
|||
- name: Run prettier
|
||||
run: pnpm run format
|
||||
|
||||
- uses: autofix-ci/action@d3e591514b99d0fca6779455ff8338516663f7cc
|
||||
- uses: autofix-ci/action@bee19d72e71787c12ca0f29de72f2833e437e4c9
|
||||
|
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Set node version to 18
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
@ -45,7 +45,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
@ -74,7 +74,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
@ -97,7 +97,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -9,7 +9,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'vuejs/core' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run')
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const user = context.payload.sender.login
|
||||
|
@ -43,7 +43,7 @@ jobs:
|
|||
})
|
||||
throw new Error('not allowed')
|
||||
}
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
id: get-pr-data
|
||||
with:
|
||||
script: |
|
||||
|
@ -58,7 +58,7 @@ jobs:
|
|||
branchName: pr.head.ref,
|
||||
repo: pr.head.repo.full_name
|
||||
}
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
id: trigger
|
||||
env:
|
||||
COMMENT: ${{ github.event.comment.body }}
|
||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
if: github.repository == 'vuejs/core'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-inactive-days: '14'
|
||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: pnpm
|
||||
|
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ Vue.js is an MIT-licensed open source project with its ongoing development made
|
|||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://vuejs.org/sponsor/#current-sponsors">
|
||||
<img alt="sponsors" src="https://sponsors.vuejs.org/sponsors.svg?v2">
|
||||
<img alt="sponsors" src="https://sponsors.vuejs.org/sponsors.svg?v3">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
|
32
package.json
32
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.4.0-alpha.1",
|
||||
"packageManager": "pnpm@8.9.2",
|
||||
"packageManager": "pnpm@8.10.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
|
@ -27,9 +27,9 @@
|
|||
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
||||
"dev-compiler": "run-p \"dev template-explorer\" serve",
|
||||
"dev-sfc": "run-s dev-sfc-prepare dev-sfc-run",
|
||||
"dev-sfc-prepare": "node scripts/pre-dev-sfc.js || npm run build-compiler-cjs",
|
||||
"dev-sfc-prepare": "node scripts/pre-dev-sfc.js || npm run build-all-cjs",
|
||||
"dev-sfc-serve": "vite packages/sfc-playground --host",
|
||||
"dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
|
||||
"dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev vue -ipf esm-browser-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
|
||||
"serve": "serve",
|
||||
"open": "open http://localhost:3000/packages/template-explorer/local.html",
|
||||
"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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@babel/parser": "^7.23.3",
|
||||
"@babel/types": "^7.23.3",
|
||||
"@rollup/plugin-alias": "^5.0.1",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-json": "^6.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "^5.0.4",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/hash-sum": "^1.0.1",
|
||||
"@types/node": "^20.8.7",
|
||||
"@typescript-eslint/parser": "^6.8.0",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/node": "^20.9.2",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"@vitest/coverage-istanbul": "^0.34.6",
|
||||
"@vue/consolidate": "0.17.3",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.19.5",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^8.52.0",
|
||||
"eslint-plugin-jest": "^27.4.3",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-jest": "^27.6.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"execa": "^8.0.1",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^15.0.2",
|
||||
"lint-staged": "^15.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.5",
|
||||
"markdown-table": "^3.0.3",
|
||||
"marked": "^9.1.2",
|
||||
"marked": "^9.1.6",
|
||||
"minimist": "^1.2.8",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"picocolors": "^1.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier": "^3.1.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.2",
|
||||
"puppeteer": "~21.4.0",
|
||||
"puppeteer": "~21.5.1",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^4.1.4",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
|
@ -102,9 +102,9 @@
|
|||
"terser": "^5.22.0",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.6.2",
|
||||
"tsx": "^3.14.0",
|
||||
"tsx": "^4.1.4",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.5.0",
|
||||
"vite": "^5.0.0",
|
||||
"vitest": "^0.34.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`compiler: expression transform > bindingMetadata > inline mode 1`] = `
|
||||
"(_ctx, _cache) => {
|
||||
return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString(__props.props) + \\" \\" + _toDisplayString(_unref(setup)) + \\" \\" + _toDisplayString(setupConst) + \\" \\" + _toDisplayString(_ctx.data) + \\" \\" + _toDisplayString(_ctx.options), 1 /* TEXT */))
|
||||
return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString(__props.props) + \\" \\" + _toDisplayString(_unref(setup)) + \\" \\" + _toDisplayString(setupConst) + \\" \\" + _toDisplayString(_ctx.data) + \\" \\" + _toDisplayString(_ctx.options) + \\" \\" + _toDisplayString(isNaN.value), 1 /* TEXT */))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -10,6 +10,48 @@ exports[`compiler: expression transform > bindingMetadata > non-inline mode 1`]
|
|||
"const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString($props.props) + \\" \\" + _toDisplayString($setup.setup) + \\" \\" + _toDisplayString($data.data) + \\" \\" + _toDisplayString($options.options), 1 /* TEXT */))
|
||||
return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString($props.props) + \\" \\" + _toDisplayString($setup.setup) + \\" \\" + _toDisplayString($data.data) + \\" \\" + _toDisplayString($options.options) + \\" \\" + _toDisplayString($setup.isNaN), 1 /* TEXT */))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: expression transform > 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\\", {
|
||||
\\"foo-value\\": model,
|
||||
\\"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)
|
||||
const { node } = parseWithElementTransform(`<div @click="foo" />`, {
|
||||
directiveTransforms: {
|
||||
|
@ -1108,12 +1108,24 @@ describe('compiler: element transform', () => {
|
|||
}
|
||||
)
|
||||
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
|
||||
test('HYDRATE_EVENTS on dynamic component', () => {
|
||||
test('NEED_HYDRATION on dynamic component', () => {
|
||||
const { node } = parseWithElementTransform(
|
||||
`<component :is="foo" @input="foo" />`,
|
||||
{
|
||||
|
@ -1123,7 +1135,7 @@ describe('compiler: element transform', () => {
|
|||
}
|
||||
)
|
||||
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,
|
||||
options: BindingTypes.OPTIONS,
|
||||
reactive: BindingTypes.SETUP_REACTIVE_CONST,
|
||||
literal: BindingTypes.LITERAL_CONST
|
||||
literal: BindingTypes.LITERAL_CONST,
|
||||
isNaN: BindingTypes.SETUP_REF
|
||||
}
|
||||
|
||||
function compileWithBindingMetadata(
|
||||
|
@ -522,19 +523,56 @@ describe('compiler: expression transform', () => {
|
|||
|
||||
test('non-inline mode', () => {
|
||||
const { code } = compileWithBindingMetadata(
|
||||
`<div>{{ props }} {{ setup }} {{ data }} {{ options }}</div>`
|
||||
`<div>{{ props }} {{ setup }} {{ data }} {{ options }} {{ isNaN }}</div>`
|
||||
)
|
||||
expect(code).toMatch(`$props.props`)
|
||||
expect(code).toMatch(`$setup.setup`)
|
||||
expect(code).toMatch(`$setup.isNaN`)
|
||||
expect(code).toMatch(`$data.data`)
|
||||
expect(code).toMatch(`$options.options`)
|
||||
expect(code).toMatch(`_ctx, _cache, $props, $setup, $data, $options`)
|
||||
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', () => {
|
||||
const { code } = compileWithBindingMetadata(
|
||||
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }}</div>`,
|
||||
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}</div>`,
|
||||
{ inline: true }
|
||||
)
|
||||
expect(code).toMatch(`__props.props`)
|
||||
|
@ -542,6 +580,7 @@ describe('compiler: expression transform', () => {
|
|||
expect(code).toMatch(`_toDisplayString(setupConst)`)
|
||||
expect(code).toMatch(`_ctx.data`)
|
||||
expect(code).toMatch(`_ctx.options`)
|
||||
expect(code).toMatch(`isNaN.value`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
|
|
|
@ -674,8 +674,8 @@ describe('compiler: v-for', () => {
|
|||
patchFlag: !disableTracking
|
||||
? genFlagText(PatchFlags.STABLE_FRAGMENT)
|
||||
: keyed
|
||||
? genFlagText(PatchFlags.KEYED_FRAGMENT)
|
||||
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
? genFlagText(PatchFlags.KEYED_FRAGMENT)
|
||||
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
children: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
|
|
|
@ -32,12 +32,12 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/shared": "3.4.0-alpha.1",
|
||||
"@babel/parser": "^7.23.3",
|
||||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.23.0"
|
||||
"@babel/types": "^7.23.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,6 +165,19 @@ export function walkBlockDeclarations(
|
|||
) {
|
||||
if (stmt.declare || !stmt.id) continue
|
||||
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'
|
||||
? RESOLVE_FILTER
|
||||
: type === 'component'
|
||||
? RESOLVE_COMPONENT
|
||||
: RESOLVE_DIRECTIVE
|
||||
? RESOLVE_COMPONENT
|
||||
: RESOLVE_DIRECTIVE
|
||||
)
|
||||
for (let i = 0; i < assets.length; i++) {
|
||||
let id = assets[i]
|
||||
|
|
|
@ -41,8 +41,8 @@ export function getBaseTransformPreset(
|
|||
transformExpression
|
||||
]
|
||||
: __BROWSER__ && __DEV__
|
||||
? [transformExpression]
|
||||
: []),
|
||||
? [transformExpression]
|
||||
: []),
|
||||
transformSlotOutlet,
|
||||
transformElement,
|
||||
trackSlotScopes,
|
||||
|
|
|
@ -811,8 +811,8 @@ function parseAttribute(
|
|||
(isPropShorthand || startsWith(name, ':')
|
||||
? 'bind'
|
||||
: startsWith(name, '@')
|
||||
? 'on'
|
||||
: 'slot')
|
||||
? 'on'
|
||||
: 'slot')
|
||||
let arg: ExpressionNode | undefined
|
||||
|
||||
if (match[2]) {
|
||||
|
@ -1063,7 +1063,7 @@ function parseTextData(
|
|||
) {
|
||||
return rawText
|
||||
} else {
|
||||
// DATA or RCDATA containing "&"". Entity decoding required.
|
||||
// DATA or RCDATA containing "&". Entity decoding is required.
|
||||
return context.options.decodeEntities(
|
||||
rawText,
|
||||
mode === TextModes.ATTRIBUTE_VALUE
|
||||
|
|
|
@ -238,8 +238,8 @@ export function createTransformContext(
|
|||
const removalIndex = node
|
||||
? list.indexOf(node)
|
||||
: context.currentNode
|
||||
? context.childIndex
|
||||
: -1
|
||||
? context.childIndex
|
||||
: -1
|
||||
/* istanbul ignore if */
|
||||
if (__DEV__ && removalIndex < 0) {
|
||||
throw new Error(`node being removed is not a child of current parent`)
|
||||
|
|
|
@ -550,7 +550,7 @@ export function buildProps(
|
|||
)
|
||||
} else {
|
||||
// directives
|
||||
const { name, arg, exp, loc } = prop
|
||||
const { name, arg, exp, loc, modifiers } = prop
|
||||
const isVBind = name === 'bind'
|
||||
const isVOn = name === 'on'
|
||||
|
||||
|
@ -678,6 +678,11 @@ export function buildProps(
|
|||
continue
|
||||
}
|
||||
|
||||
// force hydration for v-bind with .prop modifier
|
||||
if (isVBind && modifiers.includes('prop')) {
|
||||
patchFlag |= PatchFlags.NEED_HYDRATION
|
||||
}
|
||||
|
||||
const directiveTransform = context.directiveTransforms[name]
|
||||
if (directiveTransform) {
|
||||
// has built-in directive transform.
|
||||
|
@ -743,12 +748,12 @@ export function buildProps(
|
|||
patchFlag |= PatchFlags.PROPS
|
||||
}
|
||||
if (hasHydrationEventBinding) {
|
||||
patchFlag |= PatchFlags.HYDRATE_EVENTS
|
||||
patchFlag |= PatchFlags.NEED_HYDRATION
|
||||
}
|
||||
}
|
||||
if (
|
||||
!shouldUseBlock &&
|
||||
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
|
||||
(patchFlag === 0 || patchFlag === PatchFlags.NEED_HYDRATION) &&
|
||||
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
|
||||
) {
|
||||
patchFlag |= PatchFlags.NEED_PATCH
|
||||
|
|
|
@ -227,10 +227,15 @@ export function processExpression(
|
|||
const isScopeVarReference = context.identifiers[rawExp]
|
||||
const isAllowedGlobal = isGloballyAllowed(rawExp)
|
||||
const isLiteral = isLiteralWhitelisted(rawExp)
|
||||
if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) {
|
||||
if (
|
||||
!asParams &&
|
||||
!isScopeVarReference &&
|
||||
!isLiteral &&
|
||||
(!isAllowedGlobal || bindingMetadata[rawExp])
|
||||
) {
|
||||
// const bindings exposed from setup can be skipped for patching but
|
||||
// cannot be hoisted to module scope
|
||||
if (isConst(bindingMetadata[node.content])) {
|
||||
if (isConst(bindingMetadata[rawExp])) {
|
||||
node.constType = ConstantTypes.CAN_SKIP_PATCH
|
||||
}
|
||||
node.content = rewriteIdentifier(rawExp)
|
||||
|
|
|
@ -37,7 +37,8 @@ import {
|
|||
isTemplateNode,
|
||||
isSlotOutlet,
|
||||
injectProp,
|
||||
findDir
|
||||
findDir,
|
||||
forAliasRE
|
||||
} from '../utils'
|
||||
import {
|
||||
RENDER_LIST,
|
||||
|
@ -94,8 +95,8 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
const fragmentFlag = isStableFragment
|
||||
? PatchFlags.STABLE_FRAGMENT
|
||||
: keyProp
|
||||
? PatchFlags.KEYED_FRAGMENT
|
||||
: PatchFlags.UNKEYED_FRAGMENT
|
||||
? PatchFlags.KEYED_FRAGMENT
|
||||
: PatchFlags.UNKEYED_FRAGMENT
|
||||
|
||||
forNode.codegenNode = createVNodeCall(
|
||||
context,
|
||||
|
@ -140,10 +141,10 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
const slotOutlet = isSlotOutlet(node)
|
||||
? node
|
||||
: isTemplate &&
|
||||
node.children.length === 1 &&
|
||||
isSlotOutlet(node.children[0])
|
||||
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
|
||||
: null
|
||||
node.children.length === 1 &&
|
||||
isSlotOutlet(node.children[0])
|
||||
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
|
||||
: null
|
||||
|
||||
if (slotOutlet) {
|
||||
// <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,
|
||||
// but those do not make sense in the first place, so this works in practice.
|
||||
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
||||
|
|
|
@ -349,8 +349,8 @@ export function buildSlots(
|
|||
const slotFlag = hasDynamicSlots
|
||||
? SlotFlags.DYNAMIC
|
||||
: hasForwardedSlots(node.children)
|
||||
? SlotFlags.FORWARDED
|
||||
: SlotFlags.STABLE
|
||||
? SlotFlags.FORWARDED
|
||||
: SlotFlags.STABLE
|
||||
|
||||
let slots = createObjectExpression(
|
||||
slotsProperties.concat(
|
||||
|
|
|
@ -519,3 +519,5 @@ export function getMemoedVNodeCall(node: BlockCodegenNode | MemoExpression) {
|
|||
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 {
|
||||
return new Array(n)
|
||||
.fill(0)
|
||||
.map(() => code)
|
||||
.join('')
|
||||
return code.repeat(n)
|
||||
}
|
||||
|
||||
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', () => {
|
||||
|
|
|
@ -272,7 +272,7 @@ describe('compiler-dom: transform v-on', () => {
|
|||
// should not treat cached handler as dynamicProp, so it should have no
|
||||
// dynamicProps flags and only the hydration flag
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
genFlagText(PatchFlags.HYDRATE_EVENTS)
|
||||
genFlagText(PatchFlags.NEED_HYDRATION)
|
||||
)
|
||||
expect(prop).toMatchObject({
|
||||
key: {
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.4.0-alpha.1",
|
||||
"@vue/compiler-core": "3.4.0-alpha.1"
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/compiler-core": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,8 +153,8 @@ const isStringifiableAttr = (name: string, ns: DOMNamespaces) => {
|
|||
(ns === DOMNamespaces.HTML
|
||||
? isKnownHtmlAttr(name)
|
||||
: ns === DOMNamespaces.SVG
|
||||
? isKnownSvgAttr(name)
|
||||
: false) || dataAriaRE.test(name)
|
||||
? isKnownSvgAttr(name)
|
||||
: false) || dataAriaRE.test(name)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,9 @@ import {
|
|||
ElementTypes,
|
||||
findProp,
|
||||
NodeTypes,
|
||||
hasDynamicKeyVBind
|
||||
hasDynamicKeyVBind,
|
||||
findDir,
|
||||
isStaticArgOf
|
||||
} from '@vue/compiler-core'
|
||||
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
|
||||
import {
|
||||
|
@ -32,8 +34,8 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||
}
|
||||
|
||||
function checkDuplicatedValue() {
|
||||
const value = findProp(node, 'value')
|
||||
if (value) {
|
||||
const value = findDir(node, 'bind')
|
||||
if (value && isStaticArgOf(value.arg, 'value')) {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
|
||||
|
|
|
@ -96,14 +96,14 @@ const transformClick = (key: ExpressionNode, event: string) => {
|
|||
return isStaticClick
|
||||
? createSimpleExpression(event, true)
|
||||
: key.type !== NodeTypes.SIMPLE_EXPRESSION
|
||||
? createCompoundExpression([
|
||||
`(`,
|
||||
key,
|
||||
`) === "onClick" ? "${event}" : (`,
|
||||
key,
|
||||
`)`
|
||||
])
|
||||
: key
|
||||
? createCompoundExpression([
|
||||
`(`,
|
||||
key,
|
||||
`) === "onClick" ? "${event}" : (`,
|
||||
key,
|
||||
`)`
|
||||
])
|
||||
: key
|
||||
}
|
||||
|
||||
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.**
|
||||
|
||||
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
|
||||
|
||||
|
@ -77,4 +77,4 @@ export default script
|
|||
|
||||
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`] = `
|
||||
"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({
|
||||
setup(__props, { 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', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
import { FooBar, foo, bar, unused } from './x'
|
||||
import { FooBar, foo, bar, unused, baz } from './x'
|
||||
</script>
|
||||
<template>
|
||||
<FooBar #[foo.slotName] />
|
||||
<FooBar #unused />
|
||||
<div :[bar.attrName]="15"></div>
|
||||
<div unused="unused"></div>
|
||||
<div #[\`item:\${baz.key}\`]="{ value }"></div>
|
||||
</template>
|
||||
`)
|
||||
expect(content).toMatch(
|
||||
`return { get FooBar() { return FooBar }, get foo() { return foo }, ` +
|
||||
`get bar() { return bar } }`
|
||||
`get bar() { return bar }, get baz() { return baz } }`
|
||||
)
|
||||
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`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
interface Emits { (e: 'foo' | 'bar'): void }
|
||||
|
|
|
@ -46,6 +46,51 @@ export default /*#__PURE__*/_defineComponent({
|
|||
|
||||
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 { }
|
||||
}
|
||||
|
||||
|
@ -232,6 +277,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
alias: { type: Array, required: true },
|
||||
method: { type: Function, required: true },
|
||||
symbol: { type: Symbol, required: true },
|
||||
error: { type: Error, required: true },
|
||||
extract: { type: Number, required: true },
|
||||
exclude: { type: [Number, Boolean], required: true },
|
||||
uppercase: { type: String, required: true },
|
||||
|
|
|
@ -80,6 +80,18 @@ const emit = defineEmits(['a', 'b'])
|
|||
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)', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -97,6 +97,7 @@ const props = defineProps({ foo: String })
|
|||
alias: Alias
|
||||
method(): void
|
||||
symbol: symbol
|
||||
error: Error
|
||||
extract: Extract<1 | 2 | boolean, 2>
|
||||
exclude: Exclude<1 | 2 | boolean, 2>
|
||||
uppercase: Uppercase<'foo'>
|
||||
|
@ -143,6 +144,7 @@ const props = defineProps({ foo: String })
|
|||
expect(content).toMatch(`alias: { type: Array, required: true }`)
|
||||
expect(content).toMatch(`method: { type: Function, required: true }`)
|
||||
expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
|
||||
expect(content).toMatch(`error: { type: Error, required: true }`)
|
||||
expect(content).toMatch(
|
||||
`objectOrFn: { type: [Function, Object], required: true },`
|
||||
)
|
||||
|
@ -198,6 +200,7 @@ const props = defineProps({ foo: String })
|
|||
alias: BindingTypes.PROPS,
|
||||
method: BindingTypes.PROPS,
|
||||
symbol: BindingTypes.PROPS,
|
||||
error: BindingTypes.PROPS,
|
||||
objectOrFn: BindingTypes.PROPS,
|
||||
extract: BindingTypes.PROPS,
|
||||
exclude: BindingTypes.PROPS,
|
||||
|
@ -608,4 +611,103 @@ const props = defineProps({ foo: String })
|
|||
}).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', () => {
|
||||
const files = {
|
||||
'C:\\Test\\foo.ts': 'export type P = { foo: number }',
|
||||
'C:\\Test\\bar.d.ts':
|
||||
'C:\\Test\\FolderA\\foo.ts': 'export type P = { foo: number }',
|
||||
'C:\\Test\\FolderA\\bar.d.ts':
|
||||
'type X = { bar: string }; export { X as Y };' +
|
||||
// verify that we can parse syntax that is only valid in d.ts
|
||||
'export const baz: boolean'
|
||||
'export const baz: boolean',
|
||||
'C:\\Test\\FolderB\\buz.ts': 'export type Z = { buz: string }'
|
||||
}
|
||||
const { props, deps } = resolve(
|
||||
`
|
||||
import { P } from './foo'
|
||||
import { Y as PP } from './bar'
|
||||
defineProps<P & PP>()
|
||||
import { Z as PPP } from '../FolderB/buz'
|
||||
defineProps<P & PP & PPP>()
|
||||
`,
|
||||
files,
|
||||
{},
|
||||
'C:\\Test\\Test.vue'
|
||||
'C:\\Test\\FolderA\\Test.vue'
|
||||
)
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['Number'],
|
||||
bar: ['String']
|
||||
bar: ['String'],
|
||||
buz: ['String']
|
||||
})
|
||||
expect(deps && [...deps].map(normalize)).toStrictEqual(
|
||||
Object.keys(files).map(normalize)
|
||||
|
|
|
@ -32,27 +32,27 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/compiler-core": "3.4.0-alpha.1",
|
||||
"@vue/compiler-dom": "3.4.0-alpha.1",
|
||||
"@vue/compiler-ssr": "3.4.0-alpha.1",
|
||||
"@vue/reactivity-transform": "3.4.0-alpha.1",
|
||||
"@vue/shared": "3.4.0-alpha.1",
|
||||
"@babel/parser": "^7.23.3",
|
||||
"@vue/compiler-core": "workspace:*",
|
||||
"@vue/compiler-dom": "workspace:*",
|
||||
"@vue/compiler-ssr": "workspace:*",
|
||||
"@vue/reactivity-transform": "workspace:*",
|
||||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.5",
|
||||
"postcss": "^8.4.31",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.23.0",
|
||||
"@babel/types": "^7.23.3",
|
||||
"@vue/consolidate": "^0.17.3",
|
||||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "^10.0.1",
|
||||
"lru-cache": "^10.0.3",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "^9.0.3",
|
||||
"postcss-modules": "^4.3.1",
|
||||
"postcss-selector-parser": "^6.0.13",
|
||||
"pug": "^3.0.2",
|
||||
"sass": "^1.69.4"
|
||||
"sass": "^1.69.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1172,8 +1172,8 @@ function walkObjectPattern(
|
|||
const type = isDefineCall
|
||||
? BindingTypes.SETUP_CONST
|
||||
: isConst
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
registerBinding(bindings, p.key, type)
|
||||
} else {
|
||||
walkPattern(p.value, bindings, isConst, isDefineCall)
|
||||
|
@ -1208,8 +1208,8 @@ function walkPattern(
|
|||
const type = isDefineCall
|
||||
? BindingTypes.SETUP_CONST
|
||||
: isConst
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
registerBinding(bindings, node, type)
|
||||
} else if (node.type === 'RestElement') {
|
||||
// argument can only be identifier when destructuring
|
||||
|
@ -1224,8 +1224,8 @@ function walkPattern(
|
|||
const type = isDefineCall
|
||||
? BindingTypes.SETUP_CONST
|
||||
: isConst
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
? BindingTypes.SETUP_MAYBE_REF
|
||||
: BindingTypes.SETUP_LET
|
||||
registerBinding(bindings, node.left, type)
|
||||
} else {
|
||||
walkPattern(node.left, bindings, isConst)
|
||||
|
|
|
@ -124,8 +124,8 @@ export function compileTemplate(
|
|||
? preprocessCustomRequire
|
||||
? preprocessCustomRequire(preprocessLang)
|
||||
: __ESM_BROWSER__
|
||||
? undefined
|
||||
: consolidate[preprocessLang as keyof typeof consolidate]
|
||||
? undefined
|
||||
: consolidate[preprocessLang as keyof typeof consolidate]
|
||||
: false
|
||||
if (preprocessor) {
|
||||
try {
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
export const version = __VERSION__
|
||||
|
||||
// API
|
||||
export { parse, parseCache } from './parse'
|
||||
export { parse } from './parse'
|
||||
export { compileTemplate } from './compileTemplate'
|
||||
export { compileStyle, compileStyleAsync } from './compileStyle'
|
||||
export { compileScript } from './compileScript'
|
||||
export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault'
|
||||
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
|
||||
export {
|
||||
shouldTransform as shouldTransformRef,
|
||||
|
|
|
@ -164,7 +164,7 @@ export function resolveParserPlugins(
|
|||
}
|
||||
if (lang === 'ts' || lang === 'tsx') {
|
||||
plugins.push(['typescript', { dts }])
|
||||
if (!plugins.includes('decorators')) {
|
||||
if (!userPlugins || !userPlugins.includes('decorators')) {
|
||||
plugins.push('decorators-legacy')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
isCallOf,
|
||||
unwrapTSNode,
|
||||
toRuntimeTypeString,
|
||||
getEscapedKey
|
||||
getEscapedPropName
|
||||
} from './utils'
|
||||
import { genModelProps } from './defineModel'
|
||||
import { getObjectOrArrayExpressionKeys } from './analyzeScriptBindings'
|
||||
|
@ -139,7 +139,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
|
|||
const defaults: string[] = []
|
||||
for (const key in ctx.propsDestructuredBindings) {
|
||||
const d = genDestructuredDefaultValue(ctx, key)
|
||||
const finalKey = getEscapedKey(key)
|
||||
const finalKey = getEscapedPropName(key)
|
||||
if (d)
|
||||
defaults.push(
|
||||
`${finalKey}: ${d.valueString}${
|
||||
|
@ -257,7 +257,7 @@ function genRuntimePropFromType(
|
|||
}
|
||||
}
|
||||
|
||||
const finalKey = getEscapedKey(key)
|
||||
const finalKey = getEscapedPropName(key)
|
||||
if (!ctx.options.isProd) {
|
||||
return `${finalKey}: { ${concatStrings([
|
||||
`type: ${toRuntimeTypeString(type)}`,
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
NodeTypes,
|
||||
SimpleExpressionNode,
|
||||
createRoot,
|
||||
forAliasRE,
|
||||
parserOptions,
|
||||
transform,
|
||||
walkIdentifiers
|
||||
|
@ -50,12 +51,14 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
|||
if (!isBuiltInDirective(prop.name)) {
|
||||
code += `,v${capitalize(camelize(prop.name))}`
|
||||
}
|
||||
|
||||
// process dynamic directive arguments
|
||||
if (prop.arg && !(prop.arg as SimpleExpressionNode).isStatic) {
|
||||
code += `,${processExp(
|
||||
(prop.arg as SimpleExpressionNode).content,
|
||||
prop.name
|
||||
code += `,${stripStrings(
|
||||
(prop.arg as SimpleExpressionNode).content
|
||||
)}`
|
||||
}
|
||||
|
||||
if (prop.exp) {
|
||||
code += `,${processExp(
|
||||
(prop.exp as SimpleExpressionNode).content,
|
||||
|
@ -85,8 +88,6 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
|||
return code
|
||||
}
|
||||
|
||||
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
||||
|
||||
function processExp(exp: string, dir?: string): string {
|
||||
if (/ as\s+\w|<.*>|:/.test(exp)) {
|
||||
if (dir === 'slot') {
|
||||
|
|
|
@ -39,8 +39,9 @@ import { parse as babelParse } from '@babel/parser'
|
|||
import { parse } from '../parse'
|
||||
import { createCache } from '../cache'
|
||||
import type TS from 'typescript'
|
||||
import { extname, dirname } from 'path'
|
||||
import { extname, dirname, join } from 'path'
|
||||
import { minimatch as isMatch } from 'minimatch'
|
||||
import * as process from 'process'
|
||||
|
||||
export type SimpleTypeResolveOptions = Partial<
|
||||
Pick<
|
||||
|
@ -356,12 +357,15 @@ function resolveInterfaceMembers(
|
|||
continue
|
||||
}
|
||||
try {
|
||||
const { props } = resolveTypeElements(ctx, ext, scope)
|
||||
const { props, calls } = resolveTypeElements(ctx, ext, scope)
|
||||
for (const key in props) {
|
||||
if (!hasOwn(base.props, key)) {
|
||||
base.props[key] = props[key]
|
||||
}
|
||||
}
|
||||
if (calls) {
|
||||
;(base.calls || (base.calls = [])).push(...calls)
|
||||
}
|
||||
} catch (e) {
|
||||
ctx.error(
|
||||
`Failed to resolve extends base type.\nIf this previously worked in 3.2, ` +
|
||||
|
@ -653,8 +657,8 @@ function innerResolveTypeReference(
|
|||
? scope.exportedDeclares
|
||||
: scope.declares
|
||||
: onlyExported
|
||||
? scope.exportedTypes
|
||||
: scope.types
|
||||
? scope.exportedTypes
|
||||
: scope.types
|
||||
if (lookupSource[name]) {
|
||||
return lookupSource[name]
|
||||
} else {
|
||||
|
@ -697,10 +701,10 @@ function getReferenceName(node: ReferenceTypes): string | string[] {
|
|||
node.type === 'TSTypeReference'
|
||||
? node.typeName
|
||||
: node.type === 'TSExpressionWithTypeArguments'
|
||||
? node.expression
|
||||
: node.type === 'TSImportType'
|
||||
? node.qualifier
|
||||
: node.exprName
|
||||
? node.expression
|
||||
: node.type === 'TSImportType'
|
||||
? node.qualifier
|
||||
: node.exprName
|
||||
if (ref?.type === 'Identifier') {
|
||||
return ref.name
|
||||
} else if (ref?.type === 'TSQualifiedName') {
|
||||
|
@ -798,7 +802,12 @@ function importSourceToScope(
|
|||
|
||||
let resolved: string | undefined = scope.resolvedImportSources[source]
|
||||
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
|
||||
const filename = joinPaths(dirname(scope.filename), source)
|
||||
resolved = resolveExt(filename, fs)
|
||||
|
@ -1069,8 +1078,8 @@ function ctxToScope(ctx: TypeResolveContext): TypeScope {
|
|||
'ast' in ctx
|
||||
? ctx.ast
|
||||
: ctx.scriptAst
|
||||
? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body]
|
||||
: ctx.scriptSetupAst!.body
|
||||
? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body]
|
||||
: ctx.scriptSetupAst!.body
|
||||
|
||||
const scope = new TypeScope(
|
||||
ctx.filename,
|
||||
|
@ -1413,6 +1422,7 @@ export function inferRuntimeType(
|
|||
case 'WeakMap':
|
||||
case 'Date':
|
||||
case 'Promise':
|
||||
case 'Error':
|
||||
return [node.typeName.name]
|
||||
|
||||
// TS built-in utility types
|
||||
|
|
|
@ -76,8 +76,8 @@ export function getId(node: Expression) {
|
|||
return node.type === 'Identifier'
|
||||
? node.name
|
||||
: node.type === 'StringLiteral'
|
||||
? node.value
|
||||
: null
|
||||
? node.value
|
||||
: null
|
||||
}
|
||||
|
||||
const identity = (str: string) => str
|
||||
|
@ -113,8 +113,14 @@ export const joinPaths = (path.posix || path).join
|
|||
* key may contain symbols
|
||||
* e.g. onUpdate:modelValue -> "onUpdate:modelValue"
|
||||
*/
|
||||
export const escapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
|
||||
export const propNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~\-]/
|
||||
|
||||
export function getEscapedKey(key: string) {
|
||||
return escapeSymbolsRE.test(key) ? JSON.stringify(key) : key
|
||||
export function getEscapedPropName(key: string) {
|
||||
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
|
||||
} from '@vue/compiler-dom'
|
||||
import { SFCDescriptor } from '../parse'
|
||||
import { escapeSymbolsRE } from '../script/utils'
|
||||
import { getEscapedCssVarName } from '../script/utils'
|
||||
import { PluginCreator } from 'postcss'
|
||||
import hash from 'hash-sum'
|
||||
|
||||
|
@ -32,7 +32,7 @@ function genVarName(id: string, raw: string, isProd: boolean): string {
|
|||
return hash(id + raw)
|
||||
} else {
|
||||
// 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",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.4.0-alpha.1",
|
||||
"@vue/compiler-dom": "3.4.0-alpha.1"
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/compiler-dom": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ export function compile(
|
|||
// reusing core v-bind
|
||||
bind: transformBind,
|
||||
on: transformOn,
|
||||
// model and show has dedicated SSR handling
|
||||
// model and show have dedicated SSR handling
|
||||
model: ssrTransformModel,
|
||||
show: ssrTransformShow,
|
||||
// the following are ignored during SSR
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "@vue/dts-built-test",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"types": "dist/dts-built-test.d.ts",
|
||||
"dependencies": {
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/reactivity": "workspace:*",
|
||||
"vue": "workspace:*"
|
||||
},
|
||||
"version": "3.4.0-alpha.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1472,6 +1472,31 @@ describe('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 {
|
||||
DefineComponent,
|
||||
ComponentOptionsMixin,
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { defineCustomElement } from 'vue'
|
||||
import { expectType, describe } from './utils'
|
||||
import {
|
||||
defineCustomElement,
|
||||
defineComponent,
|
||||
type VueElementConstructor
|
||||
} from 'vue'
|
||||
import { expectType, describe, test } from './utils'
|
||||
|
||||
describe('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",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"vue": "workspace:*",
|
||||
"@vue/dts-built-test": "workspace:*"
|
||||
},
|
||||
"version": "3.4.0-alpha.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,10 @@ import {
|
|||
MaybeRef,
|
||||
MaybeRefOrGetter,
|
||||
ComputedRef,
|
||||
computed
|
||||
computed,
|
||||
ShallowRef
|
||||
} from 'vue'
|
||||
import { expectType, describe } from './utils'
|
||||
import { expectType, describe, IsUnion } from './utils'
|
||||
|
||||
function plainType(arg: number | Ref<number>) {
|
||||
// ref coercing
|
||||
|
@ -174,6 +175,27 @@ if (refStatus.value === 'initial') {
|
|||
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
|
||||
const r1 = reactive({
|
||||
k: 'v'
|
||||
|
|
|
@ -8,7 +8,8 @@ import {
|
|||
defineSlots,
|
||||
VNode,
|
||||
Ref,
|
||||
defineModel
|
||||
defineModel,
|
||||
toRefs
|
||||
} from 'vue'
|
||||
import { describe, expectType } from './utils'
|
||||
import { defineComponent } from 'vue'
|
||||
|
@ -20,6 +21,7 @@ describe('defineProps w/ type declaration', () => {
|
|||
foo: string
|
||||
bool?: boolean
|
||||
boolAndUndefined: boolean | undefined
|
||||
file?: File | File[]
|
||||
}>()
|
||||
// explicitly declared type should be refined
|
||||
expectType<string>(props.foo)
|
||||
|
@ -108,6 +110,7 @@ describe('defineProps w/ generic type declaration + withDefaults', <T extends
|
|||
defineProps<{
|
||||
n?: number
|
||||
bool?: boolean
|
||||
s?: string
|
||||
|
||||
generic1?: T[] | { x: T }
|
||||
generic2?: { x: T }
|
||||
|
@ -126,6 +129,10 @@ describe('defineProps w/ generic type declaration + withDefaults', <T extends
|
|||
)
|
||||
|
||||
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<{ x: T }>(res.generic2)
|
||||
|
@ -328,3 +335,11 @@ describe('useSlots', () => {
|
|||
const slots = useSlots()
|
||||
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' }]]} />
|
||||
)
|
||||
|
||||
// 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
|
||||
;<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'
|
||||
|
||||
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",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/compiler-core": "3.4.0-alpha.1",
|
||||
"@vue/shared": "3.4.0-alpha.1",
|
||||
"@babel/parser": "^7.23.3",
|
||||
"@vue/compiler-core": "workspace:*",
|
||||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/types": "^7.23.0"
|
||||
"@babel/core": "^7.23.3",
|
||||
"@babel/types": "^7.23.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -448,8 +448,8 @@ export function transformAST(
|
|||
const keyStr = isString(key)
|
||||
? `'${key}'`
|
||||
: key
|
||||
? snip(key)
|
||||
: `'${nameId.name}'`
|
||||
? snip(key)
|
||||
: `'${nameId.name}'`
|
||||
const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``
|
||||
s.appendLeft(
|
||||
call.end! + offset,
|
||||
|
|
|
@ -275,6 +275,14 @@ describe('reactivity/readonly', () => {
|
|||
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)
|
||||
}
|
||||
})
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
||||
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', () => {
|
||||
|
@ -197,5 +203,11 @@ describe('reactivity/shallowReadonly', () => {
|
|||
).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",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.4.0-alpha.1"
|
||||
"@vue/shared": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,8 +109,8 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
|||
? shallowReadonlyMap
|
||||
: readonlyMap
|
||||
: shallow
|
||||
? shallowReactiveMap
|
||||
: reactiveMap
|
||||
? shallowReactiveMap
|
||||
: reactiveMap
|
||||
).get(target)
|
||||
) {
|
||||
return target
|
||||
|
|
|
@ -228,7 +228,11 @@ function createReadonlyMethod(type: TriggerOpTypes): Function {
|
|||
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
|
||||
: shallowInstrumentations
|
||||
: isReadonly
|
||||
? readonlyInstrumentations
|
||||
: mutableInstrumentations
|
||||
? readonlyInstrumentations
|
||||
: mutableInstrumentations
|
||||
|
||||
return (
|
||||
target: CollectionTypes,
|
||||
|
|
|
@ -138,24 +138,24 @@ type Builtin = Primitive | Function | Date | Error | RegExp
|
|||
export type DeepReadonly<T> = T extends Builtin
|
||||
? T
|
||||
: T extends Map<infer K, infer V>
|
||||
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
||||
: T extends ReadonlyMap<infer K, infer V>
|
||||
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
||||
: T extends WeakMap<infer K, infer V>
|
||||
? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
|
||||
: T extends Set<infer U>
|
||||
? ReadonlySet<DeepReadonly<U>>
|
||||
: T extends ReadonlySet<infer U>
|
||||
? ReadonlySet<DeepReadonly<U>>
|
||||
: T extends WeakSet<infer U>
|
||||
? WeakSet<DeepReadonly<U>>
|
||||
: T extends Promise<infer U>
|
||||
? Promise<DeepReadonly<U>>
|
||||
: T extends Ref<infer U>
|
||||
? Readonly<Ref<DeepReadonly<U>>>
|
||||
: T extends {}
|
||||
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
||||
: Readonly<T>
|
||||
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
||||
: T extends ReadonlyMap<infer K, infer V>
|
||||
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
||||
: T extends WeakMap<infer K, infer V>
|
||||
? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
|
||||
: T extends Set<infer U>
|
||||
? ReadonlySet<DeepReadonly<U>>
|
||||
: T extends ReadonlySet<infer U>
|
||||
? ReadonlySet<DeepReadonly<U>>
|
||||
: T extends WeakSet<infer U>
|
||||
? WeakSet<DeepReadonly<U>>
|
||||
: T extends Promise<infer U>
|
||||
? Promise<DeepReadonly<U>>
|
||||
: T extends Ref<infer U>
|
||||
? Readonly<Ref<DeepReadonly<U>>>
|
||||
: T extends {}
|
||||
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
||||
: Readonly<T>
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
activeEffect,
|
||||
shouldTrack,
|
||||
|
@ -128,9 +129,8 @@ export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }
|
|||
* @param value - The "inner value" for the shallow ref.
|
||||
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref}
|
||||
*/
|
||||
export function shallowRef<T extends object>(
|
||||
value: T
|
||||
): T extends Ref ? T : ShallowRef<T>
|
||||
export function shallowRef<T>(value: MaybeRef<T>): Ref<T> | ShallowRef<T>
|
||||
export function shallowRef<T extends Ref>(value: T): T
|
||||
export function shallowRef<T>(value: T): ShallowRef<T>
|
||||
export function shallowRef<T = any>(): ShallowRef<T | undefined>
|
||||
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.
|
||||
* @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
|
||||
}
|
||||
|
||||
|
@ -244,7 +244,7 @@ export function unref<T>(ref: MaybeRef<T>): T {
|
|||
* @param source - A getter, an existing ref, or a non-function value.
|
||||
* @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)
|
||||
}
|
||||
|
||||
|
@ -429,8 +429,8 @@ export function toRef<T>(
|
|||
): T extends () => infer R
|
||||
? Readonly<Ref<R>>
|
||||
: T extends Ref
|
||||
? T
|
||||
: Ref<UnwrapRef<T>>
|
||||
? T
|
||||
: Ref<UnwrapRef<T>>
|
||||
export function toRef<T extends object, K extends keyof T>(
|
||||
object: T,
|
||||
key: K
|
||||
|
@ -491,17 +491,17 @@ export type ShallowUnwrapRef<T> = {
|
|||
[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
|
||||
: T[K] extends Ref<infer V> | undefined
|
||||
? unknown extends V
|
||||
? undefined
|
||||
: V | undefined
|
||||
: T[K]
|
||||
? unknown extends V
|
||||
? undefined
|
||||
: V | undefined
|
||||
: T[K]
|
||||
}
|
||||
|
||||
export type UnwrapRef<T> = T extends ShallowRef<infer V>
|
||||
? V
|
||||
: T extends Ref<infer V>
|
||||
? UnwrapRefSimple<V>
|
||||
: UnwrapRefSimple<T>
|
||||
? UnwrapRefSimple<V>
|
||||
: UnwrapRefSimple<T>
|
||||
|
||||
export type UnwrapRefSimple<T> = T extends
|
||||
| Function
|
||||
|
@ -512,9 +512,9 @@ export type UnwrapRefSimple<T> = T extends
|
|||
| { [RawSymbol]?: true }
|
||||
? T
|
||||
: T extends ReadonlyArray<any>
|
||||
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
|
||||
: T extends object & { [ShallowReactiveMarker]?: never }
|
||||
? {
|
||||
[P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>
|
||||
}
|
||||
: T
|
||||
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
|
||||
: T extends object & { [ShallowReactiveMarker]?: never }
|
||||
? {
|
||||
[P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>
|
||||
}
|
||||
: T
|
||||
|
|
|
@ -91,7 +91,7 @@ describe('api: watch', () => {
|
|||
array.push(1)
|
||||
await nextTick()
|
||||
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 () => {
|
||||
|
@ -1243,4 +1243,39 @@ describe('api: watch', () => {
|
|||
expect(count.value).toBe(2)
|
||||
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 },
|
||||
cls: { type: MyClass },
|
||||
fn: { type: Function },
|
||||
skipCheck: { type: [Boolean, Function], skipCheck: true }
|
||||
skipCheck: { type: [Boolean, Function], skipCheck: true },
|
||||
empty: { type: [] }
|
||||
},
|
||||
setup() {
|
||||
return () => null
|
||||
|
@ -351,7 +352,8 @@ describe('component props', () => {
|
|||
obj: 'false',
|
||||
cls: {},
|
||||
fn: true,
|
||||
skipCheck: 'foo'
|
||||
skipCheck: 'foo',
|
||||
empty: [1, 2, 3]
|
||||
}),
|
||||
nodeOps.createElement('div')
|
||||
)
|
||||
|
@ -379,6 +381,9 @@ describe('component props', () => {
|
|||
expect(
|
||||
`Invalid prop: type check failed for prop "skipCheck". Expected Boolean | Function, got String with value "foo".`
|
||||
).not.toHaveBeenWarned()
|
||||
expect(
|
||||
`Prop type [] for prop "empty" won't match anything. Did you mean to use type Array instead?`
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
// #3495
|
||||
|
|
|
@ -17,9 +17,12 @@ import {
|
|||
onUnmounted,
|
||||
onErrorCaptured,
|
||||
shallowRef,
|
||||
SuspenseProps,
|
||||
resolveDynamicComponent,
|
||||
Fragment
|
||||
} from '@vue/runtime-test'
|
||||
import { createApp, defineComponent } from 'vue'
|
||||
import { type RawSlots } from 'packages/runtime-core/src/componentSlots'
|
||||
|
||||
describe('Suspense', () => {
|
||||
const deps: Promise<any>[] = []
|
||||
|
@ -1523,4 +1526,75 @@ describe('Suspense', () => {
|
|||
expected = `<div>outerB</div><div>innerB</div>`
|
||||
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)
|
||||
})
|
||||
|
||||
// #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 () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
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', () => {
|
||||
const { container } = mountWithHydration(
|
||||
'<input type="checkbox" value="true">',
|
||||
|
@ -953,6 +965,20 @@ describe('SSR hydration', () => {
|
|||
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', () => {
|
||||
const { container } = mountWithHydration(
|
||||
'<select><option :value="true">ok</option></select>',
|
||||
|
@ -1177,5 +1203,21 @@ describe('SSR hydration', () => {
|
|||
expect(teleportContainer.innerHTML).toBe(`<span>value</span>`)
|
||||
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(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])
|
||||
})
|
||||
|
||||
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 vnode =
|
||||
(openBlock(),
|
||||
createBlock('div', null, [
|
||||
hoist,
|
||||
createVNode('div', null, 'text', PatchFlags.HYDRATE_EVENTS)
|
||||
createVNode('div', null, 'text', PatchFlags.NEED_HYDRATION)
|
||||
]))
|
||||
expect(vnode.dynamicChildren).toStrictEqual([])
|
||||
})
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.4.0-alpha.1",
|
||||
"@vue/reactivity": "3.4.0-alpha.1"
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/reactivity": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,8 +70,7 @@ export type DefineComponent<
|
|||
true,
|
||||
{},
|
||||
S
|
||||
> &
|
||||
Props
|
||||
>
|
||||
> &
|
||||
ComponentOptionsBase<
|
||||
Props,
|
||||
|
|
|
@ -4,7 +4,8 @@ import {
|
|||
isFunction,
|
||||
Prettify,
|
||||
UnionToIntersection,
|
||||
extend
|
||||
extend,
|
||||
LooseRequired
|
||||
} from '@vue/shared'
|
||||
import {
|
||||
getCurrentInstance,
|
||||
|
@ -82,7 +83,7 @@ export function defineProps<
|
|||
>(props: PP): Prettify<Readonly<ExtractPropTypes<PP>>>
|
||||
// overload 3: typed-based declaration
|
||||
export function defineProps<TypeProps>(): DefineProps<
|
||||
TypeProps,
|
||||
LooseRequired<TypeProps>,
|
||||
BooleanKey<TypeProps>
|
||||
>
|
||||
// implementation
|
||||
|
@ -297,8 +298,8 @@ type PropsWithDefaults<
|
|||
T,
|
||||
Defaults extends InferDefaults<T>,
|
||||
BKeys extends keyof T
|
||||
> = Omit<T, keyof Defaults> & {
|
||||
[K in keyof Defaults]-?: K extends keyof T
|
||||
> = Readonly<Omit<T, keyof Defaults>> & {
|
||||
readonly [K in keyof Defaults]-?: K extends keyof T
|
||||
? Defaults[K] extends undefined
|
||||
? T[K]
|
||||
: NotUndefined<T[K]>
|
||||
|
|
|
@ -60,10 +60,10 @@ type MapSources<T, Immediate> = {
|
|||
? V | undefined
|
||||
: V
|
||||
: T[K] extends object
|
||||
? Immediate extends true
|
||||
? T[K] | undefined
|
||||
: T[K]
|
||||
: never
|
||||
? Immediate extends true
|
||||
? T[K] | undefined
|
||||
: T[K]
|
||||
: never
|
||||
}
|
||||
|
||||
type OnCleanup = (cleanupFn: () => void) => void
|
||||
|
@ -288,10 +288,11 @@ function doWatch(
|
|||
getter = () => traverse(baseGetter())
|
||||
}
|
||||
|
||||
let cleanup: () => void
|
||||
let cleanup: (() => void) | undefined
|
||||
let onCleanup: OnCleanup = (fn: () => void) => {
|
||||
cleanup = effect.onStop = () => {
|
||||
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
|
||||
cleanup = effect.onStop = undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -348,8 +349,8 @@ function doWatch(
|
|||
oldValue === INITIAL_WATCHER_VALUE
|
||||
? undefined
|
||||
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
|
||||
? []
|
||||
: oldValue,
|
||||
? []
|
||||
: oldValue,
|
||||
onCleanup
|
||||
])
|
||||
oldValue = newValue
|
||||
|
|
|
@ -251,8 +251,8 @@ export function createCompatVue(
|
|||
mergeBase[key] = isArray(superValue)
|
||||
? superValue.slice()
|
||||
: isObject(superValue)
|
||||
? extend(Object.create(null), superValue)
|
||||
: superValue
|
||||
? extend(Object.create(null), superValue)
|
||||
: superValue
|
||||
}
|
||||
|
||||
SubVue.options = mergeOptions(
|
||||
|
|
|
@ -922,10 +922,10 @@ export function finishComponentSetup(
|
|||
(__ESM_BUNDLER__
|
||||
? ` Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".`
|
||||
: __ESM_BROWSER__
|
||||
? ` Use "vue.esm-browser.js" instead.`
|
||||
: __GLOBAL__
|
||||
? ` Use "vue.global.js" instead.`
|
||||
: ``) /* should not happen */
|
||||
? ` Use "vue.esm-browser.js" instead.`
|
||||
: __GLOBAL__
|
||||
? ` Use "vue.global.js" instead.`
|
||||
: ``) /* should not happen */
|
||||
)
|
||||
} else {
|
||||
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
|
||||
}
|
||||
: T extends ObjectEmitsOptions
|
||||
? {
|
||||
[K in string &
|
||||
`on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
|
||||
? T[Uncapitalize<C>] extends null
|
||||
? (...args: any[]) => any
|
||||
: (
|
||||
...args: T[Uncapitalize<C>] extends (...args: infer P) => any
|
||||
? P
|
||||
: never
|
||||
) => any
|
||||
: never
|
||||
}
|
||||
: {}
|
||||
? {
|
||||
[K in string &
|
||||
`on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
|
||||
? T[Uncapitalize<C>] extends null
|
||||
? (...args: any[]) => any
|
||||
: (
|
||||
...args: T[Uncapitalize<C>] extends (...args: infer P) => any
|
||||
? P
|
||||
: never
|
||||
) => any
|
||||
: never
|
||||
}
|
||||
: {}
|
||||
|
||||
export type EmitFn<
|
||||
Options = ObjectEmitsOptions,
|
||||
|
@ -61,14 +61,14 @@ export type EmitFn<
|
|||
> = Options extends Array<infer V>
|
||||
? (event: V, ...args: any[]) => void
|
||||
: {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
|
||||
? (event: string, ...args: any[]) => void
|
||||
: UnionToIntersection<
|
||||
{
|
||||
[key in Event]: Options[key] extends (...args: infer Args) => any
|
||||
? (event: key, ...args: Args) => void
|
||||
: (event: key, ...args: any[]) => void
|
||||
}[Event]
|
||||
>
|
||||
? (event: string, ...args: any[]) => void
|
||||
: UnionToIntersection<
|
||||
{
|
||||
[key in Event]: Options[key] extends (...args: infer Args) => any
|
||||
? (event: key, ...args: Args) => void
|
||||
: (event: key, ...args: any[]) => void
|
||||
}[Event]
|
||||
>
|
||||
|
||||
export function emit(
|
||||
instance: ComponentInternalInstance,
|
||||
|
|
|
@ -419,8 +419,8 @@ export type ExtractComputedReturns<T extends any> = {
|
|||
[key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }
|
||||
? TReturn
|
||||
: T[key] extends (...args: any[]) => infer TReturn
|
||||
? TReturn
|
||||
: never
|
||||
? TReturn
|
||||
: never
|
||||
}
|
||||
|
||||
export type ObjectWatchOptionItem = {
|
||||
|
@ -450,10 +450,10 @@ export type InjectToObject<T extends ComponentInjectOptions> =
|
|||
[K in T[number]]?: unknown
|
||||
}
|
||||
: T extends ObjectInjectOptions
|
||||
? {
|
||||
[K in keyof T]?: unknown
|
||||
}
|
||||
: never
|
||||
? {
|
||||
[K in keyof T]?: unknown
|
||||
}
|
||||
: never
|
||||
|
||||
interface LegacyOptions<
|
||||
Props,
|
||||
|
@ -752,8 +752,8 @@ export function applyOptions(instance: ComponentInternalInstance) {
|
|||
const get = isFunction(opt)
|
||||
? opt.bind(publicThis, publicThis)
|
||||
: isFunction(opt.get)
|
||||
? opt.get.bind(publicThis, publicThis)
|
||||
: NOOP
|
||||
? opt.get.bind(publicThis, publicThis)
|
||||
: NOOP
|
||||
if (__DEV__ && get === NOOP) {
|
||||
warn(`Computed property "${key}" has no getter.`)
|
||||
}
|
||||
|
@ -761,12 +761,12 @@ export function applyOptions(instance: ComponentInternalInstance) {
|
|||
!isFunction(opt) && isFunction(opt.set)
|
||||
? opt.set.bind(publicThis)
|
||||
: __DEV__
|
||||
? () => {
|
||||
warn(
|
||||
`Write operation failed: computed property "${key}" is readonly.`
|
||||
)
|
||||
}
|
||||
: NOOP
|
||||
? () => {
|
||||
warn(
|
||||
`Write operation failed: computed property "${key}" is readonly.`
|
||||
)
|
||||
}
|
||||
: NOOP
|
||||
const c = computed({
|
||||
get,
|
||||
set
|
||||
|
|
|
@ -111,22 +111,22 @@ type DefaultKeys<T> = {
|
|||
type InferPropType<T> = [T] extends [null]
|
||||
? any // null & true would fail to infer
|
||||
: [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`
|
||||
: [T] extends [ObjectConstructor | { type: ObjectConstructor }]
|
||||
? Record<string, any>
|
||||
: [T] extends [BooleanConstructor | { type: BooleanConstructor }]
|
||||
? boolean
|
||||
: [T] extends [DateConstructor | { type: DateConstructor }]
|
||||
? Date
|
||||
: [T] extends [(infer U)[] | { type: (infer U)[] }]
|
||||
? U extends DateConstructor
|
||||
? Date | InferPropType<U>
|
||||
: InferPropType<U>
|
||||
: [T] extends [Prop<infer V, infer D>]
|
||||
? unknown extends V
|
||||
? IfAny<V, V, D>
|
||||
: V
|
||||
: T
|
||||
? 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 }]
|
||||
? Record<string, any>
|
||||
: [T] extends [BooleanConstructor | { type: BooleanConstructor }]
|
||||
? boolean
|
||||
: [T] extends [DateConstructor | { type: DateConstructor }]
|
||||
? Date
|
||||
: [T] extends [(infer U)[] | { type: (infer U)[] }]
|
||||
? U extends DateConstructor
|
||||
? Date | InferPropType<U>
|
||||
: InferPropType<U>
|
||||
: [T] extends [Prop<infer V, infer D>]
|
||||
? unknown extends V
|
||||
? IfAny<V, V, D>
|
||||
: V
|
||||
: T
|
||||
|
||||
/**
|
||||
* Extract prop types from a runtime props options object.
|
||||
|
@ -725,6 +725,12 @@ function getInvalidTypeMessage(
|
|||
value: unknown,
|
||||
expectedTypes: 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 =
|
||||
`Invalid prop: type check failed for prop "${name}".` +
|
||||
` Expected ${expectedTypes.map(capitalize).join(' | ')}`
|
||||
|
|
|
@ -15,7 +15,8 @@ import {
|
|||
isString,
|
||||
isFunction,
|
||||
UnionToIntersection,
|
||||
Prettify
|
||||
Prettify,
|
||||
IfAny
|
||||
} from '@vue/shared'
|
||||
import {
|
||||
toRaw,
|
||||
|
@ -187,7 +188,6 @@ export type CreateComponentPublicInstance<
|
|||
I,
|
||||
S
|
||||
>
|
||||
|
||||
// public properties exposed on the proxy, which is used as the render context
|
||||
// in templates (as `this` in the render option)
|
||||
export type ComponentPublicInstance<
|
||||
|
@ -226,7 +226,7 @@ export type ComponentPublicInstance<
|
|||
: (...args: any) => any,
|
||||
options?: WatchOptions
|
||||
): WatchStopHandle
|
||||
} & P &
|
||||
} & IfAny<P, P, Omit<P, keyof ShallowUnwrapRef<B>>> &
|
||||
ShallowUnwrapRef<B> &
|
||||
UnwrapNestedRefs<D> &
|
||||
ExtractComputedReturns<C> &
|
||||
|
|
|
@ -73,9 +73,24 @@ export function renderComponentRoot(
|
|||
// withProxy is a proxy with a different `has` trap only for
|
||||
// runtime-compiled render functions using `with` block.
|
||||
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(
|
||||
render!.call(
|
||||
proxyToUse,
|
||||
thisProxy,
|
||||
proxyToUse!,
|
||||
renderCache,
|
||||
props,
|
||||
|
|
|
@ -474,9 +474,13 @@ function emptyPlaceholder(vnode: VNode): VNode | undefined {
|
|||
|
||||
function getKeepAliveChild(vnode: VNode): VNode | undefined {
|
||||
return isKeepAlive(vnode)
|
||||
? vnode.children
|
||||
? ((vnode.children as VNodeArrayChildren)[0] as VNode)
|
||||
: undefined
|
||||
? // #7121 ensure get the child component subtree in case
|
||||
// it's been replaced during HMR
|
||||
__DEV__ && vnode.component
|
||||
? vnode.component.subTree
|
||||
: vnode.children
|
||||
? ((vnode.children as VNodeArrayChildren)[0] as VNode)
|
||||
: undefined
|
||||
: vnode
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
assertNumber
|
||||
} from '../warning'
|
||||
import { handleError, ErrorCodes } from '../errorHandling'
|
||||
import { NULL_DYNAMIC_COMPONENT } from '../helpers/resolveAssets'
|
||||
|
||||
export interface SuspenseProps {
|
||||
onResolve?: () => void
|
||||
|
@ -795,7 +796,11 @@ function normalizeSuspenseSlot(s: any) {
|
|||
}
|
||||
if (isArray(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.`)
|
||||
}
|
||||
s = singleChild
|
||||
|
|
|
@ -63,6 +63,7 @@ const resolveTarget = <T = RendererElement>(
|
|||
}
|
||||
|
||||
export const TeleportImpl = {
|
||||
name: 'Teleport',
|
||||
__isTeleport: true,
|
||||
process(
|
||||
n1: TeleportVNode | null,
|
||||
|
|
|
@ -111,6 +111,21 @@ export function createHydrationFunctions(
|
|||
let domType = node.nodeType
|
||||
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) {
|
||||
optimized = false
|
||||
vnode.dynamicChildren = null
|
||||
|
@ -145,18 +160,17 @@ export function createHydrationFunctions(
|
|||
}
|
||||
break
|
||||
case Comment:
|
||||
if (domType !== DOMNodeTypes.COMMENT || isFragmentStart) {
|
||||
if ((node as Element).tagName.toLowerCase() === 'template') {
|
||||
const content = (vnode.el! as HTMLTemplateElement).content
|
||||
.firstChild!
|
||||
|
||||
// replace <template> node with inner children
|
||||
replaceNode(content, node, parentComponent)
|
||||
vnode.el = node = content
|
||||
nextNode = nextSibling(node)
|
||||
} else {
|
||||
nextNode = onMismatch()
|
||||
}
|
||||
if (isTemplateNode(node)) {
|
||||
nextNode = nextSibling(node)
|
||||
// wrapped <transition appear>
|
||||
// replace <template> node with inner child
|
||||
replaceNode(
|
||||
(vnode.el = node.content.firstChild!),
|
||||
node,
|
||||
parentComponent
|
||||
)
|
||||
} else if (domType !== DOMNodeTypes.COMMENT || isFragmentStart) {
|
||||
nextNode = onMismatch()
|
||||
} else {
|
||||
nextNode = nextSibling(node)
|
||||
}
|
||||
|
@ -209,7 +223,7 @@ export function createHydrationFunctions(
|
|||
(domType !== DOMNodeTypes.ELEMENT ||
|
||||
(vnode.type as string).toLowerCase() !==
|
||||
(node as Element).tagName.toLowerCase()) &&
|
||||
!isTemplateNode(node as Element)
|
||||
!isTemplateNode(node)
|
||||
) {
|
||||
nextNode = onMismatch()
|
||||
} else {
|
||||
|
@ -322,24 +336,28 @@ export function createHydrationFunctions(
|
|||
const { type, props, patchFlag, shapeFlag, dirs, transition } = vnode
|
||||
// #4006 for form elements with non-string v-model value bindings
|
||||
// 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
|
||||
// #5405 in dev, always hydrate children for HMR
|
||||
if (__DEV__ || forcePatchValue || patchFlag !== PatchFlags.HOISTED) {
|
||||
if (__DEV__ || forcePatch || patchFlag !== PatchFlags.HOISTED) {
|
||||
if (dirs) {
|
||||
invokeDirectiveHook(vnode, null, parentComponent, 'created')
|
||||
}
|
||||
// props
|
||||
if (props) {
|
||||
if (
|
||||
forcePatchValue ||
|
||||
forcePatch ||
|
||||
!optimized ||
|
||||
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.HYDRATE_EVENTS)
|
||||
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
|
||||
) {
|
||||
for (const key in props) {
|
||||
if (
|
||||
(forcePatchValue && key.endsWith('value')) ||
|
||||
(isOn(key) && !isReservedProp(key))
|
||||
(forcePatch &&
|
||||
(key.endsWith('value') || key === 'indeterminate')) ||
|
||||
(isOn(key) && !isReservedProp(key)) ||
|
||||
// force hydrate v-bind with .prop modifiers
|
||||
key[0] === '.'
|
||||
) {
|
||||
patchProp(
|
||||
el,
|
||||
|
@ -564,8 +582,8 @@ export function createHydrationFunctions(
|
|||
node.nodeType === DOMNodeTypes.TEXT
|
||||
? `(text)`
|
||||
: isComment(node) && node.data === '['
|
||||
? `(start of fragment)`
|
||||
: ``
|
||||
? `(start of fragment)`
|
||||
: ``
|
||||
)
|
||||
vnode.el = null
|
||||
|
||||
|
@ -637,17 +655,16 @@ export function createHydrationFunctions(
|
|||
let parent = parentComponent
|
||||
while (parent) {
|
||||
if (parent.vnode.el === oldNode) {
|
||||
parent.vnode.el = newNode
|
||||
parent.subTree.el = newNode
|
||||
parent.vnode.el = parent.subTree.el = newNode
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
}
|
||||
|
||||
const isTemplateNode = (node: Element): boolean => {
|
||||
const isTemplateNode = (node: Node): node is HTMLTemplateElement => {
|
||||
return (
|
||||
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
|
||||
let c2 = ch2[i] as VNode
|
||||
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.el = c1.el
|
||||
}
|
||||
|
|
|
@ -488,7 +488,7 @@ function createBaseVNode(
|
|||
(vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
|
||||
// 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.patchFlag !== PatchFlags.HYDRATE_EVENTS
|
||||
vnode.patchFlag !== PatchFlags.NEED_HYDRATION
|
||||
) {
|
||||
currentBlock.push(vnode)
|
||||
}
|
||||
|
@ -573,20 +573,20 @@ function _createVNode(
|
|||
const shapeFlag = isString(type)
|
||||
? ShapeFlags.ELEMENT
|
||||
: __FEATURE_SUSPENSE__ && isSuspense(type)
|
||||
? ShapeFlags.SUSPENSE
|
||||
: isTeleport(type)
|
||||
? ShapeFlags.TELEPORT
|
||||
: isObject(type)
|
||||
? ShapeFlags.STATEFUL_COMPONENT
|
||||
: isFunction(type)
|
||||
? ShapeFlags.FUNCTIONAL_COMPONENT
|
||||
: 0
|
||||
? ShapeFlags.SUSPENSE
|
||||
: isTeleport(type)
|
||||
? ShapeFlags.TELEPORT
|
||||
: isObject(type)
|
||||
? ShapeFlags.STATEFUL_COMPONENT
|
||||
: isFunction(type)
|
||||
? ShapeFlags.FUNCTIONAL_COMPONENT
|
||||
: 0
|
||||
|
||||
if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
|
||||
type = toRaw(type)
|
||||
warn(
|
||||
`Vue received a Component which was made a reactive object. This can ` +
|
||||
`lead to unnecessary performance overhead, and should be avoided by ` +
|
||||
`Vue received a Component that was made a reactive object. This can ` +
|
||||
`lead to unnecessary performance overhead and should be avoided by ` +
|
||||
`marking the component with \`markRaw\` or using \`shallowRef\` ` +
|
||||
`instead of \`ref\`.`,
|
||||
`\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