575fcf3fcf
## summary - Explicitly define return types - Add type of [Observer](https://github.com/vercel/next.js/pull/20728/files#diff-5de64b97b2f26e4e41d197a8295e8750825c75b8ca557a4b947a4c3569345974R7)
145 lines
4.2 KiB
TypeScript
145 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 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(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} href the route href (file-system path)
|
|
*/
|
|
_isSsg(href: string): Promise<boolean> {
|
|
const { pathname: hrefPathname } = parseRelativeUrl(href)
|
|
const route = normalizeRoute(hrefPathname)
|
|
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)
|
|
}
|
|
}
|