From cbebe9e5be88dfb1d0e894fa82026819d2eb4dec Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Tue, 5 Mar 2024 10:27:02 +0100 Subject: [PATCH 1/4] 0.9.0 (1) - adjust package shapes (#208) * rename wrapped classes to original name, also export in RSC * fix up test * fixup build types * comment shape * adjust type resolution * fixup * adjust package shapes * bundling adjustment: no `/index.js` in CJS * check "package shapes" * revert changes to examples/integration tests * reset README changes * add description to helper scripts * run test-bundle in CI --- .github/workflows/tests.yml | 17 +++++ examples/polls-demo/app/cc/poll-cc.tsx | 2 +- package.json | 4 + .../client-react-streaming/package-shape.json | 47 ++++++++++++ packages/client-react-streaming/package.json | 6 +- .../WrapApolloProvider.tsx | 10 +-- .../WrappedApolloClient.test.tsx | 12 +-- .../WrappedApolloClient.tsx | 27 ++++--- .../WrappedInMemoryCache.tsx | 12 +-- .../src/DataTransportAbstraction/index.ts | 4 +- .../client-react-streaming/src/index.rsc.ts | 1 + .../src/index.shared.ts | 7 ++ packages/client-react-streaming/src/index.ts | 16 +++- .../client-react-streaming/tsup.config.ts | 18 ++++- .../package-shape.json | 42 ++++++++++ .../package.json | 51 +++++-------- .../{index.ts => ApolloNextAppProvider.ts} | 19 +---- .../src/combined.ts | 17 ----- .../src/index.rsc.ts | 1 - .../src/rsc/index.ts | 1 + .../src/ssr/index.rsc.ts | 5 ++ .../src/ssr/index.ts | 14 ++++ .../tsconfig.json | 9 +-- .../tsup.config.ts | 27 +++++-- scripts/tsconfig.json | 10 +++ scripts/verify-package-json.mjs | 58 ++++++++++++++ scripts/verify-package-shape.mjs | 76 +++++++++++++++++++ yarn.lock | 10 +-- 28 files changed, 399 insertions(+), 124 deletions(-) create mode 100644 packages/client-react-streaming/package-shape.json create mode 100644 packages/client-react-streaming/src/index.shared.ts create mode 100644 packages/experimental-nextjs-app-support/package-shape.json rename packages/experimental-nextjs-app-support/src/{index.ts => ApolloNextAppProvider.ts} (50%) delete mode 100644 packages/experimental-nextjs-app-support/src/combined.ts delete mode 100644 packages/experimental-nextjs-app-support/src/index.rsc.ts create mode 100644 packages/experimental-nextjs-app-support/src/rsc/index.ts create mode 100644 packages/experimental-nextjs-app-support/src/ssr/index.rsc.ts create mode 100644 packages/experimental-nextjs-app-support/src/ssr/index.ts create mode 100644 scripts/tsconfig.json create mode 100644 scripts/verify-package-json.mjs create mode 100644 scripts/verify-package-shape.mjs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6c3f339b..b287875f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,6 +31,23 @@ jobs: - run: yarn workspaces foreach --all --include "@apollo/*" add -D -P @apollo/client@${{ matrix.version }} - run: yarn workspaces foreach --all --include "@apollo/*" run build - run: yarn workspaces foreach --all --include "@apollo/*" run test | tee $GITHUB_STEP_SUMMARY + packageShapes: + name: Test bundles + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + version: ["latest", "next"] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "20.x" + cache: "yarn" + - run: yarn install --immutable + - run: yarn workspaces foreach --all --include "@apollo/*" add -D -P @apollo/client@${{ matrix.version }} + - run: yarn workspaces foreach --all --include "@apollo/*" run test-bundle | tee $GITHUB_STEP_SUMMARY tests_playwright: name: Run Playwright tests if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name diff --git a/examples/polls-demo/app/cc/poll-cc.tsx b/examples/polls-demo/app/cc/poll-cc.tsx index 0d669475..73f6a4bc 100644 --- a/examples/polls-demo/app/cc/poll-cc.tsx +++ b/examples/polls-demo/app/cc/poll-cc.tsx @@ -5,7 +5,7 @@ import { useBackgroundQuery, } from "@apollo/experimental-nextjs-app-support/ssr"; import { useMutation } from "@apollo/client"; -import { QueryReference } from "@apollo/client/react/cache/QueryReference"; +import { QueryReference } from "@apollo/client/react"; import { Poll as PollInner } from "@/components/poll"; import { useState, useCallback } from "react"; diff --git a/package.json b/package.json index ae30256e..feb1a51f 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,9 @@ ], "dependencies": { "prettier": "^3.0.0" + }, + "scripts": { + "verify-package-json": "node ./scripts/verify-package-json.mjs", + "verify-package-shape": "node ./scripts/verify-package-shape.mjs" } } diff --git a/packages/client-react-streaming/package-shape.json b/packages/client-react-streaming/package-shape.json new file mode 100644 index 00000000..46a39ad6 --- /dev/null +++ b/packages/client-react-streaming/package-shape.json @@ -0,0 +1,47 @@ +{ + "@apollo/client-react-streaming": { + "react-server": [ + "ApolloClient", + "DebounceMultipartResponsesLink", + "InMemoryCache", + "RemoveMultipartDirectivesLink", + "SSRMultipartLink", + "registerApolloClient" + ], + "browser": [ + "ApolloClient", + "DataTransportContext", + "DebounceMultipartResponsesLink", + "InMemoryCache", + "RemoveMultipartDirectivesLink", + "SSRMultipartLink", + "WrapApolloProvider", + "resetApolloSingletons", + "useBackgroundQuery", + "useFragment", + "useQuery", + "useReadQuery", + "useSuspenseQuery" + ], + "node": [ + "ApolloClient", + "DataTransportContext", + "DebounceMultipartResponsesLink", + "InMemoryCache", + "RemoveMultipartDirectivesLink", + "SSRMultipartLink", + "WrapApolloProvider", + "resetApolloSingletons", + "useBackgroundQuery", + "useFragment", + "useQuery", + "useReadQuery", + "useSuspenseQuery" + ] + }, + "@apollo/client-react-streaming/experimental-manual-transport": { + "react-server": [], + "browser": ["buildManualDataTransport", "resetManualSSRApolloSingletons"], + "node": ["buildManualDataTransport", "resetManualSSRApolloSingletons"] + } +} diff --git a/packages/client-react-streaming/package.json b/packages/client-react-streaming/package.json index bb236450..962bc037 100644 --- a/packages/client-react-streaming/package.json +++ b/packages/client-react-streaming/package.json @@ -102,7 +102,10 @@ "test:rsc": "NODE_OPTIONS=\"$NODE_OPTIONS --conditions=react-server\" yarn run test:base", "prepack": "yarn build", "prepublishOnly": "yarn pack -o attw.tgz && attw attw.tgz && rm attw.tgz && yarn run test", - "test-bundle": "attw --pack .", + "test-bundle": "yarn test-bundle:attw && yarn test-bundle:package && yarn test-bundle:shape", + "test-bundle:attw": "attw --pack .", + "test-bundle:package": "yarn workspace monorepo verify-package-json $PWD/package.json", + "test-bundle:shape": "yarn workspace monorepo verify-package-shape $PWD/package-shape.json", "bundle-info": "yarn test-bundle --format json | jq '.analysis.entrypoints|to_entries|map({key:.key,value:.value.resolutions|to_entries|map({key:.key,value:.value.resolution.fileName })|from_entries})|from_entries'", "lint": "eslint --ext .ts,.tsx src" }, @@ -128,6 +131,7 @@ "react": "18.3.0-canary-60a927d04-20240113", "react-dom": "18.3.0-canary-60a927d04-20240113", "rimraf": "5.0.5", + "superjson": "1.13.3", "ts-node": "10.9.2", "tsup": "8.0.1", "tsx": "^4.7.0", diff --git a/packages/client-react-streaming/src/DataTransportAbstraction/WrapApolloProvider.tsx b/packages/client-react-streaming/src/DataTransportAbstraction/WrapApolloProvider.tsx index 1e1f95b5..1de9366c 100644 --- a/packages/client-react-streaming/src/DataTransportAbstraction/WrapApolloProvider.tsx +++ b/packages/client-react-streaming/src/DataTransportAbstraction/WrapApolloProvider.tsx @@ -1,14 +1,14 @@ "use client"; import React from "react"; import { useRef } from "react"; -import { WrappedApolloClient } from "./WrappedApolloClient.js"; +import { ApolloClient } from "./WrappedApolloClient.js"; import { ApolloProvider } from "@apollo/client/index.js"; import type { DataTransportProviderImplementation } from "./DataTransportAbstraction.js"; import { ApolloClientSingleton } from "./symbols.js"; declare global { interface Window { - [ApolloClientSingleton]?: WrappedApolloClient; + [ApolloClientSingleton]?: ApolloClient; } } @@ -25,10 +25,10 @@ export function WrapApolloProvider( ...extraProps }: React.PropsWithChildren< { - makeClient: () => WrappedApolloClient; + makeClient: () => ApolloClient; } & ExtraProps >) => { - const clientRef = useRef>(); + const clientRef = useRef>(); if (process.env.REACT_ENV === "ssr") { if (!clientRef.current) { @@ -38,7 +38,7 @@ export function WrapApolloProvider( clientRef.current = window[ApolloClientSingleton] ??= makeClient(); } - if (!(clientRef.current instanceof WrappedApolloClient)) { + if (!(clientRef.current instanceof ApolloClient)) { throw new Error( "When using Apollo Client streaming SSR, you must use the `ApolloClient` variant provided by the streaming package." ); diff --git a/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.test.tsx b/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.test.tsx index 59744246..1b193bb9 100644 --- a/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.test.tsx +++ b/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.test.tsx @@ -14,8 +14,8 @@ import { afterEach } from "node:test"; runInConditions("browser", "node"); const { - WrappedApolloClient, - WrappedInMemoryCache, + ApolloClient, + InMemoryCache, WrapApolloProvider, DataTransportContext, useSuspenseQuery, @@ -89,9 +89,9 @@ await testIn("node")( ); const link = new MockSubscriptionLink(); - const client = new WrappedApolloClient({ + const client = new ApolloClient({ connectToDevTools: false, - cache: new WrappedInMemoryCache(), + cache: new InMemoryCache(), link, }); @@ -159,9 +159,9 @@ await testIn("browser")( } ); - const client = new WrappedApolloClient({ + const client = new ApolloClient({ connectToDevTools: false, - cache: new WrappedInMemoryCache(), + cache: new InMemoryCache(), }); let attemptedRenderCount = 0; diff --git a/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.tsx b/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.tsx index 65a579b0..783b0efb 100644 --- a/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.tsx +++ b/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.tsx @@ -6,16 +6,19 @@ import type { DocumentNode, Cache, } from "@apollo/client/index.js"; -import { ApolloClient, Observable } from "@apollo/client/index.js"; +import { + ApolloClient as OrigApolloClient, + Observable, +} from "@apollo/client/index.js"; import type { QueryManager } from "@apollo/client/core/QueryManager.js"; import { print } from "@apollo/client/utilities/index.js"; import { canonicalStringify } from "@apollo/client/cache/index.js"; import { invariant } from "ts-invariant"; import { createBackpressuredCallback } from "./backpressuredCallback.js"; -import { WrappedInMemoryCache } from "./WrappedInMemoryCache.js"; +import { InMemoryCache } from "./WrappedInMemoryCache.js"; function getQueryManager( - client: ApolloClient + client: OrigApolloClient ): QueryManager { return client["queryManager"]; } @@ -26,7 +29,7 @@ type SimulatedQueryInfo = { options: WatchQueryOptions; }; -class ApolloClientSSRImpl extends ApolloClient { +class ApolloClientSSRImpl extends OrigApolloClient { watchQueryQueue = createBackpressuredCallback>(); constructor(options: ApolloClientOptions) { @@ -49,11 +52,11 @@ class ApolloClientSSRImpl extends ApolloClient { export class ApolloClientBrowserImpl< TCacheShape, -> extends ApolloClient { +> extends OrigApolloClient { constructor(options: ApolloClientOptions) { super(options); - if (!(this.cache instanceof WrappedInMemoryCache)) { + if (!(this.cache instanceof InMemoryCache)) { throw new Error( "When using Apollo Client streaming SSR, you must use the `InMemoryCache` variant provided by the streaming package." ); @@ -197,7 +200,7 @@ export class ApolloClientBrowserImpl< }; } -export type WrappedApolloClient = ApolloClient & { +export type ApolloClient = OrigApolloClient & { onRequestStarted?: ApolloClientBrowserImpl["onRequestStarted"]; onRequestData?: ApolloClientBrowserImpl["onRequestData"]; rerunSimulatedQueries?: ApolloClientBrowserImpl["rerunSimulatedQueries"]; @@ -208,15 +211,17 @@ export type WrappedApolloClient = ApolloClient & { ) => void; }; - cache: WrappedInMemoryCache; + cache: InMemoryCache; }; -export const WrappedApolloClient: { +export const ApolloClient: { new ( options: ApolloClientOptions - ): WrappedApolloClient; + ): ApolloClient; } = /*#__PURE__*/ ( process.env.REACT_ENV === "ssr" ? ApolloClientSSRImpl - : ApolloClientBrowserImpl + : process.env.REACT_ENV === "browser" + ? ApolloClientBrowserImpl + : OrigApolloClient ) as any; diff --git a/packages/client-react-streaming/src/DataTransportAbstraction/WrappedInMemoryCache.tsx b/packages/client-react-streaming/src/DataTransportAbstraction/WrappedInMemoryCache.tsx index 8bc27924..34c00668 100644 --- a/packages/client-react-streaming/src/DataTransportAbstraction/WrappedInMemoryCache.tsx +++ b/packages/client-react-streaming/src/DataTransportAbstraction/WrappedInMemoryCache.tsx @@ -3,10 +3,10 @@ import type { Cache, Reference, } from "@apollo/client/index.js"; -import { InMemoryCache } from "@apollo/client/index.js"; +import { InMemoryCache as OrigInMemoryCache } from "@apollo/client/index.js"; import { createBackpressuredCallback } from "./backpressuredCallback.js"; -class InMemoryCacheSSRImpl extends InMemoryCache { +class InMemoryCacheSSRImpl extends OrigInMemoryCache { protected writeQueue = createBackpressuredCallback(); constructor(config?: InMemoryCacheConfig) { @@ -19,7 +19,7 @@ class InMemoryCacheSSRImpl extends InMemoryCache { } } -export type WrappedInMemoryCache = InMemoryCache & { +export type InMemoryCache = OrigInMemoryCache & { writeQueue?: { register?: ( instance: ((options: Cache.WriteOptions) => void) | null @@ -27,9 +27,9 @@ export type WrappedInMemoryCache = InMemoryCache & { }; }; -export const WrappedInMemoryCache: { - new (config?: InMemoryCacheConfig): WrappedInMemoryCache; +export const InMemoryCache: { + new (config?: InMemoryCacheConfig): InMemoryCache; } = /*#__PURE__*/ process.env.REACT_ENV === "ssr" ? InMemoryCacheSSRImpl - : InMemoryCache; + : OrigInMemoryCache; diff --git a/packages/client-react-streaming/src/DataTransportAbstraction/index.ts b/packages/client-react-streaming/src/DataTransportAbstraction/index.ts index c5b9df30..47dd2bcd 100644 --- a/packages/client-react-streaming/src/DataTransportAbstraction/index.ts +++ b/packages/client-react-streaming/src/DataTransportAbstraction/index.ts @@ -1,5 +1,5 @@ -export { WrappedInMemoryCache } from "./WrappedInMemoryCache.js"; -export { WrappedApolloClient } from "./WrappedApolloClient.js"; +export { InMemoryCache } from "./WrappedInMemoryCache.js"; +export { ApolloClient } from "./WrappedApolloClient.js"; export { useFragment, diff --git a/packages/client-react-streaming/src/index.rsc.ts b/packages/client-react-streaming/src/index.rsc.ts index 25a3bde6..3abfdd05 100644 --- a/packages/client-react-streaming/src/index.rsc.ts +++ b/packages/client-react-streaming/src/index.rsc.ts @@ -1 +1,2 @@ export { registerApolloClient } from "./registerApolloClient.js"; +export * from "./index.shared.js"; diff --git a/packages/client-react-streaming/src/index.shared.ts b/packages/client-react-streaming/src/index.shared.ts new file mode 100644 index 00000000..ff43ddaa --- /dev/null +++ b/packages/client-react-streaming/src/index.shared.ts @@ -0,0 +1,7 @@ +export { AccumulateMultipartResponsesLink as DebounceMultipartResponsesLink } from "./AccumulateMultipartResponsesLink.js"; +export { RemoveMultipartDirectivesLink } from "./RemoveMultipartDirectivesLink.js"; +export { SSRMultipartLink } from "./SSRMultipartLink.js"; +export { + ApolloClient, + InMemoryCache, +} from "./DataTransportAbstraction/index.js"; diff --git a/packages/client-react-streaming/src/index.ts b/packages/client-react-streaming/src/index.ts index c8d4fb04..57ea737c 100644 --- a/packages/client-react-streaming/src/index.ts +++ b/packages/client-react-streaming/src/index.ts @@ -1,4 +1,12 @@ -export { AccumulateMultipartResponsesLink as DebounceMultipartResponsesLink } from "./AccumulateMultipartResponsesLink.js"; -export { RemoveMultipartDirectivesLink } from "./RemoveMultipartDirectivesLink.js"; -export { SSRMultipartLink } from "./SSRMultipartLink.js"; -export * from "./DataTransportAbstraction/index.js"; +export * from "./index.shared.js"; +export { + useFragment, + useQuery, + useSuspenseQuery, + useReadQuery, + useBackgroundQuery, + resetApolloSingletons, + DataTransportContext, + DataTransportProviderImplementation, + WrapApolloProvider, +} from "./DataTransportAbstraction/index.js"; diff --git a/packages/client-react-streaming/tsup.config.ts b/packages/client-react-streaming/tsup.config.ts index 2405314b..1b519ff5 100644 --- a/packages/client-react-streaming/tsup.config.ts +++ b/packages/client-react-streaming/tsup.config.ts @@ -1,5 +1,6 @@ import type { Options } from "tsup"; import { defineConfig } from "tsup"; +import type { Plugin } from "esbuild"; export default defineConfig((options) => { const defaults: Options = { @@ -19,8 +20,10 @@ export default defineConfig((options) => { "@apollo/client-react-streaming", "react", "rehackt", - "@apollo/client", + "superjson", ], + noExternal: ["@apollo/client"], // will be handled by `acModuleImports` + esbuildPlugins: [acModuleImports], }; function entry( @@ -74,3 +77,16 @@ export default defineConfig((options) => { ), ]; }); + +const acModuleImports: Plugin = { + name: "replace-ac-module-imports", + setup(build) { + build.onResolve({ filter: /^@apollo\/client/ }, async (args) => { + if (build.initialOptions.define["TSUP_FORMAT"] === '"cjs"') { + // remove trailing `/index.js` in CommonJS builds + return { path: args.path.replace(/\/index.js$/, ""), external: true }; + } + return { path: args.path, external: true }; + }); + }, +}; diff --git a/packages/experimental-nextjs-app-support/package-shape.json b/packages/experimental-nextjs-app-support/package-shape.json new file mode 100644 index 00000000..c2ac4a3b --- /dev/null +++ b/packages/experimental-nextjs-app-support/package-shape.json @@ -0,0 +1,42 @@ +{ + "@apollo/experimental-nextjs-app-support/ssr": { + "react-server": [ + "DebounceMultipartResponsesLink", + "RemoveMultipartDirectivesLink", + "SSRMultipartLink" + ], + "browser": [ + "ApolloNextAppProvider", + "DebounceMultipartResponsesLink", + "NextSSRApolloClient", + "NextSSRInMemoryCache", + "RemoveMultipartDirectivesLink", + "SSRMultipartLink", + "resetNextSSRApolloSingletons", + "useBackgroundQuery", + "useFragment", + "useQuery", + "useReadQuery", + "useSuspenseQuery" + ], + "node": [ + "ApolloNextAppProvider", + "DebounceMultipartResponsesLink", + "NextSSRApolloClient", + "NextSSRInMemoryCache", + "RemoveMultipartDirectivesLink", + "SSRMultipartLink", + "resetNextSSRApolloSingletons", + "useBackgroundQuery", + "useFragment", + "useQuery", + "useReadQuery", + "useSuspenseQuery" + ] + }, + "@apollo/experimental-nextjs-app-support/rsc": { + "react-server": ["registerApolloClient"], + "browser": [], + "node": [] + } +} diff --git a/packages/experimental-nextjs-app-support/package.json b/packages/experimental-nextjs-app-support/package.json index 691a63a2..43aa3eee 100644 --- a/packages/experimental-nextjs-app-support/package.json +++ b/packages/experimental-nextjs-app-support/package.json @@ -16,46 +16,32 @@ ], "type": "module", "exports": { - ".": { - "require": { - "types": "./dist/combined.d.cts", - "react-server": "./dist/index.rsc.cjs", - "browser": "./dist/ssr/index.browser.cjs", - "node": "./dist/ssr/index.ssr.cjs" - }, - "import": { - "types": "./dist/combined.d.ts", - "react-server": "./dist/index.rsc.js", - "browser": "./dist/ssr/index.browser.js", - "node": "./dist/ssr/index.ssr.js" - } - }, "./rsc": { "require": { - "types": "./dist/index.rsc.d.cts", - "react-server": "./dist/index.rsc.cjs", + "types": "./dist/rsc/index.d.cts", + "react-server": "./dist/rsc/index.cjs", "browser": "./dist/empty.cjs", "node": "./dist/empty.cjs" }, "import": { - "types": "./dist/index.rsc.d.ts", - "react-server": "./dist/index.rsc.js", + "types": "./dist/rsc/index.d.ts", + "react-server": "./dist/rsc/index.js", "browser": "./dist/empty.js", "node": "./dist/empty.js" } }, "./ssr": { "require": { - "types": "./dist/index.ssr.d.cts", - "react-server": "./dist/empty.cjs", - "browser": "./dist/index.browser.cjs", - "node": "./dist/index.ssr.cjs" + "types": "./dist/ssr/index.ssr.d.cts", + "react-server": "./dist/ssr/index.rsc.cjs", + "browser": "./dist/ssr/index.browser.cjs", + "node": "./dist/ssr/index.ssr.cjs" }, "import": { - "types": "./dist/index.ssr.d.ts", - "react-server": "./dist/empty.js", - "browser": "./dist/index.browser.js", - "node": "./dist/index.ssr.js" + "types": "./dist/ssr/index.ssr.d.ts", + "react-server": "./dist/ssr/index.rsc.js", + "browser": "./dist/ssr/index.browser.js", + "node": "./dist/ssr/index.ssr.js" } }, "./package.json": "./package.json" @@ -63,14 +49,14 @@ "typesVersions": { "*": { "ssr": [ - "./dist/index.ssr.d.ts" + "./dist/ssr/index.ssr.d.ts" ], "rsc": [ - "./dist/index.rsc.d.ts" + "./dist/rsc/index.d.ts" ] } }, - "typings": "./dist/combined.d.ts", + "typings": "./dist/empty.d.ts", "author": "packages@apollographql.com", "license": "MIT", "files": [ @@ -81,10 +67,13 @@ ], "scripts": { "build": "rimraf dist; tsup", - "test": "true", + "test": "yarn verify", "prepack": "yarn build", "prepublishOnly": "yarn pack -o attw.tgz && attw attw.tgz && rm attw.tgz && yarn run test", - "test-bundle": "attw --pack .", + "test-bundle": "yarn test-bundle:attw && yarn test-bundle:package && yarn test-bundle:shape", + "test-bundle:attw": "attw --pack .", + "test-bundle:package": "yarn workspace monorepo verify-package-json $PWD/package.json", + "test-bundle:shape": "yarn workspace monorepo verify-package-shape $PWD/package-shape.json", "bundle-info": "yarn test-bundle --format json | jq '.analysis.entrypoints|to_entries|map({key:.key,value:.value.resolutions|to_entries|map({key:.key,value:.value.resolution.fileName })|from_entries})|from_entries'", "lint": "eslint --ext .ts,.tsx ." }, diff --git a/packages/experimental-nextjs-app-support/src/index.ts b/packages/experimental-nextjs-app-support/src/ApolloNextAppProvider.ts similarity index 50% rename from packages/experimental-nextjs-app-support/src/index.ts rename to packages/experimental-nextjs-app-support/src/ApolloNextAppProvider.ts index 5d395a90..3e703759 100644 --- a/packages/experimental-nextjs-app-support/src/index.ts +++ b/packages/experimental-nextjs-app-support/src/ApolloNextAppProvider.ts @@ -1,8 +1,5 @@ import { useContext } from "react"; -import { - buildManualDataTransport, - resetManualSSRApolloSingletons, -} from "@apollo/client-react-streaming/experimental-manual-transport"; +import { buildManualDataTransport } from "@apollo/client-react-streaming/experimental-manual-transport"; import { WrapApolloProvider } from "@apollo/client-react-streaming"; import { ServerInsertedHTMLContext } from "next/navigation.js"; @@ -19,17 +16,3 @@ export const ApolloNextAppProvider = /*#__PURE__*/ WrapApolloProvider( }, }) ); - -export const resetNextSSRApolloSingletons = resetManualSSRApolloSingletons; -export { - WrappedInMemoryCache as NextSSRInMemoryCache, - WrappedApolloClient as NextSSRApolloClient, - useBackgroundQuery, - useFragment, - useQuery, - useReadQuery, - useSuspenseQuery, - SSRMultipartLink, - DebounceMultipartResponsesLink, - RemoveMultipartDirectivesLink, -} from "@apollo/client-react-streaming"; diff --git a/packages/experimental-nextjs-app-support/src/combined.ts b/packages/experimental-nextjs-app-support/src/combined.ts deleted file mode 100644 index 086e6ca7..00000000 --- a/packages/experimental-nextjs-app-support/src/combined.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * TypeScript does not have the concept of these environments, - * so we need to create a single entry point that combines all - * possible exports. - * That means that users will be offered "RSC" exports in a - * "SSR/Browser" code file, but those will error in a compilation - * step. - * - * This is a limitation of TypeScript, and we can't do anything - * about it. - * - * The build process will only create `.d.ts`/`d.cts` files from - * this, and not actual runtime code. - */ - -export * from "./index.rsc.js"; -export * from "./index.js"; diff --git a/packages/experimental-nextjs-app-support/src/index.rsc.ts b/packages/experimental-nextjs-app-support/src/index.rsc.ts deleted file mode 100644 index 1cd7b823..00000000 --- a/packages/experimental-nextjs-app-support/src/index.rsc.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "@apollo/client-react-streaming"; diff --git a/packages/experimental-nextjs-app-support/src/rsc/index.ts b/packages/experimental-nextjs-app-support/src/rsc/index.ts new file mode 100644 index 00000000..fe8b3f08 --- /dev/null +++ b/packages/experimental-nextjs-app-support/src/rsc/index.ts @@ -0,0 +1 @@ +export { registerApolloClient } from "@apollo/client-react-streaming"; diff --git a/packages/experimental-nextjs-app-support/src/ssr/index.rsc.ts b/packages/experimental-nextjs-app-support/src/ssr/index.rsc.ts new file mode 100644 index 00000000..71bb3d72 --- /dev/null +++ b/packages/experimental-nextjs-app-support/src/ssr/index.rsc.ts @@ -0,0 +1,5 @@ +export { + SSRMultipartLink, + DebounceMultipartResponsesLink, + RemoveMultipartDirectivesLink, +} from "@apollo/client-react-streaming"; diff --git a/packages/experimental-nextjs-app-support/src/ssr/index.ts b/packages/experimental-nextjs-app-support/src/ssr/index.ts new file mode 100644 index 00000000..cde9e143 --- /dev/null +++ b/packages/experimental-nextjs-app-support/src/ssr/index.ts @@ -0,0 +1,14 @@ +export { ApolloNextAppProvider } from "../ApolloNextAppProvider.js"; +export { resetManualSSRApolloSingletons as resetNextSSRApolloSingletons } from "@apollo/client-react-streaming/experimental-manual-transport"; +export { + InMemoryCache as NextSSRInMemoryCache, + ApolloClient as NextSSRApolloClient, + useBackgroundQuery, + useFragment, + useQuery, + useReadQuery, + useSuspenseQuery, + SSRMultipartLink, + DebounceMultipartResponsesLink, + RemoveMultipartDirectivesLink, +} from "@apollo/client-react-streaming"; diff --git a/packages/experimental-nextjs-app-support/tsconfig.json b/packages/experimental-nextjs-app-support/tsconfig.json index 0c798e89..82531828 100644 --- a/packages/experimental-nextjs-app-support/tsconfig.json +++ b/packages/experimental-nextjs-app-support/tsconfig.json @@ -11,14 +11,7 @@ "jsx": "react", "declarationMap": true, "types": ["react/canary", "node"], - "esModuleInterop": true, - "paths": { - "@apollo/experimental-nextjs-app-support": ["./src/combined.ts"], - "@apollo/experimental-nextjs-app-support/core": ["./src/combined.ts"], - "@apollo/experimental-nextjs-app-support/manual": [ - "./src/ssr/Manual/index.ts" - ] - } + "esModuleInterop": true }, "include": ["src"] } diff --git a/packages/experimental-nextjs-app-support/tsup.config.ts b/packages/experimental-nextjs-app-support/tsup.config.ts index 0c7fb0d2..773f6193 100644 --- a/packages/experimental-nextjs-app-support/tsup.config.ts +++ b/packages/experimental-nextjs-app-support/tsup.config.ts @@ -1,5 +1,6 @@ import type { Options } from "tsup"; import { defineConfig } from "tsup"; +import type { Plugin } from "esbuild"; export default defineConfig((options) => { const defaults: Options = { @@ -16,6 +17,8 @@ export default defineConfig((options) => { "react", "rehackt", ], + noExternal: ["@apollo/client"], // will be handled by `acModuleImports` + esbuildPlugins: [acModuleImports], }; function entry( @@ -39,13 +42,23 @@ export default defineConfig((options) => { } return [ - { - ...entry("other", "src/combined.ts", "combined"), - dts: { only: true }, - }, entry("other", "src/empty.ts", "empty"), - entry("rsc", "src/index.rsc.ts", "index.rsc"), - entry("ssr", "src/index.ts", "index.ssr"), - entry("browser", "src/index.ts", "index.browser"), + entry("rsc", "src/rsc/index.ts", "rsc/index"), + entry("rsc", "src/ssr/index.rsc.ts", "ssr/index.rsc"), + entry("ssr", "src/ssr/index.ts", "ssr/index.ssr"), + entry("browser", "src/ssr/index.ts", "ssr/index.browser"), ]; }); + +const acModuleImports: Plugin = { + name: "replace-ac-module-imports", + setup(build) { + build.onResolve({ filter: /^@apollo\/client/ }, async (args) => { + if (build.initialOptions.define["TSUP_FORMAT"] === '"cjs"') { + // remove trailing `/index.js` in CommonJS builds + return { path: args.path.replace(/\/index.js$/, ""), external: true }; + } + return { path: args.path, external: true }; + }); + }, +}; diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 00000000..2ee5faff --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@tsconfig/recommended/tsconfig.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "target": "esnext", + "noEmit": true, + "checkJs": true + } +} diff --git a/scripts/verify-package-json.mjs b/scripts/verify-package-json.mjs new file mode 100644 index 00000000..c187fbec --- /dev/null +++ b/scripts/verify-package-json.mjs @@ -0,0 +1,58 @@ +// @ts-check + +/** + * Verifies that all files referenced in package.json actually exist. + */ + +import { readFile, access } from "node:fs/promises"; +import { join, dirname } from "node:path"; +import { parseArgs } from "node:util"; + +const args = parseArgs({ + args: process.argv.slice(2), + allowPositionals: true, +}); + +let notfound = 0; +for (const pkg of args.positionals) { + console.log("Checking package.json: %s", pkg); + await checkPackage(pkg); +} +if (notfound > 0) { + process.exit(1); +} else { + console.log("All referenced files found."); +} + +async function checkPackage(/** @type {string} */ pkg) { + const json = JSON.parse(await readFile(pkg, { encoding: "utf-8" })); + await ensureLeafFilesExist(dirname(pkg), json.exports); + await ensureLeafFilesExist(dirname(pkg), json.typesVersions); + await ensureLeafFilesExist(dirname(pkg), json.types); + await ensureLeafFilesExist(dirname(pkg), json.typings); + await ensureLeafFilesExist(dirname(pkg), json.main); +} + +/** + * @typedef {{[key :string]: string | string[] | FileMap}} FileMap + */ + +async function ensureLeafFilesExist( + /** @type {string} */ basedir, + /** @type {FileMap | string | string[] | undefined} */ fileMap +) { + if (!fileMap) return; + if (typeof fileMap === "string") { + try { + await access(join(basedir, fileMap)); + // console.log("Found file: ", fileMap); + } catch { + notfound++; + console.error("File not found: ", fileMap); + } + return; + } + for (const entry of Object.values(fileMap)) { + ensureLeafFilesExist(basedir, entry); + } +} diff --git a/scripts/verify-package-shape.mjs b/scripts/verify-package-shape.mjs new file mode 100644 index 00000000..c1357889 --- /dev/null +++ b/scripts/verify-package-shape.mjs @@ -0,0 +1,76 @@ +// @ts-check + +/** + * Compares actual exports of a package per condition with + * the expected exports described by a package-shape.json file. + */ + +import assert from "node:assert"; +import { exec } from "node:child_process"; +import { readFile } from "node:fs/promises"; +import { dirname } from "node:path"; +import { parseArgs } from "node:util"; + +const args = parseArgs({ + args: process.argv.slice(2), + allowPositionals: true, +}); + +for (const pkg of args.positionals) { + console.log("Checking package.json: %s", pkg); + await checkPackage(pkg); +} + +async function checkPackage(/** @type {string} */ pkg) { + const json = JSON.parse(await readFile(pkg, { encoding: "utf-8" })); + for (const entryPoint of Object.keys(json)) { + for (const [condition, shape] of Object.entries(json[entryPoint])) { + shape.sort(); + await verifyESM(condition, entryPoint, pkg, shape); + await verifyCJS(condition, entryPoint, pkg, shape); + } + } +} +/** + * + * @param {string} condition + * @param {string} entryPoint + * @param {string} pkg + * @param {string[]} shape + */ +async function verifyESM(condition, entryPoint, pkg, shape) { + console.log(`Checking ESM: ${entryPoint} with condition ${condition}`); + const child = exec( + `node --input-type=module --conditions ${condition} --eval 'console.log(JSON.stringify(Object.keys(await import("${entryPoint}"))))'`, + { + cwd: dirname(pkg), + } + ); + let result = ""; + child.stdout?.on("data", (data) => (result += data.toString())); + child.stderr?.pipe(process.stderr); + await new Promise((resolve) => child.on("exit", resolve)); + assert.deepStrictEqual(JSON.parse(result).sort(), shape); +} + +/** + * + * @param {string} condition + * @param {string} entryPoint + * @param {string} pkg + * @param {string[]} shape + */ +async function verifyCJS(condition, entryPoint, pkg, shape) { + console.log(`Checking CJS: ${entryPoint} with condition ${condition}`); + const child = exec( + `node --input-type=commonjs --conditions ${condition} --eval 'console.log(JSON.stringify(Object.keys(require("${entryPoint}"))))'`, + { + cwd: dirname(pkg), + } + ); + let result = ""; + child.stdout?.on("data", (data) => (result += data.toString())); + child.stderr?.pipe(process.stderr); + await new Promise((resolve) => child.on("exit", resolve)); + assert.deepStrictEqual(JSON.parse(result).sort(), shape); +} diff --git a/yarn.lock b/yarn.lock index 9c0b7b31..073e0f89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -70,7 +70,7 @@ __metadata: react: "npm:18.3.0-canary-60a927d04-20240113" react-dom: "npm:18.3.0-canary-60a927d04-20240113" rimraf: "npm:5.0.5" - superjson: "npm:^1.12.2 || ^2.0.0" + superjson: "npm:1.13.3" ts-invariant: "npm:^0.10.3" ts-node: "npm:10.9.2" tsup: "npm:8.0.1" @@ -13407,12 +13407,12 @@ __metadata: languageName: node linkType: hard -"superjson@npm:^1.12.2 || ^2.0.0": - version: 2.2.1 - resolution: "superjson@npm:2.2.1" +"superjson@npm:1.13.3": + version: 1.13.3 + resolution: "superjson@npm:1.13.3" dependencies: copy-anything: "npm:^3.0.2" - checksum: 10/bb8743a87c97f7845e0c27af1af0731d3185b32099ebce2aee0e67ac9a6ae9a7c4b9edfca7e1fe48693a78b56d5922d1cd13ef80c2fa12b788d3fc0ca25afe47 + checksum: 10/71a186c513a9821e58264c0563cd1b3cf07d3b5ba53a09cc5c1a604d8ffeacac976a6ba1b5d5b3c71b6ab5a1941dfba5a15e3f106ad3ef22fe8d5eee3e2be052 languageName: node linkType: hard From a78996e4320d261e12e6e967fd70d3d58a529de7 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Tue, 5 Mar 2024 10:36:09 +0100 Subject: [PATCH 2/4] 0.9.0 (2) - wrap hook functionality from ApolloClient instance (#216) * rename wrapped classes to original name, also export in RSC * fix up test * fixup build types * comment shape * adjust type resolution * fixup * adjust package shapes * bundling adjustment: no `/index.js` in CJS * check "package shapes" * revert changes to examples/integration tests * reset README changes * add description to helper scripts * wrap hook functionality from ApolloClient instance * move wrappers to `QueryManager` * update version * remove hook exports from `@apollo/client-react-streaming` * run test-bundle in CI * drop hook exports --- package.json | 3 ++ .../client-react-streaming/package-shape.json | 14 +----- .../WrappedApolloClient.test.tsx | 2 +- .../WrappedApolloClient.tsx | 35 ++++++++------ .../src/DataTransportAbstraction/hooks.ts | 48 ++++++++----------- .../src/DataTransportAbstraction/index.ts | 7 --- packages/client-react-streaming/src/index.ts | 5 -- .../src/ssr/index.ts | 10 ++-- yarn.lock | 18 +++---- 9 files changed, 62 insertions(+), 80 deletions(-) diff --git a/package.json b/package.json index feb1a51f..c4786b93 100644 --- a/package.json +++ b/package.json @@ -12,5 +12,8 @@ "scripts": { "verify-package-json": "node ./scripts/verify-package-json.mjs", "verify-package-shape": "node ./scripts/verify-package-shape.mjs" + }, + "resolutions": { + "@apollo/client": "0.0.0-pr-11617-20240227102358" } } diff --git a/packages/client-react-streaming/package-shape.json b/packages/client-react-streaming/package-shape.json index 46a39ad6..be250242 100644 --- a/packages/client-react-streaming/package-shape.json +++ b/packages/client-react-streaming/package-shape.json @@ -16,12 +16,7 @@ "RemoveMultipartDirectivesLink", "SSRMultipartLink", "WrapApolloProvider", - "resetApolloSingletons", - "useBackgroundQuery", - "useFragment", - "useQuery", - "useReadQuery", - "useSuspenseQuery" + "resetApolloSingletons" ], "node": [ "ApolloClient", @@ -31,12 +26,7 @@ "RemoveMultipartDirectivesLink", "SSRMultipartLink", "WrapApolloProvider", - "resetApolloSingletons", - "useBackgroundQuery", - "useFragment", - "useQuery", - "useReadQuery", - "useSuspenseQuery" + "resetApolloSingletons" ] }, "@apollo/client-react-streaming/experimental-manual-transport": { diff --git a/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.test.tsx b/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.test.tsx index 1b193bb9..e5fa122f 100644 --- a/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.test.tsx +++ b/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.test.tsx @@ -18,8 +18,8 @@ const { InMemoryCache, WrapApolloProvider, DataTransportContext, - useSuspenseQuery, } = await import("#bundled"); +const { useSuspenseQuery } = await import("@apollo/client/index.js"); const { MockSubscriptionLink } = await import( "@apollo/client/testing/index.js" ); diff --git a/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.tsx b/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.tsx index 783b0efb..e3712b52 100644 --- a/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.tsx +++ b/packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.tsx @@ -16,10 +16,14 @@ import { canonicalStringify } from "@apollo/client/cache/index.js"; import { invariant } from "ts-invariant"; import { createBackpressuredCallback } from "./backpressuredCallback.js"; import { InMemoryCache } from "./WrappedInMemoryCache.js"; +import { hookWrappers } from "./hooks.js"; +import type { HookWrappers } from "@apollo/client/react/internal/index.js"; function getQueryManager( client: OrigApolloClient -): QueryManager { +): QueryManager & { + [wrappers]: HookWrappers; +} { return client["queryManager"]; } @@ -29,12 +33,23 @@ type SimulatedQueryInfo = { options: WatchQueryOptions; }; -class ApolloClientSSRImpl extends OrigApolloClient { - watchQueryQueue = createBackpressuredCallback>(); - +const wrappers = Symbol.for("apollo.hook.wrappers"); +class ApolloClientBase extends OrigApolloClient { constructor(options: ApolloClientOptions) { super(options); + + if (!(this.cache instanceof InMemoryCache)) { + throw new Error( + "When using Apollo Client streaming SSR, you must use the `InMemoryCache` variant provided by the streaming package." + ); + } + + getQueryManager(this)[wrappers] = hookWrappers; } +} + +class ApolloClientSSRImpl extends ApolloClientBase { + watchQueryQueue = createBackpressuredCallback>(); watchQuery< T = any, @@ -52,17 +67,7 @@ class ApolloClientSSRImpl extends OrigApolloClient { export class ApolloClientBrowserImpl< TCacheShape, -> extends OrigApolloClient { - constructor(options: ApolloClientOptions) { - super(options); - - if (!(this.cache instanceof InMemoryCache)) { - throw new Error( - "When using Apollo Client streaming SSR, you must use the `InMemoryCache` variant provided by the streaming package." - ); - } - } - +> extends ApolloClientBase { private simulatedStreamingQueries = new Map(); private identifyUniqueQuery(options: { diff --git a/packages/client-react-streaming/src/DataTransportAbstraction/hooks.ts b/packages/client-react-streaming/src/DataTransportAbstraction/hooks.ts index 944ae956..c08b0fdc 100644 --- a/packages/client-react-streaming/src/DataTransportAbstraction/hooks.ts +++ b/packages/client-react-streaming/src/DataTransportAbstraction/hooks.ts @@ -1,32 +1,26 @@ -"use client"; -import { - useFragment as orig_useFragment, - useSuspenseQuery as orig_useSuspenseQuery, - useReadQuery as orig_useReadQuery, - useQuery as orig_useQuery, - useBackgroundQuery as orig_useBackgroundQuery, -} from "@apollo/client/index.js"; +import type { HookWrappers } from "@apollo/client/react/internal/index.js"; import { useTransportValue } from "./useTransportValue.js"; -export const useFragment = wrap(orig_useFragment, [ - "data", - "complete", - "missing", -]); -export const useQuery = wrap( - process.env.REACT_ENV === "ssr" - ? (query, options) => - orig_useQuery(query, { ...options, fetchPolicy: "cache-only" }) - : orig_useQuery, - ["data", "loading", "networkStatus", "called"] -); -export const useSuspenseQuery = wrap(orig_useSuspenseQuery, [ - "data", - "networkStatus", -]); -export const useReadQuery = wrap(orig_useReadQuery, ["data", "networkStatus"]); - -export const useBackgroundQuery = orig_useBackgroundQuery; +export const hookWrappers: HookWrappers = { + useFragment(orig_useFragment) { + return wrap(orig_useFragment, ["data", "complete", "missing"]); + }, + useQuery(orig_useQuery) { + return wrap( + process.env.REACT_ENV === "ssr" + ? (query, options) => + orig_useQuery(query, { ...options, fetchPolicy: "cache-only" }) + : orig_useQuery, + ["data", "loading", "networkStatus", "called"] + ); + }, + useSuspenseQuery(orig_useSuspenseQuery) { + return wrap(orig_useSuspenseQuery, ["data", "networkStatus"]); + }, + useReadQuery(orig_useReadQuery) { + return wrap(orig_useReadQuery, ["data", "networkStatus"]); + }, +}; function wrap any>( useFn: T, diff --git a/packages/client-react-streaming/src/DataTransportAbstraction/index.ts b/packages/client-react-streaming/src/DataTransportAbstraction/index.ts index 47dd2bcd..5dc26b3f 100644 --- a/packages/client-react-streaming/src/DataTransportAbstraction/index.ts +++ b/packages/client-react-streaming/src/DataTransportAbstraction/index.ts @@ -1,13 +1,6 @@ export { InMemoryCache } from "./WrappedInMemoryCache.js"; export { ApolloClient } from "./WrappedApolloClient.js"; -export { - useFragment, - useQuery, - useSuspenseQuery, - useReadQuery, - useBackgroundQuery, -} from "./hooks.js"; export { resetApolloSingletons } from "./testHelpers.js"; export { DataTransportContext } from "./DataTransportAbstraction.js"; diff --git a/packages/client-react-streaming/src/index.ts b/packages/client-react-streaming/src/index.ts index 57ea737c..9f33a755 100644 --- a/packages/client-react-streaming/src/index.ts +++ b/packages/client-react-streaming/src/index.ts @@ -1,10 +1,5 @@ export * from "./index.shared.js"; export { - useFragment, - useQuery, - useSuspenseQuery, - useReadQuery, - useBackgroundQuery, resetApolloSingletons, DataTransportContext, DataTransportProviderImplementation, diff --git a/packages/experimental-nextjs-app-support/src/ssr/index.ts b/packages/experimental-nextjs-app-support/src/ssr/index.ts index cde9e143..6a2dcecc 100644 --- a/packages/experimental-nextjs-app-support/src/ssr/index.ts +++ b/packages/experimental-nextjs-app-support/src/ssr/index.ts @@ -3,12 +3,14 @@ export { resetManualSSRApolloSingletons as resetNextSSRApolloSingletons } from " export { InMemoryCache as NextSSRInMemoryCache, ApolloClient as NextSSRApolloClient, + SSRMultipartLink, + DebounceMultipartResponsesLink, + RemoveMultipartDirectivesLink, +} from "@apollo/client-react-streaming"; +export { useBackgroundQuery, useFragment, useQuery, useReadQuery, useSuspenseQuery, - SSRMultipartLink, - DebounceMultipartResponsesLink, - RemoveMultipartDirectivesLink, -} from "@apollo/client-react-streaming"; +} from "@apollo/client/index.js"; diff --git a/yarn.lock b/yarn.lock index 073e0f89..479a5bac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -83,9 +83,9 @@ __metadata: languageName: unknown linkType: soft -"@apollo/client@npm:3.9.1, @apollo/client@npm:^3.9.1": - version: 3.9.1 - resolution: "@apollo/client@npm:3.9.1" +"@apollo/client@npm:0.0.0-pr-11617-20240227102358": + version: 0.0.0-pr-11617-20240227102358 + resolution: "@apollo/client@npm:0.0.0-pr-11617-20240227102358" dependencies: "@graphql-typed-document-node/core": "npm:^3.1.1" "@wry/caches": "npm:^1.0.0" @@ -95,7 +95,7 @@ __metadata: hoist-non-react-statics: "npm:^3.3.2" optimism: "npm:^0.18.0" prop-types: "npm:^15.7.2" - rehackt: "npm:0.0.3" + rehackt: "npm:0.0.5" response-iterator: "npm:^0.2.6" symbol-observable: "npm:^4.0.0" ts-invariant: "npm:^0.10.3" @@ -116,7 +116,7 @@ __metadata: optional: true subscriptions-transport-ws: optional: true - checksum: 10/76c7024fbb7bd4a8e4cb1a36193c0ca23340b78e3a47006af6d91bd250b91006d8334856ac91c235d040bcaaadc0ef6d6eb0bba6a4f48b7fa12174f029e9eee1 + checksum: 10/0189a24d832ef14a408a0a3dd5e15d416ed4c223aa9aca21c80774beb60ff7303242242f688c684a20df0166ba95f4c92708dfe7badc0ab12dabe63232616483 languageName: node linkType: hard @@ -12272,9 +12272,9 @@ __metadata: languageName: node linkType: hard -"rehackt@npm:0.0.3": - version: 0.0.3 - resolution: "rehackt@npm:0.0.3" +"rehackt@npm:0.0.5": + version: 0.0.5 + resolution: "rehackt@npm:0.0.5" peerDependencies: "@types/react": "*" react: "*" @@ -12283,7 +12283,7 @@ __metadata: optional: true react: optional: true - checksum: 10/2e3674d84aca46802f38cf5b01c62bf7d0ca5e324b2749dd79156fa61723e9f84df7778c39f97d351e815a852a8a8ac9633e29b6b7b8734d18042fda86620f54 + checksum: 10/a7536eaeb47ba46bc29fa050c7cbf80860809689c893c6177664efbbdca641a1996185ea6602fb76473f0e2b145c1f3e9c27a5e738054d69809b6c39046fe269 languageName: node linkType: hard From 80c455f0e7776faf922f2c1fb1d3b0d48882408e Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Tue, 5 Mar 2024 11:05:56 +0100 Subject: [PATCH 3/4] 0.9.0 (3) - add integration test with experimentally patched React version (#219) * rename wrapped classes to original name, also export in RSC * fix up test * fixup build types * comment shape * adjust type resolution * fixup * adjust package shapes * bundling adjustment: no `/index.js` in CJS * check "package shapes" * revert changes to examples/integration tests * reset README changes * add description to helper scripts * wrap hook functionality from ApolloClient instance * move wrappers to `QueryManager` * update version * remove hook exports from `@apollo/client-react-streaming` * run test-bundle in CI * drop hook exports * move "integration-test" into "integration-test/nextjs" * initialize basic vite react-ssr project * apply prettier * [WIP] try to set up a streaming server * fix hydration, force faster flush * experimental-react demo working * add delay * also works with prod build * update lockfile * global react version * simplify a bit * move experimental detail out to `WrappedApolloProvider` * adjustments * remove `@apollo/client-react-streaming/experimental-react-transport` * alternative installation * alternative approach * fix non-failing tests * fix bug that prevented rerunning hydrated queries if initialized too late * adjust script * more packaging foo * fixup app rendering * playwright test * split up * remove some logs * update lockfile --- .github/workflows/tests.yml | 22 +- integration-test/.gitignore | 24 + .../experimental-react/index.html | 14 + .../experimental-react/package.json | 37 + .../experimental-react/playwright.config.ts | 21 + integration-test/experimental-react/server.js | 95 ++ .../experimental-react/src/App.css | 81 + .../experimental-react/src/App.tsx | 90 ++ .../experimental-react/src/Html.jsx | 47 + .../src/WrappedApolloProvider.tsx | 19 +- .../experimental-react/src/entry-client.jsx | 15 + .../experimental-react/src/entry-server.jsx | 15 + .../experimental-react/src/hydration.test.ts | 19 + .../src}/schema.ts | 0 .../experimental-react/tsconfig.json | 6 + .../experimental-react/vite.config.js | 7 + integration-test/{ => nextjs}/.env.local | 0 integration-test/{ => nextjs}/empty.har | 0 integration-test/{ => nextjs}/fixture.ts | 0 integration-test/{ => nextjs}/next-env.d.ts | 0 integration-test/{ => nextjs}/next.config.js | 0 integration-test/{ => nextjs}/package.json | 2 +- .../{ => nextjs}/playwright.config.ts | 0 .../{ => nextjs}/src/app/cc/ApolloWrapper.tsx | 0 .../src/app/cc/dynamic/dynamic.test.ts | 0 .../src/app/cc/dynamic/layout.tsx | 0 .../cc/dynamic/useBackgroundQuery/page.tsx | 0 .../page.tsx | 0 .../src/app/cc/dynamic/useQuery/page.tsx | 0 .../app/cc/dynamic/useQueryWithCache/page.tsx | 0 .../app/cc/dynamic/useSuspenseQuery/page.tsx | 0 .../{ => nextjs}/src/app/cc/static/layout.tsx | 0 .../src/app/cc/static/static.test.ts | 0 .../page.tsx | 0 .../app/cc/static/useSuspenseQuery/page.tsx | 0 .../{ => nextjs}/src/app/graphql/route.ts | 0 .../nextjs/src/app/graphql/schema.ts | 48 + .../{ => nextjs}/src/app/layout.tsx | 0 .../{ => nextjs}/src/app/rsc/client.ts | 0 .../src/app/rsc/dynamic/apollo-client.test.ts | 0 .../src/app/rsc/dynamic/query/page.tsx | 0 .../src/app/rsc/static/apollo-client.test.ts | 0 .../src/app/rsc/static/query/page.tsx | 0 integration-test/{ => nextjs}/src/global.d.ts | 0 .../{ => nextjs}/src/middleware.ts | 0 .../{ => nextjs}/src/shared/delayLink.ts | 0 integration-test/{ => nextjs}/tsconfig.json | 0 package.json | 7 +- packages/client-react-streaming/package.json | 18 - .../ManualDataTransport.tsx | 2 + .../src/ExperimentalReact/index.ts | 1 - .../client-react-streaming/tsup.config.ts | 10 - .../package.json | 1 + yarn.lock | 1341 +++++++---------- 54 files changed, 1078 insertions(+), 864 deletions(-) create mode 100644 integration-test/.gitignore create mode 100644 integration-test/experimental-react/index.html create mode 100644 integration-test/experimental-react/package.json create mode 100644 integration-test/experimental-react/playwright.config.ts create mode 100644 integration-test/experimental-react/server.js create mode 100644 integration-test/experimental-react/src/App.css create mode 100644 integration-test/experimental-react/src/App.tsx create mode 100644 integration-test/experimental-react/src/Html.jsx rename packages/client-react-streaming/src/ExperimentalReact/ExperimentalReactDataTransport.tsx => integration-test/experimental-react/src/WrappedApolloProvider.tsx (82%) create mode 100644 integration-test/experimental-react/src/entry-client.jsx create mode 100644 integration-test/experimental-react/src/entry-server.jsx create mode 100644 integration-test/experimental-react/src/hydration.test.ts rename integration-test/{src/app/graphql => experimental-react/src}/schema.ts (100%) create mode 100644 integration-test/experimental-react/tsconfig.json create mode 100644 integration-test/experimental-react/vite.config.js rename integration-test/{ => nextjs}/.env.local (100%) rename integration-test/{ => nextjs}/empty.har (100%) rename integration-test/{ => nextjs}/fixture.ts (100%) rename integration-test/{ => nextjs}/next-env.d.ts (100%) rename integration-test/{ => nextjs}/next.config.js (100%) rename integration-test/{ => nextjs}/package.json (95%) rename integration-test/{ => nextjs}/playwright.config.ts (100%) rename integration-test/{ => nextjs}/src/app/cc/ApolloWrapper.tsx (100%) rename integration-test/{ => nextjs}/src/app/cc/dynamic/dynamic.test.ts (100%) rename integration-test/{ => nextjs}/src/app/cc/dynamic/layout.tsx (100%) rename integration-test/{ => nextjs}/src/app/cc/dynamic/useBackgroundQuery/page.tsx (100%) rename integration-test/{ => nextjs}/src/app/cc/dynamic/useBackgroundQueryWithoutSsrReadQuery/page.tsx (100%) rename integration-test/{ => nextjs}/src/app/cc/dynamic/useQuery/page.tsx (100%) rename integration-test/{ => nextjs}/src/app/cc/dynamic/useQueryWithCache/page.tsx (100%) rename integration-test/{ => nextjs}/src/app/cc/dynamic/useSuspenseQuery/page.tsx (100%) rename integration-test/{ => nextjs}/src/app/cc/static/layout.tsx (100%) rename integration-test/{ => nextjs}/src/app/cc/static/static.test.ts (100%) rename integration-test/{ => nextjs}/src/app/cc/static/useBackgroundQueryWithoutSsrReadQuery/page.tsx (100%) rename integration-test/{ => nextjs}/src/app/cc/static/useSuspenseQuery/page.tsx (100%) rename integration-test/{ => nextjs}/src/app/graphql/route.ts (100%) create mode 100644 integration-test/nextjs/src/app/graphql/schema.ts rename integration-test/{ => nextjs}/src/app/layout.tsx (100%) rename integration-test/{ => nextjs}/src/app/rsc/client.ts (100%) rename integration-test/{ => nextjs}/src/app/rsc/dynamic/apollo-client.test.ts (100%) rename integration-test/{ => nextjs}/src/app/rsc/dynamic/query/page.tsx (100%) rename integration-test/{ => nextjs}/src/app/rsc/static/apollo-client.test.ts (100%) rename integration-test/{ => nextjs}/src/app/rsc/static/query/page.tsx (100%) rename integration-test/{ => nextjs}/src/global.d.ts (100%) rename integration-test/{ => nextjs}/src/middleware.ts (100%) rename integration-test/{ => nextjs}/src/shared/delayLink.ts (100%) rename integration-test/{ => nextjs}/tsconfig.json (100%) delete mode 100644 packages/client-react-streaming/src/ExperimentalReact/index.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b287875f..c444dcec 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,7 +30,7 @@ jobs: - run: yarn install --immutable - run: yarn workspaces foreach --all --include "@apollo/*" add -D -P @apollo/client@${{ matrix.version }} - run: yarn workspaces foreach --all --include "@apollo/*" run build - - run: yarn workspaces foreach --all --include "@apollo/*" run test | tee $GITHUB_STEP_SUMMARY + - run: yarn workspaces foreach --all --include "@apollo/*" run test | tee $GITHUB_STEP_SUMMARY; exit ${PIPESTATUS[0]} packageShapes: name: Test bundles if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name @@ -47,7 +47,7 @@ jobs: cache: "yarn" - run: yarn install --immutable - run: yarn workspaces foreach --all --include "@apollo/*" add -D -P @apollo/client@${{ matrix.version }} - - run: yarn workspaces foreach --all --include "@apollo/*" run test-bundle | tee $GITHUB_STEP_SUMMARY + - run: yarn workspaces foreach --all --include "@apollo/*" run test-bundle | tee $GITHUB_STEP_SUMMARY; exit ${PIPESTATUS[0]} tests_playwright: name: Run Playwright tests if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name @@ -80,9 +80,17 @@ jobs: if: steps.playwright-cache.outputs.cache-hit == 'true' - run: yarn workspaces foreach --all --include "@apollo/*" add -D -P @apollo/client@${{ matrix.version }} - - run: yarn workspace integration-test add @apollo/client@${{ matrix.version }} + - run: yarn workspaces foreach --all --include "@integration-test/*" add @apollo/client@${{ matrix.version }} - - name: Build - run: yarn workspaces foreach --all -t --include "@apollo/*" --include integration-test run build - - name: Run tests - run: yarn workspace integration-test run test | tee $GITHUB_STEP_SUMMARY + - name: Build libraries + run: yarn workspaces foreach --all -t --include "@apollo/*" --include @integration-test/nextjs run build + + - name: "Next.js: Build" + run: yarn workspace @integration-test/nextjs run build + - name: "Next.js: Test" + run: yarn workspace @integration-test/nextjs run test | tee $GITHUB_STEP_SUMMARY; exit ${PIPESTATUS[0]} + + - name: "Experimentally patched React: Build" + run: yarn workspace @integration-test/experimental-react run build + - name: "Experimentally patched React: Test" + run: yarn workspace @integration-test/experimental-react run test | tee $GITHUB_STEP_SUMMARY; exit ${PIPESTATUS[0]} diff --git a/integration-test/.gitignore b/integration-test/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/integration-test/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/integration-test/experimental-react/index.html b/integration-test/experimental-react/index.html new file mode 100644 index 00000000..bcd08531 --- /dev/null +++ b/integration-test/experimental-react/index.html @@ -0,0 +1,14 @@ + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/integration-test/experimental-react/package.json b/integration-test/experimental-react/package.json new file mode 100644 index 00000000..fdaa3247 --- /dev/null +++ b/integration-test/experimental-react/package.json @@ -0,0 +1,37 @@ +{ + "name": "@integration-test/experimental-react", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "prepare": "rm -rf node_modules/@apollo/client-react-streaming; mkdir node_modules/@apollo/client-react-streaming && cp -r ../../packages/client-react-streaming/{package.json,dist} node_modules/@apollo/client-react-streaming", + "dev": "yarn prepare; node server", + "build": "yarn prepare; npm run build:client && npm run build:server", + "build:client": "vite build --ssrManifest --outDir dist/client", + "build:server": "vite build --ssr src/entry-server.jsx --outDir dist/server", + "preview": "cross-env NODE_ENV=production node server", + "build-and-test": "yarn build && yarn test", + "test": "yarn playwright test" + }, + "dependencies": { + "@apollo/client": "^3.9.1", + "@apollo/client-react-streaming": "workspace:*", + "compression": "^1.7.4", + "express": "^4.18.2", + "graphql": "^16.8.1", + "graphql-tag": "^2.12.6", + "react": "npm:@phryneas/experimental-react@0.0.0-phryneas-a1c625c", + "react-dom": "npm:@phryneas/experimental-react-dom@0.0.0-phryneas-a1c625c", + "sirv": "^2.0.4" + }, + "devDependencies": { + "@playwright/test": "^1.39.0", + "@tsconfig/vite-react": "^3.0.0", + "@types/react": "^18.2.55", + "@types/react-dom": "^18.2.18", + "@vitejs/plugin-react": "^4.2.1", + "cross-env": "^7.0.3", + "prettier": "^3.2.5", + "vite": "^5.0.10" + } +} diff --git a/integration-test/experimental-react/playwright.config.ts b/integration-test/experimental-react/playwright.config.ts new file mode 100644 index 00000000..1a3d3e47 --- /dev/null +++ b/integration-test/experimental-react/playwright.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + webServer: { + command: "node server", + port: 3000, + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI, + env: { + PORT: 3000, + NODE_ENV: "production", + }, + }, + timeout: 120 * 1000, + use: { + headless: true, + viewport: { width: 1280, height: 720 }, + ignoreHTTPSErrors: true, + }, + testDir: "src/", +}); diff --git a/integration-test/experimental-react/server.js b/integration-test/experimental-react/server.js new file mode 100644 index 00000000..fa05c0a2 --- /dev/null +++ b/integration-test/experimental-react/server.js @@ -0,0 +1,95 @@ +import express from "express"; +import { renderToPipeableStream } from "react-dom/server"; +import { readFile } from "node:fs/promises"; + +// Constants +const isProduction = process.env.NODE_ENV === "production"; +const port = process.env.PORT || 5173; +const base = process.env.BASE || "/"; +const ABORT_DELAY = 10000; + +// Create http server +const app = express(); + +// Add Vite or respective production middlewares +let vite; +let bootstrapModules = []; +let assets = []; +if (!isProduction) { + const { createServer } = await import("vite"); + vite = await createServer({ + server: { middlewareMode: true, hmr: true }, + appType: "custom", + base, + }); + app.use(vite.middlewares); +} else { + const compression = (await import("compression")).default; + const sirv = (await import("sirv")).default; + app.use(compression()); + app.use(base, sirv("./dist/client", { extensions: [] })); + const index = await readFile("./dist/client/index.html", "utf-8"); + for (const script of index.matchAll( + / + + + )} + + + + + \ No newline at end of file diff --git a/integration-test/vite-streaming/package.json b/integration-test/vite-streaming/package.json new file mode 100644 index 00000000..a9cd8bd1 --- /dev/null +++ b/integration-test/vite-streaming/package.json @@ -0,0 +1,37 @@ +{ + "name": "@integration-test/vite-streaming", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "prepare": "rm -rf node_modules/@apollo/client-react-streaming; mkdir node_modules/@apollo/client-react-streaming && cp -r ../../packages/client-react-streaming/{package.json,dist} node_modules/@apollo/client-react-streaming", + "dev": "yarn prepare; node server", + "build": "yarn prepare; npm run build:client && npm run build:server", + "build:client": "vite build --ssrManifest --outDir dist/client", + "build:server": "vite build --ssr src/entry-server.jsx --outDir dist/server", + "preview": "cross-env NODE_ENV=production node server", + "build-and-test": "yarn build && yarn test", + "test": "yarn playwright test" + }, + "dependencies": { + "@apollo/client": "^3.9.1", + "@apollo/client-react-streaming": "workspace:*", + "compression": "^1.7.4", + "express": "^4.18.2", + "graphql": "^16.8.1", + "graphql-tag": "^2.12.6", + "react": "18.3.0-canary-60a927d04-20240113", + "react-dom": "18.3.0-canary-60a927d04-20240113", + "sirv": "^2.0.4" + }, + "devDependencies": { + "@playwright/test": "^1.39.0", + "@tsconfig/vite-react": "^3.0.0", + "@types/react": "^18.2.55", + "@types/react-dom": "^18.2.18", + "@vitejs/plugin-react": "^4.2.1", + "cross-env": "^7.0.3", + "prettier": "^3.2.5", + "vite": "^5.0.10" + } +} diff --git a/integration-test/vite-streaming/playwright.config.ts b/integration-test/vite-streaming/playwright.config.ts new file mode 100644 index 00000000..1a3d3e47 --- /dev/null +++ b/integration-test/vite-streaming/playwright.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + webServer: { + command: "node server", + port: 3000, + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI, + env: { + PORT: 3000, + NODE_ENV: "production", + }, + }, + timeout: 120 * 1000, + use: { + headless: true, + viewport: { width: 1280, height: 720 }, + ignoreHTTPSErrors: true, + }, + testDir: "src/", +}); diff --git a/integration-test/vite-streaming/server.js b/integration-test/vite-streaming/server.js new file mode 100644 index 00000000..ef7b81b7 --- /dev/null +++ b/integration-test/vite-streaming/server.js @@ -0,0 +1,101 @@ +import express from "express"; +import { renderToReadableStream } from "react-dom/server.edge"; +import { readFile } from "node:fs/promises"; + +// Constants +const isProduction = process.env.NODE_ENV === "production"; +const port = process.env.PORT || 5173; +const base = process.env.BASE || "/"; + +// Create http server +const app = express(); + +// Add Vite or respective production middlewares +let vite; +let bootstrapModules = []; +let assets = []; +if (!isProduction) { + const { createServer } = await import("vite"); + vite = await createServer({ + server: { middlewareMode: true, hmr: true }, + appType: "custom", + base, + }); + app.use(vite.middlewares); +} else { + const compression = (await import("compression")).default; + const sirv = (await import("sirv")).default; + app.use(compression()); + app.use(base, sirv("./dist/client", { extensions: [] })); + const index = await readFile("./dist/client/index.html", "utf-8"); + for (const script of index.matchAll( + / + + + )} + +