Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 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
8 changes: 6 additions & 2 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,20 +114,24 @@ steps:
key: "v1.1-cache-dev-{{ checksum 'yarn.lock' }}"
paths: ['.yarn/cache/']

- label: '[Generic Utils] Lint + Test'
- label: '[Misc Utils] Lint + Test'
agents:
queue: v1
commands:
- npm config set "//registry.npmjs.org/:_authToken" $${NPM_TOKEN}
- echo "--- Install dependencies"
- PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 HUSKY=0 yarn install --immutable
- echo "+++ Run QA"
- echo "+++ Run QA for generic-utils"
- yarn turbo run --filter=@segment/analytics-generic-utils lint
- yarn turbo run --filter=@segment/analytics-generic-utils test
- echo "+++ Run QA for page-tools"
- yarn turbo run --filter=@segment/analytics-page-tools lint
- yarn turbo run --filter=@segment/analytics-page-tools test
plugins:
- ssh://[email protected]/segmentio/cache-buildkite-plugin#v2.0.0:
key: "v1.1-cache-dev-{{ checksum 'yarn.lock' }}"
paths: ['.yarn/cache/']


- label: '[Consent] Lint + Test'
agents:
Expand Down
5 changes: 5 additions & 0 deletions .changeset/bright-readers-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@segment/analytics-next': minor
---

Refactor to use page-tools package
5 changes: 5 additions & 0 deletions .changeset/wet-waves-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@segment/analytics-page-tools': major
---

Release package
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@changesets/cli": "^2.23.2",
"@microsoft/api-extractor": "^7.47.9",
"@npmcli/promise-spawn": "^7.0.0",
"@swc/core": "^1.11.18",
"@types/express": "4",
"@types/jest": "^29.5.11",
"@types/lodash": "^4",
Expand All @@ -62,6 +63,7 @@
"prettier": "^2.6.2",
"ts-jest": "^29.1.1",
"ts-node": "^10.8.0",
"tsup": "^8.4.0",
"turbo": "^2.3.4",
"typescript": "^4.7.0",
"webpack": "^5.94.0",
Expand Down
1 change: 1 addition & 0 deletions packages/browser/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
umd.old
2 changes: 2 additions & 0 deletions packages/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@lukeed/uuid": "^2.0.0",
"@segment/analytics-core": "1.8.1",
"@segment/analytics-generic-utils": "1.2.0",
"@segment/analytics-page-tools": "0.0.1",
"@segment/analytics.js-video-plugins": "^0.2.1",
"@segment/facade": "^3.4.9",
"dset": "^3.1.4",
Expand Down Expand Up @@ -79,6 +80,7 @@
"aws-sdk": "^2.814.0",
"circular-dependency-plugin": "^5.2.2",
"compression-webpack-plugin": "^8.0.1",
"ecma-version-validator-webpack-plugin": "^1.2.1",
"execa": "^4.1.0",
"flat": "^5.0.2",
"fs-extra": "^9.0.1",
Expand Down
35 changes: 35 additions & 0 deletions packages/browser/scripts/umd-diff.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash

# This script is for detecting how the webpack build has changed.

# 1. yarn umd && cp dist/umd dist/umd.old
# 2. make changes to your webpack config
# 3. `bash scripts/umd-diff.sh` to see changes

# Ensure the script stops on errors
set -e

# Define directories
DIST_DIR="./dist/umd"
TMP_DIR="./dist/umd.old"

# Run the UMD build
echo "Running 'yarn umd' to compile into $DIST_DIR..."
yarn umd

# Check if the temporary directory exists
if [ ! -d "$TMP_DIR" ]; then
echo "Temporary directory $TMP_DIR does not exist. Exiting."
exit 1
fi

# Compare the directories
echo "Comparing $DIST_DIR with $TMP_DIR..."
if command -v colordiff &> /dev/null; then
# Use colordiff if available for a prettier diff
diff -r "$DIST_DIR" "$TMP_DIR" | grep -E "^(Only in|diff|@@)" | colordiff
fi

