This commit is contained in:
2023-10-08 21:21:21 +08:00
commit 60bf302119
377 changed files with 111630 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
/* @flow */
import { createPromiseCallback } from '../util'
import { createBundleRunner } from './create-bundle-runner'
import type { Renderer, RenderOptions } from '../create-renderer'
import { createSourceMapConsumers, rewriteErrorTrace } from './source-map-support'
const fs = require('fs')
const path = require('path')
const PassThrough = require('stream').PassThrough
const INVALID_MSG =
'Invalid server-rendering bundle format. Should be a string ' +
'or a bundle Object of type:\n\n' +
`{
entry: string;
files: { [filename: string]: string; };
maps: { [filename: string]: string; };
}\n`
// The render bundle can either be a string (single bundled file)
// or a bundle manifest object generated by vue-ssr-webpack-plugin.
type RenderBundle = {
basedir?: string;
entry: string;
files: { [filename: string]: string; };
maps: { [filename: string]: string; };
modules?: { [filename: string]: Array<string> };
};
export function createBundleRendererCreator (
createRenderer: (options?: RenderOptions) => Renderer
) {
return function createBundleRenderer (
bundle: string | RenderBundle,
rendererOptions?: RenderOptions = {}
) {
let files, entry, maps
let basedir = rendererOptions.basedir
// load bundle if given filepath
if (
typeof bundle === 'string' &&
/\.js(on)?$/.test(bundle) &&
path.isAbsolute(bundle)
) {
if (fs.existsSync(bundle)) {
const isJSON = /\.json$/.test(bundle)
basedir = basedir || path.dirname(bundle)
bundle = fs.readFileSync(bundle, 'utf-8')
if (isJSON) {
try {
bundle = JSON.parse(bundle)
} catch (e) {
throw new Error(`Invalid JSON bundle file: ${bundle}`)
}
}
} else {
throw new Error(`Cannot locate bundle file: ${bundle}`)
}
}
if (typeof bundle === 'object') {
entry = bundle.entry
files = bundle.files
basedir = basedir || bundle.basedir
maps = createSourceMapConsumers(bundle.maps)
if (typeof entry !== 'string' || typeof files !== 'object') {
throw new Error(INVALID_MSG)
}
} else if (typeof bundle === 'string') {
entry = '__vue_ssr_bundle__'
files = { '__vue_ssr_bundle__': bundle }
maps = {}
} else {
throw new Error(INVALID_MSG)
}
const renderer = createRenderer(rendererOptions)
const run = createBundleRunner(
entry,
files,
basedir,
rendererOptions.runInNewContext
)
return {
renderToString: (context?: Object, cb: any) => {
if (typeof context === 'function') {
cb = context
context = {}
}
let promise
if (!cb) {
({ promise, cb } = createPromiseCallback())
}
run(context).catch(err => {
rewriteErrorTrace(err, maps)
cb(err)
}).then(app => {
if (app) {
renderer.renderToString(app, context, (err, res) => {
rewriteErrorTrace(err, maps)
cb(err, res)
})
}
})
return promise
},
renderToStream: (context?: Object) => {
const res = new PassThrough()
run(context).catch(err => {
rewriteErrorTrace(err, maps)
// avoid emitting synchronously before user can
// attach error listener
process.nextTick(() => {
res.emit('error', err)
})
}).then(app => {
if (app) {
const renderStream = renderer.renderToStream(app, context)
renderStream.on('error', err => {
rewriteErrorTrace(err, maps)
res.emit('error', err)
})
// relay HTMLStream special events
if (rendererOptions && rendererOptions.template) {
renderStream.on('beforeStart', () => {
res.emit('beforeStart')
})
renderStream.on('beforeEnd', () => {
res.emit('beforeEnd')
})
}
renderStream.pipe(res)
}
})
return res
}
}
}
}

View File

@@ -0,0 +1,150 @@
import { isPlainObject } from 'shared/util'
const vm = require('vm')
const path = require('path')
const resolve = require('resolve')
const NativeModule = require('module')
function createSandbox (context) {
const sandbox = {
Buffer,
console,
process,
setTimeout,
setInterval,
setImmediate,
clearTimeout,
clearInterval,
clearImmediate,
__VUE_SSR_CONTEXT__: context
}
sandbox.global = sandbox
return sandbox
}
function compileModule (files, basedir, runInNewContext) {
const compiledScripts = {}
const resolvedModules = {}
function getCompiledScript (filename) {
if (compiledScripts[filename]) {
return compiledScripts[filename]
}
const code = files[filename]
const wrapper = NativeModule.wrap(code)
const script = new vm.Script(wrapper, {
filename,
displayErrors: true
})
compiledScripts[filename] = script
return script
}
function evaluateModule (filename, sandbox, evaluatedFiles = {}) {
if (evaluatedFiles[filename]) {
return evaluatedFiles[filename]
}
const script = getCompiledScript(filename)
const compiledWrapper = runInNewContext === false
? script.runInThisContext()
: script.runInNewContext(sandbox)
const m = { exports: {}}
const r = file => {
file = path.posix.join('.', file)
if (files[file]) {
return evaluateModule(file, sandbox, evaluatedFiles)
} else if (basedir) {
return require(
resolvedModules[file] ||
(resolvedModules[file] = resolve.sync(file, { basedir }))
)
} else {
return require(file)
}
}
compiledWrapper.call(m.exports, m.exports, r, m)
const res = Object.prototype.hasOwnProperty.call(m.exports, 'default')
? m.exports.default
: m.exports
evaluatedFiles[filename] = res
return res
}
return evaluateModule
}
function deepClone (val) {
if (isPlainObject(val)) {
const res = {}
for (const key in val) {
res[key] = deepClone(val[key])
}
return res
} else if (Array.isArray(val)) {
return val.slice()
} else {
return val
}
}
export function createBundleRunner (entry, files, basedir, runInNewContext) {
const evaluate = compileModule(files, basedir, runInNewContext)
if (runInNewContext !== false && runInNewContext !== 'once') {
// new context mode: creates a fresh context and re-evaluate the bundle
// on each render. Ensures entire application state is fresh for each
// render, but incurs extra evaluation cost.
return (userContext = {}) => new Promise(resolve => {
userContext._registeredComponents = new Set()
const res = evaluate(entry, createSandbox(userContext))
resolve(typeof res === 'function' ? res(userContext) : res)
})
} else {
// direct mode: instead of re-evaluating the whole bundle on
// each render, it simply calls the exported function. This avoids the
// module evaluation costs but requires the source code to be structured
// slightly differently.
let runner // lazy creation so that errors can be caught by user
let initialContext
return (userContext = {}) => new Promise(resolve => {
if (!runner) {
const sandbox = runInNewContext === 'once'
? createSandbox()
: global
// the initial context is only used for collecting possible non-component
// styles injected by vue-style-loader.
initialContext = sandbox.__VUE_SSR_CONTEXT__ = {}
runner = evaluate(entry, sandbox)
// On subsequent renders, __VUE_SSR_CONTEXT__ will not be available
// to prevent cross-request pollution.
delete sandbox.__VUE_SSR_CONTEXT__
if (typeof runner !== 'function') {
throw new Error(
'bundle export should be a function when using ' +
'{ runInNewContext: false }.'
)
}
}
userContext._registeredComponents = new Set()
// vue-style-loader styles imported outside of component lifecycle hooks
if (initialContext._styles) {
userContext._styles = deepClone(initialContext._styles)
// #6353 ensure "styles" is exposed even if no styles are injected
// in component lifecycles.
// the renderStyles fn is exposed by vue-style-loader >= 3.0.3
const renderStyles = initialContext._renderStyles
if (renderStyles) {
Object.defineProperty(userContext, 'styles', {
enumerable: true,
get () {
return renderStyles(userContext._styles)
}
})
}
}
resolve(runner(userContext))
})
}
}

View File

@@ -0,0 +1,45 @@
/* @flow */
const SourceMapConsumer = require('source-map').SourceMapConsumer
const filenameRE = /\(([^)]+\.js):(\d+):(\d+)\)$/
export function createSourceMapConsumers (rawMaps: Object) {
const maps = {}
Object.keys(rawMaps).forEach(file => {
maps[file] = new SourceMapConsumer(rawMaps[file])
})
return maps
}
export function rewriteErrorTrace (e: any, mapConsumers: {
[key: string]: SourceMapConsumer
}) {
if (e && typeof e.stack === 'string') {
e.stack = e.stack.split('\n').map(line => {
return rewriteTraceLine(line, mapConsumers)
}).join('\n')
}
}
function rewriteTraceLine (trace: string, mapConsumers: {
[key: string]: SourceMapConsumer
}) {
const m = trace.match(filenameRE)
const map = m && mapConsumers[m[1]]
if (m != null && map) {
const originalPosition = map.originalPositionFor({
line: Number(m[2]),
column: Number(m[3])
})
if (originalPosition.source != null) {
const { source, line, column } = originalPosition
const mappedPosition = `(${source.replace(/^webpack:\/\/\//, '')}:${String(line)}:${String(column)})`
return trace.replace(filenameRE, mappedPosition)
} else {
return trace
}
} else {
return trace
}
}

37
node_modules/vue/src/server/create-basic-renderer.js generated vendored Normal file
View File

@@ -0,0 +1,37 @@
/* @flow */
import { createWriteFunction } from './write'
import { createRenderFunction } from './render'
import type { RenderOptions } from './create-renderer'
export function createBasicRenderer ({
modules = [],
directives = {},
isUnaryTag = (() => false),
cache
}: RenderOptions = {}) {
const render = createRenderFunction(modules, directives, isUnaryTag, cache)
return function renderToString (
component: Component,
context: any,
done: any
): void {
if (typeof context === 'function') {
done = context
context = {}
}
let result = ''
const write = createWriteFunction(text => {
result += text
return false
}, done)
try {
render(component, write, context, () => {
done(null, result)
})
} catch (e) {
done(e)
}
}
}

152
node_modules/vue/src/server/create-renderer.js generated vendored Normal file
View File

@@ -0,0 +1,152 @@
/* @flow */
import RenderStream from './render-stream'
import { createWriteFunction } from './write'
import { createRenderFunction } from './render'
import { createPromiseCallback } from './util'
import TemplateRenderer from './template-renderer/index'
import type { ClientManifest } from './template-renderer/index'
export type Renderer = {
renderToString: (component: Component, context: any, cb: any) => ?Promise<string>;
renderToStream: (component: Component, context?: Object) => stream$Readable;
};
type RenderCache = {
get: (key: string, cb?: Function) => string | void;
set: (key: string, val: string) => void;
has?: (key: string, cb?: Function) => boolean | void;
};
export type RenderOptions = {
modules?: Array<(vnode: VNode) => ?string>;
directives?: Object;
isUnaryTag?: Function;
cache?: RenderCache;
template?: string | (content: string, context: any) => string;
inject?: boolean;
basedir?: string;
shouldPreload?: Function;
shouldPrefetch?: Function;
clientManifest?: ClientManifest;
serializer?: Function;
runInNewContext?: boolean | 'once';
};
export function createRenderer ({
modules = [],
directives = {},
isUnaryTag = (() => false),
template,
inject,
cache,
shouldPreload,
shouldPrefetch,
clientManifest,
serializer
}: RenderOptions = {}): Renderer {
const render = createRenderFunction(modules, directives, isUnaryTag, cache)
const templateRenderer = new TemplateRenderer({
template,
inject,
shouldPreload,
shouldPrefetch,
clientManifest,
serializer
})
return {
renderToString (
component: Component,
context: any,
cb: any
): ?Promise<string> {
if (typeof context === 'function') {
cb = context
context = {}
}
if (context) {
templateRenderer.bindRenderFns(context)
}
// no callback, return Promise
let promise
if (!cb) {
({ promise, cb } = createPromiseCallback())
}
let result = ''
const write = createWriteFunction(text => {
result += text
return false
}, cb)
try {
render(component, write, context, err => {
if (err) {
return cb(err)
}
if (context && context.rendered) {
context.rendered(context)
}
if (template) {
try {
const res = templateRenderer.render(result, context)
if (typeof res !== 'string') {
// function template returning promise
res
.then(html => cb(null, html))
.catch(cb)
} else {
cb(null, res)
}
} catch (e) {
cb(e)
}
} else {
cb(null, result)
}
})
} catch (e) {
cb(e)
}
return promise
},
renderToStream (
component: Component,
context?: Object
): stream$Readable {
if (context) {
templateRenderer.bindRenderFns(context)
}
const renderStream = new RenderStream((write, done) => {
render(component, write, context, done)
})
if (!template) {
if (context && context.rendered) {
const rendered = context.rendered
renderStream.once('beforeEnd', () => {
rendered(context)
})
}
return renderStream
} else if (typeof template === 'function') {
throw new Error(`function template is only supported in renderToString.`)
} else {
const templateStream = templateRenderer.createStream(context)
renderStream.on('error', err => {
templateStream.emit('error', err)
})
renderStream.pipe(templateStream)
if (context && context.rendered) {
const rendered = context.rendered
renderStream.once('beforeEnd', () => {
rendered(context)
})
}
return templateStream
}
}
}
}

View File

