582cb3766d
x-ref #39199 The change in #39199 isn't correct. Middleware manifest should only contain middleware route, so that when router navigates, it only try to apply middleware instead of checking all edge routes. This PR also changes the middleware manifest global value from array to object for easier access ## Bug - [ ] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md`
174 lines
5.5 KiB
TypeScript
174 lines
5.5 KiB
TypeScript
import type { ComponentType } from 'react'
|
|
import type { RouteLoader } from './route-loader'
|
|
import { addBasePath } from './add-base-path'
|
|
import { interpolateAs } from '../shared/lib/router/router'
|
|
import getAssetPathFromRoute from '../shared/lib/router/utils/get-asset-path-from-route'
|
|
import { addLocale } from './add-locale'
|
|
import { isDynamicRoute } from '../shared/lib/router/utils/is-dynamic'
|
|
import { parseRelativeUrl } from '../shared/lib/router/utils/parse-relative-url'
|
|
import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash'
|
|
import { createRouteLoader, getClientBuildManifest } from './route-loader'
|
|
|
|
declare global {
|
|
interface Window {
|
|
__DEV_MIDDLEWARE_MANIFEST?: { location?: string }
|
|
__DEV_PAGES_MANIFEST?: { pages: string[] }
|
|
__SSG_MANIFEST_CB?: () => void
|
|
__SSG_MANIFEST?: Set<string>
|
|
}
|
|
}
|
|
|
|
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<Set<string>>
|
|
private promisedDevPagesManifest?: Promise<string[]>
|
|
private promisedMiddlewareManifest?: Promise<{ location: string }>
|
|
|
|
public routeLoader: RouteLoader
|
|
|
|
constructor(buildId: string, assetPrefix: string) {
|
|
this.routeLoader = createRouteLoader(assetPrefix)
|
|
|
|
this.buildId = buildId
|
|
this.assetPrefix = assetPrefix
|
|
|
|
this.promisedSsgManifest = new Promise((resolve) => {
|
|
if (window.__SSG_MANIFEST) {
|
|
resolve(window.__SSG_MANIFEST)
|
|
} else {
|
|
window.__SSG_MANIFEST_CB = () => {
|
|
resolve(window.__SSG_MANIFEST!)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
getPageList() {
|
|
if (process.env.NODE_ENV === 'production') {
|
|
return getClientBuildManifest().then((manifest) => manifest.sortedPages)
|
|
} else {
|
|
if (window.__DEV_PAGES_MANIFEST) {
|
|
return window.__DEV_PAGES_MANIFEST.pages
|
|
} else {
|
|
if (!this.promisedDevPagesManifest) {
|
|
// TODO: Decide what should happen when fetching fails instead of asserting
|
|
// @ts-ignore
|
|
this.promisedDevPagesManifest = fetch(
|
|
`${this.assetPrefix}/_next/static/development/_devPagesManifest.json`
|
|
)
|
|
.then((res) => res.json())
|
|
.then((manifest: { pages: string[] }) => {
|
|
window.__DEV_PAGES_MANIFEST = manifest
|
|
return manifest.pages
|
|
})
|
|
.catch((err) => {
|
|
console.log(`Failed to fetch devPagesManifest`, err)
|
|
})
|
|
}
|
|
// TODO Remove this assertion as this could be undefined
|
|
return this.promisedDevPagesManifest!
|
|
}
|
|
}
|
|
}
|
|
|
|
getMiddleware() {
|
|
if (process.env.NODE_ENV === 'production') {
|
|
const middlewareRegex = process.env.__NEXT_MIDDLEWARE_REGEX
|
|
window.__MIDDLEWARE_MANIFEST = middlewareRegex
|
|
? { location: middlewareRegex }
|
|
: undefined
|
|
return window.__MIDDLEWARE_MANIFEST
|
|
} else {
|
|
if (window.__DEV_MIDDLEWARE_MANIFEST) {
|
|
return window.__DEV_MIDDLEWARE_MANIFEST
|
|
} else {
|
|
if (!this.promisedMiddlewareManifest) {
|
|
// TODO: Decide what should happen when fetching fails instead of asserting
|
|
// @ts-ignore
|
|
this.promisedMiddlewareManifest = fetch(
|
|
`${this.assetPrefix}/_next/static/${this.buildId}/_devMiddlewareManifest.json`
|
|
)
|
|
.then((res) => res.json())
|
|
.then((manifest: { location?: string }) => {
|
|
window.__DEV_MIDDLEWARE_MANIFEST = manifest
|
|
return manifest
|
|
})
|
|
.catch((err) => {
|
|
console.log(`Failed to fetch _devMiddlewareManifest`, err)
|
|
})
|
|
}
|
|
// TODO Remove this assertion as this could be undefined
|
|
return this.promisedMiddlewareManifest!
|
|
}
|
|
}
|
|
}
|
|
|
|
getDataHref(params: {
|
|
asPath: string
|
|
href: string
|
|
locale?: string | false
|
|
skipInterpolation?: boolean
|
|
}): string {
|
|
const { asPath, href, locale } = params
|
|
const { pathname: hrefPathname, query, search } = parseRelativeUrl(href)
|
|
const { pathname: asPathname } = parseRelativeUrl(asPath)
|
|
const route = removeTrailingSlash(hrefPathname)
|
|
if (route[0] !== '/') {
|
|
throw new Error(`Route name should start with a "/", got "${route}"`)
|
|
}
|
|
|
|
const getHrefForSlug = (path: string) => {
|
|
const dataRoute = getAssetPathFromRoute(
|
|
removeTrailingSlash(addLocale(path, locale)),
|
|
'.json'
|
|
)
|
|
return addBasePath(
|
|
`/_next/data/${this.buildId}${dataRoute}${search}`,
|
|
true
|
|
)
|
|
}
|
|
|
|
return getHrefForSlug(
|
|
params.skipInterpolation
|
|
? asPathname
|
|
: isDynamicRoute(route)
|
|
? interpolateAs(hrefPathname, asPathname, query).result
|
|
: route
|
|
)
|
|
}
|
|
|
|
/**
|
|
* @param {string} route - the route (file-system path)
|
|
*/
|
|
_isSsg(route: string): Promise<boolean> {
|
|
return this.promisedSsgManifest.then((manifest) => manifest.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)
|
|
}
|
|
}
|