rm
# Print completion message
echo "Comparison complete."
2 changes: 1 addition & 1 deletion packages/browser/src/core/page/add-page-context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { pick } from '../../lib/pick'
import { EventProperties, SegmentEvent } from '../events'
import { getDefaultPageContext } from './get-page-context'
import { getDefaultPageContext } from '@segment/analytics-page-tools'

/**
* Augments a segment event with information about the current page.
Expand Down
12 changes: 11 additions & 1 deletion packages/browser/src/core/page/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
export * from './get-page-context'
export {
type BufferedPageContext,
type PageContext,
BufferedPageContextDiscriminant,
createBufferedPageContext,
createPageContext,
getDefaultBufferedPageContext,
getDefaultPageContext,
isBufferedPageContext,
} from '@segment/analytics-page-tools'

export * from './add-page-context'
10 changes: 9 additions & 1 deletion packages/browser/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
// TODO: use webpack.config.common.js

const path = require('path')
const webpack = require('webpack')
const TerserPlugin = require('terser-webpack-plugin')
const CompressionPlugin = require('compression-webpack-plugin')
const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const CircularDependencyPlugin = require('circular-dependency-plugin')

const {
ECMAVersionValidatorPlugin,
} = require('ecma-version-validator-webpack-plugin')
const isProd = process.env.NODE_ENV === 'production'

const ASSET_PATH = process.env.ASSET_PATH
Expand All @@ -30,6 +34,10 @@ const plugins = [
new CircularDependencyPlugin({
failOnError: true,
}),
// ensure our js bundle only contains syntax supported in ie11.
// This does not check polyfills.
// This is especially neccessary because by default, node_modules are not transformed.
new ECMAVersionValidatorPlugin({ ecmaVersion: 5 }),
]

if (process.env.ANALYZE) {
Expand Down
4 changes: 4 additions & 0 deletions packages/page-tools/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type { import('eslint').Linter.Config } */
module.exports = {
extends: ['../../.eslintrc'],
}
1 change: 1 addition & 0 deletions packages/page-tools/.lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("@internal/config").lintStagedConfig
21 changes: 21 additions & 0 deletions packages/page-tools/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright © 2025 Segment

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
3 changes: 3 additions & 0 deletions packages/page-tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @segment/analytics-page-tools

Browser-only helpers for generating page events with analytics.js.
5 changes: 5 additions & 0 deletions packages/page-tools/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { createJestTSConfig } = require('@internal/config')

module.exports = createJestTSConfig(__dirname, {
testEnvironment: 'jsdom',
})
46 changes: 46 additions & 0 deletions packages/page-tools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "@segment/analytics-page-tools",
"version": "0.0.1",
"repository": {
"type": "git",
"url": "https://github.com/segmentio/analytics-next",
"directory": "packages/page-tools"
},
"license": "MIT",
"module": "./dist/esm/index.mjs",
"types": "./dist/esm/index.d.mts",
"exports": {
".": {
"import": "./dist/esm/index.mjs",
"types": "./dist/esm/index.d.mts"
}
},
"sideEffects": false,
"files": [
"LICENSE",
"dist/",
"src/",
"!**/__tests__/**",
"!**/test-helpers/**",
"!*.tsbuildinfo"
],
"scripts": {
"build": "rm -rf dist && yarn build:esm",
"build:esm": "yarn tsup src/index.ts --format esm --dts --out-dir dist/esm",
"lint": "yarn concurrently 'yarn:eslint .' 'yarn:tsc --noEmit'",
"tsup": "yarn run -T tsup",
"test": "yarn jest",
"watch": "yarn build:esm --watch",
"watch:test": "yarn test --watch",
"tsc": "yarn run -T tsc",
"eslint": "yarn run -T eslint",
"concurrently": "yarn run -T concurrently",
"jest": "yarn run -T jest"
},
"dependencies": {
"tslib": "^2.4.1"
},
"devDependencies": {
"@internal/config": "workspace:^"
}
}
130 changes: 130 additions & 0 deletions packages/page-tools/src/__tests__/page-context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import {
BufferedPageContextDiscriminant,
getDefaultBufferedPageContext,
getDefaultPageContext,
isBufferedPageContext,
} from '../'
import { pickBy } from 'lodash'

