mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Introduce "asPath" into router and getInitialProps (#1857)
* Add asPath to next/router and getInitialProps context. * Add test cases. * Update docs. * Build as-path pages before they use.
This commit is contained in:
parent
6e89d49f99
commit
24f3f143a6
|
@ -29,6 +29,8 @@ const {
|
|||
location
|
||||
} = window
|
||||
|
||||
const asPath = getURL()
|
||||
|
||||
const pageLoader = new PageLoader(buildId, assetPrefix)
|
||||
window.__NEXT_LOADED_PAGES__.forEach(({ route, fn }) => {
|
||||
pageLoader.registerPage(route, fn)
|
||||
|
@ -56,7 +58,7 @@ export default async () => {
|
|||
Component = ErrorComponent
|
||||
}
|
||||
|
||||
router = createRouter(pathname, query, getURL(), {
|
||||
router = createRouter(pathname, query, asPath, {
|
||||
pageLoader,
|
||||
Component,
|
||||
ErrorComponent,
|
||||
|
@ -107,7 +109,7 @@ export async function renderError (error) {
|
|||
console.error(errorMessage)
|
||||
|
||||
if (prod) {
|
||||
const initProps = { err: error, pathname, query }
|
||||
const initProps = { err: error, pathname, query, asPath }
|
||||
const props = await loadGetInitialProps(ErrorComponent, initProps)
|
||||
ReactDOM.render(createElement(ErrorComponent, props), errorContainer)
|
||||
} else {
|
||||
|
@ -120,8 +122,8 @@ async function doRender ({ Component, props, hash, err, emitter }) {
|
|||
Component !== ErrorComponent &&
|
||||
lastAppProps.Component === ErrorComponent) {
|
||||
// fetch props if ErrorComponent was replaced with a page component by HMR
|
||||
const { pathname, query } = router
|
||||
props = await loadGetInitialProps(Component, { err, pathname, query })
|
||||
const { pathname, query, asPath } = router
|
||||
props = await loadGetInitialProps(Component, { err, pathname, query, asPath })
|
||||
}
|
||||
|
||||
if (emitter) {
|
||||
|
|
|
@ -13,7 +13,7 @@ const SingletonRouter = {
|
|||
}
|
||||
|
||||
// Create public properties and methods of the router in the SingletonRouter
|
||||
const propertyFields = ['components', 'pathname', 'route', 'query']
|
||||
const propertyFields = ['components', 'pathname', 'route', 'query', 'asPath']
|
||||
const coreMethodFields = ['push', 'replace', 'reload', 'back', 'prefetch']
|
||||
const routerEvents = ['routeChangeStart', 'beforeHistoryChange', 'routeChangeComplete', 'routeChangeError']
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ export default class Router {
|
|||
this.ErrorComponent = ErrorComponent
|
||||
this.pathname = pathname
|
||||
this.query = query
|
||||
this.as = as
|
||||
this.asPath = as
|
||||
this.subscriptions = new Set()
|
||||
this.componentLoadCancel = null
|
||||
this.onPopState = this.onPopState.bind(this)
|
||||
|
@ -190,7 +190,7 @@ export default class Router {
|
|||
}
|
||||
|
||||
const { Component } = routeInfo
|
||||
const ctx = { pathname, query }
|
||||
const ctx = { pathname, query, asPath: as }
|
||||
routeInfo.props = await this.getInitialProps(Component, ctx)
|
||||
|
||||
this.components[route] = routeInfo
|
||||
|
@ -229,13 +229,13 @@ export default class Router {
|
|||
this.route = route
|
||||
this.pathname = pathname
|
||||
this.query = query
|
||||
this.as = as
|
||||
this.asPath = as
|
||||
this.notify(data)
|
||||
}
|
||||
|
||||
onlyAHashChange (as) {
|
||||
if (!this.as) return false
|
||||
const [ oldUrlNoHash ] = this.as.split('#')
|
||||
if (!this.asPath) return false
|
||||
const [ oldUrlNoHash ] = this.asPath.split('#')
|
||||
const [ newUrlNoHash, newHash ] = as.split('#')
|
||||
|
||||
// If the urls are change, there's more than a hash change
|
||||
|
|
|
@ -238,6 +238,7 @@ export default Page
|
|||
|
||||
- `pathname` - path section of URL
|
||||
- `query` - query string section of URL parsed as an object
|
||||
- `asPath` - the actual url path
|
||||
- `req` - HTTP request object (server only)
|
||||
- `res` - HTTP response object (server only)
|
||||
- `jsonPageRes` - [Fetch Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object (client only)
|
||||
|
@ -283,6 +284,7 @@ Each top-level component receives a `url` property with the following API:
|
|||
|
||||
- `pathname` - `String` of the current path excluding the query string
|
||||
- `query` - `Object` with the parsed query string. Defaults to `{}`
|
||||
- `asPath` - `String` of the actual path (including the query) shows in the browser
|
||||
- `push(url, as=url)` - performs a `pushState` call with the given url
|
||||
- `replace(url, as=url)` - performs a `replaceState` call with the given url
|
||||
|
||||
|
|
|
@ -54,7 +54,8 @@ async function doRender (req, res, pathname, query, {
|
|||
])
|
||||
Component = Component.default || Component
|
||||
Document = Document.default || Document
|
||||
const ctx = { err, req, res, pathname, query }
|
||||
const asPath = req.url
|
||||
const ctx = { err, req, res, pathname, query, asPath }
|
||||
const props = await loadGetInitialProps(Component, ctx)
|
||||
|
||||
// the response might be finshed on the getinitialprops call
|
||||
|
|
22
test/integration/basic/pages/nav/as-path-using-router.js
Normal file
22
test/integration/basic/pages/nav/as-path-using-router.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import React from 'react'
|
||||
import Router from 'next/router'
|
||||
|
||||
export default class extends React.Component {
|
||||
constructor (...args) {
|
||||
super(...args)
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const asPath = Router.asPath
|
||||
this.setState({ asPath })
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='as-path-content'>
|
||||
{this.state.asPath}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
15
test/integration/basic/pages/nav/as-path.js
Normal file
15
test/integration/basic/pages/nav/as-path.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import React from 'react'
|
||||
|
||||
export default class extends React.Component {
|
||||
static getInitialProps ({ asPath, req }) {
|
||||
return { asPath }
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='as-path-content'>
|
||||
{this.props.asPath}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -35,6 +35,9 @@ export default class extends Component {
|
|||
<a id='query-string-link' style={linkStyle}>QueryString</a>
|
||||
</Link>
|
||||
<Link href='/nav/about' replace><a id='about-replace-link' style={linkStyle}>Replace state</a></Link>
|
||||
<Link href='/nav/as-path' as='/as/path'><a id='as-path-link' style={linkStyle}>As Path</a></Link>
|
||||
<Link href='/nav/as-path'><a id='as-path-link-no-as' style={linkStyle}>As Path (No as)</a></Link>
|
||||
<Link href='/nav/as-path-using-router'><a id='as-path-using-router-link' style={linkStyle}>As Path (Using Router)</a></Link>
|
||||
<button
|
||||
onClick={() => this.visitQueryStringPage()}
|
||||
style={linkStyle}
|
||||
|
|
|
@ -347,5 +347,44 @@ export default (context, render) => {
|
|||
browser.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe('with asPath', () => {
|
||||
describe('inside getInitialProps', () => {
|
||||
it('should show the correct asPath with a Link with as prop', async () => {
|
||||
const browser = await webdriver(context.appPort, '/nav/')
|
||||
const asPath = await browser
|
||||
.elementByCss('#as-path-link').click()
|
||||
.waitForElementByCss('.as-path-content')
|
||||
.elementByCss('.as-path-content').text()
|
||||
|
||||
expect(asPath).toBe('/as/path')
|
||||
browser.close()
|
||||
})
|
||||
|
||||
it('should show the correct asPath with a Link without the as prop', async () => {
|
||||
const browser = await webdriver(context.appPort, '/nav/')
|
||||
const asPath = await browser
|
||||
.elementByCss('#as-path-link-no-as').click()
|
||||
.waitForElementByCss('.as-path-content')
|
||||
.elementByCss('.as-path-content').text()
|
||||
|
||||
expect(asPath).toBe('/nav/as-path')
|
||||
browser.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe('with next/router', () => {
|
||||
it('should show the correct asPath', async () => {
|
||||
const browser = await webdriver(context.appPort, '/nav/')
|
||||
const asPath = await browser
|
||||
.elementByCss('#as-path-using-router-link').click()
|
||||
.waitForElementByCss('.as-path-content')
|
||||
.elementByCss('.as-path-content').text()
|
||||
|
||||
expect(asPath).toBe('/nav/as-path-using-router')
|
||||
browser.close()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -51,6 +51,8 @@ describe('Basic Features', () => {
|
|||
renderViaHTTP(context.appPort, '/nav/hash-changes'),
|
||||
renderViaHTTP(context.appPort, '/nav/shallow-routing'),
|
||||
renderViaHTTP(context.appPort, '/nav/redirect'),
|
||||
renderViaHTTP(context.appPort, '/nav/as-path'),
|
||||
renderViaHTTP(context.appPort, '/nav/as-path-using-router'),
|
||||
|
||||
renderViaHTTP(context.appPort, '/nested-cdm/index')
|
||||
])
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
import cheerio from 'cheerio'
|
||||
|
||||
export default function ({ app }, suiteName, render) {
|
||||
async function get$ (path) {
|
||||
const html = await render(path)
|
||||
async function get$ (path, query) {
|
||||
const html = await render(path, query)
|
||||
return cheerio.load(html)
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,11 @@ export default function ({ app }, suiteName, render) {
|
|||
expect($('pre').text()).toMatch(/This is an expected error/)
|
||||
})
|
||||
|
||||
test('asPath', async () => {
|
||||
const $ = await get$('/nav/as-path', { aa: 10 })
|
||||
expect($('.as-path-content').text()).toBe('/nav/as-path?aa=10')
|
||||
})
|
||||
|
||||
test('error 404', async () => {
|
||||
const $ = await get$('/non-existent')
|
||||
expect($('h1').text()).toBe('404')
|
||||
|
|
|
@ -11,7 +11,8 @@ export const nextBuild = build
|
|||
export const pkg = _pkg
|
||||
|
||||
export function renderViaAPI (app, pathname, query = {}) {
|
||||
return app.renderToHTML({}, {}, pathname, query)
|
||||
const url = `${pathname}?${qs.stringify(query)}`
|
||||
return app.renderToHTML({ url }, {}, pathname, query)
|
||||
}
|
||||
|
||||
export function renderViaHTTP (appPort, pathname, query = {}) {
|
||||
|
|
Loading…
Reference in a new issue