mirror of https://github.com/vuejs/core.git
refactor: vapor hydration
This commit is contained in:
parent
61eaf4eb9e
commit
c511b9a5d7
|
@ -163,6 +163,7 @@ export interface ComponentNode extends BaseElementNode {
|
||||||
| MemoExpression // when cached by v-memo
|
| MemoExpression // when cached by v-memo
|
||||||
| undefined
|
| undefined
|
||||||
ssrCodegenNode?: CallExpression
|
ssrCodegenNode?: CallExpression
|
||||||
|
anchor?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SlotOutletNode extends BaseElementNode {
|
export interface SlotOutletNode extends BaseElementNode {
|
||||||
|
@ -172,6 +173,7 @@ export interface SlotOutletNode extends BaseElementNode {
|
||||||
| CacheExpression // when cached by v-once
|
| CacheExpression // when cached by v-once
|
||||||
| undefined
|
| undefined
|
||||||
ssrCodegenNode?: CallExpression
|
ssrCodegenNode?: CallExpression
|
||||||
|
anchor?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TemplateNode extends BaseElementNode {
|
export interface TemplateNode extends BaseElementNode {
|
||||||
|
@ -287,6 +289,7 @@ export interface IfNode extends Node {
|
||||||
type: NodeTypes.IF
|
type: NodeTypes.IF
|
||||||
branches: IfBranchNode[]
|
branches: IfBranchNode[]
|
||||||
codegenNode?: IfConditionalExpression | CacheExpression // <div v-if v-once>
|
codegenNode?: IfConditionalExpression | CacheExpression // <div v-if v-once>
|
||||||
|
anchor?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IfBranchNode extends Node {
|
export interface IfBranchNode extends Node {
|
||||||
|
@ -306,6 +309,7 @@ export interface ForNode extends Node {
|
||||||
parseResult: ForParseResult
|
parseResult: ForParseResult
|
||||||
children: TemplateChildNode[]
|
children: TemplateChildNode[]
|
||||||
codegenNode?: ForCodegenNode
|
codegenNode?: ForCodegenNode
|
||||||
|
anchor?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ForParseResult {
|
export interface ForParseResult {
|
||||||
|
|
|
@ -167,6 +167,7 @@ function createCodegenContext(
|
||||||
ssr = false,
|
ssr = false,
|
||||||
isTS = false,
|
isTS = false,
|
||||||
inSSR = false,
|
inSSR = false,
|
||||||
|
vapor = false,
|
||||||
}: CodegenOptions,
|
}: CodegenOptions,
|
||||||
): CodegenContext {
|
): CodegenContext {
|
||||||
const context: CodegenContext = {
|
const context: CodegenContext = {
|
||||||
|
@ -182,6 +183,7 @@ function createCodegenContext(
|
||||||
ssr,
|
ssr,
|
||||||
isTS,
|
isTS,
|
||||||
inSSR,
|
inSSR,
|
||||||
|
vapor,
|
||||||
source: ast.source,
|
source: ast.source,
|
||||||
code: ``,
|
code: ``,
|
||||||
column: 1,
|
column: 1,
|
||||||
|
|
|
@ -220,6 +220,11 @@ interface SharedTransformCodegenOptions {
|
||||||
* @default 'template.vue.html'
|
* @default 'template.vue.html'
|
||||||
*/
|
*/
|
||||||
filename?: string
|
filename?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates vapor component
|
||||||
|
*/
|
||||||
|
vapor?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransformOptions
|
export interface TransformOptions
|
||||||
|
|
|
@ -146,6 +146,7 @@ export function createTransformContext(
|
||||||
slotted = true,
|
slotted = true,
|
||||||
ssr = false,
|
ssr = false,
|
||||||
inSSR = false,
|
inSSR = false,
|
||||||
|
vapor = false,
|
||||||
ssrCssVars = ``,
|
ssrCssVars = ``,
|
||||||
bindingMetadata = EMPTY_OBJ,
|
bindingMetadata = EMPTY_OBJ,
|
||||||
inline = false,
|
inline = false,
|
||||||
|
@ -173,6 +174,7 @@ export function createTransformContext(
|
||||||
slotted,
|
slotted,
|
||||||
ssr,
|
ssr,
|
||||||
inSSR,
|
inSSR,
|
||||||
|
vapor,
|
||||||
ssrCssVars,
|
ssrCssVars,
|
||||||
bindingMetadata,
|
bindingMetadata,
|
||||||
inline,
|
inline,
|
||||||
|
|
|
@ -253,6 +253,7 @@ function doCompileTemplate({
|
||||||
slotted,
|
slotted,
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
...compilerOptions,
|
...compilerOptions,
|
||||||
|
vapor,
|
||||||
hmr: !isProd,
|
hmr: !isProd,
|
||||||
nodeTransforms: nodeTransforms.concat(
|
nodeTransforms: nodeTransforms.concat(
|
||||||
compilerOptions.nodeTransforms || [],
|
compilerOptions.nodeTransforms || [],
|
||||||
|
|
|
@ -39,7 +39,6 @@ describe('ssr: components', () => {
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("foo"), _mergeProps({ prop: "b" }, _attrs), null), _parent)
|
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("foo"), _mergeProps({ prop: "b" }, _attrs), null), _parent)
|
||||||
_push(\`<!--dynamic-component-->\`)
|
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
@ -50,7 +49,6 @@ describe('ssr: components', () => {
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.foo), _mergeProps({ prop: "b" }, _attrs), null), _parent)
|
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.foo), _mergeProps({ prop: "b" }, _attrs), null), _parent)
|
||||||
_push(\`<!--dynamic-component-->\`)
|
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -246,8 +244,7 @@ describe('ssr: components', () => {
|
||||||
_ssrRenderList(list, (i) => {
|
_ssrRenderList(list, (i) => {
|
||||||
_push(\`<span\${_scopeId}></span>\`)
|
_push(\`<span\${_scopeId}></span>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for--></div>\`)
|
_push(\`<!--]--></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -270,8 +267,7 @@ describe('ssr: components', () => {
|
||||||
_ssrRenderList(_ctx.list, (i) => {
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
_push(\`<span\${_scopeId}></span>\`)
|
_push(\`<span\${_scopeId}></span>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for--></div>\`)
|
_push(\`<!--]--></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -365,7 +361,6 @@ describe('ssr: components', () => {
|
||||||
_push(\`\`)
|
_push(\`\`)
|
||||||
if (false) {
|
if (false) {
|
||||||
_push(\`<div\${_scopeId}></div>\`)
|
_push(\`<div\${_scopeId}></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -396,50 +396,4 @@ describe('ssr: element', () => {
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('dynamic anchor', () => {
|
|
||||||
test('two consecutive components', () => {
|
|
||||||
expect(
|
|
||||||
getCompiledString(`
|
|
||||||
<div>
|
|
||||||
<div/>
|
|
||||||
<Comp1/>
|
|
||||||
<Comp2/>
|
|
||||||
<div/>
|
|
||||||
</div>
|
|
||||||
`),
|
|
||||||
).toMatchInlineSnapshot(`
|
|
||||||
"\`<div><div></div><!--[[-->\`)
|
|
||||||
_push(_ssrRenderComponent(_component_Comp1, null, null, _parent))
|
|
||||||
_push(\`<!--]]--><!--[[-->\`)
|
|
||||||
_push(_ssrRenderComponent(_component_Comp2, null, null, _parent))
|
|
||||||
_push(\`<!--]]--><div></div></div>\`"
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('multiple consecutive components', () => {
|
|
||||||
expect(
|
|
||||||
getCompiledString(`
|
|
||||||
<div>
|
|
||||||
<div/>
|
|
||||||
<Comp1/>
|
|
||||||
<Comp2/>
|
|
||||||
<Comp3/>
|
|
||||||
<Comp4/>
|
|
||||||
<div/>
|
|
||||||
</div>
|
|
||||||
`),
|
|
||||||
).toMatchInlineSnapshot(`
|
|
||||||
"\`<div><div></div><!--[[-->\`)
|
|
||||||
_push(_ssrRenderComponent(_component_Comp1, null, null, _parent))
|
|
||||||
_push(\`<!--]]--><!--[[-->\`)
|
|
||||||
_push(_ssrRenderComponent(_component_Comp2, null, null, _parent))
|
|
||||||
_push(\`<!--]]--><!--[[-->\`)
|
|
||||||
_push(_ssrRenderComponent(_component_Comp3, null, null, _parent))
|
|
||||||
_push(\`<!--]]--><!--[[-->\`)
|
|
||||||
_push(_ssrRenderComponent(_component_Comp4, null, null, _parent))
|
|
||||||
_push(\`<!--]]--><div></div></div>\`"
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -29,7 +29,6 @@ describe('ssr: attrs fallthrough', () => {
|
||||||
_push(\`<!--[-->\`)
|
_push(\`<!--[-->\`)
|
||||||
if (true) {
|
if (true) {
|
||||||
_push(\`<div></div>\`)
|
_push(\`<div></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,14 +70,12 @@ describe('ssr: inject <style vars>', () => {
|
||||||
const _cssVars = { style: { color: _ctx.color }}
|
const _cssVars = { style: { color: _ctx.color }}
|
||||||
if (_ctx.ok) {
|
if (_ctx.ok) {
|
||||||
_push(\`<div\${_ssrRenderAttrs(_mergeProps(_attrs, _cssVars))}></div>\`)
|
_push(\`<div\${_ssrRenderAttrs(_mergeProps(_attrs, _cssVars))}></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!--[--><div\${
|
_push(\`<!--[--><div\${
|
||||||
_ssrRenderAttrs(_cssVars)
|
_ssrRenderAttrs(_cssVars)
|
||||||
}></div><div\${
|
}></div><div\${
|
||||||
_ssrRenderAttrs(_cssVars)
|
_ssrRenderAttrs(_cssVars)
|
||||||
}></div><!--]-->\`)
|
}></div><!--]-->\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
|
|
|
@ -153,7 +153,6 @@ describe('ssr: <slot>', () => {
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
if (true) {
|
if (true) {
|
||||||
_ssrRenderSlotInner(_ctx.$slots, "default", {}, null, _push, _parent, null, true)
|
_ssrRenderSlotInner(_ctx.$slots, "default", {}, null, _push, _parent, null, true)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ describe('transition-group', () => {
|
||||||
_ssrRenderList(_ctx.list, (i) => {
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
_push(\`<div></div>\`)
|
_push(\`<div></div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--for--><!--]-->\`)
|
_push(\`<!--]-->\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -33,7 +33,7 @@ describe('transition-group', () => {
|
||||||
_ssrRenderList(_ctx.list, (i) => {
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
_push(\`<div></div>\`)
|
_push(\`<div></div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--for--></ul>\`)
|
_push(\`</ul>\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -52,10 +52,8 @@ describe('transition-group', () => {
|
||||||
_ssrRenderList(_ctx.list, (i) => {
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
_push(\`<div></div>\`)
|
_push(\`<div></div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--for-->\`)
|
|
||||||
if (false) {
|
if (false) {
|
||||||
_push(\`<div></div>\`)
|
_push(\`<div></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
}
|
}
|
||||||
_push(\`</ul>\`)
|
_push(\`</ul>\`)
|
||||||
}"
|
}"
|
||||||
|
@ -76,7 +74,7 @@ describe('transition-group', () => {
|
||||||
_ssrRenderList(_ctx.list, (i) => {
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
_push(\`<div></div>\`)
|
_push(\`<div></div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--for--></ul>\`)
|
_push(\`</ul>\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -98,7 +96,7 @@ describe('transition-group', () => {
|
||||||
_ssrRenderList(_ctx.list, (i) => {
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
_push(\`<div></div>\`)
|
_push(\`<div></div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--for--></\${_ctx.someTag}>\`)
|
_push(\`</\${_ctx.someTag}>\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -120,14 +118,11 @@ describe('transition-group', () => {
|
||||||
_ssrRenderList(10, (i) => {
|
_ssrRenderList(10, (i) => {
|
||||||
_push(\`<div></div>\`)
|
_push(\`<div></div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--for-->\`)
|
|
||||||
_ssrRenderList(10, (i) => {
|
_ssrRenderList(10, (i) => {
|
||||||
_push(\`<div></div>\`)
|
_push(\`<div></div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--for-->\`)
|
|
||||||
if (_ctx.ok) {
|
if (_ctx.ok) {
|
||||||
_push(\`<div>ok</div>\`)
|
_push(\`<div>ok</div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
}
|
}
|
||||||
_push(\`<!--]-->\`)
|
_push(\`<!--]-->\`)
|
||||||
}"
|
}"
|
||||||
|
|
|
@ -10,7 +10,7 @@ describe('ssr: v-for', () => {
|
||||||
_ssrRenderList(_ctx.list, (i) => {
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
_push(\`<div></div>\`)
|
_push(\`<div></div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for-->\`)
|
_push(\`<!--]-->\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -25,7 +25,7 @@ describe('ssr: v-for', () => {
|
||||||
_ssrRenderList(_ctx.list, (i) => {
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
_push(\`<div>foo<span>bar</span></div>\`)
|
_push(\`<div>foo<span>bar</span></div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for-->\`)
|
_push(\`<!--]-->\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -51,9 +51,9 @@ describe('ssr: v-for', () => {
|
||||||
_ssrInterpolate(j)
|
_ssrInterpolate(j)
|
||||||
}</div>\`)
|
}</div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for--></div>\`)
|
_push(\`<!--]--></div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for-->\`)
|
_push(\`<!--]-->\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -68,7 +68,7 @@ describe('ssr: v-for', () => {
|
||||||
_ssrRenderList(_ctx.list, (i) => {
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
_push(\`<!--[-->\${_ssrInterpolate(i)}<!--]-->\`)
|
_push(\`<!--[-->\${_ssrInterpolate(i)}<!--]-->\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for-->\`)
|
_push(\`<!--]-->\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -85,7 +85,7 @@ describe('ssr: v-for', () => {
|
||||||
_ssrRenderList(_ctx.list, (i) => {
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
_push(\`<span>\${_ssrInterpolate(i)}</span>\`)
|
_push(\`<span>\${_ssrInterpolate(i)}</span>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for-->\`)
|
_push(\`<!--]-->\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -107,7 +107,7 @@ describe('ssr: v-for', () => {
|
||||||
_ssrInterpolate(i + 1)
|
_ssrInterpolate(i + 1)
|
||||||
}</span><!--]-->\`)
|
}</span><!--]-->\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for-->\`)
|
_push(\`<!--]-->\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -127,7 +127,7 @@ describe('ssr: v-for', () => {
|
||||||
_ssrRenderList(_ctx.list, ({ foo }, index) => {
|
_ssrRenderList(_ctx.list, ({ foo }, index) => {
|
||||||
_push(\`<div>\${_ssrInterpolate(foo + _ctx.bar + index)}</div>\`)
|
_push(\`<div>\${_ssrInterpolate(foo + _ctx.bar + index)}</div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for-->\`)
|
_push(\`<!--]-->\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,6 @@ describe('ssr: v-if', () => {
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
if (_ctx.foo) {
|
if (_ctx.foo) {
|
||||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -24,7 +23,6 @@ describe('ssr: v-if', () => {
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
if (_ctx.foo) {
|
if (_ctx.foo) {
|
||||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}>hello<span>ok</span></div>\`)
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}>hello<span>ok</span></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -40,10 +38,8 @@ describe('ssr: v-if', () => {
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
if (_ctx.foo) {
|
if (_ctx.foo) {
|
||||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
|
_push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
|
@ -57,10 +53,8 @@ describe('ssr: v-if', () => {
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
if (_ctx.foo) {
|
if (_ctx.foo) {
|
||||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else if (_ctx.bar) {
|
} else if (_ctx.bar) {
|
||||||
_push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
|
_push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -76,13 +70,10 @@ describe('ssr: v-if', () => {
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
if (_ctx.foo) {
|
if (_ctx.foo) {
|
||||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else if (_ctx.bar) {
|
} else if (_ctx.bar) {
|
||||||
_push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
|
_push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<p\${_ssrRenderAttrs(_attrs)}></p>\`)
|
_push(\`<p\${_ssrRenderAttrs(_attrs)}></p>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
|
@ -95,7 +86,6 @@ describe('ssr: v-if', () => {
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
if (_ctx.foo) {
|
if (_ctx.foo) {
|
||||||
_push(\`<!--[-->hello<!--]-->\`)
|
_push(\`<!--[-->hello<!--]-->\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -112,7 +102,6 @@ describe('ssr: v-if', () => {
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
if (_ctx.foo) {
|
if (_ctx.foo) {
|
||||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}>hi</div>\`)
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}>hi</div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -129,7 +118,6 @@ describe('ssr: v-if', () => {
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
if (_ctx.foo) {
|
if (_ctx.foo) {
|
||||||
_push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`)
|
_push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -149,8 +137,7 @@ describe('ssr: v-if', () => {
|
||||||
_ssrRenderList(_ctx.list, (i) => {
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
_push(\`<div></div>\`)
|
_push(\`<div></div>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for-->\`)
|
_push(\`<!--]-->\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -169,10 +156,8 @@ describe('ssr: v-if', () => {
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
if (_ctx.foo) {
|
if (_ctx.foo) {
|
||||||
_push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`)
|
_push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
|
|
|
@ -70,7 +70,7 @@ describe('ssr: v-model', () => {
|
||||||
: _ssrLooseEqual(_ctx.model, i))) ? " selected" : ""
|
: _ssrLooseEqual(_ctx.model, i))) ? " selected" : ""
|
||||||
}></option>\`)
|
}></option>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for--></select></div>\`)
|
_push(\`<!--]--></select></div>\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
@ -91,7 +91,6 @@ describe('ssr: v-model', () => {
|
||||||
? _ssrLooseContain(_ctx.model, _ctx.i)
|
? _ssrLooseContain(_ctx.model, _ctx.i)
|
||||||
: _ssrLooseEqual(_ctx.model, _ctx.i))) ? " selected" : ""
|
: _ssrLooseEqual(_ctx.model, _ctx.i))) ? " selected" : ""
|
||||||
}></option>\`)
|
}></option>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -191,7 +190,7 @@ describe('ssr: v-model', () => {
|
||||||
_ssrInterpolate(item)
|
_ssrInterpolate(item)
|
||||||
}</option>\`)
|
}</option>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for--></optgroup></select></div>\`)
|
_push(\`<!--]--></optgroup></select></div>\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
@ -217,7 +216,6 @@ describe('ssr: v-model', () => {
|
||||||
}>\${
|
}>\${
|
||||||
_ssrInterpolate(_ctx.item)
|
_ssrInterpolate(_ctx.item)
|
||||||
}</option>\`)
|
}</option>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -252,8 +250,7 @@ describe('ssr: v-model', () => {
|
||||||
_ssrInterpolate(item)
|
_ssrInterpolate(item)
|
||||||
}</option>\`)
|
}</option>\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for-->\`)
|
_push(\`<!--]-->\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
|
@ -287,13 +284,12 @@ describe('ssr: v-model', () => {
|
||||||
}>\${
|
}>\${
|
||||||
_ssrInterpolate(item)
|
_ssrInterpolate(item)
|
||||||
}</option>\`)
|
}</option>\`)
|
||||||
_push(\`<!--if-->\`)
|
|
||||||
} else {
|
} else {
|
||||||
_push(\`<!---->\`)
|
_push(\`<!---->\`)
|
||||||
}
|
}
|
||||||
_push(\`<!--]-->\`)
|
_push(\`<!--]-->\`)
|
||||||
})
|
})
|
||||||
_push(\`<!--]--><!--for--></optgroup></select></div>\`)
|
_push(\`<!--]--></optgroup></select></div>\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,322 @@
|
||||||
|
// import {
|
||||||
|
// BLOCK_APPEND_ANCHOR_LABEL,
|
||||||
|
// BLOCK_INSERTION_ANCHOR_LABEL,
|
||||||
|
// BLOCK_PREPEND_ANCHOR_LABEL,
|
||||||
|
// } from '@vue/shared'
|
||||||
|
import { getCompiledString } from './utils'
|
||||||
|
|
||||||
|
describe('insertion anchors', () => {
|
||||||
|
describe('prepend', () => {
|
||||||
|
test('prepend anchor with component', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString('<div><Comp/><Comp/><span/></div>', { vapor: true }),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<div><!--[p-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent))
|
||||||
|
_push(\`<!--p]--><!--[p-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent))
|
||||||
|
_push(\`<!--p]--><span></span></div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prepend anchor with component in ssr slot vnode fallback', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString(
|
||||||
|
`<component :is="'div'">
|
||||||
|
<div><Comp/><Comp/><span/></div>
|
||||||
|
</component>`,
|
||||||
|
{ vapor: true },
|
||||||
|
),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<!--[a-->\`)
|
||||||
|
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
|
||||||
|
default: _withCtx((_, _push, _parent, _scopeId) => {
|
||||||
|
if (_push) {
|
||||||
|
_push(\`<div\${_scopeId}><!--[p-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
|
||||||
|
_push(\`<!--p]--><!--[p-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
|
||||||
|
_push(\`<!--p]--><span\${_scopeId}></span></div>\`)
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
_createVNode("div", null, [
|
||||||
|
_createCommentVNode("[p"),
|
||||||
|
_createVNode(_component_Comp),
|
||||||
|
_createCommentVNode("p]"),
|
||||||
|
_createCommentVNode("[p"),
|
||||||
|
_createVNode(_component_Comp),
|
||||||
|
_createCommentVNode("p]"),
|
||||||
|
_createVNode("span")
|
||||||
|
])
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_: 1 /* STABLE */
|
||||||
|
}), _parent)
|
||||||
|
_push(\`<!--dynamic-component--><!--a]-->\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prepend anchor with slot', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString('<div><slot name="foo"/><slot/><span/></div>', {
|
||||||
|
vapor: true,
|
||||||
|
}),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<div><!--[p-->\`)
|
||||||
|
_ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent)
|
||||||
|
_push(\`<!--slot--><!--p]--><!--[p-->\`)
|
||||||
|
_ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent)
|
||||||
|
_push(\`<!--slot--><!--p]--><span></span></div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prepend anchor with slot in ssr slot vnode fallback', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString(
|
||||||
|
`<component :is="'div'">
|
||||||
|
<div><slot name="foo"/><slot/><span/></div>
|
||||||
|
</component>`,
|
||||||
|
{ vapor: true },
|
||||||
|
),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<!--[a-->\`)
|
||||||
|
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
|
||||||
|
default: _withCtx((_, _push, _parent, _scopeId) => {
|
||||||
|
if (_push) {
|
||||||
|
_push(\`<div\${_scopeId}><!--[p-->\`)
|
||||||
|
_ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent, _scopeId)
|
||||||
|
_push(\`<!--slot--><!--p]--><!--[p-->\`)
|
||||||
|
_ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent, _scopeId)
|
||||||
|
_push(\`<!--slot--><!--p]--><span\${_scopeId}></span></div>\`)
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
_createVNode("div", null, [
|
||||||
|
_createCommentVNode("[p"),
|
||||||
|
_renderSlot(_ctx.$slots, "foo"),
|
||||||
|
_createCommentVNode("p]"),
|
||||||
|
_createCommentVNode("[p"),
|
||||||
|
_renderSlot(_ctx.$slots, "default"),
|
||||||
|
_createCommentVNode("p]"),
|
||||||
|
_createVNode("span")
|
||||||
|
])
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_: 3 /* FORWARDED */
|
||||||
|
}), _parent)
|
||||||
|
_push(\`<!--dynamic-component--><!--a]-->\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prepend anchor with v-if', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString('<div><span v-if="foo"/><span/></div>', {
|
||||||
|
vapor: true,
|
||||||
|
}),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<div><!--[p-->\`)
|
||||||
|
if (_ctx.foo) {
|
||||||
|
_push(\`<span></span>\`)
|
||||||
|
_push(\`<!--if-->\`)
|
||||||
|
} else {
|
||||||
|
_push(\`<!---->\`)
|
||||||
|
}
|
||||||
|
_push(\`<!--p]--><span></span></div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prepend anchor with v-if in ssr slot vnode fallback', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString(
|
||||||
|
`<component :is="'div'">
|
||||||
|
<div><span v-if="foo"/><span/></div>
|
||||||
|
</component>`,
|
||||||
|
{ vapor: true },
|
||||||
|
),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<!--[a-->\`)
|
||||||
|
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
|
||||||
|
default: _withCtx((_, _push, _parent, _scopeId) => {
|
||||||
|
if (_push) {
|
||||||
|
_push(\`<div\${_scopeId}><!--[p-->\`)
|
||||||
|
if (_ctx.foo) {
|
||||||
|
_push(\`<span\${_scopeId}></span>\`)
|
||||||
|
_push(\`<!--if-->\`)
|
||||||
|
} else {
|
||||||
|
_push(\`<!---->\`)
|
||||||
|
}
|
||||||
|
_push(\`<!--p]--><span\${_scopeId}></span></div>\`)
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
_createVNode("div", null, [
|
||||||
|
_createCommentVNode("[p"),
|
||||||
|
(_ctx.foo)
|
||||||
|
? (_openBlock(), _createBlock("span", { key: 0 }))
|
||||||
|
: _createCommentVNode("v-if", true),
|
||||||
|
_createCommentVNode("p]"),
|
||||||
|
_createVNode("span")
|
||||||
|
])
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_: 1 /* STABLE */
|
||||||
|
}), _parent)
|
||||||
|
_push(\`<!--dynamic-component--><!--a]-->\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prepend anchor with v-for', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString('<div><span v-for="item in items"/><span/></div>', {
|
||||||
|
vapor: true,
|
||||||
|
}),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<div><!--[p--><!--[-->\`)
|
||||||
|
_ssrRenderList(_ctx.items, (item) => {
|
||||||
|
_push(\`<span></span>\`)
|
||||||
|
})
|
||||||
|
_push(\`<!--]--><!--for--><!--p]--><span></span></div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prepend anchor with v-for in ssr slot vnode fallback', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString(
|
||||||
|
`<component :is="'div'">
|
||||||
|
<div><span v-for="item in items"/><span/></div>
|
||||||
|
</component>`,
|
||||||
|
{ vapor: true },
|
||||||
|
),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<!--[a-->\`)
|
||||||
|
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
|
||||||
|
default: _withCtx((_, _push, _parent, _scopeId) => {
|
||||||
|
if (_push) {
|
||||||
|
_push(\`<div\${_scopeId}><!--[p--><!--[-->\`)
|
||||||
|
_ssrRenderList(_ctx.items, (item) => {
|
||||||
|
_push(\`<span\${_scopeId}></span>\`)
|
||||||
|
})
|
||||||
|
_push(\`<!--]--><!--for--><!--p]--><span\${_scopeId}></span></div>\`)
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
_createVNode("div", null, [
|
||||||
|
_createCommentVNode("[p"),
|
||||||
|
(_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.items, (item) => {
|
||||||
|
return (_openBlock(), _createBlock("span"))
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */)),
|
||||||
|
_createCommentVNode("p]"),
|
||||||
|
_createVNode("span")
|
||||||
|
])
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_: 1 /* STABLE */
|
||||||
|
}), _parent)
|
||||||
|
_push(\`<!--dynamic-component--><!--a]-->\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO add more tests
|
||||||
|
describe('insertion anchor', () => {
|
||||||
|
test('insertion anchor with component', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString('<div><span/><Comp/><span/></div>', { vapor: true }),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<div><span></span><!--[i-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent))
|
||||||
|
_push(\`<!--i]--><span></span></div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO add more tests
|
||||||
|
describe('append', () => {
|
||||||
|
test('append anchor', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString('<div><span/><Comp/><Comp/></div>', { vapor: true }),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<div><span></span><!--[a-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent))
|
||||||
|
_push(\`<!--a]--><!--[a-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent))
|
||||||
|
_push(\`<!--a]--></div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('mixed anchors', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString('<div><Comp/><span/><Comp/><span/><Comp/></div>', {
|
||||||
|
vapor: true,
|
||||||
|
}),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<div><!--[p-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent))
|
||||||
|
_push(\`<!--p]--><span></span><!--[i-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent))
|
||||||
|
_push(\`<!--i]--><span></span><!--[a-->\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent))
|
||||||
|
_push(\`<!--a]--></div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('mixed anchors in ssr slot vnode fallback', () => {
|
||||||
|
expect(
|
||||||
|
getCompiledString(
|
||||||
|
`<component :is="'div'"><Comp/><span/><Comp/><span/><Comp/></component>`,
|
||||||
|
{
|
||||||
|
vapor: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"\`<!--[a-->\`)
|
||||||
|
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
|
||||||
|
default: _withCtx((_, _push, _parent, _scopeId) => {
|
||||||
|
if (_push) {
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
|
||||||
|
_push(\`<span\${_scopeId}></span>\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
|
||||||
|
_push(\`<span\${_scopeId}></span>\`)
|
||||||
|
_push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
_createCommentVNode("[p"),
|
||||||
|
_createVNode(_component_Comp),
|
||||||
|
_createCommentVNode("p]"),
|
||||||
|
_createVNode("span"),
|
||||||
|
_createCommentVNode("[i"),
|
||||||
|
_createVNode(_component_Comp),
|
||||||
|
_createCommentVNode("i]"),
|
||||||
|
_createVNode("span"),
|
||||||
|
_createCommentVNode("[a"),
|
||||||
|
_createVNode(_component_Comp),
|
||||||
|
_createCommentVNode("a]")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_: 1 /* STABLE */
|
||||||
|
}), _parent)
|
||||||
|
_push(\`<!--dynamic-component--><!--a]-->\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe.todo('block anchors', () => {
|
||||||
|
test('if', () => {})
|
||||||
|
|
||||||
|
test('if in ssr slot vnode fallback', () => {})
|
||||||
|
|
||||||
|
test('for', () => {})
|
||||||
|
|
||||||
|
test('for in ssr slot vnode fallback', () => {})
|
||||||
|
|
||||||
|
test('slot', () => {})
|
||||||
|
|
||||||
|
test('slot in ssr slot vnode fallback', () => {})
|
||||||
|
|
||||||
|
test('dynamic component', () => {})
|
||||||
|
|
||||||
|
test('dynamic in ssr slot vnode fallback', () => {})
|
||||||
|
})
|
|
@ -1,10 +1,14 @@
|
||||||
|
import type { CompilerOptions } from '@vue/compiler-core'
|
||||||
import { compile } from '../src'
|
import { compile } from '../src'
|
||||||
|
|
||||||
export function getCompiledString(src: string): string {
|
export function getCompiledString(
|
||||||
|
src: string,
|
||||||
|
options?: CompilerOptions,
|
||||||
|
): string {
|
||||||
// Wrap src template in a root div so that it doesn't get injected
|
// Wrap src template in a root div so that it doesn't get injected
|
||||||
// fallthrough attr. This results in less noise in generated snapshots
|
// fallthrough attr. This results in less noise in generated snapshots
|
||||||
// but also means this util can only be used for non-root cases.
|
// but also means this util can only be used for non-root cases.
|
||||||
const { code } = compile(`<div>${src}</div>`)
|
const { code } = compile(`<div>${src}</div>`, options)
|
||||||
const match = code.match(
|
const match = code.match(
|
||||||
/_push\(\`<div\${\s*_ssrRenderAttrs\(_attrs\)\s*}>([^]*)<\/div>\`\)/,
|
/_push\(\`<div\${\s*_ssrRenderAttrs\(_attrs\)\s*}>([^]*)<\/div>\`\)/,
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,8 +22,9 @@ import {
|
||||||
processExpression,
|
processExpression,
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import {
|
import {
|
||||||
BLOCK_END_ANCHOR_LABEL,
|
BLOCK_APPEND_ANCHOR_LABEL,
|
||||||
BLOCK_START_ANCHOR_LABEL,
|
BLOCK_INSERTION_ANCHOR_LABEL,
|
||||||
|
BLOCK_PREPEND_ANCHOR_LABEL,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
isString,
|
isString,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
@ -163,33 +164,23 @@ export function processChildren(
|
||||||
asFragment = false,
|
asFragment = false,
|
||||||
disableNestedFragments = false,
|
disableNestedFragments = false,
|
||||||
disableComment = false,
|
disableComment = false,
|
||||||
asBlock = false,
|
|
||||||
): void {
|
): void {
|
||||||
if (asBlock) {
|
|
||||||
context.pushStringPart(`<!--${BLOCK_START_ANCHOR_LABEL}-->`)
|
|
||||||
}
|
|
||||||
if (asFragment) {
|
if (asFragment) {
|
||||||
context.pushStringPart(`<!--[-->`)
|
context.pushStringPart(`<!--[-->`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { children, type, tagType } = parent as PlainElementNode
|
const { children, type, tagType } = parent as PlainElementNode
|
||||||
const inElement =
|
|
||||||
type === NodeTypes.ELEMENT && tagType === ElementTypes.ELEMENT
|
if (
|
||||||
if (inElement) processChildrenBlockInfo(children)
|
context.options.vapor &&
|
||||||
|
type === NodeTypes.ELEMENT &&
|
||||||
|
tagType === ElementTypes.ELEMENT
|
||||||
|
) {
|
||||||
|
processBlockNodeAnchor(children)
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
const child = children[i]
|
const child = children[i]
|
||||||
if (inElement && shouldProcessChildAsBlock(parent, child)) {
|
|
||||||
processChildren(
|
|
||||||
{ children: [child] },
|
|
||||||
context,
|
|
||||||
asFragment,
|
|
||||||
disableNestedFragments,
|
|
||||||
disableComment,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch (child.type) {
|
switch (child.type) {
|
||||||
case NodeTypes.ELEMENT:
|
case NodeTypes.ELEMENT:
|
||||||
switch (child.tagType) {
|
switch (child.tagType) {
|
||||||
|
@ -197,14 +188,14 @@ export function processChildren(
|
||||||
ssrProcessElement(child, context)
|
ssrProcessElement(child, context)
|
||||||
break
|
break
|
||||||
case ElementTypes.COMPONENT:
|
case ElementTypes.COMPONENT:
|
||||||
if (inElement)
|
if (child.anchor) context.pushStringPart(`<!--[${child.anchor}-->`)
|
||||||
context.pushStringPart(`<!--${BLOCK_START_ANCHOR_LABEL}-->`)
|
|
||||||
ssrProcessComponent(child, context, parent)
|
ssrProcessComponent(child, context, parent)
|
||||||
if (inElement)
|
if (child.anchor) context.pushStringPart(`<!--${child.anchor}]-->`)
|
||||||
context.pushStringPart(`<!--${BLOCK_END_ANCHOR_LABEL}-->`)
|
|
||||||
break
|
break
|
||||||
case ElementTypes.SLOT:
|
case ElementTypes.SLOT:
|
||||||
|
if (child.anchor) context.pushStringPart(`<!--[${child.anchor}-->`)
|
||||||
ssrProcessSlotOutlet(child, context)
|
ssrProcessSlotOutlet(child, context)
|
||||||
|
if (child.anchor) context.pushStringPart(`<!--${child.anchor}]-->`)
|
||||||
break
|
break
|
||||||
case ElementTypes.TEMPLATE:
|
case ElementTypes.TEMPLATE:
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -239,10 +230,14 @@ export function processChildren(
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case NodeTypes.IF:
|
case NodeTypes.IF:
|
||||||
|
if (child.anchor) context.pushStringPart(`<!--[${child.anchor}-->`)
|
||||||
ssrProcessIf(child, context, disableNestedFragments, disableComment)
|
ssrProcessIf(child, context, disableNestedFragments, disableComment)
|
||||||
|
if (child.anchor) context.pushStringPart(`<!--${child.anchor}]-->`)
|
||||||
break
|
break
|
||||||
case NodeTypes.FOR:
|
case NodeTypes.FOR:
|
||||||
|
if (child.anchor) context.pushStringPart(`<!--[${child.anchor}-->`)
|
||||||
ssrProcessFor(child, context, disableNestedFragments)
|
ssrProcessFor(child, context, disableNestedFragments)
|
||||||
|
if (child.anchor) context.pushStringPart(`<!--${child.anchor}]-->`)
|
||||||
break
|
break
|
||||||
case NodeTypes.IF_BRANCH:
|
case NodeTypes.IF_BRANCH:
|
||||||
// no-op - handled by ssrProcessIf
|
// no-op - handled by ssrProcessIf
|
||||||
|
@ -267,9 +262,6 @@ export function processChildren(
|
||||||
if (asFragment) {
|
if (asFragment) {
|
||||||
context.pushStringPart(`<!--]-->`)
|
context.pushStringPart(`<!--]-->`)
|
||||||
}
|
}
|
||||||
if (asBlock) {
|
|
||||||
context.pushStringPart(`<!--${BLOCK_END_ANCHOR_LABEL}-->`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processChildrenAsStatement(
|
export function processChildrenAsStatement(
|
||||||
|
@ -283,117 +275,69 @@ export function processChildrenAsStatement(
|
||||||
return createBlockStatement(childContext.body)
|
return createBlockStatement(childContext.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isStaticChildNode = (c: TemplateChildNode): boolean =>
|
export function processBlockNodeAnchor(children: TemplateChildNode[]): void {
|
||||||
(c.type === NodeTypes.ELEMENT && c.tagType !== ElementTypes.COMPONENT) ||
|
let prevBlocks: (TemplateChildNode & { anchor?: string })[] = []
|
||||||
c.type === NodeTypes.TEXT ||
|
let hasStaticNode = false
|
||||||
c.type === NodeTypes.COMMENT
|
for (const child of children) {
|
||||||
|
if (isBlockNode(child)) {
|
||||||
|
prevBlocks.push(child)
|
||||||
|
}
|
||||||
|
|
||||||
interface BlockInfo {
|
if (isStaticNode(child)) {
|
||||||
hasStaticPrevious: boolean
|
if (prevBlocks.length) {
|
||||||
hasStaticNext: boolean
|
if (hasStaticNode) {
|
||||||
prevBlockCount: number
|
// insertion anchor
|
||||||
nextBlockCount: number
|
prevBlocks.forEach(
|
||||||
|
child => (child.anchor = BLOCK_INSERTION_ANCHOR_LABEL),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// prepend
|
||||||
|
prevBlocks.forEach(
|
||||||
|
child => (child.anchor = BLOCK_PREPEND_ANCHOR_LABEL),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
prevBlocks = []
|
||||||
|
}
|
||||||
|
hasStaticNode = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevBlocks.length) {
|
||||||
|
// append anchor
|
||||||
|
prevBlocks.forEach(child => (child.anchor = BLOCK_APPEND_ANCHOR_LABEL))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processChildrenBlockInfo(
|
function isBlockNode(child: TemplateChildNode): boolean {
|
||||||
children: (TemplateChildNode & { _ssrBlockInfo?: BlockInfo })[],
|
return (
|
||||||
): void {
|
child.type === NodeTypes.IF ||
|
||||||
const filteredChildren = children.filter(
|
child.type === NodeTypes.FOR ||
|
||||||
child => !(child.type === NodeTypes.TEXT && !child.content.trim()),
|
(child.type === NodeTypes.ELEMENT &&
|
||||||
|
(child.tagType === ElementTypes.COMPONENT ||
|
||||||
|
child.tagType === ElementTypes.SLOT ||
|
||||||
|
child.props.some(
|
||||||
|
p =>
|
||||||
|
p.name === 'if' ||
|
||||||
|
p.name === 'else-if' ||
|
||||||
|
p.name === 'else' ||
|
||||||
|
p.name === 'for',
|
||||||
|
)))
|
||||||
)
|
)
|
||||||
|
|
||||||
for (let i = 0; i < filteredChildren.length; i++) {
|
|
||||||
const child = filteredChildren[i]
|
|
||||||
if (
|
|
||||||
isStaticChildNode(child) ||
|
|
||||||
// fragment has it's own anchor, which can be used to distinguish the boundary
|
|
||||||
isFragmentChild(child)
|
|
||||||
) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
child._ssrBlockInfo = {
|
|
||||||
hasStaticPrevious: false,
|
|
||||||
hasStaticNext: false,
|
|
||||||
prevBlockCount: 0,
|
|
||||||
nextBlockCount: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
const info = child._ssrBlockInfo
|
|
||||||
|
|
||||||
// Calculate the previous static and block node counts
|
|
||||||
let foundStaticPrev = false
|
|
||||||
let blockCountPrev = 0
|
|
||||||
for (let j = i - 1; j >= 0; j--) {
|
|
||||||
const prevChild = filteredChildren[j]
|
|
||||||
if (isStaticChildNode(prevChild)) {
|
|
||||||
foundStaticPrev = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// if the previous child has block info, use it
|
|
||||||
else if (prevChild._ssrBlockInfo) {
|
|
||||||
foundStaticPrev = prevChild._ssrBlockInfo.hasStaticPrevious
|
|
||||||
blockCountPrev = prevChild._ssrBlockInfo.prevBlockCount + 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
blockCountPrev++
|
|
||||||
}
|
|
||||||
info.hasStaticPrevious = foundStaticPrev
|
|
||||||
info.prevBlockCount = blockCountPrev
|
|
||||||
|
|
||||||
// Calculate the number of static and block nodes afterwards
|
|
||||||
let foundStaticNext = false
|
|
||||||
let blockCountNext = 0
|
|
||||||
for (let j = i + 1; j < filteredChildren.length; j++) {
|
|
||||||
const nextChild = filteredChildren[j]
|
|
||||||
if (isStaticChildNode(nextChild)) {
|
|
||||||
foundStaticNext = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// if the next child has block info, use it
|
|
||||||
else if (nextChild._ssrBlockInfo) {
|
|
||||||
foundStaticNext = nextChild._ssrBlockInfo.hasStaticNext
|
|
||||||
blockCountNext = nextChild._ssrBlockInfo.nextBlockCount + 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
blockCountNext++
|
|
||||||
}
|
|
||||||
info.hasStaticNext = foundStaticNext
|
|
||||||
info.nextBlockCount = blockCountNext
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldProcessChildAsBlock(
|
function isStaticNode(child: TemplateChildNode): boolean {
|
||||||
parent: { tag?: string; children: TemplateChildNode[] },
|
return (
|
||||||
node: TemplateChildNode & { _ssrBlockInfo?: BlockInfo },
|
child.type === NodeTypes.TEXT ||
|
||||||
): boolean {
|
child.type === NodeTypes.INTERPOLATION ||
|
||||||
// must be inside a parent element
|
child.type === NodeTypes.COMMENT ||
|
||||||
if (!parent.tag) return false
|
(child.type === NodeTypes.ELEMENT &&
|
||||||
|
child.tagType === ElementTypes.ELEMENT &&
|
||||||
// must has block info
|
!child.props.some(
|
||||||
const { _ssrBlockInfo: info } = node
|
p =>
|
||||||
if (!info) return false
|
p.name === 'if' ||
|
||||||
|
p.name === 'else-if' ||
|
||||||
const { hasStaticPrevious, hasStaticNext, prevBlockCount, nextBlockCount } =
|
p.name === 'else' ||
|
||||||
info
|
p.name === 'for',
|
||||||
|
))
|
||||||
// must have static nodes on both sides
|
)
|
||||||
if (!hasStaticPrevious || !hasStaticNext) return false
|
|
||||||
|
|
||||||
const blockNodeCount = 1 + prevBlockCount + nextBlockCount
|
|
||||||
|
|
||||||
// For two consecutive block nodes, mark the second one as block
|
|
||||||
if (blockNodeCount === 2) {
|
|
||||||
return prevBlockCount > 0
|
|
||||||
}
|
|
||||||
// For three or more block nodes, mark the middle nodes as block
|
|
||||||
else if (blockNodeCount >= 3) {
|
|
||||||
return prevBlockCount > 0 && nextBlockCount > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFragmentChild(child: TemplateChildNode): boolean {
|
|
||||||
const { type } = child
|
|
||||||
return type === NodeTypes.IF || type === NodeTypes.FOR
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ import {
|
||||||
import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
|
import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
type SSRTransformContext,
|
type SSRTransformContext,
|
||||||
|
processBlockNodeAnchor,
|
||||||
processChildren,
|
processChildren,
|
||||||
processChildrenAsStatement,
|
processChildrenAsStatement,
|
||||||
} from '../ssrCodegenTransform'
|
} from '../ssrCodegenTransform'
|
||||||
|
@ -271,8 +272,11 @@ export function ssrProcessComponent(
|
||||||
// dynamic component (`resolveDynamicComponent` call)
|
// dynamic component (`resolveDynamicComponent` call)
|
||||||
// the codegen node is a `renderVNode` call
|
// the codegen node is a `renderVNode` call
|
||||||
context.pushStatement(node.ssrCodegenNode)
|
context.pushStatement(node.ssrCodegenNode)
|
||||||
// anchor for dynamic component for vapor hydration
|
|
||||||
context.pushStringPart(`<!--${DYNAMIC_COMPONENT_ANCHOR_LABEL}-->`)
|
// anchor for vapor dynamic component
|
||||||
|
if (context.options.vapor) {
|
||||||
|
context.pushStringPart(`<!--${DYNAMIC_COMPONENT_ANCHOR_LABEL}-->`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -339,6 +343,11 @@ function createVNodeSlotBranch(
|
||||||
loc: locStub,
|
loc: locStub,
|
||||||
codegenNode: undefined,
|
codegenNode: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parentContext.vapor) {
|
||||||
|
injectVaporInsertionAnchors(children)
|
||||||
|
}
|
||||||
|
|
||||||
subTransform(wrapperNode, subOptions, parentContext)
|
subTransform(wrapperNode, subOptions, parentContext)
|
||||||
return createReturnStatement(children)
|
return createReturnStatement(children)
|
||||||
}
|
}
|
||||||
|
@ -380,6 +389,71 @@ function subTransform(
|
||||||
// - hoists are not enabled for the client branch here
|
// - hoists are not enabled for the client branch here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function injectVaporInsertionAnchors(children: TemplateChildNode[]) {
|
||||||
|
processBlockNodeAnchor(children)
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i]
|
||||||
|
switch (child.type) {
|
||||||
|
case NodeTypes.ELEMENT:
|
||||||
|
switch (child.tagType) {
|
||||||
|
case ElementTypes.COMPONENT:
|
||||||
|
case ElementTypes.SLOT:
|
||||||
|
if (child.anchor) {
|
||||||
|
children.splice(i, 0, {
|
||||||
|
type: NodeTypes.COMMENT,
|
||||||
|
content: `[${child.anchor}`,
|
||||||
|
loc: locStub,
|
||||||
|
})
|
||||||
|
children.splice(i + 2, 0, {
|
||||||
|
type: NodeTypes.COMMENT,
|
||||||
|
content: `${child.anchor}]`,
|
||||||
|
loc: locStub,
|
||||||
|
})
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default: {
|
||||||
|
const { props } = child
|
||||||
|
if (
|
||||||
|
props.some(
|
||||||
|
p =>
|
||||||
|
p.name === 'if' ||
|
||||||
|
p.name === 'else-if' ||
|
||||||
|
p.name === 'else' ||
|
||||||
|
p.name === 'for',
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// @ts-expect-error
|
||||||
|
if (child.anchor) {
|
||||||
|
children.splice(i, 0, {
|
||||||
|
type: NodeTypes.COMMENT,
|
||||||
|
// @ts-expect-error
|
||||||
|
content: `[${child.anchor}`,
|
||||||
|
loc: locStub,
|
||||||
|
})
|
||||||
|
children.splice(i + 2, 0, {
|
||||||
|
type: NodeTypes.COMMENT,
|
||||||
|
// @ts-expect-error
|
||||||
|
content: `${child.anchor}]`,
|
||||||
|
loc: locStub,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
i += 2
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
child.type === NodeTypes.ELEMENT &&
|
||||||
|
child.tagType === ElementTypes.ELEMENT
|
||||||
|
) {
|
||||||
|
injectVaporInsertionAnchors(child.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function clone(v: any): any {
|
function clone(v: any): any {
|
||||||
if (isArray(v)) {
|
if (isArray(v)) {
|
||||||
return v.map(clone)
|
return v.map(clone)
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
type SSRTransformContext,
|
type SSRTransformContext,
|
||||||
processChildrenAsStatement,
|
processChildrenAsStatement,
|
||||||
} from '../ssrCodegenTransform'
|
} from '../ssrCodegenTransform'
|
||||||
|
import { SLOT_ANCHOR_LABEL } from '@vue/shared'
|
||||||
|
|
||||||
export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
|
export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
|
||||||
if (isSlotOutlet(node)) {
|
if (isSlotOutlet(node)) {
|
||||||
|
@ -93,4 +94,9 @@ export function ssrProcessSlotOutlet(
|
||||||
}
|
}
|
||||||
|
|
||||||
context.pushStatement(node.ssrCodegenNode!)
|
context.pushStatement(node.ssrCodegenNode!)
|
||||||
|
|
||||||
|
// anchor for vapor slot
|
||||||
|
if (context.options.vapor) {
|
||||||
|
context.pushStringPart(`<!--${SLOT_ANCHOR_LABEL}-->`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,9 @@ export function ssrProcessFor(
|
||||||
if (!disableNestedFragments) {
|
if (!disableNestedFragments) {
|
||||||
context.pushStringPart(`<!--]-->`)
|
context.pushStringPart(`<!--]-->`)
|
||||||
}
|
}
|
||||||
// v-for anchor for vapor hydration
|
|
||||||
context.pushStringPart(`<!--${FOR_ANCHOR_LABEL}-->`)
|
// anchor for vapor v-for fragment
|
||||||
|
if (context.options.vapor) {
|
||||||
|
context.pushStringPart(`<!--${FOR_ANCHOR_LABEL}-->`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,10 +81,12 @@ function processIfBranch(
|
||||||
needFragmentWrapper,
|
needFragmentWrapper,
|
||||||
)
|
)
|
||||||
|
|
||||||
// v-if/v-else-if/v-else anchor for vapor hydration
|
// anchor for vapor v-if/v-else-if
|
||||||
statement.body.push(
|
if (context.options.vapor) {
|
||||||
createCallExpression(`_push`, [`\`<!--${IF_ANCHOR_LABEL}-->\``]),
|
statement.body.push(
|
||||||
)
|
createCallExpression(`_push`, [`\`<!--${IF_ANCHOR_LABEL}-->\``]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return statement
|
return statement
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ export function render(_ctx) {
|
||||||
"default": () => {
|
"default": () => {
|
||||||
const n0 = _createIf(() => (true), () => {
|
const n0 = _createIf(() => (true), () => {
|
||||||
const n3 = t0()
|
const n3 = t0()
|
||||||
_setInsertionState(n3)
|
_setInsertionState(n3, null)
|
||||||
const n2 = _createComponentWithFallback(_component_Bar)
|
const n2 = _createComponentWithFallback(_component_Bar)
|
||||||
_withVaporDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]])
|
_withVaporDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]])
|
||||||
return n3
|
return n3
|
||||||
|
@ -157,7 +157,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
|
||||||
const _component_Comp = _resolveComponent("Comp")
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const n3 = t1()
|
const n3 = t1()
|
||||||
const n2 = _child(n3, 1)
|
const n2 = _child(n3)
|
||||||
_setInsertionState(n3, 0)
|
_setInsertionState(n3, 0)
|
||||||
const n1 = _createComponentWithFallback(_component_Comp)
|
const n1 = _createComponentWithFallback(_component_Comp)
|
||||||
_renderEffect(() => {
|
_renderEffect(() => {
|
||||||
|
@ -220,9 +220,9 @@ export function render(_ctx) {
|
||||||
const _component_Comp = _resolveComponent("Comp")
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
const n3 = t0()
|
const n3 = t0()
|
||||||
const n1 = _child(n3)
|
const n1 = _child(n3)
|
||||||
_setInsertionState(n1)
|
_setInsertionState(n1, null)
|
||||||
const n0 = _createSlot("default", null)
|
const n0 = _createSlot("default", null)
|
||||||
_setInsertionState(n3, null, 1)
|
_setInsertionState(n3, null)
|
||||||
const n2 = _createComponentWithFallback(_component_Comp)
|
const n2 = _createComponentWithFallback(_component_Comp)
|
||||||
return n3
|
return n3
|
||||||
}"
|
}"
|
||||||
|
|
|
@ -87,7 +87,7 @@ const t1 = _template("<div></div>", true)
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
|
const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
|
||||||
const n5 = t1()
|
const n5 = t1()
|
||||||
_setInsertionState(n5)
|
_setInsertionState(n5, null)
|
||||||
const n2 = _createFor(() => (_for_item0.value), (_for_item1) => {
|
const n2 = _createFor(() => (_for_item0.value), (_for_item1) => {
|
||||||
const n4 = t0()
|
const n4 = t0()
|
||||||
const x4 = _child(n4, -1)
|
const x4 = _child(n4, -1)
|
||||||
|
|
|
@ -144,12 +144,12 @@ const t3 = _template("<div></div>", true)
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n8 = t3()
|
const n8 = t3()
|
||||||
_setInsertionState(n8)
|
_setInsertionState(n8, null)
|
||||||
const n0 = _createIf(() => (_ctx.foo), () => {
|
const n0 = _createIf(() => (_ctx.foo), () => {
|
||||||
const n2 = t0()
|
const n2 = t0()
|
||||||
return n2
|
return n2
|
||||||
})
|
})
|
||||||
_setInsertionState(n8)
|
_setInsertionState(n8, null)
|
||||||
const n3 = _createIf(() => (_ctx.bar), () => {
|
const n3 = _createIf(() => (_ctx.bar), () => {
|
||||||
const n5 = t1()
|
const n5 = t1()
|
||||||
return n5
|
return n5
|
||||||
|
|
|
@ -42,7 +42,7 @@ const t0 = _template("<div></div>", true)
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const _component_Comp = _resolveComponent("Comp")
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
const n1 = t0()
|
const n1 = t0()
|
||||||
_setInsertionState(n1)
|
_setInsertionState(n1, null)
|
||||||
const n0 = _createComponentWithFallback(_component_Comp, { id: () => (_ctx.foo) }, null, null, true)
|
const n0 = _createComponentWithFallback(_component_Comp, { id: () => (_ctx.foo) }, null, null, true)
|
||||||
return n1
|
return n1
|
||||||
}"
|
}"
|
||||||
|
|
|
@ -352,9 +352,9 @@ export function render(_ctx) {
|
||||||
const _component_Foo = _resolveComponent("Foo")
|
const _component_Foo = _resolveComponent("Foo")
|
||||||
const _component_Bar = _resolveComponent("Bar")
|
const _component_Bar = _resolveComponent("Bar")
|
||||||
const n6 = t0()
|
const n6 = t0()
|
||||||
_setInsertionState(n6)
|
_setInsertionState(n6, null)
|
||||||
const n0 = _createSlot("foo", null)
|
const n0 = _createSlot("foo", null)
|
||||||
_setInsertionState(n6)
|
_setInsertionState(n6, null)
|
||||||
const n1 = _createIf(() => (true), () => {
|
const n1 = _createIf(() => (true), () => {
|
||||||
const n3 = _createComponentWithFallback(_component_Foo)
|
const n3 = _createComponentWithFallback(_component_Foo)
|
||||||
return n3
|
return n3
|
||||||
|
|
|
@ -39,8 +39,6 @@ export class CodegenContext {
|
||||||
|
|
||||||
seenInlineHandlerNames: Record<string, number> = Object.create(null)
|
seenInlineHandlerNames: Record<string, number> = Object.create(null)
|
||||||
|
|
||||||
seenChildIndexes: Map<number, number> = new Map()
|
|
||||||
|
|
||||||
block: BlockIRNode
|
block: BlockIRNode
|
||||||
withId<T>(
|
withId<T>(
|
||||||
fn: () => T,
|
fn: () => T,
|
||||||
|
@ -88,6 +86,7 @@ export class CodegenContext {
|
||||||
isTS: false,
|
isTS: false,
|
||||||
inSSR: false,
|
inSSR: false,
|
||||||
inline: false,
|
inline: false,
|
||||||
|
vapor: false,
|
||||||
bindingMetadata: {},
|
bindingMetadata: {},
|
||||||
expressionPlugins: [],
|
expressionPlugins: [],
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,33 +168,19 @@ function genInsertionState(
|
||||||
operation: InsertionStateTypes,
|
operation: InsertionStateTypes,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
): CodeFragment[] {
|
): CodeFragment[] {
|
||||||
const { seenChildIndexes } = context
|
const { parent, anchor } = operation
|
||||||
const { parent, childIndex, anchor } = operation
|
|
||||||
const insertionAnchor =
|
|
||||||
anchor == null
|
|
||||||
? undefined
|
|
||||||
: anchor === -1 // -1 indicates prepend
|
|
||||||
? `0` // runtime anchor value for prepend
|
|
||||||
: `n${anchor}`
|
|
||||||
|
|
||||||
// the index of next block node, used to locate node during hydration
|
|
||||||
// only passed when anchor is null and childIndex > 0
|
|
||||||
let index: number | undefined
|
|
||||||
if (anchor == null && childIndex) {
|
|
||||||
const existingOffset = seenChildIndexes.get(parent!)
|
|
||||||
seenChildIndexes.set(
|
|
||||||
parent!,
|
|
||||||
(index = existingOffset ? existingOffset + 1 : childIndex),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
...genCall(
|
...genCall(
|
||||||
context.helper('setInsertionState'),
|
context.helper('setInsertionState'),
|
||||||
`n${parent}`,
|
`n${parent}`,
|
||||||
insertionAnchor,
|
anchor == null
|
||||||
index ? `${index}` : undefined,
|
? undefined
|
||||||
|
: anchor === -1 // -1 indicates prepend
|
||||||
|
? `0` // runtime anchor value for prepend
|
||||||
|
: anchor === -2 // -2 indicates append
|
||||||
|
? `null` // runtime anchor value for append
|
||||||
|
: `n${anchor}`,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,11 +53,9 @@ export function genChildren(
|
||||||
const { children } = dynamic
|
const { children } = dynamic
|
||||||
|
|
||||||
let offset = 0
|
let offset = 0
|
||||||
let ifBranchCount = 0
|
|
||||||
let prev: [variable: string, elementIndex: number] | undefined
|
let prev: [variable: string, elementIndex: number] | undefined
|
||||||
|
|
||||||
for (const [index, child] of children.entries()) {
|
for (const [index, child] of children.entries()) {
|
||||||
if (child.isIfBranch) ifBranchCount++
|
|
||||||
if (child.flags & DynamicFlag.NON_TEMPLATE) {
|
if (child.flags & DynamicFlag.NON_TEMPLATE) {
|
||||||
offset--
|
offset--
|
||||||
}
|
}
|
||||||
|
@ -87,29 +85,11 @@ export function genChildren(
|
||||||
pushBlock(...genCall(helper('nthChild'), from, String(elementIndex)))
|
pushBlock(...genCall(helper('nthChild'), from, String(elementIndex)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// child index is used to find the child during hydration.
|
|
||||||
// if offset is not 0, we need to specify the offset to skip the dynamic
|
|
||||||
// children and get the correct child.
|
|
||||||
const asAnchor =
|
|
||||||
id !== undefined && children.some(child => child.anchor === id)
|
|
||||||
let childIndex =
|
|
||||||
offset === 0
|
|
||||||
? undefined
|
|
||||||
: // if the current node is used as insertionAnchor, subtract 1 here
|
|
||||||
// this ensures that insertionAnchor points to the current node itself
|
|
||||||
// rather than its next sibling, since insertionAnchor is used as the
|
|
||||||
// hydration node
|
|
||||||
`${
|
|
||||||
(asAnchor ? index - 1 : index) -
|
|
||||||
// treat v-if/v-else/v-else-if as a single node
|
|
||||||
ifBranchCount
|
|
||||||
}`
|
|
||||||
|
|
||||||
if (elementIndex === 0) {
|
if (elementIndex === 0) {
|
||||||
pushBlock(...genCall(helper('child'), from, childIndex))
|
pushBlock(...genCall(helper('child'), from))
|
||||||
} else {
|
} else {
|
||||||
// check if there's a node that we can reuse from
|
// check if there's a node that we can reuse from
|
||||||
let init = genCall(helper('child'), from, childIndex)
|
let init = genCall(helper('child'), from)
|
||||||
if (elementIndex === 1) {
|
if (elementIndex === 1) {
|
||||||
init = genCall(helper('next'), init)
|
init = genCall(helper('next'), init)
|
||||||
} else if (elementIndex > 1) {
|
} else if (elementIndex > 1) {
|
||||||
|
|
|
@ -69,7 +69,7 @@ export class TransformContext<T extends AllNode = AllNode> {
|
||||||
|
|
||||||
block: BlockIRNode = this.ir.block
|
block: BlockIRNode = this.ir.block
|
||||||
options: Required<
|
options: Required<
|
||||||
Omit<TransformOptions, 'filename' | keyof CompilerCompatOptions>
|
Omit<TransformOptions, 'vapor' | 'filename' | keyof CompilerCompatOptions>
|
||||||
>
|
>
|
||||||
|
|
||||||
template: string = ''
|
template: string = ''
|
||||||
|
|
|
@ -70,30 +70,12 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
|
||||||
if (!(child.flags & DynamicFlag.NON_TEMPLATE)) {
|
if (!(child.flags & DynamicFlag.NON_TEMPLATE)) {
|
||||||
if (prevDynamics.length) {
|
if (prevDynamics.length) {
|
||||||
if (hasStaticTemplate) {
|
if (hasStaticTemplate) {
|
||||||
// each dynamic child gets its own placeholder node.
|
context.childrenTemplate[index - prevDynamics.length] = `<!>`
|
||||||
// this makes it easier to locate the corresponding node during hydration.
|
prevDynamics[0].flags -= DynamicFlag.NON_TEMPLATE
|
||||||
for (let i = 0; i < prevDynamics.length; i++) {
|
const anchor = (prevDynamics[0].anchor = context.increaseId())
|
||||||
const idx = index - prevDynamics.length + i
|
registerInsertion(prevDynamics, context, anchor)
|
||||||
context.childrenTemplate[idx] = `<!>`
|
|
||||||
const dynamicChild = prevDynamics[i]
|
|
||||||
dynamicChild.flags -= DynamicFlag.NON_TEMPLATE
|
|
||||||
const anchor = (dynamicChild.anchor = context.increaseId())
|
|
||||||
if (
|
|
||||||
dynamicChild.operation &&
|
|
||||||
isBlockOperation(dynamicChild.operation)
|
|
||||||
) {
|
|
||||||
// block types
|
|
||||||
dynamicChild.operation.parent = context.reference()
|
|
||||||
dynamicChild.operation.anchor = anchor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
registerInsertion(
|
registerInsertion(prevDynamics, context, -1 /* prepend */)
|
||||||
prevDynamics,
|
|
||||||
context,
|
|
||||||
-1 /* prepend */,
|
|
||||||
getChildIndex(children, prevDynamics[0]),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
prevDynamics = []
|
prevDynamics = []
|
||||||
}
|
}
|
||||||
|
@ -102,12 +84,7 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevDynamics.length) {
|
if (prevDynamics.length) {
|
||||||
registerInsertion(
|
registerInsertion(prevDynamics, context, -2 /* append */)
|
||||||
prevDynamics,
|
|
||||||
context,
|
|
||||||
undefined,
|
|
||||||
getChildIndex(children, prevDynamics[0]),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +92,6 @@ function registerInsertion(
|
||||||
dynamics: IRDynamicInfo[],
|
dynamics: IRDynamicInfo[],
|
||||||
context: TransformContext,
|
context: TransformContext,
|
||||||
anchor?: number,
|
anchor?: number,
|
||||||
childIndex?: number,
|
|
||||||
) {
|
) {
|
||||||
for (const child of dynamics) {
|
for (const child of dynamics) {
|
||||||
if (child.template != null) {
|
if (child.template != null) {
|
||||||
|
@ -124,27 +100,12 @@ function registerInsertion(
|
||||||
type: IRNodeTypes.INSERT_NODE,
|
type: IRNodeTypes.INSERT_NODE,
|
||||||
elements: dynamics.map(child => child.id!),
|
elements: dynamics.map(child => child.id!),
|
||||||
parent: context.reference(),
|
parent: context.reference(),
|
||||||
anchor,
|
anchor: anchor === -2 ? undefined : anchor,
|
||||||
})
|
})
|
||||||
} else if (child.operation && isBlockOperation(child.operation)) {
|
} else if (child.operation && isBlockOperation(child.operation)) {
|
||||||
// block types
|
// block types
|
||||||
child.operation.parent = context.reference()
|
child.operation.parent = context.reference()
|
||||||
child.operation.anchor = anchor
|
child.operation.anchor = anchor
|
||||||
child.operation.childIndex = childIndex
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChildIndex(
|
|
||||||
children: IRDynamicInfo[],
|
|
||||||
child: IRDynamicInfo,
|
|
||||||
): number {
|
|
||||||
let index = 0
|
|
||||||
for (const c of children) {
|
|
||||||
// treat v-if/v-else/v-else-if as a single node
|
|
||||||
if (c.isIfBranch) continue
|
|
||||||
if (c === child) break
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
|
|
|
@ -601,14 +601,14 @@ describe('SSR hydration', () => {
|
||||||
const ctx: SSRContext = {}
|
const ctx: SSRContext = {}
|
||||||
container.innerHTML = await renderToString(h(App), ctx)
|
container.innerHTML = await renderToString(h(App), ctx)
|
||||||
expect(container.innerHTML).toBe(
|
expect(container.innerHTML).toBe(
|
||||||
'<div><!--teleport start--><!--teleport end--><!--if--></div>',
|
'<div><!--teleport start--><!--teleport end--></div>',
|
||||||
)
|
)
|
||||||
teleportContainer.innerHTML = ctx.teleports!['#target']
|
teleportContainer.innerHTML = ctx.teleports!['#target']
|
||||||
|
|
||||||
// hydrate
|
// hydrate
|
||||||
createSSRApp(App).mount(container)
|
createSSRApp(App).mount(container)
|
||||||
expect(container.innerHTML).toBe(
|
expect(container.innerHTML).toBe(
|
||||||
'<div><!--teleport start--><!--teleport end--><!--if--></div>',
|
'<div><!--teleport start--><!--teleport end--></div>',
|
||||||
)
|
)
|
||||||
expect(teleportContainer.innerHTML).toBe(
|
expect(teleportContainer.innerHTML).toBe(
|
||||||
'<!--teleport start anchor--><span>Teleported Comp1</span><!--teleport anchor-->',
|
'<!--teleport start anchor--><span>Teleported Comp1</span><!--teleport anchor-->',
|
||||||
|
@ -617,7 +617,7 @@ describe('SSR hydration', () => {
|
||||||
|
|
||||||
toggle.value = false
|
toggle.value = false
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(container.innerHTML).toBe('<div><div>Comp2</div><!--if--></div>')
|
expect(container.innerHTML).toBe('<div><div>Comp2</div></div>')
|
||||||
expect(teleportContainer.innerHTML).toBe('')
|
expect(teleportContainer.innerHTML).toBe('')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -660,21 +660,21 @@ describe('SSR hydration', () => {
|
||||||
// server render
|
// server render
|
||||||
container.innerHTML = await renderToString(h(App))
|
container.innerHTML = await renderToString(h(App))
|
||||||
expect(container.innerHTML).toBe(
|
expect(container.innerHTML).toBe(
|
||||||
'<div><!--teleport start--><!--teleport end--><!--if--></div>',
|
'<div><!--teleport start--><!--teleport end--></div>',
|
||||||
)
|
)
|
||||||
expect(teleportContainer.innerHTML).toBe('')
|
expect(teleportContainer.innerHTML).toBe('')
|
||||||
|
|
||||||
// hydrate
|
// hydrate
|
||||||
createSSRApp(App).mount(container)
|
createSSRApp(App).mount(container)
|
||||||
expect(container.innerHTML).toBe(
|
expect(container.innerHTML).toBe(
|
||||||
'<div><!--teleport start--><!--teleport end--><!--if--></div>',
|
'<div><!--teleport start--><!--teleport end--></div>',
|
||||||
)
|
)
|
||||||
expect(teleportContainer.innerHTML).toBe('<span>Teleported Comp1</span>')
|
expect(teleportContainer.innerHTML).toBe('<span>Teleported Comp1</span>')
|
||||||
expect(`Hydration children mismatch`).toHaveBeenWarned()
|
expect(`Hydration children mismatch`).toHaveBeenWarned()
|
||||||
|
|
||||||
toggle.value = false
|
toggle.value = false
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(container.innerHTML).toBe('<div><div>Comp2</div><!--if--></div>')
|
expect(container.innerHTML).toBe('<div><div>Comp2</div></div>')
|
||||||
expect(teleportContainer.innerHTML).toBe('')
|
expect(teleportContainer.innerHTML).toBe('')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -713,7 +713,7 @@ describe('SSR hydration', () => {
|
||||||
// server render
|
// server render
|
||||||
container.innerHTML = await renderToString(h(App))
|
container.innerHTML = await renderToString(h(App))
|
||||||
expect(container.innerHTML).toBe(
|
expect(container.innerHTML).toBe(
|
||||||
'<div><!--[[--><!--teleport start--><!--teleport end--><!--]]--></div>',
|
'<div><!--teleport start--><!--teleport end--></div>',
|
||||||
)
|
)
|
||||||
expect(teleportContainer1.innerHTML).toBe('')
|
expect(teleportContainer1.innerHTML).toBe('')
|
||||||
expect(teleportContainer2.innerHTML).toBe('')
|
expect(teleportContainer2.innerHTML).toBe('')
|
||||||
|
@ -721,7 +721,7 @@ describe('SSR hydration', () => {
|
||||||
// hydrate
|
// hydrate
|
||||||
createSSRApp(App).mount(container)
|
createSSRApp(App).mount(container)
|
||||||
expect(container.innerHTML).toBe(
|
expect(container.innerHTML).toBe(
|
||||||
'<div><!--[[--><!--teleport start--><!--teleport end--><!--]]--></div>',
|
'<div><!--teleport start--><!--teleport end--></div>',
|
||||||
)
|
)
|
||||||
expect(teleportContainer1.innerHTML).toBe('<span>Teleported</span>')
|
expect(teleportContainer1.innerHTML).toBe('<span>Teleported</span>')
|
||||||
expect(teleportContainer2.innerHTML).toBe('')
|
expect(teleportContainer2.innerHTML).toBe('')
|
||||||
|
@ -1005,7 +1005,7 @@ describe('SSR hydration', () => {
|
||||||
// server render
|
// server render
|
||||||
container.innerHTML = await renderToString(h(App))
|
container.innerHTML = await renderToString(h(App))
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
`"<div><!--[[--><span>1</span><!--]]--><!--[[--><span>2</span><!--]]--></div>"`,
|
`"<div><span>1</span><span>2</span></div>"`,
|
||||||
)
|
)
|
||||||
// reset asyncDeps from ssr
|
// reset asyncDeps from ssr
|
||||||
asyncDeps.length = 0
|
asyncDeps.length = 0
|
||||||
|
@ -1869,36 +1869,6 @@ describe('SSR hydration', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('dynamic anchor', () => {
|
|
||||||
test('two consecutive components', () => {
|
|
||||||
const Comp = {
|
|
||||||
render() {
|
|
||||||
return createTextVNode('foo')
|
|
||||||
},
|
|
||||||
}
|
|
||||||
const { vnode, container } = mountWithHydration(
|
|
||||||
`<div><span></span>foo<!--[[-->foo<!--]]--><span></span></div>`,
|
|
||||||
() => h('div', null, [h('span'), h(Comp), h(Comp), h('span')]),
|
|
||||||
)
|
|
||||||
expect(vnode.el).toBe(container.firstChild)
|
|
||||||
expect(`Hydration children mismatch`).not.toHaveBeenWarned()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('multiple consecutive components', () => {
|
|
||||||
const Comp = {
|
|
||||||
render() {
|
|
||||||
return createTextVNode('foo')
|
|
||||||
},
|
|
||||||
}
|
|
||||||
const { vnode, container } = mountWithHydration(
|
|
||||||
`<div><span></span>foo<!--[[-->foo<!--]]-->foo<span></span></div>`,
|
|
||||||
() => h('div', null, [h('span'), h(Comp), h(Comp), h(Comp), h('span')]),
|
|
||||||
)
|
|
||||||
expect(vnode.el).toBe(container.firstChild)
|
|
||||||
expect(`Hydration children mismatch`).not.toHaveBeenWarned()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('hmr reload child wrapped in KeepAlive', async () => {
|
test('hmr reload child wrapped in KeepAlive', async () => {
|
||||||
const id = 'child-reload'
|
const id = 'child-reload'
|
||||||
const Child = {
|
const Child = {
|
||||||
|
@ -1923,14 +1893,14 @@ describe('SSR hydration', () => {
|
||||||
const root = document.createElement('div')
|
const root = document.createElement('div')
|
||||||
root.innerHTML = await renderToString(h(App))
|
root.innerHTML = await renderToString(h(App))
|
||||||
createSSRApp(App).mount(root)
|
createSSRApp(App).mount(root)
|
||||||
expect(root.innerHTML).toBe('<div><!--[[--><div>foo</div><!--]]--></div>')
|
expect(root.innerHTML).toBe('<div><div>foo</div></div>')
|
||||||
|
|
||||||
reload(id, {
|
reload(id, {
|
||||||
__hmrId: id,
|
__hmrId: id,
|
||||||
template: `<div>bar</div>`,
|
template: `<div>bar</div>`,
|
||||||
})
|
})
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(root.innerHTML).toBe('<div><!--[[--><div>bar</div><!--]]--></div>')
|
expect(root.innerHTML).toBe('<div><div>bar</div></div>')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('hmr root reload', async () => {
|
test('hmr root reload', async () => {
|
||||||
|
|
|
@ -32,7 +32,6 @@ import {
|
||||||
isRenderableAttrValue,
|
isRenderableAttrValue,
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
isString,
|
isString,
|
||||||
isVaporAnchor,
|
|
||||||
normalizeClass,
|
normalizeClass,
|
||||||
normalizeCssVarValue,
|
normalizeCssVarValue,
|
||||||
normalizeStyle,
|
normalizeStyle,
|
||||||
|
@ -118,7 +117,7 @@ export function createHydrationFunctions(
|
||||||
o: {
|
o: {
|
||||||
patchProp,
|
patchProp,
|
||||||
createText,
|
createText,
|
||||||
nextSibling: next,
|
nextSibling,
|
||||||
parentNode,
|
parentNode,
|
||||||
remove,
|
remove,
|
||||||
insert,
|
insert,
|
||||||
|
@ -126,15 +125,6 @@ export function createHydrationFunctions(
|
||||||
},
|
},
|
||||||
} = rendererInternals
|
} = rendererInternals
|
||||||
|
|
||||||
function nextSibling(node: Node) {
|
|
||||||
let n = next(node)
|
|
||||||
// skip vapor mode specific anchors
|
|
||||||
if (n && isVaporAnchor(n)) {
|
|
||||||
n = next(n)
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
const hydrate: RootHydrateFunction = (vnode, container) => {
|
const hydrate: RootHydrateFunction = (vnode, container) => {
|
||||||
if (!container.hasChildNodes()) {
|
if (!container.hasChildNodes()) {
|
||||||
;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||||
|
@ -161,10 +151,6 @@ export function createHydrationFunctions(
|
||||||
slotScopeIds: string[] | null,
|
slotScopeIds: string[] | null,
|
||||||
optimized = false,
|
optimized = false,
|
||||||
): Node | null => {
|
): Node | null => {
|
||||||
// skip vapor mode specific anchors
|
|
||||||
if (isVaporAnchor(node)) {
|
|
||||||
node = nextSibling(node)!
|
|
||||||
}
|
|
||||||
optimized = optimized || !!vnode.dynamicChildren
|
optimized = optimized || !!vnode.dynamicChildren
|
||||||
const isFragmentStart = isComment(node) && node.data === '['
|
const isFragmentStart = isComment(node) && node.data === '['
|
||||||
const onMismatch = () =>
|
const onMismatch = () =>
|
||||||
|
@ -486,7 +472,7 @@ export function createHydrationFunctions(
|
||||||
|
|
||||||
// The SSRed DOM contains more nodes than it should. Remove them.
|
// The SSRed DOM contains more nodes than it should. Remove them.
|
||||||
const cur = next
|
const cur = next
|
||||||
next = nextSibling(next)
|
next = next.nextSibling
|
||||||
remove(cur)
|
remove(cur)
|
||||||
}
|
}
|
||||||
} else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
|
} else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
|
||||||
|
@ -592,7 +578,7 @@ export function createHydrationFunctions(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextSibling(el)
|
return el.nextSibling
|
||||||
}
|
}
|
||||||
|
|
||||||
const hydrateChildren = (
|
const hydrateChildren = (
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,19 +1,25 @@
|
||||||
import { warn } from '@vue/runtime-dom'
|
import { warn } from '@vue/runtime-dom'
|
||||||
import {
|
import {
|
||||||
insertionAnchor,
|
insertionAnchor,
|
||||||
insertionChildIndex,
|
|
||||||
insertionParent,
|
insertionParent,
|
||||||
resetInsertionState,
|
resetInsertionState,
|
||||||
setInsertionState,
|
setInsertionState,
|
||||||
} from '../insertionState'
|
} from '../insertionState'
|
||||||
import {
|
import {
|
||||||
__next,
|
_child,
|
||||||
__nthChild,
|
_next,
|
||||||
createTextNode,
|
createTextNode,
|
||||||
disableHydrationNodeLookup,
|
disableHydrationNodeLookup,
|
||||||
enableHydrationNodeLookup,
|
enableHydrationNodeLookup,
|
||||||
} from './node'
|
} from './node'
|
||||||
import { BLOCK_END_ANCHOR_LABEL, isVaporAnchor } from '@vue/shared'
|
import {
|
||||||
|
BLOCK_APPEND_ANCHOR_LABEL,
|
||||||
|
BLOCK_INSERTION_ANCHOR_LABEL,
|
||||||
|
BLOCK_PREPEND_ANCHOR_LABEL,
|
||||||
|
isVaporAnchor,
|
||||||
|
} from '@vue/shared'
|
||||||
|
|
||||||
|
const isHydratingStack = [] as boolean[]
|
||||||
|
|
||||||
export let isHydrating = false
|
export let isHydrating = false
|
||||||
export let currentHydrationNode: Node | null = null
|
export let currentHydrationNode: Node | null = null
|
||||||
|
@ -24,21 +30,13 @@ export function setCurrentHydrationNode(node: Node | null): void {
|
||||||
|
|
||||||
function findParentSibling(n: Node): Node | null {
|
function findParentSibling(n: Node): Node | null {
|
||||||
if (!n.parentNode) return null
|
if (!n.parentNode) return null
|
||||||
let next = n.parentNode.nextSibling
|
return n.parentNode.nextSibling || findParentSibling(n.parentNode)
|
||||||
while (next && isComment(next, BLOCK_END_ANCHOR_LABEL)) {
|
|
||||||
next = next.nextElementSibling
|
|
||||||
}
|
|
||||||
return next ? next : findParentSibling(n.parentNode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function advanceHydrationNode(node: Node & { $ps?: Node | null }): void {
|
export function advanceHydrationNode(node: Node & { $ps?: Node | null }): void {
|
||||||
let next = node.nextSibling
|
|
||||||
while (next && isComment(next, BLOCK_END_ANCHOR_LABEL)) {
|
|
||||||
next = next.nextSibling
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no next sibling, find the next node in the parent chain
|
// if no next sibling, find the next node in the parent chain
|
||||||
const ret = next || node.$ps || (node.$ps = findParentSibling(node))
|
const ret =
|
||||||
|
node.nextSibling || node.$ps || (node.$ps = findParentSibling(node))
|
||||||
if (ret) setCurrentHydrationNode(ret)
|
if (ret) setCurrentHydrationNode(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,22 +52,26 @@ function performHydration<T>(
|
||||||
locateHydrationNode = locateHydrationNodeImpl
|
locateHydrationNode = locateHydrationNodeImpl
|
||||||
// optimize anchor cache lookup
|
// optimize anchor cache lookup
|
||||||
;(Comment.prototype as any).$fe = undefined
|
;(Comment.prototype as any).$fe = undefined
|
||||||
;(Node.prototype as any).$np = undefined
|
;(Node.prototype as any).$pa = undefined
|
||||||
|
;(Node.prototype as any).$ia = undefined
|
||||||
|
;(Node.prototype as any).$aa = undefined
|
||||||
isOptimized = true
|
isOptimized = true
|
||||||
}
|
}
|
||||||
enableHydrationNodeLookup()
|
enableHydrationNodeLookup()
|
||||||
isHydrating = true
|
isHydratingStack.push((isHydrating = true))
|
||||||
setup()
|
setup()
|
||||||
const res = fn()
|
const res = fn()
|
||||||
cleanup()
|
cleanup()
|
||||||
currentHydrationNode = null
|
currentHydrationNode = null
|
||||||
isHydrating = false
|
isHydratingStack.pop()
|
||||||
|
isHydrating = isHydratingStack[isHydratingStack.length - 1] || false
|
||||||
disableHydrationNodeLookup()
|
disableHydrationNodeLookup()
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withHydration(container: ParentNode, fn: () => void): void {
|
export function withHydration(container: ParentNode, fn: () => void): void {
|
||||||
const setup = () => setInsertionState(container, 0)
|
// @ts-expect-error
|
||||||
|
const setup = () => setInsertionState(container, -1)
|
||||||
const cleanup = () => resetInsertionState()
|
const cleanup = () => resetInsertionState()
|
||||||
return performHydration(fn, setup, cleanup)
|
return performHydration(fn, setup, cleanup)
|
||||||
}
|
}
|
||||||
|
@ -136,20 +138,32 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
|
||||||
|
|
||||||
function locateHydrationNodeImpl(): void {
|
function locateHydrationNodeImpl(): void {
|
||||||
let node: Node | null
|
let node: Node | null
|
||||||
// prepend / firstChild
|
// @ts-expect-error
|
||||||
if (insertionAnchor === 0) {
|
if (insertionAnchor === -1) {
|
||||||
const n = insertionParent!.$np || 0
|
// firstChild
|
||||||
node = __nthChild(insertionParent!, n)
|
node = _child(insertionParent!)!
|
||||||
insertionParent!.$np = n + 1
|
} else if (insertionAnchor === 0) {
|
||||||
|
// prepend
|
||||||
|
node = insertionParent!.$pa = locateHydrationNodeByAnchor(
|
||||||
|
insertionParent!.$pa || _child(insertionParent!),
|
||||||
|
BLOCK_PREPEND_ANCHOR_LABEL,
|
||||||
|
)!
|
||||||
} else if (insertionAnchor) {
|
} else if (insertionAnchor) {
|
||||||
// `insertionAnchor` is a Node, it is the DOM node to hydrate
|
// insertion anchor
|
||||||
// Template: `...<span/><!----><span/>...`// `insertionAnchor` is the placeholder
|
node = insertionParent!.$ia = locateHydrationNodeByAnchor(
|
||||||
// SSR Output: `...<span/>Content<span/>...`// `insertionAnchor` is the actual node
|
insertionParent!.$ia || _child(insertionParent!),
|
||||||
node = insertionAnchor
|
BLOCK_INSERTION_ANCHOR_LABEL,
|
||||||
|
)!
|
||||||
|
} else if (insertionAnchor === null) {
|
||||||
|
// append anchor
|
||||||
|
node = insertionParent!.$aa = locateHydrationNodeByAnchor(
|
||||||
|
insertionParent!.$aa || _child(insertionParent!),
|
||||||
|
BLOCK_APPEND_ANCHOR_LABEL,
|
||||||
|
)!
|
||||||
} else {
|
} else {
|
||||||
node = currentHydrationNode
|
node = currentHydrationNode
|
||||||
if (insertionParent && (!node || node.parentNode !== insertionParent)) {
|
if (insertionParent && (!node || node.parentNode !== insertionParent)) {
|
||||||
node = __nthChild(insertionParent, insertionChildIndex || 0)
|
node = _child(insertionParent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,3 +227,20 @@ export function locateVaporFragmentAnchor(
|
||||||
export function isEmptyTextNode(node: Node): node is Text {
|
export function isEmptyTextNode(node: Node): node is Text {
|
||||||
return node.nodeType === 3 && !(node as Text).data.trim()
|
return node.nodeType === 3 && !(node as Text).data.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function locateHydrationNodeByAnchor(
|
||||||
|
node: Node,
|
||||||
|
anchorLabel: string,
|
||||||
|
): Node | null {
|
||||||
|
while (node) {
|
||||||
|
if (isComment(node, `[${anchorLabel}`)) return node.nextSibling
|
||||||
|
node = node.nextSibling!
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not locate hydration node with anchor label: ${anchorLabel}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { isComment, isNonHydrationNode, locateEndAnchor } from './hydration'
|
import { isComment, isNonHydrationNode, locateEndAnchor } from './hydration'
|
||||||
import {
|
import {
|
||||||
BLOCK_END_ANCHOR_LABEL,
|
BLOCK_INSERTION_ANCHOR_LABEL,
|
||||||
BLOCK_START_ANCHOR_LABEL,
|
BLOCK_PREPEND_ANCHOR_LABEL,
|
||||||
isVaporAnchor,
|
isVaporAnchor,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
|
||||||
|
@ -25,6 +25,29 @@ export function querySelector(selectors: string): Element | null {
|
||||||
return document.querySelector(selectors)
|
return document.querySelector(selectors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function skipBlockNodes(node: Node): Node {
|
||||||
|
while (node) {
|
||||||
|
if (isComment(node, `[${BLOCK_PREPEND_ANCHOR_LABEL}`)) {
|
||||||
|
node = locateEndAnchor(
|
||||||
|
node,
|
||||||
|
`[${BLOCK_PREPEND_ANCHOR_LABEL}`,
|
||||||
|
`${BLOCK_PREPEND_ANCHOR_LABEL}]`,
|
||||||
|
)!
|
||||||
|
continue
|
||||||
|
} else if (isComment(node, `[${BLOCK_INSERTION_ANCHOR_LABEL}`)) {
|
||||||
|
node = locateEndAnchor(
|
||||||
|
node,
|
||||||
|
`[${BLOCK_INSERTION_ANCHOR_LABEL}`,
|
||||||
|
`${BLOCK_INSERTION_ANCHOR_LABEL}]`,
|
||||||
|
)!
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function _child(node: ParentNode): Node {
|
export function _child(node: ParentNode): Node {
|
||||||
return node.firstChild!
|
return node.firstChild!
|
||||||
|
@ -60,16 +83,19 @@ export function _child(node: ParentNode): Node {
|
||||||
*/
|
*/
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function __child(node: ParentNode, offset?: number): Node {
|
export function __child(node: ParentNode, offset?: number): Node {
|
||||||
|
let n = node.firstChild!
|
||||||
|
|
||||||
// when offset is -1, it means we need to get the text node of this element
|
// when offset is -1, it means we need to get the text node of this element
|
||||||
// since server-side rendering doesn't generate whitespace placeholder text nodes,
|
// since server-side rendering doesn't generate whitespace placeholder text nodes,
|
||||||
// if firstChild is null, manually insert a text node and return it
|
// if firstChild is null, manually insert a text node and return it
|
||||||
if (offset === -1 && !node.firstChild) {
|
if (offset === -1 && !n) {
|
||||||
node.textContent = ' '
|
node.textContent = ' '
|
||||||
return node.firstChild!
|
return node.firstChild!
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = offset ? __nthChild(node, offset) : node.firstChild!
|
|
||||||
while (n && (isComment(n, '[') || isVaporAnchor(n))) {
|
while (n && (isComment(n, '[') || isVaporAnchor(n))) {
|
||||||
|
// skip block node
|
||||||
|
n = skipBlockNodes(n) as ChildNode
|
||||||
if (isComment(n, '[')) {
|
if (isComment(n, '[')) {
|
||||||
n = locateEndAnchor(n)!.nextSibling!
|
n = locateEndAnchor(n)!.nextSibling!
|
||||||
} else {
|
} else {
|
||||||
|
@ -90,7 +116,7 @@ export function _nthChild(node: Node, i: number): Node {
|
||||||
*/
|
*/
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function __nthChild(node: Node, i: number): Node {
|
export function __nthChild(node: Node, i: number): Node {
|
||||||
let n = node.firstChild!
|
let n = __child(node as ParentNode)
|
||||||
for (let start = 0; start < i; start++) {
|
for (let start = 0; start < i; start++) {
|
||||||
n = __next(n) as ChildNode
|
n = __next(n) as ChildNode
|
||||||
}
|
}
|
||||||
|
@ -105,7 +131,7 @@ export function _next(node: Node): Node {
|
||||||
/**
|
/**
|
||||||
* Hydration-specific version of `next`.
|
* Hydration-specific version of `next`.
|
||||||
*
|
*
|
||||||
* SSR comment anchors (fragments `<!--[-->...<!--]-->`, block `<!--[[-->...<!--]]-->`)
|
* SSR comment anchors (fragments `<!--[-->...<!--]-->`, block nodes `<!--[x-->...<!--x]-->`)
|
||||||
* disrupt standard `node.nextSibling` traversal during hydration. `_next` might
|
* disrupt standard `node.nextSibling` traversal during hydration. `_next` might
|
||||||
* return a comment node or an internal node of a fragment instead of skipping
|
* return a comment node or an internal node of a fragment instead of skipping
|
||||||
* the entire fragment block.
|
* the entire fragment block.
|
||||||
|
@ -144,20 +170,13 @@ export function _next(node: Node): Node {
|
||||||
*/
|
*/
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function __next(node: Node): Node {
|
export function __next(node: Node): Node {
|
||||||
// process block node (<!--[[-->...<!--]]-->) as a single node
|
|
||||||
if (isComment(node, BLOCK_START_ANCHOR_LABEL)) {
|
|
||||||
node = locateEndAnchor(
|
|
||||||
node,
|
|
||||||
BLOCK_START_ANCHOR_LABEL,
|
|
||||||
BLOCK_END_ANCHOR_LABEL,
|
|
||||||
)!
|
|
||||||
}
|
|
||||||
|
|
||||||
// process fragment (<!--[-->...<!--]-->) as a single node
|
// process fragment (<!--[-->...<!--]-->) as a single node
|
||||||
else if (isComment(node, '[')) {
|
if (isComment(node, '[')) {
|
||||||
node = locateEndAnchor(node)!
|
node = locateEndAnchor(node)!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node = skipBlockNodes(node)
|
||||||
|
|
||||||
let n = node.nextSibling!
|
let n = node.nextSibling!
|
||||||
while (n && isNonHydrationNode(n)) {
|
while (n && isNonHydrationNode(n)) {
|
||||||
n = n.nextSibling!
|
n = n.nextSibling!
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
export let insertionParent:
|
export let insertionParent:
|
||||||
| (ParentNode & {
|
| (ParentNode & {
|
||||||
// number of prepends - hydration only
|
$pa?: Node
|
||||||
// consecutive prepends need to skip nodes that were prepended earlier
|
$ia?: Node
|
||||||
// each prepend increases the value of $prepend
|
$aa?: Node
|
||||||
$np?: number
|
|
||||||
})
|
})
|
||||||
| undefined
|
| undefined
|
||||||
export let insertionAnchor: Node | 0 | undefined
|
export let insertionAnchor: Node | 0 | undefined | null
|
||||||
|
|
||||||
export let insertionChildIndex: number | undefined
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is called before a block type that requires insertion
|
* This function is called before a block type that requires insertion
|
||||||
|
@ -17,14 +14,12 @@ export let insertionChildIndex: number | undefined
|
||||||
*/
|
*/
|
||||||
export function setInsertionState(
|
export function setInsertionState(
|
||||||
parent: ParentNode,
|
parent: ParentNode,
|
||||||
anchor?: Node | 0,
|
anchor?: Node | 0 | null,
|
||||||
offset?: number,
|
|
||||||
): void {
|
): void {
|
||||||
insertionParent = parent
|
insertionParent = parent
|
||||||
insertionAnchor = anchor
|
insertionAnchor = anchor
|
||||||
insertionChildIndex = offset
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetInsertionState(): void {
|
export function resetInsertionState(): void {
|
||||||
insertionParent = insertionAnchor = insertionChildIndex = undefined
|
insertionParent = insertionAnchor = undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -446,7 +446,7 @@ function testRender(type: string, render: typeof renderToString) {
|
||||||
).toBe(
|
).toBe(
|
||||||
`<div>parent<div class="child">` +
|
`<div>parent<div class="child">` +
|
||||||
`<!--[--><span>from slot</span><!--]-->` +
|
`<!--[--><span>from slot</span><!--]-->` +
|
||||||
`<!--slot--></div></div>`,
|
`</div></div>`,
|
||||||
)
|
)
|
||||||
|
|
||||||
// test fallback
|
// test fallback
|
||||||
|
@ -461,7 +461,7 @@ function testRender(type: string, render: typeof renderToString) {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(
|
).toBe(
|
||||||
`<div>parent<div class="child"><!--[-->fallback<!--]--><!--slot--></div></div>`,
|
`<div>parent<div class="child"><!--[-->fallback<!--]--></div></div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -507,7 +507,7 @@ function testRender(type: string, render: typeof renderToString) {
|
||||||
).toBe(
|
).toBe(
|
||||||
`<div>parent<div class="child">` +
|
`<div>parent<div class="child">` +
|
||||||
`<!--[--><span>from slot</span><!--]-->` +
|
`<!--[--><span>from slot</span><!--]-->` +
|
||||||
`<!--slot--></div></div>`,
|
`</div></div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -525,7 +525,7 @@ function testRender(type: string, render: typeof renderToString) {
|
||||||
expect(await render(app)).toBe(
|
expect(await render(app)).toBe(
|
||||||
`<div>parent<div class="child">` +
|
`<div>parent<div class="child">` +
|
||||||
`<!--[--><span>from slot</span><!--]-->` +
|
`<!--[--><span>from slot</span><!--]-->` +
|
||||||
`<!--slot--></div></div>`,
|
`</div></div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -572,7 +572,7 @@ function testRender(type: string, render: typeof renderToString) {
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(await render(app)).toBe(
|
expect(await render(app)).toBe(
|
||||||
`<div><!--[--><!--[-->hello<!--]--><!--slot--><!--]--><!--slot--></div>`,
|
`<div><!--[--><!--[-->hello<!--]--><!--]--></div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -593,7 +593,7 @@ function testRender(type: string, render: typeof renderToString) {
|
||||||
|
|
||||||
expect(await render(app)).toBe(
|
expect(await render(app)).toBe(
|
||||||
// should only have a single fragment
|
// should only have a single fragment
|
||||||
`<div><!--[--><!--]--><!--slot--></div>`,
|
`<div><!--[--><!--]--></div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -614,7 +614,7 @@ function testRender(type: string, render: typeof renderToString) {
|
||||||
|
|
||||||
expect(await render(app)).toBe(
|
expect(await render(app)).toBe(
|
||||||
// should only have a single fragment
|
// should only have a single fragment
|
||||||
`<div><!--[-->fallback<!--]--><!--slot--></div>`,
|
`<div><!--[-->fallback<!--]--></div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -25,10 +25,10 @@ describe('ssr: attr fallthrough', () => {
|
||||||
template: `<child :ok="ok" class="bar"/>`,
|
template: `<child :ok="ok" class="bar"/>`,
|
||||||
}
|
}
|
||||||
expect(await renderToString(createApp(Parent, { ok: true }))).toBe(
|
expect(await renderToString(createApp(Parent, { ok: true }))).toBe(
|
||||||
`<div class="foo bar"></div><!--if-->`,
|
`<div class="foo bar"></div>`,
|
||||||
)
|
)
|
||||||
expect(await renderToString(createApp(Parent, { ok: false }))).toBe(
|
expect(await renderToString(createApp(Parent, { ok: false }))).toBe(
|
||||||
`<span class="bar"></span><!--if-->`,
|
`<span class="bar"></span>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,7 @@ describe('ssr: dynamic component', () => {
|
||||||
template: `<component :is="'one'"><span>slot</span></component>`,
|
template: `<component :is="'one'"><span>slot</span></component>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(
|
).toBe(`<div><!--[--><span>slot</span><!--]--></div>`)
|
||||||
`<div><!--[--><span>slot</span><!--]--><!--slot--></div><!--dynamic-component-->`,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('resolved to component with v-show', async () => {
|
test('resolved to component with v-show', async () => {
|
||||||
|
@ -32,7 +30,7 @@ describe('ssr: dynamic component', () => {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(
|
).toBe(
|
||||||
`<div><!--[--><div style="display:none;"><!--[-->hi<!--]--></div><!--dynamic-component--><!--]--></div><!--dynamic-component-->`,
|
`<div><!--[--><div style="display:none;"><!--[-->hi<!--]--></div><!--]--></div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -43,7 +41,7 @@ describe('ssr: dynamic component', () => {
|
||||||
template: `<component :is="'p'"><span>slot</span></component>`,
|
template: `<component :is="'p'"><span>slot</span></component>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<p><span>slot</span></p><!--dynamic-component-->`)
|
).toBe(`<p><span>slot</span></p>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('resolve to component vnode', async () => {
|
test('resolve to component vnode', async () => {
|
||||||
|
@ -62,9 +60,7 @@ describe('ssr: dynamic component', () => {
|
||||||
template: `<component :is="vnode"><span>slot</span></component>`,
|
template: `<component :is="vnode"><span>slot</span></component>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(
|
).toBe(`<div>test<!--[--><span>slot</span><!--]--></div>`)
|
||||||
`<div>test<!--[--><span>slot</span><!--]--><!--slot--></div><!--dynamic-component-->`,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('resolve to element vnode', async () => {
|
test('resolve to element vnode', async () => {
|
||||||
|
@ -79,6 +75,6 @@ describe('ssr: dynamic component', () => {
|
||||||
template: `<component :is="vnode"><span>slot</span></component>`,
|
template: `<component :is="vnode"><span>slot</span></component>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<div id="test"><span>slot</span></div><!--dynamic-component-->`)
|
).toBe(`<div id="test"><span>slot</span></div>`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -68,9 +68,7 @@ describe('ssr: scopedId runtime behavior', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await renderToString(createApp(Comp))
|
const result = await renderToString(createApp(Comp))
|
||||||
expect(result).toBe(
|
expect(result).toBe(`<!--[--><div parent wrapper-s child></div><!--]-->`)
|
||||||
`<!--[--><div parent wrapper-s child></div><!--]--><!--slot-->`,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// #2892
|
// #2892
|
||||||
|
@ -152,8 +150,8 @@ describe('ssr: scopedId runtime behavior', () => {
|
||||||
const result = await renderToString(createApp(Root))
|
const result = await renderToString(createApp(Root))
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
`<div class="wrapper" root slotted wrapper>` +
|
`<div class="wrapper" root slotted wrapper>` +
|
||||||
`<!--[--><!--[--><div root slotted-s wrapper-s></div><!--]--><!--slot--><!--]-->` +
|
`<!--[--><!--[--><div root slotted-s wrapper-s></div><!--]--><!--]-->` +
|
||||||
`<!--slot--></div>`,
|
`</div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -267,8 +265,8 @@ describe('ssr: scopedId runtime behavior', () => {
|
||||||
const result = await renderToString(createApp(Root))
|
const result = await renderToString(createApp(Root))
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
`<div class="wrapper" root slotted wrapper>` +
|
`<div class="wrapper" root slotted wrapper>` +
|
||||||
`<!--[--><!--[--><div root slotted-s wrapper-s></div><!--]--><!--slot--><!--]-->` +
|
`<!--[--><!--[--><div root slotted-s wrapper-s></div><!--]--><!--]-->` +
|
||||||
`<!--slot--></div>`,
|
`</div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe('ssr: slot', () => {
|
||||||
template: `<one>hello</one>`,
|
template: `<one>hello</one>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<div><!--[-->hello<!--]--><!--slot--></div>`)
|
).toBe(`<div><!--[-->hello<!--]--></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('element slot', async () => {
|
test('element slot', async () => {
|
||||||
|
@ -27,7 +27,7 @@ describe('ssr: slot', () => {
|
||||||
template: `<one><div>hi</div></one>`,
|
template: `<one><div>hi</div></one>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<div><!--[--><div>hi</div><!--]--><!--slot--></div>`)
|
).toBe(`<div><!--[--><div>hi</div><!--]--></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('empty slot', async () => {
|
test('empty slot', async () => {
|
||||||
|
@ -42,7 +42,7 @@ describe('ssr: slot', () => {
|
||||||
template: `<one><template v-if="false"/></one>`,
|
template: `<one><template v-if="false"/></one>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<div><!--[--><!--]--><!--slot--></div>`)
|
).toBe(`<div><!--[--><!--]--></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('empty slot (manual comments)', async () => {
|
test('empty slot (manual comments)', async () => {
|
||||||
|
@ -57,7 +57,7 @@ describe('ssr: slot', () => {
|
||||||
template: `<one><!--hello--></one>`,
|
template: `<one><!--hello--></one>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<div><!--[--><!--]--><!--slot--></div>`)
|
).toBe(`<div><!--[--><!--]--></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('empty slot (multi-line comments)', async () => {
|
test('empty slot (multi-line comments)', async () => {
|
||||||
|
@ -72,7 +72,7 @@ describe('ssr: slot', () => {
|
||||||
template: `<one><!--he\nllo--></one>`,
|
template: `<one><!--he\nllo--></one>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<div><!--[--><!--]--><!--slot--></div>`)
|
).toBe(`<div><!--[--><!--]--></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('multiple elements', async () => {
|
test('multiple elements', async () => {
|
||||||
|
@ -83,7 +83,7 @@ describe('ssr: slot', () => {
|
||||||
template: `<one><div>one</div><div>two</div></one>`,
|
template: `<one><div>one</div><div>two</div></one>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<div><!--[--><div>one</div><div>two</div><!--]--><!--slot--></div>`)
|
).toBe(`<div><!--[--><div>one</div><div>two</div><!--]--></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('fragment slot (template v-if)', async () => {
|
test('fragment slot (template v-if)', async () => {
|
||||||
|
@ -94,9 +94,7 @@ describe('ssr: slot', () => {
|
||||||
template: `<one><template v-if="true">hello</template></one>`,
|
template: `<one><template v-if="true">hello</template></one>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(
|
).toBe(`<div><!--[--><!--[-->hello<!--]--><!--]--></div>`)
|
||||||
`<div><!--[--><!--[-->hello<!--]--><!--if--><!--]--><!--slot--></div>`,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('fragment slot (template v-if + multiple elements)', async () => {
|
test('fragment slot (template v-if + multiple elements)', async () => {
|
||||||
|
@ -108,7 +106,7 @@ describe('ssr: slot', () => {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(
|
).toBe(
|
||||||
`<div><!--[--><!--[--><div>one</div><div>two</div><!--]--><!--if--><!--]--><!--slot--></div>`,
|
`<div><!--[--><!--[--><div>one</div><div>two</div><!--]--><!--]--></div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -137,7 +135,7 @@ describe('ssr: slot', () => {
|
||||||
template: `<one><div v-if="true">foo</div></one>`,
|
template: `<one><div v-if="true">foo</div></one>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<div>foo</div><!--if-->`)
|
).toBe(`<div>foo</div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
// #9933
|
// #9933
|
||||||
|
@ -185,7 +183,7 @@ describe('ssr: slot', () => {
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<div><!--[--> new header <!--]--><!--slot--></div>`)
|
).toBe(`<div><!--[--> new header <!--]--></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
// #11326
|
// #11326
|
||||||
|
@ -204,9 +202,7 @@ describe('ssr: slot', () => {
|
||||||
template: `<ButtonComp><Wrap><div v-if="false">hello</div></Wrap></ButtonComp>`,
|
template: `<ButtonComp><Wrap><div v-if="false">hello</div></Wrap></ButtonComp>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(
|
).toBe(`<button><!--[--><div><!--[--><!--]--></div><!--]--></button>`)
|
||||||
`<button><!--[--><div><!--[--><!--]--><!--slot--></div><!--]--></button><!--dynamic-component-->`,
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await renderToString(
|
await renderToString(
|
||||||
|
@ -223,7 +219,7 @@ describe('ssr: slot', () => {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(
|
).toBe(
|
||||||
`<button><!--[--><div><!--[--><div>hello</div><!--]--><!--slot--></div><!--]--></button><!--dynamic-component-->`,
|
`<button><!--[--><div><!--[--><div>hello</div><!--]--></div><!--]--></button>`,
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -237,6 +233,6 @@ describe('ssr: slot', () => {
|
||||||
template: `<ButtonComp><template v-if="false">hello</template></ButtonComp>`,
|
template: `<ButtonComp><template v-if="false">hello</template></ButtonComp>`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe(`<button><!--[--><!--]--></button><!--dynamic-component-->`)
|
).toBe(`<button><!--[--><!--]--></button>`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
type SSRBufferItem,
|
type SSRBufferItem,
|
||||||
renderVNodeChildren,
|
renderVNodeChildren,
|
||||||
} from '../render'
|
} from '../render'
|
||||||
import { SLOT_ANCHOR_LABEL, isArray } from '@vue/shared'
|
import { isArray } from '@vue/shared'
|
||||||
|
|
||||||
const { ensureValidVNode } = ssrUtils
|
const { ensureValidVNode } = ssrUtils
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export function ssrRenderSlot(
|
||||||
parentComponent,
|
parentComponent,
|
||||||
slotScopeId,
|
slotScopeId,
|
||||||
)
|
)
|
||||||
push(`<!--]--><!--${SLOT_ANCHOR_LABEL}-->`)
|
push(`<!--]-->`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ssrRenderSlotInner(
|
export function ssrRenderSlotInner(
|
||||||
|
@ -104,7 +104,7 @@ export function ssrRenderSlotInner(
|
||||||
if (
|
if (
|
||||||
transition &&
|
transition &&
|
||||||
slotBuffer[0] === '<!--[-->' &&
|
slotBuffer[0] === '<!--[-->' &&
|
||||||
(slotBuffer[end - 1] as string).startsWith('<!--]-->')
|
slotBuffer[end - 1] === '<!--]-->'
|
||||||
) {
|
) {
|
||||||
start++
|
start++
|
||||||
end--
|
end--
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
export const BLOCK_START_ANCHOR_LABEL = '[['
|
export const BLOCK_INSERTION_ANCHOR_LABEL = 'i'
|
||||||
export const BLOCK_END_ANCHOR_LABEL = ']]'
|
export const BLOCK_APPEND_ANCHOR_LABEL = 'a'
|
||||||
|
export const BLOCK_PREPEND_ANCHOR_LABEL = 'p'
|
||||||
export const IF_ANCHOR_LABEL: string = 'if'
|
export const IF_ANCHOR_LABEL: string = 'if'
|
||||||
export const ELSE_IF_ANCHOR_LABEL: string = 'else-if'
|
export const ELSE_IF_ANCHOR_LABEL: string = 'else-if'
|
||||||
export const DYNAMIC_COMPONENT_ANCHOR_LABEL: string = 'dynamic-component'
|
export const DYNAMIC_COMPONENT_ANCHOR_LABEL: string = 'dynamic-component'
|
||||||
export const FOR_ANCHOR_LABEL: string = 'for'
|
export const FOR_ANCHOR_LABEL: string = 'for'
|
||||||
export const SLOT_ANCHOR_LABEL: string = 'slot'
|
export const SLOT_ANCHOR_LABEL: string = 'slot'
|
||||||
|
|
||||||
export function isBlockAnchor(node: Node): node is Comment {
|
export function isInsertionAnchor(node: Node): node is Comment {
|
||||||
if (node.nodeType !== 8) return false
|
if (node.nodeType !== 8) return false
|
||||||
|
|
||||||
const data = (node as Comment).data
|
const data = (node as Comment).data
|
||||||
return data === BLOCK_START_ANCHOR_LABEL || data === BLOCK_END_ANCHOR_LABEL
|
return (
|
||||||
|
data === `[${BLOCK_INSERTION_ANCHOR_LABEL}` ||
|
||||||
|
data === `${BLOCK_INSERTION_ANCHOR_LABEL}]` ||
|
||||||
|
data === `[${BLOCK_APPEND_ANCHOR_LABEL}` ||
|
||||||
|
data === `${BLOCK_APPEND_ANCHOR_LABEL}]` ||
|
||||||
|
data === `[${BLOCK_PREPEND_ANCHOR_LABEL}` ||
|
||||||
|
data === `${BLOCK_PREPEND_ANCHOR_LABEL}]`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVaporFragmentAnchor(node: Node): node is Comment {
|
export function isVaporFragmentAnchor(node: Node): node is Comment {
|
||||||
|
@ -27,5 +34,5 @@ export function isVaporFragmentAnchor(node: Node): node is Comment {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVaporAnchor(node: Node): node is Comment {
|
export function isVaporAnchor(node: Node): node is Comment {
|
||||||
return isBlockAnchor(node) || isVaporFragmentAnchor(node)
|
return isVaporFragmentAnchor(node) || isInsertionAnchor(node)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue