mirror of https://github.com/vuejs/core.git
chore: Merge branch 'main' into minor
This commit is contained in:
commit
bb5c31e614
|
@ -26,13 +26,23 @@ module.exports = {
|
|||
'no-restricted-syntax': [
|
||||
'error',
|
||||
banConstEnum,
|
||||
// since we target ES2015 for baseline support, we need to forbid object
|
||||
// rest spread usage in destructure as it compiles into a verbose helper.
|
||||
'ObjectPattern > RestElement',
|
||||
// tsc compiles assignment spread into Object.assign() calls, but esbuild
|
||||
// still generates verbose helpers, so spread assignment is also prohiboted
|
||||
'ObjectExpression > SpreadElement',
|
||||
'AwaitExpression',
|
||||
{
|
||||
selector: 'ObjectPattern > RestElement',
|
||||
message:
|
||||
'Our output target is ES2016, and object rest spread results in ' +
|
||||
'verbose helpers and should be avoided.',
|
||||
},
|
||||
{
|
||||
selector: 'ObjectExpression > SpreadElement',
|
||||
message:
|
||||
'esbuild transpiles object spread into very verbose inline helpers.\n' +
|
||||
'Please use the `extend` helper from @vue/shared instead.',
|
||||
},
|
||||
{
|
||||
selector: 'AwaitExpression',
|
||||
message:
|
||||
'Our output target is ES2016, so async/await syntax should be avoided.',
|
||||
},
|
||||
],
|
||||
'sort-imports': ['error', { ignoreDeclarationSort: true }],
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
|
||||
- name: Set node version to 18
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
ref: minor
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
|
||||
- name: Set node version to 18
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
@ -6,6 +6,7 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- minor
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
@ -20,7 +21,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -42,7 +43,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -71,7 +72,7 @@ jobs:
|
|||
key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -97,7 +98,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -125,7 +126,7 @@ jobs:
|
|||
# - uses: actions/checkout@v4
|
||||
|
||||
# - name: Install pnpm
|
||||
# uses: pnpm/action-setup@v2
|
||||
# uses: pnpm/action-setup@v3.0.0
|
||||
|
||||
# - name: Install Node.js
|
||||
# uses: actions/setup-node@v4
|
||||
|
|
|
@ -7,6 +7,7 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- minor
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
@ -22,7 +23,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
@ -10,3 +10,4 @@ TODOs.md
|
|||
.eslintcache
|
||||
dts-build/packages
|
||||
*.tsbuildinfo
|
||||
*.tgz
|
||||
|
|
|
@ -5,24 +5,15 @@
|
|||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Jest",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||
"stopOnEntry": false,
|
||||
"args": ["${fileBasename}", "--runInBand", "--detectOpenHandles"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": null,
|
||||
"runtimeExecutable": null,
|
||||
"runtimeArgs": ["--nolazy"],
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"sourceMaps": true,
|
||||
"windows": {
|
||||
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
|
||||
}
|
||||
"name": "Vitest - Debug Current Test File",
|
||||
"autoAttachChildProcesses": true,
|
||||
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
|
||||
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
|
||||
"args": ["run", "${relativeFile}"],
|
||||
"smartStep": true,
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
52
CHANGELOG.md
52
CHANGELOG.md
|
@ -1,3 +1,55 @@
|
|||
## [3.4.22](https://github.com/vuejs/core/compare/v3.4.21...v3.4.22) (2024-04-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compat:** fix $options mutation + adjust private API initialization ([d58d133](https://github.com/vuejs/core/commit/d58d133b1cde5085cc5ab0012d544cafd62a6ee6)), closes [#10626](https://github.com/vuejs/core/issues/10626) [#10636](https://github.com/vuejs/core/issues/10636)
|
||||
* **compile-sfc:** analyze v-bind shorthand usage in template ([#10518](https://github.com/vuejs/core/issues/10518)) ([e5919d4](https://github.com/vuejs/core/commit/e5919d4658cfe0bb18c76611dd3c3432c57f94ab)), closes [#10515](https://github.com/vuejs/core/issues/10515)
|
||||
* **compiler-core:** fix loc.source for end tags with whitespace before > ([16174da](https://github.com/vuejs/core/commit/16174da21d6c8ac0aae027dd964fc35e221ded0a)), closes [#10694](https://github.com/vuejs/core/issues/10694) [#10695](https://github.com/vuejs/core/issues/10695)
|
||||
* **compiler-core:** fix v-bind shorthand for component :is ([04af950](https://github.com/vuejs/core/commit/04af9504a720c8e6de26c04b1282cf14fa1bcee3)), closes [#10469](https://github.com/vuejs/core/issues/10469) [#10471](https://github.com/vuejs/core/issues/10471)
|
||||
* **compiler-sfc:** :is() and :where() in compound selectors ([#10522](https://github.com/vuejs/core/issues/10522)) ([660cadc](https://github.com/vuejs/core/commit/660cadc7aadb909ef33a6055c4374902a82607a4)), closes [#10511](https://github.com/vuejs/core/issues/10511)
|
||||
* **compiler-sfc:** also search for `.tsx` when type import's extension is omitted ([#10637](https://github.com/vuejs/core/issues/10637)) ([34106bc](https://github.com/vuejs/core/commit/34106bc9c715247211273bb9c64712f04bd4879d)), closes [#10635](https://github.com/vuejs/core/issues/10635)
|
||||
* **compiler-sfc:** fix defineModel coercion for boolean + string union types ([#9603](https://github.com/vuejs/core/issues/9603)) ([0cef65c](https://github.com/vuejs/core/commit/0cef65cee411356e721bbc90d731fc52fc8fce94)), closes [#9587](https://github.com/vuejs/core/issues/9587) [#10676](https://github.com/vuejs/core/issues/10676)
|
||||
* **compiler-sfc:** fix universal selector scope ([#10551](https://github.com/vuejs/core/issues/10551)) ([54a6afa](https://github.com/vuejs/core/commit/54a6afa75a546078e901ce0882da53b97420fe94)), closes [#10548](https://github.com/vuejs/core/issues/10548)
|
||||
* **compiler-sfc:** use options module name if options provide runtimeModuleName options ([#10457](https://github.com/vuejs/core/issues/10457)) ([e76d743](https://github.com/vuejs/core/commit/e76d7430aa7470342f3fe263145a0fa92f5898ca)), closes [#10454](https://github.com/vuejs/core/issues/10454)
|
||||
* **custom-element:** avoid setting attr to null if it is removed ([#9012](https://github.com/vuejs/core/issues/9012)) ([b49306a](https://github.com/vuejs/core/commit/b49306adff4572d90a42ccd231387f16eb966bbe)), closes [#9006](https://github.com/vuejs/core/issues/9006) [#10324](https://github.com/vuejs/core/issues/10324)
|
||||
* **hydration:** properly handle optimized mode during hydrate node ([#10638](https://github.com/vuejs/core/issues/10638)) ([2ec06fd](https://github.com/vuejs/core/commit/2ec06fd6c8383e11cdf4efcab1707f973bd6a54c)), closes [#10607](https://github.com/vuejs/core/issues/10607)
|
||||
* **reactivity:** computed should not be detected as true by isProxy ([#10401](https://github.com/vuejs/core/issues/10401)) ([9da34d7](https://github.com/vuejs/core/commit/9da34d7af81607fddd1f32f21b3b4002402ff1cc))
|
||||
* **reactivity:** fix hasOwnProperty key coercion edge cases ([969c5fb](https://github.com/vuejs/core/commit/969c5fb30f4c725757c7385abfc74772514eae4b))
|
||||
* **reactivity:** fix tracking when hasOwnProperty is called with non-string value ([c3c5dc9](https://github.com/vuejs/core/commit/c3c5dc93fbccc196771458f0b43cd5b7ad1863f4)), closes [#10455](https://github.com/vuejs/core/issues/10455) [#10464](https://github.com/vuejs/core/issues/10464)
|
||||
* **runtime-core:** fix errorHandler causes an infinite loop during execution ([#9575](https://github.com/vuejs/core/issues/9575)) ([ab59bed](https://github.com/vuejs/core/commit/ab59bedae4e5e40b28804d88a51305b236d4a873))
|
||||
* **runtime-core:** handle invalid values in callWithAsyncErrorHandling ([53d15d3](https://github.com/vuejs/core/commit/53d15d3f76184eed67a18d35e43d9a2062f8e121))
|
||||
* **runtime-core:** show hydration mismatch details for non-rectified mismatches too when __PROD_HYDRATION_MISMATCH_DETAILS__ is set ([#10599](https://github.com/vuejs/core/issues/10599)) ([0dea7f9](https://github.com/vuejs/core/commit/0dea7f9a260d93eb6c39aabac8c94c2c9b2042dd))
|
||||
* **runtime-dom:** `v-model` string/number coercion for multiselect options ([#10576](https://github.com/vuejs/core/issues/10576)) ([db374e5](https://github.com/vuejs/core/commit/db374e54c9f5e07324728b85c74eca84e28dd352))
|
||||
* **runtime-dom:** fix css v-bind for suspensed components ([#8523](https://github.com/vuejs/core/issues/8523)) ([67722ba](https://github.com/vuejs/core/commit/67722ba23b7c36ab8f3fa2d2b4df08e4ddc322e1)), closes [#8520](https://github.com/vuejs/core/issues/8520)
|
||||
* **runtime-dom:** force update v-model number with leading 0 ([#10506](https://github.com/vuejs/core/issues/10506)) ([15ffe8f](https://github.com/vuejs/core/commit/15ffe8f2c954359770c57e4d9e589b0b622e4a60)), closes [#10503](https://github.com/vuejs/core/issues/10503) [#10615](https://github.com/vuejs/core/issues/10615)
|
||||
* **runtime-dom:** sanitize wrongly passed string value as event handler ([#8953](https://github.com/vuejs/core/issues/8953)) ([7ccd453](https://github.com/vuejs/core/commit/7ccd453dd004076cad49ec9f56cd5fe97b7b6ed8)), closes [#8818](https://github.com/vuejs/core/issues/8818)
|
||||
* **ssr:** don't render v-if comments in TransitionGroup ([#6732](https://github.com/vuejs/core/issues/6732)) ([5a96267](https://github.com/vuejs/core/commit/5a9626708e970c6fc0b6f786e3c80c22273d126f)), closes [#6715](https://github.com/vuejs/core/issues/6715)
|
||||
* **Transition:** ensure the KeepAlive children unmount w/ out-in mode ([#10632](https://github.com/vuejs/core/issues/10632)) ([fc99e4d](https://github.com/vuejs/core/commit/fc99e4d3f01b190ef9fd3c218a668ba9124a32bc)), closes [#10620](https://github.com/vuejs/core/issues/10620)
|
||||
* **TransitionGroup:** avoid set transition hooks for comment nodes and text nodes ([#9421](https://github.com/vuejs/core/issues/9421)) ([140a768](https://github.com/vuejs/core/commit/140a7681cc3bba22f55d97fd85a5eafe97a1230f)), closes [#4621](https://github.com/vuejs/core/issues/4621) [#4622](https://github.com/vuejs/core/issues/4622) [#5153](https://github.com/vuejs/core/issues/5153) [#5168](https://github.com/vuejs/core/issues/5168) [#7898](https://github.com/vuejs/core/issues/7898) [#9067](https://github.com/vuejs/core/issues/9067)
|
||||
* **types:** avoid merging object union types when using withDefaults ([#10596](https://github.com/vuejs/core/issues/10596)) ([37ba93c](https://github.com/vuejs/core/commit/37ba93c213a81f99a68a99ef5d4065d61b150ba3)), closes [#10594](https://github.com/vuejs/core/issues/10594)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* add `__NO_SIDE_EFFECTS__` comments ([#9053](https://github.com/vuejs/core/issues/9053)) ([d46df6b](https://github.com/vuejs/core/commit/d46df6bdb14b0509eb2134b3f85297a306821c61))
|
||||
* optimize component props/slots internal object checks ([6af733d](https://github.com/vuejs/core/commit/6af733d68eb400a3d2c5ef5f465fff32b72a324e))
|
||||
* **ssr:** avoid calling markRaw on component instance proxy ([4bc9f39](https://github.com/vuejs/core/commit/4bc9f39f028af7313e5cf24c16915a1985d27bf8))
|
||||
* **ssr:** optimize setup context creation for ssr in v8 ([ca84316](https://github.com/vuejs/core/commit/ca84316bfb3410efe21333670a6ad5cd21857396))
|
||||
|
||||
|
||||
|
||||
## [3.4.21](https://github.com/vuejs/core/compare/v3.4.20...v3.4.21) (2024-02-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **runtime-dom:** avoid unset option's value ([#10416](https://github.com/vuejs/core/issues/10416)) ([b3f8b5a](https://github.com/vuejs/core/commit/b3f8b5a4e700d4c47a146b6040882287d180f6cb)), closes [#10412](https://github.com/vuejs/core/issues/10412) [#10396](https://github.com/vuejs/core/issues/10396)
|
||||
* **suspense:** ensure nested suspense patching if in fallback state ([#10417](https://github.com/vuejs/core/issues/10417)) ([7c97778](https://github.com/vuejs/core/commit/7c97778aec1e3513035e5df265e1b8a7801f6106)), closes [#10415](https://github.com/vuejs/core/issues/10415)
|
||||
* **warning:** stringify args in warn handler ([#10414](https://github.com/vuejs/core/issues/10414)) ([bc37258](https://github.com/vuejs/core/commit/bc37258caa2f6f67f4554ab8587aca3798d92124)), closes [#10409](https://github.com/vuejs/core/issues/10409)
|
||||
|
||||
|
||||
|
||||
## [3.4.20](https://github.com/vuejs/core/compare/v3.4.19...v3.4.20) (2024-02-26)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"drips": {
|
||||
"ethereum": {
|
||||
"ownedBy": "0x5393BdeA2a020769256d9f337B0fc81a2F64850A"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
# vuejs/core [](https://www.npmjs.com/package/vue) [](https://github.com/vuejs/core/actions/workflows/ci.yml)
|
||||
# vuejs/core [](https://www.npmjs.com/package/vue) [](https://github.com/vuejs/core/actions/workflows/ci.yml) [](https://www.npmjs.com/package/vue)
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
|
44
package.json
44
package.json
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.4.20",
|
||||
"packageManager": "pnpm@8.15.4",
|
||||
"version": "3.4.22",
|
||||
"packageManager": "pnpm@8.15.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
"build": "node scripts/build.js",
|
||||
"build-dts": "tsc -p tsconfig.build.json && rollup -c rollup.dts.config.js",
|
||||
"build-dts": "tsc -p tsconfig.build-browser.json && tsc -p tsconfig.build-node.json && rollup -c rollup.dts.config.js",
|
||||
"clean": "rimraf packages/*/dist temp .eslintcache",
|
||||
"size": "run-s \"size-*\" && tsx scripts/usage-size.ts",
|
||||
"size-global": "node scripts/build.js vue runtime-dom -f global -p --size",
|
||||
|
@ -20,7 +20,7 @@
|
|||
"test-unit": "vitest -c vitest.unit.config.ts",
|
||||
"test-e2e": "node scripts/build.js vue -f global -d && vitest -c vitest.e2e.config.ts",
|
||||
"test-dts": "run-s build-dts test-dts-only",
|
||||
"test-dts-only": "tsc -p ./packages/dts-test/tsconfig.test.json",
|
||||
"test-dts-only": "tsc -p packages/dts-built-test/tsconfig.json && tsc -p ./packages/dts-test/tsconfig.test.json",
|
||||
"test-coverage": "vitest -c vitest.unit.config.ts --coverage",
|
||||
"test-bench": "vitest bench",
|
||||
"release": "node scripts/release.js",
|
||||
|
@ -59,9 +59,9 @@
|
|||
"node": ">=18.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.23.9",
|
||||
"@babel/types": "^7.23.9",
|
||||
"@codspeed/vitest-plugin": "^2.3.1",
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@babel/types": "^7.24.0",
|
||||
"@codspeed/vitest-plugin": "^3.1.0",
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
|
@ -70,15 +70,15 @@
|
|||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^20.11.20",
|
||||
"@types/node": "^20.12.5",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.2",
|
||||
"@typescript-eslint/parser": "^7.0.2",
|
||||
"@vitest/coverage-istanbul": "^1.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.4.0",
|
||||
"@vitest/coverage-istanbul": "^1.4.0",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.20.1",
|
||||
"esbuild": "^0.20.2",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-define-config": "^2.1.0",
|
||||
|
@ -89,30 +89,30 @@
|
|||
"jsdom": "^24.0.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.7",
|
||||
"magic-string": "^0.30.8",
|
||||
"markdown-table": "^3.0.3",
|
||||
"marked": "^12.0.0",
|
||||
"marked": "^12.0.1",
|
||||
"minimist": "^1.2.8",
|
||||
"npm-run-all2": "^6.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"prettier": "^3.2.5",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.2",
|
||||
"puppeteer": "~22.2.0",
|
||||
"puppeteer": "~22.6.3",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^4.12.0",
|
||||
"rollup": "^4.13.2",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.6.0",
|
||||
"serve": "^14.2.1",
|
||||
"simple-git-hooks": "^2.9.0",
|
||||
"terser": "^5.28.1",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"terser": "^5.30.1",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.6.2",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.1.4",
|
||||
"vitest": "^1.3.1"
|
||||
"tsx": "^4.7.2",
|
||||
"typescript": "~5.4.5",
|
||||
"vite": "^5.2.7",
|
||||
"vitest": "^1.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2070,6 +2070,16 @@ describe('compiler: parse', () => {
|
|||
baseParse(`<Foo>`, { parseMode: 'sfc', onError() {} })
|
||||
expect(() => baseParse(`{ foo }`)).not.toThrow()
|
||||
})
|
||||
|
||||
test('correct loc when the closing > is foarmatted', () => {
|
||||
const [span] = baseParse(`<span></span
|
||||
|
||||
>`).children
|
||||
|
||||
expect(span.loc.source).toBe('<span></span\n \n >')
|
||||
expect(span.loc.start.offset).toBe(0)
|
||||
expect(span.loc.end.offset).toBe(27)
|
||||
})
|
||||
})
|
||||
|
||||
describe('decodeEntities option', () => {
|
||||
|
@ -2166,7 +2176,7 @@ describe('compiler: parse', () => {
|
|||
})
|
||||
|
||||
test('should remove leading newline character immediately following the pre element start tag', () => {
|
||||
const ast = baseParse(`<pre>\n foo bar </pre>`, {
|
||||
const ast = parse(`<pre>\n foo bar </pre>`, {
|
||||
isPreTag: tag => tag === 'pre',
|
||||
})
|
||||
expect(ast.children).toHaveLength(1)
|
||||
|
@ -2176,7 +2186,7 @@ describe('compiler: parse', () => {
|
|||
})
|
||||
|
||||
test('should NOT remove leading newline character immediately following child-tag of pre element', () => {
|
||||
const ast = baseParse(`<pre><span></span>\n foo bar </pre>`, {
|
||||
const ast = parse(`<pre><span></span>\n foo bar </pre>`, {
|
||||
isPreTag: tag => tag === 'pre',
|
||||
})
|
||||
const preElement = ast.children[0] as ElementNode
|
||||
|
@ -2187,7 +2197,7 @@ describe('compiler: parse', () => {
|
|||
})
|
||||
|
||||
test('self-closing pre tag', () => {
|
||||
const ast = baseParse(`<pre/><span>\n foo bar</span>`, {
|
||||
const ast = parse(`<pre/><span>\n foo bar</span>`, {
|
||||
isPreTag: tag => tag === 'pre',
|
||||
})
|
||||
const elementAfterPre = ast.children[1] as ElementNode
|
||||
|
@ -2196,7 +2206,7 @@ describe('compiler: parse', () => {
|
|||
})
|
||||
|
||||
test('should NOT condense whitespaces in RCDATA text mode', () => {
|
||||
const ast = baseParse(`<textarea>Text:\n foo</textarea>`, {
|
||||
const ast = parse(`<textarea>Text:\n foo</textarea>`, {
|
||||
parseMode: 'html',
|
||||
})
|
||||
const preElement = ast.children[0] as ElementNode
|
||||
|
|
|
@ -1231,6 +1231,24 @@ describe('compiler: element transform', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('dynamic binding shorthand', () => {
|
||||
const { node, root } = parseWithBind(`<component :is />`)
|
||||
expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
|
||||
expect(node).toMatchObject({
|
||||
isBlock: true,
|
||||
tag: {
|
||||
callee: RESOLVE_DYNAMIC_COMPONENT,
|
||||
arguments: [
|
||||
{
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'is',
|
||||
isStatic: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('is casting', () => {
|
||||
const { node, root } = parseWithBind(`<div is="vue:foo" />`)
|
||||
expect(root.helpers).toContain(RESOLVE_COMPONENT)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.4.20",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
@ -46,13 +46,13 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.9",
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@vue/shared": "workspace:*",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.0.2"
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.23.9"
|
||||
"@babel/types": "^7.24.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
getVNodeHelper,
|
||||
locStub,
|
||||
} from './ast'
|
||||
import { type RawSourceMap, SourceMapGenerator } from 'source-map-js'
|
||||
import { SourceMapGenerator } from 'source-map-js'
|
||||
import {
|
||||
advancePositionWithMutation,
|
||||
assert,
|
||||
|
@ -56,6 +56,45 @@ import {
|
|||
} from './runtimeHelpers'
|
||||
import type { ImportItem } from './transform'
|
||||
|
||||
/**
|
||||
* The `SourceMapGenerator` type from `source-map-js` is a bit incomplete as it
|
||||
* misses `toJSON()`. We also need to add types for internal properties which we
|
||||
* need to access for better performance.
|
||||
*
|
||||
* Since TS 5.3, dts generation starts to strangely include broken triple slash
|
||||
* references for source-map-js, so we are inlining all source map related types
|
||||
* here to to workaround that.
|
||||
*/
|
||||
export interface CodegenSourceMapGenerator {
|
||||
setSourceContent(sourceFile: string, sourceContent: string): void
|
||||
// SourceMapGenerator has this method but the types do not include it
|
||||
toJSON(): RawSourceMap
|
||||
_sources: Set<string>
|
||||
_names: Set<string>
|
||||
_mappings: {
|
||||
add(mapping: MappingItem): void
|
||||
}
|
||||
}
|
||||
|
||||
export interface RawSourceMap {
|
||||
file?: string
|
||||
sourceRoot?: string
|
||||
version: string
|
||||
sources: string[]
|
||||
names: string[]
|
||||
sourcesContent?: string[]
|
||||
mappings: string
|
||||
}
|
||||
|
||||
interface MappingItem {
|
||||
source: string
|
||||
generatedLine: number
|
||||
generatedColumn: number
|
||||
originalLine: number
|
||||
originalColumn: number
|
||||
name: string | null
|
||||
}
|
||||
|
||||
const PURE_ANNOTATION = `/*#__PURE__*/`
|
||||
|
||||
const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
|
||||
|
@ -85,7 +124,7 @@ export interface CodegenContext
|
|||
offset: number
|
||||
indentLevel: number
|
||||
pure: boolean
|
||||
map?: SourceMapGenerator
|
||||
map?: CodegenSourceMapGenerator
|
||||
helper(key: symbol): string
|
||||
push(code: string, newlineIndex?: number, node?: CodegenNode): void
|
||||
indent(): void
|
||||
|
@ -218,14 +257,14 @@ function createCodegenContext(
|
|||
generatedLine: context.line,
|
||||
generatedColumn: context.column - 1,
|
||||
source: filename,
|
||||
// @ts-expect-error it is possible to be null
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
if (!__BROWSER__ && sourceMap) {
|
||||
// lazy require source-map implementation, only in non-browser builds
|
||||
context.map = new SourceMapGenerator()
|
||||
context.map =
|
||||
new SourceMapGenerator() as unknown as CodegenSourceMapGenerator
|
||||
context.map.setSourceContent(filename, context.source)
|
||||
context.map._sources.add(filename)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,13 @@ export {
|
|||
type StructuralDirectiveTransform,
|
||||
type DirectiveTransform,
|
||||
} from './transform'
|
||||
export { generate, type CodegenContext, type CodegenResult } from './codegen'
|
||||
export {
|
||||
generate,
|
||||
type CodegenContext,
|
||||
type CodegenResult,
|
||||
type CodegenSourceMapGenerator,
|
||||
type RawSourceMap,
|
||||
} from './codegen'
|
||||
export {
|
||||
ErrorCodes,
|
||||
errorMessages,
|
||||
|
|
|
@ -74,6 +74,7 @@ export interface ParserOptions
|
|||
delimiters?: [string, string]
|
||||
/**
|
||||
* Whitespace handling strategy
|
||||
* @default 'condense'
|
||||
*/
|
||||
whitespace?: 'preserve' | 'condense'
|
||||
/**
|
||||
|
|
|
@ -613,7 +613,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
|
|||
// implied close, end should be backtracked to close
|
||||
setLocEnd(el.loc, backTrack(end, CharCodes.Lt))
|
||||
} else {
|
||||
setLocEnd(el.loc, end + 1)
|
||||
setLocEnd(el.loc, lookAhead(end, CharCodes.Gt) + 1)
|
||||
}
|
||||
|
||||
if (tokenizer.inSFCRoot) {
|
||||
|
@ -736,6 +736,12 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
|
|||
}
|
||||
}
|
||||
|
||||
function lookAhead(index: number, c: number) {
|
||||
let i = index
|
||||
while (currentInput.charCodeAt(i) !== c && i < currentInput.length - 1) i++
|
||||
return i
|
||||
}
|
||||
|
||||
function backTrack(index: number, c: number) {
|
||||
let i = index
|
||||
while (currentInput.charCodeAt(i) !== c && i >= 0) i--
|
||||
|
|
|
@ -64,6 +64,7 @@ import {
|
|||
checkCompatEnabled,
|
||||
isCompatEnabled,
|
||||
} from '../compat/compatConfig'
|
||||
import { processExpression } from './transformExpression'
|
||||
|
||||
// some directive transforms (e.g. v-model) may return a symbol for runtime
|
||||
// import, which should be used instead of a resolveDirective call.
|
||||
|
@ -253,7 +254,7 @@ export function resolveComponentType(
|
|||
|
||||
// 1. dynamic component
|
||||
const isExplicitDynamic = isComponentTag(tag)
|
||||
const isProp = findProp(node, 'is')
|
||||
const isProp = findProp(node, 'is', false, true /* allow empty */)
|
||||
if (isProp) {
|
||||
if (
|
||||
isExplicitDynamic ||
|
||||
|
@ -263,10 +264,19 @@ export function resolveComponentType(
|
|||
context,
|
||||
))
|
||||
) {
|
||||
const exp =
|
||||
isProp.type === NodeTypes.ATTRIBUTE
|
||||
? isProp.value && createSimpleExpression(isProp.value.content, true)
|
||||
: isProp.exp
|
||||
let exp: ExpressionNode | undefined
|
||||
if (isProp.type === NodeTypes.ATTRIBUTE) {
|
||||
exp = isProp.value && createSimpleExpression(isProp.value.content, true)
|
||||
} else {
|
||||
exp = isProp.exp
|
||||
if (!exp) {
|
||||
// #10469 handle :is shorthand
|
||||
exp = createSimpleExpression(`is`, false, isProp.loc)
|
||||
if (!__BROWSER__) {
|
||||
exp = isProp.exp = processExpression(exp, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exp) {
|
||||
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
|
||||
exp,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.4.20",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
|
|
@ -1362,3 +1362,24 @@ return { get foo() { return foo } }
|
|||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compileScript > should care about runtimeModuleName 1`] = `
|
||||
"import { withAsyncContext as _withAsyncContext } from "npm:vue"
|
||||
|
||||
export default {
|
||||
async setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
let __temp, __restore
|
||||
|
||||
;(
|
||||
([__temp,__restore] = _withAsyncContext(() => Promise.resolve(1))),
|
||||
await __temp,
|
||||
__restore()
|
||||
)
|
||||
|
||||
return { }
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -1472,3 +1472,26 @@ describe('SFC genDefaultAs', () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('compileScript', () => {
|
||||
test('should care about runtimeModuleName', () => {
|
||||
const { content } = compile(
|
||||
`
|
||||
<script setup>
|
||||
await Promise.resolve(1)
|
||||
</script>
|
||||
`,
|
||||
{
|
||||
templateOptions: {
|
||||
compilerOptions: {
|
||||
runtimeModuleName: 'npm:vue',
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
expect(content).toMatch(
|
||||
`import { withAsyncContext as _withAsyncContext } from "npm:vue"\n`,
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -103,6 +103,26 @@ return { modelValue }
|
|||
})"
|
||||
`;
|
||||
|
||||
exports[`defineModel() > w/ Boolean And Function types, production mode 1`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": { type: [Boolean, String] },
|
||||
"modelModifiers": {},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const modelValue = _useModel<boolean | string>(__props, "modelValue")
|
||||
|
||||
return { modelValue }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineModel() > w/ array props 1`] = `
|
||||
"import { useModel as _useModel, mergeModels as _mergeModels } from 'vue'
|
||||
|
||||
|
|
|
@ -66,29 +66,14 @@ return { get vMyDir() { return vMyDir } }
|
|||
|
||||
exports[`dynamic arguments 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { FooBar, foo, bar, unused, baz } from './x'
|
||||
import { FooBar, foo, bar, unused, baz, msg } from './x'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
||||
return { get FooBar() { return FooBar }, get foo() { return foo }, get bar() { return bar }, get baz() { return baz } }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`import namespace 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import * as Foo from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
||||
return { get Foo() { return Foo } }
|
||||
return { get FooBar() { return FooBar }, get foo() { return foo }, get bar() { return bar }, get baz() { return baz }, get msg() { return msg } }
|
||||
}
|
||||
|
||||
})"
|
||||
|
|
|
@ -221,4 +221,24 @@ describe('defineModel()', () => {
|
|||
assertCode(content)
|
||||
expect(content).toMatch(`set: (v) => { return v + __props.x }`)
|
||||
})
|
||||
|
||||
test('w/ Boolean And Function types, production mode', () => {
|
||||
const { content, bindings } = compile(
|
||||
`
|
||||
<script setup lang="ts">
|
||||
const modelValue = defineModel<boolean | string>()
|
||||
</script>
|
||||
`,
|
||||
{ isProd: true },
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
|
||||
expect(content).toMatch('emits: ["update:modelValue"]')
|
||||
expect(content).toMatch(
|
||||
`const modelValue = _useModel<boolean | string>(__props, "modelValue")`,
|
||||
)
|
||||
expect(bindings).toStrictEqual({
|
||||
modelValue: BindingTypes.SETUP_REF,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -45,7 +45,7 @@ test('directive', () => {
|
|||
test('dynamic arguments', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
import { FooBar, foo, bar, unused, baz } from './x'
|
||||
import { FooBar, foo, bar, unused, baz, msg } from './x'
|
||||
</script>
|
||||
<template>
|
||||
<FooBar #[foo.slotName] />
|
||||
|
@ -53,11 +53,12 @@ test('dynamic arguments', () => {
|
|||
<div :[bar.attrName]="15"></div>
|
||||
<div unused="unused"></div>
|
||||
<div #[\`item:\${baz.key}\`]="{ value }"></div>
|
||||
<FooBar :msg />
|
||||
</template>
|
||||
`)
|
||||
expect(content).toMatch(
|
||||
`return { get FooBar() { return FooBar }, get foo() { return foo }, ` +
|
||||
`get bar() { return bar }, get baz() { return baz } }`,
|
||||
`get bar() { return bar }, get baz() { return baz }, get msg() { return msg } }`,
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
|
|
|
@ -561,6 +561,27 @@ describe('resolveType', () => {
|
|||
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
|
||||
})
|
||||
|
||||
// #10635
|
||||
test('relative tsx', () => {
|
||||
const files = {
|
||||
'/foo.tsx': 'export type P = { foo: number }',
|
||||
'/bar/index.tsx': 'export type PP = { bar: string }',
|
||||
}
|
||||
const { props, deps } = resolve(
|
||||
`
|
||||
import { P } from './foo'
|
||||
import { PP } from './bar'
|
||||
defineProps<P & PP>()
|
||||
`,
|
||||
files,
|
||||
)
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['Number'],
|
||||
bar: ['String'],
|
||||
})
|
||||
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
|
||||
})
|
||||
|
||||
test.runIf(process.platform === 'win32')('relative ts on Windows', () => {
|
||||
const files = {
|
||||
'C:\\Test\\FolderA\\foo.ts': 'export type P = { foo: number }',
|
||||
|
|
|
@ -161,6 +161,45 @@ describe('SFC scoped CSS', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
// #10511
|
||||
test(':is() and :where() in compound selectors', () => {
|
||||
expect(
|
||||
compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
|
||||
).toMatchInlineSnapshot(`
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:where(:hover) { color: blue;
|
||||
}"`)
|
||||
|
||||
expect(
|
||||
compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
|
||||
).toMatchInlineSnapshot(`
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:is(:hover) { color: blue;
|
||||
}"`)
|
||||
|
||||
expect(
|
||||
compileScoped(
|
||||
`.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:where(.foo:hover) { color: blue;
|
||||
}"`)
|
||||
|
||||
expect(
|
||||
compileScoped(
|
||||
`.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:is(.foo:hover) { color: blue;
|
||||
}"`)
|
||||
})
|
||||
|
||||
test('media query', () => {
|
||||
expect(compileScoped(`@media print { .foo { color: red }}`))
|
||||
.toMatchInlineSnapshot(`
|
||||
|
@ -390,4 +429,23 @@ describe('SFC style preprocessors', () => {
|
|||
|
||||
expect(res.errors.length).toBe(0)
|
||||
})
|
||||
|
||||
test('should mount scope on correct selector when have universal selector', () => {
|
||||
expect(compileScoped(`* { color: red; }`)).toMatchInlineSnapshot(`
|
||||
"[data-v-test] { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compileScoped('* .foo { color: red; }')).toMatchInlineSnapshot(`
|
||||
".foo[data-v-test] { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compileScoped(`*.foo { color: red; }`)).toMatchInlineSnapshot(`
|
||||
".foo[data-v-test] { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compileScoped(`.foo * { color: red; }`)).toMatchInlineSnapshot(`
|
||||
".foo[data-v-test] * { color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.4.20",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
@ -42,26 +42,26 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.9",
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@vue/compiler-core": "workspace:*",
|
||||
"@vue/compiler-dom": "workspace:*",
|
||||
"@vue/compiler-ssr": "workspace:*",
|
||||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.7",
|
||||
"postcss": "^8.4.35",
|
||||
"source-map-js": "^1.0.2"
|
||||
"magic-string": "^0.30.8",
|
||||
"postcss": "^8.4.38",
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.23.9",
|
||||
"@babel/types": "^7.24.0",
|
||||
"@vue/consolidate": "^1.0.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "10.1.0",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "^9.0.3",
|
||||
"minimatch": "^9.0.4",
|
||||
"postcss-modules": "^6.0.0",
|
||||
"postcss-selector-parser": "^6.0.15",
|
||||
"postcss-selector-parser": "^6.0.16",
|
||||
"pug": "^3.0.2",
|
||||
"sass": "^1.71.1"
|
||||
"sass": "^1.74.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -989,10 +989,15 @@ export function compileScript(
|
|||
|
||||
// 11. finalize Vue helper imports
|
||||
if (ctx.helperImports.size > 0) {
|
||||
const runtimeModuleName =
|
||||
options.templateOptions?.compilerOptions?.runtimeModuleName
|
||||
const importSrc = runtimeModuleName
|
||||
? JSON.stringify(runtimeModuleName)
|
||||
: `'vue'`
|
||||
ctx.s.prepend(
|
||||
`import { ${[...ctx.helperImports]
|
||||
.map(h => `${h} as _${h}`)
|
||||
.join(', ')} } from 'vue'\n`,
|
||||
.join(', ')} } from ${importSrc}\n`,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
type StylePreprocessorResults,
|
||||
processors,
|
||||
} from './style/preprocessors'
|
||||
import type { RawSourceMap } from 'source-map-js'
|
||||
import type { RawSourceMap } from '@vue/compiler-core'
|
||||
import { cssVarsPlugin } from './style/cssVars'
|
||||
import postcssModules from 'postcss-modules'
|
||||
|
||||
|
|
|
@ -6,14 +6,11 @@ import {
|
|||
type NodeTransform,
|
||||
NodeTypes,
|
||||
type ParserOptions,
|
||||
type RawSourceMap,
|
||||
type RootNode,
|
||||
createRoot,
|
||||
} from '@vue/compiler-core'
|
||||
import {
|
||||
type RawSourceMap,
|
||||
SourceMapConsumer,
|
||||
SourceMapGenerator,
|
||||
} from 'source-map-js'
|
||||
import { SourceMapConsumer, SourceMapGenerator } from 'source-map-js'
|
||||
import {
|
||||
type AssetURLOptions,
|
||||
type AssetURLTagConfig,
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import {
|
||||
type BindingMetadata,
|
||||
type CodegenSourceMapGenerator,
|
||||
type CompilerError,
|
||||
type ElementNode,
|
||||
NodeTypes,
|
||||
type ParserOptions,
|
||||
type RawSourceMap,
|
||||
type RootNode,
|
||||
type SourceLocation,
|
||||
createRoot,
|
||||
} from '@vue/compiler-core'
|
||||
import * as CompilerDOM from '@vue/compiler-dom'
|
||||
import { type RawSourceMap, SourceMapGenerator } from 'source-map-js'
|
||||
import { SourceMapGenerator } from 'source-map-js'
|
||||
import type { TemplateCompiler } from './compileTemplate'
|
||||
import { parseCssVars } from './style/cssVars'
|
||||
import { createCache } from './cache'
|
||||
|
@ -375,7 +377,7 @@ function generateSourceMap(
|
|||
const map = new SourceMapGenerator({
|
||||
file: filename.replace(/\\/g, '/'),
|
||||
sourceRoot: sourceRoot.replace(/\\/g, '/'),
|
||||
})
|
||||
}) as unknown as CodegenSourceMapGenerator
|
||||
map.setSourceContent(filename, source)
|
||||
map._sources.add(filename)
|
||||
generated.split(splitRE).forEach((line, index) => {
|
||||
|
@ -390,7 +392,6 @@ function generateSourceMap(
|
|||
generatedLine,
|
||||
generatedColumn: i,
|
||||
source: filename,
|
||||
// @ts-expect-error
|
||||
name: null,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -129,15 +129,19 @@ export function genModelProps(ctx: ScriptCompileContext) {
|
|||
|
||||
let runtimeTypes = type && inferRuntimeType(ctx, type)
|
||||
if (runtimeTypes) {
|
||||
const hasBoolean = runtimeTypes.includes('Boolean')
|
||||
const hasUnknownType = runtimeTypes.includes(UNKNOWN_TYPE)
|
||||
|
||||
runtimeTypes = runtimeTypes.filter(el => {
|
||||
if (el === UNKNOWN_TYPE) return false
|
||||
return isProd
|
||||
? el === 'Boolean' || (el === 'Function' && options)
|
||||
: true
|
||||
})
|
||||
skipCheck = !isProd && hasUnknownType && runtimeTypes.length > 0
|
||||
if (isProd || hasUnknownType) {
|
||||
runtimeTypes = runtimeTypes.filter(
|
||||
t =>
|
||||
t === 'Boolean' ||
|
||||
(hasBoolean && t === 'String') ||
|
||||
(t === 'Function' && options),
|
||||
)
|
||||
|
||||
skipCheck = !isProd && hasUnknownType && runtimeTypes.length > 0
|
||||
}
|
||||
}
|
||||
|
||||
let runtimeType =
|
||||
|
|
|
@ -60,6 +60,9 @@ function resolveTemplateUsedIdentifiers(sfc: SFCDescriptor): Set<string> {
|
|||
extractIdentifiers(ids, prop.forParseResult!.source)
|
||||
} else if (prop.exp) {
|
||||
extractIdentifiers(ids, prop.exp)
|
||||
} else if (prop.name === 'bind' && !prop.exp) {
|
||||
// v-bind shorthand name as identifier
|
||||
ids.add((prop.arg as SimpleExpressionNode).content)
|
||||
}
|
||||
}
|
||||
if (
|
||||
|
|
|
@ -956,8 +956,10 @@ function resolveExt(filename: string, fs: FS) {
|
|||
return (
|
||||
tryResolve(filename) ||
|
||||
tryResolve(filename + `.ts`) ||
|
||||
tryResolve(filename + `.tsx`) ||
|
||||
tryResolve(filename + `.d.ts`) ||
|
||||
tryResolve(joinPaths(filename, `index.ts`)) ||
|
||||
tryResolve(joinPaths(filename, `index.tsx`)) ||
|
||||
tryResolve(joinPaths(filename, `index.d.ts`))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -170,9 +170,37 @@ function rewriteSelector(
|
|||
}
|
||||
}
|
||||
|
||||
if (n.type === 'universal') {
|
||||
const prev = selector.at(selector.index(n) - 1)
|
||||
const next = selector.at(selector.index(n) + 1)
|
||||
// * ... {}
|
||||
if (!prev) {
|
||||
// * .foo {} -> .foo[xxxxxxx] {}
|
||||
if (next) {
|
||||
if (next.type === 'combinator' && next.value === ' ') {
|
||||
selector.removeChild(next)
|
||||
}
|
||||
selector.removeChild(n)
|
||||
return
|
||||
} else {
|
||||
// * {} -> [xxxxxxx] {}
|
||||
node = selectorParser.combinator({
|
||||
value: '',
|
||||
})
|
||||
selector.insertBefore(n, node)
|
||||
selector.removeChild(n)
|
||||
return false
|
||||
}
|
||||
}
|
||||
// .foo * -> .foo[xxxxxxx] *
|
||||
if (node) return
|
||||
}
|
||||
|
||||
if (
|
||||
(n.type !== 'pseudo' && n.type !== 'combinator') ||
|
||||
(n.type === 'pseudo' && (n.value === ':is' || n.value === ':where'))
|
||||
(n.type === 'pseudo' &&
|
||||
(n.value === ':is' || n.value === ':where') &&
|
||||
!node)
|
||||
) {
|
||||
node = n
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import merge from 'merge-source-map'
|
||||
import type { RawSourceMap } from 'source-map-js'
|
||||
import type { RawSourceMap } from '@vue/compiler-core'
|
||||
import type { SFCStyleCompileOptions } from '../compileStyle'
|
||||
import { isFunction } from '@vue/shared'
|
||||
|
||||
|
|
|
@ -82,8 +82,6 @@ describe('transition-group', () => {
|
|||
})
|
||||
if (_ctx.ok) {
|
||||
_push(\`<div>ok</div>\`)
|
||||
} else {
|
||||
_push(\`<!---->\`)
|
||||
}
|
||||
_push(\`<!--]-->\`)
|
||||
}"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.4.20",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
@ -141,6 +141,7 @@ export function processChildren(
|
|||
context: SSRTransformContext,
|
||||
asFragment = false,
|
||||
disableNestedFragments = false,
|
||||
disableCommentAsIfAlternate = false,
|
||||
) {
|
||||
if (asFragment) {
|
||||
context.pushStringPart(`<!--[-->`)
|
||||
|
@ -191,7 +192,12 @@ export function processChildren(
|
|||
)
|
||||
break
|
||||
case NodeTypes.IF:
|
||||
ssrProcessIf(child, context, disableNestedFragments)
|
||||
ssrProcessIf(
|
||||
child,
|
||||
context,
|
||||
disableNestedFragments,
|
||||
disableCommentAsIfAlternate,
|
||||
)
|
||||
break
|
||||
case NodeTypes.FOR:
|
||||
ssrProcessFor(child, context, disableNestedFragments)
|
||||
|
|
|
@ -87,6 +87,13 @@ export function ssrProcessTransitionGroup(
|
|||
* by disabling nested fragment wrappers from being generated.
|
||||
*/
|
||||
true,
|
||||
/**
|
||||
* TransitionGroup filters out comment children at runtime and thus
|
||||
* doesn't expect comments to be present during hydration. We need to
|
||||
* account for that by disabling the empty comment that is otherwise
|
||||
* rendered for a falsy v-if that has no v-else specified. (#6715)
|
||||
*/
|
||||
true,
|
||||
)
|
||||
context.pushStringPart(`</`)
|
||||
context.pushStringPart(tag.exp!)
|
||||
|
@ -106,6 +113,6 @@ export function ssrProcessTransitionGroup(
|
|||
}
|
||||
} else {
|
||||
// fragment
|
||||
processChildren(node, context, true, true)
|
||||
processChildren(node, context, true, true, true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ export function ssrProcessIf(
|
|||
node: IfNode,
|
||||
context: SSRTransformContext,
|
||||
disableNestedFragments = false,
|
||||
disableCommentAsIfAlternate = false,
|
||||
) {
|
||||
const [rootBranch] = node.branches
|
||||
const ifStatement = createIfStatement(
|
||||
|
@ -54,7 +55,7 @@ export function ssrProcessIf(
|
|||
}
|
||||
}
|
||||
|
||||
if (!currentIf.alternate) {
|
||||
if (!currentIf.alternate && !disableCommentAsIfAlternate) {
|
||||
currentIf.alternate = createBlockStatement([
|
||||
createCallExpression(`_push`, ['`<!---->`']),
|
||||
])
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "@vue/dts-built-test",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"types": "dist/dts-built-test.d.ts",
|
||||
"types": "dist/index.d.ts",
|
||||
"dependencies": {
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/reactivity": "workspace:*",
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
"jsx": "preserve",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["esnext", "dom"],
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
|
@ -102,6 +102,41 @@ describe('defineProps w/ union type declaration + withDefaults', () => {
|
|||
)
|
||||
})
|
||||
|
||||
describe('defineProps w/ object union + withDefaults', () => {
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
{
|
||||
foo: string
|
||||
} & (
|
||||
| {
|
||||
type: 'hello'
|
||||
bar: string
|
||||
}
|
||||
| {
|
||||
type: 'world'
|
||||
bar: number
|
||||
}
|
||||
)
|
||||
>(),
|
||||
{
|
||||
foo: 'default value!',
|
||||
},
|
||||
)
|
||||
|
||||
expectType<
|
||||
| {
|
||||
readonly type: 'hello'
|
||||
readonly bar: string
|
||||
readonly foo: string
|
||||
}
|
||||
| {
|
||||
readonly type: 'world'
|
||||
readonly bar: number
|
||||
readonly foo: string
|
||||
}
|
||||
>(props)
|
||||
})
|
||||
|
||||
describe('defineProps w/ generic type declaration + withDefaults', <T extends
|
||||
number, TA extends {
|
||||
a: string
|
||||
|
|
|
@ -45,18 +45,6 @@ declare module 'estree-walker' {
|
|||
)
|
||||
}
|
||||
|
||||
declare module 'source-map-js' {
|
||||
export interface SourceMapGenerator {
|
||||
// SourceMapGenerator has this method but the types do not include it
|
||||
toJSON(): RawSourceMap
|
||||
_sources: Set<string>
|
||||
_names: Set<string>
|
||||
_mappings: {
|
||||
add(mapping: MappingItem): void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare interface String {
|
||||
/**
|
||||
* @deprecated Please use String.prototype.slice instead of String.prototype.substring in the repository.
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
EffectScope,
|
||||
computed,
|
||||
effect,
|
||||
effectScope,
|
||||
getCurrentScope,
|
||||
onScopeDispose,
|
||||
reactive,
|
||||
|
@ -13,21 +14,21 @@ import {
|
|||
describe('reactivity/effect/scope', () => {
|
||||
it('should run', () => {
|
||||
const fnSpy = vi.fn(() => {})
|
||||
new EffectScope().run(fnSpy)
|
||||
effectScope().run(fnSpy)
|
||||
expect(fnSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should accept zero argument', () => {
|
||||
const scope = new EffectScope()
|
||||
const scope = effectScope()
|
||||
expect(scope.effects.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should return run value', () => {
|
||||
expect(new EffectScope().run(() => 1)).toBe(1)
|
||||
expect(effectScope().run(() => 1)).toBe(1)
|
||||
})
|
||||
|
||||
it('should work w/ active property', () => {
|
||||
const scope = new EffectScope()
|
||||
const scope = effectScope()
|
||||
scope.run(() => 1)
|
||||
expect(scope.active).toBe(true)
|
||||
scope.stop()
|
||||
|
@ -35,7 +36,7 @@ describe('reactivity/effect/scope', () => {
|
|||
})
|
||||
|
||||
it('should collect the effects', () => {
|
||||
const scope = new EffectScope()
|
||||
const scope = effectScope()
|
||||
scope.run(() => {
|
||||
let dummy
|
||||
const counter = reactive({ num: 0 })
|
||||
|
@ -53,7 +54,7 @@ describe('reactivity/effect/scope', () => {
|
|||
let dummy, doubled
|
||||
const counter = reactive({ num: 0 })
|
||||
|
||||
const scope = new EffectScope()
|
||||
const scope = effectScope()
|
||||
scope.run(() => {
|
||||
effect(() => (dummy = counter.num))
|
||||
effect(() => (doubled = counter.num * 2))
|
||||
|
@ -77,11 +78,11 @@ describe('reactivity/effect/scope', () => {
|
|||
let dummy, doubled
|
||||
const counter = reactive({ num: 0 })
|
||||
|
||||
const scope = new EffectScope()
|
||||
const scope = effectScope()
|
||||
scope.run(() => {
|
||||
effect(() => (dummy = counter.num))
|
||||
// nested scope
|
||||
new EffectScope().run(() => {
|
||||
effectScope().run(() => {
|
||||
effect(() => (doubled = counter.num * 2))
|
||||
})
|
||||
})
|
||||
|
@ -107,11 +108,11 @@ describe('reactivity/effect/scope', () => {
|
|||
let dummy, doubled
|
||||
const counter = reactive({ num: 0 })
|
||||
|
||||
const scope = new EffectScope()
|
||||
const scope = effectScope()
|
||||
scope.run(() => {
|
||||
effect(() => (dummy = counter.num))
|
||||
// nested scope
|
||||
new EffectScope(true).run(() => {
|
||||
effectScope(true).run(() => {
|
||||
effect(() => (doubled = counter.num * 2))
|
||||
})
|
||||
})
|
||||
|
@ -136,7 +137,7 @@ describe('reactivity/effect/scope', () => {
|
|||
let dummy, doubled
|
||||
const counter = reactive({ num: 0 })
|
||||
|
||||
const scope = new EffectScope()
|
||||
const scope = effectScope()
|
||||
scope.run(() => {
|
||||
effect(() => (dummy = counter.num))
|
||||
})
|
||||
|
@ -160,7 +161,7 @@ describe('reactivity/effect/scope', () => {
|
|||
let dummy, doubled
|
||||
const counter = reactive({ num: 0 })
|
||||
|
||||
const scope = new EffectScope()
|
||||
const scope = effectScope()
|
||||
scope.run(() => {
|
||||
effect(() => (dummy = counter.num))
|
||||
})
|
||||
|
@ -185,7 +186,7 @@ describe('reactivity/effect/scope', () => {
|
|||
it('should fire onScopeDispose hook', () => {
|
||||
let dummy = 0
|
||||
|
||||
const scope = new EffectScope()
|
||||
const scope = effectScope()
|
||||
scope.run(() => {
|
||||
onScopeDispose(() => (dummy += 1))
|
||||
onScopeDispose(() => (dummy += 2))
|
||||
|
@ -203,7 +204,7 @@ describe('reactivity/effect/scope', () => {
|
|||
|
||||
it('should warn onScopeDispose() is called when there is no active effect scope', () => {
|
||||
const spy = vi.fn()
|
||||
const scope = new EffectScope()
|
||||
const scope = effectScope()
|
||||
scope.run(() => {
|
||||
onScopeDispose(spy)
|
||||
})
|
||||
|
@ -221,8 +222,8 @@ describe('reactivity/effect/scope', () => {
|
|||
})
|
||||
|
||||
it('should dereference child scope from parent scope after stopping child scope (no memleaks)', () => {
|
||||
const parent = new EffectScope()
|
||||
const child = parent.run(() => new EffectScope())!
|
||||
const parent = effectScope()
|
||||
const child = parent.run(() => effectScope())!
|
||||
expect(parent.scopes!.includes(child)).toBe(true)
|
||||
child.stop()
|
||||
expect(parent.scopes!.includes(child)).toBe(false)
|
||||
|
@ -236,7 +237,7 @@ describe('reactivity/effect/scope', () => {
|
|||
const watchEffectSpy = vi.fn()
|
||||
|
||||
let c: ComputedRef
|
||||
const scope = new EffectScope()
|
||||
const scope = effectScope()
|
||||
scope.run(() => {
|
||||
c = computed(() => {
|
||||
computedSpy()
|
||||
|
@ -272,12 +273,12 @@ describe('reactivity/effect/scope', () => {
|
|||
})
|
||||
|
||||
it('getCurrentScope() stays valid when running a detached nested EffectScope', () => {
|
||||
const parentScope = new EffectScope()
|
||||
const parentScope = effectScope()
|
||||
|
||||
parentScope.run(() => {
|
||||
const currentScope = getCurrentScope()
|
||||
expect(currentScope).toBeDefined()
|
||||
const detachedScope = new EffectScope(true)
|
||||
const detachedScope = effectScope(true)
|
||||
detachedScope.run(() => {})
|
||||
|
||||
expect(getCurrentScope()).toBe(currentScope)
|
||||
|
@ -285,10 +286,10 @@ describe('reactivity/effect/scope', () => {
|
|||
})
|
||||
|
||||
it('calling .off() of a detached scope inside an active scope should not break currentScope', () => {
|
||||
const parentScope = new EffectScope()
|
||||
const parentScope = effectScope()
|
||||
|
||||
parentScope.run(() => {
|
||||
const childScope = new EffectScope(true)
|
||||
const childScope = effectScope(true)
|
||||
childScope.on()
|
||||
childScope.off()
|
||||
expect(getCurrentScope()).toBe(parentScope)
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
import { isRef, ref } from '../src/ref'
|
||||
import { isReactive, markRaw, reactive, toRaw } from '../src/reactive'
|
||||
import {
|
||||
isProxy,
|
||||
isReactive,
|
||||
markRaw,
|
||||
reactive,
|
||||
readonly,
|
||||
shallowReactive,
|
||||
shallowReadonly,
|
||||
toRaw,
|
||||
} from '../src/reactive'
|
||||
import { computed } from '../src/computed'
|
||||
import { effect } from '../src/effect'
|
||||
|
||||
|
@ -302,4 +311,52 @@ describe('reactivity/reactive', () => {
|
|||
const observed = reactive(original)
|
||||
expect(isReactive(observed)).toBe(false)
|
||||
})
|
||||
|
||||
test('hasOwnProperty edge case: Symbol values', () => {
|
||||
const key = Symbol()
|
||||
const obj = reactive({ [key]: 1 }) as { [key]?: 1 }
|
||||
let dummy
|
||||
effect(() => {
|
||||
dummy = obj.hasOwnProperty(key)
|
||||
})
|
||||
expect(dummy).toBe(true)
|
||||
|
||||
delete obj[key]
|
||||
expect(dummy).toBe(false)
|
||||
})
|
||||
|
||||
test('hasOwnProperty edge case: non-string values', () => {
|
||||
const key = {}
|
||||
const obj = reactive({ '[object Object]': 1 }) as { '[object Object]'?: 1 }
|
||||
let dummy
|
||||
effect(() => {
|
||||
// @ts-expect-error
|
||||
dummy = obj.hasOwnProperty(key)
|
||||
})
|
||||
expect(dummy).toBe(true)
|
||||
|
||||
// @ts-expect-error
|
||||
delete obj[key]
|
||||
expect(dummy).toBe(false)
|
||||
})
|
||||
|
||||
test('isProxy', () => {
|
||||
const foo = {}
|
||||
expect(isProxy(foo)).toBe(false)
|
||||
|
||||
const fooRe = reactive(foo)
|
||||
expect(isProxy(fooRe)).toBe(true)
|
||||
|
||||
const fooSRe = shallowReactive(foo)
|
||||
expect(isProxy(fooSRe)).toBe(true)
|
||||
|
||||
const barRl = readonly(foo)
|
||||
expect(isProxy(barRl)).toBe(true)
|
||||
|
||||
const barSRl = shallowReadonly(foo)
|
||||
expect(isProxy(barSRl)).toBe(true)
|
||||
|
||||
const c = computed(() => {})
|
||||
expect(isProxy(c)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -100,6 +100,21 @@ describe('reactivity/reactive/Array', () => {
|
|||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('should track hasOwnProperty call with index', () => {
|
||||
const original = [1, 2, 3]
|
||||
const observed = reactive(original)
|
||||
|
||||
let dummy
|
||||
effect(() => {
|
||||
dummy = observed.hasOwnProperty(0)
|
||||
})
|
||||
|
||||
expect(dummy).toBe(true)
|
||||
|
||||
delete observed[0]
|
||||
expect(dummy).toBe(false)
|
||||
})
|
||||
|
||||
test('shift on Array should trigger dependency once', () => {
|
||||
const arr = reactive([1, 2, 3])
|
||||
const fn = vi.fn()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/reactivity",
|
||||
"version": "3.4.20",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
@ -38,10 +38,12 @@ const builtInSymbols = new Set(
|
|||
.filter(isSymbol),
|
||||
)
|
||||
|
||||
function hasOwnProperty(this: object, key: string) {
|
||||
function hasOwnProperty(this: object, key: unknown) {
|
||||
// #10455 hasOwnProperty may be called with non-string values
|
||||
if (!isSymbol(key)) key = String(key)
|
||||
const obj = toRaw(this)
|
||||
track(obj, TrackOpTypes.HAS, key)
|
||||
return obj.hasOwnProperty(key)
|
||||
return obj.hasOwnProperty(key as string)
|
||||
}
|
||||
|
||||
class BaseReactiveHandler implements ProxyHandler<Target> {
|
||||
|
|
|
@ -232,8 +232,10 @@ function createReadonlyMethod(type: TriggerOpTypes): Function {
|
|||
}
|
||||
}
|
||||
|
||||
type Instrumentations = Record<string | symbol, Function | number>
|
||||
|
||||
function createInstrumentations() {
|
||||
const mutableInstrumentations: Record<string, Function | number> = {
|
||||
const mutableInstrumentations: Instrumentations = {
|
||||
get(this: MapTypes, key: unknown) {
|
||||
return get(this, key)
|
||||
},
|
||||
|
@ -248,7 +250,7 @@ function createInstrumentations() {
|
|||
forEach: createForEach(false, false),
|
||||
}
|
||||
|
||||
const shallowInstrumentations: Record<string, Function | number> = {
|
||||
const shallowInstrumentations: Instrumentations = {
|
||||
get(this: MapTypes, key: unknown) {
|
||||
return get(this, key, false, true)
|
||||
},
|
||||
|
@ -263,7 +265,7 @@ function createInstrumentations() {
|
|||
forEach: createForEach(false, true),
|
||||
}
|
||||
|
||||
const readonlyInstrumentations: Record<string, Function | number> = {
|
||||
const readonlyInstrumentations: Instrumentations = {
|
||||
get(this: MapTypes, key: unknown) {
|
||||
return get(this, key, true)
|
||||
},
|
||||
|
@ -280,7 +282,7 @@ function createInstrumentations() {
|
|||
forEach: createForEach(true, false),
|
||||
}
|
||||
|
||||
const shallowReadonlyInstrumentations: Record<string, Function | number> = {
|
||||
const shallowReadonlyInstrumentations: Instrumentations = {
|
||||
get(this: MapTypes, key: unknown) {
|
||||
return get(this, key, true, true)
|
||||
},
|
||||
|
@ -297,24 +299,18 @@ function createInstrumentations() {
|
|||
forEach: createForEach(true, true),
|
||||
}
|
||||
|
||||
const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
|
||||
const iteratorMethods = [
|
||||
'keys',
|
||||
'values',
|
||||
'entries',
|
||||
Symbol.iterator,
|
||||
] as const
|
||||
|
||||
iteratorMethods.forEach(method => {
|
||||
mutableInstrumentations[method as string] = createIterableMethod(
|
||||
method,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
readonlyInstrumentations[method as string] = createIterableMethod(
|
||||
method,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
shallowInstrumentations[method as string] = createIterableMethod(
|
||||
method,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
shallowReadonlyInstrumentations[method as string] = createIterableMethod(
|
||||
mutableInstrumentations[method] = createIterableMethod(method, false, false)
|
||||
readonlyInstrumentations[method] = createIterableMethod(method, true, false)
|
||||
shallowInstrumentations[method] = createIterableMethod(method, false, true)
|
||||
shallowReadonlyInstrumentations[method] = createIterableMethod(
|
||||
method,
|
||||
true,
|
||||
true,
|
||||
|
|
|
@ -329,8 +329,8 @@ export function isShallow(value: unknown): boolean {
|
|||
* @param value - The value to check.
|
||||
* @see {@link https://vuejs.org/api/reactivity-utilities.html#isproxy}
|
||||
*/
|
||||
export function isProxy(value: unknown): boolean {
|
||||
return isReactive(value) || isReadonly(value)
|
||||
export function isProxy(value: any): boolean {
|
||||
return value ? !!value[ReactiveFlags.RAW] : false
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -409,5 +409,5 @@ export const toReactive = <T extends unknown>(value: T): T =>
|
|||
*
|
||||
* @param value - The value for which a readonly proxy shall be created.
|
||||
*/
|
||||
export const toReadonly = <T extends unknown>(value: T): T =>
|
||||
isObject(value) ? readonly(value) : value
|
||||
export const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>
|
||||
isObject(value) ? readonly(value) : (value as DeepReadonly<T>)
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
onUnmounted,
|
||||
ref,
|
||||
render,
|
||||
serialize,
|
||||
|
@ -768,6 +769,42 @@ describe('BaseTransition', () => {
|
|||
test('w/ KeepAlive', async () => {
|
||||
await runTestWithKeepAlive(testOutIn)
|
||||
})
|
||||
|
||||
test('w/ KeepAlive + unmount innerChild', async () => {
|
||||
const unmountSpy = vi.fn()
|
||||
const includeRef = ref(['TrueBranch'])
|
||||
const trueComp = {
|
||||
name: 'TrueBranch',
|
||||
setup() {
|
||||
onUnmounted(unmountSpy)
|
||||
const count = ref(0)
|
||||
return () => h('div', count.value)
|
||||
},
|
||||
}
|
||||
|
||||
const toggle = ref(true)
|
||||
const { props } = mockProps({ mode: 'out-in' }, true /*withKeepAlive*/)
|
||||
const root = nodeOps.createElement('div')
|
||||
const App = {
|
||||
render() {
|
||||
return h(BaseTransition, props, () => {
|
||||
return h(
|
||||
KeepAlive,
|
||||
{ include: includeRef.value },
|
||||
toggle.value ? h(trueComp) : h('div'),
|
||||
)
|
||||
})
|
||||
},
|
||||
}
|
||||
render(h(App), root)
|
||||
|
||||
// trigger toggle
|
||||
toggle.value = false
|
||||
includeRef.value = []
|
||||
|
||||
await nextTick()
|
||||
expect(unmountSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
// #6835
|
||||
|
|
|
@ -54,6 +54,18 @@ describe('Suspense', () => {
|
|||
}
|
||||
}
|
||||
|
||||
const RouterView = {
|
||||
setup(_: any, { slots }: any) {
|
||||
const route = inject('route') as any
|
||||
const depth = inject('depth', 0)
|
||||
provide('depth', depth + 1)
|
||||
return () => {
|
||||
const current = route.value[depth]
|
||||
return slots.default({ Component: current })[0]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
test('fallback content', async () => {
|
||||
const Async = defineAsyncComponent({
|
||||
render() {
|
||||
|
@ -1041,18 +1053,6 @@ describe('Suspense', () => {
|
|||
|
||||
// #10098
|
||||
test('switching branches w/ nested suspense', async () => {
|
||||
const RouterView = {
|
||||
setup(_: any, { slots }: any) {
|
||||
const route = inject('route') as any
|
||||
const depth = inject('depth', 0)
|
||||
provide('depth', depth + 1)
|
||||
return () => {
|
||||
const current = route.value[depth]
|
||||
return slots.default({ Component: current })[0]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const OuterB = defineAsyncComponent({
|
||||
setup: () => {
|
||||
return () =>
|
||||
|
@ -1132,6 +1132,121 @@ describe('Suspense', () => {
|
|||
expect(serializeInner(root)).toBe(`<div>innerA</div>`)
|
||||
})
|
||||
|
||||
// #10415
|
||||
test('nested suspense (w/ suspensible) switch several times before parent suspense resolve', async () => {
|
||||
const OuterA = defineAsyncComponent({
|
||||
setup: () => {
|
||||
return () =>
|
||||
h(RouterView, null, {
|
||||
default: ({ Component }: any) => [
|
||||
h(Suspense, null, {
|
||||
default: () => h(Component),
|
||||
}),
|
||||
],
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const InnerA = defineAsyncComponent({
|
||||
setup: () => {
|
||||
return () => h('div', 'innerA')
|
||||
},
|
||||
})
|
||||
|
||||
const route = shallowRef([OuterA, InnerA])
|
||||
const InnerB = defineAsyncComponent(
|
||||
{
|
||||
setup: () => {
|
||||
return () => h('div', 'innerB')
|
||||
},
|
||||
},
|
||||
5,
|
||||
)
|
||||
|
||||
const InnerB1 = defineAsyncComponent(
|
||||
{
|
||||
setup: () => {
|
||||
return () => h('div', 'innerB1')
|
||||
},
|
||||
},
|
||||
5,
|
||||
)
|
||||
|
||||
const InnerB2 = defineAsyncComponent(
|
||||
{
|
||||
setup: () => {
|
||||
return () => h('div', 'innerB2')
|
||||
},
|
||||
},
|
||||
5,
|
||||
)
|
||||
|
||||
const OuterB = defineAsyncComponent(
|
||||
{
|
||||
setup() {
|
||||
nextTick(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1))
|
||||
route.value = [OuterB, InnerB1]
|
||||
})
|
||||
|
||||
nextTick(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1))
|
||||
route.value = [OuterB, InnerB2]
|
||||
})
|
||||
|
||||
return () =>
|
||||
h(RouterView, null, {
|
||||
default: ({ Component }: any) => [
|
||||
h(
|
||||
Suspense,
|
||||
{ suspensible: true },
|
||||
{
|
||||
default: () => h(Component),
|
||||
},
|
||||
),
|
||||
],
|
||||
})
|
||||
},
|
||||
},
|
||||
5,
|
||||
)
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
provide('route', route)
|
||||
return () =>
|
||||
h(RouterView, null, {
|
||||
default: ({ Component }: any) => [
|
||||
h(Suspense, null, {
|
||||
default: () => h(Component),
|
||||
}),
|
||||
],
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
await Promise.all(deps)
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<!---->`)
|
||||
|
||||
await Promise.all(deps)
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>innerA</div>`)
|
||||
|
||||
deps.length = 0
|
||||
|
||||
route.value = [OuterB, InnerB]
|
||||
await nextTick()
|
||||
|
||||
await Promise.all(deps)
|
||||
await Promise.all(deps)
|
||||
await Promise.all(deps)
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>innerB2</div>`)
|
||||
})
|
||||
|
||||
test('branch switch to 3rd branch before resolve', async () => {
|
||||
const calls: string[] = []
|
||||
|
||||
|
|
|
@ -583,5 +583,31 @@ describe('error handling', () => {
|
|||
expect(handler).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
// #9574
|
||||
test('should pause tracking in error handler', async () => {
|
||||
const error = new Error('error')
|
||||
const x = ref(Math.random())
|
||||
|
||||
const handler = vi.fn(() => {
|
||||
x.value
|
||||
x.value = Math.random()
|
||||
})
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
return () => {
|
||||
throw error
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
app.config.errorHandler = handler
|
||||
app.mount(nodeOps.createElement('div'))
|
||||
|
||||
await nextTick()
|
||||
expect(handler).toHaveBeenCalledWith(error, {}, 'render function')
|
||||
expect(handler).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
// native event handler handling should be tested in respective renderers
|
||||
})
|
||||
|
|
|
@ -7,7 +7,10 @@ import {
|
|||
Teleport,
|
||||
Transition,
|
||||
type VNode,
|
||||
createBlock,
|
||||
createCommentVNode,
|
||||
createElementBlock,
|
||||
createElementVNode,
|
||||
createSSRApp,
|
||||
createStaticVNode,
|
||||
createTextVNode,
|
||||
|
@ -17,16 +20,19 @@ import {
|
|||
h,
|
||||
nextTick,
|
||||
onMounted,
|
||||
openBlock,
|
||||
ref,
|
||||
renderSlot,
|
||||
useCssVars,
|
||||
vModelCheckbox,
|
||||
vShow,
|
||||
withCtx,
|
||||
withDirectives,
|
||||
} from '@vue/runtime-dom'
|
||||
import { type SSRContext, renderToString } from '@vue/server-renderer'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
|
||||
import { expect } from 'vitest'
|
||||
|
||||
function mountWithHydration(html: string, render: () => any) {
|
||||
const container = document.createElement('div')
|
||||
|
@ -1292,6 +1298,81 @@ 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 () => {
|
||||
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)
|
||||
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div show="false"><!--[--><div><div><!----></div></div><div>1</div><!--]--></div>`,
|
||||
)
|
||||
|
||||
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>`,
|
||||
)
|
||||
__DEV__ = true
|
||||
})
|
||||
|
||||
describe('mismatch handling', () => {
|
||||
test('text node', () => {
|
||||
const { container } = mountWithHydration(`foo`, () => 'bar')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.4.20",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
|
|
|
@ -233,7 +233,7 @@ export type DefineModelOptions<T = any> = {
|
|||
* Otherwise the prop name will default to "modelValue". In both cases, you
|
||||
* can also pass an additional object which will be used as the prop's options.
|
||||
*
|
||||
* The the returned ref behaves differently depending on whether the parent
|
||||
* The returned ref behaves differently depending on whether the parent
|
||||
* provided the corresponding v-model props or not:
|
||||
* - If yes, the returned ref's value will always be in sync with the parent
|
||||
* prop.
|
||||
|
@ -284,6 +284,9 @@ export function defineModel(): any {
|
|||
}
|
||||
|
||||
type NotUndefined<T> = T extends undefined ? never : T
|
||||
type MappedOmit<T, K extends keyof any> = {
|
||||
[P in keyof T as P extends K ? never : P]: T[P]
|
||||
}
|
||||
|
||||
type InferDefaults<T> = {
|
||||
[K in keyof T]?: InferDefault<T, T[K]>
|
||||
|
@ -299,7 +302,7 @@ type PropsWithDefaults<
|
|||
T,
|
||||
Defaults extends InferDefaults<T>,
|
||||
BKeys extends keyof T,
|
||||
> = Readonly<Omit<T, keyof Defaults>> & {
|
||||
> = Readonly<MappedOmit<T, keyof Defaults>> & {
|
||||
readonly [K in keyof Defaults]-?: K extends keyof T
|
||||
? Defaults[K] extends undefined
|
||||
? T[K]
|
||||
|
|
|
@ -427,15 +427,14 @@ function applySingletonPrototype(app: App, Ctor: Function) {
|
|||
app.config.globalProperties = Object.create(Ctor.prototype)
|
||||
}
|
||||
let hasPrototypeAugmentations = false
|
||||
const descriptors = Object.getOwnPropertyDescriptors(Ctor.prototype)
|
||||
for (const key in descriptors) {
|
||||
for (const key of Object.getOwnPropertyNames(Ctor.prototype)) {
|
||||
if (key !== 'constructor') {
|
||||
hasPrototypeAugmentations = true
|
||||
if (enabled) {
|
||||
Object.defineProperty(
|
||||
app.config.globalProperties,
|
||||
key,
|
||||
descriptors[key],
|
||||
Object.getOwnPropertyDescriptor(Ctor.prototype, key)!,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
DeprecationTypes,
|
||||
assertCompatEnabled,
|
||||
isCompatEnabled,
|
||||
warnDeprecation,
|
||||
} from './compatConfig'
|
||||
import { off, on, once } from './instanceEventEmitter'
|
||||
import { getCompatListeners } from './instanceListeners'
|
||||
|
@ -121,50 +122,77 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
|
|||
|
||||
$children: getCompatChildren,
|
||||
$listeners: getCompatListeners,
|
||||
|
||||
// inject additional properties into $options for compat
|
||||
// e.g. vuex needs this.$options.parent
|
||||
$options: i => {
|
||||
if (!isCompatEnabled(DeprecationTypes.PRIVATE_APIS, i)) {
|
||||
return resolveMergedOptions(i)
|
||||
}
|
||||
if (i.resolvedOptions) {
|
||||
return i.resolvedOptions
|
||||
}
|
||||
const res = (i.resolvedOptions = extend({}, resolveMergedOptions(i)))
|
||||
Object.defineProperties(res, {
|
||||
parent: {
|
||||
get() {
|
||||
warnDeprecation(DeprecationTypes.PRIVATE_APIS, i, '$options.parent')
|
||||
return i.proxy!.$parent
|
||||
},
|
||||
},
|
||||
propsData: {
|
||||
get() {
|
||||
warnDeprecation(
|
||||
DeprecationTypes.PRIVATE_APIS,
|
||||
i,
|
||||
'$options.propsData',
|
||||
)
|
||||
return i.vnode.props
|
||||
},
|
||||
},
|
||||
})
|
||||
return res
|
||||
},
|
||||
} as PublicPropertiesMap)
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (isCompatEnabled(DeprecationTypes.PRIVATE_APIS, null)) {
|
||||
extend(map, {
|
||||
// needed by many libs / render fns
|
||||
$vnode: i => i.vnode,
|
||||
const privateAPIs = {
|
||||
// needed by many libs / render fns
|
||||
$vnode: i => i.vnode,
|
||||
|
||||
// inject additional properties into $options for compat
|
||||
// e.g. vuex needs this.$options.parent
|
||||
$options: i => {
|
||||
const res = extend({}, resolveMergedOptions(i))
|
||||
res.parent = i.proxy!.$parent
|
||||
res.propsData = i.vnode.props
|
||||
return res
|
||||
},
|
||||
// some private properties that are likely accessed...
|
||||
_self: i => i.proxy,
|
||||
_uid: i => i.uid,
|
||||
_data: i => i.data,
|
||||
_isMounted: i => i.isMounted,
|
||||
_isDestroyed: i => i.isUnmounted,
|
||||
|
||||
// some private properties that are likely accessed...
|
||||
_self: i => i.proxy,
|
||||
_uid: i => i.uid,
|
||||
_data: i => i.data,
|
||||
_isMounted: i => i.isMounted,
|
||||
_isDestroyed: i => i.isUnmounted,
|
||||
// v2 render helpers
|
||||
$createElement: () => compatH,
|
||||
_c: () => compatH,
|
||||
_o: () => legacyMarkOnce,
|
||||
_n: () => looseToNumber,
|
||||
_s: () => toDisplayString,
|
||||
_l: () => renderList,
|
||||
_t: i => legacyRenderSlot.bind(null, i),
|
||||
_q: () => looseEqual,
|
||||
_i: () => looseIndexOf,
|
||||
_m: i => legacyRenderStatic.bind(null, i),
|
||||
_f: () => resolveFilter,
|
||||
_k: i => legacyCheckKeyCodes.bind(null, i),
|
||||
_b: () => legacyBindObjectProps,
|
||||
_v: () => createTextVNode,
|
||||
_e: () => createCommentVNode,
|
||||
_u: () => legacyresolveScopedSlots,
|
||||
_g: () => legacyBindObjectListeners,
|
||||
_d: () => legacyBindDynamicKeys,
|
||||
_p: () => legacyPrependModifier,
|
||||
} as PublicPropertiesMap
|
||||
|
||||
// v2 render helpers
|
||||
$createElement: () => compatH,
|
||||
_c: () => compatH,
|
||||
_o: () => legacyMarkOnce,
|
||||
_n: () => looseToNumber,
|
||||
_s: () => toDisplayString,
|
||||
_l: () => renderList,
|
||||
_t: i => legacyRenderSlot.bind(null, i),
|
||||
_q: () => looseEqual,
|
||||
_i: () => looseIndexOf,
|
||||
_m: i => legacyRenderStatic.bind(null, i),
|
||||
_f: () => resolveFilter,
|
||||
_k: i => legacyCheckKeyCodes.bind(null, i),
|
||||
_b: () => legacyBindObjectProps,
|
||||
_v: () => createTextVNode,
|
||||
_e: () => createCommentVNode,
|
||||
_u: () => legacyresolveScopedSlots,
|
||||
_g: () => legacyBindObjectListeners,
|
||||
_d: () => legacyBindDynamicKeys,
|
||||
_p: () => legacyPrependModifier,
|
||||
} as PublicPropertiesMap)
|
||||
for (const key in privateAPIs) {
|
||||
map[key] = i => {
|
||||
if (isCompatEnabled(DeprecationTypes.PRIVATE_APIS, i)) {
|
||||
return privateAPIs[key](i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ import { type Directive, validateDirectiveName } from './directives'
|
|||
import {
|
||||
type ComponentOptions,
|
||||
type ComputedOptions,
|
||||
type MergedComponentOptions,
|
||||
type MethodOptions,
|
||||
applyOptions,
|
||||
resolveMergedOptions,
|
||||
|
@ -528,6 +529,12 @@ export interface ComponentInternalInstance {
|
|||
* @internal
|
||||
*/
|
||||
getCssVars?: () => Record<string, string>
|
||||
|
||||
/**
|
||||
* v2 compat only, for caching mutated $options
|
||||
* @internal
|
||||
*/
|
||||
resolvedOptions?: MergedComponentOptions
|
||||
}
|
||||
|
||||
const emptyAppContext = createAppContext()
|
||||
|
@ -780,8 +787,7 @@ function setupStatefulComponent(
|
|||
// 0. create render proxy property access cache
|
||||
instance.accessCache = Object.create(null)
|
||||
// 1. create public instance / render proxy
|
||||
// also mark it raw so it's never observed
|
||||
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
|
||||
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
|
||||
if (__DEV__) {
|
||||
exposePropsOnRenderContext(instance)
|
||||
}
|
||||
|
@ -1010,36 +1016,28 @@ export function finishComponentSetup(
|
|||
}
|
||||
}
|
||||
|
||||
function getAttrsProxy(instance: ComponentInternalInstance): Data {
|
||||
return (
|
||||
instance.attrsProxy ||
|
||||
(instance.attrsProxy = new Proxy(
|
||||
instance.attrs,
|
||||
__DEV__
|
||||
? {
|
||||
get(target, key: string) {
|
||||
markAttrsAccessed()
|
||||
track(instance, TrackOpTypes.GET, '$attrs')
|
||||
return target[key]
|
||||
},
|
||||
set() {
|
||||
warn(`setupContext.attrs is readonly.`)
|
||||
return false
|
||||
},
|
||||
deleteProperty() {
|
||||
warn(`setupContext.attrs is readonly.`)
|
||||
return false
|
||||
},
|
||||
}
|
||||
: {
|
||||
get(target, key: string) {
|
||||
track(instance, TrackOpTypes.GET, '$attrs')
|
||||
return target[key]
|
||||
},
|
||||
},
|
||||
))
|
||||
)
|
||||
}
|
||||
const attrsProxyHandlers = __DEV__
|
||||
? {
|
||||
get(target: Data, key: string) {
|
||||
markAttrsAccessed()
|
||||
track(target, TrackOpTypes.GET, '')
|
||||
return target[key]
|
||||
},
|
||||
set() {
|
||||
warn(`setupContext.attrs is readonly.`)
|
||||
return false
|
||||
},
|
||||
deleteProperty() {
|
||||
warn(`setupContext.attrs is readonly.`)
|
||||
return false
|
||||
},
|
||||
}
|
||||
: {
|
||||
get(target: Data, key: string) {
|
||||
track(target, TrackOpTypes.GET, '')
|
||||
return target[key]
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Dev-only
|
||||
|
@ -1086,9 +1084,13 @@ export function createSetupContext(
|
|||
if (__DEV__) {
|
||||
// We use getters in dev in case libs like test-utils overwrite instance
|
||||
// properties (overwrites should not be done in prod)
|
||||
let attrsProxy: Data
|
||||
return Object.freeze({
|
||||
get attrs() {
|
||||
return getAttrsProxy(instance)
|
||||
return (
|
||||
attrsProxy ||
|
||||
(attrsProxy = new Proxy(instance.attrs, attrsProxyHandlers))
|
||||
)
|
||||
},
|
||||
get slots() {
|
||||
return getSlotsProxy(instance)
|
||||
|
@ -1100,9 +1102,7 @@ export function createSetupContext(
|
|||
})
|
||||
} else {
|
||||
return {
|
||||
get attrs() {
|
||||
return getAttrsProxy(instance)
|
||||
},
|
||||
attrs: new Proxy(instance.attrs, attrsProxyHandlers),
|
||||
slots: instance.slots,
|
||||
emit: instance.emit,
|
||||
expose,
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
PatchFlags,
|
||||
camelize,
|
||||
capitalize,
|
||||
def,
|
||||
extend,
|
||||
hasOwn,
|
||||
hyphenate,
|
||||
|
@ -34,7 +33,6 @@ import {
|
|||
setCurrentInstance,
|
||||
} from './component'
|
||||
import { isEmitListener } from './componentEmits'
|
||||
import { InternalObjectKey } from './vnode'
|
||||
import type { AppContext } from './apiCreateApp'
|
||||
import { createPropsDefaultThis } from './compat/props'
|
||||
import { isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig'
|
||||
|
@ -187,6 +185,13 @@ type NormalizedProp =
|
|||
export type NormalizedProps = Record<string, NormalizedProp>
|
||||
export type NormalizedPropsOptions = [NormalizedProps, string[]] | []
|
||||
|
||||
/**
|
||||
* Used during vnode props normalization to check if the vnode props is the
|
||||
* attrs object of a component via `Object.getPrototypeOf`. This is more
|
||||
* performant than defining a non-enumerable property.
|
||||
*/
|
||||
export const attrsProto = {}
|
||||
|
||||
export function initProps(
|
||||
instance: ComponentInternalInstance,
|
||||
rawProps: Data | null,
|
||||
|
@ -194,8 +199,7 @@ export function initProps(
|
|||
isSSR = false,
|
||||
) {
|
||||
const props: Data = {}
|
||||
const attrs: Data = {}
|
||||
def(attrs, InternalObjectKey, 1)
|
||||
const attrs: Data = Object.create(attrsProto)
|
||||
|
||||
instance.propsDefaults = Object.create(null)
|
||||
|
||||
|
@ -361,7 +365,7 @@ export function updateProps(
|
|||
|
||||
// trigger updates for $attrs in case it's used in component slots
|
||||
if (hasAttrsChanged) {
|
||||
trigger(instance, TriggerOpTypes.SET, '$attrs')
|
||||
trigger(instance.attrs, TriggerOpTypes.SET, '')
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
isString,
|
||||
} from '@vue/shared'
|
||||
import {
|
||||
ReactiveFlags,
|
||||
type ShallowUnwrapRef,
|
||||
TrackOpTypes,
|
||||
type UnwrapNestedRefs,
|
||||
|
@ -306,6 +307,10 @@ const hasSetupBinding = (state: Data, key: string) =>
|
|||
|
||||
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||
get({ _: instance }: ComponentRenderContext, key: string) {
|
||||
if (key === ReactiveFlags.SKIP) {
|
||||
return true
|
||||
}
|
||||
|
||||
const { ctx, setupState, data, props, accessCache, type, appContext } =
|
||||
instance
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { type ComponentInternalInstance, currentInstance } from './component'
|
||||
import {
|
||||
InternalObjectKey,
|
||||
type VNode,
|
||||
type VNodeChild,
|
||||
type VNodeNormalizedChildren,
|
||||
|
@ -174,7 +173,7 @@ export const initSlots = (
|
|||
// we should avoid the proxy object polluting the slots of the internal instance
|
||||
instance.slots = toRaw(children as InternalSlots)
|
||||
// make compiler marker non-enumerable
|
||||
def(children as InternalSlots, '_', type)
|
||||
def(instance.slots, '_', type)
|
||||
} else {
|
||||
normalizeObjectSlots(
|
||||
children as RawSlots,
|
||||
|
@ -188,7 +187,6 @@ export const initSlots = (
|
|||
normalizeVNodeSlots(instance, children)
|
||||
}
|
||||
}
|
||||
def(instance.slots, InternalObjectKey, 1)
|
||||
}
|
||||
|
||||
export const updateSlots = (
|
||||
|
|
|
@ -254,7 +254,7 @@ const KeepAliveImpl: ComponentOptions = {
|
|||
pendingCacheKey = null
|
||||
|
||||
if (!slots.default) {
|
||||
return null
|
||||
return (current = null)
|
||||
}
|
||||
|
||||
const children = slots.default()
|
||||
|
|
|
@ -99,7 +99,11 @@ export const SuspenseImpl = {
|
|||
// 2. mounting along with the pendingBranch of parentSuspense
|
||||
// it is necessary to skip the current patch to avoid multiple mounts
|
||||
// of inner components.
|
||||
if (parentSuspense && parentSuspense.deps > 0) {
|
||||
if (
|
||||
parentSuspense &&
|
||||
parentSuspense.deps > 0 &&
|
||||
!n1.suspense!.isInFallback
|
||||
) {
|
||||
n2.suspense = n1.suspense!
|
||||
n2.suspense.vnode = n2
|
||||
n2.el = n1.el
|
||||
|
|
|
@ -123,6 +123,7 @@ export const devtoolsComponentRemoved = (
|
|||
}
|
||||
}
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
function createDevtoolsComponentHook(hook: DevtoolsHooks) {
|
||||
return (component: ComponentInternalInstance) => {
|
||||
emit(
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { pauseTracking, resetTracking } from '@vue/reactivity'
|
||||
import type { VNode } from './vnode'
|
||||
import type { ComponentInternalInstance } from './component'
|
||||
import { popWarningContext, pushWarningContext, warn } from './warning'
|
||||
import { isFunction, isPromise } from '@vue/shared'
|
||||
import { isArray, isFunction, isPromise } from '@vue/shared'
|
||||
import { LifecycleHooks } from './enums'
|
||||
|
||||
// contexts where user provided function may be executed, in addition to
|
||||
|
@ -78,7 +79,7 @@ export function callWithAsyncErrorHandling(
|
|||
instance: ComponentInternalInstance | null,
|
||||
type: ErrorTypes,
|
||||
args?: unknown[],
|
||||
): any[] {
|
||||
): any {
|
||||
if (isFunction(fn)) {
|
||||
const res = callWithErrorHandling(fn, instance, type, args)
|
||||
if (res && isPromise(res)) {
|
||||
|
@ -89,11 +90,17 @@ export function callWithAsyncErrorHandling(
|
|||
return res
|
||||
}
|
||||
|
||||
const values = []
|
||||
for (let i = 0; i < fn.length; i++) {
|
||||
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
|
||||
if (isArray(fn)) {
|
||||
const values = []
|
||||
for (let i = 0; i < fn.length; i++) {
|
||||
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
|
||||
}
|
||||
return values
|
||||
} else if (__DEV__) {
|
||||
warn(
|
||||
`Invalid value type passed to callWithAsyncErrorHandling(): ${typeof fn}`,
|
||||
)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
export function handleError(
|
||||
|
@ -127,12 +134,14 @@ export function handleError(
|
|||
// app-level handling
|
||||
const appErrorHandler = instance.appContext.config.errorHandler
|
||||
if (appErrorHandler) {
|
||||
pauseTracking()
|
||||
callWithErrorHandling(
|
||||
appErrorHandler,
|
||||
null,
|
||||
ErrorCodes.APP_ERROR_HANDLER,
|
||||
[err, exposedInstance, errorInfo],
|
||||
)
|
||||
resetTracking()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,6 +120,7 @@ export function createHydrationFunctions(
|
|||
slotScopeIds: string[] | null,
|
||||
optimized = false,
|
||||
): Node | null => {
|
||||
optimized = optimized || !!vnode.dynamicChildren
|
||||
const isFragmentStart = isComment(node) && node.data === '['
|
||||
const onMismatch = () =>
|
||||
handleMismatch(
|
||||
|
@ -443,6 +444,7 @@ export function createHydrationFunctions(
|
|||
if (props) {
|
||||
if (
|
||||
__DEV__ ||
|
||||
__FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__ ||
|
||||
forcePatch ||
|
||||
!optimized ||
|
||||
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
|
||||
|
@ -450,7 +452,7 @@ export function createHydrationFunctions(
|
|||
for (const key in props) {
|
||||
// check hydration mismatch
|
||||
if (
|
||||
__DEV__ &&
|
||||
(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
propHasMismatch(el, key, props[key], vnode, parentComponent)
|
||||
) {
|
||||
hasMismatch = true
|
||||
|
|
|
@ -55,6 +55,7 @@ import { convertLegacyVModelProps } from './compat/componentVModel'
|
|||
import { defineLegacyVNodeProperties } from './compat/renderFn'
|
||||
import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||
import type { ComponentPublicInstance } from './componentPublicInstance'
|
||||
import { attrsProto } from './componentProps'
|
||||
|
||||
export const Fragment = Symbol.for('v-fgt') as any as {
|
||||
__isFragment: true
|
||||
|
@ -404,8 +405,6 @@ const createVNodeWithArgsTransform = (
|
|||
)
|
||||
}
|
||||
|
||||
export const InternalObjectKey = `__vInternal`
|
||||
|
||||
const normalizeKey = ({ key }: VNodeProps): VNode['key'] =>
|
||||
key != null ? key : null
|
||||
|
||||
|
@ -618,7 +617,7 @@ function _createVNode(
|
|||
|
||||
export function guardReactiveProps(props: (Data & VNodeProps) | null) {
|
||||
if (!props) return null
|
||||
return isProxy(props) || InternalObjectKey in props
|
||||
return isProxy(props) || Object.getPrototypeOf(props) === attrsProto
|
||||
? extend({}, props)
|
||||
: props
|
||||
}
|
||||
|
@ -792,7 +791,7 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
|
|||
} else {
|
||||
type = ShapeFlags.SLOTS_CHILDREN
|
||||
const slotFlag = (children as RawSlots)._
|
||||
if (!slotFlag && !(InternalObjectKey in children!)) {
|
||||
if (!slotFlag) {
|
||||
// if slots are not normalized, attach context instance
|
||||
// (compiled / normalized slots already have context)
|
||||
;(children as RawSlots)._ctx = currentRenderingInstance
|
||||
|
|
|
@ -45,7 +45,7 @@ export function warn(msg: string, ...args: any[]) {
|
|||
instance,
|
||||
ErrorCodes.APP_WARN_HANDLER,
|
||||
[
|
||||
msg + args.join(''),
|
||||
msg + args.map(a => a.toString?.() ?? JSON.stringify(a)).join(''),
|
||||
instance && instance.proxy,
|
||||
trace
|
||||
.map(
|
||||
|
|
|
@ -139,6 +139,12 @@ describe('defineCustomElement', () => {
|
|||
expect(e.shadowRoot!.innerHTML).toBe('<div></div><div>two</div>')
|
||||
expect(e.hasAttribute('foo')).toBe(false)
|
||||
|
||||
e.foo = undefined
|
||||
await nextTick()
|
||||
expect(e.shadowRoot!.innerHTML).toBe('<div></div><div>two</div>')
|
||||
expect(e.hasAttribute('foo')).toBe(false)
|
||||
expect(e.foo).toBe(undefined)
|
||||
|
||||
e.bazQux = 'four'
|
||||
await nextTick()
|
||||
expect(e.shadowRoot!.innerHTML).toBe('<div></div><div>four</div>')
|
||||
|
|
|
@ -1237,4 +1237,73 @@ describe('vModel', () => {
|
|||
await nextTick()
|
||||
expect(data.value).toEqual('使用拼音输入')
|
||||
})
|
||||
|
||||
it('multiple select (model is number, option value is string)', async () => {
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return {
|
||||
value: [1, 2],
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return [
|
||||
withVModel(
|
||||
h(
|
||||
'select',
|
||||
{
|
||||
multiple: true,
|
||||
'onUpdate:modelValue': setValue.bind(this),
|
||||
},
|
||||
[h('option', { value: '1' }), h('option', { value: '2' })],
|
||||
),
|
||||
this.value,
|
||||
),
|
||||
]
|
||||
},
|
||||
})
|
||||
render(h(component), root)
|
||||
|
||||
await nextTick()
|
||||
const [foo, bar] = root.querySelectorAll('option')
|
||||
|
||||
expect(foo.selected).toEqual(true)
|
||||
expect(bar.selected).toEqual(true)
|
||||
})
|
||||
|
||||
// #10503
|
||||
test('equal value with a leading 0 should trigger update.', async () => {
|
||||
const setNum = function (this: any, value: any) {
|
||||
this.num = value
|
||||
}
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { num: 0 }
|
||||
},
|
||||
render() {
|
||||
return [
|
||||
withVModel(
|
||||
h('input', {
|
||||
id: 'input_num1',
|
||||
type: 'number',
|
||||
'onUpdate:modelValue': setNum.bind(this),
|
||||
}),
|
||||
this.num,
|
||||
),
|
||||
]
|
||||
},
|
||||
})
|
||||
|
||||
render(h(component), root)
|
||||
const data = root._vnode.component.data
|
||||
|
||||
const inputNum1 = root.querySelector('#input_num1')!
|
||||
expect(inputNum1.value).toBe('0')
|
||||
|
||||
inputNum1.value = '01'
|
||||
triggerEvent('input', inputNum1)
|
||||
await nextTick()
|
||||
expect(data.num).toBe(1)
|
||||
|
||||
expect(inputNum1.value).toBe('1')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -118,6 +118,63 @@ describe('useCssVars', () => {
|
|||
}
|
||||
})
|
||||
|
||||
test('with v-if & async component & suspense', async () => {
|
||||
const state = reactive({ color: 'red' })
|
||||
const root = document.createElement('div')
|
||||
const show = ref(false)
|
||||
let resolveAsync: any
|
||||
let asyncPromise: any
|
||||
|
||||
const AsyncComp = {
|
||||
setup() {
|
||||
useCssVars(() => state)
|
||||
asyncPromise = new Promise(r => {
|
||||
resolveAsync = () => {
|
||||
r(() => h('p', 'default'))
|
||||
}
|
||||
})
|
||||
return asyncPromise
|
||||
},
|
||||
}
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: h('div', {}, show.value ? h(AsyncComp) : h('p')),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
render(h(App), root)
|
||||
await nextTick()
|
||||
// AsyncComp resolve
|
||||
show.value = true
|
||||
await nextTick()
|
||||
resolveAsync()
|
||||
await asyncPromise.then(() => {})
|
||||
// Suspense effects flush
|
||||
await nextTick()
|
||||
// css vars use with default tree
|
||||
for (const c of [].slice.call(root.children as any)) {
|
||||
expect(
|
||||
((c as any).children[0] as HTMLElement).style.getPropertyValue(
|
||||
`--color`,
|
||||
),
|
||||
).toBe(`red`)
|
||||
}
|
||||
|
||||
state.color = 'green'
|
||||
await nextTick()
|
||||
for (const c of [].slice.call(root.children as any)) {
|
||||
expect(
|
||||
((c as any).children[0] as HTMLElement).style.getPropertyValue(
|
||||
`--color`,
|
||||
),
|
||||
).toBe('green')
|
||||
}
|
||||
})
|
||||
|
||||
test('with subTree changed', async () => {
|
||||
const state = reactive({ color: 'red' })
|
||||
const value = ref(true)
|
||||
|
|
|
@ -192,4 +192,14 @@ describe(`runtime-dom: events patching`, () => {
|
|||
testElement.dispatchEvent(new CustomEvent('foobar'))
|
||||
expect(fn2).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('handles an unknown type', () => {
|
||||
const el = document.createElement('div')
|
||||
patchProp(el, 'onClick', null, 'test')
|
||||
el.dispatchEvent(new Event('click'))
|
||||
expect(
|
||||
`Wrong type passed as event handler to onClick - did you forget @ or : ` +
|
||||
`in front of your prop?\nExpected function or array of functions, received type string.`,
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -291,6 +291,18 @@ describe('runtime-dom: props patching', () => {
|
|||
expect(el.value).toBe('baz')
|
||||
})
|
||||
|
||||
test('init empty value for option', () => {
|
||||
const root = document.createElement('div')
|
||||
render(
|
||||
h('select', { value: 'foo' }, [h('option', { value: '' }, 'foo')]),
|
||||
root,
|
||||
)
|
||||
const select = root.children[0] as HTMLSelectElement
|
||||
const option = select.children[0] as HTMLOptionElement
|
||||
expect(select.value).toBe('')
|
||||
expect(option.value).toBe('')
|
||||
})
|
||||
|
||||
// #8780
|
||||
test('embedded tag with width and height', () => {
|
||||
// Width and height of some embedded element such as img、video、source、canvas
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-dom",
|
||||
"version": "3.4.20",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/runtime-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-dom.esm-bundler.js",
|
||||
|
|
|
@ -313,7 +313,7 @@ export class VueElement extends BaseClass {
|
|||
}
|
||||
|
||||
protected _setAttr(key: string) {
|
||||
let value = this.getAttribute(key)
|
||||
let value = this.hasAttribute(key) ? this.getAttribute(key) : undefined
|
||||
const camelKey = camelize(key)
|
||||
if (this._numberProps && this._numberProps[camelKey]) {
|
||||
value = toNumber(value)
|
||||
|
|
|
@ -112,7 +112,29 @@ const TransitionGroupImpl: ComponentOptions = {
|
|||
tag = 'span'
|
||||
}
|
||||
|
||||
prevChildren = children
|
||||
prevChildren = []
|
||||
if (children) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
if (child.el && child.el instanceof Element) {
|
||||
prevChildren.push(child)
|
||||
setTransitionHooks(
|
||||
child,
|
||||
resolveTransitionHooks(
|
||||
child,
|
||||
cssTransitionProps,
|
||||
state,
|
||||
instance,
|
||||
),
|
||||
)
|
||||
positionMap.set(
|
||||
child,
|
||||
(child.el as Element).getBoundingClientRect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
children = slots.default ? getTransitionRawChildren(slots.default()) : []
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
|
@ -127,17 +149,6 @@ const TransitionGroupImpl: ComponentOptions = {
|
|||
}
|
||||
}
|
||||
|
||||
if (prevChildren) {
|
||||
for (let i = 0; i < prevChildren.length; i++) {
|
||||
const child = prevChildren[i]
|
||||
setTransitionHooks(
|
||||
child,
|
||||
resolveTransitionHooks(child, cssTransitionProps, state, instance),
|
||||
)
|
||||
positionMap.set(child, (child.el as Element).getBoundingClientRect())
|
||||
}
|
||||
}
|
||||
|
||||
return createVNode(tag, null, children)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -86,9 +86,10 @@ export const vModelText: ModelDirective<
|
|||
el[assignKey] = getModelAssigner(vnode)
|
||||
// avoid clearing unresolved text. #2302
|
||||
if ((el as any).composing) return
|
||||
|
||||
const elValue =
|
||||
number || el.type === 'number' ? looseToNumber(el.value) : el.value
|
||||
(number || el.type === 'number') && !/^0\d/.test(el.value)
|
||||
? looseToNumber(el.value)
|
||||
: el.value
|
||||
const newValue = value == null ? '' : value
|
||||
|
||||
if (elValue === newValue) {
|
||||
|
@ -242,9 +243,7 @@ function setSelected(el: HTMLSelectElement, value: any, number: boolean) {
|
|||
const optionType = typeof optionValue
|
||||
// fast path for string / number values
|
||||
if (optionType === 'string' || optionType === 'number') {
|
||||
option.selected = value.includes(
|
||||
number ? looseToNumber(optionValue) : optionValue,
|
||||
)
|
||||
option.selected = value.some(v => String(v) === String(optionValue))
|
||||
} else {
|
||||
option.selected = looseIndexOf(value, optionValue) > -1
|
||||
}
|
||||
|
|
|
@ -42,9 +42,8 @@ export function useCssVars(getter: (ctx: any) => Record<string, string>) {
|
|||
updateTeleports(vars)
|
||||
}
|
||||
|
||||
watchPostEffect(setVars)
|
||||
|
||||
onMounted(() => {
|
||||
watchPostEffect(setVars)
|
||||
const ob = new MutationObserver(setVars)
|
||||
ob.observe(instance.subTree.el!.parentNode, { childList: true })
|
||||
onUnmounted(() => ob.disconnect())
|
||||
|
|
|
@ -1348,8 +1348,9 @@ export interface Events {
|
|||
// selection events
|
||||
onSelect: Event
|
||||
|
||||
// UI events
|
||||
onScroll: UIEvent
|
||||
// scroll events
|
||||
onScroll: Event
|
||||
onScrollend: Event
|
||||
|
||||
// touch events
|
||||
onTouchcancel: TouchEvent
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { hyphenate, isArray } from '@vue/shared'
|
||||
import { NOOP, hyphenate, isArray, isFunction } from '@vue/shared'
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
ErrorCodes,
|
||||
callWithAsyncErrorHandling,
|
||||
warn,
|
||||
} from '@vue/runtime-core'
|
||||
|
||||
interface Invoker extends EventListener {
|
||||
|
@ -36,7 +37,7 @@ export function patchEvent(
|
|||
el: Element & { [veiKey]?: Record<string, Invoker | undefined> },
|
||||
rawName: string,
|
||||
prevValue: EventValue | null,
|
||||
nextValue: EventValue | null,
|
||||
nextValue: EventValue | unknown,
|
||||
instance: ComponentInternalInstance | null = null,
|
||||
) {
|
||||
// vei = vue event invokers
|
||||
|
@ -44,12 +45,19 @@ export function patchEvent(
|
|||
const existingInvoker = invokers[rawName]
|
||||
if (nextValue && existingInvoker) {
|
||||
// patch
|
||||
existingInvoker.value = nextValue
|
||||
existingInvoker.value = __DEV__
|
||||
? sanitizeEventValue(nextValue, rawName)
|
||||
: (nextValue as EventValue)
|
||||
} else {
|
||||
const [name, options] = parseName(rawName)
|
||||
if (nextValue) {
|
||||
// add
|
||||
const invoker = (invokers[rawName] = createInvoker(nextValue, instance))
|
||||
const invoker = (invokers[rawName] = createInvoker(
|
||||
__DEV__
|
||||
? sanitizeEventValue(nextValue, rawName)
|
||||
: (nextValue as EventValue),
|
||||
instance,
|
||||
))
|
||||
addEventListener(el, name, invoker, options)
|
||||
} else if (existingInvoker) {
|
||||
// remove
|
||||
|
@ -116,6 +124,17 @@ function createInvoker(
|
|||
return invoker
|
||||
}
|
||||
|
||||
function sanitizeEventValue(value: unknown, propName: string): EventValue {
|
||||
if (isFunction(value) || isArray(value)) {
|
||||
return value as EventValue
|
||||
}
|
||||
warn(
|
||||
`Wrong type passed as event handler to ${propName} - did you forget @ or : ` +
|
||||
`in front of your prop?\nExpected function or array of functions, received type ${typeof value}.`,
|
||||
)
|
||||
return NOOP
|
||||
}
|
||||
|
||||
function patchStopImmediatePropagation(
|
||||
e: Event,
|
||||
value: EventValue,
|
||||
|
@ -126,7 +145,9 @@ function patchStopImmediatePropagation(
|
|||
originalStop.call(e)
|
||||
;(e as any)._stopped = true
|
||||
}
|
||||
return value.map(fn => (e: Event) => !(e as any)._stopped && fn && fn(e))
|
||||
return (value as Function[]).map(
|
||||
fn => (e: Event) => !(e as any)._stopped && fn && fn(e),
|
||||
)
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
|
|
|
@ -34,20 +34,20 @@ export function patchDOMProp(
|
|||
// custom elements may use _value internally
|
||||
!tag.includes('-')
|
||||
) {
|
||||
// store value as _value as well since
|
||||
// non-string values will be stringified.
|
||||
el._value = value
|
||||
// #4956: <option> value will fallback to its text content so we need to
|
||||
// compare against its attribute value instead.
|
||||
const oldValue =
|
||||
tag === 'OPTION' ? el.getAttribute('value') || '' : el.value
|
||||
const newValue = value == null ? '' : value
|
||||
if (oldValue !== newValue) {
|
||||
if (oldValue !== newValue || !('_value' in el)) {
|
||||
el.value = newValue
|
||||
}
|
||||
if (value == null) {
|
||||
el.removeAttribute(key)
|
||||
}
|
||||
// store value as _value as well since
|
||||
// non-string values will be stringified.
|
||||
el._value = value
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/server-renderer",
|
||||
"version": "3.4.20",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/server-renderer",
|
||||
"main": "index.js",
|
||||
"module": "dist/server-renderer.esm-bundler.js",
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '@vue/shared'
|
||||
|
||||
// leading comma for empty string ""
|
||||
const shouldIgnoreProp = makeMap(
|
||||
const shouldIgnoreProp = /*#__PURE__*/ makeMap(
|
||||
`,key,ref,innerHTML,textContent,ref_key,ref_for`,
|
||||
)
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"vite": "^5.1.4"
|
||||
"vite": "^5.2.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^4.1.1",
|
||||
|
|
|
@ -27,7 +27,7 @@ export async function downloadProject(store: ReplStore) {
|
|||
|
||||
const files = store.getFiles()
|
||||
for (const file in files) {
|
||||
if (file !== 'import-map.json') {
|
||||
if (file !== 'import-map.json' && file !== 'tsconfig.json') {
|
||||
src.file(file, files[file])
|
||||
} else {
|
||||
zip.file(file, files[file])
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Vite Vue Starter
|
||||
|
||||
This is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.org) v12+.
|
||||
This is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.org) version 18+, 20+.
|
||||
|
||||
To start:
|
||||
|
||||
|
|
|
@ -12,6 +12,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"vite": "^5.1.4"
|
||||
"vite": "^5.2.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
// serve vue to the iframe sandbox during dev.
|
||||
// @ts-expect-error
|
||||
export * from 'vue/dist/vue.runtime.esm-browser.prod.js'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/shared",
|
||||
"version": "3.4.20",
|
||||
"version": "3.4.22",
|
||||
"description": "internal utils shared across @vue packages",
|
||||
"main": "index.js",
|
||||
"module": "dist/shared.esm-bundler.js",
|
||||
|
|
|
@ -165,6 +165,9 @@ export const toNumber = (val: any): any => {
|
|||
return isNaN(n) ? val : n
|
||||
}
|
||||
|
||||
// for typeof global checks without @types/node
|
||||
declare var global: {}
|
||||
|
||||
let _globalThis: any
|
||||
export const getGlobalThis = (): any => {
|
||||
return (
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* \/\*#\_\_PURE\_\_\*\/
|
||||
* So that rollup can tree-shake them if necessary.
|
||||
*/
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function makeMap(
|
||||
str: string,
|
||||
expectsLowerCase?: boolean,
|
||||
|
|
|
@ -54,4 +54,6 @@ const replacer = (_key: string, val: any): any => {
|
|||
}
|
||||
|
||||
const stringifySymbol = (v: unknown, i: number | string = ''): any =>
|
||||
isSymbol(v) ? `Symbol(${v.description ?? i})` : v
|
||||
// Symbol.description in es2019+ so we need to cast here to pass
|
||||
// the lib: es2016 check
|
||||
isSymbol(v) ? `Symbol(${(v as any).description ?? i})` : v
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"enableNonBrowserBranches": true
|
||||
},
|
||||
"dependencies": {
|
||||
"monaco-editor": "^0.46.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
"monaco-editor": "^0.47.0",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue