未验证 提交 7e7f2c0a 编写于 作者: T Tim Neutkens 提交者: GitHub

Simplify a few parts of the codebase (#7506)

* Move client-side dev JS to dev folder

* Move eventsource polyfill

* Move source-map-support

* Move error boundary

* Deprecate Container in _app

* Make initialRender check better

* Remove unused code

* Only support one subscription as there is only one

* Don’t spread object

* Shorten property name

* Add container in development too

* Simplify query update logic
上级 c821e830
......@@ -38,7 +38,6 @@
],
"*.ts": [
"prettier --write",
"standard --fix --parser typescript-eslint-parser --plugin typescript",
"git add"
]
},
......
......@@ -34,10 +34,12 @@ type RouteInfo = {
error?: any
}
type Subscription = (data: { App?: ComponentType } & RouteInfo) => void
type Subscription = (data: RouteInfo, App?: ComponentType) => void
type BeforePopStateCallback = (state: any) => boolean
type ComponentLoadCancel = (() => void) | null
export default class Router implements BaseRouter {
route: string
pathname: string
......@@ -47,8 +49,8 @@ export default class Router implements BaseRouter {
* Map of all components loaded in `Router`
*/
components: { [pathname: string]: RouteInfo }
subscriptions: Set<Subscription>
componentLoadCancel: (() => void) | null
sub: Subscription
clc: ComponentLoadCancel
pageLoader: any
_bps: BeforePopStateCallback | undefined
......@@ -64,7 +66,9 @@ export default class Router implements BaseRouter {
App,
Component,
err,
subscription,
}: {
subscription: Subscription
initialProps: any
pageLoader: any
Component: ComponentType
......@@ -95,8 +99,8 @@ export default class Router implements BaseRouter {
this.pathname = pathname
this.query = query
this.asPath = as
this.subscriptions = new Set()
this.componentLoadCancel = null
this.sub = subscription
this.clc = null
if (typeof window !== 'undefined') {
// in order for `e.state` to work on the `onpopstate` event
......@@ -522,7 +526,7 @@ export default class Router implements BaseRouter {
async fetchComponent(route: string): Promise<ComponentType> {
let cancelled = false
const cancel = (this.componentLoadCancel = () => {
const cancel = (this.clc = () => {
cancelled = true
})
......@@ -536,8 +540,8 @@ export default class Router implements BaseRouter {
throw error
}
if (cancel === this.componentLoadCancel) {
this.componentLoadCancel = null
if (cancel === this.clc) {
this.clc = null
}
return Component
......@@ -551,7 +555,7 @@ export default class Router implements BaseRouter {
const cancel = () => {
cancelled = true
}
this.componentLoadCancel = cancel
this.clc = cancel
const { Component: App } = this.components['/_app']
const props = await loadGetInitialProps<AppContextType<Router>>(App, {
......@@ -560,8 +564,8 @@ export default class Router implements BaseRouter {
ctx,
})
if (cancel === this.componentLoadCancel) {
this.componentLoadCancel = null
if (cancel === this.clc) {
this.clc = null
}
if (cancelled) {
......@@ -574,20 +578,14 @@ export default class Router implements BaseRouter {
}
abortComponentLoad(as: string): void {
if (this.componentLoadCancel) {
if (this.clc) {
Router.events.emit('routeChangeError', new Error('Route Cancelled'), as)
this.componentLoadCancel()
this.componentLoadCancel = null
this.clc()
this.clc = null
}
}
notify(data: RouteInfo): void {
const { Component: App } = this.components['/_app']
this.subscriptions.forEach(fn => fn({ ...data, App }))
}
subscribe(fn: Subscription): () => void {
this.subscriptions.add(fn)
return () => this.subscriptions.delete(fn)
this.sub(data, this.components['/_app'].Component)
}
}
......@@ -11,7 +11,7 @@ import nanoid from 'next/dist/compiled/nanoid/index.js'
import path from 'path'
import fs from 'fs'
import { promisify } from 'util'
import formatWebpackMessages from '../client/dev-error-overlay/format-webpack-messages'
import formatWebpackMessages from '../client/dev/error-overlay/format-webpack-messages'
import { recursiveDelete } from '../lib/recursive-delete'
import { verifyTypeScriptSetup } from '../lib/verifyTypeScriptSetup'
import { CompilerResult, runCompiler } from './compiler'
......
......@@ -3,7 +3,7 @@ import textTable from 'next/dist/compiled/text-table'
import createStore from 'next/dist/compiled/unistore'
import stripAnsi from 'strip-ansi'
import formatWebpackMessages from '../../client/dev-error-overlay/format-webpack-messages'
import formatWebpackMessages from '../../client/dev/error-overlay/format-webpack-messages'
import { OutputState, store as consoleStore } from './store'
export function startedDevelopmentServer(appUrl: string) {
......
/* globals __webpack_hash__ */
import fetch from 'unfetch'
import EventSourcePolyfill from './event-source-polyfill'
import { getEventSourceWrapper } from './dev-error-overlay/eventsource'
import { getEventSourceWrapper } from './error-overlay/eventsource'
import { setupPing } from './on-demand-entries-utils'
if (!window.EventSource) {
......
import { getEventSourceWrapper } from './dev-error-overlay/eventsource'
import { getEventSourceWrapper } from './error-overlay/eventsource'
export default function initializeBuildWatcher () {
const shadowHost = document.createElement('div')
......
......@@ -30,7 +30,7 @@ import { getEventSourceWrapper } from './eventsource'
import formatWebpackMessages from './format-webpack-messages'
import * as ErrorOverlay from 'react-error-overlay'
import stripAnsi from 'strip-ansi'
import { rewriteStacktrace } from '../source-map-support'
import { rewriteStacktrace } from './source-map-support'
import fetch from 'unfetch'
// This alternative WebpackDevServer combines the functionality of:
......
/* global window, location */
import fetch from 'unfetch'
import { getEventSourceWrapper } from './dev-error-overlay/eventsource'
import { getEventSourceWrapper } from './error-overlay/eventsource'
let evtSource
export let currentPage
......
import connect from './dev-error-overlay/hot-dev-client'
import connect from './error-overlay/hot-dev-client'
export default ({ assetPrefix }) => {
const options = {
......
import React, { ErrorInfo } from 'react'
export class ErrorBoundary extends React.Component<{
fn: (err: Error, info: ErrorInfo) => void
}> {
componentDidCatch(err: Error, info: ErrorInfo) {
this.props.fn(err, info)
}
render() {
return this.props.children
}
}
......@@ -6,12 +6,12 @@ import mitt from 'next-server/dist/lib/mitt'
import { loadGetInitialProps, getURL } from 'next-server/dist/lib/utils'
import PageLoader from './page-loader'
import * as envConfig from 'next-server/config'
import { ErrorBoundary } from './error-boundary'
import Loadable from 'next-server/dist/lib/loadable'
import { HeadManagerContext } from 'next-server/dist/lib/head-manager-context'
import { DataManagerContext } from 'next-server/dist/lib/data-manager-context'
import { RouterContext } from 'next-server/dist/lib/router-context'
import { DataManager } from 'next-server/dist/lib/data-manager'
import { parse as parseQs, stringify as stringifyQs } from 'querystring'
// Polyfill Promise globally
// This is needed because Webpack's dynamic loading(common chunks) code
......@@ -71,6 +71,55 @@ export let ErrorComponent
let Component
let App
class Container extends React.Component {
componentDidCatch (err, info) {
this.props.fn(err, info)
}
componentDidMount () {
this.scrollToHash()
// If page was exported and has a querystring
// If it's a dynamic route (/$ inside) or has a querystring
if (
data.nextExport &&
(router.pathname.indexOf('/$') !== -1 || window.location.search)
) {
// update query on mount for exported pages
router.replace(
router.pathname +
'?' +
stringifyQs({
...router.query,
...parseQs(window.location.search.substr(1))
}),
asPath
)
}
}
componentDidUpdate () {
this.scrollToHash()
}
scrollToHash () {
let { hash } = window.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 () {
return this.props.children
}
}
export const emitter = mitt()
export default async ({ webpackHMR: passedWebpackHMR } = {}) => {
......@@ -109,11 +158,10 @@ export default async ({ webpackHMR: passedWebpackHMR } = {}) => {
pageLoader,
App,
Component,
err: initialErr
})
router.subscribe(({ App, Component, props, err }) => {
render({ App, Component, props, err, emitter })
err: initialErr,
subscription: ({ Component, props, err }, App) => {
render({ App, Component, props, err, emitter })
}
})
render({ App, Component, props, err: initialErr, emitter })
......@@ -163,10 +211,11 @@ export async function renderError (props) {
await doRender({ ...props, err, Component: ErrorComponent, props: initProps })
}
let isInitialRender = true
// If hydrate does not exist, eg in preact.
let isInitialRender = typeof ReactDOM.hydrate === 'function'
function renderReactElement (reactEl, domEl) {
// The check for `.hydrate` is there to support React alternatives like preact
if (isInitialRender && typeof ReactDOM.hydrate === 'function') {
if (isInitialRender) {
ReactDOM.hydrate(reactEl, domEl)
isInitialRender = false
} else {
......@@ -207,21 +256,29 @@ async function doRender ({ App, Component, props, err }) {
// In development runtime errors are caught by react-error-overlay.
if (process.env.NODE_ENV === 'development') {
renderReactElement(
<Suspense fallback={<div>Loading...</div>}>
<RouterContext.Provider value={makePublicRouterInstance(router)}>
<DataManagerContext.Provider value={dataManager}>
<HeadManagerContext.Provider value={headManager.updateHead}>
<App {...appProps} />
</HeadManagerContext.Provider>
</DataManagerContext.Provider>
</RouterContext.Provider>
</Suspense>,
<Container
fn={error =>
renderError({ App, err: error }).catch(err =>
console.error('Error rendering page: ', err)
)
}
>
<Suspense fallback={<div>Loading...</div>}>
<RouterContext.Provider value={makePublicRouterInstance(router)}>
<DataManagerContext.Provider value={dataManager}>
<HeadManagerContext.Provider value={headManager.updateHead}>
<App {...appProps} />
</HeadManagerContext.Provider>
</DataManagerContext.Provider>
</RouterContext.Provider>
</Suspense>
</Container>,
appContainer
)
} else {
// In production we catch runtime errors using componentDidCatch which will trigger renderError.
renderReactElement(
<ErrorBoundary
<Container
fn={error =>
renderError({ App, err: error }).catch(err =>
console.error('Error rendering page: ', err)
......@@ -237,7 +294,7 @@ async function doRender ({ App, Component, props, err }) {
</DataManagerContext.Provider>
</RouterContext.Provider>
</Suspense>
</ErrorBoundary>,
</Container>,
appContainer
)
}
......
import initNext, * as next from './'
import EventSourcePolyfill from './event-source-polyfill'
import initOnDemandEntries from './on-demand-entries-client'
import initWebpackHMR from './webpack-hot-middleware-client'
import initializeBuildWatcher from './dev-build-watcher'
import EventSourcePolyfill from './dev/event-source-polyfill'
import initOnDemandEntries from './dev/on-demand-entries-client'
import initWebpackHMR from './dev/webpack-hot-middleware-client'
import initializeBuildWatcher from './dev/dev-build-watcher'
// Temporary workaround for the issue described here:
// https://github.com/zeit/next.js/issues/3775#issuecomment-407438123
......
......@@ -7,12 +7,7 @@ import {
AppInitialProps,
AppPropsType,
} from 'next-server/dist/lib/utils'
import {
default as singletonRouter,
Router,
makePublicRouterInstance,
} from '../client/router'
import { parse as parseQs, stringify as stringifyQs } from 'querystring'
import { Router, makePublicRouterInstance } from '../client/router'
export { AppInitialProps }
......@@ -65,55 +60,9 @@ export default class App<P = {}, CP = P> extends React.Component<
}
}
export class Container extends React.Component {
componentDidMount() {
this.scrollToHash()
// @ts-ignore __NEXT_DATA__ is global
if (__NEXT_DATA__.nextExport) {
const curQuery = '?' + stringifyQs(singletonRouter.query)
const hasDiffQuery = location.search && curQuery !== location.search
const isDynamic = singletonRouter.pathname.indexOf('/$') !== -1
if (isDynamic || hasDiffQuery) {
const parsedQuery = parseQs(
location.search.startsWith('?')
? location.search.substr(1)
: location.search
)
// update query on mount for exported pages
let qsString = stringifyQs({
...singletonRouter.query,
...parsedQuery,
})
qsString = qsString ? '?' + qsString : qsString
singletonRouter.replace(
singletonRouter.pathname + qsString,
location.pathname + qsString
)
}
}
}
componentDidUpdate() {
this.scrollToHash()
}
private scrollToHash() {
let { hash } = window.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() {
return this.props.children
}
// @deprecated noop for now until removal
export function Container(p: any) {
return p.children
}
const warnUrl = execOnce(() => {
......
......@@ -207,7 +207,8 @@ export class Head extends Component<OriginProps> {
// show a warning if Head contains <title> (only in development)
if (process.env.NODE_ENV !== 'production') {
children = React.Children.map(children, (child: any) => {
const isReactHelmet = child && child.props && child.props['data-react-helmet']
const isReactHelmet =
child && child.props && child.props['data-react-helmet']
if (child && child.type === 'title' && !isReactHelmet) {
console.warn(
"Warning: <title> should not be used in _document.js's <Head>. https://err.sh/next.js/no-document-title"
......
......@@ -227,7 +227,10 @@ export default class HotReloader {
let additionalClientEntrypoints = {}
additionalClientEntrypoints[CLIENT_STATIC_FILES_RUNTIME_AMP] =
`.${sep}` +
relativePath(this.dir, join(NEXT_PROJECT_ROOT_DIST_CLIENT, 'amp-dev'))
relativePath(
this.dir,
join(NEXT_PROJECT_ROOT_DIST_CLIENT, 'dev', 'amp-dev')
)
return Promise.all([
getBaseWebpackConfig(this.dir, {
......
......@@ -31,7 +31,7 @@ module.exports = function (task) {
if (file.base === 'next-dev.js') {
output.code = output.code.replace(
'// REPLACE_NOOP_IMPORT',
`import('./noop');`
`import('./dev/noop');`
)
}
......
import React from 'react'
import { NextPageContext, NextComponentType, NextApiResponse, NextApiRequest } from 'next-server/dist/lib/utils'
import {
NextPageContext,
NextComponentType,
NextApiResponse,
NextApiRequest,
} from 'next-server/dist/lib/utils'
/// <reference types="node" />
/// <reference types="react" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册