-
Notifications
You must be signed in to change notification settings - Fork 26.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expose app.js #4129
Expose app.js #4129
Changes from 4 commits
e24d861
d01623f
6eb23be
7c9a422
407f067
1ac18ca
8c1d25c
a87e37f
ffcc795
97ca475
3bec435
1e7cde8
3ac230a
b7bbee2
f126dfb
30089b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('./dist/lib/app') |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,34 @@ | ||
import React, { Component } from 'react' | ||
import PropTypes from 'prop-types' | ||
import shallowEquals from './shallow-equals' | ||
import { warn } from './utils' | ||
import { warn, loadGetInitialProps } from './utils' | ||
import { makePublicRouterInstance } from './router' | ||
|
||
export default class App extends Component { | ||
state = { | ||
hasError: null | ||
} | ||
|
||
static displayName = 'App' | ||
|
||
static async getInitialProps ({ Component, router, ctx }) { | ||
const pageProps = await loadGetInitialProps(Component, ctx) | ||
return {pageProps} | ||
} | ||
|
||
static childContextTypes = { | ||
_containerProps: PropTypes.any, | ||
headManager: PropTypes.object, | ||
router: PropTypes.object | ||
} | ||
|
||
getChildContext () { | ||
const { headManager } = this.props | ||
const {hasError} = this.state | ||
return { | ||
headManager, | ||
router: makePublicRouterInstance(this.props.router) | ||
router: makePublicRouterInstance(this.props.router), | ||
_containerProps: {...this.props, hasError} | ||
} | ||
} | ||
|
||
|
@@ -29,22 +39,19 @@ export default class App extends Component { | |
} | ||
|
||
render () { | ||
if (this.state.hasError) return null | ||
|
||
const { Component, props, hash, router } = this.props | ||
const {router, Component, pageProps} = this.props | ||
const url = createUrl(router) | ||
// If there no component exported we can't proceed. | ||
// We'll tackle that here. | ||
if (typeof Component !== 'function') { | ||
throw new Error(`The default export is not a React Component in page: "${url.pathname}"`) | ||
} | ||
const containerProps = { Component, props, hash, router, url } | ||
|
||
return <Container {...containerProps} /> | ||
return <Container> | ||
<Component url={url} {...pageProps} /> | ||
</Container> | ||
} | ||
} | ||
|
||
class Container extends Component { | ||
export class Container extends Component { | ||
static contextTypes = { | ||
_containerProps: PropTypes.any | ||
} | ||
|
||
componentDidMount () { | ||
this.scrollToHash() | ||
} | ||
|
@@ -71,10 +78,16 @@ class Container extends Component { | |
} | ||
|
||
render () { | ||
const { Component, props, url } = this.props | ||
const { hasError } = this.context._containerProps | ||
|
||
if (hasError) { | ||
return null | ||
} | ||
|
||
const {children} = this.props | ||
|
||
if (process.env.NODE_ENV === 'production') { | ||
return (<Component {...props} url={url} />) | ||
return <>{children}</> | ||
} else { | ||
const ErrorDebug = require('./error-debug').default | ||
const { AppContainer } = require('react-hot-loader') | ||
|
@@ -83,39 +96,48 @@ class Container extends Component { | |
// https://github.com/gaearon/react-hot-loader/issues/442 | ||
return ( | ||
<AppContainer warnings={false} errorReporter={ErrorDebug}> | ||
<Component {...props} url={url} /> | ||
{children} | ||
</AppContainer> | ||
) | ||
} | ||
} | ||
} | ||
|
||
function createUrl (router) { | ||
export function createUrl (router) { | ||
return { | ||
query: router.query, | ||
pathname: router.pathname, | ||
asPath: router.asPath, | ||
get query () { | ||
warn(`Warning: 'url.query' is deprecated. https://err.sh/next.js/url-deprecated`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shall we make sure we only print this once? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done 👍 |
||
return router.query | ||
}, | ||
get pathname () { | ||
warn(`Warning: 'url.pathname' is deprecated. https://err.sh/next.js/url-deprecated`) | ||
return router.pathname | ||
}, | ||
get asPath () { | ||
warn(`Warning: 'url.asPath' is deprecated. https://err.sh/next.js/url-deprecated`) | ||
return router.asPath | ||
}, | ||
back: () => { | ||
warn(`Warning: 'url.back()' is deprecated. Use "window.history.back()"`) | ||
warn(`Warning: 'url.back()' is deprecated. Use "window.history.back()" https://err.sh/next.js/url-deprecated`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we use something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this was deprecated in v2 or v3. That's what I didn't want to touch it too much, just adding the url to the message. In theory we could use the same message though. Will update it. |
||
router.back() | ||
}, | ||
push: (url, as) => { | ||
warn(`Warning: 'url.push()' is deprecated. Use "next/router" APIs.`) | ||
warn(`Warning: 'url.push()' is deprecated. Use "next/router" APIs. https://err.sh/next.js/url-deprecated`) | ||
return router.push(url, as) | ||
}, | ||
pushTo: (href, as) => { | ||
warn(`Warning: 'url.pushTo()' is deprecated. Use "next/router" APIs.`) | ||
warn(`Warning: 'url.pushTo()' is deprecated. Use "next/router" APIs. https://err.sh/next.js/url-deprecated`) | ||
const pushRoute = as ? href : null | ||
const pushUrl = as || href | ||
|
||
return router.push(pushRoute, pushUrl) | ||
}, | ||
replace: (url, as) => { | ||
warn(`Warning: 'url.replace()' is deprecated. Use "next/router" APIs.`) | ||
warn(`Warning: 'url.replace()' is deprecated. Use "next/router" APIs. https://err.sh/next.js/url-deprecated`) | ||
return router.replace(url, as) | ||
}, | ||
replaceTo: (href, as) => { | ||
warn(`Warning: 'url.replaceTo()' is deprecated. Use "next/router" APIs.`) | ||
warn(`Warning: 'url.replaceTo()' is deprecated. Use "next/router" APIs. https://err.sh/next.js/url-deprecated`) | ||
const replaceRoute = as ? href : null | ||
const replaceUrl = as || href | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('next/app') |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -923,6 +923,47 @@ const HelloBundle = dynamic({ | |
export default () => <HelloBundle title="Dynamic Bundle" /> | ||
``` | ||
|
||
### Custom `<App>` | ||
|
||
<p><details> | ||
<summary><b>Examples</b></summary> | ||
<ul><li><a href="./examples/layout-component">Using `_app.js` for layout</a></li></ul> | ||
<ul><li><a href="./examples/componentdidcatch">Using `_app.js` to override `componentDidCatch`</a></li></ul> | ||
</details></p> | ||
|
||
- Is rendered on both client and server side | ||
- `this.state` should not be overwritten. Instead create a new component to handle the state ([layout example](./examples/layout-component)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not clear. What are we going to say. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a clearer example. |
||
- Handles calling `getInitialProps` of the **page** `Component` | ||
- Implements `componentDidCatch` on the client side, and renders the error page accordingly. ([overriding `componentDidCatch` example](./examples/componentdidcatch)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's try not say what've inside Next.js has a App layout which initialize pages in your app. You can override it with
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated |
||
- The `Container` implements React Hot Loader when in development mode. | ||
- The `Container` implements scrolling to a hash when using `<Link href="/about#example-hash">` | ||
|
||
To override the default `_app.js` behaviour you can create a file `./pages/_app.js`, where you can extend the `App` class: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To override, create a file in |
||
|
||
```js | ||
import App, {Container} from 'next/app' | ||
import React from 'react' | ||
|
||
export default class MyApp extends App { | ||
static async getInitialProps ({ Component, router, ctx }) { | ||
let pageProps = {} | ||
|
||
if (Component.getInitialProps) { | ||
pageProps = await Component.getInitialProps(ctx) | ||
} | ||
|
||
return {pageProps} | ||
} | ||
|
||
render () { | ||
const {Component, pageProps} = this.props | ||
return <Container> | ||
<Component {...pageProps} /> | ||
</Container> | ||
} | ||
} | ||
``` | ||
|
||
### Custom `<Document>` | ||
|
||
<p><details> | ||
|
@@ -931,6 +972,10 @@ export default () => <HelloBundle title="Dynamic Bundle" /> | |
<ul><li><a href="./examples/with-amp">Google AMP</a></li></ul> | ||
</details></p> | ||
|
||
- Is rendered on the server side | ||
- Is used to change the initial server side rendered document markup | ||
- Commonly used to implement server side rendering for css-in-js libraries like [styled-components](./examples/with-styled-components), [glamorous](./examples/with-glamorous) or [emotion](with-emotion). [styled-jsx](https://github.com/zeit/styled-jsx) is included with Next.js by default. | ||
|
||
Pages in `Next.js` skip the definition of the surrounding document's markup. For example, you never include `<html>`, `<body>`, etc. To override that default behavior, you must create a file at `./pages/_document.js`, where you can extend the `Document` class: | ||
|
||
```jsx | ||
|
@@ -969,7 +1014,7 @@ The `ctx` object is equivalent to the one received in all [`getInitialProps`](#f | |
|
||
- `renderPage` (`Function`) a callback that executes the actual React rendering logic (synchronously). It's useful to decorate this function in order to support server-rendering wrappers like Aphrodite's [`renderStatic`](https://github.com/Khan/aphrodite#server-side-rendering) | ||
|
||
__Note: React-components outside of `<Main />` will not be initialised by the browser. If you need shared components in all your pages (like a menu or a toolbar), do _not_ add application logic here, but take a look at [this example](https://github.com/zeit/next.js/tree/master/examples/layout-component).__ | ||
__Note: React-components outside of `<Main />` will not be initialised by the browser. Do _not_ add application logic here. If you need shared components in all your pages (like a menu or a toolbar), take a look at the `App` component instead.__ | ||
|
||
### Custom error handling | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If user didn't override
getInitialProps
in his/her sub class, is this going to execute?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep 👍 (Basically the same as
_document.js
that also implementsgetInitialProps