diff --git a/dotcom-rendering/src/model/pageModel.ts b/dotcom-rendering/src/model/pageModel.ts new file mode 100644 index 00000000000..f63b5ea0e04 --- /dev/null +++ b/dotcom-rendering/src/model/pageModel.ts @@ -0,0 +1,33 @@ +import type { NavType } from 'src/model/extract-nav'; +import type { Newsletter } from 'src/types/content'; +import type { TagType } from 'src/types/tag'; +import type { EditionId } from 'src/web/lib/edition'; +import type { ConfigType } from '../types/config'; +import type { FooterType } from '../types/footer'; + +export type BasePageModel = { + webTitle: string; + description?: string; + canonicalUrl?: string; + sectionName?: string; + format?: CAPIFormat; + editionId: EditionId; + tags?: TagType[]; + renderAds?: boolean; + subscribeUrl: string; + contributionsServiceUrl: string; + beaconURL: string; + twitterData?: { + [key: string]: string; + }; + openGraphData?: { + [key: string]: string; + }; + config: ConfigType; + nav: NavType; + footer: FooterType; +}; + +export type NewslettersPageModel = BasePageModel & { + newsletters: Newsletter[]; +}; diff --git a/dotcom-rendering/src/server/dev-middleware/defaultData.ts b/dotcom-rendering/src/server/dev-middleware/defaultData.ts new file mode 100644 index 00000000000..db1d2346f49 --- /dev/null +++ b/dotcom-rendering/src/server/dev-middleware/defaultData.ts @@ -0,0 +1,192 @@ +import { ArticlePillar } from '@guardian/libs'; +import type { NavType } from 'src/model/extract-nav'; +import type { ConfigType } from 'src/types/config'; +import type { FooterType } from 'src/types/footer'; + +export const STATIC_FOOTER: FooterType = { footerLinks: [[]] }; + +export const STATIC_NAV: NavType = { + otherLinks: { + url: '/uk', + title: 'nav link', + longTitle: 'this is a nav link', + children: [], + mobileOnly: false, + more: true, + }, + brandExtensions: [], + currentNavLink: '/', + subNavSections: { + links: [ + { + url: '/world', + title: 'World', + longTitle: 'World news', + children: [], + mobileOnly: false, + more: false, + }, + { + url: '/email-newsletters', + title: 'Newsletters', + longTitle: 'Newsletters', + children: [], + mobileOnly: false, + more: false, + }, + ], + }, + readerRevenueLinks: { + header: { + contribute: 'contribute', + subscribe: 'contribute', + support: 'contribute', + supporter: 'contribute', + }, + footer: { + contribute: 'contribute', + subscribe: 'contribute', + support: 'contribute', + supporter: 'contribute', + }, + sideMenu: { + contribute: 'contribute', + subscribe: 'contribute', + support: 'contribute', + supporter: 'contribute', + }, + ampHeader: { + contribute: 'contribute', + subscribe: 'contribute', + support: 'contribute', + supporter: 'contribute', + }, + ampFooter: { + contribute: 'contribute', + subscribe: 'contribute', + support: 'contribute', + supporter: 'contribute', + }, + }, + pillars: [ + { + url: '/', + title: 'News', + longTitle: 'News', + pillar: ArticlePillar.News, + children: [ + { + title: 'World', + url: '/world', + longTitle: 'World news', + more: true, + children: [ + { + title: 'Europe', + longTitle: 'Europe', + url: '/world/europe-news', + children: [], + }, + { + title: 'US', + url: '/us-news', + longTitle: 'US news', + children: [], + }, + { + title: 'Americas', + longTitle: 'Americas', + url: '/world/americas', + children: [], + }, + { + title: 'Asia', + longTitle: 'Asia', + url: '/world/asia', + children: [], + }, + { + title: 'Australia', + url: '/australia-news', + longTitle: 'Australia news', + children: [], + }, + { + title: 'Middle East', + longTitle: 'Middle East', + url: '/world/middleeast', + children: [], + }, + { + title: 'Africa', + longTitle: 'Africa', + url: '/world/africa', + children: [], + }, + { + title: 'Inequality', + longTitle: 'Inequality', + url: '/inequality', + children: [], + }, + { + title: 'Global development', + longTitle: 'Global development', + url: '/global-development', + children: [], + }, + ], + }, + ], + }, + { + url: '/commentisfree', + title: 'Opinion', + longTitle: 'Opinion', + pillar: ArticlePillar.Opinion, + }, + { + url: '/uk/sport', + title: 'Sport', + longTitle: 'Sport', + pillar: ArticlePillar.Sport, + }, + ], +}; + +export const STATIC_CONFIG: ConfigType = { + isPaidContent: false, + pageId: 'unknown-page', + contentType: 'static', + ampIframeUrl: + 'https://assets.guim.co.uk/data/vendor/b242a49b1588bb36bdaacefe001ca77a/amp-iframe.html', + ajaxUrl: 'https://api.nextgen.guardianapps.co.uk', + shortUrlId: '/p/d8ex5', + switches: {}, + keywordIds: '', + sharedAdTargeting: {}, + dcrSentryDsn: 'https://1937ab71c8804b2b8438178dfdd6468f@sentry.io/1377847', + discussionApiUrl: 'https://discussion.theguardian.com/discussion-api', + sentryPublicApiKey: '344003a8d11c41d8800fbad8383fdc50', + commercialBundleUrl: + 'https://assets.guim.co.uk/javascripts/bc58c17d75809551440f/graun.commercial.dcr.js', + discussionApiClientHeader: 'nextgen', + shouldHideReaderRevenue: false, + sentryHost: 'app.getsentry.com/35463', + idApiUrl: 'https://idapi.theguardian.com', + showRelatedContent: true, + adUnit: '/59666047/theguardian.com/environment/article/ng', + stage: 'DEV', + isSensitive: false, + revisionNumber: 'DEV', + section: 'environment', + brazeApiKey: '7f28c639-8bda-48ff-a3f6-24345abfc07c', + dfpAccountId: '59666047', + googletagUrl: '//securepubads.g.doubleclick.net/tag/js/gpt.js', + abTests: {}, + edition: 'UK', + frontendAssetsFullURL: 'https://assets.guim.co.uk/', + webPublicationDate: Date.now(), + discussionD2Uid: 'zHoBy6HNKsk', + mmaUrl: 'https://manage.theguardian.com', +}; diff --git a/dotcom-rendering/src/server/dev-middleware/provideStaticNewslettersModel.ts b/dotcom-rendering/src/server/dev-middleware/provideStaticNewslettersModel.ts new file mode 100644 index 00000000000..5107f4b0e0c --- /dev/null +++ b/dotcom-rendering/src/server/dev-middleware/provideStaticNewslettersModel.ts @@ -0,0 +1,78 @@ +import type { RequestHandler } from 'express'; +import type { Newsletter } from '../../../src/types/content'; +import type { NewslettersPageModel } from '../../model/pageModel'; +import { STATIC_CONFIG, STATIC_FOOTER, STATIC_NAV } from './defaultData'; + +const TEST_NEWSLETTERS: Newsletter[] = [ + { + identityName: 'morning-mail', + name: "Guardian Australia's Morning Mail", + theme: 'news', + description: + 'Our Australian morning briefing email breaks down the key national and international stories of the day and why they matter', + frequency: 'Every weekday', + listId: 4148, + group: 'News in depth', + successDescription: + "We'll send you Guardian Australia's Morning Mail every weekday", + regionFocus: 'AU', + }, + { + identityName: 'moving-the-goalposts', + name: 'Moving the Goalposts', + theme: 'sport', + description: + 'Informative, passionate, entertaining. Sign up to our weekly round-up of women’s football now.', + frequency: 'Weekly', + listId: 6020, + group: 'Sport', + successDescription: "We'll send you Moving the Goalposts every week", + }, + { + listId: 123, + identityName: 'patriarchy', + description: + 'Reviewing the most important stories on feminism and sexism and those fighting for equality', + name: 'The Week in Patriarchy', + frequency: 'Weekly', + successDescription: 'You have signed up, but the newsletter is fake', + theme: 'opinion', + group: 'Opinion', + }, + { + listId: 124, + identityName: 'according-to', + description: 'A newsletter made up for testing the component', + name: 'According to us', + frequency: 'Montly', + successDescription: 'You have signed up, but the newsletter is fake', + theme: 'opinion', + group: 'Opinion', + }, +]; + +const STATIC_NEWSLETTERS_MODEL: NewslettersPageModel = { + newsletters: TEST_NEWSLETTERS, + footer: STATIC_FOOTER, + nav: STATIC_NAV, + config: { + ...STATIC_CONFIG, + pageId: 'static-email-newsletters', + }, + editionId: 'UK', + webTitle: 'Guardian newsletters: sign up', + description: + "Scroll less and understand more about the subjects you care about with the Guardian's brilliant email newsletters, free to your inbox.", + beaconURL: '//phar.gu-web.net', + subscribeUrl: '/', + contributionsServiceUrl: 'https://contributions.guardianapis.com', +}; + +export const provideStaticDataMiddleware: RequestHandler = ( + { body }, + res, + next, +): void => { + (body as Record).model = STATIC_NEWSLETTERS_MODEL; + next(); +}; diff --git a/dotcom-rendering/src/server/dev-server.ts b/dotcom-rendering/src/server/dev-server.ts index 7bd55544a53..4fdc85f59e0 100644 --- a/dotcom-rendering/src/server/dev-server.ts +++ b/dotcom-rendering/src/server/dev-server.ts @@ -8,7 +8,9 @@ import { handleFrontJson, handleInteractive, handleKeyEvents, + handleNewslettersPage, } from '../web/server'; +import { provideStaticDataMiddleware } from './dev-middleware/provideStaticNewslettersModel'; /** article URLs contain a part that looks like “2022/nov/25” */ const ARTICLE_URL = /\/\d{4}\/[a-z]{3}\/\d{2}\//; @@ -38,6 +40,10 @@ export const devServer = (): Handler => { return handleFront(req, res, next); case '/FrontJSON': return handleFrontJson(req, res, next); + case '/email-newsletters': + return provideStaticDataMiddleware(req, res, () => { + handleNewslettersPage(req, res, next); + }); default: { if (req.url.match(ARTICLE_URL)) { const url = new URL( diff --git a/dotcom-rendering/src/web/components/stand-alone/NewslettersList.tsx b/dotcom-rendering/src/web/components/stand-alone/NewslettersList.tsx new file mode 100644 index 00000000000..9e806f14d3a --- /dev/null +++ b/dotcom-rendering/src/web/components/stand-alone/NewslettersList.tsx @@ -0,0 +1,114 @@ +import { css } from '@emotion/react'; +import { brandAlt, space } from '@guardian/source-foundations'; +import { headline } from '@guardian/source-foundations/dist/cjs/typography/api'; +import { LinkButton } from '@guardian/source-react-components'; +import type { Newsletter } from '../../../../src/types/content'; +import { EmailSignup } from '../EmailSignup'; +import { Hide } from '../Hide'; +import { NewsletterPrivacyMessage } from '../NewsletterPrivacyMessage'; +import { Section } from '../Section'; + +export interface NewslettersListProps { + newsletters: Newsletter[]; + headingText: string; + mmaUrl?: string; +} + +type GroupedNewsletters = { groupName: string; newsletters: Newsletter[] }[]; + +const putNewslettersInGroups = ( + newsletters: Newsletter[], +): GroupedNewsletters => { + const groupedList: GroupedNewsletters = []; + + newsletters.forEach((newsletter) => { + const { group: groupName } = newsletter; + + const existingGroup = groupedList.find( + (grouping) => grouping.groupName === groupName, + ); + + if (existingGroup) { + existingGroup.newsletters.push(newsletter); + } else { + groupedList.push({ groupName, newsletters: [newsletter] }); + } + }); + + return groupedList; +}; + +/** + * @description + * This is a placeholder component to test the handleNewslettersPage method + * on the dev server - not an approved / final design. + */ +export const NewslettersList = ({ + newsletters, + headingText, + mmaUrl, +}: NewslettersListProps) => { + const groupedNewsletters = putNewslettersInGroups(newsletters); + + return ( + <> +
+

