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.