Skip to content

Commit b7d8563

Browse files
authored
Add architecture documentation (#1319)
1 parent e0db020 commit b7d8563

File tree

5 files changed

+226
-103
lines changed

5 files changed

+226
-103
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ This library is one of Segment’s most popular Flagship libraries. It is active
3535

3636
- Contribution guidelines: [CONTRIBUTING.md](CONTRIBUTING.md)
3737
- Development instructions: [DEVELOPMENT.md](DEVELOPMENT.md)
38+
- Releasing (to npm): [RELEASING.md](RELEASING.md)

packages/browser/ARCHITECTURE.md

Lines changed: 0 additions & 51 deletions
This file was deleted.

packages/browser/README.md

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Analytics Next (aka Analytics 2.0) is the latest version of Segment’s JavaScri
77
- [🏎️ Quickstart](#-quickstart)
88
- [💡 Using with Segment](#-using-with-segment)
99
- [💻 Using as an `npm` package](#-using-as-an-npm-package)
10-
- [🔌 Plugins](#-plugins)
10+
- [🔌 Architecture](#-architecture--plugins)
1111
- [🐒 Development](#-development)
1212

1313
---
@@ -187,6 +187,8 @@ declare global {
187187
}
188188
```
189189

190+
## 🔌 Architecture & Plugins
191+
- See [ARCHITECTURE.md](architecture/ARCHITECTURE.md)
190192

191193
## 🐒 Development
192194

@@ -207,56 +209,5 @@ Then, make your changes and test them out in the test app!
207209

208210
<img src="https://user-images.githubusercontent.com/2866515/135407053-7561d522-b969-484d-8d3a-6f1c4d9c025b.gif" alt="Example of the development app" width="500px">
209211

210-
# 🔌 Plugins
211-
212-
When developing against Analytics Next you will likely be writing plugins, which can augment functionality and enrich data. Plugins are isolated chunks which you can build, test, version, and deploy independently of the rest of the codebase. Plugins are bounded by Analytics Next which handles things such as observability, retries, and error management.
213-
214-
Plugins can be of two different priorities:
215-
216-
1. **Critical**: Analytics Next should expect this plugin to be loaded before starting event delivery
217-
2. **Non-critical**: Analytics Next can start event delivery before this plugin has finished loading
218-
219-
and can be of five different types:
220-
221-
1. **Before**: Plugins that need to be run before any other plugins are run. An example of this would be validating events before passing them along to other plugins.
222-
2. **After**: Plugins that need to run after all other plugins have run. An example of this is the segment.io integration, which will wait for destinations to succeed or fail so that it can send its observability metrics.
223-
3. **Destination**: Destinations to send the event to (ie. legacy destinations). Does not modify the event and failure does not halt execution.
224-
4. **Enrichment**: Modifies an event, failure here could halt the event pipeline.
225-
5. **Utility**: Plugins that change Analytics Next functionality and don't fall into the other categories.
226-
227-
Here is an example of a simple plugin that would convert all track events event names to lowercase before the event gets sent through the rest of the pipeline:
228-
229-
```ts
230-
import type { Plugin } from '@segment/analytics-next'
231-
232-
export const lowercase: Plugin = {
233-
name: 'Lowercase Event Name',
234-
type: 'before',
235-
version: '1.0.0',
236-
237-
isLoaded: () => true,
238-
load: () => Promise.resolve(),
239-
240-
track: (ctx) => {
241-
ctx.event.event = ctx.event.event.toLowerCase()
242-
return ctx
243-
}
244-
}
245-
246-
analytics.register(lowercase)
247-
```
248-
249-
For further examples check out our [existing plugins](/packages/browser/src/plugins).
250-
251-
## 🧪 QA
252-
Feature work and bug fixes should include tests. Run all [Jest](https://jestjs.io) tests:
253-
```
254-
$ yarn test
255-
```
256-
Lint all with [ESLint](https://github.com/typescript-eslint/typescript-eslint/):
257-
```
258-
$ yarn lint
259-
```
260-
261212

262213

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
## Analytics.js Plugin Architecture
2+
3+
> [!IMPORTANT]
4+
> This doc may get out-of-date. Please prefer to use and link to Segment documentation for the most up-to-date information. It would be advisable to move this doc to https://segment.com/docs/connections/sources/catalog/libraries/website/javascript, so there is a single source of truth.
5+
6+
7+
### You can use the [vscode mermaid extension](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid) to preview the following diagram code block inside of vscode, or copy and paste into the [mermaid live editor](https://mermaid.live/).
8+
9+
## Table of Contents
10+
- [Initialization Flow](#initialization-flow)
11+
- [Plugin Types and Middleware](#plugin-types-and-middleware)
12+
13+
14+
### Analytics.js Initialization Flow
15+
The following diagram illustrates how Analytics.js bootstraps and initializes:
16+
17+
```mermaid
18+
graph TD
19+
subgraph Load Phase
20+
Start([Web Page Load]) --> LoadAJS[Fetch analytics.js<br/>from CDN]
21+
LoadAJS --> InitAJS[analytics.load<br/>with writeKey]
22+
InitAJS --> FetchSettings[Fetch settings<br/>from cdn.segment.io]
23+
end
24+
25+
subgraph Plugin Registration Phase
26+
FetchSettings --> RegisterCore[Register Core Plugins<br/>and Middleware]
27+
RegisterCore --> ParseDest[Parse Device Mode<br/>Destinations]
28+
end
29+
30+
subgraph Destination Loading Phase
31+
ParseDest --> LoadDests{Load Device Mode<br/>Destinations}
32+
LoadDests -->|Async| Dest1[Load Destination 1<br/>+ Scripts]
33+
LoadDests -->|Async| Dest2[Load Destination 2<br/>+ Scripts]
34+
LoadDests -->|Async| DestN[Load Destination N<br/>+ Scripts]
35+
end
36+
37+
subgraph Event Processing Phase
38+
Dest1 --> Ready1[Destination 1<br/>Ready]
39+
Dest2 --> Ready2[Destination 2<br/>Ready]
40+
DestN --> ReadyN[Destination N<br/>Ready]
41+
42+
Ready1 --> Flush1[Flush Events to<br/>Destination 1]
43+
Ready2 --> Flush2[Flush Events to<br/>Destination 2]
44+
ReadyN --> FlushN[Flush Events to<br/>Destination N]
45+
end
46+
47+
RegisterCore -.->|Events Blocked<br/>Until Complete| ParseDest
48+
LoadDests -.->|Critical Plugins<br/>Block Events| EventReady[Event Pipeline Ready]
49+
50+
51+
52+
class Dest1,Dest2,DestN,Flush1,Flush2,FlushN async
53+
```
54+
55+
56+
Key Points:
57+
1. **Load Phase**
58+
- Analytics.js is fetched from CDN
59+
- Initialized with writeKey that identifies your Segment source
60+
- CDN settings are fetched containing destination configurations
61+
62+
2. **Plugin Registration Phase**
63+
- Core plugins and middleware are registered synchronously
64+
- Event queue is blocked until this phase completes
65+
- Device mode destinations are identified from CDN settings
66+
67+
3. **Destination Loading Phase**
68+
- Device mode destinations are loaded in parallel
69+
- Each destination loads its own third-party scripts
70+
- Critical plugins (if any) must complete registration
71+
72+
4. **Event Processing Phase**
73+
- Destinations become ready independently
74+
- Buffered events are flushed to each destination in parallel
75+
- Non-critical destinations can receive events while others are still loading
76+
77+
78+
79+
### Event Flow
80+
```mermaid
81+
graph TD
82+
subgraph Event Creation
83+
UserAction[analytics.track/page/identify] --> EventCreate[Event Factory]
84+
EventCreate --> Queue[Event Queue]
85+
end
86+
87+
subgraph Plugin Pipeline
88+
Queue --> BeforePlugins[Before Plugins e.g add page context]
89+
BeforePlugins --> EnrichPlugins[Enrichment Plugins]
90+
EnrichPlugins --> DestPlugins[Destination Plugins e.g Segment.io]
91+
DestPlugins --> AfterPlugins[After Plugins]
92+
end
93+
94+
subgraph Plugin Types Details
95+
BeforeDetails[Before Plugins<br/>Priority: Critical<br/>Example: Event Validation] --- BeforePlugins
96+
EnrichDetails[Enrichment Plugins<br/>Priority: Critical<br/>Parallel Execution<br/>Can Modify Events<br/>Example: Add User Agent] --- EnrichPlugins
97+
DestDetails[Destination Plugins<br/>Parallel Execution<br/>Cannot Modify Events<br/>Example: Google Analytics] --- DestPlugins
98+
AfterDetails[After Plugins<br/>Example: Metrics Collection] --- AfterPlugins
99+
end
100+
101+
subgraph Notes
102+
Priorities[Plugin Priorities]
103+
Critical[Critical - Must load before<br/>event delivery starts]
104+
NonCritical[Non-Critical - Can load<br/>after event delivery starts]
105+
Priorities --> Critical
106+
Priorities --> NonCritical
107+
end
108+
```
109+
110+
111+
### Plugin Types and Middleware
112+
[This information is also available in the Segment documentation](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/#plugins-and-source-middleware)
113+
114+
- **Source Middleware** (see [Example](#example-source-middleware-implementation))
115+
- **Source Middleware is just a light API wrapper around a "Before" type plugin Plugin**
116+
- Source Middleware is the legacy API (pre-analytics next). It's less verbose than the full plugin API, but a bit less powerful. It is functionally equivalent to a "Before" type plugin.
117+
118+
- **Before Plugins** (see [Example](#example-plugin-implementation))
119+
- Run before any other plugins
120+
- Critical priority - block event pipeline until `.load()` resolves
121+
- Use cases: Event validation, data transformation
122+
- Example: Event validation before passing to other plugins)
123+
124+
125+
- **Enrichment Plugins**
126+
- Functionally Identitical to "before" plugins, but run after them. Before plugins are typically used internally (e.g adding page info), but there's no hard and fast rule.
127+
128+
- **Destination Plugins**
129+
- Run after enrichment
130+
- Cannot modify the event
131+
- Execute in parallel
132+
- Failures do not halt pipeline
133+
- Example: Segment.io, Google Analytics, Mixpanel
134+
135+
- **After Plugins (uncommon)**
136+
- Run after all other plugins complete
137+
- Use cases: Metrics, logging
138+
- Example: segment.io plugin for observability metrics
139+
140+
- **Utility Plugins**
141+
- Executes only once during the analytics.js bootstrap. Gives you access to the analytics instance using the plugin's load() method. This doesn't allow you to modify events.
142+
- Do not directly process events
143+
- Example: some plugin that registers a bunch of analytics event listeners (e.g. analytics.on('track', ...) and reports them to an external system)
144+
145+
- **Destination Middleware** (See [Example](#example-destination-middleware-implementation))
146+
- A special type of plugin that allows you to add a plugin that only affects a specific (device mode) destination plugin.
147+
148+
149+
### Example: Plugin Implementation
150+
```ts
151+
analytics.register({
152+
name: 'My Plugin',
153+
type: 'before',
154+
isLoaded: () => true,
155+
load: () => Promise.resolve(),
156+
// lowercase all track event names
157+
track: (ctx) => {
158+
ctx.event.event = ctx.event.event.toLowerCase()
159+
return ctx
160+
},
161+
// drop page events with a specific title
162+
page: (ctx) => {
163+
if (ctx.properties.title === 'some title') {
164+
return null
165+
}
166+
return ctx
167+
}
168+
})
169+
```
170+
### Example: Source Middleware Implementation
171+
```ts
172+
analytics.addSourceMiddleware(({ payload, next }) => {
173+
const { event } = payload.obj.context
174+
if (event.type === 'track') {
175+
// change the event name to lowercase
176+
event.event = event.event.toLowerCase()
177+
} else if (event.type === 'page') {
178+
// drop any page events with a specific title
179+
if (event.properties.title === 'some title') {
180+
return null
181+
}
182+
}
183+
next(payload)
184+
})
185+
```
186+
187+
### Example: Destination Middleware Implementation
188+
> [!NOTE]
189+
> It is not currently possible to add a destination middleware to the Segment.io destination.
190+
```ts
191+
analytics.addDestinationMiddleware('amplitude', ({ next, payload }) => {
192+
payload.obj.properties!.hello = 'from the other side'
193+
next(payload)
194+
})
195+
```
196+
or, to apply to all destinations
197+
```ts
198+
analytics.addDestinationMiddleware('*', ({ next, payload }) => {
199+
...
200+
})
201+
```
202+
203+
204+
### Event Flow Example
205+
206+
When `analytics.track()` is called:
207+
208+
1. Event is created via Event Factory
209+
2. Event enters the queue
210+
3. Before plugins run, in order of registration
211+
3. Enrichment plugins run, in order of registration
212+
4. Destination plugins receive the event in parallel (including Segment.io plugin)
213+
5. Any after plugins handle post-processing (e.g. metrics collection)
214+
215+
### Plugin Priorities
216+
217+
- **Critical Plugins**: Must be loaded before event delivery starts
218+
- Example: Before plugins, Validation plugins
219+
- **Non-Critical Plugins**: Can load after event delivery begins
220+
- Example: Destination plugins
221+
222+
377 KB
Loading

0 commit comments

Comments
 (0)