From 6ac61768d9d5251924a986d5fe62352f55e3dc40 Mon Sep 17 00:00:00 2001 From: Brandon Bayer Date: Tue, 19 Oct 2021 18:22:14 -0400 Subject: [PATCH] Move internal @blitzjs/core package into nextjs fork core (meta) (#2857) --- .node-version | 2 +- examples/auth/app/pages/index.test.tsx | 6 +- examples/cypress/app/pages/index.test.tsx | 4 +- examples/cypress/package.json | 2 +- nextjs/.node-version | 1 + nextjs/packages/next/build/routes.ts | 10 +- nextjs/packages/next/client/router.ts | 89 +++++- .../next/compiled/lodash.frompairs/LICENSE | 22 ++ .../next/compiled/lodash.frompairs/index.js | 1 + .../compiled/lodash.frompairs/package.json | 1 + .../next/lib-types/lodash.frompairs.d.ts | 6 + .../packages/next/lib-types/micromatch.d.ts | 3 + nextjs/packages/next/lib-types/npm-which.d.ts | 176 ++++++++++++ nextjs/packages/next/package.json | 8 +- nextjs/packages/next/pages/_app.tsx | 3 +- nextjs/packages/next/pages/_error.tsx | 3 +- nextjs/packages/next/server/render.tsx | 8 +- nextjs/packages/next/shared/lib/dynamic.tsx | 3 +- nextjs/packages/next/shared/lib/head.tsx | 36 +-- .../packages/next/shared/lib/router/router.ts | 77 ++++++ .../next/shared/lib/runtime-config.ts | 3 +- nextjs/packages/next/stdlib-server/index.ts | 1 + .../packages/next/stdlib-server}/resolver.ts | 125 ++++++--- .../packages/next/stdlib}/blitz-app-root.tsx | 94 ++++--- .../packages/next/stdlib}/error-boundary.tsx | 78 +++--- nextjs/packages/next/stdlib/index.ts | 17 ++ nextjs/packages/next/stdlib/prisma-utils.ts | 66 +++++ .../packages/next/stdlib/zod-utils.ts | 38 +-- nextjs/packages/next/taskfile.js | 11 + nextjs/packages/next/tsconfig.json | 3 + nextjs/packages/next/types/global.d.ts | 4 + nextjs/packages/next/types/index.d.ts | 2 + nextjs/packages/next/types/misc.d.ts | 48 +--- .../unit/error-boundary-hook.unit.test.tsx | 50 ++-- .../test/unit/error-boundary.unit.test.tsx | 167 ++++++------ nextjs/test/unit/resolver.unit.test.ts | 62 +++++ nextjs/test/unit/router-hooks.unit.test.ts | 257 ++++++++++++++++++ nextjs/test/unit/zod-utils.unit.test.ts | 105 +++++++ package.json | 3 +- .../babel-preset/src/add-blitz-app-root.ts | 2 +- packages/babel-preset/src/rewrite-imports.ts | 37 ++- packages/blitz/package.json | 2 +- packages/blitz/scripts/postinstall.js | 2 +- packages/blitz/src/index.ts | 14 +- packages/core/.eslintrc.js | 16 -- packages/core/.gitignore | 8 - packages/core/README.md | 39 --- packages/core/app/package.json | 4 - packages/core/config/package.json | 5 - packages/core/dynamic/package.json | 5 - packages/core/head/package.json | 5 - packages/core/jest.config.js | 4 - packages/core/jest.setup.js | 2 - packages/core/package.json | 53 ---- packages/core/server/package.json | 5 - packages/core/src/app.ts | 6 - packages/core/src/config.ts | 6 - packages/core/src/dynamic.ts | 6 - packages/core/src/error.ts | 1 - packages/core/src/head.ts | 6 - packages/core/src/index.ts | 8 - packages/core/src/prisma-utils.ts | 53 ---- packages/core/src/router/index.tsx | 69 ----- packages/core/src/router/router-hooks.ts | 143 ---------- packages/core/src/server/index.ts | 7 - packages/core/src/server/resolver.test.ts | 62 ----- packages/core/src/suspense.ts | 26 -- packages/core/src/types.ts | 29 -- packages/core/src/utils/hooks.ts | 7 - packages/core/src/utils/index.test.ts | 98 ------- packages/core/src/utils/pretty-ms.test.ts | 20 -- packages/core/src/utils/pretty-ms.ts | 20 -- packages/core/test/router-hooks.test.ts | 215 --------------- packages/core/test/test-utils.tsx | 124 --------- packages/core/types/index.d.ts | 7 - packages/server/package.json | 1 - scripts/prepack.js | 2 +- yarn.lock | 25 +- 78 files changed, 1333 insertions(+), 1406 deletions(-) create mode 100644 nextjs/.node-version create mode 100644 nextjs/packages/next/compiled/lodash.frompairs/LICENSE create mode 100644 nextjs/packages/next/compiled/lodash.frompairs/index.js create mode 100644 nextjs/packages/next/compiled/lodash.frompairs/package.json create mode 100644 nextjs/packages/next/lib-types/lodash.frompairs.d.ts create mode 100644 nextjs/packages/next/lib-types/micromatch.d.ts create mode 100644 nextjs/packages/next/lib-types/npm-which.d.ts rename {packages/core/src/server => nextjs/packages/next/stdlib-server}/resolver.ts (72%) rename {packages/core/src => nextjs/packages/next/stdlib}/blitz-app-root.tsx (56%) rename {packages/core/src => nextjs/packages/next/stdlib}/error-boundary.tsx (70%) create mode 100644 nextjs/packages/next/stdlib/prisma-utils.ts rename packages/core/src/utils/index.ts => nextjs/packages/next/stdlib/zod-utils.ts (64%) rename packages/core/src/error-boundary.hook.test.tsx => nextjs/test/unit/error-boundary-hook.unit.test.tsx (66%) rename packages/core/src/error-boundary.test.tsx => nextjs/test/unit/error-boundary.unit.test.tsx (70%) create mode 100644 nextjs/test/unit/resolver.unit.test.ts create mode 100644 nextjs/test/unit/router-hooks.unit.test.ts create mode 100644 nextjs/test/unit/zod-utils.unit.test.ts delete mode 100644 packages/core/.eslintrc.js delete mode 100644 packages/core/.gitignore delete mode 100644 packages/core/README.md delete mode 100644 packages/core/app/package.json delete mode 100644 packages/core/config/package.json delete mode 100644 packages/core/dynamic/package.json delete mode 100644 packages/core/head/package.json delete mode 100644 packages/core/jest.config.js delete mode 100644 packages/core/jest.setup.js delete mode 100644 packages/core/package.json delete mode 100644 packages/core/server/package.json delete mode 100644 packages/core/src/app.ts delete mode 100644 packages/core/src/config.ts delete mode 100644 packages/core/src/dynamic.ts delete mode 100644 packages/core/src/error.ts delete mode 100644 packages/core/src/head.ts delete mode 100644 packages/core/src/index.ts delete mode 100644 packages/core/src/prisma-utils.ts delete mode 100644 packages/core/src/router/index.tsx delete mode 100644 packages/core/src/router/router-hooks.ts delete mode 100644 packages/core/src/server/index.ts delete mode 100644 packages/core/src/server/resolver.test.ts delete mode 100644 packages/core/src/suspense.ts delete mode 100644 packages/core/src/types.ts delete mode 100644 packages/core/src/utils/hooks.ts delete mode 100644 packages/core/src/utils/index.test.ts delete mode 100644 packages/core/src/utils/pretty-ms.test.ts delete mode 100644 packages/core/src/utils/pretty-ms.ts delete mode 100644 packages/core/test/router-hooks.test.ts delete mode 100644 packages/core/test/test-utils.tsx delete mode 100644 packages/core/types/index.d.ts diff --git a/.node-version b/.node-version index 4a41167051..31102b28de 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -12.20.0 +14.18.1 diff --git a/examples/auth/app/pages/index.test.tsx b/examples/auth/app/pages/index.test.tsx index dd9fc429be..596401407a 100644 --- a/examples/auth/app/pages/index.test.tsx +++ b/examples/auth/app/pages/index.test.tsx @@ -1,8 +1,8 @@ import {render} from "test/utils" import Home from "./index" -jest.mock("@blitzjs/core", () => ({ - ...jest.requireActual("@blitzjs/core")!, +jest.mock("next/data-client", () => ({ + ...jest.requireActual("next/data-client")!, useQuery: () => [ { id: 1, @@ -24,4 +24,4 @@ test("renders blitz documentation link", () => { const element = getByText(/powered by blitz/i) // @ts-ignore expect(element).toBeInTheDocument() -}) \ No newline at end of file +}) diff --git a/examples/cypress/app/pages/index.test.tsx b/examples/cypress/app/pages/index.test.tsx index dfe3e47b60..0eca16119b 100644 --- a/examples/cypress/app/pages/index.test.tsx +++ b/examples/cypress/app/pages/index.test.tsx @@ -1,8 +1,8 @@ import { render } from "test/utils" import Home from "./index" -jest.mock("@blitzjs/core", () => ({ - ...jest.requireActual("@blitzjs/core")!, +jest.mock("next/data-client", () => ({ + ...jest.requireActual("next/data-client")!, useQuery: () => [ { id: 1, diff --git a/examples/cypress/package.json b/examples/cypress/package.json index e8ff0d3d55..027412e7e4 100644 --- a/examples/cypress/package.json +++ b/examples/cypress/package.json @@ -39,7 +39,7 @@ "react": "0.0.0-experimental-6a589ad71", "react-dom": "0.0.0-experimental-6a589ad71", "react-final-form": "6.5.2", - "zod": "3.8.1" + "zod": "3.10.1" }, "devDependencies": { "@testing-library/cypress": "8.0.1", diff --git a/nextjs/.node-version b/nextjs/.node-version new file mode 100644 index 0000000000..31102b28de --- /dev/null +++ b/nextjs/.node-version @@ -0,0 +1 @@ +14.18.1 diff --git a/nextjs/packages/next/build/routes.ts b/nextjs/packages/next/build/routes.ts index 2015f6dbbe..11edf7b6ae 100644 --- a/nextjs/packages/next/build/routes.ts +++ b/nextjs/packages/next/build/routes.ts @@ -152,10 +152,10 @@ export async function saveRouteManifest( async function findNodeModulesRoot(src: string) { /* * Because of our package structure, and because of how things like pnpm link modules, - * we must first find blitz package, and then find @blitzjs/core and then - * the root of @blitzjs/core + * we must first find blitz package, and then find `next` and then + * the root of `next` * - * This is because we import from `.blitz` inside @blitzjs/core. + * This is because we import from `.blitz` inside `next/stdlib`. * If that changes, then this logic here will need to change */ manifestDebug('src ' + src) @@ -189,13 +189,13 @@ async function findNodeModulesRoot(src: string) { } const blitzCorePkgLocation = dirname( (await findUp('package.json', { - cwd: resolveFrom(blitzPkgLocation, '@blitzjs/core'), + cwd: resolveFrom(blitzPkgLocation, 'next'), })) ?? '' ) manifestDebug('blitzCorePkgLocation ' + blitzCorePkgLocation) if (!blitzCorePkgLocation) { throw new Error( - "Internal Blitz Error: unable to find '@blitzjs/core' package location" + "Internal Blitz Error: unable to find 'next' package location" ) } root = join(blitzCorePkgLocation, '../../') diff --git a/nextjs/packages/next/client/router.ts b/nextjs/packages/next/client/router.ts index ac2f7e3cd3..d42846a81b 100644 --- a/nextjs/packages/next/client/router.ts +++ b/nextjs/packages/next/client/router.ts @@ -1,6 +1,9 @@ /* global window */ import React from 'react' -import Router from '../shared/lib/router/router' +import Router, { + extractQueryFromAsPath, + extractRouterParams, +} from '../shared/lib/router/router' import type { NextRouter } from '../shared/lib/router/router' import { RouterContext } from '../shared/lib/router-context' @@ -17,6 +20,7 @@ type SingletonRouterBase = { export { Router } export type { NextRouter } +export type BlitzRouter = NextRouter export type SingletonRouter = SingletonRouterBase & NextRouter @@ -177,3 +181,86 @@ export function makePublicRouterInstance(router: Router): NextRouter { return instance } + +export function useRouterQuery() { + const router = useRouter() + + const query = React.useMemo(() => { + const query = extractQueryFromAsPath(router.asPath) + return query + }, [router.asPath]) + + return query +} + +type Dict = Record +type ReturnTypes = 'string' | 'number' | 'array' + +export function useParams(): Dict +export function useParams(returnType?: ReturnTypes): Dict +export function useParams(returnType: 'string'): Dict +export function useParams(returnType: 'number'): Dict +export function useParams(returnType: 'array'): Dict + +export function useParams( + returnType?: 'string' | 'number' | 'array' | undefined +) { + const router = useRouter() + const query = useRouterQuery() + + const params = React.useMemo(() => { + const rawParams = extractRouterParams(router.query, query) + + if (returnType === 'string') { + const params: Dict = {} + for (const key in rawParams) { + if (typeof rawParams[key] === 'string') { + params[key] = rawParams[key] as string + } + } + return params + } + + if (returnType === 'number') { + const params: Dict = {} + for (const key in rawParams) { + if (rawParams[key]) { + const num = Number(rawParams[key]) + params[key] = isNaN(num) ? undefined : num + } + } + return params + } + + if (returnType === 'array') { + const params: Dict = {} + for (const key in rawParams) { + const rawValue = rawParams[key] + if (Array.isArray(rawParams[key])) { + params[key] = rawValue as string[] + } else if (typeof rawValue === 'string') { + params[key] = [rawValue] + } + } + return params + } + + return rawParams + }, [router.query, query, returnType]) + + return params +} + +export function useParam(key: string): undefined | string | string[] +export function useParam(key: string, returnType: 'string'): string | undefined +export function useParam(key: string, returnType: 'number'): number | undefined +export function useParam(key: string, returnType: 'array'): string[] | undefined +export function useParam( + key: string, + returnType?: ReturnTypes +): undefined | number | string | string[] { + const params = useParams(returnType) + const value = params[key] + + return value +} diff --git a/nextjs/packages/next/compiled/lodash.frompairs/LICENSE b/nextjs/packages/next/compiled/lodash.frompairs/LICENSE new file mode 100644 index 0000000000..b054ca5a3a --- /dev/null +++ b/nextjs/packages/next/compiled/lodash.frompairs/LICENSE @@ -0,0 +1,22 @@ +Copyright 2012-2016 The Dojo Foundation +Based on Underscore.js, copyright 2009-2016 Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/nextjs/packages/next/compiled/lodash.frompairs/index.js b/nextjs/packages/next/compiled/lodash.frompairs/index.js new file mode 100644 index 0000000000..e010a793ce --- /dev/null +++ b/nextjs/packages/next/compiled/lodash.frompairs/index.js @@ -0,0 +1 @@ +module.exports=(()=>{var r={460:r=>{function fromPairs(r){var e=-1,_=r?r.length:0,a={};while(++e<_){var t=r[e];a[t[0]]=t[1]}return a}r.exports=fromPairs}};var e={};function __nccwpck_require__(_){if(e[_]){return e[_].exports}var a=e[_]={exports:{}};var t=true;try{r[_](a,a.exports,__nccwpck_require__);t=false}finally{if(t)delete e[_]}return a.exports}__nccwpck_require__.ab=__dirname+"/";return __nccwpck_require__(460)})(); \ No newline at end of file diff --git a/nextjs/packages/next/compiled/lodash.frompairs/package.json b/nextjs/packages/next/compiled/lodash.frompairs/package.json new file mode 100644 index 0000000000..062bda5d4d --- /dev/null +++ b/nextjs/packages/next/compiled/lodash.frompairs/package.json @@ -0,0 +1 @@ +{"name":"lodash.frompairs","main":"index.js","author":"John-David Dalton (http://allyoucanleet.com/)","license":"MIT"} diff --git a/nextjs/packages/next/lib-types/lodash.frompairs.d.ts b/nextjs/packages/next/lib-types/lodash.frompairs.d.ts new file mode 100644 index 0000000000..2b20a0fd6b --- /dev/null +++ b/nextjs/packages/next/lib-types/lodash.frompairs.d.ts @@ -0,0 +1,6 @@ +declare module 'lodash.frompairs' { + // eslint-disable-next-line + export default function fromPairs( + pairs: List<[string, T]> | null | undefined + ): Dictionary +} diff --git a/nextjs/packages/next/lib-types/micromatch.d.ts b/nextjs/packages/next/lib-types/micromatch.d.ts new file mode 100644 index 0000000000..a0fe935d57 --- /dev/null +++ b/nextjs/packages/next/lib-types/micromatch.d.ts @@ -0,0 +1,3 @@ +declare module 'micromatch' { + export function isMatch(source: string, patterns: string[]): boolean +} diff --git a/nextjs/packages/next/lib-types/npm-which.d.ts b/nextjs/packages/next/lib-types/npm-which.d.ts new file mode 100644 index 0000000000..97a3ad890c --- /dev/null +++ b/nextjs/packages/next/lib-types/npm-which.d.ts @@ -0,0 +1,176 @@ +// Type definitions for npm-which 3.0 +// Project: https://github.com/timoxley/npm-which +// Definitions by: Manuel Thalmann +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +declare module 'npm-which' { + /** + * Provides options for the `npmwhich`-module. + */ + interface NpmWhichOptions { + /** + * The environment to use for resolving the binary. + */ + env?: NodeJS.ProcessEnv + + /** + * The directory to find the binary for. + */ + cwd?: string + } + + /** + * Provides options for the `npmwhich`-module. + */ + interface StaticWhichOptions { + /** + * The environment to use for resolving the binary. + */ + env?: NodeJS.ProcessEnv + + /** + * The directory to find the binary for. + */ + cwd: string + } + + /** + * Represents a callback for handling the result of `NpmWhich`. + */ + interface NpmWhichCallback { + /** + * Handles the result of `NpmWhich`. + * + * @param error + * The error-message. + * + * @param result + * The result. + */ + (error: string, result: string): void + } + + /** + * Represents a basic interface for `npm-which`. + */ + interface WhichBase { + /** + * Creates a searcher for the specified command. + * + * @param cmd + * The command to look for. + * + * @param options + * The default options. + * + * @return + * A searcher for the specified command. + */ + (cmd: string, options?: TOptions): InnerWhich + + /** + * Searches for the specified command. + * + * @param cmd + * The command to look for. + * + * @param callback + * A callback for handling the result. + */ + (cmd: string, callback: NpmWhichCallback): void + + /** + * Searches for the specified command. + * + * @param cmd + * The command to look for. + * + * @param options + * The options for searching the command. + * + * @param callback + * A callback for handling the result. + */ + (cmd: string, options: TOptions, callback: NpmWhichCallback): void + } + + /** + * Represents the static instance of `npm-which`. + */ + interface StaticWhich extends WhichBase { + /** + * Initializes an `NpmWhich`-instance for the specified working-directory. + * + * @param cwd + * The working-directory to browse. + */ + (cwd?: string): NpmWhich + + /** + * Searches for the specified command. + * + * @param cmd + * The command to look for. + * + * @param options + * The options for searching the command. + */ + sync(cmd: string, options: StaticWhichOptions): string + } + + /** + * Provides the functionality to search for a command. + */ + interface NpmWhich extends WhichBase { + /** + * Searches for the specified command. + * + * @param cmd + * The command to look for. + * + * @param options + * The options for searching the command. + */ + sync(cmd: string, options?: StaticWhichOptions): string + } + + interface InnerWhich { + /** + * Creates a searcher for the specified command. + * + * @param options + * The options for searching the command. + */ + (options?: NpmWhichOptions): InnerWhich + + /** + * Searches for the command. + * + * @param callback + * A callback for handling the result. + */ + (callback: NpmWhichCallback): void + + /** + * Searches for the command. + * + * @param options + * The options for searching the command. + * + * @param callback + * A callback for handling the result. + */ + (options: NpmWhichOptions, callback: NpmWhichCallback): void + + /** + * Searches for the command. + * + * @param options + * The options for searching the command. + */ + sync(options?: NpmWhichOptions): string + } + + let npmWhich: StaticWhich + export = npmWhich +} diff --git a/nextjs/packages/next/package.json b/nextjs/packages/next/package.json index 1657643fb3..8636d2a5cc 100644 --- a/nextjs/packages/next/package.json +++ b/nextjs/packages/next/package.json @@ -92,6 +92,7 @@ "chokidar": "3.5.1", "constants-browserify": "1.0.0", "cookie-session": "^1.4.0", + "cross-spawn": "7.0.3", "crypto-browserify": "3.12.0", "cssnano-simple": "3.0.0", "debug": "4.3.1", @@ -108,6 +109,7 @@ "node-fetch": "2.6.1", "node-html-parser": "1.4.9", "node-libs-browser": "^2.2.1", + "npm-which": "^3.0.1", "null-loader": "4.0.1", "os-browserify": "0.3.0", "p-limit": "3.1.0", @@ -192,6 +194,7 @@ "@types/fresh": "0.5.0", "@types/jsonwebtoken": "8.5.0", "@types/lodash.curry": "4.1.6", + "@types/lodash.frompairs": "4.0.6", "@types/lru-cache": "5.1.0", "@types/node-fetch": "2.5.8", "@types/path-to-regexp": "1.7.0", @@ -221,7 +224,6 @@ "conf": "5.0.0", "content-type": "1.0.4", "cookie": "0.4.1", - "cross-spawn": "7.0.3", "css-loader": "4.3.0", "devalue": "2.0.1", "escape-string-regexp": "2.0.0", @@ -238,6 +240,7 @@ "jsonwebtoken": "8.5.1", "loader-utils": "2.0.0", "lodash.curry": "4.1.1", + "lodash.frompairs": "4.0.1", "lru-cache": "5.1.1", "mini-css-extract-plugin": "1.5.0", "nanoid": "^3.1.20", @@ -264,7 +267,8 @@ "unistore": "3.4.1", "web-vitals": "2.1.0", "webpack": "4.44.1", - "webpack-sources": "1.4.3" + "webpack-sources": "1.4.3", + "zod": "3.10.1" }, "engines": { "node": ">=12.0.0" diff --git a/nextjs/packages/next/pages/_app.tsx b/nextjs/packages/next/pages/_app.tsx index 334a34b4a3..3900267b57 100644 --- a/nextjs/packages/next/pages/_app.tsx +++ b/nextjs/packages/next/pages/_app.tsx @@ -28,7 +28,7 @@ async function appGetInitialProps({ return { pageProps } } -export default class App

extends React.Component< +export class App

extends React.Component< P & AppProps, S > { @@ -41,3 +41,4 @@ export default class App

extends React.Component< return } } +export default App diff --git a/nextjs/packages/next/pages/_error.tsx b/nextjs/packages/next/pages/_error.tsx index fde0e21bb7..6c01af2fc5 100644 --- a/nextjs/packages/next/pages/_error.tsx +++ b/nextjs/packages/next/pages/_error.tsx @@ -26,7 +26,7 @@ function _getInitialProps({ /** * `Error` component used for handling errors. */ -export default class Error

extends React.Component

{ +export class ErrorComponent

extends React.Component

{ static displayName = 'ErrorPage' static getInitialProps = _getInitialProps @@ -69,6 +69,7 @@ export default class Error

extends React.Component

{ ) } } +export default ErrorComponent const styles: { [k: string]: React.CSSProperties } = { error: { diff --git a/nextjs/packages/next/server/render.tsx b/nextjs/packages/next/server/render.tsx index 32a711cf5b..db402fa7ea 100644 --- a/nextjs/packages/next/server/render.tsx +++ b/nextjs/packages/next/server/render.tsx @@ -31,7 +31,11 @@ import Loadable from '../shared/lib/loadable' import { LoadableContext } from '../shared/lib/loadable-context' import postProcess from '../shared/lib/post-process' import { RouterContext } from '../shared/lib/router-context' -import { NextRouter } from '../shared/lib/router/router' +import { + NextRouter, + extractRouterParams, + extractQueryFromAsPath, +} from '../shared/lib/router/router' import { isDynamicRoute } from '../shared/lib/router/utils/is-dynamic' import { AppType, @@ -74,6 +78,7 @@ class ServerRouter implements NextRouter { route: string pathname: string query: ParsedUrlQuery + params: ParsedUrlQuery asPath: string basePath: string events: any @@ -103,6 +108,7 @@ class ServerRouter implements NextRouter { this.route = pathname.replace(/\/$/, '') || '/' this.pathname = pathname this.query = query + this.params = extractRouterParams(query, extractQueryFromAsPath(as)) this.asPath = as this.isFallback = isFallback this.basePath = basePath diff --git a/nextjs/packages/next/shared/lib/dynamic.tsx b/nextjs/packages/next/shared/lib/dynamic.tsx index 0b260d19e9..867d382f5a 100644 --- a/nextjs/packages/next/shared/lib/dynamic.tsx +++ b/nextjs/packages/next/shared/lib/dynamic.tsx @@ -65,7 +65,7 @@ export function noSSR

( // function dynamic

(options: O): -export default function dynamic

( +export function dynamic

( dynamicOptions: DynamicOptions

| Loader

, options?: DynamicOptions

): React.ComponentType

{ @@ -130,3 +130,4 @@ export default function dynamic

( return loadableFn(loadableOptions) } +export default dynamic diff --git a/nextjs/packages/next/shared/lib/head.tsx b/nextjs/packages/next/shared/lib/head.tsx index a862819ad5..e9e6280bcf 100644 --- a/nextjs/packages/next/shared/lib/head.tsx +++ b/nextjs/packages/next/shared/lib/head.tsx @@ -27,18 +27,21 @@ function onlyReactElement( // Adds support for React.Fragment if (child.type === React.Fragment) { return list.concat( - React.Children.toArray(child.props.children).reduce(( - fragmentList: Array>, - fragmentChild: any // blitz :React.ReactChild - ): Array> => { - if ( - typeof fragmentChild === 'string' || - typeof fragmentChild === 'number' - ) { - return fragmentList - } - return fragmentList.concat(fragmentChild) - }, []) as any //blitz + React.Children.toArray(child.props.children).reduce( + ( + fragmentList: Array>, + fragmentChild: any // blitz :React.ReactChild + ): Array> => { + if ( + typeof fragmentChild === 'string' || + typeof fragmentChild === 'number' + ) { + return fragmentList + } + return fragmentList.concat(fragmentChild) + }, + [] + ) as any //blitz ) } return list.concat(child) @@ -144,10 +147,9 @@ function reduceComponents( c.type === 'link' && c.props['href'] && // TODO(prateekbh@): Replace this with const from `constants` when the tree shaking works. - [ - 'https://fonts.googleapis.com/css', - 'https://use.typekit.net/', - ].some((url) => c.props['href'].startsWith(url)) + ['https://fonts.googleapis.com/css', 'https://use.typekit.net/'].some( + (url) => c.props['href'].startsWith(url) + ) ) { const newProps = { ...(c.props || {}) } newProps['data-href'] = newProps['href'] @@ -167,7 +169,7 @@ function reduceComponents( * This component injects elements to `` of your page. * To avoid duplicated `tags` in `` you can use the `key` property, which will make sure every tag is only rendered once. */ -function Head({ children }: { children: React.ReactNode }) { +export function Head({ children }: { children: React.ReactNode }) { const ampState = useContext(AmpStateContext) const headManager = useContext(HeadManagerContext) return ( diff --git a/nextjs/packages/next/shared/lib/router/router.ts b/nextjs/packages/next/shared/lib/router/router.ts index 0864a7ef66..c07e219835 100644 --- a/nextjs/packages/next/shared/lib/router/router.ts +++ b/nextjs/packages/next/shared/lib/router/router.ts @@ -34,6 +34,7 @@ import { searchParamsToUrlQuery } from './utils/querystring' import resolveRewrites from './utils/resolve-rewrites' import { getRouteMatcher } from './utils/route-matcher' import { getRouteRegex } from './utils/route-regex' +import fromPairs from 'next/dist/compiled/lodash.frompairs' declare global { interface Window { @@ -405,6 +406,7 @@ export type BaseRouter = { route: string pathname: string query: ParsedUrlQuery + params: ParsedUrlQuery asPath: string basePath: string locale?: string @@ -529,6 +531,7 @@ export default class Router implements BaseRouter { route: string pathname: string query: ParsedUrlQuery + params: ParsedUrlQuery asPath: string basePath: string @@ -630,6 +633,7 @@ export default class Router implements BaseRouter { this.pageLoader = pageLoader this.pathname = pathname this.query = query + this.params = extractRouterParams(query, extractQueryFromAsPath(as)) // if auto prerendered and dynamic route wait to update asPath // until after mount to prevent hydration mismatch const autoExportDynamic = @@ -1442,6 +1446,7 @@ export default class Router implements BaseRouter { this.route = route this.pathname = pathname this.query = query + this.params = extractRouterParams(query, extractQueryFromAsPath(as)) this.asPath = as return this.notify(data, resetScroll) } @@ -1709,3 +1714,75 @@ export default class Router implements BaseRouter { ) } } + +/* + * Based on the code of https://github.com/lukeed/qss + */ +const decodeString = (str: string) => + decodeURIComponent(str.replace(/\+/g, '%20')) + +function decode(str: string) { + if (!str) return {} + + let out: Record = {} + + for (const current of str.split('&')) { + let [key, value = ''] = current.split('=') + key = decodeString(key) + value = decodeString(value) + + if (key.length === 0) continue + + if (key in out) { + out[key] = ([] as string[]).concat(out[key], value) + } else { + out[key] = value + } + } + + return out +} + +type ParsedUrlQueryValue = string | string[] | undefined +function areQueryValuesEqual( + value1: ParsedUrlQueryValue, + value2: ParsedUrlQueryValue +) { + // Check if their type match + if (typeof value1 !== typeof value2) { + return false + } + + if (Array.isArray(value1) && Array.isArray(value2)) { + if (value1.length !== value2.length) { + return false + } + + for (let i = 0; i < value1.length; i++) { + if (value1[i] !== value2[i]) { + return false + } + } + + return true + } + + return value1 === value2 +} + +export function extractQueryFromAsPath(asPath: string) { + return decode(asPath.split('?', 2)[1]) +} + +export function extractRouterParams( + routerQuery: ParsedUrlQuery, + asPathQuery: ParsedUrlQuery +) { + return fromPairs( + Object.entries(routerQuery).filter( + ([key, value]) => + typeof asPathQuery[key] === 'undefined' || + !areQueryValuesEqual(value, asPathQuery[key]) + ) + ) +} diff --git a/nextjs/packages/next/shared/lib/runtime-config.ts b/nextjs/packages/next/shared/lib/runtime-config.ts index b4a48a362d..1a6752937d 100644 --- a/nextjs/packages/next/shared/lib/runtime-config.ts +++ b/nextjs/packages/next/shared/lib/runtime-config.ts @@ -1,8 +1,9 @@ let runtimeConfig: any -export default () => { +export const getConfig = () => { return runtimeConfig } +export default getConfig export function setConfig(configValue: any): void { runtimeConfig = configValue diff --git a/nextjs/packages/next/stdlib-server/index.ts b/nextjs/packages/next/stdlib-server/index.ts index 362d3c3d80..3867620233 100644 --- a/nextjs/packages/next/stdlib-server/index.ts +++ b/nextjs/packages/next/stdlib-server/index.ts @@ -6,6 +6,7 @@ export * from './middleware' export * from './auth-sessions' export * from './auth-utils' export * from './passport-adapter' +export * from './resolver' export function isLocalhost(req: NextApiRequest | IncomingMessage): boolean { let { host } = req.headers diff --git a/packages/core/src/server/resolver.ts b/nextjs/packages/next/stdlib-server/resolver.ts similarity index 72% rename from packages/core/src/server/resolver.ts rename to nextjs/packages/next/stdlib-server/resolver.ts index 152aa596c2..f85ec8387f 100644 --- a/packages/core/src/server/resolver.ts +++ b/nextjs/packages/next/stdlib-server/resolver.ts @@ -1,6 +1,12 @@ -import {AuthenticatedSessionContext, Ctx, SessionContext, SessionContextBase} from "next/types" -import {Await, EnsurePromise} from "next/types/utils" -import type {input as zInput, output as zOutput, ZodTypeAny} from "zod" +import { + AuthenticatedSessionContext, + Ctx, + SessionContext, + SessionContextBase, +} from 'next/types' +import { Await, EnsurePromise } from 'next/types/utils' +import type { input as zInput, output as zOutput, ZodTypeAny } from 'zod' +import { ParserType } from '../types/index' interface ResultWithContext { __blitz: true @@ -9,49 +15,84 @@ interface ResultWithContext { } function isResultWithContext(x: unknown): x is ResultWithContext { return ( - typeof x === "object" && x !== null && "ctx" in x && (x as ResultWithContext).__blitz === true + typeof x === 'object' && + x !== null && + 'ctx' in x && + (x as ResultWithContext).__blitz === true ) } -export interface AuthenticatedMiddlewareCtx extends Omit { +export interface AuthenticatedMiddlewareCtx extends Omit { session: AuthenticatedSessionContext } type PipeFn = ( i: Await, - c: PrevCtx, -) => Next extends ResultWithContext ? never : Next | ResultWithContext + c: PrevCtx +) => Next extends ResultWithContext + ? never + : Next | ResultWithContext -function pipe(ab: (i: A, c: Ctx) => Z): (input: A, ctx: Ctx) => EnsurePromise +function pipe( + ab: (i: A, c: Ctx) => Z +): (input: A, ctx: Ctx) => EnsurePromise function pipe( ab: PipeFn, - bc: PipeFn, + bc: PipeFn ): (input: A, ctx: CA) => EnsurePromise function pipe( ab: PipeFn, bc: PipeFn, - cd: PipeFn, + cd: PipeFn ): (input: A, ctx: CA) => EnsurePromise function pipe( ab: PipeFn, bc: PipeFn, cd: PipeFn, - de: PipeFn, + de: PipeFn ): (input: A, ctx: CA) => EnsurePromise -function pipe( +function pipe< + A, + B, + C, + D, + E, + F, + CA = Ctx, + CB = CA, + CC = CB, + CD = CC, + CE = CD, + CF = CE +>( ab: PipeFn, bc: PipeFn, cd: PipeFn, de: PipeFn, - ef: PipeFn, + ef: PipeFn ): (input: A, ctx: CA) => EnsurePromise -function pipe( +function pipe< + A, + B, + C, + D, + E, + F, + G, + CA = Ctx, + CB = CA, + CC = CB, + CD = CC, + CE = CD, + CF = CE, + CG = CF +>( ab: PipeFn, bc: PipeFn, cd: PipeFn, de: PipeFn, ef: PipeFn, - fg: PipeFn, + fg: PipeFn ): (input: A, ctx: CA) => EnsurePromise function pipe< A, @@ -77,7 +118,7 @@ function pipe< de: PipeFn, ef: PipeFn, fg: PipeFn, - gh: PipeFn, + gh: PipeFn ): (input: A, ctx: CA) => EnsurePromise function pipe< A, @@ -106,7 +147,7 @@ function pipe< ef: PipeFn, fg: PipeFn, gh: PipeFn, - hi: PipeFn, + hi: PipeFn ): (input: A, ctx: CA) => EnsurePromise function pipe< A, @@ -138,7 +179,7 @@ function pipe< fg: PipeFn, gh: PipeFn, hi: PipeFn, - ij: PipeFn, + ij: PipeFn ): (input: A, ctx: CA) => EnsurePromise function pipe< A, @@ -173,7 +214,7 @@ function pipe< gh: PipeFn, hi: PipeFn, ij: PipeFn, - jk: PipeFn, + jk: PipeFn ): (input: A, ctx: CA) => EnsurePromise function pipe< A, @@ -211,7 +252,7 @@ function pipe< hi: PipeFn, ij: PipeFn, jk: PipeFn, - kl: PipeFn, + kl: PipeFn ): (input: A, ctx: CA) => EnsurePromise function pipe< A, @@ -252,7 +293,7 @@ function pipe< ij: PipeFn, jk: PipeFn, kl: PipeFn, - lm: PipeFn, + lm: PipeFn ): (input: A, ctx: CA) => EnsurePromise function pipe(...args: unknown[]): unknown { const functions = args as PipeFn[] @@ -271,9 +312,9 @@ function pipe(...args: unknown[]): unknown { } interface ResolverAuthorize { - (...args: Parameters): ( + (...args: Parameters): ( input: T, - ctx: C, + ctx: C ) => ResultWithContext } @@ -290,24 +331,30 @@ const authorize: ResolverAuthorize = (...args) => { } } -export type ParserType = "sync" | "async" - -function zod, OutputType = zOutput>( - schema: Schema, - parserType: "sync", -): (input: InputType) => OutputType -function zod, OutputType = zOutput>( - schema: Schema, - parserType: "async", -): (input: InputType) => Promise -function zod, OutputType = zOutput>( +function zod< + Schema extends ZodTypeAny, + InputType = zInput, + OutputType = zOutput +>(schema: Schema, parserType: 'sync'): (input: InputType) => OutputType +function zod< + Schema extends ZodTypeAny, + InputType = zInput, + OutputType = zOutput +>( schema: Schema, + parserType: 'async' ): (input: InputType) => Promise -function zod, OutputType = zOutput>( - schema: Schema, - parserType: ParserType = "async", -) { - if (parserType === "sync") { +function zod< + Schema extends ZodTypeAny, + InputType = zInput, + OutputType = zOutput +>(schema: Schema): (input: InputType) => Promise +function zod< + Schema extends ZodTypeAny, + InputType = zInput, + OutputType = zOutput +>(schema: Schema, parserType: ParserType = 'async') { + if (parserType === 'sync') { return (input: InputType): OutputType => schema.parse(input) } else { return (input: InputType): Promise => schema.parseAsync(input) diff --git a/packages/core/src/blitz-app-root.tsx b/nextjs/packages/next/stdlib/blitz-app-root.tsx similarity index 56% rename from packages/core/src/blitz-app-root.tsx rename to nextjs/packages/next/stdlib/blitz-app-root.tsx index 34ab443ef1..0a59955ccc 100644 --- a/packages/core/src/blitz-app-root.tsx +++ b/nextjs/packages/next/stdlib/blitz-app-root.tsx @@ -1,12 +1,16 @@ -import {getPublicDataStore, useAuthorizeIf, useSession} from "next/data-client" -import {BlitzProvider} from "next/data-client" -import {formatWithValidation} from "next/dist/shared/lib/utils" -import {RedirectError} from "next/stdlib" -import {AppProps, BlitzPage} from "next/types" -import React, {ComponentPropsWithoutRef, useEffect} from "react" -import SuperJSON from "superjson" -import {Head} from "./head" -import {clientDebug} from "./utils" +import { + getPublicDataStore, + useAuthorizeIf, + useSession, +} from '../data-client/auth' +import { BlitzProvider } from '../data-client/react-query' +import { formatWithValidation } from '../shared/lib/utils' +import { Head } from '../shared/lib/head' +import { RedirectError } from './errors' +import { AppProps, BlitzPage } from '../types/index' +import React, { ComponentPropsWithoutRef, useEffect } from 'react' +import SuperJSON from 'superjson' +const debug = require('debug')('blitz:approot') const customCSS = ` body::before { @@ -34,15 +38,18 @@ const noscriptCSS = ` const NoPageFlicker = () => { return ( -