@@ -0,0 +1,264 @@
/* @flow */
// The SSR codegen is essentially extending the default codegen to handle
// SSR-optimizable nodes and turn them into string render fns. In cases where
// a node is not optimizable it simply falls back to the default codegen.
import {
genIf,
genFor,
genData,
genText,
genElement,
genChildren,
CodegenState
} from 'compiler/codegen/index'
import {
genAttrSegments,
genDOMPropSegments,
genClassSegments,
genStyleSegments,
applyModelTransform
} from './modules'
import { escape } from 'web/server/util'
import { optimizability } from './optimizer'
import type { CodegenResult } from 'compiler/codegen/index'
export type StringSegment = {
type: number;
value: string;
};
// segment types
export const RAW = 0
export const INTERPOLATION = 1
export const EXPRESSION = 2
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genSSRElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
function genSSRElement (el: ASTElement, state: CodegenState): string {
if (el.for && !el.forProcessed) {
return genFor(el, state, genSSRElement)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state, genSSRElement)
} else if (el.tag === 'template' && !el.slotTarget) {
return el.ssrOptimizability === optimizability.FULL
? genChildrenAsStringNode(el, state)
: genSSRChildren(el, state) || 'void 0'
}
switch (el.ssrOptimizability) {
case optimizability.FULL:
// stringify whole tree
return genStringElement(el, state)
case optimizability.SELF:
// stringify self and check children
return genStringElementWithChildren(el, state)
case optimizability.CHILDREN:
// generate self as VNode and stringify children
return genNormalElement(el, state, true)
case optimizability.PARTIAL:
// generate self as VNode and check children
return genNormalElement(el, state, false)
default:
// bail whole tree
return genElement(el, state)
}
}
function genNormalElement (el, state, stringifyChildren) {
const data = el.plain ? undefined : genData(el, state)
const children = stringifyChildren
? `[${genChildrenAsStringNode(el, state)}]`
: genSSRChildren(el, state, true)
return `_c('${el.tag}'${
data ? `,${data}` : ''
}${
children ? `,${children}` : ''
})`
}
function genSSRChildren (el, state, checkSkip) {
return genChildren(el, state, checkSkip, genSSRElement, genSSRNode)
}
function genSSRNode (el, state) {
return el.type === 1
? genSSRElement(el, state)
: genText(el)
}
function genChildrenAsStringNode (el, state) {
return el.children.length
? `_ssrNode(${flattenSegments(childrenToSegments(el, state))})`
: ''
}
function genStringElement (el, state) {
return `_ssrNode(${elementToString(el, state)})`
}
function genStringElementWithChildren (el, state) {
const children = genSSRChildren(el, state, true)
return `_ssrNode(${
flattenSegments(elementToOpenTagSegments(el, state))
},"</${el.tag}>"${
children ? `,${children}` : ''
})`
}
function elementToString (el, state) {
return `(${flattenSegments(elementToSegments(el, state))})`
}
function elementToSegments (el, state): Array<StringSegment> {
// v-for / v-if
if (el.for && !el.forProcessed) {
el.forProcessed = true
return [{
type: EXPRESSION,
value: genFor(el, state, elementToString, '_ssrList')
}]
} else if (el.if && !el.ifProcessed) {
el.ifProcessed = true
return [{
type: EXPRESSION,
value: genIf(el, state, elementToString, '"<!---->"')
}]
} else if (el.tag === 'template') {
return childrenToSegments(el, state)
}
const openSegments = elementToOpenTagSegments(el, state)
const childrenSegments = childrenToSegments(el, state)
const { isUnaryTag } = state.options
const close = (isUnaryTag && isUnaryTag(el.tag))
? []
: [{ type: RAW, value: `</${el.tag}>` }]
return openSegments.concat(childrenSegments, close)
}
function elementToOpenTagSegments (el, state): Array<StringSegment> {
applyModelTransform(el, state)
let binding
const segments = [{ type: RAW, value: `<${el.tag}` }]
// attrs
if (el.attrs) {
segments.push.apply(segments, genAttrSegments(el.attrs))
}
// domProps
if (el.props) {
segments.push.apply(segments, genDOMPropSegments(el.props, el.attrs))
}
// v-bind="object"
if ((binding = el.attrsMap['v-bind'])) {
segments.push({ type: EXPRESSION, value: `_ssrAttrs(${binding})` })
}
// v-bind.prop="object"
if ((binding = el.attrsMap['v-bind.prop'])) {
segments.push({ type: EXPRESSION, value: `_ssrDOMProps(${binding})` })
}
// class
if (el.staticClass || el.classBinding) {
segments.push.apply(
segments,
genClassSegments(el.staticClass, el.classBinding)
)
}
// style & v-show
if (el.staticStyle || el.styleBinding || el.attrsMap['v-show']) {
segments.push.apply(
segments,
genStyleSegments(
el.attrsMap.style,
el.staticStyle,
el.styleBinding,
el.attrsMap['v-show']
)
)
}
// _scopedId
if (state.options.scopeId) {
segments.push({ type: RAW, value: ` ${state.options.scopeId}` })
}
segments.push({ type: RAW, value: `>` })
return segments
}
function childrenToSegments (el, state): Array<StringSegment> {
let binding
if ((binding = el.attrsMap['v-html'])) {
return [{ type: EXPRESSION, value: `_s(${binding})` }]
}
if ((binding = el.attrsMap['v-text'])) {
return [{ type: INTERPOLATION, value: `_s(${binding})` }]
}
if (el.tag === 'textarea' && (binding = el.attrsMap['v-model'])) {
return [{ type: INTERPOLATION, value: `_s(${binding})` }]
}
return el.children
? nodesToSegments(el.children, state)
: []
}
function nodesToSegments (
children: Array<ASTNode>,
state: CodegenState
): Array<StringSegment> {
const segments = []
for (let i = 0; i < children.length; i++) {
const c = children[i]
if (c.type === 1) {
segments.push.apply(segments, elementToSegments(c, state))
} else if (c.type === 2) {
segments.push({ type: INTERPOLATION, value: c.expression })
} else if (c.type === 3) {
let text = escape(c.text)
if (c.isComment) {
text = '<!--' + text + '-->'
}
segments.push({ type: RAW, value: text })
}
}
return segments
}
function flattenSegments (segments: Array<StringSegment>): string {
const mergedSegments = []
let textBuffer = ''
const pushBuffer = () => {
if (textBuffer) {
mergedSegments.push(JSON.stringify(textBuffer))
textBuffer = ''
}
}
for (let i = 0; i < segments.length; i++) {
const s = segments[i]
if (s.type === RAW) {
textBuffer += s.value
} else if (s.type === INTERPOLATION) {
pushBuffer()
mergedSegments.push(`_ssrEscape(${s.value})`)
} else if (s.type === EXPRESSION) {
pushBuffer()
mergedSegments.push(`(${s.value})`)
}
}
pushBuffer()
return mergedSegments.join('+')
}

View File

@@ -0,0 +1,20 @@
/* @flow */
import { parse } from 'compiler/parser/index'
import { generate } from './codegen'
import { optimize } from './optimizer'
import { createCompilerCreator } from 'compiler/create-compiler'
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)
optimize(ast, options)
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})

View File

