2020-02-02 05:05:27 +00:00
|
|
|
import {
|
|
|
|
RootNode,
|
|
|
|
BlockStatement,
|
|
|
|
TemplateLiteral,
|
|
|
|
createCallExpression,
|
|
|
|
createTemplateLiteral,
|
|
|
|
NodeTypes,
|
|
|
|
TemplateChildNode,
|
2020-02-03 02:47:10 +00:00
|
|
|
ElementTypes,
|
2020-02-03 03:28:54 +00:00
|
|
|
createBlockStatement,
|
|
|
|
CompilerOptions,
|
2020-02-03 20:51:41 +00:00
|
|
|
isText,
|
|
|
|
IfStatement,
|
|
|
|
CallExpression
|
2020-02-02 05:05:27 +00:00
|
|
|
} from '@vue/compiler-dom'
|
2020-02-03 03:28:54 +00:00
|
|
|
import { isString, escapeHtml, NO } from '@vue/shared'
|
2020-02-05 16:20:50 +00:00
|
|
|
import { SSR_INTERPOLATE, ssrHelpers } from './runtimeHelpers'
|
2020-02-06 02:04:40 +00:00
|
|
|
import { ssrProcessIf } from './transforms/ssrVIf'
|
|
|
|
import { ssrProcessFor } from './transforms/ssrVFor'
|
|
|
|
import { ssrProcessSlotOutlet } from './transforms/ssrTransformSlotOutlet'
|
2020-02-06 04:07:23 +00:00
|
|
|
import { ssrProcessComponent } from './transforms/ssrTransformComponent'
|
2020-02-02 05:05:27 +00:00
|
|
|
|
|
|
|
// Because SSR codegen output is completely different from client-side output
|
|
|
|
// (e.g. multiple elements can be concatenated into a single template literal
|
|
|
|
// instead of each getting a corresponding call), we need to apply an extra
|
|
|
|
// transform pass to convert the template AST into a fresh JS AST before
|
|
|
|
// passing it to codegen.
|
|
|
|
|
2020-02-03 03:28:54 +00:00
|
|
|
export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
|
|
|
|
const context = createSSRTransformContext(options)
|
2020-02-02 05:05:27 +00:00
|
|
|
|
2020-02-03 03:28:54 +00:00
|
|
|
const isFragment =
|
|
|
|
ast.children.length > 1 && !ast.children.every(c => isText(c))
|
2020-02-02 05:05:27 +00:00
|
|
|
if (isFragment) {
|
|
|
|
context.pushStringPart(`<!---->`)
|
|
|
|
}
|
|
|
|
processChildren(ast.children, context)
|
|
|
|
if (isFragment) {
|
|
|
|
context.pushStringPart(`<!---->`)
|
|
|
|
}
|
|
|
|
|
2020-02-03 02:47:10 +00:00
|
|
|
ast.codegenNode = createBlockStatement(context.body)
|
2020-02-05 16:20:50 +00:00
|
|
|
|
|
|
|
// Finalize helpers.
|
|
|
|
// We need to separate helpers imported from 'vue' vs. '@vue/server-renderer'
|
|
|
|
ast.ssrHelpers = [
|
|
|
|
...ast.helpers.filter(h => h in ssrHelpers),
|
|
|
|
...context.helpers
|
|
|
|
]
|
|
|
|
ast.helpers = ast.helpers.filter(h => !(h in ssrHelpers))
|
2020-02-02 05:05:27 +00:00
|
|
|
}
|
|
|
|
|
2020-02-03 20:51:41 +00:00
|
|
|
export type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
|
2020-02-02 05:05:27 +00:00
|
|
|
|
2020-02-03 22:47:06 +00:00
|
|
|
function createSSRTransformContext(
|
|
|
|
options: CompilerOptions,
|
|
|
|
helpers: Set<symbol> = new Set()
|
|
|
|
) {
|
2020-02-02 05:05:27 +00:00
|
|
|
const body: BlockStatement['body'] = []
|
|
|
|
let currentString: TemplateLiteral | null = null
|
|
|
|
|
|
|
|
return {
|
2020-02-03 03:28:54 +00:00
|
|
|
options,
|
2020-02-02 05:05:27 +00:00
|
|
|
body,
|
2020-02-03 22:47:06 +00:00
|
|
|
helpers,
|
|
|
|
helper<T extends symbol>(name: T): T {
|
|
|
|
helpers.add(name)
|
|
|
|
return name
|
|
|
|
},
|
2020-02-02 05:05:27 +00:00
|
|
|
pushStringPart(part: TemplateLiteral['elements'][0]) {
|
|
|
|
if (!currentString) {
|
2020-02-03 20:51:41 +00:00
|
|
|
const currentCall = createCallExpression(`_push`)
|
|
|
|
body.push(currentCall)
|
2020-02-02 05:05:27 +00:00
|
|
|
currentString = createTemplateLiteral([])
|
|
|
|
currentCall.arguments.push(currentString)
|
|
|
|
}
|
|
|
|
const bufferedElements = currentString.elements
|
|
|
|
const lastItem = bufferedElements[bufferedElements.length - 1]
|
|
|
|
if (isString(part) && isString(lastItem)) {
|
|
|
|
bufferedElements[bufferedElements.length - 1] += part
|
|
|
|
} else {
|
|
|
|
bufferedElements.push(part)
|
|
|
|
}
|
2020-02-03 20:51:41 +00:00
|
|
|
},
|
|
|
|
pushStatement(statement: IfStatement | CallExpression) {
|
|
|
|
// close current string
|
|
|
|
currentString = null
|
|
|
|
body.push(statement)
|
2020-02-02 05:05:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-03 22:47:06 +00:00
|
|
|
export function createChildContext(
|
|
|
|
parent: SSRTransformContext
|
|
|
|
): SSRTransformContext {
|
|
|
|
// ensure child inherits parent helpers
|
|
|
|
return createSSRTransformContext(parent.options, parent.helpers)
|
|
|
|
}
|
|
|
|
|
2020-02-03 20:51:41 +00:00
|
|
|
export function processChildren(
|
2020-02-02 05:05:27 +00:00
|
|
|
children: TemplateChildNode[],
|
|
|
|
context: SSRTransformContext
|
|
|
|
) {
|
2020-02-03 03:28:54 +00:00
|
|
|
const isVoidTag = context.options.isVoidTag || NO
|
2020-02-02 05:05:27 +00:00
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
|
const child = children[i]
|
|
|
|
if (child.type === NodeTypes.ELEMENT) {
|
|
|
|
if (child.tagType === ElementTypes.ELEMENT) {
|
|
|
|
const elementsToAdd = child.ssrCodegenNode!.elements
|
|
|
|
for (let j = 0; j < elementsToAdd.length; j++) {
|
|
|
|
context.pushStringPart(elementsToAdd[j])
|
|
|
|
}
|
|
|
|
if (child.children.length) {
|
|
|
|
processChildren(child.children, context)
|
|
|
|
}
|
2020-02-03 03:28:54 +00:00
|
|
|
|
|
|
|
if (!isVoidTag(child.tag)) {
|
|
|
|
// push closing tag
|
|
|
|
context.pushStringPart(`</${child.tag}>`)
|
|
|
|
}
|
2020-02-02 05:05:27 +00:00
|
|
|
} else if (child.tagType === ElementTypes.COMPONENT) {
|
2020-02-06 04:07:23 +00:00
|
|
|
ssrProcessComponent(child, context)
|
2020-02-02 05:05:27 +00:00
|
|
|
} else if (child.tagType === ElementTypes.SLOT) {
|
2020-02-06 02:04:40 +00:00
|
|
|
ssrProcessSlotOutlet(child, context)
|
2020-02-02 05:05:27 +00:00
|
|
|
}
|
|
|
|
} else if (child.type === NodeTypes.TEXT) {
|
2020-02-03 03:08:20 +00:00
|
|
|
context.pushStringPart(escapeHtml(child.content))
|
2020-02-03 03:28:54 +00:00
|
|
|
} else if (child.type === NodeTypes.INTERPOLATION) {
|
2020-02-03 22:47:06 +00:00
|
|
|
context.pushStringPart(
|
|
|
|
createCallExpression(context.helper(SSR_INTERPOLATE), [child.content])
|
|
|
|
)
|
2020-02-02 05:05:27 +00:00
|
|
|
} else if (child.type === NodeTypes.IF) {
|
2020-02-06 02:04:40 +00:00
|
|
|
ssrProcessIf(child, context)
|
2020-02-02 05:05:27 +00:00
|
|
|
} else if (child.type === NodeTypes.FOR) {
|
2020-02-06 02:04:40 +00:00
|
|
|
ssrProcessFor(child, context)
|
2020-02-02 05:05:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|