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

next/script: Correctly apply async and defer props #52939

Merged
merged 23 commits into from
Apr 15, 2024

Conversation

domdomegg
Copy link
Contributor

@domdomegg domdomegg commented Jul 20, 2023

Summary

Fixes #52935

next/script has a Script component that supports an async prop. However, when scripts are loaded with the async 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-reproduction

I 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.

@domdomegg domdomegg marked this pull request as ready for review July 20, 2023 16:17
Copy link
Member

@timneutkens timneutkens left a 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?

@domdomegg domdomegg requested a review from ztanner as a code owner August 5, 2023 23:55
@domdomegg
Copy link
Contributor Author

Thanks @timneutkens, have added.

@@ -1748,6 +1748,50 @@ describe('Client Navigation', () => {
expect(value).toBe(false)
})

it.each([true, false])(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it.each([true, false])(
it.each(['true', 'false'])(

Copy link
Contributor Author

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)

Copy link
Member

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

Copy link
Contributor Author

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])(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it.each([true, false])(
it.each(['true', 'false'])(

@domdomegg domdomegg requested a review from timneutkens August 18, 2023 08:18
@domdomegg
Copy link
Contributor Author

@timneutkens, can you take another look now that the comments have been addressed, or get someone else on the team to?

@domdomegg
Copy link
Contributor Author

@balazsorban44 can you advise on next steps for getting this reviewed?

@ijjk
Copy link
Member

ijjk commented Sep 4, 2023

Allow CI Workflow Run

  • approve CI run for commit: 848510f

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@timneutkens timneutkens added the CI approved Approve running CI for fork label Sep 4, 2023
@@ -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'
Copy link
Member

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.

@ijjk
Copy link
Member

ijjk commented Sep 4, 2023

Stats from current PR

Default Build (Increase detected ⚠️)
General Overall increase ⚠️
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 ⚠️ +5.7 kB
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 ⚠️ +7.95 kB
index.pack gzip 106 kB 106 kB N/A
Overall change 1.58 MB 1.59 MB ⚠️ +7.95 kB
Diff details
Diff for middleware.js

Diff too large to display

Diff for main-HASH.js

Diff too large to display

Commit: 9256f1f

timneutkens
timneutkens previously approved these changes Sep 11, 2023
@timneutkens
Copy link
Member

If CI passes please don't click update branch then it should auto-land 🙏

@domdomegg
Copy link
Contributor Author

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?

@domdomegg
Copy link
Contributor Author

Hi @timneutkens could I bump on running CI again?

@samcx samcx removed the CI approved Approve running CI for fork label Mar 9, 2024
@ozzyonfire
Copy link

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!

@domdomegg
Copy link
Contributor Author

@samcx Can you approve the CI run on this PR please?

@samcx
Copy link
Member

samcx commented Mar 17, 2024

@domdomegg Thanks for the ping! Taking a look :frog-eyes:

@domdomegg
Copy link
Contributor Author

domdomegg commented Apr 5, 2024

Whoo, all the CI is passing! 🎉

@ijjk can we merge this now?


const attr = DOMAttributeNames[p] || p.toLowerCase()

if (el.tagName === 'SCRIPT' && isBooleanScriptAttribute(attr)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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?

Copy link
Contributor Author

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 to false, 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

@ijjk ijjk merged commit f563940 into vercel:canary Apr 15, 2024
73 checks passed
domdomegg added a commit to bluedotimpact/meet that referenced this pull request Apr 19, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 30, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

next/script always loads scripts with async set to true
5 participants