mirror of https://github.com/vuejs/core.git
Merge remote-tracking branch 'upstream/minor'
This commit is contained in:
commit
b8c609f437
|
@ -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 }],
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
41
CHANGELOG.md
41
CHANGELOG.md
|
@ -1,3 +1,44 @@
|
|||
## [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)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"drips": {
|
||||
"ethereum": {
|
||||
"ownedBy": "0x5393BdeA2a020769256d9f337B0fc81a2F64850A"
|
||||
}
|
||||
}
|
||||
}
|
36
package.json
36
package.json
|
@ -6,7 +6,7 @@
|
|||
"scripts": {
|
||||
"dev": "node scripts/dev.js vue vue-vapor",
|
||||
"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 vue-vapor runtime-dom runtime-vapor compiler-dom compiler-vapor -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.24.0",
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@babel/types": "^7.24.0",
|
||||
"@codspeed/vitest-plugin": "^2.3.1",
|
||||
"@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,16 +70,16 @@
|
|||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^20.11.25",
|
||||
"@types/node": "^20.12.5",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"@vitest/coverage-istanbul": "^1.3.1",
|
||||
"@vitest/ui": "^1.2.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.4.0",
|
||||
"@vitest/coverage-istanbul": "^1.4.0",
|
||||
"@vitest/ui": "^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",
|
||||
|
@ -99,21 +99,21 @@
|
|||
"prettier": "^3.2.5",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.2",
|
||||
"puppeteer": "~22.4.1",
|
||||
"puppeteer": "~22.6.3",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^4.12.1",
|
||||
"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.10.0",
|
||||
"terser": "^5.29.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.5",
|
||||
"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.21",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
@ -46,11 +46,11 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-core#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.0",
|
||||
"@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.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]}`
|
||||
|
@ -101,7 +140,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
|
||||
|
@ -234,14 +273,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)
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ export {
|
|||
NewlineType,
|
||||
type CodegenContext,
|
||||
type CodegenResult,
|
||||
type CodegenSourceMapGenerator,
|
||||
type RawSourceMap,
|
||||
type BaseCodegenResult,
|
||||
} from './codegen'
|
||||
export {
|
||||
|
|
|
@ -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.21",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
|
|
@ -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,14 +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 } }
|
||||
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.21",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
@ -42,7 +42,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-sfc#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.0",
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@vue/compiler-core": "workspace:*",
|
||||
"@vue/compiler-dom": "workspace:*",
|
||||
"@vue/compiler-ssr": "workspace:*",
|
||||
|
@ -50,8 +50,8 @@
|
|||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.8",
|
||||
"postcss": "^8.4.35",
|
||||
"source-map-js": "^1.0.2"
|
||||
"postcss": "^8.4.38",
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.24.0",
|
||||
|
@ -59,10 +59,10 @@
|
|||
"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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
@ -382,7 +384,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) => {
|
||||
|
@ -397,7 +399,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.21",
|
||||
"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.21",
|
||||
"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> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.21",
|
||||
"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,
|
||||
|
@ -527,6 +528,12 @@ export interface ComponentInternalInstance {
|
|||
* @internal
|
||||
*/
|
||||
getCssVars?: () => Record<string, string>
|
||||
|
||||
/**
|
||||
* v2 compat only, for caching mutated $options
|
||||
* @internal
|
||||
*/
|
||||
resolvedOptions?: MergedComponentOptions
|
||||
}
|
||||
|
||||
const emptyAppContext = createAppContext()
|
||||
|
@ -779,8 +786,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)
|
||||
}
|
||||
|
@ -1009,36 +1015,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
|
||||
|
@ -1085,9 +1083,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)
|
||||
|
@ -1099,9 +1101,7 @@ export function createSetupContext(
|
|||
})
|
||||
} else {
|
||||
return {
|
||||
get attrs() {
|
||||
return getAttrsProxy(instance)
|
||||
},
|
||||
attrs: new Proxy(instance.attrs, attrsProxyHandlers),
|
||||
slots: instance.slots,
|
||||
emit: instance.emit,
|
||||
expose,
|
||||
|
|
|
@ -13,7 +13,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()
|
||||
|
|
|
@ -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'
|
||||
import { BaseWatchErrorCodes } from '@vue/reactivity'
|
||||
|
||||
|
@ -82,7 +83,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)) {
|
||||
|
@ -93,11 +94,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(
|
||||
|
@ -131,12 +138,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
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-dom",
|
||||
"version": "3.4.21",
|
||||
"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())
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/server-renderer",
|
||||
"version": "3.4.21",
|
||||
"version": "3.4.22",
|
||||
"description": "@vue/server-renderer",
|
||||
"main": "index.js",
|
||||
"module": "dist/server-renderer.esm-bundler.js",
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"vite": "^5.1.5"
|
||||
"vite": "^5.2.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^4.1.1",
|
||||
|
|
|
@ -12,6 +12,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"vite": "^5.1.5"
|
||||
"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.21",
|
||||
"version": "3.4.22",
|
||||
"description": "internal utils shared across @vue packages",
|
||||
"main": "index.js",
|
||||
"module": "dist/shared.esm-bundler.js",
|
||||
|
|
|
@ -172,6 +172,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 (
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@vue/compiler-vapor": "workspace:^",
|
||||
"monaco-editor": "^0.46.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
"monaco-editor": "^0.47.0",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ beforeEach(() => {
|
|||
Vue.configureCompat({
|
||||
MODE: 2,
|
||||
GLOBAL_MOUNT: 'suppress-warning',
|
||||
PRIVATE_APIS: 'suppress-warning',
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -331,3 +332,43 @@ test('INSTANCE_ATTR_CLASS_STYLE', () => {
|
|||
)('Anonymous'),
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('$options mutation', () => {
|
||||
const Comp = {
|
||||
props: ['id'],
|
||||
template: '<div/>',
|
||||
data() {
|
||||
return {
|
||||
foo: '',
|
||||
}
|
||||
},
|
||||
created(this: any) {
|
||||
expect(this.$options.parent).toBeDefined()
|
||||
expect(this.$options.test).toBeUndefined()
|
||||
this.$options.test = this.id
|
||||
expect(this.$options.test).toBe(this.id)
|
||||
},
|
||||
}
|
||||
|
||||
new Vue({
|
||||
template: `<div><Comp id="1"/><Comp id="2"/></div>`,
|
||||
components: { Comp },
|
||||
}).$mount()
|
||||
})
|
||||
|
||||
test('other private APIs', () => {
|
||||
new Vue({
|
||||
created() {
|
||||
expect(this.$createElement).toBeTruthy()
|
||||
},
|
||||
})
|
||||
|
||||
new Vue({
|
||||
compatConfig: {
|
||||
PRIVATE_APIS: false,
|
||||
},
|
||||
created() {
|
||||
expect(this.$createElement).toBeUndefined()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compat",
|
||||
"version": "3.4.21",
|
||||
"version": "3.4.22",
|
||||
"description": "Vue 3 compatibility build for Vue 2",
|
||||
"main": "index.js",
|
||||
"module": "dist/vue.runtime.esm-bundler.js",
|
||||
|
@ -52,9 +52,9 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/vue-compat#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.0",
|
||||
"@babel/parser": "^7.24.1",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.0.2"
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "workspace:*"
|
||||
|
|
|
@ -508,4 +508,126 @@ describe('e2e: TransitionGroup', () => {
|
|||
|
||||
expect(`<TransitionGroup> children must be keyed`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
// #5168, #7898, #9067
|
||||
test(
|
||||
'avoid set transition hooks for comment node',
|
||||
async () => {
|
||||
await page().evaluate(duration => {
|
||||
const { createApp, ref, h, createCommentVNode } = (window as any).Vue
|
||||
|
||||
const show = ref(false)
|
||||
createApp({
|
||||
template: `
|
||||
<div id="container">
|
||||
<transition-group name="test">
|
||||
<div v-for="item in items" :key="item" class="test">{{item}}</div>
|
||||
<Child key="child"/>
|
||||
</transition-group>
|
||||
</div>
|
||||
<button id="toggleBtn" @click="click">button</button>
|
||||
`,
|
||||
components: {
|
||||
Child: {
|
||||
setup() {
|
||||
return () =>
|
||||
show.value
|
||||
? h('div', { class: 'test' }, 'child')
|
||||
: createCommentVNode('v-if', true)
|
||||
},
|
||||
},
|
||||
},
|
||||
setup: () => {
|
||||
const items = ref([])
|
||||
const click = () => {
|
||||
items.value = ['a', 'b', 'c']
|
||||
setTimeout(() => {
|
||||
show.value = true
|
||||
}, duration)
|
||||
}
|
||||
return { click, items }
|
||||
},
|
||||
}).mount('#app')
|
||||
}, duration)
|
||||
|
||||
expect(await html('#container')).toBe(`<!--v-if-->`)
|
||||
|
||||
expect(await htmlWhenTransitionStart()).toBe(
|
||||
`<div class="test test-enter-from test-enter-active">a</div>` +
|
||||
`<div class="test test-enter-from test-enter-active">b</div>` +
|
||||
`<div class="test test-enter-from test-enter-active">c</div>` +
|
||||
`<!--v-if-->`,
|
||||
)
|
||||
|
||||
await transitionFinish(duration)
|
||||
await nextFrame()
|
||||
expect(await html('#container')).toBe(
|
||||
`<div class="test">a</div>` +
|
||||
`<div class="test">b</div>` +
|
||||
`<div class="test">c</div>` +
|
||||
`<div class="test test-enter-active test-enter-to">child</div>`,
|
||||
)
|
||||
|
||||
await transitionFinish(duration)
|
||||
expect(await html('#container')).toBe(
|
||||
`<div class="test">a</div>` +
|
||||
`<div class="test">b</div>` +
|
||||
`<div class="test">c</div>` +
|
||||
`<div class="test">child</div>`,
|
||||
)
|
||||
},
|
||||
E2E_TIMEOUT,
|
||||
)
|
||||
|
||||
// #4621, #4622, #5153
|
||||
test(
|
||||
'avoid set transition hooks for text node',
|
||||
async () => {
|
||||
await page().evaluate(() => {
|
||||
const { createApp, ref } = (window as any).Vue
|
||||
const app = createApp({
|
||||
template: `
|
||||
<div id="container">
|
||||
<transition-group name="test">
|
||||
<div class="test">foo</div>
|
||||
<div class="test" v-if="show">bar</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
<button id="toggleBtn" @click="click">button</button>
|
||||
`,
|
||||
setup: () => {
|
||||
const show = ref(false)
|
||||
const click = () => {
|
||||
show.value = true
|
||||
}
|
||||
return { show, click }
|
||||
},
|
||||
})
|
||||
|
||||
app.config.compilerOptions.whitespace = 'preserve'
|
||||
app.mount('#app')
|
||||
})
|
||||
|
||||
expect(await html('#container')).toBe(`<div class="test">foo</div>` + ` `)
|
||||
|
||||
expect(await htmlWhenTransitionStart()).toBe(
|
||||
`<div class="test">foo</div>` +
|
||||
` ` +
|
||||
`<div class="test test-enter-from test-enter-active">bar</div>`,
|
||||
)
|
||||
|
||||
await nextFrame()
|
||||
expect(await html('#container')).toBe(
|
||||
`<div class="test">foo</div>` +
|
||||
` ` +
|
||||
`<div class="test test-enter-active test-enter-to">bar</div>`,
|
||||
)
|
||||
|
||||
await transitionFinish(duration)
|
||||
expect(await html('#container')).toBe(
|
||||
`<div class="test">foo</div>` + ` ` + `<div class="test">bar</div>`,
|
||||
)
|
||||
},
|
||||
E2E_TIMEOUT,
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vue",
|
||||
"version": "3.4.21",
|
||||
"version": "3.4.22",
|
||||
"description": "The progressive JavaScript framework for building modern web UI.",
|
||||
"main": "index.js",
|
||||
"module": "dist/vue.runtime.esm-bundler.js",
|
||||
|
|
1028
pnpm-lock.yaml
1028
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -180,7 +180,7 @@ function createConfig(format, output, plugins = []) {
|
|||
tsconfig: path.resolve(__dirname, 'tsconfig.json'),
|
||||
sourceMap: output.sourcemap,
|
||||
minify: false,
|
||||
target: isServerRenderer || isCJSBuild ? 'es2019' : 'es2015',
|
||||
target: isServerRenderer || isCJSBuild ? 'es2019' : 'es2016',
|
||||
define: resolveDefine(),
|
||||
}),
|
||||
...resolveNodePlugins(),
|
||||
|
@ -369,7 +369,7 @@ function createMinifiedConfig(/** @type {PackageFormat} */ format) {
|
|||
terser({
|
||||
module: /^esm/.test(format),
|
||||
compress: {
|
||||
ecma: 2015,
|
||||
ecma: 2016,
|
||||
pure_getters: true,
|
||||
},
|
||||
safari10: true,
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": [],
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"stripInternal": true
|
||||
},
|
||||
"include": [
|
||||
"packages/global.d.ts",
|
||||
"packages/vue/src",
|
||||
"packages/vue-compat/src",
|
||||
"packages/compiler-core/src",
|
||||
"packages/compiler-dom/src",
|
||||
"packages/runtime-core/src",
|
||||
"packages/runtime-dom/src",
|
||||
"packages/reactivity/src",
|
||||
"packages/shared/src"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node"],
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"stripInternal": true
|
||||
},
|
||||
"include": [
|
||||
"packages/global.d.ts",
|
||||
"packages/compiler-sfc/src",
|
||||
"packages/compiler-ssr/src",
|
||||
"packages/server-renderer/src"
|
||||
]
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"stripInternal": true
|
||||
},
|
||||
"exclude": [
|
||||
"packages/*/__tests__",
|
||||
"packages/runtime-test",
|
||||
"packages/template-explorer",
|
||||
"packages/sfc-playground",
|
||||
"packages/dts-test",
|
||||
"playground"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue