diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 000000000..d462c7d66 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,464 @@ +# Migrating v5 to v6 + +This guide will help you migrate your implementation from Optimizely JavaScript SDK v5 to v6. The new version introduces several architectural changes that provide more flexibility and control over SDK components. + +## Table of Contents + +1. [Major Changes](#major-changes) +2. [Client Initialization](#client-initialization) +3. [Project Configuration Management](#project-configuration-management) +4. [Event Processing](#event-processing) +5. [ODP Management](#odp-management) +6. [VUID Management](#vuid-management) +7. [Error Handling](#error-handling) +8. [Logging](#logging) +9. [onReady Promise Behavior](#onready-promise-behavior) +10. [Dispose of Client](#dispose-of-client) +11. [Migration Examples](#migration-examples) + +## Major Changes + +In v6, the SDK architecture has been modularized to give you more control over different components: + +- The monolithic `createInstance` call is now split into multiple factory functions +- Core functionality (project configuration, event processing, ODP, VUID, logging, and error handling) is now configured through dedicated components created via factory functions, giving you greater flexibility and control in enabling/disabling certain components and allowing optimizing the bundle size for frontend projects. +- Event dispatcher interface has been updated to use Promises +- onReady Promise behavior has changed + +## Client Initialization + +### v5 (Before) + +```javascript +import { createInstance } from '@optimizely/optimizely-sdk'; + +const optimizely = createInstance({ + sdkKey: '', + datafile: datafile, // optional + datafileOptions: { + autoUpdate: true, + updateInterval: 300000, // 5 minutes + }, + eventBatchSize: 10, + eventFlushInterval: 1000, + logLevel: LogLevel.DEBUG, + errorHandler: { handleError: (error) => console.error(error) }, + odpOptions: { + disabled: false, + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes + } +}); +``` + +### v6 (After) + +```javascript +import { + createInstance, + createPollingProjectConfigManager, + createBatchEventProcessor, + createOdpManager, + createVuidManager, + createLogger, + createErrorNotifier, + DEBUG +} from "@optimizely/optimizely-sdk"; + +// Create a project config manager +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '', + datafile: datafile, // optional + autoUpdate: true, + updateInterval: 300000, // 5 minutes in milliseconds +}); + +// Create an event processor +const eventProcessor = createBatchEventProcessor({ + batchSize: 10, + flushInterval: 1000, +}); + +// Create an ODP manager +const odpManager = createOdpManager({ + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes +}); + +// Create a VUID manager (optional) +const vuidManager = createVuidManager({ + enableVuid: true +}); + +// Create a logger +const logger = createLogger({ + level: DEBUG +}); + +// Create an error notifier +const errorNotifier = createErrorNotifier({ + handleError: (error) => console.error(error) +}); + +// Create the Optimizely client instance +const optimizely = createInstance({ + projectConfigManager, + eventProcessor, + odpManager, + vuidManager, + logger, + errorNotifier +}); +``` + +In case an invalid config is passed to `createInstance`, it returned `null` in v5. In v6, it will throw an error instead of returning null. + +## Project Configuration Management + +In v6, datafile management must be configured by passing in a `projectConfigManager`. Choose either: + +### Polling Project Config Manager + +For automatic datafile updates: + +```javascript +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '', + datafile: datafileString, // optional + autoUpdate: true, + updateInterval: 60000, // 1 minute + urlTemplate: 'https://custom-cdn.com/datafiles/%s.json' // optional +}); +``` + +### Static Project Config Manager + +When you want to manage datafile updates manually or want to use a fixed datafile: + +```javascript +const projectConfigManager = createStaticProjectConfigManager({ + datafile: datafileString, +}); +``` + +## Event Processing + +In v5, a batch event processor was enabled by default. In v6, an event processor must be instantiated and passed in +explicitly to `createInstance` via the `eventProcessor` option to enable event processing, otherwise no events will +be dispatched. v6 provides two types of event processors: + +### Batch Event Processor + +Queues events and sends them in batches: + +```javascript +const batchEventProcessor = createBatchEventProcessor({ + batchSize: 10, // optional, default is 10 + flushInterval: 1000, // optional, default 1000 for browser +}); +``` + +### Forwarding Event Processor + +Sends events immediately: + +```javascript +const forwardingEventProcessor = createForwardingEventProcessor(); +``` + +### Custom event dispatcher +In both v5 and v6, custom event dispatchers must implement the `EventDispatcher` interface. In v6, the `EventDispatcher` interface has been updated so that the `dispatchEvent` method returns a Promise instead of calling a callback. + +In v5 (Before): + +```javascript +export type EventDispatcherResponse = { + statusCode: number +} + +export type EventDispatcherCallback = (response: EventDispatcherResponse) => void + +export interface EventDispatcher { + dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void +} +``` + +In v6(After): + +```javascript +export type EventDispatcherResponse = { + statusCode?: number +} + +export interface EventDispatcher { + dispatchEvent(event: LogEvent): Promise +} +``` + +## ODP Management + +In v5, ODP functionality was configured via `odpOptions` and enabled by default. In v6, instantiate an OdpManager and pass to `createInstance` to enable ODP: + +### v5 (Before) + +```javascript +const optimizely = createInstance({ + sdkKey: '', + odpOptions: { + disabled: false, + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes + eventApiTimeout: 1000, + segmentsApiTimeout: 1000, + } +}); +``` + +### v6 (After) + +```javascript +const odpManager = createOdpManager({ + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes + eventApiTimeout: 1000, + segmentsApiTimeout: 1000, + eventBatchSize: 5, // Now configurable in browser + eventFlushInterval: 3000, // Now configurable in browser +}); + +const optimizely = createInstance({ + projectConfigManager, + odpManager +}); +``` + +To disable ODP functionality in v6, simply don't provide an ODP Manager to the client instance. + +## VUID Management + +In v6, VUID tracking is disabled by default and must be explicitly enabled by createing a vuidManager with `enableVuid` set to `true` and passing it to `createInstance`: + +```javascript +const vuidManager = createVuidManager({ + enableVuid: true, // Explicitly enable VUID tracking +}); + +const optimizely = createInstance({ + projectConfigManager, + vuidManager +}); +``` + +## Error Handling + +Error handling in v6 uses a new errorNotifier object: + +### v5 (Before) + +```javascript +const optimizely = createInstance({ + errorHandler: { + handleError: (error) => { + console.error("Custom error handler", error); + } + } +}); +``` + +### v6 (After) + +```javascript +const errorNotifier = createErrorNotifier({ + handleError: (error) => { + console.error("Custom error handler", error); + } +}); + +const optimizely = createInstance({ + projectConfigManager, + errorNotifier +}); +``` + +## Logging + +Logging in v6 is disabled by defualt, and must be enabled by passing in a logger created via a factory function: + +### v5 (Before) + +```javascript +const optimizely = createInstance({ + logLevel: LogLevel.DEBUG +}); +``` + +### v6 (After) + +```javascript +import { createLogger, DEBUG } from "@optimizely/optimizely-sdk"; + +const logger = createLogger({ + level: DEBUG +}); + +const optimizely = createInstance({ + projectConfigManager, + logger +}); +``` + +## onReady Promise Behavior + +The `onReady()` method behavior has changed in v6. In v5, onReady() fulfilled with an object that had two fields: `success` and `reason`. If the instance failed to initialize, `success` would be `false` and `reason` will contain an error message. In v6, if onReady() fulfills, that means the instance is ready to use, the fulfillment value is of unknown type and need not to be inspected. If the promise rejects, that means there was an error during initialization. + +### v5 (Before) + +```javascript +optimizely.onReady().then(({ success, reason }) => { + if (success) { + // optimizely is ready to use + } else { + console.log(`initialization unsuccessful: ${reason}`); + } +}); +``` + +### v6 (After) + +```javascript +optimizely + .onReady() + .then(() => { + // optimizely is ready to use + console.log("Client is ready"); + }) + .catch((err) => { + console.error("Error initializing Optimizely client:", err); + }); +``` + +## Migration Examples + +### Basic Example with SDK Key + +#### v5 (Before) + +```javascript +import { createInstance } from '@optimizely/optimizely-sdk'; + +const optimizely = createInstance({ + sdkKey: '' +}); + +optimizely.onReady().then(({ success }) => { + if (success) { + // Use the client + } +}); +``` + +#### v6 (After) + +```javascript +import { + createInstance, + createPollingProjectConfigManager +} from '@optimizely/optimizely-sdk'; + +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '' +}); + +const optimizely = createInstance({ + projectConfigManager +}); + +optimizely + .onReady() + .then(() => { + // Use the client + }) + .catch(err => { + console.error(err); + }); +``` + +### Complete Example with ODP and Event Batching + +#### v5 (Before) + +```javascript +import { createInstance, LogLevel } from '@optimizely/optimizely-sdk'; + +const optimizely = createInstance({ + sdkKey: '', + datafileOptions: { + autoUpdate: true, + updateInterval: 60000 // 1 minute + }, + eventBatchSize: 3, + eventFlushInterval: 10000, // 10 seconds + logLevel: LogLevel.DEBUG, + odpOptions: { + segmentsCacheSize: 10, + segmentsCacheTimeout: 60000 // 1 minute + } +}); + +optimizely.notificationCenter.addNotificationListener( + enums.NOTIFICATION_TYPES.TRACK, + (payload) => { + console.log("Track event", payload); + } +); +``` + +#### v6 (After) + +```javascript +import { + createInstance, + createPollingProjectConfigManager, + createBatchEventProcessor, + createOdpManager, + createLogger, + DEBUG, + NOTIFICATION_TYPES +} from '@optimizely/optimizely-sdk'; + +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '', + autoUpdate: true, + updateInterval: 60000 // 1 minute +}); + +const batchEventProcessor = createBatchEventProcessor({ + batchSize: 3, + flushInterval: 10000, // 10 seconds +}); + +const odpManager = createOdpManager({ + segmentsCacheSize: 10, + segmentsCacheTimeout: 60000 // 1 minute +}); + +const logger = createLogger({ + level: DEBUG +}); + +const optimizely = createInstance({ + projectConfigManager, + eventProcessor: batchEventProcessor, + odpManager, + logger +}); + +optimizely.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.TRACK, + (payload) => { + console.log("Track event", payload); + } +); +``` + +For complete implementation examples, refer to the [Optimizely JavaScript SDK documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-browser-sdk-v6). diff --git a/README.md b/README.md index 9e16f6cd7..67ac9e583 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk) [![license](https://img.shields.io/github/license/optimizely/javascript-sdk.svg)](https://choosealicense.com/licenses/apache-2.0/) -This repository houses the JavaScript SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). +This repository houses the JavaScript SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). The SDK now features a modular architecture for greater flexibility and control. If you're upgrading from a previous version, see our [Migration Guide](MIGRATION.md). Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at [Optimizely.com](https://www.optimizely.com/products/experiment/feature-experimentation/), or see the [developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/introduction). @@ -21,6 +21,7 @@ Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feat > For **Node.js** applications, refer to the [JavaScript (Node) variant of the developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-node-sdk). > For **Edge Functions**, we provide starter kits that utilize the Optimizely JavaScript SDK for the following platforms: +> > - [Akamai (Edgeworkers)](https://github.com/optimizely/akamai-edgeworker-starter-kit) > - [AWS Lambda@Edge](https://github.com/optimizely/aws-lambda-at-edge-starter-kit) > - [Cloudflare Worker](https://github.com/optimizely/cloudflare-worker-template) @@ -32,95 +33,146 @@ Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feat ### Prerequisites Ensure the SDK supports all of the platforms you're targeting. In particular, the SDK targets modern ES6-compliant JavaScript environments. We officially support: + - Node.js >= 18.0.0. By extension, environments like AWS Lambda, Google Cloud Functions, and Auth0 Webtasks are supported as well. Older Node.js releases likely work too (try `npm test` to validate for yourself), but are not formally supported. - Modern Web Browsers, such as Microsoft Edge 84+, Firefox 91+, Safari 13+, and Chrome 102+, Opera 76+ In addition, other environments are likely compatible but are not formally supported including: + - Progressive Web Apps, WebViews, and hybrid mobile apps like those built with React Native and Apache Cordova. - [Cloudflare Workers](https://developers.cloudflare.com/workers/) and [Fly](https://fly.io/), both of which are powered by recent releases of V8. - Anywhere else you can think of that might embed a JavaScript engine. The sky is the limit; experiment everywhere! 🚀 - ### Install the SDK Once you've validated that the SDK supports the platforms you're targeting, fetch the package from [NPM](https://www.npmjs.com/package/@optimizely/optimizely-sdk): Using `npm`: + ```sh npm install --save @optimizely/optimizely-sdk ``` Using `yarn`: + ```sh yarn add @optimizely/optimizely-sdk ``` Using `pnpm`: + ```sh pnpm add @optimizely/optimizely-sdk ``` Using `deno` (no installation required): + ```javascript -import optimizely from "npm:@optimizely/optimizely-sdk" +import optimizely from 'npm:@optimizely/optimizely-sdk'; ``` -## Use the JavaScript SDK (Browser) -See the [Optimizely Feature Experimentation developer documentation for JavaScript (Browser)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) to learn how to set up your first JavaScript project and use the SDK for client-side applications. +## Use the JavaScript SDK -### Initialization (Browser) +See the [Optimizely Feature Experimentation developer documentation for JavaScript](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) to learn how to set up your first JavaScript project and use the SDK for client-side applications. -The package has different entry points for different environments. The browser entry point is an ES module, which can be used with an appropriate bundler like **Webpack** or **Rollup**. Additionally, for ease of use during initial evaluations you can include a standalone umd bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): +The SDK uses a modular architecture with dedicated components for project configuration, event processing, and more. The examples below demonstrate the recommended initialization pattern. -```html - - - - -``` - -When evaluated, that bundle assigns the SDK's exports to `window.optimizelySdk`. If you wish to use the asset locally (for example, if unpkg is down), you can find it in your local copy of the package at dist/optimizely.browser.umd.min.js. We do not recommend using this method in production settings as it introduces a third-party performance dependency. - -As `window.optimizelySdk` should be a global variable at this point, you can continue to use it like so: +### Initialization with Package Managers (npm, yarn, pnpm) ```javascript -const optimizelyClient = window.optimizelySdk.createInstance({ +import { + createInstance, + createPollingProjectConfigManager, + createBatchEventProcessor, + createOdpManager, +} from '@optimizely/optimizely-sdk'; + +// 1. Configure your project config manager +const pollingConfigManager = createPollingProjectConfigManager({ sdkKey: '', - // datafile: window.optimizelyDatafile, - // etc. + autoUpdate: true, // Optional: enable automatic updates + updateInterval: 300000, // Optional: update every 5 minutes (in ms) }); -optimizelyClient.onReady().then(({ success, reason }) => { - if (success) { - // Create the Optimizely user context, make decisions, and more here! - } +// 2. Create an event processor for analytics +const batchEventProcessor = createBatchEventProcessor({ + batchSize: 10, // Optional: default batch size + flushInterval: 1000, // Optional: flush interval in ms }); -``` -Regarding `EventDispatcher`s: In Node.js and browser environments, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) modules and by [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Browser_compatibility), respectively. In all other environments, you must supply your own `EventDispatcher`. +// 3. Set up ODP manager for segments and audience targeting +const odpManager = createOdpManager(); -## Use the JavaScript SDK (Node) +// 4. Initialize the Optimizely client with the components +const optimizelyClient = createInstance({ + projectConfigManager: pollingConfigManager, + eventProcessor: batchEventProcessor, + odpManager: odpManager, +}); -See the [Optimizely Feature Experimentation developer documentation for JavaScript (Node)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-node-sdk) to learn how to set up your first JavaScript project and use the SDK for server-side applications. +optimizelyClient + .onReady() + .then(() => { + console.log('Optimizely client is ready'); + // Your application code using Optimizely goes here + }) + .catch(error => { + console.error('Error initializing Optimizely client:', error); + }); +``` -### Initialization (Node) +### Initialization (Using HTML) -The package has different entry points for different environments. The node entry point is CommonJS module. +The package has different entry points for different environments. The browser entry point is an ES module, which can be used with an appropriate bundler like **Webpack** or **Rollup**. Additionally, for ease of use during initial evaluations you can include a standalone umd bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): -```javascript -const optimizelySdk = require('@optimizely/optimizely-sdk'); +```html + -const optimizelyClient = optimizelySdk.createInstance({ - sdkKey: '', - // datafile: window.optimizelyDatafile, - // etc. -}); + + +``` -optimizelyClient.onReady().then(({ success, reason }) => { - if (success) { - // Create the Optimizely user context, make decisions, and more here! - } -}); +When evaluated, that bundle assigns the SDK's exports to `window.optimizelySdk`. If you wish to use the asset locally (for example, if unpkg is down), you can find it in your local copy of the package at dist/optimizely.browser.umd.min.js. We do not recommend using this method in production settings as it introduces a third-party performance dependency. + +As `window.optimizelySdk` should be a global variable at this point, you can continue to use it like so: + +```html + ``` Regarding `EventDispatcher`s: In Node.js environment, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) module. @@ -165,9 +217,12 @@ For more information regarding contributing to the Optimizely JavaScript SDK, pl ## Special Notes -### Migrating from 4.x.x +### Migration Guides + +If you're updating your SDK version, please check the appropriate migration guide: -This version represents a major version change and, as such, introduces some breaking changes. Please refer to the [Changelog](CHANGELOG.md#500---january-19-2024) for more details. +- **Migrating from 5.x to 6.x**: See our [Migration Guide](MIGRATION.md) for detailed instructions on updating to the new modular architecture. +- **Migrating from 4.x to 5.x**: Please refer to the [Changelog](CHANGELOG.md#500---january-19-2024) for details on these breaking changes. ### Feature Management access @@ -201,4 +256,4 @@ First-party code (under `packages/optimizely-sdk/lib/`, `packages/datafile-manag - Ruby - https://github.com/optimizely/ruby-sdk -- Swift - https://github.com/optimizely/swift-sdk \ No newline at end of file +- Swift - https://github.com/optimizely/swift-sdk