-
-
Notifications
You must be signed in to change notification settings - Fork 8.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
223 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
test('ssr: escape HTML', () => {}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// import { renderToString, renderComponent } from '../src' | ||
|
||
describe('ssr: renderToString', () => { | ||
test('basic', () => {}) | ||
|
||
test('nested components', () => {}) | ||
|
||
test('nested components with optimized slots', () => {}) | ||
|
||
test('mixing optimized / vnode components', () => {}) | ||
|
||
test('nested components with vnode slots', () => {}) | ||
|
||
test('async components', () => {}) | ||
|
||
test('parallel async components', () => {}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
describe('ssr: render raw vnodes', () => { | ||
test('class', () => {}) | ||
|
||
test('styles', () => { | ||
// only render numbers for properties that allow no unit numbers | ||
}) | ||
|
||
describe('attrs', () => { | ||
test('basic', () => {}) | ||
|
||
test('boolean attrs', () => {}) | ||
|
||
test('enumerated attrs', () => {}) | ||
|
||
test('skip falsy values', () => {}) | ||
}) | ||
|
||
describe('domProps', () => { | ||
test('innerHTML', () => {}) | ||
|
||
test('textContent', () => {}) | ||
|
||
test('textarea', () => {}) | ||
|
||
test('other renderable domProps', () => { | ||
// also test camel to kebab case conversion for some props | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,104 +1,16 @@ | ||
import { | ||
App, | ||
Component, | ||
ComponentInternalInstance, | ||
createComponentInstance, | ||
setupComponent, | ||
VNode, | ||
createVNode | ||
} from 'vue' | ||
import { isString, isPromise, isArray } from '@vue/shared' | ||
import { toDisplayString } from 'vue' | ||
|
||
export * from './helpers' | ||
export { renderToString, renderComponent } from './renderToString' | ||
|
||
type SSRBuffer = SSRBufferItem[] | ||
type SSRBufferItem = string | ResolvedSSRBuffer | Promise<SSRBuffer> | ||
type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[] | ||
export { | ||
renderVNode, | ||
renderClass, | ||
renderStyle, | ||
renderProps | ||
} from './renderVnode' | ||
|
||
function createBuffer() { | ||
let appendable = false | ||
let hasAsync = false | ||
const buffer: SSRBuffer = [] | ||
return { | ||
buffer, | ||
hasAsync() { | ||
return hasAsync | ||
}, | ||
push(item: SSRBufferItem) { | ||
const isStringItem = isString(item) | ||
if (appendable && isStringItem) { | ||
buffer[buffer.length - 1] += item as string | ||
} else { | ||
buffer.push(item) | ||
} | ||
appendable = isStringItem | ||
if (!isStringItem && !isArray(item)) { | ||
// promise | ||
hasAsync = true | ||
} | ||
} | ||
} | ||
} | ||
|
||
function unrollBuffer(buffer: ResolvedSSRBuffer): string { | ||
let ret = '' | ||
for (let i = 0; i < buffer.length; i++) { | ||
const item = buffer[i] | ||
if (isString(item)) { | ||
ret += item | ||
} else { | ||
ret += unrollBuffer(item) | ||
} | ||
} | ||
return ret | ||
} | ||
|
||
export async function renderToString(app: App): Promise<string> { | ||
const resolvedBuffer = (await renderComponent( | ||
app._component, | ||
app._props | ||
)) as ResolvedSSRBuffer | ||
return unrollBuffer(resolvedBuffer) | ||
} | ||
|
||
export function renderComponent( | ||
comp: Component, | ||
props: Record<string, any> | null = null, | ||
children: VNode['children'] = null, | ||
parentComponent: ComponentInternalInstance | null = null | ||
): ResolvedSSRBuffer | Promise<SSRBuffer> { | ||
const vnode = createVNode(comp, props, children) | ||
const instance = createComponentInstance(vnode, parentComponent) | ||
const res = setupComponent(instance, null) | ||
if (isPromise(res)) { | ||
return res.then(() => innerRenderComponent(comp, instance)) | ||
} else { | ||
return innerRenderComponent(comp, instance) | ||
} | ||
} | ||
export { escape } from './escape' | ||
|
||
function innerRenderComponent( | ||
comp: Component, | ||
instance: ComponentInternalInstance | ||
): ResolvedSSRBuffer | Promise<SSRBuffer> { | ||
const { buffer, push, hasAsync } = createBuffer() | ||
if (typeof comp === 'function') { | ||
// TODO FunctionalComponent | ||
} else { | ||
if (comp.ssrRender) { | ||
// optimized | ||
comp.ssrRender(push, instance.proxy) | ||
} else if (comp.render) { | ||
// TODO fallback to vdom serialization | ||
} else { | ||
// TODO warn component missing render function | ||
} | ||
} | ||
// If the current component's buffer contains any Promise from async children, | ||
// then it must return a Promise too. Otherwise this is a component that | ||
// contains only sync children so we can avoid the async book-keeping overhead. | ||
return hasAsync() | ||
? // TS can't figure out the typing due to recursive appearance of Promise | ||
Promise.all(buffer as any) | ||
: (buffer as ResolvedSSRBuffer) | ||
export function interpolate(value: unknown) { | ||
return escape(toDisplayString(value)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { | ||
App, | ||
Component, | ||
ComponentInternalInstance, | ||
VNode, | ||
createComponentInstance, | ||
setupComponent, | ||
createVNode, | ||
renderComponentRoot | ||
} from 'vue' | ||
import { isString, isPromise, isArray, isFunction } from '@vue/shared' | ||
import { renderVNode } from './renderVnode' | ||
|
||
export type SSRBuffer = SSRBufferItem[] | ||
export type SSRBufferItem = string | ResolvedSSRBuffer | Promise<SSRBuffer> | ||
export type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[] | ||
|
||
function createBuffer() { | ||
let appendable = false | ||
let hasAsync = false | ||
const buffer: SSRBuffer = [] | ||
return { | ||
buffer, | ||
hasAsync() { | ||
return hasAsync | ||
}, | ||
push(item: SSRBufferItem) { | ||
const isStringItem = isString(item) | ||
if (appendable && isStringItem) { | ||
buffer[buffer.length - 1] += item as string | ||
} else { | ||
buffer.push(item) | ||
} | ||
appendable = isStringItem | ||
if (!isStringItem && !isArray(item)) { | ||
// promise | ||
hasAsync = true | ||
} | ||
} | ||
} | ||
} | ||
|
||
function unrollBuffer(buffer: ResolvedSSRBuffer): string { | ||
let ret = '' | ||
for (let i = 0; i < buffer.length; i++) { | ||
const item = buffer[i] | ||
if (isString(item)) { | ||
ret += item | ||
} else { | ||
ret += unrollBuffer(item) | ||
} | ||
} | ||
return ret | ||
} | ||
|
||
export async function renderToString(app: App): Promise<string> { | ||
const resolvedBuffer = (await renderComponent( | ||
app._component, | ||
app._props | ||
)) as ResolvedSSRBuffer | ||
return unrollBuffer(resolvedBuffer) | ||
} | ||
|
||
export function renderComponent( | ||
comp: Component, | ||
props: Record<string, any> | null = null, | ||
children: VNode['children'] = null, | ||
parentComponent: ComponentInternalInstance | null = null | ||
): ResolvedSSRBuffer | Promise<SSRBuffer> { | ||
const vnode = createVNode(comp, props, children) | ||
const instance = createComponentInstance(vnode, parentComponent) | ||
const res = setupComponent(instance, null) | ||
if (isPromise(res)) { | ||
return res.then(() => innerRenderComponent(comp, instance)) | ||
} else { | ||
return innerRenderComponent(comp, instance) | ||
} | ||
} | ||
|
||
function innerRenderComponent( | ||
comp: Component, | ||
instance: ComponentInternalInstance | ||
): ResolvedSSRBuffer | Promise<SSRBuffer> { | ||
const { buffer, push, hasAsync } = createBuffer() | ||
if (isFunction(comp)) { | ||
renderVNode(push, renderComponentRoot(instance)) | ||
} else { | ||
if (comp.ssrRender) { | ||
// optimized | ||
comp.ssrRender(push, instance.proxy) | ||
} else if (comp.render) { | ||
renderVNode(push, renderComponentRoot(instance)) | ||
} else { | ||
// TODO on the fly template compilation support | ||
throw new Error( | ||
`Component ${ | ||
comp.name ? `${comp.name} ` : `` | ||
} is missing render function.` | ||
) | ||
} | ||
} | ||
// If the current component's buffer contains any Promise from async children, | ||
// then it must return a Promise too. Otherwise this is a component that | ||
// contains only sync children so we can avoid the async book-keeping overhead. | ||
return hasAsync() | ||
? // TS can't figure out the typing due to recursive appearance of Promise | ||
Promise.all(buffer as any) | ||
: (buffer as ResolvedSSRBuffer) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { VNode } from 'vue' | ||
import { SSRBufferItem } from './renderToString' | ||
|
||
export function renderVNode( | ||
push: (item: SSRBufferItem) => void, | ||
vnode: VNode | ||
) {} | ||
|
||
export function renderProps() {} | ||
|
||
export function renderClass() {} | ||
|
||
export function renderStyle() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.