Skip to content

Commit

Permalink
Merge branch 'canary' into app/allow-empty-pages-folder
Browse files Browse the repository at this point in the history
  • Loading branch information
ijjk committed Sep 2, 2022
2 parents 1034a5b + f9706e0 commit b358f24
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 198 deletions.
2 changes: 1 addition & 1 deletion errors/middleware-upgrade-guide.md
Expand Up @@ -388,4 +388,4 @@ Prior to Next.js `v12.2`, Middleware was not executed for `_next` requests.

For cases where Middleware is used for authorization, you should migrate to use `rewrite`/`redirect` to Pages that show an authorization error, login forms, or to an API Route.

See [No Reponse Body](#no-response-body) for an example of how to migrate to use `rewrite`/`redirect`.
See [No Response Body](#no-response-body) for an example of how to migrate to use `rewrite`/`redirect`.
2 changes: 1 addition & 1 deletion packages/next/README.md
@@ -1,6 +1,6 @@
<p align="center">
<a href="https://nextjs.org">
<img src="https://assets.vercel.com/image/upload/v1607554385/repositories/next-js/next-logo.png" height="128">
<img src="https://assets.vercel.com/image/upload/v1662130559/nextjs/Icon_light_background.png" height="128">
<h1 align="center">Next.js</h1>
</a>
</p>
Expand Down
62 changes: 47 additions & 15 deletions packages/next/client/script.tsx
@@ -1,4 +1,4 @@
import React, { useEffect, useContext } from 'react'
import React, { useEffect, useContext, useRef } from 'react'
import { ScriptHTMLAttributes } from 'react'
import { HeadManagerContext } from '../shared/lib/head-manager-context'
import { DOMAttributeNames } from './head-manager'
Expand Down Expand Up @@ -57,21 +57,25 @@ const loadScript = (props: ScriptProps): void => {
return
}

/** Execute after the script first loaded */
const afterLoad = () => {
// Run onReady for the first time after load event
if (onReady) {
onReady()
}
// add cacheKey to LoadCache when load successfully
LoadCache.add(cacheKey)
}

const el = document.createElement('script')

const loadPromise = new Promise<void>((resolve, reject) => {
el.addEventListener('load', function (e) {
// add cacheKey to LoadCache when load successfully
LoadCache.add(cacheKey)

resolve()
if (onLoad) {
onLoad.call(this, e)
}
// Run onReady for the first time after load event
if (onReady) {
onReady()
}
afterLoad()
})
el.addEventListener('error', function (e) {
reject(e)
Expand All @@ -85,8 +89,7 @@ const loadScript = (props: ScriptProps): void => {
if (dangerouslySetInnerHTML) {
el.innerHTML = dangerouslySetInnerHTML.__html || ''

// add cacheKey to LoadCache for inline script
LoadCache.add(cacheKey)
afterLoad()
} else if (children) {
el.textContent =
typeof children === 'string'
Expand All @@ -95,8 +98,7 @@ const loadScript = (props: ScriptProps): void => {
? children.join('')
: ''

// add cacheKey to LoadCache for inline script
LoadCache.add(cacheKey)
afterLoad()
} else if (src) {
el.src = src
// do not add cacheKey into LoadCache for remote script here
Expand Down Expand Up @@ -174,12 +176,42 @@ function Script(props: ScriptProps): JSX.Element | null {
// Context is available only during SSR
const { updateScripts, scripts, getIsSsr } = useContext(HeadManagerContext)

/**
* - First mount:
* 1. The useEffect for onReady executes
* 2. hasOnReadyEffectCalled.current is false, but the script hasn't loaded yet (not in LoadCache)
* onReady is skipped, set hasOnReadyEffectCalled.current to true
* 3. The useEffect for loadScript executes
* Once the script is loaded, the onReady will be called by then
* [If strict mode is enabled / is wrapped in <OffScreen /> component]
* 5. The useEffect for onReady executes again
* 6. hasOnReadyEffectCalled.current is true, so entire effect is skipped
* 7. The useEffect for loadScript executes again
* 8. The script is already loaded/loading, loadScript bails out
*
* - Second mount:
* 1. The useEffect for onReady executes
* 2. hasOnReadyEffectCalled.current is false, but the script has already loaded (found in LoadCache)
* onReady is called, set hasOnReadyEffectCalled.current to true
* 3. The useEffect for loadScript executes
* 4. The script is already loaded, loadScript bails out
* [If strict mode is enabled / is wrapped in <OffScreen /> component]
* 5. The useEffect for onReady executes again
* 6. hasOnReadyEffectCalled.current is true, so entire effect is skipped
* 7. The useEffect for loadScript executes again
* 8. The script is already loaded, loadScript will bail out
*/
const hasOnReadyEffectCalled = useRef(false)

useEffect(() => {
const cacheKey = id || src
if (!hasOnReadyEffectCalled.current) {
// Run onReady if script has loaded before but component is re-mounted
if (onReady && cacheKey && LoadCache.has(cacheKey)) {
onReady()
}

// Run onReady if script has loaded before but component is re-mounted
if (onReady && cacheKey && LoadCache.has(cacheKey)) {
onReady()
hasOnReadyEffectCalled.current = true
}
}, [onReady, id, src])

Expand Down
7 changes: 2 additions & 5 deletions test/integration/script-loader/base/pages/page10.js
@@ -1,11 +1,6 @@
import Script from 'next/script'
import Link from 'next/link'

if (typeof window !== 'undefined') {
window.remoteScriptsOnReadyCalls ??= 0
window.inlineScriptsOnReadyCalls ??= 0
}

const Page = () => {
return (
<div className="container">
Expand All @@ -14,13 +9,15 @@ const Page = () => {
<Script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"
onReady={() => {
window.remoteScriptsOnReadyCalls ??= 0
window.remoteScriptsOnReadyCalls++
}}
/>
<Script
id="i-am-an-inline-script-that-has-on-ready"
dangerouslySetInnerHTML={{ __html: 'console.log("inline script!")' }}
onReady={() => {
window.inlineScriptsOnReadyCalls ??= 0
window.inlineScriptsOnReadyCalls++
}}
/>
Expand Down
31 changes: 0 additions & 31 deletions test/integration/script-loader/strictmode/pages/onready.js

This file was deleted.

0 comments on commit b358f24

Please sign in to comment.