Improve exported router types (#7853)

* Added the RouteUrl type and improved router types

* Added more tests for router types

* Add build test for typescript types

* Add next-env.d.ts to the typescript test

* Removed next-env.d.ts

* Added next-env.d.ts to gitignore

* Remove route url re-exports

* renamed PublicRouterInstance to be NextRouter

* export the Url type

* Replaced BaseRouter with NextRouter in server/utils

* Don't export the Url type

* Update tsconfig.json
This commit is contained in:
Luis Fernando Alvarez D 2019-07-11 12:35:39 -05:00 committed by Joe Haddad
parent 13cf664898
commit b94123ccc9
13 changed files with 78 additions and 30 deletions

1
.gitignore vendored
View file

@ -19,6 +19,7 @@ coverage
# test output
test/**/out*
test/**/next-env.d.ts
.DS_Store
# Editors

View file

@ -1,3 +1,4 @@
import * as React from 'react'
import { NextRouter } from './router/router'
export const RouterContext: React.Context<any> = React.createContext(null)
export const RouterContext = React.createContext<NextRouter>(null as any)

View file

@ -30,6 +30,18 @@ export type BaseRouter = {
asPath: string
}
export type NextRouter = BaseRouter &
Pick<
Router,
| 'push'
| 'replace'
| 'reload'
| 'back'
| 'prefetch'
| 'beforePopState'
| 'events'
>
type RouteInfo = {
Component: ComponentType
props?: any
@ -56,6 +68,7 @@ export default class Router implements BaseRouter {
clc: ComponentLoadCancel
pageLoader: any
_bps: BeforePopStateCallback | undefined
events: MittEmitter
static events: MittEmitter = mitt()

View file

@ -3,7 +3,7 @@ import { ServerResponse, IncomingMessage } from 'http'
import { ComponentType } from 'react'
import { ParsedUrlQuery } from 'querystring'
import { ManifestItem } from '../server/render'
import { BaseRouter } from './router/router'
import { NextRouter } from './router/router'
/**
* Types used by both next and next-server
@ -97,7 +97,7 @@ export interface NextPageContext {
asPath?: string
}
export type AppContextType<R extends BaseRouter = BaseRouter> = {
export type AppContextType<R extends NextRouter = NextRouter> = {
Component: NextComponentType<NextPageContext>
router: R
ctx: NextPageContext
@ -108,7 +108,7 @@ export type AppInitialProps = {
}
export type AppPropsType<
R extends BaseRouter = BaseRouter,
R extends NextRouter = NextRouter,
P = {}
> = AppInitialProps & {
Component: NextComponentType<NextPageContext, any, P>

View file

@ -2,7 +2,7 @@ import { IncomingMessage, ServerResponse } from 'http'
import { ParsedUrlQuery } from 'querystring'
import React from 'react'
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
import { BaseRouter } from '../lib/router/router'
import { NextRouter } from '../lib/router/router'
import mitt, { MittEmitter } from '../lib/mitt'
import {
loadGetInitialProps,
@ -45,11 +45,12 @@ function noRouter() {
throw new Error(message)
}
class ServerRouter implements BaseRouter {
class ServerRouter implements NextRouter {
route: string
pathname: string
query: ParsedUrlQuery
asPath: string
events: any
// TODO: Remove in the next major version, as this would mean the user is adding event listeners in server-side `render` method
static events: MittEmitter = mitt()
@ -60,23 +61,19 @@ class ServerRouter implements BaseRouter {
this.asPath = as
this.pathname = pathname
}
// @ts-ignore
push() {
push(): any {
noRouter()
}
// @ts-ignore
replace() {
replace(): any {
noRouter()
}
// @ts-ignore
reload() {
noRouter()
}
back() {
noRouter()
}
// @ts-ignore
prefetch() {
prefetch(): any {
noRouter()
}
beforePopState() {

View file

@ -1,6 +1,6 @@
/* global window */
import React from 'react'
import Router, { BaseRouter } from 'next-server/dist/lib/router/router'
import Router, { NextRouter } from 'next-server/dist/lib/router/router'
import { RouterContext } from 'next-server/dist/lib/router-context'
import { RequestContext } from 'next-server/dist/lib/request-context'
@ -14,17 +14,9 @@ type SingletonRouterBase = {
ready(cb: () => any): void
}
export { Router }
export { Router, NextRouter }
export type PublicRouterInstance = BaseRouter &
Pick<
Router,
'push' | 'replace' | 'reload' | 'back' | 'prefetch' | 'beforePopState'
> & {
events: typeof Router['events']
}
export type SingletonRouter = SingletonRouterBase & PublicRouterInstance
export type SingletonRouter = SingletonRouterBase & NextRouter
const singletonRouter: SingletonRouterBase = {
router: null, // holds the actual router instance
@ -146,7 +138,7 @@ export const createRouter = (...args: RouterArgs) => {
}
// This function is used to create the `withRouter` router instance
export function makePublicRouterInstance(router: Router): PublicRouterInstance {
export function makePublicRouterInstance(router: Router): NextRouter {
const _router = router as any
const instance = {} as any

View file

@ -1,10 +1,10 @@
import React from 'react'
import PropTypes from 'prop-types'
import { NextComponentType, NextPageContext } from 'next-server/dist/lib/utils'
import { PublicRouterInstance } from './router'
import { NextRouter } from './router'
export type WithRouterProps = {
router: PublicRouterInstance
router: NextRouter
}
export type ExcludeRouterProps<P> = Pick<

View file

@ -26,7 +26,7 @@ export function createHook(
throw new Error('key not provided to createHook options.')
}
return function useData(...args: Array<string | number>) {
const router: import('next-server/lib/router/router').default = useContext(
const router: import('next-server/lib/router/router').NextRouter = useContext(
RouterContext
)
const dataManager: import('next-server/lib/data-manager').DataManager = useContext(

View file

@ -0,0 +1,19 @@
import React from 'react'
import Router, { withRouter } from 'next/router'
export default withRouter(({ router }) => {
React.useEffect(() => {
Router.events.on('event', () => {})
Router.prefetch('/page')
Router.push
Router.back
Router.reload
router.events.on('event', () => {})
router.prefetch('/page')
router.push
router.back
router.reload
})
return <>{router.pathname}</>
})

View file

@ -1,13 +1,17 @@
import React from 'react'
import { useRouter } from 'next/router'
import { hello } from '../components/hello'
import { World } from '../components/world'
import Router from '../components/router'
export default function HelloPage(): JSX.Element {
// TEST: verify process.browser extension works
const router = useRouter()
console.log(process.browser)
console.log(router.pathname)
return (
<div>
{hello()} <World />
<Router />
</div>
)
}

View file

@ -17,6 +17,7 @@ describe('TypeScript Features', () => {
// pre-build all pages at the start
await Promise.all([renderViaHTTP(context.appPort, '/hello')])
await Promise.all([renderViaHTTP(context.appPort, '/type-error-recover')])
})
afterAll(() => killApp(context.server))

View file

@ -1,5 +1,9 @@
/* eslint-env jest */
import { join } from 'path'
import cheerio from 'cheerio'
import { runNextCommand } from 'next-test-utils'
const appDir = join(__dirname, '../')
export default (context, render) => {
async function get$ (path, query) {
@ -12,6 +16,11 @@ export default (context, render) => {
const $ = await get$('/hello')
expect($('body').text()).toMatch(/Hello World/)
})
it('Should compile the app', async () => {
const output = await runNextCommand(['build', appDir], { stdout: true })
expect(output.stdout).toMatch(/Compiled successfully/)
})
})
})
}

View file

@ -2,7 +2,18 @@
"compilerOptions": {
"esModuleInterop": true,
"module": "esnext",
"jsx": "preserve"
"jsx": "preserve",
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "components", "pages"]
}