mirror of https://github.com/vuejs/core.git
Merge remote-tracking branch 'upstream/minor'
This commit is contained in:
commit
a8248cf152
|
@ -17,6 +17,27 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
|
|||
|
||||
## Pull Request Guidelines
|
||||
|
||||
### What kinds of Pull Requests are accepted?
|
||||
|
||||
- Bug fix that addresses a clearly identified bug. **"Clearly identified bug"** means the bug has a proper reproduction either from a related open issue, or is included in the PR itself. Avoid submitting PRs that claim to fix something but do not sufficiently explain what is being fixed.
|
||||
|
||||
- New feature that addresses a clearly explained and widely applicable use case. **"Widely applicable"** means the new feature should provide non-trivial improvements to the majority of the user base. Vue already has a large API surface so we are quite cautious about adding new features - if the use case is niche and can be addressed via userland implementations, it likely isn't suitable to go into core.
|
||||
|
||||
The feature implementation should also consider the trade-off between the added complexity vs. the benefits gained. For example, if a small feature requires significant changes that spreads across the codebase, it is likely not worth it, or the approach should be reconsidered.
|
||||
|
||||
If the feature has a non-trivial API surface addition, or significantly affects the way a common use case is approached by the users, it should go through a discussion first in the [RFC repo](https://github.com/vuejs/rfcs/discussions). PRs of such features without prior discussion make it really difficult to steer / adjust the API design due to coupling with concrete implementations, and can lead to wasted work.
|
||||
|
||||
- Chore: typos, comment improvements, build config, CI config, etc. For typos and comment changes, try to combine multiple of them into a single PR.
|
||||
|
||||
- **It should be noted that we discourage contributors from submitting code refactors that are largely stylistic.** Code refactors are only accepted if it improves performance, or comes with sufficient explanations on why it objectively improves the code quality (e.g. makes a related feature implementation easier).
|
||||
|
||||
The reason is that code readability is subjective. The maintainers of this project have chosen to write the code in its current style based on our preferences, and we do not want to spend time explaining our stylistic preferences. Contributors should just respect the established conventions when contributing code.
|
||||
|
||||
Another aspect of it is that large scale stylistic changes result in massive diffs that touch multiple files, adding noise to the git history and makes tracing behavior changes across commits more cumbersome.
|
||||
|
||||
|
||||
### Pull Request Checklist
|
||||
|
||||
- Vue core has two primary work branches: `main` and `minor`.
|
||||
|
||||
- If your pull request is a feature that adds new API surface, it should be submitted against the `minor` branch.
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
},
|
||||
{
|
||||
groupName: 'build',
|
||||
matchPackageNames: ['vite', 'terser'],
|
||||
matchPackageNames: ['vite', '@swc/core'],
|
||||
matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
name: autofix.ci
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
- name: Run eslint
|
||||
run: pnpm run lint --fix
|
||||
|
||||
- name: Run prettier
|
||||
run: pnpm run format
|
||||
|
||||
- uses: autofix-ci/action@2891949f3779a1cafafae1523058501de3d4e944
|
|
@ -88,7 +88,7 @@ jobs:
|
|||
|
||||
- run: pnpm install
|
||||
|
||||
- run: pnpm release --vapor --skip-tests --tag ${{ github.ref == 'refs/heads/main' && 'latest' || 'branch' }}
|
||||
- run: pnpm release --vapor --skipTests --tag ${{ github.ref == 'refs/heads/main' && 'latest' || 'branch' }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: 'true'
|
||||
|
|
|
@ -52,3 +52,13 @@ jobs:
|
|||
with:
|
||||
name: size-data
|
||||
path: temp/size
|
||||
|
||||
- name: Save PR number
|
||||
if: ${{github.event_name == 'pull_request'}}
|
||||
run: echo ${{ github.event.number }} > ./pr.txt
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{github.event_name == 'pull_request'}}
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr.txt
|
||||
|
|
|
@ -35,15 +35,28 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Download PR number
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
name: pr-number
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: /tmp/pr-number
|
||||
|
||||
- name: Read PR Number
|
||||
id: pr-number
|
||||
uses: juliangruber/read-file-action@v1
|
||||
with:
|
||||
path: /tmp/pr-number/pr.txt
|
||||
|
||||
- name: Download Size Data
|
||||
uses: dawidd6/action-download-artifact@v4
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
name: size-data
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: temp/size
|
||||
|
||||
- name: Download Previous Size Data
|
||||
uses: dawidd6/action-download-artifact@v4
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
branch: main
|
||||
workflow: size-data.yml
|
||||
|
@ -52,7 +65,7 @@ jobs:
|
|||
path: temp/size-prev
|
||||
if_no_artifact_found: warn
|
||||
|
||||
- name: Compare size
|
||||
- name: Prepare report
|
||||
run: pnpm tsx scripts/size-report.ts > size-report.md
|
||||
|
||||
- name: Read Size Report
|
||||
|
@ -65,6 +78,7 @@ jobs:
|
|||
uses: actions-cool/maintain-one-comment@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
number: ${{ steps.pr-number.outputs.content }}
|
||||
body: |
|
||||
${{ steps.size-report.outputs.content }}
|
||||
<!-- VUE_CORE_SIZE -->
|
||||
|
|
81
CHANGELOG.md
81
CHANGELOG.md
|
@ -1,5 +1,86 @@
|
|||
# [3.5.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.33...v3.5.0-alpha.3) (2024-07-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** enable SSR branches in esm-browser builds ([b14cd9a](https://github.com/vuejs/core/commit/b14cd9a68bab082332b0169be075be357be076ca))
|
||||
* **compiler-core:** change node hoisting to caching per instance ([#11067](https://github.com/vuejs/core/issues/11067)) ([cd0ea0d](https://github.com/vuejs/core/commit/cd0ea0d479a276583fa181d8ecbc97fb0e4a9dce)), closes [#5256](https://github.com/vuejs/core/issues/5256) [#9219](https://github.com/vuejs/core/issues/9219) [#10959](https://github.com/vuejs/core/issues/10959)
|
||||
* **compiler-sfc:** should properly walk desutructured props when reactive destructure is not enabled ([0fd6193](https://github.com/vuejs/core/commit/0fd6193def2380916eb51a118f37f2d9ec2ace23)), closes [#11325](https://github.com/vuejs/core/issues/11325)
|
||||
* **types:** respect props with default on instance type when using __typeProps ([96e4738](https://github.com/vuejs/core/commit/96e473833422342c5ca371ae1aeb186dec9a55e3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **runtime-core:** useTemplateRef() ([3ba70e4](https://github.com/vuejs/core/commit/3ba70e49b5856c53611c314d4855d679a546a7df))
|
||||
* **runtime-core:** useId() and app.config.idPrefix ([#11404](https://github.com/vuejs/core/issues/11404)) ([73ef156](https://github.com/vuejs/core/commit/73ef1561f6905d69f968c094d0180c61824f1247))
|
||||
* **runtime-core:** add app.config.throwUnhandledErrorInProduction ([f476b7f](https://github.com/vuejs/core/commit/f476b7f030f2dd427ca655fcea36f4933a4b4da0)), closes [#7876](https://github.com/vuejs/core/issues/7876)
|
||||
* **teleport:** support deferred Teleport ([#11387](https://github.com/vuejs/core/issues/11387)) ([59a3e88](https://github.com/vuejs/core/commit/59a3e88903b10ac2278170a44d5a03f24fef23ef)), closes [#2015](https://github.com/vuejs/core/issues/2015) [#11386](https://github.com/vuejs/core/issues/11386)
|
||||
* **compiler-core:** support `Symbol` global in template expressions ([#9069](https://github.com/vuejs/core/issues/9069)) ([a501a85](https://github.com/vuejs/core/commit/a501a85a7c910868e01a5c70a2abea4e9d9e87f3))
|
||||
* **types:** export more emit related types ([#11017](https://github.com/vuejs/core/issues/11017)) ([189573d](https://github.com/vuejs/core/commit/189573dcee2a16bd3ed36ff5589d43f535e5e733))
|
||||
|
||||
|
||||
|
||||
## [3.4.33](https://github.com/vuejs/core/compare/v3.4.32...v3.4.33) (2024-07-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **runtime-dom:** handle undefined values in v-html ([#11403](https://github.com/vuejs/core/issues/11403)) ([5df67e3](https://github.com/vuejs/core/commit/5df67e36756639ea7b923d1b139d6cb14450123b))
|
||||
|
||||
|
||||
|
||||
## [3.4.32](https://github.com/vuejs/core/compare/v3.4.31...v3.4.32) (2024-07-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** use consistent minify options from previous terser config ([789675f](https://github.com/vuejs/core/commit/789675f65d2b72cf979ba6a29bd323f716154a4b))
|
||||
* **compiler-sfc:** correctly resolve type annotation for declared function ([#11279](https://github.com/vuejs/core/issues/11279)) ([b287aee](https://github.com/vuejs/core/commit/b287aeec3ea85f20e4b1fc3d907c901bdc2a0176)), closes [#11266](https://github.com/vuejs/core/issues/11266)
|
||||
* **defineModel:** force local update when setter results in same emitted value ([de174e1](https://github.com/vuejs/core/commit/de174e1aa756508c7542605a448e55a373afb1ed)), closes [#10279](https://github.com/vuejs/core/issues/10279) [#10301](https://github.com/vuejs/core/issues/10301)
|
||||
* **hmr:** hmr reload should work with async component ([#11248](https://github.com/vuejs/core/issues/11248)) ([c8b9794](https://github.com/vuejs/core/commit/c8b97945759e869c997d60c3350d2451c5ff7887))
|
||||
* **hydration:** fix tracking of reactive style objects in production ([c10e40a](https://github.com/vuejs/core/commit/c10e40a217b89ab7e0f7f3515242d4246ecffbdd)), closes [#11372](https://github.com/vuejs/core/issues/11372)
|
||||
* **hydration:** handle consectuvie text nodes during hydration ([f44c3b3](https://github.com/vuejs/core/commit/f44c3b37d446d5f8e34539029dae0d806b25bb47)), closes [#7285](https://github.com/vuejs/core/issues/7285) [#7301](https://github.com/vuejs/core/issues/7301)
|
||||
* **reactivity:** ensure `unref` correctly resolves type for `ShallowRef` ([#11360](https://github.com/vuejs/core/issues/11360)) ([a509e30](https://github.com/vuejs/core/commit/a509e30f059fcdd158f39fdf34670b1019eaf2d1)), closes [#11356](https://github.com/vuejs/core/issues/11356)
|
||||
* **reactivity:** shallowReactive map "unwraps" the nested refs ([#8503](https://github.com/vuejs/core/issues/8503)) ([50ddafe](https://github.com/vuejs/core/commit/50ddafe91b9195cf94124466239f82c9794699fb)), closes [#8501](https://github.com/vuejs/core/issues/8501) [#11249](https://github.com/vuejs/core/issues/11249)
|
||||
* **runtime-core:** avoid recursive warning ([3ee7b4c](https://github.com/vuejs/core/commit/3ee7b4c7b1374c5bdc50a579b49f6bc15022b085)), closes [#8074](https://github.com/vuejs/core/issues/8074)
|
||||
* **runtime-core:** bail manually rendered compiler slot fragments in all cases ([3d34f40](https://github.com/vuejs/core/commit/3d34f406ac7497dafd2f4e62ab23579b78a0e08a)), closes [#10870](https://github.com/vuejs/core/issues/10870)
|
||||
* **runtime-core:** do not emit when defineModel ref is set with same value ([#11162](https://github.com/vuejs/core/issues/11162)) ([f1bb0ae](https://github.com/vuejs/core/commit/f1bb0aef084b5cdd4d49aecfed01ec106d9b6897)), closes [#11125](https://github.com/vuejs/core/issues/11125)
|
||||
* **runtime-core:** errors during component patch should be caught by error handlers ([ee0248a](https://github.com/vuejs/core/commit/ee0248accff589a94688e177e5e3af10c18288cb))
|
||||
* **runtime-core:** force diff slot fallback content and provided content ([d76dd9c](https://github.com/vuejs/core/commit/d76dd9c58de24b273bc55af3a8ed81ba693e9683)), closes [#7256](https://github.com/vuejs/core/issues/7256) [#9200](https://github.com/vuejs/core/issues/9200) [#9308](https://github.com/vuejs/core/issues/9308) [#7266](https://github.com/vuejs/core/issues/7266) [#9213](https://github.com/vuejs/core/issues/9213)
|
||||
* **runtime-core:** more edge case fix for manually rendered compiled slot ([685e3f3](https://github.com/vuejs/core/commit/685e3f381c024b9f4023e60fe0545dc60d90d984)), closes [#11336](https://github.com/vuejs/core/issues/11336)
|
||||
* **runtime-core:** use separate prop caches for components and mixins ([#11350](https://github.com/vuejs/core/issues/11350)) ([b0aa234](https://github.com/vuejs/core/commit/b0aa234e5e7a611c018de68bc31e0cf55518d5ce)), closes [#7998](https://github.com/vuejs/core/issues/7998)
|
||||
* **runtime-dom:** properly handle innerHTML unmount into new children ([#11159](https://github.com/vuejs/core/issues/11159)) ([3e9e32e](https://github.com/vuejs/core/commit/3e9e32ee0a6d0fbf67e9098a66ff0a1ea6647806)), closes [#9135](https://github.com/vuejs/core/issues/9135)
|
||||
* **teleport:** skip teleported nodes when locating patch anchor ([8655ced](https://github.com/vuejs/core/commit/8655ced480ea0fe453ff5fe445cecf97b91ec260)), closes [#9071](https://github.com/vuejs/core/issues/9071) [#9134](https://github.com/vuejs/core/issues/9134) [#9313](https://github.com/vuejs/core/issues/9313) [#9313](https://github.com/vuejs/core/issues/9313)
|
||||
* **v-model:** component v-model modifiers trim and number when cases don't match ([#9609](https://github.com/vuejs/core/issues/9609)) ([7fb6eb8](https://github.com/vuejs/core/commit/7fb6eb882b64bf99a99d00606e54b0e050674206)), closes [#4848](https://github.com/vuejs/core/issues/4848) [#4850](https://github.com/vuejs/core/issues/4850) [#4850](https://github.com/vuejs/core/issues/4850)
|
||||
* **v-once:** properly unmount v-once cached trees ([d343a0d](https://github.com/vuejs/core/commit/d343a0dc01663f91db42b4ddb693e6fffcb45873)), closes [#5154](https://github.com/vuejs/core/issues/5154) [#8809](https://github.com/vuejs/core/issues/8809)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **server-renderer:** avoid unnecessary checks in `createBuffer` ([#11364](https://github.com/vuejs/core/issues/11364)) ([fc205bf](https://github.com/vuejs/core/commit/fc205bf4decde5ce0f4a61394ffa3914b502c287))
|
||||
* **server-renderer:** optimize `unrollBuffer` by avoiding promises ([#11340](https://github.com/vuejs/core/issues/11340)) ([05779a7](https://github.com/vuejs/core/commit/05779a70bd0b567ae458a07636d229bd07c44c4e))
|
||||
|
||||
|
||||
|
||||
## [3.4.31](https://github.com/vuejs/core/compare/v3.4.30...v3.4.31) (2024-06-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** handle inline comments with undefined bindings ([#11217](https://github.com/vuejs/core/issues/11217)) ([746352a](https://github.com/vuejs/core/commit/746352a14d62e9d3d9a38c359d2c54d418c1e0ac)), closes [#11216](https://github.com/vuejs/core/issues/11216)
|
||||
* **shared:** unwrap refs in toDisplayString ([#7306](https://github.com/vuejs/core/issues/7306)) ([0126cff](https://github.com/vuejs/core/commit/0126cfff9d93bcec70e5745519f6378e3cd3f39c)), closes [#5578](https://github.com/vuejs/core/issues/5578) [#5593](https://github.com/vuejs/core/issues/5593) [#11199](https://github.com/vuejs/core/issues/11199) [#11201](https://github.com/vuejs/core/issues/11201)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "fix(reactivity): avoid infinite loop when render access a side effect computed ([#11135](https://github.com/vuejs/core/issues/11135))" ([e0df985](https://github.com/vuejs/core/commit/e0df985f0317fb65c5b461bf224375c7763f0269))
|
||||
* Revert "fix(reactivity): fix side effect computed dirty level (#11183)" ([6c303ea](https://github.com/vuejs/core/commit/6c303eacd14b7b0de0accc228f6abeb43d706f63)), closes [#11183](https://github.com/vuejs/core/issues/11183)
|
||||
|
||||
|
||||
|
||||
## [3.4.30](https://github.com/vuejs/core/compare/v3.4.29...v3.4.30) (2024-06-22)
|
||||
|
||||
**Note: this release contains a fix (#11150) that requires `vue-tsc` to also be updated in sync to ^2.0.22. See #11196**
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
|
39
package.json
39
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.0.0-vapor",
|
||||
"packageManager": "pnpm@9.3.0",
|
||||
"packageManager": "pnpm@9.5.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js vue vue-vapor",
|
||||
|
@ -59,59 +59,54 @@
|
|||
"node": ">=18.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7",
|
||||
"@codspeed/vitest-plugin": "^3.1.0",
|
||||
"@babel/parser": "catalog:",
|
||||
"@babel/types": "catalog:",
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.8",
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "5.0.4",
|
||||
"@swc/core": "^1.6.1",
|
||||
"@swc/core": "^1.6.13",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^20.14.2",
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vitest/coverage-istanbul": "^1.6.0",
|
||||
"@vitest/ui": "^1.6.0",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.21.5",
|
||||
"esbuild": "^0.23.0",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^9.5.0",
|
||||
"eslint-plugin-import-x": "^0.5.1",
|
||||
"eslint": "^9.6.0",
|
||||
"eslint-plugin-import-x": "^0.5.3",
|
||||
"eslint-plugin-vitest": "^0.5.4",
|
||||
"estree-walker": "^2.0.2",
|
||||
"execa": "^9.2.0",
|
||||
"estree-walker": "catalog:",
|
||||
"jsdom": "^24.1.0",
|
||||
"lint-staged": "^15.2.7",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.10",
|
||||
"markdown-table": "^3.0.3",
|
||||
"marked": "^12.0.2",
|
||||
"minimist": "^1.2.8",
|
||||
"npm-run-all2": "^6.2.0",
|
||||
"npm-run-all2": "^6.2.2",
|
||||
"picocolors": "^1.0.1",
|
||||
"prettier": "^3.3.2",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~22.11.0",
|
||||
"rimraf": "^5.0.7",
|
||||
"rollup": "^4.18.0",
|
||||
"puppeteer": "~22.12.1",
|
||||
"rimraf": "^5.0.9",
|
||||
"rollup": "^4.18.1",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.6.2",
|
||||
"serve": "^14.2.3",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"terser": "^5.31.1",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.15.5",
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "~5.4.5",
|
||||
"typescript-eslint": "^7.13.0",
|
||||
"vite": "^5.3.1",
|
||||
"typescript-eslint": "^7.15.0",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
"pnpm": {
|
||||
|
|
|
@ -19,12 +19,12 @@ export function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: codegen > CacheExpression w/ isVNode: true 1`] = `
|
||||
exports[`compiler: codegen > CacheExpression w/ isVOnce: true 1`] = `
|
||||
"
|
||||
export function render(_ctx, _cache) {
|
||||
return _cache[1] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[1] = foo,
|
||||
(_cache[1] = foo).cacheIndex = 1,
|
||||
_setBlockTracking(1),
|
||||
_cache[1]
|
||||
)
|
||||
|
@ -54,7 +54,7 @@ return function render(_ctx, _cache) {
|
|||
[foo + bar]: bar
|
||||
}, [
|
||||
_createElementVNode("p", { "some-key": "foo" })
|
||||
], 16)
|
||||
], 16 /* FULL_PROPS */)
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -98,7 +98,7 @@ exports[`compiler: codegen > forNode 1`] = `
|
|||
"
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(), 1))
|
||||
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(), 1 /* TEXT */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -267,7 +267,7 @@ describe('compiler: codegen', () => {
|
|||
disableTracking: true,
|
||||
props: undefined,
|
||||
children: createCallExpression(RENDER_LIST),
|
||||
patchFlag: '1',
|
||||
patchFlag: PatchFlags.TEXT,
|
||||
dynamicProps: undefined,
|
||||
directives: undefined,
|
||||
loc: locStub,
|
||||
|
@ -303,7 +303,7 @@ describe('compiler: codegen', () => {
|
|||
disableTracking: false,
|
||||
props: undefined,
|
||||
children: createCallExpression(RENDER_LIST),
|
||||
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
|
||||
patchFlag: PatchFlags.STABLE_FRAGMENT,
|
||||
dynamicProps: undefined,
|
||||
directives: undefined,
|
||||
loc: locStub,
|
||||
|
@ -364,7 +364,7 @@ describe('compiler: codegen', () => {
|
|||
),
|
||||
],
|
||||
// flag
|
||||
PatchFlags.FULL_PROPS + '',
|
||||
PatchFlags.FULL_PROPS,
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
@ -375,7 +375,7 @@ describe('compiler: codegen', () => {
|
|||
[foo + bar]: bar
|
||||
}, [
|
||||
_${helperNameMap[CREATE_ELEMENT_VNODE]}("p", { "some-key": "foo" })
|
||||
], ${PatchFlags.FULL_PROPS})`)
|
||||
], ${genFlagText(PatchFlags.FULL_PROPS)})`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
|
@ -437,7 +437,7 @@ describe('compiler: codegen', () => {
|
|||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('CacheExpression w/ isVNode: true', () => {
|
||||
test('CacheExpression w/ isVOnce: true', () => {
|
||||
const { code } = generate(
|
||||
createRoot({
|
||||
cached: [],
|
||||
|
@ -456,7 +456,7 @@ describe('compiler: codegen', () => {
|
|||
`
|
||||
_cache[1] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[1] = foo,
|
||||
(_cache[1] = foo).cacheIndex = 1,
|
||||
_setBlockTracking(1),
|
||||
_cache[1]
|
||||
)
|
||||
|
@ -666,11 +666,14 @@ describe('compiler: codegen', () => {
|
|||
})
|
||||
|
||||
test('with patchFlag and no children/props', () => {
|
||||
expect(genCode(createVNodeCall(null, `"div"`, undefined, undefined, '1')))
|
||||
.toMatchInlineSnapshot(`
|
||||
"return _createElementVNode("div", null, null, 1)
|
||||
"
|
||||
`)
|
||||
expect(
|
||||
genCode(
|
||||
createVNodeCall(null, `"div"`, undefined, undefined, PatchFlags.TEXT),
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
"return _createElementVNode("div", null, null, 1 /* TEXT */)
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('as block', () => {
|
||||
|
|
|
@ -19,7 +19,6 @@ import { transformFor } from '../src/transforms/vFor'
|
|||
import { transformElement } from '../src/transforms/transformElement'
|
||||
import { transformSlotOutlet } from '../src/transforms/transformSlotOutlet'
|
||||
import { transformText } from '../src/transforms/transformText'
|
||||
import { genFlagText } from './testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
describe('compiler: transform', () => {
|
||||
|
@ -358,7 +357,7 @@ describe('compiler: transform', () => {
|
|||
{ type: NodeTypes.ELEMENT, tag: `div` },
|
||||
{ type: NodeTypes.ELEMENT, tag: `div` },
|
||||
] as any,
|
||||
genFlagText(PatchFlags.STABLE_FRAGMENT),
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
),
|
||||
)
|
||||
})
|
||||
|
@ -374,10 +373,7 @@ describe('compiler: transform', () => {
|
|||
{ type: NodeTypes.ELEMENT, tag: `div` },
|
||||
{ type: NodeTypes.COMMENT },
|
||||
] as any,
|
||||
genFlagText([
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
PatchFlags.DEV_ROOT_FRAGMENT,
|
||||
]),
|
||||
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
|
||||
),
|
||||
)
|
||||
})
|
||||
|
|
|
@ -8,7 +8,7 @@ return function render(_ctx, _cache) {
|
|||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
|
||||
_createElementVNode("div", { key: "foo" }, null, -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
@ -25,11 +25,11 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("p", null, [
|
||||
_createElementVNode("span"),
|
||||
_createElementVNode("span")
|
||||
], -1 /* CACHED */),
|
||||
], -1 /* HOISTED */),
|
||||
_createElementVNode("p", null, [
|
||||
_createElementVNode("span"),
|
||||
_createElementVNode("span")
|
||||
], -1 /* CACHED */)
|
||||
], -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
@ -45,7 +45,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", null, [
|
||||
_createCommentVNode("comment")
|
||||
], -1 /* CACHED */)
|
||||
], -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
@ -59,9 +59,9 @@ return function render(_ctx, _cache) {
|
|||
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */),
|
||||
_createElementVNode("span", null, null, -1 /* HOISTED */),
|
||||
_createTextVNode("foo"),
|
||||
_createElementVNode("div", null, null, -1 /* CACHED */)
|
||||
_createElementVNode("div", null, null, -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
@ -75,7 +75,7 @@ return function render(_ctx, _cache) {
|
|||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
|
||||
_createElementVNode("span", { class: "inline" }, "hello", -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
@ -148,7 +148,7 @@ return function render(_ctx, _cache) {
|
|||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
|
||||
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
@ -162,7 +162,7 @@ return function render(_ctx, _cache) {
|
|||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
|
||||
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* HOISTED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
|
@ -178,7 +178,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => {
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { class: "hi" }, null, -1 /* CACHED */)
|
||||
_createElementVNode("span", { class: "hi" }, null, -1 /* HOISTED */)
|
||||
]))]))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
]))
|
||||
|
@ -216,7 +216,7 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
|
||||
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* HOISTED */)
|
||||
]))), [
|
||||
[_directive_foo]
|
||||
])
|
||||
|
@ -402,7 +402,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
ok
|
||||
? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||
_createElementVNode("span", null, null, -1 /* HOISTED */)
|
||||
])))
|
||||
: _createCommentVNode("v-if", true)
|
||||
]))
|
||||
|
@ -423,7 +423,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
||||
return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||
_createElementVNode("span", null, null, -1 /* HOISTED */)
|
||||
])))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
]))
|
||||
|
|
|
@ -9,7 +9,7 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return _cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"]),
|
||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
)
|
||||
|
@ -29,7 +29,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"]),
|
||||
(_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
)
|
||||
|
@ -48,7 +48,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"]),
|
||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
)
|
||||
|
@ -67,7 +67,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[0] = _renderSlot($slots, "default"),
|
||||
(_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
)
|
||||
|
@ -86,7 +86,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[0] = _createElementVNode("div"),
|
||||
(_cache[0] = _createElementVNode("div")).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
)
|
||||
|
|
|
@ -21,7 +21,7 @@ import { transformIf } from '../../src/transforms/vIf'
|
|||
import { transformFor } from '../../src/transforms/vFor'
|
||||
import { transformBind } from '../../src/transforms/vBind'
|
||||
import { transformOn } from '../../src/transforms/vOn'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
import { createObjectMatcher } from '../testUtils'
|
||||
import { transformText } from '../../src/transforms/transformText'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
|
@ -348,7 +348,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
id: `[foo]`,
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
patchFlag: PatchFlags.PROPS,
|
||||
dynamicProps: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `_hoisted_1`,
|
||||
|
@ -402,7 +402,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
ref: `[foo]`,
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.NEED_PATCH),
|
||||
patchFlag: PatchFlags.NEED_PATCH,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -424,7 +424,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
content: `_hoisted_1`,
|
||||
},
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.NEED_PATCH),
|
||||
patchFlag: PatchFlags.NEED_PATCH,
|
||||
directives: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
|
@ -448,7 +448,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
tag: `"div"`,
|
||||
props: { content: `_hoisted_1` },
|
||||
children: { type: NodeTypes.INTERPOLATION },
|
||||
patchFlag: genFlagText(PatchFlags.TEXT),
|
||||
patchFlag: PatchFlags.TEXT,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -520,7 +520,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
},
|
||||
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
patchFlag: PatchFlags.UNKEYED_FRAGMENT,
|
||||
})
|
||||
const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
|
||||
expect(innerBlockCodegen.returns).toMatchObject({
|
||||
|
@ -620,7 +620,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
constType: ConstantTypes.NOT_CONSTANT,
|
||||
},
|
||||
},
|
||||
patchFlag: `1 /* TEXT */`,
|
||||
patchFlag: PatchFlags.TEXT,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -750,7 +750,7 @@ describe('compiler: cacheStatic transform', () => {
|
|||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
},
|
||||
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
patchFlag: PatchFlags.UNKEYED_FRAGMENT,
|
||||
})
|
||||
const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
|
||||
expect(innerBlockCodegen.returns).toMatchObject({
|
||||
|
|
|
@ -37,7 +37,7 @@ import { transformStyle } from '../../../compiler-dom/src/transforms/transformSt
|
|||
import { transformOn } from '../../src/transforms/vOn'
|
||||
import { transformBind } from '../../src/transforms/vBind'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
import { createObjectMatcher } from '../testUtils'
|
||||
import { transformText } from '../../src/transforms/transformText'
|
||||
import { parseWithForTransform } from './vFor.spec'
|
||||
|
||||
|
@ -521,7 +521,7 @@ describe('compiler: element transform', () => {
|
|||
// keep-alive should not compile content to slots
|
||||
children: [{ type: NodeTypes.ELEMENT, tag: 'span' }],
|
||||
// should get a dynamic slots flag to force updates
|
||||
patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS),
|
||||
patchFlag: PatchFlags.DYNAMIC_SLOTS,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -588,7 +588,7 @@ describe('compiler: element transform', () => {
|
|||
})
|
||||
// should factor in props returned by custom directive transforms
|
||||
// in patchFlag analysis
|
||||
expect(node.patchFlag).toMatch(PatchFlags.PROPS + '')
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS)
|
||||
expect(node.dynamicProps).toMatch(`"bar"`)
|
||||
})
|
||||
|
||||
|
@ -612,7 +612,7 @@ describe('compiler: element transform', () => {
|
|||
tag: `"div"`,
|
||||
props: undefined,
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.NEED_PATCH), // should generate appropriate flag
|
||||
patchFlag: PatchFlags.NEED_PATCH, // should generate appropriate flag
|
||||
directives: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
elements: [
|
||||
|
@ -945,26 +945,26 @@ describe('compiler: element transform', () => {
|
|||
expect(node.patchFlag).toBeUndefined()
|
||||
|
||||
const { node: node2 } = parseWithBind(`<div>{{ foo }}</div>`)
|
||||
expect(node2.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
|
||||
expect(node2.patchFlag).toBe(PatchFlags.TEXT)
|
||||
|
||||
// multiple nodes, merged with optimize text
|
||||
const { node: node3 } = parseWithBind(`<div>foo {{ bar }} baz</div>`)
|
||||
expect(node3.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
|
||||
expect(node3.patchFlag).toBe(PatchFlags.TEXT)
|
||||
})
|
||||
|
||||
test('CLASS', () => {
|
||||
const { node } = parseWithBind(`<div :class="foo" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.CLASS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.CLASS)
|
||||
})
|
||||
|
||||
test('STYLE', () => {
|
||||
const { node } = parseWithBind(`<div :style="foo" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.STYLE))
|
||||
expect(node.patchFlag).toBe(PatchFlags.STYLE)
|
||||
})
|
||||
|
||||
test('PROPS', () => {
|
||||
const { node } = parseWithBind(`<div id="foo" :foo="bar" :baz="qux" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS)
|
||||
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
|
||||
})
|
||||
|
||||
|
@ -973,7 +973,7 @@ describe('compiler: element transform', () => {
|
|||
`<div id="foo" :class="cls" :style="styl" :foo="bar" :baz="qux"/>`,
|
||||
)
|
||||
expect(node.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.CLASS, PatchFlags.STYLE, PatchFlags.PROPS]),
|
||||
PatchFlags.CLASS | PatchFlags.STYLE | PatchFlags.PROPS,
|
||||
)
|
||||
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
|
||||
})
|
||||
|
@ -983,40 +983,40 @@ describe('compiler: element transform', () => {
|
|||
const { node } = parseWithBind(
|
||||
`<Foo :id="foo" :class="cls" :style="styl" />`,
|
||||
)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS)
|
||||
expect(node.dynamicProps).toBe(`["id", "class", "style"]`)
|
||||
})
|
||||
|
||||
test('FULL_PROPS (v-bind)', () => {
|
||||
const { node } = parseWithBind(`<div v-bind="foo" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS)
|
||||
})
|
||||
|
||||
test('FULL_PROPS (dynamic key)', () => {
|
||||
const { node } = parseWithBind(`<div :[foo]="bar" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS)
|
||||
})
|
||||
|
||||
test('FULL_PROPS (w/ others)', () => {
|
||||
const { node } = parseWithBind(
|
||||
`<div id="foo" v-bind="bar" :class="cls" />`,
|
||||
)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.FULL_PROPS)
|
||||
})
|
||||
|
||||
test('NEED_PATCH (static ref)', () => {
|
||||
const { node } = parseWithBind(`<div ref="foo" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
|
||||
})
|
||||
|
||||
test('NEED_PATCH (dynamic ref)', () => {
|
||||
const { node } = parseWithBind(`<div :ref="foo" />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
|
||||
})
|
||||
|
||||
test('NEED_PATCH (custom directives)', () => {
|
||||
const { node } = parseWithBind(`<div v-foo />`)
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
|
||||
})
|
||||
|
||||
test('NEED_PATCH (vnode hooks)', () => {
|
||||
|
@ -1025,7 +1025,7 @@ describe('compiler: element transform', () => {
|
|||
cacheHandlers: true,
|
||||
}).ast
|
||||
const node = (root as any).children[0].codegenNode
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_PATCH)
|
||||
})
|
||||
|
||||
test('script setup inline mode template ref (binding exists)', () => {
|
||||
|
@ -1120,7 +1120,7 @@ describe('compiler: element transform', () => {
|
|||
},
|
||||
})
|
||||
// should only have props flag
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS)
|
||||
|
||||
const { node: node2 } = parseWithElementTransform(
|
||||
`<div @keyup="foo" />`,
|
||||
|
@ -1130,21 +1130,15 @@ describe('compiler: element transform', () => {
|
|||
},
|
||||
},
|
||||
)
|
||||
expect(node2.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]),
|
||||
)
|
||||
expect(node2.patchFlag).toBe(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]),
|
||||
)
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION)
|
||||
|
||||
const { node: node2 } = parseWithBind(`<div .id="id" />`)
|
||||
expect(node2.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]),
|
||||
)
|
||||
expect(node2.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION)
|
||||
})
|
||||
|
||||
// #5870
|
||||
|
@ -1157,9 +1151,7 @@ describe('compiler: element transform', () => {
|
|||
},
|
||||
},
|
||||
)
|
||||
expect(node.patchFlag).toBe(
|
||||
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]),
|
||||
)
|
||||
expect(node.patchFlag).toBe(PatchFlags.PROPS | PatchFlags.NEED_HYDRATION)
|
||||
})
|
||||
|
||||
test('should not have PROPS patchflag for constant v-on handlers', () => {
|
||||
|
@ -1173,7 +1165,7 @@ describe('compiler: element transform', () => {
|
|||
},
|
||||
})
|
||||
// should only have hydration flag
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_HYDRATION))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_HYDRATION)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -384,6 +384,17 @@ describe('compiler: expression transform', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('should not error', () => {
|
||||
const onError = vi.fn()
|
||||
parseWithExpressionTransform(
|
||||
`<p :id="undefined /* force override the id */"/>`,
|
||||
{
|
||||
onError,
|
||||
},
|
||||
)
|
||||
expect(onError).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('should prefix in assignment', () => {
|
||||
const node = parseWithExpressionTransform(
|
||||
`{{ x = 1 }}`,
|
||||
|
|
|
@ -18,8 +18,8 @@ import {
|
|||
import { ErrorCodes } from '../../src/errors'
|
||||
import { type CompilerOptions, generate } from '../../src'
|
||||
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
|
||||
import { PatchFlagNames, PatchFlags } from '@vue/shared'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { createObjectMatcher } from '../testUtils'
|
||||
|
||||
export function parseWithForTransform(
|
||||
template: string,
|
||||
|
@ -696,10 +696,10 @@ describe('compiler: v-for', () => {
|
|||
tag: FRAGMENT,
|
||||
disableTracking,
|
||||
patchFlag: !disableTracking
|
||||
? genFlagText(PatchFlags.STABLE_FRAGMENT)
|
||||
? PatchFlags.STABLE_FRAGMENT
|
||||
: keyed
|
||||
? genFlagText(PatchFlags.KEYED_FRAGMENT)
|
||||
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
? PatchFlags.KEYED_FRAGMENT
|
||||
: PatchFlags.UNKEYED_FRAGMENT,
|
||||
children: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
|
@ -822,7 +822,7 @@ describe('compiler: v-for', () => {
|
|||
constType: ConstantTypes.NOT_CONSTANT,
|
||||
},
|
||||
},
|
||||
patchFlag: genFlagText(PatchFlags.TEXT),
|
||||
patchFlag: PatchFlags.TEXT,
|
||||
},
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
|
@ -846,7 +846,7 @@ describe('compiler: v-for', () => {
|
|||
{ type: NodeTypes.TEXT, content: `hello` },
|
||||
{ type: NodeTypes.ELEMENT, tag: `span` },
|
||||
],
|
||||
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
|
||||
patchFlag: PatchFlags.STABLE_FRAGMENT,
|
||||
},
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
|
@ -950,7 +950,7 @@ describe('compiler: v-for', () => {
|
|||
{ type: NodeTypes.TEXT, content: `hello` },
|
||||
{ type: NodeTypes.ELEMENT, tag: `span` },
|
||||
],
|
||||
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
|
||||
patchFlag: PatchFlags.STABLE_FRAGMENT,
|
||||
},
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
|
@ -971,7 +971,7 @@ describe('compiler: v-for', () => {
|
|||
}),
|
||||
isBlock: true,
|
||||
disableTracking: true,
|
||||
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
patchFlag: PatchFlags.UNKEYED_FRAGMENT,
|
||||
children: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
|
@ -1009,7 +1009,7 @@ describe('compiler: v-for', () => {
|
|||
}),
|
||||
isBlock: true,
|
||||
disableTracking: true,
|
||||
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
patchFlag: PatchFlags.UNKEYED_FRAGMENT,
|
||||
children: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
|
@ -1048,9 +1048,7 @@ describe('compiler: v-for', () => {
|
|||
const {
|
||||
node: { codegenNode },
|
||||
} = parseWithForTransform('<div v-for="key in keys" :key>test</div>')
|
||||
expect(codegenNode.patchFlag).toBe(
|
||||
`${PatchFlags.KEYED_FRAGMENT} /* ${PatchFlagNames[PatchFlags.KEYED_FRAGMENT]} */`,
|
||||
)
|
||||
expect(codegenNode.patchFlag).toBe(PatchFlags.KEYED_FRAGMENT)
|
||||
})
|
||||
|
||||
test('template v-for key w/ :key shorthand on template injected to the child', () => {
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
trackVForSlotScopes,
|
||||
} from '../../src/transforms/vSlot'
|
||||
import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeHelpers'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
import { createObjectMatcher } from '../testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { transformFor } from '../../src/transforms/vFor'
|
||||
import { transformIf } from '../../src/transforms/vIf'
|
||||
|
@ -432,7 +432,7 @@ describe('compiler: transform component slots', () => {
|
|||
),
|
||||
// nested slot should be forced dynamic, since scope variables
|
||||
// are not tracked as dependencies of the slot.
|
||||
patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS),
|
||||
patchFlag: PatchFlags.DYNAMIC_SLOTS,
|
||||
},
|
||||
},
|
||||
// test scope
|
||||
|
@ -474,9 +474,7 @@ describe('compiler: transform component slots', () => {
|
|||
const div = ((root.children[0] as ForNode).children[0] as ElementNode)
|
||||
.codegenNode as any
|
||||
const comp = div.children[0]
|
||||
expect(comp.codegenNode.patchFlag).toBe(
|
||||
genFlagText(PatchFlags.DYNAMIC_SLOTS),
|
||||
)
|
||||
expect(comp.codegenNode.patchFlag).toBe(PatchFlags.DYNAMIC_SLOTS)
|
||||
})
|
||||
|
||||
test('should only force dynamic slots when actually using scope vars w/ prefixIdentifiers: true', () => {
|
||||
|
@ -494,7 +492,7 @@ describe('compiler: transform component slots', () => {
|
|||
flag = (innerComp.codegenNode as VNodeCall).patchFlag
|
||||
}
|
||||
if (shouldForce) {
|
||||
expect(flag).toBe(genFlagText(PatchFlags.DYNAMIC_SLOTS))
|
||||
expect(flag).toBe(PatchFlags.DYNAMIC_SLOTS)
|
||||
} else {
|
||||
expect(flag).toBeUndefined()
|
||||
}
|
||||
|
@ -581,8 +579,8 @@ describe('compiler: transform component slots', () => {
|
|||
},
|
||||
],
|
||||
})
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||
PatchFlags.DYNAMIC_SLOTS + '',
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
@ -630,8 +628,8 @@ describe('compiler: transform component slots', () => {
|
|||
},
|
||||
],
|
||||
})
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||
PatchFlags.DYNAMIC_SLOTS + '',
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||
})
|
||||
|
@ -693,8 +691,8 @@ describe('compiler: transform component slots', () => {
|
|||
},
|
||||
],
|
||||
})
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||
PatchFlags.DYNAMIC_SLOTS + '',
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
expect((root as any).children[0].children.length).toBe(3)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
|
@ -744,8 +742,8 @@ describe('compiler: transform component slots', () => {
|
|||
},
|
||||
],
|
||||
})
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||
PatchFlags.DYNAMIC_SLOTS + '',
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
@ -46,13 +46,13 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-core#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/parser": "catalog:",
|
||||
"@vue/shared": "workspace:*",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.0"
|
||||
"estree-walker": "catalog:",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.24.7"
|
||||
"@babel/types": "catalog:"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { isString } from '@vue/shared'
|
||||
import { type PatchFlags, isString } from '@vue/shared'
|
||||
import {
|
||||
CREATE_BLOCK,
|
||||
CREATE_ELEMENT_BLOCK,
|
||||
|
@ -339,7 +339,7 @@ export interface VNodeCall extends Node {
|
|||
| SimpleExpressionNode // hoisted
|
||||
| CacheExpression // cached
|
||||
| undefined
|
||||
patchFlag: string | undefined
|
||||
patchFlag: PatchFlags | undefined
|
||||
dynamicProps: string | SimpleExpressionNode | undefined
|
||||
directives: DirectiveArguments | undefined
|
||||
isBlock: boolean
|
||||
|
@ -570,7 +570,7 @@ export interface ForCodegenNode extends VNodeCall {
|
|||
tag: typeof FRAGMENT
|
||||
props: undefined
|
||||
children: ForRenderListExpression
|
||||
patchFlag: string
|
||||
patchFlag: PatchFlags
|
||||
disableTracking: boolean
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export function walkIdentifiers(
|
|||
root: Node,
|
||||
onIdentifier: (
|
||||
node: Identifier,
|
||||
parent: Node,
|
||||
parent: Node | null,
|
||||
parentStack: Node[],
|
||||
isReference: boolean,
|
||||
isLocal: boolean,
|
||||
|
@ -36,7 +36,7 @@ export function walkIdentifiers(
|
|||
: root
|
||||
|
||||
walk(root, {
|
||||
enter(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
|
||||
enter(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
|
||||
parent && parentStack.push(parent)
|
||||
if (
|
||||
parent &&
|
||||
|
@ -47,9 +47,9 @@ export function walkIdentifiers(
|
|||
}
|
||||
if (node.type === 'Identifier') {
|
||||
const isLocal = !!knownIds[node.name]
|
||||
const isRefed = isReferencedIdentifier(node, parent!, parentStack)
|
||||
const isRefed = isReferencedIdentifier(node, parent, parentStack)
|
||||
if (includeAll || (isRefed && !isLocal)) {
|
||||
onIdentifier(node, parent!, parentStack, isRefed, isLocal)
|
||||
onIdentifier(node, parent, parentStack, isRefed, isLocal)
|
||||
}
|
||||
} else if (
|
||||
node.type === 'ObjectProperty' &&
|
||||
|
@ -79,7 +79,7 @@ export function walkIdentifiers(
|
|||
}
|
||||
}
|
||||
},
|
||||
leave(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
|
||||
leave(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
|
||||
parent && parentStack.pop()
|
||||
if (node !== rootExp && node.scopeIds) {
|
||||
for (const id of node.scopeIds) {
|
||||
|
|
|
@ -35,7 +35,13 @@ import {
|
|||
isSimpleIdentifier,
|
||||
toValidAssetId,
|
||||
} from './utils'
|
||||
import { isArray, isString, isSymbol } from '@vue/shared'
|
||||
import {
|
||||
PatchFlagNames,
|
||||
type PatchFlags,
|
||||
isArray,
|
||||
isString,
|
||||
isSymbol,
|
||||
} from '@vue/shared'
|
||||
import {
|
||||
CREATE_COMMENT,
|
||||
CREATE_ELEMENT_VNODE,
|
||||
|
@ -833,6 +839,28 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
|||
disableTracking,
|
||||
isComponent,
|
||||
} = node
|
||||
|
||||
// add dev annotations to patch flags
|
||||
let patchFlagString
|
||||
if (patchFlag) {
|
||||
if (__DEV__) {
|
||||
if (patchFlag < 0) {
|
||||
// special flags (negative and mutually exclusive)
|
||||
patchFlagString = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
|
||||
} else {
|
||||
// bitwise flags
|
||||
const flagNames = Object.keys(PatchFlagNames)
|
||||
.map(Number)
|
||||
.filter(n => n > 0 && patchFlag & n)
|
||||
.map(n => PatchFlagNames[n as PatchFlags])
|
||||
.join(`, `)
|
||||
patchFlagString = patchFlag + ` /* ${flagNames} */`
|
||||
}
|
||||
} else {
|
||||
patchFlagString = String(patchFlag)
|
||||
}
|
||||
}
|
||||
|
||||
if (directives) {
|
||||
push(helper(WITH_DIRECTIVES) + `(`)
|
||||
}
|
||||
|
@ -847,7 +875,7 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
|||
: getVNodeHelper(context.inSSR, isComponent)
|
||||
push(helper(callHelper) + `(`, NewlineType.None, node)
|
||||
genNodeList(
|
||||
genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
|
||||
genNullableArgs([tag, props, children, patchFlagString, dynamicProps]),
|
||||
context,
|
||||
)
|
||||
push(`)`)
|
||||
|
@ -1007,11 +1035,12 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
|||
indent()
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
|
||||
newline()
|
||||
push(`(`)
|
||||
}
|
||||
push(`_cache[${node.index}] = `)
|
||||
genNode(node.value, context)
|
||||
if (needPauseTracking) {
|
||||
push(`,`)
|
||||
push(`).cacheIndex = ${node.index},`)
|
||||
newline()
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
|
||||
newline()
|
||||
|
|
|
@ -388,7 +388,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
|||
helper(FRAGMENT),
|
||||
undefined,
|
||||
root.children,
|
||||
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
|
||||
patchFlag,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
|
|
|
@ -75,8 +75,7 @@ function walk(
|
|||
: getConstantType(child, context)
|
||||
if (constantType > ConstantTypes.NOT_CONSTANT) {
|
||||
if (constantType >= ConstantTypes.CAN_CACHE) {
|
||||
;(child.codegenNode as VNodeCall).patchFlag =
|
||||
PatchFlags.CACHED + (__DEV__ ? ` /* CACHED */` : ``)
|
||||
;(child.codegenNode as VNodeCall).patchFlag = PatchFlags.CACHED
|
||||
toCache.push(child)
|
||||
continue
|
||||
}
|
||||
|
@ -85,9 +84,9 @@ function walk(
|
|||
// hoisting.
|
||||
const codegenNode = child.codegenNode!
|
||||
if (codegenNode.type === NodeTypes.VNODE_CALL) {
|
||||
const flag = getPatchFlag(codegenNode)
|
||||
const flag = codegenNode.patchFlag
|
||||
if (
|
||||
(!flag ||
|
||||
(flag === undefined ||
|
||||
flag === PatchFlags.NEED_PATCH ||
|
||||
flag === PatchFlags.TEXT) &&
|
||||
getGeneratedPropsConstantType(child, context) >=
|
||||
|
@ -259,8 +258,7 @@ export function getConstantType(
|
|||
) {
|
||||
return ConstantTypes.NOT_CONSTANT
|
||||
}
|
||||
const flag = getPatchFlag(codegenNode)
|
||||
if (!flag) {
|
||||
if (codegenNode.patchFlag === undefined) {
|
||||
let returnType = ConstantTypes.CAN_STRINGIFY
|
||||
|
||||
// Element itself has no patch flag. However we still need to check:
|
||||
|
@ -447,8 +445,3 @@ function getNodeProps(node: PlainElementNode) {
|
|||
return codegenNode.props
|
||||
}
|
||||
}
|
||||
|
||||
function getPatchFlag(node: VNodeCall): number | undefined {
|
||||
const flag = node.patchFlag
|
||||
return flag ? parseInt(flag, 10) : undefined
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import {
|
|||
createVNodeCall,
|
||||
} from '../ast'
|
||||
import {
|
||||
PatchFlagNames,
|
||||
PatchFlags,
|
||||
camelize,
|
||||
capitalize,
|
||||
|
@ -101,8 +100,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||
|
||||
let vnodeProps: VNodeCall['props']
|
||||
let vnodeChildren: VNodeCall['children']
|
||||
let vnodePatchFlag: VNodeCall['patchFlag']
|
||||
let patchFlag: number = 0
|
||||
let patchFlag: VNodeCall['patchFlag'] | 0 = 0
|
||||
let vnodeDynamicProps: VNodeCall['dynamicProps']
|
||||
let dynamicPropNames: string[] | undefined
|
||||
let vnodeDirectives: VNodeCall['directives']
|
||||
|
@ -206,27 +204,8 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||
}
|
||||
|
||||
// patchFlag & dynamicPropNames
|
||||
if (patchFlag !== 0) {
|
||||
if (__DEV__) {
|
||||
if (patchFlag < 0) {
|
||||
// special flags (negative and mutually exclusive)
|
||||
vnodePatchFlag =
|
||||
patchFlag + ` /* ${PatchFlagNames[patchFlag as PatchFlags]} */`
|
||||
} else {
|
||||
// bitwise flags
|
||||
const flagNames = Object.keys(PatchFlagNames)
|
||||
.map(Number)
|
||||
.filter(n => n > 0 && patchFlag & n)
|
||||
.map(n => PatchFlagNames[n as PatchFlags])
|
||||
.join(`, `)
|
||||
vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
|
||||
}
|
||||
} else {
|
||||
vnodePatchFlag = String(patchFlag)
|
||||
}
|
||||
if (dynamicPropNames && dynamicPropNames.length) {
|
||||
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
|
||||
}
|
||||
if (dynamicPropNames && dynamicPropNames.length) {
|
||||
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
|
||||
}
|
||||
|
||||
node.codegenNode = createVNodeCall(
|
||||
|
@ -234,7 +213,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||
vnodeTag,
|
||||
vnodeProps,
|
||||
vnodeChildren,
|
||||
vnodePatchFlag,
|
||||
patchFlag === 0 ? undefined : patchFlag,
|
||||
vnodeDynamicProps,
|
||||
vnodeDirectives,
|
||||
!!shouldUseBlock,
|
||||
|
|
|
@ -118,7 +118,11 @@ export function processExpression(
|
|||
}
|
||||
|
||||
const { inline, bindingMetadata } = context
|
||||
const rewriteIdentifier = (raw: string, parent?: Node, id?: Identifier) => {
|
||||
const rewriteIdentifier = (
|
||||
raw: string,
|
||||
parent?: Node | null,
|
||||
id?: Identifier,
|
||||
) => {
|
||||
const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw]
|
||||
if (inline) {
|
||||
// x = y
|
||||
|
@ -315,9 +319,10 @@ export function processExpression(
|
|||
// local scope variable (a v-for alias, or a v-slot prop)
|
||||
if (
|
||||
!(needPrefix && isLocal) &&
|
||||
parent.type !== 'CallExpression' &&
|
||||
parent.type !== 'NewExpression' &&
|
||||
parent.type !== 'MemberExpression'
|
||||
(!parent ||
|
||||
(parent.type !== 'CallExpression' &&
|
||||
parent.type !== 'NewExpression' &&
|
||||
parent.type !== 'MemberExpression'))
|
||||
) {
|
||||
;(node as QualifiedId).isConstant = true
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ import {
|
|||
} from '../runtimeHelpers'
|
||||
import { processExpression } from './transformExpression'
|
||||
import { validateBrowserExpression } from '../validateExpression'
|
||||
import { PatchFlagNames, PatchFlags } from '@vue/shared'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { transformBindShorthand } from './vBind'
|
||||
|
||||
export const transformFor = createStructuralDirectiveTransform(
|
||||
|
@ -109,8 +109,7 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
helper(FRAGMENT),
|
||||
undefined,
|
||||
renderExp,
|
||||
fragmentFlag +
|
||||
(__DEV__ ? ` /* ${PatchFlagNames[fragmentFlag]} */` : ``),
|
||||
fragmentFlag,
|
||||
undefined,
|
||||
undefined,
|
||||
true /* isBlock */,
|
||||
|
@ -169,10 +168,7 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
helper(FRAGMENT),
|
||||
keyProperty ? createObjectExpression([keyProperty]) : undefined,
|
||||
node.children,
|
||||
PatchFlags.STABLE_FRAGMENT +
|
||||
(__DEV__
|
||||
? ` /* ${PatchFlagNames[PatchFlags.STABLE_FRAGMENT]} */`
|
||||
: ``),
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
|
|
|
@ -280,7 +280,7 @@ function createChildrenCodegenNode(
|
|||
helper(FRAGMENT),
|
||||
createObjectExpression([keyProperty]),
|
||||
children,
|
||||
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
|
||||
patchFlag,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
|
|
|
@ -40,5 +40,7 @@ describe('decodeHtmlBrowser', () => {
|
|||
true,
|
||||
),
|
||||
).toBe('<strong><strong>&</strong></strong>')
|
||||
expect(decodeHtmlBrowser('"', true)).toBe('"')
|
||||
expect(decodeHtmlBrowser("'", true)).toBe("'")
|
||||
})
|
||||
})
|
||||
|
|
|
@ -31,7 +31,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 })
|
||||
], -1 /* CACHED */)
|
||||
], -1 /* HOISTED */)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
@ -48,7 +48,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("img", { src: _imports_0_ })
|
||||
], -1 /* CACHED */)
|
||||
], -1 /* HOISTED */)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -6,10 +6,7 @@ import {
|
|||
} from '@vue/compiler-core'
|
||||
import { transformVHtml } from '../../src/transforms/vHtml'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
import {
|
||||
createObjectMatcher,
|
||||
genFlagText,
|
||||
} from '../../../compiler-core/__tests__/testUtils'
|
||||
import { createObjectMatcher } from '../../../compiler-core/__tests__/testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { DOMErrorCodes } from '../../src/errors'
|
||||
|
||||
|
@ -34,7 +31,7 @@ describe('compiler: v-html transform', () => {
|
|||
innerHTML: `[test]`,
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
patchFlag: PatchFlags.PROPS,
|
||||
dynamicProps: `["innerHTML"]`,
|
||||
})
|
||||
})
|
||||
|
@ -53,7 +50,7 @@ describe('compiler: v-html transform', () => {
|
|||
innerHTML: `[test]`,
|
||||
}),
|
||||
children: undefined, // <-- children should have been removed
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
patchFlag: PatchFlags.PROPS,
|
||||
dynamicProps: `["innerHTML"]`,
|
||||
})
|
||||
})
|
||||
|
|
|
@ -14,7 +14,6 @@ import { transformOn } from '../../src/transforms/vOn'
|
|||
import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../../src/runtimeHelpers'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
import { transformExpression } from '../../../compiler-core/src/transforms/transformExpression'
|
||||
import { genFlagText } from '../../../compiler-core/__tests__/testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
||||
|
@ -272,7 +271,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.NEED_HYDRATION),
|
||||
PatchFlags.NEED_HYDRATION,
|
||||
)
|
||||
expect(prop).toMatchObject({
|
||||
key: {
|
||||
|
@ -300,6 +299,6 @@ describe('compiler-dom: transform v-on', () => {
|
|||
},
|
||||
})
|
||||
// should only have hydration flag
|
||||
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_HYDRATION))
|
||||
expect(node.patchFlag).toBe(PatchFlags.NEED_HYDRATION)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,10 +6,7 @@ import {
|
|||
} from '@vue/compiler-core'
|
||||
import { transformVText } from '../../src/transforms/vText'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
import {
|
||||
createObjectMatcher,
|
||||
genFlagText,
|
||||
} from '../../../compiler-core/__tests__/testUtils'
|
||||
import { createObjectMatcher } from '../../../compiler-core/__tests__/testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { DOMErrorCodes } from '../../src/errors'
|
||||
|
||||
|
@ -36,7 +33,7 @@ describe('compiler: v-text transform', () => {
|
|||
},
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
patchFlag: PatchFlags.PROPS,
|
||||
dynamicProps: `["textContent"]`,
|
||||
})
|
||||
})
|
||||
|
@ -57,7 +54,7 @@ describe('compiler: v-text transform', () => {
|
|||
},
|
||||
}),
|
||||
children: undefined, // <-- children should have been removed
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
patchFlag: PatchFlags.PROPS,
|
||||
dynamicProps: `["textContent"]`,
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
|
|
@ -861,7 +861,7 @@ export default {
|
|||
return (_ctx, _cache) => {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_createElementVNode("div", null, _toDisplayString(count.value), 1 /* TEXT */),
|
||||
_cache[0] || (_cache[0] = _createElementVNode("div", null, "static", -1 /* CACHED */))
|
||||
_cache[0] || (_cache[0] = _createElementVNode("div", null, "static", -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,8 @@ const _hoisted_1 = _imports_0 + '#fragment'
|
|||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */))
|
||||
_cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -10,8 +10,8 @@ const _hoisted_2 = _imports_0 + ' 1x, ' + "/foo/logo.png" + ' 2x'
|
|||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* CACHED */))
|
||||
_cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
@ -35,51 +35,51 @@ export function render(_ctx, _cache) {
|
|||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_1
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_2
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_3
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_4
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_5
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_6
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_7
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_8
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* CACHED */))
|
||||
}, null, -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
@ -92,51 +92,51 @@ export function render(_ctx, _cache) {
|
|||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png, /foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x, /foo/logo.png"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x, /foo/logo.png 3x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* CACHED */))
|
||||
}, null, -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
@ -162,51 +162,51 @@ export function render(_ctx, _cache) {
|
|||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_1
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_2
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_3
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_4
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_5
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_6
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_7
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_8
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_9
|
||||
}, null, -1 /* CACHED */)),
|
||||
}, null, -1 /* HOISTED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* CACHED */))
|
||||
}, null, -1 /* HOISTED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -87,7 +87,7 @@ export default /*#__PURE__*/_defineComponent({
|
|||
|
||||
const { foo } = __props
|
||||
|
||||
return { }
|
||||
return { foo }
|
||||
}
|
||||
|
||||
})"
|
||||
|
|
|
@ -591,7 +591,7 @@ const props = defineProps({ foo: String })
|
|||
|
||||
// #8289
|
||||
test('destructure without enabling reactive destructure', () => {
|
||||
const { content } = compile(
|
||||
const { content, bindings } = compile(
|
||||
`<script setup lang="ts">
|
||||
const { foo } = defineProps<{
|
||||
foo: Foo
|
||||
|
@ -602,6 +602,10 @@ const props = defineProps({ foo: String })
|
|||
},
|
||||
)
|
||||
expect(content).toMatch(`const { foo } = __props`)
|
||||
expect(content).toMatch(`return { foo }`)
|
||||
expect(bindings).toStrictEqual({
|
||||
foo: BindingTypes.SETUP_CONST,
|
||||
})
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
|
|
|
@ -635,6 +635,26 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
// #11266
|
||||
test('correctly parse type annotation for declared function', () => {
|
||||
const { props } = resolve(`
|
||||
import { ExtractPropTypes } from 'vue'
|
||||
interface UploadFile<T = any> {
|
||||
xhr?: T
|
||||
}
|
||||
declare function uploadProps<T = any>(): {
|
||||
fileList: {
|
||||
type: PropType<UploadFile<T>[]>
|
||||
default: UploadFile<T>[]
|
||||
}
|
||||
}
|
||||
type UploadProps = ExtractPropTypes<ReturnType<typeof uploadProps>>
|
||||
defineProps<UploadProps>()`)
|
||||
expect(props).toStrictEqual({
|
||||
fileList: ['Array'],
|
||||
})
|
||||
})
|
||||
|
||||
describe('generics', () => {
|
||||
test('generic with type literal', () => {
|
||||
expect(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
@ -42,27 +42,27 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-sfc#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/parser": "catalog:",
|
||||
"@vue/compiler-core": "workspace:*",
|
||||
"@vue/compiler-dom": "workspace:*",
|
||||
"@vue/compiler-ssr": "workspace:*",
|
||||
"@vue/compiler-vapor": "workspace:*",
|
||||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.10",
|
||||
"postcss": "^8.4.38",
|
||||
"source-map-js": "^1.2.0"
|
||||
"estree-walker": "catalog:",
|
||||
"magic-string": "catalog:",
|
||||
"postcss": "^8.4.39",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.24.7",
|
||||
"@babel/types": "catalog:",
|
||||
"@vue/consolidate": "^1.0.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "10.1.0",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "^9.0.4",
|
||||
"minimatch": "^9.0.5",
|
||||
"postcss-modules": "^6.0.0",
|
||||
"postcss-selector-parser": "^6.1.0",
|
||||
"pug": "^3.0.3",
|
||||
"sass": "^1.77.5"
|
||||
"sass": "^1.77.8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -607,6 +607,7 @@ export function compileScript(
|
|||
setupBindings,
|
||||
vueImportAliases,
|
||||
hoistStatic,
|
||||
!!ctx.propsDestructureDecl,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -623,7 +624,7 @@ export function compileScript(
|
|||
) {
|
||||
const scope: Statement[][] = [scriptSetupAst.body]
|
||||
walk(node, {
|
||||
enter(child: Node, parent: Node | undefined) {
|
||||
enter(child: Node, parent: Node | null) {
|
||||
if (isFunctionType(child)) {
|
||||
this.skip()
|
||||
}
|
||||
|
@ -1071,6 +1072,7 @@ function walkDeclaration(
|
|||
bindings: Record<string, BindingTypes>,
|
||||
userImportAliases: Record<string, string>,
|
||||
hoistStatic: boolean,
|
||||
isPropsDestructureEnabled = false,
|
||||
): boolean {
|
||||
let isAllLiteral = false
|
||||
|
||||
|
@ -1139,7 +1141,7 @@ function walkDeclaration(
|
|||
}
|
||||
registerBinding(bindings, id, bindingType)
|
||||
} else {
|
||||
if (isCallOf(init, DEFINE_PROPS)) {
|
||||
if (isCallOf(init, DEFINE_PROPS) && isPropsDestructureEnabled) {
|
||||
continue
|
||||
}
|
||||
if (id.type === 'ObjectPattern') {
|
||||
|
|
|
@ -233,7 +233,7 @@ export function transformDestructuredProps(
|
|||
const ast = ctx.scriptSetupAst!
|
||||
walkScope(ast, true)
|
||||
walk(ast, {
|
||||
enter(node: Node, parent?: Node) {
|
||||
enter(node: Node, parent: Node | null) {
|
||||
parent && parentStack.push(parent)
|
||||
|
||||
// skip type nodes
|
||||
|
@ -288,7 +288,7 @@ export function transformDestructuredProps(
|
|||
}
|
||||
}
|
||||
},
|
||||
leave(node: Node, parent?: Node) {
|
||||
leave(node: Node, parent: Node | null) {
|
||||
parent && parentStack.pop()
|
||||
if (
|
||||
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
|
||||
|
|
|
@ -177,6 +177,7 @@ function innerResolveTypeElements(
|
|||
case 'TSInterfaceDeclaration':
|
||||
return resolveInterfaceMembers(ctx, node, scope, typeParameters)
|
||||
case 'TSTypeAliasDeclaration':
|
||||
case 'TSTypeAnnotation':
|
||||
case 'TSParenthesizedType':
|
||||
return resolveTypeElements(
|
||||
ctx,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
toRefs,
|
||||
toValue,
|
||||
unref,
|
||||
useTemplateRef,
|
||||
} from 'vue'
|
||||
import { type IsAny, type IsUnion, describe, expectType } from './utils'
|
||||
|
||||
|
@ -452,3 +453,14 @@ describe('toRef <-> toValue', () => {
|
|||
),
|
||||
)
|
||||
})
|
||||
|
||||
// unref
|
||||
declare const text: ShallowRef<string> | ComputedRef<string> | MaybeRef<string>
|
||||
expectType<string>(unref(text))
|
||||
|
||||
// useTemplateRef
|
||||
const tRef = useTemplateRef('foo')
|
||||
expectType<Readonly<ShallowRef<unknown>>>(tRef)
|
||||
|
||||
const tRef2 = useTemplateRef<HTMLElement>('bar')
|
||||
expectType<Readonly<ShallowRef<HTMLElement | null>>>(tRef2)
|
||||
|
|
|
@ -19,15 +19,6 @@ declare var __FEATURE_PROD_DEVTOOLS__: boolean
|
|||
declare var __FEATURE_SUSPENSE__: boolean
|
||||
declare var __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: boolean
|
||||
|
||||
// for tests
|
||||
declare namespace jest {
|
||||
interface Matchers<R, T> {
|
||||
toHaveBeenWarned(): R
|
||||
toHaveBeenWarnedLast(): R
|
||||
toHaveBeenWarnedTimes(n: number): R
|
||||
}
|
||||
}
|
||||
|
||||
declare module '*.vue' {}
|
||||
|
||||
declare module 'file-saver' {
|
||||
|
@ -38,8 +29,8 @@ declare module 'estree-walker' {
|
|||
export function walk<T>(
|
||||
root: T,
|
||||
options: {
|
||||
enter?: (node: T, parent: T | undefined) => any
|
||||
leave?: (node: T, parent: T | undefined) => any
|
||||
enter?: (node: T, parent: T | null) => any
|
||||
leave?: (node: T, parent: T | null) => any
|
||||
exit?: (node: T) => any
|
||||
} & ThisType<{ skip: () => void }>,
|
||||
)
|
||||
|
|
|
@ -904,6 +904,22 @@ describe('reactivity/computed', () => {
|
|||
expect(serializeInner(root)).toBe('11')
|
||||
})
|
||||
|
||||
it('should be recomputed without being affected by side effects', () => {
|
||||
const v = ref(0)
|
||||
const c1 = computed(() => {
|
||||
v.value = 1
|
||||
return 0
|
||||
})
|
||||
const c2 = computed(() => {
|
||||
return v.value + ',' + c1.value
|
||||
})
|
||||
|
||||
expect(c2.value).toBe('0,0')
|
||||
v.value = 1
|
||||
expect(c2.value).toBe('1,0')
|
||||
// expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('debug: onTrigger (ref)', () => {
|
||||
let events: DebuggerEvent[] = []
|
||||
const onTrigger = vi.fn((e: DebuggerEvent) => {
|
||||
|
|
|
@ -2,6 +2,8 @@ import { isRef, ref } from '../src/ref'
|
|||
import {
|
||||
isProxy,
|
||||
isReactive,
|
||||
isReadonly,
|
||||
isShallow,
|
||||
markRaw,
|
||||
reactive,
|
||||
readonly,
|
||||
|
@ -359,4 +361,25 @@ describe('reactivity/reactive', () => {
|
|||
const c = computed(() => {})
|
||||
expect(isProxy(c)).toBe(false)
|
||||
})
|
||||
|
||||
test('The results of the shallow and readonly assignments are the same (Map)', () => {
|
||||
const map = reactive(new Map())
|
||||
map.set('foo', shallowReactive({ a: 2 }))
|
||||
expect(isShallow(map.get('foo'))).toBe(true)
|
||||
|
||||
map.set('bar', readonly({ b: 2 }))
|
||||
expect(isReadonly(map.get('bar'))).toBe(true)
|
||||
})
|
||||
|
||||
test('The results of the shallow and readonly assignments are the same (Set)', () => {
|
||||
const set = reactive(new Set())
|
||||
set.add(shallowReactive({ a: 2 }))
|
||||
set.add(readonly({ b: 2 }))
|
||||
let count = 0
|
||||
for (const i of set) {
|
||||
if (count === 0) expect(isShallow(i)).toBe(true)
|
||||
else expect(isReadonly(i)).toBe(true)
|
||||
count++
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -123,6 +123,29 @@ describe('shallowReactive', () => {
|
|||
shallowSet.forEach(x => expect(isReactive(x)).toBe(false))
|
||||
})
|
||||
|
||||
test('Setting a reactive object on a shallowReactive map', () => {
|
||||
const msg = ref('ads')
|
||||
const bar = reactive({ msg })
|
||||
const foo = shallowReactive(new Map([['foo1', bar]]))
|
||||
foo.set('foo2', bar)
|
||||
|
||||
expect(isReactive(foo.get('foo2'))).toBe(true)
|
||||
expect(isReactive(foo.get('foo1'))).toBe(true)
|
||||
})
|
||||
|
||||
test('Setting a reactive object on a shallowReactive set', () => {
|
||||
const msg = ref(1)
|
||||
const bar = reactive({ msg })
|
||||
const foo = reactive({ msg })
|
||||
|
||||
const deps = shallowReactive(new Set([bar]))
|
||||
deps.add(foo)
|
||||
|
||||
deps.forEach(dep => {
|
||||
expect(isReactive(dep)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// #1210
|
||||
test('onTrack on called on objectSpread', () => {
|
||||
const onTrackFn = vi.fn()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/reactivity",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
@ -198,7 +198,7 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
|
|||
// instrument iterators to take ARRAY_ITERATE dependency
|
||||
function iterator(
|
||||
self: unknown[],
|
||||
method: keyof Array<any>,
|
||||
method: keyof Array<unknown>,
|
||||
wrapValue: (value: any) => unknown,
|
||||
) {
|
||||
// note that taking ARRAY_ITERATE dependency here is not strictly equivalent
|
||||
|
@ -210,11 +210,13 @@ function iterator(
|
|||
// given that JS iterator can only be read once, this doesn't seem like
|
||||
// a plausible use-case, so this tracking simplification seems ok.
|
||||
const arr = shallowReadArray(self)
|
||||
const iter = (arr[method] as any)()
|
||||
const iter = (arr[method] as any)() as IterableIterator<unknown> & {
|
||||
_next: IterableIterator<unknown>['next']
|
||||
}
|
||||
if (arr !== self && !isShallow(self)) {
|
||||
;(iter as any)._next = iter.next
|
||||
iter._next = iter.next
|
||||
iter.next = () => {
|
||||
const result = (iter as any)._next()
|
||||
const result = iter._next()
|
||||
if (result.value) {
|
||||
result.value = wrapValue(result.value)
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ const builtInSymbols = new Set(
|
|||
// but accessing them on Symbol leads to TypeError because Symbol is a strict mode
|
||||
// function
|
||||
.filter(key => key !== 'arguments' && key !== 'caller')
|
||||
.map(key => (Symbol as any)[key])
|
||||
.map(key => Symbol[key as keyof SymbolConstructor])
|
||||
.filter(isSymbol),
|
||||
)
|
||||
|
||||
|
@ -137,12 +137,12 @@ class MutableReactiveHandler extends BaseReactiveHandler {
|
|||
}
|
||||
|
||||
set(
|
||||
target: object,
|
||||
target: Record<string | symbol, unknown>,
|
||||
key: string | symbol,
|
||||
value: unknown,
|
||||
receiver: object,
|
||||
): boolean {
|
||||
let oldValue = (target as any)[key]
|
||||
let oldValue = target[key]
|
||||
if (!this._isShallow) {
|
||||
const isOldValueReadonly = isReadonly(oldValue)
|
||||
if (!isShallow(value) && !isReadonly(value)) {
|
||||
|
@ -177,9 +177,12 @@ class MutableReactiveHandler extends BaseReactiveHandler {
|
|||
return result
|
||||
}
|
||||
|
||||
deleteProperty(target: object, key: string | symbol): boolean {
|
||||
deleteProperty(
|
||||
target: Record<string | symbol, unknown>,
|
||||
key: string | symbol,
|
||||
): boolean {
|
||||
const hadKey = hasOwn(target, key)
|
||||
const oldValue = (target as any)[key]
|
||||
const oldValue = target[key]
|
||||
const result = Reflect.deleteProperty(target, key)
|
||||
if (result && hadKey) {
|
||||
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
|
||||
|
@ -187,14 +190,15 @@ class MutableReactiveHandler extends BaseReactiveHandler {
|
|||
return result
|
||||
}
|
||||
|
||||
has(target: object, key: string | symbol): boolean {
|
||||
has(target: Record<string | symbol, unknown>, key: string | symbol): boolean {
|
||||
const result = Reflect.has(target, key)
|
||||
if (!isSymbol(key) || !builtInSymbols.has(key)) {
|
||||
track(target, TrackOpTypes.HAS, key)
|
||||
}
|
||||
return result
|
||||
}
|
||||
ownKeys(target: object): (string | symbol)[] {
|
||||
|
||||
ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {
|
||||
track(
|
||||
target,
|
||||
TrackOpTypes.ITERATE,
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { toRaw, toReactive, toReadonly } from './reactive'
|
||||
import {
|
||||
type Target,
|
||||
isReadonly,
|
||||
isShallow,
|
||||
toRaw,
|
||||
toReactive,
|
||||
toReadonly,
|
||||
} from './reactive'
|
||||
import { ITERATE_KEY, MAP_KEY_ITERATE_KEY, track, trigger } from './dep'
|
||||
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
||||
import { capitalize, hasChanged, hasOwn, isMap, toRawType } from '@vue/shared'
|
||||
|
@ -6,10 +13,10 @@ import { warn } from './warning'
|
|||
|
||||
type CollectionTypes = IterableCollections | WeakCollections
|
||||
|
||||
type IterableCollections = Map<any, any> | Set<any>
|
||||
type WeakCollections = WeakMap<any, any> | WeakSet<any>
|
||||
type MapTypes = Map<any, any> | WeakMap<any, any>
|
||||
type SetTypes = Set<any> | WeakSet<any>
|
||||
type IterableCollections = (Map<any, any> | Set<any>) & Target
|
||||
type WeakCollections = (WeakMap<any, any> | WeakSet<any>) & Target
|
||||
type MapTypes = (Map<any, any> | WeakMap<any, any>) & Target
|
||||
type SetTypes = (Set<any> | WeakSet<any>) & Target
|
||||
|
||||
const toShallow = <T extends unknown>(value: T): T => value
|
||||
|
||||
|
@ -24,7 +31,7 @@ function get(
|
|||
) {
|
||||
// #1772: readonly(reactive(Map)) should return readonly + reactive version
|
||||
// of the value
|
||||
target = (target as any)[ReactiveFlags.RAW]
|
||||
target = target[ReactiveFlags.RAW]
|
||||
const rawTarget = toRaw(target)
|
||||
const rawKey = toRaw(key)
|
||||
if (!isReadonly) {
|
||||
|
@ -47,7 +54,7 @@ function get(
|
|||
}
|
||||
|
||||
function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
|
||||
const target = (this as any)[ReactiveFlags.RAW]
|
||||
const target = this[ReactiveFlags.RAW]
|
||||
const rawTarget = toRaw(target)
|
||||
const rawKey = toRaw(key)
|
||||
if (!isReadonly) {
|
||||
|
@ -62,13 +69,15 @@ function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
|
|||
}
|
||||
|
||||
function size(target: IterableCollections, isReadonly = false) {
|
||||
target = (target as any)[ReactiveFlags.RAW]
|
||||
target = target[ReactiveFlags.RAW]
|
||||
!isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
|
||||
return Reflect.get(target, 'size', target)
|
||||
}
|
||||
|
||||
function add(this: SetTypes, value: unknown) {
|
||||
value = toRaw(value)
|
||||
function add(this: SetTypes, value: unknown, _isShallow = false) {
|
||||
if (!_isShallow && !isShallow(value) && !isReadonly(value)) {
|
||||
value = toRaw(value)
|
||||
}
|
||||
const target = toRaw(this)
|
||||
const proto = getProto(target)
|
||||
const hadKey = proto.has.call(target, value)
|
||||
|
@ -79,8 +88,10 @@ function add(this: SetTypes, value: unknown) {
|
|||
return this
|
||||
}
|
||||
|
||||
function set(this: MapTypes, key: unknown, value: unknown) {
|
||||
value = toRaw(value)
|
||||
function set(this: MapTypes, key: unknown, value: unknown, _isShallow = false) {
|
||||
if (!_isShallow && !isShallow(value) && !isReadonly(value)) {
|
||||
value = toRaw(value)
|
||||
}
|
||||
const target = toRaw(this)
|
||||
const { has, get } = getProto(target)
|
||||
|
||||
|
@ -144,7 +155,7 @@ function createForEach(isReadonly: boolean, isShallow: boolean) {
|
|||
callback: Function,
|
||||
thisArg?: unknown,
|
||||
) {
|
||||
const observed = this as any
|
||||
const observed = this
|
||||
const target = observed[ReactiveFlags.RAW]
|
||||
const rawTarget = toRaw(target)
|
||||
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
|
||||
|
@ -180,7 +191,7 @@ function createIterableMethod(
|
|||
this: IterableCollections,
|
||||
...args: unknown[]
|
||||
): Iterable & Iterator {
|
||||
const target = (this as any)[ReactiveFlags.RAW]
|
||||
const target = this[ReactiveFlags.RAW]
|
||||
const rawTarget = toRaw(target)
|
||||
const targetIsMap = isMap(rawTarget)
|
||||
const isPair =
|
||||
|
@ -258,8 +269,12 @@ function createInstrumentations() {
|
|||
return size(this as unknown as IterableCollections)
|
||||
},
|
||||
has,
|
||||
add,
|
||||
set,
|
||||
add(this: SetTypes, value: unknown) {
|
||||
return add.call(this, value, true)
|
||||
},
|
||||
set(this: MapTypes, key: unknown, value: unknown) {
|
||||
return set.call(this, key, value, true)
|
||||
},
|
||||
delete: deleteEntry,
|
||||
clear,
|
||||
forEach: createForEach(false, true),
|
||||
|
|
|
@ -210,7 +210,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> | ComputedRef<T>): T {
|
||||
export function unref<T>(ref: MaybeRef<T> | ComputedRef<T> | ShallowRef<T>): T {
|
||||
return isRef(ref) ? ref.value : ref
|
||||
}
|
||||
|
||||
|
@ -230,7 +230,9 @@ export function unref<T>(ref: MaybeRef<T> | ComputedRef<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> | ComputedRef<T>): T {
|
||||
export function toValue<T>(
|
||||
source: MaybeRefOrGetter<T> | ComputedRef<T> | ShallowRef<T>,
|
||||
): T {
|
||||
return isFunction(source) ? source() : unref(source)
|
||||
}
|
||||
|
||||
|
@ -248,11 +250,9 @@ const shallowUnwrapHandlers: ProxyHandler<any> = {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a reactive proxy for the given object.
|
||||
*
|
||||
* If the object already is reactive, it's returned as-is. If not, a new
|
||||
* reactive proxy is created. Direct child properties that are refs are properly
|
||||
* handled, as well.
|
||||
* Returns a proxy for the given object that shallowly unwraps properties that
|
||||
* are refs. If the object already is reactive, it's returned as-is. If not, a
|
||||
* new reactive proxy is created.
|
||||
*
|
||||
* @param objectWithRefs - Either an already-reactive object or a simple object
|
||||
* that contains refs.
|
||||
|
|
|
@ -538,6 +538,23 @@ describe('api: createApp', () => {
|
|||
expect(serializeInner(root)).toBe('hello')
|
||||
})
|
||||
|
||||
test('config.throwUnhandledErrorInProduction', () => {
|
||||
__DEV__ = false
|
||||
try {
|
||||
const err = new Error()
|
||||
const app = createApp({
|
||||
setup() {
|
||||
throw err
|
||||
},
|
||||
})
|
||||
app.config.throwUnhandledErrorInProduction = true
|
||||
const root = nodeOps.createElement('div')
|
||||
expect(() => app.mount(root)).toThrow(err)
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
}
|
||||
})
|
||||
|
||||
test('return property "_" should not overwrite "ctx._", __isScriptSetup: false', () => {
|
||||
const Comp = defineComponent({
|
||||
setup() {
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
defineComponent,
|
||||
getCurrentInstance,
|
||||
nextTick,
|
||||
onErrorCaptured,
|
||||
onWatcherCleanup,
|
||||
reactive,
|
||||
ref,
|
||||
|
@ -1649,4 +1650,60 @@ describe('api: watch', () => {
|
|||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
expect(foo.value.a).toBe(2)
|
||||
})
|
||||
|
||||
test('watch immediate error in effect scope should be catched by onErrorCaptured', async () => {
|
||||
const warn = vi.spyOn(console, 'warn')
|
||||
warn.mockImplementation(() => {})
|
||||
const ERROR_IN_SCOPE = 'ERROR_IN_SCOPE'
|
||||
const ERROR_OUT_SCOPE = 'ERROR_OUT_SCOPE'
|
||||
|
||||
const errors = ref<string[]>([])
|
||||
const Comp = {
|
||||
setup() {
|
||||
const trigger = ref(0)
|
||||
|
||||
effectScope(true).run(() => {
|
||||
watch(
|
||||
trigger,
|
||||
() => {
|
||||
throw new Error(ERROR_IN_SCOPE)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
throw new Error(ERROR_OUT_SCOPE)
|
||||
})
|
||||
|
||||
return () => ''
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(
|
||||
h(
|
||||
{
|
||||
setup(_, { slots }) {
|
||||
onErrorCaptured(e => {
|
||||
errors.value.push(e.message)
|
||||
return false
|
||||
})
|
||||
|
||||
return () => h('div', slots.default && slots.default())
|
||||
},
|
||||
},
|
||||
null,
|
||||
() => [h(Comp)],
|
||||
),
|
||||
root,
|
||||
)
|
||||
await nextTick()
|
||||
// only watchEffect as ran so far
|
||||
expect(errors.value).toHaveLength(2)
|
||||
expect(errors.value[0]).toBe(ERROR_IN_SCOPE)
|
||||
expect(errors.value[1]).toBe(ERROR_OUT_SCOPE)
|
||||
|
||||
warn.mockRestore()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -356,6 +356,83 @@ describe('component: emit', () => {
|
|||
expect(fn2).toHaveBeenCalledWith('two')
|
||||
})
|
||||
|
||||
test('.trim modifier should work with v-model on component for kebab-cased props and camelCased emit', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
created() {
|
||||
this.$emit('update:firstName', ' one ')
|
||||
},
|
||||
})
|
||||
|
||||
const fn1 = vi.fn()
|
||||
|
||||
const Comp = () =>
|
||||
h(Foo, {
|
||||
'first-name': null,
|
||||
'first-nameModifiers': { trim: true },
|
||||
'onUpdate:first-name': fn1,
|
||||
})
|
||||
|
||||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
expect(fn1).toHaveBeenCalledTimes(1)
|
||||
expect(fn1).toHaveBeenCalledWith('one')
|
||||
})
|
||||
|
||||
test('.trim modifier should work with v-model on component for camelCased props and kebab-cased emit', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
created() {
|
||||
this.$emit('update:model-value', ' one ')
|
||||
this.$emit('update:first-name', ' two ')
|
||||
},
|
||||
})
|
||||
|
||||
const fn1 = vi.fn()
|
||||
const fn2 = vi.fn()
|
||||
|
||||
const Comp = () =>
|
||||
h(Foo, {
|
||||
modelValue: null,
|
||||
modelModifiers: { trim: true },
|
||||
'onUpdate:modelValue': fn1,
|
||||
|
||||
firstName: null,
|
||||
firstNameModifiers: { trim: true },
|
||||
'onUpdate:firstName': fn2,
|
||||
})
|
||||
|
||||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
expect(fn1).toHaveBeenCalledTimes(1)
|
||||
expect(fn1).toHaveBeenCalledWith('one')
|
||||
expect(fn2).toHaveBeenCalledTimes(1)
|
||||
expect(fn2).toHaveBeenCalledWith('two')
|
||||
})
|
||||
|
||||
test('.trim modifier should work with v-model on component for mixed cased props and emit', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
created() {
|
||||
this.$emit('update:base-URL', ' one ')
|
||||
},
|
||||
})
|
||||
|
||||
const fn1 = vi.fn()
|
||||
|
||||
const Comp = () =>
|
||||
h(Foo, {
|
||||
'base-URL': null,
|
||||
'base-URLModifiers': { trim: true },
|
||||
'onUpdate:base-URL': fn1,
|
||||
})
|
||||
|
||||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
expect(fn1).toHaveBeenCalledTimes(1)
|
||||
expect(fn1).toHaveBeenCalledWith('one')
|
||||
})
|
||||
|
||||
test('.trim and .number modifiers should work with v-model on component', () => {
|
||||
const Foo = defineComponent({
|
||||
render() {},
|
||||
|
|
|
@ -538,6 +538,96 @@ describe('component props', () => {
|
|||
expect(renderProxy.$props).toMatchObject(props)
|
||||
})
|
||||
|
||||
test('merging props from global mixins and extends', () => {
|
||||
let renderProxy: any
|
||||
let extendedRenderProxy: any
|
||||
|
||||
const defaultProp = ' from global'
|
||||
const props = {
|
||||
globalProp: {
|
||||
type: String,
|
||||
default: defaultProp,
|
||||
},
|
||||
}
|
||||
const globalMixin = {
|
||||
props,
|
||||
}
|
||||
const Comp = {
|
||||
render(this: any) {
|
||||
renderProxy = this
|
||||
return h('div', ['Comp', this.globalProp])
|
||||
},
|
||||
}
|
||||
const ExtendedComp = {
|
||||
extends: Comp,
|
||||
render(this: any) {
|
||||
extendedRenderProxy = this
|
||||
return h('div', ['ExtendedComp', this.globalProp])
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp(
|
||||
{
|
||||
render: () => [h(ExtendedComp), h(Comp)],
|
||||
},
|
||||
{},
|
||||
)
|
||||
app.mixin(globalMixin)
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
app.mount(root)
|
||||
|
||||
expect(serializeInner(root)).toMatch(
|
||||
`<div>ExtendedComp from global</div><div>Comp from global</div>`,
|
||||
)
|
||||
expect(renderProxy.$props).toMatchObject({ globalProp: defaultProp })
|
||||
expect(extendedRenderProxy.$props).toMatchObject({
|
||||
globalProp: defaultProp,
|
||||
})
|
||||
})
|
||||
|
||||
test('merging props for a component that is also used as a mixin', () => {
|
||||
const CompA = {
|
||||
render(this: any) {
|
||||
return this.foo
|
||||
},
|
||||
}
|
||||
|
||||
const mixin = {
|
||||
props: {
|
||||
foo: {
|
||||
default: 'from mixin',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const CompB = {
|
||||
mixins: [mixin, CompA],
|
||||
render(this: any) {
|
||||
return this.foo
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
render() {
|
||||
return [h(CompA), ', ', h(CompB)]
|
||||
},
|
||||
})
|
||||
|
||||
app.mixin({
|
||||
props: {
|
||||
foo: {
|
||||
default: 'from global mixin',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
app.mount(root)
|
||||
|
||||
expect(serializeInner(root)).toMatch(`from global mixin, from mixin`)
|
||||
})
|
||||
|
||||
test('props type support BigInt', () => {
|
||||
const Comp = {
|
||||
props: {
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
ref,
|
||||
render,
|
||||
} from '@vue/runtime-test'
|
||||
import { normalizeVNode } from '../src/vnode'
|
||||
import { createBlock, normalizeVNode } from '../src/vnode'
|
||||
import { createSlots } from '../src/helpers/createSlots'
|
||||
|
||||
describe('component: slots', () => {
|
||||
|
@ -25,8 +25,21 @@ describe('component: slots', () => {
|
|||
}
|
||||
|
||||
test('initSlots: instance.slots should be set correctly', () => {
|
||||
let instance: any
|
||||
const Comp = {
|
||||
render() {
|
||||
instance = getCurrentInstance()
|
||||
return h('div')
|
||||
},
|
||||
}
|
||||
const slots = { foo: () => {}, _: 1 }
|
||||
render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
|
||||
expect(instance.slots).toMatchObject(slots)
|
||||
})
|
||||
|
||||
test('initSlots: instance.slots should remove compiler marker if parent is using manual render function', () => {
|
||||
const { slots } = renderWithSlots({ _: 1 })
|
||||
expect(slots).toMatchObject({ _: 1 })
|
||||
expect(slots).toMatchObject({})
|
||||
})
|
||||
|
||||
test('initSlots: should normalize object slots (when value is null, string, array)', () => {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,6 @@
|
|||
import {
|
||||
type VNode,
|
||||
computed,
|
||||
createApp,
|
||||
defineComponent,
|
||||
h,
|
||||
|
@ -11,6 +13,7 @@ import {
|
|||
watch,
|
||||
watchEffect,
|
||||
} from '@vue/runtime-test'
|
||||
import { ErrorCodes, ErrorTypeStrings } from '../src/errorHandling'
|
||||
|
||||
describe('error handling', () => {
|
||||
test('propagation', () => {
|
||||
|
@ -609,5 +612,63 @@ describe('error handling', () => {
|
|||
expect(handler).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('errors in scheduler job with owner instance should be caught', async () => {
|
||||
let vnode: VNode
|
||||
const x = ref(0)
|
||||
const app = createApp({
|
||||
render() {
|
||||
return (vnode = vnode || h('div', x.value))
|
||||
},
|
||||
})
|
||||
|
||||
app.config.errorHandler = vi.fn()
|
||||
app.mount(nodeOps.createElement('div'))
|
||||
|
||||
const error = new Error('error')
|
||||
Object.defineProperty(vnode!, 'el', {
|
||||
get() {
|
||||
throw error
|
||||
},
|
||||
})
|
||||
|
||||
x.value++
|
||||
await nextTick()
|
||||
expect(app.config.errorHandler).toHaveBeenCalledWith(
|
||||
error,
|
||||
{},
|
||||
ErrorTypeStrings[ErrorCodes.COMPONENT_UPDATE],
|
||||
)
|
||||
})
|
||||
|
||||
// #11286
|
||||
test('handle error in computed', async () => {
|
||||
const err = new Error()
|
||||
const handler = vi.fn()
|
||||
|
||||
const count = ref(1)
|
||||
const x = computed(() => {
|
||||
if (count.value === 2) throw err
|
||||
return count.value + 1
|
||||
})
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
return () => x.value
|
||||
},
|
||||
})
|
||||
|
||||
app.config.errorHandler = handler
|
||||
app.mount(nodeOps.createElement('div'))
|
||||
|
||||
count.value = 2
|
||||
|
||||
await nextTick()
|
||||
expect(handler).toHaveBeenCalledWith(
|
||||
err,
|
||||
{},
|
||||
ErrorTypeStrings[ErrorCodes.COMPONENT_UPDATE],
|
||||
)
|
||||
})
|
||||
|
||||
// native event handler handling should be tested in respective renderers
|
||||
})
|
||||
|
|
|
@ -26,13 +26,17 @@ describe('renderSlot', () => {
|
|||
const vnode = renderSlot(
|
||||
{ default: () => [(child = h('child'))] },
|
||||
'default',
|
||||
{ key: 'foo' },
|
||||
)
|
||||
expect(vnode.children).toEqual([child])
|
||||
expect(vnode.key).toBe('foo')
|
||||
})
|
||||
|
||||
it('should render slot fallback', () => {
|
||||
const vnode = renderSlot({}, 'default', {}, () => ['fallback'])
|
||||
const vnode = renderSlot({}, 'default', { key: 'foo' }, () => ['fallback'])
|
||||
expect(vnode.children).toEqual(['fallback'])
|
||||
// should attach fallback key postfix
|
||||
expect(vnode.key).toBe('foo_fb')
|
||||
})
|
||||
|
||||
it('should warn render ssr slot', () => {
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import {
|
||||
type App,
|
||||
Suspense,
|
||||
createApp,
|
||||
defineAsyncComponent,
|
||||
defineComponent,
|
||||
h,
|
||||
useId,
|
||||
} from 'vue'
|
||||
import { renderToString } from '@vue/server-renderer'
|
||||
|
||||
type TestCaseFactory = () => [App, Promise<any>[]]
|
||||
|
||||
async function runOnClient(factory: TestCaseFactory) {
|
||||
const [app, deps] = factory()
|
||||
const root = document.createElement('div')
|
||||
app.mount(root)
|
||||
await Promise.all(deps)
|
||||
await promiseWithDelay(null, 0)
|
||||
return root.innerHTML
|
||||
}
|
||||
|
||||
async function runOnServer(factory: TestCaseFactory) {
|
||||
const [app, _] = factory()
|
||||
return (await renderToString(app))
|
||||
.replace(/<!--[\[\]]-->/g, '') // remove fragment wrappers
|
||||
.trim()
|
||||
}
|
||||
|
||||
async function getOutput(factory: TestCaseFactory) {
|
||||
const clientResult = await runOnClient(factory)
|
||||
const serverResult = await runOnServer(factory)
|
||||
expect(serverResult).toBe(clientResult)
|
||||
return clientResult
|
||||
}
|
||||
|
||||
function promiseWithDelay(res: any, delay: number) {
|
||||
return new Promise<any>(r => {
|
||||
setTimeout(() => r(res), delay)
|
||||
})
|
||||
}
|
||||
|
||||
const BasicComponentWithUseId = defineComponent({
|
||||
setup() {
|
||||
const id1 = useId()
|
||||
const id2 = useId()
|
||||
return () => [id1, ' ', id2]
|
||||
},
|
||||
})
|
||||
|
||||
describe('useId', () => {
|
||||
test('basic', async () => {
|
||||
expect(
|
||||
await getOutput(() => {
|
||||
const app = createApp(BasicComponentWithUseId)
|
||||
return [app, []]
|
||||
}),
|
||||
).toBe('v:0 v:1')
|
||||
})
|
||||
|
||||
test('with config.idPrefix', async () => {
|
||||
expect(
|
||||
await getOutput(() => {
|
||||
const app = createApp(BasicComponentWithUseId)
|
||||
app.config.idPrefix = 'foo'
|
||||
return [app, []]
|
||||
}),
|
||||
).toBe('foo:0 foo:1')
|
||||
})
|
||||
|
||||
test('async component', async () => {
|
||||
const factory = (
|
||||
delay1: number,
|
||||
delay2: number,
|
||||
): ReturnType<TestCaseFactory> => {
|
||||
const p1 = promiseWithDelay(BasicComponentWithUseId, delay1)
|
||||
const p2 = promiseWithDelay(BasicComponentWithUseId, delay2)
|
||||
const AsyncOne = defineAsyncComponent(() => p1)
|
||||
const AsyncTwo = defineAsyncComponent(() => p2)
|
||||
const app = createApp({
|
||||
setup() {
|
||||
const id1 = useId()
|
||||
const id2 = useId()
|
||||
return () => [id1, ' ', id2, ' ', h(AsyncOne), ' ', h(AsyncTwo)]
|
||||
},
|
||||
})
|
||||
return [app, [p1, p2]]
|
||||
}
|
||||
|
||||
const expected =
|
||||
'v:0 v:1 ' + // root
|
||||
'v:0-0 v:0-1 ' + // inside first async subtree
|
||||
'v:1-0 v:1-1' // inside second async subtree
|
||||
// assert different async resolution order does not affect id stable-ness
|
||||
expect(await getOutput(() => factory(10, 20))).toBe(expected)
|
||||
expect(await getOutput(() => factory(20, 10))).toBe(expected)
|
||||
})
|
||||
|
||||
test('serverPrefetch', async () => {
|
||||
const factory = (
|
||||
delay1: number,
|
||||
delay2: number,
|
||||
): ReturnType<TestCaseFactory> => {
|
||||
const p1 = promiseWithDelay(null, delay1)
|
||||
const p2 = promiseWithDelay(null, delay2)
|
||||
|
||||
const SPOne = defineComponent({
|
||||
async serverPrefetch() {
|
||||
await p1
|
||||
},
|
||||
render() {
|
||||
return h(BasicComponentWithUseId)
|
||||
},
|
||||
})
|
||||
|
||||
const SPTwo = defineComponent({
|
||||
async serverPrefetch() {
|
||||
await p2
|
||||
},
|
||||
render() {
|
||||
return h(BasicComponentWithUseId)
|
||||
},
|
||||
})
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
const id1 = useId()
|
||||
const id2 = useId()
|
||||
return () => [id1, ' ', id2, ' ', h(SPOne), ' ', h(SPTwo)]
|
||||
},
|
||||
})
|
||||
return [app, [p1, p2]]
|
||||
}
|
||||
|
||||
const expected =
|
||||
'v:0 v:1 ' + // root
|
||||
'v:0-0 v:0-1 ' + // inside first async subtree
|
||||
'v:1-0 v:1-1' // inside second async subtree
|
||||
// assert different async resolution order does not affect id stable-ness
|
||||
expect(await getOutput(() => factory(10, 20))).toBe(expected)
|
||||
expect(await getOutput(() => factory(20, 10))).toBe(expected)
|
||||
})
|
||||
|
||||
test('async setup()', async () => {
|
||||
const factory = (
|
||||
delay1: number,
|
||||
delay2: number,
|
||||
): ReturnType<TestCaseFactory> => {
|
||||
const p1 = promiseWithDelay(null, delay1)
|
||||
const p2 = promiseWithDelay(null, delay2)
|
||||
|
||||
const ASOne = defineComponent({
|
||||
async setup() {
|
||||
await p1
|
||||
return {}
|
||||
},
|
||||
render() {
|
||||
return h(BasicComponentWithUseId)
|
||||
},
|
||||
})
|
||||
|
||||
const ASTwo = defineComponent({
|
||||
async setup() {
|
||||
await p2
|
||||
return {}
|
||||
},
|
||||
render() {
|
||||
return h(BasicComponentWithUseId)
|
||||
},
|
||||
})
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
const id1 = useId()
|
||||
const id2 = useId()
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: h('div', [id1, ' ', id2, ' ', h(ASOne), ' ', h(ASTwo)]),
|
||||
})
|
||||
},
|
||||
})
|
||||
return [app, [p1, p2]]
|
||||
}
|
||||
|
||||
const expected =
|
||||
'<div>' +
|
||||
'v:0 v:1 ' + // root
|
||||
'v:0-0 v:0-1 ' + // inside first async subtree
|
||||
'v:1-0 v:1-1' + // inside second async subtree
|
||||
'</div>'
|
||||
// assert different async resolution order does not affect id stable-ness
|
||||
expect(await getOutput(() => factory(10, 20))).toBe(expected)
|
||||
expect(await getOutput(() => factory(20, 10))).toBe(expected)
|
||||
})
|
||||
|
||||
test('deep nested', async () => {
|
||||
const factory = (): ReturnType<TestCaseFactory> => {
|
||||
const p = Promise.resolve()
|
||||
const One = {
|
||||
async setup() {
|
||||
const id = useId()
|
||||
await p
|
||||
return () => [id, ' ', h(Two), ' ', h(Three)]
|
||||
},
|
||||
}
|
||||
const Two = {
|
||||
async setup() {
|
||||
const id = useId()
|
||||
await p
|
||||
return () => [id, ' ', h(Three), ' ', h(Three)]
|
||||
},
|
||||
}
|
||||
const Three = {
|
||||
async setup() {
|
||||
const id = useId()
|
||||
return () => id
|
||||
},
|
||||
}
|
||||
const app = createApp({
|
||||
setup() {
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: h(One),
|
||||
})
|
||||
},
|
||||
})
|
||||
return [app, [p]]
|
||||
}
|
||||
|
||||
const expected =
|
||||
'v:0 ' + // One
|
||||
'v:0-0 ' + // Two
|
||||
'v:0-0-0 v:0-0-1 ' + // Three + Three nested in Two
|
||||
'v:0-1' // Three after Two
|
||||
// assert different async resolution order does not affect id stable-ness
|
||||
expect(await getOutput(() => factory())).toBe(expected)
|
||||
expect(await getOutput(() => factory())).toBe(expected)
|
||||
})
|
||||
})
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
Fragment,
|
||||
type Ref,
|
||||
type TestElement,
|
||||
createApp,
|
||||
createBlock,
|
||||
createElementBlock,
|
||||
|
@ -526,4 +527,129 @@ describe('useModel', () => {
|
|||
await nextTick()
|
||||
expect(msg.value).toBe('UGHH')
|
||||
})
|
||||
|
||||
// #10279
|
||||
test('force local update when setter formats value to the same value', async () => {
|
||||
let childMsg: Ref<string>
|
||||
let childModifiers: Record<string, true | undefined>
|
||||
|
||||
const compRender = vi.fn()
|
||||
const parentRender = vi.fn()
|
||||
|
||||
const Comp = defineComponent({
|
||||
props: ['msg', 'msgModifiers'],
|
||||
emits: ['update:msg'],
|
||||
setup(props) {
|
||||
;[childMsg, childModifiers] = useModel(props, 'msg', {
|
||||
set(val) {
|
||||
if (childModifiers.number) {
|
||||
return val.replace(/\D+/g, '')
|
||||
}
|
||||
},
|
||||
})
|
||||
return () => {
|
||||
compRender()
|
||||
return h('input', {
|
||||
// simulate how v-model works
|
||||
onVnodeBeforeMount(vnode) {
|
||||
;(vnode.el as TestElement).props.value = childMsg.value
|
||||
},
|
||||
onVnodeBeforeUpdate(vnode) {
|
||||
;(vnode.el as TestElement).props.value = childMsg.value
|
||||
},
|
||||
onInput(value: any) {
|
||||
childMsg.value = value
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const msg = ref(1)
|
||||
const Parent = defineComponent({
|
||||
setup() {
|
||||
return () => {
|
||||
parentRender()
|
||||
return h(Comp, {
|
||||
msg: msg.value,
|
||||
msgModifiers: { number: true },
|
||||
'onUpdate:msg': val => {
|
||||
msg.value = val
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Parent), root)
|
||||
|
||||
expect(parentRender).toHaveBeenCalledTimes(1)
|
||||
expect(compRender).toHaveBeenCalledTimes(1)
|
||||
expect(serializeInner(root)).toBe('<input value=1></input>')
|
||||
|
||||
const input = root.children[0] as TestElement
|
||||
|
||||
// simulate v-model update
|
||||
input.props.onInput((input.props.value = '2'))
|
||||
await nextTick()
|
||||
expect(msg.value).toBe(2)
|
||||
expect(parentRender).toHaveBeenCalledTimes(2)
|
||||
expect(compRender).toHaveBeenCalledTimes(2)
|
||||
expect(serializeInner(root)).toBe('<input value=2></input>')
|
||||
|
||||
input.props.onInput((input.props.value = '2a'))
|
||||
await nextTick()
|
||||
expect(msg.value).toBe(2)
|
||||
expect(parentRender).toHaveBeenCalledTimes(2)
|
||||
// should force local update
|
||||
expect(compRender).toHaveBeenCalledTimes(3)
|
||||
expect(serializeInner(root)).toBe('<input value=2></input>')
|
||||
|
||||
input.props.onInput((input.props.value = '2a'))
|
||||
await nextTick()
|
||||
expect(parentRender).toHaveBeenCalledTimes(2)
|
||||
// should not force local update if set to the same value
|
||||
expect(compRender).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
test('set no change value', async () => {
|
||||
let changeChildMsg: (() => void) | null = null
|
||||
|
||||
const compRender = vi.fn()
|
||||
const Comp = defineComponent({
|
||||
props: ['msg'],
|
||||
emits: ['update:msg'],
|
||||
setup(props) {
|
||||
const childMsg = useModel(props, 'msg')
|
||||
changeChildMsg = () => {
|
||||
childMsg.value = childMsg.value
|
||||
}
|
||||
return () => {
|
||||
return childMsg.value
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const msg = ref('HI')
|
||||
const Parent = defineComponent({
|
||||
setup() {
|
||||
return () =>
|
||||
h(Comp, {
|
||||
msg: msg.value,
|
||||
'onUpdate:msg': val => {
|
||||
msg.value = val
|
||||
compRender()
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Parent), root)
|
||||
|
||||
expect(compRender).toBeCalledTimes(0)
|
||||
changeChildMsg!()
|
||||
expect(compRender).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import {
|
||||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
ref,
|
||||
render,
|
||||
useTemplateRef,
|
||||
} from '@vue/runtime-test'
|
||||
|
||||
describe('useTemplateRef', () => {
|
||||
test('should work', () => {
|
||||
let tRef
|
||||
const key = 'refKey'
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef(key)
|
||||
},
|
||||
render() {
|
||||
return h('div', { ref: key })
|
||||
},
|
||||
}
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
expect(tRef!.value).toBe(root.children[0])
|
||||
})
|
||||
|
||||
test('should be readonly', () => {
|
||||
let tRef
|
||||
const key = 'refKey'
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef(key)
|
||||
},
|
||||
render() {
|
||||
return h('div', { ref: key })
|
||||
},
|
||||
}
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
// @ts-expect-error
|
||||
tRef.value = 123
|
||||
|
||||
expect(tRef!.value).toBe(root.children[0])
|
||||
expect('target is readonly').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('should be updated for ref of dynamic strings', async () => {
|
||||
let t1, t2
|
||||
const key = ref('t1')
|
||||
const Comp = {
|
||||
setup() {
|
||||
t1 = useTemplateRef<HTMLAnchorElement>('t1')
|
||||
t2 = useTemplateRef('t2')
|
||||
},
|
||||
render() {
|
||||
return h('div', { ref: key.value })
|
||||
},
|
||||
}
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect(t1!.value).toBe(root.children[0])
|
||||
expect(t2!.value).toBe(null)
|
||||
|
||||
key.value = 't2'
|
||||
await nextTick()
|
||||
expect(t2!.value).toBe(root.children[0])
|
||||
expect(t1!.value).toBe(null)
|
||||
})
|
||||
})
|
|
@ -29,6 +29,8 @@ function compileToFunction(template: string) {
|
|||
return render
|
||||
}
|
||||
|
||||
const timeout = (n: number = 0) => new Promise(r => setTimeout(r, n))
|
||||
|
||||
describe('hot module replacement', () => {
|
||||
test('inject global runtime', () => {
|
||||
expect(createRecord).toBeDefined()
|
||||
|
@ -436,18 +438,23 @@ describe('hot module replacement', () => {
|
|||
|
||||
const Parent: ComponentOptions = {
|
||||
setup() {
|
||||
const com = ref()
|
||||
const changeRef = (value: any) => {
|
||||
com.value = value
|
||||
}
|
||||
const com1 = ref()
|
||||
const changeRef1 = (value: any) => (com1.value = value)
|
||||
|
||||
return () => [h(Child, { ref: changeRef }), com.value?.count]
|
||||
const com2 = ref()
|
||||
const changeRef2 = (value: any) => (com2.value = value)
|
||||
|
||||
return () => [
|
||||
h(Child, { ref: changeRef1 }),
|
||||
h(Child, { ref: changeRef2 }),
|
||||
com1.value?.count,
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
render(h(Parent), root)
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>0</div>0`)
|
||||
expect(serializeInner(root)).toBe(`<div>0</div><div>0</div>0`)
|
||||
|
||||
reload(childId, {
|
||||
__hmrId: childId,
|
||||
|
@ -458,9 +465,9 @@ describe('hot module replacement', () => {
|
|||
render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
|
||||
})
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>1</div>1`)
|
||||
expect(unmountSpy).toHaveBeenCalledTimes(1)
|
||||
expect(mountSpy).toHaveBeenCalledTimes(1)
|
||||
expect(serializeInner(root)).toBe(`<div>1</div><div>1</div>1`)
|
||||
expect(unmountSpy).toHaveBeenCalledTimes(2)
|
||||
expect(mountSpy).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
// #1156 - static nodes should retain DOM element reference across updates
|
||||
|
@ -805,4 +812,43 @@ describe('hot module replacement', () => {
|
|||
`<div><div>1<p>3</p></div></div><div><div>1<p>3</p></div></div><p>2</p>`,
|
||||
)
|
||||
})
|
||||
|
||||
// #11248
|
||||
test('reload async component with multiple instances', async () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
const childId = 'test-child-id'
|
||||
const Child: ComponentOptions = {
|
||||
__hmrId: childId,
|
||||
data() {
|
||||
return { count: 0 }
|
||||
},
|
||||
render: compileToFunction(`<div>{{ count }}</div>`),
|
||||
}
|
||||
const Comp = runtimeTest.defineAsyncComponent(() => Promise.resolve(Child))
|
||||
const appId = 'test-app-id'
|
||||
const App: ComponentOptions = {
|
||||
__hmrId: appId,
|
||||
render: () => [h(Comp), h(Comp)],
|
||||
}
|
||||
createRecord(appId, App)
|
||||
|
||||
render(h(App), root)
|
||||
|
||||
await timeout()
|
||||
|
||||
expect(serializeInner(root)).toBe(`<div>0</div><div>0</div>`)
|
||||
|
||||
// change count to 1
|
||||
reload(childId, {
|
||||
__hmrId: childId,
|
||||
data() {
|
||||
return { count: 1 }
|
||||
},
|
||||
render: compileToFunction(`<div>{{ count }}</div>`),
|
||||
})
|
||||
|
||||
await timeout()
|
||||
|
||||
expect(serializeInner(root)).toBe(`<div>1</div><div>1</div>`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
nextTick,
|
||||
onMounted,
|
||||
openBlock,
|
||||
reactive,
|
||||
ref,
|
||||
renderSlot,
|
||||
useCssVars,
|
||||
|
@ -31,7 +32,7 @@ import {
|
|||
withDirectives,
|
||||
} from '@vue/runtime-dom'
|
||||
import { type SSRContext, renderToString } from '@vue/server-renderer'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { PatchFlags, normalizeStyle } from '@vue/shared'
|
||||
import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
|
||||
import { expect } from 'vitest'
|
||||
|
||||
|
@ -148,6 +149,15 @@ describe('SSR hydration', () => {
|
|||
expect(container.innerHTML).toBe(`<div class="bar">bar</div>`)
|
||||
})
|
||||
|
||||
// #7285
|
||||
test('element with multiple continuous text vnodes', async () => {
|
||||
// should no mismatch warning
|
||||
const { container } = mountWithHydration('<div>fooo</div>', () =>
|
||||
h('div', ['fo', createTextVNode('o'), 'o']),
|
||||
)
|
||||
expect(container.textContent).toBe('fooo')
|
||||
})
|
||||
|
||||
test('element with elements children', async () => {
|
||||
const msg = ref('foo')
|
||||
const fn = vi.fn()
|
||||
|
@ -239,6 +249,17 @@ describe('SSR hydration', () => {
|
|||
)
|
||||
})
|
||||
|
||||
// #7285
|
||||
test('Fragment (multiple continuous text vnodes)', async () => {
|
||||
// should no mismatch warning
|
||||
const { container } = mountWithHydration('<!--[-->fooo<!--]-->', () => [
|
||||
'fo',
|
||||
createTextVNode('o'),
|
||||
'o',
|
||||
])
|
||||
expect(container.textContent).toBe('fooo')
|
||||
})
|
||||
|
||||
test('Teleport', async () => {
|
||||
const msg = ref('foo')
|
||||
const fn = vi.fn()
|
||||
|
@ -1176,6 +1197,38 @@ describe('SSR hydration', () => {
|
|||
expect(text.nodeType).toBe(3)
|
||||
})
|
||||
|
||||
// #11372
|
||||
test('object style value tracking in prod', async () => {
|
||||
__DEV__ = false
|
||||
try {
|
||||
const style = reactive({ color: 'red' })
|
||||
const Comp = {
|
||||
render(this: any) {
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock(
|
||||
'div',
|
||||
{
|
||||
style: normalizeStyle(style),
|
||||
},
|
||||
null,
|
||||
4 /* STYLE */,
|
||||
)
|
||||
)
|
||||
},
|
||||
}
|
||||
const { container } = mountWithHydration(
|
||||
`<div style="color: red;"></div>`,
|
||||
() => h(Comp),
|
||||
)
|
||||
style.color = 'green'
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(`<div style="color: green;"></div>`)
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
}
|
||||
})
|
||||
|
||||
test('app.unmount()', async () => {
|
||||
const container = document.createElement('DIV')
|
||||
container.innerHTML = '<button></button>'
|
||||
|
@ -1317,76 +1370,83 @@ describe('SSR hydration', () => {
|
|||
// #10607
|
||||
test('update component stable slot (prod + optimized mode)', async () => {
|
||||
__DEV__ = false
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = `<template><div show="false"><!--[--><div><div><!----></div></div><div>0</div><!--]--></div></template>`
|
||||
const Comp = {
|
||||
render(this: any) {
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock('div', null, [renderSlot(this.$slots, 'default')])
|
||||
)
|
||||
},
|
||||
}
|
||||
const show = ref(false)
|
||||
const clicked = ref(false)
|
||||
|
||||
const Wrapper = {
|
||||
setup() {
|
||||
const items = ref<number[]>([])
|
||||
onMounted(() => {
|
||||
items.value = [1]
|
||||
})
|
||||
return () => {
|
||||
try {
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = `<template><div show="false"><!--[--><div><div><!----></div></div><div>0</div><!--]--></div></template>`
|
||||
const Comp = {
|
||||
render(this: any) {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(Comp, null, {
|
||||
default: withCtx(() => [
|
||||
createElementVNode('div', null, [
|
||||
createElementVNode('div', null, [
|
||||
clicked.value
|
||||
? (openBlock(),
|
||||
createElementBlock('div', { key: 0 }, 'foo'))
|
||||
: createCommentVNode('v-if', true),
|
||||
]),
|
||||
]),
|
||||
createElementVNode(
|
||||
'div',
|
||||
null,
|
||||
items.value.length,
|
||||
1 /* TEXT */,
|
||||
),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
})
|
||||
createElementBlock('div', null, [
|
||||
renderSlot(this.$slots, 'default'),
|
||||
])
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
createSSRApp({
|
||||
components: { Wrapper },
|
||||
data() {
|
||||
return { show }
|
||||
},
|
||||
template: `<Wrapper :show="show"/>`,
|
||||
}).mount(container)
|
||||
},
|
||||
}
|
||||
const show = ref(false)
|
||||
const clicked = ref(false)
|
||||
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div show="false"><!--[--><div><div><!----></div></div><div>1</div><!--]--></div>`,
|
||||
)
|
||||
const Wrapper = {
|
||||
setup() {
|
||||
const items = ref<number[]>([])
|
||||
onMounted(() => {
|
||||
items.value = [1]
|
||||
})
|
||||
return () => {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(Comp, null, {
|
||||
default: withCtx(() => [
|
||||
createElementVNode('div', null, [
|
||||
createElementVNode('div', null, [
|
||||
clicked.value
|
||||
? (openBlock(),
|
||||
createElementBlock('div', { key: 0 }, 'foo'))
|
||||
: createCommentVNode('v-if', true),
|
||||
]),
|
||||
]),
|
||||
createElementVNode(
|
||||
'div',
|
||||
null,
|
||||
items.value.length,
|
||||
1 /* TEXT */,
|
||||
),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
createSSRApp({
|
||||
components: { Wrapper },
|
||||
data() {
|
||||
return { show }
|
||||
},
|
||||
template: `<Wrapper :show="show"/>`,
|
||||
}).mount(container)
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
expect(async () => {
|
||||
clicked.value = true
|
||||
await nextTick()
|
||||
}).not.toThrow("Cannot read properties of null (reading 'insertBefore')")
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div show="false"><!--[--><div><div><!----></div></div><div>1</div><!--]--></div>`,
|
||||
)
|
||||
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div show="true"><!--[--><div><div><div>foo</div></div></div><div>1</div><!--]--></div>`,
|
||||
)
|
||||
__DEV__ = true
|
||||
show.value = true
|
||||
await nextTick()
|
||||
expect(async () => {
|
||||
clicked.value = true
|
||||
await nextTick()
|
||||
}).not.toThrow("Cannot read properties of null (reading 'insertBefore')")
|
||||
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div show="true"><!--[--><div><div><div>foo</div></div></div><div>1</div><!--]--></div>`,
|
||||
)
|
||||
} catch (e) {
|
||||
throw e
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
}
|
||||
})
|
||||
|
||||
describe('mismatch handling', () => {
|
||||
|
|
|
@ -8,6 +8,8 @@ import {
|
|||
createApp,
|
||||
createBlock,
|
||||
createCommentVNode,
|
||||
createElementBlock,
|
||||
createElementVNode,
|
||||
createTextVNode,
|
||||
createVNode,
|
||||
defineComponent,
|
||||
|
@ -23,6 +25,7 @@ import {
|
|||
renderList,
|
||||
renderSlot,
|
||||
serialize,
|
||||
setBlockTracking,
|
||||
withCtx,
|
||||
} from '@vue/runtime-test'
|
||||
import { PatchFlags, SlotFlags } from '@vue/shared'
|
||||
|
@ -434,7 +437,7 @@ describe('renderer: optimized mode', () => {
|
|||
const App = {
|
||||
setup() {
|
||||
return () => {
|
||||
return createVNode(Comp, null, {
|
||||
return createBlock(Comp, null, {
|
||||
default: withCtx(() => [
|
||||
createVNode('p', null, foo.value, PatchFlags.TEXT),
|
||||
]),
|
||||
|
@ -560,6 +563,7 @@ describe('renderer: optimized mode', () => {
|
|||
const state = ref(0)
|
||||
|
||||
const CompA = {
|
||||
name: 'A',
|
||||
setup(props: any, { slots }: SetupContext) {
|
||||
return () => {
|
||||
return (
|
||||
|
@ -571,6 +575,7 @@ describe('renderer: optimized mode', () => {
|
|||
}
|
||||
|
||||
const Wrapper = {
|
||||
name: 'Wrapper',
|
||||
setup(props: any, { slots }: SetupContext) {
|
||||
// use the manually written render function to rendering the optimized slots,
|
||||
// which should make subsequent updates exit the optimized mode correctly
|
||||
|
@ -581,6 +586,7 @@ describe('renderer: optimized mode', () => {
|
|||
}
|
||||
|
||||
const app = createApp({
|
||||
name: 'App',
|
||||
setup() {
|
||||
return () => {
|
||||
return (
|
||||
|
@ -612,7 +618,7 @@ describe('renderer: optimized mode', () => {
|
|||
})
|
||||
|
||||
//#3623
|
||||
test('nested teleport unmount need exit the optimization mode', () => {
|
||||
test('nested teleport unmount need exit the optimization mode', async () => {
|
||||
const target = nodeOps.createElement('div')
|
||||
const root = nodeOps.createElement('div')
|
||||
|
||||
|
@ -641,6 +647,7 @@ describe('renderer: optimized mode', () => {
|
|||
])),
|
||||
root,
|
||||
)
|
||||
await nextTick()
|
||||
expect(inner(target)).toMatchInlineSnapshot(
|
||||
`"<div><!--teleport start--><!--teleport end--></div><div>foo</div>"`,
|
||||
)
|
||||
|
@ -959,4 +966,271 @@ describe('renderer: optimized mode', () => {
|
|||
// should successfully unmount without error
|
||||
expect(inner(root)).toBe(`<!---->`)
|
||||
})
|
||||
|
||||
// #10870
|
||||
test('should bail manually rendered compiler slots for both mount and update', async () => {
|
||||
// only reproducible in prod
|
||||
__DEV__ = false
|
||||
function Outer(_: any, { slots }: any) {
|
||||
return slots.default()
|
||||
}
|
||||
const Mid = {
|
||||
render(ctx: any) {
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock('div', null, [renderSlot(ctx.$slots, 'default')])
|
||||
)
|
||||
},
|
||||
}
|
||||
const state1 = ref(true)
|
||||
const state2 = ref(true)
|
||||
const App = {
|
||||
render() {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(Outer, null, {
|
||||
default: withCtx(() => [
|
||||
createVNode(
|
||||
Mid,
|
||||
{ foo: state2.value },
|
||||
{
|
||||
default: withCtx(() => [
|
||||
createElementVNode('div', null, [
|
||||
createElementVNode('div', null, [
|
||||
state2.value
|
||||
? (openBlock(),
|
||||
createElementBlock(
|
||||
'div',
|
||||
{
|
||||
key: 0,
|
||||
id: 'if',
|
||||
foo: state1.value,
|
||||
},
|
||||
null,
|
||||
8 /* PROPS */,
|
||||
['foo'],
|
||||
))
|
||||
: createCommentVNode('v-if', true),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
},
|
||||
8 /* PROPS */,
|
||||
['foo'],
|
||||
),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
})
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp(App)
|
||||
app.config.errorHandler = vi.fn()
|
||||
|
||||
try {
|
||||
app.mount(root)
|
||||
|
||||
state1.value = false
|
||||
await nextTick()
|
||||
|
||||
state2.value = false
|
||||
await nextTick()
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
expect(app.config.errorHandler).not.toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
|
||||
// #11336
|
||||
test('should bail manually rendered compiler slots for both mount and update (2)', async () => {
|
||||
// only reproducible in prod
|
||||
__DEV__ = false
|
||||
const n = ref(0)
|
||||
function Outer(_: any, { slots }: any) {
|
||||
n.value // track
|
||||
return slots.default()
|
||||
}
|
||||
const Mid = {
|
||||
render(ctx: any) {
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock('div', null, [renderSlot(ctx.$slots, 'default')])
|
||||
)
|
||||
},
|
||||
}
|
||||
const show = ref(false)
|
||||
const App = {
|
||||
render() {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(Outer, null, {
|
||||
default: withCtx(() => [
|
||||
createVNode(Mid, null, {
|
||||
default: withCtx(() => [
|
||||
createElementVNode('div', null, [
|
||||
show.value
|
||||
? (openBlock(),
|
||||
createElementBlock('div', { key: 0 }, '1'))
|
||||
: createCommentVNode('v-if', true),
|
||||
createElementVNode('div', null, '2'),
|
||||
createElementVNode('div', null, '3'),
|
||||
]),
|
||||
createElementVNode('div', null, '4'),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
}),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
})
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp(App)
|
||||
app.config.errorHandler = vi.fn()
|
||||
|
||||
try {
|
||||
app.mount(root)
|
||||
|
||||
// force Outer update, which will assign new slots to Mid
|
||||
// we want to make sure the compiled slot flag doesn't accidentally
|
||||
// get assigned again
|
||||
n.value++
|
||||
await nextTick()
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
expect(app.config.errorHandler).not.toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
|
||||
test('diff slot and slot fallback node', async () => {
|
||||
const Comp = {
|
||||
props: ['show'],
|
||||
setup(props: any, { slots }: SetupContext) {
|
||||
return () => {
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock('div', null, [
|
||||
renderSlot(slots, 'default', { hide: !props.show }, () => [
|
||||
(openBlock(),
|
||||
(block = createElementBlock(
|
||||
Fragment,
|
||||
{ key: 0 },
|
||||
[createTextVNode('foo')],
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
))),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const show = ref(true)
|
||||
const app = createApp({
|
||||
render() {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(
|
||||
Comp,
|
||||
{ show: show.value },
|
||||
{
|
||||
default: withCtx(({ hide }: { hide: boolean }) => [
|
||||
!hide
|
||||
? (openBlock(),
|
||||
createElementBlock(
|
||||
Fragment,
|
||||
{ key: 0 },
|
||||
[
|
||||
createCommentVNode('comment'),
|
||||
createElementVNode(
|
||||
'div',
|
||||
null,
|
||||
'bar',
|
||||
PatchFlags.CACHED,
|
||||
),
|
||||
],
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
))
|
||||
: createCommentVNode('v-if', true),
|
||||
]),
|
||||
_: SlotFlags.STABLE,
|
||||
},
|
||||
PatchFlags.PROPS,
|
||||
['show'],
|
||||
)
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
app.mount(root)
|
||||
expect(inner(root)).toBe('<div><!--comment--><div>bar</div></div>')
|
||||
expect(block).toBe(null)
|
||||
|
||||
show.value = false
|
||||
await nextTick()
|
||||
expect(inner(root)).toBe('<div>foo</div>')
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
expect(inner(root)).toBe('<div><!--comment--><div>bar</div></div>')
|
||||
})
|
||||
|
||||
test('should not take unmount children fast path if children contain cached nodes', async () => {
|
||||
const show = ref(true)
|
||||
const spyUnmounted = vi.fn()
|
||||
|
||||
const Child = {
|
||||
setup() {
|
||||
onUnmounted(spyUnmounted)
|
||||
return () => createVNode('div', null, 'Child')
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
render(_: any, cache: any) {
|
||||
return show.value
|
||||
? (openBlock(),
|
||||
createBlock('div', null, [
|
||||
createVNode('div', null, [
|
||||
cache[0] ||
|
||||
(setBlockTracking(-1),
|
||||
((cache[0] = createVNode('div', null, [
|
||||
createVNode(Child),
|
||||
])).cacheIndex = 0),
|
||||
setBlockTracking(1),
|
||||
cache[0]),
|
||||
]),
|
||||
]))
|
||||
: createCommentVNode('v-if', true)
|
||||
},
|
||||
})
|
||||
|
||||
app.mount(root)
|
||||
expect(inner(root)).toBe(
|
||||
'<div><div><div><div>Child</div></div></div></div>',
|
||||
)
|
||||
|
||||
show.value = false
|
||||
await nextTick()
|
||||
expect(inner(root)).toBe('<!--v-if-->')
|
||||
expect(spyUnmounted).toHaveBeenCalledTimes(1)
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
expect(inner(root)).toBe(
|
||||
'<div><div><div><div>Child</div></div></div></div>',
|
||||
)
|
||||
|
||||
// should unmount again, this verifies previous cache was properly cleared
|
||||
show.value = false
|
||||
await nextTick()
|
||||
expect(inner(root)).toBe('<!--v-if-->')
|
||||
expect(spyUnmounted).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
Comment,
|
||||
Fragment,
|
||||
Text,
|
||||
type VNode,
|
||||
cloneVNode,
|
||||
createBlock,
|
||||
createVNode,
|
||||
|
@ -633,7 +634,9 @@ describe('vnode', () => {
|
|||
setBlockTracking(1),
|
||||
vnode1,
|
||||
]))
|
||||
expect(vnode.dynamicChildren).toStrictEqual([])
|
||||
const expected: VNode['dynamicChildren'] = []
|
||||
expected.hasOnce = true
|
||||
expect(vnode.dynamicChildren).toStrictEqual(expected)
|
||||
})
|
||||
// #5657
|
||||
test('error of slot function execution should not affect block tracking', () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
|
|
|
@ -15,6 +15,7 @@ import { ref } from '@vue/reactivity'
|
|||
import { ErrorCodes, handleError } from './errorHandling'
|
||||
import { isKeepAlive } from './components/KeepAlive'
|
||||
import { queueJob } from './scheduler'
|
||||
import { markAsyncBoundary } from './helpers/useId'
|
||||
|
||||
export type AsyncComponentResolveResult<T = Component> = T | { default: T } // es modules
|
||||
|
||||
|
@ -157,6 +158,8 @@ export function defineAsyncComponent<
|
|||
})
|
||||
: null
|
||||
})
|
||||
} else {
|
||||
markAsyncBoundary(instance)
|
||||
}
|
||||
|
||||
const loaded = ref(false)
|
||||
|
|
|
@ -124,6 +124,18 @@ export interface AppConfig {
|
|||
* Enable warnings for computed getters that recursively trigger itself.
|
||||
*/
|
||||
warnRecursiveComputed?: boolean
|
||||
|
||||
/**
|
||||
* Whether to throw unhandled errors in production.
|
||||
* Default is `false` to avoid crashing on any error (and only logs it)
|
||||
* But in some cases, e.g. SSR, throwing might be more desirable.
|
||||
*/
|
||||
throwUnhandledErrorInProduction?: boolean
|
||||
|
||||
/**
|
||||
* Prefix for all useId() calls within this app
|
||||
*/
|
||||
idPrefix?: string
|
||||
}
|
||||
|
||||
export interface AppContext {
|
||||
|
|
|
@ -93,6 +93,7 @@ import type { SuspenseProps } from './components/Suspense'
|
|||
import type { KeepAliveProps } from './components/KeepAlive'
|
||||
import type { BaseTransitionProps } from './components/BaseTransition'
|
||||
import type { DefineComponent } from './apiDefineComponent'
|
||||
import { markAsyncBoundary } from './helpers/useId'
|
||||
|
||||
/**
|
||||
* Public utility type for extracting the instance type of a component.
|
||||
|
@ -100,7 +101,7 @@ import type { DefineComponent } from './apiDefineComponent'
|
|||
* the usage of `InstanceType<typeof Comp>` which only works for
|
||||
* constructor-based component definition types.
|
||||
*
|
||||
* Exmaple:
|
||||
* @example
|
||||
* ```ts
|
||||
* const MyComp = { ... }
|
||||
* declare const instance: ComponentInstance<typeof MyComp>
|
||||
|
@ -355,6 +356,13 @@ export interface ComponentInternalInstance {
|
|||
* @internal
|
||||
*/
|
||||
provides: Data
|
||||
/**
|
||||
* for tracking useId()
|
||||
* first element is the current boundary prefix
|
||||
* second number is the index of the useId call within that boundary
|
||||
* @internal
|
||||
*/
|
||||
ids: [string, number, number]
|
||||
/**
|
||||
* Tracking reactive effects (e.g. watchers) associated with this component
|
||||
* so that they can be automatically stopped on component unmount
|
||||
|
@ -445,9 +453,6 @@ export interface ComponentInternalInstance {
|
|||
refs: Data
|
||||
emit: EmitFn
|
||||
|
||||
attrsProxy: Data | null
|
||||
slotsProxy: Slots | null
|
||||
|
||||
/**
|
||||
* used for keeping track of .once event handlers on components
|
||||
* @internal
|
||||
|
@ -618,6 +623,7 @@ export function createComponentInstance(
|
|||
withProxy: null,
|
||||
|
||||
provides: parent ? parent.provides : Object.create(appContext.provides),
|
||||
ids: parent ? parent.ids : ['', 0, 0],
|
||||
accessCache: null!,
|
||||
renderCache: [],
|
||||
|
||||
|
@ -649,9 +655,6 @@ export function createComponentInstance(
|
|||
setupState: EMPTY_OBJ,
|
||||
setupContext: null,
|
||||
|
||||
attrsProxy: null,
|
||||
slotsProxy: null,
|
||||
|
||||
// suspense related
|
||||
suspense,
|
||||
suspenseId: suspense ? suspense.pendingId : 0,
|
||||
|
@ -784,13 +787,14 @@ export let isInSSRComponentSetup = false
|
|||
export function setupComponent(
|
||||
instance: ComponentInternalInstance,
|
||||
isSSR = false,
|
||||
optimized = false,
|
||||
) {
|
||||
isSSR && setInSSRSetupState(isSSR)
|
||||
|
||||
const { props, children } = instance.vnode
|
||||
const isStateful = isStatefulComponent(instance)
|
||||
initProps(instance, props, isStateful, isSSR)
|
||||
initSlots(instance, children)
|
||||
initSlots(instance, children, optimized)
|
||||
|
||||
const setupResult = isStateful
|
||||
? setupStatefulComponent(instance, isSSR)
|
||||
|
@ -858,6 +862,8 @@ function setupStatefulComponent(
|
|||
reset()
|
||||
|
||||
if (isPromise(setupResult)) {
|
||||
// async setup, mark as async boundary for useId()
|
||||
markAsyncBoundary(instance)
|
||||
setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
|
||||
if (isSSR) {
|
||||
// return the promise so server-renderer can wait on it
|
||||
|
@ -1089,15 +1095,12 @@ const attrsProxyHandlers = __DEV__
|
|||
* Dev-only
|
||||
*/
|
||||
function getSlotsProxy(instance: ComponentInternalInstance): Slots {
|
||||
return (
|
||||
instance.slotsProxy ||
|
||||
(instance.slotsProxy = new Proxy(instance.slots, {
|
||||
get(target, key: string) {
|
||||
track(instance, TrackOpTypes.GET, '$slots')
|
||||
return target[key]
|
||||
},
|
||||
}))
|
||||
)
|
||||
return new Proxy(instance.slots, {
|
||||
get(target, key: string) {
|
||||
track(instance, TrackOpTypes.GET, '$slots')
|
||||
return target[key]
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function createSetupContext(
|
||||
|
@ -1131,6 +1134,7 @@ export function createSetupContext(
|
|||
// We use getters in dev in case libs like test-utils overwrite instance
|
||||
// properties (overwrites should not be done in prod)
|
||||
let attrsProxy: Data
|
||||
let slotsProxy: Slots
|
||||
return Object.freeze({
|
||||
get attrs() {
|
||||
return (
|
||||
|
@ -1139,7 +1143,7 @@ export function createSetupContext(
|
|||
)
|
||||
},
|
||||
get slots() {
|
||||
return getSlotsProxy(instance)
|
||||
return slotsProxy || (slotsProxy = getSlotsProxy(instance))
|
||||
},
|
||||
get emit() {
|
||||
return (event: string, ...args: any[]) => instance.emit(event, ...args)
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
compatModelEventPrefix,
|
||||
} from './compat/componentVModel'
|
||||
import type { ComponentTypeEmits } from './apiSetupHelpers'
|
||||
import { getModelModifiers } from './helpers/useModel'
|
||||
|
||||
export type ObjectEmitsOptions = Record<
|
||||
string,
|
||||
|
@ -145,16 +146,12 @@ export function emit(
|
|||
const isModelListener = event.startsWith('update:')
|
||||
|
||||
// for v-model update:xxx events, apply modifiers on args
|
||||
const modelArg = isModelListener && event.slice(7)
|
||||
if (modelArg && modelArg in props) {
|
||||
const modifiersKey = `${
|
||||
modelArg === 'modelValue' ? 'model' : modelArg
|
||||
}Modifiers`
|
||||
const { number, trim } = props[modifiersKey] || EMPTY_OBJ
|
||||
if (trim) {
|
||||
const modifiers = isModelListener && getModelModifiers(props, event.slice(7))
|
||||
if (modifiers) {
|
||||
if (modifiers.trim) {
|
||||
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
|
||||
}
|
||||
if (number) {
|
||||
if (modifiers.number) {
|
||||
args = rawArgs.map(looseToNumber)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,6 +85,7 @@ import {
|
|||
type ComponentTypeEmits,
|
||||
normalizePropsOrEmits,
|
||||
} from './apiSetupHelpers'
|
||||
import { markAsyncBoundary } from './helpers/useId'
|
||||
|
||||
/**
|
||||
* Interface for declaring custom options.
|
||||
|
@ -772,6 +773,10 @@ export function applyOptions(instance: ComponentInternalInstance) {
|
|||
) {
|
||||
instance.filters = filters
|
||||
}
|
||||
|
||||
if (__SSR__ && serverPrefetch) {
|
||||
markAsyncBoundary(instance)
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveInjections(
|
||||
|
|
|
@ -498,12 +498,15 @@ function resolvePropValue(
|
|||
return value
|
||||
}
|
||||
|
||||
const mixinPropsCache = new WeakMap<ConcreteComponent, NormalizedPropsOptions>()
|
||||
|
||||
export function normalizePropsOptions(
|
||||
comp: ConcreteComponent,
|
||||
appContext: AppContext,
|
||||
asMixin = false,
|
||||
): NormalizedPropsOptions {
|
||||
const cache = appContext.propsCache
|
||||
const cache =
|
||||
__FEATURE_OPTIONS_API__ && asMixin ? mixinPropsCache : appContext.propsCache
|
||||
const cached = cache.get(comp)
|
||||
if (cached) {
|
||||
return cached
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
ShapeFlags,
|
||||
SlotFlags,
|
||||
def,
|
||||
extend,
|
||||
isArray,
|
||||
isFunction,
|
||||
} from '@vue/shared'
|
||||
|
@ -161,17 +160,36 @@ const normalizeVNodeSlots = (
|
|||
instance.slots.default = () => normalized
|
||||
}
|
||||
|
||||
const assignSlots = (
|
||||
slots: InternalSlots,
|
||||
children: Slots,
|
||||
optimized: boolean,
|
||||
) => {
|
||||
for (const key in children) {
|
||||
// #2893
|
||||
// when rendering the optimized slots by manually written render function,
|
||||
// do not copy the `slots._` compiler flag so that `renderSlot` creates
|
||||
// slot Fragment with BAIL patchFlag to force full updates
|
||||
if (optimized || key !== '_') {
|
||||
slots[key] = children[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const initSlots = (
|
||||
instance: ComponentInternalInstance,
|
||||
children: VNodeNormalizedChildren,
|
||||
optimized: boolean,
|
||||
) => {
|
||||
const slots = (instance.slots = createInternalObject())
|
||||
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
||||
const type = (children as RawSlots)._
|
||||
if (type) {
|
||||
extend(slots, children as InternalSlots)
|
||||
assignSlots(slots, children as Slots, optimized)
|
||||
// make compiler marker non-enumerable
|
||||
def(slots, '_', type, true)
|
||||
if (optimized) {
|
||||
def(slots, '_', type, true)
|
||||
}
|
||||
} else {
|
||||
normalizeObjectSlots(children as RawSlots, slots, instance)
|
||||
}
|
||||
|
@ -195,7 +213,7 @@ export const updateSlots = (
|
|||
if (__DEV__ && isHmrUpdating) {
|
||||
// Parent was HMR updated so slot content may have changed.
|
||||
// force update slots and mark instance for hmr as well
|
||||
extend(slots, children as Slots)
|
||||
assignSlots(slots, children as Slots, optimized)
|
||||
trigger(instance, TriggerOpTypes.SET, '$slots')
|
||||
} else if (optimized && type === SlotFlags.STABLE) {
|
||||
// compiled AND stable.
|
||||
|
@ -204,14 +222,7 @@ export const updateSlots = (
|
|||
} else {
|
||||
// compiled but dynamic (v-if/v-for on slots) - update slots, but skip
|
||||
// normalization.
|
||||
extend(slots, children as Slots)
|
||||
// #2893
|
||||
// when rendering the optimized slots by manually written render function,
|
||||
// we need to delete the `slots._` flag if necessary to make subsequent updates reliable,
|
||||
// i.e. let the `renderSlot` create the bailed Fragment
|
||||
if (!optimized && type === SlotFlags.STABLE) {
|
||||
delete slots._
|
||||
}
|
||||
assignSlots(slots, children as Slots, optimized)
|
||||
}
|
||||
} else {
|
||||
needDeletionCheck = !(children as RawSlots).$stable
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
type RendererInternals,
|
||||
type RendererNode,
|
||||
type RendererOptions,
|
||||
queuePostRenderEffect,
|
||||
traverseStaticChildren,
|
||||
} from '../renderer'
|
||||
import type { VNode, VNodeArrayChildren, VNodeProps } from '../vnode'
|
||||
|
@ -19,13 +20,19 @@ export type TeleportVNode = VNode<RendererNode, RendererElement, TeleportProps>
|
|||
export interface TeleportProps {
|
||||
to: string | RendererElement | null | undefined
|
||||
disabled?: boolean
|
||||
defer?: boolean
|
||||
}
|
||||
|
||||
export const TeleportEndKey = Symbol('_vte')
|
||||
|
||||
export const isTeleport = (type: any): boolean => type.__isTeleport
|
||||
|
||||
const isTeleportDisabled = (props: VNode['props']): boolean =>
|
||||
props && (props.disabled || props.disabled === '')
|
||||
|
||||
const isTeleportDeferred = (props: VNode['props']): boolean =>
|
||||
props && (props.defer || props.defer === '')
|
||||
|
||||
const isTargetSVG = (target: RendererElement): boolean =>
|
||||
typeof SVGElement !== 'undefined' && target instanceof SVGElement
|
||||
|
||||
|
@ -105,21 +112,13 @@ export const TeleportImpl = {
|
|||
const mainAnchor = (n2.anchor = __DEV__
|
||||
? createComment('teleport end')
|
||||
: createText(''))
|
||||
const targetStart = (n2.targetStart = createText(''))
|
||||
const targetAnchor = (n2.targetAnchor = createText(''))
|
||||
insert(placeholder, container, anchor)
|
||||
insert(mainAnchor, container, anchor)
|
||||
const target = (n2.target = resolveTarget(n2.props, querySelector))
|
||||
const targetAnchor = (n2.targetAnchor = createText(''))
|
||||
if (target) {
|
||||
insert(targetAnchor, target)
|
||||
// #2652 we could be teleporting from a non-SVG tree into an SVG tree
|
||||
if (namespace === 'svg' || isTargetSVG(target)) {
|
||||
namespace = 'svg'
|
||||
} else if (namespace === 'mathml' || isTargetMathML(target)) {
|
||||
namespace = 'mathml'
|
||||
}
|
||||
} else if (__DEV__ && !disabled) {
|
||||
warn('Invalid Teleport target on mount:', target, `(${typeof target})`)
|
||||
}
|
||||
// attach a special property so we can skip teleported content in
|
||||
// renderer's nextSibling search
|
||||
targetStart[TeleportEndKey] = targetAnchor
|
||||
|
||||
const mount = (container: RendererElement, anchor: RendererNode) => {
|
||||
// Teleport *always* has Array children. This is enforced in both the
|
||||
|
@ -138,14 +137,44 @@ export const TeleportImpl = {
|
|||
}
|
||||
}
|
||||
|
||||
const mountToTarget = () => {
|
||||
const target = (n2.target = resolveTarget(n2.props, querySelector))
|
||||
if (target) {
|
||||
insert(targetStart, target)
|
||||
insert(targetAnchor, target)
|
||||
// #2652 we could be teleporting from a non-SVG tree into an SVG tree
|
||||
if (namespace !== 'svg' && isTargetSVG(target)) {
|
||||
namespace = 'svg'
|
||||
} else if (namespace !== 'mathml' && isTargetMathML(target)) {
|
||||
namespace = 'mathml'
|
||||
}
|
||||
if (!disabled) {
|
||||
mount(target, targetAnchor)
|
||||
updateCssVars(n2)
|
||||
}
|
||||
} else if (__DEV__ && !disabled) {
|
||||
warn(
|
||||
'Invalid Teleport target on mount:',
|
||||
target,
|
||||
`(${typeof target})`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
mount(container, mainAnchor)
|
||||
} else if (target) {
|
||||
mount(target, targetAnchor)
|
||||
updateCssVars(n2)
|
||||
}
|
||||
|
||||
if (isTeleportDeferred(n2.props)) {
|
||||
queuePostRenderEffect(mountToTarget, parentSuspense)
|
||||
} else {
|
||||
mountToTarget()
|
||||
}
|
||||
} else {
|
||||
// update content
|
||||
n2.el = n1.el
|
||||
n2.targetStart = n1.targetStart
|
||||
const mainAnchor = (n2.anchor = n1.anchor)!
|
||||
const target = (n2.target = n1.target)!
|
||||
const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
|
||||
|
@ -241,9 +270,8 @@ export const TeleportImpl = {
|
|||
)
|
||||
}
|
||||
}
|
||||
updateCssVars(n2)
|
||||
}
|
||||
|
||||
updateCssVars(n2)
|
||||
},
|
||||
|
||||
remove(
|
||||
|
@ -253,9 +281,18 @@ export const TeleportImpl = {
|
|||
{ um: unmount, o: { remove: hostRemove } }: RendererInternals,
|
||||
doRemove: boolean,
|
||||
) {
|
||||
const { shapeFlag, children, anchor, targetAnchor, target, props } = vnode
|
||||
const {
|
||||
shapeFlag,
|
||||
children,
|
||||
anchor,
|
||||
targetStart,
|
||||
targetAnchor,
|
||||
target,
|
||||
props,
|
||||
} = vnode
|
||||
|
||||
if (target) {
|
||||
hostRemove(targetStart!)
|
||||
hostRemove(targetAnchor!)
|
||||
}
|
||||
|
||||
|
@ -424,7 +461,7 @@ function updateCssVars(vnode: VNode) {
|
|||
// code path here can assume browser environment.
|
||||
const ctx = vnode.ctx
|
||||
if (ctx && ctx.ut) {
|
||||
let node = (vnode.children as VNode[])[0].el!
|
||||
let node = vnode.targetStart
|
||||
while (node && node !== vnode.targetAnchor) {
|
||||
if (node.nodeType === 1) node.setAttribute('data-v-owner', ctx.uid)
|
||||
node = node.nextSibling
|
||||
|
|
|
@ -24,6 +24,7 @@ export function initCustomFormatter() {
|
|||
// custom formatter for Chrome
|
||||
// https://www.mattzeunert.com/2016/02/19/custom-chrome-devtools-object-formatters.html
|
||||
const formatter = {
|
||||
__vue_custom_formatter: true,
|
||||
header(obj: unknown) {
|
||||
// TODO also format ComponentPublicInstance & ctx.slots/attrs in setup
|
||||
if (!isObject(obj)) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { pauseTracking, resetTracking } from '@vue/reactivity'
|
|||
import type { VNode } from './vnode'
|
||||
import type { ComponentInternalInstance } from './component'
|
||||
import { popWarningContext, pushWarningContext, warn } from './warning'
|
||||
import { isArray, isFunction, isPromise } from '@vue/shared'
|
||||
import { EMPTY_OBJ, isArray, isFunction, isPromise } from '@vue/shared'
|
||||
import { LifecycleHooks } from './enums'
|
||||
import { BaseWatchErrorCodes } from '@vue/reactivity'
|
||||
|
||||
|
@ -27,6 +27,7 @@ export enum ErrorCodes {
|
|||
FUNCTION_REF,
|
||||
ASYNC_COMPONENT_LOADER,
|
||||
SCHEDULER,
|
||||
COMPONENT_UPDATE,
|
||||
APP_UNMOUNT_CLEANUP,
|
||||
}
|
||||
|
||||
|
@ -61,15 +62,14 @@ export const ErrorTypeStrings: Record<ErrorTypes, string> = {
|
|||
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
|
||||
[ErrorCodes.FUNCTION_REF]: 'ref function',
|
||||
[ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
|
||||
[ErrorCodes.SCHEDULER]:
|
||||
'scheduler flush. This is likely a Vue internals bug. ' +
|
||||
'Please open an issue at https://github.com/vuejs/core .',
|
||||
[ErrorCodes.SCHEDULER]: 'scheduler flush',
|
||||
[ErrorCodes.COMPONENT_UPDATE]: 'component update',
|
||||
[ErrorCodes.APP_UNMOUNT_CLEANUP]: 'app unmount cleanup function',
|
||||
}
|
||||
|
||||
export function callWithErrorHandling(
|
||||
fn: Function,
|
||||
instance: ComponentInternalInstance | null,
|
||||
instance: ComponentInternalInstance | null | undefined,
|
||||
type: ErrorTypes,
|
||||
args?: unknown[],
|
||||
) {
|
||||
|
@ -111,11 +111,13 @@ export function callWithAsyncErrorHandling(
|
|||
|
||||
export function handleError(
|
||||
err: unknown,
|
||||
instance: ComponentInternalInstance | null,
|
||||
instance: ComponentInternalInstance | null | undefined,
|
||||
type: ErrorTypes,
|
||||
throwInDev = true,
|
||||
) {
|
||||
const contextVNode = instance ? instance.vnode : null
|
||||
const { errorHandler, throwUnhandledErrorInProduction } =
|
||||
(instance && instance.appContext.config) || EMPTY_OBJ
|
||||
if (instance) {
|
||||
let cur = instance.parent
|
||||
// the exposed instance is the render proxy to keep it consistent with 2.x
|
||||
|
@ -138,20 +140,18 @@ export function handleError(
|
|||
cur = cur.parent
|
||||
}
|
||||
// app-level handling
|
||||
const appErrorHandler = instance.appContext.config.errorHandler
|
||||
if (appErrorHandler) {
|
||||
if (errorHandler) {
|
||||
pauseTracking()
|
||||
callWithErrorHandling(
|
||||
appErrorHandler,
|
||||
null,
|
||||
ErrorCodes.APP_ERROR_HANDLER,
|
||||
[err, exposedInstance, errorInfo],
|
||||
)
|
||||
callWithErrorHandling(errorHandler, null, ErrorCodes.APP_ERROR_HANDLER, [
|
||||
err,
|
||||
exposedInstance,
|
||||
errorInfo,
|
||||
])
|
||||
resetTracking()
|
||||
return
|
||||
}
|
||||
}
|
||||
logError(err, type, contextVNode, throwInDev)
|
||||
logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction)
|
||||
}
|
||||
|
||||
function logError(
|
||||
|
@ -159,6 +159,7 @@ function logError(
|
|||
type: ErrorTypes,
|
||||
contextVNode: VNode | null,
|
||||
throwInDev = true,
|
||||
throwInProd = false,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
const info = ErrorTypeStrings[type]
|
||||
|
@ -175,6 +176,8 @@ function logError(
|
|||
} else if (!__TEST__) {
|
||||
console.error(err)
|
||||
}
|
||||
} else if (throwInProd) {
|
||||
throw err
|
||||
} else {
|
||||
// recover in prod to reduce the impact on end-user
|
||||
console.error(err)
|
||||
|
|
|
@ -65,11 +65,13 @@ export function renderSlot(
|
|||
Fragment,
|
||||
{
|
||||
key:
|
||||
props.key ||
|
||||
// slot content array of a dynamic conditional slot may have a branch
|
||||
// key attached in the `createSlots` helper, respect that
|
||||
(validSlotContent && (validSlotContent as any).key) ||
|
||||
`_${name}`,
|
||||
(props.key ||
|
||||
// slot content array of a dynamic conditional slot may have a branch
|
||||
// key attached in the `createSlots` helper, respect that
|
||||
(validSlotContent && (validSlotContent as any).key) ||
|
||||
`_${name}`) +
|
||||
// #7256 force differentiate fallback content from actual content
|
||||
(!validSlotContent && fallback ? '_fb' : ''),
|
||||
},
|
||||
validSlotContent || (fallback ? fallback() : []),
|
||||
validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import {
|
||||
type ComponentInternalInstance,
|
||||
getCurrentInstance,
|
||||
} from '../component'
|
||||
import { warn } from '../warning'
|
||||
|
||||
export function useId() {
|
||||
const i = getCurrentInstance()
|
||||
if (i) {
|
||||
return (i.appContext.config.idPrefix || 'v') + ':' + i.ids[0] + i.ids[1]++
|
||||
} else if (__DEV__) {
|
||||
warn(
|
||||
`useId() is called when there is no active component ` +
|
||||
`instance to be associated with.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* There are 3 types of async boundaries:
|
||||
* - async components
|
||||
* - components with async setup()
|
||||
* - components with serverPrefetch
|
||||
*/
|
||||
export function markAsyncBoundary(instance: ComponentInternalInstance) {
|
||||
instance.ids = [instance.ids[0] + instance.ids[2]++ + '-', 0, 0]
|
||||
}
|
|
@ -29,9 +29,13 @@ export function useModel(
|
|||
|
||||
const camelizedName = camelize(name)
|
||||
const hyphenatedName = hyphenate(name)
|
||||
const modifiers = getModelModifiers(props, name)
|
||||
|
||||
const res = customRef((track, trigger) => {
|
||||
let localValue: any
|
||||
let prevSetValue: any
|
||||
let prevEmittedValue: any
|
||||
|
||||
watchSyncEffect(() => {
|
||||
const propValue = props[name]
|
||||
if (hasChanged(localValue, propValue)) {
|
||||
|
@ -39,12 +43,17 @@ export function useModel(
|
|||
trigger()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
get() {
|
||||
track()
|
||||
return options.get ? options.get(localValue) : localValue
|
||||
},
|
||||
|
||||
set(value) {
|
||||
if (!hasChanged(value, localValue)) {
|
||||
return
|
||||
}
|
||||
const rawProps = i.vnode!.props
|
||||
if (
|
||||
!(
|
||||
|
@ -56,27 +65,38 @@ export function useModel(
|
|||
(`onUpdate:${name}` in rawProps ||
|
||||
`onUpdate:${camelizedName}` in rawProps ||
|
||||
`onUpdate:${hyphenatedName}` in rawProps)
|
||||
) &&
|
||||
hasChanged(value, localValue)
|
||||
)
|
||||
) {
|
||||
// no v-model, local update
|
||||
localValue = value
|
||||
trigger()
|
||||
}
|
||||
i.emit(`update:${name}`, options.set ? options.set(value) : value)
|
||||
const emittedValue = options.set ? options.set(value) : value
|
||||
i.emit(`update:${name}`, emittedValue)
|
||||
// #10279: if the local value is converted via a setter but the value
|
||||
// emitted to parent was the same, the parent will not trigger any
|
||||
// updates and there will be no prop sync. However the local input state
|
||||
// may be out of sync, so we need to force an update here.
|
||||
if (
|
||||
value !== emittedValue &&
|
||||
value !== prevSetValue &&
|
||||
emittedValue === prevEmittedValue
|
||||
) {
|
||||
trigger()
|
||||
}
|
||||
prevSetValue = value
|
||||
prevEmittedValue = emittedValue
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const modifierKey =
|
||||
name === 'modelValue' ? 'modelModifiers' : `${name}Modifiers`
|
||||
|
||||
// @ts-expect-error
|
||||
res[Symbol.iterator] = () => {
|
||||
let i = 0
|
||||
return {
|
||||
next() {
|
||||
if (i < 2) {
|
||||
return { value: i++ ? props[modifierKey] || {} : res, done: false }
|
||||
return { value: i++ ? modifiers || EMPTY_OBJ : res, done: false }
|
||||
} else {
|
||||
return { done: true }
|
||||
}
|
||||
|
@ -86,3 +106,14 @@ export function useModel(
|
|||
|
||||
return res
|
||||
}
|
||||
|
||||
export const getModelModifiers = (
|
||||
props: Record<string, any>,
|
||||
modelName: string,
|
||||
): Record<string, boolean> | undefined => {
|
||||
return modelName === 'modelValue' || modelName === 'model-value'
|
||||
? props.modelModifiers
|
||||
: props[`${modelName}Modifiers`] ||
|
||||
props[`${camelize(modelName)}Modifiers`] ||
|
||||
props[`${hyphenate(modelName)}Modifiers`]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { type ShallowRef, readonly, shallowRef } from '@vue/reactivity'
|
||||
import { getCurrentInstance } from '../component'
|
||||
import { warn } from '../warning'
|
||||
import { EMPTY_OBJ } from '@vue/shared'
|
||||
|
||||
export function useTemplateRef<T = unknown>(
|
||||
key: string,
|
||||
): Readonly<ShallowRef<T | null>> {
|
||||
const i = getCurrentInstance()
|
||||
const r = shallowRef(null)
|
||||
if (i) {
|
||||
const refs = i.refs === EMPTY_OBJ ? (i.refs = {}) : i.refs
|
||||
Object.defineProperty(refs, key, {
|
||||
enumerable: true,
|
||||
get: () => r.value,
|
||||
set: val => (r.value = val),
|
||||
})
|
||||
} else if (__DEV__) {
|
||||
warn(
|
||||
`useTemplateRef() is called when there is no active component ` +
|
||||
`instance to be associated with.`,
|
||||
)
|
||||
}
|
||||
return (__DEV__ ? readonly(r) : r) as any
|
||||
}
|
|
@ -15,7 +15,7 @@ export function withMemo(
|
|||
|
||||
// shallow clone
|
||||
ret.memo = memo.slice()
|
||||
ret.memoIndex = index
|
||||
ret.cacheIndex = index
|
||||
|
||||
return (cache[index] = ret)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,10 @@ type HMRComponent = ComponentOptions | ClassComponent
|
|||
|
||||
export let isHmrUpdating = false
|
||||
|
||||
export const hmrDirtyComponents = new Set<ConcreteComponent>()
|
||||
export const hmrDirtyComponents = new Map<
|
||||
ConcreteComponent,
|
||||
Set<ComponentInternalInstance>
|
||||
>()
|
||||
|
||||
export interface HMRRuntime {
|
||||
createRecord: typeof createRecord
|
||||
|
@ -109,18 +112,21 @@ function reload(id: string, newComp: HMRComponent) {
|
|||
// create a snapshot which avoids the set being mutated during updates
|
||||
const instances = [...record.instances]
|
||||
|
||||
for (const instance of instances) {
|
||||
for (let i = 0; i < instances.length; i++) {
|
||||
const instance = instances[i]
|
||||
const oldComp = normalizeClassComponent(instance.type as HMRComponent)
|
||||
|
||||
if (!hmrDirtyComponents.has(oldComp)) {
|
||||
let dirtyInstances = hmrDirtyComponents.get(oldComp)
|
||||
if (!dirtyInstances) {
|
||||
// 1. Update existing comp definition to match new one
|
||||
if (oldComp !== record.initialDef) {
|
||||
updateComponentDef(oldComp, newComp)
|
||||
}
|
||||
// 2. mark definition dirty. This forces the renderer to replace the
|
||||
// component on patch.
|
||||
hmrDirtyComponents.add(oldComp)
|
||||
hmrDirtyComponents.set(oldComp, (dirtyInstances = new Set()))
|
||||
}
|
||||
dirtyInstances.add(instance)
|
||||
|
||||
// 3. invalidate options resolution cache
|
||||
instance.appContext.propsCache.delete(instance.type as any)
|
||||
|
@ -130,17 +136,17 @@ function reload(id: string, newComp: HMRComponent) {
|
|||
// 4. actually update
|
||||
if (instance.ceReload) {
|
||||
// custom element
|
||||
hmrDirtyComponents.add(oldComp)
|
||||
dirtyInstances.add(instance)
|
||||
instance.ceReload((newComp as any).styles)
|
||||
hmrDirtyComponents.delete(oldComp)
|
||||
dirtyInstances.delete(instance)
|
||||
} else if (instance.parent) {
|
||||
// 4. Force the parent instance to re-render. This will cause all updated
|
||||
// components to be unmounted and re-mounted. Queue the update so that we
|
||||
// don't end up forcing the same parent to re-render multiple times.
|
||||
queueJob(() => {
|
||||
instance.parent!.update()
|
||||
// #6930 avoid infinite recursion
|
||||
hmrDirtyComponents.delete(oldComp)
|
||||
// #6930, #11248 avoid infinite recursion
|
||||
dirtyInstances.delete(instance)
|
||||
})
|
||||
} else if (instance.appContext.reload) {
|
||||
// root instance mounted via createApp() has a reload method
|
||||
|
@ -157,11 +163,7 @@ function reload(id: string, newComp: HMRComponent) {
|
|||
|
||||
// 5. make sure to cleanup dirty hmr components after update
|
||||
queuePostFlushCb(() => {
|
||||
for (const instance of instances) {
|
||||
hmrDirtyComponents.delete(
|
||||
normalizeClassComponent(instance.type as HMRComponent),
|
||||
)
|
||||
}
|
||||
hmrDirtyComponents.clear()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
} from './components/Suspense'
|
||||
import type { TeleportImpl, TeleportVNode } from './components/Teleport'
|
||||
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||
import { isReactive } from '@vue/reactivity'
|
||||
|
||||
export type RootHydrateFunction = (
|
||||
vnode: VNode<Node, Element>,
|
||||
|
@ -464,15 +465,7 @@ export function createHydrationFunctions(
|
|||
// force hydrate v-bind with .prop modifiers
|
||||
key[0] === '.'
|
||||
) {
|
||||
patchProp(
|
||||
el,
|
||||
key,
|
||||
null,
|
||||
props[key],
|
||||
undefined,
|
||||
undefined,
|
||||
parentComponent,
|
||||
)
|
||||
patchProp(el, key, null, props[key], undefined, parentComponent)
|
||||
}
|
||||
}
|
||||
} else if (props.onClick) {
|
||||
|
@ -484,9 +477,13 @@ export function createHydrationFunctions(
|
|||
null,
|
||||
props.onClick,
|
||||
undefined,
|
||||
undefined,
|
||||
parentComponent,
|
||||
)
|
||||
} else if (patchFlag & PatchFlags.STYLE && isReactive(props.style)) {
|
||||
// #11372: object style values are iterated during patch instead of
|
||||
// render/normalization phase, but style patch is skipped during
|
||||
// hydration, so we need to force iterate the object to track deps
|
||||
for (const key in props.style) props.style[key]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -531,7 +528,27 @@ export function createHydrationFunctions(
|
|||
const vnode = optimized
|
||||
? children[i]
|
||||
: (children[i] = normalizeVNode(children[i]))
|
||||
const isText = vnode.type === Text
|
||||
if (node) {
|
||||
if (isText && !optimized) {
|
||||
// #7285 possible consecutive text vnodes from manual render fns or
|
||||
// JSX-compiled fns, but on the client the browser parses only 1 text
|
||||
// node.
|
||||
// look ahead for next possible text vnode
|
||||
let next = children[i + 1]
|
||||
if (next && (next = normalizeVNode(next)).type === Text) {
|
||||
// create an extra TextNode on the client for the next vnode to
|
||||
// adopt
|
||||
insert(
|
||||
createText(
|
||||
(node as Text).data.slice((vnode.children as string).length),
|
||||
),
|
||||
container,
|
||||
nextSibling(node),
|
||||
)
|
||||
;(node as Text).data = vnode.children as string
|
||||
}
|
||||
}
|
||||
node = hydrateNode(
|
||||
node,
|
||||
vnode,
|
||||
|
@ -540,7 +557,7 @@ export function createHydrationFunctions(
|
|||
slotScopeIds,
|
||||
optimized,
|
||||
)
|
||||
} else if (vnode.type === Text && !vnode.children) {
|
||||
} else if (isText && !vnode.children) {
|
||||
// #7215 create a TextNode for empty text node
|
||||
// because server rendered HTML won't contain a text node
|
||||
insert((vnode.el = createText('')), container)
|
||||
|
|
|
@ -63,6 +63,8 @@ export { defineComponent } from './apiDefineComponent'
|
|||
export { defineAsyncComponent } from './apiAsyncComponent'
|
||||
export { useAttrs, useSlots } from './apiSetupHelpers'
|
||||
export { useModel } from './helpers/useModel'
|
||||
export { useTemplateRef } from './helpers/useTemplateRef'
|
||||
export { useId } from './helpers/useId'
|
||||
|
||||
// <script setup> API ----------------------------------------------------------
|
||||
|
||||
|
|
|
@ -65,7 +65,11 @@ import {
|
|||
type SuspenseImpl,
|
||||
queueEffectWithSuspense,
|
||||
} from './components/Suspense'
|
||||
import type { TeleportImpl, TeleportVNode } from './components/Teleport'
|
||||
import {
|
||||
TeleportEndKey,
|
||||
type TeleportImpl,
|
||||
type TeleportVNode,
|
||||
} from './components/Teleport'
|
||||
import { type KeepAliveContext, isKeepAlive } from './components/KeepAlive'
|
||||
import { isHmrUpdating, registerHMR, unregisterHMR } from './hmr'
|
||||
import { type RootHydrateFunction, createHydrationFunctions } from './hydration'
|
||||
|
@ -110,10 +114,7 @@ export interface RendererOptions<
|
|||
prevValue: any,
|
||||
nextValue: any,
|
||||
namespace?: ElementNamespace,
|
||||
prevChildren?: VNode<HostNode, HostElement>[],
|
||||
parentComponent?: ComponentInternalInstance | null,
|
||||
parentSuspense?: SuspenseBoundary | null,
|
||||
unmountChildren?: UnmountChildrenFn,
|
||||
): void
|
||||
insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
|
||||
remove(el: HostNode): void
|
||||
|
@ -147,7 +148,7 @@ export interface RendererOptions<
|
|||
// functions provided via options, so the internal constraint is really just
|
||||
// a generic object.
|
||||
export interface RendererNode {
|
||||
[key: string]: any
|
||||
[key: string | symbol]: any
|
||||
}
|
||||
|
||||
export interface RendererElement extends RendererNode {}
|
||||
|
@ -685,17 +686,7 @@ function baseCreateRenderer(
|
|||
if (props) {
|
||||
for (const key in props) {
|
||||
if (key !== 'value' && !isReservedProp(key)) {
|
||||
hostPatchProp(
|
||||
el,
|
||||
key,
|
||||
null,
|
||||
props[key],
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
hostPatchProp(el, key, null, props[key], namespace, parentComponent)
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -848,6 +839,15 @@ function baseCreateRenderer(
|
|||
dynamicChildren = null
|
||||
}
|
||||
|
||||
// #9135 innerHTML / textContent unset needs to happen before possible
|
||||
// new children mount
|
||||
if (
|
||||
(oldProps.innerHTML && newProps.innerHTML == null) ||
|
||||
(oldProps.textContent && newProps.textContent == null)
|
||||
) {
|
||||
hostSetElementText(el, '')
|
||||
}
|
||||
|
||||
if (dynamicChildren) {
|
||||
patchBlockChildren(
|
||||
n1.dynamicChildren!,
|
||||
|
@ -884,15 +884,7 @@ function baseCreateRenderer(
|
|||
// (i.e. at the exact same position in the source template)
|
||||
if (patchFlag & PatchFlags.FULL_PROPS) {
|
||||
// element props contain dynamic keys, full diff needed
|
||||
patchProps(
|
||||
el,
|
||||
n2,
|
||||
oldProps,
|
||||
newProps,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
namespace,
|
||||
)
|
||||
patchProps(el, oldProps, newProps, parentComponent, namespace)
|
||||
} else {
|
||||
// class
|
||||
// this flag is matched when the element has dynamic class bindings.
|
||||
|
@ -923,17 +915,7 @@ function baseCreateRenderer(
|
|||
const next = newProps[key]
|
||||
// #1471 force patch value
|
||||
if (next !== prev || key === 'value') {
|
||||
hostPatchProp(
|
||||
el,
|
||||
key,
|
||||
prev,
|
||||
next,
|
||||
namespace,
|
||||
n1.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
hostPatchProp(el, key, prev, next, namespace, parentComponent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -948,15 +930,7 @@ function baseCreateRenderer(
|
|||
}
|
||||
} else if (!optimized && dynamicChildren == null) {
|
||||
// unoptimized, full diff
|
||||
patchProps(
|
||||
el,
|
||||
n2,
|
||||
oldProps,
|
||||
newProps,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
namespace,
|
||||
)
|
||||
patchProps(el, oldProps, newProps, parentComponent, namespace)
|
||||
}
|
||||
|
||||
if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {
|
||||
|
@ -1013,11 +987,9 @@ function baseCreateRenderer(
|
|||
|
||||
const patchProps = (
|
||||
el: RendererElement,
|
||||
vnode: VNode,
|
||||
oldProps: Data,
|
||||
newProps: Data,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
namespace: ElementNamespace,
|
||||
) => {
|
||||
if (oldProps !== newProps) {
|
||||
|
@ -1030,10 +1002,7 @@ function baseCreateRenderer(
|
|||
oldProps[key],
|
||||
null,
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1045,17 +1014,7 @@ function baseCreateRenderer(
|
|||
const prev = oldProps[key]
|
||||
// defer patching value
|
||||
if (next !== prev && key !== 'value') {
|
||||
hostPatchProp(
|
||||
el,
|
||||
key,
|
||||
prev,
|
||||
next,
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
hostPatchProp(el, key, prev, next, namespace, parentComponent)
|
||||
}
|
||||
}
|
||||
if ('value' in newProps) {
|
||||
|
@ -1248,7 +1207,7 @@ function baseCreateRenderer(
|
|||
if (__DEV__) {
|
||||
startMeasure(instance, `init`)
|
||||
}
|
||||
setupComponent(instance)
|
||||
setupComponent(instance, false, optimized)
|
||||
if (__DEV__) {
|
||||
endMeasure(instance, `init`)
|
||||
}
|
||||
|
@ -1599,6 +1558,7 @@ function baseCreateRenderer(
|
|||
|
||||
const update = (instance.update = effect.run.bind(effect))
|
||||
const job: SchedulerJob = (instance.job = effect.runIfDirty.bind(effect))
|
||||
job.i = instance
|
||||
job.id = instance.uid
|
||||
effect.scheduler = () => queueJob(job)
|
||||
|
||||
|
@ -1613,7 +1573,6 @@ function baseCreateRenderer(
|
|||
effect.onTrigger = instance.rtg
|
||||
? e => invokeArrayFns(instance.rtg!, e)
|
||||
: void 0
|
||||
job.ownerInstance = instance
|
||||
}
|
||||
|
||||
update()
|
||||
|
@ -2123,7 +2082,7 @@ function baseCreateRenderer(
|
|||
shapeFlag,
|
||||
patchFlag,
|
||||
dirs,
|
||||
memoIndex,
|
||||
cacheIndex,
|
||||
} = vnode
|
||||
|
||||
if (patchFlag === PatchFlags.BAIL) {
|
||||
|
@ -2136,8 +2095,8 @@ function baseCreateRenderer(
|
|||
}
|
||||
|
||||
// #6593 should clean memo cache when unmount
|
||||
if (memoIndex != null) {
|
||||
parentComponent!.renderCache[memoIndex] = undefined
|
||||
if (cacheIndex != null) {
|
||||
parentComponent!.renderCache[cacheIndex] = undefined
|
||||
}
|
||||
|
||||
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
|
||||
|
@ -2178,6 +2137,12 @@ function baseCreateRenderer(
|
|||
)
|
||||
} else if (
|
||||
dynamicChildren &&
|
||||
// #5154
|
||||
// when v-once is used inside a block, setBlockTracking(-1) marks the
|
||||
// parent block with hasOnce: true
|
||||
// so that it doesn't take the fast path during unmount - otherwise
|
||||
// components nested in v-once are never unmounted.
|
||||
!dynamicChildren.hasOnce &&
|
||||
// #1153: fast path should not be taken for non-stable (v-for) fragments
|
||||
(type !== Fragment ||
|
||||
(patchFlag > 0 && patchFlag & PatchFlags.STABLE_FRAGMENT))
|
||||
|
@ -2376,7 +2341,12 @@ function baseCreateRenderer(
|
|||
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
|
||||
return vnode.suspense!.next()
|
||||
}
|
||||
return hostNextSibling((vnode.anchor || vnode.el)!)
|
||||
const el = hostNextSibling((vnode.anchor || vnode.el)!)
|
||||
// #9071, #9313
|
||||
// teleported content can mess up nextSibling searches during patch so
|
||||
// we need to skip them during nextSibling search
|
||||
const teleportEnd = el && el[TeleportEndKey]
|
||||
return teleportEnd ? hostNextSibling(teleportEnd) : el
|
||||
}
|
||||
|
||||
let isFlushing = false
|
||||
|
|
|
@ -12,9 +12,8 @@ export interface SchedulerJob extends BaseSchedulerJob {
|
|||
/**
|
||||
* Attached by renderer.ts when setting up a component's render effect
|
||||
* Used to obtain component information when reporting max recursive updates.
|
||||
* dev only.
|
||||
*/
|
||||
ownerInstance?: ComponentInternalInstance
|
||||
i?: ComponentInternalInstance
|
||||
}
|
||||
|
||||
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
|
||||
|
@ -106,7 +105,9 @@ export function invalidateJob(job: SchedulerJob) {
|
|||
|
||||
export function queuePostFlushCb(cb: SchedulerJobs) {
|
||||
if (!isArray(cb)) {
|
||||
if (!(cb.flags! & SchedulerJobFlags.QUEUED)) {
|
||||
if (activePostFlushCbs && cb.id === -1) {
|
||||
activePostFlushCbs.splice(postFlushIndex + 1, 0, cb)
|
||||
} else if (!(cb.flags! & SchedulerJobFlags.QUEUED)) {
|
||||
pendingPostFlushCbs.push(cb)
|
||||
if (!(cb.flags! & SchedulerJobFlags.ALLOW_RECURSE)) {
|
||||
cb.flags! |= SchedulerJobFlags.QUEUED
|
||||
|
@ -228,7 +229,11 @@ function flushJobs(seen?: CountMap) {
|
|||
if (__DEV__ && check(job)) {
|
||||
continue
|
||||
}
|
||||
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
|
||||
callWithErrorHandling(
|
||||
job,
|
||||
job.i,
|
||||
job.i ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER,
|
||||
)
|
||||
job.flags! &= ~SchedulerJobFlags.QUEUED
|
||||
}
|
||||
}
|
||||
|
@ -254,7 +259,7 @@ function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) {
|
|||
} else {
|
||||
const count = seen.get(fn)!
|
||||
if (count > RECURSION_LIMIT) {
|
||||
const instance = fn.ownerInstance
|
||||
const instance = fn.i
|
||||
const componentName = instance && getComponentName(instance.type)
|
||||
handleError(
|
||||
`Maximum recursive updates exceeded${
|
||||
|
|
|
@ -92,10 +92,22 @@ export type VNodeRef =
|
|||
) => void)
|
||||
|
||||
export type VNodeNormalizedRefAtom = {
|
||||
/**
|
||||
* component instance
|
||||
*/
|
||||
i: ComponentInternalInstance
|
||||
/**
|
||||
* Actual ref
|
||||
*/
|
||||
r: VNodeRef
|
||||
k?: string // setup ref key
|
||||
f?: boolean // refInFor marker
|
||||
/**
|
||||
* setup ref key
|
||||
*/
|
||||
k?: string
|
||||
/**
|
||||
* refInFor marker
|
||||
*/
|
||||
f?: boolean
|
||||
}
|
||||
|
||||
export type VNodeNormalizedRef =
|
||||
|
@ -186,6 +198,7 @@ export interface VNode<
|
|||
el: HostNode | null
|
||||
anchor: HostNode | null // fragment anchor
|
||||
target: HostElement | null // teleport target
|
||||
targetStart: HostNode | null // teleport target start anchor
|
||||
targetAnchor: HostNode | null // teleport target anchor
|
||||
/**
|
||||
* number of elements contained in a static vnode
|
||||
|
@ -214,7 +227,7 @@ export interface VNode<
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
dynamicChildren: VNode[] | null
|
||||
dynamicChildren: (VNode[] & { hasOnce?: boolean }) | null
|
||||
|
||||
// application root node only
|
||||
appContext: AppContext | null
|
||||
|
@ -231,7 +244,7 @@ export interface VNode<
|
|||
/**
|
||||
* @internal index for cleaning v-memo cache
|
||||
*/
|
||||
memoIndex?: number
|
||||
cacheIndex?: number
|
||||
/**
|
||||
* @internal __COMPAT__ only
|
||||
*/
|
||||
|
@ -247,8 +260,8 @@ export interface VNode<
|
|||
// can divide a template into nested blocks, and within each block the node
|
||||
// structure would be stable. This allows us to skip most children diffing
|
||||
// and only worry about the dynamic nodes (indicated by patch flags).
|
||||
export const blockStack: (VNode[] | null)[] = []
|
||||
export let currentBlock: VNode[] | null = null
|
||||
export const blockStack: VNode['dynamicChildren'][] = []
|
||||
export let currentBlock: VNode['dynamicChildren'] = null
|
||||
|
||||
/**
|
||||
* Open a block.
|
||||
|
@ -299,6 +312,11 @@ export let isBlockTreeEnabled = 1
|
|||
*/
|
||||
export function setBlockTracking(value: number) {
|
||||
isBlockTreeEnabled += value
|
||||
if (value < 0 && currentBlock) {
|
||||
// mark current block so it doesn't take fast path and skip possible
|
||||
// nested components duriung unmount
|
||||
currentBlock.hasOnce = true
|
||||
}
|
||||
}
|
||||
|
||||
function setupBlock(vnode: VNode) {
|
||||
|
@ -370,17 +388,16 @@ export function isVNode(value: any): value is VNode {
|
|||
}
|
||||
|
||||
export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
|
||||
if (
|
||||
__DEV__ &&
|
||||
n2.shapeFlag & ShapeFlags.COMPONENT &&
|
||||
hmrDirtyComponents.has(n2.type as ConcreteComponent)
|
||||
) {
|
||||
// #7042, ensure the vnode being unmounted during HMR
|
||||
// bitwise operations to remove keep alive flags
|
||||
n1.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
||||
n2.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE
|
||||
// HMR only: if the component has been hot-updated, force a reload.
|
||||
return false
|
||||
if (__DEV__ && n2.shapeFlag & ShapeFlags.COMPONENT && n1.component) {
|
||||
const dirtyInstances = hmrDirtyComponents.get(n2.type as ConcreteComponent)
|
||||
if (dirtyInstances && dirtyInstances.has(n1.component)) {
|
||||
// #7042, ensure the vnode being unmounted during HMR
|
||||
// bitwise operations to remove keep alive flags
|
||||
n1.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
||||
n2.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE
|
||||
// HMR only: if the component has been hot-updated, force a reload.
|
||||
return false
|
||||
}
|
||||
}
|
||||
return n1.type === n2.type && n1.key === n2.key
|
||||
}
|
||||
|
@ -461,6 +478,7 @@ function createBaseVNode(
|
|||
el: null,
|
||||
anchor: null,
|
||||
target: null,
|
||||
targetStart: null,
|
||||
targetAnchor: null,
|
||||
staticCount: 0,
|
||||
shapeFlag,
|
||||
|
@ -661,6 +679,7 @@ export function cloneVNode<T, U>(
|
|||
? (children as VNode[]).map(deepCloneVNode)
|
||||
: children,
|
||||
target: vnode.target,
|
||||
targetStart: vnode.targetStart,
|
||||
targetAnchor: vnode.targetAnchor,
|
||||
staticCount: vnode.staticCount,
|
||||
shapeFlag: vnode.shapeFlag,
|
||||
|
|
|
@ -30,7 +30,12 @@ export function popWarningContext() {
|
|||
stack.pop()
|
||||
}
|
||||
|
||||
let isWarning = false
|
||||
|
||||
export function warn(msg: string, ...args: any[]) {
|
||||
if (isWarning) return
|
||||
isWarning = true
|
||||
|
||||
// avoid props formatting or warn handler tracking deps that might be mutated
|
||||
// during patch, leading to infinite recursion.
|
||||
pauseTracking()
|
||||
|
@ -70,6 +75,7 @@ export function warn(msg: string, ...args: any[]) {
|
|||
}
|
||||
|
||||
resetTracking()
|
||||
isWarning = false
|
||||
}
|
||||
|
||||
export function getComponentTrace(): ComponentTraceStack {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { patchProp } from '../src/patchProp'
|
||||
import { h, render } from '../src'
|
||||
import { h, nextTick, ref, render } from '../src'
|
||||
|
||||
describe('runtime-dom: props patching', () => {
|
||||
test('basic', () => {
|
||||
|
@ -133,6 +133,31 @@ describe('runtime-dom: props patching', () => {
|
|||
expect(fn).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('patch innerHTML porp', async () => {
|
||||
const root = document.createElement('div')
|
||||
const state = ref(false)
|
||||
const Comp = {
|
||||
render: () => {
|
||||
if (state.value) {
|
||||
return h('div', [h('del', null, 'baz')])
|
||||
} else {
|
||||
return h('div', { innerHTML: 'baz' })
|
||||
}
|
||||
},
|
||||
}
|
||||
render(h(Comp), root)
|
||||
expect(root.innerHTML).toBe(`<div>baz</div>`)
|
||||
state.value = true
|
||||
await nextTick()
|
||||
expect(root.innerHTML).toBe(`<div><del>baz</del></div>`)
|
||||
})
|
||||
|
||||
test('patch innerHTML porp w/ undefined value', async () => {
|
||||
const root = document.createElement('div')
|
||||
render(h('div', { innerHTML: undefined }), root)
|
||||
expect(root.innerHTML).toBe(`<div></div>`)
|
||||
})
|
||||
|
||||
test('textContent unmount prev children', () => {
|
||||
const fn = vi.fn()
|
||||
const comp = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-dom",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"description": "@vue/runtime-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-dom.esm-bundler.js",
|
||||
|
|
|
@ -10,19 +10,13 @@ export function patchDOMProp(
|
|||
el: any,
|
||||
key: string,
|
||||
value: any,
|
||||
// the following args are passed only due to potential innerHTML/textContent
|
||||
// overriding existing VNodes, in which case the old tree must be properly
|
||||
// unmounted.
|
||||
prevChildren: any,
|
||||
parentComponent: any,
|
||||
parentSuspense: any,
|
||||
unmountChildren: any,
|
||||
) {
|
||||
if (key === 'innerHTML' || key === 'textContent') {
|
||||
if (prevChildren) {
|
||||
unmountChildren(prevChildren, parentComponent, parentSuspense)
|
||||
}
|
||||
el[key] = value == null ? '' : value
|
||||
// null value case is handled in renderer patchElement before patching
|
||||
// children
|
||||
if (value == null) return
|
||||
el[key] = value
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -20,10 +20,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
|||
prevValue,
|
||||
nextValue,
|
||||
namespace,
|
||||
prevChildren,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
) => {
|
||||
const isSVG = namespace === 'svg'
|
||||
if (key === 'class') {
|
||||
|
@ -42,15 +39,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
|||
? ((key = key.slice(1)), false)
|
||||
: shouldSetAsProp(el, key, nextValue, isSVG)
|
||||
) {
|
||||
patchDOMProp(
|
||||
el,
|
||||
key,
|
||||
nextValue,
|
||||
prevChildren,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
patchDOMProp(el, key, nextValue, parentComponent)
|
||||
// #6007 also set form state as attributes so they work with
|
||||
// <input type="reset"> or libs / extensions that expect attributes
|
||||
// #11163 custom elements may use value as an prop and set it as object
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import { bench, describe } from 'vitest'
|
||||
|
||||
import { createBuffer } from '../src/render'
|
||||
|
||||
describe('createBuffer', () => {
|
||||
let stringBuffer = createBuffer()
|
||||
|
||||
bench(
|
||||
'string only',
|
||||
() => {
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
stringBuffer.push('hello')
|
||||
}
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
stringBuffer = createBuffer()
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
let stringNestedBuffer = createBuffer()
|
||||
|
||||
bench(
|
||||
'string with nested',
|
||||
() => {
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
if (i % 3 === 0) {
|
||||
stringNestedBuffer.push('hello')
|
||||
} else {
|
||||
const buffer = createBuffer()
|
||||
buffer.push('hello')
|
||||
stringNestedBuffer.push(buffer.getBuffer())
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
stringNestedBuffer = createBuffer()
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
bench(
|
||||
'string with nested async',
|
||||
() => {
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
if (i % 3 === 0) {
|
||||
const buffer = createBuffer()
|
||||
buffer.push('hello')
|
||||
stringNestedBuffer.push(Promise.resolve(buffer.getBuffer()))
|
||||
} else {
|
||||
const buffer = createBuffer()
|
||||
buffer.push('hello')
|
||||
stringNestedBuffer.push(buffer.getBuffer())
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
stringNestedBuffer = createBuffer()
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
|
@ -0,0 +1,74 @@
|
|||
import { bench, describe } from 'vitest'
|
||||
|
||||
import { type SSRBuffer, createBuffer } from '../src/render'
|
||||
import { unrollBuffer } from '../src/renderToString'
|
||||
|
||||
function createSyncBuffer(levels: number, itemsPerLevel: number): SSRBuffer {
|
||||
const buffer = createBuffer()
|
||||
|
||||
function addItems(buf: ReturnType<typeof createBuffer>, level: number) {
|
||||
for (let i = 1; i <= levels * itemsPerLevel; i++) {
|
||||
buf.push(`sync${level}.${i}`)
|
||||
}
|
||||
if (level < levels) {
|
||||
const subBuffer = createBuffer()
|
||||
addItems(subBuffer, level + 1)
|
||||
buf.push(subBuffer.getBuffer())
|
||||
}
|
||||
}
|
||||
|
||||
addItems(buffer, 1)
|
||||
return buffer.getBuffer()
|
||||
}
|
||||
|
||||
function createMixedBuffer(levels: number, itemsPerLevel: number): SSRBuffer {
|
||||
const buffer = createBuffer()
|
||||
|
||||
function addItems(buf: ReturnType<typeof createBuffer>, level: number) {
|
||||
for (let i = 1; i <= levels * itemsPerLevel; i++) {
|
||||
if (i % 3 === 0) {
|
||||
// @ts-expect-error testing...
|
||||
buf.push(Promise.resolve(`async${level}.${i}`))
|
||||
} else {
|
||||
buf.push(`sync${level}.${i}`)
|
||||
}
|
||||
}
|
||||
if (level < levels) {
|
||||
const subBuffer = createBuffer()
|
||||
addItems(subBuffer, level + 1)
|
||||
buf.push(subBuffer.getBuffer())
|
||||
}
|
||||
}
|
||||
|
||||
addItems(buffer, 1)
|
||||
return buffer.getBuffer()
|
||||
}
|
||||
|
||||
describe('unrollBuffer', () => {
|
||||
let syncBuffer = createBuffer().getBuffer()
|
||||
let mixedBuffer = createBuffer().getBuffer()
|
||||
|
||||
bench(
|
||||
'sync',
|
||||
() => {
|
||||
return unrollBuffer(syncBuffer) as any
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
syncBuffer = createSyncBuffer(5, 3)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
bench(
|
||||
'mixed',
|
||||
() => {
|
||||
return unrollBuffer(mixedBuffer) as any
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
mixedBuffer = createMixedBuffer(5, 3)
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/server-renderer",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"description": "@vue/server-renderer",
|
||||
"main": "index.js",
|
||||
"module": "dist/server-renderer.esm-bundler.js",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue