@@ -2514,6 +2514,97 @@ describe("bundleToSingleHtml script order", () => {
25142514 }
25152515 } ) ;
25162516
2517+ const LOCAL_ORDER_GSAP = "https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js" ;
2518+ async function bundledBody ( scripts : string , files : Record < string , string > ) {
2519+ const dir = makeTempProject ( {
2520+ "index.html" : `<!doctype html>
2521+ <html><body>
2522+ <div data-composition-id="root" data-width="320" data-height="180"></div>
2523+ ${ scripts }
2524+ </body></html>` ,
2525+ ...files ,
2526+ } ) ;
2527+ try {
2528+ const { document } = parseHTML ( await bundleToSingleHtml ( dir ) ) ;
2529+ return [ ...document . querySelectorAll ( "body script" ) ] ;
2530+ } finally {
2531+ rmSync ( dir , { recursive : true , force : true } ) ;
2532+ }
2533+ }
2534+ const dataUrlSource = ( el : Element | undefined ) =>
2535+ decodeURIComponent (
2536+ ( el ?. getAttribute ( "src" ) ?? "" ) . replace ( / ^ d a t a : t e x t \/ j a v a s c r i p t ; c h a r s e t = u t f - 8 , / , "" ) ,
2537+ ) ;
2538+
2539+ it ( "keeps each local script in its own place around a CDN script" , async ( ) => {
2540+ const scripts = await bundledBody (
2541+ `<script src="setup.js"></script>
2542+ <script src="${ LOCAL_ORDER_GSAP } "></script>
2543+ <script src="main.js"></script>` ,
2544+ { "setup.js" : "window.SETUP_RAN = 1;" , "main.js" : "window.MAIN_RAN = gsap.version;" } ,
2545+ ) ;
2546+ const setupAt = scripts . findIndex ( ( el ) => el . textContent ?. includes ( "SETUP_RAN" ) ) ;
2547+ const gsapAt = scripts . findIndex ( ( el ) => el . getAttribute ( "src" ) === LOCAL_ORDER_GSAP ) ;
2548+ const mainAt = scripts . findIndex ( ( el ) => el . textContent ?. includes ( "MAIN_RAN" ) ) ;
2549+ expect ( setupAt ) . toBeGreaterThan ( - 1 ) ;
2550+ expect ( setupAt ) . toBeLessThan ( gsapAt ) ;
2551+ expect ( gsapAt ) . toBeLessThan ( mainAt ) ;
2552+ } ) ;
2553+
2554+ it . each ( [ "defer" , "async" ] ) (
2555+ "keeps a local %s script's own timing, with its file as the source" ,
2556+ async ( when ) => {
2557+ const scripts = await bundledBody (
2558+ `<script defer src="${ LOCAL_ORDER_GSAP } "></script>
2559+ <script ${ when } src="main.js"></script>` ,
2560+ { "main.js" : "window.MAIN_RAN = gsap.version;" } ,
2561+ ) ;
2562+ const gsapAt = scripts . findIndex ( ( el ) => el . getAttribute ( "src" ) === LOCAL_ORDER_GSAP ) ;
2563+ const main = scripts [ gsapAt + 1 ] ;
2564+ expect ( main ?. hasAttribute ( when ) ) . toBe ( true ) ;
2565+ expect ( main ?. getAttribute ( "src" ) ) . toMatch ( / ^ d a t a : t e x t \/ j a v a s c r i p t ; c h a r s e t = u t f - 8 , / ) ;
2566+ expect ( dataUrlSource ( main ) ) . toBe ( "window.MAIN_RAN = gsap.version;" ) ;
2567+ expect (
2568+ scripts . some ( ( el ) => ! el . hasAttribute ( "src" ) && el . textContent ?. includes ( "MAIN_RAN" ) ) ,
2569+ ) . toBe ( false ) ;
2570+ } ,
2571+ ) ;
2572+
2573+ it ( "keeps a throw in one local script from stopping the next" , async ( ) => {
2574+ const scripts = await bundledBody (
2575+ `<script src="a.js"></script>\n <script src="${ LOCAL_ORDER_GSAP } "></script>\n <script src="b.js"></script>` ,
2576+ {
2577+ "a.js" : "throw new Error('a');" ,
2578+ "b.js" : "window.B_RAN = 1;" ,
2579+ } ,
2580+ ) ;
2581+ const holders = scripts . filter ( ( el ) => / t h r o w n e w E r r o r | B _ R A N / . test ( el . textContent ?? "" ) ) ;
2582+ expect ( holders ) . toHaveLength ( 2 ) ;
2583+ } ) ;
2584+
2585+ it . each ( [
2586+ [ "a local file" , `<script nomodule src="legacy.js"></script>` ] ,
2587+ [ "an inline script" , `<script nomodule>window.LEGACY_RAN = 1;</script>` ] ,
2588+ ] ) ( "keeps nomodule on %s, so modern browsers still skip it" , async ( _ , legacy ) => {
2589+ const scripts = await bundledBody (
2590+ `<script>window.FIRST = 1;</script>\n ${ legacy } \n <script>window.LAST = 1;</script>` ,
2591+ {
2592+ "legacy.js" : "window.LEGACY_RAN = 1;" ,
2593+ } ,
2594+ ) ;
2595+ const holder = scripts . find ( ( el ) => el . textContent ?. includes ( "LEGACY_RAN" ) ) ;
2596+ expect ( holder ?. hasAttribute ( "nomodule" ) ) . toBe ( true ) ;
2597+ expect ( holder ?. textContent ) . not . toMatch ( / F I R S T | L A S T / ) ;
2598+ } ) ;
2599+
2600+ it ( "keeps a local script's non-JavaScript type, so it is not run as JavaScript" , async ( ) => {
2601+ const scripts = await bundledBody ( `<script type="text/babel" src="app.jsx"></script>` , {
2602+ "app.jsx" : "const App = () => <div />;" ,
2603+ } ) ;
2604+ const holder = scripts . find ( ( el ) => el . textContent ?. includes ( "const App" ) ) ;
2605+ expect ( holder ?. getAttribute ( "type" ) ) . toBe ( "text/babel" ) ;
2606+ } ) ;
2607+
25172608 it ( "still merges adjacent inline scripts into one at the end of the body" , async ( ) => {
25182609 const dir = makeTempProject ( {
25192610 "index.html" : `<!doctype html>
0 commit comments