+ {headingText} +

+ + {!!mmaUrl && ( + + Manage my newsletters + + )} +
+ +
+
+ +
+
+ + {groupedNewsletters.map((group) => ( +
+ {group.groupName}({group.newsletters.length}) + + } + key={group.groupName} + > + +

+ {group.groupName}({group.newsletters.length}) +

+
+ {group.newsletters.map((newsletter) => ( + + ))} +
+ ))} + + ); +}; diff --git a/dotcom-rendering/src/web/layouts/StandAlonePage.tsx b/dotcom-rendering/src/web/layouts/StandAlonePage.tsx new file mode 100644 index 00000000000..184a779d57e --- /dev/null +++ b/dotcom-rendering/src/web/layouts/StandAlonePage.tsx @@ -0,0 +1,207 @@ +import { css, Global } from '@emotion/react'; +import { + brandAlt, + brandBackground, + brandBorder, + brandLine, + focusHalo, + neutral, + palette, +} from '@guardian/source-foundations'; +import { StraightLines } from '@guardian/source-react-components-development-kitchen'; +import type { ReactNode } from 'react'; +import { StrictMode } from 'react'; +import type { BasePageModel } from 'src/model/pageModel'; +import { AlreadyVisited } from '../components//AlreadyVisited.importable'; +import { CoreVitals } from '../components//CoreVitals.importable'; +import { FocusStyles } from '../components//FocusStyles.importable'; +import { Footer } from '../components/Footer'; +import { Header } from '../components/Header'; +import { HeaderAdSlot } from '../components/HeaderAdSlot'; +import { Island } from '../components/Island'; +import { Nav } from '../components/Nav/Nav'; +import { ReaderRevenueDev } from '../components/ReaderRevenueDev.importable'; +import { Section } from '../components/Section'; +import { SkipTo } from '../components/SkipTo'; +import { SubNav } from '../components/SubNav.importable'; +import { Stuck } from '../layouts/lib/stickiness'; +import { decideFormat } from '../lib/decideFormat'; + +type Props = BasePageModel & { + children: ReactNode; +}; + +/** + * @description + * Article is a high level wrapper for stand alone pages on Dotcom. Sets strict mode and some globals + * + * */ +export const StandAlonePage = ({ + children, + renderAds, + subscribeUrl, + editionId, + nav, + footer, + format, + contributionsServiceUrl: pageContributionsServiceUrl, + config, +}: Props) => { + const articleFormat: ArticleFormat = decideFormat(format ?? {}); + + const isInEuropeTest = + config.abTests.europeNetworkFrontVariant === 'variant'; + + const contributionsServiceUrl = + process.env.SDC_URL ?? pageContributionsServiceUrl; + + return ( + + + + + + + + + + + + + + + + + + + +
+ <> + {renderAds && ( + +
+ +
+
+ )} +
+
+
+
+
+ {nav.subNavSections && ( + <> +
+ + + +
+
+ +
+ + )} + +
+ +
+ {children} +
+ +
+
+
+
+ ); +}; diff --git a/dotcom-rendering/src/web/server/index.ts b/dotcom-rendering/src/web/server/index.ts index caaca88b4c6..d9368e76141 100644 --- a/dotcom-rendering/src/web/server/index.ts +++ b/dotcom-rendering/src/web/server/index.ts @@ -6,6 +6,7 @@ import { enhanceCollections } from '../../model/enhanceCollections'; import { enhanceCommercialProperties } from '../../model/enhanceCommercialProperties'; import { enhanceStandfirst } from '../../model/enhanceStandfirst'; import { enhanceTableOfContents } from '../../model/enhanceTableOfContents'; +import type { NewslettersPageModel } from '../../model/pageModel'; import { validateAsCAPIType, validateAsFrontType } from '../../model/validate'; import type { DCRFrontType, FEFrontType } from '../../types/front'; import type { FEArticleType } from '../../types/frontend'; @@ -14,6 +15,7 @@ import { articleToHtml } from './articleToHtml'; import { blocksToHtml } from './blocksToHtml'; import { frontToHtml } from './frontToHtml'; import { keyEventsToHtml } from './keyEventsToHtml'; +import { newslettersToHtml } from './newslettersToHtml'; function enhancePinnedPost(format: CAPIFormat, block?: Block) { return block ? enhanceBlocks([block], format)[0] : block; @@ -194,3 +196,15 @@ export const handleFront: RequestHandler = ({ body }, res) => { export const handleFrontJson: RequestHandler = ({ body }, res) => { res.json(enhanceFront(body)); }; + +export const handleNewslettersPage: RequestHandler = ({ body }, res) => { + try { + // TO DO - data validation function + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- in progress + const model = body.model as NewslettersPageModel; + const content = newslettersToHtml(model); + res.status(200).send(content); + } catch (e) { + res.status(500).send(`
${getStack(e)}
`); + } +}; diff --git a/dotcom-rendering/src/web/server/newslettersToHtml.tsx b/dotcom-rendering/src/web/server/newslettersToHtml.tsx new file mode 100644 index 00000000000..701f1d6a8f1 --- /dev/null +++ b/dotcom-rendering/src/web/server/newslettersToHtml.tsx @@ -0,0 +1,25 @@ +import createCache from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; +import { renderToString } from 'react-dom/server'; +import type { NewslettersPageModel } from '../../model/pageModel'; +import { NewslettersList } from '../components/stand-alone/NewslettersList'; +import { StandAlonePage } from '../layouts/StandAlonePage'; +import { populatePageTemplate } from './populatePageTemplate'; + +export const newslettersToHtml = (model: NewslettersPageModel): string => { + const key = 'dcr'; + const cache = createCache({ key }); + const html = renderToString( + + + + + , + ); + + return populatePageTemplate(model, html, cache); +}; diff --git a/dotcom-rendering/src/web/server/populatePageTemplate/index.ts b/dotcom-rendering/src/web/server/populatePageTemplate/index.ts new file mode 100644 index 00000000000..a669d3e6f2f --- /dev/null +++ b/dotcom-rendering/src/web/server/populatePageTemplate/index.ts @@ -0,0 +1,74 @@ +import type { EmotionCache } from '@emotion/cache'; +import createEmotionServer from '@emotion/server/create-instance'; +import { + BUILD_VARIANT, + dcrJavascriptBundle, +} from '../../../../scripts/webpack/bundles'; +import type { BasePageModel } from '../../../model/pageModel'; +import { extractExpeditedIslands } from '../extractIslands'; +import { pageTemplate } from '../pageTemplate'; +import { buildWindowGuardian } from './makeWindowGuardian'; +import { + getGaPath, + getLowPriorityScriptTags, + getPriorityScriptTags, +} from './scriptTags'; + +export const populatePageTemplate = ( + model: BasePageModel, + html: string, + cache: EmotionCache, +): string => { + const { + canonicalUrl, + description, + webTitle, + config, + openGraphData, + twitterData, + } = model; + const { offerHttp3 = false } = config.switches; + const shouldServeVariantBundle: boolean = [ + BUILD_VARIANT, + config.abTests[dcrJavascriptBundle('Variant')] === 'variant', + ].every(Boolean); + + // eslint-disable-next-line @typescript-eslint/unbound-method -- because + const { extractCriticalToChunks, constructStyleTagsFromChunks } = + createEmotionServer(cache); + const chunks = extractCriticalToChunks(html); + const extractedCss = constructStyleTagsFromChunks(chunks); + const expeditedIslands = extractExpeditedIslands(html); + + const priorityScriptTags = getPriorityScriptTags( + expeditedIslands, + offerHttp3, + shouldServeVariantBundle, + ); + const lowPriorityScriptTags = getLowPriorityScriptTags( + offerHttp3, + shouldServeVariantBundle, + ); + const gaPath = getGaPath(shouldServeVariantBundle); + + const windowGuardian = buildWindowGuardian(model); + + return pageTemplate({ + linkedData: {}, + priorityScriptTags, + lowPriorityScriptTags, + css: extractedCss, + html, + title: webTitle, + description, + windowGuardian, + gaPath, + ampLink: undefined, + openGraphData, + twitterData, + initTwitter: undefined, + keywords: config.keywords ?? '', + offerHttp3, + canonicalUrl, + }); +}; diff --git a/dotcom-rendering/src/web/server/populatePageTemplate/makeWindowGuardian.ts b/dotcom-rendering/src/web/server/populatePageTemplate/makeWindowGuardian.ts new file mode 100644 index 00000000000..747da499a1f --- /dev/null +++ b/dotcom-rendering/src/web/server/populatePageTemplate/makeWindowGuardian.ts @@ -0,0 +1,53 @@ +import { escapeData } from '../../../lib/escapeData'; +import { extractGA } from '../../../model/extract-ga'; +import type { BasePageModel } from '../../../model/pageModel'; +import { makeWindowGuardian } from '../../../model/window-guardian'; + +export const buildWindowGuardian = (model: BasePageModel): string => { + const { config, editionId } = model; + + /** + * We escape windowGuardian here to prevent errors when the data + * is placed in a script tag on the page + */ + const windowGuardian = escapeData( + JSON.stringify( + makeWindowGuardian({ + editionId, + stage: config.stage, + frontendAssetsFullURL: config.frontendAssetsFullURL, + revisionNumber: config.revisionNumber, + sentryPublicApiKey: config.sentryPublicApiKey, + sentryHost: config.sentryHost, + keywordIds: config.keywordIds, + dfpAccountId: config.dfpAccountId, + adUnit: config.adUnit, + ajaxUrl: config.ajaxUrl, + googletagUrl: config.googletagUrl, + switches: config.switches, + abTests: config.abTests, + brazeApiKey: config.brazeApiKey, + isPaidContent: config.isPaidContent, + contentType: config.contentType, + shouldHideReaderRevenue: config.shouldHideReaderRevenue, + GAData: extractGA({ + webTitle: model.webTitle, + format: model.format ?? { + design: 'ArticleDesign', + theme: 'NewsPillar', + display: 'StandardDisplay', + }, + sectionName: model.sectionName, + contentType: config.contentType, + tags: model.tags ?? [], + pageId: config.pageId, + editionId, + beaconURL: model.beaconURL, + }), + unknownConfig: config, + }), + ), + ); + + return windowGuardian; +}; diff --git a/dotcom-rendering/src/web/server/populatePageTemplate/scriptTags.ts b/dotcom-rendering/src/web/server/populatePageTemplate/scriptTags.ts new file mode 100644 index 00000000000..9894f6e9851 --- /dev/null +++ b/dotcom-rendering/src/web/server/populatePageTemplate/scriptTags.ts @@ -0,0 +1,80 @@ +import { + generateScriptTags, + getScriptsFromManifest, + LEGACY_SCRIPT, + MODERN_SCRIPT, + VARIANT_SCRIPT, +} from '../../../lib/assets'; +import { getHttp3Url } from '../../lib/getHttp3Url'; + +export const getPriorityScriptTags = ( + expeditedIslands: string[], + offerHttp3: boolean, + shouldServeVariantBundle: boolean, +): string[] => { + const getScriptArrayFromFile = getScriptsFromManifest( + shouldServeVariantBundle, + ); + + const polyfillIO = + 'https://assets.guim.co.uk/polyfill.io/v3/polyfill.min.js?rum=0&features=es6,es7,es2017,es2018,es2019,default-3.6,HTMLPictureElement,IntersectionObserver,IntersectionObserverEntry,URLSearchParams,fetch,NodeList.prototype.forEach,navigator.sendBeacon,performance.now,Promise.allSettled&flags=gated&callback=guardianPolyfilled&unknown=polyfill&cacheClear=1'; + + const priorityScriptTags = generateScriptTags( + [ + polyfillIO, + ...getScriptArrayFromFile('bootCmp.js'), + ...getScriptArrayFromFile('ophan.js'), + + ...getScriptArrayFromFile('sentryLoader.js'), + ...getScriptArrayFromFile('dynamicImport.js'), + + ...getScriptArrayFromFile('islands.js'), + ...expeditedIslands.flatMap((name) => + getScriptArrayFromFile(`${name}.js`), + ), + ].map((script) => + offerHttp3 && script ? getHttp3Url(script) : script, + ), + ); + return priorityScriptTags; +}; + +export const getLowPriorityScriptTags = ( + offerHttp3: boolean, + shouldServeVariantBundle: boolean, +): string[] => { + const getScriptArrayFromFile = getScriptsFromManifest( + shouldServeVariantBundle, + ); + + const lowPriorityScriptTags = generateScriptTags( + [ + ...getScriptArrayFromFile('atomIframe.js'), + ...getScriptArrayFromFile('embedIframe.js'), + ...getScriptArrayFromFile('newsletterEmbedIframe.js'), + ...getScriptArrayFromFile('relativeTime.js'), + ...getScriptArrayFromFile('initDiscussion.js'), + ].map((script) => (offerHttp3 ? getHttp3Url(script) : script)), + ); + + return lowPriorityScriptTags; +}; + +export const getGaPath = ( + shouldServeVariantBundle: boolean, +): { modern: string; legacy: string } => { + const getScriptArrayFromFile = getScriptsFromManifest( + shouldServeVariantBundle, + ); + const gaChunk = getScriptArrayFromFile('ga.js'); + const modernScript = gaChunk.find((script) => script.match(MODERN_SCRIPT)); + const legacyScript = gaChunk.find((script) => script.match(LEGACY_SCRIPT)); + const variantScript = gaChunk.find((script) => + script.match(VARIANT_SCRIPT), + ); + const gaPath = { + modern: (modernScript ?? variantScript) as string, + legacy: legacyScript as string, + }; + return gaPath; +};