rsnext/packages/next/next-server/lib/router/router.ts

1344 lines
38 KiB
TypeScript
Raw Normal View History

/* global __NEXT_DATA__ */
// tslint:disable:no-console
import { ParsedUrlQuery } from 'querystring'
import { ComponentType } from 'react'
import { UrlObject } from 'url'
import {
normalizePathTrailingSlash,
removePathTrailingSlash,
} from '../../../client/normalize-trailing-slash'
import { GoodPageCache, StyleSheetTuple } from '../../../client/page-loader'
import {
getClientBuildManifest,
isAssetError,
markAssetError,
} from '../../../client/route-loader'
import { denormalizePagePath } from '../../server/denormalize-page-path'
import mitt, { MittEmitter } from '../mitt'
import {
AppContextType,
formatWithValidation,
getLocationOrigin,
getURL,
loadGetInitialProps,
NextPageContext,
ST,
} from '../utils'
import escapePathDelimiters from './utils/escape-path-delimiters'
import { isDynamicRoute } from './utils/is-dynamic'
import { parseRelativeUrl } from './utils/parse-relative-url'
import { searchParamsToUrlQuery } from './utils/querystring'
import resolveRewrites from './utils/resolve-rewrites'
import { getRouteMatcher } from './utils/route-matcher'
import { getRouteRegex } from './utils/route-regex'
2020-08-01 20:13:03 +02:00
interface TransitionOptions {
shallow?: boolean
locale?: string | false
2020-08-01 20:13:03 +02:00
}
interface NextHistoryState {
url: string
as: string
options: TransitionOptions
}
type HistoryState = null | { __N: false } | ({ __N: true } & NextHistoryState)
const basePath = (process.env.__NEXT_ROUTER_BASEPATH as string) || ''
2020-07-21 19:33:11 +02:00
function buildCancellationError() {
return Object.assign(new Error('Route Cancelled'), {
cancelled: true,
})
}
function addPathPrefix(path: string, prefix?: string) {
return prefix && path.startsWith('/')
? path === '/'
? normalizePathTrailingSlash(prefix)
: `${prefix}${path}`
: path
}
export function addLocale(
path: string,
locale?: string | false,
defaultLocale?: string
) {
if (process.env.__NEXT_I18N_SUPPORT) {
return locale &&
locale !== defaultLocale &&
!path.startsWith('/' + locale + '/') &&
path !== '/' + locale
? addPathPrefix(path, '/' + locale)
: path
}
return path
}
export function delLocale(path: string, locale?: string) {
if (process.env.__NEXT_I18N_SUPPORT) {
return locale &&
(path.startsWith('/' + locale + '/') || path === '/' + locale)
? path.substr(locale.length + 1) || '/'
: path
}
return path
}
export function hasBasePath(path: string): boolean {
return path === basePath || path.startsWith(basePath + '/')
}
export function addBasePath(path: string): string {
// we only add the basepath on relative urls
return addPathPrefix(path, basePath)
}
export function delBasePath(path: string): string {
return path.slice(basePath.length) || '/'
}
/**
* Detects whether a given url is routable by the Next.js router (browser only).
*/
export function isLocalURL(url: string): boolean {
if (url.startsWith('/')) return true
try {
// absolute urls can be local if they are on the same origin
const locationOrigin = getLocationOrigin()
const resolved = new URL(url, locationOrigin)
return resolved.origin === locationOrigin && hasBasePath(resolved.pathname)
} catch (_) {
return false
}
}
type Url = UrlObject | string
export function interpolateAs(
route: string,
asPathname: string,
query: ParsedUrlQuery
) {
let interpolatedRoute = ''
const dynamicRegex = getRouteRegex(route)
const dynamicGroups = dynamicRegex.groups
const dynamicMatches =
// Try to match the dynamic route against the asPath
(asPathname !== route ? getRouteMatcher(dynamicRegex)(asPathname) : '') ||
// Fall back to reading the values from the href
// TODO: should this take priority; also need to change in the router.
query
interpolatedRoute = route
const params = Object.keys(dynamicGroups)
if (
!params.every((param) => {
let value = dynamicMatches[param] || ''
const { repeat, optional } = dynamicGroups[param]
// support single-level catch-all
// TODO: more robust handling for user-error (passing `/`)
let replaced = `[${repeat ? '...' : ''}${param}]`
if (optional) {
replaced = `${!value ? '/' : ''}[${replaced}]`
}
if (repeat && !Array.isArray(value)) value = [value]
return (
(optional || param in dynamicMatches) &&
// Interpolate group into data URL if present
(interpolatedRoute =
interpolatedRoute!.replace(
replaced,
repeat
? (value as string[]).map(escapePathDelimiters).join('/')
: escapePathDelimiters(value as string)
) || '/')
)
})
) {
interpolatedRoute = '' // did not satisfy all requirements
// n.b. We ignore this error because we handle warning for this case in
// development in the `<Link>` component directly.
}
return {
params,
result: interpolatedRoute,
}
}
function omitParmsFromQuery(query: ParsedUrlQuery, params: string[]) {
const filteredQuery: ParsedUrlQuery = {}
Object.keys(query).forEach((key) => {
if (!params.includes(key)) {
filteredQuery[key] = query[key]
}
})
return filteredQuery
}
/**
* Resolves a given hyperlink with a certain router state (basePath not included).
* Preserves absolute urls.
*/
export function resolveHref(
currentPath: string,
href: Url,
resolveAs?: boolean
): string {
// we use a dummy base url for relative urls
const base = new URL(currentPath, 'http://n')
const urlAsString =
typeof href === 'string' ? href : formatWithValidation(href)
try {
const finalUrl = new URL(urlAsString, base)
finalUrl.pathname = normalizePathTrailingSlash(finalUrl.pathname)
let interpolatedAs = ''
if (
isDynamicRoute(finalUrl.pathname) &&
finalUrl.searchParams &&
resolveAs
) {
const query = searchParamsToUrlQuery(finalUrl.searchParams)
const { result, params } = interpolateAs(
finalUrl.pathname,
finalUrl.pathname,
query
)
if (result) {
interpolatedAs = formatWithValidation({
pathname: result,
hash: finalUrl.hash,
query: omitParmsFromQuery(query, params),
})
}
}
// if the origin didn't change, it means we received a relative href
const resolvedHref =
finalUrl.origin === base.origin
? finalUrl.href.slice(finalUrl.origin.length)
: finalUrl.href
return (resolveAs
? [resolvedHref, interpolatedAs || resolvedHref]
: resolvedHref) as string
} catch (_) {
return (resolveAs ? [urlAsString] : urlAsString) as string
}
}
function prepareUrlAs(router: NextRouter, url: Url, as: Url) {
// If url and as provided as an object representation,
// we'll format them into the string version here.
return {
url: addBasePath(resolveHref(router.pathname, url)),
as: as ? addBasePath(resolveHref(router.pathname, as)) : as,
}
}
2019-04-26 09:37:57 +02:00
export type BaseRouter = {
route: string
pathname: string
query: ParsedUrlQuery
asPath: string
basePath: string
locale?: string
locales?: string[]
defaultLocale?: string
}
export type NextRouter = BaseRouter &
Pick<
Router,
| 'push'
| 'replace'
| 'reload'
| 'back'
| 'prefetch'
| 'beforePopState'
| 'events'
| 'isFallback'
>
export type PrefetchOptions = {
priority?: boolean
locale?: string | false
}
export type PrivateRouteInfo =
| (Omit<CompletePrivateRouteInfo, 'styleSheets'> & { initial: true })
| CompletePrivateRouteInfo
export type CompletePrivateRouteInfo = {
Component: ComponentType
styleSheets: StyleSheetTuple[]
__N_SSG?: boolean
__N_SSP?: boolean
props?: Record<string, any>
err?: Error
error?: any
}
export type AppProps = Pick<CompletePrivateRouteInfo, 'Component' | 'err'> & {
router: Router
} & Record<string, any>
export type AppComponent = ComponentType<AppProps>
type Subscription = (data: PrivateRouteInfo, App: AppComponent) => Promise<void>
2020-08-01 20:13:03 +02:00
type BeforePopStateCallback = (state: NextHistoryState) => boolean
2016-10-06 01:52:50 +02:00
type ComponentLoadCancel = (() => void) | null
type HistoryMethod = 'replaceState' | 'pushState'
const manualScrollRestoration =
process.env.__NEXT_SCROLL_RESTORATION &&
typeof window !== 'undefined' &&
'scrollRestoration' in window.history
const SSG_DATA_NOT_FOUND_ERROR = 'SSG Data NOT_FOUND'
Fix static data fetching when using absolute assetprefix (#15287) Fixes https://github.com/vercel/next.js/issues/15188 `parseRelativeUrl` was used on urls that weren't always relative. It was used to generate a cache key, but we actually don't need these cache keys to be relative if the urls aren't relative. Also took a look at the overall static data fetching logic and found a few things: - [x] cache key is unnecessarily transformed through `prepareRoute`, we can just cache by resolved `dataHref` and remove that function. Pretty sure that `prepareRoute` was also introducing edge cases with `assetPath` and `delBasePath` - [x] there is [a bug in the caching logic](https://github.com/vercel/next.js/blob/ebdfa2e7a3f8e22e03b94dfb5f00481bf06254b6/packages/next/next-server/lib/router/router.ts#L898) that made it fail on the second visit: it should be `Promise.resolve(this.sdc[pathname])` instead of `Promise.resolve(this.sdc[dataHref])`. Also added a test for this - [x] ~converted to async await to improve stacktraces and readability.~ I assumed this was fine since I saw some async/awaits in that file already but it seems to just blow up the size of the non-modern bundle. - [x] extracted nested `getResponse` function and define it top level. this should improve runtime performance - [x] convert `_getStaticData` and `_getServerData` to class methods instead of properties. Not sure why they were defined as properties but I think they belong on the prototype instead. - [x] remove `cb` property from `fetchNextData`, it's unnecessary and makes the async flow hard to understand. The exact same logic can go in the `.then` instead. - [ ] data fetching logic [retries on 5xx errors](https://github.com/vercel/next.js/blob/ebdfa2e7a3f8e22e03b94dfb5f00481bf06254b6/packages/next/next-server/lib/router/router.ts#L157), but not on network level errors. It should also retry on those. It should also not retry on every 5xx, probably only makes sense on 502, 503 and 504. (e.g. 500 is a server error that I wouldn't expect to succeed on a retry) The overall result also is a few bytes smaller in size
2020-07-19 06:02:01 +02:00
function fetchRetry(url: string, attempts: number): Promise<any> {
return fetch(url, {
// Cookies are required to be present for Next.js' SSG "Preview Mode".
// Cookies may also be required for `getServerSideProps`.
//
// > `fetch` wont send cookies, unless you set the credentials init
// > option.
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
//
// > For maximum browser compatibility when it comes to sending &
// > receiving cookies, always supply the `credentials: 'same-origin'`
// > option instead of relying on the default.
// https://github.com/github/fetch#caveats
credentials: 'same-origin',
}).then((res) => {
if (!res.ok) {
if (attempts > 1 && res.status >= 500) {
return fetchRetry(url, attempts - 1)
}
if (res.status === 404) {
// TODO: handle reloading in development from fallback returning 200
// to on-demand-entry-handler causing it to reload periodically
throw new Error(SSG_DATA_NOT_FOUND_ERROR)
}
Fix static data fetching when using absolute assetprefix (#15287) Fixes https://github.com/vercel/next.js/issues/15188 `parseRelativeUrl` was used on urls that weren't always relative. It was used to generate a cache key, but we actually don't need these cache keys to be relative if the urls aren't relative. Also took a look at the overall static data fetching logic and found a few things: - [x] cache key is unnecessarily transformed through `prepareRoute`, we can just cache by resolved `dataHref` and remove that function. Pretty sure that `prepareRoute` was also introducing edge cases with `assetPath` and `delBasePath` - [x] there is [a bug in the caching logic](https://github.com/vercel/next.js/blob/ebdfa2e7a3f8e22e03b94dfb5f00481bf06254b6/packages/next/next-server/lib/router/router.ts#L898) that made it fail on the second visit: it should be `Promise.resolve(this.sdc[pathname])` instead of `Promise.resolve(this.sdc[dataHref])`. Also added a test for this - [x] ~converted to async await to improve stacktraces and readability.~ I assumed this was fine since I saw some async/awaits in that file already but it seems to just blow up the size of the non-modern bundle. - [x] extracted nested `getResponse` function and define it top level. this should improve runtime performance - [x] convert `_getStaticData` and `_getServerData` to class methods instead of properties. Not sure why they were defined as properties but I think they belong on the prototype instead. - [x] remove `cb` property from `fetchNextData`, it's unnecessary and makes the async flow hard to understand. The exact same logic can go in the `.then` instead. - [ ] data fetching logic [retries on 5xx errors](https://github.com/vercel/next.js/blob/ebdfa2e7a3f8e22e03b94dfb5f00481bf06254b6/packages/next/next-server/lib/router/router.ts#L157), but not on network level errors. It should also retry on those. It should also not retry on every 5xx, probably only makes sense on 502, 503 and 504. (e.g. 500 is a server error that I wouldn't expect to succeed on a retry) The overall result also is a few bytes smaller in size
2020-07-19 06:02:01 +02:00
throw new Error(`Failed to load static props`)
}
return res.json()
})
}
function fetchNextData(dataHref: string, isServerRender: boolean) {
return fetchRetry(dataHref, isServerRender ? 3 : 1).catch((err: Error) => {
// We should only trigger a server-side transition if this was caused
// on a client-side transition. Otherwise, we'd get into an infinite
// loop.
if (!isServerRender) {
markAssetError(err)
Fix static data fetching when using absolute assetprefix (#15287) Fixes https://github.com/vercel/next.js/issues/15188 `parseRelativeUrl` was used on urls that weren't always relative. It was used to generate a cache key, but we actually don't need these cache keys to be relative if the urls aren't relative. Also took a look at the overall static data fetching logic and found a few things: - [x] cache key is unnecessarily transformed through `prepareRoute`, we can just cache by resolved `dataHref` and remove that function. Pretty sure that `prepareRoute` was also introducing edge cases with `assetPath` and `delBasePath` - [x] there is [a bug in the caching logic](https://github.com/vercel/next.js/blob/ebdfa2e7a3f8e22e03b94dfb5f00481bf06254b6/packages/next/next-server/lib/router/router.ts#L898) that made it fail on the second visit: it should be `Promise.resolve(this.sdc[pathname])` instead of `Promise.resolve(this.sdc[dataHref])`. Also added a test for this - [x] ~converted to async await to improve stacktraces and readability.~ I assumed this was fine since I saw some async/awaits in that file already but it seems to just blow up the size of the non-modern bundle. - [x] extracted nested `getResponse` function and define it top level. this should improve runtime performance - [x] convert `_getStaticData` and `_getServerData` to class methods instead of properties. Not sure why they were defined as properties but I think they belong on the prototype instead. - [x] remove `cb` property from `fetchNextData`, it's unnecessary and makes the async flow hard to understand. The exact same logic can go in the `.then` instead. - [ ] data fetching logic [retries on 5xx errors](https://github.com/vercel/next.js/blob/ebdfa2e7a3f8e22e03b94dfb5f00481bf06254b6/packages/next/next-server/lib/router/router.ts#L157), but not on network level errors. It should also retry on those. It should also not retry on every 5xx, probably only makes sense on 502, 503 and 504. (e.g. 500 is a server error that I wouldn't expect to succeed on a retry) The overall result also is a few bytes smaller in size
2020-07-19 06:02:01 +02:00
}
throw err
})
}
2019-04-26 09:37:57 +02:00
export default class Router implements BaseRouter {
route: string
pathname: string
query: ParsedUrlQuery
asPath: string
basePath: string
/**
* Map of all components loaded in `Router`
*/
components: { [pathname: string]: PrivateRouteInfo }
// Static Data Cache
sdc: { [asPath: string]: object } = {}
sub: Subscription
clc: ComponentLoadCancel
pageLoader: any
_bps: BeforePopStateCallback | undefined
events: MittEmitter
_wrapApp: (App: AppComponent) => any
isSsr: boolean
isFallback: boolean
_inFlightRoute?: string
_shallow?: boolean
locale?: string
locales?: string[]
defaultLocale?: string
static events: MittEmitter = mitt()
constructor(
pathname: string,
query: ParsedUrlQuery,
as: string,
{
initialProps,
pageLoader,
App,
wrapApp,
Component,
err,
subscription,
isFallback,
locale,
locales,
defaultLocale,
}: {
subscription: Subscription
initialProps: any
pageLoader: any
Component: ComponentType
App: AppComponent
wrapApp: (App: AppComponent) => any
err?: Error
isFallback: boolean
locale?: string
locales?: string[]
defaultLocale?: string
}
) {
2016-10-08 07:12:51 +02:00
// represents the current component key
this.route = removePathTrailingSlash(pathname)
2016-10-06 01:52:50 +02:00
// set up the component cache (by route keys)
this.components = {}
// We should not keep the cache, if there's an error
// Otherwise, this cause issues when when going back and
// come again to the errored page.
if (pathname !== '/_error') {
this.components[this.route] = {
Component,
initial: true,
props: initialProps,
err,
__N_SSG: initialProps && initialProps.__N_SSG,
__N_SSP: initialProps && initialProps.__N_SSP,
}
}
2017-02-15 20:29:42 +01:00
this.components['/_app'] = {
Component: App as ComponentType,
styleSheets: [
/* /_app does not need its stylesheets managed */
],
}
// Backwards compat for Router.router.events
// TODO: Should be remove the following major version as it was never documented
this.events = Router.events
this.pageLoader = pageLoader
this.pathname = pathname
this.query = query
// if auto prerendered and dynamic route wait to update asPath
// until after mount to prevent hydration mismatch
this.asPath =
// @ts-ignore this is temporarily global (attached to window)
isDynamicRoute(pathname) && __NEXT_DATA__.autoExport ? pathname : as
this.basePath = basePath
this.sub = subscription
this.clc = null
this._wrapApp = wrapApp
// make sure to ignore extra popState in safari on navigating
// back from external site
this.isSsr = true
2016-10-08 07:12:51 +02:00
this.isFallback = isFallback
if (process.env.__NEXT_I18N_SUPPORT) {
this.locale = locale
this.locales = locales
this.defaultLocale = defaultLocale
}
if (typeof window !== 'undefined') {
// make sure "as" doesn't start with double slashes or else it can
// throw an error as it's considered invalid
if (as.substr(0, 2) !== '//') {
// in order for `e.state` to work on the `onpopstate` event
// we have to register the initial route upon initialization
this.changeState(
'replaceState',
formatWithValidation({ pathname: addBasePath(pathname), query }),
getURL()
)
}
2016-12-24 18:29:23 +01:00
2016-10-08 12:16:22 +02:00
window.addEventListener('popstate', this.onPopState)
// enable custom scroll restoration handling when available
// otherwise fallback to browser's default handling
if (process.env.__NEXT_SCROLL_RESTORATION) {
if (manualScrollRestoration) {
window.history.scrollRestoration = 'manual'
let scrollDebounceTimeout: undefined | NodeJS.Timeout
const debouncedScrollSave = () => {
if (scrollDebounceTimeout) clearTimeout(scrollDebounceTimeout)
scrollDebounceTimeout = setTimeout(() => {
const { url, as: curAs, options } = history.state
this.changeState(
'replaceState',
url,
curAs,
Object.assign({}, options, {
_N_X: window.scrollX,
_N_Y: window.scrollY,
})
)
}, 10)
}
window.addEventListener('scroll', debouncedScrollSave)
}
}
2016-10-08 12:16:22 +02:00
}
2016-10-06 01:52:50 +02:00
}
onPopState = (e: PopStateEvent): void => {
2020-08-01 20:13:03 +02:00
const state = e.state as HistoryState
if (!state) {
// We get state as undefined for two reasons.
// 1. With older safari (< 8) and older chrome (< 34)
// 2. When the URL changed with #
//
// In the both cases, we don't need to proceed and change the route.
// (as it's already changed)
// But we can simply replace the state with the new changes.
// Actually, for (1) we don't need to nothing. But it's hard to detect that event.
// So, doing the following for (1) does no harm.
const { pathname, query } = this
this.changeState(
'replaceState',
formatWithValidation({ pathname: addBasePath(pathname), query }),
getURL()
)
return
}
2020-08-01 20:13:03 +02:00
if (!state.__N) {
return
}
2020-08-01 20:13:03 +02:00
const { url, as, options } = state
const { pathname } = parseRelativeUrl(url)
// Make sure we don't re-render on initial load,
// can be caused by navigating back from an external site
if (this.isSsr && as === this.asPath && pathname === this.pathname) {
return
}
// If the downstream application returns falsy, return.
// They will then be responsible for handling the event.
2020-08-01 20:13:03 +02:00
if (this._bps && !this._bps(state)) {
return
}
this.change(
'replaceState',
url,
as,
Object.assign({}, options, {
shallow: options.shallow && this._shallow,
locale: options.locale || this.defaultLocale,
})
)
}
2016-10-06 01:52:50 +02:00
reload(): void {
window.location.reload()
2016-10-24 09:22:15 +02:00
}
/**
* Go back in history
*/
back() {
window.history.back()
2016-10-06 01:52:50 +02:00
}
/**
* Performs a `pushState` with arguments
* @param url of the route
* @param as masks `url` for the browser
* @param options object you can define `shallow` and other options
*/
2020-08-01 20:13:03 +02:00
push(url: Url, as: Url = url, options: TransitionOptions = {}) {
;({ url, as } = prepareUrlAs(this, url, as))
return this.change('pushState', url, as, options)
2016-10-06 01:52:50 +02:00
}
/**
* Performs a `replaceState` with arguments
* @param url of the route
* @param as masks `url` for the browser
* @param options object you can define `shallow` and other options
*/
2020-08-01 20:13:03 +02:00
replace(url: Url, as: Url = url, options: TransitionOptions = {}) {
;({ url, as } = prepareUrlAs(this, url, as))
return this.change('replaceState', url, as, options)
2016-10-06 01:52:50 +02:00
}
async change(
method: HistoryMethod,
url: string,
as: string,
2020-08-01 20:13:03 +02:00
options: TransitionOptions
): Promise<boolean> {
if (!isLocalURL(url)) {
window.location.href = url
return false
}
if (process.env.__NEXT_I18N_SUPPORT) {
this.locale = options.locale || this.locale
if (typeof options.locale === 'undefined') {
options.locale = this.locale
}
const {
normalizeLocalePath,
} = require('../i18n/normalize-locale-path') as typeof import('../i18n/normalize-locale-path')
const localePathResult = normalizeLocalePath(as, this.locales)
if (localePathResult.detectedLocale) {
this.locale = localePathResult.detectedLocale
url = localePathResult.pathname
}
}
2020-08-01 20:13:03 +02:00
if (!(options as any)._h) {
this.isSsr = false
}
// marking route changes as a navigation start entry
if (ST) {
performance.mark('routeChange')
}
if (this._inFlightRoute) {
this.abortComponentLoad(this._inFlightRoute)
}
as = addLocale(as, options.locale, this.defaultLocale)
const cleanedAs = delLocale(
hasBasePath(as) ? delBasePath(as) : as,
this.locale
)
this._inFlightRoute = as
// If the url change is only related to a hash change
// We should not proceed. We should only change the state.
// WARNING: `_h` is an internal option for handing Next.js client-side
// hydration. Your app should _never_ use this property. It may change at
// any time without notice.
2020-08-01 20:13:03 +02:00
if (!(options as any)._h && this.onlyAHashChange(cleanedAs)) {
this.asPath = cleanedAs
Router.events.emit('hashChangeStart', as)
// TODO: do we need the resolved href when only a hash change?
this.changeState(method, url, as, options)
this.scrollToHash(cleanedAs)
this.notify(this.components[this.route])
Router.events.emit('hashChangeComplete', as)
return true
}
let parsed = parseRelativeUrl(url)
let { pathname, query } = parsed
// The build manifest needs to be loaded before auto-static dynamic pages
// get their query parameters to allow ensuring they can be parsed properly
// when rewritten to
let pages: any, rewrites: any
try {
pages = await this.pageLoader.getPageList()
;({ __rewrites: rewrites } = await getClientBuildManifest())
} catch (err) {
// If we fail to resolve the page list or client-build manifest, we must
// do a server-side transition:
window.location.href = as
return false
}
parsed = this._resolveHref(parsed, pages) as typeof parsed
if (parsed.pathname !== pathname) {
pathname = parsed.pathname
url = formatWithValidation(parsed)
}
// url and as should always be prefixed with basePath by this
// point by either next/link or router.push/replace so strip the
// basePath from the pathname to match the pages dir 1-to-1
pathname = pathname
? removePathTrailingSlash(delBasePath(pathname))
: pathname
// If asked to change the current URL we should reload the current page
// (not location.reload() but reload getInitialProps and other Next.js stuffs)
// We also need to set the method = replaceState always
// as this should not go into the history (That's how browsers work)
// We should compare the new asPath to the current asPath, not the url
if (!this.urlIsNew(cleanedAs)) {
method = 'replaceState'
}
2016-10-08 07:12:51 +02:00
let route = removePathTrailingSlash(pathname)
const { shallow = false } = options
// we need to resolve the as value using rewrites for dynamic SSG
// pages to allow building the data URL correctly
let resolvedAs = as
if (process.env.__NEXT_HAS_REWRITES) {
resolvedAs = resolveRewrites(
parseRelativeUrl(as).pathname,
pages,
basePath,
rewrites,
query,
(p: string) => this._resolveHref({ pathname: p }, pages).pathname!
)
if (resolvedAs !== as) {
const potentialHref = removePathTrailingSlash(
this._resolveHref(
Object.assign({}, parsed, { pathname: resolvedAs }),
pages,
false
).pathname!
)
// if this directly matches a page we need to update the href to
// allow the correct page chunk to be loaded
if (pages.includes(potentialHref)) {
route = potentialHref
pathname = potentialHref
parsed.pathname = pathname
url = formatWithValidation(parsed)
}
}
}
resolvedAs = delLocale(delBasePath(resolvedAs), this.locale)
if (isDynamicRoute(route)) {
const parsedAs = parseRelativeUrl(resolvedAs)
const asPathname = parsedAs.pathname
const routeRegex = getRouteRegex(route)
const routeMatch = getRouteMatcher(routeRegex)(asPathname)
const shouldInterpolate = route === asPathname
const interpolatedAs = shouldInterpolate
? interpolateAs(route, asPathname, query)
: ({} as { result: undefined; params: undefined })
if (!routeMatch || (shouldInterpolate && !interpolatedAs.result)) {
const missingParams = Object.keys(routeRegex.groups).filter(
(param) => !query[param]
)
if (missingParams.length > 0) {
if (process.env.NODE_ENV !== 'production') {
console.warn(
`${
shouldInterpolate
? `Interpolating href`
: `Mismatching \`as\` and \`href\``
} failed to manually provide ` +
`the params: ${missingParams.join(
', '
)} in the \`href\`'s \`query\``
)
}
throw new Error(
(shouldInterpolate
? `The provided \`href\` (${url}) value is missing query values (${missingParams.join(
', '
)}) to be interpolated properly. `
: `The provided \`as\` value (${asPathname}) is incompatible with the \`href\` value (${route}). `) +
`Read more: https://err.sh/vercel/next.js/${
shouldInterpolate
? 'href-interpolation-failed'
: 'incompatible-href-as'
}`
)
}
} else if (shouldInterpolate) {
as = formatWithValidation(
Object.assign({}, parsedAs, {
pathname: interpolatedAs.result,
query: omitParmsFromQuery(query, interpolatedAs.params!),
})
)
} else {
// Merge params into `query`, overwriting any specified in search
Object.assign(query, routeMatch)
}
}
Router.events.emit('routeChangeStart', as)
2020-07-21 19:33:11 +02:00
try {
const routeInfo = await this.getRouteInfo(
route,
pathname,
query,
as,
shallow
)
let { error, props, __N_SSG, __N_SSP } = routeInfo
// handle redirect on client-transition
if (
(__N_SSG || __N_SSP) &&
props &&
(props as any).pageProps &&
(props as any).pageProps.__N_REDIRECT
) {
const destination = (props as any).pageProps.__N_REDIRECT
// check if destination is internal (resolves to a page) and attempt
// client-navigation if it is falling back to hard navigation if
// it's not
if (destination.startsWith('/')) {
const parsedHref = parseRelativeUrl(destination)
this._resolveHref(parsedHref, pages, false)
if (pages.includes(parsedHref.pathname)) {
const { url: newUrl, as: newAs } = prepareUrlAs(
this,
destination,
destination
)
return this.change(method, newUrl, newAs, options)
}
}
window.location.href = destination
return new Promise(() => {})
}
2020-07-21 19:33:11 +02:00
Router.events.emit('beforeHistoryChange', as)
this.changeState(
method,
url,
addLocale(as, options.locale, this.defaultLocale),
options
)
2020-07-21 19:33:11 +02:00
if (process.env.NODE_ENV !== 'production') {
const appComp: any = this.components['/_app'].Component
;(window as any).next.isPrerendered =
appComp.getInitialProps === appComp.origGetInitialProps &&
!(routeInfo.Component as any).getInitialProps
}
await this.set(route, pathname!, query, cleanedAs, routeInfo).catch(
(e) => {
if (e.cancelled) error = error || e
else throw e
}
)
2020-07-21 19:33:11 +02:00
if (error) {
Router.events.emit('routeChangeError', error, cleanedAs)
throw error
}
if (process.env.__NEXT_SCROLL_RESTORATION) {
if (manualScrollRestoration && '_N_X' in options) {
2020-08-01 20:13:03 +02:00
window.scrollTo((options as any)._N_X, (options as any)._N_Y)
}
2020-07-21 19:33:11 +02:00
}
if (process.env.__NEXT_I18N_SUPPORT) {
if (this.locale) {
document.documentElement.lang = this.locale
}
}
2020-07-21 19:33:11 +02:00
Router.events.emit('routeChangeComplete', as)
2020-07-21 19:33:11 +02:00
return true
} catch (err) {
if (err.cancelled) {
return false
}
2020-07-21 19:33:11 +02:00
throw err
}
}
changeState(
method: HistoryMethod,
url: string,
as: string,
2020-08-01 20:13:03 +02:00
options: TransitionOptions = {}
): void {
if (process.env.NODE_ENV !== 'production') {
if (typeof window.history === 'undefined') {
console.error(`Warning: window.history is not available.`)
return
}
if (typeof window.history[method] === 'undefined') {
console.error(`Warning: window.history.${method} is not available`)
return
}
}
if (method !== 'pushState' || getURL() !== as) {
this._shallow = options.shallow
window.history[method](
{
url,
as,
options,
__N: true,
2020-08-01 20:13:03 +02:00
} as HistoryState,
// Most browsers currently ignores this parameter, although they may use it in the future.
// Passing the empty string here should be safe against future changes to the method.
// https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState
'',
as
)
}
2016-10-06 01:52:50 +02:00
}
2020-07-21 19:33:11 +02:00
async handleRouteInfoError(
err: Error & { code: any; cancelled: boolean },
pathname: string,
2020-08-01 20:13:03 +02:00
query: ParsedUrlQuery,
as: string,
2020-07-21 19:33:11 +02:00
loadErrorFail?: boolean
): Promise<CompletePrivateRouteInfo> {
2020-07-21 19:33:11 +02:00
if (err.cancelled) {
// bubble up cancellation errors
throw err
}
if (isAssetError(err) || loadErrorFail) {
2020-07-21 19:33:11 +02:00
Router.events.emit('routeChangeError', err, as)
2020-07-21 19:33:11 +02:00
// If we can't load the page it could be one of following reasons
// 1. Page doesn't exists
// 2. Page does exist in a different zone
// 3. Internal error while loading the page
// So, doing a hard reload is the proper way to deal with this.
window.location.href = as
// Changing the URL doesn't block executing the current code path.
// So let's throw a cancellation error stop the routing logic.
throw buildCancellationError()
}
2020-07-21 19:33:11 +02:00
try {
let Component: ComponentType
let styleSheets: StyleSheetTuple[]
let props: Record<string, any> | undefined
const ssg404 = err.message === SSG_DATA_NOT_FOUND_ERROR
if (ssg404) {
try {
let mod: any
;({ page: Component, styleSheets, mod } = await this.fetchComponent(
'/404'
))
// TODO: should we tolerate these props missing and still render the
// page instead of falling back to _error?
if (mod && mod.__N_SSG) {
props = await this._getStaticData(
this.pageLoader.getDataHref('/404', '/404', true, this.locale)
)
}
} catch (_err) {
// non-fatal fallback to _error
}
}
if (
typeof Component! === 'undefined' ||
typeof styleSheets! === 'undefined'
) {
;({ page: Component, styleSheets } = await this.fetchComponent(
'/_error'
))
}
const routeInfo: CompletePrivateRouteInfo = {
props,
Component,
styleSheets,
err: ssg404 ? undefined : err,
error: ssg404 ? undefined : err,
}
2020-07-21 19:33:11 +02:00
if (!routeInfo.props) {
try {
routeInfo.props = await this.getInitialProps(Component, {
err,
pathname,
query,
} as any)
} catch (gipErr) {
console.error('Error in error page `getInitialProps`: ', gipErr)
routeInfo.props = {}
}
}
2020-07-21 19:33:11 +02:00
return routeInfo
} catch (routeInfoErr) {
return this.handleRouteInfoError(routeInfoErr, pathname, query, as, true)
}
}
async getRouteInfo(
route: string,
pathname: string,
query: any,
as: string,
shallow: boolean = false
): Promise<PrivateRouteInfo> {
2020-07-21 19:33:11 +02:00
try {
const existingRouteInfo: PrivateRouteInfo | undefined = this.components[
route
]
if (shallow && existingRouteInfo && this.route === route) {
return existingRouteInfo
2020-07-21 19:33:11 +02:00
}
const cachedRouteInfo: CompletePrivateRouteInfo | undefined =
existingRouteInfo && 'initial' in existingRouteInfo
? undefined
: existingRouteInfo
const routeInfo: CompletePrivateRouteInfo = cachedRouteInfo
2020-07-21 19:33:11 +02:00
? cachedRouteInfo
: await this.fetchComponent(route).then((res) => ({
Component: res.page,
styleSheets: res.styleSheets,
__N_SSG: res.mod.__N_SSG,
__N_SSP: res.mod.__N_SSP,
}))
2020-07-21 19:33:11 +02:00
const { Component, __N_SSG, __N_SSP } = routeInfo
2020-07-21 19:33:11 +02:00
if (process.env.NODE_ENV !== 'production') {
const { isValidElementType } = require('react-is')
if (!isValidElementType(Component)) {
throw new Error(
`The default export is not a React Component in page: "${pathname}"`
)
}
2020-07-21 19:33:11 +02:00
}
let dataHref: string | undefined
2020-07-21 19:33:11 +02:00
if (__N_SSG || __N_SSP) {
dataHref = this.pageLoader.getDataHref(
formatWithValidation({ pathname, query }),
delBasePath(as),
__N_SSG,
this.locale
2020-07-21 19:33:11 +02:00
)
}
const props = await this._getData<CompletePrivateRouteInfo>(() =>
2020-07-21 19:33:11 +02:00
__N_SSG
? this._getStaticData(dataHref!)
: __N_SSP
? this._getServerData(dataHref!)
: this.getInitialProps(
Component,
// we provide AppTree later so this needs to be `any`
{
pathname,
query,
asPath: as,
} as any
)
)
2020-07-21 19:33:11 +02:00
routeInfo.props = props
this.components[route] = routeInfo
return routeInfo
} catch (err) {
return this.handleRouteInfoError(err, pathname, query, as)
}
}
set(
route: string,
pathname: string,
2020-08-01 20:13:03 +02:00
query: ParsedUrlQuery,
as: string,
data: PrivateRouteInfo
): Promise<void> {
this.isFallback = false
this.route = route
this.pathname = pathname
this.query = query
this.asPath = as
return this.notify(data)
2016-10-06 01:52:50 +02:00
}
/**
* Callback to execute before replacing router state
* @param cb callback to be executed
*/
beforePopState(cb: BeforePopStateCallback) {
this._bps = cb
}
onlyAHashChange(as: string): boolean {
if (!this.asPath) return false
const [oldUrlNoHash, oldHash] = this.asPath.split('#')
const [newUrlNoHash, newHash] = as.split('#')
// Makes sure we scroll to the provided hash if the url/hash are the same
if (newHash && oldUrlNoHash === newUrlNoHash && oldHash === newHash) {
return true
}
// If the urls are change, there's more than a hash change
if (oldUrlNoHash !== newUrlNoHash) {
return false
}
// If the hash has changed, then it's a hash only change.
// This check is necessary to handle both the enter and
// leave hash === '' cases. The identity case falls through
// and is treated as a next reload.
return oldHash !== newHash
}
scrollToHash(as: string): void {
const [, hash] = as.split('#')
// Scroll to top if the hash is just `#` with no value
if (hash === '') {
window.scrollTo(0, 0)
return
}
// First we check if the element by id is found
const idEl = document.getElementById(hash)
if (idEl) {
idEl.scrollIntoView()
return
}
// If there's no element with the id, we check the `name` property
// To mirror browsers
const nameEl = document.getElementsByName(hash)[0]
if (nameEl) {
nameEl.scrollIntoView()
}
}
urlIsNew(asPath: string): boolean {
return this.asPath !== asPath
2016-10-06 01:52:50 +02:00
}
_resolveHref(parsedHref: UrlObject, pages: string[], applyBasePath = true) {
const { pathname } = parsedHref
const cleanPathname = removePathTrailingSlash(
denormalizePagePath(applyBasePath ? delBasePath(pathname!) : pathname!)
)
if (cleanPathname === '/404' || cleanPathname === '/_error') {
return parsedHref
}
// handle resolving href for dynamic routes
if (!pages.includes(cleanPathname!)) {
Reduce router code (#16159) This reduces the code as suggested: https://github.com/vercel/next.js/pull/15231#discussion_r469691649. Removes these bloated Babel helpers: ```diff @@ -678,80 +678,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI var _createClass = __webpack_require__("W8MJ"); - function _createForOfIteratorHelper(o, allowArrayLike) { - var it; - if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { - if ( - Array.isArray(o) || - (it = _unsupportedIterableToArray(o)) || - (allowArrayLike && o && typeof o.length === "number") - ) { - if (it) o = it; - var i = 0; - var F = function F() {}; - return { - s: F, - n: function n() { - if (i >= o.length) return { done: true }; - return { done: false, value: o[i++] }; - }, - e: function e(_e) { - throw _e; - }, - f: F - }; - } - throw new TypeError( - "Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method." - ); - } - var normalCompletion = true, - didErr = false, - err; - return { - s: function s() { - it = o[Symbol.iterator](); - }, - n: function n() { - var step = it.next(); - normalCompletion = step.done; - return step; - }, - e: function e(_e2) { - didErr = true; - err = _e2; - }, - f: function f() { - try { - if (!normalCompletion && it["return"] != null) it["return"](); - } finally { - if (didErr) throw err; - } - } - }; - } - - function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if ( - n === "Arguments" || - /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) - ) - return _arrayLikeToArray(o, minLen); - } - - function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) { - arr2[i] = arr[i]; - } - return arr2; - } - exports.__esModule = true; exports.hasBasePath = hasBasePath; exports.addBasePath = addBasePath; @@ -1864,28 +1790,16 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI } // handle resolving href for dynamic routes if (!pages.includes(cleanPathname)) { - var _iterator = _createForOfIteratorHelper(pages), - _step; - - try { - for (_iterator.s(); !(_step = _iterator.n()).done; ) { - var page = _step.value; - - if ( - (0, _isDynamic.isDynamicRoute)(page) && - (0, _routeRegex.getRouteRegex)(page).re.test( - cleanPathname - ) - ) { - parsedHref.pathname = addBasePath(page); - break; - } + // eslint-disable-next-line array-callback-return + pages.some(function(page) { + if ( + (0, _isDynamic.isDynamicRoute)(page) && + (0, _routeRegex.getRouteRegex)(page).re.test(cleanPathname) + ) { + parsedHref.pathname = addBasePath(page); + return true; } - } catch (err) { - _iterator.e(err); - } finally { - _iterator.f(); - } + }); } return parsedHref; @@ -2069,10 +1983,9 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI } if (cancelled) { - var _err = new Error("Loading initial props cancelled"); - - _err.cancelled = true; - throw _err; + var err = new Error("Loading initial props cancelled"); + err.cancelled = true; + throw err; } return data; ```
2020-08-13 16:50:01 +02:00
// eslint-disable-next-line array-callback-return
pages.some((page) => {
if (
isDynamicRoute(page) &&
getRouteRegex(page).re.test(cleanPathname!)
) {
parsedHref.pathname = applyBasePath ? addBasePath(page) : page
Reduce router code (#16159) This reduces the code as suggested: https://github.com/vercel/next.js/pull/15231#discussion_r469691649. Removes these bloated Babel helpers: ```diff @@ -678,80 +678,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI var _createClass = __webpack_require__("W8MJ"); - function _createForOfIteratorHelper(o, allowArrayLike) { - var it; - if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { - if ( - Array.isArray(o) || - (it = _unsupportedIterableToArray(o)) || - (allowArrayLike && o && typeof o.length === "number") - ) { - if (it) o = it; - var i = 0; - var F = function F() {}; - return { - s: F, - n: function n() { - if (i >= o.length) return { done: true }; - return { done: false, value: o[i++] }; - }, - e: function e(_e) { - throw _e; - }, - f: F - }; - } - throw new TypeError( - "Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method." - ); - } - var normalCompletion = true, - didErr = false, - err; - return { - s: function s() { - it = o[Symbol.iterator](); - }, - n: function n() { - var step = it.next(); - normalCompletion = step.done; - return step; - }, - e: function e(_e2) { - didErr = true; - err = _e2; - }, - f: function f() { - try { - if (!normalCompletion && it["return"] != null) it["return"](); - } finally { - if (didErr) throw err; - } - } - }; - } - - function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if ( - n === "Arguments" || - /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) - ) - return _arrayLikeToArray(o, minLen); - } - - function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) { - arr2[i] = arr[i]; - } - return arr2; - } - exports.__esModule = true; exports.hasBasePath = hasBasePath; exports.addBasePath = addBasePath; @@ -1864,28 +1790,16 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI } // handle resolving href for dynamic routes if (!pages.includes(cleanPathname)) { - var _iterator = _createForOfIteratorHelper(pages), - _step; - - try { - for (_iterator.s(); !(_step = _iterator.n()).done; ) { - var page = _step.value; - - if ( - (0, _isDynamic.isDynamicRoute)(page) && - (0, _routeRegex.getRouteRegex)(page).re.test( - cleanPathname - ) - ) { - parsedHref.pathname = addBasePath(page); - break; - } + // eslint-disable-next-line array-callback-return + pages.some(function(page) { + if ( + (0, _isDynamic.isDynamicRoute)(page) && + (0, _routeRegex.getRouteRegex)(page).re.test(cleanPathname) + ) { + parsedHref.pathname = addBasePath(page); + return true; } - } catch (err) { - _iterator.e(err); - } finally { - _iterator.f(); - } + }); } return parsedHref; @@ -2069,10 +1983,9 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI } if (cancelled) { - var _err = new Error("Loading initial props cancelled"); - - _err.cancelled = true; - throw _err; + var err = new Error("Loading initial props cancelled"); + err.cancelled = true; + throw err; } return data; ```
2020-08-13 16:50:01 +02:00
return true
}
Reduce router code (#16159) This reduces the code as suggested: https://github.com/vercel/next.js/pull/15231#discussion_r469691649. Removes these bloated Babel helpers: ```diff @@ -678,80 +678,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI var _createClass = __webpack_require__("W8MJ"); - function _createForOfIteratorHelper(o, allowArrayLike) { - var it; - if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { - if ( - Array.isArray(o) || - (it = _unsupportedIterableToArray(o)) || - (allowArrayLike && o && typeof o.length === "number") - ) { - if (it) o = it; - var i = 0; - var F = function F() {}; - return { - s: F, - n: function n() { - if (i >= o.length) return { done: true }; - return { done: false, value: o[i++] }; - }, - e: function e(_e) { - throw _e; - }, - f: F - }; - } - throw new TypeError( - "Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method." - ); - } - var normalCompletion = true, - didErr = false, - err; - return { - s: function s() { - it = o[Symbol.iterator](); - }, - n: function n() { - var step = it.next(); - normalCompletion = step.done; - return step; - }, - e: function e(_e2) { - didErr = true; - err = _e2; - }, - f: function f() { - try { - if (!normalCompletion && it["return"] != null) it["return"](); - } finally { - if (didErr) throw err; - } - } - }; - } - - function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if ( - n === "Arguments" || - /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) - ) - return _arrayLikeToArray(o, minLen); - } - - function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) { - arr2[i] = arr[i]; - } - return arr2; - } - exports.__esModule = true; exports.hasBasePath = hasBasePath; exports.addBasePath = addBasePath; @@ -1864,28 +1790,16 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI } // handle resolving href for dynamic routes if (!pages.includes(cleanPathname)) { - var _iterator = _createForOfIteratorHelper(pages), - _step; - - try { - for (_iterator.s(); !(_step = _iterator.n()).done; ) { - var page = _step.value; - - if ( - (0, _isDynamic.isDynamicRoute)(page) && - (0, _routeRegex.getRouteRegex)(page).re.test( - cleanPathname - ) - ) { - parsedHref.pathname = addBasePath(page); - break; - } + // eslint-disable-next-line array-callback-return + pages.some(function(page) { + if ( + (0, _isDynamic.isDynamicRoute)(page) && + (0, _routeRegex.getRouteRegex)(page).re.test(cleanPathname) + ) { + parsedHref.pathname = addBasePath(page); + return true; } - } catch (err) { - _iterator.e(err); - } finally { - _iterator.f(); - } + }); } return parsedHref; @@ -2069,10 +1983,9 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI } if (cancelled) { - var _err = new Error("Loading initial props cancelled"); - - _err.cancelled = true; - throw _err; + var err = new Error("Loading initial props cancelled"); + err.cancelled = true; + throw err; } return data; ```
2020-08-13 16:50:01 +02:00
})
}
return parsedHref
}
/**
* Prefetch page code, you may wait for the data during page rendering.
* This feature only works in production!
* @param url the href of prefetched page
* @param asPath the as path of the prefetched page
*/
async prefetch(
url: string,
asPath: string = url,
options: PrefetchOptions = {}
): Promise<void> {
let parsed = parseRelativeUrl(url)
let { pathname } = parsed
if (process.env.__NEXT_I18N_SUPPORT) {
const normalizeLocalePath = require('../i18n/normalize-locale-path')
.normalizeLocalePath as typeof import('../i18n/normalize-locale-path').normalizeLocalePath
if (options.locale === false) {
pathname = normalizeLocalePath!(pathname, this.locales).pathname
parsed.pathname = pathname
url = formatWithValidation(parsed)
let parsedAs = parseRelativeUrl(asPath)
const localePathResult = normalizeLocalePath!(
parsedAs.pathname,
this.locales
)
parsedAs.pathname = localePathResult.pathname
options.locale = localePathResult.detectedLocale || options.locale
asPath = formatWithValidation(parsedAs)
}
}
const pages = await this.pageLoader.getPageList()
parsed = this._resolveHref(parsed, pages) as typeof parsed
if (parsed.pathname !== pathname) {
pathname = parsed.pathname
url = formatWithValidation(parsed)
}
// Prefetch is not supported in development mode because it would trigger on-demand-entries
if (process.env.NODE_ENV !== 'production') {
return
}
const route = removePathTrailingSlash(pathname)
await Promise.all([
this.pageLoader._isSsg(url).then((isSsg: boolean) => {
return isSsg
? this._getStaticData(
this.pageLoader.getDataHref(
url,
asPath,
true,
typeof options.locale !== 'undefined'
? options.locale
: this.locale
)
)
: false
}),
this.pageLoader[options.priority ? 'loadPage' : 'prefetch'](route),
])
}
async fetchComponent(route: string): Promise<GoodPageCache> {
let cancelled = false
const cancel = (this.clc = () => {
cancelled = true
})
const componentResult = await this.pageLoader.loadPage(route)
Universal Webpack (#3578) * Speed up next build * Document webpack config * Speed up next build * Remove comment * Add comment * Clean up rules * Add comments * Run in parallel * Push plugins seperately * Create a new chunk for react * Don’t uglify react since it’s already uglified. Move react to commons in development * Use the minified version directly * Re-add globpattern * Move loaders into a separate variable * Add comment linking to Dan’s explanation * Remove dot * Add universal webpack * Initial dev support * Fix linting * Add changes from Arunoda's work * Made next dev works. But super slow and no HMR support. * Fix client side hot reload * Server side hmr * Only in dev * Add on-demand-entries client + hot-middleware * Add .babelrc support * Speed up on demand entries by running in parallel * Serve static generated files * Add missing config in dev * Add sass support * Add support for .map * Add cssloader config and fix .jsx support * Rename * use same defaults as css-loader. Fix linting * Add NoEmitErrorsPlugin * Add clientBootstrap * Use webpackhotmiddleware on the multi compiler * alpha.3 * Use babel 16.2.x * Fix reloading after error * Remove comment * Release 5.0.0-univeral-alpha.1 * Remove check for React 16 * Release 5.0.0-universal-alpha.2 * React hot loader v4 * Use our static file rendering machanism to serve pages. This should work well since the file path for a page is predictable. * Release 5.0.0-universal-alpha.3 * Remove optional loaders * Release 5.0.0-universal-alpha.4 * Remove clientBootstrap * Remove renderScript * Make sure pages bundles are served correctly * Remove unused import * Revert to using the same code as canary * Fix hot loader * Release 5.0.0-universal-alpha.5 * Check if externals dir exist before applying config * Add typescript support * Add support for transpiling certain packages in node_modules Thanks to @giuseppeg’s work in https://github.com/zeit/next.js/pull/3319 * Add BABEL_DISABLE_CACHE support * Make sourcemaps in production opt-in * Revert "Add support for transpiling certain packages in node_modules" This reverts commit d4b1d9babfb4b9ed4f4b12d56d52dee233e862da. In favor of a better api around this. * Support typescript through next.config.js * Remove comments * Bring back commons.js calculation * Remove unused dependencies * Move base.config.js to webpack.js * Make sure to only invalidate webpackDevMiddleware one after other. * Allow babel-loder caching by default. * Add comment about preact support * Bring back buildir replace * Remove obsolete plugin * Remove build replace, speed up build * Resolve page entries like pages/day/index.js to pages/day.js * Add componentDidCatch back * Compile to bundles * Use config.distDir everywhere * Make sure the file is an array * Remove console.log * Apply optimization to uglifyjs * Add comment pointing to source * Create entries the same way in dev and production * Remove unused and broken pagesGlobPattern * day/index.js is automatically turned into day.js at build time * Remove poweredByHeader option * Load pages with the correct path. * Release 5.0.0-universal-alpha.6 * Make sure react-dom/server can be overwritten by module-alias * Only add react-hot-loader babel plugin in dev * Release 5.0.0-universal-alpha.7 * Revert tests * Release 5.0.0-universal-alpha.10 * Make sure next/head is working properly. * Add wepack alias for 'next' back. * Make sure overriding className in next/head works * Alias react too * Add missing r * Fragment fallback has to wrap the children * Use min.js * Remove css.js * Remove wallaby.js * Release 5.0.0-universal-alpha.11 * Resolve relative to workdir instead of next * Make sure we touch the right file * Resolve next modules * Remove dotjsx removal plugins since we use webpack on the server * Revert "Resolve relative to workdir instead of next" This reverts commit a13f3e4ab565df9e2c9a3dfc8eb4009c0c2e02ed. * Externalize any locally loaded module lives outside of app dir. * Remove server aliases * Check node_modules reliably * Add symlink to next for tests * Make sure dynamic imports work locally. This is why we need it: https://github.com/webpack/webpack/blob/b545b519b2024e3f8be3041385bd326bf5d24449/lib/MainTemplate.js#L68 We need to have the finally clause in the above in __webpack_require__. webpack output option strictModuleExceptionHandling does that. * dynmaic -> dynamic * Remove webpack-node-externals * Make sure dynamic imports support SSR. * Remove css support in favor of next-css * Make sure we load path from `/` since it’s included in the path matching * Catch when ensurepage couldn’t be fulfilled for `.js.map` * Register require cache flusher for both client and server * Add comment explaining this is to facilitate hot reloading * Only load module when needed * Remove unused modules * Release 5.0.0-universal-alpha.12 * Only log the `found babel` message once * Make sure ondemand entries working correctly. Now we are just using a single instance of OnDemandEntryHandler. * Better sourcemaps * Release 5.0.0-universal-alpha.13 * Lock uglify version to 1.1.6 * Release 5.0.0-universal-alpha.14 * Fix a typo. * Introduce multi-zones support for mircofrontends * Add section on css
2018-01-30 16:40:52 +01:00
if (cancelled) {
const error: any = new Error(
`Abort fetching component for route: "${route}"`
)
Universal Webpack (#3578) * Speed up next build * Document webpack config * Speed up next build * Remove comment * Add comment * Clean up rules * Add comments * Run in parallel * Push plugins seperately * Create a new chunk for react * Don’t uglify react since it’s already uglified. Move react to commons in development * Use the minified version directly * Re-add globpattern * Move loaders into a separate variable * Add comment linking to Dan’s explanation * Remove dot * Add universal webpack * Initial dev support * Fix linting * Add changes from Arunoda's work * Made next dev works. But super slow and no HMR support. * Fix client side hot reload * Server side hmr * Only in dev * Add on-demand-entries client + hot-middleware * Add .babelrc support * Speed up on demand entries by running in parallel * Serve static generated files * Add missing config in dev * Add sass support * Add support for .map * Add cssloader config and fix .jsx support * Rename * use same defaults as css-loader. Fix linting * Add NoEmitErrorsPlugin * Add clientBootstrap * Use webpackhotmiddleware on the multi compiler * alpha.3 * Use babel 16.2.x * Fix reloading after error * Remove comment * Release 5.0.0-univeral-alpha.1 * Remove check for React 16 * Release 5.0.0-universal-alpha.2 * React hot loader v4 * Use our static file rendering machanism to serve pages. This should work well since the file path for a page is predictable. * Release 5.0.0-universal-alpha.3 * Remove optional loaders * Release 5.0.0-universal-alpha.4 * Remove clientBootstrap * Remove renderScript * Make sure pages bundles are served correctly * Remove unused import * Revert to using the same code as canary * Fix hot loader * Release 5.0.0-universal-alpha.5 * Check if externals dir exist before applying config * Add typescript support * Add support for transpiling certain packages in node_modules Thanks to @giuseppeg’s work in https://github.com/zeit/next.js/pull/3319 * Add BABEL_DISABLE_CACHE support * Make sourcemaps in production opt-in * Revert "Add support for transpiling certain packages in node_modules" This reverts commit d4b1d9babfb4b9ed4f4b12d56d52dee233e862da. In favor of a better api around this. * Support typescript through next.config.js * Remove comments * Bring back commons.js calculation * Remove unused dependencies * Move base.config.js to webpack.js * Make sure to only invalidate webpackDevMiddleware one after other. * Allow babel-loder caching by default. * Add comment about preact support * Bring back buildir replace * Remove obsolete plugin * Remove build replace, speed up build * Resolve page entries like pages/day/index.js to pages/day.js * Add componentDidCatch back * Compile to bundles * Use config.distDir everywhere * Make sure the file is an array * Remove console.log * Apply optimization to uglifyjs * Add comment pointing to source * Create entries the same way in dev and production * Remove unused and broken pagesGlobPattern * day/index.js is automatically turned into day.js at build time * Remove poweredByHeader option * Load pages with the correct path. * Release 5.0.0-universal-alpha.6 * Make sure react-dom/server can be overwritten by module-alias * Only add react-hot-loader babel plugin in dev * Release 5.0.0-universal-alpha.7 * Revert tests * Release 5.0.0-universal-alpha.10 * Make sure next/head is working properly. * Add wepack alias for 'next' back. * Make sure overriding className in next/head works * Alias react too * Add missing r * Fragment fallback has to wrap the children * Use min.js * Remove css.js * Remove wallaby.js * Release 5.0.0-universal-alpha.11 * Resolve relative to workdir instead of next * Make sure we touch the right file * Resolve next modules * Remove dotjsx removal plugins since we use webpack on the server * Revert "Resolve relative to workdir instead of next" This reverts commit a13f3e4ab565df9e2c9a3dfc8eb4009c0c2e02ed. * Externalize any locally loaded module lives outside of app dir. * Remove server aliases * Check node_modules reliably * Add symlink to next for tests * Make sure dynamic imports work locally. This is why we need it: https://github.com/webpack/webpack/blob/b545b519b2024e3f8be3041385bd326bf5d24449/lib/MainTemplate.js#L68 We need to have the finally clause in the above in __webpack_require__. webpack output option strictModuleExceptionHandling does that. * dynmaic -> dynamic * Remove webpack-node-externals * Make sure dynamic imports support SSR. * Remove css support in favor of next-css * Make sure we load path from `/` since it’s included in the path matching * Catch when ensurepage couldn’t be fulfilled for `.js.map` * Register require cache flusher for both client and server * Add comment explaining this is to facilitate hot reloading * Only load module when needed * Remove unused modules * Release 5.0.0-universal-alpha.12 * Only log the `found babel` message once * Make sure ondemand entries working correctly. Now we are just using a single instance of OnDemandEntryHandler. * Better sourcemaps * Release 5.0.0-universal-alpha.13 * Lock uglify version to 1.1.6 * Release 5.0.0-universal-alpha.14 * Fix a typo. * Introduce multi-zones support for mircofrontends * Add section on css
2018-01-30 16:40:52 +01:00
error.cancelled = true
throw error
}
if (cancel === this.clc) {
this.clc = null
}
Universal Webpack (#3578) * Speed up next build * Document webpack config * Speed up next build * Remove comment * Add comment * Clean up rules * Add comments * Run in parallel * Push plugins seperately * Create a new chunk for react * Don’t uglify react since it’s already uglified. Move react to commons in development * Use the minified version directly * Re-add globpattern * Move loaders into a separate variable * Add comment linking to Dan’s explanation * Remove dot * Add universal webpack * Initial dev support * Fix linting * Add changes from Arunoda's work * Made next dev works. But super slow and no HMR support. * Fix client side hot reload * Server side hmr * Only in dev * Add on-demand-entries client + hot-middleware * Add .babelrc support * Speed up on demand entries by running in parallel * Serve static generated files * Add missing config in dev * Add sass support * Add support for .map * Add cssloader config and fix .jsx support * Rename * use same defaults as css-loader. Fix linting * Add NoEmitErrorsPlugin * Add clientBootstrap * Use webpackhotmiddleware on the multi compiler * alpha.3 * Use babel 16.2.x * Fix reloading after error * Remove comment * Release 5.0.0-univeral-alpha.1 * Remove check for React 16 * Release 5.0.0-universal-alpha.2 * React hot loader v4 * Use our static file rendering machanism to serve pages. This should work well since the file path for a page is predictable. * Release 5.0.0-universal-alpha.3 * Remove optional loaders * Release 5.0.0-universal-alpha.4 * Remove clientBootstrap * Remove renderScript * Make sure pages bundles are served correctly * Remove unused import * Revert to using the same code as canary * Fix hot loader * Release 5.0.0-universal-alpha.5 * Check if externals dir exist before applying config * Add typescript support * Add support for transpiling certain packages in node_modules Thanks to @giuseppeg’s work in https://github.com/zeit/next.js/pull/3319 * Add BABEL_DISABLE_CACHE support * Make sourcemaps in production opt-in * Revert "Add support for transpiling certain packages in node_modules" This reverts commit d4b1d9babfb4b9ed4f4b12d56d52dee233e862da. In favor of a better api around this. * Support typescript through next.config.js * Remove comments * Bring back commons.js calculation * Remove unused dependencies * Move base.config.js to webpack.js * Make sure to only invalidate webpackDevMiddleware one after other. * Allow babel-loder caching by default. * Add comment about preact support * Bring back buildir replace * Remove obsolete plugin * Remove build replace, speed up build * Resolve page entries like pages/day/index.js to pages/day.js * Add componentDidCatch back * Compile to bundles * Use config.distDir everywhere * Make sure the file is an array * Remove console.log * Apply optimization to uglifyjs * Add comment pointing to source * Create entries the same way in dev and production * Remove unused and broken pagesGlobPattern * day/index.js is automatically turned into day.js at build time * Remove poweredByHeader option * Load pages with the correct path. * Release 5.0.0-universal-alpha.6 * Make sure react-dom/server can be overwritten by module-alias * Only add react-hot-loader babel plugin in dev * Release 5.0.0-universal-alpha.7 * Revert tests * Release 5.0.0-universal-alpha.10 * Make sure next/head is working properly. * Add wepack alias for 'next' back. * Make sure overriding className in next/head works * Alias react too * Add missing r * Fragment fallback has to wrap the children * Use min.js * Remove css.js * Remove wallaby.js * Release 5.0.0-universal-alpha.11 * Resolve relative to workdir instead of next * Make sure we touch the right file * Resolve next modules * Remove dotjsx removal plugins since we use webpack on the server * Revert "Resolve relative to workdir instead of next" This reverts commit a13f3e4ab565df9e2c9a3dfc8eb4009c0c2e02ed. * Externalize any locally loaded module lives outside of app dir. * Remove server aliases * Check node_modules reliably * Add symlink to next for tests * Make sure dynamic imports work locally. This is why we need it: https://github.com/webpack/webpack/blob/b545b519b2024e3f8be3041385bd326bf5d24449/lib/MainTemplate.js#L68 We need to have the finally clause in the above in __webpack_require__. webpack output option strictModuleExceptionHandling does that. * dynmaic -> dynamic * Remove webpack-node-externals * Make sure dynamic imports support SSR. * Remove css support in favor of next-css * Make sure we load path from `/` since it’s included in the path matching * Catch when ensurepage couldn’t be fulfilled for `.js.map` * Register require cache flusher for both client and server * Add comment explaining this is to facilitate hot reloading * Only load module when needed * Remove unused modules * Release 5.0.0-universal-alpha.12 * Only log the `found babel` message once * Make sure ondemand entries working correctly. Now we are just using a single instance of OnDemandEntryHandler. * Better sourcemaps * Release 5.0.0-universal-alpha.13 * Lock uglify version to 1.1.6 * Release 5.0.0-universal-alpha.14 * Fix a typo. * Introduce multi-zones support for mircofrontends * Add section on css
2018-01-30 16:40:52 +01:00
return componentResult
2016-10-08 07:12:51 +02:00
}
_getData<T>(fn: () => Promise<T>): Promise<T> {
2016-10-08 07:12:51 +02:00
let cancelled = false
const cancel = () => {
cancelled = true
}
this.clc = cancel
2020-05-18 21:24:37 +02:00
return fn().then((data) => {
if (cancel === this.clc) {
this.clc = null
}
2016-10-08 07:12:51 +02:00
if (cancelled) {
const err: any = new Error('Loading initial props cancelled')
err.cancelled = true
throw err
}
return data
})
}
Fix static data fetching when using absolute assetprefix (#15287) Fixes https://github.com/vercel/next.js/issues/15188 `parseRelativeUrl` was used on urls that weren't always relative. It was used to generate a cache key, but we actually don't need these cache keys to be relative if the urls aren't relative. Also took a look at the overall static data fetching logic and found a few things: - [x] cache key is unnecessarily transformed through `prepareRoute`, we can just cache by resolved `dataHref` and remove that function. Pretty sure that `prepareRoute` was also introducing edge cases with `assetPath` and `delBasePath` - [x] there is [a bug in the caching logic](https://github.com/vercel/next.js/blob/ebdfa2e7a3f8e22e03b94dfb5f00481bf06254b6/packages/next/next-server/lib/router/router.ts#L898) that made it fail on the second visit: it should be `Promise.resolve(this.sdc[pathname])` instead of `Promise.resolve(this.sdc[dataHref])`. Also added a test for this - [x] ~converted to async await to improve stacktraces and readability.~ I assumed this was fine since I saw some async/awaits in that file already but it seems to just blow up the size of the non-modern bundle. - [x] extracted nested `getResponse` function and define it top level. this should improve runtime performance - [x] convert `_getStaticData` and `_getServerData` to class methods instead of properties. Not sure why they were defined as properties but I think they belong on the prototype instead. - [x] remove `cb` property from `fetchNextData`, it's unnecessary and makes the async flow hard to understand. The exact same logic can go in the `.then` instead. - [ ] data fetching logic [retries on 5xx errors](https://github.com/vercel/next.js/blob/ebdfa2e7a3f8e22e03b94dfb5f00481bf06254b6/packages/next/next-server/lib/router/router.ts#L157), but not on network level errors. It should also retry on those. It should also not retry on every 5xx, probably only makes sense on 502, 503 and 504. (e.g. 500 is a server error that I wouldn't expect to succeed on a retry) The overall result also is a few bytes smaller in size
2020-07-19 06:02:01 +02:00
_getStaticData(dataHref: string): Promise<object> {
const { href: cacheKey } = new URL(dataHref, window.location.href)
if (process.env.NODE_ENV === 'production' && this.sdc[cacheKey]) {
return Promise.resolve(this.sdc[cacheKey])
}
return fetchNextData(dataHref, this.isSsr).then((data) => {
this.sdc[cacheKey] = data
return data
})
}
Fix static data fetching when using absolute assetprefix (#15287) Fixes https://github.com/vercel/next.js/issues/15188 `parseRelativeUrl` was used on urls that weren't always relative. It was used to generate a cache key, but we actually don't need these cache keys to be relative if the urls aren't relative. Also took a look at the overall static data fetching logic and found a few things: - [x] cache key is unnecessarily transformed through `prepareRoute`, we can just cache by resolved `dataHref` and remove that function. Pretty sure that `prepareRoute` was also introducing edge cases with `assetPath` and `delBasePath` - [x] there is [a bug in the caching logic](https://github.com/vercel/next.js/blob/ebdfa2e7a3f8e22e03b94dfb5f00481bf06254b6/packages/next/next-server/lib/router/router.ts#L898) that made it fail on the second visit: it should be `Promise.resolve(this.sdc[pathname])` instead of `Promise.resolve(this.sdc[dataHref])`. Also added a test for this - [x] ~converted to async await to improve stacktraces and readability.~ I assumed this was fine since I saw some async/awaits in that file already but it seems to just blow up the size of the non-modern bundle. - [x] extracted nested `getResponse` function and define it top level. this should improve runtime performance - [x] convert `_getStaticData` and `_getServerData` to class methods instead of properties. Not sure why they were defined as properties but I think they belong on the prototype instead. - [x] remove `cb` property from `fetchNextData`, it's unnecessary and makes the async flow hard to understand. The exact same logic can go in the `.then` instead. - [ ] data fetching logic [retries on 5xx errors](https://github.com/vercel/next.js/blob/ebdfa2e7a3f8e22e03b94dfb5f00481bf06254b6/packages/next/next-server/lib/router/router.ts#L157), but not on network level errors. It should also retry on those. It should also not retry on every 5xx, probably only makes sense on 502, 503 and 504. (e.g. 500 is a server error that I wouldn't expect to succeed on a retry) The overall result also is a few bytes smaller in size
2020-07-19 06:02:01 +02:00
_getServerData(dataHref: string): Promise<object> {
return fetchNextData(dataHref, this.isSsr)
2019-12-30 19:40:23 +01:00
}
getInitialProps(
Component: ComponentType,
ctx: NextPageContext
): Promise<any> {
const { Component: App } = this.components['/_app']
const AppTree = this._wrapApp(App as AppComponent)
ctx.AppTree = AppTree
2019-12-30 19:40:23 +01:00
return loadGetInitialProps<AppContextType<Router>>(App, {
AppTree,
Component,
router: this,
ctx,
})
2016-10-06 01:52:50 +02:00
}
abortComponentLoad(as: string): void {
if (this.clc) {
2020-07-21 19:33:11 +02:00
Router.events.emit('routeChangeError', buildCancellationError(), as)
this.clc()
this.clc = null
2016-10-06 01:52:50 +02:00
}
}
notify(data: PrivateRouteInfo): Promise<void> {
return this.sub(data, this.components['/_app'].Component as AppComponent)
2016-10-06 01:52:50 +02:00
}
}