@@ -0,0 +1,124 @@
/* @flow */
import {
RAW,
// INTERPOLATION,
EXPRESSION
} from './codegen'
import {
propsToAttrMap,
isRenderableAttr
} from 'web/server/util'
import {
isBooleanAttr,
isEnumeratedAttr
} from 'web/util/attrs'
import type { StringSegment } from './codegen'
import type { CodegenState } from 'compiler/codegen/index'
const plainStringRE = /^"(?:[^"\\]|\\.)*"$|^'(?:[^'\\]|\\.)*'$/
// let the model AST transform translate v-model into appropriate
// props bindings
export function applyModelTransform (el: ASTElement, state: CodegenState) {
if (el.directives) {
for (let i = 0; i < el.directives.length; i++) {
const dir = el.directives[i]
if (dir.name === 'model') {
state.directives.model(el, dir, state.warn)
// remove value for textarea as its converted to text
if (el.tag === 'textarea' && el.props) {
el.props = el.props.filter(p => p.name !== 'value')
}
break
}
}
}
}
export function genAttrSegments (
attrs: Array<ASTAttr>
): Array<StringSegment> {
return attrs.map(({ name, value }) => genAttrSegment(name, value))
}
export function genDOMPropSegments (
props: Array<ASTAttr>,
attrs: ?Array<ASTAttr>
): Array<StringSegment> {
const segments = []
props.forEach(({ name, value }) => {
name = propsToAttrMap[name] || name.toLowerCase()
if (isRenderableAttr(name) &&
!(attrs && attrs.some(a => a.name === name))
) {
segments.push(genAttrSegment(name, value))
}
})
return segments
}
function genAttrSegment (name: string, value: string): StringSegment {
if (plainStringRE.test(value)) {
// force double quote
value = value.replace(/^'|'$/g, '"')
// force enumerated attr to "true"
if (isEnumeratedAttr(name) && value !== `"false"`) {
value = `"true"`
}
return {
type: RAW,
value: isBooleanAttr(name)
? ` ${name}="${name}"`
: value === '""'
? ` ${name}`
: ` ${name}="${JSON.parse(value)}"`
}
} else {
return {
type: EXPRESSION,
value: `_ssrAttr(${JSON.stringify(name)},${value})`
}
}
}
export function genClassSegments (
staticClass: ?string,
classBinding: ?string
): Array<StringSegment> {
if (staticClass && !classBinding) {
return [{ type: RAW, value: ` class="${JSON.parse(staticClass)}"` }]
} else {
return [{
type: EXPRESSION,
value: `_ssrClass(${staticClass || 'null'},${classBinding || 'null'})`
}]
}
}
export function genStyleSegments (
staticStyle: ?string,
parsedStaticStyle: ?string,
styleBinding: ?string,
vShowExpression: ?string
): Array<StringSegment> {
if (staticStyle && !styleBinding && !vShowExpression) {
return [{ type: RAW, value: ` style=${JSON.stringify(staticStyle)}` }]
} else {
return [{
type: EXPRESSION,
value: `_ssrStyle(${
parsedStaticStyle || 'null'
},${
styleBinding || 'null'
}, ${
vShowExpression
? `{ display: (${vShowExpression}) ? '' : 'none' }`
: 'null'
})`
}]
}
}

View File

@@ -0,0 +1,141 @@
/* @flow */
/**
* In SSR, the vdom tree is generated only once and never patched, so
* we can optimize most element / trees into plain string render functions.
* The SSR optimizer walks the AST tree to detect optimizable elements and trees.
*
* The criteria for SSR optimizability is quite a bit looser than static tree
* detection (which is designed for client re-render). In SSR we bail only for
* components/slots/custom directives.
*/
import { no, makeMap, isBuiltInTag } from 'shared/util'
// optimizability constants
export const optimizability = {
FALSE: 0, // whole sub tree un-optimizable
FULL: 1, // whole sub tree optimizable
SELF: 2, // self optimizable but has some un-optimizable children
CHILDREN: 3, // self un-optimizable but have fully optimizable children
PARTIAL: 4 // self un-optimizable with some un-optimizable children
}
let isPlatformReservedTag
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
isPlatformReservedTag = options.isReservedTag || no
walk(root, true)
}
function walk (node: ASTNode, isRoot?: boolean) {
if (isUnOptimizableTree(node)) {
node.ssrOptimizability = optimizability.FALSE
return
}
// root node or nodes with custom directives should always be a VNode
const selfUnoptimizable = isRoot || hasCustomDirective(node)
const check = child => {
if (child.ssrOptimizability !== optimizability.FULL) {
node.ssrOptimizability = selfUnoptimizable
? optimizability.PARTIAL
: optimizability.SELF
}
}
if (selfUnoptimizable) {
node.ssrOptimizability = optimizability.CHILDREN
}
if (node.type === 1) {
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
walk(child)
check(child)
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block
walk(block, isRoot)
check(block)
}
}
if (node.ssrOptimizability == null ||
(!isRoot && (node.attrsMap['v-html'] || node.attrsMap['v-text']))
) {
node.ssrOptimizability = optimizability.FULL
} else {
node.children = optimizeSiblings(node)
}
} else {
node.ssrOptimizability = optimizability.FULL
}
}
function optimizeSiblings (el) {
const children = el.children
const optimizedChildren = []
let currentOptimizableGroup = []
const pushGroup = () => {
if (currentOptimizableGroup.length) {
optimizedChildren.push({
type: 1,
parent: el,
tag: 'template',
attrsList: [],
attrsMap: {},
rawAttrsMap: {},
children: currentOptimizableGroup,
ssrOptimizability: optimizability.FULL
})
}
currentOptimizableGroup = []
}
for (let i = 0; i < children.length; i++) {
const c = children[i]
if (c.ssrOptimizability === optimizability.FULL) {
currentOptimizableGroup.push(c)
} else {
// wrap fully-optimizable adjacent siblings inside a template tag
// so that they can be optimized into a single ssrNode by codegen
pushGroup()
optimizedChildren.push(c)
}
}
pushGroup()
return optimizedChildren
}
function isUnOptimizableTree (node: ASTNode): boolean {
if (node.type === 2 || node.type === 3) { // text or expression
return false
}
return (
isBuiltInTag(node.tag) || // built-in (slot, component)
!isPlatformReservedTag(node.tag) || // custom component
!!node.component || // "is" component
isSelectWithModel(node) // <select v-model> requires runtime inspection
)
}
const isBuiltInDir = makeMap('text,html,show,on,bind,model,pre,cloak,once')
function hasCustomDirective (node: ASTNode): ?boolean {
return (
node.type === 1 &&
node.directives &&
node.directives.some(d => !isBuiltInDir(d.name))
)
}
// <select v-model> cannot be optimized because it requires a runtime check
// to determine proper selected option
function isSelectWithModel (node: ASTNode): boolean {
return (
node.type === 1 &&
node.tag === 'select' &&
node.directives != null &&
node.directives.some(d => d.name === 'model')
)
}

View File

