Skip to content
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

Open
wants to merge 45 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6066f0b
feat: introduce minimal plugin system to offset non-essenial logic
ramboz Jan 14, 2025
8642557
feat: introduce minimal plugin system to offset non-essenial logic
ramboz Jan 14, 2025
d5aa373
refactor: rreduce code duplication and add error handling
ramboz Jan 14, 2025
4613b00
feat: allow external extension via window.RUM_PLUGINS
ramboz Jan 14, 2025
9726dc8
fix: set proper url base for plugins
ramboz Jan 14, 2025
068c5c5
refactor: address PR feedback
ramboz Jan 16, 2025
95a653e
refactor: address PR feedback
ramboz Jan 16, 2025
5c9adbc
fix: bugs and failing tests
ramboz Jan 16, 2025
1c21afd
fix: cross-browser compatibility
ramboz Jan 16, 2025
d88b010
build(rollup): remove broken cleanup plugin, replace with babel plugin
trieloff Jan 16, 2025
7d89e9f
feat: inline web-vitals.js
ramboz Jan 16, 2025
b3f32eb
build(rollup): remove broken cleanup plugin, replace with babel plugin
ramboz Jan 16, 2025
93b4537
chore: update file size limit to 15KB
ramboz Jan 16, 2025
c73eb31
fix: use proper fully qualified URLs for plugin path to avoid CORS
ramboz Jan 16, 2025
f77d736
fix: plugin path resolution
ramboz Jan 16, 2025
8964f80
test: ignore code coverage for error handler
ramboz Jan 16, 2025
8ee7b04
test: add explicit test for broken plugin
ramboz Jan 16, 2025
29e7138
ci(release): enable prereleases on beta branch
trieloff Oct 9, 2024
3181698
feat: handle pre-rendering
kptdobe Oct 8, 2024
9a9608b
chore(release): 2.26.0-beta.1 [skip ci]
semantic-release-bot Oct 9, 2024
08d9933
refactor(fflags): more compact feature flags code
trieloff Jan 14, 2025
b2e0b5d
fixing coverage report
vdua Jan 14, 2025
ab954d7
tests: removing only src/index.js from instrumentation to get the pro…
vdua Jan 14, 2025
351876c
ci(github): report test results in github, pretty
trieloff Jan 14, 2025
1f1fa8f
ci(github): update test results reporter format to jest-junit
trieloff Jan 14, 2025
aacaf8d
test: implement custom JSON test reporter
trieloff Jan 14, 2025
be228fa
ci(github): use the test-summary action instead
trieloff Jan 14, 2025
a8fa936
feat(loadresource): add `allresources` feature flag that enables trac…
trieloff Jan 14, 2025
f531f09
chore(release): 2.29.0 [skip ci]
semantic-release-bot Jan 15, 2025
e3ad482
feat(missingresource): report all resource error with http status code
trieloff Jan 14, 2025
8bf19f4
chore(release): 2.30.0 [skip ci]
semantic-release-bot Jan 15, 2025
f1bf66a
chore: align with main branch
trieloff Jan 16, 2025
b030130
chore(release): 2.31.0-beta.1 [skip ci]
semantic-release-bot Jan 16, 2025
9603927
Revert "feat: handle pre-rendering"
trieloff Jan 16, 2025
179dd62
chore(release): 2.31.0-beta.2 [skip ci]
semantic-release-bot Jan 16, 2025
bbae3e7
chore(release): 2.31.0-beta.3 [skip ci]
semantic-release-bot Jan 16, 2025
65ee6dc
chore(release): 2.31.0-beta.4 [skip ci]
semantic-release-bot Jan 16, 2025
0a643d5
fix: just forcing a release
ramboz Jan 17, 2025
361afee
chore(release): 2.31.0-beta.5 [skip ci]
semantic-release-bot Jan 17, 2025
dfc1eea
fix: plugins are not executing because they are exported as iife
ramboz Jan 17, 2025
325e9be
Merge branch 'main' into plugin-system
ramboz Jan 17, 2025
10decef
chore: cleanup PR
ramboz Jan 17, 2025
f3a3385
chore: cleanup PR
ramboz Jan 17, 2025
2bb5f45
chore: ignore uncovered lines
ramboz Jan 17, 2025
fa1ac2b
Merge branch 'main' into plugin-system
ramboz Jan 20, 2025
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
160 changes: 65 additions & 95 deletions modules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Comment on lines -17 to -22
Copy link
Collaborator Author

@ramboz ramboz Jan 14, 2025

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

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;
Expand Down Expand Up @@ -64,55 +103,9 @@ function processQueue() {
}
}

function addCWVTracking() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to cwv.js

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() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to navigation.js

// 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
Expand Down Expand Up @@ -211,8 +204,6 @@ function getIntersectionObsever(checkpoint) {
if (!window.IntersectionObserver) {
return null;
}
activateBlocksMO();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved down addTrackingFromConfig

activateMediaMO();
const observer = new IntersectionObserver((entries) => {
try {
entries
Expand Down Expand Up @@ -251,30 +242,6 @@ function addViewMediaTracking(parent) {
}
}

function addFormTracking(parent) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to form.js

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) => {
if (e.target.checkVisibility()) {
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);
}
Expand All @@ -286,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);
});
}
Expand All @@ -301,6 +268,9 @@ function mediaMCB(mutations) {
}

function addTrackingFromConfig() {
activateBlocksMO();
activateMediaMO();

let lastSource;
let lastTarget;
document.addEventListener('click', (event) => {
Expand All @@ -312,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;
Expand Down
Loading
Loading