diff --git a/packages/compiler-ssr/__tests__/ssrText.spec.ts b/packages/compiler-ssr/__tests__/ssrText.spec.ts index 21ef5518cbf..281f518f07f 100644 --- a/packages/compiler-ssr/__tests__/ssrText.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrText.spec.ts @@ -6,6 +6,12 @@ describe('ssr: text', () => { expect(getCompiledString(`foo`)).toMatchInlineSnapshot(`"\`foo\`"`) }) + test('comments', () => { + expect(getCompiledString(``)).toMatchInlineSnapshot( + `"\`\`"` + ) + }) + test('static text escape', () => { expect(getCompiledString(`<foo>`)).toMatchInlineSnapshot( `"\`<foo>\`"` diff --git a/packages/compiler-ssr/src/errors.ts b/packages/compiler-ssr/src/errors.ts index 282c918f926..d3e209add12 100644 --- a/packages/compiler-ssr/src/errors.ts +++ b/packages/compiler-ssr/src/errors.ts @@ -19,11 +19,13 @@ export function createSSRCompilerError( export const enum SSRErrorCodes { X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM = DOMErrorCodes.__EXTEND_POINT__, 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 } = { [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_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` } diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts index 2d68800f1d8..cca7318e79e 100644 --- a/packages/compiler-ssr/src/ssrCodegenTransform.ts +++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts @@ -20,6 +20,7 @@ import { ssrProcessFor } from './transforms/ssrVFor' import { ssrProcessSlotOutlet } from './transforms/ssrTransformSlotOutlet' import { ssrProcessComponent } from './transforms/ssrTransformComponent' import { ssrProcessElement } from './transforms/ssrTransformElement' +import { createSSRCompilerError, SSRErrorCodes } from './errors' // Because SSR codegen output is completely different from client-side output // (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++) { const child = children[i] - if (child.type === NodeTypes.ELEMENT) { - if (child.tagType === ElementTypes.ELEMENT) { - ssrProcessElement(child, context) - } else if (child.tagType === ElementTypes.COMPONENT) { - ssrProcessComponent(child, context) - } else if (child.tagType === ElementTypes.SLOT) { - ssrProcessSlotOutlet(child, context) - } - } else if (child.type === NodeTypes.TEXT) { - context.pushStringPart(escapeHtml(child.content)) - } else if (child.type === NodeTypes.INTERPOLATION) { - context.pushStringPart( - createCallExpression(context.helper(SSR_INTERPOLATE), [child.content]) - ) - } else if (child.type === NodeTypes.IF) { - ssrProcessIf(child, context) - } else if (child.type === NodeTypes.FOR) { - ssrProcessFor(child, context) + switch (child.type) { + case NodeTypes.ELEMENT: + switch (child.tagType) { + case ElementTypes.ELEMENT: + ssrProcessElement(child, context) + break + case ElementTypes.COMPONENT: + ssrProcessComponent(child, context) + break + case ElementTypes.SLOT: + ssrProcessSlotOutlet(child, context) + break + case ElementTypes.TEMPLATE: + // TODO + 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 + } + 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(``) + 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) { diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index 57f4ab1d32c..8720929b0e1 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -27,7 +27,8 @@ import { isVoidTag, escapeHtml, NO, - generateCodeFrame + generateCodeFrame, + escapeHtmlComment } from '@vue/shared' import { compile } from '@vue/compiler-ssr' import { ssrRenderAttrs } from './helpers/ssrRenderAttrs' @@ -230,9 +231,6 @@ function ssrCompile( return (compileCache[template] = Function('require', code)(require)) } -// https://www.w3.org/TR/html52/syntax.html#comments -const commentStripRE = /^-?>||--!>|` - : `` + children ? `` : `` ) break case Static: diff --git a/packages/shared/src/escapeHtml.ts b/packages/shared/src/escapeHtml.ts index 7546471a9cb..4a6435e6a51 100644 --- a/packages/shared/src/escapeHtml.ts +++ b/packages/shared/src/escapeHtml.ts @@ -43,3 +43,10 @@ export function escapeHtml(string: unknown) { return lastIndex !== index ? html + str.substring(lastIndex, index) : html } + +// https://www.w3.org/TR/html52/syntax.html#comments +const commentStripRE = /^-?>||--!>|