Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions packages/integration/src/manifest-facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();`,
Comment on lines +54 to +55
``
].join('\n')
}
Expand Down Expand Up @@ -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')
}
18 changes: 17 additions & 1 deletion packages/integration/tests/module.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
toManifestJSON,
toManifestPreloadLinkAttrs,
toManifestPreloadLinkTag,
toBrowserManifestFacadeModule,
toEmittedGlobalsModule,
toUniversalManifestFacadeModule
} from '../src/module'
Expand Down Expand Up @@ -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')
})
})
Loading