Browse Source

adjust async chunk inference strategy

dev
Evan You 8 years ago
parent
commit
65cb5b4f78
  1. 1
      package.json
  2. 10
      src/server/bundle-renderer/create-bundle-renderer.js
  3. 10
      src/server/bundle-renderer/create-bundle-runner.js
  4. 5
      src/server/create-renderer.js
  5. 53
      src/server/template-renderer/create-async-file-mapper.js
  6. 37
      src/server/template-renderer/index.js
  7. 6
      test/ssr/async-loader.js
  8. 4
      test/ssr/compile-with-webpack.js
  9. 5
      test/ssr/fixtures/async-bar.js
  10. 5
      test/ssr/fixtures/async-foo.js
  11. 4
      test/ssr/fixtures/nested-cache.js

1
package.json

@ -77,6 +77,7 @@
"eslint-plugin-vue": "^2.0.0",
"file-loader": "^0.10.1",
"flow-bin": "^0.39.0",
"hash-sum": "^1.0.2",
"he": "^1.1.0",
"http-server": "^0.9.0",
"jasmine": "^2.5.2",

10
src/server/bundle-renderer/create-bundle-renderer.js

@ -32,7 +32,7 @@ export function createBundleRendererCreator (createRenderer: () => Renderer) {
bundle: string | RenderBundle,
rendererOptions?: RenderOptions = {}
) {
let files, entry, maps, moduleMappings
let files, entry, maps
let basedir = rendererOptions.basedir
const direct = rendererOptions.directMode
@ -63,7 +63,6 @@ export function createBundleRendererCreator (createRenderer: () => Renderer) {
files = bundle.files
basedir = basedir || bundle.basedir
maps = createSourceMapConsumers(bundle.maps)
moduleMappings = bundle.modules
if (typeof entry !== 'string' || typeof files !== 'object') {
throw new Error(INVALID_MSG)
}
@ -75,13 +74,6 @@ export function createBundleRendererCreator (createRenderer: () => Renderer) {
throw new Error(INVALID_MSG)
}
if (moduleMappings) {
rendererOptions = Object.assign({}, rendererOptions, {
serverManifest: {
modules: moduleMappings
}
})
}
const renderer = createRenderer(rendererOptions)
const run = createBundleRunner(entry, files, basedir, direct)

10
src/server/bundle-renderer/create-bundle-runner.js

@ -40,7 +40,7 @@ function compileModule (files, basedir) {
return script
}
function evaluateModule (filename, context, evaluatedFiles) {
function evaluateModule (filename, context, evaluatedFiles = {}) {
if (evaluatedFiles[filename]) {
return evaluatedFiles[filename]
}
@ -93,9 +93,8 @@ export function createBundleRunner (entry, files, basedir, direct) {
// on each render. Ensures entire application state is fresh for each
// render, but incurs extra evaluation cost.
return (_context = {}) => new Promise((resolve, reject) => {
const context = createContext(_context)
const evaluatedFiles = _context._evaluatedFiles = {}
const res = evaluate(entry, context, evaluatedFiles)
_context._registeredComponents = new Set()
const res = evaluate(entry, createContext(_context))
resolve(typeof res === 'function' ? res(_context) : res)
})
} else {
@ -105,12 +104,13 @@ export function createBundleRunner (entry, files, basedir, direct) {
// slightly differently.
const initialExposedContext = {}
const context = createContext(initialExposedContext)
const runner = evaluate(entry, context, {})
const runner = evaluate(entry, context)
if (typeof runner !== 'function') {
throw new Error('direct mode expects bundle export to be a function.')
}
return (_context = {}) => {
context.__VUE_SSR_CONTEXT__ = _context
_context._registeredComponents = new Set()
// vue-style-loader styles imported outside of component lifecycle hooks
if (initialExposedContext._styles) {
_context._styles = deepClone(initialExposedContext._styles)

5
src/server/create-renderer.js

@ -4,7 +4,7 @@ import RenderStream from './render-stream'
import TemplateRenderer from './template-renderer/index'
import { createWriteFunction } from './write'
import { createRenderFunction } from './render'
import type { ClientManifest, ServerManifest } from './template-renderer/index'
import type { ClientManifest } from './template-renderer/index'
export type Renderer = {
renderToString: (component: Component, cb: (err: ?Error, res: ?string) => void) => void;
@ -26,7 +26,6 @@ export type RenderOptions = {
basedir?: string;
directMode?: boolean;
shouldPreload?: Function;
serverManifest?: ServerManifest;
clientManifest?: ClientManifest;
};
@ -37,14 +36,12 @@ export function createRenderer ({
template,
cache,
shouldPreload,
serverManifest,
clientManifest
}: RenderOptions = {}): Renderer {
const render = createRenderFunction(modules, directives, isUnaryTag, cache)
const templateRenderer = new TemplateRenderer({
template,
shouldPreload,
serverManifest,
clientManifest
})

53
src/server/template-renderer/create-async-file-mapper.js

@ -1,25 +1,24 @@
/* @flow */
/**
* Creates a mapper that maps files used during a server-side render
* 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 { ServerManifest, ClientManifest } from './index'
import type { ClientManifest } from './index'
export type AsyncFileMapper = (files: Array<string>) => Array<string>;
export function createMapper (
serverManifest: ServerManifest,
clientManifest: ClientManifest
): AsyncFileMapper {
const fileMap = createFileMap(serverManifest, clientManifest)
return function mapFiles (files: Array<string>): Array<string> {
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 < files.length; i++) {
const mapped = fileMap.get(files[i])
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])
@ -30,27 +29,25 @@ export function createMapper (
}
}
function createFileMap (serverManifest, clientManifest) {
const fileMap = new Map()
Object.keys(serverManifest.modules).forEach(file => {
fileMap.set(file, mapFile(serverManifest.modules[file], clientManifest))
function createMap (clientManifest) {
const map = new Map()
Object.keys(clientManifest.modules).forEach(id => {
map.set(id, mapIdToFile(id, clientManifest))
})
return fileMap
return map
}
function mapFile (moduleIds, clientManifest) {
const files = new Set()
moduleIds.forEach(id => {
const fileIndices = clientManifest.modules[id]
if (fileIndices) {
fileIndices.forEach(index => {
const file = clientManifest.all[index]
// only include async files or non-js assets
if (clientManifest.async.indexOf(file) > -1 || !(/\.js($|\?)/.test(file))) {
files.add(file)
}
})
}
})
return Array.from(files)
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 assets
if (clientManifest.async.indexOf(file) > -1 || !(/\.js($|\?)/.test(file))) {
files.push(file)
}
})
}
return files
}

37
src/server/template-renderer/index.js

@ -14,17 +14,10 @@ export const isJS = (file: string): boolean => JS_RE.test(file)
type TemplateRendererOptions = {
template: ?string;
serverManifest?: ServerManifest;
clientManifest?: ClientManifest;
shouldPreload?: (file: string, type: string) => boolean;
};
export type ServerManifest = {
modules: {
[file: string]: Array<string>;
}
};
export type ClientManifest = {
publicPath: string;
all: Array<string>;
@ -42,7 +35,6 @@ export default class TemplateRenderer {
options: TemplateRendererOptions;
parsedTemplate: ParsedTemplate | null;
publicPath: string;
serverManifest: ServerManifest;
clientManifest: ClientManifest;
preloadFiles: Array<string>;
prefetchFiles: Array<string>;
@ -57,15 +49,14 @@ export default class TemplateRenderer {
: null
// extra functionality with client manifest
if (options.serverManifest && options.clientManifest) {
const serverManifest = this.serverManifest = options.serverManifest
if (options.clientManifest) {
const clientManifest = this.clientManifest = options.clientManifest
this.publicPath = clientManifest.publicPath.replace(/\/$/, '')
// preload/prefetch drectives
this.preloadFiles = clientManifest.initial
this.prefetchFiles = clientManifest.async
// initial async chunk mapping
this.mapFiles = createMapper(serverManifest, clientManifest)
this.mapFiles = createMapper(clientManifest)
}
}
@ -125,7 +116,7 @@ export default class TemplateRenderer {
renderPrefetchLinks (context: Object): string {
if (this.prefetchFiles) {
const usedAsyncFiles = this.getUsedAsyncFiles(context, true)
const usedAsyncFiles = this.getUsedAsyncFiles(context)
const alreadyRendered = file => {
return usedAsyncFiles && usedAsyncFiles.some(f => f === file)
}
@ -162,25 +153,11 @@ export default class TemplateRenderer {
}
}
getUsedAsyncFiles (context: Object, raw?: boolean): ?Array<string> {
if (!context._mappedfiles && context._evaluatedFiles && this.mapFiles) {
let mapped = this.mapFiles(Object.keys(context._evaluatedFiles))
context._rawMappedFiles = mapped
// if a file has a no-css version (produced by vue-ssr-webpack-plugin),
// we should use that instead.
const noCssHash = this.clientManifest && this.clientManifest.hasNoCssVersion
if (noCssHash) {
mapped = mapped.map(file => {
return noCssHash[file]
? file.replace(JS_RE, '.no-css.js')
: file
})
}
context._mappedFiles = mapped
getUsedAsyncFiles (context: Object): ?Array<string> {
if (!context._mappedfiles && context._registeredComponents && this.mapFiles) {
context._mappedFiles = this.mapFiles(Array.from(context._registeredComponents))
}
return raw
? context._rawMappedFiles
: context._mappedFiles
return context._mappedFiles
}
// create a transform stream

6
test/ssr/async-loader.js

@ -0,0 +1,6 @@
const hash = require('hash-sum')
module.exports = function (code) {
const id = hash(this.request) // simulating vue-loader module id injection
return code.replace('__MODULE_ID__', id)
}

4
test/ssr/compile-with-webpack.js

@ -11,6 +11,10 @@ export function compileWithWebpack (file, extraConfig, cb) {
test: /\.js$/,
loader: 'babel-loader'
},
{
test: /async-.*\.js$/,
loader: require.resolve('./async-loader')
},
{
test: /\.(png|woff2)$/,
loader: 'file-loader',

5
test/ssr/fixtures/async-bar.js

@ -1,4 +1,9 @@
/* globals __VUE_SSR_CONTEXT__ */
module.exports = {
beforeCreate () {
__VUE_SSR_CONTEXT__._registeredComponents.add('__MODULE_ID__')
},
render (h) {
return h('div', 'async bar')
}

5
test/ssr/fixtures/async-foo.js

@ -1,8 +1,13 @@
/* globals __VUE_SSR_CONTEXT__ */
// import image and font
import font from './test.woff2'
import image from './test.png'
module.exports = {
beforeCreate () {
__VUE_SSR_CONTEXT__._registeredComponents.add('__MODULE_ID__')
},
render (h) {
return h('div', `async ${font} ${image}`)
}

4
test/ssr/fixtures/nested-cache.js

@ -1,7 +1,9 @@
/* globals __VUE_SSR_CONTEXT__ */
import Vue from '../../../dist/vue.runtime.common.js'
function register (id, context) {
context = context || __VUE_SSR_CONTEXT__ // eslint-disable-line
context = context || __VUE_SSR_CONTEXT__
context.registered.push(id)
}

Loading…
Cancel
Save