diff --git a/.github/contributing.md b/.github/contributing.md
index afdae6711..da1bd5ec4 100644
--- a/.github/contributing.md
+++ b/.github/contributing.md
@@ -63,7 +63,7 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
You will need [Node.js](https://nodejs.org) **version 18.12+**, and [PNPM](https://pnpm.io) **version 8+**.
-We also recommend installing [ni](https://github.com/antfu/ni) to help switching between repos using different package managers. `ni` also provides the handy `nr` command which running npm scripts easier.
+We also recommend installing [@antfu/ni](https://github.com/antfu/ni) to help switching between repos using different package managers. `ni` also provides the handy `nr` command which running npm scripts easier.
After cloning the repo, run:
@@ -86,11 +86,11 @@ The project uses [simple-git-hooks](https://github.com/toplenboren/simple-git-ho
- Type check the entire project
- Automatically format changed files using Prettier
-- Verify commit message format (logic in `scripts/verifyCommit.js`)
+- Verify commit message format (logic in `scripts/verify-commit.js`)
## Scripts
-**The examples below will be using the `nr` command from the [ni](https://github.com/antfu/ni) package.** You can also use plain `npm run`, but you will need to pass all additional arguments after the command after an extra `--`. For example, `nr build runtime --all` is equivalent to `npm run build -- runtime --all`.
+**The examples below will be using the `nr` command from the [@antfu/ni](https://github.com/antfu/ni) package.** You can also use plain `npm run`, but you will need to pass all additional arguments after the command after an extra `--`. For example, `nr build runtime --all` is equivalent to `npm run build -- runtime --all`.
The `run-s` and `run-p` commands found in some scripts are from [npm-run-all](https://github.com/mysticatea/npm-run-all) for orchestrating multiple scripts. `run-s` means "run in sequence" while `run-p` means "run in parallel".
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f91a87abe..d08f41e05 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,56 @@
+# [3.4.0-rc.2](https://github.com/vuejs/core/compare/v3.4.0-rc.1...v3.4.0-rc.2) (2023-12-26)
+
+
+### Bug Fixes
+
+* **deps:** update dependency @vue/repl to ^3.1.0 ([#9911](https://github.com/vuejs/core/issues/9911)) ([f96c413](https://github.com/vuejs/core/commit/f96c413e8ef2f24cacda5bb499492922f62c6e8b))
+* **types:** fix distribution of union types when unwrapping setup bindings ([#9909](https://github.com/vuejs/core/issues/9909)) ([0695c69](https://github.com/vuejs/core/commit/0695c69e0dfaf99882a623fe75b433c9618ea648)), closes [#9903](https://github.com/vuejs/core/issues/9903)
+* **warning:** ensure prod hydration warnings actually work ([b4ebe7a](https://github.com/vuejs/core/commit/b4ebe7ae8b904f28cdda33caf87bc05718d3a08a))
+
+
+### Features
+
+* **compiler-sfc:** export aggregated error messages for compiler-core and compiler-dom ([25c726e](https://github.com/vuejs/core/commit/25c726eca81fc384b41fafbeba5e8dfcda1f030f))
+
+
+
+# [3.4.0-rc.1](https://github.com/vuejs/core/compare/v3.4.0-beta.4...v3.4.0-rc.1) (2023-12-25)
+
+
+### Bug Fixes
+
+* **compiler-core:** fix parsing `',
+ { parseMode: 'sfc' }
+ )
+ const element = ast.children[0] as ElementNode
+ expect(element).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ ns: Namespaces.HTML,
+ tag: 'script',
+ tagType: ElementTypes.ELEMENT,
+ codegenNode: undefined,
+ children: [],
+ innerLoc: {
+ start: { column: 67, line: 1, offset: 66 },
+ end: { column: 67, line: 1, offset: 66 }
+ },
+ props: [
+ {
+ loc: {
+ source: 'setup',
+ end: { column: 14, line: 1, offset: 13 },
+ start: { column: 9, line: 1, offset: 8 }
+ },
+ name: 'setup',
+ nameLoc: {
+ source: 'setup',
+ end: { column: 14, line: 1, offset: 13 },
+ start: { column: 9, line: 1, offset: 8 }
+ },
+ type: NodeTypes.ATTRIBUTE,
+ value: undefined
+ },
+ {
+ loc: {
+ source: 'lang="ts"',
+ end: { column: 24, line: 1, offset: 23 },
+ start: { column: 15, line: 1, offset: 14 }
+ },
+ name: 'lang',
+ nameLoc: {
+ source: 'lang',
+ end: { column: 19, line: 1, offset: 18 },
+ start: { column: 15, line: 1, offset: 14 }
+ },
+ type: NodeTypes.ATTRIBUTE,
+ value: {
+ content: 'ts',
+ loc: {
+ source: '"ts"',
+ end: { column: 24, line: 1, offset: 23 },
+ start: { column: 20, line: 1, offset: 19 }
+ },
+ type: NodeTypes.TEXT
+ }
+ },
+ {
+ loc: {
+ source: 'generic="T extends Record"',
+ end: { column: 66, line: 1, offset: 65 },
+ start: { column: 25, line: 1, offset: 24 }
+ },
+ name: 'generic',
+ nameLoc: {
+ source: 'generic',
+ end: { column: 32, line: 1, offset: 31 },
+ start: { column: 25, line: 1, offset: 24 }
+ },
+ type: NodeTypes.ATTRIBUTE,
+ value: {
+ content: 'T extends Record',
+ loc: {
+ source: '"T extends Record"',
+ end: { column: 66, line: 1, offset: 65 },
+ start: { column: 33, line: 1, offset: 32 }
+ },
+ type: NodeTypes.TEXT
+ }
+ }
+ ]
+ })
+ })
+
test('multiple attributes', () => {
const ast = baseParse('')
const element = ast.children[0] as ElementNode
diff --git a/packages/compiler-core/__tests__/utils.spec.ts b/packages/compiler-core/__tests__/utils.spec.ts
index 7baa1ae89..6ef65af63 100644
--- a/packages/compiler-core/__tests__/utils.spec.ts
+++ b/packages/compiler-core/__tests__/utils.spec.ts
@@ -95,6 +95,10 @@ describe('isMemberExpression', () => {
expect(fn(`123[a]`)).toBe(true)
expect(fn(`foo() as string`)).toBe(false)
expect(fn(`a + b as string`)).toBe(false)
+ // #9865
+ expect(fn('""')).toBe(false)
+ expect(fn('undefined')).toBe(false)
+ expect(fn('null')).toBe(false)
})
})
diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json
index 9f7ef615f..b321d763d 100644
--- a/packages/compiler-core/package.json
+++ b/packages/compiler-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-core",
- "version": "3.4.0-beta.3",
+ "version": "3.4.0-rc.2",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
@@ -32,13 +32,13 @@
},
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-core#readme",
"dependencies": {
- "@babel/parser": "^7.23.5",
+ "@babel/parser": "^7.23.6",
"@vue/shared": "workspace:*",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
},
"devDependencies": {
- "@babel/types": "^7.23.5"
+ "@babel/types": "^7.23.6"
}
}
diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts
index a9c1ebe9c..c00c18b3f 100644
--- a/packages/compiler-core/src/babelUtils.ts
+++ b/packages/compiler-core/src/babelUtils.ts
@@ -55,14 +55,24 @@ export function walkIdentifiers(
// mark property in destructure pattern
;(node as any).inPattern = true
} else if (isFunctionType(node)) {
- // walk function expressions and add its arguments to known identifiers
- // so that we don't prefix them
- walkFunctionParams(node, id => markScopeIdentifier(node, id, knownIds))
+ if (node.scopeIds) {
+ node.scopeIds.forEach(id => markKnownIds(id, knownIds))
+ } else {
+ // walk function expressions and add its arguments to known identifiers
+ // so that we don't prefix them
+ walkFunctionParams(node, id =>
+ markScopeIdentifier(node, id, knownIds)
+ )
+ }
} else if (node.type === 'BlockStatement') {
- // #3445 record block-level local variables
- walkBlockDeclarations(node, id =>
- markScopeIdentifier(node, id, knownIds)
- )
+ if (node.scopeIds) {
+ node.scopeIds.forEach(id => markKnownIds(id, knownIds))
+ } else {
+ // #3445 record block-level local variables
+ walkBlockDeclarations(node, id =>
+ markScopeIdentifier(node, id, knownIds)
+ )
+ }
}
},
leave(node: Node & { scopeIds?: Set }, parent: Node | undefined) {
@@ -227,6 +237,14 @@ export function extractIdentifiers(
return nodes
}
+function markKnownIds(name: string, knownIds: Record) {
+ if (name in knownIds) {
+ knownIds[name]++
+ } else {
+ knownIds[name] = 1
+ }
+}
+
function markScopeIdentifier(
node: Node & { scopeIds?: Set },
child: Identifier,
@@ -236,11 +254,7 @@ function markScopeIdentifier(
if (node.scopeIds && node.scopeIds.has(name)) {
return
}
- if (name in knownIds) {
- knownIds[name]++
- } else {
- knownIds[name] = 1
- }
+ markKnownIds(name, knownIds)
;(node.scopeIds || (node.scopeIds = new Set())).add(name)
}
diff --git a/packages/compiler-core/src/parser.ts b/packages/compiler-core/src/parser.ts
index 2250719f5..923d161dd 100644
--- a/packages/compiler-core/src/parser.ts
+++ b/packages/compiler-core/src/parser.ts
@@ -145,13 +145,6 @@ const tokenizer = new Tokenizer(stack, {
loc: getLoc(start - 1, end),
codegenNode: undefined
}
- if (tokenizer.inSFCRoot) {
- // in SFC mode, generate locations for root-level tags' inner content.
- currentOpenTag.innerLoc = getLoc(
- end + fastForward(end, CharCodes.Gt) + 1,
- end
- )
- }
},
onopentagend(end) {
@@ -572,6 +565,10 @@ function getSlice(start: number, end: number) {
}
function endOpenTag(end: number) {
+ if (tokenizer.inSFCRoot) {
+ // in SFC mode, generate locations for root-level tags' inner content.
+ currentOpenTag!.innerLoc = getLoc(end + 1, end + 1)
+ }
addNode(currentOpenTag!)
const { tag, ns } = currentOpenTag!
if (ns === Namespaces.HTML && currentOptions.isPreTag(tag)) {
@@ -616,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 + fastForward(end, CharCodes.Gt) + 1)
+ setLocEnd(el.loc, end + 1)
}
if (tokenizer.inSFCRoot) {
@@ -738,17 +735,6 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
}
}
-function fastForward(start: number, c: number) {
- let offset = 0
- while (
- currentInput.charCodeAt(start + offset) !== CharCodes.Gt &&
- start + offset < currentInput.length
- ) {
- offset++
- }
- return offset
-}
-
function backTrack(index: number, c: number) {
let i = index
while (currentInput.charCodeAt(i) !== c && i >= 0) i--
diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts
index 09185bdca..cb4067c4d 100644
--- a/packages/compiler-core/src/utils.ts
+++ b/packages/compiler-core/src/utils.ts
@@ -169,7 +169,7 @@ export const isMemberExpressionNode = __BROWSER__
return (
ret.type === 'MemberExpression' ||
ret.type === 'OptionalMemberExpression' ||
- ret.type === 'Identifier'
+ (ret.type === 'Identifier' && ret.name !== 'undefined')
)
} catch (e) {
return false
diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json
index 05cb56509..59e61df0c 100644
--- a/packages/compiler-dom/package.json
+++ b/packages/compiler-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-dom",
- "version": "3.4.0-beta.3",
+ "version": "3.4.0-rc.2",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",
diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
index 01267cd42..a2d24599c 100644
--- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
@@ -1383,6 +1383,21 @@ return { D, C, B, Foo }
})"
`;
+exports[`SFC compile `)
+ assertCode(content)
+ })
})
describe('async/await detection', () => {
diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
index b67423e0a..a8074419c 100644
--- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
@@ -939,6 +939,34 @@ describe('resolveType', () => {
manufacturer: ['Object']
})
})
+
+ // #9871
+ test('shared generics with different args', () => {
+ const files = {
+ '/foo.ts': `export interface Foo { value: T }`
+ }
+ const { props } = resolve(
+ `import type { Foo } from './foo'
+ defineProps>()`,
+ files,
+ undefined,
+ `/One.vue`
+ )
+ expect(props).toStrictEqual({
+ value: ['String']
+ })
+ const { props: props2 } = resolve(
+ `import type { Foo } from './foo'
+ defineProps>()`,
+ files,
+ undefined,
+ `/Two.vue`,
+ false /* do not invalidate cache */
+ )
+ expect(props2).toStrictEqual({
+ value: ['Number']
+ })
+ })
})
describe('errors', () => {
@@ -1012,7 +1040,8 @@ function resolve(
code: string,
files: Record = {},
options?: Partial,
- sourceFileName: string = '/Test.vue'
+ sourceFileName: string = '/Test.vue',
+ invalidateCache = true
) {
const { descriptor } = parse(``, {
filename: sourceFileName
@@ -1030,8 +1059,10 @@ function resolve(
...options
})
- for (const file in files) {
- invalidateTypeCache(file)
+ if (invalidateCache) {
+ for (const file in files) {
+ invalidateTypeCache(file)
+ }
}
// ctx.userImports is collected when calling compileScript(), but we are
diff --git a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
index 95e1b7aa8..26a8b573f 100644
--- a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
@@ -4,6 +4,7 @@ import {
SFCTemplateCompileOptions
} from '../src/compileTemplate'
import { parse, SFCTemplateBlock } from '../src/parse'
+import { compileScript } from '../src'
function compile(opts: Omit) {
return compileTemplate({
@@ -397,6 +398,35 @@ test('dynamic v-on + static v-on should merged', () => {
expect(result.code).toMatchSnapshot()
})
+// #9853 regression found in Nuxt tests
+// walkIdentifiers can get called multiple times on the same node
+// due to #9729 calling it during SFC template usage check.
+// conditions needed:
+// 1. `
+
+ {{ list.map((t, index) => ({ t: t })) }}
+
+ `
+ const { descriptor } = parse(src)
+ // compileScript triggers importUsageCheck
+ compileScript(descriptor, { id: 'xxx' })
+ const { code } = compileTemplate({
+ id: 'xxx',
+ filename: 'test.vue',
+ ast: descriptor.template!.ast,
+ source: descriptor.template!.content
+ })
+ expect(code).not.toMatch(`_ctx.t`)
+})
+
interface Pos {
line: number
column: number
diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json
index 68455b8b7..b4f64f9cb 100644
--- a/packages/compiler-sfc/package.json
+++ b/packages/compiler-sfc/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-sfc",
- "version": "3.4.0-beta.3",
+ "version": "3.4.0-rc.2",
"description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js",
"module": "dist/compiler-sfc.esm-browser.js",
@@ -32,7 +32,7 @@
},
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-sfc#readme",
"dependencies": {
- "@babel/parser": "^7.23.5",
+ "@babel/parser": "^7.23.6",
"@vue/compiler-core": "workspace:*",
"@vue/compiler-dom": "workspace:*",
"@vue/compiler-ssr": "workspace:*",
@@ -43,7 +43,7 @@
"source-map-js": "^1.0.2"
},
"devDependencies": {
- "@babel/types": "^7.23.5",
+ "@babel/types": "^7.23.6",
"@vue/consolidate": "^0.17.3",
"hash-sum": "^2.0.0",
"lru-cache": "^10.1.0",
diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts
index 1603f1d0e..f40cf2053 100644
--- a/packages/compiler-sfc/src/compileScript.ts
+++ b/packages/compiler-sfc/src/compileScript.ts
@@ -834,6 +834,7 @@ export function compileScript(
// inline it right here
const { code, ast, preamble, tips, errors } = compileTemplate({
filename,
+ ast: sfc.template.ast,
source: sfc.template.content,
inMap: sfc.template.map,
...options.templateOptions,
diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts
index 7297acbed..98b01c4be 100644
--- a/packages/compiler-sfc/src/index.ts
+++ b/packages/compiler-sfc/src/index.ts
@@ -12,6 +12,17 @@ import { SFCParseResult, parseCache as _parseCache } from './parse'
// #9521 export parseCache as a simple map to avoid exposing LRU types
export const parseCache = _parseCache as Map
+// error messages
+import {
+ errorMessages as coreErrorMessages,
+ DOMErrorMessages
+} from '@vue/compiler-dom'
+
+export const errorMessages = {
+ ...coreErrorMessages,
+ ...DOMErrorMessages
+}
+
// Utilities
export { parse as babelParse } from '@babel/parser'
import MagicString from 'magic-string'
diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts
index f38374bd5..3df3bdafb 100644
--- a/packages/compiler-sfc/src/script/resolveType.ts
+++ b/packages/compiler-sfc/src/script/resolveType.ts
@@ -115,7 +115,7 @@ export class TypeScope {
public types: Record = Object.create(null),
public declares: Record = Object.create(null)
) {}
-
+ isGenericScope = false
resolvedImportSources: Record = Object.create(null)
exportedTypes: Record = Object.create(null)
exportedDeclares: Record = Object.create(null)
@@ -146,15 +146,17 @@ export function resolveTypeElements(
scope?: TypeScope,
typeParameters?: Record
): ResolvedElements {
- if (node._resolvedElements) {
+ const canCache = !typeParameters
+ if (canCache && node._resolvedElements) {
return node._resolvedElements
}
- return (node._resolvedElements = innerResolveTypeElements(
+ const resolved = innerResolveTypeElements(
ctx,
node,
node._ownerScope || scope || ctxToScope(ctx),
typeParameters
- ))
+ )
+ return canCache ? (node._resolvedElements = resolved) : resolved
}
function innerResolveTypeElements(
@@ -215,17 +217,18 @@ function innerResolveTypeElements(
}
const resolved = resolveTypeReference(ctx, node, scope)
if (resolved) {
- const typeParams: Record = Object.create(null)
+ let typeParams: Record | undefined
if (
(resolved.type === 'TSTypeAliasDeclaration' ||
resolved.type === 'TSInterfaceDeclaration') &&
resolved.typeParameters &&
node.typeParameters
) {
+ typeParams = Object.create(null)
resolved.typeParameters.params.forEach((p, i) => {
let param = typeParameters && typeParameters[p.name]
if (!param) param = node.typeParameters!.params[i]
- typeParams[p.name] = param
+ typeParams![p.name] = param
})
}
return resolveTypeElements(
@@ -322,6 +325,7 @@ function typeElementsToMap(
// capture generic parameters on node's scope
if (typeParameters) {
scope = createChildScope(scope)
+ scope.isGenericScope = true
Object.assign(scope.types, typeParameters)
}
;(e as MaybeWithScope)._ownerScope = scope
@@ -694,16 +698,18 @@ function resolveTypeReference(
name?: string,
onlyExported = false
): ScopeTypeNode | undefined {
- if (node._resolvedReference) {
+ const canCache = !scope?.isGenericScope
+ if (canCache && node._resolvedReference) {
return node._resolvedReference
}
- return (node._resolvedReference = innerResolveTypeReference(
+ const resolved = innerResolveTypeReference(
ctx,
scope || ctxToScope(ctx),
name || getReferenceName(node),
node,
onlyExported
- ))
+ )
+ return canCache ? (node._resolvedReference = resolved) : resolved
}
function innerResolveTypeReference(
diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json
index 6be50fec9..d3303dc6a 100644
--- a/packages/compiler-ssr/package.json
+++ b/packages/compiler-ssr/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-ssr",
- "version": "3.4.0-beta.3",
+ "version": "3.4.0-rc.2",
"description": "@vue/compiler-ssr",
"main": "dist/compiler-ssr.cjs.js",
"types": "dist/compiler-ssr.d.ts",
diff --git a/packages/dts-test/reactivity.test-d.ts b/packages/dts-test/reactivity.test-d.ts
index 431bbafbd..e0d8e207f 100644
--- a/packages/dts-test/reactivity.test-d.ts
+++ b/packages/dts-test/reactivity.test-d.ts
@@ -77,6 +77,18 @@ describe('should unwrap Map correctly', () => {
expectType(wm2.get({})!.wrap)
})
+describe('should unwrap extended Map correctly', () => {
+ class ExtendendMap1 extends Map }> {
+ foo = ref('foo')
+ bar = 1
+ }
+
+ const emap1 = reactive(new ExtendendMap1())
+ expectType(emap1.foo)
+ expectType(emap1.bar)
+ expectType(emap1.get('a')!.wrap)
+})
+
describe('should unwrap Set correctly', () => {
const set = reactive(new Set[>())
expectType>>(set)
@@ -90,3 +102,14 @@ describe('should unwrap Set correctly', () => {
const ws2 = reactive(new WeakSet<{ wrap: Ref }>())
expectType>(ws2)
})
+
+describe('should unwrap extended Set correctly', () => {
+ class ExtendendSet1 extends Set<{ wrap: Ref }> {
+ foo = ref('foo')
+ bar = 1
+ }
+
+ const eset1 = reactive(new ExtendendSet1())
+ expectType(eset1.foo)
+ expectType(eset1.bar)
+})
diff --git a/packages/dts-test/ref.test-d.ts b/packages/dts-test/ref.test-d.ts
index 62bad77c2..fc3c9e65e 100644
--- a/packages/dts-test/ref.test-d.ts
+++ b/packages/dts-test/ref.test-d.ts
@@ -244,13 +244,19 @@ expectType(p1)
// proxyRefs: `ShallowUnwrapRef`
const r2 = {
a: ref(1),
+ c: computed(() => 1),
+ u: undefined,
obj: {
k: ref('foo')
- }
+ },
+ union: Math.random() > 0 - 5 ? ref({ name: 'yo' }) : null
}
const p2 = proxyRefs(r2)
expectType(p2.a)
+expectType(p2.c)
+expectType(p2.u)
expectType][>(p2.obj.k)
+expectType<{ name: string } | null>(p2.union)
// toRef and toRefs
{
diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json
index 8158708c5..5844ea459 100644
--- a/packages/reactivity/package.json
+++ b/packages/reactivity/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/reactivity",
- "version": "3.4.0-beta.3",
+ "version": "3.4.0-rc.2",
"description": "@vue/reactivity",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts
index 46c734ee0..6c8ec9644 100644
--- a/packages/reactivity/src/ref.ts
+++ b/packages/reactivity/src/ref.ts
@@ -490,15 +490,11 @@ type BaseTypes = string | number | boolean
export interface RefUnwrapBailTypes {}
export type ShallowUnwrapRef = {
- [K in keyof T]: T[K] extends Ref
- ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined
- : T[K] extends Ref | undefined
- ? unknown extends V
- ? undefined
- : V | undefined
- : T[K]
+ [K in keyof T]: DistrubuteRef
}
+type DistrubuteRef = T extends Ref ? V : T
+
export type UnwrapRef = T extends ShallowRef
? V
: T extends Ref
@@ -513,13 +509,14 @@ export type UnwrapRefSimple = T extends
| { [RawSymbol]?: true }
? T
: T extends Map
- ? Map>
+ ? Map> & UnwrapRef>>
: T extends WeakMap
- ? WeakMap>
+ ? WeakMap> &
+ UnwrapRef>>
: T extends Set
- ? Set>
+ ? Set> & UnwrapRef>>
: T extends WeakSet
- ? WeakSet>
+ ? WeakSet> & UnwrapRef>>
: T extends ReadonlyArray
? { [K in keyof T]: UnwrapRefSimple }
: T extends object & { [ShallowReactiveMarker]?: never }
diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts
index fa069b925..07f19527c 100644
--- a/packages/runtime-core/__tests__/hydration.spec.ts
+++ b/packages/runtime-core/__tests__/hydration.spec.ts
@@ -1406,6 +1406,14 @@ describe('SSR hydration', () => {
mountWithHydration(``, () =>
h('div', { class: 'foo bar' })
)
+ // SVG classes
+ mountWithHydration(``, () =>
+ h('svg', { class: 'foo bar' })
+ )
+ // class with different order
+ mountWithHydration(``, () =>
+ h('div', { class: 'bar foo' })
+ )
expect(`Hydration class mismatch`).not.toHaveBeenWarned()
mountWithHydration(``, () =>
h('div', { class: 'foo' })
diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json
index d9f5993f8..d6baf56f3 100644
--- a/packages/runtime-core/package.json
+++ b/packages/runtime-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-core",
- "version": "3.4.0-beta.3",
+ "version": "3.4.0-rc.2",
"description": "@vue/runtime-core",
"main": "index.js",
"module": "dist/runtime-core.esm-bundler.js",
diff --git a/packages/runtime-core/src/devtools.ts b/packages/runtime-core/src/devtools.ts
index 240a4aa04..bf6f09f32 100644
--- a/packages/runtime-core/src/devtools.ts
+++ b/packages/runtime-core/src/devtools.ts
@@ -21,7 +21,7 @@ enum DevtoolsHooks {
PERFORMANCE_END = 'perf:end'
}
-interface DevtoolsHook {
+export interface DevtoolsHook {
enabled?: boolean
emit: (event: string, ...payload: any[]) => void
on: (event: string, handler: Function) => void
diff --git a/packages/runtime-core/src/errorHandling.ts b/packages/runtime-core/src/errorHandling.ts
index aff4f5567..d2438ba8d 100644
--- a/packages/runtime-core/src/errorHandling.ts
+++ b/packages/runtime-core/src/errorHandling.ts
@@ -55,7 +55,7 @@ export const ErrorTypeStrings: Record = {
[ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
[ErrorCodes.SCHEDULER]:
'scheduler flush. This is likely a Vue internals bug. ' +
- 'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core'
+ 'Please open an issue at https://github.com/vuejs/core .'
}
export type ErrorTypes = LifecycleHooks | ErrorCodes
diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts
index b873a37dc..d42a9952f 100644
--- a/packages/runtime-core/src/hydration.ts
+++ b/packages/runtime-core/src/hydration.ts
@@ -718,9 +718,11 @@ function propHasMismatch(el: Element, key: string, clientValue: any): boolean {
let actual: any
let expected: any
if (key === 'class') {
- actual = el.className
- expected = normalizeClass(clientValue)
- if (actual !== expected) {
+ // classes might be in different order, but that doesn't affect cascade
+ // so we just need to check if the class lists contain the same classes.
+ actual = toClassSet(el.getAttribute('class') || '')
+ expected = toClassSet(normalizeClass(clientValue))
+ if (!isSetEqual(actual, expected)) {
mismatchType = mismatchKey = `class`
}
} else if (key === 'style') {
@@ -765,3 +767,19 @@ function propHasMismatch(el: Element, key: string, clientValue: any): boolean {
}
return false
}
+
+function toClassSet(str: string): Set {
+ return new Set(str.trim().split(/\s+/))
+}
+
+function isSetEqual(a: Set, b: Set): boolean {
+ if (a.size !== b.size) {
+ return false
+ }
+ for (const s of a) {
+ if (!b.has(s)) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index 9770e6ba0..98d1fe849 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -116,7 +116,9 @@ export { useSSRContext, ssrContextKey } from './helpers/useSsrContext'
export { createRenderer, createHydrationRenderer } from './renderer'
export { queuePostFlushCb } from './scheduler'
-export { warn } from './warning'
+import { warn as _warn } from './warning'
+export const warn = (__DEV__ ? _warn : NOOP) as typeof _warn
+
/** @internal */
export { assertNumber } from './warning'
export {
@@ -146,11 +148,22 @@ import { ErrorTypeStrings as _ErrorTypeStrings } from './errorHandling'
* @internal
*/
export const ErrorTypeStrings = (
- __ESM_BUNDLER__ || __DEV__ ? _ErrorTypeStrings : null
+ __ESM_BUNDLER__ || __NODE_JS__ || __DEV__ ? _ErrorTypeStrings : null
) as typeof _ErrorTypeStrings
// For devtools
-export { devtools, setDevtoolsHook } from './devtools'
+import {
+ devtools as _devtools,
+ setDevtoolsHook as _setDevtoolsHook,
+ DevtoolsHook
+} from './devtools'
+
+export const devtools = (
+ __DEV__ || __FEATURE_PROD_DEVTOOLS__ ? _devtools : undefined
+) as DevtoolsHook
+export const setDevtoolsHook = (
+ __DEV__ || __FEATURE_PROD_DEVTOOLS__ ? _setDevtoolsHook : NOOP
+) as typeof _setDevtoolsHook
// Types -------------------------------------------------------------------------
@@ -378,6 +391,7 @@ import {
softAssertCompatEnabled
} from './compat/compatConfig'
import { resolveFilter as _resolveFilter } from './helpers/resolveAssets'
+import { NOOP } from '@vue/shared'
/**
* @internal only exposed in compat builds
diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts
index 5b096b563..0b3175810 100644
--- a/packages/runtime-core/src/scheduler.ts
+++ b/packages/runtime-core/src/scheduler.ts
@@ -1,7 +1,6 @@
-import { ErrorCodes, callWithErrorHandling } from './errorHandling'
+import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
import { Awaited, isArray, NOOP } from '@vue/shared'
import { ComponentInternalInstance, getComponentName } from './component'
-import { warn } from './warning'
export interface SchedulerJob extends Function {
id?: number
@@ -271,14 +270,16 @@ function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) {
if (count > RECURSION_LIMIT) {
const instance = fn.ownerInstance
const componentName = instance && getComponentName(instance.type)
- warn(
+ handleError(
`Maximum recursive updates exceeded${
componentName ? ` in component <${componentName}>` : ``
}. ` +
`This means you have a reactive effect that is mutating its own ` +
`dependencies and thus recursively triggering itself. Possible sources ` +
`include component template, render function, updated hook or ` +
- `watcher source function.`
+ `watcher source function.`,
+ null,
+ ErrorCodes.APP_ERROR_HANDLER
)
return true
} else {
diff --git a/packages/runtime-core/src/warning.ts b/packages/runtime-core/src/warning.ts
index f22c18b8a..9007da2a7 100644
--- a/packages/runtime-core/src/warning.ts
+++ b/packages/runtime-core/src/warning.ts
@@ -30,8 +30,6 @@ export function popWarningContext() {
}
export function warn(msg: string, ...args: any[]) {
- if (!__DEV__) return
-
// avoid props formatting or warn handler tracking deps that might be mutated
// during patch, leading to infinite recursion.
pauseTracking()
diff --git a/packages/runtime-dom/__tests__/customizedBuiltIn.spec.ts b/packages/runtime-dom/__tests__/customizedBuiltIn.spec.ts
index 89ccf02bc..09542a355 100644
--- a/packages/runtime-dom/__tests__/customizedBuiltIn.spec.ts
+++ b/packages/runtime-dom/__tests__/customizedBuiltIn.spec.ts
@@ -1,8 +1,8 @@
-import { type SpyInstance } from 'vitest'
+import { type MockInstance } from 'vitest'
import { render, h } from '@vue/runtime-dom'
describe('customized built-in elements support', () => {
- let createElement: SpyInstance
+ let createElement: MockInstance
afterEach(() => {
createElement.mockRestore()
})
diff --git a/packages/runtime-dom/__tests__/directives/vOn.spec.ts b/packages/runtime-dom/__tests__/directives/vOn.spec.ts
index 2a4b02478..33ffec637 100644
--- a/packages/runtime-dom/__tests__/directives/vOn.spec.ts
+++ b/packages/runtime-dom/__tests__/directives/vOn.spec.ts
@@ -135,4 +135,32 @@ describe('runtime-dom: v-on directive', () => {
handler(event, 'value', true)
expect(fn).toBeCalledWith(event, 'value', true)
})
+
+ it('withKeys cache wrapped listener separately for different modifiers', () => {
+ const el1 = document.createElement('button')
+ const el2 = document.createElement('button')
+ const fn = vi.fn()
+ const handler1 = withKeys(fn, ['a'])
+ const handler2 = withKeys(fn, ['b'])
+ expect(handler1 === handler2).toBe(false)
+ patchEvent(el1, 'onKeyup', null, handler1, null)
+ patchEvent(el2, 'onKeyup', null, handler2, null)
+ triggerEvent(el1, 'keyup', e => (e.key = 'a'))
+ triggerEvent(el2, 'keyup', e => (e.key = 'b'))
+ expect(fn).toBeCalledTimes(2)
+ })
+
+ it('withModifiers cache wrapped listener separately for different modifiers', () => {
+ const el1 = document.createElement('button')
+ const el2 = document.createElement('button')
+ const fn = vi.fn()
+ const handler1 = withModifiers(fn, ['ctrl'])
+ const handler2 = withModifiers(fn, ['shift'])
+ expect(handler1 === handler2).toBe(false)
+ patchEvent(el1, 'onClick', null, handler1, null)
+ patchEvent(el2, 'onClick', null, handler2, null)
+ triggerEvent(el1, 'click', e => (e.ctrlKey = true))
+ triggerEvent(el2, 'click', e => (e.shiftKey = true))
+ expect(fn).toBeCalledTimes(2)
+ })
})
diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json
index bca1dbd6a..9713c3757 100644
--- a/packages/runtime-dom/package.json
+++ b/packages/runtime-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-dom",
- "version": "3.4.0-beta.3",
+ "version": "3.4.0-rc.2",
"description": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js",
diff --git a/packages/runtime-dom/src/directives/vOn.ts b/packages/runtime-dom/src/directives/vOn.ts
index 8054efb9e..823c3fb4c 100644
--- a/packages/runtime-dom/src/directives/vOn.ts
+++ b/packages/runtime-dom/src/directives/vOn.ts
@@ -35,12 +35,14 @@ const modifierGuards: Record<
export const withModifiers = <
T extends (event: Event, ...args: unknown[]) => any
>(
- fn: T & { _withMods?: T },
+ fn: T & { _withMods?: { [key: string]: T } },
modifiers: string[]
) => {
+ const cache = fn._withMods || (fn._withMods = {})
+ const cacheKey = modifiers.join('.')
return (
- fn._withMods ||
- (fn._withMods = ((event, ...args) => {
+ cache[cacheKey] ||
+ (cache[cacheKey] = ((event, ...args) => {
for (let i = 0; i < modifiers.length; i++) {
const guard = modifierGuards[modifiers[i]]
if (guard && guard(event, modifiers)) return
@@ -66,7 +68,7 @@ const keyNames: Record = {
* @private
*/
export const withKeys = any>(
- fn: T & { _withKeys?: T },
+ fn: T & { _withKeys?: { [k: string]: T } },
modifiers: string[]
) => {
let globalKeyCodes: LegacyConfig['keyCodes']
@@ -88,9 +90,12 @@ export const withKeys = any>(
}
}
+ const cache: { [k: string]: T } = fn._withKeys || (fn._withKeys = {})
+ const cacheKey = modifiers.join('.')
+
return (
- fn._withKeys ||
- (fn._withKeys = (event => {
+ cache[cacheKey] ||
+ (cache[cacheKey] = (event => {
if (!('key' in event)) {
return
}
diff --git a/packages/server-renderer/package.json b/packages/server-renderer/package.json
index 22ca44259..65cd22f75 100644
--- a/packages/server-renderer/package.json
+++ b/packages/server-renderer/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/server-renderer",
- "version": "3.4.0-beta.3",
+ "version": "3.4.0-rc.2",
"description": "@vue/server-renderer",
"main": "index.js",
"module": "dist/server-renderer.esm-bundler.js",
diff --git a/packages/sfc-playground/package.json b/packages/sfc-playground/package.json
index 6900eaf78..0755c46fb 100644
--- a/packages/sfc-playground/package.json
+++ b/packages/sfc-playground/package.json
@@ -13,7 +13,7 @@
"vite": "^5.0.5"
},
"dependencies": {
- "@vue/repl": "^3.0.0",
+ "@vue/repl": "^3.1.0",
"file-saver": "^2.0.5",
"jszip": "^3.10.1",
"vue": "workspace:*"
diff --git a/packages/sfc-playground/src/App.vue b/packages/sfc-playground/src/App.vue
index e5e52dd42..e1a279de6 100644
--- a/packages/sfc-playground/src/App.vue
+++ b/packages/sfc-playground/src/App.vue
@@ -26,13 +26,17 @@ const setVH = () => {
window.addEventListener('resize', setVH)
setVH()
-const useDevMode = ref(true)
+const useProdMode = ref(false)
const useSSRMode = ref(false)
let hash = location.hash.slice(1)
if (hash.startsWith('__DEV__')) {
hash = hash.slice(7)
- useDevMode.value = true
+ useProdMode.value = false
+}
+if (hash.startsWith('__PROD__')) {
+ hash = hash.slice(8)
+ useProdMode.value = true
}
if (hash.startsWith('__SSR__')) {
hash = hash.slice(7)
@@ -41,7 +45,7 @@ if (hash.startsWith('__SSR__')) {
const store = new ReplStore({
serializedState: hash,
- productionMode: !useDevMode.value,
+ productionMode: useProdMode.value,
defaultVueRuntimeURL: import.meta.env.PROD
? `${location.origin}/vue.runtime.esm-browser.js`
: `${location.origin}/src/vue-dev-proxy`,
@@ -56,15 +60,15 @@ const store = new ReplStore({
// enable experimental features
const sfcOptions: SFCOptions = {
script: {
- inlineTemplate: !useDevMode.value,
- isProd: !useDevMode.value,
+ inlineTemplate: useProdMode.value,
+ isProd: useProdMode.value,
propsDestructure: true
},
style: {
- isProd: !useDevMode.value
+ isProd: useProdMode.value
},
template: {
- isProd: !useDevMode.value
+ isProd: useProdMode.value
}
}
@@ -73,18 +77,19 @@ watchEffect(() => {
const newHash = store
.serialize()
.replace(/^#/, useSSRMode.value ? `#__SSR__` : `#`)
- .replace(/^#/, useDevMode.value ? `#__DEV__` : `#`)
+ .replace(/^#/, useProdMode.value ? `#__PROD__` : `#`)
history.replaceState({}, '', newHash)
})
-function toggleDevMode() {
- const dev = (useDevMode.value = !useDevMode.value)
+function toggleProdMode() {
+ const isProd = (useProdMode.value = !useProdMode.value)
sfcOptions.script!.inlineTemplate =
sfcOptions.script!.isProd =
sfcOptions.template!.isProd =
sfcOptions.style!.isProd =
- !dev
+ isProd
store.toggleProduction()
+ store.setFiles(store.getFiles())
}
function toggleSSR() {
@@ -109,10 +114,10 @@ onMounted(() => {
diff --git a/packages/sfc-playground/src/Header.vue b/packages/sfc-playground/src/Header.vue
index 7a60201b7..33da636b9 100644
--- a/packages/sfc-playground/src/Header.vue
+++ b/packages/sfc-playground/src/Header.vue
@@ -12,13 +12,13 @@ import VersionSelect from './VersionSelect.vue'
const props = defineProps<{
store: ReplStore
- dev: boolean
+ prod: boolean
ssr: boolean
}>()
const emit = defineEmits([
'toggle-theme',
'toggle-ssr',
- 'toggle-dev',
+ 'toggle-prod',
'reload-page'
])
@@ -90,11 +90,11 @@ function toggleDark() {
]