Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2546db7
chore: Initial move from ld-nextjs.
yusinto Jun 27, 2024
b4e31a4
fix: Include react canary types to use cache. Fixed serverCache bug. …
yusinto Jun 27, 2024
1fe5bb0
chore: Added example app.
yusinto Jun 27, 2024
4473b5f
chore: Fix eslint for example app.
yusinto Jun 27, 2024
4034f45
Delete .eslintrc.json
yusinto Jun 27, 2024
1e681df
chore: Improved readme.
yusinto Jun 27, 2024
34f5452
chore: Add release please scripts.
yusinto Jun 27, 2024
0aa6748
adding example content from launchdarkly/ld-nextjs
codyde Jun 27, 2024
2fcbd33
Merge branch 'yus/sc-248499/move-ld-nextjs-to-js-core' of https://git…
codyde Jun 27, 2024
3f75b81
chore: Ran prettier on example app. Removed extraneous prettierrc.
yusinto Jun 27, 2024
5b8fc42
chore: Fix @ld imports.
yusinto Jun 27, 2024
9769092
Create .eslintrc.js
yusinto Jun 27, 2024
d95dd13
Update globals.css
yusinto Jun 27, 2024
c2ac484
chore: Replace dev-test-flag with my-boolean-flag-1.
yusinto Jun 27, 2024
2c0a188
chore: Remove redundant comments.
yusinto Jun 27, 2024
8c2c6df
Update .eslintrc.js
yusinto Jun 27, 2024
efe4513
Renaming .env.local to .example.env.local and adding warning
codyde Jun 27, 2024
7cd1384
Merge branch 'yus/sc-248499/move-ld-nextjs-to-js-core' of https://git…
codyde Jun 27, 2024
141bb41
Create .eslintignore
yusinto Jun 27, 2024
5ebdf31
chore: Added login and cookie example.
yusinto Jun 27, 2024
b2dc0c1
Update release-please.yml
yusinto Jun 27, 2024
1f9e52d
Update release-please-config.json
yusinto Jun 27, 2024
ef5c89d
chore: Improved readme.
yusinto Jun 27, 2024
1fc710a
Update package.json
yusinto Jun 27, 2024
1d57e17
Update README.md
yusinto Jun 27, 2024
431dc65
fix: Replace common types with node server types.
yusinto Jun 27, 2024
4419ec0
chore: Added login input and improved button styles.
yusinto Jun 27, 2024
d5bbc27
chore: Add server variation call for analytics.
yusinto Jun 27, 2024
78a6810
chore: Added warning about directly calling ldClient.variation. Added…
yusinto Jun 27, 2024
a5abddc
Update helloClientComponent.tsx
yusinto Jun 27, 2024
8b9ff98
chore: Rolled back client component. Moved identify example to its ow…
yusinto Jun 27, 2024
b88fa95
fix: Check for global variable in server variation.
yusinto Jun 28, 2024
e319133
chore: Added minor comment.
yusinto Jun 28, 2024
a383441
Update LDProvider.tsx
yusinto Jul 2, 2024
ac67a7e
chore: Added clientSideID to LDProvider.
yusinto Jul 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
'**/dist/**',
'**/vercel/examples/**',
'**/react-native/example/**',
'**/react-universal/example/**',
'**/fromExternal/**',
],
rules: {
Expand Down Expand Up @@ -41,10 +42,10 @@ module.exports = {
'import/no-cycle': 'error',
'import/no-useless-path-segments': 'error',
'import/no-duplicates': 'error',
'import/prefer-default-export': 'off',
'jest/no-disabled-tests': 'warn',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
// 'jest/prefer-to-have-length': 'warn',
'jest/valid-expect': 'error',
},
globals: {
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,6 @@ jobs:
permissions:
id-token: write
contents: write
# HACK: react-universal sdk is not ready for release yet.
if: false #${{ needs.release-please.outputs.package-react-universal-release == 'true' }}
steps:
- uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"packages/sdk/react-native",
"packages/sdk/react-native/example",
"packages/sdk/react-universal",
"packages/sdk/react-universal/example",
"packages/sdk/vercel",
"packages/sdk/akamai-base",
"packages/sdk/akamai-base/example",
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/react-native/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ yarn && yarn build
MOBILE_KEY=abcdef12456
```

3. Replace `dev-test-flag` with your flag key in `src/welcome.tsx`.
3. Replace `my-boolean-flag-1` with your flag key in `src/welcome.tsx`.

4. Run the app:

Expand Down
1 change: 1 addition & 0 deletions packages/sdk/react-universal/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
example
69 changes: 26 additions & 43 deletions packages/sdk/react-universal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,62 +9,49 @@
> [!CAUTION]
> This library is a beta version and should not be considered ready for production use while this message is visible.

> **An idiomatic LaunchDarkly SDK which supports RSC, server side rendering and bootstrapping** :clap:
## Features

This SDK supports:
- Supports both React Server Components and Client Components
- Idiomatic server side rendering
- Bootstrapping out of the box

- React Server Components
- Server side rendering
- Bootstrapping

## Installation
## Install

```shell
# npm
npm i @launchdarkly/react-universal-sdk --save-dev
npm i @launchdarkly/react-universal-sdk

# yarn
yarn add -D @launchdarkly/react-universal-sdk
```

### Server API
## Server API

- `initNodeSdk` - Initializes the Node SDK on server startup using the [instrumentation hook](https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation)
- `initNodeSdk` - Initializes the Node SDK on startup.

- `getBootstrap` - Returns a json suitable for bootstrapping the js sdk.
- `getBootstrap` - Produces suitable bootstrap the js sdk.

- `useLDClientRsc` - Use this to get an ldClient for Server Components.
- `useLDClientRsc` - Gets a suitable ld client for Server Components.

### Client API
## Client API

- `LDProvider` - The react context provider.

- `useLDClient` - Use this to get an ldClient for Client Components.
- `useLDClient` - Gets a suitable ld client for Client Components.

## Usage

1. Enable [instrumentationHook](https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation) in `next.config.mjs`:
1. On server start, initialize the Node Server SDK. If you are using NextJS App Router, do this in `instrumentation.ts`. You'll need to enable the [instrumentationHook](https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This universal sdk should work with all framework which supports rsc (gatsby, remix, etc).


```ts
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: { instrumentationHook: true },
};

export default nextConfig;
```

2. Create a new file `instrumentation.ts` at the root of your project. This will initialize the Node Server SDK.

```ts
import { initNodeSdk } from '@/ld/server';
import { initNodeSdk } from '@launchdarkly/react-universal-sdk/server';

export async function register() {
await initNodeSdk();
}
```

3. In your root layout component, render the `LDProvider` using your `LDContext` and `bootstrap`:
2. At the application root, render the `LDProvider` with your `LDContext` and `bootstrap`. In App Router, do this in the root layout:

```tsx
export default async function RootLayout({
Expand All @@ -91,42 +78,38 @@ export default async function RootLayout({
}
```

4. Server Components must use the async `useLDClientRsc` function:
3. Server Components must use the async `useLDClientRsc` function:

```tsx
// You should use your own getLDContext function.
import { getLDContext } from '@/app/utils';
import { useLDClientRsc } from '@/ld/server';

export default async function Page() {
import { useLDClientRsc } from '@launchdarkly/react-universal-sdk/server';

export default async function ServerComponent() {
const ldc = await useLDClientRsc(getLDContext());
const flagValue = ldc.variation('dev-test-flag');
const flagValue = ldc.variation('my-boolean-flag-1');

return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
Server Component: {flagValue.toString()}
</main>
);
return <>Server Component: {flagValue.toString()}</>;
}
```

5. Client Components must use the `useLDClient` hook:
Client Components must use the `useLDClient` hook:

```tsx
'use client';

import { useLDClient } from '@/ld/client';
import { useLDClient } from '@launchdarkly/react-universal-sdk/client';

export default function LDButton() {
export default function ClientComponent() {
const ldc = useLDClient();
const flagValue = ldc.variation('dev-test-flag');
const flagValue = ldc.variation('my-boolean-flag-1');

return <p>Client Component: {flagValue.toString()}</p>;
}
```

You will see both components are rendered on the server (view source on your browser). However, only Client Components
will respond to live changes.
You will see both Server and Client Components are rendered on the server (view source on your browser). However, only Client Components will respond to live changes because Server Components are excluded from the client bundle.

## Verifying SDK build provenance with the SLSA framework

Expand Down
3 changes: 3 additions & 0 deletions packages/sdk/react-universal/example/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ['plugin:@next/next/recommended'],
};
4 changes: 4 additions & 0 deletions packages/sdk/react-universal/example/.example.env.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Example only - Do not commit

LD_SDK_KEY='<YOUR LD SERVER SDK KEY>'
NEXT_PUBLIC_LD_CLIENT_SIDE_ID='<YOUR LD CLIENT SDK KEY>'
38 changes: 38 additions & 0 deletions packages/sdk/react-universal/example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local
.env

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
.idea
34 changes: 22 additions & 12 deletions packages/sdk/react-universal/example/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
# LaunchDarkly Universal SDK example

> [!IMPORTANT]
> This is an experimental project to demonstrate the use of LaunchDarkly with Next.js App Router.
>
> This is designed for the App Router. Pages router is not supported.

This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) using App Router.
This example app uses the LaunchDarkly React Universal SDK. It features:

## Quickstart
- Server side rendering with both Server Components and Client Components.
- A Client Component example in [app/components/helloClientComponent.tsx](https://github.com/launchdarkly/js-core/tree/main/packages/sdk/react-universal/example/app/components/helloClientComponent.tsx)
- A Server Component (RSC) example in [app/components/helloServerComponent.tsx](https://github.com/launchdarkly/js-core/tree/main/packages/sdk/react-universal/example/app/components/helloServerComponent.tsx)
- Out of the box bootstrapping.

To run this project:
This is a [Next.js](https://nextjs.org/) project created with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) using App Router.

## Quickstart

1. Create an .env file at repo root.
2. Add your SDK key and client-side ID:
1. Rename `.example.env.local` to `.env.local` and use your LaunchDarkly SDK keys:

```dotenv
LD_SDK_KEY=sdk-***
NEXT_PUBLIC_LD_CLIENT_SIDE_ID=***
LD_SDK_KEY='<YOUR LD SERVER SDK KEY>'
NEXT_PUBLIC_LD_CLIENT_SIDE_ID='<YOUR LD CLIENT SDK KEY>'
```

3. Replace `dev-test-flag` with your own flags in `app.tsx` and `LDButton.tsx`.
4. `yarn && yarn dev`
2. Either create `my-boolean-flag-1` in your LaunchDarkly environment or replace with your own flag in [helloClientComponent.tsx](https://github.com/launchdarkly/js-core/tree/main/packages/sdk/react-universal/example/app/components/helloClientComponent.tsx) and [helloServerComponent.tsx](https://github.com/launchdarkly/js-core/tree/main/packages/sdk/react-universal/example/app/components/helloServerComponent.tsx).

3. Finally:

```bash
npm i && npm run dev

# or
yarn && yarn dev
```

You should see your flag value rendered in the browser.
You will see both Server and Client Components are rendered on the server (view source on your browser). However, only Client Components will respond to live changes because Server Components are excluded from the client bundle.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';

import { useLDClient } from '@launchdarkly/react-universal-sdk/client';

export default function HelloClientComponent() {
const ldc = useLDClient();

// WARNING: Using the ldClient to evaluate flags directly like this in prod
// can result in high event volumes. This example is contrived and is meant for
// demo purposes only. The recommended way is to utilise the `useVariation` hooks
// which should be supported soon.
const flagValue = ldc.variation('my-boolean-flag-1');

return (
<div className="border-2 border-white/20 p-4 ">
<div>
<p className="ldgradient text-xl">
{flagValue
? 'This flag is evaluating True running Client-Side JavaScript'
: 'This flag is evaluating False running Client-Side JavaScript'}
</p>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use client';

import { useState } from 'react';
import { useCookies } from 'react-cookie';

import type { JSSdk } from '@launchdarkly/react-universal-sdk';
import { useLDClient } from '@launchdarkly/react-universal-sdk/client';

export default function HelloIdentify() {
const ldc = useLDClient();
const [_, setCookie] = useCookies(['ld']);
const [contextKey, setContextKey] = useState('');

// WARNING: Using the ldClient to evaluate flags directly like this in prod
// can result in high event volumes. This example is contrived and is meant for
// demo purposes only. The recommended way is to utilise the `useVariation` hooks
// which should be supported soon.
const flagValue = ldc.variation('my-boolean-flag-1');

function onClickLogin() {
const context = { kind: 'user', key: contextKey };
(ldc as JSSdk).identify(context).then(() => {
console.log('identify successful, persisting to cookies');
setCookie('ld', context);
});
}

return (
<div className="border-2 border-white/20 p-4 ">
<div>
<p className="ldgradient text-xl">
{flagValue
? 'This flag is evaluating True running Client-Side JavaScript'
: 'This flag is evaluating False running Client-Side JavaScript'}
</p>
</div>
<br />
<div>
<label>
Context key: &nbsp;
<input value={contextKey} onChange={(e) => setContextKey(e.target.value)} />
</label>
</div>
<br />
<button role="button" className="login" onClick={onClickLogin}>
Login
</button>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getLDContext } from '@/app/utils';

import { useLDClientRsc } from '@launchdarkly/react-universal-sdk/server';

export default async function HelloServerComponent() {
const ldc = await useLDClientRsc(getLDContext());
const flagValue = ldc.variation('my-boolean-flag-1');

return (
<div className="border-2 border-white/20 p-4">
<p className="text-xl ldgradient">
{flagValue
? 'This flag is evaluating True in a React Server Component'
: 'This flag is evaluating False in a React Server Component'}
</p>
</div>
);
}
Binary file not shown.
Loading