wip(ssr): initial scaffold for compiler-ssr
This commit is contained in:
94
packages/compiler-ssr/src/codegen.ts
Normal file
94
packages/compiler-ssr/src/codegen.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
CodegenResult,
|
||||
RootNode,
|
||||
generate as baseGenerate,
|
||||
CodegenOptions,
|
||||
NodeTypes,
|
||||
locStub,
|
||||
BlockStatement,
|
||||
ElementTypes,
|
||||
createCallExpression,
|
||||
TemplateLiteral,
|
||||
createTemplateLiteral,
|
||||
CallExpression,
|
||||
TemplateChildNode
|
||||
} from '@vue/compiler-dom'
|
||||
import { isString } from '@vue/shared'
|
||||
|
||||
export function generate(
|
||||
ast: RootNode,
|
||||
options: CodegenOptions
|
||||
): CodegenResult {
|
||||
// construct a SSR-specific codegen tree to pass to core codegen
|
||||
const body: BlockStatement['body'] = []
|
||||
let currentCall: CallExpression | null = null
|
||||
let currentString: TemplateLiteral | null = null
|
||||
|
||||
function ensureCurrentString() {
|
||||
if (!currentCall) {
|
||||
currentCall = createCallExpression(`_push`)
|
||||
body.push(currentCall)
|
||||
}
|
||||
if (!currentString) {
|
||||
currentString = createTemplateLiteral([])
|
||||
currentCall.arguments.push(currentString)
|
||||
}
|
||||
return currentString.elements
|
||||
}
|
||||
|
||||
function pushStringPart(part: TemplateLiteral['elements'][0]) {
|
||||
const bufferedElements = ensureCurrentString()
|
||||
const lastItem = bufferedElements[bufferedElements.length - 1]
|
||||
if (isString(part) && isString(lastItem)) {
|
||||
bufferedElements[bufferedElements.length - 1] += part
|
||||
} else {
|
||||
bufferedElements.push(part)
|
||||
}
|
||||
}
|
||||
|
||||
function processChildren(children: TemplateChildNode[]) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
if (child.type === NodeTypes.ELEMENT) {
|
||||
if (child.tagType === ElementTypes.ELEMENT) {
|
||||
const elementsToAdd = child.ssrCodegenNode!.elements
|
||||
for (let j = 0; j < elementsToAdd.length; j++) {
|
||||
pushStringPart(elementsToAdd[j])
|
||||
}
|
||||
if (child.children.length) {
|
||||
processChildren(child.children)
|
||||
}
|
||||
// push closing tag
|
||||
pushStringPart(`</${child.tag}>`)
|
||||
} else if (child.tagType === ElementTypes.COMPONENT) {
|
||||
// TODO
|
||||
} else if (child.tagType === ElementTypes.SLOT) {
|
||||
// TODO
|
||||
}
|
||||
} else if (child.type === NodeTypes.TEXT) {
|
||||
// TODO
|
||||
} else if (child.type === NodeTypes.IF) {
|
||||
// TODO
|
||||
} else if (child.type === NodeTypes.FOR) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isFragment = ast.children.length > 1
|
||||
if (isFragment) {
|
||||
pushStringPart(`<!---->`)
|
||||
}
|
||||
processChildren(ast.children)
|
||||
if (isFragment) {
|
||||
pushStringPart(`<!---->`)
|
||||
}
|
||||
|
||||
ast.codegenNode = {
|
||||
type: NodeTypes.JS_BLOCK_STATEMENT,
|
||||
loc: locStub,
|
||||
body
|
||||
}
|
||||
|
||||
return baseGenerate(ast, options)
|
||||
}
|
||||
@@ -1,3 +1,63 @@
|
||||
export function hello(): string {
|
||||
return 'TODO'
|
||||
import {
|
||||
CodegenResult,
|
||||
baseParse,
|
||||
parserOptions,
|
||||
transform,
|
||||
generate,
|
||||
CompilerOptions,
|
||||
transformExpression,
|
||||
trackVForSlotScopes,
|
||||
trackSlotScopes
|
||||
} from '@vue/compiler-dom'
|
||||
import { ssrCodegenTransform } from './ssrCodegenTransform'
|
||||
import { ssrTransformIf } from './transforms/ssrVIf'
|
||||
import { ssrTransformFor } from './transforms/ssrVFor'
|
||||
import { ssrTransformElement } from './transforms/ssrTransformElement'
|
||||
import { ssrTransformComponent } from './transforms/ssrTransformComponent'
|
||||
import { ssrTransformSlotOutlet } from './transforms/ssrTransformSlotOutlet'
|
||||
|
||||
export interface SSRCompilerOptions extends CompilerOptions {}
|
||||
|
||||
export function compile(
|
||||
template: string,
|
||||
options: SSRCompilerOptions = {}
|
||||
): CodegenResult {
|
||||
const ast = baseParse(template, {
|
||||
...parserOptions,
|
||||
...options
|
||||
})
|
||||
|
||||
transform(ast, {
|
||||
...options,
|
||||
prefixIdentifiers: true,
|
||||
// disalbe optimizations that are unnecessary for ssr
|
||||
cacheHandlers: false,
|
||||
hoistStatic: false,
|
||||
nodeTransforms: [
|
||||
ssrTransformIf,
|
||||
ssrTransformFor,
|
||||
trackVForSlotScopes,
|
||||
transformExpression,
|
||||
ssrTransformSlotOutlet,
|
||||
ssrTransformElement,
|
||||
ssrTransformComponent,
|
||||
trackSlotScopes,
|
||||
...(options.nodeTransforms || []) // user transforms
|
||||
],
|
||||
directiveTransforms: {
|
||||
// TODO server-side directive transforms
|
||||
...(options.directiveTransforms || {}) // user transforms
|
||||
}
|
||||
})
|
||||
|
||||
// traverse the template AST and convert into SSR codegen AST
|
||||
// by replacing ast.codegenNode.
|
||||
ssrCodegenTransform(ast)
|
||||
|
||||
return generate(ast, {
|
||||
mode: 'cjs',
|
||||
...options,
|
||||
ssr: true,
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
}
|
||||
|
||||
1
packages/compiler-ssr/src/runtimeHelpers.ts
Normal file
1
packages/compiler-ssr/src/runtimeHelpers.ts
Normal file
@@ -0,0 +1 @@
|
||||
//
|
||||
99
packages/compiler-ssr/src/ssrCodegenTransform.ts
Normal file
99
packages/compiler-ssr/src/ssrCodegenTransform.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
RootNode,
|
||||
BlockStatement,
|
||||
CallExpression,
|
||||
TemplateLiteral,
|
||||
createCallExpression,
|
||||
createTemplateLiteral,
|
||||
locStub,
|
||||
NodeTypes,
|
||||
TemplateChildNode,
|
||||
ElementTypes
|
||||
} from '@vue/compiler-dom'
|
||||
import { isString } from '@vue/shared'
|
||||
|
||||
// Because SSR codegen output is completely different from client-side output
|
||||
// (e.g. multiple elements can be concatenated into a single template literal
|
||||
// instead of each getting a corresponding call), we need to apply an extra
|
||||
// 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()
|
||||
|
||||
const isFragment = ast.children.length > 1
|
||||
if (isFragment) {
|
||||
context.pushStringPart(`<!---->`)
|
||||
}
|
||||
processChildren(ast.children, context)
|
||||
if (isFragment) {
|
||||
context.pushStringPart(`<!---->`)
|
||||
}
|
||||
|
||||
ast.codegenNode = {
|
||||
type: NodeTypes.JS_BLOCK_STATEMENT,
|
||||
loc: locStub,
|
||||
body: context.body
|
||||
}
|
||||
}
|
||||
|
||||
type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
|
||||
|
||||
function createSSRTransformContext() {
|
||||
const body: BlockStatement['body'] = []
|
||||
let currentCall: CallExpression | null = null
|
||||
let currentString: TemplateLiteral | null = null
|
||||
|
||||
return {
|
||||
body,
|
||||
pushStringPart(part: TemplateLiteral['elements'][0]) {
|
||||
if (!currentCall) {
|
||||
currentCall = createCallExpression(`_push`)
|
||||
body.push(currentCall)
|
||||
}
|
||||
if (!currentString) {
|
||||
currentString = createTemplateLiteral([])
|
||||
currentCall.arguments.push(currentString)
|
||||
}
|
||||
const bufferedElements = currentString.elements
|
||||
const lastItem = bufferedElements[bufferedElements.length - 1]
|
||||
if (isString(part) && isString(lastItem)) {
|
||||
bufferedElements[bufferedElements.length - 1] += part
|
||||
} else {
|
||||
bufferedElements.push(part)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processChildren(
|
||||
children: TemplateChildNode[],
|
||||
context: SSRTransformContext
|
||||
) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
if (child.type === NodeTypes.ELEMENT) {
|
||||
if (child.tagType === ElementTypes.ELEMENT) {
|
||||
const elementsToAdd = child.ssrCodegenNode!.elements
|
||||
for (let j = 0; j < elementsToAdd.length; j++) {
|
||||
context.pushStringPart(elementsToAdd[j])
|
||||
}
|
||||
if (child.children.length) {
|
||||
processChildren(child.children, context)
|
||||
}
|
||||
// push closing tag
|
||||
context.pushStringPart(`</${child.tag}>`)
|
||||
} else if (child.tagType === ElementTypes.COMPONENT) {
|
||||
// TODO
|
||||
} else if (child.tagType === ElementTypes.SLOT) {
|
||||
// TODO
|
||||
}
|
||||
} else if (child.type === NodeTypes.TEXT) {
|
||||
// TODO
|
||||
} else if (child.type === NodeTypes.IF) {
|
||||
// TODO
|
||||
} else if (child.type === NodeTypes.FOR) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NodeTransform, NodeTypes, ElementTypes } from '@vue/compiler-dom'
|
||||
|
||||
export const ssrTransformComponent: NodeTransform = (node, context) => {
|
||||
if (
|
||||
node.type === NodeTypes.ELEMENT &&
|
||||
node.tagType === ElementTypes.COMPONENT
|
||||
) {
|
||||
return function ssrPostTransformComponent() {
|
||||
// generate a _push(_renderComponent) call
|
||||
// dynamic component as well
|
||||
// !check if we need to bail out for slots
|
||||
// TODO also handle scopeID here
|
||||
}
|
||||
}
|
||||
}
|
||||
70
packages/compiler-ssr/src/transforms/ssrTransformElement.ts
Normal file
70
packages/compiler-ssr/src/transforms/ssrTransformElement.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
NodeTransform,
|
||||
NodeTypes,
|
||||
ElementTypes,
|
||||
TemplateLiteral,
|
||||
createTemplateLiteral
|
||||
} from '@vue/compiler-dom'
|
||||
import { escapeHtml } from '@vue/server-renderer/src'
|
||||
|
||||
/*
|
||||
## Simple Element
|
||||
|
||||
``` html
|
||||
<div></div>
|
||||
```
|
||||
``` js
|
||||
return function render(_ctx, _push, _parent) {
|
||||
_push(`<div></div>`)
|
||||
}
|
||||
```
|
||||
|
||||
## Consecutive Elements
|
||||
|
||||
``` html
|
||||
<div>
|
||||
<span></span>
|
||||
</div>
|
||||
<div></div>
|
||||
```
|
||||
``` js
|
||||
return function render(_ctx, _push, _parent) {
|
||||
_push(`<div><span></span></div><div></div>`)
|
||||
}
|
||||
```
|
||||
*/
|
||||
|
||||
export const ssrTransformElement: NodeTransform = (node, context) => {
|
||||
if (
|
||||
node.type === NodeTypes.ELEMENT &&
|
||||
node.tagType === ElementTypes.ELEMENT
|
||||
) {
|
||||
return function ssrPostTransformElement() {
|
||||
// element
|
||||
// generate the template literal representing the open tag.
|
||||
const openTag: TemplateLiteral['elements'] = [`<${node.tag}`]
|
||||
|
||||
for (let i = 0; i < node.props.length; i++) {
|
||||
const prop = node.props[i]
|
||||
if (prop.type === NodeTypes.DIRECTIVE) {
|
||||
const directiveTransform = context.directiveTransforms[prop.name]
|
||||
if (directiveTransform) {
|
||||
// TODO directive transforms
|
||||
} else {
|
||||
// no corresponding ssr directive transform found.
|
||||
// TODO emit error
|
||||
}
|
||||
} else {
|
||||
// static prop
|
||||
openTag.push(
|
||||
` ${prop.name}` +
|
||||
(prop.value ? `="${escapeHtml(prop.value.content)}"` : ``)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
openTag.push(`>`)
|
||||
node.ssrCodegenNode = createTemplateLiteral(openTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { NodeTransform } from '@vue/compiler-dom'
|
||||
|
||||
export const ssrTransformSlotOutlet: NodeTransform = () => {}
|
||||
1
packages/compiler-ssr/src/transforms/ssrVBind.ts
Normal file
1
packages/compiler-ssr/src/transforms/ssrVBind.ts
Normal file
@@ -0,0 +1 @@
|
||||
// TODO
|
||||
1
packages/compiler-ssr/src/transforms/ssrVCloak.ts
Normal file
1
packages/compiler-ssr/src/transforms/ssrVCloak.ts
Normal file
@@ -0,0 +1 @@
|
||||
// TODO
|
||||
3
packages/compiler-ssr/src/transforms/ssrVFor.ts
Normal file
3
packages/compiler-ssr/src/transforms/ssrVFor.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { NodeTransform } from '@vue/compiler-dom'
|
||||
|
||||
export const ssrTransformFor: NodeTransform = () => {}
|
||||
1
packages/compiler-ssr/src/transforms/ssrVHtml.ts
Normal file
1
packages/compiler-ssr/src/transforms/ssrVHtml.ts
Normal file
@@ -0,0 +1 @@
|
||||
// TODO
|
||||
3
packages/compiler-ssr/src/transforms/ssrVIf.ts
Normal file
3
packages/compiler-ssr/src/transforms/ssrVIf.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { NodeTransform } from '@vue/compiler-dom'
|
||||
|
||||
export const ssrTransformIf: NodeTransform = () => {}
|
||||
1
packages/compiler-ssr/src/transforms/ssrVModel.ts
Normal file
1
packages/compiler-ssr/src/transforms/ssrVModel.ts
Normal file
@@ -0,0 +1 @@
|
||||
// TODO
|
||||
1
packages/compiler-ssr/src/transforms/ssrVOn.ts
Normal file
1
packages/compiler-ssr/src/transforms/ssrVOn.ts
Normal file
@@ -0,0 +1 @@
|
||||
// TODO
|
||||
1
packages/compiler-ssr/src/transforms/ssrVShow.ts
Normal file
1
packages/compiler-ssr/src/transforms/ssrVShow.ts
Normal file
@@ -0,0 +1 @@
|
||||
// TODO
|
||||
1
packages/compiler-ssr/src/transforms/ssrVSlot.ts
Normal file
1
packages/compiler-ssr/src/transforms/ssrVSlot.ts
Normal file
@@ -0,0 +1 @@
|
||||
// TODO
|
||||
1
packages/compiler-ssr/src/transforms/ssrVText.ts
Normal file
1
packages/compiler-ssr/src/transforms/ssrVText.ts
Normal file
@@ -0,0 +1 @@
|
||||
// TODO
|
||||
Reference in New Issue
Block a user