Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dry-buckets-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-js': minor
---

feat: Introduce internal config to improve tree-shaking
15 changes: 15 additions & 0 deletions packages/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@
"react/package.json",
"react/surveys/package.json"
],
"exports": {
".": {
"import": "./dist/module.js",
"require": "./dist/main.js",
"types": "./dist/module.d.ts"
},
"./slim": {
"import": "./dist/module.slim.js",
"types": "./dist/module.slim.d.ts"
},
"./extensions": {
"import": "./lib/src/extensions/extension-bundles.js",
"types": "./lib/src/extensions/extension-bundles.d.ts"
}
},
"dependencies": {
"@posthog/core": "workspace:*",
"@posthog/types": "workspace:*",
Expand Down
89 changes: 89 additions & 0 deletions packages/browser/src/__tests__/extension-classes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { PostHog } from '../posthog-core'
import { PostHogConfig } from '../types'
import { AllExtensions } from '../extensions/extension-bundles'
import { Autocapture } from '../autocapture'
import { SessionRecording } from '../extensions/replay/session-recording'
import { createPosthogInstance } from './helpers/posthog-instance'

describe('__extensionClasses enrollment', () => {
let savedDefaults: PostHogConfig['__extensionClasses']

beforeEach(() => {
savedDefaults = PostHog.__defaultExtensionClasses
console.error = jest.fn()
})

afterEach(() => {
PostHog.__defaultExtensionClasses = savedDefaults
})

it('initializes only extensions provided via __extensionClasses', async () => {
PostHog.__defaultExtensionClasses = {}

const posthog = await createPosthogInstance(undefined, {
__preview_deferred_init_extensions: false,
__extensionClasses: { autocapture: Autocapture, sessionRecording: SessionRecording },
capture_pageview: false,
})

expect(posthog.autocapture).toBeDefined()
expect(posthog.sessionRecording).toBeDefined()

expect(posthog.heatmaps).toBeUndefined()
expect(posthog.exceptionObserver).toBeUndefined()
expect(posthog.deadClicksAutocapture).toBeUndefined()
expect(posthog.webVitalsAutocapture).toBeUndefined()
expect(posthog.productTours).toBeUndefined()
expect(posthog.siteApps).toBeUndefined()
})

it('initializes no extensions when none are provided and no defaults exist', async () => {
PostHog.__defaultExtensionClasses = {}

const posthog = await createPosthogInstance(undefined, {
__preview_deferred_init_extensions: false,
capture_pageview: false,
})

expect(posthog.autocapture).toBeUndefined()
expect(posthog.sessionRecording).toBeUndefined()
expect(posthog.heatmaps).toBeUndefined()
expect(posthog.exceptionObserver).toBeUndefined()
expect(posthog.deadClicksAutocapture).toBeUndefined()
expect(posthog.webVitalsAutocapture).toBeUndefined()
expect(posthog.productTours).toBeUndefined()
expect(posthog.siteApps).toBeUndefined()
})

it('__extensionClasses overrides __defaultExtensionClasses', async () => {
PostHog.__defaultExtensionClasses = AllExtensions

class MockAutocapture extends Autocapture {}

const posthog = await createPosthogInstance(undefined, {
__preview_deferred_init_extensions: false,
__extensionClasses: { autocapture: MockAutocapture },
capture_pageview: false,
})

expect(posthog.autocapture).toBeInstanceOf(MockAutocapture)
})

it('default extensions are used when __extensionClasses is not provided', async () => {
PostHog.__defaultExtensionClasses = AllExtensions

const posthog = await createPosthogInstance(undefined, {
__preview_deferred_init_extensions: false,
capture_pageview: false,
})

expect(posthog.autocapture).toBeDefined()
expect(posthog.sessionRecording).toBeDefined()
expect(posthog.heatmaps).toBeDefined()
expect(posthog.exceptionObserver).toBeDefined()
expect(posthog.deadClicksAutocapture).toBeDefined()
expect(posthog.webVitalsAutocapture).toBeDefined()
expect(posthog.productTours).toBeDefined()
expect(posthog.siteApps).toBeDefined()
})
})
1 change: 1 addition & 0 deletions packages/browser/src/__tests__/helpers/posthog-instance.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// The library depends on having the module initialized before it can be used.
import '../../entrypoints/default-extensions'

import { PostHog, init_as_module } from '../../posthog-core'
import { PostHogConfig } from '../../types'
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/entrypoints/array.no-external.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './default-extensions'
import { init_from_snippet } from '../posthog-core'

init_from_snippet()
6 changes: 6 additions & 0 deletions packages/browser/src/entrypoints/default-extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { PostHog } from '../posthog-core'
import { AllExtensions } from '../extensions/extension-bundles'

PostHog.__defaultExtensionClasses = {
...AllExtensions,
}
1 change: 1 addition & 0 deletions packages/browser/src/entrypoints/main.cjs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './default-extensions'
import './external-scripts-loader'
import { init_as_module } from '../posthog-core'
export { PostHog } from '../posthog-core'
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/entrypoints/module.no-external.es.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './default-extensions'
import { init_as_module } from '../posthog-core'
export { PostHog } from '../posthog-core'
export * from '../types'
Expand Down
8 changes: 8 additions & 0 deletions packages/browser/src/entrypoints/module.slim.es.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* eslint-disable-next-line no-console */
console.warn(
'[PostHog Experimental] The slim module is experimental and may break or change on any release. Please use with caution and report any issues you encounter.'
)
import './external-scripts-loader'
import posthog from './module.slim.no-external.es'
export * from './module.slim.no-external.es'
export default posthog
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { init_as_module } from '../posthog-core'
export { PostHog } from '../posthog-core'
export * from '../types'
export * from '../posthog-surveys-types'
export * from '../posthog-product-tours-types'
export * from '../posthog-conversations-types'
export const posthog = init_as_module()
export default posthog
80 changes: 80 additions & 0 deletions packages/browser/src/extensions/extension-bundles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Pre-grouped extension bundles for tree-shaking support.
*
* Use these with `__extensionClasses` to control which extensions are included in your bundle.
* The default `posthog-js` entrypoint includes all extensions. When using `posthog-js/slim`,
* you can import only the bundles you need:
*
* @example
* ```ts
* import posthog from 'posthog-js/slim'
* import { ReplayExtensions, AnalyticsExtensions } from 'posthog-js/extensions'
*
* posthog.init('ph_key', {
* __extensionClasses: {
* ...ReplayExtensions,
* ...AnalyticsExtensions,
* }
* })
* ```
*
* @module
*/

import { Autocapture } from '../autocapture'
import { DeadClicksAutocapture } from './dead-clicks-autocapture'
import { ExceptionObserver } from './exception-autocapture'
import { HistoryAutocapture } from './history-autocapture'
import { TracingHeaders } from './tracing-headers'
import { WebVitalsAutocapture } from './web-vitals'
import { SessionRecording } from './replay/session-recording'
import { Heatmaps } from '../heatmaps'
import { PostHogProductTours } from '../posthog-product-tours'
import { SiteApps } from '../site-apps'
import { PostHogConfig } from '../types'

type ExtensionClasses = NonNullable<PostHogConfig['__extensionClasses']>

/** Session replay and related extensions. */
export const SessionReplayExtensions = {
sessionRecording: SessionRecording,
} as const satisfies ExtensionClasses

/** Autocapture, click tracking, heatmaps, and web vitals. */
export const AnalyticsExtensions = {
autocapture: Autocapture,
historyAutocapture: HistoryAutocapture,
heatmaps: Heatmaps,
deadClicksAutocapture: DeadClicksAutocapture,
webVitalsAutocapture: WebVitalsAutocapture,
} as const satisfies ExtensionClasses

/** Automatic exception and error capture. */
export const ErrorTrackingExtensions = {
exceptionObserver: ExceptionObserver,
} as const satisfies ExtensionClasses

/** In-app product tours. */
export const ProductToursExtensions = {
productTours: PostHogProductTours,
} as const satisfies ExtensionClasses

/** Site apps support. */
export const SiteAppsExtensions = {
siteApps: SiteApps,
} as const satisfies ExtensionClasses

/** Distributed tracing header injection. */
export const TracingExtensions = {
tracingHeaders: TracingHeaders,
} as const satisfies ExtensionClasses

/** All extensions — equivalent to the default `posthog-js` bundle. */
export const AllExtensions = {
...SessionReplayExtensions,
...AnalyticsExtensions,
...ErrorTrackingExtensions,
...ProductToursExtensions,
...SiteAppsExtensions,
...TracingExtensions,
} as const satisfies ExtensionClasses
Loading
Loading