@@ -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 */
3853const 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 ( / < s c r i p t [ ^ > ] * > ( [ \s \S ] * ?) < \/ s c r i p t > / i) ;
58- if ( scriptMatch && scriptMatch [ 1 ] ) {
78+ const scriptMatch = / < s c r i p t [ ^ > ] * > ( [ \s \S ] * ?) < \/ s c r i p t > / 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 */
88116export 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 */
96127export 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