From 89534d2c04a61bb503556b8acc421f688e95f9c8 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Fri, 24 Jan 2025 12:57:03 +0000 Subject: [PATCH 1/8] Refactor module loading to make it easier to read --- src/commercial.ts | 13 +- src/init/ad-free.ts | 29 ++++ src/init/consented-advertising.ts | 56 ++++++ src/init/consented.ts | 163 ------------------ ...sentless.ts => consentless-advertising.ts} | 0 src/lib/init-utils.ts | 96 +++++++++++ 6 files changed, 190 insertions(+), 167 deletions(-) create mode 100644 src/init/ad-free.ts create mode 100644 src/init/consented-advertising.ts delete mode 100644 src/init/consented.ts rename src/init/{consentless.ts => consentless-advertising.ts} (100%) create mode 100644 src/lib/init-utils.ts diff --git a/src/commercial.ts b/src/commercial.ts index 9ddec9a35..d3dac5a01 100644 --- a/src/commercial.ts +++ b/src/commercial.ts @@ -18,13 +18,18 @@ void (async () => { !commercialFeatures.adFree ) { void import( - /* webpackChunkName: "consentless" */ - './init/consentless' + /* webpackChunkName: "consentless-advertising" */ + './init/consentless-advertising' ).then(({ bootConsentless }) => bootConsentless(consentState)); + } else if (commercialFeatures.adFree) { + void import( + /* webpackChunkName: "ad-free" */ + './init/ad-free' + ).then(({ bootCommercialWhenReady }) => bootCommercialWhenReady()); } else { void import( - /* webpackChunkName: "consented" */ - './init/consented' + /* webpackChunkName: "consented-advertising" */ + './init/consented-advertising' ).then(({ bootCommercialWhenReady }) => bootCommercialWhenReady()); } })(); diff --git a/src/init/ad-free.ts b/src/init/ad-free.ts new file mode 100644 index 000000000..8600676f6 --- /dev/null +++ b/src/init/ad-free.ts @@ -0,0 +1,29 @@ +import { bootCommercial, type Modules } from '../lib/init-utils'; +import { adFreeSlotRemove } from './consented/ad-free-slot-remove'; +import { init as initComscore } from './consented/comscore'; +import { init as initIpsosMori } from './consented/ipsos-mori'; +import { removeDisabledSlots as closeDisabledSlots } from './consented/remove-slots'; +import { initTeadsCookieless } from './consented/teads-cookieless'; +import { init as initTrackGpcSignal } from './consented/track-gpc-signal'; +import { init as initTrackScrollDepth } from './consented/track-scroll-depth'; + +// modules not related to ad loading +const commercialModules: Modules = [ + ['cm-adFreeSlotRemoveFronts', adFreeSlotRemove], + ['cm-closeDisabledSlots', closeDisabledSlots], + ['cm-comscore', initComscore], + ['cm-ipsosmori', initIpsosMori], + ['cm-teadsCookieless', initTeadsCookieless], + ['cm-trackScrollDepth', initTrackScrollDepth], + ['cm-trackGpcSignal', initTrackGpcSignal], +]; + +const bootCommercialWhenReady = () => { + if (!!window.guardian.mustardCut || !!window.guardian.polyfilled) { + void bootCommercial(commercialModules); + } else { + window.guardian.queue.push(() => bootCommercial(commercialModules)); + } +}; + +export { bootCommercialWhenReady }; diff --git a/src/init/consented-advertising.ts b/src/init/consented-advertising.ts new file mode 100644 index 000000000..54b2ac77a --- /dev/null +++ b/src/init/consented-advertising.ts @@ -0,0 +1,56 @@ +import { init as prepareAdVerification } from '../lib/ad-verification/prepare-ad-verification'; +import { bootCommercial, type Modules } from '../lib/init-utils'; +import { adFreeSlotRemove } from './consented/ad-free-slot-remove'; +import { init as initComscore } from './consented/comscore'; +import { initDfpListeners } from './consented/dfp-listeners'; +import { initDynamicAdSlots } from './consented/dynamic-ad-slots'; +import { initFillSlotListener } from './consented/fill-slot-listener'; +import { init as initIpsosMori } from './consented/ipsos-mori'; +import { init as initMessenger } from './consented/messenger'; +import { init as prepareA9 } from './consented/prepare-a9'; +import { init as prepareGoogletag } from './consented/prepare-googletag'; +import { initPermutive } from './consented/prepare-permutive'; +import { init as preparePrebid } from './consented/prepare-prebid'; +import { removeDisabledSlots as closeDisabledSlots } from './consented/remove-slots'; +import { initTeadsCookieless } from './consented/teads-cookieless'; +import { init as initThirdPartyTags } from './consented/third-party-tags'; +import { init as initTrackGpcSignal } from './consented/track-gpc-signal'; +import { init as initTrackScrollDepth } from './consented/track-scroll-depth'; +import { reloadPageOnConsentChange } from './shared/reload-page-on-consent-change'; +import { init as setAdTestCookie } from './shared/set-adtest-cookie'; +import { init as setAdTestInLabelsCookie } from './shared/set-adtest-in-labels-cookie'; + +// all modules needed for commercial code and ads to run +const commercialModules: Modules = [ + ['cm-adFreeSlotRemoveFronts', adFreeSlotRemove], + ['cm-closeDisabledSlots', closeDisabledSlots], + ['cm-comscore', initComscore], + ['cm-ipsosmori', initIpsosMori], + ['cm-teadsCookieless', initTeadsCookieless], + ['cm-trackScrollDepth', initTrackScrollDepth], + ['cm-trackGpcSignal', initTrackGpcSignal], + ['cm-messenger', initMessenger], + ['cm-setAdTestCookie', setAdTestCookie], + ['cm-setAdTestInLabelsCookie', setAdTestInLabelsCookie], + ['cm-reloadPageOnConsentChange', reloadPageOnConsentChange], + ['cm-prepare-prebid', preparePrebid], + // Permutive init code must run before google tag enableServices() + // The permutive lib however is loaded async with the third party tags + ['cm-dfp-listeners', initDfpListeners], + ['cm-prepare-googletag', () => initPermutive().then(prepareGoogletag)], + ['cm-dynamic-a-slots', initDynamicAdSlots], + ['cm-prepare-a9', prepareA9], + ['cm-prepare-fill-slot-listener', initFillSlotListener], + ['cm-prepare-adverification', prepareAdVerification], + ['cm-thirdPartyTags', initThirdPartyTags], +]; + +const bootCommercialWhenReady = () => { + if (!!window.guardian.mustardCut || !!window.guardian.polyfilled) { + void bootCommercial(commercialModules); + } else { + window.guardian.queue.push(() => bootCommercial(commercialModules)); + } +}; + +export { bootCommercialWhenReady }; diff --git a/src/init/consented.ts b/src/init/consented.ts deleted file mode 100644 index 090b4b8e6..000000000 --- a/src/init/consented.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { log } from '@guardian/libs'; -import { init as prepareAdVerification } from '../lib/ad-verification/prepare-ad-verification'; -import { commercialFeatures } from '../lib/commercial-features'; -import { adSlotIdPrefix } from '../lib/dfp/dfp-env-globals'; -import { reportError } from '../lib/error/report-error'; -import { catchErrorsAndReport } from '../lib/error/robust'; -import { EventTimer } from '../lib/event-timer'; -import { adFreeSlotRemove } from './consented/ad-free-slot-remove'; -import { init as initComscore } from './consented/comscore'; -import { initDfpListeners } from './consented/dfp-listeners'; -import { initDynamicAdSlots } from './consented/dynamic-ad-slots'; -import { initFillSlotListener } from './consented/fill-slot-listener'; -import { init as initIpsosMori } from './consented/ipsos-mori'; -import { init as initMessenger } from './consented/messenger'; -import { init as prepareA9 } from './consented/prepare-a9'; -import { init as prepareGoogletag } from './consented/prepare-googletag'; -import { initPermutive } from './consented/prepare-permutive'; -import { init as preparePrebid } from './consented/prepare-prebid'; -import { removeDisabledSlots as closeDisabledSlots } from './consented/remove-slots'; -import { initTeadsCookieless } from './consented/teads-cookieless'; -import { init as initThirdPartyTags } from './consented/third-party-tags'; -import { init as initTrackGpcSignal } from './consented/track-gpc-signal'; -import { init as initTrackScrollDepth } from './consented/track-scroll-depth'; -import { reloadPageOnConsentChange } from './shared/reload-page-on-consent-change'; -import { init as setAdTestCookie } from './shared/set-adtest-cookie'; -import { init as setAdTestInLabelsCookie } from './shared/set-adtest-in-labels-cookie'; - -type Modules = Array<[`${string}-${string}`, () => Promise]>; - -const tags: Record = { - bundle: 'standalone', -}; -// modules necessary to load the first ads on the page -const commercialBaseModules: Modules = []; - -// remaining modules not necessary to load an ad -const commercialExtraModules: Modules = [ - ['cm-adFreeSlotRemoveFronts', adFreeSlotRemove], - ['cm-closeDisabledSlots', closeDisabledSlots], - ['cm-comscore', initComscore], - ['cm-ipsosmori', initIpsosMori], - ['cm-teadsCookieless', initTeadsCookieless], - ['cm-trackScrollDepth', initTrackScrollDepth], - ['cm-trackGpcSignal', initTrackGpcSignal], -]; - -if (!commercialFeatures.adFree) { - commercialBaseModules.push( - ['cm-messenger', initMessenger], - ['cm-setAdTestCookie', setAdTestCookie], - ['cm-setAdTestInLabelsCookie', setAdTestInLabelsCookie], - ['cm-reloadPageOnConsentChange', reloadPageOnConsentChange], - ['cm-prepare-prebid', preparePrebid], - // Permutive init code must run before google tag enableServices() - // The permutive lib however is loaded async with the third party tags - ['cm-dfp-listeners', initDfpListeners], - ['cm-prepare-googletag', () => initPermutive().then(prepareGoogletag)], - ['cm-dynamic-a-slots', initDynamicAdSlots], - ['cm-prepare-a9', prepareA9], - ['cm-prepare-fill-slot-listener', initFillSlotListener], - ); - commercialExtraModules.push( - ['cm-prepare-adverification', prepareAdVerification], - ['cm-thirdPartyTags', initThirdPartyTags], - ); -} - -const loadModules = (modules: Modules, eventName: string) => { - const modulePromises: Array> = []; - - modules.forEach((module) => { - const [moduleName, moduleInit] = module; - - catchErrorsAndReport( - [ - [ - moduleName, - function pushAfterComplete(): void { - const result = moduleInit(); - modulePromises.push(result); - }, - ], - ], - tags, - ); - }); - - return Promise.allSettled(modulePromises).then(() => { - EventTimer.get().mark(eventName); - }); -}; - -const recordCommercialMetrics = () => { - const eventTimer = EventTimer.get(); - eventTimer.mark('commercialBootEnd'); - eventTimer.mark('commercialModulesLoaded'); - // record the number of ad slots on the page - const adSlotsTotal = document.querySelectorAll( - `[id^="${adSlotIdPrefix}"]`, - ).length; - eventTimer.setProperty('adSlotsTotal', adSlotsTotal); - - // and the number of inline ad slots - const adSlotsInline = document.querySelectorAll( - `[id^="${adSlotIdPrefix}inline"]`, - ).length; - eventTimer.setProperty('adSlotsInline', adSlotsInline); -}; - -const bootCommercial = async (): Promise => { - log('commercial', '📦 standalone.commercial.ts', __webpack_public_path__); - if (process.env.COMMIT_SHA) { - log( - 'commercial', - `@guardian/commercial commit https://github.com/guardian/commercial/blob/${process.env.COMMIT_SHA}`, - ); - } - - // Init Commercial event timers - EventTimer.init(); - - catchErrorsAndReport( - [ - [ - 'ga-user-timing-commercial-start', - function runTrackPerformance() { - EventTimer.get().mark('commercialStart'); - EventTimer.get().mark('commercialBootStart'); - }, - ], - ], - tags, - ); - - // Stub the command queue - // @ts-expect-error -- it’s a stub, not the whole Googletag object - window.googletag = { - cmd: [], - }; - - try { - const allModules: Array> = [ - [commercialBaseModules, 'commercialBaseModulesLoaded'], - [commercialExtraModules, 'commercialExtraModulesLoaded'], - ]; - const promises = allModules.map((args) => loadModules(...args)); - - await Promise.all(promises).then(recordCommercialMetrics); - } catch (error) { - // report async errors in bootCommercial to Sentry with the commercial feature tag - reportError(error, 'commercial', tags); - } -}; - -const bootCommercialWhenReady = () => { - if (!!window.guardian.mustardCut || !!window.guardian.polyfilled) { - void bootCommercial(); - } else { - window.guardian.queue.push(bootCommercial); - } -}; - -export { bootCommercialWhenReady }; diff --git a/src/init/consentless.ts b/src/init/consentless-advertising.ts similarity index 100% rename from src/init/consentless.ts rename to src/init/consentless-advertising.ts diff --git a/src/lib/init-utils.ts b/src/lib/init-utils.ts new file mode 100644 index 000000000..3e58decbb --- /dev/null +++ b/src/lib/init-utils.ts @@ -0,0 +1,96 @@ +import { log } from '@guardian/libs'; +import { adSlotIdPrefix } from '../lib/dfp/dfp-env-globals'; +import { reportError } from '../lib/error/report-error'; +import { catchErrorsAndReport } from './error/robust'; +import { EventTimer } from './event-timer'; + +type Modules = Array<[`${string}-${string}`, () => Promise]>; + +const tags: Record = { + bundle: 'standalone', +}; + +const loadModules = (modules: Modules) => { + const modulePromises: Array> = []; + + modules.forEach((module) => { + const [moduleName, moduleInit] = module; + + catchErrorsAndReport( + [ + [ + moduleName, + function pushAfterComplete(): void { + const result = moduleInit(); + modulePromises.push(result); + }, + ], + ], + tags, + ); + }); + + return Promise.allSettled(modulePromises).then(() => { + EventTimer.get().mark('commercialModulesLoaded'); + }); +}; + +const recordCommercialMetrics = () => { + const eventTimer = EventTimer.get(); + eventTimer.mark('commercialBootEnd'); + eventTimer.mark('commercialModulesLoaded'); + // record the number of ad slots on the page + const adSlotsTotal = document.querySelectorAll( + `[id^="${adSlotIdPrefix}"]`, + ).length; + eventTimer.setProperty('adSlotsTotal', adSlotsTotal); + + // and the number of inline ad slots + const adSlotsInline = document.querySelectorAll( + `[id^="${adSlotIdPrefix}inline"]`, + ).length; + eventTimer.setProperty('adSlotsInline', adSlotsInline); +}; + +const bootCommercial = async (modules: Modules): Promise => { + log('commercial', '📦 standalone.commercial.ts', __webpack_public_path__); + if (process.env.COMMIT_SHA) { + log( + 'commercial', + `@guardian/commercial commit https://github.com/guardian/commercial/blob/${process.env.COMMIT_SHA}`, + ); + } + + // Init Commercial event timers + EventTimer.init(); + + catchErrorsAndReport( + [ + [ + 'ga-user-timing-commercial-start', + function runTrackPerformance() { + EventTimer.get().mark('commercialStart'); + EventTimer.get().mark('commercialBootStart'); + }, + ], + ], + tags, + ); + + // Stub the command queue + // @ts-expect-error -- it’s a stub, not the whole Googletag object + window.googletag = { + cmd: [], + }; + + try { + const promises = loadModules(modules); + + await Promise.resolve(promises).then(recordCommercialMetrics); + } catch (error) { + // report async errors in bootCommercial to Sentry with the commercial feature tag + reportError(error, 'commercial', tags); + } +}; + +export { type Modules, bootCommercial }; From 76c55365b0e733151091d508874cd58aa9d675b7 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Tue, 28 Jan 2025 10:51:54 +0000 Subject: [PATCH 2/8] Move permutive comment to the right place --- src/init/consented-advertising.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init/consented-advertising.ts b/src/init/consented-advertising.ts index 54b2ac77a..3b9b9aa52 100644 --- a/src/init/consented-advertising.ts +++ b/src/init/consented-advertising.ts @@ -34,9 +34,9 @@ const commercialModules: Modules = [ ['cm-setAdTestInLabelsCookie', setAdTestInLabelsCookie], ['cm-reloadPageOnConsentChange', reloadPageOnConsentChange], ['cm-prepare-prebid', preparePrebid], + ['cm-dfp-listeners', initDfpListeners], // Permutive init code must run before google tag enableServices() // The permutive lib however is loaded async with the third party tags - ['cm-dfp-listeners', initDfpListeners], ['cm-prepare-googletag', () => initPermutive().then(prepareGoogletag)], ['cm-dynamic-a-slots', initDynamicAdSlots], ['cm-prepare-a9', prepareA9], From 7bf3ed9617efc3d1d2f1b2ff23d824f2e8827072 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Tue, 28 Jan 2025 11:13:52 +0000 Subject: [PATCH 3/8] Don't use catchErrorsAndReport to load modules --- src/init/ad-free.ts | 18 +++++----- src/init/consented-advertising.ts | 42 +++++++++++----------- src/lib/error/robust.spec.ts | 58 ------------------------------- src/lib/error/robust.ts | 31 ----------------- src/lib/init-utils.ts | 57 ++++++------------------------ 5 files changed, 41 insertions(+), 165 deletions(-) delete mode 100644 src/lib/error/robust.spec.ts delete mode 100644 src/lib/error/robust.ts diff --git a/src/init/ad-free.ts b/src/init/ad-free.ts index 8600676f6..24f010957 100644 --- a/src/init/ad-free.ts +++ b/src/init/ad-free.ts @@ -1,4 +1,4 @@ -import { bootCommercial, type Modules } from '../lib/init-utils'; +import { bootCommercial } from '../lib/init-utils'; import { adFreeSlotRemove } from './consented/ad-free-slot-remove'; import { init as initComscore } from './consented/comscore'; import { init as initIpsosMori } from './consented/ipsos-mori'; @@ -8,14 +8,14 @@ import { init as initTrackGpcSignal } from './consented/track-gpc-signal'; import { init as initTrackScrollDepth } from './consented/track-scroll-depth'; // modules not related to ad loading -const commercialModules: Modules = [ - ['cm-adFreeSlotRemoveFronts', adFreeSlotRemove], - ['cm-closeDisabledSlots', closeDisabledSlots], - ['cm-comscore', initComscore], - ['cm-ipsosmori', initIpsosMori], - ['cm-teadsCookieless', initTeadsCookieless], - ['cm-trackScrollDepth', initTrackScrollDepth], - ['cm-trackGpcSignal', initTrackGpcSignal], +const commercialModules = [ + adFreeSlotRemove(), + closeDisabledSlots(), + initComscore(), + initIpsosMori(), + initTeadsCookieless(), + initTrackScrollDepth(), + initTrackGpcSignal(), ]; const bootCommercialWhenReady = () => { diff --git a/src/init/consented-advertising.ts b/src/init/consented-advertising.ts index 3b9b9aa52..457d0c75d 100644 --- a/src/init/consented-advertising.ts +++ b/src/init/consented-advertising.ts @@ -1,5 +1,5 @@ import { init as prepareAdVerification } from '../lib/ad-verification/prepare-ad-verification'; -import { bootCommercial, type Modules } from '../lib/init-utils'; +import { bootCommercial } from '../lib/init-utils'; import { adFreeSlotRemove } from './consented/ad-free-slot-remove'; import { init as initComscore } from './consented/comscore'; import { initDfpListeners } from './consented/dfp-listeners'; @@ -21,28 +21,28 @@ import { init as setAdTestCookie } from './shared/set-adtest-cookie'; import { init as setAdTestInLabelsCookie } from './shared/set-adtest-in-labels-cookie'; // all modules needed for commercial code and ads to run -const commercialModules: Modules = [ - ['cm-adFreeSlotRemoveFronts', adFreeSlotRemove], - ['cm-closeDisabledSlots', closeDisabledSlots], - ['cm-comscore', initComscore], - ['cm-ipsosmori', initIpsosMori], - ['cm-teadsCookieless', initTeadsCookieless], - ['cm-trackScrollDepth', initTrackScrollDepth], - ['cm-trackGpcSignal', initTrackGpcSignal], - ['cm-messenger', initMessenger], - ['cm-setAdTestCookie', setAdTestCookie], - ['cm-setAdTestInLabelsCookie', setAdTestInLabelsCookie], - ['cm-reloadPageOnConsentChange', reloadPageOnConsentChange], - ['cm-prepare-prebid', preparePrebid], - ['cm-dfp-listeners', initDfpListeners], +const commercialModules = [ + adFreeSlotRemove(), + closeDisabledSlots(), + initComscore(), + initIpsosMori(), + initTeadsCookieless(), + initTrackScrollDepth(), + initTrackGpcSignal(), + initMessenger(), + setAdTestCookie(), + setAdTestInLabelsCookie(), + reloadPageOnConsentChange(), + preparePrebid(), + initDfpListeners(), // Permutive init code must run before google tag enableServices() // The permutive lib however is loaded async with the third party tags - ['cm-prepare-googletag', () => initPermutive().then(prepareGoogletag)], - ['cm-dynamic-a-slots', initDynamicAdSlots], - ['cm-prepare-a9', prepareA9], - ['cm-prepare-fill-slot-listener', initFillSlotListener], - ['cm-prepare-adverification', prepareAdVerification], - ['cm-thirdPartyTags', initThirdPartyTags], + initPermutive().then(prepareGoogletag), + initDynamicAdSlots(), + prepareA9(), + initFillSlotListener(), + prepareAdVerification(), + initThirdPartyTags(), ]; const bootCommercialWhenReady = () => { diff --git a/src/lib/error/robust.spec.ts b/src/lib/error/robust.spec.ts deleted file mode 100644 index 7b826ef87..000000000 --- a/src/lib/error/robust.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { reportError } from './report-error'; -import { catchErrorsAndReport } from './robust'; -import type { Modules } from './robust'; - -jest.mock('./report-error', () => ({ - reportError: jest.fn(), -})); - -beforeEach(() => { - jest.spyOn(global.console, 'warn'); -}); - -afterEach(() => { - jest.spyOn(global.console, 'warn').mockRestore(); - jest.resetAllMocks(); -}); - -describe('robust', () => { - const ERROR = new Error('Deliberate test error'); - const tags = { tag: 'test' }; - - const throwError = () => { - throw ERROR; - }; - - test('catchErrorsAndReport with no errors', () => { - const runner = jest.fn(); - - const modules = [ - ['test-1', runner], - ['test-2', runner], - ['test-3', runner], - ] as Modules; - - catchErrorsAndReport(modules); - expect(runner).toHaveBeenCalledTimes(modules.length); - expect(reportError).not.toHaveBeenCalled(); - }); - - test('catchErrorsAndReport with one error', () => { - const runner = jest.fn(); - - const modules = [ - ['test-1', runner], - ['test-2', throwError], - ['test-3', runner], - ] as Modules; - - catchErrorsAndReport(modules, tags); - expect(runner).toHaveBeenCalledTimes(2); - expect(reportError).toHaveBeenCalledTimes(1); - expect(window.console.warn).toHaveBeenCalledTimes(1); - expect(reportError).toHaveBeenCalledWith(ERROR, 'commercial', { - tag: 'test', - module: 'test-2', - }); - }); -}); diff --git a/src/lib/error/robust.ts b/src/lib/error/robust.ts deleted file mode 100644 index f0cd7f708..000000000 --- a/src/lib/error/robust.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Swallows and reports exceptions. Designed to wrap around modules at the "bootstrap" level. - */ -import { reportError } from './report-error'; - -type ModuleFunction = () => void; -type Module = [string, ModuleFunction]; -type Modules = Module[]; - -const catchErrorsAndReport = ( - modules: Modules, - tags?: Record, -): void => { - modules.forEach(([name, fn]) => { - let error: Error | undefined; - - try { - fn(); - } catch (e) { - error = e instanceof Error ? e : new Error(String(e)); - } - - if (error) { - window.console.warn('Caught error.', error.stack); - reportError(error, 'commercial', { ...tags, module: name }); - } - }); -}; - -export { catchErrorsAndReport }; -export type { Modules }; diff --git a/src/lib/init-utils.ts b/src/lib/init-utils.ts index 3e58decbb..3a797bc22 100644 --- a/src/lib/init-utils.ts +++ b/src/lib/init-utils.ts @@ -1,40 +1,12 @@ import { log } from '@guardian/libs'; import { adSlotIdPrefix } from '../lib/dfp/dfp-env-globals'; import { reportError } from '../lib/error/report-error'; -import { catchErrorsAndReport } from './error/robust'; import { EventTimer } from './event-timer'; -type Modules = Array<[`${string}-${string}`, () => Promise]>; - const tags: Record = { bundle: 'standalone', }; -const loadModules = (modules: Modules) => { - const modulePromises: Array> = []; - - modules.forEach((module) => { - const [moduleName, moduleInit] = module; - - catchErrorsAndReport( - [ - [ - moduleName, - function pushAfterComplete(): void { - const result = moduleInit(); - modulePromises.push(result); - }, - ], - ], - tags, - ); - }); - - return Promise.allSettled(modulePromises).then(() => { - EventTimer.get().mark('commercialModulesLoaded'); - }); -}; - const recordCommercialMetrics = () => { const eventTimer = EventTimer.get(); eventTimer.mark('commercialBootEnd'); @@ -52,7 +24,9 @@ const recordCommercialMetrics = () => { eventTimer.setProperty('adSlotsInline', adSlotsInline); }; -const bootCommercial = async (modules: Modules): Promise => { +const bootCommercial = async ( + modules: Array>, +): Promise => { log('commercial', '📦 standalone.commercial.ts', __webpack_public_path__); if (process.env.COMMIT_SHA) { log( @@ -63,19 +37,8 @@ const bootCommercial = async (modules: Modules): Promise => { // Init Commercial event timers EventTimer.init(); - - catchErrorsAndReport( - [ - [ - 'ga-user-timing-commercial-start', - function runTrackPerformance() { - EventTimer.get().mark('commercialStart'); - EventTimer.get().mark('commercialBootStart'); - }, - ], - ], - tags, - ); + EventTimer.get().mark('commercialStart'); + EventTimer.get().mark('commercialBootStart'); // Stub the command queue // @ts-expect-error -- it’s a stub, not the whole Googletag object @@ -84,13 +47,15 @@ const bootCommercial = async (modules: Modules): Promise => { }; try { - const promises = loadModules(modules); - - await Promise.resolve(promises).then(recordCommercialMetrics); + return Promise.allSettled(modules) + .then(() => { + EventTimer.get().mark('commercialModulesLoaded'); + }) + .then(recordCommercialMetrics); } catch (error) { // report async errors in bootCommercial to Sentry with the commercial feature tag reportError(error, 'commercial', tags); } }; -export { type Modules, bootCommercial }; +export { bootCommercial }; From 579c0508e137d1004206dd3ddba5b6f760f991b4 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Tue, 28 Jan 2025 11:15:33 +0000 Subject: [PATCH 4/8] Remove duplicate modules loaded event --- src/lib/init-utils.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lib/init-utils.ts b/src/lib/init-utils.ts index 3a797bc22..07e99dc07 100644 --- a/src/lib/init-utils.ts +++ b/src/lib/init-utils.ts @@ -47,11 +47,7 @@ const bootCommercial = async ( }; try { - return Promise.allSettled(modules) - .then(() => { - EventTimer.get().mark('commercialModulesLoaded'); - }) - .then(recordCommercialMetrics); + return Promise.allSettled(modules).then(recordCommercialMetrics); } catch (error) { // report async errors in bootCommercial to Sentry with the commercial feature tag reportError(error, 'commercial', tags); From abf1b4e13871dfbce45965c6c43bec6f94263d3d Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Tue, 28 Jan 2025 11:42:11 +0000 Subject: [PATCH 5/8] Fix module loading --- src/init/ad-free.ts | 14 ++++++------ src/init/consented-advertising.ts | 38 +++++++++++++++---------------- src/lib/init-utils.ts | 6 +++-- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/init/ad-free.ts b/src/init/ad-free.ts index 24f010957..46ce5be1f 100644 --- a/src/init/ad-free.ts +++ b/src/init/ad-free.ts @@ -9,13 +9,13 @@ import { init as initTrackScrollDepth } from './consented/track-scroll-depth'; // modules not related to ad loading const commercialModules = [ - adFreeSlotRemove(), - closeDisabledSlots(), - initComscore(), - initIpsosMori(), - initTeadsCookieless(), - initTrackScrollDepth(), - initTrackGpcSignal(), + adFreeSlotRemove, + closeDisabledSlots, + initComscore, + initIpsosMori, + initTeadsCookieless, + initTrackScrollDepth, + initTrackGpcSignal, ]; const bootCommercialWhenReady = () => { diff --git a/src/init/consented-advertising.ts b/src/init/consented-advertising.ts index 457d0c75d..750ab9639 100644 --- a/src/init/consented-advertising.ts +++ b/src/init/consented-advertising.ts @@ -22,27 +22,27 @@ import { init as setAdTestInLabelsCookie } from './shared/set-adtest-in-labels-c // all modules needed for commercial code and ads to run const commercialModules = [ - adFreeSlotRemove(), - closeDisabledSlots(), - initComscore(), - initIpsosMori(), - initTeadsCookieless(), - initTrackScrollDepth(), - initTrackGpcSignal(), - initMessenger(), - setAdTestCookie(), - setAdTestInLabelsCookie(), - reloadPageOnConsentChange(), - preparePrebid(), - initDfpListeners(), + adFreeSlotRemove, + closeDisabledSlots, + initComscore, + initIpsosMori, + initTeadsCookieless, + initTrackScrollDepth, + initTrackGpcSignal, + initMessenger, + setAdTestCookie, + setAdTestInLabelsCookie, + reloadPageOnConsentChange, + preparePrebid, + initDfpListeners, // Permutive init code must run before google tag enableServices() // The permutive lib however is loaded async with the third party tags - initPermutive().then(prepareGoogletag), - initDynamicAdSlots(), - prepareA9(), - initFillSlotListener(), - prepareAdVerification(), - initThirdPartyTags(), + () => initPermutive().then(prepareGoogletag), + initDynamicAdSlots, + prepareA9, + initFillSlotListener, + prepareAdVerification, + initThirdPartyTags, ]; const bootCommercialWhenReady = () => { diff --git a/src/lib/init-utils.ts b/src/lib/init-utils.ts index 07e99dc07..533ec7e9a 100644 --- a/src/lib/init-utils.ts +++ b/src/lib/init-utils.ts @@ -25,7 +25,7 @@ const recordCommercialMetrics = () => { }; const bootCommercial = async ( - modules: Array>, + modules: Array<() => Promise>, ): Promise => { log('commercial', '📦 standalone.commercial.ts', __webpack_public_path__); if (process.env.COMMIT_SHA) { @@ -47,7 +47,9 @@ const bootCommercial = async ( }; try { - return Promise.allSettled(modules).then(recordCommercialMetrics); + return Promise.allSettled(modules.map((module) => module())).then( + recordCommercialMetrics, + ); } catch (error) { // report async errors in bootCommercial to Sentry with the commercial feature tag reportError(error, 'commercial', tags); From 304bbe0bea20b69c8151ef0907596edce4409a75 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Thu, 30 Jan 2025 10:30:03 +0000 Subject: [PATCH 6/8] Rename init-utils as commercial-boot-utils --- src/init/ad-free.ts | 2 +- src/init/consented-advertising.ts | 2 +- src/lib/{init-utils.ts => commercial-boot-utils.ts} | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/lib/{init-utils.ts => commercial-boot-utils.ts} (93%) diff --git a/src/init/ad-free.ts b/src/init/ad-free.ts index 46ce5be1f..bc293fac8 100644 --- a/src/init/ad-free.ts +++ b/src/init/ad-free.ts @@ -1,4 +1,4 @@ -import { bootCommercial } from '../lib/init-utils'; +import { bootCommercial } from '../lib/commercial-boot-utils'; import { adFreeSlotRemove } from './consented/ad-free-slot-remove'; import { init as initComscore } from './consented/comscore'; import { init as initIpsosMori } from './consented/ipsos-mori'; diff --git a/src/init/consented-advertising.ts b/src/init/consented-advertising.ts index 750ab9639..61f01e186 100644 --- a/src/init/consented-advertising.ts +++ b/src/init/consented-advertising.ts @@ -1,5 +1,5 @@ import { init as prepareAdVerification } from '../lib/ad-verification/prepare-ad-verification'; -import { bootCommercial } from '../lib/init-utils'; +import { bootCommercial } from '../lib/commercial-boot-utils'; import { adFreeSlotRemove } from './consented/ad-free-slot-remove'; import { init as initComscore } from './consented/comscore'; import { initDfpListeners } from './consented/dfp-listeners'; diff --git a/src/lib/init-utils.ts b/src/lib/commercial-boot-utils.ts similarity index 93% rename from src/lib/init-utils.ts rename to src/lib/commercial-boot-utils.ts index 533ec7e9a..709bc4151 100644 --- a/src/lib/init-utils.ts +++ b/src/lib/commercial-boot-utils.ts @@ -1,6 +1,6 @@ import { log } from '@guardian/libs'; -import { adSlotIdPrefix } from '../lib/dfp/dfp-env-globals'; -import { reportError } from '../lib/error/report-error'; +import { adSlotIdPrefix } from './dfp/dfp-env-globals'; +import { reportError } from './error/report-error'; import { EventTimer } from './event-timer'; const tags: Record = { From cc154084399dd85dc31d7bd4f80fde9824031c76 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Thu, 30 Jan 2025 10:34:08 +0000 Subject: [PATCH 7/8] Separate consentless boot checks into named const for readability --- src/commercial.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/commercial.ts b/src/commercial.ts index d3dac5a01..2aecad423 100644 --- a/src/commercial.ts +++ b/src/commercial.ts @@ -1,6 +1,16 @@ +import type { ConsentState } from '@guardian/libs'; import { getConsentFor, onConsent } from '@guardian/libs'; import { commercialFeatures } from './lib/commercial-features'; +const shouldBootConsentless = (consentState: ConsentState) => { + return ( + window.guardian.config.switches.optOutAdvertising && + consentState.tcfv2 && + !getConsentFor('googletag', consentState) && + !commercialFeatures.adFree + ); +}; + /** * Choose whether to launch Googletag or Opt Out tag (ootag) based on consent state */ @@ -11,12 +21,7 @@ void (async () => { // - in TCF region // - no consent for Googletag // - the user is not a subscriber - if ( - window.guardian.config.switches.optOutAdvertising && - consentState.tcfv2 && - !getConsentFor('googletag', consentState) && - !commercialFeatures.adFree - ) { + if (shouldBootConsentless(consentState)) { void import( /* webpackChunkName: "consentless-advertising" */ './init/consentless-advertising' From 77a618d0539f14d3a5bcad6ae58c19aee366a2fa Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Thu, 30 Jan 2025 12:16:06 +0000 Subject: [PATCH 8/8] Changeset --- .changeset/famous-poems-count.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-poems-count.md diff --git a/.changeset/famous-poems-count.md b/.changeset/famous-poems-count.md new file mode 100644 index 000000000..39975ea86 --- /dev/null +++ b/.changeset/famous-poems-count.md @@ -0,0 +1,5 @@ +--- +'@guardian/commercial': minor +--- + +Refactor boot logic for readability