-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: introduce plugin system to offset non-essenial logic #348
base: main
Are you sure you want to change the base?
Changes from 43 commits
6066f0b
8642557
d5aa373
4613b00
9726dc8
068c5c5
95a653e
5c9adbc
1c21afd
d88b010
7d89e9f
b3f32eb
93b4537
c73eb31
f77d736
8964f80
8ee7b04
29e7138
3181698
9a9608b
08d9933
b2e0b5d
ab954d7
351876c
1f1fa8f
aacaf8d
be228fa
a8fa936
f531f09
e3ad482
8bf19f4
f1bf66a
b030130
9603927
179dd62
bbae3e7
65ee6dc
0a643d5
361afee
dfc1eea
325e9be
10decef
f3a3385
2bb5f45
fa1ac2b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,26 +14,65 @@ | |
import { KNOWN_PROPERTIES, DEFAULT_TRACKING_EVENTS } from './defaults.js'; | ||
import { urlSanitizers } from './utils.js'; | ||
import { targetSelector, sourceSelector } from './dom.js'; | ||
import { | ||
addAdsParametersTracking, | ||
addCookieConsentTracking, | ||
addEmailParameterTracking, | ||
addUTMParametersTracking, | ||
} from './martech.js'; | ||
import { fflags } from './fflags.js'; | ||
|
||
const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum | ||
/* c8 ignore next */ : {}; | ||
|
||
// blocks mutation observer | ||
// eslint-disable-next-line no-use-before-define, max-len | ||
const blocksMO = window.MutationObserver ? new MutationObserver(blocksMCB) | ||
/* c8 ignore next */ : {}; | ||
const createMO = (cb) => (window.MutationObserver ? new MutationObserver(cb) | ||
/* c8 ignore next */ : {}); | ||
|
||
// media mutation observer | ||
// eslint-disable-next-line no-use-before-define, max-len | ||
const mediaMO = window.MutationObserver ? new MutationObserver(mediaMCB) | ||
/* c8 ignore next */ : {}; | ||
// blocks & media mutation observer | ||
// eslint-disable-next-line no-use-before-define | ||
const [blocksMO, mediaMO] = [blocksMCB, mediaMCB].map(createMO); | ||
|
||
// Check for the presence of a given cookie | ||
const hasCookieKey = (key) => () => document.cookie.split(';').some((c) => c.trim().startsWith(`${key}=`)); | ||
|
||
// Set the base path for the plugins | ||
const pluginBasePath = new URL(document.currentScript.src).href.replace(/index(\.map)?\.js/, 'plugins'); | ||
|
||
const PLUGINS = { | ||
cwv: `${pluginBasePath}/cwv.js`, | ||
// Interactive elements | ||
form: { url: `${pluginBasePath}/form.js`, when: () => document.querySelector('form'), isBlockDependent: true }, | ||
video: { url: `${pluginBasePath}/video.js`, when: () => document.querySelector('video'), isBlockDependent: true }, | ||
// Martech | ||
martech: { url: `${pluginBasePath}/martech.js`, when: ({ urlParameters }) => urlParameters.size > 0 }, | ||
onetrust: { url: `${pluginBasePath}/onetrust.js`, when: () => (document.querySelector('#onetrust-consent-sdk') || hasCookieKey('OptanonAlertBoxClosed')), isBlockDependent: true }, | ||
// test: broken-plugin | ||
}; | ||
|
||
const PLUGIN_PARAMETERS = { | ||
context: document.body, | ||
fflags, | ||
sampleRUM, | ||
sourceSelector, | ||
targetSelector, | ||
}; | ||
|
||
const pluginCache = new Map(); | ||
|
||
function loadPlugin(key, params) { | ||
const plugin = PLUGINS[key]; | ||
const usp = new URLSearchParams(window.location.search); | ||
if (!pluginCache.has(key) && plugin.when && !plugin.when({ urlParameters: usp })) { | ||
return null; | ||
} | ||
if (!pluginCache.has(key)) { | ||
pluginCache.set(key, import(`${plugin.url || plugin}`)); | ||
} | ||
const pluginLoadPromise = pluginCache.get(key); | ||
return pluginLoadPromise | ||
.then((p) => (p.default && p.default(params)) || (typeof p === 'function' && p(params))) | ||
.catch(() => { /* silent plugin error catching */ }); | ||
} | ||
|
||
function loadPlugins(filter = () => true, params = PLUGIN_PARAMETERS) { | ||
Object.entries(PLUGINS) | ||
.filter(([, plugin]) => filter(plugin)) | ||
.map(([key]) => loadPlugin(key, params)); | ||
} | ||
|
||
function trackCheckpoint(checkpoint, data, t) { | ||
const { weight, id } = window.hlx.rum; | ||
|
@@ -64,55 +103,9 @@ function processQueue() { | |
} | ||
} | ||
|
||
function addCWVTracking() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to |
||
setTimeout(() => { | ||
try { | ||
const cwvScript = new URL('.rum/web-vitals/dist/web-vitals.iife.js', sampleRUM.baseURL).href; | ||
if (document.querySelector(`script[src="${cwvScript}"]`)) { | ||
// web vitals script has been loaded already | ||
return; | ||
} | ||
const script = document.createElement('script'); | ||
script.src = cwvScript; | ||
script.onload = () => { | ||
const storeCWV = (measurement) => { | ||
const data = { cwv: {} }; | ||
data.cwv[measurement.name] = measurement.value; | ||
if (measurement.name === 'LCP' && measurement.entries.length > 0) { | ||
const { element } = measurement.entries.pop(); | ||
data.target = targetSelector(element); | ||
data.source = sourceSelector(element) || (element && element.outerHTML.slice(0, 30)); | ||
} | ||
sampleRUM('cwv', data); | ||
}; | ||
|
||
const isEager = (metric) => ['CLS', 'LCP'].includes(metric); | ||
|
||
// When loading `web-vitals` using a classic script, all the public | ||
// methods can be found on the `webVitals` global namespace. | ||
['INP', 'TTFB', 'CLS', 'LCP'].forEach((metric) => { | ||
const metricFn = window.webVitals[`on${metric}`]; | ||
if (typeof metricFn === 'function') { | ||
let opts = {}; | ||
fflags.enabled('eagercwv', () => { | ||
opts = { reportAllChanges: isEager(metric) }; | ||
}); | ||
metricFn(storeCWV, opts); | ||
} | ||
}); | ||
}; | ||
document.head.appendChild(script); | ||
/* c8 ignore next 3 */ | ||
} catch (error) { | ||
// something went wrong | ||
} | ||
}, 2000); // wait for delayed | ||
} | ||
|
||
function addNavigationTracking() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to |
||
// enter checkpoint when referrer is not the current page url | ||
const navigate = (source, type, redirectCount) => { | ||
// target can be 'visible', 'hidden' (background tab) or 'prerendered' (speculation rules) | ||
const payload = { source, target: document.visibilityState }; | ||
/* c8 ignore next 13 */ | ||
// prerendering cannot be tested yet with headless browsers | ||
|
@@ -211,8 +204,6 @@ function getIntersectionObsever(checkpoint) { | |
if (!window.IntersectionObserver) { | ||
return null; | ||
} | ||
activateBlocksMO(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved down |
||
activateMediaMO(); | ||
const observer = new IntersectionObserver((entries) => { | ||
try { | ||
entries | ||
|
@@ -251,28 +242,6 @@ function addViewMediaTracking(parent) { | |
} | ||
} | ||
|
||
function addFormTracking(parent) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to |
||
activateBlocksMO(); | ||
activateMediaMO(); | ||
parent.querySelectorAll('form').forEach((form) => { | ||
form.addEventListener('submit', (e) => sampleRUM('formsubmit', { target: targetSelector(e.target), source: sourceSelector(e.target) }), { once: true }); | ||
let lastSource; | ||
form.addEventListener('change', (e) => { | ||
const source = sourceSelector(e.target); | ||
if (source !== lastSource) { | ||
sampleRUM('fill', { source }); | ||
lastSource = source; | ||
} | ||
}); | ||
form.addEventListener('focusin', (e) => { | ||
if (['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].includes(e.target.tagName) | ||
|| e.target.getAttribute('contenteditable') === 'true') { | ||
sampleRUM('click', { source: sourceSelector(e.target) }); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
function addObserver(ck, fn, block) { | ||
return DEFAULT_TRACKING_EVENTS.includes(ck) && fn(block); | ||
} | ||
|
@@ -284,7 +253,7 @@ function blocksMCB(mutations) { | |
.filter((m) => m.type === 'attributes' && m.attributeName === 'data-block-status') | ||
.filter((m) => m.target.dataset.blockStatus === 'loaded') | ||
.forEach((m) => { | ||
addObserver('form', addFormTracking, m.target); | ||
addObserver('form', (el) => loadPlugins((p) => p.isBlockDependent, { ...PLUGIN_PARAMETERS, context: el }), m.target); | ||
addObserver('viewblock', addViewBlockTracking, m.target); | ||
}); | ||
} | ||
|
@@ -299,6 +268,9 @@ function mediaMCB(mutations) { | |
} | ||
|
||
function addTrackingFromConfig() { | ||
activateBlocksMO(); | ||
activateMediaMO(); | ||
|
||
let lastSource; | ||
let lastTarget; | ||
document.addEventListener('click', (event) => { | ||
|
@@ -310,16 +282,16 @@ function addTrackingFromConfig() { | |
lastTarget = target; | ||
} | ||
}); | ||
addCWVTracking(); | ||
addFormTracking(window.document.body); | ||
|
||
// Core tracking | ||
addNavigationTracking(); | ||
addLoadResourceTracking(); | ||
addUTMParametersTracking(sampleRUM); | ||
addViewBlockTracking(window.document.body); | ||
addViewMediaTracking(window.document.body); | ||
addCookieConsentTracking(sampleRUM); | ||
addAdsParametersTracking(sampleRUM); | ||
addEmailParameterTracking(sampleRUM); | ||
addViewBlockTracking(document.body); | ||
addViewMediaTracking(document.body); | ||
|
||
// Tracking extensions | ||
loadPlugins(); | ||
|
||
fflags.enabled('language', () => { | ||
const target = navigator.language; | ||
const source = document.documentElement.lang; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to
martech.js
&onetrust.js