@@ -0,0 +1,150 @@
/* @flow */
import { escape, isSSRUnsafeAttr } from 'web/server/util'
import { isObject, extend } from 'shared/util'
import { renderAttr } from 'web/server/modules/attrs'
import { renderClass } from 'web/util/class'
import { genStyle } from 'web/server/modules/style'
import { normalizeStyleBinding } from 'web/util/style'
import {
normalizeChildren,
simpleNormalizeChildren
} from 'core/vdom/helpers/normalize-children'
import {
propsToAttrMap,
isRenderableAttr
} from 'web/server/util'
const ssrHelpers = {
_ssrEscape: escape,
_ssrNode: renderStringNode,
_ssrList: renderStringList,
_ssrAttr: renderAttr,
_ssrAttrs: renderAttrs,
_ssrDOMProps: renderDOMProps,
_ssrClass: renderSSRClass,
_ssrStyle: renderSSRStyle
}
export function installSSRHelpers (vm: Component) {
if (vm._ssrNode) {
return
}
let Vue = vm.constructor
while (Vue.super) {
Vue = Vue.super
}
extend(Vue.prototype, ssrHelpers)
if (Vue.FunctionalRenderContext) {
extend(Vue.FunctionalRenderContext.prototype, ssrHelpers)
}
}
class StringNode {
isString: boolean;
open: string;
close: ?string;
children: ?Array<any>;
constructor (
open: string,
close?: string,
children?: Array<any>,
normalizationType?: number
) {
this.isString = true
this.open = open
this.close = close
if (children) {
this.children = normalizationType === 1
? simpleNormalizeChildren(children)
: normalizationType === 2
? normalizeChildren(children)
: children
} else {
this.children = void 0
}
}
}
function renderStringNode (
open: string,
close?: string,
children?: Array<any>,
normalizationType?: number
): StringNode {
return new StringNode(open, close, children, normalizationType)
}
function renderStringList (
val: any,
render: (
val: any,
keyOrIndex: string | number,
index?: number
) => string
): string {
let ret = ''
let i, l, keys, key
if (Array.isArray(val) || typeof val === 'string') {
for (i = 0, l = val.length; i < l; i++) {
ret += render(val[i], i)
}
} else if (typeof val === 'number') {
for (i = 0; i < val; i++) {
ret += render(i + 1, i)
}
} else if (isObject(val)) {
keys = Object.keys(val)
for (i = 0, l = keys.length; i < l; i++) {
key = keys[i]
ret += render(val[key], key, i)
}
}
return ret
}
function renderAttrs (obj: Object): string {
let res = ''
for (const key in obj) {
if (isSSRUnsafeAttr(key)) {
continue
}
res += renderAttr(key, obj[key])
}
return res
}
function renderDOMProps (obj: Object): string {
let res = ''
for (const key in obj) {
const attr = propsToAttrMap[key] || key.toLowerCase()
if (isRenderableAttr(attr)) {
res += renderAttr(attr, obj[key])
}
}
return res
}
function renderSSRClass (
staticClass: ?string,
dynamic: any
): string {
const res = renderClass(staticClass, dynamic)
return res === '' ? res : ` class="${escape(res)}"`
}
function renderSSRStyle (
staticStyle: ?Object,
dynamic: any,
extra: ?Object
): string {
const style = {}
if (staticStyle) extend(style, staticStyle)
if (dynamic) extend(style, normalizeStyleBinding(dynamic))
if (extra) extend(style, extra)
const res = genStyle(style)
return res === '' ? res : ` style=${JSON.stringify(escape(res))}`
}

130
node_modules/vue/src/server/render-context.js generated vendored Normal file
View File

@@ -0,0 +1,130 @@
/* @flow */
import { isUndef } from 'shared/util'
type RenderState = {
type: 'Element';
rendered: number;
total: number;
children: Array<VNode>;
endTag: string;
} | {
type: 'Fragment';
rendered: number;
total: number;
children: Array<VNode>;
} | {
type: 'Component';
prevActive: Component;
} | {
type: 'ComponentWithCache';
buffer: Array<string>;
bufferIndex: number;
componentBuffer: Array<Set<Class<Component>>>;
key: string;
};
export class RenderContext {
userContext: ?Object;
activeInstance: Component;
renderStates: Array<RenderState>;
write: (text: string, next: Function) => void;
renderNode: (node: VNode, isRoot: boolean, context: RenderContext) => void;
next: () => void;
done: (err: ?Error) => void;
modules: Array<(node: VNode) => ?string>;
directives: Object;
isUnaryTag: (tag: string) => boolean;
cache: any;
get: ?(key: string, cb: Function) => void;
has: ?(key: string, cb: Function) => void;
constructor (options: Object) {
this.userContext = options.userContext
this.activeInstance = options.activeInstance
this.renderStates = []
this.write = options.write
this.done = options.done
this.renderNode = options.renderNode
this.isUnaryTag = options.isUnaryTag
this.modules = options.modules
this.directives = options.directives
const cache = options.cache
if (cache && (!cache.get || !cache.set)) {
throw new Error('renderer cache must implement at least get & set.')
}
this.cache = cache
this.get = cache && normalizeAsync(cache, 'get')
this.has = cache && normalizeAsync(cache, 'has')
this.next = this.next.bind(this)
}
next () {
// eslint-disable-next-line
while (true) {
const lastState = this.renderStates[this.renderStates.length - 1]
if (isUndef(lastState)) {
return this.done()
}
/* eslint-disable no-case-declarations */
switch (lastState.type) {
case 'Element':
case 'Fragment':
const { children, total } = lastState
const rendered = lastState.rendered++
if (rendered < total) {
return this.renderNode(children[rendered], false, this)
} else {
this.renderStates.pop()
if (lastState.type === 'Element') {
return this.write(lastState.endTag, this.next)
}
}
break
case 'Component':
this.renderStates.pop()
this.activeInstance = lastState.prevActive
break
case 'ComponentWithCache':
this.renderStates.pop()
const { buffer, bufferIndex, componentBuffer, key } = lastState
const result = {
html: buffer[bufferIndex],
components: componentBuffer[bufferIndex]
}
this.cache.set(key, result)
if (bufferIndex === 0) {
// this is a top-level cached component,
// exit caching mode.
this.write.caching = false
} else {
// parent component is also being cached,
// merge self into parent's result
buffer[bufferIndex - 1] += result.html
const prev = componentBuffer[bufferIndex - 1]
result.components.forEach(c => prev.add(c))
}
buffer.length = bufferIndex
componentBuffer.length = bufferIndex
break
}
}
}
}
function normalizeAsync (cache, method) {
const fn = cache[method]
if (isUndef(fn)) {
return
} else if (fn.length > 1) {
return (key, cb) => fn.call(cache, key, cb)
} else {
return (key, cb) => cb(fn.call(cache, key))
}
}

95
node_modules/vue/src/server/render-stream.js generated vendored Normal file
View File

@@ -0,0 +1,95 @@
/* @flow */
/**
* Original RenderStream implementation by Sasha Aickin (@aickin)
* Licensed under the Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Modified by Evan You (@yyx990803)
*/
const stream = require('stream')
import { isTrue, isUndef } from 'shared/util'
import { createWriteFunction } from './write'
export default class RenderStream extends stream.Readable {
buffer: string;
render: (write: Function, done: Function) => void;
expectedSize: number;
write: Function;
next: Function;
end: Function;
done: boolean;
constructor (render: Function) {
super()
this.buffer = ''
this.render = render
this.expectedSize = 0
this.write = createWriteFunction((text, next) => {
const n = this.expectedSize
this.buffer += text
if (this.buffer.length >= n) {
this.next = next
this.pushBySize(n)
return true // we will decide when to call next
}
return false
}, err => {
this.emit('error', err)
})
this.end = () => {
this.emit('beforeEnd')
// the rendering is finished; we should push out the last of the buffer.
this.done = true
this.push(this.buffer)
}
}
pushBySize (n: number) {
const bufferToPush = this.buffer.substring(0, n)
this.buffer = this.buffer.substring(n)
this.push(bufferToPush)
}
tryRender () {
try {
this.render(this.write, this.end)
} catch (e) {
this.emit('error', e)
}
}
tryNext () {
try {
this.next()
} catch (e) {
this.emit('error', e)
}
}
_read (n: number) {
this.expectedSize = n
// it's possible that the last chunk added bumped the buffer up to > 2 * n,
// which means we will need to go through multiple read calls to drain it
// down to < n.
if (isTrue(this.done)) {
this.push(null)
return
}
if (this.buffer.length >= n) {
this.pushBySize(n)
return
}
if (isUndef(this.next)) {
// start the rendering chain.
this.tryRender()
} else {
// continue with the rendering.
this.tryNext()
}
}
}

437
node_modules/vue/src/server/render.js generated vendored Normal file
View File

