Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sure polyfills are added for browsers without module support #41029

Merged
merged 3 commits into from Sep 29, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -17,17 +17,10 @@ export class SubresourceIntegrityPlugin {
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
},
(assets) => {
// Collect all the entrypoint files.
// Collect all the assets.
let files = new Set<string>()
for (const entrypoint of compilation.entrypoints.values()) {
const iterator = entrypoint?.getFiles()
if (!iterator) {
continue
}

for (const file of iterator) {
files.add(file)
}
for (const asset of compilation.getAssets()) {
files.add(asset.name)
}

// For each file, deduped, calculate the file hash.
Expand Down
30 changes: 22 additions & 8 deletions packages/next/server/app-render.tsx
Expand Up @@ -1287,6 +1287,16 @@ export async function renderToHTMLOrFlight(
return flushed
}

const polyfills = buildManifest.polyfillFiles
.filter(
(polyfill) =>
polyfill.endsWith('.js') && !polyfill.endsWith('.module.js')
)
.map((polyfill) => ({
src: `${renderOpts.assetPrefix || ''}/_next/${polyfill}`,
integrity: subresourceIntegrityManifest?.[polyfill],
}))

try {
const renderStream = await renderToInitialStream({
ReactDOMServer,
Expand All @@ -1295,14 +1305,16 @@ export async function renderToHTMLOrFlight(
onError: htmlRendererErrorHandler,
nonce,
// Include hydration scripts in the HTML
bootstrapScripts: subresourceIntegrityManifest
? buildManifest.rootMainFiles.map((src) => ({
src: `${renderOpts.assetPrefix || ''}/_next/` + src,
integrity: subresourceIntegrityManifest[src],
}))
: buildManifest.rootMainFiles.map(
(src) => `${renderOpts.assetPrefix || ''}/_next/` + src
),
bootstrapScripts: [
...(subresourceIntegrityManifest
? buildManifest.rootMainFiles.map((src) => ({
src: `${renderOpts.assetPrefix || ''}/_next/` + src,
integrity: subresourceIntegrityManifest[src],
}))
: buildManifest.rootMainFiles.map(
(src) => `${renderOpts.assetPrefix || ''}/_next/` + src
)),
],
},
})

Expand All @@ -1311,6 +1323,7 @@ export async function renderToHTMLOrFlight(
generateStaticHTML: generateStaticHTML,
flushEffectHandler,
flushEffectsToHead: true,
polyfills,
})
} catch (err: any) {
// TODO-APP: show error overlay in development. `element` should probably be wrapped in AppRouter for this case.
Expand Down Expand Up @@ -1341,6 +1354,7 @@ export async function renderToHTMLOrFlight(
generateStaticHTML: generateStaticHTML,
flushEffectHandler,
flushEffectsToHead: true,
polyfills,
})
}
}
Expand Down
17 changes: 16 additions & 1 deletion packages/next/server/node-web-streams-helper.ts
Expand Up @@ -265,12 +265,14 @@ export async function continueFromInitialStream(
generateStaticHTML,
flushEffectHandler,
flushEffectsToHead,
polyfills,
}: {
suffix?: string
dataStream?: ReadableStream<Uint8Array>
generateStaticHTML: boolean
flushEffectHandler?: () => Promise<string>
flushEffectsToHead: boolean
polyfills?: { src: string; integrity: string | undefined }[]
}
): Promise<ReadableStream<Uint8Array>> {
const closeTag = '</body></html>'
Expand All @@ -289,13 +291,26 @@ export async function continueFromInitialStream(
dataStream ? createInlineDataStream(dataStream) : null,
suffixUnclosed != null ? createSuffixStream(closeTag) : null,
createHeadInjectionTransformStream(async () => {
// Inject polyfills for browsers that don't support modules. It has to be
// blocking here and can't be `defer` because other scripts have `async`.
const polyfillScripts = polyfills
? polyfills
.map(
({ src, integrity }) =>
`<script src="${src}" nomodule=""${
integrity ? ` integrity="${integrity}"` : ''
}></script>`
)
.join('')
: ''

// TODO-APP: Inject flush effects to end of head in app layout rendering, to avoid
// hydration errors. Remove this once it's ready to be handled by react itself.
const flushEffectsContent =
flushEffectHandler && flushEffectsToHead
? await flushEffectHandler()
: ''
return flushEffectsContent
return polyfillScripts + flushEffectsContent
}),
].filter(nonNullable)

Expand Down
7 changes: 7 additions & 0 deletions test/e2e/app-dir/index.test.ts
Expand Up @@ -99,6 +99,13 @@ describe('app dir', () => {
expect(html).toContain('hello from dynamic on client')
})

it('should serve polyfills for browsers that do not support modules', async () => {
const html = await renderViaHTTP(next.url, '/dashboard/index')
expect(html).toMatch(
/<script src="\/_next\/static\/chunks\/polyfills(-\w+)?\.js" nomodule="">/
)
})

// TODO-APP: handle css modules fouc in dev
it.skip('should handle css imports in next/dynamic correctly', async () => {
const browser = await webdriver(next.url, '/dashboard/index')
Expand Down