-
Notifications
You must be signed in to change notification settings - Fork 27.2k
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
next/script: Correctly apply async and defer props #52939
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add integration tests for these changes?
Thanks @timneutkens, have added. |
@@ -1748,6 +1748,50 @@ describe('Client Navigation', () => { | |||
expect(value).toBe(false) | |||
}) | |||
|
|||
it.each([true, false])( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it.each([true, false])( | |
it.each(['true', 'false'])( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will break the assertion on line 1759 (because "false"
!= false
)
(similarly for other comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, just want to make sure -${bool}
doesn't break in the future. Maybe you can change that one to JSON.stringify
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I've wrapped it with JSON.stringify.
} | ||
) | ||
|
||
it.each([true, false])( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it.each([true, false])( | |
it.each(['true', 'false'])( |
@timneutkens, can you take another look now that the comments have been addressed, or get someone else on the team to? |
@balazsorban44 can you advise on next steps for getting this reviewed? |
Allow CI Workflow Run
Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer |
packages/next/src/client/script.tsx
Outdated
@@ -4,7 +4,7 @@ import ReactDOM from 'react-dom' | |||
import React, { useEffect, useContext, useRef } from 'react' | |||
import { ScriptHTMLAttributes } from 'react' | |||
import { HeadManagerContext } from '../shared/lib/head-manager-context' | |||
import { DOMAttributeNames } from './head-manager' | |||
import { setBasicAttributesFromProps } from './head-manager' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move this function into a separate file? The way it's set up now is that it will end up pulling in head-manager
in App Router too.
After that the PR looks good to land to me.
Stats from current PRDefault Build (Increase detected
|
vercel/next.js canary | domdomegg/next.js fix-52935 | Change | |
---|---|---|---|
buildDuration | 13.8s | 13.8s | N/A |
buildDurationCached | 7.4s | 6.1s | N/A |
nodeModulesSize | 199 MB | 199 MB | |
nextStartRea..uration (ms) | 406ms | 387ms | N/A |
Client Bundles (main, webpack)
vercel/next.js canary | domdomegg/next.js fix-52935 | Change | |
---|---|---|---|
2453-HASH.js gzip | 31.4 kB | 31.4 kB | N/A |
3304.HASH.js gzip | 181 B | 181 B | ✓ |
3f784ff6-HASH.js gzip | 53.7 kB | 53.7 kB | ✓ |
8299-HASH.js gzip | 5.04 kB | 5.04 kB | N/A |
framework-HASH.js gzip | 45.2 kB | 45.2 kB | ✓ |
main-app-HASH.js gzip | 242 B | 241 B | N/A |
main-HASH.js gzip | 32.2 kB | 32.3 kB | N/A |
webpack-HASH.js gzip | 1.68 kB | 1.68 kB | N/A |
Overall change | 99 kB | 99 kB | ✓ |
Legacy Client Bundles (polyfills)
vercel/next.js canary | domdomegg/next.js fix-52935 | Change | |
---|---|---|---|
polyfills-HASH.js gzip | 31 kB | 31 kB | ✓ |
Overall change | 31 kB | 31 kB | ✓ |
Client Pages
vercel/next.js canary | domdomegg/next.js fix-52935 | Change | |
---|---|---|---|
_app-HASH.js gzip | 196 B | 197 B | N/A |
_error-HASH.js gzip | 184 B | 184 B | ✓ |
amp-HASH.js gzip | 505 B | 505 B | ✓ |
css-HASH.js gzip | 324 B | 325 B | N/A |
dynamic-HASH.js gzip | 2.5 kB | 2.5 kB | N/A |
edge-ssr-HASH.js gzip | 258 B | 258 B | ✓ |
head-HASH.js gzip | 352 B | 352 B | ✓ |
hooks-HASH.js gzip | 370 B | 371 B | N/A |
image-HASH.js gzip | 4.21 kB | 4.21 kB | ✓ |
index-HASH.js gzip | 259 B | 259 B | ✓ |
link-HASH.js gzip | 2.67 kB | 2.67 kB | N/A |
routerDirect..HASH.js gzip | 314 B | 312 B | N/A |
script-HASH.js gzip | 386 B | 386 B | ✓ |
withRouter-HASH.js gzip | 309 B | 309 B | ✓ |
1afbb74e6ecf..834.css gzip | 106 B | 106 B | ✓ |
Overall change | 6.57 kB | 6.57 kB | ✓ |
Client Build Manifests
vercel/next.js canary | domdomegg/next.js fix-52935 | Change | |
---|---|---|---|
_buildManifest.js gzip | 481 B | 484 B | N/A |
Overall change | 0 B | 0 B | ✓ |
Rendered Page Sizes
vercel/next.js canary | domdomegg/next.js fix-52935 | Change | |
---|---|---|---|
index.html gzip | 529 B | 529 B | ✓ |
link.html gzip | 542 B | 542 B | ✓ |
withRouter.html gzip | 525 B | 523 B | N/A |
Overall change | 1.07 kB | 1.07 kB | ✓ |
Edge SSR bundle Size
vercel/next.js canary | domdomegg/next.js fix-52935 | Change | |
---|---|---|---|
edge-ssr.js gzip | 95.4 kB | 95.4 kB | N/A |
page.js gzip | 3.06 kB | 3.06 kB | N/A |
Overall change | 0 B | 0 B | ✓ |
Middleware size
vercel/next.js canary | domdomegg/next.js fix-52935 | Change | |
---|---|---|---|
middleware-b..fest.js gzip | 627 B | 625 B | N/A |
middleware-r..fest.js gzip | 151 B | 151 B | ✓ |
middleware.js gzip | 25.5 kB | 25.5 kB | N/A |
edge-runtime..pack.js gzip | 839 B | 839 B | ✓ |
Overall change | 990 B | 990 B | ✓ |
Next Runtimes
vercel/next.js canary | domdomegg/next.js fix-52935 | Change | |
---|---|---|---|
app-page-exp...dev.js gzip | 170 kB | 170 kB | ✓ |
app-page-exp..prod.js gzip | 97 kB | 97 kB | ✓ |
app-page-tur..prod.js gzip | 98.8 kB | 98.8 kB | ✓ |
app-page-tur..prod.js gzip | 93 kB | 93 kB | ✓ |
app-page.run...dev.js gzip | 144 kB | 144 kB | ✓ |
app-page.run..prod.js gzip | 91.5 kB | 91.5 kB | ✓ |
app-route-ex...dev.js gzip | 21.4 kB | 21.4 kB | ✓ |
app-route-ex..prod.js gzip | 15.2 kB | 15.2 kB | ✓ |
app-route-tu..prod.js gzip | 15.2 kB | 15.2 kB | ✓ |
app-route-tu..prod.js gzip | 14.9 kB | 14.9 kB | ✓ |
app-route.ru...dev.js gzip | 21.1 kB | 21.1 kB | ✓ |
app-route.ru..prod.js gzip | 14.9 kB | 14.9 kB | ✓ |
pages-api-tu..prod.js gzip | 9.55 kB | 9.55 kB | ✓ |
pages-api.ru...dev.js gzip | 9.82 kB | 9.82 kB | ✓ |
pages-api.ru..prod.js gzip | 9.55 kB | 9.55 kB | ✓ |
pages-turbo...prod.js gzip | 22.5 kB | 22.5 kB | ✓ |
pages.runtim...dev.js gzip | 23.1 kB | 23.1 kB | ✓ |
pages.runtim..prod.js gzip | 22.4 kB | 22.4 kB | ✓ |
server.runti..prod.js gzip | 51.1 kB | 51.1 kB | ✓ |
Overall change | 945 kB | 945 kB | ✓ |
build cache Overall increase ⚠️
vercel/next.js canary | domdomegg/next.js fix-52935 | Change | |
---|---|---|---|
0.pack gzip | 1.58 MB | 1.59 MB | |
index.pack gzip | 106 kB | 106 kB | N/A |
Overall change | 1.58 MB | 1.59 MB |
Diff details
Diff for middleware.js
Diff too large to display
Diff for main-HASH.js
Diff too large to display
If CI passes please don't click update branch then it should auto-land 🙏 |
Gah, foiled by a typo. Have fixed merge conflicts again but this devleopment process is highly frustrating. Is there a way that either it's possible for me to:
In the interim, can you approve again so CI runs? |
Hi @timneutkens could I bump on running CI again? |
This looks like this will solve the exact issue I was facing, but it is unable to be merged because of a failing test. When I run this tests locally (and prettier) everything works. Do we need to kick off another CI? Thanks - looking forward to this getting merged in! |
@samcx Can you approve the CI run on this PR please? |
@domdomegg Thanks for the ping! Taking a look |
Whoo, all the CI is passing! 🎉 @ijjk can we merge this now? |
|
||
const attr = DOMAttributeNames[p] || p.toLowerCase() | ||
|
||
if (el.tagName === 'SCRIPT' && isBooleanScriptAttribute(attr)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (el.tagName === 'SCRIPT' && isBooleanScriptAttribute(attr)) { | |
if (el.tagName === 'SCRIPT' && isBooleanScriptAttribute(attr) && value !== 'false') { |
Couldn't we avoid setting the initial value here for the 'false'
case and then that should remove the need for the setAttribute(attr, ''); removeAttribute(attr)
condition below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, this will have the same problem that this PR is trying to fix:
false
, this will enter this if condition and set the attribute tofalse
, which HTML casts to"false"
which is interpreted as true (non-empty string)"false"
, this will enter the else condition and set the attribute to"false"
which is interpreted as true (non-empty string)
This also breaks for script elements, which when inserted by JavaScript automatically default to async=true. So for async=false we need to do this setAttribute/removeAttribute trick explicitly. See https://html.spec.whatwg.org/multipage/scripting.html#script-force-async
Summary
Fixes #52935
next/script
has aScript
component that supports anasync
prop. However, when scripts are loaded with theasync
prop set to false, the script is loaded as if async was set to true. This may cause scripts to execute out of order. Repro: https://github.com/domdomegg/next-async-script-reproductionI think this is occurring because Next uses setAttribute to set the async and defer attributes. However, this is not a valid way to set these properties on a script. This is because . Demo: https://jsfiddle.net/6ktpfae1/
This PR fixes this behaviour by using removeAttribute after calling setAttribute (rather than using setAttribute "false"). This appears to result in correct behaviour.
Given it appears this workaround was already applied in
next/head
, I've harmonised the code between these two.Next steps
I think this PR is ready for review. I acknowledge there are no test changes, but there are no existing tests for
next/script
at all and creating them I think would be disproportionally difficult given issues in #52943.