@@ -0,0 +1,437 @@
/* @flow */
import { escape } from 'web/server/util'
import { SSR_ATTR } from 'shared/constants'
import { RenderContext } from './render-context'
import { resolveAsset } from 'core/util/options'
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
// eslint-disable-next-line no-console
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 waitForServerPrefetch (vm, resolve, reject) {
let handlers = vm.$options.serverPrefetch
if (isDef(handlers)) {
if (!Array.isArray(handlers)) handlers = [handlers]
try {
const promises = []
for (let i = 0, j = handlers.length; i < j; i++) {
const result = handlers[i].call(vm, vm)
if (result && typeof result.then === 'function') {
promises.push(result)
}
}
Promise.all(promises).then(resolve).catch(reject)
return
} catch (e) {
reject(e)
}
}
resolve()
}
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 rawKey = getKey(node.componentOptions.propsData)
if (rawKey === false) {
renderComponentInner(node, isRoot, context)
return
}
const key = name + '::' + rawKey
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 resolve = () => {
const childNode = child._render()
childNode.parent = node
context.renderStates.push({
type: 'Component',
prevActive
})
renderNode(childNode, isRoot, context)
}
const reject = context.done
waitForServerPrefetch(child, resolve, reject)
}
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
if (name !== 'show') {
const dirRenderer = resolveAsset(context, 'directives', name)
if (dirRenderer) {
// 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)
const resolve = () => {
renderNode(component._render(), true, context)
}
waitForServerPrefetch(component, resolve, done)
}
}

View File

@@ -0,0 +1,57 @@
/* @flow */
/**
* Creates a mapper that maps components used during a server-side render
* to async chunk files in the client-side build, so that we can inline them
* directly in the rendered HTML to avoid waterfall requests.
*/
import type { ClientManifest } from './index'
export type AsyncFileMapper = (files: Array<string>) => Array<string>;
export function createMapper (
clientManifest: ClientManifest
): AsyncFileMapper {
const map = createMap(clientManifest)
// map server-side moduleIds to client-side files
return function mapper (moduleIds: Array<string>): Array<string> {
const res = new Set()
for (let i = 0; i < moduleIds.length; i++) {
const mapped = map.get(moduleIds[i])
if (mapped) {
for (let j = 0; j < mapped.length; j++) {
res.add(mapped[j])
}
}
}
return Array.from(res)
}
}
function createMap (clientManifest) {
const map = new Map()
Object.keys(clientManifest.modules).forEach(id => {
map.set(id, mapIdToFile(id, clientManifest))
})
return map
}
function mapIdToFile (id, clientManifest) {
const files = []
const fileIndices = clientManifest.modules[id]
if (fileIndices) {
fileIndices.forEach(index => {
const file = clientManifest.all[index]
// only include async files or non-js, non-css assets
if (
file &&
(clientManifest.async.indexOf(file) > -1 ||
!/\.(js|css)($|\?)/.test(file))
) {
files.push(file)
}
})
}
return files
}

277
node_modules/vue/src/server/template-renderer/index.js generated vendored Normal file
View File

@@ -0,0 +1,277 @@
/* @flow */
const path = require('path')
const serialize = require('serialize-javascript')
import { isJS, isCSS } from '../util'
import TemplateStream from './template-stream'
import { parseTemplate } from './parse-template'
import { createMapper } from './create-async-file-mapper'
import type { ParsedTemplate } from './parse-template'
import type { AsyncFileMapper } from './create-async-file-mapper'
type TemplateRendererOptions = {
template?: string | (content: string, context: any) => string;
inject?: boolean;
clientManifest?: ClientManifest;
shouldPreload?: (file: string, type: string) => boolean;
shouldPrefetch?: (file: string, type: string) => boolean;
serializer?: Function;
};
export type ClientManifest = {
publicPath: string;
all: Array<string>;
initial: Array<string>;
async: Array<string>;
modules: {
[id: string]: Array<number>;
},
hasNoCssVersion?: {
[file: string]: boolean;
}
};
type Resource = {
file: string;
extension: string;
fileWithoutQuery: string;
asType: string;
};
export default class TemplateRenderer {
options: TemplateRendererOptions;
inject: boolean;
parsedTemplate: ParsedTemplate | Function | null;
publicPath: string;
clientManifest: ClientManifest;
preloadFiles: Array<Resource>;
prefetchFiles: Array<Resource>;
mapFiles: AsyncFileMapper;
serialize: Function;
constructor (options: TemplateRendererOptions) {
this.options = options
this.inject = options.inject !== false
// if no template option is provided, the renderer is created
// as a utility object for rendering assets like preload links and scripts.
const { template } = options
this.parsedTemplate = template
? typeof template === 'string'
? parseTemplate(template)
: template
: null
// function used to serialize initial state JSON
this.serialize = options.serializer || (state => {
return serialize(state, { isJSON: true })
})
// extra functionality with client manifest
if (options.clientManifest) {
const clientManifest = this.clientManifest = options.clientManifest
// ensure publicPath ends with /
this.publicPath = clientManifest.publicPath === ''
? ''
: clientManifest.publicPath.replace(/([^\/])$/, '$1/')
// preload/prefetch directives
this.preloadFiles = (clientManifest.initial || []).map(normalizeFile)
this.prefetchFiles = (clientManifest.async || []).map(normalizeFile)
// initial async chunk mapping
this.mapFiles = createMapper(clientManifest)
}
}
bindRenderFns (context: Object) {
const renderer: any = this
;['ResourceHints', 'State', 'Scripts', 'Styles'].forEach(type => {
context[`render${type}`] = renderer[`render${type}`].bind(renderer, context)
})
// also expose getPreloadFiles, useful for HTTP/2 push
context.getPreloadFiles = renderer.getPreloadFiles.bind(renderer, context)
}
// render synchronously given rendered app content and render context
render (content: string, context: ?Object): string | Promise<string> {
const template = this.parsedTemplate
if (!template) {
throw new Error('render cannot be called without a template.')
}
context = context || {}
if (typeof template === 'function') {
return template(content, context)
}
if (this.inject) {
return (
template.head(context) +
(context.head || '') +
this.renderResourceHints(context) +
this.renderStyles(context) +
template.neck(context) +
content +
this.renderState(context) +
this.renderScripts(context) +
template.tail(context)
)
} else {
return (
template.head(context) +
template.neck(context) +
content +
template.tail(context)
)
}
}
renderStyles (context: Object): string {
const initial = this.preloadFiles || []
const async = this.getUsedAsyncFiles(context) || []
const cssFiles = initial.concat(async).filter(({ file }) => isCSS(file))
return (
// render links for css files
(cssFiles.length
? cssFiles.map(({ file }) => `<link rel="stylesheet" href="${this.publicPath}${file}">`).join('')
: '') +
// context.styles is a getter exposed by vue-style-loader which contains
// the inline component styles collected during SSR
(context.styles || '')
)
}
renderResourceHints (context: Object): string {
return this.renderPreloadLinks(context) + this.renderPrefetchLinks(context)
}
getPreloadFiles (context: Object): Array<Resource> {
const usedAsyncFiles = this.getUsedAsyncFiles(context)
if (this.preloadFiles || usedAsyncFiles) {
return (this.preloadFiles || []).concat(usedAsyncFiles || [])
} else {
return []
}
}
renderPreloadLinks (context: Object): string {
const files = this.getPreloadFiles(context)
const shouldPreload = this.options.shouldPreload
if (files.length) {
return files.map(({ file, extension, fileWithoutQuery, asType }) => {
let extra = ''
// by default, we only preload scripts or css
if (!shouldPreload && asType !== 'script' && asType !== 'style') {
return ''
}
// user wants to explicitly control what to preload
if (shouldPreload && !shouldPreload(fileWithoutQuery, asType)) {
return ''
}
if (asType === 'font') {
extra = ` type="font/${extension}" crossorigin`
}
return `<link rel="preload" href="${
this.publicPath}${file
}"${
asType !== '' ? ` as="${asType}"` : ''
}${
extra
}>`
}).join('')
} else {
return ''
}
}
renderPrefetchLinks (context: Object): string {
const shouldPrefetch = this.options.shouldPrefetch
if (this.prefetchFiles) {
const usedAsyncFiles = this.getUsedAsyncFiles(context)
const alreadyRendered = file => {
return usedAsyncFiles && usedAsyncFiles.some(f => f.file === file)
}
return this.prefetchFiles.map(({ file, fileWithoutQuery, asType }) => {
if (shouldPrefetch && !shouldPrefetch(fileWithoutQuery, asType)) {
return ''
}
if (alreadyRendered(file)) {
return ''
}
return `<link rel="prefetch" href="${this.publicPath}${file}">`
}).join('')
} else {
return ''
}
}
renderState (context: Object, options?: Object): string {
const {
contextKey = 'state',
windowKey = '__INITIAL_STATE__'
} = options || {}
const state = this.serialize(context[contextKey])
const autoRemove = process.env.NODE_ENV === 'production'
? ';(function(){var s;(s=document.currentScript||document.scripts[document.scripts.length-1]).parentNode.removeChild(s);}());'
: ''
const nonceAttr = context.nonce ? ` nonce="${context.nonce}"` : ''
return context[contextKey]
? `<script${nonceAttr}>window.${windowKey}=${state}${autoRemove}</script>`
: ''
}
renderScripts (context: Object): string {
if (this.clientManifest) {
const initial = this.preloadFiles.filter(({ file }) => isJS(file))
const async = (this.getUsedAsyncFiles(context) || []).filter(({ file }) => isJS(file))
const needed = [initial[0]].concat(async, initial.slice(1))
return needed.map(({ file }) => {
return `<script src="${this.publicPath}${file}" defer></script>`
}).join('')
} else {
return ''
}
}
getUsedAsyncFiles (context: Object): ?Array<Resource> {
if (!context._mappedFiles && context._registeredComponents && this.mapFiles) {
const registered = Array.from(context._registeredComponents)
context._mappedFiles = this.mapFiles(registered).map(normalizeFile)
}
return context._mappedFiles
}
// create a transform stream
createStream (context: ?Object): TemplateStream {
if (!this.parsedTemplate) {
throw new Error('createStream cannot be called without a template.')
}
return new TemplateStream(this, this.parsedTemplate, context || {})
}
}
function normalizeFile (file: string): Resource {
const withoutQuery = file.replace(/\?.*/, '')
const extension = path.extname(withoutQuery).slice(1)
return {
file,
extension,
fileWithoutQuery: withoutQuery,
asType: getPreloadType(extension)
}
}
function getPreloadType (ext: string): string {
if (ext === 'js') {
return 'script'
} else if (ext === 'css') {
return 'style'
} else if (/jpe?g|png|svg|gif|webp|ico/.test(ext)) {
return 'image'
} else if (/woff2?|ttf|otf|eot/.test(ext)) {
return 'font'
} else {
// not exhausting all possibilities here, but above covers common cases
return ''
}
}

