Skip to content

Commit 42fbd41

Browse files
authored
chore(browser): reduce bundle size by 6.6 KB (-3.7%) (#3253)
1 parent f4b928c commit 42fbd41

32 files changed

+471
-532
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'posthog-js': patch
3+
---
4+
5+
Reduce browser SDK bundle size by ~6.6 KB (-3.7%) through code modernization, build config tuning, string deduplication, enum-to-const conversions, and property access shorthand getters.

packages/browser/rollup.config.mjs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const plugins = (es5, noExternal) => [
6868
babelHelpers: 'bundled',
6969
plugins: [
7070
'@babel/plugin-transform-nullish-coalescing-operator',
71-
// Explicitly included so we transform 1 ** 2 to Math.pow(1, 2) for ES6 compatability
71+
// Explicitly included so we transform 1 ** 2 to Math.pow(1, 2) for ES6 compatibility
7272
'@babel/plugin-transform-exponentiation-operator',
7373
],
7474
presets: [
@@ -103,6 +103,13 @@ const plugins = (es5, noExternal) => [
103103
toplevel: true,
104104
compress: {
105105
ecma: es5 ? 5 : 6,
106+
passes: 2,
107+
pure_getters: true,
108+
unsafe_methods: true,
109+
unsafe_comps: true,
110+
unsafe_math: true,
111+
unsafe_proto: true,
112+
unsafe_regexp: true,
106113
},
107114
format: {
108115
comments: false,

packages/browser/src/consent.ts

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import { PostHog } from './posthog-core'
2-
import { find } from './utils'
2+
import { COOKIELESS_ALWAYS, COOKIELESS_ON_REJECT } from './constants'
33
import { assignableWindow, navigator } from './utils/globals'
44
import { cookieStore, localStore } from './storage'
55
import { PersistentStore } from './types'
66
import { isNoLike, isYesLike } from '@posthog/core'
77

88
const OPT_OUT_PREFIX = '__ph_opt_in_out_'
99

10-
export enum ConsentStatus {
11-
PENDING = -1,
12-
DENIED = 0,
13-
GRANTED = 1,
14-
}
10+
export const ConsentStatus = {
11+
PENDING: -1,
12+
DENIED: 0,
13+
GRANTED: 1,
14+
} as const
15+
export type ConsentStatus = (typeof ConsentStatus)[keyof typeof ConsentStatus]
1516

1617
/**
1718
* ConsentManager provides tools for managing user consent as configured by the application.
@@ -34,7 +35,7 @@ export class ConsentManager {
3435
}
3536

3637
public isOptedOut() {
37-
if (this._config.cookieless_mode === 'always') {
38+
if (this._config.cookieless_mode === COOKIELESS_ALWAYS) {
3839
return true
3940
}
4041
// we are opted out if:
@@ -44,7 +45,7 @@ export class ConsentManager {
4445
return (
4546
this.consent === ConsentStatus.DENIED ||
4647
(this.consent === ConsentStatus.PENDING &&
47-
(this._config.opt_out_capturing_by_default || this._config.cookieless_mode === 'on_reject'))
48+
(this._config.opt_out_capturing_by_default || this._config.cookieless_mode === COOKIELESS_ON_REJECT))
4849
)
4950
}
5051

@@ -114,15 +115,10 @@ export class ConsentManager {
114115
if (!this._config.respect_dnt) {
115116
return false
116117
}
117-
return !!find(
118-
[
119-
navigator?.doNotTrack, // standard
120-
(navigator as any)?.['msDoNotTrack'],
121-
assignableWindow['doNotTrack'],
122-
],
123-
(dntValue): boolean => {
124-
return isYesLike(dntValue)
125-
}
126-
)
118+
return [
119+
navigator?.doNotTrack, // standard
120+
(navigator as any)?.['msDoNotTrack'],
121+
assignableWindow['doNotTrack'],
122+
].some((dntValue) => isYesLike(dntValue))
127123
}
128124
}

packages/browser/src/constants.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,28 @@ export const PERSISTENCE_RESERVED_PROPERTIES = [
112112
]
113113

114114
export const SURVEYS_REQUEST_TIMEOUT_MS = 10000
115+
export const LOAD_EXT_NOT_FOUND = 'PostHog loadExternalDependency extension not found.'
116+
117+
/* EVENT NAMES - interned to reduce bundle size */
118+
/* COOKIELESS MODE VALUES */
119+
export const COOKIELESS_ON_REJECT = 'on_reject' as const
120+
export const COOKIELESS_ALWAYS = 'always' as const
121+
122+
/* USER STATE VALUES */
123+
export const USER_STATE_ANONYMOUS = 'anonymous'
124+
export const USER_STATE_IDENTIFIED = 'identified'
125+
126+
/* PERSON PROFILE MODES */
127+
export const PERSON_PROFILES_IDENTIFIED_ONLY = 'identified_only' as const
128+
129+
/* DOM EVENT NAMES - interned to reduce bundle size */
130+
export const DOM_EVENT_VISIBILITYCHANGE = 'visibilitychange'
131+
export const DOM_EVENT_BEFOREUNLOAD = 'beforeunload'
132+
133+
export const EVENT_PAGEVIEW = '$pageview'
134+
export const EVENT_PAGELEAVE = '$pageleave'
135+
export const EVENT_IDENTIFY = '$identify'
136+
export const EVENT_GROUPIDENTIFY = '$groupidentify'
115137

116138
/* Z-INDEX HIERARCHY: tours > surveys > support */
117139
export const Z_INDEX_TOURS = 2147483646

packages/browser/src/extensions/conversations/posthog-conversations.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { LOAD_EXT_NOT_FOUND } from '../../constants'
12
import { PostHog } from '../../posthog-core'
23
import {
34
ConversationsRemoteConfig,
@@ -18,6 +19,7 @@ import { isToolbarInstance } from '../../utils'
1819
import { Extension } from '../types'
1920

2021
const logger = createLogger('[Conversations]')
22+
const NOT_AVAILABLE = 'Conversations not available yet.'
2123

2224
export type ConversationsManager = LazyLoadedConversationsInterface
2325

@@ -128,7 +130,7 @@ export class PostHogConversations implements Extension {
128130
// If we reach here, conversations code is not loaded yet
129131
const loadExternalDependency = phExtensions.loadExternalDependency
130132
if (!loadExternalDependency) {
131-
this._handleLoadError('PostHog loadExternalDependency extension not found.')
133+
this._handleLoadError(LOAD_EXT_NOT_FOUND)
132134
return
133135
}
134136

@@ -235,7 +237,7 @@ export class PostHogConversations implements Extension {
235237
newTicket?: boolean
236238
): Promise<SendMessageResponse | null> {
237239
if (!this._conversationsManager) {
238-
logger.warn('Conversations not available yet.')
240+
logger.warn(NOT_AVAILABLE)
239241
return null
240242
}
241243
return this._conversationsManager.sendMessage(message, userTraits, newTicket)
@@ -258,7 +260,7 @@ export class PostHogConversations implements Extension {
258260
*/
259261
async getMessages(ticketId?: string, after?: string): Promise<GetMessagesResponse | null> {
260262
if (!this._conversationsManager) {
261-
logger.warn('Conversations not available yet.')
263+
logger.warn(NOT_AVAILABLE)
262264
return null
263265
}
264266
return this._conversationsManager.getMessages(ticketId, after)
@@ -276,7 +278,7 @@ export class PostHogConversations implements Extension {
276278
*/
277279
async markAsRead(ticketId?: string): Promise<MarkAsReadResponse | null> {
278280
if (!this._conversationsManager) {
279-
logger.warn('Conversations not available yet.')
281+
logger.warn(NOT_AVAILABLE)
280282
return null
281283
}
282284
return this._conversationsManager.markAsRead(ticketId)
@@ -298,7 +300,7 @@ export class PostHogConversations implements Extension {
298300
*/
299301
async getTickets(options?: GetTicketsOptions): Promise<GetTicketsResponse | null> {
300302
if (!this._conversationsManager) {
301-
logger.warn('Conversations not available yet.')
303+
logger.warn(NOT_AVAILABLE)
302304
return null
303305
}
304306
return this._conversationsManager.getTickets(options)
@@ -312,7 +314,7 @@ export class PostHogConversations implements Extension {
312314
*/
313315
async requestRestoreLink(email: string): Promise<RequestRestoreLinkResponse | null> {
314316
if (!this._conversationsManager) {
315-
logger.warn('Conversations not available yet.')
317+
logger.warn(NOT_AVAILABLE)
316318
return null
317319
}
318320
return this._conversationsManager.requestRestoreLink(email)
@@ -326,7 +328,7 @@ export class PostHogConversations implements Extension {
326328
*/
327329
async restoreFromToken(restoreToken: string): Promise<RestoreFromTokenResponse | null> {
328330
if (!this._conversationsManager) {
329-
logger.warn('Conversations not available yet.')
331+
logger.warn(NOT_AVAILABLE)
330332
return null
331333
}
332334
return this._conversationsManager.restoreFromToken(restoreToken)
@@ -339,7 +341,7 @@ export class PostHogConversations implements Extension {
339341
*/
340342
async restoreFromUrlToken(): Promise<RestoreFromTokenResponse | null> {
341343
if (!this._conversationsManager) {
342-
logger.warn('Conversations not available yet.')
344+
logger.warn(NOT_AVAILABLE)
343345
return null
344346
}
345347
return this._conversationsManager.restoreFromUrlToken()

packages/browser/src/extensions/history-autocapture.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PostHog } from '../posthog-core'
2+
import { EVENT_PAGEVIEW } from '../constants'
23
import { window } from '../utils/globals'
34
import { addEventListener } from '../utils'
45
import { logger } from '../utils/logger'
@@ -95,7 +96,7 @@ export class HistoryAutocapture implements Extension {
9596

9697
// Only capture pageview if the pathname has changed and the feature is enabled
9798
if (currentPathname !== this._lastPathname && this.isEnabled) {
98-
this._instance.capture('$pageview', { navigation_type: navigationType })
99+
this._instance.capture(EVENT_PAGEVIEW, { navigation_type: navigationType })
99100
}
100101

101102
this._lastPathname = currentPathname

packages/browser/src/extensions/replay/session-recording.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
SESSION_RECORDING_OVERRIDE_EVENT_TRIGGER,
66
SESSION_RECORDING_OVERRIDE_URL_TRIGGER,
77
SESSION_RECORDING_REMOTE_CONFIG,
8+
COOKIELESS_ALWAYS,
89
} from '../../constants'
910
import { PostHog } from '../../posthog-core'
1011
import { RemoteConfigLoader } from '../../remote-config'
@@ -38,6 +39,14 @@ export class SessionRecording implements Extension {
3839

3940
private _recordingStatus: SessionRecordingStatus = DISABLED
4041

42+
private get _config() {
43+
return this._instance.config
44+
}
45+
46+
private get _persistence() {
47+
return this._instance.persistence
48+
}
49+
4150
private _persistFlagsOnSessionListener: (() => void) | undefined = undefined
4251
private _lazyLoadedSessionRecording: LazyLoadedSessionRecordingInterface | undefined
4352

@@ -58,7 +67,7 @@ export class SessionRecording implements Extension {
5867
throw new Error(LOGGER_PREFIX + ' started without valid sessionManager. This is a bug.')
5968
}
6069

61-
if (this._instance.config.cookieless_mode === 'always') {
70+
if (this._config.cookieless_mode === COOKIELESS_ALWAYS) {
6271
throw new Error(LOGGER_PREFIX + ' cannot be used with cookieless_mode="always"')
6372
}
6473
}
@@ -69,8 +78,8 @@ export class SessionRecording implements Extension {
6978

7079
private get _isRecordingEnabled() {
7180
const enabled_server_side = !!this._instance.get_property(SESSION_RECORDING_REMOTE_CONFIG)?.enabled
72-
const enabled_client_side = !this._instance.config.disable_session_recording
73-
const isDisabled = this._instance.config.disable_session_recording || this._instance.consent.isOptedOut()
81+
const enabled_client_side = !this._config.disable_session_recording
82+
const isDisabled = this._config.disable_session_recording || this._instance.consent.isOptedOut()
7483
return window && enabled_server_side && enabled_client_side && !isDisabled
7584
}
7685

@@ -148,7 +157,7 @@ export class SessionRecording implements Extension {
148157
}
149158

150159
private _resetSampling() {
151-
this._instance.persistence?.unregister(SESSION_RECORDING_IS_SAMPLED)
160+
this._persistence?.unregister(SESSION_RECORDING_IS_SAMPLED)
152161
}
153162

154163
private _validateSampleRate(rate: unknown, source: string): number | null {
@@ -164,15 +173,15 @@ export class SessionRecording implements Extension {
164173
}
165174

166175
private _persistRemoteConfig(response: RemoteConfig): void {
167-
if (this._instance.persistence) {
168-
const persistence = this._instance.persistence
176+
if (this._persistence) {
177+
const persistence = this._persistence
169178

170179
const persistResponse = () => {
171180
const sessionRecordingConfigResponse =
172181
response.sessionRecording === false ? undefined : response.sessionRecording
173182

174183
const localSampleRate = this._validateSampleRate(
175-
this._instance.config.session_recording?.sampleRate,
184+
this._config.session_recording?.sampleRate,
176185
'session_recording.sampleRate'
177186
)
178187
const remoteSampleRate = this._validateSampleRate(
@@ -317,7 +326,7 @@ export class SessionRecording implements Extension {
317326
* */
318327
public overrideLinkedFlag() {
319328
if (!this._lazyLoadedSessionRecording) {
320-
this._instance.persistence?.register({
329+
this._persistence?.register({
321330
[SESSION_RECORDING_OVERRIDE_LINKED_FLAG]: true,
322331
})
323332
}
@@ -333,7 +342,7 @@ export class SessionRecording implements Extension {
333342
* */
334343
public overrideSampling() {
335344
if (!this._lazyLoadedSessionRecording) {
336-
this._instance.persistence?.register({
345+
this._persistence?.register({
337346
[SESSION_RECORDING_OVERRIDE_SAMPLING]: true,
338347
})
339348
}
@@ -349,7 +358,7 @@ export class SessionRecording implements Extension {
349358
* */
350359
public overrideTrigger(triggerType: TriggerType) {
351360
if (!this._lazyLoadedSessionRecording) {
352-
this._instance.persistence?.register({
361+
this._persistence?.register({
353362
[triggerType === 'url'
354363
? SESSION_RECORDING_OVERRIDE_URL_TRIGGER
355364
: SESSION_RECORDING_OVERRIDE_EVENT_TRIGGER]: true,

packages/browser/src/extensions/segment-integration.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import { PostHog } from '../posthog-core'
2020
import { createLogger } from '../utils/logger'
2121

22-
import { USER_STATE } from '../constants'
22+
import { EVENT_IDENTIFY, EVENT_PAGEVIEW, USER_STATE, USER_STATE_IDENTIFIED } from '../constants'
2323
import { isFunction } from '@posthog/core'
2424
import { uuidv7 } from '../uuidv7'
2525

@@ -63,8 +63,8 @@ const createSegmentIntegration = (posthog: PostHog): SegmentPlugin => {
6363
// eslint-disable-next-line compat/compat
6464
load: () => Promise.resolve(),
6565
track: (ctx) => enrichEvent(ctx, ctx.event.event),
66-
page: (ctx) => enrichEvent(ctx, '$pageview'),
67-
identify: (ctx) => enrichEvent(ctx, '$identify'),
66+
page: (ctx) => enrichEvent(ctx, EVENT_PAGEVIEW),
67+
identify: (ctx) => enrichEvent(ctx, EVENT_IDENTIFY),
6868
screen: (ctx) => enrichEvent(ctx, '$screen'),
6969
}
7070
}
@@ -86,7 +86,7 @@ function setupPostHogFromSegment(posthog: PostHog, done: () => void) {
8686
distinct_id: user.id(),
8787
$device_id: getSegmentAnonymousId(),
8888
})
89-
posthog.persistence!.set_property(USER_STATE, 'identified')
89+
posthog.persistence!.set_property(USER_STATE, USER_STATE_IDENTIFIED)
9090
}
9191

9292
done()

packages/browser/src/extensions/toolbar.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,8 @@ const LOCALSTORAGE_KEY = '_postHogToolbarParams'
1818

1919
const logger = createLogger('[Toolbar]')
2020

21-
enum ToolbarState {
22-
UNINITIALIZED = 0,
23-
LOADING = 1,
24-
LOADED = 2,
25-
}
21+
const ToolbarState = { UNINITIALIZED: 0, LOADING: 1, LOADED: 2 } as const
22+
type ToolbarState = (typeof ToolbarState)[keyof typeof ToolbarState]
2623

2724
export class Toolbar implements Extension {
2825
instance: PostHog

0 commit comments

Comments
 (0)