From 57ab969c41448159fd123d3feb21dde91ba82d2f Mon Sep 17 00:00:00 2001 From: Adam Wootton Date: Thu, 25 Jan 2024 09:42:38 -0500 Subject: [PATCH] feat: add IfEnabled helper (#704) --- .../app/app/normal/ClientComponent.tsx | 7 + .../app/normal/ConditionalClientComponent.tsx | 9 ++ .../app-router/app/app/normal/devcycle.ts | 36 ++--- e2e/nextjs/app-router/app/app/normal/page.tsx | 2 +- e2e/nextjs/app-router/app/package.json | 2 +- e2e/nextjs/app-router/app/yarn.lock | 139 ++++++++---------- .../app-router/tests/app-router.spec.ts | 4 + sdk/nextjs/index.ts | 15 +- sdk/nextjs/src/client/renderIfEnabled.tsx | 18 +++ .../{setupDevCycle.ts => setupDevCycle.tsx} | 0 10 files changed, 130 insertions(+), 102 deletions(-) create mode 100644 e2e/nextjs/app-router/app/app/normal/ConditionalClientComponent.tsx create mode 100644 sdk/nextjs/src/client/renderIfEnabled.tsx rename sdk/nextjs/src/server/{setupDevCycle.ts => setupDevCycle.tsx} (100%) diff --git a/e2e/nextjs/app-router/app/app/normal/ClientComponent.tsx b/e2e/nextjs/app-router/app/app/normal/ClientComponent.tsx index 57e5ec6bd..2c8665bd9 100644 --- a/e2e/nextjs/app-router/app/app/normal/ClientComponent.tsx +++ b/e2e/nextjs/app-router/app/app/normal/ClientComponent.tsx @@ -3,8 +3,14 @@ import { useVariableValue, useAllVariables, useAllFeatures, + renderIfEnabled, } from '@devcycle/nextjs-sdk' +const ConditionalComponent = renderIfEnabled( + 'enabled-feature', + () => import('./ConditionalClientComponent'), +) + export const ClientComponent = () => { const enabledVar = useVariableValue('enabled-feature', false) const disabledVar = useVariableValue('disabled-feature', false) @@ -18,6 +24,7 @@ export const ClientComponent = () => {

Client Disabled Variable: {JSON.stringify(disabledVar)}

Client All Variables: {JSON.stringify(allVariables)}

Client All Features: {JSON.stringify(allFeatures)}

+ ) } diff --git a/e2e/nextjs/app-router/app/app/normal/ConditionalClientComponent.tsx b/e2e/nextjs/app-router/app/app/normal/ConditionalClientComponent.tsx new file mode 100644 index 000000000..dc4e58442 --- /dev/null +++ b/e2e/nextjs/app-router/app/app/normal/ConditionalClientComponent.tsx @@ -0,0 +1,9 @@ +'use client' + +export default function ConditionalClientComponent() { + return ( +
+

Client Component Conditionally Bundled

+
+ ) +} diff --git a/e2e/nextjs/app-router/app/app/normal/devcycle.ts b/e2e/nextjs/app-router/app/app/normal/devcycle.ts index 7677597b4..892fb91f5 100644 --- a/e2e/nextjs/app-router/app/app/normal/devcycle.ts +++ b/e2e/nextjs/app-router/app/app/normal/devcycle.ts @@ -1,19 +1,21 @@ import { setupDevCycle } from '@devcycle/nextjs-sdk/server' import { headers } from 'next/headers' -const { getVariableValue, getClientContext, getAllVariables, getAllFeatures } = - setupDevCycle( - process.env.NEXT_PUBLIC_E2E_NEXTJS_KEY ?? '', - async () => { - const reqHeaders = headers() - return { - user_id: '123', - customData: { - // set a dummy field here so that the headers call stays in the build output - someKey: reqHeaders.get('some-key'), - }, - } - }, - { enableStreaming: false }, - ) - -export { getVariableValue, getClientContext, getAllVariables, getAllFeatures } +export const { + getVariableValue, + getClientContext, + getAllVariables, + getAllFeatures, +} = setupDevCycle( + process.env.NEXT_PUBLIC_E2E_NEXTJS_KEY ?? '', + async () => { + const reqHeaders = headers() + return { + user_id: '123', + customData: { + // set a dummy field here so that the headers call stays in the build output + someKey: reqHeaders.get('some-key'), + }, + } + }, + { enableStreaming: false }, +) diff --git a/e2e/nextjs/app-router/app/app/normal/page.tsx b/e2e/nextjs/app-router/app/app/normal/page.tsx index 3861ee1b8..e72d9c85f 100644 --- a/e2e/nextjs/app-router/app/app/normal/page.tsx +++ b/e2e/nextjs/app-router/app/app/normal/page.tsx @@ -3,7 +3,7 @@ import { ServerComponent } from './ServerComponent' import React, { Suspense } from 'react' import Link from 'next/link' -const Home = () => { +const Home = async () => { return (
Streaming Disabled
diff --git a/e2e/nextjs/app-router/app/package.json b/e2e/nextjs/app-router/app/package.json index a6b664931..aa5c32c1c 100644 --- a/e2e/nextjs/app-router/app/package.json +++ b/e2e/nextjs/app-router/app/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@devcycle/nextjs-sdk": "file:../../../../dist/sdk/nextjs", - "next": "14.0.5-canary.38", + "next": "14.1.1-canary.7", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/e2e/nextjs/app-router/app/yarn.lock b/e2e/nextjs/app-router/app/yarn.lock index 6adf6f391..c1d28a915 100644 --- a/e2e/nextjs/app-router/app/yarn.lock +++ b/e2e/nextjs/app-router/app/yarn.lock @@ -41,18 +41,16 @@ __metadata: linkType: hard "@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs::locator=app%40workspace%3A.": - version: 0.0.7 - resolution: "@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs#../../../../dist/sdk/nextjs::hash=804f8c&locator=app%40workspace%3A." + version: 1.0.0 + resolution: "@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs#../../../../dist/sdk/nextjs::hash=8e3789&locator=app%40workspace%3A." dependencies: "@devcycle/bucketing": "npm:^1.7.7" "@devcycle/js-client-sdk": "npm:^1.16.6" "@devcycle/react-client-sdk": "npm:^1.14.6" "@devcycle/types": "npm:^1.4.6" hoist-non-react-statics: "npm:^3.3.2" - peerDependencies: - next: ">=14.0.0" - react: ^18.2.0 - checksum: 0d9e23b033c555a7a77b733fe2512fbeea64828c4c87d7556de337e34d73bbcf56fd87abfcb0c25bbade715d29f9765fa86d64d692aca5ed3d821f14530a051b + server-only: "npm:^0.0.1" + checksum: cb51ef310f1fdf42be3ca85a5e030589064c41b2f7a12ace8d6c959f6ead4bf44abd793cf7f8041481cf690c4f40111469599ab7835d9d35eb62ff3db745fb49 languageName: node linkType: hard @@ -92,72 +90,72 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:14.0.5-canary.38": - version: 14.0.5-canary.38 - resolution: "@next/env@npm:14.0.5-canary.38" - checksum: 529c6e6ee1981027073854ab4d0d33b0faeaa3c07d2bc27508cdc6d79ccb412422c845bab8865d2c1f8e8a5b6a0c684eb30670b0b8471e8284349d568f91a928 +"@next/env@npm:14.1.1-canary.7": + version: 14.1.1-canary.7 + resolution: "@next/env@npm:14.1.1-canary.7" + checksum: 495fdab773a22bb7b9409bfd05cd5527c6032a39e086b8054c94e46193ad26ee25f83e990846a2b0b0db23b17ec2d4088dd619b29873b143ba9bfb3d1b30734e languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:14.0.5-canary.38": - version: 14.0.5-canary.38 - resolution: "@next/swc-darwin-arm64@npm:14.0.5-canary.38" +"@next/swc-darwin-arm64@npm:14.1.1-canary.7": + version: 14.1.1-canary.7 + resolution: "@next/swc-darwin-arm64@npm:14.1.1-canary.7" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:14.0.5-canary.38": - version: 14.0.5-canary.38 - resolution: "@next/swc-darwin-x64@npm:14.0.5-canary.38" +"@next/swc-darwin-x64@npm:14.1.1-canary.7": + version: 14.1.1-canary.7 + resolution: "@next/swc-darwin-x64@npm:14.1.1-canary.7" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:14.0.5-canary.38": - version: 14.0.5-canary.38 - resolution: "@next/swc-linux-arm64-gnu@npm:14.0.5-canary.38" +"@next/swc-linux-arm64-gnu@npm:14.1.1-canary.7": + version: 14.1.1-canary.7 + resolution: "@next/swc-linux-arm64-gnu@npm:14.1.1-canary.7" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:14.0.5-canary.38": - version: 14.0.5-canary.38 - resolution: "@next/swc-linux-arm64-musl@npm:14.0.5-canary.38" +"@next/swc-linux-arm64-musl@npm:14.1.1-canary.7": + version: 14.1.1-canary.7 + resolution: "@next/swc-linux-arm64-musl@npm:14.1.1-canary.7" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:14.0.5-canary.38": - version: 14.0.5-canary.38 - resolution: "@next/swc-linux-x64-gnu@npm:14.0.5-canary.38" +"@next/swc-linux-x64-gnu@npm:14.1.1-canary.7": + version: 14.1.1-canary.7 + resolution: "@next/swc-linux-x64-gnu@npm:14.1.1-canary.7" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:14.0.5-canary.38": - version: 14.0.5-canary.38 - resolution: "@next/swc-linux-x64-musl@npm:14.0.5-canary.38" +"@next/swc-linux-x64-musl@npm:14.1.1-canary.7": + version: 14.1.1-canary.7 + resolution: "@next/swc-linux-x64-musl@npm:14.1.1-canary.7" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:14.0.5-canary.38": - version: 14.0.5-canary.38 - resolution: "@next/swc-win32-arm64-msvc@npm:14.0.5-canary.38" +"@next/swc-win32-arm64-msvc@npm:14.1.1-canary.7": + version: 14.1.1-canary.7 + resolution: "@next/swc-win32-arm64-msvc@npm:14.1.1-canary.7" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:14.0.5-canary.38": - version: 14.0.5-canary.38 - resolution: "@next/swc-win32-ia32-msvc@npm:14.0.5-canary.38" +"@next/swc-win32-ia32-msvc@npm:14.1.1-canary.7": + version: 14.1.1-canary.7 + resolution: "@next/swc-win32-ia32-msvc@npm:14.1.1-canary.7" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:14.0.5-canary.38": - version: 14.0.5-canary.38 - resolution: "@next/swc-win32-x64-msvc@npm:14.0.5-canary.38" +"@next/swc-win32-x64-msvc@npm:14.1.1-canary.7": + version: 14.1.1-canary.7 + resolution: "@next/swc-win32-x64-msvc@npm:14.1.1-canary.7" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -222,7 +220,7 @@ __metadata: "@types/node": "npm:^20" "@types/react": "npm:18.2.20" "@types/react-dom": "npm:18.2.6" - next: "npm:14.0.5-canary.38" + next: "npm:14.1.1-canary.7" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" typescript: "npm:^5" @@ -266,10 +264,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001406": - version: 1.0.30001570 - resolution: "caniuse-lite@npm:1.0.30001570" - checksum: a9b939e003dd70580cc18bce54627af84f298af7c774415c8d6c99871e7cee13bd8278b67955a979cd338369c73e8821a10b37e607d1fff2fbc8ff92fc489653 +"caniuse-lite@npm:^1.0.30001579": + version: 1.0.30001579 + resolution: "caniuse-lite@npm:1.0.30001579" + checksum: 2cd0c02e5d66b09888743ad2b624dbde697ace5c76b55bfd6065ea033f6abea8ac3f5d3c9299c042f91b396e2141b49bc61f5e17086dc9ba3a866cc6790134c0 languageName: node linkType: hard @@ -331,14 +329,7 @@ __metadata: languageName: node linkType: hard -"glob-to-regexp@npm:^0.4.1": - version: 0.4.1 - resolution: "glob-to-regexp@npm:0.4.1" - checksum: 9009529195a955c40d7b9690794aeff5ba665cc38f1519e111c58bb54366fd0c106bde80acf97ba4e533208eb53422c83b136611a54c5fefb1edd8dc267cb62e - languageName: node - linkType: hard - -"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.2.11": +"graceful-fs@npm:^4.2.11": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 @@ -432,27 +423,26 @@ __metadata: languageName: node linkType: hard -"next@npm:14.0.5-canary.38": - version: 14.0.5-canary.38 - resolution: "next@npm:14.0.5-canary.38" +"next@npm:14.1.1-canary.7": + version: 14.1.1-canary.7 + resolution: "next@npm:14.1.1-canary.7" dependencies: - "@next/env": "npm:14.0.5-canary.38" - "@next/swc-darwin-arm64": "npm:14.0.5-canary.38" - "@next/swc-darwin-x64": "npm:14.0.5-canary.38" - "@next/swc-linux-arm64-gnu": "npm:14.0.5-canary.38" - "@next/swc-linux-arm64-musl": "npm:14.0.5-canary.38" - "@next/swc-linux-x64-gnu": "npm:14.0.5-canary.38" - "@next/swc-linux-x64-musl": "npm:14.0.5-canary.38" - "@next/swc-win32-arm64-msvc": "npm:14.0.5-canary.38" - "@next/swc-win32-ia32-msvc": "npm:14.0.5-canary.38" - "@next/swc-win32-x64-msvc": "npm:14.0.5-canary.38" + "@next/env": "npm:14.1.1-canary.7" + "@next/swc-darwin-arm64": "npm:14.1.1-canary.7" + "@next/swc-darwin-x64": "npm:14.1.1-canary.7" + "@next/swc-linux-arm64-gnu": "npm:14.1.1-canary.7" + "@next/swc-linux-arm64-musl": "npm:14.1.1-canary.7" + "@next/swc-linux-x64-gnu": "npm:14.1.1-canary.7" + "@next/swc-linux-x64-musl": "npm:14.1.1-canary.7" + "@next/swc-win32-arm64-msvc": "npm:14.1.1-canary.7" + "@next/swc-win32-ia32-msvc": "npm:14.1.1-canary.7" + "@next/swc-win32-x64-msvc": "npm:14.1.1-canary.7" "@swc/helpers": "npm:0.5.2" busboy: "npm:1.6.0" - caniuse-lite: "npm:^1.0.30001406" + caniuse-lite: "npm:^1.0.30001579" graceful-fs: "npm:^4.2.11" postcss: "npm:8.4.31" styled-jsx: "npm:5.1.1" - watchpack: "npm:2.4.0" peerDependencies: "@opentelemetry/api": ^1.1.0 react: ^18.2.0 @@ -484,7 +474,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: 3e9e1372aa0f00c5f70d2f45a7c00cf10e69a9e59f8d9677697fe2125c133759a8729c770513da9743c710804d0787d3e649b3e78bcc0e9a6087b2e541bcd987 + checksum: b542e2d8cb50ba10e88a9bbc25629f2b6f237254455ae5699136272f32375bbbb28e7f15527a5879b4e46fbebdfef2463529a2e96c9833a3e3fa3201058b9139 languageName: node linkType: hard @@ -564,6 +554,13 @@ __metadata: languageName: node linkType: hard +"server-only@npm:^0.0.1": + version: 0.0.1 + resolution: "server-only@npm:0.0.1" + checksum: c432348956641ea3f460af8dc3765f3a1bdbcf7a1e0205b0756d868e6e6fe8934cdee6bff68401a1dd49ba4a831c75916517a877446d54b334f7de36fa273e53 + languageName: node + linkType: hard + "source-map-js@npm:^1.0.2": version: 1.0.2 resolution: "source-map-js@npm:1.0.2" @@ -650,13 +647,3 @@ __metadata: checksum: 4bf094641eb71729c06a42d669840e7189597ba655a8264adabac9bf03f95cd6fde5fbc894b0a13ee861bd4a852f56d2afdc9391aeaeb3fc0f9633a974140e12 languageName: node linkType: hard - -"watchpack@npm:2.4.0": - version: 2.4.0 - resolution: "watchpack@npm:2.4.0" - dependencies: - glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.1.2" - checksum: 4280b45bc4b5d45d5579113f2a4af93b67ae1b9607cc3d86ae41cdd53ead10db5d9dc3237f24256d05ef88b28c69a02712f78e434cb7ecc8edaca134a56e8cab - languageName: node - linkType: hard diff --git a/e2e/nextjs/app-router/tests/app-router.spec.ts b/e2e/nextjs/app-router/tests/app-router.spec.ts index 2f2cc0231..5ab2d8cc1 100644 --- a/e2e/nextjs/app-router/tests/app-router.spec.ts +++ b/e2e/nextjs/app-router/tests/app-router.spec.ts @@ -91,6 +91,10 @@ test('has expected page elements', async ({ page }) => { /Client All Features: .*"key":"enabled-feature","type":"permission"/, ), ).toBeVisible() + + await expect( + page.getByText('Client Component Conditionally Bundled'), + ).toBeVisible() }) test('works after a client side navigation', async ({ page }) => { diff --git a/sdk/nextjs/index.ts b/sdk/nextjs/index.ts index 8dc736fc3..8832ff882 100644 --- a/sdk/nextjs/index.ts +++ b/sdk/nextjs/index.ts @@ -1,9 +1,10 @@ // Use this file to export React client code (e.g. those with 'use client' directive) // or other non-server utilities -export * from './src/client/useVariableValue' -export * from './src/client/DevCycleClientsideProvider' -export * from './src/common/types' -export * from './src/client/useUserIdentity' -export * from './src/client/useTrack' -export * from './src/client/useAllVariables' -export * from './src/client/useAllFeatures' +export { useVariable, useVariableValue } from './src/client/useVariableValue' +export type * from './src/common/types' +export { useUserIdentity } from './src/client/useUserIdentity' +export { useTrack } from './src/client/useTrack' +export { useAllVariables } from './src/client/useAllVariables' +export { useAllFeatures } from './src/client/useAllFeatures' +export { renderIfEnabled } from './src/client/renderIfEnabled' +export { DevCycleClientsideProvider } from './src/client/DevCycleClientsideProvider' diff --git a/sdk/nextjs/src/client/renderIfEnabled.tsx b/sdk/nextjs/src/client/renderIfEnabled.tsx new file mode 100644 index 000000000..c9b78a3c8 --- /dev/null +++ b/sdk/nextjs/src/client/renderIfEnabled.tsx @@ -0,0 +1,18 @@ +import { ComponentProps, ComponentType } from 'react' +import dynamic from 'next/dynamic' +import useVariableValue from './useVariableValue' + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const renderIfEnabled = }>( + key: string, + importFunc: () => Promise, +) => { + const Component = dynamic(() => importFunc()) + return function (props: ComponentProps) { + const isEnabled = useVariableValue(key, false) + if (isEnabled) { + return + } + return null + } +} diff --git a/sdk/nextjs/src/server/setupDevCycle.ts b/sdk/nextjs/src/server/setupDevCycle.tsx similarity index 100% rename from sdk/nextjs/src/server/setupDevCycle.ts rename to sdk/nextjs/src/server/setupDevCycle.tsx