Skip to content

Commit

Permalink
feat(ssr): add shouldPrefetch option
Browse files Browse the repository at this point in the history
close #5964
  • Loading branch information
yyx990803 committed Oct 6, 2017
1 parent f881dd1 commit 7bc899c
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 27 deletions.
3 changes: 3 additions & 0 deletions src/server/create-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type RenderOptions = {
inject?: boolean;
basedir?: string;
shouldPreload?: Function;
shouldPrefetch?: Function;
clientManifest?: ClientManifest;
runInNewContext?: boolean | 'once';
};
Expand All @@ -39,13 +40,15 @@ export function createRenderer ({
inject,
cache,
shouldPreload,
shouldPrefetch,
clientManifest
}: RenderOptions = {}): Renderer {
const render = createRenderFunction(modules, directives, isUnaryTag, cache)
const templateRenderer = new TemplateRenderer({
template,
inject,
shouldPreload,
shouldPrefetch,
clientManifest
})

Expand Down
59 changes: 33 additions & 26 deletions src/server/template-renderer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type TemplateRendererOptions = {
inject?: boolean;
clientManifest?: ClientManifest;
shouldPreload?: (file: string, type: string) => boolean;
shouldPrefetch?: (file: string, type: string) => boolean;
};

export type ClientManifest = {
Expand All @@ -30,7 +31,7 @@ export type ClientManifest = {
}
};

type PreloadFile = {
type Resource = {
file: string;
extension: string;
fileWithoutQuery: string;
Expand All @@ -43,8 +44,8 @@ export default class TemplateRenderer {
parsedTemplate: ParsedTemplate | null;
publicPath: string;
clientManifest: ClientManifest;
preloadFiles: Array<string>;
prefetchFiles: Array<string>;
preloadFiles: Array<Resource>;
prefetchFiles: Array<Resource>;
mapFiles: AsyncFileMapper;

constructor (options: TemplateRendererOptions) {
Expand All @@ -61,8 +62,8 @@ export default class TemplateRenderer {
const clientManifest = this.clientManifest = options.clientManifest
this.publicPath = clientManifest.publicPath.replace(/\/$/, '')
// preload/prefetch directives
this.preloadFiles = clientManifest.initial
this.prefetchFiles = clientManifest.async
this.preloadFiles = (clientManifest.initial || []).map(normalizeFile)
this.prefetchFiles = (clientManifest.async || []).map(normalizeFile)
// initial async chunk mapping
this.mapFiles = createMapper(clientManifest)
}
Expand Down Expand Up @@ -125,30 +126,21 @@ export default class TemplateRenderer {
return this.renderPreloadLinks(context) + this.renderPrefetchLinks(context)
}

getPreloadFiles (context: Object): Array<PreloadFile> {
getPreloadFiles (context: Object): Array<Resource> {
const usedAsyncFiles = this.getUsedAsyncFiles(context)
if (this.preloadFiles || usedAsyncFiles) {
return (this.preloadFiles || []).concat(usedAsyncFiles || []).map(file => {
const withoutQuery = file.replace(/\?.*/, '')
const extension = path.extname(withoutQuery).slice(1)
return {
file,
extension,
fileWithoutQuery: withoutQuery,
asType: getPreloadType(extension)
}
})
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 = ''
const shouldPreload = this.options.shouldPreload
// by default, we only preload scripts or css
if (!shouldPreload && asType !== 'script' && asType !== 'style') {
return ''
Expand All @@ -174,17 +166,20 @@ export default class TemplateRenderer {
}

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)
return usedAsyncFiles && usedAsyncFiles.some(f => f.file === file)
}
return this.prefetchFiles.map(file => {
if (!alreadyRendered(file)) {
return `<link rel="prefetch" href="${this.publicPath}/${file}">`
} else {
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 ''
Expand All @@ -205,20 +200,21 @@ export default class TemplateRenderer {

renderScripts (context: Object): string {
if (this.clientManifest) {
const initial = this.clientManifest.initial
const initial = this.preloadFiles
const async = this.getUsedAsyncFiles(context)
const needed = [initial[0]].concat(async || [], initial.slice(1))
return needed.filter(isJS).map(file => {
return needed.filter(({ file }) => isJS(file)).map(({ file }) => {
return `<script src="${this.publicPath}/${file}" defer></script>`
}).join('')
} else {
return ''
}
}

getUsedAsyncFiles (context: Object): ?Array<string> {
getUsedAsyncFiles (context: Object): ?Array<Resource> {
if (!context._mappedFiles && context._registeredComponents && this.mapFiles) {
context._mappedFiles = this.mapFiles(Array.from(context._registeredComponents))
const registered = Array.from(context._registeredComponents)
context._mappedFiles = this.mapFiles(registered).map(normalizeFile)
}
return context._mappedFiles
}
Expand All @@ -232,6 +228,17 @@ export default class TemplateRenderer {
}
}

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'
Expand Down
25 changes: 24 additions & 1 deletion test/ssr/ssr-template.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ describe('SSR: template option', () => {
(options.preloadOtherAssets ? `<link rel="preload" href="/test.png" as="image">` : ``) +
(options.preloadOtherAssets ? `<link rel="preload" href="/test.woff2" as="font" type="font/woff2" crossorigin>` : ``) +
// unused chunks should have prefetch
`<link rel="prefetch" href="/1.js">` +
(options.noPrefetch ? `` : `<link rel="prefetch" href="/1.js">`) +
// css assets should be loaded
`<link rel="stylesheet" href="/test.css">` +
`</head><body>` +
Expand Down Expand Up @@ -281,6 +281,29 @@ describe('SSR: template option', () => {
})
})

it('bundleRenderer + renderToStream + clientManifest + shouldPrefetch', done => {
createRendererWithManifest('split.js', {
runInNewContext,
shouldPrefetch: (file, type) => {
if (type === 'script') {
return false
}
}
}, renderer => {
const stream = renderer.renderToStream({ state: { a: 1 }})
let res = ''
stream.on('data', chunk => {
res += chunk.toString()
})
stream.on('end', () => {
expect(res).toContain(expectedHTMLWithManifest({
noPrefetch: true
}))
done()
})
})
})

it('bundleRenderer + renderToString + clientManifest + inject: false', done => {
createRendererWithManifest('split.js', {
runInNewContext,
Expand Down

0 comments on commit 7bc899c

Please sign in to comment.