From: Evan You Date: Mon, 3 Feb 2020 03:28:54 +0000 (-0500) Subject: wip(compiler-ssr): text and interpolation X-Git-Tag: v3.0.0-alpha.5~134 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=63e4486645fa6a7c727ec44a117ee9b807cf347b;p=thirdparty%2Fvuejs%2Fcore.git wip(compiler-ssr): text and interpolation --- diff --git a/packages/compiler-core/src/transforms/transformText.ts b/packages/compiler-core/src/transforms/transformText.ts index 4a4d3fdc8c..a15a2fd180 100644 --- a/packages/compiler-core/src/transforms/transformText.ts +++ b/packages/compiler-core/src/transforms/transformText.ts @@ -1,22 +1,15 @@ import { NodeTransform } from '../transform' import { NodeTypes, - TemplateChildNode, - TextNode, - InterpolationNode, CompoundExpressionNode, createCallExpression, CallExpression, ElementTypes } from '../ast' +import { isText } from '../utils' import { CREATE_TEXT } from '../runtimeHelpers' import { PatchFlags, PatchFlagNames } from '@vue/shared' -const isText = ( - node: TemplateChildNode -): node is TextNode | InterpolationNode => - node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT - // Merge adjacent text nodes and expressions into a single expression // e.g.
abc {{ d }} {{ e }}
should have a single expression node as child. export const transformText: NodeTransform = (node, context) => { diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index 280e9c5e2d..78e952e43e 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -22,7 +22,9 @@ import { SlotOutletCodegenNode, ComponentCodegenNode, ExpressionNode, - IfBranchNode + IfBranchNode, + TextNode, + InterpolationNode } from './ast' import { parse } from 'acorn' import { walk } from 'estree-walker' @@ -213,6 +215,12 @@ export function createBlockExpression( ]) } +export function isText( + node: TemplateChildNode +): node is TextNode | InterpolationNode { + return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT +} + export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode { return p.type === NodeTypes.DIRECTIVE && p.name === 'slot' } @@ -257,10 +265,11 @@ export function injectProp( // check existing key to avoid overriding user provided keys if (prop.key.type === NodeTypes.SIMPLE_EXPRESSION) { const propKeyName = prop.key.content - alreadyExists = props.properties.some(p => ( - p.key.type === NodeTypes.SIMPLE_EXPRESSION && - p.key.content === propKeyName - )) + alreadyExists = props.properties.some( + p => + p.key.type === NodeTypes.SIMPLE_EXPRESSION && + p.key.content === propKeyName + ) } if (!alreadyExists) { props.properties.unshift(prop) diff --git a/packages/compiler-ssr/__tests__/ssrCompile.spec.ts b/packages/compiler-ssr/__tests__/ssrCompile.spec.ts index 14dd767ec3..ab9013a091 100644 --- a/packages/compiler-ssr/__tests__/ssrCompile.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrCompile.spec.ts @@ -1,33 +1,62 @@ import { compile } from '../src' -function getElementString(src: string): string { +function getString(src: string): string { return compile(src).code.match(/_push\((.*)\)/)![1] } -describe('ssr compile integration test', () => { +describe('element', () => { test('basic elements', () => { - expect(getElementString(`
`)).toMatchInlineSnapshot( - `"\`
\`"` - ) + expect(getString(`
`)).toMatchInlineSnapshot(`"\`
\`"`) + expect(getString(`
`)).toMatchInlineSnapshot(`"\`
\`"`) }) test('static attrs', () => { - expect( - getElementString(`
`) - ).toMatchInlineSnapshot(`"\`
\`"`) + expect(getString(`
`)).toMatchInlineSnapshot( + `"\`
\`"` + ) }) test('nested elements', () => { expect( - getElementString(`
`) + getString(`
`) ).toMatchInlineSnapshot(`"\`
\`"`) }) + test('void element', () => { + expect(getString(``)).toMatchInlineSnapshot(`"\`\`"`) + }) +}) + +describe('text', () => { + test('static text', () => { + expect(getString(`foo`)).toMatchInlineSnapshot(`"\`foo\`"`) + }) + + test('static text escape', () => { + expect(getString(`<foo>`)).toMatchInlineSnapshot(`"\`<foo>\`"`) + }) + test('nested elements with static text', () => { expect( - getElementString(`
hello>bye
`) + getString(`
hellobye
`) + ).toMatchInlineSnapshot( + `"\`
hellobye
\`"` + ) + }) + + test('interpolation', () => { + expect(getString(`foo {{ bar }} baz`)).toMatchInlineSnapshot( + `"\`foo \${interpolate(_ctx.bar)} baz\`"` + ) + }) + + test('nested elements with interpolation', () => { + expect( + getString( + `
{{ foo }} barbaz {{ qux }}
` + ) ).toMatchInlineSnapshot( - `"\`
hello>bye
\`"` + `"\`
\${interpolate(_ctx.foo)} barbaz \${interpolate(_ctx.qux)}
\`"` ) }) }) diff --git a/packages/compiler-ssr/src/index.ts b/packages/compiler-ssr/src/index.ts index 32307c0896..cbc954ac65 100644 --- a/packages/compiler-ssr/src/index.ts +++ b/packages/compiler-ssr/src/index.ts @@ -22,10 +22,13 @@ export function compile( template: string, options: SSRCompilerOptions = {} ): CodegenResult { - const ast = baseParse(template, { + // apply DOM-specific parsing options + options = { ...parserOptions, ...options - }) + } + + const ast = baseParse(template, options) transform(ast, { ...options, @@ -52,7 +55,7 @@ export function compile( // traverse the template AST and convert into SSR codegen AST // by replacing ast.codegenNode. - ssrCodegenTransform(ast) + ssrCodegenTransform(ast, options) return generate(ast, { mode: 'cjs', diff --git a/packages/compiler-ssr/src/runtimeHelpers.ts b/packages/compiler-ssr/src/runtimeHelpers.ts index 8337712ea5..3af2ec552c 100644 --- a/packages/compiler-ssr/src/runtimeHelpers.ts +++ b/packages/compiler-ssr/src/runtimeHelpers.ts @@ -1 +1,7 @@ -// +import { registerRuntimeHelpers } from '@vue/compiler-core' + +export const INTERPOLATE = Symbol(`interpolate`) + +registerRuntimeHelpers({ + [INTERPOLATE]: `interpolate` +}) diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts index 30cf872f52..9eec2c6aeb 100644 --- a/packages/compiler-ssr/src/ssrCodegenTransform.ts +++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts @@ -8,9 +8,12 @@ import { NodeTypes, TemplateChildNode, ElementTypes, - createBlockStatement + createBlockStatement, + CompilerOptions, + isText } from '@vue/compiler-dom' -import { isString, escapeHtml } from '@vue/shared' +import { isString, escapeHtml, NO } from '@vue/shared' +import { INTERPOLATE } from './runtimeHelpers' // Because SSR codegen output is completely different from client-side output // (e.g. multiple elements can be concatenated into a single template literal @@ -18,10 +21,11 @@ import { isString, escapeHtml } from '@vue/shared' // transform pass to convert the template AST into a fresh JS AST before // passing it to codegen. -export function ssrCodegenTransform(ast: RootNode) { - const context = createSSRTransformContext() +export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) { + const context = createSSRTransformContext(options) - const isFragment = ast.children.length > 1 + const isFragment = + ast.children.length > 1 && !ast.children.every(c => isText(c)) if (isFragment) { context.pushStringPart(``) } @@ -35,12 +39,13 @@ export function ssrCodegenTransform(ast: RootNode) { type SSRTransformContext = ReturnType -function createSSRTransformContext() { +function createSSRTransformContext(options: CompilerOptions) { const body: BlockStatement['body'] = [] let currentCall: CallExpression | null = null let currentString: TemplateLiteral | null = null return { + options, body, pushStringPart(part: TemplateLiteral['elements'][0]) { if (!currentCall) { @@ -66,6 +71,7 @@ function processChildren( children: TemplateChildNode[], context: SSRTransformContext ) { + const isVoidTag = context.options.isVoidTag || NO for (let i = 0; i < children.length; i++) { const child = children[i] if (child.type === NodeTypes.ELEMENT) { @@ -77,8 +83,11 @@ function processChildren( if (child.children.length) { processChildren(child.children, context) } - // push closing tag - context.pushStringPart(``) + + if (!isVoidTag(child.tag)) { + // push closing tag + context.pushStringPart(``) + } } else if (child.tagType === ElementTypes.COMPONENT) { // TODO } else if (child.tagType === ElementTypes.SLOT) { @@ -86,6 +95,8 @@ function processChildren( } } else if (child.type === NodeTypes.TEXT) { context.pushStringPart(escapeHtml(child.content)) + } else if (child.type === NodeTypes.INTERPOLATION) { + context.pushStringPart(createCallExpression(INTERPOLATE, [child.content])) } else if (child.type === NodeTypes.IF) { // TODO } else if (child.type === NodeTypes.FOR) {