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:
parent
13cf664898
commit
b94123ccc9
13 changed files with 78 additions and 30 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -19,6 +19,7 @@ coverage
|
|||
|
||||
# test output
|
||||
test/**/out*
|
||||
test/**/next-env.d.ts
|
||||
.DS_Store
|
||||
|
||||
# Editors
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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<
|
||||
|
|
|
@ -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(
|
||||
|
|
19
test/integration/typescript/components/router.tsx
Normal file
19
test/integration/typescript/components/router.tsx
Normal 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}</>
|
||||
})
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue