packages/server-renderer/src/render.ts TYPESCRIPT 460 lines View on github.com → Search inside
1import { escape } from './util'2import { SSR_ATTR } from 'shared/constants'3import { RenderContext } from './render-context'4import { resolveAsset } from 'core/util/options'5import { generateComponentTrace } from 'core/util/debug'6import { ssrCompileToFunctions } from './compiler'7import { installSSRHelpers } from './optimizing-compiler/runtime-helpers'89import { isDef, isUndef, isTrue } from 'shared/util'1011import {12  createComponent,13  createComponentInstanceForVnode14} from 'core/vdom/create-component'15import VNode from 'core/vdom/vnode'16import type { VNodeDirective } from 'types/vnode'17import type { Component } from 'types/component'1819let warned = Object.create(null)20const warnOnce = msg => {21  if (!warned[msg]) {22    warned[msg] = true23    // eslint-disable-next-line no-console24    console.warn(`\n\u001b[31m${msg}\u001b[39m\n`)25  }26}2728const onCompilationError = (err, vm) => {29  const trace = vm ? generateComponentTrace(vm) : ''30  throw new Error(`\n\u001b[31m${err}${trace}\u001b[39m\n`)31}3233const normalizeRender = vm => {34  const { render, template, _scopeId } = vm.$options35  if (isUndef(render)) {36    if (template) {37      const compiled = ssrCompileToFunctions(38        template,39        {40          scopeId: _scopeId,41          warn: onCompilationError42        },43        vm44      )4546      vm.$options.render = compiled.render47      vm.$options.staticRenderFns = compiled.staticRenderFns48    } else {49      throw new Error(50        `render function or template not defined in component: ${51          vm.$options.name || vm.$options._componentTag || 'anonymous'52        }`53      )54    }55  }56}5758function waitForServerPrefetch(vm, resolve, reject) {59  let handlers = vm.$options.serverPrefetch60  if (isDef(handlers)) {61    if (!Array.isArray(handlers)) handlers = [handlers]62    try {63      const promises: Promise<any>[] = []64      for (let i = 0, j = handlers.length; i < j; i++) {65        const result = handlers[i].call(vm, vm)66        if (result && typeof result.then === 'function') {67          promises.push(result)68        }69      }70      Promise.all(promises).then(resolve).catch(reject)71      return72    } catch (e: any) {73      reject(e)74    }75  }76  resolve()77}7879function renderNode(node, isRoot, context) {80  if (node.isString) {81    renderStringNode(node, context)82  } else if (isDef(node.componentOptions)) {83    renderComponent(node, isRoot, context)84  } else if (isDef(node.tag)) {85    renderElement(node, isRoot, context)86  } else if (isTrue(node.isComment)) {87    if (isDef(node.asyncFactory)) {88      // async component89      renderAsyncComponent(node, isRoot, context)90    } else {91      context.write(`<!--${node.text}-->`, context.next)92    }93  } else {94    context.write(95      node.raw ? node.text : escape(String(node.text)),96      context.next97    )98  }99}100101function registerComponentForCache(options, write) {102  // exposed by vue-loader, need to call this if cache hit because103  // component lifecycle hooks will not be called.104  const register = options._ssrRegister105  if (write.caching && isDef(register)) {106    write.componentBuffer[write.componentBuffer.length - 1].add(register)107  }108  return register109}110111function renderComponent(node, isRoot, context) {112  const { write, next, userContext } = context113114  // check cache hit115  const Ctor = node.componentOptions.Ctor116  const getKey = Ctor.options.serverCacheKey117  const name = Ctor.options.name118  const cache = context.cache119  const registerComponent = registerComponentForCache(Ctor.options, write)120121  if (isDef(getKey) && isDef(cache) && isDef(name)) {122    const rawKey = getKey(node.componentOptions.propsData)123    if (rawKey === false) {124      renderComponentInner(node, isRoot, context)125      return126    }127    const key = name + '::' + rawKey128    const { has, get } = context129    if (isDef(has)) {130      has(key, hit => {131        if (hit === true && isDef(get)) {132          get(key, res => {133            if (isDef(registerComponent)) {134              registerComponent(userContext)135            }136            res.components.forEach(register => register(userContext))137            write(res.html, next)138          })139        } else {140          renderComponentWithCache(node, isRoot, key, context)141        }142      })143    } else if (isDef(get)) {144      get(key, res => {145        if (isDef(res)) {146          if (isDef(registerComponent)) {147            registerComponent(userContext)148          }149          res.components.forEach(register => register(userContext))150          write(res.html, next)151        } else {152          renderComponentWithCache(node, isRoot, key, context)153        }154      })155    }156  } else {157    if (isDef(getKey) && isUndef(cache)) {158      warnOnce(159        `[vue-server-renderer] Component ${160          Ctor.options.name || '(anonymous)'161        } implemented serverCacheKey, ` +162          'but no cache was provided to the renderer.'163      )164    }165    if (isDef(getKey) && isUndef(name)) {166      warnOnce(167        `[vue-server-renderer] Components that implement "serverCacheKey" ` +168          `must also define a unique "name" option.`169      )170    }171    renderComponentInner(node, isRoot, context)172  }173}174175function renderComponentWithCache(node, isRoot, key, context) {176  const write = context.write177  write.caching = true178  const buffer = write.cacheBuffer179  const bufferIndex = buffer.push('') - 1180  const componentBuffer = write.componentBuffer181  componentBuffer.push(new Set())182  context.renderStates.push({183    type: 'ComponentWithCache',184    key,185    buffer,186    bufferIndex,187    componentBuffer188  })189  renderComponentInner(node, isRoot, context)190}191192function renderComponentInner(node, isRoot, context) {193  const prevActive = context.activeInstance194  // expose userContext on vnode195  node.ssrContext = context.userContext196  const child = (context.activeInstance = createComponentInstanceForVnode(197    node,198    context.activeInstance199  ))200  normalizeRender(child)201202  const resolve = () => {203    const childNode = child._render()204    childNode.parent = node205    context.renderStates.push({206      type: 'Component',207      prevActive208    })209    if (isDef(node.data) && isDef(node.data.directives)) {210      childNode.data = childNode.data || {}211      childNode.data.directives = node.data.directives212      childNode.isComponentRootElement = true213    }214    renderNode(childNode, isRoot, context)215  }216217  const reject = context.done218219  waitForServerPrefetch(child, resolve, reject)220}221222function renderAsyncComponent(node, isRoot, context) {223  const factory = node.asyncFactory224225  const resolve = comp => {226    if (comp.__esModule && comp.default) {227      comp = comp.default228    }229    const { data, children, tag } = node.asyncMeta230    const nodeContext = node.asyncMeta.context231    const resolvedNode: any = createComponent(232      comp,233      data,234      nodeContext,235      children,236      tag237    )238    if (resolvedNode) {239      if (resolvedNode.componentOptions) {240        // normal component241        renderComponent(resolvedNode, isRoot, context)242      } else if (!Array.isArray(resolvedNode)) {243        // single return node from functional component244        renderNode(resolvedNode, isRoot, context)245      } else {246        // multiple return nodes from functional component247        context.renderStates.push({248          type: 'Fragment',249          children: resolvedNode,250          rendered: 0,251          total: resolvedNode.length252        })253        context.next()254      }255    } else {256      // invalid component, but this does not throw on the client257      // so render empty comment node258      context.write(`<!---->`, context.next)259    }260  }261262  if (factory.resolved) {263    resolve(factory.resolved)264    return265  }266267  const reject = context.done268  let res269  try {270    res = factory(resolve, reject)271  } catch (e: any) {272    reject(e)273  }274  if (res) {275    if (typeof res.then === 'function') {276      res.then(resolve, reject).catch(reject)277    } else {278      // new syntax in 2.3279      const comp = res.component280      if (comp && typeof comp.then === 'function') {281        comp.then(resolve, reject).catch(reject)282      }283    }284  }285}286287function renderStringNode(el, context) {288  const { write, next } = context289  if (isUndef(el.children) || el.children.length === 0) {290    write(el.open + (el.close || ''), next)291  } else {292    const children: Array<VNode> = el.children293    context.renderStates.push({294      type: 'Element',295      children,296      rendered: 0,297      total: children.length,298      endTag: el.close299    })300    write(el.open, next)301  }302}303304function renderElement(el, isRoot, context) {305  const { write, next } = context306307  if (isTrue(isRoot)) {308    if (!el.data) el.data = {}309    if (!el.data.attrs) el.data.attrs = {}310    el.data.attrs[SSR_ATTR] = 'true'311  }312313  if (el.fnOptions) {314    registerComponentForCache(el.fnOptions, write)315  }316317  const startTag = renderStartingTag(el, context)318  const endTag = `</${el.tag}>`319  if (context.isUnaryTag(el.tag)) {320    write(startTag, next)321  } else if (isUndef(el.children) || el.children.length === 0) {322    write(startTag + endTag, next)323  } else {324    const children: Array<VNode> = el.children325    context.renderStates.push({326      type: 'Element',327      children,328      rendered: 0,329      total: children.length,330      endTag331    })332    write(startTag, next)333  }334}335336function hasAncestorData(node: VNode) {337  const parentNode = node.parent338  return (339    isDef(parentNode) && (isDef(parentNode.data) || hasAncestorData(parentNode))340  )341}342343function getVShowDirectiveInfo(node: VNode): VNodeDirective | undefined {344  let dir: VNodeDirective345  let tmp346347  while (isDef(node)) {348    if (node.data && node.data.directives) {349      tmp = node.data.directives.find(dir => dir.name === 'show')350      if (tmp) {351        dir = tmp352      }353    }354    node = node.parent!355  }356  //@ts-expect-error357  return dir358}359360function renderStartingTag(node: VNode, context) {361  let markup = `<${node.tag}`362  const { directives, modules } = context363364  // construct synthetic data for module processing365  // because modules like style also produce code by parent VNode data366  if (isUndef(node.data) && hasAncestorData(node)) {367    node.data = {}368  }369  if (isDef(node.data)) {370    // check directives371    const dirs = node.data.directives372    if (dirs) {373      for (let i = 0; i < dirs.length; i++) {374        const name = dirs[i].name375        if (name !== 'show') {376          const dirRenderer = resolveAsset(context, 'directives', name)377          if (dirRenderer) {378            // directives mutate the node's data379            // which then gets rendered by modules380            dirRenderer(381              node.isComponentRootElement ? node.parent : node,382              dirs[i]383            )384          }385        }386      }387    }388389    // v-show directive needs to be merged from parent to child390    const vshowDirectiveInfo = getVShowDirectiveInfo(node)391    if (vshowDirectiveInfo) {392      directives.show(node, vshowDirectiveInfo)393    }394395    // apply other modules396    for (let i = 0; i < modules.length; i++) {397      const res = modules[i](node)398      if (res) {399        markup += res400      }401    }402  }403  // attach scoped CSS ID404  let scopeId405  const activeInstance = context.activeInstance406  if (407    isDef(activeInstance) &&408    activeInstance !== node.context &&409    isDef((scopeId = activeInstance.$options._scopeId))410  ) {411    markup += ` ${scopeId as any}`412  }413  if (isDef(node.fnScopeId)) {414    markup += ` ${node.fnScopeId}`415  } else {416    while (isDef(node)) {417      //@ts-expect-error418      if (isDef((scopeId = node.context.$options._scopeId))) {419        markup += ` ${scopeId}`420      }421      node = node.parent!422    }423  }424  return markup + '>'425}426427export function createRenderFunction(428  modules: Array<(node: VNode) => string | null>,429  directives: Object,430  isUnaryTag: Function,431  cache: any432) {433  return function render(434    component: Component,435    write: (text: string, next: Function) => void,436    userContext: Record<string, any> | null,437    done: Function438  ) {439    warned = Object.create(null)440    const context = new RenderContext({441      activeInstance: component,442      userContext,443      write,444      done,445      renderNode,446      isUnaryTag,447      modules,448      directives,449      cache450    })451    installSSRHelpers(component)452    normalizeRender(component)453454    const resolve = () => {455      renderNode(component._render(), true, context)456    }457    waitForServerPrefetch(component, resolve, done)458  }459}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.