From 69fb28034076decdb47ed8054b81304e86e509c3 Mon Sep 17 00:00:00 2001 From: Aleksandra Date: Tue, 23 Aug 2022 16:24:28 +0200 Subject: [PATCH] Copy App component properties inside withBlitz (#3778) * Copy App component properties inside withBlitz --- .changeset/cyan-bulldogs-heal.md | 5 ++ integration-tests/get-initial-props/.env | 2 + .../get-initial-props/.eslintrc.js | 1 + .../get-initial-props/.gitignore | 3 + .../get-initial-props/app/blitz-client.ts | 14 ++++ .../get-initial-props/app/blitz-server.ts | 16 +++++ .../get-initial-props/app/queries/getBasic.ts | 3 + .../get-initial-props/db/index.ts | 8 +++ .../20220330155417_init/migration.sql | 47 +++++++++++++ .../db/migrations/migration_lock.toml | 3 + .../get-initial-props/db/schema.prisma | 50 ++++++++++++++ .../get-initial-props/db/seed.ts | 7 ++ .../get-initial-props/next-env.d.ts | 5 ++ .../get-initial-props/next.config.js | 4 ++ .../get-initial-props/package.json | 44 ++++++++++++ .../get-initial-props/pages/_app.tsx | 46 +++++++++++++ .../pages/api/rpc/[[...blitz]].ts | 4 ++ .../get-initial-props/pages/index.tsx | 22 ++++++ .../get-initial-props/test/index.test.ts | 67 +++++++++++++++++++ .../get-initial-props/tsconfig.json | 11 +++ integration-tests/get-initial-props/types.ts | 15 +++++ packages/blitz-next/src/index-browser.tsx | 8 ++- pnpm-lock.yaml | 53 ++++++++++++++- 23 files changed, 433 insertions(+), 5 deletions(-) create mode 100644 .changeset/cyan-bulldogs-heal.md create mode 100644 integration-tests/get-initial-props/.env create mode 100644 integration-tests/get-initial-props/.eslintrc.js create mode 100644 integration-tests/get-initial-props/.gitignore create mode 100644 integration-tests/get-initial-props/app/blitz-client.ts create mode 100644 integration-tests/get-initial-props/app/blitz-server.ts create mode 100644 integration-tests/get-initial-props/app/queries/getBasic.ts create mode 100644 integration-tests/get-initial-props/db/index.ts create mode 100644 integration-tests/get-initial-props/db/migrations/20220330155417_init/migration.sql create mode 100644 integration-tests/get-initial-props/db/migrations/migration_lock.toml create mode 100644 integration-tests/get-initial-props/db/schema.prisma create mode 100644 integration-tests/get-initial-props/db/seed.ts create mode 100644 integration-tests/get-initial-props/next-env.d.ts create mode 100644 integration-tests/get-initial-props/next.config.js create mode 100644 integration-tests/get-initial-props/package.json create mode 100644 integration-tests/get-initial-props/pages/_app.tsx create mode 100644 integration-tests/get-initial-props/pages/api/rpc/[[...blitz]].ts create mode 100644 integration-tests/get-initial-props/pages/index.tsx create mode 100644 integration-tests/get-initial-props/test/index.test.ts create mode 100644 integration-tests/get-initial-props/tsconfig.json create mode 100644 integration-tests/get-initial-props/types.ts diff --git a/.changeset/cyan-bulldogs-heal.md b/.changeset/cyan-bulldogs-heal.md new file mode 100644 index 0000000000..5af9b1fa47 --- /dev/null +++ b/.changeset/cyan-bulldogs-heal.md @@ -0,0 +1,5 @@ +--- +"@blitzjs/next": patch +--- + +Allow setting static page properties (e.g. `getInitialProps`) on the App component diff --git a/integration-tests/get-initial-props/.env b/integration-tests/get-initial-props/.env new file mode 100644 index 0000000000..dc405d323a --- /dev/null +++ b/integration-tests/get-initial-props/.env @@ -0,0 +1,2 @@ +SESSION_SECRET_KEY=hsdenhJfpLHrGjgdgg3jdF8g2bYD2PaQ +HEADLESS=true \ No newline at end of file diff --git a/integration-tests/get-initial-props/.eslintrc.js b/integration-tests/get-initial-props/.eslintrc.js new file mode 100644 index 0000000000..517eb569e6 --- /dev/null +++ b/integration-tests/get-initial-props/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require("@blitzjs/config/eslint") diff --git a/integration-tests/get-initial-props/.gitignore b/integration-tests/get-initial-props/.gitignore new file mode 100644 index 0000000000..1fbe169d99 --- /dev/null +++ b/integration-tests/get-initial-props/.gitignore @@ -0,0 +1,3 @@ +node_modules +# Keep environment variables out of version control +*.sqlite diff --git a/integration-tests/get-initial-props/app/blitz-client.ts b/integration-tests/get-initial-props/app/blitz-client.ts new file mode 100644 index 0000000000..a2c9078ac5 --- /dev/null +++ b/integration-tests/get-initial-props/app/blitz-client.ts @@ -0,0 +1,14 @@ +import {BlitzRpcPlugin} from "@blitzjs/rpc" +import {setupBlitzClient} from "@blitzjs/next" +import {AuthClientPlugin} from "@blitzjs/auth" + +const {withBlitz} = setupBlitzClient({ + plugins: [ + AuthClientPlugin({ + cookiePrefix: "trailing-slash-tests-cookie-prefix", + }), + BlitzRpcPlugin({}), + ], +}) + +export {withBlitz} diff --git a/integration-tests/get-initial-props/app/blitz-server.ts b/integration-tests/get-initial-props/app/blitz-server.ts new file mode 100644 index 0000000000..bc44d6957b --- /dev/null +++ b/integration-tests/get-initial-props/app/blitz-server.ts @@ -0,0 +1,16 @@ +import {setupBlitzServer} from "@blitzjs/next" +import {AuthServerPlugin, PrismaStorage} from "@blitzjs/auth" +import {simpleRolesIsAuthorized} from "@blitzjs/auth" +import db from "../db" + +const {gSSP, gSP, api} = setupBlitzServer({ + plugins: [ + AuthServerPlugin({ + cookiePrefix: "trailing-slash-tests-cookie-prefix", + storage: PrismaStorage(db), + isAuthorized: simpleRolesIsAuthorized, + }), + ], +}) + +export {gSSP, gSP, api} diff --git a/integration-tests/get-initial-props/app/queries/getBasic.ts b/integration-tests/get-initial-props/app/queries/getBasic.ts new file mode 100644 index 0000000000..ff5bfaa2a0 --- /dev/null +++ b/integration-tests/get-initial-props/app/queries/getBasic.ts @@ -0,0 +1,3 @@ +export default async function getBasic() { + return "basic-result" +} diff --git a/integration-tests/get-initial-props/db/index.ts b/integration-tests/get-initial-props/db/index.ts new file mode 100644 index 0000000000..4fb88468ff --- /dev/null +++ b/integration-tests/get-initial-props/db/index.ts @@ -0,0 +1,8 @@ +import {enhancePrisma} from "blitz" +import {PrismaClient} from "@prisma/client" + +const EnhancedPrisma = enhancePrisma(PrismaClient) + +export * from "@prisma/client" +const prisma = new EnhancedPrisma() +export default prisma diff --git a/integration-tests/get-initial-props/db/migrations/20220330155417_init/migration.sql b/integration-tests/get-initial-props/db/migrations/20220330155417_init/migration.sql new file mode 100644 index 0000000000..299b5a5151 --- /dev/null +++ b/integration-tests/get-initial-props/db/migrations/20220330155417_init/migration.sql @@ -0,0 +1,47 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "name" TEXT, + "email" TEXT NOT NULL, + "hashedPassword" TEXT, + "role" TEXT NOT NULL DEFAULT 'user' +); + +-- CreateTable +CREATE TABLE "Session" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "expiresAt" DATETIME, + "handle" TEXT NOT NULL, + "userId" INTEGER, + "hashedSessionToken" TEXT, + "antiCSRFToken" TEXT, + "publicData" TEXT, + "privateData" TEXT, + CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Token" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "hashedToken" TEXT NOT NULL, + "type" TEXT NOT NULL, + "expiresAt" DATETIME NOT NULL, + "sentTo" TEXT NOT NULL, + "userId" INTEGER NOT NULL, + CONSTRAINT "Token_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_handle_key" ON "Session"("handle"); + +-- CreateIndex +CREATE UNIQUE INDEX "Token_hashedToken_type_key" ON "Token"("hashedToken", "type"); diff --git a/integration-tests/get-initial-props/db/migrations/migration_lock.toml b/integration-tests/get-initial-props/db/migrations/migration_lock.toml new file mode 100644 index 0000000000..e5e5c4705a --- /dev/null +++ b/integration-tests/get-initial-props/db/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "sqlite" \ No newline at end of file diff --git a/integration-tests/get-initial-props/db/schema.prisma b/integration-tests/get-initial-props/db/schema.prisma new file mode 100644 index 0000000000..867ef79286 --- /dev/null +++ b/integration-tests/get-initial-props/db/schema.prisma @@ -0,0 +1,50 @@ +datasource sqlite { + provider = "sqlite" + url = "file:./db.sqlite" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + name String? + email String @unique + hashedPassword String? + role String @default("user") + + sessions Session[] + tokens Token[] +} + +model Session { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + expiresAt DateTime? + handle String @unique + user User? @relation(fields: [userId], references: [id]) + userId Int? + hashedSessionToken String? + antiCSRFToken String? + publicData String? + privateData String? +} + +model Token { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + hashedToken String + type String + expiresAt DateTime + sentTo String + + user User @relation(fields: [userId], references: [id]) + userId Int + + @@unique([hashedToken, type]) +} diff --git a/integration-tests/get-initial-props/db/seed.ts b/integration-tests/get-initial-props/db/seed.ts new file mode 100644 index 0000000000..1c3c302234 --- /dev/null +++ b/integration-tests/get-initial-props/db/seed.ts @@ -0,0 +1,7 @@ +import prisma from "./index" + +const seed = async () => { + await prisma.$reset() +} + +export default seed diff --git a/integration-tests/get-initial-props/next-env.d.ts b/integration-tests/get-initial-props/next-env.d.ts new file mode 100644 index 0000000000..4f11a03dc6 --- /dev/null +++ b/integration-tests/get-initial-props/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/integration-tests/get-initial-props/next.config.js b/integration-tests/get-initial-props/next.config.js new file mode 100644 index 0000000000..090457a0ec --- /dev/null +++ b/integration-tests/get-initial-props/next.config.js @@ -0,0 +1,4 @@ +const {withBlitz} = require("@blitzjs/next") +module.exports = withBlitz({ + trailingSlash: true, +}) diff --git a/integration-tests/get-initial-props/package.json b/integration-tests/get-initial-props/package.json new file mode 100644 index 0000000000..09d7bef0fa --- /dev/null +++ b/integration-tests/get-initial-props/package.json @@ -0,0 +1,44 @@ +{ + "name": "test-trailing-slash", + "version": "0.0.0", + "private": true, + "scripts": { + "start:dev": "pnpm run prisma:start && next dev", + "test": "pnpm run prisma:start && vitest run", + "test-watch": "vitest", + "start": "next start", + "lint": "next lint", + "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next", + "prisma:start": "prisma generate && prisma migrate deploy", + "prisma:studio": "prisma studio" + }, + "prisma": { + "schema": "db/schema.prisma" + }, + "dependencies": { + "@blitzjs/auth": "workspace:*", + "@blitzjs/next": "workspace:*", + "@blitzjs/rpc": "workspace:*", + "@prisma/client": "4.0.0", + "blitz": "workspace:*", + "lowdb": "3.0.0", + "next": "12.2.5", + "prisma": "4.0.0", + "react": "18.2.0", + "react-dom": "18.2.0" + }, + "devDependencies": { + "@blitzjs/config": "workspace:*", + "@next/bundle-analyzer": "12.0.8", + "@types/express": "4.17.13", + "@types/fs-extra": "9.0.13", + "@types/node-fetch": "2.6.1", + "@types/react": "18.0.17", + "b64-lite": "1.4.0", + "eslint": "7.32.0", + "fs-extra": "10.0.1", + "get-port": "6.1.2", + "node-fetch": "3.2.3", + "typescript": "^4.5.3" + } +} diff --git a/integration-tests/get-initial-props/pages/_app.tsx b/integration-tests/get-initial-props/pages/_app.tsx new file mode 100644 index 0000000000..e2aa5dcf09 --- /dev/null +++ b/integration-tests/get-initial-props/pages/_app.tsx @@ -0,0 +1,46 @@ +import {ErrorFallbackProps, ErrorComponent, ErrorBoundary, AppProps} from "@blitzjs/next" +import {AuthenticationError, AuthorizationError} from "blitz" +import App, {AppContext} from "next/app" +import React, {Suspense} from "react" +import {withBlitz} from "../app/blitz-client" + +function RootErrorFallback({error}: ErrorFallbackProps) { + if (error instanceof AuthenticationError) { + return
Error: You are not authenticated
+ } else if (error instanceof AuthorizationError) { + return ( + + ) + } else { + return ( + + ) + } +} + +function MyApp({Component, pageProps, testProp}: AppProps & {testProp: any}) { + return ( + + + + + + ) +} + +MyApp.getInitialProps = async (context: AppContext) => { + const props = await App.getInitialProps(context) + + return { + ...props, + testProp: "_app.tsx: testing getInitialProps", + } +} + +export default withBlitz(MyApp) diff --git a/integration-tests/get-initial-props/pages/api/rpc/[[...blitz]].ts b/integration-tests/get-initial-props/pages/api/rpc/[[...blitz]].ts new file mode 100644 index 0000000000..5fc23680c9 --- /dev/null +++ b/integration-tests/get-initial-props/pages/api/rpc/[[...blitz]].ts @@ -0,0 +1,4 @@ +import {rpcHandler} from "@blitzjs/rpc" +import {api} from "../../../app/blitz-server" + +export default api(rpcHandler({onError: console.log})) diff --git a/integration-tests/get-initial-props/pages/index.tsx b/integration-tests/get-initial-props/pages/index.tsx new file mode 100644 index 0000000000..15c9bc1299 --- /dev/null +++ b/integration-tests/get-initial-props/pages/index.tsx @@ -0,0 +1,22 @@ +import {NextPage} from "next" +import {Suspense} from "react" + +const Page: NextPage = (props) => { + return ( +
+ +
{JSON.stringify(props, null, 2)}
+
+
+ ) +} + +Page.getInitialProps = async (context) => { + return { + props: { + anotherTestProp: "index.tsx: testing getInitialProps", + }, + } +} + +export default Page diff --git a/integration-tests/get-initial-props/test/index.test.ts b/integration-tests/get-initial-props/test/index.test.ts new file mode 100644 index 0000000000..b1e39042c0 --- /dev/null +++ b/integration-tests/get-initial-props/test/index.test.ts @@ -0,0 +1,67 @@ +import {describe, it, expect, beforeAll, afterAll} from "vitest" +import {killApp, findPort, launchApp, nextBuild, nextStart} from "../../utils/next-test-utils" +import webdriver from "../../utils/next-webdriver" + +import {join} from "path" + +let app: any +let appPort: number +const appDir = join(__dirname, "../") + +const runTests = (mode?: string) => { + describe("getInitialProps", () => { + it( + "should render a custom prop provided in getInitialProps in _app.tsx", + async () => { + const browser = await webdriver(appPort, "/") + await browser.waitForElementByCss("#content", 0) + const text = await browser.elementByCss("#content").text() + expect(text).toMatch(/_app.tsx: testing getInitialProps/) + if (browser) await browser.close() + }, + 5000 * 60 * 2, + ) + it( + "should render custom props provided in getInitialProps in both _app.tsx and index.tsx", + async () => { + const browser = await webdriver(appPort, "/") + await browser.waitForElementByCss("#content", 0) + const text = await browser.elementByCss("#content").text() + expect(text).toMatch(/_app.tsx: testing getInitialProps/) + expect(text).toMatch(/index.tsx: testing getInitialProps/) + if (browser) await browser.close() + }, + 5000 * 60 * 2, + ) + }) +} + +describe("getInitialProps Tests", () => { + describe("dev mode", () => { + beforeAll(async () => { + try { + appPort = await findPort() + app = await launchApp(appDir, appPort, {cwd: process.cwd()}) + } catch (error) { + console.log(error) + } + }, 5000 * 60 * 2) + afterAll(async () => await killApp(app)) + runTests() + }) + + describe("server mode", () => { + beforeAll(async () => { + try { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort, {cwd: process.cwd()}) + } catch (err) { + console.log(err) + } + }, 5000 * 60 * 2) + afterAll(async () => await killApp(app)) + + runTests() + }) +}) diff --git a/integration-tests/get-initial-props/tsconfig.json b/integration-tests/get-initial-props/tsconfig.json new file mode 100644 index 0000000000..f391da135f --- /dev/null +++ b/integration-tests/get-initial-props/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@blitzjs/config/tsconfig.nextjs.json", + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "types"], + "compilerOptions": { + "paths": { + "react": ["./node_modules/@types/react"] + } + }, + "exclude": ["node_modules"], + "baseUrl": "." +} diff --git a/integration-tests/get-initial-props/types.ts b/integration-tests/get-initial-props/types.ts new file mode 100644 index 0000000000..dbc0b34ffc --- /dev/null +++ b/integration-tests/get-initial-props/types.ts @@ -0,0 +1,15 @@ +import {SimpleRolesIsAuthorized} from "@blitzjs/auth" +import {User} from "./db" + +export type Role = "ADMIN" | "USER" + +declare module "@blitzjs/auth" { + export interface Session { + isAuthorized: SimpleRolesIsAuthorized + PublicData: { + userId: User["id"] + role: Role + views?: number + } + } +} diff --git a/packages/blitz-next/src/index-browser.tsx b/packages/blitz-next/src/index-browser.tsx index 03fdeef7e1..46f242d86c 100644 --- a/packages/blitz-next/src/index-browser.tsx +++ b/packages/blitz-next/src/index-browser.tsx @@ -30,9 +30,10 @@ const buildWithBlitz = []>(plugin const providers = plugins.reduce((acc, plugin) => { return plugin.withProvider ? acc.concat(plugin.withProvider) : acc }, [] as BlitzProviderType[]) + const withPlugins = compose(...providers) - return function withBlitzAppRoot(UserAppRoot: React.ComponentType) { + return function withBlitzAppRoot(UserAppRoot: React.ComponentType) { const BlitzOuterRoot = (props: AppProps) => { const component = React.useMemo(() => withPlugins(props.Component), [props.Component]) @@ -52,6 +53,8 @@ const buildWithBlitz = []>(plugin ) } + + Object.assign(BlitzOuterRoot, UserAppRoot) return withSuperJSONPage(BlitzOuterRoot) } } @@ -145,10 +148,9 @@ const setupBlitzClient = []>({ // todo: finish this // Used to build BlitzPage type - const types = {} as {plugins: typeof plugins} + // const types = {} as {plugins: typeof plugins} return { - types, withBlitz, ...(exports as PluginsExports), } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d85ddc0a2..7ca9cc19cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -196,6 +196,55 @@ importers: node-fetch: 3.2.3 typescript: 4.6.3 + integration-tests/get-initial-props: + specifiers: + "@blitzjs/auth": workspace:* + "@blitzjs/config": workspace:* + "@blitzjs/next": workspace:* + "@blitzjs/rpc": workspace:* + "@next/bundle-analyzer": 12.0.8 + "@prisma/client": 4.0.0 + "@types/express": 4.17.13 + "@types/fs-extra": 9.0.13 + "@types/node-fetch": 2.6.1 + "@types/react": 18.0.17 + b64-lite: 1.4.0 + blitz: workspace:* + eslint: 7.32.0 + fs-extra: 10.0.1 + get-port: 6.1.2 + lowdb: 3.0.0 + next: 12.2.5 + node-fetch: 3.2.3 + prisma: 4.0.0 + react: 18.2.0 + react-dom: 18.2.0 + typescript: ^4.5.3 + dependencies: + "@blitzjs/auth": link:../../packages/blitz-auth + "@blitzjs/next": link:../../packages/blitz-next + "@blitzjs/rpc": link:../../packages/blitz-rpc + "@prisma/client": 4.0.0_prisma@4.0.0 + blitz: link:../../packages/blitz + lowdb: 3.0.0 + next: 12.2.5_biqbaboplfbrettd7655fr4n2y + prisma: 4.0.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + devDependencies: + "@blitzjs/config": link:../../packages/config + "@next/bundle-analyzer": 12.0.8 + "@types/express": 4.17.13 + "@types/fs-extra": 9.0.13 + "@types/node-fetch": 2.6.1 + "@types/react": 18.0.17 + b64-lite: 1.4.0 + eslint: 7.32.0 + fs-extra: 10.0.1 + get-port: 6.1.2 + node-fetch: 3.2.3 + typescript: 4.7.4 + integration-tests/middleware: specifiers: "@blitzjs/config": workspace:* @@ -12070,7 +12119,7 @@ packages: pretty-format: 27.5.1 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.7.0_fxg3r7oju3tntkxsvleuiot4fa + ts-node: 10.7.0_typescript@4.6.3 transitivePeerDependencies: - bufferutil - canvas @@ -17344,6 +17393,7 @@ packages: typescript: 4.6.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + dev: false /ts-node/10.7.0_typescript@4.6.3: resolution: @@ -17376,7 +17426,6 @@ packages: typescript: 4.6.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - dev: false /tsconfig-paths/3.14.1: resolution: