diff --git a/jest.config.js b/jest.config.js index 748bed51..48ded2ca 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,6 +7,7 @@ module.exports = { __BROWSER__: false, __BUNDLER__: true, __RUNTIME_COMPILE__: true, + __SSR__: false, __FEATURE_OPTIONS__: true, __FEATURE_SUSPENSE__: true }, diff --git a/packages/global.d.ts b/packages/global.d.ts index cfe02b96..5af13958 100644 --- a/packages/global.d.ts +++ b/packages/global.d.ts @@ -4,6 +4,7 @@ declare var __TEST__: boolean declare var __BROWSER__: boolean declare var __BUNDLER__: boolean declare var __RUNTIME_COMPILE__: boolean +declare var __SSR__: boolean declare var __COMMIT__: string declare var __VERSION__: string diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index fb0f3d68..77f76787 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -5,8 +5,8 @@ import { ComponentPublicInstance, runtimeCompiledRenderProxyHandlers } from './componentProxy' -import { ComponentPropsOptions } from './componentProps' -import { Slots } from './componentSlots' +import { ComponentPropsOptions, resolveProps } from './componentProps' +import { Slots, resolveSlots } from './componentSlots' import { warn } from './warning' import { ErrorCodes, @@ -34,6 +34,7 @@ import { currentRenderingInstance, markAttrsAccessed } from './componentRenderUtils' +import { ShapeFlags } from '.' export type Data = { [key: string]: unknown } @@ -268,10 +269,26 @@ export function validateComponentName(name: string, config: AppConfig) { } } -export function setupStatefulComponent( +export function setupComponent( instance: ComponentInternalInstance, parentSuspense: SuspenseBoundary | null ) { + const propsOptions = instance.type.props + const { props, children, shapeFlag } = instance.vnode + resolveProps(instance, props, propsOptions) + resolveSlots(instance, children) + + // setup stateful logic + if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { + return setupStatefulComponent(instance, parentSuspense) + } +} + +function setupStatefulComponent( + instance: ComponentInternalInstance, + parentSuspense: SuspenseBoundary | null +) { + let setupResult const Component = instance.type as ComponentOptions if (__DEV__) { @@ -307,7 +324,7 @@ export function setupStatefulComponent( currentInstance = instance currentSuspense = parentSuspense - const setupResult = callWithErrorHandling( + setupResult = callWithErrorHandling( setup, instance, ErrorCodes.SETUP_FUNCTION, @@ -333,6 +350,8 @@ export function setupStatefulComponent( } else { finishComponentSetup(instance, parentSuspense) } + + return setupResult } export function handleSetupResult( @@ -398,7 +417,7 @@ function finishComponentSetup( `does not support runtime template compilation. Either use the ` + `full build or pre-compile the template using Vue CLI.` ) - } else { + } else if (!__SSR__ || !Component.ssrRender) { warn( `Component is missing${ __RUNTIME_COMPILE__ ? ` template or` : `` diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 435ede78..2976c53a 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -97,6 +97,9 @@ export const camelize = _camelize as (s: string) => string // For integration with runtime compiler export { registerRuntimeCompiler } from './component' +// For server-renderer +export { createComponentInstance, setupComponent } from './component' + // Types ----------------------------------------------------------------------- export { diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 2fb27c41..fc208d8c 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -13,9 +13,9 @@ import { import { ComponentInternalInstance, createComponentInstance, - setupStatefulComponent, Component, - Data + Data, + setupComponent } from './component' import { renderComponentRoot, @@ -940,8 +940,6 @@ export function createRenderer< pushWarningContext(initialVNode) } - const Comp = initialVNode.type as Component - // inject renderer internals for keepAlive if (isKeepAlive(initialVNode)) { const sink = instance.sink as KeepAliveSink @@ -950,14 +948,7 @@ export function createRenderer< } // resolve props and slots for setup context - const propsOptions = Comp.props - resolveProps(instance, initialVNode.props, propsOptions) - resolveSlots(instance, initialVNode.children) - - // setup stateful logic - if (initialVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { - setupStatefulComponent(instance, parentSuspense) - } + setupComponent(instance, parentSuspense) // setup() is async. This component relies on async logic to be resolved // before proceeding diff --git a/packages/server-renderer/package.json b/packages/server-renderer/package.json index 0e8743b4..038dae62 100644 --- a/packages/server-renderer/package.json +++ b/packages/server-renderer/package.json @@ -27,6 +27,6 @@ }, "homepage": "https://github.com/vuejs/vue/tree/dev/packages/server-renderer#readme", "peerDependencies": { - "@vue/runtime-dom": "3.0.0-alpha.3" + "vue": "3.0.0-alpha.3" } } diff --git a/packages/server-renderer/src/index.ts b/packages/server-renderer/src/index.ts index 17c5f9be..e78099a6 100644 --- a/packages/server-renderer/src/index.ts +++ b/packages/server-renderer/src/index.ts @@ -2,8 +2,11 @@ import { App, Component, ComponentInternalInstance, - SuspenseBoundary -} from '@vue/runtime-dom' + createComponentInstance, + setupComponent, + VNode, + createVNode +} from 'vue' import { isString } from '@vue/shared' type SSRBuffer = SSRBufferItem[] @@ -30,9 +33,7 @@ function createSSRBuffer() { export async function renderToString(app: App): Promise { const resolvedBuffer = (await renderComponent( app._component, - app._props, - null, - null + app._props )) as ResolvedSSRBuffer return unrollBuffer(resolvedBuffer) } @@ -52,19 +53,17 @@ function unrollBuffer(buffer: ResolvedSSRBuffer): string { export async function renderComponent( comp: Component, - props: Record | null, - parentComponent: ComponentInternalInstance | null, - parentSuspense: SuspenseBoundary | null + props: Record | null = null, + children: VNode['children'] = null, + parentComponent: ComponentInternalInstance | null = null ): Promise { // 1. create component buffer const { buffer, push } = createSSRBuffer() - // 2. TODO create actual instance - const instance = { - proxy: { - msg: 'hello' - } - } + // 2. create actual instance + const vnode = createVNode(comp, props, children) + const instance = createComponentInstance(vnode, parentComponent) + await setupComponent(instance, null) if (typeof comp === 'function') { // TODO FunctionalComponent diff --git a/rollup.config.js b/rollup.config.js index 0604dff9..29ead12f 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -79,6 +79,7 @@ function createConfig(format, output, plugins = []) { process.env.__DEV__ === 'false' || /\.prod\.js$/.test(output.file) const isGlobalBuild = format === 'global' const isRawESMBuild = format === 'esm' + const isNodeBuild = format === 'cjs' const isBundlerESMBuild = /esm-bundler/.test(format) const isRuntimeCompileBuild = /vue\./.test(output.file) @@ -111,14 +112,16 @@ function createConfig(format, output, plugins = []) { const entryFile = format === 'esm-bundler-runtime' ? `src/runtime.ts` : `src/index.ts` + const external = + isGlobalBuild || isRawESMBuild + ? [] + : knownExternals.concat(Object.keys(pkg.dependencies || [])) + return { input: resolve(entryFile), // Global and Browser ESM builds inlines everything so that they can be // used alone. - external: - isGlobalBuild || isRawESMBuild - ? [] - : knownExternals.concat(Object.keys(pkg.dependencies || [])), + external, plugins: [ json({ namedExports: false @@ -127,9 +130,11 @@ function createConfig(format, output, plugins = []) { createReplacePlugin( isProductionBuild, isBundlerESMBuild, + // isBrowserBuild? (isGlobalBuild || isRawESMBuild || isBundlerESMBuild) && !packageOptions.enableNonBrowserBranches, - isRuntimeCompileBuild + isRuntimeCompileBuild, + isNodeBuild ), ...plugins ], @@ -146,7 +151,8 @@ function createReplacePlugin( isProduction, isBundlerESMBuild, isBrowserBuild, - isRuntimeCompileBuild + isRuntimeCompileBuild, + isNodeBuild ) { const replacements = { __COMMIT__: `"${process.env.COMMIT}"`, @@ -164,6 +170,8 @@ function createReplacePlugin( __BUNDLER__: isBundlerESMBuild, // support compile in browser? __RUNTIME_COMPILE__: isRuntimeCompileBuild, + // is targeting Node (SSR)? + __SSR__: isNodeBuild, // support options? // the lean build drops options related code with buildOptions.lean: true __FEATURE_OPTIONS__: !packageOptions.lean && !process.env.LEAN,