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/.env.template b/packages/sdk/browser/example/.env.template new file mode 100644 index 000000000..896d2d206 --- /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= diff --git a/packages/sdk/browser/example/README.md b/packages/sdk/browser/example/README.md new file mode 100644 index 000000000..53983ed1c --- /dev/null +++ b/packages/sdk/browser/example/README.md @@ -0,0 +1,52 @@ +# 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). + +## Prerequisites + +Nodejs 20.6.0 or later + +## Build instructions + +1. Make a copy of the `.env.template` and name it `.env` + ``` + cp .env.template .env + ``` + +2. Set the variables in `.env` to your specific LD values + ``` + # 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. 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. 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..3d52d11fe --- /dev/null +++ b/packages/sdk/browser/example/package.json @@ -0,0 +1,26 @@ +{ + "name": "@launchdarkly/browser-example", + "version": "0.0.0", + "private": 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:^" + }, + "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..8ca290132 --- /dev/null +++ b/packages/sdk/browser/example/src/app.ts @@ -0,0 +1,61 @@ +import { initialize } from '@launchdarkly/js-client-sdk'; + +// Set clientSideID to your LaunchDarkly client-side ID +const clientSideID = 'LD_CLIENT_SIDE_ID'; + +// Set flagKey to the feature flag key you want to evaluate +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. +const context = { + kind: 'user', + key: 'example-user-key', + name: 'Sandy', +}; + +const div = document.createElement('div'); +const statusBox = document.createElement('div'); + +document.body.appendChild(statusBox); +document.body.appendChild(div); + +div.appendChild(document.createTextNode('No flag evaluations yet')); +statusBox.appendChild(document.createTextNode('Initializing...')); + +const main = async () => { + 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, + ); + } + + render(); +}; + +main(); 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..608b8be04 --- /dev/null +++ b/packages/sdk/browser/example/tsdown.config.ts @@ -0,0 +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: 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)); + }); + }, +});