Skip to content

Commit

Permalink
Prefetch SSG Data
Browse files Browse the repository at this point in the history
  • Loading branch information
Timer committed Jan 16, 2020
1 parent 57ec121 commit 190a914
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 11 deletions.
44 changes: 42 additions & 2 deletions packages/next/build/index.ts
@@ -1,5 +1,6 @@
import chalk from 'chalk'
import ciEnvironment from 'ci-info'
import devalue from 'devalue'
import findUp from 'find-up'
import fs from 'fs'
import Worker from 'jest-worker'
Expand All @@ -11,10 +12,10 @@ import { promisify } from 'util'
import formatWebpackMessages from '../client/dev/error-overlay/format-webpack-messages'
import checkCustomRoutes, {
getRedirectStatus,
RouteType,
Header,
Redirect,
Rewrite,
Header,
RouteType,
} from '../lib/check-custom-routes'
import { PUBLIC_DIR_MIDDLEWARE_CONFLICT } from '../lib/constants'
import { findPagesDir } from '../lib/find-pages-dir'
Expand All @@ -23,6 +24,7 @@ import { recursiveReadDir } from '../lib/recursive-readdir'
import { verifyTypeScriptSetup } from '../lib/verifyTypeScriptSetup'
import {
BUILD_MANIFEST,
CLIENT_STATIC_FILES_PATH,
EXPORT_DETAIL,
EXPORT_MARKER,
PAGES_MANIFEST,
Expand Down Expand Up @@ -726,6 +728,14 @@ export default async function build(dir: string, conf = null): Promise<void> {
JSON.stringify(prerenderManifest),
'utf8'
)

generateClientSsgManifest(prerenderManifest, { distDir, buildId })
} else {
// Generate empty version if not used
generateClientSsgManifest(
{ version: 0, routes: {}, dynamicRoutes: {} },
{ distDir, buildId }
)
}

await fsWriteFile(
Expand Down Expand Up @@ -824,3 +834,33 @@ export default async function build(dir: string, conf = null): Promise<void> {

await telemetry.flush()
}

function generateClientSsgManifest(
prerenderManifest: PrerenderManifest,
{ buildId, distDir }: { buildId: string; distDir: string }
) {
const regexOperatorsRegex = /[|\\{}()[\]^$+*?.-]/g
const ssgMatchers: RegExp[] = []
Object.keys(prerenderManifest.routes).forEach(route =>
ssgMatchers.push(
new RegExp('^' + route.replace(regexOperatorsRegex, '\\$&') + '$')
)
)
Object.values(prerenderManifest.dynamicRoutes).forEach(entry =>
ssgMatchers.push(new RegExp(entry.routeRegex))
)

const clientSsgManifestPaths = [
'_ssgManifest.js',
'_ssgManifest.module.js',
].map(f => path.join(`${CLIENT_STATIC_FILES_PATH}/${buildId}`, f))
const clientSsgManifestContent = `self.__SSG_MANIFEST = ${devalue(
ssgMatchers
)};self.__SSG_MANIFEST_CB && self.__SSG_MANIFEST_CB()`
clientSsgManifestPaths.forEach(clientSsgManifestPath =>
fs.writeFileSync(
path.join(distDir, clientSsgManifestPath),
clientSsgManifestContent
)
)
}
11 changes: 11 additions & 0 deletions packages/next/build/webpack/plugins/build-manifest-plugin.ts
Expand Up @@ -149,6 +149,17 @@ export default class BuildManifestPlugin {
}
}

// Add the runtime ssg manifest file (generated later in the build) as
// a dependency for the app.
assetMap.pages['/_app'].push(
`${CLIENT_STATIC_FILES_PATH}/${this.buildId}/_ssgManifest.js`
)
if (this.modern) {
assetMap.pages['/_app'].push(
`${CLIENT_STATIC_FILES_PATH}/${this.buildId}/_ssgManifest.module.js`
)
}

assetMap.pages = Object.keys(assetMap.pages)
.sort()
// eslint-disable-next-line
Expand Down
14 changes: 9 additions & 5 deletions packages/next/client/link.tsx
Expand Up @@ -127,14 +127,15 @@ class Link extends Component<LinkProps> {
this.cleanUpListeners()
}