View File

@@ -0,0 +1,42 @@
/* @flow */
const compile = require('lodash.template')
const compileOptions = {
escape: /{{([^{][\s\S]+?[^}])}}/g,
interpolate: /{{{([\s\S]+?)}}}/g
}
export type ParsedTemplate = {
head: (data: any) => string;
neck: (data: any) => string;
tail: (data: any) => string;
};
export function parseTemplate (
template: string,
contentPlaceholder?: string = '<!--vue-ssr-outlet-->'
): ParsedTemplate {
if (typeof template === 'object') {
return template
}
let i = template.indexOf('</head>')
const j = template.indexOf(contentPlaceholder)
if (j < 0) {
throw new Error(`Content placeholder not found in template.`)
}
if (i < 0) {
i = template.indexOf('<body>')
if (i < 0) {
i = j
}
}
return {
head: compile(template.slice(0, i), compileOptions),
neck: compile(template.slice(i, j), compileOptions),
tail: compile(template.slice(j + contentPlaceholder.length), compileOptions)
}
}

View File

@@ -0,0 +1,82 @@
/* @flow */
const Transform = require('stream').Transform
import type TemplateRenderer from './index'
import type { ParsedTemplate } from './parse-template'
export default class TemplateStream extends Transform {
started: boolean;
renderer: TemplateRenderer;
template: ParsedTemplate;
context: Object;
inject: boolean;
constructor (
renderer: TemplateRenderer,
template: ParsedTemplate,
context: Object
) {
super()
this.started = false
this.renderer = renderer
this.template = template
this.context = context || {}
this.inject = renderer.inject
}
_transform (data: Buffer | string, encoding: string, done: Function) {
if (!this.started) {
this.emit('beforeStart')
this.start()
}
this.push(data)
done()
}
start () {
this.started = true
this.push(this.template.head(this.context))
if (this.inject) {
// inline server-rendered head meta information
if (this.context.head) {
this.push(this.context.head)
}
// inline preload/prefetch directives for initial/async chunks
const links = this.renderer.renderResourceHints(this.context)
if (links) {
this.push(links)
}
// CSS files and inline server-rendered CSS collected by vue-style-loader
const styles = this.renderer.renderStyles(this.context)
if (styles) {
this.push(styles)
}
}
this.push(this.template.neck(this.context))
}
_flush (done: Function) {
this.emit('beforeEnd')
if (this.inject) {
// inline initial store state
const state = this.renderer.renderState(this.context)
if (state) {
this.push(state)
}
// embed scripts needed
const scripts = this.renderer.renderScripts(this.context)
if (scripts) {
this.push(scripts)
}
}
this.push(this.template.tail(this.context))
done()
}
}

18
node_modules/vue/src/server/util.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
/* @flow */
export const isJS = (file: string): boolean => /\.js(\?[^.]+)?$/.test(file)
export const isCSS = (file: string): boolean => /\.css(\?[^.]+)?$/.test(file)
export function createPromiseCallback () {
let resolve, reject
const promise: Promise<string> = new Promise((_resolve, _reject) => {
resolve = _resolve
reject = _reject
})
const cb = (err: Error, res?: string) => {
if (err) return reject(err)
resolve(res || '')
}
return { promise, cb }
}

67
node_modules/vue/src/server/webpack-plugin/client.js generated vendored Normal file
View File

