diff --git a/packages/integration/src/manifest-facade.ts b/packages/integration/src/manifest-facade.ts index 0326d515e..df91483fb 100644 --- a/packages/integration/src/manifest-facade.ts +++ b/packages/integration/src/manifest-facade.ts @@ -41,11 +41,18 @@ export function toManifestPreloadLinkTag(href: string) { } export function toBrowserManifestFacadeModule(urlExpression: string) { + // Load the manifest with `fetch` rather than a JSON import attribute + // (`import(url, { with: { type: 'json' } })`). Import attributes are only + // supported in Safari 17.2+, Chrome 123+, and Firefox 130+; on older engines + // — notably all iOS 15/16 (Safari < 17.2) — evaluating that dynamic import, + // even inside `new Function`, throws `SyntaxError: import call expects exactly + // one argument` while the entry module is being evaluated, so the whole app + // fails to bootstrap and renders blank with no obvious console error. `fetch` + // is universally supported and needs no bundler-hiding indirection. return [ `const masterCSSManifestURL = ${urlExpression};`, - `const loadMasterCSSManifestModule = new Function('specifier', "return import(specifier, { with: { type: 'json' } })");`, - `const masterCSSManifestModule = await loadMasterCSSManifestModule(typeof masterCSSManifestURL === 'string' ? masterCSSManifestURL : masterCSSManifestURL.href);`, - `export default masterCSSManifestModule.default;`, + `const masterCSSManifestResponse = await fetch(typeof masterCSSManifestURL === 'string' ? masterCSSManifestURL : masterCSSManifestURL.href);`, + `export default await masterCSSManifestResponse.json();`, `` ].join('\n') } @@ -89,16 +96,18 @@ export function toUniversalManifestFacadeModule(urlExpression: string) { ` throw lastError;`, `}`, ``, - `async function loadMasterCSSManifestFromImport(url) {`, + `async function loadMasterCSSManifestFromFetch(url) {`, ` const specifier = typeof url === 'string' ? url : url.href;`, - ` const load = new Function('specifier', "return import(specifier, { with: { type: 'json' } })");`, - ` const manifestModule = await load(specifier);`, - ` return manifestModule.default;`, + ` const response = await fetch(specifier);`, + ` return response.json();`, `}`, ``, + // Browsers use `fetch`; only the Node branch keeps the JSON import attribute. + // See `toBrowserManifestFacadeModule` for why import attributes cannot be used + // on the browser (they break Safari < 17.2 / iOS 15-16 at parse time). `export default typeof window === 'undefined'`, ` ? await loadMasterCSSManifestFromFile(masterCSSManifestURL)`, - ` : await loadMasterCSSManifestFromImport(masterCSSManifestURL);`, + ` : await loadMasterCSSManifestFromFetch(masterCSSManifestURL);`, `` ].join('\n') } diff --git a/packages/integration/tests/module.test.ts b/packages/integration/tests/module.test.ts index 3004e5b88..30a331cfc 100644 --- a/packages/integration/tests/module.test.ts +++ b/packages/integration/tests/module.test.ts @@ -15,6 +15,7 @@ import { toManifestJSON, toManifestPreloadLinkAttrs, toManifestPreloadLinkTag, + toBrowserManifestFacadeModule, toEmittedGlobalsModule, toUniversalManifestFacadeModule } from '../src/module' @@ -69,9 +70,24 @@ describe('@master/css-integration module helpers', () => { expect(source).toContain(`join(process.cwd(), '.next', value.slice('/_next/'.length))`) expect(source).toContain(`join(process.cwd(), '.next', 'dev', value.slice('/_next/'.length))`) expect(source).toContain('for (const file of files)') + // Node branch keeps the JSON import attribute (Node supports it). expect(source).toContain(`return import(specifier, options)`) expect(source).toContain(`with: { type: 'json' }`) - expect(source).not.toContain('fetch(') + // Browser branch loads via fetch; import attributes break Safari < 17.2 (iOS 15/16). + expect(source).toContain('await fetch(specifier)') + expect(source).toContain(': await loadMasterCSSManifestFromFetch(masterCSSManifestURL)') expect(source).not.toContain('readFile') }) + + it('builds a browser manifest facade that avoids import attributes', () => { + const source = toBrowserManifestFacadeModule('new URL("./master-css-manifest.json", import.meta.url)') + + // Must load the manifest with fetch, never `import(url, { with: { type: 'json' } })`. + // Import attributes are a SyntaxError on Safari < 17.2 (all iOS 15/16), and evaluating + // them — even inside `new Function` — breaks the entire app at bootstrap with a blank page. + expect(source).toContain('await fetch(') + expect(source).toContain('.json()') + expect(source).not.toContain('with: { type:') + expect(source).not.toContain('new Function') + }) })