From ee5ed73361769019565237cf90f3534dd54ba5bc Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 5 Feb 2020 23:07:23 -0500 Subject: [PATCH] wip(ssr): basic components --- .../__snapshots__/codegen.spec.ts.snap | 4 +- .../__snapshots__/scopeId.spec.ts.snap | 6 +- .../__snapshots__/hoistStatic.spec.ts.snap | 8 +- .../__snapshots__/vFor.spec.ts.snap | 2 +- .../__snapshots__/vOnce.spec.ts.snap | 2 +- .../__snapshots__/vSlot.spec.ts.snap | 20 +-- packages/compiler-core/src/codegen.ts | 12 +- packages/compiler-core/src/index.ts | 4 +- packages/compiler-core/src/parse.ts | 3 +- .../src/transforms/transformElement.ts | 157 +++++++++--------- .../compiler-core/src/transforms/vModel.ts | 5 +- packages/compiler-core/src/utils.ts | 18 +- .../__snapshots__/vModel.spec.ts.snap | 4 +- .../__tests__/ssrComponent.spec.ts | 48 ++++++ .../compiler-ssr/src/ssrCodegenTransform.ts | 3 +- .../src/transforms/ssrTransformComponent.ts | 65 +++++++- .../src/transforms/ssrTransformElement.ts | 8 +- .../runtime-core/src/componentRenderUtils.ts | 7 + packages/runtime-core/src/index.ts | 6 +- .../server-renderer/src/renderToString.ts | 4 + 20 files changed, 254 insertions(+), 132 deletions(-) create mode 100644 packages/compiler-ssr/__tests__/ssrComponent.spec.ts diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap index 1593cd63..ceb8d41b 100644 --- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap @@ -79,8 +79,8 @@ return function render() { const _component_Foo = _resolveComponent(\\"Foo\\") const _component_bar_baz = _resolveComponent(\\"bar-baz\\") const _component_barbaz = _resolveComponent(\\"barbaz\\") - const _directive_my_dir = _resolveDirective(\\"my_dir\\") - +const _directive_my_dir = _resolveDirective(\\"my_dir\\") + return null } }" diff --git a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap index e08bf685..75fec293 100644 --- a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap @@ -25,7 +25,7 @@ const withId = withScopeId(\\"test\\") export const render = withId(function render() { const _ctx = this const _component_Child = resolveComponent(\\"Child\\") - + return (openBlock(), createBlock(_component_Child, null, { default: withId(() => [ createVNode(\\"div\\") @@ -42,7 +42,7 @@ const withId = withScopeId(\\"test\\") export const render = withId(function render() { const _ctx = this const _component_Child = resolveComponent(\\"Child\\") - + return (openBlock(), createBlock(_component_Child, null, createSlots({ _compiled: true }, [ (_ctx.ok) ? { @@ -71,7 +71,7 @@ const withId = withScopeId(\\"test\\") export const render = withId(function render() { const _ctx = this const _component_Child = resolveComponent(\\"Child\\") - + return (openBlock(), createBlock(_component_Child, null, { foo: withId(({ msg }) => [ createTextVNode(toDisplayString(msg), 1 /* TEXT */) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap index 9e4a7c79..8febc444 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap @@ -103,7 +103,7 @@ return function render() { const { createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue const _directive_foo = _resolveDirective(\\"foo\\") - + return (_openBlock(), _createBlock(\\"div\\", null, [ _withDirectives(_createVNode(\\"div\\", _hoisted_1, null, 32 /* NEED_PATCH */), [ [_directive_foo] @@ -141,7 +141,7 @@ return function render() { const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue const _component_Comp = _resolveComponent(\\"Comp\\") - + return (_openBlock(), _createBlock(\\"div\\", null, [ _createVNode(\\"div\\", _hoisted_1, [ _createVNode(_component_Comp) @@ -249,7 +249,7 @@ return function render() { const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue const _component_Comp = _resolveComponent(\\"Comp\\") - + return (_openBlock(), _createBlock(_component_Comp, null, { default: ({ foo }) => [_toDisplayString(_ctx.foo)], _compiled: true @@ -284,7 +284,7 @@ return function render() { const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue const _component_Comp = _resolveComponent(\\"Comp\\") - + return (_openBlock(), _createBlock(\\"div\\", null, [ _createVNode(_component_Comp) ])) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap index 8baed87f..b8ecee14 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -140,7 +140,7 @@ return function render() { const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective } = _Vue const _directive_foo = _resolveDirective(\\"foo\\") - + return (_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (i) => { return (_openBlock(), _withDirectives(_createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */), [ [_directive_foo] diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap index 4c631279..27c226d1 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap @@ -27,7 +27,7 @@ return function render() { const _cache = $cache const _component_Comp = _resolveComponent(\\"Comp\\") - + return (_openBlock(), _createBlock(\\"div\\", null, [ _cache[1] || ( _setBlockTracking(-1), diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index ef980e09..7afd3165 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -6,7 +6,7 @@ exports[`compiler: transform component slots dynamically named slots 1`] = ` return function render() { const _ctx = this const _component_Comp = resolveComponent(\\"Comp\\") - + return (openBlock(), createBlock(_component_Comp, null, { [_ctx.one]: ({ foo }) => [toDisplayString(foo), toDisplayString(_ctx.bar)], [_ctx.two]: ({ bar }) => [toDisplayString(_ctx.foo), toDisplayString(bar)], @@ -21,7 +21,7 @@ exports[`compiler: transform component slots implicit default slot 1`] = ` return function render() { const _ctx = this const _component_Comp = resolveComponent(\\"Comp\\") - + return (openBlock(), createBlock(_component_Comp, null, { default: () => [ createVNode(\\"div\\") @@ -37,7 +37,7 @@ exports[`compiler: transform component slots named slot with v-for w/ prefixIden return function render() { const _ctx = this const _component_Comp = resolveComponent(\\"Comp\\") - + return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [ renderList(_ctx.list, (name) => { return { @@ -55,7 +55,7 @@ exports[`compiler: transform component slots named slot with v-if + prefixIdenti return function render() { const _ctx = this const _component_Comp = resolveComponent(\\"Comp\\") - + return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [ (_ctx.ok) ? { @@ -75,7 +75,7 @@ return function render() { const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue const _component_Comp = _resolveComponent(\\"Comp\\") - + return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _compiled: true }, [ ok ? { @@ -104,7 +104,7 @@ return function render() { const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue const _component_Comp = _resolveComponent(\\"Comp\\") - + return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _compiled: true }, [ ok ? { @@ -123,7 +123,7 @@ exports[`compiler: transform component slots named slots 1`] = ` return function render() { const _ctx = this const _component_Comp = resolveComponent(\\"Comp\\") - + return (openBlock(), createBlock(_component_Comp, null, { one: ({ foo }) => [toDisplayString(foo), toDisplayString(_ctx.bar)], two: ({ bar }) => [toDisplayString(_ctx.foo), toDisplayString(bar)], @@ -140,7 +140,7 @@ return function render() { const { createVNode: _createVNode, resolveComponent: _resolveComponent, createBlock: _createBlock, openBlock: _openBlock } = _Vue const _component_Comp = _resolveComponent(\\"Comp\\") - + return (_openBlock(), _createBlock(_component_Comp, null, { one: () => [\\"foo\\"], default: () => [ @@ -160,7 +160,7 @@ return function render() { const _ctx = this const _component_Inner = resolveComponent(\\"Inner\\") const _component_Comp = resolveComponent(\\"Comp\\") - + return (openBlock(), createBlock(_component_Comp, null, { default: ({ foo }) => [ createVNode(_component_Inner, null, { @@ -183,7 +183,7 @@ exports[`compiler: transform component slots on-component default slot 1`] = ` return function render() { const _ctx = this const _component_Comp = resolveComponent(\\"Comp\\") - + return (openBlock(), createBlock(_component_Comp, null, { default: ({ foo }) => [toDisplayString(foo), toDisplayString(_ctx.bar)], _compiled: true diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index d58c83bb..4f9290be 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -353,17 +353,21 @@ function genModulePreamble( function genAssets( assets: string[], type: 'component' | 'directive', - context: CodegenContext + { helper, push, newline }: CodegenContext ) { - const resolver = context.helper( + const resolver = helper( type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE ) for (let i = 0; i < assets.length; i++) { const id = assets[i] - context.push( + push( `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})` ) - context.newline() + if (i < assets.length - 1) { + newline() + } else { + push(`\n`) + } } } diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 65f3884f..8e0152f5 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -34,7 +34,7 @@ export { transformOn } from './transforms/vOn' export { transformBind } from './transforms/vBind' // exported for compiler-ssr -export { MERGE_PROPS } from './runtimeHelpers' +export * from './runtimeHelpers' export { processIf } from './transforms/vIf' export { processFor, createForLoopParams } from './transforms/vFor' export { @@ -42,7 +42,7 @@ export { processExpression } from './transforms/transformExpression' export { trackVForSlotScopes, trackSlotScopes } from './transforms/vSlot' -export { buildProps } from './transforms/transformElement' +export { resolveComponentType, buildProps } from './transforms/transformElement' export { processSlotOutlet } from './transforms/transformSlotOutlet' // utility, but need to rewrite typing to avoid dts relying on @vue/shared diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 421f32a5..3a4b8dad 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -465,7 +465,8 @@ function parseTag( } else if ( isCoreComponent(tag) || (options.isBuiltInComponent && options.isBuiltInComponent(tag)) || - /^[A-Z]/.test(tag) + /^[A-Z]/.test(tag) || + tag === 'component' ) { tagType = ElementTypes.COMPONENT } diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 23ece5ed..1473f428 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -14,7 +14,8 @@ import { createSimpleExpression, createObjectExpression, Property, - createSequenceExpression + createSequenceExpression, + ComponentNode } from '../ast' import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared' import { createCompilerError, ErrorCodes } from '../errors' @@ -35,7 +36,8 @@ import { getInnerRange, toValidAssetId, findProp, - isCoreComponent + isCoreComponent, + isBindKey } from '../utils' import { buildSlots } from './vSlot' import { isStaticNode } from './hoistStatic' @@ -58,69 +60,30 @@ export const transformElement: NodeTransform = (node, context) => { // perform the work on exit, after all child expressions have been // processed and merged. return function postTransformElement() { - const { tag, tagType, props } = node - const builtInComponentSymbol = - isCoreComponent(tag) || context.isBuiltInComponent(tag) - const isComponent = tagType === ElementTypes.COMPONENT + const { tag, props } = node + const isComponent = node.tagType === ElementTypes.COMPONENT + + // and must be forced into blocks so that block + // updates inside get proper isSVG flag at runtime. (#639, #643) + // This is technically web-specific, but splitting the logic out of core + // leads to too much unnecessary complexity. + const shouldUseBlock = + !isComponent && (tag === 'svg' || tag === 'foreignObject') + + const nodeType = isComponent + ? resolveComponentType(node as ComponentNode, context) + : `"${tag}"` + + const args: CallExpression['arguments'] = [nodeType] let hasProps = props.length > 0 let patchFlag: number = 0 let runtimeDirectives: DirectiveNode[] | undefined let dynamicPropNames: string[] | undefined - let dynamicComponent: string | CallExpression | undefined - let shouldUseBlock = false - // handle dynamic component - const isProp = tag === 'component' && findProp(node, 'is') - if (isProp) { - // static - if (isProp.type === NodeTypes.ATTRIBUTE) { - const tag = isProp.value && isProp.value.content - if (tag) { - context.helper(RESOLVE_COMPONENT) - context.components.add(tag) - dynamicComponent = toValidAssetId(tag, `component`) - } - } - // dynamic - else if (isProp.exp) { - dynamicComponent = createCallExpression( - context.helper(RESOLVE_DYNAMIC_COMPONENT), - // _ctx.$ exposes the owner instance of current render function - [isProp.exp, context.prefixIdentifiers ? `_ctx.$` : `$`] - ) - } - } - - let nodeType - if (dynamicComponent) { - nodeType = dynamicComponent - } else if (builtInComponentSymbol) { - nodeType = context.helper(builtInComponentSymbol) - } else if (isComponent) { - // user component w/ resolve - context.helper(RESOLVE_COMPONENT) - context.components.add(tag) - nodeType = toValidAssetId(tag, `component`) - } else { - // plain element - nodeType = `"${tag}"` - // and must be forced into blocks so that block - // updates inside get proper isSVG flag at runtime. (#639, #643) - // This is technically web-specific, but splitting the logic out of core - // leads to too much unnecessary complexity. - shouldUseBlock = tag === 'svg' || tag === 'foreignObject' - } - - const args: CallExpression['arguments'] = [nodeType] // props if (hasProps) { - const propsBuildResult = buildProps( - node, - context, - // skip reserved "is" prop - isProp ? node.props.filter(p => p !== isProp) : node.props - ) + const propsBuildResult = buildProps(node, context) patchFlag = propsBuildResult.patchFlag dynamicPropNames = propsBuildResult.dynamicPropNames runtimeDirectives = propsBuildResult.directives @@ -130,6 +93,7 @@ export const transformElement: NodeTransform = (node, context) => { args.push(propsBuildResult.props) } } + // children const hasChildren = node.children.length > 0 if (hasChildren) { @@ -140,11 +104,7 @@ export const transformElement: NodeTransform = (node, context) => { // Portal is not a real component has dedicated handling in the renderer // KeepAlive should not track its own deps so that it can be used inside // Transition - if ( - isComponent && - builtInComponentSymbol !== PORTAL && - builtInComponentSymbol !== KEEP_ALIVE - ) { + if (isComponent && nodeType !== PORTAL && nodeType !== KEEP_ALIVE) { const { slots, hasDynamicSlots } = buildSlots(node, context) args.push(slots) if (hasDynamicSlots) { @@ -171,6 +131,7 @@ export const transformElement: NodeTransform = (node, context) => { args.push(node.children) } } + // patchFlag & dynamicPropNames if (patchFlag !== 0) { if (!hasChildren) { @@ -219,13 +180,45 @@ export const transformElement: NodeTransform = (node, context) => { } } -function stringifyDynamicPropNames(props: string[]): string { - let propsNamesString = `[` - for (let i = 0, l = props.length; i < l; i++) { - propsNamesString += JSON.stringify(props[i]) - if (i < l - 1) propsNamesString += ', ' +export function resolveComponentType( + node: ComponentNode, + context: TransformContext +) { + const { tag } = node + + // 1. dynamic component + const isProp = node.tag === 'component' && findProp(node, 'is') + if (isProp) { + // static + if (isProp.type === NodeTypes.ATTRIBUTE) { + const isType = isProp.value && isProp.value.content + if (isType) { + context.helper(RESOLVE_COMPONENT) + context.components.add(isType) + return toValidAssetId(isType, `component`) + } + } + // dynamic + else if (isProp.exp) { + return createCallExpression( + context.helper(RESOLVE_DYNAMIC_COMPONENT), + // _ctx.$ exposes the owner instance of current render function + [isProp.exp, context.prefixIdentifiers ? `_ctx.$` : `$`] + ) + } } - return propsNamesString + `]` + + // 2. built-in components (Portal, Transition, KeepAlive, Suspense...) + const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag) + if (builtIn) { + context.helper(builtIn) + return builtIn + } + + // 3. user component (resolve) + context.helper(RESOLVE_COMPONENT) + context.components.add(tag) + return toValidAssetId(tag, `component`) } export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode @@ -241,7 +234,7 @@ export function buildProps( patchFlag: number dynamicPropNames: string[] } { - const elementLoc = node.loc + const { tag, loc: elementLoc } = node const isComponent = node.tagType === ElementTypes.COMPONENT let properties: ObjectExpression['properties'] = [] const mergeArgs: PropsExpression[] = [] @@ -288,6 +281,10 @@ export function buildProps( if (name === 'ref') { hasRef = true } + // skip :is on + if (name === 'is' && tag === 'component') { + continue + } properties.push( createObjectProperty( createSimpleExpression( @@ -305,6 +302,8 @@ export function buildProps( } else { // directives const { name, arg, exp, loc } = prop + const isBind = name === 'bind' + const isOn = name === 'on' // skip v-slot - it is handled by its dedicated transform. if (name === 'slot') { @@ -315,17 +314,16 @@ export function buildProps( } continue } - // skip v-once - it is handled by its dedicated transform. if (name === 'once') { continue } - - const isBind = name === 'bind' - const isOn = name === 'on' - + // skip :is on + if (isBind && tag === 'component' && isBindKey(arg, 'is')) { + continue + } // skip v-on in SSR compilation - if (ssr && isOn) { + if (isOn && ssr) { continue } @@ -518,3 +516,12 @@ function buildDirectiveArgs( } return createArrayExpression(dirArgs, dir.loc) } + +function stringifyDynamicPropNames(props: string[]): string { + let propsNamesString = `[` + for (let i = 0, l = props.length; i < l; i++) { + propsNamesString += JSON.stringify(props[i]) + if (i < l - 1) propsNamesString += ', ' + } + return propsNamesString + `]` +} diff --git a/packages/compiler-core/src/transforms/vModel.ts b/packages/compiler-core/src/transforms/vModel.ts index 7211334c..26d4ec85 100644 --- a/packages/compiler-core/src/transforms/vModel.ts +++ b/packages/compiler-core/src/transforms/vModel.ts @@ -44,10 +44,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => { const eventName = arg ? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic ? `onUpdate:${arg.content}` - : createCompoundExpression([ - '"onUpdate:" + ', - ...(arg.type === NodeTypes.SIMPLE_EXPRESSION ? [arg] : arg.children) - ]) + : createCompoundExpression(['"onUpdate:" + ', arg]) : `onUpdate:modelValue` const props = [ diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index d160e96e..3a99ff8e 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -192,19 +192,21 @@ export function findProp( if (p.name === name && p.value) { return p } - } else if ( - p.name === 'bind' && - p.arg && - p.arg.type === NodeTypes.SIMPLE_EXPRESSION && - p.arg.isStatic && - p.arg.content === name && - p.exp - ) { + } else if (p.name === 'bind' && p.exp && isBindKey(p.arg, name)) { return p } } } +export function isBindKey(arg: DirectiveNode['arg'], name: string): boolean { + return !!( + arg && + arg.type === NodeTypes.SIMPLE_EXPRESSION && + arg.isStatic && + arg.content === name + ) +} + export function hasDynamicKeyVBind(node: ElementNode): boolean { return node.props.some( p => diff --git a/packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap index 88bba3b0..d63829a4 100644 --- a/packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap +++ b/packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap @@ -25,7 +25,7 @@ return function render() { const { vModelDynamic: _vModelDynamic, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue const _directive_bind = _resolveDirective(\\"bind\\") - + return (_openBlock(), _withDirectives(_createBlock(\\"input\\", { modelValue: model, \\"onUpdate:modelValue\\": $event => (model = $event) @@ -146,7 +146,7 @@ return function render() { const { vModelDynamic: _vModelDynamic, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue const _directive_bind = _resolveDirective(\\"bind\\") - + return (_openBlock(), _withDirectives(_createBlock(\\"input\\", { modelValue: model, \\"onUpdate:modelValue\\": $event => (model = $event) diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts new file mode 100644 index 00000000..b47c4f94 --- /dev/null +++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts @@ -0,0 +1,48 @@ +import { compile } from '../src' + +describe('ssr: components', () => { + test('basic', () => { + expect(compile(``).code).toMatchInlineSnapshot(` + "const { resolveComponent } = require(\\"vue\\") + const { _renderComponent } = require(\\"@vue/server-renderer\\") + + return function ssrRender(_ctx, _push, _parent) { + const _component_foo = resolveComponent(\\"foo\\") + + _renderComponent(_component_foo, { + id: \\"a\\", + prop: _ctx.b + }, null, _parent) + }" + `) + }) + + test('dynamic component', () => { + expect(compile(``).code) + .toMatchInlineSnapshot(` + "const { resolveComponent } = require(\\"vue\\") + const { _renderComponent } = require(\\"@vue/server-renderer\\") + + return function ssrRender(_ctx, _push, _parent) { + const _component_foo = resolveComponent(\\"foo\\") + + _renderComponent(_component_foo, { prop: \\"b\\" }, null, _parent) + }" + `) + + expect(compile(``).code) + .toMatchInlineSnapshot(` + "const { resolveComponent } = require(\\"vue\\") + const { _renderComponent } = require(\\"@vue/server-renderer\\") + + return function ssrRender(_ctx, _push, _parent) { + const _component_compoonent = resolveComponent(\\"compoonent\\") + + _renderComponent(_component_compoonent, { + is: _ctx.foo, + prop: \\"b\\" + }, null, _parent) + }" + `) + }) +}) diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts index c0b960e6..ed5cc103 100644 --- a/packages/compiler-ssr/src/ssrCodegenTransform.ts +++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts @@ -18,6 +18,7 @@ import { SSR_INTERPOLATE, ssrHelpers } from './runtimeHelpers' import { ssrProcessIf } from './transforms/ssrVIf' import { ssrProcessFor } from './transforms/ssrVFor' import { ssrProcessSlotOutlet } from './transforms/ssrTransformSlotOutlet' +import { ssrProcessComponent } from './transforms/ssrTransformComponent' // Because SSR codegen output is completely different from client-side output // (e.g. multiple elements can be concatenated into a single template literal @@ -118,7 +119,7 @@ export function processChildren( context.pushStringPart(``) } } else if (child.tagType === ElementTypes.COMPONENT) { - // TODO + ssrProcessComponent(child, context) } else if (child.tagType === ElementTypes.SLOT) { ssrProcessSlotOutlet(child, context) } diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts index 602474d9..034f1efd 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts @@ -1,15 +1,64 @@ -import { NodeTransform, NodeTypes, ElementTypes } from '@vue/compiler-dom' +import { + NodeTransform, + NodeTypes, + ElementTypes, + createCallExpression, + resolveComponentType, + buildProps, + ComponentNode, + PORTAL, + SUSPENSE +} from '@vue/compiler-dom' +import { SSR_RENDER_COMPONENT } from '../runtimeHelpers' +import { SSRTransformContext } from '../ssrCodegenTransform' +import { isSymbol } from '@vue/shared' export const ssrTransformComponent: NodeTransform = (node, context) => { if ( - node.type === NodeTypes.ELEMENT && - node.tagType === ElementTypes.COMPONENT + 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 + return + } + + return function ssrPostTransformComponent() { + const component = resolveComponentType(node, context) + + if (isSymbol(component)) { + // built-in compoonent + if (component === PORTAL) { + // TODO + } else if (component === SUSPENSE) { + // TODO fallthrough + // TODO option to use fallback content and resolve on client + } else { + // TODO fallthrough for KeepAlive & Transition + } } + + // note we are not passing ssr: true here because for components, v-on + // handlers should still be passed + const { props } = buildProps(node, context) + + // TODO slots + // TODO option for slots bail out + // TODO scopeId + + node.ssrCodegenNode = createCallExpression( + context.helper(SSR_RENDER_COMPONENT), + [ + component, + props || `null`, + `null`, // TODO slots + `_parent` + ] + ) } } + +export function ssrProcessComponent( + node: ComponentNode, + context: SSRTransformContext +) { + context.pushStatement(node.ssrCodegenNode!) +} diff --git a/packages/compiler-ssr/src/transforms/ssrTransformElement.ts b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts index 5345c10a..df409ef2 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformElement.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts @@ -21,7 +21,8 @@ import { createAssignmentExpression, TextNode, hasDynamicKeyVBind, - MERGE_PROPS + MERGE_PROPS, + isBindKey } from '@vue/compiler-dom' import { escapeHtml, isBooleanAttr, isSSRSafeAttrName } from '@vue/shared' import { createSSRCompilerError, SSRErrorCodes } from '../errors' @@ -261,10 +262,7 @@ function isTextareaWithValue( return !!( node.tag === 'textarea' && prop.name === 'bind' && - prop.arg && - prop.arg.type === NodeTypes.SIMPLE_EXPRESSION && - prop.arg.isStatic && - prop.arg.content === 'value' + isBindKey(prop.arg, 'value') ) } diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index 01629fd2..eb26c1d9 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -19,6 +19,13 @@ import { warn } from './warning' // resolveComponent, resolveDirective) during render export let currentRenderingInstance: ComponentInternalInstance | null = null +// exposed for server-renderer only +export function setCurrentRenderingInstance( + instance: ComponentInternalInstance | null +) { + currentRenderingInstance = instance +} + // dev only flag to track whether $attrs was used during render. // If $attrs was used during render then the warning for failed attrs // fallthrough can be suppressed. diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index b56ff56c..01ba0344 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -101,7 +101,10 @@ export { registerRuntimeCompiler } from './component' // SSR ------------------------------------------------------------------------- import { createComponentInstance, setupComponent } from './component' -import { renderComponentRoot } from './componentRenderUtils' +import { + renderComponentRoot, + setCurrentRenderingInstance +} from './componentRenderUtils' import { isVNode, normalizeVNode } from './vnode' // SSR utils are only exposed in cjs builds. @@ -109,6 +112,7 @@ const _ssrUtils = { createComponentInstance, setupComponent, renderComponentRoot, + setCurrentRenderingInstance, isVNode, normalizeVNode } diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index d7b6626b..be62560e 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -27,6 +27,7 @@ import { SSRSlots } from './helpers/renderSlot' const { isVNode, createComponentInstance, + setCurrentRenderingInstance, setupComponent, renderComponentRoot, normalizeVNode @@ -135,7 +136,10 @@ function renderComponentSubTree( } else { if (comp.ssrRender) { // optimized + // set current rendering instance for asset resoolution + setCurrentRenderingInstance(instance) comp.ssrRender(instance.proxy, push, instance) + setCurrentRenderingInstance(null) } else if (comp.render) { renderVNode(push, renderComponentRoot(instance), instance) } else {