const originalLocation = window.location
beforeEach(() => {
Object.defineProperty(window, 'location', {
value: {
...originalLocation,
},
writable: true,
})
})

describe(isBufferedPageContext, () => {
it('should return true if object is page context', () => {
expect(isBufferedPageContext(getDefaultBufferedPageContext())).toBe(true)
})
it('should return false if object is not page context', () => {
expect(isBufferedPageContext(undefined)).toBe(false)
expect(isBufferedPageContext({})).toBe(false)
expect(isBufferedPageContext('')).toBe(false)
expect(isBufferedPageContext({ foo: false })).toBe(false)
expect(isBufferedPageContext({ u: 'hello' })).toBe(false)
expect(isBufferedPageContext(null)).toBe(false)

expect(
isBufferedPageContext({
...getDefaultBufferedPageContext(),
some_unknown_key: 123,
})
).toBe(false)

const missingDiscriminant = pickBy(
getDefaultBufferedPageContext(),
(v) => v !== BufferedPageContextDiscriminant
)
// should not be missing the dscriminant
expect(isBufferedPageContext(missingDiscriminant)).toBe(false)
})
})

describe(getDefaultPageContext, () => {
describe('hash', () => {
it('strips the hash from the URL', () => {
window.location.href = 'http://www.segment.local#test'
const defs = getDefaultPageContext()
expect(defs.url).toBe('http://www.segment.local')

window.location.href = 'http://www.segment.local/#test'
const defs2 = getDefaultPageContext()
expect(defs2.url).toBe('http://www.segment.local/')
})
})

describe('canonical URL', () => {
const el = document.createElement('link')
beforeEach(() => {
el.setAttribute('rel', 'canonical')
el.setAttribute('href', '')
document.clear()
})

it('returns location.href if canonical URL does not exist', () => {
el.setAttribute('rel', 'nope')
document.body.appendChild(el)
const defs = getDefaultPageContext()
expect(defs.url).toEqual(window.location.href)
})

it('does not throw an error if canonical URL is not a valid URL', () => {
el.setAttribute('href', 'foo.com/bar')
document.body.appendChild(el)
const defs = getDefaultPageContext()
expect(defs.url).toEqual('foo.com/bar') // this is analytics.js classic behavior
expect(defs.path).toEqual('/foo.com/bar') // this is analytics.js classic behavior
})

it('handles a leading slash', () => {
el.setAttribute('href', 'foo')
document.body.appendChild(el)
const defs = getDefaultPageContext()
expect(defs.url).toEqual('foo')
expect(defs.path).toEqual('/foo') // this is analytics.js classic behavior
})

it('handles canonical links', () => {
el.setAttribute('href', 'http://www.segment.local')
document.body.appendChild(el)
const defs = getDefaultPageContext()
expect(defs.url).toEqual('http://www.segment.local')
})

it('favors canonical path over location.pathname', () => {
window.location.pathname = '/nope'
el.setAttribute('href', 'http://www.segment.local/test')
document.body.appendChild(el)
const defs = getDefaultPageContext()
expect(defs.path).toEqual('/test')
})

it('handles canonical links with a path', () => {
el.setAttribute('href', 'http://www.segment.local/test')
document.body.appendChild(el)
const defs = getDefaultPageContext()
expect(defs.url).toEqual('http://www.segment.local/test')
expect(defs.path).toEqual('/test')
})

it('handles canonical links with search params in the url', () => {
el.setAttribute('href', 'http://www.segment.local?test=true')
document.body.appendChild(el)
const defs = getDefaultPageContext()
expect(defs.url).toEqual('http://www.segment.local?test=true')
})

it('will add search params from the document to the canonical path if it does not have search params', () => {
// This seems like weird behavior to me, but I found it in the codebase so adding a test for it.
window.location.search = '?foo=123'
el.setAttribute('href', 'http://www.segment.local')
document.body.appendChild(el)
const defs = getDefaultPageContext()
expect(defs.url).toEqual('http://www.segment.local?foo=123')
})
})
})
1 change: 1 addition & 0 deletions packages/page-tools/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './page-context'
Loading