Skip to content

Commit

Permalink
wip(ssr): proper scope analysis for ssr vnode slot fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Feb 7, 2020
1 parent b7a74d0 commit a51e710
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 89 deletions.
19 changes: 19 additions & 0 deletions packages/compiler-core/src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,25 @@ export const locStub: SourceLocation = {
end: { line: 1, column: 1, offset: 0 }
}

export function createRoot(
children: TemplateChildNode[],
loc = locStub
): RootNode {
return {
type: NodeTypes.ROOT,
children,
helpers: [],
components: [],
directives: [],
hoists: [],
imports: [],
cached: 0,
temps: 0,
codegenNode: undefined,
loc
}
}

export function createArrayExpression(
elements: ArrayExpression['elements'],
loc: SourceLocation = locStub
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export { baseParse, TextModes } from './parse'
export {
transform,
TransformContext,
createTransformContext,
traverseNode,
createStructuralDirectiveTransform,
NodeTransform,
StructuralDirectiveTransform,
Expand Down
21 changes: 6 additions & 15 deletions packages/compiler-core/src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {
SourceLocation,
TextNode,
TemplateChildNode,
InterpolationNode
InterpolationNode,
createRoot
} from './ast'
import { extend } from '@vue/shared'

Expand Down Expand Up @@ -72,20 +73,10 @@ export function baseParse(
): RootNode {
const context = createParserContext(content, options)
const start = getCursor(context)

return {
type: NodeTypes.ROOT,
children: parseChildren(context, TextModes.DATA, []),
helpers: [],
components: [],
directives: [],
hoists: [],
imports: [],
cached: 0,
temps: 0,
codegenNode: undefined,
loc: getSelection(context, start)
}
return createRoot(
parseChildren(context, TextModes.DATA, []),
getSelection(context, start)
)
}

function createParserContext(
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-core/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export interface TransformContext extends Required<TransformOptions> {
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
}

function createTransformContext(
export function createTransformContext(
root: RootNode,
{
prefixIdentifiers = false,
Expand Down
12 changes: 8 additions & 4 deletions packages/compiler-core/src/transforms/transformExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,23 @@ export const transformExpression: NodeTransform = (node, context) => {
const dir = node.props[i]
// do not process for v-on & v-for since they are special handled
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
const exp = dir.exp as SimpleExpressionNode | undefined
const arg = dir.arg as SimpleExpressionNode | undefined
const exp = dir.exp
const arg = dir.arg
// do not process exp if this is v-on:arg - we need special handling
// for wrapping inline statements.
if (exp && !(dir.name === 'on' && arg)) {
if (
exp &&
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
!(dir.name === 'on' && arg)
) {
dir.exp = processExpression(
exp,
context,
// slot args must be processed as function params
dir.name === 'slot'
)
}
if (arg && !arg.isStatic) {
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
dir.arg = processExpression(arg, context)
}
}
Expand Down
13 changes: 1 addition & 12 deletions packages/compiler-dom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
baseParse,
CompilerOptions,
CodegenResult,
isBuiltInType,
ParserOptions,
RootNode,
noopDirectiveTransform,
Expand All @@ -18,21 +17,12 @@ import { transformVText } from './transforms/vText'
import { transformModel } from './transforms/vModel'
import { transformOn } from './transforms/vOn'
import { transformShow } from './transforms/vShow'
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
import { warnTransitionChildren } from './transforms/warnTransitionChildren'

export const parserOptions = __BROWSER__
? parserOptionsMinimal
: parserOptionsStandard

export const isBuiltInDOMComponent = (tag: string): symbol | undefined => {
if (isBuiltInType(tag, `Transition`)) {
return TRANSITION
} else if (isBuiltInType(tag, `TransitionGroup`)) {
return TRANSITION_GROUP
}
}

export function getDOMTransformPreset(
prefixIdentifiers?: boolean
): TransformPreset {
Expand Down Expand Up @@ -71,8 +61,7 @@ export function compile(
directiveTransforms: {
...directiveTransforms,
...(options.directiveTransforms || {})
},
isBuiltInComponent: isBuiltInDOMComponent
}
})
}

Expand Down
12 changes: 11 additions & 1 deletion packages/compiler-dom/src/parserOptionsMinimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {
ParserOptions,
ElementNode,
Namespaces,
NodeTypes
NodeTypes,
isBuiltInType
} from '@vue/compiler-core'
import { makeMap, isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'

const isRawTextContainer = /*#__PURE__*/ makeMap(
'style,iframe,script,noscript',
Expand All @@ -23,6 +25,14 @@ export const parserOptionsMinimal: ParserOptions = {
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
isPreTag: tag => tag === 'pre',

isBuiltInComponent: (tag: string): symbol | undefined => {
if (isBuiltInType(tag, `Transition`)) {
return TRANSITION
} else if (isBuiltInType(tag, `TransitionGroup`)) {
return TRANSITION_GROUP
}
},

// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {
let ns = parent ? parent.ns : DOMNamespaces.HTML
Expand Down
94 changes: 87 additions & 7 deletions packages/compiler-ssr/__tests__/ssrComponent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('ssr: components', () => {
describe('slots', () => {
test('implicit default slot', () => {
expect(compile(`<foo>hello<div/></foo>`).code).toMatchInlineSnapshot(`
"const { resolveComponent } = require(\\"vue\\")
"const { resolveComponent, createVNode, createTextVNode } = require(\\"vue\\")
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
Expand All @@ -75,7 +75,7 @@ describe('ssr: components', () => {
test('explicit default slot', () => {
expect(compile(`<foo v-slot="{ msg }">{{ msg + outer }}</foo>`).code)
.toMatchInlineSnapshot(`
"const { resolveComponent } = require(\\"vue\\")
"const { resolveComponent, createTextVNode } = require(\\"vue\\")
const { _ssrRenderComponent, _ssrInterpolate } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
Expand All @@ -87,7 +87,7 @@ describe('ssr: components', () => {
_push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
} else {
return [
createTextVNode(toDisplayString(_ctx.msg + _ctx.outer))
createTextVNode(toDisplayString(msg + _ctx.outer))
]
}
},
Expand All @@ -104,7 +104,7 @@ describe('ssr: components', () => {
<template v-slot:named>bar</template>
</foo>`).code
).toMatchInlineSnapshot(`
"const { resolveComponent } = require(\\"vue\\")
"const { resolveComponent, createTextVNode } = require(\\"vue\\")
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
Expand Down Expand Up @@ -141,7 +141,7 @@ describe('ssr: components', () => {
<template v-slot:named v-if="ok">foo</template>
</foo>`).code
).toMatchInlineSnapshot(`
"const { resolveComponent, createSlots } = require(\\"vue\\")
"const { resolveComponent, createTextVNode, createSlots } = require(\\"vue\\")
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
Expand Down Expand Up @@ -173,7 +173,7 @@ describe('ssr: components', () => {
<template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template>
</foo>`).code
).toMatchInlineSnapshot(`
"const { resolveComponent, renderList, createSlots } = require(\\"vue\\")
"const { resolveComponent, createTextVNode, renderList, createSlots } = require(\\"vue\\")
const { _ssrRenderComponent, _ssrInterpolate } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
Expand All @@ -184,7 +184,13 @@ describe('ssr: components', () => {
return {
name: key,
fn: ({ msg }, _push, _parent, _scopeId) => {
_push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
if (_push) {
_push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
} else {
return [
createTextVNode(toDisplayString(msg + _ctx.key + _ctx.bar))
]
}
}
}
})
Expand All @@ -193,6 +199,80 @@ describe('ssr: components', () => {
`)
})

test('nested transform scoping in vnode branch', () => {
expect(
compile(`<foo>
<template v-slot:foo="{ list }">
<div v-if="ok">
<span v-for="i in list"></span>
</div>
</template>
<template v-slot:bar="{ ok }">
<div v-if="ok">
<span v-for="i in list"></span>
</div>
</template>
</foo>`).code
).toMatchInlineSnapshot(`
"const { resolveComponent, renderList, openBlock, createBlock, Fragment, createVNode, createCommentVNode } = require(\\"vue\\")
const { _ssrRenderComponent, _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
const _component_foo = resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, null, {
foo: ({ list }, _push, _parent, _scopeId) => {
if (_push) {
if (_ctx.ok) {
_push(\`<div\${_scopeId}><!---->\`)
_ssrRenderList(list, (i) => {
_push(\`<span\${_scopeId}></span>\`)
})
_push(\`<!----></div>\`)
} else {
_push(\`<!---->\`)
}
} else {
return [
(openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, [
(openBlock(false), createBlock(Fragment, null, renderList(list, (i) => {
return (openBlock(), createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
])
: createCommentVNode(\\"v-if\\", true))
]
}
},
bar: ({ ok }, _push, _parent, _scopeId) => {
if (_push) {
if (ok) {
_push(\`<div\${_scopeId}><!---->\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<span\${_scopeId}></span>\`)
})
_push(\`<!----></div>\`)
} else {
_push(\`<!---->\`)
}
} else {
return [
(openBlock(), ok
? createBlock(\\"div\\", { key: 0 }, [
(openBlock(false), createBlock(Fragment, null, renderList(_ctx.list, (i) => {
return (openBlock(), createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
])
: createCommentVNode(\\"v-if\\", true))
]
}
},
_compiled: true
}, _parent))
}"
`)
})

test('built-in fallthroughs', () => {
// no fragment
expect(compile(`<transition><div/></transition>`).code)
Expand Down
8 changes: 4 additions & 4 deletions packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('ssr: scopeId', () => {
scopeId
}).code
).toMatchInlineSnapshot(`
"const { resolveComponent } = require(\\"vue\\")
"const { resolveComponent, createTextVNode } = require(\\"vue\\")
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
Expand Down Expand Up @@ -51,7 +51,7 @@ describe('ssr: scopeId', () => {
scopeId
}).code
).toMatchInlineSnapshot(`
"const { resolveComponent } = require(\\"vue\\")
"const { resolveComponent, createVNode } = require(\\"vue\\")
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
Expand Down Expand Up @@ -79,12 +79,12 @@ describe('ssr: scopeId', () => {
scopeId
}).code
).toMatchInlineSnapshot(`
"const { resolveComponent } = require(\\"vue\\")
"const { resolveComponent, createVNode } = require(\\"vue\\")
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
const _component_bar = resolveComponent(\\"bar\\")
const _component_foo = resolveComponent(\\"foo\\")
const _component_bar = resolveComponent(\\"bar\\")
_push(_ssrRenderComponent(_component_foo, null, {
default: (_, _push, _parent, _scopeId) => {
Expand Down
15 changes: 10 additions & 5 deletions packages/compiler-ssr/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {
trackSlotScopes,
noopDirectiveTransform,
transformBind,
transformStyle,
isBuiltInDOMComponent
transformStyle
} from '@vue/compiler-dom'
import { ssrCodegenTransform } from './ssrCodegenTransform'
import { ssrTransformElement } from './transforms/ssrTransformElement'
import { ssrTransformComponent } from './transforms/ssrTransformComponent'
import {
ssrTransformComponent,
rawOptionsMap
} from './transforms/ssrTransformComponent'
import { ssrTransformSlotOutlet } from './transforms/ssrTransformSlotOutlet'
import { ssrTransformIf } from './transforms/ssrVIf'
import { ssrTransformFor } from './transforms/ssrVFor'
Expand All @@ -41,6 +43,10 @@ export function compile(

const ast = baseParse(template, options)

// Save raw options for AST. This is needed when performing sub-transforms
// on slot vnode branches.
rawOptionsMap.set(ast, options)

transform(ast, {
...options,
nodeTransforms: [
Expand All @@ -66,8 +72,7 @@ export function compile(
cloak: noopDirectiveTransform,
once: noopDirectiveTransform,
...(options.directiveTransforms || {}) // user transforms
},
isBuiltInComponent: isBuiltInDOMComponent
}
})

// traverse the template AST and convert into SSR codegen AST
Expand Down
Loading

0 comments on commit a51e710

Please sign in to comment.