Skip to content

Commit fb58e65

Browse files
committed
Merge remote-tracking branch 'upstream/minor'
2 parents e42fecb + 5590ca3 commit fb58e65

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1653
-558
lines changed

CHANGELOG.md

+23
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
# [3.5.0-alpha.1](https://github.com/vuejs/core/compare/v3.4.25...v3.5.0-alpha.1) (2024-04-29)
2+
3+
4+
### Bug Fixes
5+
6+
* **reactivity:** fix call sequence of ontrigger in effect ([#10501](https://github.com/vuejs/core/issues/10501)) ([28841fe](https://github.com/vuejs/core/commit/28841fee43a45c37905c2c1ed9ace23067539045))
7+
8+
9+
### Features
10+
11+
* **compiler-sfc:** enable reactive props destructure by default ([d2dac0e](https://github.com/vuejs/core/commit/d2dac0e359c47d1ed0aa77eda488e76fd6466d2d))
12+
* **reactivity:** `onEffectCleanup` API ([2cc5615](https://github.com/vuejs/core/commit/2cc5615590de77126e8df46136de0240dbde5004)), closes [#10173](https://github.com/vuejs/core/issues/10173)
13+
* **reactivity:** add failSilently argument for onScopeDispose ([9a936aa](https://github.com/vuejs/core/commit/9a936aaec489c79433a32791ecf5ddb1739a62bd))
14+
* **transition:** support directly nesting Teleport inside Transition ([#6548](https://github.com/vuejs/core/issues/6548)) ([0e6e3c7](https://github.com/vuejs/core/commit/0e6e3c7eb0e5320b7c1818e025cb4a490fede9c0)), closes [#5836](https://github.com/vuejs/core/issues/5836)
15+
* **types:** provide internal options for directly using user types in language tools ([#10801](https://github.com/vuejs/core/issues/10801)) ([75c8cf6](https://github.com/vuejs/core/commit/75c8cf63a1ef30ac84f91282d66ad3f57c6612e9))
16+
17+
18+
### Performance Improvements
19+
20+
* **reactivity:** optimize array tracking ([#9511](https://github.com/vuejs/core/issues/9511)) ([70196a4](https://github.com/vuejs/core/commit/70196a40cc078f50fcc1110c38c06fbcc70b205e)), closes [#4318](https://github.com/vuejs/core/issues/4318)
21+
22+
23+
124
## [3.4.25](https://github.com/vuejs/core/compare/v3.4.24...v3.4.25) (2024-04-24)
225

326

packages/compiler-core/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vue/compiler-core",
3-
"version": "3.4.25",
3+
"version": "3.5.0-alpha.1",
44
"description": "@vue/compiler-core",
55
"main": "index.js",
66
"module": "dist/compiler-core.esm-bundler.js",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { type CompilerError, compile } from '../../src'
2+
3+
describe('validate html nesting', () => {
4+
it('should warn with p > div', () => {
5+
let err: CompilerError | undefined
6+
compile(`<p><div></div></p>`, {
7+
onWarn: e => (err = e),
8+
})
9+
expect(err).toBeDefined()
10+
expect(err!.message).toMatch(`<div> cannot be child of <p>`)
11+
})
12+
13+
it('should not warn with select > hr', () => {
14+
let err: CompilerError | undefined
15+
compile(`<select><hr></select>`, {
16+
onWarn: e => (err = e),
17+
})
18+
expect(err).toBeUndefined()
19+
})
20+
})

packages/compiler-dom/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vue/compiler-dom",
3-
"version": "3.4.25",
3+
"version": "3.5.0-alpha.1",
44
"description": "@vue/compiler-dom",
55
"main": "index.js",
66
"module": "dist/compiler-dom.esm-bundler.js",
+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/**
2+
* Copied from https://github.com/MananTank/validate-html-nesting
3+
* with ISC license
4+
*
5+
* To avoid runtime dependency on validate-html-nesting
6+
* This file should not change very often in the original repo
7+
* but we may need to keep it up-to-date from time to time.
8+
*/
9+
10+
/**
11+
* returns true if given parent-child nesting is valid HTML
12+
*/
13+
export function isValidHTMLNesting(parent: string, child: string): boolean {
14+
// if we know the list of children that are the only valid children for the given parent
15+
if (parent in onlyValidChildren) {
16+
return onlyValidChildren[parent].has(child)
17+
}
18+
19+
// if we know the list of parents that are the only valid parents for the given child
20+
if (child in onlyValidParents) {
21+
return onlyValidParents[child].has(parent)
22+
}
23+
24+
// if we know the list of children that are NOT valid for the given parent
25+
if (parent in knownInvalidChildren) {
26+
// check if the child is in the list of invalid children
27+
// if so, return false
28+
if (knownInvalidChildren[parent].has(child)) return false
29+
}
30+
31+
// if we know the list of parents that are NOT valid for the given child
32+
if (child in knownInvalidParents) {
33+
// check if the parent is in the list of invalid parents
34+
// if so, return false
35+
if (knownInvalidParents[child].has(parent)) return false
36+
}
37+
38+
return true
39+
}
40+
41+
const headings = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
42+
const emptySet = new Set([])
43+
44+
/**
45+
* maps element to set of elements that can be it's children, no other */
46+
const onlyValidChildren: Record<string, Set<string>> = {
47+
head: new Set([
48+
'base',
49+
'basefront',
50+
'bgsound',
51+
'link',
52+
'meta',
53+
'title',
54+
'noscript',
55+
'noframes',
56+
'style',
57+
'script',
58+
'template',
59+
]),
60+
optgroup: new Set(['option']),
61+
select: new Set(['optgroup', 'option', 'hr']),
62+
// table
63+
table: new Set(['caption', 'colgroup', 'tbody', 'tfoot', 'thead']),
64+
tr: new Set(['td', 'th']),
65+
colgroup: new Set(['col']),
66+
tbody: new Set(['tr']),
67+
thead: new Set(['tr']),
68+
tfoot: new Set(['tr']),
69+
// these elements can not have any children elements
70+
script: emptySet,
71+
iframe: emptySet,
72+
option: emptySet,
73+
textarea: emptySet,
74+
style: emptySet,
75+
title: emptySet,
76+
}
77+
78+
/** maps elements to set of elements which can be it's parent, no other */
79+
const onlyValidParents: Record<string, Set<string>> = {
80+
// sections
81+
html: emptySet,
82+
body: new Set(['html']),
83+
head: new Set(['html']),
84+
// table
85+
td: new Set(['tr']),
86+
colgroup: new Set(['table']),
87+
caption: new Set(['table']),
88+
tbody: new Set(['table']),
89+
tfoot: new Set(['table']),
90+
col: new Set(['colgroup']),
91+
th: new Set(['tr']),
92+
thead: new Set(['table']),
93+
tr: new Set(['tbody', 'thead', 'tfoot']),
94+
// data list
95+
dd: new Set(['dl', 'div']),
96+
dt: new Set(['dl', 'div']),
97+
// other
98+
figcaption: new Set(['figure']),
99+
// li: new Set(["ul", "ol"]),
100+
summary: new Set(['details']),
101+
area: new Set(['map']),
102+
} as const
103+
104+
/** maps element to set of elements that can not be it's children, others can */
105+
const knownInvalidChildren: Record<string, Set<string>> = {
106+
p: new Set([
107+
'address',
108+
'article',
109+
'aside',
110+
'blockquote',
111+
'center',
112+
'details',
113+
'dialog',
114+
'dir',
115+
'div',
116+
'dl',
117+
'fieldset',
118+
'figure',
119+
'footer',
120+
'form',
121+
'h1',
122+
'h2',
123+
'h3',
124+
'h4',
125+
'h5',
126+
'h6',
127+
'header',
128+
'hgroup',
129+
'hr',
130+
'li',
131+
'main',
132+
'nav',
133+
'menu',
134+
'ol',
135+
'p',
136+
'pre',
137+
'section',
138+
'table',
139+
'ul',
140+
]),
141+
svg: new Set([
142+
'b',
143+
'blockquote',
144+
'br',
145+
'code',
146+
'dd',
147+
'div',
148+
'dl',
149+
'dt',
150+
'em',
151+
'embed',
152+
'h1',
153+
'h2',
154+
'h3',
155+
'h4',
156+
'h5',
157+
'h6',
158+
'hr',
159+
'i',
160+
'img',
161+
'li',
162+
'menu',
163+
'meta',
164+
'ol',
165+
'p',
166+
'pre',
167+
'ruby',
168+
's',
169+
'small',
170+
'span',
171+
'strong',
172+
'sub',
173+
'sup',
174+
'table',
175+
'u',
176+
'ul',
177+
'var',
178+
]),
179+
} as const
180+
181+
/** maps element to set of elements that can not be it's parent, others can */
182+
const knownInvalidParents: Record<string, Set<string>> = {
183+
a: new Set(['a']),
184+
button: new Set(['button']),
185+
dd: new Set(['dd', 'dt']),
186+
dt: new Set(['dd', 'dt']),
187+
form: new Set(['form']),
188+
li: new Set(['li']),
189+
h1: headings,
190+
h2: headings,
191+
h3: headings,
192+
h4: headings,
193+
h5: headings,
194+
h6: headings,
195+
}

packages/compiler-dom/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ import { transformShow } from './transforms/vShow'
1919
import { transformTransition } from './transforms/Transition'
2020
import { stringifyStatic } from './transforms/stringifyStatic'
2121
import { ignoreSideEffectTags } from './transforms/ignoreSideEffectTags'
22+
import { validateHtmlNesting } from './transforms/validateHtmlNesting'
2223
import { extend } from '@vue/shared'
2324

2425
export { parserOptions }
2526

2627
export const DOMNodeTransforms: NodeTransform[] = [
2728
transformStyle,
28-
...(__DEV__ ? [transformTransition] : []),
29+
...(__DEV__ ? [transformTransition, validateHtmlNesting] : []),
2930
]
3031

3132
export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
type CompilerError,
3+
ElementTypes,
4+
type NodeTransform,
5+
NodeTypes,
6+
} from '@vue/compiler-core'
7+
import { isValidHTMLNesting } from '../htmlNesting'
8+
9+
export const validateHtmlNesting: NodeTransform = (node, context) => {
10+
if (
11+
node.type === NodeTypes.ELEMENT &&
12+
node.tagType === ElementTypes.ELEMENT &&
13+
context.parent &&
14+
context.parent.type === NodeTypes.ELEMENT &&
15+
context.parent.tagType === ElementTypes.ELEMENT &&
16+
!isValidHTMLNesting(context.parent.tag, node.tag)
17+
) {
18+
const error = new SyntaxError(
19+
`<${node.tag}> cannot be child of <${context.parent.tag}>, ` +
20+
'according to HTML specifications. ' +
21+
'This can cause hydration errors or ' +
22+
'potentially disrupt future functionality.',
23+
) as CompilerError
24+
error.loc = node.loc
25+
context.onWarn(error)
26+
}
27+
}

packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts

+18
Original file line numberDiff line numberDiff line change
@@ -597,11 +597,29 @@ const props = defineProps({ foo: String })
597597
foo: Foo
598598
}>()
599599
</script>`,
600+
{
601+
propsDestructure: false,
602+
},
600603
)
601604
expect(content).toMatch(`const { foo } = __props`)
602605
assertCode(content)
603606
})
604607

608+
test('prohibiting reactive destructure', () => {
609+
expect(() =>
610+
compile(
611+
`<script setup lang="ts">
612+
const { foo } = defineProps<{
613+
foo: Foo
614+
}>()
615+
</script>`,
616+
{
617+
propsDestructure: 'error',
618+
},
619+
),
620+
).toThrow()
621+
})
622+
605623
describe('errors', () => {
606624
test('w/ both type and non-type args', () => {
607625
expect(() => {

packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ describe('sfc reactive props destructure', () => {
66
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
77
return compileSFCScript(src, {
88
inlineTemplate: true,
9-
propsDestructure: true,
109
...options,
1110
})
1211
}

packages/compiler-sfc/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vue/compiler-sfc",
3-
"version": "3.4.25",
3+
"version": "3.5.0-alpha.1",
44
"description": "@vue/compiler-sfc",
55
"main": "dist/compiler-sfc.cjs.js",
66
"module": "dist/compiler-sfc.esm-browser.js",

packages/compiler-sfc/src/compileScript.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,11 @@ export interface SFCScriptCompileOptions {
106106
*/
107107
hoistStatic?: boolean
108108
/**
109-
* (**Experimental**) Enable reactive destructure for `defineProps`
110-
* @default false
109+
* Set to `false` to disable reactive destructure for `defineProps` (pre-3.5
110+
* behavior), or set to `'error'` to throw hard error on props destructures.
111+
* @default true
111112
*/
112-
propsDestructure?: boolean
113+
propsDestructure?: boolean | 'error'
113114
/**
114115
* File system access methods to be used when resolving types
115116
* imported in SFC macros. Defaults to ts.sys in Node.js, can be overwritten

0 commit comments

Comments
 (0)