From b94123ccc9d801848a949586ed654a69a5bb970a Mon Sep 17 00:00:00 2001 From: Luis Fernando Alvarez D Date: Thu, 11 Jul 2019 12:35:39 -0500 Subject: [PATCH] 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 --- .gitignore | 1 + packages/next-server/lib/router-context.ts | 3 ++- packages/next-server/lib/router/router.ts | 13 +++++++++++++ packages/next-server/lib/utils.ts | 6 +++--- packages/next-server/server/render.tsx | 15 ++++++--------- packages/next/client/router.ts | 16 ++++------------ packages/next/client/with-router.tsx | 4 ++-- packages/next/lib/data.ts | 2 +- .../typescript/components/router.tsx | 19 +++++++++++++++++++ test/integration/typescript/pages/hello.tsx | 6 +++++- .../integration/typescript/test/index.test.js | 1 + .../integration/typescript/test/typescript.js | 9 +++++++++ test/integration/typescript/tsconfig.json | 13 ++++++++++++- 13 files changed, 78 insertions(+), 30 deletions(-) create mode 100644 test/integration/typescript/components/router.tsx diff --git a/.gitignore b/.gitignore index 86ecc6d4c4..774d9ab5fa 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ coverage # test output test/**/out* +test/**/next-env.d.ts .DS_Store # Editors diff --git a/packages/next-server/lib/router-context.ts b/packages/next-server/lib/router-context.ts index 8b4faf0a62..d90de6bd38 100644 --- a/packages/next-server/lib/router-context.ts +++ b/packages/next-server/lib/router-context.ts @@ -1,3 +1,4 @@ import * as React from 'react' +import { NextRouter } from './router/router' -export const RouterContext: React.Context = React.createContext(null) +export const RouterContext = React.createContext(null as any) diff --git a/packages/next-server/lib/router/router.ts b/packages/next-server/lib/router/router.ts index c3b9f6a084..b797902395 100644 --- a/packages/next-server/lib/router/router.ts +++ b/packages/next-server/lib/router/router.ts @@ -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() diff --git a/packages/next-server/lib/utils.ts b/packages/next-server/lib/utils.ts index 0080a16f73..6188017b01 100644 --- a/packages/next-server/lib/utils.ts +++ b/packages/next-server/lib/utils.ts @@ -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 = { +export type AppContextType = { Component: NextComponentType 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 diff --git a/packages/next-server/server/render.tsx b/packages/next-server/server/render.tsx index 1d121b4c34..4b4c70d79e 100644 --- a/packages/next-server/server/render.tsx +++ b/packages/next-server/server/render.tsx @@ -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() { diff --git a/packages/next/client/router.ts b/packages/next/client/router.ts index c25600c9ab..7dbaa37ed7 100644 --- a/packages/next/client/router.ts +++ b/packages/next/client/router.ts @@ -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 diff --git a/packages/next/client/with-router.tsx b/packages/next/client/with-router.tsx index c2aa9e5492..618c7ee196 100644 --- a/packages/next/client/with-router.tsx +++ b/packages/next/client/with-router.tsx @@ -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

= Pick< diff --git a/packages/next/lib/data.ts b/packages/next/lib/data.ts index 745f813106..342a74231c 100644 --- a/packages/next/lib/data.ts +++ b/packages/next/lib/data.ts @@ -26,7 +26,7 @@ export function createHook( throw new Error('key not provided to createHook options.') } return function useData(...args: Array) { - 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( diff --git a/test/integration/typescript/components/router.tsx b/test/integration/typescript/components/router.tsx new file mode 100644 index 0000000000..1af51adba4 --- /dev/null +++ b/test/integration/typescript/components/router.tsx @@ -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} +}) diff --git a/test/integration/typescript/pages/hello.tsx b/test/integration/typescript/pages/hello.tsx index 63e0ff1c72..80302d0ded 100644 --- a/test/integration/typescript/pages/hello.tsx +++ b/test/integration/typescript/pages/hello.tsx @@ -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 (

{hello()} +
) } diff --git a/test/integration/typescript/test/index.test.js b/test/integration/typescript/test/index.test.js index 27e976bc4f..94f741f4fc 100644 --- a/test/integration/typescript/test/index.test.js +++ b/test/integration/typescript/test/index.test.js @@ -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)) diff --git a/test/integration/typescript/test/typescript.js b/test/integration/typescript/test/typescript.js index f7905e6dd5..289cf16063 100644 --- a/test/integration/typescript/test/typescript.js +++ b/test/integration/typescript/test/typescript.js @@ -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/) + }) }) }) } diff --git a/test/integration/typescript/tsconfig.json b/test/integration/typescript/tsconfig.json index c569b8637f..15cec79584 100644 --- a/test/integration/typescript/tsconfig.json +++ b/test/integration/typescript/tsconfig.json @@ -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"] }