diff --git a/types/next/index.d.ts b/types/next/index.d.ts new file mode 100644 index 0000000000..ab6e3df6f5 --- /dev/null +++ b/types/next/index.d.ts @@ -0,0 +1,170 @@ +// Type definitions for next 2.4 +// Project: https://github.com/zeit/next.js +// Definitions by: Drew Hays +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.3 + +/// + +declare module 'next' { + import * as http from 'http'; + import * as url from 'url'; + + type UrlLike = url.UrlObject | url.Url; + + interface ServerConfig { + // known keys + webpack?: any; + webpackDevMiddleware?: any; + poweredByHeader?: boolean; + distDir?: string; + assetPrefix?: string; + configOrigin?: string; + useFileSystemPublicRoutes?: boolean; + + // and since this is a config, it can take anything else, too. + [key: string]: any; + } + + interface ServerOptions { + dir?: string; + dev?: boolean; + staticMarkup?: boolean; + quiet?: boolean; + conf?: ServerConfig; + } + + interface Server { + handleRequest(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl?: UrlLike): Promise; + getRequestHandler(): (req: http.IncomingMessage, res: http.ServerResponse, parsedUrl?: UrlLike) => Promise; + prepare(): Promise; + close(): Promise; + defineRoutes(): Promise; + start(): Promise; + run(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: UrlLike): Promise; + + render(req: http.IncomingMessage, res: http.ServerResponse, pathname: string, query: {[key: string]: any}, parsedUrl: UrlLike): Promise; + renderError(err: any, req: http.IncomingMessage, res: http.ServerResponse, pathname: string, query: {[key: string]: any}): Promise; + render404(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: UrlLike): Promise; + renderToHTML(req: http.IncomingMessage, res: http.ServerResponse, pathname: string, query: {[key: string]: any}): Promise; + renderErrorToHTML(err: any, req: http.IncomingMessage, res: http.ServerResponse, pathname: string, query: {[key: string]: any}): Promise; + + serveStatic(req: http.IncomingMessage, res: http.ServerResponse, path: string): Promise; + isServeableUrl(path: string): boolean; + isInternalUrl(req: http.IncomingMessage): boolean; + readBuildId(): string; + handleBuildId(buildId: string, res: http.ServerResponse): boolean; + getCompilationError(page: string, req: http.IncomingMessage, res: http.ServerResponse): Promise; + handleBuildHash(filename: string, hash: string, res: http.ServerResponse): void; + send404(res: http.ServerResponse): void; + } + + export default function(options?: ServerOptions): Server; +} + +declare module 'next/error' { + import * as React from 'react'; + export default class extends React.Component<{statusCode: number}, {}> {} +} + +declare module 'next/head' { + import * as React from 'react'; + + function defaultHead(): JSX.Element[]; + export default class extends React.Component<{}, {}> {} +} + +declare module 'next/document' { + import * as React from 'react'; + + interface DocumentProps { + __NEXT_DATA__?: any; + dev?: boolean; + chunks?: string[]; + head?: Array>; + styles?: Array>; + [key: string]: any; + } + + class Head extends React.Component {} + class Main extends React.Component<{}, {}> {} + class NextScript extends React.Component<{}, {}> {} + export default class extends React.Component {} +} + +declare module 'next/link' { + import * as url from 'url'; + import * as React from 'react'; + + type UrlLike = url.UrlObject | url.Url; + interface LinkState { + prefetch?: boolean; + shallow?: boolean; + scroll?: boolean; + replace?: boolean; + onError?(error: any): void; + href?: string | UrlLike; + as?: string | UrlLike; + children: React.ReactElement; + } + + export default class extends React.Component {} +} + +declare module 'next/dynamic' { + import * as React from 'react'; + + interface DynamicOptions { + loading?: React.ComponentType; + ssr?: boolean; + modules?(props: TCProps & TLProps): { [key: string]: Promise> }; + render?(props: TCProps & TLProps, modules: { [key: string]: React.ComponentType }): void; + } + + class SameLoopPromise extends Promise { + constructor(executor: (resolve: (value?: T) => void, reject: (reason?: any) => void) => void); + setResult(value: T): void; + setError(value: any): void; + runIfNeeded(): void; + } + export default function(componentPromise: Promise>, options?: DynamicOptions): React.ComponentType; +} + +declare module 'next/router' { + import * as React from 'react'; + + interface EventChangeOptions { + shallow?: boolean; + [key: string]: any; + } + + type RouterCallback = () => void; + interface SingletonRouter { + readyCallbacks: RouterCallback[]; + ready(cb: RouterCallback): void; + + // router properties + readonly components: { [key: string]: { Component: React.ComponentType, err: any } }; + readonly pathname: string; + readonly route: string; + readonly asPath: string; + readonly query: { [key: string]: any }; + + // router methods + reload(route: string): Promise; + back(): void; + push(url: string, as?: string, options?: EventChangeOptions): Promise; + replace(url: string, as?: string, options?: EventChangeOptions): Promise; + prefetch(url: string): Promise>; + + // router events + onAppUpdated?(nextRoute: string): void; + onRouteChangeStart?(url: string): void; + onBeforeHistoryChange?(as: string): void; + onRouteChangeComplete?(url: string): void; + onRouteChangeError?(error: any, url: string): void; + } + + const Singleton: SingletonRouter; + export default Singleton; +} diff --git a/types/next/next-tests.ts b/types/next/next-tests.ts new file mode 100644 index 0000000000..bfa93bfff8 --- /dev/null +++ b/types/next/next-tests.ts @@ -0,0 +1,54 @@ +import createServer from 'next'; +import * as http from 'http'; +import * as url from 'url'; + +const defaultServer = createServer(); +const server = createServer({ + dir: '..', + quiet: true, + conf: { + distDir: './dist', + useFileSystemPublicRoutes: false, + anotherProperty: { + key: true + } + } +}); + +const voidFunc = () => {}; +const stringFunc = (x: string) => x.split('\n'); + +server.prepare().then(voidFunc); +server.close().then(voidFunc); +server.defineRoutes().then(voidFunc); +server.start().then(voidFunc); + +const parsedUrl = url.parse('https://www.example.com'); +const handler = server.getRequestHandler(); + +function handle(req: http.IncomingMessage, res: http.ServerResponse) { + handler(req, res); + handler(req, res, parsedUrl).then(voidFunc); + server.run(req, res, parsedUrl).then(voidFunc); + + server.render(req, res, '/path/to/resource', {}, parsedUrl).then(voidFunc); + server.render(req, res, '/path/to/resource', { key: 'value' }, parsedUrl).then(voidFunc); + server.renderError(new Error(), req, res, '/path/to/resource', { key: 'value' }).then(voidFunc); + server.renderError('this can be an error, too!', req, res, '/path/to/resource', { key: 'value' }).then(voidFunc); + server.render404(req, res, parsedUrl).then(voidFunc); + + server.renderToHTML(req, res, '/path/to/resource', { foo: 'bar' }).then(x => x.split('\n')); + server.renderErrorToHTML(new Error(), req, res, '/path/to/resource', { foo: 'bar' }).then(x => x.split('\n')); + + server.serveStatic(req, res, '/path/to/thing').then(voidFunc); + + let b: boolean; + b = server.isServeableUrl('/path/to/thing'); + b = server.isInternalUrl(req); + b = server.handleBuildId('{buildId}', res); + + const s: string = server.readBuildId(); + server.getCompilationError('page', req, res).then(err => err.thisIsAnAny); + server.handleBuildHash('filename', 'hash', res); + server.send404(res); +} diff --git a/types/next/test/next-document-tests.tsx b/types/next/test/next-document-tests.tsx new file mode 100644 index 0000000000..4fbb25948b --- /dev/null +++ b/types/next/test/next-document-tests.tsx @@ -0,0 +1,12 @@ +import Document, * as document from 'next/document'; +import * as React from 'react'; + +const results = ( + + + + + + + +); diff --git a/types/next/test/next-dynamic-tests.tsx b/types/next/test/next-dynamic-tests.tsx new file mode 100644 index 0000000000..be10954de9 --- /dev/null +++ b/types/next/test/next-dynamic-tests.tsx @@ -0,0 +1,23 @@ +import dynamic, * as d from 'next/dynamic'; +import * as React from 'react'; + +// typically you'd use this with an esnext-style import() statement, but we'll make do without +interface DynamicComponentProps { + foo: string; + bar: number; +} +async function getComponent() { + return ( + (props: DynamicComponentProps) =>
I'm an async component! {props.foo} {props.bar}
+ ); +} + +interface LoadingComponentProps { + baz: boolean; +} + +const DynamicComponent = dynamic(getComponent(), { + loading: (props: LoadingComponentProps) =>
Loading! {props.baz}
+}); + +const jsx = (); diff --git a/types/next/test/next-error-tests.tsx b/types/next/test/next-error-tests.tsx new file mode 100644 index 0000000000..38692ac6de --- /dev/null +++ b/types/next/test/next-error-tests.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import ErrorComponent from 'next/error'; + +const result = ( + +); diff --git a/types/next/test/next-head-tests.tsx b/types/next/test/next-head-tests.tsx new file mode 100644 index 0000000000..eaf6754b49 --- /dev/null +++ b/types/next/test/next-head-tests.tsx @@ -0,0 +1,9 @@ +import Head, * as head from 'next/head'; +import * as React from 'react'; + +const elements: JSX.Element[] = head.defaultHead(); +const jsx = ( + + {elements} + +); diff --git a/types/next/test/next-link-tests.tsx b/types/next/test/next-link-tests.tsx new file mode 100644 index 0000000000..281c6aff7a --- /dev/null +++ b/types/next/test/next-link-tests.tsx @@ -0,0 +1,13 @@ +import Link from 'next/link'; +import * as React from 'react'; + +const links = ( +
+ { console.log("Handled error!", e); }} prefetch replace scroll shallow> + Gotta link to somewhere! + + + All props are optional! + +
+); diff --git a/types/next/test/next-router-tests.tsx b/types/next/test/next-router-tests.tsx new file mode 100644 index 0000000000..38d091dcc2 --- /dev/null +++ b/types/next/test/next-router-tests.tsx @@ -0,0 +1,50 @@ +import Router, * as r from 'next/router'; +import * as React from 'react'; +import * as qs from 'querystring'; + +Router.readyCallbacks.push(() => { console.log("I'll get called when the router initializes."); }); +Router.ready(() => { console.log("I'll get called immediately if the router initializes, or when it eventually does."); }); + +// Access readonly properties of the router. + +Object.keys(Router.components).forEach(key => { + const c = Router.components[key]; + c.err.isAnAny; + + return ; +}); + +function split(routeLike: string) { + routeLike.split('/').forEach(part => { + console.log("path part: ", part); + }); +} + +split(Router.pathname); +split(Router.asPath); +split(Router.asPath); + +const query = `?${qs.stringify(Router.query)}`; + +// Assign some callback methods. +Router.onAppUpdated = (nextRoute: string) => console.log(nextRoute); +Router.onRouteChangeStart = (url: string) => console.log("Route is starting to change.", url); +Router.onBeforeHistoryChange = (as: string) => console.log("History hasn't changed yet.", as); +Router.onRouteChangeComplete = (url: string) => console.log("Route chaneg is complete.", url); +Router.onRouteChangeError = (err: any, url: string) => console.log("Route is starting to change.", url, err); + +// Call methods on the router itself. +Router.reload('/route').then(() => console.log('route was reloaded')); +Router.back(); + +Router.push('/route').then((success: boolean) => console.log('route push success: ', success)); +Router.push('/route', '/asRoute').then((success: boolean) => console.log('route push success: ', success)); +Router.push('/route', '/asRoute', {shallow: false}).then((success: boolean) => console.log('route push success: ', success)); + +Router.replace('/route').then((success: boolean) => console.log('route replace success: ', success)); +Router.replace('/route', '/asRoute').then((success: boolean) => console.log('route replace success: ', success)); +Router.replace('/route', '/asRoute', {shallow: false}).then((success: boolean) => console.log('route replace success: ', success)); + +Router.prefetch('/route').then(Component => { + const element = (); +}); diff --git a/types/next/tsconfig.json b/types/next/tsconfig.json new file mode 100644 index 0000000000..0bcbd622d9 --- /dev/null +++ b/types/next/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "target": "es6", + "jsx": "react", + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "next-tests.ts", + "test/next-error-tests.tsx", + "test/next-head-tests.tsx", + "test/next-document-tests.tsx", + "test/next-link-tests.tsx", + "test/next-dynamic-tests.tsx", + "test/next-router-tests.tsx" + ] +} diff --git a/types/next/tslint.json b/types/next/tslint.json new file mode 100644 index 0000000000..98f9451050 --- /dev/null +++ b/types/next/tslint.json @@ -0,0 +1,9 @@ +{ + "extends": "dtslint/dt.json", + "rules": { + // All of the different "export default" lines in the index.d.ts + // appear to be triggering this. Remove this when I know of a way + // to declare a default export across multiple package/subpackages. + "strict-export-declare-modifiers": false + } +}