Skip to content

Commit

Permalink
feat: bring back streaming mode with docs updates (#656)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajwootto authored Dec 20, 2023
1 parent 28eb0be commit 46bfceb
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 94 deletions.
22 changes: 11 additions & 11 deletions e2e/nextjs/app-router/app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ __metadata:

"@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs::locator=app%40workspace%3A.":
version: 0.0.4
resolution: "@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs#../../../../dist/sdk/nextjs::hash=919019&locator=app%40workspace%3A."
resolution: "@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs#../../../../dist/sdk/nextjs::hash=11f915&locator=app%40workspace%3A."
dependencies:
"@devcycle/bucketing": "npm:^1.7.4"
"@devcycle/js-client-sdk": "npm:^1.16.3"
Expand All @@ -53,7 +53,7 @@ __metadata:
peerDependencies:
next: ^14.0.0
react: ^18.2.0
checksum: 07fc055921546c529ade5f2717b660a60eb975d90553108ad5d42efd464e4f10a4e8acb12f268b17b613496b5d74b592b6f78e9a0eb3f1b95afda8dc841986f8
checksum: 3b1947b28c1cbf2ac2eded4d30d48312a4b33e8d606b0cc3c13431ddf0f5bdbc608248ce9ed4ee0cb9a188e61563f325d9c003903d2ea4038bb194a18c5e2dca
languageName: node
linkType: hard

Expand Down Expand Up @@ -173,11 +173,11 @@ __metadata:
linkType: hard

"@types/node@npm:^20":
version: 20.10.4
resolution: "@types/node@npm:20.10.4"
version: 20.10.5
resolution: "@types/node@npm:20.10.5"
dependencies:
undici-types: "npm:~5.26.4"
checksum: c10c1dd13f5c2341ad866777dc32946538a99e1ebd203ae127730814b8e5fa4aedfbcb01cb3e24a5466f1af64bcdfa16e7de6e745ff098fff0942aa779b7fe03
checksum: 4a378428d2c9f692b19801a5a3d20dc4c0ad5d4a3d103350f8b401af439941a9aa5efeadc8eb9db13c66c620318bc7f336abfc8934f82fd32c4a689d85068c6f
languageName: node
linkType: hard

Expand Down Expand Up @@ -377,9 +377,9 @@ __metadata:
linkType: hard

"libphonenumber-js@npm:^1.9.43":
version: 1.10.51
resolution: "libphonenumber-js@npm:1.10.51"
checksum: 925fda2ecba5d1e655f9d6e43cd15b2d41f7fe9db38828f4159442bc263ae959a26f212223336831c6f64e58b82e91477f7ac814d66b48c80e5852529545032b
version: 1.10.52
resolution: "libphonenumber-js@npm:1.10.52"
checksum: d8a41623e1144b07482c01156316e35708fd9b371a2a72086d25213f2dee1755ff2abf660dfe9df3c38500689868dc2fb7e462ca23207b0b8ca95daad9c5bd74
languageName: node
linkType: hard

Expand Down Expand Up @@ -550,9 +550,9 @@ __metadata:
linkType: hard

"regenerator-runtime@npm:^0.14.0":
version: 0.14.0
resolution: "regenerator-runtime@npm:0.14.0"
checksum: 6c19495baefcf5fbb18a281b56a97f0197b5f219f42e571e80877f095320afac0bdb31dab8f8186858e6126950068c3f17a1226437881e3e70446ea66751897c
version: 0.14.1
resolution: "regenerator-runtime@npm:0.14.1"
checksum: 5db3161abb311eef8c45bcf6565f4f378f785900ed3945acf740a9888c792f75b98ecb77f0775f3bf95502ff423529d23e94f41d80c8256e8fa05ed4b07cf471
languageName: node
linkType: hard

Expand Down
2 changes: 0 additions & 2 deletions e2e/nextjs/app-router/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ export default defineConfig({
baseURL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
video: 'retain-on-failure',
},

/* Run your local dev server before starting the tests */
webServer: {
command: 'yarn e2e:nextjs-app-router:start',
Expand Down
22 changes: 11 additions & 11 deletions e2e/nextjs/pages-router/app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ __metadata:

"@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs::locator=app%40workspace%3A.":
version: 0.0.4
resolution: "@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs#../../../../dist/sdk/nextjs::hash=7c9d07&locator=app%40workspace%3A."
resolution: "@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs#../../../../dist/sdk/nextjs::hash=11f915&locator=app%40workspace%3A."
dependencies:
"@devcycle/bucketing": "npm:^1.7.4"
"@devcycle/js-client-sdk": "npm:^1.16.3"
Expand All @@ -53,7 +53,7 @@ __metadata:
peerDependencies:
next: ^14.0.0
react: ^18.2.0
checksum: 0913c9cbf1cfe9263b7d9367f001ac3526378340a1943d917177af122abb9b1d796b1c9947811c5e7aae11556e9ba06e6eb239d8fd84318718584b6fa69af253
checksum: 3b1947b28c1cbf2ac2eded4d30d48312a4b33e8d606b0cc3c13431ddf0f5bdbc608248ce9ed4ee0cb9a188e61563f325d9c003903d2ea4038bb194a18c5e2dca
languageName: node
linkType: hard

Expand Down Expand Up @@ -173,11 +173,11 @@ __metadata:
linkType: hard

"@types/node@npm:^20":
version: 20.10.4
resolution: "@types/node@npm:20.10.4"
version: 20.10.5
resolution: "@types/node@npm:20.10.5"
dependencies:
undici-types: "npm:~5.26.4"
checksum: c10c1dd13f5c2341ad866777dc32946538a99e1ebd203ae127730814b8e5fa4aedfbcb01cb3e24a5466f1af64bcdfa16e7de6e745ff098fff0942aa779b7fe03
checksum: 4a378428d2c9f692b19801a5a3d20dc4c0ad5d4a3d103350f8b401af439941a9aa5efeadc8eb9db13c66c620318bc7f336abfc8934f82fd32c4a689d85068c6f
languageName: node
linkType: hard

Expand Down Expand Up @@ -377,9 +377,9 @@ __metadata:
linkType: hard

"libphonenumber-js@npm:^1.9.43":
version: 1.10.51
resolution: "libphonenumber-js@npm:1.10.51"
checksum: 925fda2ecba5d1e655f9d6e43cd15b2d41f7fe9db38828f4159442bc263ae959a26f212223336831c6f64e58b82e91477f7ac814d66b48c80e5852529545032b
version: 1.10.52
resolution: "libphonenumber-js@npm:1.10.52"
checksum: d8a41623e1144b07482c01156316e35708fd9b371a2a72086d25213f2dee1755ff2abf660dfe9df3c38500689868dc2fb7e462ca23207b0b8ca95daad9c5bd74
languageName: node
linkType: hard

Expand Down Expand Up @@ -550,9 +550,9 @@ __metadata:
linkType: hard

"regenerator-runtime@npm:^0.14.0":
version: 0.14.0
resolution: "regenerator-runtime@npm:0.14.0"
checksum: 6c19495baefcf5fbb18a281b56a97f0197b5f219f42e571e80877f095320afac0bdb31dab8f8186858e6126950068c3f17a1226437881e3e70446ea66751897c
version: 0.14.1
resolution: "regenerator-runtime@npm:0.14.1"
checksum: 5db3161abb311eef8c45bcf6565f4f378f785900ed3945acf740a9888c792f75b98ecb77f0775f3bf95502ff423529d23e94f41d80c8256e8fa05ed4b07cf471
languageName: node
linkType: hard

Expand Down
10 changes: 4 additions & 6 deletions examples/nextjs/app-router/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ export default async function RootLayout({
user_id:
process.env.NEXT_PUBLIC_USER_ID || 'server-user',
}}
options={
{
// enableStreaming:
// process.env.NEXT_PUBLIC_ENABLE_STREAMING === '1',
}
}
options={{
enableStreaming:
process.env.NEXT_PUBLIC_ENABLE_STREAMING === '1',
}}
>
{children}
</DevCycleServersideProvider>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"murmurhash": "^2.0.0",
"next": "14.0.0",
"protobufjs": "^7.2.5",
"react": "^18.2.0",
"react": "18.2.0",
"react-bootstrap": "^2.2.1",
"react-dom": "18.2.0",
"react-is": "18.2.0",
Expand Down
17 changes: 6 additions & 11 deletions sdk/nextjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ Official SDK for integrating DevCycle feature flags with your Next.js applicatio
- keep server and client rendered content in sync with the same flag values
- keep user data for targeting rule evaluation private on the server
- realtime updates to flag values for both server and client components
- support for non-blocking flag state retrieval and streaming (not implemented, see below)

## Requirements
- Minimum Next.js version: 14.0.0
- Minimum React version: 18.2
- support for non-blocking flag state retrieval and streaming

## Limitations
- Minimum Next.js version: 14.0.0
- Minimum React version: 18.3 (currently only available in Canary and Experimental releases). This version is required
because the SDK relies on the new [React Cache API](https://react.dev/reference/react/cache)
in order to share context across Server Components during rendering.
- variable evaluations are only tracked in client components in App Router.

## Installation
Expand Down Expand Up @@ -115,12 +115,7 @@ Currently, tracking events in server components is not supported. Please trigger
from client components.
## Advanced
### Non-Blocking Initialization (Not Implemented)
**Note**: This feature is not yet implemented because it relies on the new React `use` function, which is only
available in canary builds currently. When `use` is released in a stable React version, the SDK will be updated to
include this feature. The documentation for the feature as it will exist continues below:
### Non-Blocking Initialization
If you wish to render your page without waiting for the DevCycle configuration to be retrieved, you can use the
`enableStreaming` option. Doing so enables the following behaviour:
- the DevCycleServersideProvider will not block rendering of the rest of the server component tree
Expand Down
76 changes: 37 additions & 39 deletions sdk/nextjs/src/client/DevCycleClientsideProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client'
import React, { useRef } from 'react'
import React, { Suspense, use, useContext, useRef, useState } from 'react'
import {
DevCycleClient,
DevCycleUser,
Expand Down Expand Up @@ -29,36 +29,35 @@ export const DevCycleClientContext = React.createContext<ClientProviderContext>(
{} as ClientProviderContext,
)

// /**
// * Component which renders nothing, but runs code to keep client state in sync with server
// * Also waits for the server's data promise with the `use` hook. This triggers the nearest suspense boundary,
// * so this component is being rendered inside of a Suspense by the DevCycleClientsideProvider.
// * @param serverDataPromise
// * @constructor
// */
// TODO - re-add when React 18.3 is released with a stable "use" function
// export const SuspendedProviderInitialization = ({
// serverDataPromise,
// }: Pick<
// DevCycleClientsideProviderProps,
// 'serverDataPromise'
// >): React.ReactElement => {
// const serverData = use(serverDataPromise)
// const [previousContext, setPreviousContext] = useState<
// DevCycleServerDataForClient | undefined
// >()
// const context = useContext(DevCycleClientContext)
// if (previousContext !== serverData) {
// // change user and config data to match latest server data
// // if the data has changed since the last invocation
// context.client.synchronizeBootstrapData(
// serverData.config,
// serverData.user,
// )
// setPreviousContext(serverData)
// }
// return <></>
// }
/**
* Component which renders nothing, but runs code to keep client state in sync with server
* Also waits for the server's data promise with the `use` hook. This triggers the nearest suspense boundary,
* so this component is being rendered inside of a Suspense by the DevCycleClientsideProvider.
* @param serverDataPromise
* @constructor
*/
export const SuspendedProviderInitialization = ({
serverDataPromise,
}: Pick<
DevCycleClientsideProviderProps,
'serverDataPromise'
>): React.ReactElement => {
const serverData = use(serverDataPromise)
const [previousContext, setPreviousContext] = useState<
DevCycleServerDataForClient | undefined
>()
const context = useContext(DevCycleClientContext)
if (previousContext !== serverData) {
// change user and config data to match latest server data
// if the data has changed since the last invocation
context.client.synchronizeBootstrapData(
serverData.config,
serverData.user,
)
setPreviousContext(serverData)
}
return <></>
}

export const DevCycleClientsideProvider = ({
serverDataPromise,
Expand Down Expand Up @@ -97,14 +96,13 @@ export const DevCycleClientsideProvider = ({
serverDataPromise,
}}
>
{/* TODO - re-add when React 18.3 is released with a stable "use" function */}
{/*{enableStreaming && (*/}
{/* <Suspense>*/}
{/* <SuspendedProviderInitialization*/}
{/* serverDataPromise={serverDataPromise}*/}
{/* />*/}
{/* </Suspense>*/}
{/*)}*/}
{enableStreaming && (
<Suspense>
<SuspendedProviderInitialization
serverDataPromise={serverDataPromise}
/>
</Suspense>
)}
{children}
</DevCycleClientContext.Provider>
)
Expand Down
9 changes: 4 additions & 5 deletions sdk/nextjs/src/client/useVariableValue.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'
import type { DVCVariableValue } from '@devcycle/js-client-sdk'
import { useCallback, useContext, useEffect, useState } from 'react'
import { useCallback, useContext, useEffect, useState, use } from 'react'
import { VariableTypeAlias } from '@devcycle/types'
import { DVCVariable } from '@devcycle/js-client-sdk'
import { DevCycleClientContext } from './DevCycleClientsideProvider'
Expand All @@ -13,11 +13,10 @@ export const useVariable = <T extends DVCVariableValue>(
const forceRerenderCallback = useCallback(() => forceRerender({}), [])
const context = useContext(DevCycleClientContext)

// TODO - re-add when React 18.3 is released with a stable "use" function
// Fall back to nearest suspense boundary if client is not initialized yet.
// if (context.enableStreaming) {
// use(context.serverDataPromise)
// }
if (context.enableStreaming) {
use(context.serverDataPromise)
}

useEffect(() => {
context.client.subscribe(
Expand Down
6 changes: 2 additions & 4 deletions sdk/nextjs/src/server/DevCycleServersideProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,11 @@ export const DevCycleServersideProvider = async ({
<DevCycleClientsideProvider
serverDataPromise={serverDataPromise}
serverData={
// options?.enableStreaming ? undefined : await serverDataPromise
await serverDataPromise
options?.enableStreaming ? undefined : await serverDataPromise
}
user={identifiedUser}
sdkKey={getSDKKey()}
// enableStreaming={options?.enableStreaming ?? false}
enableStreaming={false}
enableStreaming={options?.enableStreaming ?? false}
>
{children}
</DevCycleClientsideProvider>
Expand Down
3 changes: 1 addition & 2 deletions sdk/nextjs/src/server/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ export type DevCycleNextOptions = DevCycleOptions & {
* When this is enabled, client components will initially render using default variable values,
* and will re-render when the configuration is ready.
*/
// TODO - re-add when React 18.3 is released with a stable "use" function
// enableStreaming?: boolean
enableStreaming?: boolean

/**
* Used to disable any SDK features that require dynamic request context. This allows the SDK to be used in pages
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15964,7 +15964,7 @@ __metadata:
prettier-eslint-cli: ~5.0.1
protobufjs: ^7.2.5
protobufjs-cli: ^1.1.2
react: ^18.2.0
react: 18.2.0
react-bootstrap: ^2.2.1
react-dom: 18.2.0
react-is: 18.2.0
Expand Down Expand Up @@ -28860,7 +28860,7 @@ __metadata:
languageName: node
linkType: hard

"react@npm:*, react@npm:^18.2.0":
"react@npm:*, react@npm:18.2.0":
version: 18.2.0
resolution: "react@npm:18.2.0"
dependencies:
Expand Down

2 comments on commit 46bfceb

@vercel
Copy link

@vercel vercel bot commented on 46bfceb Dec 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 46bfceb Dec 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.