From 10487270ed0e3577c21bfc46ae07cb09a1988666 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Mon, 1 Dec 2025 13:48:28 -0600 Subject: [PATCH 1/4] chore: add an example app for browser sdk --- package.json | 3 +- packages/sdk/browser/example/README.md | 44 +++++++++++++++++++ packages/sdk/browser/example/index.css | 11 +++++ packages/sdk/browser/example/index.html | 11 +++++ packages/sdk/browser/example/package.json | 27 ++++++++++++ packages/sdk/browser/example/src/app.ts | 42 ++++++++++++++++++ packages/sdk/browser/example/tsconfig.json | 22 ++++++++++ packages/sdk/browser/example/tsdown.config.ts | 9 ++++ 8 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 packages/sdk/browser/example/README.md create mode 100644 packages/sdk/browser/example/index.css create mode 100644 packages/sdk/browser/example/index.html create mode 100644 packages/sdk/browser/example/package.json create mode 100644 packages/sdk/browser/example/src/app.ts create mode 100644 packages/sdk/browser/example/tsconfig.json create mode 100644 packages/sdk/browser/example/tsdown.config.ts diff --git a/package.json b/package.json index 87317618e..11e71f91d 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "packages/sdk/combined-browser", "packages/sdk/shopify-oxygen", "packages/sdk/shopify-oxygen/contract-tests", - "packages/sdk/shopify-oxygen/example" + "packages/sdk/shopify-oxygen/example", + "packages/sdk/browser/example" ], "private": true, "scripts": { diff --git a/packages/sdk/browser/example/README.md b/packages/sdk/browser/example/README.md new file mode 100644 index 000000000..09294d194 --- /dev/null +++ b/packages/sdk/browser/example/README.md @@ -0,0 +1,44 @@ +# LaunchDarkly sample javascript application + +# ⛔️⛔️⛔️⛔️ + +> [!CAUTION] +> This example is created against a non-production SDK which means things may change and this example might +> not work while this message is visible. + +# ☝️☝️☝️☝️☝️☝️ + +We've built a simple browser application that demonstrates how this LaunchDarkly SDK works. + +Below, you'll find the build procedure. For more comprehensive instructions, you can visit your [Quickstart page](https://app.launchdarkly.com/quickstart#/) or +the [{name of SDK} reference guide](https://docs.launchdarkly.com/sdk/client-side/javascript). + +## Build instructions + +Modify [app.ts](./src/app.ts) with the following changes: + +1. Set the value of the {`clientSideID`} variable in {file name} to your client-side ID: + ```typescript + const clientSideID = "my-client-side-id"; + ``` + +2. If there is an existing boolean feature flag in your LaunchDarkly project that + you want to evaluate, set `flagKey` to the flag key: + ```typescript + const flagKey = "my-flag-key"; + ``` + +3. If you haven't already, install and build the project: + ```bash + yarn && yarn build + ``` + +4. On the command line, run `yarn start` + ```bash + yarn start + ``` + > [!NOTE] + > The `yarn start` script simply runs `open index.html`. If that is not working for you, + > you can open the `index.html` file in a browser for the same results. + +The application will run continuously and react to the flag changes in LaunchDarkly. \ No newline at end of file diff --git a/packages/sdk/browser/example/index.css b/packages/sdk/browser/example/index.css new file mode 100644 index 000000000..90dc2b50f --- /dev/null +++ b/packages/sdk/browser/example/index.css @@ -0,0 +1,11 @@ +body { + margin: 0; + background: #373841; + color: white; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', + 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-align: center; +} diff --git a/packages/sdk/browser/example/index.html b/packages/sdk/browser/example/index.html new file mode 100644 index 000000000..26e38b21d --- /dev/null +++ b/packages/sdk/browser/example/index.html @@ -0,0 +1,11 @@ + + + + + + LaunchDarkly tutorial + + + + + diff --git a/packages/sdk/browser/example/package.json b/packages/sdk/browser/example/package.json new file mode 100644 index 000000000..122b9cabb --- /dev/null +++ b/packages/sdk/browser/example/package.json @@ -0,0 +1,27 @@ +{ + "name": "@launchdarkly/browser-example", + "version": "0.0.0", + "prviate": true, + "description": "LaunchDarkly example for JavaScript Browser SDK", + "homepage": "https://github.com/launchdarkly/js-core/tree/main/packages/sdk/browser/example", + "repository": { + "type": "git", + "url": "https://github.com/launchdarkly/js-core.git" + }, + "license": "Apache-2.0", + "packageManager": "yarn@3.4.1", + "type": "module", + "scripts": { + "start": "open index.html", + "clean": "rm -rf dist dist-static", + "build": "npm run clean && tsdown" + }, + "dependencies": { + "@launchdarkly/js-client-sdk": "workspace:^", + "express": "^5.1.0" + }, + "devDependencies": { + "tsdown": "^0.17.0-beta.4", + "typescript": "^5.9.3" + } +} diff --git a/packages/sdk/browser/example/src/app.ts b/packages/sdk/browser/example/src/app.ts new file mode 100644 index 000000000..9b93c3e29 --- /dev/null +++ b/packages/sdk/browser/example/src/app.ts @@ -0,0 +1,42 @@ +import { initialize } from '@launchdarkly/js-client-sdk'; + +// Set clientSideID to your LaunchDarkly client-side ID +const clientSideID = ''; + +// Set flagKey to the feature flag key you want to evaluate +const flagKey = 'sample-feature'; + +// Set up the evaluation context. This context should appear on your +// LaunchDarkly contexts dashboard soon after you run the demo. +const context = { + kind: 'user', + key: 'example-user-key', + name: 'Sandy', +}; + +const div = document.createElement('div'); +document.body.appendChild(div); +div.appendChild(document.createTextNode('Initializing...')); + +const ldclient = initialize(clientSideID); + +function render() { + const flagValue = ldclient.variation(flagKey, false); + const label = `The ${flagKey} feature flag evaluates to ${flagValue}.`; + document.body.style.background = flagValue ? '#00844B' : '#373841'; + div.replaceChild(document.createTextNode(label), div.firstChild as Node); +} + +ldclient.identify(context).then(() => { + ldclient.on('initialized', () => { + div.replaceChild( + document.createTextNode('SDK successfully initialized!'), + div.firstChild as Node, + ); + }); + ldclient.on('failed', () => { + div.replaceChild(document.createTextNode('SDK failed to initialize'), div.firstChild as Node); + }); + ldclient.on('ready', render); + ldclient.on('change', render); +}); diff --git a/packages/sdk/browser/example/tsconfig.json b/packages/sdk/browser/example/tsconfig.json new file mode 100644 index 000000000..a1aec48ce --- /dev/null +++ b/packages/sdk/browser/example/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "declaration": true, + "declarationMap": true, + "lib": ["ES2017", "dom"], + "module": "ESNext", + "moduleResolution": "node", + "noImplicitOverride": true, + "resolveJsonModule": true, + "rootDir": ".", + "outDir": "dist", + "skipLibCheck": true, + "sourceMap": true, + "inlineSources": true, + "strict": true, + "stripInternal": true, + "target": "ES2017", + "allowJs": true + }, + "include": ["src"] +} diff --git a/packages/sdk/browser/example/tsdown.config.ts b/packages/sdk/browser/example/tsdown.config.ts new file mode 100644 index 000000000..f040e8c0f --- /dev/null +++ b/packages/sdk/browser/example/tsdown.config.ts @@ -0,0 +1,9 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + entry: 'src/app.ts', + platform: 'browser', + outDir: 'dist', + noExternal: ['@launchdarkly/js-client-sdk'], +}); From f4ad1e74b39c6248fed97c1cedde12ad901fec1a Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Mon, 1 Dec 2025 17:17:49 -0600 Subject: [PATCH 2/4] chore: addressing cursor comments --- packages/sdk/browser/example/package.json | 5 ++- packages/sdk/browser/example/src/app.ts | 42 +++++++++++++---------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/sdk/browser/example/package.json b/packages/sdk/browser/example/package.json index 122b9cabb..3d52d11fe 100644 --- a/packages/sdk/browser/example/package.json +++ b/packages/sdk/browser/example/package.json @@ -1,7 +1,7 @@ { "name": "@launchdarkly/browser-example", "version": "0.0.0", - "prviate": true, + "private": true, "description": "LaunchDarkly example for JavaScript Browser SDK", "homepage": "https://github.com/launchdarkly/js-core/tree/main/packages/sdk/browser/example", "repository": { @@ -17,8 +17,7 @@ "build": "npm run clean && tsdown" }, "dependencies": { - "@launchdarkly/js-client-sdk": "workspace:^", - "express": "^5.1.0" + "@launchdarkly/js-client-sdk": "workspace:^" }, "devDependencies": { "tsdown": "^0.17.0-beta.4", diff --git a/packages/sdk/browser/example/src/app.ts b/packages/sdk/browser/example/src/app.ts index 9b93c3e29..add826bc9 100644 --- a/packages/sdk/browser/example/src/app.ts +++ b/packages/sdk/browser/example/src/app.ts @@ -17,26 +17,30 @@ const context = { const div = document.createElement('div'); document.body.appendChild(div); div.appendChild(document.createTextNode('Initializing...')); +try { + const ldclient = initialize(clientSideID); + const render = () => { + const flagValue = ldclient.variation(flagKey, false); + const label = `The ${flagKey} feature flag evaluates to ${flagValue}.`; + document.body.style.background = flagValue ? '#00844B' : '#373841'; + div.replaceChild(document.createTextNode(label), div.firstChild as Node); + }; -const ldclient = initialize(clientSideID); - -function render() { - const flagValue = ldclient.variation(flagKey, false); - const label = `The ${flagKey} feature flag evaluates to ${flagValue}.`; - document.body.style.background = flagValue ? '#00844B' : '#373841'; - div.replaceChild(document.createTextNode(label), div.firstChild as Node); -} + ldclient.on('error', () => { + div.replaceChild(document.createTextNode('Error caught in client SDK'), div.firstChild as Node); + }); -ldclient.identify(context).then(() => { - ldclient.on('initialized', () => { - div.replaceChild( - document.createTextNode('SDK successfully initialized!'), - div.firstChild as Node, - ); + ldclient.on('change', () => { + render(); }); - ldclient.on('failed', () => { - div.replaceChild(document.createTextNode('SDK failed to initialize'), div.firstChild as Node); + + ldclient.identify(context).catch(() => { + div.replaceChild(document.createTextNode('Error identifying client'), div.firstChild as Node); }); - ldclient.on('ready', render); - ldclient.on('change', render); -}); +} catch (error) { + div.replaceChild( + document.createTextNode(`Error initializing LaunchDarkly client: ${error}`), + div.firstChild as Node, + ); + document.body.style.background = '#373841'; +} From 30d81c6129d695c07402048b25241a3ce9505cf3 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Mon, 8 Dec 2025 10:43:28 -0600 Subject: [PATCH 3/4] chore: addressing PR comments --- packages/sdk/browser/example/.env.template | 5 ++ packages/sdk/browser/example/README.md | 30 +++++--- packages/sdk/browser/example/src/app.ts | 70 +++++++++++-------- packages/sdk/browser/example/tsdown.config.ts | 30 +++++++- 4 files changed, 94 insertions(+), 41 deletions(-) create mode 100644 packages/sdk/browser/example/.env.template diff --git a/packages/sdk/browser/example/.env.template b/packages/sdk/browser/example/.env.template new file mode 100644 index 000000000..89ddb1d7f --- /dev/null +++ b/packages/sdk/browser/example/.env.template @@ -0,0 +1,5 @@ +# Set LD_CLIENT_SIDE_ID to your LaunchDarkly client-side ID +LD_CLIENT_SIDE_ID= + +# Set LD_FLAG_KEY to the feature flag key you want to evaluate +LD_FLAG_KEY= \ No newline at end of file diff --git a/packages/sdk/browser/example/README.md b/packages/sdk/browser/example/README.md index 09294d194..53983ed1c 100644 --- a/packages/sdk/browser/example/README.md +++ b/packages/sdk/browser/example/README.md @@ -13,22 +13,30 @@ We've built a simple browser application that demonstrates how this LaunchDarkly Below, you'll find the build procedure. For more comprehensive instructions, you can visit your [Quickstart page](https://app.launchdarkly.com/quickstart#/) or the [{name of SDK} reference guide](https://docs.launchdarkly.com/sdk/client-side/javascript). +## Prerequisites + +Nodejs 20.6.0 or later + ## Build instructions -Modify [app.ts](./src/app.ts) with the following changes: +1. Make a copy of the `.env.template` and name it `.env` + ``` + cp .env.template .env + ``` -1. Set the value of the {`clientSideID`} variable in {file name} to your client-side ID: - ```typescript - const clientSideID = "my-client-side-id"; +2. Set the variables in `.env` to your specific LD values ``` - -2. If there is an existing boolean feature flag in your LaunchDarkly project that - you want to evaluate, set `flagKey` to the flag key: - ```typescript - const flagKey = "my-flag-key"; + # Set LD_CLIENT_SIDE_ID to your LaunchDarkly client-side ID + LD_CLIENT_SIDE_ID= + + # Set LD_FLAG_KEY to the feature flag key you want to evaluate + LD_FLAG_KEY= ``` + > [!NOTE] + > Setting these values is equivilent to modifying the `clientSideID` and `flagKey` + > in [app.ts](./src/app.ts). -3. If you haven't already, install and build the project: +3. Install and build the project: ```bash yarn && yarn build ``` @@ -41,4 +49,4 @@ Modify [app.ts](./src/app.ts) with the following changes: > The `yarn start` script simply runs `open index.html`. If that is not working for you, > you can open the `index.html` file in a browser for the same results. -The application will run continuously and react to the flag changes in LaunchDarkly. \ No newline at end of file +The application will run continuously and react to the flag changes in LaunchDarkly. diff --git a/packages/sdk/browser/example/src/app.ts b/packages/sdk/browser/example/src/app.ts index add826bc9..9c7b58eaa 100644 --- a/packages/sdk/browser/example/src/app.ts +++ b/packages/sdk/browser/example/src/app.ts @@ -1,10 +1,10 @@ import { initialize } from '@launchdarkly/js-client-sdk'; // Set clientSideID to your LaunchDarkly client-side ID -const clientSideID = ''; +const clientSideID = 'LD_CLIENT_SIDE_ID'; // Set flagKey to the feature flag key you want to evaluate -const flagKey = 'sample-feature'; +const flagKey = 'LD_FLAG_KEY'; // Set up the evaluation context. This context should appear on your // LaunchDarkly contexts dashboard soon after you run the demo. @@ -17,30 +17,42 @@ const context = { const div = document.createElement('div'); document.body.appendChild(div); div.appendChild(document.createTextNode('Initializing...')); -try { - const ldclient = initialize(clientSideID); - const render = () => { - const flagValue = ldclient.variation(flagKey, false); - const label = `The ${flagKey} feature flag evaluates to ${flagValue}.`; - document.body.style.background = flagValue ? '#00844B' : '#373841'; - div.replaceChild(document.createTextNode(label), div.firstChild as Node); - }; - - ldclient.on('error', () => { - div.replaceChild(document.createTextNode('Error caught in client SDK'), div.firstChild as Node); - }); - - ldclient.on('change', () => { - render(); - }); - - ldclient.identify(context).catch(() => { - div.replaceChild(document.createTextNode('Error identifying client'), div.firstChild as Node); - }); -} catch (error) { - div.replaceChild( - document.createTextNode(`Error initializing LaunchDarkly client: ${error}`), - div.firstChild as Node, - ); - document.body.style.background = '#373841'; -} + +const main = async () => { + try { + const ldclient = initialize(clientSideID); + const render = () => { + const flagValue = ldclient.variation(flagKey, false); + const label = `The ${flagKey} feature flag evaluates to ${flagValue}.`; + document.body.style.background = flagValue ? '#00844B' : '#373841'; + div.replaceChild(document.createTextNode(label), div.firstChild as Node); + }; + + ldclient.on('error', () => { + div.replaceChild( + document.createTextNode('Error caught in client SDK'), + div.firstChild as Node, + ); + }); + + // Listen for flag changes + ldclient.on('change', () => { + render(); + }); + + const { status } = await ldclient.identify(context); + if (status === 'completed') { + render(); + } else if (status === 'error') { + div.replaceChild(document.createTextNode('Error identifying client'), div.firstChild as Node); + } + } catch (error) { + div.replaceChild( + document.createTextNode(`Error initializing LaunchDarkly client: ${error}`), + div.firstChild as Node, + ); + document.body.style.background = '#373841'; + } +}; + +main(); diff --git a/packages/sdk/browser/example/tsdown.config.ts b/packages/sdk/browser/example/tsdown.config.ts index f040e8c0f..608b8be04 100644 --- a/packages/sdk/browser/example/tsdown.config.ts +++ b/packages/sdk/browser/example/tsdown.config.ts @@ -1,9 +1,37 @@ /* eslint-disable import/no-extraneous-dependencies */ +import fs from 'node:fs'; +import path from 'node:path'; +import { loadEnvFile } from 'node:process'; import { defineConfig } from 'tsdown'; +if (fs.existsSync('.env')) { + loadEnvFile('.env'); +} + +const ENTRY_FILE = path.join('src', 'app.ts'); +const OUTPUT_FILE = path.join('dist', 'app.js'); +const { LD_CLIENT_SIDE_ID, LD_FLAG_KEY } = process.env; + +const CLIENT_SIDE_ID_PLACEHOLDER = 'LD_CLIENT_SIDE_ID'; +const FLAG_KEY_PLACEHOLDER = 'LD_FLAG_KEY'; + export default defineConfig({ - entry: 'src/app.ts', + entry: ENTRY_FILE, platform: 'browser', outDir: 'dist', noExternal: ['@launchdarkly/js-client-sdk'], + hooks(hooks) { + hooks.hook('build:done', () => { + if (LD_CLIENT_SIDE_ID) { + const content = fs.readFileSync(OUTPUT_FILE).toString(); + fs.writeFileSync( + OUTPUT_FILE, + content.replaceAll(CLIENT_SIDE_ID_PLACEHOLDER, LD_CLIENT_SIDE_ID), + ); + } + const flagKey = LD_FLAG_KEY || 'sample-feature'; + const content = fs.readFileSync(OUTPUT_FILE).toString(); + fs.writeFileSync(OUTPUT_FILE, content.replaceAll(FLAG_KEY_PLACEHOLDER, flagKey)); + }); + }, }); From 4ba5b0b6287e7e18d57c4ca00f15b7c6f754585d Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Tue, 9 Dec 2025 11:29:51 -0600 Subject: [PATCH 4/4] chore: addressing PR comments --- packages/sdk/browser/example/.env.template | 2 +- packages/sdk/browser/example/src/app.ts | 69 +++++++++++----------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/packages/sdk/browser/example/.env.template b/packages/sdk/browser/example/.env.template index 89ddb1d7f..896d2d206 100644 --- a/packages/sdk/browser/example/.env.template +++ b/packages/sdk/browser/example/.env.template @@ -2,4 +2,4 @@ LD_CLIENT_SIDE_ID= # Set LD_FLAG_KEY to the feature flag key you want to evaluate -LD_FLAG_KEY= \ No newline at end of file +LD_FLAG_KEY= diff --git a/packages/sdk/browser/example/src/app.ts b/packages/sdk/browser/example/src/app.ts index 9c7b58eaa..8ca290132 100644 --- a/packages/sdk/browser/example/src/app.ts +++ b/packages/sdk/browser/example/src/app.ts @@ -15,44 +15,47 @@ const context = { }; const div = document.createElement('div'); +const statusBox = document.createElement('div'); + +document.body.appendChild(statusBox); document.body.appendChild(div); -div.appendChild(document.createTextNode('Initializing...')); + +div.appendChild(document.createTextNode('No flag evaluations yet')); +statusBox.appendChild(document.createTextNode('Initializing...')); const main = async () => { - try { - const ldclient = initialize(clientSideID); - const render = () => { - const flagValue = ldclient.variation(flagKey, false); - const label = `The ${flagKey} feature flag evaluates to ${flagValue}.`; - document.body.style.background = flagValue ? '#00844B' : '#373841'; - div.replaceChild(document.createTextNode(label), div.firstChild as Node); - }; - - ldclient.on('error', () => { - div.replaceChild( - document.createTextNode('Error caught in client SDK'), - div.firstChild as Node, - ); - }); - - // Listen for flag changes - ldclient.on('change', () => { - render(); - }); - - const { status } = await ldclient.identify(context); - if (status === 'completed') { - render(); - } else if (status === 'error') { - div.replaceChild(document.createTextNode('Error identifying client'), div.firstChild as Node); - } - } catch (error) { - div.replaceChild( - document.createTextNode(`Error initializing LaunchDarkly client: ${error}`), - div.firstChild as Node, + const ldclient = initialize(clientSideID); + const render = () => { + const flagValue = ldclient.variation(flagKey, false); + const label = `The ${flagKey} feature flag evaluates to ${flagValue}.`; + document.body.style.background = flagValue ? '#00844B' : '#373841'; + div.replaceChild(document.createTextNode(label), div.firstChild as Node); + }; + + ldclient.on('error', () => { + statusBox.replaceChild( + document.createTextNode('Error caught in client SDK'), + statusBox.firstChild as Node, + ); + }); + + // Listen for flag changes + ldclient.on('change', () => { + render(); + }); + + const { status } = await ldclient.identify(context); + + if (status === 'completed') { + statusBox.replaceChild(document.createTextNode('Initialized'), statusBox.firstChild as Node); + } else if (status === 'error') { + statusBox.replaceChild( + document.createTextNode('Error identifying client'), + statusBox.firstChild as Node, ); - document.body.style.background = '#373841'; } + + render(); }; main();