rsnext/packages/next/client/page-loader.ts
JJ Kasper d130f63c41
Add handling fo beforeFiles, afterFiles, and fallback rewrites (#23407)
This adds support for returning an object from `rewrites` in `next.config.js` with `beforeFiles`, `afterFiles`, and `fallback` to allow specifying rewrites at different stages of routing. The existing support for returning an array for rewrites is still supported and behaves the same way. The documentation has been updated to include information on these new stages that can be rewritten and removes the outdated note of rewrites not being able to override pages. 



## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.

## Documentation / Examples

- [ ] Make sure the linting passes
2021-03-26 15:19:48 +00:00

147 lines
4.2 KiB
TypeScript

import { ComponentType } from 'react'
import { ClientSsgManifest } from '../build'
import {
addBasePath,
addLocale,
interpolateAs,
} from '../next-server/lib/router/router'
import getAssetPathFromRoute from '../next-server/lib/router/utils/get-asset-path-from-route'
import { isDynamicRoute } from '../next-server/lib/router/utils/is-dynamic'
import { parseRelativeUrl } from '../next-server/lib/router/utils/parse-relative-url'
import { removePathTrailingSlash } from './normalize-trailing-slash'
import createRouteLoader, {
getClientBuildManifest,
RouteLoader,
} from './route-loader'
function normalizeRoute(route: string): string {
if (route[0] !== '/') {
throw new Error(`Route name should start with a "/", got "${route}"`)
}
if (route === '/') return route
return route.replace(/\/$/, '')
}
export type StyleSheetTuple = { href: string; text: string }
export type GoodPageCache = {
page: ComponentType
mod: any
styleSheets: StyleSheetTuple[]
}
export default class PageLoader {
private buildId: string
private assetPrefix: string
private promisedSsgManifest?: Promise<ClientSsgManifest>
private promisedDevPagesManifest?: Promise<any>
public routeLoader: RouteLoader
constructor(buildId: string, assetPrefix: string) {
this.routeLoader = createRouteLoader(assetPrefix)
this.buildId = buildId
this.assetPrefix = assetPrefix
/** @type {Promise<Set<string>>} */
this.promisedSsgManifest = new Promise((resolve) => {
if ((window as any).__SSG_MANIFEST) {
resolve((window as any).__SSG_MANIFEST)
} else {
;(window as any).__SSG_MANIFEST_CB = () => {
resolve((window as any).__SSG_MANIFEST)
}
}
})
}
getPageList() {
if (process.env.NODE_ENV === 'production') {
return getClientBuildManifest().then((manifest) => manifest.sortedPages)
} else {
if ((window as any).__DEV_PAGES_MANIFEST) {
return (window as any).__DEV_PAGES_MANIFEST.pages
} else {
if (!this.promisedDevPagesManifest) {
this.promisedDevPagesManifest = fetch(
`${this.assetPrefix}/_next/static/development/_devPagesManifest.json`
)
.then((res) => res.json())
.then((manifest) => {
;(window as any).__DEV_PAGES_MANIFEST = manifest
return manifest.pages
})
.catch((err) => {
console.log(`Failed to fetch devPagesManifest`, err)
})
}
return this.promisedDevPagesManifest
}
}
}
/**
* @param {string} href the route href (file-system path)
* @param {string} asPath the URL as shown in browser (virtual path); used for dynamic routes
* @returns {string}
*/
getDataHref(
href: string,
asPath: string,
ssg: boolean,
locale?: string | false
): string {
const { pathname: hrefPathname, query, search } = parseRelativeUrl(href)
const { pathname: asPathname } = parseRelativeUrl(asPath)
const route = normalizeRoute(hrefPathname)
const getHrefForSlug = (path: string) => {
const dataRoute = getAssetPathFromRoute(
removePathTrailingSlash(addLocale(path, locale)),
'.json'
)
return addBasePath(
`/_next/data/${this.buildId}${dataRoute}${ssg ? '' : search}`
)
}
const isDynamic: boolean = isDynamicRoute(route)
const interpolatedRoute = isDynamic
? interpolateAs(hrefPathname, asPathname, query).result
: ''
return isDynamic
? interpolatedRoute && getHrefForSlug(interpolatedRoute)
: getHrefForSlug(route)
}
/**
* @param {string} route - the route (file-system path)
*/
_isSsg(route: string): Promise<boolean> {
return this.promisedSsgManifest!.then((s: ClientSsgManifest) =>
s.has(route)
)
}
loadPage(route: string): Promise<GoodPageCache> {
return this.routeLoader.loadRoute(route).then((res) => {
if ('component' in res) {
return {
page: res.component,
mod: res.exports,
styleSheets: res.styles.map((o) => ({
href: o.href,
text: o.content,
})),
}
}
throw res.error
})
}
prefetch(route: string): Promise<void> {
return this.routeLoader.prefetch(route)
}
}