Skip to content

Commit 97d5654

Browse files
author
Nathan Booker
committed
f
1 parent 42ef745 commit 97d5654

3 files changed

Lines changed: 51 additions & 20 deletions

File tree

core/app/[locale]/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import { revalidate } from '~/client/revalidate-target';
1919
import { WebAnalyticsFragment } from '~/components/analytics/fragment';
2020
import { AnalyticsProvider } from '~/components/analytics/provider';
2121
import { ContainerQueryPolyfill } from '~/components/polyfills/container-query';
22+
import { FooterScripts, HeaderScripts, ScriptsFragment } from '~/components/scripts';
2223
import { routing } from '~/i18n/routing';
2324
import { getToastNotification } from '~/lib/server-toast';
24-
import { HeaderScripts, FooterScripts, ScriptsFragment } from '~/components/scripts';
2525

2626
const RootLayoutMetadataQuery = graphql(
2727
`

core/components/scripts/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export { HeaderScripts, FooterScripts } from './scripts';
2-
export { ScriptsFragment } from './fragment';
2+
export { ScriptsFragment } from './fragment';

core/components/scripts/scripts.tsx

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,50 +32,75 @@ interface ScriptsProps {
3232
scripts: ScriptsData | null;
3333
}
3434

35+
interface ScriptProps {
36+
src?: string;
37+
dangerouslySetInnerHTML?: {
38+
__html: string;
39+
};
40+
strategy: 'afterInteractive' | 'lazyOnload';
41+
integrity?: string;
42+
id?: string;
43+
}
44+
3545
/**
3646
* Shared component that renders BigCommerce scripts using Next.js Script component
47+
* @param {ScriptRendererProps} props - The props for the script renderer
48+
* @param {ScriptsData | null} props.scripts - The scripts data from GraphQL
49+
* @param {'head' | 'footer'} props.location - Where to render the scripts
50+
* @param {'afterInteractive' | 'lazyOnload'} props.strategy - The loading strategy
51+
* @returns {JSX.Element | null} JSX element containing the rendered scripts
3752
*/
3853
const ScriptRenderer = ({ scripts, location, strategy }: ScriptRendererProps) => {
3954
if (!scripts?.edges) return null;
4055

4156
const scriptNodes = removeEdgesAndNodes(scripts);
42-
const locationScripts = scriptNodes.filter((script) => script.location.toLowerCase() === location);
43-
const filteredScripts = locationScripts.filter((script) => script.visibility === 'ALL_PAGES' || script.visibility === 'STOREFRONT');
57+
const locationScripts = scriptNodes.filter(
58+
(script) => script.location.toLowerCase() === location,
59+
);
60+
const filteredScripts = locationScripts.filter(
61+
(script) => script.visibility === 'ALL_PAGES' || script.visibility === 'STOREFRONT',
62+
);
4463

4564
return (
4665
<>
4766
{filteredScripts.map((script) => {
48-
const scriptProps: any = {};
49-
67+
const scriptProps: ScriptProps = {
68+
strategy,
69+
};
70+
5071
// Handle external scripts (SrcScript)
5172
if (script.__typename === 'SrcScript' && script.src) {
5273
scriptProps.src = script.src;
5374
}
54-
75+
5576
// Handle inline scripts (InlineScript)
5677
if (script.__typename === 'InlineScript' && script.scriptTag) {
57-
const scriptMatch = script.scriptTag.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
58-
if (scriptMatch && scriptMatch[1]) {
78+
const scriptMatch = /<script[^>]*>([\s\S]*?)<\/script>/i.exec(script.scriptTag);
79+
80+
if (scriptMatch?.[1]) {
5981
scriptProps.dangerouslySetInnerHTML = {
6082
__html: scriptMatch[1],
6183
};
6284
}
6385
}
64-
65-
// Set loading strategy
66-
scriptProps.strategy = strategy;
67-
86+
6887
// Add integrity hashes if provided
69-
if (script.integrityHashes && script.integrityHashes.length > 0) {
88+
if (script.integrityHashes.length > 0) {
7089
scriptProps.integrity = script.integrityHashes.map((hashObj) => hashObj.hash).join(' ');
7190
}
72-
91+
7392
// Add id for better debugging
93+
const entityId =
94+
(script.entityId && typeof script.entityId === 'string') ||
95+
typeof script.entityId === 'number'
96+
? String(script.entityId)
97+
: `fallback-${Math.random().toString(36).substring(2, 11)}`;
98+
7499
if (script.entityId) {
75-
scriptProps.id = `bc-script-${script.entityId}`;
100+
scriptProps.id = `bc-script-${entityId}`;
76101
}
77102

78-
return <Script key={String(script.entityId)} {...scriptProps} />;
103+
return <Script key={`script-${entityId}`} {...scriptProps} />;
79104
})}
80105
</>
81106
);
@@ -84,15 +109,21 @@ const ScriptRenderer = ({ scripts, location, strategy }: ScriptRendererProps) =>
84109
/**
85110
* Renders scripts that should be placed in the <head> tag
86111
* Uses afterInteractive strategy for critical functionality
112+
* @param {ScriptsProps} props - The props containing scripts data
113+
* @param {ScriptsData | null} props.scripts - The scripts data from GraphQL
114+
* @returns {JSX.Element | null} JSX element containing header scripts
87115
*/
88116
export const HeaderScripts = ({ scripts }: ScriptsProps) => {
89-
return <ScriptRenderer scripts={scripts} location="head" strategy="afterInteractive" />;
117+
return <ScriptRenderer location="head" scripts={scripts} strategy="afterInteractive" />;
90118
};
91119

92120
/**
93121
* Renders scripts that should be placed before the closing </body> tag
94122
* Uses lazyOnload strategy for better performance
123+
* @param {ScriptsProps} props - The props containing scripts data
124+
* @param {ScriptsData | null} props.scripts - The scripts data from GraphQL
125+
* @returns {JSX.Element | null} JSX element containing footer scripts
95126
*/
96127
export const FooterScripts = ({ scripts }: ScriptsProps) => {
97-
return <ScriptRenderer scripts={scripts} location="footer" strategy="lazyOnload" />;
98-
};
128+
return <ScriptRenderer location="footer" scripts={scripts} strategy="lazyOnload" />;
129+
};

0 commit comments

Comments
 (0)