wip(ssr): ssr slot vnode fallback
This commit is contained in:
parent
31f3383a02
commit
b7a74d0439
@ -51,7 +51,8 @@ export const enum NodeTypes {
|
|||||||
JS_BLOCK_STATEMENT,
|
JS_BLOCK_STATEMENT,
|
||||||
JS_TEMPLATE_LITERAL,
|
JS_TEMPLATE_LITERAL,
|
||||||
JS_IF_STATEMENT,
|
JS_IF_STATEMENT,
|
||||||
JS_ASSIGNMENT_EXPRESSION
|
JS_ASSIGNMENT_EXPRESSION,
|
||||||
|
JS_RETURN_STATEMENT
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum ElementTypes {
|
export const enum ElementTypes {
|
||||||
@ -294,7 +295,7 @@ export interface FunctionExpression extends Node {
|
|||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
||||||
params: ExpressionNode | string | (ExpressionNode | string)[] | undefined
|
params: ExpressionNode | string | (ExpressionNode | string)[] | undefined
|
||||||
returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode
|
returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode
|
||||||
body?: BlockStatement
|
body?: BlockStatement | IfStatement
|
||||||
newline: boolean
|
newline: boolean
|
||||||
// so that codegen knows it needs to generate ScopeId wrapper
|
// so that codegen knows it needs to generate ScopeId wrapper
|
||||||
isSlot: boolean
|
isSlot: boolean
|
||||||
@ -322,7 +323,12 @@ export interface CacheExpression extends Node {
|
|||||||
|
|
||||||
// SSR-specific Node Types -----------------------------------------------------
|
// SSR-specific Node Types -----------------------------------------------------
|
||||||
|
|
||||||
export type SSRCodegenNode = BlockStatement | TemplateLiteral | IfStatement
|
export type SSRCodegenNode =
|
||||||
|
| BlockStatement
|
||||||
|
| TemplateLiteral
|
||||||
|
| IfStatement
|
||||||
|
| AssignmentExpression
|
||||||
|
| ReturnStatement
|
||||||
|
|
||||||
export interface BlockStatement extends Node {
|
export interface BlockStatement extends Node {
|
||||||
type: NodeTypes.JS_BLOCK_STATEMENT
|
type: NodeTypes.JS_BLOCK_STATEMENT
|
||||||
@ -338,7 +344,7 @@ export interface IfStatement extends Node {
|
|||||||
type: NodeTypes.JS_IF_STATEMENT
|
type: NodeTypes.JS_IF_STATEMENT
|
||||||
test: ExpressionNode
|
test: ExpressionNode
|
||||||
consequent: BlockStatement
|
consequent: BlockStatement
|
||||||
alternate: IfStatement | BlockStatement | undefined
|
alternate: IfStatement | BlockStatement | ReturnStatement | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AssignmentExpression extends Node {
|
export interface AssignmentExpression extends Node {
|
||||||
@ -347,6 +353,11 @@ export interface AssignmentExpression extends Node {
|
|||||||
right: JSChildNode
|
right: JSChildNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ReturnStatement extends Node {
|
||||||
|
type: NodeTypes.JS_RETURN_STATEMENT
|
||||||
|
returns: TemplateChildNode | TemplateChildNode[] | JSChildNode
|
||||||
|
}
|
||||||
|
|
||||||
// Codegen Node Types ----------------------------------------------------------
|
// Codegen Node Types ----------------------------------------------------------
|
||||||
|
|
||||||
// createVNode(...)
|
// createVNode(...)
|
||||||
@ -733,3 +744,13 @@ export function createAssignmentExpression(
|
|||||||
loc: locStub
|
loc: locStub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createReturnStatement(
|
||||||
|
returns: ReturnStatement['returns']
|
||||||
|
): ReturnStatement {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.JS_RETURN_STATEMENT,
|
||||||
|
returns,
|
||||||
|
loc: locStub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -22,7 +22,8 @@ import {
|
|||||||
SSRCodegenNode,
|
SSRCodegenNode,
|
||||||
TemplateLiteral,
|
TemplateLiteral,
|
||||||
IfStatement,
|
IfStatement,
|
||||||
AssignmentExpression
|
AssignmentExpression,
|
||||||
|
ReturnStatement
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||||
import {
|
import {
|
||||||
@ -44,8 +45,7 @@ import {
|
|||||||
CREATE_TEXT,
|
CREATE_TEXT,
|
||||||
PUSH_SCOPE_ID,
|
PUSH_SCOPE_ID,
|
||||||
POP_SCOPE_ID,
|
POP_SCOPE_ID,
|
||||||
WITH_SCOPE_ID,
|
WITH_SCOPE_ID
|
||||||
CREATE_BLOCK
|
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { ImportItem } from './transform'
|
import { ImportItem } from './transform'
|
||||||
|
|
||||||
@ -334,24 +334,12 @@ function genModulePreamble(
|
|||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
genScopeId: boolean
|
genScopeId: boolean
|
||||||
) {
|
) {
|
||||||
const { push, helper, newline, scopeId, runtimeModuleName, ssr } = context
|
const { push, helper, newline, scopeId, runtimeModuleName } = context
|
||||||
|
|
||||||
if (!__BROWSER__) {
|
if (!__BROWSER__ && genScopeId) {
|
||||||
// in ssr mode, `withId` helper is only needed if the template contains
|
ast.helpers.push(WITH_SCOPE_ID)
|
||||||
// de-optimized component slots (which uses the createVNode helper)
|
if (ast.hoists.length) {
|
||||||
if (
|
ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
|
||||||
ssr &&
|
|
||||||
!(
|
|
||||||
ast.helpers.includes(CREATE_VNODE) || ast.helpers.includes(CREATE_BLOCK)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
genScopeId = false
|
|
||||||
}
|
|
||||||
if (genScopeId) {
|
|
||||||
ast.helpers.push(WITH_SCOPE_ID)
|
|
||||||
if (ast.hoists.length) {
|
|
||||||
ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -572,6 +560,9 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
|
|||||||
case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
|
case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
|
||||||
!__BROWSER__ && genAssignmentExpression(node, context)
|
!__BROWSER__ && genAssignmentExpression(node, context)
|
||||||
break
|
break
|
||||||
|
case NodeTypes.JS_RETURN_STATEMENT:
|
||||||
|
!__BROWSER__ && genReturnStatement(node, context)
|
||||||
|
break
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
default:
|
default:
|
||||||
@ -851,3 +842,15 @@ function genAssignmentExpression(
|
|||||||
context.push(` = `)
|
context.push(` = `)
|
||||||
genNode(node.right, context)
|
genNode(node.right, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function genReturnStatement(
|
||||||
|
{ returns }: ReturnStatement,
|
||||||
|
context: CodegenContext
|
||||||
|
) {
|
||||||
|
context.push(`return `)
|
||||||
|
if (isArray(returns)) {
|
||||||
|
genNodeListAsArray(returns, context)
|
||||||
|
} else {
|
||||||
|
genNode(returns, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { CompilerOptions } from './options'
|
import { CompilerOptions } from './options'
|
||||||
import { baseParse } from './parse'
|
import { baseParse } from './parse'
|
||||||
import { transform } from './transform'
|
import { transform, NodeTransform, DirectiveTransform } from './transform'
|
||||||
import { generate, CodegenResult } from './codegen'
|
import { generate, CodegenResult } from './codegen'
|
||||||
import { RootNode } from './ast'
|
import { RootNode } from './ast'
|
||||||
import { isString } from '@vue/shared'
|
import { isString } from '@vue/shared'
|
||||||
@ -17,6 +17,39 @@ import { transformOnce } from './transforms/vOnce'
|
|||||||
import { transformModel } from './transforms/vModel'
|
import { transformModel } from './transforms/vModel'
|
||||||
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
||||||
|
|
||||||
|
export type TransformPreset = [
|
||||||
|
NodeTransform[],
|
||||||
|
Record<string, DirectiveTransform>
|
||||||
|
]
|
||||||
|
|
||||||
|
export function getBaseTransformPreset(
|
||||||
|
prefixIdentifiers?: boolean
|
||||||
|
): TransformPreset {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
transformOnce,
|
||||||
|
transformIf,
|
||||||
|
transformFor,
|
||||||
|
...(!__BROWSER__ && prefixIdentifiers
|
||||||
|
? [
|
||||||
|
// order is important
|
||||||
|
trackVForSlotScopes,
|
||||||
|
transformExpression
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
transformSlotOutlet,
|
||||||
|
transformElement,
|
||||||
|
trackSlotScopes,
|
||||||
|
transformText
|
||||||
|
],
|
||||||
|
{
|
||||||
|
on: transformOn,
|
||||||
|
bind: transformBind,
|
||||||
|
model: transformModel
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// we name it `baseCompile` so that higher order compilers like
|
// we name it `baseCompile` so that higher order compilers like
|
||||||
// @vue/compiler-dom can export `compile` while re-exporting everything else.
|
// @vue/compiler-dom can export `compile` while re-exporting everything else.
|
||||||
export function baseCompile(
|
export function baseCompile(
|
||||||
@ -44,30 +77,18 @@ export function baseCompile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ast = isString(template) ? baseParse(template, options) : template
|
const ast = isString(template) ? baseParse(template, options) : template
|
||||||
|
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
|
||||||
|
prefixIdentifiers
|
||||||
|
)
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
...options,
|
...options,
|
||||||
prefixIdentifiers,
|
prefixIdentifiers,
|
||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
transformOnce,
|
...nodeTransforms,
|
||||||
transformIf,
|
|
||||||
transformFor,
|
|
||||||
...(prefixIdentifiers
|
|
||||||
? [
|
|
||||||
// order is important
|
|
||||||
trackVForSlotScopes,
|
|
||||||
transformExpression
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
transformSlotOutlet,
|
|
||||||
transformElement,
|
|
||||||
trackSlotScopes,
|
|
||||||
transformText,
|
|
||||||
...(options.nodeTransforms || []) // user transforms
|
...(options.nodeTransforms || []) // user transforms
|
||||||
],
|
],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
on: transformOn,
|
...directiveTransforms,
|
||||||
bind: transformBind,
|
|
||||||
model: transformModel,
|
|
||||||
...(options.directiveTransforms || {}) // user transforms
|
...(options.directiveTransforms || {}) // user transforms
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -10,8 +10,8 @@ export {
|
|||||||
export { baseParse, TextModes } from './parse'
|
export { baseParse, TextModes } from './parse'
|
||||||
export {
|
export {
|
||||||
transform,
|
transform,
|
||||||
createStructuralDirectiveTransform,
|
|
||||||
TransformContext,
|
TransformContext,
|
||||||
|
createStructuralDirectiveTransform,
|
||||||
NodeTransform,
|
NodeTransform,
|
||||||
StructuralDirectiveTransform,
|
StructuralDirectiveTransform,
|
||||||
DirectiveTransform
|
DirectiveTransform
|
||||||
@ -23,18 +23,16 @@ export {
|
|||||||
CompilerError,
|
CompilerError,
|
||||||
createCompilerError
|
createCompilerError
|
||||||
} from './errors'
|
} from './errors'
|
||||||
|
|
||||||
export * from './ast'
|
export * from './ast'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
export { registerRuntimeHelpers } from './runtimeHelpers'
|
export * from './runtimeHelpers'
|
||||||
export { noopDirectiveTransform } from './transforms/noopDirectiveTransform'
|
|
||||||
|
|
||||||
// expose transforms so higher-order compilers can import and extend them
|
export { getBaseTransformPreset, TransformPreset } from './compile'
|
||||||
export { transformModel } from './transforms/vModel'
|
export { transformModel } from './transforms/vModel'
|
||||||
export { transformOn } from './transforms/vOn'
|
export { transformOn } from './transforms/vOn'
|
||||||
export { transformBind } from './transforms/vBind'
|
export { transformBind } from './transforms/vBind'
|
||||||
|
export { noopDirectiveTransform } from './transforms/noopDirectiveTransform'
|
||||||
// exported for compiler-ssr
|
|
||||||
export * from './runtimeHelpers'
|
|
||||||
export { processIf } from './transforms/vIf'
|
export { processIf } from './transforms/vIf'
|
||||||
export { processFor, createForLoopParams } from './transforms/vFor'
|
export { processFor, createForLoopParams } from './transforms/vFor'
|
||||||
export {
|
export {
|
||||||
|
@ -85,8 +85,8 @@ export interface TransformContext extends Required<TransformOptions> {
|
|||||||
components: Set<string>
|
components: Set<string>
|
||||||
directives: Set<string>
|
directives: Set<string>
|
||||||
hoists: JSChildNode[]
|
hoists: JSChildNode[]
|
||||||
temps: number
|
|
||||||
imports: Set<ImportItem>
|
imports: Set<ImportItem>
|
||||||
|
temps: number
|
||||||
cached: number
|
cached: number
|
||||||
identifiers: { [name: string]: number | undefined }
|
identifiers: { [name: string]: number | undefined }
|
||||||
scopes: {
|
scopes: {
|
||||||
@ -141,8 +141,8 @@ function createTransformContext(
|
|||||||
components: new Set(),
|
components: new Set(),
|
||||||
directives: new Set(),
|
directives: new Set(),
|
||||||
hoists: [],
|
hoists: [],
|
||||||
temps: 0,
|
|
||||||
imports: new Set(),
|
imports: new Set(),
|
||||||
|
temps: 0,
|
||||||
cached: 0,
|
cached: 0,
|
||||||
identifiers: {},
|
identifiers: {},
|
||||||
scopes: {
|
scopes: {
|
||||||
|
@ -77,7 +77,7 @@ export const transformText: NodeTransform = (node, context) => {
|
|||||||
callArgs.push(child)
|
callArgs.push(child)
|
||||||
}
|
}
|
||||||
// mark dynamic text with flag so it gets patched inside a block
|
// mark dynamic text with flag so it gets patched inside a block
|
||||||
if (child.type !== NodeTypes.TEXT) {
|
if (!context.ssr && child.type !== NodeTypes.TEXT) {
|
||||||
callArgs.push(
|
callArgs.push(
|
||||||
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
|
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,9 @@ import {
|
|||||||
isBuiltInType,
|
isBuiltInType,
|
||||||
ParserOptions,
|
ParserOptions,
|
||||||
RootNode,
|
RootNode,
|
||||||
noopDirectiveTransform
|
noopDirectiveTransform,
|
||||||
|
TransformPreset,
|
||||||
|
getBaseTransformPreset
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { parserOptionsMinimal } from './parserOptionsMinimal'
|
import { parserOptionsMinimal } from './parserOptionsMinimal'
|
||||||
import { parserOptionsStandard } from './parserOptionsStandard'
|
import { parserOptionsStandard } from './parserOptionsStandard'
|
||||||
@ -31,25 +33,43 @@ export const isBuiltInDOMComponent = (tag: string): symbol | undefined => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compile(
|
export function getDOMTransformPreset(
|
||||||
template: string,
|
prefixIdentifiers?: boolean
|
||||||
options: CompilerOptions = {}
|
): TransformPreset {
|
||||||
): CodegenResult {
|
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
|
||||||
return baseCompile(template, {
|
prefixIdentifiers
|
||||||
...parserOptions,
|
)
|
||||||
...options,
|
return [
|
||||||
nodeTransforms: [
|
[
|
||||||
|
...nodeTransforms,
|
||||||
transformStyle,
|
transformStyle,
|
||||||
...(__DEV__ ? [warnTransitionChildren] : []),
|
...(__DEV__ ? [warnTransitionChildren] : [])
|
||||||
...(options.nodeTransforms || [])
|
|
||||||
],
|
],
|
||||||
directiveTransforms: {
|
{
|
||||||
|
...directiveTransforms,
|
||||||
cloak: noopDirectiveTransform,
|
cloak: noopDirectiveTransform,
|
||||||
html: transformVHtml,
|
html: transformVHtml,
|
||||||
text: transformVText,
|
text: transformVText,
|
||||||
model: transformModel, // override compiler-core
|
model: transformModel, // override compiler-core
|
||||||
on: transformOn,
|
on: transformOn, // override compiler-core
|
||||||
show: transformShow,
|
show: transformShow
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compile(
|
||||||
|
template: string,
|
||||||
|
options: CompilerOptions = {}
|
||||||
|
): CodegenResult {
|
||||||
|
const [nodeTransforms, directiveTransforms] = getDOMTransformPreset(
|
||||||
|
options.prefixIdentifiers
|
||||||
|
)
|
||||||
|
return baseCompile(template, {
|
||||||
|
...parserOptions,
|
||||||
|
...options,
|
||||||
|
nodeTransforms: [...nodeTransforms, ...(options.nodeTransforms || [])],
|
||||||
|
directiveTransforms: {
|
||||||
|
...directiveTransforms,
|
||||||
...(options.directiveTransforms || {})
|
...(options.directiveTransforms || {})
|
||||||
},
|
},
|
||||||
isBuiltInComponent: isBuiltInDOMComponent
|
isBuiltInComponent: isBuiltInDOMComponent
|
||||||
|
@ -57,10 +57,13 @@ describe('ssr: components', () => {
|
|||||||
|
|
||||||
_push(_ssrRenderComponent(_component_foo, null, {
|
_push(_ssrRenderComponent(_component_foo, null, {
|
||||||
default: (_, _push, _parent, _scopeId) => {
|
default: (_, _push, _parent, _scopeId) => {
|
||||||
if (_scopeId) {
|
if (_push) {
|
||||||
_push(\`hello<div \${_scopeId}></div>\`)
|
_push(\`hello<div\${_scopeId}></div>\`)
|
||||||
} else {
|
} else {
|
||||||
_push(\`hello<div></div>\`)
|
return [
|
||||||
|
createTextVNode(\\"hello\\"),
|
||||||
|
createVNode(\\"div\\")
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_compiled: true
|
_compiled: true
|
||||||
@ -80,7 +83,13 @@ describe('ssr: components', () => {
|
|||||||
|
|
||||||
_push(_ssrRenderComponent(_component_foo, null, {
|
_push(_ssrRenderComponent(_component_foo, null, {
|
||||||
default: ({ msg }, _push, _parent, _scopeId) => {
|
default: ({ msg }, _push, _parent, _scopeId) => {
|
||||||
_push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
|
if (_push) {
|
||||||
|
_push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
createTextVNode(toDisplayString(_ctx.msg + _ctx.outer))
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_compiled: true
|
_compiled: true
|
||||||
}, _parent))
|
}, _parent))
|
||||||
@ -103,10 +112,22 @@ describe('ssr: components', () => {
|
|||||||
|
|
||||||
_push(_ssrRenderComponent(_component_foo, null, {
|
_push(_ssrRenderComponent(_component_foo, null, {
|
||||||
default: (_, _push, _parent, _scopeId) => {
|
default: (_, _push, _parent, _scopeId) => {
|
||||||
_push(\`foo\`)
|
if (_push) {
|
||||||
|
_push(\`foo\`)
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
createTextVNode(\\"foo\\")
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
named: (_, _push, _parent, _scopeId) => {
|
named: (_, _push, _parent, _scopeId) => {
|
||||||
_push(\`bar\`)
|
if (_push) {
|
||||||
|
_push(\`bar\`)
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
createTextVNode(\\"bar\\")
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_compiled: true
|
_compiled: true
|
||||||
}, _parent))
|
}, _parent))
|
||||||
@ -131,7 +152,13 @@ describe('ssr: components', () => {
|
|||||||
? {
|
? {
|
||||||
name: \\"named\\",
|
name: \\"named\\",
|
||||||
fn: (_, _push, _parent, _scopeId) => {
|
fn: (_, _push, _parent, _scopeId) => {
|
||||||
_push(\`foo\`)
|
if (_push) {
|
||||||
|
_push(\`foo\`)
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
createTextVNode(\\"foo\\")
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
|
@ -31,7 +31,13 @@ describe('ssr: scopeId', () => {
|
|||||||
|
|
||||||
_push(_ssrRenderComponent(_component_foo, null, {
|
_push(_ssrRenderComponent(_component_foo, null, {
|
||||||
default: (_, _push, _parent, _scopeId) => {
|
default: (_, _push, _parent, _scopeId) => {
|
||||||
_push(\`foo\`)
|
if (_push) {
|
||||||
|
_push(\`foo\`)
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
createTextVNode(\\"foo\\")
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_compiled: true
|
_compiled: true
|
||||||
}, _parent))
|
}, _parent))
|
||||||
@ -53,10 +59,12 @@ describe('ssr: scopeId', () => {
|
|||||||
|
|
||||||
_push(_ssrRenderComponent(_component_foo, null, {
|
_push(_ssrRenderComponent(_component_foo, null, {
|
||||||
default: (_, _push, _parent, _scopeId) => {
|
default: (_, _push, _parent, _scopeId) => {
|
||||||
if (_scopeId) {
|
if (_push) {
|
||||||
_push(\`<span data-v-xxxxxxx \${_scopeId}>hello</span>\`)
|
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
|
||||||
} else {
|
} else {
|
||||||
_push(\`<span data-v-xxxxxxx>hello</span>\`)
|
return [
|
||||||
|
createVNode(\\"span\\", null, \\"hello\\")
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_compiled: true
|
_compiled: true
|
||||||
@ -80,30 +88,30 @@ describe('ssr: scopeId', () => {
|
|||||||
|
|
||||||
_push(_ssrRenderComponent(_component_foo, null, {
|
_push(_ssrRenderComponent(_component_foo, null, {
|
||||||
default: (_, _push, _parent, _scopeId) => {
|
default: (_, _push, _parent, _scopeId) => {
|
||||||
if (_scopeId) {
|
if (_push) {
|
||||||
_push(\`<span data-v-xxxxxxx \${_scopeId}>hello</span>\`)
|
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
|
||||||
_push(_ssrRenderComponent(_component_bar, null, {
|
_push(_ssrRenderComponent(_component_bar, null, {
|
||||||
default: (_, _push, _parent, _scopeId) => {
|
default: (_, _push, _parent, _scopeId) => {
|
||||||
if (_scopeId) {
|
if (_push) {
|
||||||
_push(\`<span data-v-xxxxxxx \${_scopeId}></span>\`)
|
_push(\`<span data-v-xxxxxxx\${_scopeId}></span>\`)
|
||||||
} else {
|
} else {
|
||||||
_push(\`<span data-v-xxxxxxx></span>\`)
|
return [
|
||||||
|
createVNode(\\"span\\")
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_compiled: true
|
_compiled: true
|
||||||
}, _parent))
|
}, _parent))
|
||||||
} else {
|
} else {
|
||||||
_push(\`<span data-v-xxxxxxx>hello</span>\`)
|
return [
|
||||||
_push(_ssrRenderComponent(_component_bar, null, {
|
createVNode(\\"span\\", null, \\"hello\\"),
|
||||||
default: (_, _push, _parent, _scopeId) => {
|
createVNode(_component_bar, null, {
|
||||||
if (_scopeId) {
|
default: () => [
|
||||||
_push(\`<span data-v-xxxxxxx \${_scopeId}></span>\`)
|
createVNode(\\"span\\")
|
||||||
} else {
|
],
|
||||||
_push(\`<span data-v-xxxxxxx></span>\`)
|
_compiled: true
|
||||||
}
|
})
|
||||||
},
|
]
|
||||||
_compiled: true
|
|
||||||
}, _parent))
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_compiled: true
|
_compiled: true
|
||||||
|
@ -28,7 +28,7 @@ import { ssrProcessElement } from './transforms/ssrTransformElement'
|
|||||||
// passing it to codegen.
|
// passing it to codegen.
|
||||||
|
|
||||||
export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
|
export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
|
||||||
const context = createSSRTransformContext(options)
|
const context = createSSRTransformContext(ast, options)
|
||||||
const isFragment =
|
const isFragment =
|
||||||
ast.children.length > 1 && ast.children.some(c => !isText(c))
|
ast.children.length > 1 && ast.children.some(c => !isText(c))
|
||||||
processChildren(ast.children, context, isFragment)
|
processChildren(ast.children, context, isFragment)
|
||||||
@ -46,6 +46,7 @@ export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
|
|||||||
export type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
|
export type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
|
||||||
|
|
||||||
function createSSRTransformContext(
|
function createSSRTransformContext(
|
||||||
|
root: RootNode,
|
||||||
options: CompilerOptions,
|
options: CompilerOptions,
|
||||||
helpers: Set<symbol> = new Set(),
|
helpers: Set<symbol> = new Set(),
|
||||||
withSlotScopeId = false
|
withSlotScopeId = false
|
||||||
@ -54,6 +55,7 @@ function createSSRTransformContext(
|
|||||||
let currentString: TemplateLiteral | null = null
|
let currentString: TemplateLiteral | null = null
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
root,
|
||||||
options,
|
options,
|
||||||
body,
|
body,
|
||||||
helpers,
|
helpers,
|
||||||
@ -91,6 +93,7 @@ function createChildContext(
|
|||||||
): SSRTransformContext {
|
): SSRTransformContext {
|
||||||
// ensure child inherits parent helpers
|
// ensure child inherits parent helpers
|
||||||
return createSSRTransformContext(
|
return createSSRTransformContext(
|
||||||
|
parent.root,
|
||||||
parent.options,
|
parent.options,
|
||||||
parent.helpers,
|
parent.helpers,
|
||||||
withSlotScopeId
|
withSlotScopeId
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
ComponentNode,
|
ComponentNode,
|
||||||
SlotFnBuilder,
|
SlotFnBuilder,
|
||||||
createFunctionExpression,
|
createFunctionExpression,
|
||||||
createBlockStatement,
|
|
||||||
buildSlots,
|
buildSlots,
|
||||||
FunctionExpression,
|
FunctionExpression,
|
||||||
TemplateChildNode,
|
TemplateChildNode,
|
||||||
@ -17,7 +16,14 @@ import {
|
|||||||
TRANSITION_GROUP,
|
TRANSITION_GROUP,
|
||||||
createIfStatement,
|
createIfStatement,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
isText
|
getDOMTransformPreset,
|
||||||
|
transform,
|
||||||
|
createReturnStatement,
|
||||||
|
ReturnStatement,
|
||||||
|
Namespaces,
|
||||||
|
locStub,
|
||||||
|
RootNode,
|
||||||
|
TransformContext
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
@ -25,7 +31,7 @@ import {
|
|||||||
processChildren,
|
processChildren,
|
||||||
processChildrenAsStatement
|
processChildrenAsStatement
|
||||||
} from '../ssrCodegenTransform'
|
} from '../ssrCodegenTransform'
|
||||||
import { isSymbol } from '@vue/shared'
|
import { isSymbol, isObject, isArray } from '@vue/shared'
|
||||||
|
|
||||||
// We need to construct the slot functions in the 1st pass to ensure proper
|
// We need to construct the slot functions in the 1st pass to ensure proper
|
||||||
// scope tracking, but the children of each slot cannot be processed until
|
// scope tracking, but the children of each slot cannot be processed until
|
||||||
@ -36,6 +42,7 @@ const wipMap = new WeakMap<ComponentNode, WIPSlotEntry[]>()
|
|||||||
interface WIPSlotEntry {
|
interface WIPSlotEntry {
|
||||||
fn: FunctionExpression
|
fn: FunctionExpression
|
||||||
children: TemplateChildNode[]
|
children: TemplateChildNode[]
|
||||||
|
vnodeBranch: ReturnStatement
|
||||||
}
|
}
|
||||||
|
|
||||||
const componentTypeMap = new WeakMap<ComponentNode, symbol>()
|
const componentTypeMap = new WeakMap<ComponentNode, symbol>()
|
||||||
@ -55,26 +62,32 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
|||||||
return // built-in component: fallthrough
|
return // built-in component: fallthrough
|
||||||
}
|
}
|
||||||
|
|
||||||
// note we are not passing ssr: true here because for components, v-on
|
|
||||||
// handlers should still be passed
|
|
||||||
const props =
|
const props =
|
||||||
node.props.length > 0 ? buildProps(node, context).props || `null` : `null`
|
node.props.length > 0
|
||||||
|
? // note we are not passing ssr: true here because for components, v-on
|
||||||
|
// handlers should still be passed
|
||||||
|
buildProps(node, context).props || `null`
|
||||||
|
: `null`
|
||||||
|
|
||||||
const wipEntries: WIPSlotEntry[] = []
|
const wipEntries: WIPSlotEntry[] = []
|
||||||
wipMap.set(node, wipEntries)
|
wipMap.set(node, wipEntries)
|
||||||
|
|
||||||
const buildSSRSlotFn: SlotFnBuilder = (props, children, loc) => {
|
const buildSSRSlotFn: SlotFnBuilder = (props, children, loc) => {
|
||||||
// An SSR slot function has the signature of
|
|
||||||
// (props, _push, _parent, _scopeId) => void
|
|
||||||
// See server-renderer/src/helpers/renderSlot.ts
|
|
||||||
const fn = createFunctionExpression(
|
const fn = createFunctionExpression(
|
||||||
[props || `_`, `_push`, `_parent`, `_scopeId`],
|
[props || `_`, `_push`, `_parent`, `_scopeId`],
|
||||||
undefined, // no return, assign body later
|
undefined, // no return, assign body later
|
||||||
true, // newline
|
true, // newline
|
||||||
false, // isSlot: pass false since we don't need client scopeId codegen
|
true, // isSlot
|
||||||
loc
|
loc
|
||||||
)
|
)
|
||||||
wipEntries.push({ fn, children })
|
wipEntries.push({
|
||||||
|
fn,
|
||||||
|
children,
|
||||||
|
// build the children using normal vnode-based transforms
|
||||||
|
// TODO fixme: `children` here has already been mutated at this point
|
||||||
|
// so the sub-transform runs into errors :/
|
||||||
|
vnodeBranch: createVNodeSlotBranch(clone(children), context)
|
||||||
|
})
|
||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,9 +95,6 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
|||||||
? buildSlots(node, context, buildSSRSlotFn).slots
|
? buildSlots(node, context, buildSSRSlotFn).slots
|
||||||
: `null`
|
: `null`
|
||||||
|
|
||||||
// TODO option for slots bail out
|
|
||||||
// TODO scopeId
|
|
||||||
|
|
||||||
node.ssrCodegenNode = createCallExpression(
|
node.ssrCodegenNode = createCallExpression(
|
||||||
context.helper(SSR_RENDER_COMPONENT),
|
context.helper(SSR_RENDER_COMPONENT),
|
||||||
[component, props, slots, `_parent`]
|
[component, props, slots, `_parent`]
|
||||||
@ -113,26 +123,79 @@ export function ssrProcessComponent(
|
|||||||
// finish up slot function expressions from the 1st pass.
|
// finish up slot function expressions from the 1st pass.
|
||||||
const wipEntries = wipMap.get(node) || []
|
const wipEntries = wipMap.get(node) || []
|
||||||
for (let i = 0; i < wipEntries.length; i++) {
|
for (let i = 0; i < wipEntries.length; i++) {
|
||||||
const { fn, children } = wipEntries[i]
|
const { fn, children, vnodeBranch } = wipEntries[i]
|
||||||
const hasNonTextChild = children.some(c => !isText(c))
|
// For each slot, we generate two branches: one SSR-optimized branch and
|
||||||
if (hasNonTextChild) {
|
// one normal vnode-based branch. The branches are taken based on the
|
||||||
// SSR slots need to handled potential presence of scopeId of the child
|
// presence of the 2nd `_push` argument (which is only present if the slot
|
||||||
// component. To avoid the cost of concatenation when it's unnecessary,
|
// is called by `_ssrRenderSlot`.
|
||||||
// we split the code into two paths, one with slot scopeId and one without.
|
fn.body = createIfStatement(
|
||||||
fn.body = createBlockStatement([
|
createSimpleExpression(`_push`, false),
|
||||||
createIfStatement(
|
processChildrenAsStatement(
|
||||||
createSimpleExpression(`_scopeId`, false),
|
children,
|
||||||
// branch with scopeId concatenation
|
context,
|
||||||
processChildrenAsStatement(children, context, false, true),
|
false,
|
||||||
// branch without scopeId concatenation
|
true /* withSlotScopeId */
|
||||||
processChildrenAsStatement(children, context, false, false)
|
),
|
||||||
)
|
vnodeBranch
|
||||||
])
|
)
|
||||||
} else {
|
|
||||||
// only text, no need for scopeId branching.
|
|
||||||
fn.body = processChildrenAsStatement(children, context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
context.pushStatement(createCallExpression(`_push`, [node.ssrCodegenNode]))
|
context.pushStatement(createCallExpression(`_push`, [node.ssrCodegenNode]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createVNodeSlotBranch(
|
||||||
|
children: TemplateChildNode[],
|
||||||
|
context: TransformContext
|
||||||
|
): ReturnStatement {
|
||||||
|
// we need to process the slot children using client-side transforms.
|
||||||
|
// in order to do that we need to construct a fresh root.
|
||||||
|
// in addition, wrap the children with a wrapper template for proper child
|
||||||
|
// treatment.
|
||||||
|
const { root } = context
|
||||||
|
const childRoot: RootNode = {
|
||||||
|
...root,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
ns: Namespaces.HTML,
|
||||||
|
tag: 'template',
|
||||||
|
tagType: ElementTypes.TEMPLATE,
|
||||||
|
isSelfClosing: false,
|
||||||
|
props: [],
|
||||||
|
children,
|
||||||
|
loc: locStub,
|
||||||
|
codegenNode: undefined
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const [nodeTransforms, directiveTransforms] = getDOMTransformPreset(true)
|
||||||
|
transform(childRoot, {
|
||||||
|
...context, // copy transform options on context
|
||||||
|
nodeTransforms,
|
||||||
|
directiveTransforms
|
||||||
|
})
|
||||||
|
|
||||||
|
// merge helpers/components/directives/imports from the childRoot
|
||||||
|
// back to current root
|
||||||
|
;(['helpers', 'components', 'directives', 'imports'] as const).forEach(
|
||||||
|
key => {
|
||||||
|
root[key] = [...new Set([...root[key], ...childRoot[key]])] as any
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return createReturnStatement(children)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clone(v: any): any {
|
||||||
|
if (isArray(v)) {
|
||||||
|
return v.map(clone)
|
||||||
|
} else if (isObject(v)) {
|
||||||
|
const res: any = {}
|
||||||
|
for (const key in v) {
|
||||||
|
res[key] = v[key]
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
} else {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -309,7 +309,6 @@ export function ssrProcessElement(
|
|||||||
|
|
||||||
// Handle slot scopeId
|
// Handle slot scopeId
|
||||||
if (context.withSlotScopeId) {
|
if (context.withSlotScopeId) {
|
||||||
context.pushStringPart(` `)
|
|
||||||
context.pushStringPart(createSimpleExpression(`_scopeId`, false))
|
context.pushStringPart(createSimpleExpression(`_scopeId`, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
VNode
|
VNode
|
||||||
} from '../vnode'
|
} from '../vnode'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags } from '@vue/shared'
|
||||||
|
import { warn } from '../warning'
|
||||||
|
|
||||||
export function renderSlot(
|
export function renderSlot(
|
||||||
slots: Record<string, Slot>,
|
slots: Record<string, Slot>,
|
||||||
@ -17,7 +18,17 @@ export function renderSlot(
|
|||||||
// the compiler and guaranteed to be an array
|
// the compiler and guaranteed to be an array
|
||||||
fallback?: VNodeArrayChildren
|
fallback?: VNodeArrayChildren
|
||||||
): VNode {
|
): VNode {
|
||||||
const slot = slots[name]
|
let slot = slots[name]
|
||||||
|
|
||||||
|
if (__DEV__ && slot.length > 1) {
|
||||||
|
warn(
|
||||||
|
`SSR-optimized slot function detected in a non-SSR-optimized render ` +
|
||||||
|
`function. You need to mark this component with $dynamic-slots in the ` +
|
||||||
|
`parent template.`
|
||||||
|
)
|
||||||
|
slot = () => []
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
openBlock(),
|
openBlock(),
|
||||||
createBlock(
|
createBlock(
|
||||||
|
@ -25,7 +25,7 @@ export function ssrRenderSlot(
|
|||||||
if (slotFn.length > 1) {
|
if (slotFn.length > 1) {
|
||||||
// only ssr-optimized slot fns accept more than 1 arguments
|
// only ssr-optimized slot fns accept more than 1 arguments
|
||||||
const scopeId = parentComponent && parentComponent.type.__scopeId
|
const scopeId = parentComponent && parentComponent.type.__scopeId
|
||||||
slotFn(slotProps, push, parentComponent, scopeId ? scopeId + `-s` : null)
|
slotFn(slotProps, push, parentComponent, scopeId ? ` ${scopeId}-s` : ``)
|
||||||
} else {
|
} else {
|
||||||
// normal slot
|
// normal slot
|
||||||
renderVNodeChildren(push, (slotFn as Slot)(slotProps), parentComponent)
|
renderVNodeChildren(push, (slotFn as Slot)(slotProps), parentComponent)
|
||||||
|
@ -30,7 +30,7 @@ window.init = () => {
|
|||||||
ssrMode.value = persistedState.ssr
|
ssrMode.value = persistedState.ssr
|
||||||
Object.assign(compilerOptions, persistedState.options)
|
Object.assign(compilerOptions, persistedState.options)
|
||||||
|
|
||||||
let lastSuccessfulCode: string = `/* See console for error */`
|
let lastSuccessfulCode: string
|
||||||
let lastSuccessfulMap: SourceMapConsumer | undefined = undefined
|
let lastSuccessfulMap: SourceMapConsumer | undefined = undefined
|
||||||
function compileCode(source: string): string {
|
function compileCode(source: string): string {
|
||||||
console.clear()
|
console.clear()
|
||||||
@ -57,6 +57,9 @@ window.init = () => {
|
|||||||
lastSuccessfulMap = new window._deps['source-map'].SourceMapConsumer(map)
|
lastSuccessfulMap = new window._deps['source-map'].SourceMapConsumer(map)
|
||||||
lastSuccessfulMap!.computeColumnSpans()
|
lastSuccessfulMap!.computeColumnSpans()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
lastSuccessfulCode = `/* ERROR: ${
|
||||||
|
e.message
|
||||||
|
} (see console for more info) */`
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
return lastSuccessfulCode
|
return lastSuccessfulCode
|
||||||
|
Loading…
Reference in New Issue
Block a user