From a8186065b8eb30ef73d82075dbeb2a6fd94c2eaa Mon Sep 17 00:00:00 2001 From: Houssein Djirdeh Date: Thu, 28 Jul 2022 16:42:52 -0400 Subject: [PATCH] [Script] Adds `onReady` prop to `next/script` (#38849) Closes: #30962 This PR adds a new `onReady` prop to `next/script` to handle shortcomings of the current `onLoad` prop. Some third-party providers and widgets require initialization code to run after the script's `load` event and every time the component is mounted. The `onReady` should solve that use case. For more details, refer to the discussion in #30962. CC @janicklas-ralph ## Bug - [X] Related issues linked using `fixes #number` - [X] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- docs/api-reference/next/script.md | 33 ++++++++++++++++-- docs/basic-features/script.md | 34 +++++++++++++++++-- packages/next/client/script.tsx | 22 +++++++++++- .../script-loader/base/pages/page8.js | 24 +++++++++++++ .../script-loader/base/pages/page9.js | 11 ++++++ .../script-loader/test/index.test.js | 24 +++++++++++++ 6 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 test/integration/script-loader/base/pages/page8.js create mode 100644 test/integration/script-loader/base/pages/page9.js diff --git a/docs/api-reference/next/script.md b/docs/api-reference/next/script.md index c1470361fcc1f..e6bed4fa6389a 100644 --- a/docs/api-reference/next/script.md +++ b/docs/api-reference/next/script.md @@ -20,6 +20,7 @@ description: Optimize loading of third-party scripts with the built-in Script co | Version | Changes | | --------- | ------------------------- | +| `v12.2.4` | `onReady` prop added. | | `v11.0.0` | `next/script` introduced. | @@ -47,9 +48,9 @@ The loading strategy of the script. ### onLoad -A method that returns additional JavaScript that should be executed after the script has finished loading. +A method that returns additional JavaScript that should be executed once after the script has finished loading. -> **Note: `onLoad` can't be used with the `beforeInteractive` loading strategy.** +> **Note: `onLoad` can't be used with the `beforeInteractive` loading strategy. Consider using `onReady` instead.** The following is an example of how to use the `onLoad` property: @@ -74,6 +75,34 @@ export default function Home() { } ``` +### onReady + +A method that returns additional JavaScript that should be executed after the script has finished loading and every time the component is mounted. + +The following is an example of how to use the `onReady` property: + +```jsx +import { useState } from 'react' +import Script from 'next/script' + +export default function Home() { + return ( + <> + + + ) +} + +export default Page diff --git a/test/integration/script-loader/base/pages/page9.js b/test/integration/script-loader/base/pages/page9.js new file mode 100644 index 0000000000000..6e82d8627683c --- /dev/null +++ b/test/integration/script-loader/base/pages/page9.js @@ -0,0 +1,11 @@ +import Link from 'next/link' + +const Page = () => { + return ( + <> + Page 8 + + ) +} + +export default Page diff --git a/test/integration/script-loader/test/index.test.js b/test/integration/script-loader/test/index.test.js index 0a5ca5d50440c..cad3fc6bede15 100644 --- a/test/integration/script-loader/test/index.test.js +++ b/test/integration/script-loader/test/index.test.js @@ -190,6 +190,30 @@ describe('Next.js Script - Primary Strategies', () => { } }) + it('onReady fires after load event and then on every subsequent re-mount', async () => { + let browser + try { + browser = await webdriver(appPort, '/page8') + + const text = await browser.elementById('text').text() + + expect(text).toBe('aaa') + + // Navigate to different page and back + await browser.waitForElementByCss('[href="/page9"]') + await browser.click('[href="/page9"]') + await browser.waitForElementByCss('[href="/page8"]') + await browser.click('[href="/page8"]') + + await browser.waitForElementByCss('.container') + const sameText = await browser.elementById('text').text() + + expect(sameText).toBe('aaa') // onReady should fire again + } finally { + if (browser) await browser.close() + } + }) + it('priority beforeInteractive with inline script', async () => { const html = await renderViaHTTP(appPort, '/page5') const $ = cheerio.load(html)