@@ -0,0 +1,67 @@
const hash = require('hash-sum')
const uniq = require('lodash.uniq')
import { isJS, isCSS, getAssetName, onEmit, stripModuleIdHash } from './util'
export default class VueSSRClientPlugin {
constructor (options = {}) {
this.options = Object.assign({
filename: 'vue-ssr-client-manifest.json'
}, options)
}
apply (compiler) {
const stage = 'PROCESS_ASSETS_STAGE_ADDITIONAL'
onEmit(compiler, 'vue-client-plugin', stage, (compilation, cb) => {
const stats = compilation.getStats().toJson()
const allFiles = uniq(stats.assets
.map(a => a.name))
const initialFiles = uniq(Object.keys(stats.entrypoints)
.map(name => stats.entrypoints[name].assets)
.reduce((assets, all) => all.concat(assets), [])
.map(getAssetName)
.filter((file) => isJS(file) || isCSS(file)))
const asyncFiles = allFiles
.filter((file) => isJS(file) || isCSS(file))
.filter(file => initialFiles.indexOf(file) < 0)
const manifest = {
publicPath: stats.publicPath,
all: allFiles,
initial: initialFiles,
async: asyncFiles,
modules: { /* [identifier: string]: Array<index: number> */ }
}
const assetModules = stats.modules.filter(m => m.assets.length)
const fileToIndex = asset => manifest.all.indexOf(getAssetName(asset))
stats.modules.forEach(m => {
// ignore modules duplicated in multiple chunks
if (m.chunks.length === 1) {
const cid = m.chunks[0]
const chunk = stats.chunks.find(c => c.id === cid)
if (!chunk || !chunk.files) {
return
}
const id = stripModuleIdHash(m.identifier)
const files = manifest.modules[hash(id)] = chunk.files.map(fileToIndex)
// find all asset modules associated with the same chunk
assetModules.forEach(m => {
if (m.chunks.some(id => id === cid)) {
files.push.apply(files, m.assets.map(fileToIndex))
}
})
}
})
const json = JSON.stringify(manifest, null, 2)
compilation.assets[this.options.filename] = {
source: () => json,
size: () => json.length
}
cb()
})
}
}

69
node_modules/vue/src/server/webpack-plugin/server.js generated vendored Normal file
View File

@@ -0,0 +1,69 @@
import { validate, isJS, getAssetName, onEmit } from './util'
export default class VueSSRServerPlugin {
constructor (options = {}) {
this.options = Object.assign({
filename: 'vue-ssr-server-bundle.json'
}, options)
}
apply (compiler) {
validate(compiler)
const stage = 'PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER'
onEmit(compiler, 'vue-server-plugin', stage, (compilation, cb) => {
const stats = compilation.getStats().toJson()
const entryName = Object.keys(stats.entrypoints)[0]
const entryInfo = stats.entrypoints[entryName]
if (!entryInfo) {
// #5553
return cb()
}
const entryAssets = entryInfo.assets
.map(getAssetName)
.filter(isJS)
if (entryAssets.length > 1) {
throw new Error(
`Server-side bundle should have one single entry file. ` +
`Avoid using CommonsChunkPlugin in the server config.`
)
}
const entry = entryAssets[0]
if (!entry || typeof entry !== 'string') {
throw new Error(
`Entry "${entryName}" not found. Did you specify the correct entry option?`
)
}
const bundle = {
entry,
files: {},
maps: {}
}
Object.keys(compilation.assets).forEach(name => {
if (isJS(name)) {
bundle.files[name] = compilation.assets[name].source()
} else if (name.match(/\.js\.map$/)) {
bundle.maps[name.replace(/\.map$/, '')] = JSON.parse(compilation.assets[name].source())
}
// do not emit anything else for server
delete compilation.assets[name]
})
const json = JSON.stringify(bundle, null, 2)
const filename = this.options.filename
compilation.assets[filename] = {
source: () => json,
size: () => json.length
}
cb()
})
}
}

73
node_modules/vue/src/server/webpack-plugin/util.js generated vendored Normal file
View File

@@ -0,0 +1,73 @@
const { red, yellow } = require('chalk')
const webpack = require('webpack')
const prefix = `[vue-server-renderer-webpack-plugin]`
const warn = exports.warn = msg => console.error(red(`${prefix} ${msg}\n`))
const tip = exports.tip = msg => console.log(yellow(`${prefix} ${msg}\n`))
const isWebpack5 = !!(webpack.version && webpack.version[0] > 4)
export const validate = compiler => {
if (compiler.options.target !== 'node') {
warn('webpack config `target` should be "node".')
}
if (compiler.options.output) {
if (compiler.options.output.library) {
// Webpack >= 5.0.0
if (compiler.options.output.library.type !== 'commonjs2') {
warn('webpack config `output.library.type` should be "commonjs2".')
}
} else if (compiler.options.output.libraryTarget !== 'commonjs2') {
// Webpack < 5.0.0
warn('webpack config `output.libraryTarget` should be "commonjs2".')
}
}
if (!compiler.options.externals) {
tip(
'It is recommended to externalize dependencies in the server build for ' +
'better build performance.'
)
}
}
export const onEmit = (compiler, name, stageName, hook) => {
if (isWebpack5) {
// Webpack >= 5.0.0
compiler.hooks.compilation.tap(name, compilation => {
if (compilation.compiler !== compiler) {
// Ignore child compilers
return
}
const stage = webpack.Compilation[stageName]
compilation.hooks.processAssets.tapAsync({ name, stage }, (assets, cb) => {
hook(compilation, cb)
})
})
} else if (compiler.hooks) {
// Webpack >= 4.0.0
compiler.hooks.emit.tapAsync(name, hook)
} else {
// Webpack < 4.0.0
compiler.plugin('emit', hook)
}
}
export const stripModuleIdHash = id => {
if (isWebpack5) {
// Webpack >= 5.0.0
return id.replace(/\|\w+$/, '')
}
// Webpack < 5.0.0
return id.replace(/\s\w+$/, '')
}
export const getAssetName = asset => {
if (typeof asset === 'string') {
return asset
}
return asset.name
}
export { isJS, isCSS } from '../util'

50
node_modules/vue/src/server/write.js generated vendored Normal file
View File

@@ -0,0 +1,50 @@
/* @flow */
const MAX_STACK_DEPTH = 800
const noop = _ => _
const defer = typeof process !== 'undefined' && process.nextTick
? process.nextTick
: typeof Promise !== 'undefined'
? fn => Promise.resolve().then(fn)
: typeof setTimeout !== 'undefined'
? setTimeout
: noop
if (defer === noop) {
throw new Error(
'Your JavaScript runtime does not support any asynchronous primitives ' +
'that are required by vue-server-renderer. Please use a polyfill for ' +
'either Promise or setTimeout.'
)
}
export function createWriteFunction (
write: (text: string, next: Function) => boolean,
onError: Function
): Function {
let stackDepth = 0
const cachedWrite = (text, next) => {
if (text && cachedWrite.caching) {
cachedWrite.cacheBuffer[cachedWrite.cacheBuffer.length - 1] += text
}
const waitForNext = write(text, next)
if (waitForNext !== true) {
if (stackDepth >= MAX_STACK_DEPTH) {
defer(() => {
try { next() } catch (e) {
onError(e)
}
})
} else {
stackDepth++
next()
stackDepth--
}
}
}
cachedWrite.caching = false
cachedWrite.cacheBuffer = []
cachedWrite.componentBuffer = []
return cachedWrite
}