diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index db874b1240e..d4ec93e3e22 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -80,7 +80,7 @@ jobs:
cache: 'pnpm'
- run: pnpm install
- - run: node node_modules/puppeteer/install.js
+ - run: node node_modules/puppeteer/install.mjs
- name: Run e2e tests
run: pnpm run test-e2e
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12be7dc8a3a..c8cdb94b5a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,22 @@
+## [3.3.7](https://github.com/vuejs/core/compare/v3.3.6...v3.3.7) (2023-10-24)
+
+
+### Bug Fixes
+
+* **compiler-sfc:** avoid gen useCssVars when targeting SSR ([#6979](https://github.com/vuejs/core/issues/6979)) ([c568778](https://github.com/vuejs/core/commit/c568778ea3265d8e57f788b00864c9509bf88a4e)), closes [#6926](https://github.com/vuejs/core/issues/6926)
+* **compiler-ssr:** proper scope analysis for ssr vnode slot fallback ([#7184](https://github.com/vuejs/core/issues/7184)) ([e09c26b](https://github.com/vuejs/core/commit/e09c26bc9bc4394c2c2d928806d382515c2676f3)), closes [#7095](https://github.com/vuejs/core/issues/7095)
+* correctly resolve types from relative paths on Windows ([#9446](https://github.com/vuejs/core/issues/9446)) ([089d36d](https://github.com/vuejs/core/commit/089d36d167dc7834065b03ca689f9b6a44eead8a)), closes [#8671](https://github.com/vuejs/core/issues/8671)
+* **hmr:** fix hmr error for hoisted children array in v-for ([7334376](https://github.com/vuejs/core/commit/733437691f70ebca8dd6cc3bc8356f5b57d4d5d8)), closes [#6978](https://github.com/vuejs/core/issues/6978) [#7114](https://github.com/vuejs/core/issues/7114)
+* **reactivity:** assigning array.length while observing a symbol property ([#7568](https://github.com/vuejs/core/issues/7568)) ([e9e2778](https://github.com/vuejs/core/commit/e9e2778e9ec5cca07c1df5f0c9b7b3595a1a3244))
+* **scheduler:** ensure jobs are in the correct order ([#7748](https://github.com/vuejs/core/issues/7748)) ([a8f6638](https://github.com/vuejs/core/commit/a8f663867b8cd2736b82204bc58756ef02441276)), closes [#7576](https://github.com/vuejs/core/issues/7576)
+* **ssr:** fix hydration mismatch for disabled teleport at component root ([#9399](https://github.com/vuejs/core/issues/9399)) ([d8990fc](https://github.com/vuejs/core/commit/d8990fc6182d1c2cf0a8eab7b35a9d04df668507)), closes [#6152](https://github.com/vuejs/core/issues/6152)
+* **Suspense:** calling hooks before the transition finishes ([#9388](https://github.com/vuejs/core/issues/9388)) ([00de3e6](https://github.com/vuejs/core/commit/00de3e61ed7a55e7d6c2e1987551d66ad0f909ff)), closes [#5844](https://github.com/vuejs/core/issues/5844) [#5952](https://github.com/vuejs/core/issues/5952)
+* **transition/ssr:** make transition appear work with SSR ([#8859](https://github.com/vuejs/core/issues/8859)) ([5ea8a8a](https://github.com/vuejs/core/commit/5ea8a8a4fab4e19a71e123e4d27d051f5e927172)), closes [#6951](https://github.com/vuejs/core/issues/6951)
+* **types:** fix ComponentCustomProps augmentation ([#9468](https://github.com/vuejs/core/issues/9468)) ([7374e93](https://github.com/vuejs/core/commit/7374e93f0281f273b90ab5a6724cc47332a01d6c)), closes [#8376](https://github.com/vuejs/core/issues/8376)
+* **types:** improve `h` overload to support union of string and component ([#5432](https://github.com/vuejs/core/issues/5432)) ([16ecb44](https://github.com/vuejs/core/commit/16ecb44c89cd8299a3b8de33cccc2e2cc36f065b)), closes [#5431](https://github.com/vuejs/core/issues/5431)
+
+
+
## [3.3.6](https://github.com/vuejs/core/compare/v3.3.5...v3.3.6) (2023-10-20)
diff --git a/package.json b/package.json
index d903294ab1e..092feef1382 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"private": true,
- "version": "3.3.6",
+ "version": "3.3.7",
"packageManager": "pnpm@8.9.2",
"type": "module",
"scripts": {
@@ -66,16 +66,16 @@
"@rollup/plugin-replace": "^5.0.4",
"@rollup/plugin-terser": "^0.4.4",
"@types/hash-sum": "^1.0.1",
- "@types/node": "^18.18.6",
+ "@types/node": "^20.8.7",
"@typescript-eslint/parser": "^6.8.0",
- "@vitest/coverage-istanbul": "^0.34.4",
+ "@vitest/coverage-istanbul": "^0.34.6",
"@vue/consolidate": "0.17.3",
"conventional-changelog-cli": "^4.1.0",
"enquirer": "^2.4.1",
"esbuild": "^0.19.5",
"esbuild-plugin-polyfill-node": "^0.3.0",
- "eslint": "^8.51.0",
- "eslint-plugin-jest": "^27.4.2",
+ "eslint": "^8.52.0",
+ "eslint-plugin-jest": "^27.4.3",
"estree-walker": "^2.0.2",
"execa": "^8.0.1",
"jsdom": "^22.1.0",
@@ -90,9 +90,9 @@
"prettier": "^3.0.3",
"pretty-bytes": "^6.1.1",
"pug": "^3.0.2",
- "puppeteer": "~21.2.1",
+ "puppeteer": "~21.4.0",
"rimraf": "^5.0.5",
- "rollup": "^3.29.4",
+ "rollup": "^4.1.4",
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-esbuild": "^6.1.0",
"rollup-plugin-polyfill-node": "^0.12.0",
@@ -100,11 +100,11 @@
"serve": "^14.2.1",
"simple-git-hooks": "^2.9.0",
"terser": "^5.22.0",
- "todomvc-app-css": "^2.4.2",
+ "todomvc-app-css": "^2.4.3",
"tslib": "^2.6.2",
"tsx": "^3.14.0",
- "typescript": "^5.1.6",
- "vite": "^4.3.0",
- "vitest": "^0.34.4"
+ "typescript": "^5.2.2",
+ "vite": "^4.5.0",
+ "vitest": "^0.34.6"
}
}
diff --git a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
index eec5a76d363..49ad7ad8982 100644
--- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
@@ -593,5 +593,17 @@ describe('compiler: hoistStatic transform', () => {
expect(root.hoists.length).toBe(2)
expect(generate(root).code).toMatchSnapshot()
})
+
+ test('clone hoisted array children in HMR mode', () => {
+ const root = transformWithHoist(`
`, {
+ hmr: true
+ })
+ expect(root.hoists.length).toBe(2)
+ expect(root.codegenNode).toMatchObject({
+ children: {
+ content: '[..._hoisted_2]'
+ }
+ })
+ })
})
})
diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json
index f885e2046e1..8c9f06f3543 100644
--- a/packages/compiler-core/package.json
+++ b/packages/compiler-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-core",
- "version": "3.3.6",
+ "version": "3.3.7",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
@@ -33,7 +33,7 @@
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
"dependencies": {
"@babel/parser": "^7.23.0",
- "@vue/shared": "3.3.6",
+ "@vue/shared": "3.3.7",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
},
diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts
index 65bbcb36dd6..abfba98e35c 100644
--- a/packages/compiler-core/src/options.ts
+++ b/packages/compiler-core/src/options.ts
@@ -256,6 +256,12 @@ export interface TransformOptions
* needed to render inline CSS variables on component root
*/
ssrCssVars?: string
+ /**
+ * Whether to compile the template assuming it needs to handle HMR.
+ * Some edge cases may need to generate different code for HMR to work
+ * correctly, e.g. #6938, #7138
+ */
+ hmr?: boolean
}
export interface CodegenOptions extends SharedTransformCodegenOptions {
diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts
index d26c11bba20..04f85679cae 100644
--- a/packages/compiler-core/src/transform.ts
+++ b/packages/compiler-core/src/transform.ts
@@ -129,6 +129,7 @@ export function createTransformContext(
filename = '',
prefixIdentifiers = false,
hoistStatic = false,
+ hmr = false,
cacheHandlers = false,
nodeTransforms = [],
directiveTransforms = {},
@@ -155,6 +156,7 @@ export function createTransformContext(
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
prefixIdentifiers,
hoistStatic,
+ hmr,
cacheHandlers,
nodeTransforms,
directiveTransforms,
diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts
index 5526163c6f9..fd443496ca7 100644
--- a/packages/compiler-core/src/transforms/hoistStatic.ts
+++ b/packages/compiler-core/src/transforms/hoistStatic.ts
@@ -140,9 +140,16 @@ function walk(
node.codegenNode.type === NodeTypes.VNODE_CALL &&
isArray(node.codegenNode.children)
) {
- node.codegenNode.children = context.hoist(
+ const hoisted = context.hoist(
createArrayExpression(node.codegenNode.children)
)
+ // #6978, #7138, #7114
+ // a hoisted children array inside v-for can caused HMR errors since
+ // it might be mutated when mounting the v-for list
+ if (context.hmr) {
+ hoisted.content = `[...${hoisted.content}]`
+ }
+ node.codegenNode.children = hoisted
}
}
diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts
index c4416dd45f7..ffa90ea1171 100644
--- a/packages/compiler-core/src/transforms/vSlot.ts
+++ b/packages/compiler-core/src/transforms/vSlot.ts
@@ -100,11 +100,12 @@ export const trackVForSlotScopes: NodeTransform = (node, context) => {
export type SlotFnBuilder = (
slotProps: ExpressionNode | undefined,
+ vForExp: ExpressionNode | undefined,
slotChildren: TemplateChildNode[],
loc: SourceLocation
) => FunctionExpression
-const buildClientSlotFn: SlotFnBuilder = (props, children, loc) =>
+const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) =>
createFunctionExpression(
props,
children,
@@ -149,7 +150,7 @@ export function buildSlots(
slotsProperties.push(
createObjectProperty(
arg || createSimpleExpression('default', true),
- buildSlotFn(exp, children, loc)
+ buildSlotFn(exp, undefined, children, loc)
)
)
}
@@ -201,11 +202,17 @@ export function buildSlots(
hasDynamicSlots = true
}
- const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc)
+ const vFor = findDir(slotElement, 'for')
+ const slotFunction = buildSlotFn(
+ slotProps,
+ vFor?.exp,
+ slotChildren,
+ slotLoc
+ )
+
// check if this slot is conditional (v-if/v-for)
let vIf: DirectiveNode | undefined
let vElse: DirectiveNode | undefined
- let vFor: DirectiveNode | undefined
if ((vIf = findDir(slotElement, 'if'))) {
hasDynamicSlots = true
dynamicSlots.push(
@@ -257,7 +264,7 @@ export function buildSlots(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc)
)
}
- } else if ((vFor = findDir(slotElement, 'for'))) {
+ } else if (vFor) {
hasDynamicSlots = true
const parseResult =
vFor.parseResult ||
@@ -306,7 +313,7 @@ export function buildSlots(
props: ExpressionNode | undefined,
children: TemplateChildNode[]
) => {
- const fn = buildSlotFn(props, children, loc)
+ const fn = buildSlotFn(props, undefined, children, loc)
if (__COMPAT__ && context.compatConfig) {
fn.isNonScopedSlot = true
}
diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json
index 8bde209e292..4e6ea338bb9 100644
--- a/packages/compiler-dom/package.json
+++ b/packages/compiler-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-dom",
- "version": "3.3.6",
+ "version": "3.3.7",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",
@@ -37,7 +37,7 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme",
"dependencies": {
- "@vue/shared": "3.3.6",
- "@vue/compiler-core": "3.3.6"
+ "@vue/shared": "3.3.7",
+ "@vue/compiler-core": "3.3.7"
}
}
diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
index 607654a952b..fc600f1a518 100644
--- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
@@ -1,3 +1,4 @@
+import { normalize } from 'node:path'
import { Identifier } from '@babel/types'
import { SFCScriptCompileOptions, parse } from '../../src'
import { ScriptCompileContext } from '../../src/script/context'
@@ -478,6 +479,33 @@ describe('resolveType', () => {
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
+ test.runIf(process.platform === 'win32')('relative ts on Windows', () => {
+ const files = {
+ 'C:\\Test\\foo.ts': 'export type P = { foo: number }',
+ 'C:\\Test\\bar.d.ts':
+ 'type X = { bar: string }; export { X as Y };' +
+ // verify that we can parse syntax that is only valid in d.ts
+ 'export const baz: boolean'
+ }
+ const { props, deps } = resolve(
+ `
+ import { P } from './foo'
+ import { Y as PP } from './bar'
+ defineProps()
+ `,
+ files,
+ {},
+ 'C:\\Test\\Test.vue'
+ )
+ expect(props).toStrictEqual({
+ foo: ['Number'],
+ bar: ['String']
+ })
+ expect(deps && [...deps].map(normalize)).toStrictEqual(
+ Object.keys(files).map(normalize)
+ )
+ })
+
// #8244
test('utility type in external file', () => {
const files = {
@@ -898,19 +926,20 @@ describe('resolveType', () => {
function resolve(
code: string,
files: Record = {},
- options?: Partial
+ options?: Partial,
+ sourceFileName: string = '/Test.vue'
) {
const { descriptor } = parse(``, {
- filename: '/Test.vue'
+ filename: sourceFileName
})
const ctx = new ScriptCompileContext(descriptor, {
id: 'test',
fs: {
fileExists(file) {
- return !!files[file]
+ return !!(files[file] ?? files[normalize(file)])
},
readFile(file) {
- return files[file]
+ return files[file] ?? files[normalize(file)]
}
},
...options
diff --git a/packages/compiler-sfc/__tests__/cssVars.spec.ts b/packages/compiler-sfc/__tests__/cssVars.spec.ts
index 5b01d73d772..9fb72d7ad50 100644
--- a/packages/compiler-sfc/__tests__/cssVars.spec.ts
+++ b/packages/compiler-sfc/__tests__/cssVars.spec.ts
@@ -272,5 +272,73 @@ describe('CSS vars injection', () => {
`export default {\n setup(__props, { expose: __expose }) {\n __expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))`
)
})
+
+ describe('skip codegen in SSR', () => {
+ test('script setup, inline', () => {
+ const { content } = compileSFCScript(
+ `\n` +
+ ``,
+ {
+ inlineTemplate: true,
+ templateOptions: {
+ ssr: true
+ }
+ }
+ )
+ expect(content).not.toMatch(`_useCssVars`)
+ })
+
+ // #6926
+ test('script, non-inline', () => {
+ const { content } = compileSFCScript(
+ `\n` +
+ ``,
+ {
+ inlineTemplate: false,
+ templateOptions: {
+ ssr: true
+ }
+ }
+ )
+ expect(content).not.toMatch(`_useCssVars`)
+ })
+
+ test('normal script', () => {
+ const { content } = compileSFCScript(
+ `\n` +
+ ``,
+ {
+ templateOptions: {
+ ssr: true
+ }
+ }
+ )
+ expect(content).not.toMatch(`_useCssVars`)
+ })
+ })
})
})
diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json
index 6a1fc8bba3a..550b5a7e927 100644
--- a/packages/compiler-sfc/package.json
+++ b/packages/compiler-sfc/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-sfc",
- "version": "3.3.6",
+ "version": "3.3.7",
"description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js",
"module": "dist/compiler-sfc.esm-browser.js",
@@ -33,11 +33,11 @@
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
"dependencies": {
"@babel/parser": "^7.23.0",
- "@vue/compiler-core": "3.3.6",
- "@vue/compiler-dom": "3.3.6",
- "@vue/compiler-ssr": "3.3.6",
- "@vue/reactivity-transform": "3.3.6",
- "@vue/shared": "3.3.6",
+ "@vue/compiler-core": "3.3.7",
+ "@vue/compiler-dom": "3.3.7",
+ "@vue/compiler-ssr": "3.3.7",
+ "@vue/reactivity-transform": "3.3.7",
+ "@vue/shared": "3.3.7",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.5",
"postcss": "^8.4.31",
diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts
index cfcc607c72d..2a33f69936d 100644
--- a/packages/compiler-sfc/src/compileScript.ts
+++ b/packages/compiler-sfc/src/compileScript.ts
@@ -765,7 +765,7 @@ export function compileScript(
if (
sfc.cssVars.length &&
// no need to do this when targeting SSR
- !(options.inlineTemplate && options.templateOptions?.ssr)
+ !options.templateOptions?.ssr
) {
ctx.helperImports.add(CSS_VARS_HELPER)
ctx.helperImports.add('unref')
diff --git a/packages/compiler-sfc/src/compileTemplate.ts b/packages/compiler-sfc/src/compileTemplate.ts
index fbd100c9784..b036619c794 100644
--- a/packages/compiler-sfc/src/compileTemplate.ts
+++ b/packages/compiler-sfc/src/compileTemplate.ts
@@ -212,6 +212,7 @@ function doCompileTemplate({
slotted,
sourceMap: true,
...compilerOptions,
+ hmr: !isProd,
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
filename,
onError: e => errors.push(e),
diff --git a/packages/compiler-sfc/src/script/normalScript.ts b/packages/compiler-sfc/src/script/normalScript.ts
index 76b25c66350..d0f16134273 100644
--- a/packages/compiler-sfc/src/script/normalScript.ts
+++ b/packages/compiler-sfc/src/script/normalScript.ts
@@ -55,7 +55,7 @@ export function processNormalScript(
const s = new MagicString(content)
rewriteDefaultAST(scriptAst.body, s, defaultVar)
content = s.toString()
- if (cssVars.length) {
+ if (cssVars.length && !ctx.options.templateOptions?.ssr) {
content += genNormalScriptCssVarsCode(
cssVars,
bindings,
diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts
index 78581432366..215081dc0b7 100644
--- a/packages/compiler-sfc/src/script/resolveType.ts
+++ b/packages/compiler-sfc/src/script/resolveType.ts
@@ -778,7 +778,7 @@ function importSourceToScope(
if (!resolved) {
if (source.startsWith('.')) {
// relative import - fast path
- const filename = joinPaths(scope.filename, '..', source)
+ const filename = joinPaths(dirname(scope.filename), source)
resolved = resolveExt(filename, fs)
} else {
// module or aliased import - use full TS resolution, only supported in Node
diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
index 9391c01e37e..a8ea08a5349 100644
--- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
@@ -181,11 +181,14 @@ describe('ssr: components', () => {
})
test('v-for slot', () => {
- expect(
- compile(`
- {{ msg + key + bar }}
- `).code
- ).toMatchInlineSnapshot(`
+ const { code } = compile(`
+ {{ msg + key + index + bar }}
+ `)
+ expect(code).not.toMatch(`_ctx.msg`)
+ expect(code).not.toMatch(`_ctx.key`)
+ expect(code).not.toMatch(`_ctx.index`)
+ expect(code).toMatch(`_ctx.bar`)
+ expect(code).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"vue/server-renderer\\")
@@ -193,15 +196,15 @@ describe('ssr: components', () => {
const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 /* DYNAMIC */ }, [
- _renderList(_ctx.names, (key) => {
+ _renderList(_ctx.names, (key, index) => {
return {
name: key,
fn: _withCtx(({ msg }, _push, _parent, _scopeId) => {
if (_push) {
- _push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
+ _push(\`\${_ssrInterpolate(msg + key + index + _ctx.bar)}\`)
} else {
return [
- _createTextVNode(_toDisplayString(msg + _ctx.key + _ctx.bar), 1 /* TEXT */)
+ _createTextVNode(_toDisplayString(msg + key + index + _ctx.bar), 1 /* TEXT */)
]
}
})
diff --git a/packages/compiler-ssr/__tests__/ssrTransition.spec.ts b/packages/compiler-ssr/__tests__/ssrTransition.spec.ts
new file mode 100644
index 00000000000..319b3902239
--- /dev/null
+++ b/packages/compiler-ssr/__tests__/ssrTransition.spec.ts
@@ -0,0 +1,25 @@
+import { compile } from '../src'
+
+describe('transition', () => {
+ test('basic', () => {
+ expect(compile(`foo
`).code)
+ .toMatchInlineSnapshot(`
+ "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent, _attrs) {
+ _push(\`foo
\`)
+ }"
+ `)
+ })
+
+ test('with appear', () => {
+ expect(compile(`foo
`).code)
+ .toMatchInlineSnapshot(`
+ "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent, _attrs) {
+ _push(\`foo
\`)
+ }"
+ `)
+ })
+})
diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json
index dc4933349f0..556c43f5971 100644
--- a/packages/compiler-ssr/package.json
+++ b/packages/compiler-ssr/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-ssr",
- "version": "3.3.6",
+ "version": "3.3.7",
"description": "@vue/compiler-ssr",
"main": "dist/compiler-ssr.cjs.js",
"types": "dist/compiler-ssr.d.ts",
@@ -28,7 +28,7 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme",
"dependencies": {
- "@vue/shared": "3.3.6",
- "@vue/compiler-dom": "3.3.6"
+ "@vue/shared": "3.3.7",
+ "@vue/compiler-dom": "3.3.7"
}
}
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
index dc8c6a4ae4f..7a12cb29009 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
@@ -56,6 +56,10 @@ import {
} from './ssrTransformTransitionGroup'
import { isSymbol, isObject, isArray } from '@vue/shared'
import { buildSSRProps } from './ssrTransformElement'
+import {
+ ssrProcessTransition,
+ ssrTransformTransition
+} from './ssrTransformTransition'
// We need to construct the slot functions in the 1st pass to ensure proper
// scope tracking, but the children of each slot cannot be processed until
@@ -99,9 +103,10 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
if (isSymbol(component)) {
if (component === SUSPENSE) {
return ssrTransformSuspense(node, context)
- }
- if (component === TRANSITION_GROUP) {
+ } else if (component === TRANSITION_GROUP) {
return ssrTransformTransitionGroup(node, context)
+ } else if (component === TRANSITION) {
+ return ssrTransformTransition(node, context)
}
return // other built-in components: fallthrough
}
@@ -120,8 +125,10 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
// fallback in case the child is render-fn based). Store them in an array
// for later use.
if (clonedNode.children.length) {
- buildSlots(clonedNode, context, (props, children) => {
- vnodeBranches.push(createVNodeSlotBranch(props, children, context))
+ buildSlots(clonedNode, context, (props, vFor, children) => {
+ vnodeBranches.push(
+ createVNodeSlotBranch(props, vFor, children, context)
+ )
return createFunctionExpression(undefined)
})
}
@@ -145,7 +152,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
const wipEntries: WIPSlotEntry[] = []
wipMap.set(node, wipEntries)
- const buildSSRSlotFn: SlotFnBuilder = (props, children, loc) => {
+ const buildSSRSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) => {
const param0 = (props && stringifyExpression(props)) || `_`
const fn = createFunctionExpression(
[param0, `_push`, `_parent`, `_scopeId`],
@@ -216,9 +223,8 @@ export function ssrProcessComponent(
if ((parent as WIPSlotEntry).type === WIP_SLOT) {
context.pushStringPart(``)
}
- // #5351: filter out comment children inside transition
if (component === TRANSITION) {
- node.children = node.children.filter(c => c.type !== NodeTypes.COMMENT)
+ return ssrProcessTransition(node, context)
}
processChildren(node, context)
}
@@ -273,6 +279,7 @@ const vnodeDirectiveTransforms = {
function createVNodeSlotBranch(
props: ExpressionNode | undefined,
+ vForExp: ExpressionNode | undefined,
children: TemplateChildNode[],
parentContext: TransformContext
): ReturnStatement {
@@ -299,8 +306,8 @@ function createVNodeSlotBranch(
tag: 'template',
tagType: ElementTypes.TEMPLATE,
isSelfClosing: false,
- // important: provide v-slot="props" on the wrapper for proper
- // scope analysis
+ // important: provide v-slot="props" and v-for="exp" on the wrapper for
+ // proper scope analysis
props: [
{
type: NodeTypes.DIRECTIVE,
@@ -309,6 +316,14 @@ function createVNodeSlotBranch(
arg: undefined,
modifiers: [],
loc: locStub
+ },
+ {
+ type: NodeTypes.DIRECTIVE,
+ name: 'for',
+ exp: vForExp,
+ arg: undefined,
+ modifiers: [],
+ loc: locStub
}
],
children,
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts b/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts
index 207e9348eef..e7efbe1fb73 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts
@@ -36,20 +36,24 @@ export function ssrTransformSuspense(
wipSlots: []
}
wipMap.set(node, wipEntry)
- wipEntry.slotsExp = buildSlots(node, context, (_props, children, loc) => {
- const fn = createFunctionExpression(
- [],
- undefined, // no return, assign body later
- true, // newline
- false, // suspense slots are not treated as normal slots
- loc
- )
- wipEntry.wipSlots.push({
- fn,
- children
- })
- return fn
- }).slots
+ wipEntry.slotsExp = buildSlots(
+ node,
+ context,
+ (_props, _vForExp, children, loc) => {
+ const fn = createFunctionExpression(
+ [],
+ undefined, // no return, assign body later
+ true, // newline
+ false, // suspense slots are not treated as normal slots
+ loc
+ )
+ wipEntry.wipSlots.push({
+ fn,
+ children
+ })
+ return fn
+ }
+ ).slots
}
}
}
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformTransition.ts b/packages/compiler-ssr/src/transforms/ssrTransformTransition.ts
new file mode 100644
index 00000000000..d09a806f7b0
--- /dev/null
+++ b/packages/compiler-ssr/src/transforms/ssrTransformTransition.ts
@@ -0,0 +1,36 @@
+import {
+ ComponentNode,
+ findProp,
+ NodeTypes,
+ TransformContext
+} from '@vue/compiler-dom'
+import { processChildren, SSRTransformContext } from '../ssrCodegenTransform'
+
+const wipMap = new WeakMap()
+
+export function ssrTransformTransition(
+ node: ComponentNode,
+ context: TransformContext
+) {
+ return () => {
+ const appear = findProp(node, 'appear', false, true)
+ wipMap.set(node, !!appear)
+ }
+}
+
+export function ssrProcessTransition(
+ node: ComponentNode,
+ context: SSRTransformContext
+) {
+ // #5351: filter out comment children inside transition
+ node.children = node.children.filter(c => c.type !== NodeTypes.COMMENT)
+
+ const appear = wipMap.get(node)
+ if (appear) {
+ context.pushStringPart(``)
+ processChildren(node, context, false, true)
+ context.pushStringPart(``)
+ } else {
+ processChildren(node, context, false, true)
+ }
+}
diff --git a/packages/dts-built-test/README.md b/packages/dts-built-test/README.md
new file mode 100644
index 00000000000..8191d66e32e
--- /dev/null
+++ b/packages/dts-built-test/README.md
@@ -0,0 +1,5 @@
+# dts built-package test
+
+This package is private and for testing only. It is used to verify edge cases for external libraries that build their types using Vue core types - e.g. Vuetify as in [#8376](https://github.com/vuejs/core/issues/8376).
+
+When running the `build-dts` task, this package's types are built alongside other packages. Then, during `test-dts-only` it is imported and used in [`packages/dts-test/built.test-d.ts`](https://github.com/vuejs/core/blob/main/packages/dts-test/built.test-d.ts) to verify that the built types work correctly.
diff --git a/packages/dts-built-test/package.json b/packages/dts-built-test/package.json
new file mode 100644
index 00000000000..fb332328fb9
--- /dev/null
+++ b/packages/dts-built-test/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@vue/dts-built-test",
+ "private": true,
+ "types": "dist/dts-built-test.d.ts",
+ "dependencies": {
+ "@vue/shared": "workspace:*",
+ "@vue/reactivity": "workspace:*",
+ "vue": "workspace:*"
+ },
+ "version": "3.3.7"
+}
diff --git a/packages/dts-built-test/src/index.ts b/packages/dts-built-test/src/index.ts
new file mode 100644
index 00000000000..2d9d4033254
--- /dev/null
+++ b/packages/dts-built-test/src/index.ts
@@ -0,0 +1,12 @@
+import { defineComponent } from 'vue'
+
+const _CustomPropsNotErased = defineComponent({
+ props: {},
+ setup() {}
+})
+
+// #8376
+export const CustomPropsNotErased =
+ _CustomPropsNotErased as typeof _CustomPropsNotErased & {
+ foo: string
+ }
diff --git a/packages/dts-test/built.test-d.ts b/packages/dts-test/built.test-d.ts
new file mode 100644
index 00000000000..8ac3e333f99
--- /dev/null
+++ b/packages/dts-test/built.test-d.ts
@@ -0,0 +1,13 @@
+import { CustomPropsNotErased } from '@vue/dts-built-test'
+import { expectType, describe } from './utils'
+
+declare module 'vue' {
+ interface ComponentCustomProps {
+ custom?: number
+ }
+}
+
+// #8376 - custom props should not be erased
+describe('Custom Props not erased', () => {
+ expectType(new CustomPropsNotErased().$props.custom)
+})
diff --git a/packages/dts-test/h.test-d.ts b/packages/dts-test/h.test-d.ts
index 5c700800e94..f2e984b49b8 100644
--- a/packages/dts-test/h.test-d.ts
+++ b/packages/dts-test/h.test-d.ts
@@ -1,6 +1,7 @@
import {
h,
defineComponent,
+ DefineComponent,
ref,
Fragment,
Teleport,
@@ -231,3 +232,18 @@ describe('resolveComponent should work', () => {
message: '1'
})
})
+
+// #5431
+describe('h should work with multiple types', () => {
+ const serializers = {
+ Paragraph: 'p',
+ Component: {} as Component,
+ DefineComponent: {} as DefineComponent
+ }
+
+ const sampleComponent = serializers['' as keyof typeof serializers]
+
+ h(sampleComponent)
+ h(sampleComponent, {})
+ h(sampleComponent, {}, [])
+})
diff --git a/packages/dts-test/package.json b/packages/dts-test/package.json
index da8424e254c..ac246e704af 100644
--- a/packages/dts-test/package.json
+++ b/packages/dts-test/package.json
@@ -2,7 +2,8 @@
"name": "dts-test",
"private": true,
"dependencies": {
- "vue": "workspace:*"
+ "vue": "workspace:*",
+ "@vue/dts-built-test": "workspace:*"
},
- "version": "3.3.6"
+ "version": "3.3.7"
}
diff --git a/packages/reactivity-transform/package.json b/packages/reactivity-transform/package.json
index 887fedabc6e..b9f8a74f353 100644
--- a/packages/reactivity-transform/package.json
+++ b/packages/reactivity-transform/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/reactivity-transform",
- "version": "3.3.6",
+ "version": "3.3.7",
"description": "@vue/reactivity-transform",
"main": "dist/reactivity-transform.cjs.js",
"files": [
@@ -29,8 +29,8 @@
"homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme",
"dependencies": {
"@babel/parser": "^7.23.0",
- "@vue/compiler-core": "3.3.6",
- "@vue/shared": "3.3.6",
+ "@vue/compiler-core": "3.3.7",
+ "@vue/shared": "3.3.7",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.5"
},
diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts
index 635e6534abe..e34c7b31e40 100644
--- a/packages/reactivity/__tests__/effect.spec.ts
+++ b/packages/reactivity/__tests__/effect.spec.ts
@@ -243,6 +243,22 @@ describe('reactivity/effect', () => {
expect(dummy).toBe(undefined)
})
+ it('should support manipulating an array while observing symbol keyed properties', () => {
+ const key = Symbol()
+ let dummy
+ const array: any = reactive([1, 2, 3])
+ effect(() => (dummy = array[key]))
+
+ expect(dummy).toBe(undefined)
+ array.pop()
+ array.shift()
+ array.splice(0, 1)
+ expect(dummy).toBe(undefined)
+ array[key] = 'value'
+ array.length = 0
+ expect(dummy).toBe('value')
+ })
+
it('should observe function valued properties', () => {
const oldFunc = () => {}
const newFunc = () => {}
diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json
index ac49b4ec7d2..712bb26a0eb 100644
--- a/packages/reactivity/package.json
+++ b/packages/reactivity/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/reactivity",
- "version": "3.3.6",
+ "version": "3.3.7",
"description": "@vue/reactivity",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
@@ -36,6 +36,6 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme",
"dependencies": {
- "@vue/shared": "3.3.6"
+ "@vue/shared": "3.3.7"
}
}
diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts
index bbac96a4b2a..c982dbd0b5a 100644
--- a/packages/reactivity/src/effect.ts
+++ b/packages/reactivity/src/effect.ts
@@ -1,5 +1,5 @@
import { TrackOpTypes, TriggerOpTypes } from './operations'
-import { extend, isArray, isIntegerKey, isMap } from '@vue/shared'
+import { extend, isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared'
import { EffectScope, recordEffectScope } from './effectScope'
import {
createDep,
@@ -324,7 +324,7 @@ export function trigger(
} else if (key === 'length' && isArray(target)) {
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
- if (key === 'length' || key >= newLength) {
+ if (key === 'length' || (!isSymbol(key) && key >= newLength)) {
deps.push(dep)
}
})
diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts
index db713a3f276..2e989e368a3 100644
--- a/packages/runtime-core/__tests__/hmr.spec.ts
+++ b/packages/runtime-core/__tests__/hmr.spec.ts
@@ -20,7 +20,7 @@ const { createRecord, rerender, reload } = __VUE_HMR_RUNTIME__
registerRuntimeCompiler(compileToFunction)
function compileToFunction(template: string) {
- const { code } = baseCompile(template)
+ const { code } = baseCompile(template, { hoistStatic: true, hmr: true })
const render = new Function('Vue', code)(
runtimeTest
) as InternalRenderFunction
@@ -567,4 +567,40 @@ describe('hot module replacement', () => {
rerender(parentId, compileToFunction(`2`))
expect(serializeInner(root)).toBe(`2`)
})
+
+ // #6978, #7138, #7114
+ test('hoisted children array inside v-for', () => {
+ const root = nodeOps.createElement('div')
+ const appId = 'test-app-id'
+ const App: ComponentOptions = {
+ __hmrId: appId,
+ render: compileToFunction(
+ `
+ 2
+ 3
`
+ )
+ }
+ createRecord(appId, App)
+
+ render(h(App), root)
+ expect(serializeInner(root)).toBe(
+ `2
3
`
+ )
+
+ // move the 3
into the 1
+ rerender(
+ appId,
+ compileToFunction(
+ `
+ 2
`
+ )
+ )
+ expect(serializeInner(root)).toBe(
+ `2
`
+ )
+ })
})
diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts
index d3cfd47c6be..759804b97f1 100644
--- a/packages/runtime-core/__tests__/hydration.spec.ts
+++ b/packages/runtime-core/__tests__/hydration.spec.ts
@@ -18,10 +18,14 @@ import {
createVNode,
withDirectives,
vModelCheckbox,
- renderSlot
+ renderSlot,
+ Transition,
+ createCommentVNode,
+ vShow
} from '@vue/runtime-dom'
import { renderToString, SSRContext } from '@vue/server-renderer'
-import { PatchFlags } from '../../shared/src'
+import { PatchFlags } from '@vue/shared'
+import { vShowOldKey } from '../../runtime-dom/src/directives/vShow'
function mountWithHydration(html: string, render: () => any) {
const container = document.createElement('div')
@@ -393,6 +397,28 @@ describe('SSR hydration', () => {
)
})
+ // #6152
+ test('Teleport (disabled + as component root)', () => {
+ const { container } = mountWithHydration(
+ 'Parent fragment
Teleport content
',
+ () => [
+ h('div', 'Parent fragment'),
+ h(() =>
+ h(Teleport, { to: 'body', disabled: true }, [
+ h('div', 'Teleport content')
+ ])
+ )
+ ]
+ )
+ expect(document.body.innerHTML).toBe('')
+ expect(container.innerHTML).toBe(
+ 'Parent fragment
Teleport content
'
+ )
+ expect(
+ `Hydration completed but contains mismatches.`
+ ).not.toHaveBeenWarned()
+ })
+
test('Teleport (as component root)', () => {
const teleportContainer = document.createElement('div')
teleportContainer.id = 'teleport4'
@@ -994,6 +1020,74 @@ describe('SSR hydration', () => {
expect(`mismatch`).not.toHaveBeenWarned()
})
+ test('transition appear', () => {
+ const { vnode, container } = mountWithHydration(
+ `foo
`,
+ () =>
+ h(
+ Transition,
+ { appear: true },
+ {
+ default: () => h('div', 'foo')
+ }
+ )
+ )
+ expect(container.firstChild).toMatchInlineSnapshot(`
+
+ foo
+
+ `)
+ expect(vnode.el).toBe(container.firstChild)
+ expect(`mismatch`).not.toHaveBeenWarned()
+ })
+
+ test('transition appear with v-if', () => {
+ const show = false
+ const { vnode, container } = mountWithHydration(
+ ``,
+ () =>
+ h(
+ Transition,
+ { appear: true },
+ {
+ default: () => (show ? h('div', 'foo') : createCommentVNode(''))
+ }
+ )
+ )
+ expect(container.firstChild).toMatchInlineSnapshot('')
+ expect(vnode.el).toBe(container.firstChild)
+ expect(`mismatch`).not.toHaveBeenWarned()
+ })
+
+ test('transition appear with v-show', () => {
+ const show = false
+ const { vnode, container } = mountWithHydration(
+ `foo
`,
+ () =>
+ h(
+ Transition,
+ { appear: true },
+ {
+ default: () =>
+ withDirectives(createVNode('div', null, 'foo'), [[vShow, show]])
+ }
+ )
+ )
+ expect(container.firstChild).toMatchInlineSnapshot(`
+
+ foo
+
+ `)
+ expect((container.firstChild as any)[vShowOldKey]).toBe('')
+ expect(vnode.el).toBe(container.firstChild)
+ expect(`mismatch`).not.toHaveBeenWarned()
+ })
+
describe('mismatch handling', () => {
test('text node', () => {
const { container } = mountWithHydration(`foo`, () => 'bar')
diff --git a/packages/runtime-core/__tests__/scheduler.spec.ts b/packages/runtime-core/__tests__/scheduler.spec.ts
index 6246a87e8f7..119d0f7080c 100644
--- a/packages/runtime-core/__tests__/scheduler.spec.ts
+++ b/packages/runtime-core/__tests__/scheduler.spec.ts
@@ -143,6 +143,7 @@ describe('scheduler', () => {
queueJob(job1)
// cb2 should execute before the job
queueJob(cb2)
+ queueJob(cb3)
}
cb1.pre = true
@@ -152,9 +153,60 @@ describe('scheduler', () => {
cb2.pre = true
cb2.id = 1
+ const cb3 = () => {
+ calls.push('cb3')
+ }
+ cb3.pre = true
+ cb3.id = 1
+
queueJob(cb1)
await nextTick()
- expect(calls).toEqual(['cb1', 'cb2', 'job1'])
+ expect(calls).toEqual(['cb1', 'cb2', 'cb3', 'job1'])
+ })
+
+ it('should insert jobs after pre jobs with the same id', async () => {
+ const calls: string[] = []
+ const job1 = () => {
+ calls.push('job1')
+ }
+ job1.id = 1
+ job1.pre = true
+ const job2 = () => {
+ calls.push('job2')
+ queueJob(job5)
+ queueJob(job6)
+ }
+ job2.id = 2
+ job2.pre = true
+ const job3 = () => {
+ calls.push('job3')
+ }
+ job3.id = 2
+ job3.pre = true
+ const job4 = () => {
+ calls.push('job4')
+ }
+ job4.id = 3
+ job4.pre = true
+ const job5 = () => {
+ calls.push('job5')
+ }
+ job5.id = 2
+ const job6 = () => {
+ calls.push('job6')
+ }
+ job6.id = 2
+ job6.pre = true
+
+ // We need several jobs to test this properly, otherwise
+ // findInsertionIndex can yield the correct index by chance
+ queueJob(job4)
+ queueJob(job2)
+ queueJob(job3)
+ queueJob(job1)
+
+ await nextTick()
+ expect(calls).toEqual(['job1', 'job2', 'job3', 'job6', 'job5', 'job4'])
})
it('preFlushCb inside queueJob', async () => {
diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json
index 9d2c04d757f..a6335626bea 100644
--- a/packages/runtime-core/package.json
+++ b/packages/runtime-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-core",
- "version": "3.3.6",
+ "version": "3.3.7",
"description": "@vue/runtime-core",
"main": "index.js",
"module": "dist/runtime-core.esm-bundler.js",
@@ -32,7 +32,7 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme",
"dependencies": {
- "@vue/shared": "3.3.6",
- "@vue/reactivity": "3.3.6"
+ "@vue/shared": "3.3.7",
+ "@vue/reactivity": "3.3.7"
}
}
diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts
index dc575aafff9..b7ef1e07302 100644
--- a/packages/runtime-core/src/componentPublicInstance.ts
+++ b/packages/runtime-core/src/componentPublicInstance.ts
@@ -206,11 +206,9 @@ export type ComponentPublicInstance<
> = {
$: ComponentInternalInstance
$data: D
- $props: Prettify<
- MakeDefaultsOptional extends true
- ? Partial & Omit
- : P & PublicProps
- >
+ $props: MakeDefaultsOptional extends true
+ ? Partial & Omit & PublicProps, keyof Defaults>
+ : Prettify & PublicProps
$attrs: Data
$refs: Data
$slots: UnwrapSlotsType
diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts
index 0fa07d9beec..3640733d734 100644
--- a/packages/runtime-core/src/components/Suspense.ts
+++ b/packages/runtime-core/src/components/Suspense.ts
@@ -491,10 +491,12 @@ function createSuspenseBoundary(
container
} = suspense
+ // if there's a transition happening we need to wait it to finish.
+ let delayEnter: boolean | null = false
if (suspense.isHydrating) {
suspense.isHydrating = false
} else if (!resume) {
- const delayEnter =
+ delayEnter =
activeBranch &&
pendingBranch!.transition &&
pendingBranch!.transition.mode === 'out-in'
@@ -502,6 +504,7 @@ function createSuspenseBoundary(
activeBranch!.transition!.afterLeave = () => {
if (pendingId === suspense.pendingId) {
move(pendingBranch!, container, anchor, MoveType.ENTER)
+ queuePostFlushCb(effects)
}
}
}
@@ -538,8 +541,8 @@ function createSuspenseBoundary(
}
parent = parent.parent
}
- // no pending parent suspense, flush all jobs
- if (!hasUnresolvedAncestor) {
+ // no pending parent suspense nor transition, flush all jobs
+ if (!hasUnresolvedAncestor && !delayEnter) {
queuePostFlushCb(effects)
}
suspense.effects = []
diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts
index 73b27107b8b..4ca90262f2a 100644
--- a/packages/runtime-core/src/h.ts
+++ b/packages/runtime-core/src/h.ts
@@ -174,6 +174,14 @@ export function h
(
children?: RawChildren | RawSlots
): VNode
+// catch all types
+export function h(type: string | Component, children?: RawChildren): VNode
+export function h
(
+ type: string | Component
,
+ props?: (RawProps & P) | ({} extends P ? null : never),
+ children?: RawChildren | RawSlots
+): VNode
+
// Actual implementation
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
const l = arguments.length
diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts
index 89a00886332..4e91cb3d1cb 100644
--- a/packages/runtime-core/src/hydration.ts
+++ b/packages/runtime-core/src/hydration.ts
@@ -15,7 +15,7 @@ import { ComponentInternalInstance } from './component'
import { invokeDirectiveHook } from './directives'
import { warn } from './warning'
import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
-import { RendererInternals } from './renderer'
+import { needTransition, RendererInternals } from './renderer'
import { setRef } from './rendererTemplateRef'
import {
SuspenseImpl,
@@ -146,7 +146,17 @@ export function createHydrationFunctions(
break
case Comment:
if (domType !== DOMNodeTypes.COMMENT || isFragmentStart) {
- nextNode = onMismatch()
+ if ((node as Element).tagName.toLowerCase() === 'template') {
+ const content = (vnode.el! as HTMLTemplateElement).content
+ .firstChild!
+
+ // replace node with inner children
+ replaceNode(content, node, parentComponent)
+ vnode.el = node = content
+ nextNode = nextSibling(node)
+ } else {
+ nextNode = onMismatch()
+ }
} else {
nextNode = nextSibling(node)
}
@@ -196,9 +206,10 @@ export function createHydrationFunctions(
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
if (
- domType !== DOMNodeTypes.ELEMENT ||
- (vnode.type as string).toLowerCase() !==
- (node as Element).tagName.toLowerCase()
+ (domType !== DOMNodeTypes.ELEMENT ||
+ (vnode.type as string).toLowerCase() !==
+ (node as Element).tagName.toLowerCase()) &&
+ !isTemplateNode(node as Element)
) {
nextNode = onMismatch()
} else {
@@ -217,6 +228,21 @@ export function createHydrationFunctions(
// on its sub-tree.
vnode.slotScopeIds = slotScopeIds
const container = parentNode(node)!
+
+ // Locate the next node.
+ if (isFragmentStart) {
+ // If it's a fragment: since components may be async, we cannot rely
+ // on component's rendered output to determine the end of the
+ // fragment. Instead, we do a lookahead to find the end anchor node.
+ nextNode = locateClosingAnchor(node)
+ } else if (isComment(node) && node.data === 'teleport start') {
+ // #4293 #6152
+ // If a teleport is at component root, look ahead for teleport end.
+ nextNode = locateClosingAnchor(node, node.data, 'teleport end')
+ } else {
+ nextNode = nextSibling(node)
+ }
+
mountComponent(
vnode,
container,
@@ -227,22 +253,6 @@ export function createHydrationFunctions(
optimized
)
- // component may be async, so in the case of fragments we cannot rely
- // on component's rendered output to determine the end of the fragment
- // instead, we do a lookahead to find the end anchor node.
- nextNode = isFragmentStart
- ? locateClosingAsyncAnchor(node)
- : nextSibling(node)
-
- // #4293 teleport as component root
- if (
- nextNode &&
- isComment(nextNode) &&
- nextNode.data === 'teleport end'
- ) {
- nextNode = nextSibling(nextNode)
- }
-
// #3787
// if component is async, it may get moved / unmounted before its
// inner component is loaded, so we need to give it a placeholder
@@ -309,7 +319,7 @@ export function createHydrationFunctions(
optimized: boolean
) => {
optimized = optimized || !!vnode.dynamicChildren
- const { type, props, patchFlag, shapeFlag, dirs } = vnode
+ const { type, props, patchFlag, shapeFlag, dirs, transition } = vnode
// #4006 for form elements with non-string v-model value bindings
// e.g.