wip(compiler-ssr): text and interpolation
This commit is contained in:
parent
d1eed36452
commit
63e4486645
@ -1,22 +1,15 @@
|
|||||||
import { NodeTransform } from '../transform'
|
import { NodeTransform } from '../transform'
|
||||||
import {
|
import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
TemplateChildNode,
|
|
||||||
TextNode,
|
|
||||||
InterpolationNode,
|
|
||||||
CompoundExpressionNode,
|
CompoundExpressionNode,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
ElementTypes
|
ElementTypes
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
|
import { isText } from '../utils'
|
||||||
import { CREATE_TEXT } from '../runtimeHelpers'
|
import { CREATE_TEXT } from '../runtimeHelpers'
|
||||||
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
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
|
// 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.
|
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
|
||||||
export const transformText: NodeTransform = (node, context) => {
|
export const transformText: NodeTransform = (node, context) => {
|
||||||
|
@ -22,7 +22,9 @@ import {
|
|||||||
SlotOutletCodegenNode,
|
SlotOutletCodegenNode,
|
||||||
ComponentCodegenNode,
|
ComponentCodegenNode,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
IfBranchNode
|
IfBranchNode,
|
||||||
|
TextNode,
|
||||||
|
InterpolationNode
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { parse } from 'acorn'
|
import { parse } from 'acorn'
|
||||||
import { walk } from 'estree-walker'
|
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 {
|
export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
|
||||||
return p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
return p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
||||||
}
|
}
|
||||||
@ -257,10 +265,11 @@ export function injectProp(
|
|||||||
// check existing key to avoid overriding user provided keys
|
// check existing key to avoid overriding user provided keys
|
||||||
if (prop.key.type === NodeTypes.SIMPLE_EXPRESSION) {
|
if (prop.key.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
const propKeyName = prop.key.content
|
const propKeyName = prop.key.content
|
||||||
alreadyExists = props.properties.some(p => (
|
alreadyExists = props.properties.some(
|
||||||
|
p =>
|
||||||
p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
|
p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
p.key.content === propKeyName
|
p.key.content === propKeyName
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
if (!alreadyExists) {
|
if (!alreadyExists) {
|
||||||
props.properties.unshift(prop)
|
props.properties.unshift(prop)
|
||||||
|
@ -1,33 +1,62 @@
|
|||||||
import { compile } from '../src'
|
import { compile } from '../src'
|
||||||
|
|
||||||
function getElementString(src: string): string {
|
function getString(src: string): string {
|
||||||
return compile(src).code.match(/_push\((.*)\)/)![1]
|
return compile(src).code.match(/_push\((.*)\)/)![1]
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ssr compile integration test', () => {
|
describe('element', () => {
|
||||||
test('basic elements', () => {
|
test('basic elements', () => {
|
||||||
expect(getElementString(`<div></div>`)).toMatchInlineSnapshot(
|
expect(getString(`<div></div>`)).toMatchInlineSnapshot(`"\`<div></div>\`"`)
|
||||||
`"\`<div></div>\`"`
|
expect(getString(`<div/>`)).toMatchInlineSnapshot(`"\`<div></div>\`"`)
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('static attrs', () => {
|
test('static attrs', () => {
|
||||||
expect(
|
expect(getString(`<div id="foo" class="bar"></div>`)).toMatchInlineSnapshot(
|
||||||
getElementString(`<div id="foo" class="bar"></div>`)
|
`"\`<div id=\\"foo\\" class=\\"bar\\"></div>\`"`
|
||||||
).toMatchInlineSnapshot(`"\`<div id=\\"foo\\" class=\\"bar\\"></div>\`"`)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('nested elements', () => {
|
test('nested elements', () => {
|
||||||
expect(
|
expect(
|
||||||
getElementString(`<div><span></span><span></span></div>`)
|
getString(`<div><span></span><span></span></div>`)
|
||||||
).toMatchInlineSnapshot(`"\`<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', () => {
|
test('nested elements with static text', () => {
|
||||||
expect(
|
expect(
|
||||||
getElementString(`<div><span>hello</span>><span>bye</span></div>`)
|
getString(`<div><span>hello</span><span>bye</span></div>`)
|
||||||
).toMatchInlineSnapshot(
|
).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,
|
template: string,
|
||||||
options: SSRCompilerOptions = {}
|
options: SSRCompilerOptions = {}
|
||||||
): CodegenResult {
|
): CodegenResult {
|
||||||
const ast = baseParse(template, {
|
// apply DOM-specific parsing options
|
||||||
|
options = {
|
||||||
...parserOptions,
|
...parserOptions,
|
||||||
...options
|
...options
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const ast = baseParse(template, options)
|
||||||
|
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
...options,
|
...options,
|
||||||
@ -52,7 +55,7 @@ export function compile(
|
|||||||
|
|
||||||
// traverse the template AST and convert into SSR codegen AST
|
// traverse the template AST and convert into SSR codegen AST
|
||||||
// by replacing ast.codegenNode.
|
// by replacing ast.codegenNode.
|
||||||
ssrCodegenTransform(ast)
|
ssrCodegenTransform(ast, options)
|
||||||
|
|
||||||
return generate(ast, {
|
return generate(ast, {
|
||||||
mode: 'cjs',
|
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,
|
NodeTypes,
|
||||||
TemplateChildNode,
|
TemplateChildNode,
|
||||||
ElementTypes,
|
ElementTypes,
|
||||||
createBlockStatement
|
createBlockStatement,
|
||||||
|
CompilerOptions,
|
||||||
|
isText
|
||||||
} from '@vue/compiler-dom'
|
} 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
|
// 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
|
||||||
@ -18,10 +21,11 @@ import { isString, escapeHtml } from '@vue/shared'
|
|||||||
// transform pass to convert the template AST into a fresh JS AST before
|
// transform pass to convert the template AST into a fresh JS AST before
|
||||||
// passing it to codegen.
|
// passing it to codegen.
|
||||||
|
|
||||||
export function ssrCodegenTransform(ast: RootNode) {
|
export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
|
||||||
const context = createSSRTransformContext()
|
const context = createSSRTransformContext(options)
|
||||||
|
|
||||||
const isFragment = ast.children.length > 1
|
const isFragment =
|
||||||
|
ast.children.length > 1 && !ast.children.every(c => isText(c))
|
||||||
if (isFragment) {
|
if (isFragment) {
|
||||||
context.pushStringPart(`<!---->`)
|
context.pushStringPart(`<!---->`)
|
||||||
}
|
}
|
||||||
@ -35,12 +39,13 @@ export function ssrCodegenTransform(ast: RootNode) {
|
|||||||
|
|
||||||
type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
|
type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
|
||||||
|
|
||||||
function createSSRTransformContext() {
|
function createSSRTransformContext(options: CompilerOptions) {
|
||||||
const body: BlockStatement['body'] = []
|
const body: BlockStatement['body'] = []
|
||||||
let currentCall: CallExpression | null = null
|
let currentCall: CallExpression | null = null
|
||||||
let currentString: TemplateLiteral | null = null
|
let currentString: TemplateLiteral | null = null
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
options,
|
||||||
body,
|
body,
|
||||||
pushStringPart(part: TemplateLiteral['elements'][0]) {
|
pushStringPart(part: TemplateLiteral['elements'][0]) {
|
||||||
if (!currentCall) {
|
if (!currentCall) {
|
||||||
@ -66,6 +71,7 @@ function processChildren(
|
|||||||
children: TemplateChildNode[],
|
children: TemplateChildNode[],
|
||||||
context: SSRTransformContext
|
context: SSRTransformContext
|
||||||
) {
|
) {
|
||||||
|
const isVoidTag = context.options.isVoidTag || NO
|
||||||
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) {
|
if (child.type === NodeTypes.ELEMENT) {
|
||||||
@ -77,8 +83,11 @@ function processChildren(
|
|||||||
if (child.children.length) {
|
if (child.children.length) {
|
||||||
processChildren(child.children, context)
|
processChildren(child.children, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isVoidTag(child.tag)) {
|
||||||
// push closing tag
|
// push closing tag
|
||||||
context.pushStringPart(`</${child.tag}>`)
|
context.pushStringPart(`</${child.tag}>`)
|
||||||
|
}
|
||||||
} else if (child.tagType === ElementTypes.COMPONENT) {
|
} else if (child.tagType === ElementTypes.COMPONENT) {
|
||||||
// TODO
|
// TODO
|
||||||
} else if (child.tagType === ElementTypes.SLOT) {
|
} else if (child.tagType === ElementTypes.SLOT) {
|
||||||
@ -86,6 +95,8 @@ function processChildren(
|
|||||||
}
|
}
|
||||||
} else if (child.type === NodeTypes.TEXT) {
|
} else if (child.type === NodeTypes.TEXT) {
|
||||||
context.pushStringPart(escapeHtml(child.content))
|
context.pushStringPart(escapeHtml(child.content))
|
||||||
|
} else if (child.type === NodeTypes.INTERPOLATION) {
|
||||||
|
context.pushStringPart(createCallExpression(INTERPOLATE, [child.content]))
|
||||||
} else if (child.type === NodeTypes.IF) {
|
} else if (child.type === NodeTypes.IF) {
|
||||||
// TODO
|
// TODO
|
||||||
} else if (child.type === NodeTypes.FOR) {
|
} else if (child.type === NodeTypes.FOR) {
|
||||||
|
Loading…
Reference in New Issue
Block a user