fix(compiler-ssr): handle comments codegen + refactor ssr codegen transform

This commit is contained in:
Evan You 2020-05-01 17:04:36 -04:00
parent 0e730c06e4
commit 6c60ce13e0
5 changed files with 85 additions and 27 deletions

View File

@ -6,6 +6,12 @@ describe('ssr: text', () => {
expect(getCompiledString(`foo`)).toMatchInlineSnapshot(`"\`foo\`"`) expect(getCompiledString(`foo`)).toMatchInlineSnapshot(`"\`foo\`"`)
}) })
test('comments', () => {
expect(getCompiledString(`<!--bar-->`)).toMatchInlineSnapshot(
`"\`<!--bar-->\`"`
)
})
test('static text escape', () => { test('static text escape', () => {
expect(getCompiledString(`&lt;foo&gt;`)).toMatchInlineSnapshot( expect(getCompiledString(`&lt;foo&gt;`)).toMatchInlineSnapshot(
`"\`&lt;foo&gt;\`"` `"\`&lt;foo&gt;\`"`

View File

@ -19,11 +19,13 @@ export function createSSRCompilerError(
export const enum SSRErrorCodes { export const enum SSRErrorCodes {
X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM = DOMErrorCodes.__EXTEND_POINT__, X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM = DOMErrorCodes.__EXTEND_POINT__,
X_SSR_UNSAFE_ATTR_NAME, X_SSR_UNSAFE_ATTR_NAME,
X_SSR_NO_TELEPORT_TARGET X_SSR_NO_TELEPORT_TARGET,
X_SSR_INVALID_AST_NODE
} }
export const SSRErrorMessages: { [code: number]: string } = { export const SSRErrorMessages: { [code: number]: string } = {
[SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM]: `Custom directive is missing corresponding SSR transform and will be ignored.`, [SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM]: `Custom directive is missing corresponding SSR transform and will be ignored.`,
[SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME]: `Unsafe attribute name for SSR.`, [SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME]: `Unsafe attribute name for SSR.`,
[SSRErrorCodes.X_SSR_NO_TELEPORT_TARGET]: `No target prop on teleport element.` [SSRErrorCodes.X_SSR_NO_TELEPORT_TARGET]: `No target prop on teleport element.`,
[SSRErrorCodes.X_SSR_INVALID_AST_NODE]: `Invalid AST node during ssr transform`
} }

View File

@ -20,6 +20,7 @@ import { ssrProcessFor } from './transforms/ssrVFor'
import { ssrProcessSlotOutlet } from './transforms/ssrTransformSlotOutlet' import { ssrProcessSlotOutlet } from './transforms/ssrTransformSlotOutlet'
import { ssrProcessComponent } from './transforms/ssrTransformComponent' import { ssrProcessComponent } from './transforms/ssrTransformComponent'
import { ssrProcessElement } from './transforms/ssrTransformElement' import { ssrProcessElement } from './transforms/ssrTransformElement'
import { createSSRCompilerError, SSRErrorCodes } from './errors'
// Because SSR codegen output is completely different from client-side output // Because SSR codegen output is completely different from client-side output
// (e.g. multiple elements can be concatenated into a single template literal // (e.g. multiple elements can be concatenated into a single template literal
@ -115,24 +116,70 @@ export function processChildren(
} }
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 (child.type === NodeTypes.ELEMENT) { switch (child.type) {
if (child.tagType === ElementTypes.ELEMENT) { case NodeTypes.ELEMENT:
ssrProcessElement(child, context) switch (child.tagType) {
} else if (child.tagType === ElementTypes.COMPONENT) { case ElementTypes.ELEMENT:
ssrProcessComponent(child, context) ssrProcessElement(child, context)
} else if (child.tagType === ElementTypes.SLOT) { break
ssrProcessSlotOutlet(child, context) case ElementTypes.COMPONENT:
} ssrProcessComponent(child, context)
} else if (child.type === NodeTypes.TEXT) { break
context.pushStringPart(escapeHtml(child.content)) case ElementTypes.SLOT:
} else if (child.type === NodeTypes.INTERPOLATION) { ssrProcessSlotOutlet(child, context)
context.pushStringPart( break
createCallExpression(context.helper(SSR_INTERPOLATE), [child.content]) case ElementTypes.TEMPLATE:
) // TODO
} else if (child.type === NodeTypes.IF) { break
ssrProcessIf(child, context) default:
} else if (child.type === NodeTypes.FOR) { context.onError(
ssrProcessFor(child, context) createSSRCompilerError(
SSRErrorCodes.X_SSR_INVALID_AST_NODE,
(child as any).loc
)
)
// make sure we exhaust all possible types
const exhaustiveCheck: never = child
return exhaustiveCheck
}
break
case NodeTypes.TEXT:
context.pushStringPart(escapeHtml(child.content))
break
case NodeTypes.COMMENT:
// no need to escape comment here because the AST can only
// contain valid comments.
context.pushStringPart(`<!--${child.content}-->`)
break
case NodeTypes.INTERPOLATION:
context.pushStringPart(
createCallExpression(context.helper(SSR_INTERPOLATE), [child.content])
)
break
case NodeTypes.IF:
ssrProcessIf(child, context)
break
case NodeTypes.FOR:
ssrProcessFor(child, context)
break
case NodeTypes.IF_BRANCH:
// no-op - handled by ssrProcessIf
break
case NodeTypes.TEXT_CALL:
case NodeTypes.COMPOUND_EXPRESSION:
// no-op - these two types can never appear as template child node since
// `transformText` is not used during SSR compile.
break
default:
context.onError(
createSSRCompilerError(
SSRErrorCodes.X_SSR_INVALID_AST_NODE,
(child as any).loc
)
)
// make sure we exhaust all possible types
const exhaustiveCheck: never = child
return exhaustiveCheck
} }
} }
if (asFragment) { if (asFragment) {

View File

@ -27,7 +27,8 @@ import {
isVoidTag, isVoidTag,
escapeHtml, escapeHtml,
NO, NO,
generateCodeFrame generateCodeFrame,
escapeHtmlComment
} from '@vue/shared' } from '@vue/shared'
import { compile } from '@vue/compiler-ssr' import { compile } from '@vue/compiler-ssr'
import { ssrRenderAttrs } from './helpers/ssrRenderAttrs' import { ssrRenderAttrs } from './helpers/ssrRenderAttrs'
@ -230,9 +231,6 @@ function ssrCompile(
return (compileCache[template] = Function('require', code)(require)) return (compileCache[template] = Function('require', code)(require))
} }
// https://www.w3.org/TR/html52/syntax.html#comments
const commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g
function renderVNode( function renderVNode(
push: PushFn, push: PushFn,
vnode: VNode, vnode: VNode,
@ -245,9 +243,7 @@ function renderVNode(
break break
case Comment: case Comment:
push( push(
children children ? `<!--${escapeHtmlComment(children as string)}-->` : `<!---->`
? `<!--${(children as string).replace(commentStripRE, '')}-->`
: `<!---->`
) )
break break
case Static: case Static:

View File

@ -43,3 +43,10 @@ export function escapeHtml(string: unknown) {
return lastIndex !== index ? html + str.substring(lastIndex, index) : html return lastIndex !== index ? html + str.substring(lastIndex, index) : html
} }
// https://www.w3.org/TR/html52/syntax.html#comments
const commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g
export function escapeHtmlComment(src: string): string {
return src.replace(commentStripRE, '')
}