123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- /* @flow */
-
- import { escape } from 'web/server/util'
- import { SSR_ATTR } from 'shared/constants'
- import { RenderContext } from './render-context'
- import { generateComponentTrace } from 'core/util/debug'
- import { ssrCompileToFunctions } from 'web/server/compiler'
- import { installSSRHelpers } from './optimizing-compiler/runtime-helpers'
-
- import { isDef, isUndef, isTrue } from 'shared/util'
-
- import {
- createComponent,
- createComponentInstanceForVnode
- } from 'core/vdom/create-component'
-
- let warned = Object.create(null)
- const warnOnce = msg => {
- if (!warned[msg]) {
- warned[msg] = true
- console.warn(`\n\u001b[31m${msg}\u001b[39m\n`)
- }
- }
-
- const onCompilationError = (err, vm) => {
- const trace = vm ? generateComponentTrace(vm) : ''
- throw new Error(`\n\u001b[31m${err}${trace}\u001b[39m\n`)
- }
-
- const normalizeRender = vm => {
- const { render, template, _scopeId } = vm.$options
- if (isUndef(render)) {
- if (template) {
- const compiled = ssrCompileToFunctions(template, {
- scopeId: _scopeId,
- warn: onCompilationError
- }, vm)
-
- vm.$options.render = compiled.render
- vm.$options.staticRenderFns = compiled.staticRenderFns
- } else {
- throw new Error(
- `render function or template not defined in component: ${
- vm.$options.name || vm.$options._componentTag || 'anonymous'
- }`
- )
- }
- }
- }
-
- function renderNode (node, isRoot, context) {
- if (node.isString) {
- renderStringNode(node, context)
- } else if (isDef(node.componentOptions)) {
- renderComponent(node, isRoot, context)
- } else if (isDef(node.tag)) {
- renderElement(node, isRoot, context)
- } else if (isTrue(node.isComment)) {
- if (isDef(node.asyncFactory)) {
- // async component
- renderAsyncComponent(node, isRoot, context)
- } else {
- context.write(`<!--${node.text}-->`, context.next)
- }
- } else {
- context.write(
- node.raw ? node.text : escape(String(node.text)),
- context.next
- )
- }
- }
-
- function registerComponentForCache (options, write) {
- // exposed by vue-loader, need to call this if cache hit because
- // component lifecycle hooks will not be called.
- const register = options._ssrRegister
- if (write.caching && isDef(register)) {
- write.componentBuffer[write.componentBuffer.length - 1].add(register)
- }
- return register
- }
-
- function renderComponent (node, isRoot, context) {
- const { write, next, userContext } = context
-
- // check cache hit
- const Ctor = node.componentOptions.Ctor
- const getKey = Ctor.options.serverCacheKey
- const name = Ctor.options.name
- const cache = context.cache
- const registerComponent = registerComponentForCache(Ctor.options, write)
-
- if (isDef(getKey) && isDef(cache) && isDef(name)) {
- const key = name + '::' + getKey(node.componentOptions.propsData)
- const { has, get } = context
- if (isDef(has)) {
- has(key, hit => {
- if (hit === true && isDef(get)) {
- get(key, res => {
- if (isDef(registerComponent)) {
- registerComponent(userContext)
- }
- res.components.forEach(register => register(userContext))
- write(res.html, next)
- })
- } else {
- renderComponentWithCache(node, isRoot, key, context)
- }
- })
- } else if (isDef(get)) {
- get(key, res => {
- if (isDef(res)) {
- if (isDef(registerComponent)) {
- registerComponent(userContext)
- }
- res.components.forEach(register => register(userContext))
- write(res.html, next)
- } else {
- renderComponentWithCache(node, isRoot, key, context)
- }
- })
- }
- } else {
- if (isDef(getKey) && isUndef(cache)) {
- warnOnce(
- `[vue-server-renderer] Component ${
- Ctor.options.name || '(anonymous)'
- } implemented serverCacheKey, ` +
- 'but no cache was provided to the renderer.'
- )
- }
- if (isDef(getKey) && isUndef(name)) {
- warnOnce(
- `[vue-server-renderer] Components that implement "serverCacheKey" ` +
- `must also define a unique "name" option.`
- )
- }
- renderComponentInner(node, isRoot, context)
- }
- }
-
- function renderComponentWithCache (node, isRoot, key, context) {
- const write = context.write
- write.caching = true
- const buffer = write.cacheBuffer
- const bufferIndex = buffer.push('') - 1
- const componentBuffer = write.componentBuffer
- componentBuffer.push(new Set())
- context.renderStates.push({
- type: 'ComponentWithCache',
- key,
- buffer,
- bufferIndex,
- componentBuffer
- })
- renderComponentInner(node, isRoot, context)
- }
-
- function renderComponentInner (node, isRoot, context) {
- const prevActive = context.activeInstance
- // expose userContext on vnode
- node.ssrContext = context.userContext
- const child = context.activeInstance = createComponentInstanceForVnode(
- node,
- context.activeInstance
- )
- normalizeRender(child)
- const childNode = child._render()
- childNode.parent = node
- context.renderStates.push({
- type: 'Component',
- prevActive
- })
- renderNode(childNode, isRoot, context)
- }
-
- function renderAsyncComponent (node, isRoot, context) {
- const factory = node.asyncFactory
-
- const resolve = comp => {
- if (comp.__esModule && comp.default) {
- comp = comp.default
- }
- const { data, children, tag } = node.asyncMeta
- const nodeContext = node.asyncMeta.context
- const resolvedNode: any = createComponent(
- comp,
- data,
- nodeContext,
- children,
- tag
- )
- if (resolvedNode) {
- if (resolvedNode.componentOptions) {
- // normal component
- renderComponent(resolvedNode, isRoot, context)
- } else if (!Array.isArray(resolvedNode)) {
- // single return node from functional component
- renderNode(resolvedNode, isRoot, context)
- } else {
- // multiple return nodes from functional component
- context.renderStates.push({
- type: 'Fragment',
- children: resolvedNode,
- rendered: 0,
- total: resolvedNode.length
- })
- context.next()
- }
- } else {
- // invalid component, but this does not throw on the client
- // so render empty comment node
- context.write(`<!---->`, context.next)
- }
- }
-
- if (factory.resolved) {
- resolve(factory.resolved)
- return
- }
-
- const reject = context.done
- let res
- try {
- res = factory(resolve, reject)
- } catch (e) {
- reject(e)
- }
- if (res) {
- if (typeof res.then === 'function') {
- res.then(resolve, reject).catch(reject)
- } else {
- // new syntax in 2.3
- const comp = res.component
- if (comp && typeof comp.then === 'function') {
- comp.then(resolve, reject).catch(reject)
- }
- }
- }
- }
-
- function renderStringNode (el, context) {
- const { write, next } = context
- if (isUndef(el.children) || el.children.length === 0) {
- write(el.open + (el.close || ''), next)
- } else {
- const children: Array<VNode> = el.children
- context.renderStates.push({
- type: 'Element',
- children,
- rendered: 0,
- total: children.length,
- endTag: el.close
- })
- write(el.open, next)
- }
- }
-
- function renderElement (el, isRoot, context) {
- const { write, next } = context
-
- if (isTrue(isRoot)) {
- if (!el.data) el.data = {}
- if (!el.data.attrs) el.data.attrs = {}
- el.data.attrs[SSR_ATTR] = 'true'
- }
-
- if (el.fnOptions) {
- registerComponentForCache(el.fnOptions, write)
- }
-
- const startTag = renderStartingTag(el, context)
- const endTag = `</${el.tag}>`
- if (context.isUnaryTag(el.tag)) {
- write(startTag, next)
- } else if (isUndef(el.children) || el.children.length === 0) {
- write(startTag + endTag, next)
- } else {
- const children: Array<VNode> = el.children
- context.renderStates.push({
- type: 'Element',
- children,
- rendered: 0,
- total: children.length,
- endTag
- })
- write(startTag, next)
- }
- }
-
- function hasAncestorData (node: VNode) {
- const parentNode = node.parent
- return isDef(parentNode) && (isDef(parentNode.data) || hasAncestorData(parentNode))
- }
-
- function getVShowDirectiveInfo (node: VNode): ?VNodeDirective {
- let dir: VNodeDirective
- let tmp
-
- while (isDef(node)) {
- if (node.data && node.data.directives) {
- tmp = node.data.directives.find(dir => dir.name === 'show')
- if (tmp) {
- dir = tmp
- }
- }
- node = node.parent
- }
- return dir
- }
-
- function renderStartingTag (node: VNode, context) {
- let markup = `<${node.tag}`
- const { directives, modules } = context
-
- // construct synthetic data for module processing
- // because modules like style also produce code by parent VNode data
- if (isUndef(node.data) && hasAncestorData(node)) {
- node.data = {}
- }
- if (isDef(node.data)) {
- // check directives
- const dirs = node.data.directives
- if (dirs) {
- for (let i = 0; i < dirs.length; i++) {
- const name = dirs[i].name
- const dirRenderer = directives[name]
- if (dirRenderer && name !== 'show') {
- // directives mutate the node's data
- // which then gets rendered by modules
- dirRenderer(node, dirs[i])
- }
- }
- }
-
- // v-show directive needs to be merged from parent to child
- const vshowDirectiveInfo = getVShowDirectiveInfo(node)
- if (vshowDirectiveInfo) {
- directives.show(node, vshowDirectiveInfo)
- }
-
- // apply other modules
- for (let i = 0; i < modules.length; i++) {
- const res = modules[i](node)
- if (res) {
- markup += res
- }
- }
- }
- // attach scoped CSS ID
- let scopeId
- const activeInstance = context.activeInstance
- if (isDef(activeInstance) &&
- activeInstance !== node.context &&
- isDef(scopeId = activeInstance.$options._scopeId)
- ) {
- markup += ` ${(scopeId: any)}`
- }
- if (isDef(node.fnScopeId)) {
- markup += ` ${node.fnScopeId}`
- } else {
- while (isDef(node)) {
- if (isDef(scopeId = node.context.$options._scopeId)) {
- markup += ` ${scopeId}`
- }
- node = node.parent
- }
- }
- return markup + '>'
- }
-
- export function createRenderFunction (
- modules: Array<(node: VNode) => ?string>,
- directives: Object,
- isUnaryTag: Function,
- cache: any
- ) {
- return function render (
- component: Component,
- write: (text: string, next: Function) => void,
- userContext: ?Object,
- done: Function
- ) {
- warned = Object.create(null)
- const context = new RenderContext({
- activeInstance: component,
- userContext,
- write, done, renderNode,
- isUnaryTag, modules, directives,
- cache
- })
- installSSRHelpers(component)
- normalizeRender(component)
- renderNode(component._render(), true, context)
- }
- }
|