wip(compiler-ssr): text and interpolation
This commit is contained in:
parent
d1eed36452
commit
63e4486645
@ -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. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
|
||||
export const transformText: NodeTransform = (node, context) => {
|
||||
|
@ -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 => (
|
||||
alreadyExists = props.properties.some(
|
||||
p =>
|
||||
p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||
p.key.content === propKeyName
|
||||
))
|
||||
)
|
||||
}
|
||||
if (!alreadyExists) {
|
||||
props.properties.unshift(prop)
|
||||
|
@ -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(`<div></div>`)).toMatchInlineSnapshot(
|
||||
`"\`<div></div>\`"`
|
||||
)
|
||||
expect(getString(`<div></div>`)).toMatchInlineSnapshot(`"\`<div></div>\`"`)
|
||||
expect(getString(`<div/>`)).toMatchInlineSnapshot(`"\`<div></div>\`"`)
|
||||
})
|
||||
|
||||
test('static attrs', () => {
|
||||
expect(
|
||||
getElementString(`<div id="foo" class="bar"></div>`)
|
||||
).toMatchInlineSnapshot(`"\`<div id=\\"foo\\" class=\\"bar\\"></div>\`"`)
|
||||
expect(getString(`<div id="foo" class="bar"></div>`)).toMatchInlineSnapshot(
|
||||
`"\`<div id=\\"foo\\" class=\\"bar\\"></div>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('nested elements', () => {
|
||||
expect(
|
||||
getElementString(`<div><span></span><span></span></div>`)
|
||||
getString(`<div><span></span><span></span></div>`)
|
||||
).toMatchInlineSnapshot(`"\`<div><span></span><span></span></div>\`"`)
|
||||
})
|
||||
|
||||
test('void element', () => {
|
||||
expect(getString(`<input>`)).toMatchInlineSnapshot(`"\`<input>\`"`)
|
||||
})
|
||||
})
|
||||
|
||||
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(`<div><span>hello</span>><span>bye</span></div>`)
|
||||
getString(`<div><span>hello</span><span>bye</span></div>`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<div><span>hello</span>><span>bye</span></div>\`"`
|
||||
`"\`<div><span>hello</span><span>bye</span></div>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('interpolation', () => {
|
||||
expect(getString(`foo {{ bar }} baz`)).toMatchInlineSnapshot(
|
||||
`"\`foo \${interpolate(_ctx.bar)} baz\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('nested elements with interpolation', () => {
|
||||
expect(
|
||||
getString(
|
||||
`<div><span>{{ foo }} bar</span><span>baz {{ qux }}</span></div>`
|
||||
)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<div><span>\${interpolate(_ctx.foo)} bar</span><span>baz \${interpolate(_ctx.qux)}</span></div>\`"`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -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',
|
||||
|
@ -1 +1,7 @@
|
||||
//
|
||||
import { registerRuntimeHelpers } from '@vue/compiler-core'
|
||||
|
||||
export const INTERPOLATE = Symbol(`interpolate`)
|
||||
|
||||
registerRuntimeHelpers({
|
||||
[INTERPOLATE]: `interpolate`
|
||||
})
|
||||
|
@ -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<typeof createSSRTransformContext>
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if (!isVoidTag(child.tag)) {
|
||||
// push closing tag
|
||||
context.pushStringPart(`</${child.tag}>`)
|
||||
}
|
||||
} 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) {
|
||||
|
Loading…
Reference in New Issue
Block a user