rsnext/packages/next/client/index.tsx

776 lines
22 KiB
TypeScript
Raw Normal View History

/* global location */
import '@next/polyfill-module'
import React from 'react'
import ReactDOM from 'react-dom'
import { HeadManagerContext } from '../next-server/lib/head-manager-context'
import mitt from '../next-server/lib/mitt'
import { RouterContext } from '../next-server/lib/router-context'
import type Router from '../next-server/lib/router/router'
import type {
AppComponent,
AppProps,
PrivateRouteInfo,
} from '../next-server/lib/router/router'
import { delBasePath, hasBasePath } from '../next-server/lib/router/router'
import { isDynamicRoute } from '../next-server/lib/router/utils/is-dynamic'
import * as querystring from '../next-server/lib/router/utils/querystring'
import * as envConfig from '../next-server/lib/runtime-config'
import type { NEXT_DATA } from '../next-server/lib/utils'
import { getURL, loadGetInitialProps, ST } from '../next-server/lib/utils'
import initHeadManager from './head-manager'
2020-08-27 04:13:28 +02:00
import PageLoader, { looseToArray, StyleSheetTuple } from './page-loader'
import measureWebVitals from './performance-relayer'
import { createRouter, makePublicRouterInstance } from './router'
/// <reference types="react-dom/experimental" />
declare let __webpack_public_path__: string
declare global {
interface Window {
/* test fns */
__NEXT_HYDRATED?: boolean
__NEXT_HYDRATED_CB?: () => void
/* prod */
__NEXT_PRELOADREADY?: (ids?: string[]) => void
__NEXT_DATA__: NEXT_DATA
__NEXT_P: any[]
}
}
type RenderRouteInfo = PrivateRouteInfo & { App: AppComponent }
type RenderErrorProps = Omit<RenderRouteInfo, 'Component' | 'styleSheets'>
const data: typeof window['__NEXT_DATA__'] = JSON.parse(
document.getElementById('__NEXT_DATA__')!.textContent!
)
window.__NEXT_DATA__ = data
export const version = process.env.__NEXT_VERSION
const {
props: hydrateProps,
err: hydrateErr,
page,
query,
buildId,
assetPrefix,
runtimeConfig,
dynamicIds,
isFallback,
Remove `next-head-count` (#16758) Removes `next-head-count`, improving support for 3rd party libraries that insert or append new elements to `<head>`. --- This is more or less what a solution with a `data-` attribute would look like, except that instead of directly searching for elements with that attribute, we serialize the elements expected in `<head>` and then find them/assume ownership of them during initialization (in a manner similar to React's reconciliation) based on their properties. There are two main assumptions here: 1. Content is served with compression, so duplicate serialization of e.g. inline script or style tags doesn't have a meaningful impact. Storing a hash would be a potential optimization. 2. 3rd party libraries primarily only insert new, unique elements to head. Libraries trying to actively manage elements that overlap with those that Next.js claims ownership of will still be unsupported. The reason for this roundabout approach is that I'd really like to avoid `data-` if possible, for maximum compatibility. Implicitly adding an attribute could be a breaking change for some class of tools or crawlers and makes it otherwise impossible to insert raw HTML into `<head>`. Adding an unexpected attribute is why the original `class="next-head"` approach was problematic in the first place! That said, while I don't expect this to be more problematic than `next-head-count` (anything that would break in this new model also should have broken in the old model), if that does end up being the case, it might make sense to just bite the bullet. Fixes #11012 Closes #16707 --- cc @Timer @timneutkens
2020-09-09 03:41:04 +02:00
head: initialHeadData,
locales,
} = data
let { locale, defaultLocale } = data
const prefix = assetPrefix || ''
// With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
// So, this is how we do it in the client side at runtime
__webpack_public_path__ = `${prefix}/_next/` //eslint-disable-line
// Initialize next/config with the environment configuration
2018-02-27 17:50:14 +01:00
envConfig.setConfig({
serverRuntimeConfig: {},
publicRuntimeConfig: runtimeConfig || {},
2018-02-27 17:50:14 +01:00
})
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
let asPath = getURL()
// make sure not to attempt stripping basePath for 404s
if (hasBasePath(asPath)) {
asPath = delBasePath(asPath)
}
if (process.env.__NEXT_I18N_SUPPORT) {
const {
normalizeLocalePath,
} = require('../next-server/lib/i18n/normalize-locale-path') as typeof import('../next-server/lib/i18n/normalize-locale-path')
const {
detectDomainLocale,
} = require('../next-server/lib/i18n/detect-domain-locale') as typeof import('../next-server/lib/i18n/detect-domain-locale')
const {
parseRelativeUrl,
} = require('../next-server/lib/router/utils/parse-relative-url') as typeof import('../next-server/lib/router/utils/parse-relative-url')
const {
formatUrl,
} = require('../next-server/lib/router/utils/format-url') as typeof import('../next-server/lib/router/utils/format-url')
if (locales) {
const parsedAs = parseRelativeUrl(asPath)
const localePathResult = normalizeLocalePath(parsedAs.pathname, locales)
if (localePathResult.detectedLocale) {
parsedAs.pathname = localePathResult.pathname
asPath = formatUrl(parsedAs)
} else {
// derive the default locale if it wasn't detected in the asPath
// since we don't prerender static pages with all possible default
// locales
defaultLocale = locale
}
// attempt detecting default locale based on hostname
const detectedDomain = detectDomainLocale(
process.env.__NEXT_I18N_DOMAINS as any,
window.location.hostname
)
// TODO: investigate if defaultLocale needs to be populated after
// hydration to prevent mismatched renders
if (detectedDomain) {
defaultLocale = detectedDomain.defaultLocale
}
}
}
type RegisterFn = (input: [string, () => void]) => void
2020-08-27 04:13:28 +02:00
const pageLoader = new PageLoader(buildId, prefix, page)
const register: RegisterFn = ([r, f]) => pageLoader.registerPage(r, f)
if (window.__NEXT_P) {
2020-05-23 22:38:48 +02:00
// Defer page registration for another tick. This will increase the overall
// latency in hydrating the page, but reduce the total blocking time.
2020-05-23 23:17:26 +02:00
window.__NEXT_P.map((p) => setTimeout(() => register(p), 0))
}
window.__NEXT_P = []
;(window.__NEXT_P as any).push = register
2017-04-05 08:45:39 +02:00
Remove `next-head-count` (#16758) Removes `next-head-count`, improving support for 3rd party libraries that insert or append new elements to `<head>`. --- This is more or less what a solution with a `data-` attribute would look like, except that instead of directly searching for elements with that attribute, we serialize the elements expected in `<head>` and then find them/assume ownership of them during initialization (in a manner similar to React's reconciliation) based on their properties. There are two main assumptions here: 1. Content is served with compression, so duplicate serialization of e.g. inline script or style tags doesn't have a meaningful impact. Storing a hash would be a potential optimization. 2. 3rd party libraries primarily only insert new, unique elements to head. Libraries trying to actively manage elements that overlap with those that Next.js claims ownership of will still be unsupported. The reason for this roundabout approach is that I'd really like to avoid `data-` if possible, for maximum compatibility. Implicitly adding an attribute could be a breaking change for some class of tools or crawlers and makes it otherwise impossible to insert raw HTML into `<head>`. Adding an unexpected attribute is why the original `class="next-head"` approach was problematic in the first place! That said, while I don't expect this to be more problematic than `next-head-count` (anything that would break in this new model also should have broken in the old model), if that does end up being the case, it might make sense to just bite the bullet. Fixes #11012 Closes #16707 --- cc @Timer @timneutkens
2020-09-09 03:41:04 +02:00
const headManager = initHeadManager(initialHeadData)
const appElement = document.getElementById('__next')
2017-04-05 13:43:34 +02:00
let lastAppProps: AppProps
let lastRenderReject: (() => void) | null
let webpackHMR: any
export let router: Router
let CachedComponent: React.ComponentType
let cachedStyleSheets: StyleSheetTuple[]
let CachedApp: AppComponent, onPerfEntry: (metric: any) => void
class Container extends React.Component<{
fn: (err: Error, info?: any) => void
}> {
componentDidCatch(componentErr: Error, info: any) {
this.props.fn(componentErr, info)
}
componentDidMount() {
this.scrollToHash()
// We need to replace the router state if:
// - the page was (auto) exported and has a query string or search (hash)
// - it was auto exported and is a dynamic route (to provide params)
// - if it is a client-side skeleton (fallback render)
if (
router.isSsr &&
(isFallback ||
(data.nextExport &&
(isDynamicRoute(router.pathname) || location.search)) ||
(hydrateProps && hydrateProps.__N_SSG && location.search))
) {
// update query on mount for exported pages
router.replace(
router.pathname +
'?' +
String(
querystring.assign(
querystring.urlQueryToSearchParams(router.query),
new URLSearchParams(location.search)
)
),
asPath,
{
// @ts-ignore
// 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.
_h: 1,
// Fallback pages must trigger the data fetch, so the transition is
// not shallow.
// Other pages (strictly updating query) happens shallowly, as data
// requirements would already be present.
shallow: !isFallback,
}
)
}
}
componentDidUpdate() {
this.scrollToHash()
}
scrollToHash() {
let { hash } = location
hash = hash && hash.substring(1)
if (!hash) return
const el = document.getElementById(hash)
if (!el) return
// If we call scrollIntoView() in here without a setTimeout
// it won't scroll properly.
setTimeout(() => el.scrollIntoView(), 0)
}
render() {
if (process.env.NODE_ENV === 'production') {
return this.props.children
} else {
2020-05-15 20:14:44 +02:00
const { ReactDevOverlay } = require('@next/react-dev-overlay/lib/client')
return <ReactDevOverlay>{this.props.children}</ReactDevOverlay>
}
}
}
export const emitter = mitt()
export default async (opts: { webpackHMR?: any } = {}) => {
// This makes sure this specific lines are removed in production
if (process.env.NODE_ENV === 'development') {
webpackHMR = opts.webpackHMR
}
const { page: app, mod } = await pageLoader.loadPage('/_app')
CachedApp = app as AppComponent
if (mod && mod.reportWebVitals) {
onPerfEntry = ({
id,
name,
startTime,
value,
duration,
entryType,
entries,
}) => {
// Combines timestamp with random number for unique ID
2020-05-18 21:24:37 +02:00
const uniqueID = `${Date.now()}-${
Math.floor(Math.random() * (9e12 - 1)) + 1e12
}`
let perfStartEntry
if (entries && entries.length) {
perfStartEntry = entries[0].startTime
}
mod.reportWebVitals({
id: id || uniqueID,
name,
startTime: startTime || perfStartEntry,
value: value == null ? duration : value,
label:
entryType === 'mark' || entryType === 'measure'
? 'custom'
: 'web-vital',
})
}
}
let initialErr = hydrateErr
2017-04-06 08:11:13 +02:00
try {
;({
page: CachedComponent,
styleSheets: cachedStyleSheets,
} = await pageLoader.loadPage(page))
if (process.env.NODE_ENV !== 'production') {
const { isValidElementType } = require('react-is')
if (!isValidElementType(CachedComponent)) {
throw new Error(
`The default export is not a React Component in page: "${page}"`
)
}
}
} catch (error) {
// This catches errors like throwing in the top level of a module
initialErr = error
2017-04-06 08:11:13 +02:00
}
if (process.env.NODE_ENV === 'development') {
const { getNodeError } = require('@next/react-dev-overlay/lib/client')
// Server-side runtime errors need to be re-thrown on the client-side so
// that the overlay is rendered.
if (initialErr) {
if (initialErr === hydrateErr) {
setTimeout(() => {
let error
try {
// Generate a new error object. We `throw` it because some browsers
// will set the `stack` when thrown, and we want to ensure ours is
// not overridden when we re-throw it below.
throw new Error(initialErr!.message)
} catch (e) {
error = e
}
error.name = initialErr!.name
error.stack = initialErr!.stack
const node = getNodeError(error)
throw node
})
}
// We replaced the server-side error with a client-side error, and should
// no longer rewrite the stack trace to a Node error.
else {
setTimeout(() => {
throw initialErr
})
}
}
}
if (window.__NEXT_PRELOADREADY) {
await window.__NEXT_PRELOADREADY(dynamicIds)
}
router = createRouter(page, query, asPath, {
initialProps: hydrateProps,
2017-04-06 08:11:13 +02:00
pageLoader,
App: CachedApp,
Component: CachedComponent,
initialStyleSheets: cachedStyleSheets,
wrapApp,
err: initialErr,
isFallback: Boolean(isFallback),
subscription: ({ Component, styleSheets, props, err }, App) =>
render({ App, Component, styleSheets, props, err }),
locale,
locales,
defaultLocale,
})
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
// call init-client middleware
if (process.env.__NEXT_PLUGINS) {
// @ts-ignore
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
// eslint-disable-next-line
import('next-plugin-loader?middleware=on-init-client!')
.then((initClientModule) => {
return initClientModule.default({ router })
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
})
.catch((initClientErr) => {
console.error('Error calling client-init for plugins', initClientErr)
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
})
}
const renderCtx = {
App: CachedApp,
Component: CachedComponent,
styleSheets: cachedStyleSheets,
props: hydrateProps,
err: initialErr,
}
if (process.env.NODE_ENV === 'production') {
render(renderCtx)
return emitter
} else {
return { emitter, render, renderCtx }
}
}
export async function render(renderingProps: RenderRouteInfo) {
if (renderingProps.err) {
await renderError(renderingProps)
return
}
try {
await doRender(renderingProps)
} catch (renderErr) {
// bubble up cancelation errors
if (renderErr.cancelled) {
throw renderErr
}
if (process.env.NODE_ENV === 'development') {
// Ensure this error is displayed in the overlay in development
setTimeout(() => {
throw renderErr
})
}
await renderError({ ...renderingProps, err: renderErr })
}
}
// This method handles all runtime and debug errors.
// 404 and 500 errors are special kind of errors
// and they are still handle via the main render method.
export function renderError(renderErrorProps: RenderErrorProps) {
const { App, err } = renderErrorProps
2020-05-15 20:14:44 +02:00
// In development runtime errors are caught by our overlay
2019-07-11 23:23:07 +02:00
// In production we catch runtime errors using componentDidCatch which will trigger renderError
if (process.env.NODE_ENV !== 'production') {
2020-05-15 20:14:44 +02:00
// A Next.js rendering runtime error is always unrecoverable
// FIXME: let's make this recoverable (error in GIP client-transition)
webpackHMR.onUnrecoverableError()
// We need to render an empty <App> so that the `<ReactDevOverlay>` can
// render itself.
return doRender({
App: () => null,
props: {},
Component: () => null,
styleSheets: [],
2020-05-15 20:14:44 +02:00
})
}
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
if (process.env.__NEXT_PLUGINS) {
// @ts-ignore
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
// eslint-disable-next-line
import('next-plugin-loader?middleware=on-error-client!')
.then((onClientErrorModule) => {
return onClientErrorModule.default({ err })
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
})
.catch((onClientErrorErr) => {
console.error(
'error calling on-error-client for plugins',
onClientErrorErr
)
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
})
}
// Make sure we log the error to the console, otherwise users can't track down issues.
console.error(err)
return pageLoader
.loadPage('/_error')
.then(({ page: ErrorComponent, styleSheets }) => {
// In production we do a normal render with the `ErrorComponent` as component.
// If we've gotten here upon initial render, we can use the props from the server.
// Otherwise, we need to call `getInitialProps` on `App` before mounting.
const AppTree = wrapApp(App)
const appCtx = {
Component: ErrorComponent,
AppTree,
router,
ctx: { err, pathname: page, query, asPath, AppTree },
}
return Promise.resolve(
renderErrorProps.props
? renderErrorProps.props
: loadGetInitialProps(App, appCtx)
).then((initProps) =>
doRender({
...renderErrorProps,
err,
Component: ErrorComponent,
styleSheets,
props: initProps,
})
)
})
}
// If hydrate does not exist, eg in preact.
let isInitialRender = typeof ReactDOM.hydrate === 'function'
let reactRoot: any = null
function renderReactElement(reactEl: JSX.Element, domEl: HTMLElement) {
if (process.env.__NEXT_REACT_MODE !== 'legacy') {
if (!reactRoot) {
const opts = { hydrate: true }
reactRoot =
process.env.__NEXT_REACT_MODE === 'concurrent'
? (ReactDOM as any).unstable_createRoot(domEl, opts)
: (ReactDOM as any).unstable_createBlockingRoot(domEl, opts)
}
reactRoot.render(reactEl)
} else {
// mark start of hydrate/render
if (ST) {
performance.mark('beforeRender')
}
// The check for `.hydrate` is there to support React alternatives like preact
if (isInitialRender) {
ReactDOM.hydrate(reactEl, domEl, markHydrateComplete)
isInitialRender = false
} else {
ReactDOM.render(reactEl, domEl, markRenderComplete)
}
}
}
function markHydrateComplete() {
if (!ST) return
performance.mark('afterHydrate') // mark end of hydration
performance.measure(
'Next.js-before-hydration',
'navigationStart',
'beforeRender'
)
performance.measure('Next.js-hydration', 'beforeRender', 'afterHydrate')
if (onPerfEntry) {
performance.getEntriesByName('Next.js-hydration').forEach(onPerfEntry)
}
clearMarks()
}
function markRenderComplete() {
if (!ST) return
performance.mark('afterRender') // mark end of render
const navStartEntries = performance.getEntriesByName('routeChange', 'mark')
if (!navStartEntries.length) {
return
}
performance.measure(
'Next.js-route-change-to-render',
navStartEntries[0].name,
'beforeRender'
)
performance.measure('Next.js-render', 'beforeRender', 'afterRender')
if (onPerfEntry) {
performance.getEntriesByName('Next.js-render').forEach(onPerfEntry)
performance
.getEntriesByName('Next.js-route-change-to-render')
.forEach(onPerfEntry)
}
clearMarks()
2020-05-18 21:24:37 +02:00
;['Next.js-route-change-to-render', 'Next.js-render'].forEach((measure) =>
performance.clearMeasures(measure)
)
}
function clearMarks() {
;[
'beforeRender',
'afterHydrate',
'afterRender',
'routeChange',
2020-05-18 21:24:37 +02:00
].forEach((mark) => performance.clearMarks(mark))
}
function AppContainer({
children,
}: React.PropsWithChildren<{}>): React.ReactElement {
return (
<Container
2020-05-18 21:24:37 +02:00
fn={(error) =>
renderError({ App: CachedApp, err: error }).catch((err) =>
console.error('Error rendering page: ', err)
)
}
>
<RouterContext.Provider value={makePublicRouterInstance(router)}>
<HeadManagerContext.Provider value={headManager}>
{children}
</HeadManagerContext.Provider>
</RouterContext.Provider>
</Container>
)
}
const wrapApp = (App: AppComponent) => (
wrappedAppProps: Record<string, any>
) => {
const appProps: AppProps = {
...wrappedAppProps,
Component: CachedComponent,
err: hydrateErr,
router,
}
return (
<AppContainer>
<App {...appProps} />
</AppContainer>
)
}
function doRender({
App,
Component,
props,
err,
styleSheets,
}: RenderRouteInfo): Promise<any> {
Component = Component || lastAppProps.Component
props = props || lastAppProps.props
const appProps: AppProps = {
...props,
Component,
err,
router,
}
// lastAppProps has to be set before ReactDom.render to account for ReactDom throwing an error.
lastAppProps = appProps
let canceled = false
let resolvePromise: () => void
const renderPromise = new Promise((resolve, reject) => {
if (lastRenderReject) {
lastRenderReject()
}
resolvePromise = () => {
lastRenderReject = null
resolve()
}
lastRenderReject = () => {
canceled = true
lastRenderReject = null
const error: any = new Error('Cancel rendering route')
error.cancelled = true
reject(error)
}
})
// This function has a return type to ensure it doesn't start returning a
// Promise. It should remain synchronous.
function onStart(): boolean {
if (
// We can skip this during hydration. Running it wont cause any harm, but
// we may as well save the CPU cycles.
isInitialRender ||
// We use `style-loader` in development, so we don't need to do anything
// unless we're in production:
process.env.NODE_ENV !== 'production'
) {
return false
}
const currentStyleTags = looseToArray<HTMLStyleElement>(
document.querySelectorAll('style[data-n-href]')
)
const currentHrefs = new Set(
currentStyleTags.map((tag) => tag.getAttribute('data-n-href'))
)
styleSheets.forEach(({ href, text }) => {
if (!currentHrefs.has(href)) {
const styleTag = document.createElement('style')
styleTag.setAttribute('data-n-href', href)
styleTag.setAttribute('media', 'x')
document.head.appendChild(styleTag)
styleTag.appendChild(document.createTextNode(text))
}
})
return true
}
function onCommit() {
if (
// We use `style-loader` in development, so we don't need to do anything
// unless we're in production:
process.env.NODE_ENV === 'production' &&
// We can skip this during hydration. Running it wont cause any harm, but
// we may as well save the CPU cycles:
!isInitialRender &&
// Ensure this render was not canceled
!canceled
) {
const desiredHrefs = new Set(styleSheets.map((s) => s.href))
const currentStyleTags = looseToArray<HTMLStyleElement>(
document.querySelectorAll('style[data-n-href]')
)
const currentHrefs = currentStyleTags.map(
(tag) => tag.getAttribute('data-n-href')!
)
// Toggle `<style>` tags on or off depending on if they're needed:
for (let idx = 0; idx < currentHrefs.length; ++idx) {
if (desiredHrefs.has(currentHrefs[idx])) {
currentStyleTags[idx].removeAttribute('media')
} else {
currentStyleTags[idx].setAttribute('media', 'x')
}
}
// Reorder styles into intended order:
let referenceNode = document.querySelector('noscript[data-n-css]')
if (
// This should be an invariant:
referenceNode
) {
styleSheets.forEach(({ href }) => {
const targetTag = document.querySelector(
`style[data-n-href="${href}"]`
)
if (
// This should be an invariant:
targetTag
) {
referenceNode!.parentNode!.insertBefore(
targetTag,
referenceNode!.nextSibling
)
referenceNode = targetTag
}
})
}
// Finally, clean up server rendered stylesheets:
looseToArray<HTMLLinkElement>(
document.querySelectorAll('link[data-n-p]')
).forEach((el) => {
el.parentNode!.removeChild(el)
})
// Force browser to recompute layout, which should prevent a flash of
// unstyled content:
getComputedStyle(document.body, 'height')
}
resolvePromise()
}
const elem = (
<Root callback={onCommit}>
<AppContainer>
<App {...appProps} />
</AppContainer>
</Root>
)
onStart()
// We catch runtime errors using componentDidCatch which will trigger renderError
renderReactElement(
process.env.__NEXT_STRICT_MODE ? (
<React.StrictMode>{elem}</React.StrictMode>
) : (
elem
),
appElement!
)
return renderPromise
}
function Root({
callback,
children,
}: React.PropsWithChildren<{
callback: () => void
}>): React.ReactElement {
// We use `useLayoutEffect` to guarantee the callback is executed
// as soon as React flushes the update.
React.useLayoutEffect(() => callback(), [callback])
if (process.env.__NEXT_TEST_MODE) {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
window.__NEXT_HYDRATED = true
if (window.__NEXT_HYDRATED_CB) {
window.__NEXT_HYDRATED_CB()
}
}, [])
}
// We should ask to measure the Web Vitals after rendering completes so we
// don't cause any hydration delay:
React.useEffect(() => {
measureWebVitals(onPerfEntry)
}, [])
return children as React.ReactElement
}