init
This commit is contained in:
151
node_modules/vue/src/server/bundle-renderer/create-bundle-renderer.js
generated
vendored
Normal file
151
node_modules/vue/src/server/bundle-renderer/create-bundle-renderer.js
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
150
node_modules/vue/src/server/bundle-renderer/create-bundle-runner.js
generated
vendored
Normal file
150
node_modules/vue/src/server/bundle-renderer/create-bundle-runner.js
generated
vendored
Normal 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))
|
||||
})
|
||||
}
|
||||
}
|
45
node_modules/vue/src/server/bundle-renderer/source-map-support.js
generated
vendored
Normal file
45
node_modules/vue/src/server/bundle-renderer/source-map-support.js
generated
vendored
Normal 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
37
node_modules/vue/src/server/create-basic-renderer.js
generated
vendored
Normal 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
152
node_modules/vue/src/server/create-renderer.js
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
264
node_modules/vue/src/server/optimizing-compiler/codegen.js
generated
vendored
Normal file
264
node_modules/vue/src/server/optimizing-compiler/codegen.js
generated
vendored
Normal 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('+')
|
||||
}
|
20
node_modules/vue/src/server/optimizing-compiler/index.js
generated
vendored
Normal file
20
node_modules/vue/src/server/optimizing-compiler/index.js
generated
vendored
Normal 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
|
||||
}
|
||||
})
|
124
node_modules/vue/src/server/optimizing-compiler/modules.js
generated
vendored
Normal file
124
node_modules/vue/src/server/optimizing-compiler/modules.js
generated
vendored
Normal 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'
|
||||
})`
|
||||
}]
|
||||
}
|
||||
}
|
141
node_modules/vue/src/server/optimizing-compiler/optimizer.js
generated
vendored
Normal file
141
node_modules/vue/src/server/optimizing-compiler/optimizer.js
generated
vendored
Normal 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')
|
||||
)
|
||||
}
|
150
node_modules/vue/src/server/optimizing-compiler/runtime-helpers.js
generated
vendored
Normal file
150
node_modules/vue/src/server/optimizing-compiler/runtime-helpers.js
generated
vendored
Normal 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
130
node_modules/vue/src/server/render-context.js
generated
vendored
Normal 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
95
node_modules/vue/src/server/render-stream.js
generated
vendored
Normal 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
437
node_modules/vue/src/server/render.js
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
57
node_modules/vue/src/server/template-renderer/create-async-file-mapper.js
generated
vendored
Normal file
57
node_modules/vue/src/server/template-renderer/create-async-file-mapper.js
generated
vendored
Normal 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
277
node_modules/vue/src/server/template-renderer/index.js
generated
vendored
Normal 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 ''
|
||||
}
|
||||
}
|
42
node_modules/vue/src/server/template-renderer/parse-template.js
generated
vendored
Normal file
42
node_modules/vue/src/server/template-renderer/parse-template.js
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
82
node_modules/vue/src/server/template-renderer/template-stream.js
generated
vendored
Normal file
82
node_modules/vue/src/server/template-renderer/template-stream.js
generated
vendored
Normal 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
18
node_modules/vue/src/server/util.js
generated
vendored
Normal 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
67
node_modules/vue/src/server/webpack-plugin/client.js
generated
vendored
Normal 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
69
node_modules/vue/src/server/webpack-plugin/server.js
generated
vendored
Normal 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
73
node_modules/vue/src/server/webpack-plugin/util.js
generated
vendored
Normal 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
50
node_modules/vue/src/server/write.js
generated
vendored
Normal 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
|
||||
}
|
Reference in New Issue
Block a user