Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefetch SSG Data #10127

Merged
merged 48 commits into from Mar 2, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
190a914
Prefetch SSG Data
Timer Jan 16, 2020
d22efaa
Update packages/next/client/page-loader.js
Timer Jan 16, 2020
630f516
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Jan 16, 2020
8c1fcef
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Feb 16, 2020
eee688c
Revert router.ts
Timer Feb 16, 2020
555b097
Revert link.tsx
Timer Feb 16, 2020
1490cd2
undo change
Timer Feb 16, 2020
5ddf762
Merge branch 'enhancement/prefetch-ssg-data' of github.com:Timer/next…
Timer Feb 16, 2020
98852c4
mimmic existing
Timer Feb 16, 2020
ed7bfc1
simplify
Timer Feb 16, 2020
098391f
Prefetch href and asPath
Timer Feb 16, 2020
2643285
fix load
Timer Feb 16, 2020
6a25c93
dedupe prefetchAs
Timer Feb 16, 2020
825ee1a
Inject script tag on hover
Timer Feb 16, 2020
928717c
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Feb 28, 2020
ce085a5
comment prefetchAs
Timer Feb 28, 2020
74ff44c
minify code
Timer Feb 29, 2020
b4585d9
introduce lazy files
Timer Feb 29, 2020
80621c6
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Feb 29, 2020
612dfb6
Add some breathing room
Timer Feb 29, 2020
ec5ce44
correct default type
Timer Feb 29, 2020
0595464
Prefetch non-dynamic data
Timer Feb 29, 2020
6e623d8
Prefetch dynamic route data
Timer Mar 1, 2020
00109cb
Fix size test
Timer Mar 1, 2020
c39219d
Humanize code
Timer Mar 1, 2020
ff7486c
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 1, 2020
e27d49d
add tests
Timer Mar 1, 2020
3915ace
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 1, 2020
24ae003
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 1, 2020
103a2ca
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 1, 2020
c9e6123
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 1, 2020
b082c6d
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 1, 2020
2293750
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 1, 2020
e4a12ab
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 1, 2020
363928b
Disable code
Timer Mar 2, 2020
1c15762
Only generate modern version in modern mode
Timer Mar 2, 2020
0a23d7c
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 2, 2020
c0772ce
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 2, 2020
fffeb50
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 2, 2020
17fb3e4
Extract function helper
Timer Mar 2, 2020
e799bf3
add comments
Timer Mar 2, 2020
3b55b4f
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 2, 2020
2ba0b8a
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 2, 2020
936bb98
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 2, 2020
939e8be
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 2, 2020
bd3f208
Merge branch 'canary' into enhancement/prefetch-ssg-data
Timer Mar 2, 2020
c85a3d9
Filter out dynamic route to simplify manifest size
Timer Mar 2, 2020
6ead1b4
add test
Timer Mar 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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',
Timer marked this conversation as resolved.
Show resolved Hide resolved
].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)
Timer marked this conversation as resolved.
Show resolved Hide resolved
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/data/${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