getHref() {
getHrefs(): { href: string; as: string } {
const { pathname } = window.location
const { href: parsedHref } = this.formatUrls(this.props.href, this.props.as)
return resolve(pathname, parsedHref)
const { href, as } = this.formatUrls(this.props.href, this.props.as)
const parsedHref = resolve(pathname, href)
return { href: parsedHref, as: as ? resolve(pathname, as) : parsedHref }
}

handleRef(ref: Element) {
const isPrefetched = prefetched[this.getHref()]
const isPrefetched = prefetched[this.getHrefs().href]
if (this.p && IntersectionObserver && ref && ref.tagName) {
this.cleanUpListeners()

Expand Down Expand Up @@ -204,8 +205,11 @@ class Link extends Component<LinkProps> {
prefetch() {
if (!this.p || typeof window === 'undefined') return
// Prefetch the JSON page if asked (only in the client)
const href = this.getHref()
const { href, as } = this.getHrefs()
Router.prefetch(href)
// Cast to any because this is a private method, might be public in the
// future
;(Router as any).prefetchAs(as)
prefetched[href] = true
}

Expand Down
33 changes: 30 additions & 3 deletions packages/next/client/page-loader.js
Expand Up @@ -56,12 +56,18 @@ export default class PageLoader {
if (window.__BUILD_MANIFEST) {
resolve(window.__BUILD_MANIFEST)
} else {
window.__BUILD_MANIFEST_CB = () => {
resolve(window.__BUILD_MANIFEST)
}
window.__BUILD_MANIFEST_CB = () => resolve(window.__BUILD_MANIFEST)
}
})
}
/** @type {Promise<RegExp[]>} */
this.promisedSsgManifest = new Promise(resolve => {
if (window.__SSG_MANIFEST) {
resolve(window.__SSG_MANIFEST)
} else {
window.__SSG_MANIFEST_CB = () => resolve(window.__SSG_MANIFEST)
}
})
}

// Returns a promise for the dependencies for a particular route
Expand All @@ -76,6 +82,22 @@ export default class PageLoader {
)
}

/** @param {string} asPath */
prefetchAs(asPath) {
asPath = normalizeRoute(asPath)
return this.promisedSsgManifest.then(
m =>
m.some(r => r.test(asPath)) &&
appendLink(
`${this.assetPrefix}/_next/${this.buildId}/${
asPath === '/' ? '/index' : asPath
}.json`,
relPrefetch,
'fetch'
)
)
}

loadPage(route) {
return this.loadPageScript(route).then(v => v.page)
}
Expand Down Expand Up @@ -206,6 +228,10 @@ export default class PageLoader {
register()
}

/**
* @param {string} route
* @param {boolean} [isDependency]
*/
prefetch(route, isDependency) {
// https://github.com/GoogleChromeLabs/quicklink/blob/453a661fa1fa940e2d2e044452398e38c67a98fb/src/index.mjs#L115-L118
// License: Apache 2.0
Expand All @@ -215,6 +241,7 @@ export default class PageLoader {
if (cn.saveData || /2g/.test(cn.effectiveType)) return Promise.resolve()
}

/** @type {string} */
let url
if (isDependency) {
url = route
Expand Down
10 changes: 10 additions & 0 deletions packages/next/next-server/lib/router/router.ts
Expand Up @@ -620,6 +620,16 @@ export default class Router implements BaseRouter {
})
}

prefetchAs(url: string): Promise<void> {
return new Promise((resolve, reject) => {
const { pathname, protocol } = parse(url)
if (!pathname || protocol || process.env.NODE_ENV !== 'production') {
return
}
this.pageLoader.prefetchAs(pathname).then(resolve, reject)
})
}

async fetchComponent(route: string): Promise<ComponentType> {
let cancelled = false
const cancel = (this.clc = () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/next/pages/_document.tsx
Expand Up @@ -45,7 +45,7 @@ function getOptionalModernScriptVariant(path: string) {
}

function isLowPriority(file: string) {
return file.includes('_buildManifest')
return file.includes('_buildManifest') || file.includes('_ssgManifest')
}

/**
Expand Down

0 comments on commit 190a914

Please sign in to comment.