diff --git a/docs/basic-features/built-in-css-support.md b/docs/basic-features/built-in-css-support.md index 2f758b65e6afa..1fa7c5f48f360 100644 --- a/docs/basic-features/built-in-css-support.md +++ b/docs/basic-features/built-in-css-support.md @@ -158,22 +158,10 @@ export default HelloWorld Please see the [styled-jsx documentation](https://github.com/zeit/styled-jsx) for more examples. -## Sass Support +## Sass, Less and Stylus Support -Next.js allows you to import Sass using both the `.scss` and `.sass` extensions. -You can use component-level Sass via CSS Modules and the `.module.scss` or `.module.sass` extension. - -Before you can use Next.js' built-in Sass support, be sure to install [`sass`](https://github.com/sass/sass): - -```bash -npm install sass -``` - -Sass support has the same benefits and restrictions as the built-in CSS support detailed above. - -## Less and Stylus Support - -To support importing `.less` or `.styl` files you can use the following plugins: +To support importing `.scss`, `.sass`, `.less`, or `.styl` files you can use the following plugins: +- [@zeit/next-sass](https://github.com/zeit/next-plugins/tree/master/packages/next-sass) - [@zeit/next-less](https://github.com/zeit/next-plugins/tree/master/packages/next-less) - [@zeit/next-stylus](https://github.com/zeit/next-plugins/tree/master/packages/next-stylus) diff --git a/docs/basic-features/typescript.md b/docs/basic-features/typescript.md index aa2366a9fc299..f0f9a6d4700c7 100644 --- a/docs/basic-features/typescript.md +++ b/docs/basic-features/typescript.md @@ -101,7 +101,7 @@ The following is an example of how to use the built-in types for API routes: import { NextApiRequest, NextApiResponse } from 'next' export default (req: NextApiRequest, res: NextApiResponse) => { - res.status(200).json({ name: 'Jhon Doe' }) + res.status(200).json({ name: 'John Doe' }) } ``` @@ -115,6 +115,6 @@ type Data = { } export default (req: NextApiRequest, res: NextApiResponse) => { - res.status(200).json({ name: 'Jhon Doe' }) + res.status(200).json({ name: 'John Doe' }) } ``` diff --git a/docs/getting-started.md b/docs/getting-started.md index 60135f5b8dac9..64155e0391045 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -39,7 +39,7 @@ These scripts refer to the different stages of developing an application: - `build` - Runs `next build` which builds the application for production usage - `start` - Runs `next start` which starts a Next.js production server -Next.js is built around the concept of pages. A page is a [React Component](https://reactjs.org/docs/components-and-props.html) exported from a `.js`, `.ts`, or `.tsx` file in the `pages` directory. +Next.js is built around the concept of pages. A page is a [React Component](https://reactjs.org/docs/components-and-props.html) exported from a `.js`, `.jsx`, `.ts`, or `.tsx` file in the `pages` directory. Pages are associated with a route based on their file name. For example `pages/about.js` is mapped to `/about`. You can even add dynamic route parameters with the filename. diff --git a/docs/routing/dynamic-routes.md b/docs/routing/dynamic-routes.md index b69af21ae98f8..483de9d1732fc 100644 --- a/docs/routing/dynamic-routes.md +++ b/docs/routing/dynamic-routes.md @@ -60,7 +60,7 @@ Client-side navigations to a dynamic route can be handled with [`next/link`](/do Dynamic routes can be extended to catch all paths by adding three dots (`...`) inside the brackets. For example: -- `pages/post/[...slug]` matches `/post/a`, but also `post/a/b`, `post/a/b/c` and so on. +- `pages/post/[...slug].js` matches `/post/a`, but also `post/a/b`, `post/a/b/c` and so on. Matched parameters will be sent as a query parameter (`slug` in the example) to the page, and it will always be an array, so, the path `/post/a` will have the following `query` object: diff --git a/docs/routing/introduction.md b/docs/routing/introduction.md index 1b92f83378253..4191ff1d4ece9 100644 --- a/docs/routing/introduction.md +++ b/docs/routing/introduction.md @@ -30,7 +30,7 @@ To match a dynamic segment you can use the bracket syntax. This allows you to ma - `pages/blog/[slug].js` → `/blog/:slug` (`/blog/hello-world`) - `pages/[username]/settings.js` → `/:username/settings` (`/foo/settings`) -- `pages/post/[...all]` → `/post/*` (`/post/2020/id/title`) +- `pages/post/[...all].js` → `/post/*` (`/post/2020/id/title`) ## Linking between pages diff --git a/examples/with-graphql-faunadb/README.md b/examples/with-graphql-faunadb/README.md index 9201dce913aaf..42fa330ebde36 100644 --- a/examples/with-graphql-faunadb/README.md +++ b/examples/with-graphql-faunadb/README.md @@ -16,7 +16,7 @@ By importing a `.gql` or `.graphql` schema into FaunaDB ([see our sample schema You can start with this template [using `create-next-app`](#using-create-next-app) or by [downloading the repository manually](#download-manually). -To use a live FaunaDB database, create a database at [dashboard.fauna.com](https://dashboard.fauna.com/) and generate a server token by going to the **Security** tab on the left and then click **New Key**. Give the new key a name and select the 'Server' Role. Copy the token since the setup script will ask for it. Do not use it in the frontend, it has superpowers which you don't want to give to your users. +To use a live FaunaDB database, create a database at [dashboard.fauna.com](https://dashboard.fauna.com/) and generate an admin token by going to the **Security** tab on the left and then click **New Key**. Give the new key a name and select the 'Admin' Role. Copy the token since the setup script will ask for it. Do not use it in the frontend, it has superpowers which you don't want to give to your users. The database can then be set up with the delivered setup by running: @@ -24,7 +24,7 @@ The database can then be set up with the delivered setup by running: yarn setup ``` -This script will ask for the server token. Once you provide it with a valid token, this is what the script automatically does for you: +This script will ask for the admin token. Once you provide it with a valid token, this is what the script automatically does for you: - **Import the GraphQL schema**, by importing a GraphQL schema in FaunaDB, FaunaDB automatically sets up collections and indexes to support your queries. This is now done for you with this script but can also be done from the [dashboard.fauna.com](https://dashboard.fauna.com/) UI by going to the GraphQL tab - **Create a role suitable for the Client**, FaunaDB has a security system that allows you to define which resources can be accessed for a specific token. That's how we limit our clients powers, feel free to look at the scripts/setup.js script to see how we make roles and tokens. diff --git a/examples/with-graphql-faunadb/scripts/setup.js b/examples/with-graphql-faunadb/scripts/setup.js index f4410feda221f..ca701141a7064 100644 --- a/examples/with-graphql-faunadb/scripts/setup.js +++ b/examples/with-graphql-faunadb/scripts/setup.js @@ -11,13 +11,13 @@ const readline = require('readline').createInterface({ output: process.stdout, }) -// In order to set up a database, we need a server key, so let's ask the user for a key. -readline.question(`Please provide the FaunaDB server key\n`, serverKey => { +// In order to set up a database, we need an admin key, so let's ask the user for a key. +readline.question(`Please provide the FaunaDB admin key\n`, adminKey => { // A graphql schema can be imported in override or merge mode: 'https://docs.fauna.com/fauna/current/api/graphql/endpoints#import' const options = { model: 'merge', uri: 'https://graphql.fauna.com/import', - headers: { Authorization: `Bearer ${serverKey}` }, + headers: { Authorization: `Bearer ${adminKey}` }, } const stream = fs.createReadStream('./schema.gql').pipe(request.post(options)) @@ -44,7 +44,7 @@ readline.question(`Please provide the FaunaDB server key\n`, serverKey => { .then(res => { // The GraphQL schema is important, this means that we now have a GuestbookEntry Colleciton and an entries index. // Then we create a token that can only read and write to that index and collection - var client = new faunadb.Client({ secret: serverKey }) + var client = new faunadb.Client({ secret: adminKey }) return client .query( q.CreateRole({ @@ -81,7 +81,7 @@ readline.question(`Please provide the FaunaDB server key\n`, serverKey => { .then(res => { // The GraphQL schema is important, this means that we now have a GuestbookEntry Colleciton and an entries index. // Then we create a token that can only read and write to that index and collection - var client = new faunadb.Client({ secret: serverKey }) + var client = new faunadb.Client({ secret: adminKey }) return client .query( q.CreateKey({ diff --git a/examples/with-typescript-graphql/README.md b/examples/with-typescript-graphql/README.md index bf299a32ec938..cd4ae272cd9c2 100644 --- a/examples/with-typescript-graphql/README.md +++ b/examples/with-typescript-graphql/README.md @@ -1,11 +1,11 @@ -# GraphQL and TypeScript Example +# TypeScript and GraphQL Example One of the strengths of GraphQL is [enforcing data types on runtime](https://graphql.github.io/graphql-spec/June2018/#sec-Value-Completion). Further, TypeScript and [GraphQL Code Generator](https://graphql-code-generator.com/) (graphql-codegen) make it safer by typing data statically, so you can write truly type-protected code with rich IDE assists. This template extends [Apollo Server and Client Example](https://github.com/zeit/next.js/tree/canary/examples/api-routes-apollo-server-and-client#readme) by rewriting in TypeScript and integrating [graphql-let](https://github.com/piglovesyou/graphql-let#readme), which runs [TypeScript React Apollo](https://graphql-code-generator.com/docs/plugins/typescript-react-apollo) in [graphql-codegen](https://github.com/dotansimha/graphql-code-generator#readme) under the hood. It enhances the typed GraphQL use as below. ```typescript jsx -import { useNewsQuery } from './news.grpahql' +import { useNewsQuery } from './news.graphql' const News: React.FC = () => { // Typed already️⚡️ @@ -59,7 +59,7 @@ now ## Notes -By default `**/*.graphqls` is recognized as GraphQL schema and `**/*.graphql` as GraphQL documents. If you prefer the other extensions, make sure the settings of the webpack loader in `next.config.js` and `.graphql-let.yml` point to the same files. +By default `**/*.graphqls` is recognized as GraphQL schema and `**/*.graphql` as GraphQL documents. If you prefer the other extensions, make sure the settings of the webpack loader in `next.config.js` and `.graphql-let.yml` are consistent. Note: Do not be alarmed that you see two renders being executed. Apollo recursively traverses the React render tree looking for Apollo query components. When it has done that, it fetches all these queries and then passes the result to a cache. This cache is then used to render the data on the server side (another React render). https://www.apollographql.com/docs/react/api/react-ssr/#getdatafromtree diff --git a/examples/with-typescript-graphql/lib/resolvers.ts b/examples/with-typescript-graphql/lib/resolvers.ts index b408e6353a9ab..1bd9511549d68 100644 --- a/examples/with-typescript-graphql/lib/resolvers.ts +++ b/examples/with-typescript-graphql/lib/resolvers.ts @@ -1,4 +1,6 @@ -const resolvers = { +import { IResolvers } from 'apollo-server-micro' + +const resolvers: IResolvers = { Query: { viewer(_parent, _args, _context, _info) { return { id: 1, name: 'John Smith', status: 'cached' } diff --git a/packages/next/build/babel/plugins/next-page-config.ts b/packages/next/build/babel/plugins/next-page-config.ts index a101ca91f463a..9fb733db40a16 100644 --- a/packages/next/build/babel/plugins/next-page-config.ts +++ b/packages/next/build/babel/plugins/next-page-config.ts @@ -1,6 +1,6 @@ import { NodePath, PluginObj } from '@babel/core' import * as BabelTypes from '@babel/types' -import { PageConfig } from '../../../types' +import { PageConfig } from 'next/types' const configKeys = new Set(['amp']) const STRING_LITERAL_DROP_BUNDLE = '__NEXT_DROP_CLIENT_FILE__' diff --git a/packages/next/build/babel/preset.ts b/packages/next/build/babel/preset.ts index 45beb38ece891..8e95a8c2c0dcc 100644 --- a/packages/next/build/babel/preset.ts +++ b/packages/next/build/babel/preset.ts @@ -177,6 +177,7 @@ module.exports = ( ], require('@babel/plugin-proposal-optional-chaining'), require('@babel/plugin-proposal-nullish-coalescing-operator'), + isServer && require('@babel/plugin-syntax-bigint'), ].filter(Boolean), } } diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 30585597ce51f..964082a2b8e1d 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -53,6 +53,7 @@ import { generateBuildId } from './generate-build-id' import { isWriteable } from './is-writeable' import createSpinner from './spinner' import { + isPageStatic, collectPages, getPageSizeInKb, hasCustomAppGetInitialProps, @@ -416,7 +417,8 @@ export default async function build(dir: string, conf = null): Promise { const staticCheckWorkers = new Worker(staticCheckWorker, { numWorkers: config.experimental.cpus, enableWorkerThreads: config.experimental.workerThreads, - }) + }) as Worker & { isPageStatic: typeof isPageStatic } + staticCheckWorkers.getStdout().pipe(process.stdout) staticCheckWorkers.getStderr().pipe(process.stderr) @@ -481,7 +483,7 @@ export default async function build(dir: string, conf = null): Promise { if (nonReservedPage) { try { - let result: any = await (staticCheckWorkers as any).isPageStatic( + let result = await staticCheckWorkers.isPageStatic( page, serverBundle, runtimeEnvConfig @@ -492,7 +494,7 @@ export default async function build(dir: string, conf = null): Promise { hybridAmpPages.add(page) } - if (result.prerender) { + if (result.hasStaticProps) { ssgPages.add(page) isSsg = true @@ -500,7 +502,7 @@ export default async function build(dir: string, conf = null): Promise { additionalSsgPaths.set(page, result.prerenderRoutes) ssgPageRoutes = result.prerenderRoutes } - } else if (result.static && customAppGetInitialProps === false) { + } else if (result.isStatic && customAppGetInitialProps === false) { staticPages.add(page) isStatic = true } diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 85c4f7ed728b6..ac126d9c77494 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -479,9 +479,9 @@ export async function isPageStatic( serverBundle: string, runtimeEnvConfig: any ): Promise<{ - static?: boolean - prerender?: boolean + isStatic?: boolean isHybridAmp?: boolean + hasStaticProps?: boolean prerenderRoutes?: string[] | undefined }> { try { @@ -593,10 +593,10 @@ export async function isPageStatic( const config = mod.config || {} return { - static: !hasStaticProps && !hasGetInitialProps, + isStatic: !hasStaticProps && !hasGetInitialProps, isHybridAmp: config.amp === 'hybrid', prerenderRoutes: prerenderPaths, - prerender: hasStaticProps, + hasStaticProps, } } catch (err) { if (err.code === 'MODULE_NOT_FOUND') return {} diff --git a/packages/next/build/webpack/loaders/next-serverless-loader.ts b/packages/next/build/webpack/loaders/next-serverless-loader.ts index a454ca739ad59..4948c38d3735c 100644 --- a/packages/next/build/webpack/loaders/next-serverless-loader.ts +++ b/packages/next/build/webpack/loaders/next-serverless-loader.ts @@ -8,6 +8,7 @@ import { } from '../../../next-server/lib/constants' import { isDynamicRoute } from '../../../next-server/lib/router/utils' import { API_ROUTE } from '../../../lib/constants' +import escapeRegexp from 'escape-string-regexp' export type ServerlessLoaderQuery = { page: string @@ -46,7 +47,7 @@ const nextServerlessLoader: loader.Loader = function() { ) const routesManifest = join(distDir, ROUTES_MANIFEST).replace(/\\/g, '/') - const escapedBuildId = buildId.replace(/[|\\{}()[\]^$+*?.-]/g, '\\$&') + const escapedBuildId = escapeRegexp(buildId) const pageIsDynamicRoute = isDynamicRoute(page) const dynamicRouteImports = pageIsDynamicRoute @@ -215,13 +216,14 @@ const nextServerlessLoader: loader.Loader = function() { } let _nextData = false - if (req.url.match(/_next\\/data/)) { + const parsedUrl = handleRewrites(parse(req.url, true)) + + if (parsedUrl.pathname.match(/_next\\/data/)) { _nextData = true - req.url = req.url + parsedUrl.pathname = parsedUrl.pathname .replace(new RegExp('/_next/data/${escapedBuildId}/'), '/') .replace(/\\.json$/, '') } - const parsedUrl = handleRewrites(parse(req.url, true)) const renderOpts = Object.assign( { diff --git a/packages/next/next-server/lib/utils.ts b/packages/next/next-server/lib/utils.ts index 9ce465d40b052..875e8417a7332 100644 --- a/packages/next/next-server/lib/utils.ts +++ b/packages/next/next-server/lib/utils.ts @@ -3,7 +3,7 @@ import { ParsedUrlQuery } from 'querystring' import { ComponentType } from 'react' import { format, URLFormatOptions, UrlObject } from 'url' -import { ManifestItem } from '../server/render' +import { ManifestItem } from '../server/load-components' import { NextRouter } from './router/router' /** diff --git a/packages/next/next-server/server/api-utils.ts b/packages/next/next-server/server/api-utils.ts index 10c1a229801ae..33c6d45f4ce73 100644 --- a/packages/next/next-server/server/api-utils.ts +++ b/packages/next/next-server/server/api-utils.ts @@ -4,7 +4,7 @@ import { Stream } from 'stream' import getRawBody from 'raw-body' import { parse } from 'content-type' import { Params } from './router' -import { PageConfig } from '../../types' +import { PageConfig } from 'next/types' import { interopDefault } from './load-components' import { isResSent } from '../lib/utils' @@ -59,7 +59,7 @@ export async function apiResolver( if (process.env.NODE_ENV !== 'production' && !isResSent(res)) { console.warn( - `API resolved without sending a response for ${req.url}, this may result in a stalled requests.` + `API resolved without sending a response for ${req.url}, this may result in stalled requests.` ) } } catch (err) { diff --git a/packages/next/next-server/server/config.ts b/packages/next/next-server/server/config.ts index 564742f40bf6c..fb47ecb9df874 100644 --- a/packages/next/next-server/server/config.ts +++ b/packages/next/next-server/server/config.ts @@ -97,7 +97,7 @@ function assignDefaults(userConfig: { [key: string]: any }) { `The 'public' directory is reserved in Next.js and can not be set as the 'distDir'. https://err.sh/zeit/next.js/can-not-output-to-public` ) } - // make sure distDir isn't an empty string which can result the provided + // make sure distDir isn't an empty string as it can result in the provided // directory being deleted in development mode if (userDistDir.length === 0) { throw new Error( diff --git a/packages/next/next-server/server/load-components.ts b/packages/next/next-server/server/load-components.ts index a6aee255a0cc0..c8559610c9d3f 100644 --- a/packages/next/next-server/server/load-components.ts +++ b/packages/next/next-server/server/load-components.ts @@ -5,28 +5,44 @@ import { SERVER_DIRECTORY, } from '../lib/constants' import { join } from 'path' -import { PageConfig } from '../../types' import { requirePage } from './require' +import { ParsedUrlQuery } from 'querystring' +import { BuildManifest } from './get-page-files' +import { AppType, DocumentType } from '../lib/utils' +import { PageConfig, NextPageContext } from 'next/types' export function interopDefault(mod: any) { return mod.default || mod } +export type ManifestItem = { + id: number | string + name: string + file: string + publicPath: string +} + +type ReactLoadableManifest = { [moduleId: string]: ManifestItem[] } + +type Unstable_getStaticProps = (params: { + params: ParsedUrlQuery | undefined +}) => Promise<{ + props: { [key: string]: any } + revalidate?: number | boolean +}> + +type Unstable_getStaticPaths = () => Promise> + export type LoadComponentsReturnType = { - Component: any - pageConfig: PageConfig - unstable_getStaticProps?: (params: { - params: any - }) => { - props: any - revalidate?: number | boolean - } - unstable_getStaticPaths?: () => void - buildManifest?: any - reactLoadableManifest?: any - Document?: any - DocumentMiddleware?: any - App?: any + Component: React.ComponentType + pageConfig?: PageConfig + buildManifest: BuildManifest + reactLoadableManifest: ReactLoadableManifest + Document: DocumentType + DocumentMiddleware?: (ctx: NextPageContext) => void + App: AppType + unstable_getStaticProps?: Unstable_getStaticProps + unstable_getStaticPaths?: Unstable_getStaticPaths } export async function loadComponents( @@ -42,7 +58,7 @@ export async function loadComponents( pageConfig: Component.config || {}, unstable_getStaticProps: Component.unstable_getStaticProps, unstable_getStaticPaths: Component.unstable_getStaticPaths, - } + } as LoadComponentsReturnType } const documentPath = join( distDir, diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts index 5f4f8f2e218a6..e2ac323dce028 100644 --- a/packages/next/next-server/server/next-server.ts +++ b/packages/next/next-server/server/next-server.ts @@ -863,7 +863,7 @@ export default class Server { // check request state const isLikeServerless = typeof result.Component === 'object' && - typeof result.Component.renderReqToHTML === 'function' + typeof (result.Component as any).renderReqToHTML === 'function' const isSSG = !!result.unstable_getStaticProps // non-spr requests should render like normal @@ -871,7 +871,7 @@ export default class Server { // handle serverless if (isLikeServerless) { this.prepareServerlessUrl(req, query) - return result.Component.renderReqToHTML(req, res) + return (result.Component as any).renderReqToHTML(req, res) } return renderToHTML(req, res, pathname, query, { @@ -929,7 +929,11 @@ export default class Server { let renderResult // handle serverless if (isLikeServerless) { - renderResult = await result.Component.renderReqToHTML(req, res, true) + renderResult = await (result.Component as any).renderReqToHTML( + req, + res, + true + ) html = renderResult.html pageData = renderResult.renderOpts.pageData diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx index be8f52450c753..ec7fd0a4d63df 100644 --- a/packages/next/next-server/server/render.tsx +++ b/packages/next/next-server/server/render.tsx @@ -14,31 +14,19 @@ import { NextComponentType, DocumentType, AppType, - NextPageContext, } from '../lib/utils' import Head, { defaultHead } from '../lib/head' -// @ts-ignore types will be added later as it's an internal module import Loadable from '../lib/loadable' import { LoadableContext } from '../lib/loadable-context' import { RouterContext } from '../lib/router-context' -import { getPageFiles, BuildManifest } from './get-page-files' +import { getPageFiles } from './get-page-files' import { AmpStateContext } from '../lib/amp-context' import optimizeAmp from './optimize-amp' import { isInAmpMode } from '../lib/amp' -// Uses a module path because of the compiled output directory location -import { PageConfig } from 'next/types' import { isDynamicRoute } from '../lib/router/utils/is-dynamic' import { SSG_GET_INITIAL_PROPS_CONFLICT } from '../../lib/constants' import { AMP_RENDER_TARGET } from '../lib/constants' - -export type ManifestItem = { - id: number | string - name: string - file: string - publicPath: string -} - -type ReactLoadableManifest = { [moduleId: string]: ManifestItem[] } +import { LoadComponentsReturnType, ManifestItem } from './load-components' function noRouter() { const message = @@ -122,8 +110,7 @@ function render( return { html, head } } -type RenderOpts = { - documentMiddlewareEnabled: boolean +type RenderOpts = LoadComponentsReturnType & { staticMarkup: boolean buildId: string canonicalBase: string @@ -139,22 +126,9 @@ type RenderOpts = { ampPath?: string inAmpMode?: boolean hybridAmp?: boolean - buildManifest: BuildManifest - reactLoadableManifest: ReactLoadableManifest - pageConfig: PageConfig - Component: React.ComponentType - Document: DocumentType - DocumentMiddleware: (ctx: NextPageContext) => void - App: AppType ErrorDebug?: React.ComponentType<{ error: Error }> ampValidator?: (html: string, pathname: string) => Promise - unstable_getStaticProps?: (params: { - params: any | undefined - }) => { - props: any - revalidate?: number | boolean - } - unstable_getStaticPaths?: () => void + documentMiddlewareEnabled?: boolean } function renderDocument( diff --git a/packages/next/package.json b/packages/next/package.json index 042409932aa3a..0d48e104397a5 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -63,6 +63,7 @@ "@babel/plugin-proposal-nullish-coalescing-operator": "7.7.4", "@babel/plugin-proposal-object-rest-spread": "7.6.2", "@babel/plugin-proposal-optional-chaining": "7.7.4", + "@babel/plugin-syntax-bigint": "7.8.3", "@babel/plugin-syntax-dynamic-import": "7.2.0", "@babel/plugin-transform-modules-commonjs": "7.7.0", "@babel/plugin-transform-runtime": "7.6.2", @@ -92,6 +93,7 @@ "css-loader": "3.3.0", "cssnano-simple": "1.0.0", "devalue": "2.0.1", + "escape-string-regexp": "2.0.0", "etag": "1.8.1", "file-loader": "4.2.0", "find-up": "4.0.0", diff --git a/test/integration/api-support/test/index.test.js b/test/integration/api-support/test/index.test.js index 5fb522dd3019e..a803bbdaceb26 100644 --- a/test/integration/api-support/test/index.test.js +++ b/test/integration/api-support/test/index.test.js @@ -344,7 +344,7 @@ function runTests(dev = false) { signal: controller.signal, }).catch(() => {}) expect(stderr).toContain( - `API resolved without sending a response for /api/test-no-end, this may result in a stalled requests` + `API resolved without sending a response for /api/test-no-end, this may result in stalled requests.` ) }) } else { diff --git a/test/integration/bigint/pages/api/bigint.js b/test/integration/bigint/pages/api/bigint.js new file mode 100644 index 0000000000000..8baefcfb20304 --- /dev/null +++ b/test/integration/bigint/pages/api/bigint.js @@ -0,0 +1,4 @@ +export default (req, res) => { + res.statusCode = 200 + res.send((1n + 2n).toString()) +} diff --git a/test/integration/bigint/test/index.test.js b/test/integration/bigint/test/index.test.js new file mode 100644 index 0000000000000..b4d49886ab08b --- /dev/null +++ b/test/integration/bigint/test/index.test.js @@ -0,0 +1,77 @@ +/* eslint-env jest */ +/* global jasmine */ +import fs from 'fs-extra' +import { join } from 'path' +import { + fetchViaHTTP, + launchApp, + nextBuild, + nextStart, + killApp, + findPort, +} from 'next-test-utils' + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2 + +const appDir = join(__dirname, '..') +const nextConfig = join(appDir, 'next.config.js') +let appPort +let app + +const runTests = () => { + it('should return 200', async () => { + const res = await fetchViaHTTP(appPort, '/api/bigint', null, { + method: 'GET', + }) + expect(res.status).toEqual(200) + }) + + it('should return the BigInt result text', async () => { + const resText = await fetchViaHTTP(appPort, '/api/bigint', null, { + method: 'GET', + }).then(res => res.ok && res.text()) + expect(resText).toEqual('3') + }) +} + +describe('bigint API route support', () => { + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + }) + afterAll(() => killApp(app)) + + runTests() + }) + + describe('server mode', () => { + beforeAll(async () => { + await fs.remove(nextConfig) + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(() => killApp(app)) + + runTests() + }) + + describe('serverless mode', () => { + beforeAll(async () => { + await fs.writeFile( + nextConfig, + `module.exports = { target: 'serverless' }` + ) + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(async () => { + await killApp(app) + await fs.remove(nextConfig) + }) + + runTests() + }) +}) diff --git a/test/integration/custom-routes/test/index.test.js b/test/integration/custom-routes/test/index.test.js index 8b249d82e08d3..5b954b48bd91a 100644 --- a/test/integration/custom-routes/test/index.test.js +++ b/test/integration/custom-routes/test/index.test.js @@ -6,6 +6,7 @@ import fs from 'fs-extra' import { join } from 'path' import cheerio from 'cheerio' import webdriver from 'next-webdriver' +import escapeRegex from 'escape-string-regexp' import { launchApp, killApp, @@ -29,8 +30,6 @@ let stdout = '' let appPort let app -const escapeRegex = str => str.replace(/[|\\{}()[\]^$+*?.-]/g, '\\$&') - const runTests = (isDev = false) => { it('should handle one-to-one rewrite successfully', async () => { const html = await renderViaHTTP(appPort, '/first') diff --git a/test/integration/prerender/test/index.test.js b/test/integration/prerender/test/index.test.js index 036070238794e..a6ff7e056ba68 100644 --- a/test/integration/prerender/test/index.test.js +++ b/test/integration/prerender/test/index.test.js @@ -2,8 +2,9 @@ /* global jasmine */ import fs from 'fs-extra' import { join } from 'path' -import webdriver from 'next-webdriver' import cheerio from 'cheerio' +import webdriver from 'next-webdriver' +import escapeRegex from 'escape-string-regexp' import { renderViaHTTP, fetchViaHTTP, @@ -264,6 +265,16 @@ const runTests = (dev = false) => { expect(JSON.parse(params)).toEqual({}) }) + it('should not supply query values to params in /_next/data request', async () => { + const data = JSON.parse( + await renderViaHTTP( + appPort, + `/_next/data/${buildId}/something.json?hello=world` + ) + ) + expect(data.pageProps.params).toEqual({}) + }) + it('should not supply query values to params or useRouter dynamic page SSR', async () => { const html = await renderViaHTTP(appPort, '/blog/post-1?hello=world') const $ = cheerio.load(html) @@ -410,7 +421,7 @@ const runTests = (dev = false) => { const manifest = JSON.parse( await fs.readFile(join(appDir, '.next/prerender-manifest.json'), 'utf8') ) - const escapedBuildId = buildId.replace(/[|\\{}()[\]^$+*?.-]/g, '\\$&') + const escapedBuildId = escapeRegex(buildId) Object.keys(manifest.dynamicRoutes).forEach(key => { const item = manifest.dynamicRoutes[key] @@ -579,7 +590,7 @@ describe('SPR Prerender', () => { await nextBuild(appDir) stderr = '' appPort = await findPort() - app = nextStart(appDir, appPort, { + app = await nextStart(appDir, appPort, { onStderr: msg => { stderr += msg }, diff --git a/yarn.lock b/yarn.lock index e1b0f6dd10365..9fb92865ae140 100644 --- a/yarn.lock +++ b/yarn.lock @@ -220,6 +220,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== +"@babel/helper-plugin-utils@^7.8.0": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + "@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.5.5.tgz#0aa6824f7100a2e0e89c1527c23936c152cab351" @@ -384,6 +389,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-bigint@7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + "@babel/plugin-syntax-dynamic-import@7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612"