From edabb95f3aa9d9d6b666b30d6046de51d20458cf Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Sat, 4 Apr 2020 14:36:29 -0700 Subject: [PATCH 01/20] Updating native-url version --- packages/next/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/package.json b/packages/next/package.json index a72c2e1e6af4b..3f0e8fd3f508c 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -83,7 +83,7 @@ "jest-worker": "24.9.0", "loader-utils": "2.0.0", "mini-css-extract-plugin": "0.8.0", - "native-url": "0.2.6", + "native-url": "0.3.0", "pnp-webpack-plugin": "1.5.0", "postcss": "7.0.27", "prop-types": "15.7.2", From c1470991c1efb7b995961e0d743d6029768ade6a Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Sat, 4 Apr 2020 17:32:37 -0700 Subject: [PATCH 02/20] Bump version --- packages/next/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/package.json b/packages/next/package.json index 3f0e8fd3f508c..8cb0f1dd11d3d 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -83,7 +83,7 @@ "jest-worker": "24.9.0", "loader-utils": "2.0.0", "mini-css-extract-plugin": "0.8.0", - "native-url": "0.3.0", + "native-url": "0.3.1", "pnp-webpack-plugin": "1.5.0", "postcss": "7.0.27", "prop-types": "15.7.2", From 38aa0afdb4ce0114d5d2a7eaa8a090a27417a976 Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Mon, 28 Sep 2020 11:48:40 -0700 Subject: [PATCH 03/20] ScriptLoader component --- packages/next/client/script.tsx | 148 ++++++++++++++++++ .../next-server/lib/head-manager-context.ts | 2 + packages/next/next-server/lib/utils.ts | 1 + packages/next/next-server/server/render.tsx | 10 ++ packages/next/pages/_document.tsx | 80 ++++++++-- packages/next/script.d.ts | 2 + packages/next/script.js | 1 + 7 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 packages/next/client/script.tsx create mode 100644 packages/next/script.d.ts create mode 100644 packages/next/script.js diff --git a/packages/next/client/script.tsx b/packages/next/client/script.tsx new file mode 100644 index 0000000000000..a6e571307f406 --- /dev/null +++ b/packages/next/client/script.tsx @@ -0,0 +1,148 @@ +import React, { useEffect, useContext } from 'react' +import { ScriptHTMLAttributes } from 'react' +import { HeadManagerContext } from '../next-server/lib/head-manager-context' + +const ScriptCache = new Set() + +const DOMAttributeNames: Record = { + acceptCharset: 'accept-charset', + className: 'class', + htmlFor: 'for', + httpEquiv: 'http-equiv', +} + +interface Props extends ScriptHTMLAttributes { + priority?: string + key?: string + onLoad?: () => void + onError?: () => void + children?: any + preload?: boolean +} + +const onErrorHandler: Function = ( + cacheKey: string, + onError: Function +) => () => { + if (cacheKey) { + ScriptCache.delete(cacheKey) + } + + if (onError) { + onError() + } +} + +const loadScript = (props: Props) => { + const { + src = '', + onLoad = () => {}, + dangerouslySetInnerHTML, + children = '', + key, + onError, + } = props + + if (ScriptCache.has(key || src)) { + return + } + + const el = document.createElement('script') + const cacheKey = key || src + + if (cacheKey) { + ScriptCache.add(cacheKey) + } + + if (dangerouslySetInnerHTML) { + el.innerHTML = dangerouslySetInnerHTML.__html || '' + } else if (children) { + el.textContent = typeof children === 'string' ? children : children.join('') + } else if (src) { + el.src = src + } + + el.onload = onLoad + el.onerror = onErrorHandler(cacheKey, onError) + + for (const [k, value] of Object.entries(props)) { + if (value === undefined) { + continue + } + + const attr = DOMAttributeNames[k] || k.toLowerCase() + el.setAttribute(attr, value) + } + + document.body.appendChild(el) +} + +export default function Script(props: Props) { + const { + src = '', + onLoad = () => {}, + dangerouslySetInnerHTML, + children = '', + priority = 'default', + key, + onError, + preload = true, + ...restProps + } = props + + // Context is available only during SSR + const { updateScripts, scripts } = useContext(HeadManagerContext) + + useEffect(() => { + if (priority === 'default') { + loadScript(props) + } else if (priority === 'lazy') { + window.addEventListener('load', () => { + if ('requestIdleCallback' in window) { + ;(window as any).requestIdleCallback(() => loadScript(props), { + timeout: 3000, + }) + } else { + setTimeout(() => loadScript(props), 0) + } + }) + } + }, [priority, props]) + + if (priority === 'renderBlocking') { + const syncProps: Props = { ...restProps } + + for (const [k, value] of Object.entries({ + src, + onLoad, + onError, + dangerouslySetInnerHTML, + children, + })) { + if (!value) { + continue + } + if (k === 'children') { + syncProps.dangerouslySetInnerHTML = { + __html: typeof value === 'string' ? value : value.join(''), + } + } else { + ;(syncProps as any)[k] = value + } + } + + return +
index
+ + ) +} + +export default Page diff --git a/test/integration/script-loader/pages/page1.js b/test/integration/script-loader/pages/page1.js new file mode 100644 index 0000000000000..c82d2466e43e3 --- /dev/null +++ b/test/integration/script-loader/pages/page1.js @@ -0,0 +1,16 @@ +import Script from 'next/script' + +const Page = () => { + return ( +
+ +
page1
+
+ ) +} + +export default Page diff --git a/test/integration/script-loader/pages/page2.js b/test/integration/script-loader/pages/page2.js new file mode 100644 index 0000000000000..f32d2556a086d --- /dev/null +++ b/test/integration/script-loader/pages/page2.js @@ -0,0 +1,16 @@ +import Script from 'next/script' + +const Page = () => { + return ( +
+ +
page2
+
+ ) +} + +export default Page diff --git a/test/integration/script-loader/test/index.test.js b/test/integration/script-loader/test/index.test.js new file mode 100644 index 0000000000000..fb6caf21cbf93 --- /dev/null +++ b/test/integration/script-loader/test/index.test.js @@ -0,0 +1,86 @@ +/* eslint-env jest */ + +import { join } from 'path' +import { + renderViaHTTP, + nextServer, + startApp, + stopApp, + nextBuild, + waitFor, +} from 'next-test-utils' +import webdriver from 'next-webdriver' +import cheerio from 'cheerio' + +jest.setTimeout(1000 * 60 * 5) + +let appDir = join(__dirname, '..') +let server +let appPort + +describe('Script Loader', () => { + beforeAll(async () => { + await nextBuild(appDir) + const app = nextServer({ + dir: appDir, + dev: false, + quiet: true, + }) + + server = await startApp(app) + appPort = server.address().port + }) + afterAll(() => { + stopApp(server) + }) + + it('priority default', async () => { + let browser + try { + browser = await webdriver(appPort, '/') + const preloads = await browser.elementsByCss('link[rel=preload]') + const preloadEndHref = await preloads[preloads.length - 1].getAttribute( + 'href' + ) + + await waitFor(1000) + + const script = await browser.elementById('script') + const src = await script.getAttribute('src') + const scripts = await browser.elementsByCss('script') + const scriptEnd = await scripts[scripts.length - 1].getAttribute('id') + + // Renders script tag + expect(script).toBeDefined() + // Renders preload + expect(preloadEndHref).toBe(src) + // Script is inserted at the end + expect(scriptEnd).toBe('script') + } finally { + if (browser) await browser.close() + } + }) + + it('priority beforeHydrate', async () => { + const html = await renderViaHTTP(appPort, '/page1') + const $ = cheerio.load(html) + + const preloads = $('link[rel=preload]').toArray() + + // Preload is inserted at the beginning + expect(preloads[0].attribs.href).toMatch(/beforeHydrate/) + // Script is inserted before NextScripts + expect( + $('#__NEXT_DATA__ ~ #script ~ script[src^="/_next/static/chunks/main"]') + .length + ).toBeGreaterThan(0) + }) + + it('priority renderBlocking', async () => { + const html = await renderViaHTTP(appPort, '/page2') + const $ = cheerio.load(html) + + // Script is inserted in place + expect($('.container #script').length).toBeGreaterThan(0) + }) +}) From aab70cd26db67c374f98b893d022262b146da58a Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Thu, 1 Oct 2020 16:43:12 -0700 Subject: [PATCH 05/20] Fixing review comments --- packages/next/client/script.tsx | 16 ++++----- packages/next/pages/_document.tsx | 4 +-- .../script-loader/test/index.test.js | 36 +++++++++++++------ 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/packages/next/client/script.tsx b/packages/next/client/script.tsx index 2bdc6398ebe5b..451d5e897a868 100644 --- a/packages/next/client/script.tsx +++ b/packages/next/client/script.tsx @@ -12,7 +12,7 @@ const DOMAttributeNames: Record = { } interface Props extends ScriptHTMLAttributes { - priority?: string + strategy?: 'default' | 'lazy' | 'renderBlocking' | 'beforeHydrate' key?: string onLoad?: () => void onError?: () => void @@ -83,7 +83,7 @@ export default function Script(props: Props) { onLoad = () => {}, dangerouslySetInnerHTML, children = '', - priority = 'default', + strategy = 'default', key, onError, preload = true, @@ -94,9 +94,9 @@ export default function Script(props: Props) { const { updateScripts, scripts } = useContext(HeadManagerContext) useEffect(() => { - if (priority === 'default') { + if (strategy === 'default') { loadScript(props) - } else if (priority === 'lazy') { + } else if (strategy === 'lazy') { window.addEventListener('load', () => { if ('requestIdleCallback' in window) { ;(window as any).requestIdleCallback(() => loadScript(props), { @@ -107,9 +107,9 @@ export default function Script(props: Props) { } }) } - }, [priority, props]) + }, [strategy, props]) - if (priority === 'renderBlocking') { + if (strategy === 'renderBlocking') { const syncProps: Props = { ...restProps } for (const [k, value] of Object.entries({ @@ -132,12 +132,12 @@ export default function Script(props: Props) { } return
page1
diff --git a/test/integration/script-loader/pages/page2.js b/test/integration/script-loader/pages/page2.js index f32d2556a086d..1134c3254a09e 100644 --- a/test/integration/script-loader/pages/page2.js +++ b/test/integration/script-loader/pages/page2.js @@ -6,7 +6,7 @@ const Page = () => {
page2
From bf72597feeeae90d8d5c3974ba38526f3c1104d5 Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Wed, 14 Oct 2020 22:33:52 -0700 Subject: [PATCH 07/20] Addressing review comments. Updating loading names and adding test case for lazy load --- packages/next/client/script.tsx | 18 ++++---- packages/next/next-server/lib/utils.ts | 2 +- packages/next/pages/_document.tsx | 6 +-- test/integration/script-loader/pages/_app.js | 7 +++ .../script-loader/pages/_document.js | 30 +++++++++++++ test/integration/script-loader/pages/index.js | 3 +- test/integration/script-loader/pages/page1.js | 4 +- test/integration/script-loader/pages/page2.js | 4 +- test/integration/script-loader/pages/page3.js | 23 ++++++++++ .../script-loader/styles/styles.css | 6 +++ .../script-loader/test/index.test.js | 43 +++++++++++++++++-- 11 files changed, 124 insertions(+), 22 deletions(-) create mode 100644 test/integration/script-loader/pages/_app.js create mode 100644 test/integration/script-loader/pages/_document.js create mode 100644 test/integration/script-loader/pages/page3.js create mode 100644 test/integration/script-loader/styles/styles.css diff --git a/packages/next/client/script.tsx b/packages/next/client/script.tsx index be2305a2f0284..23baa91a7a9a0 100644 --- a/packages/next/client/script.tsx +++ b/packages/next/client/script.tsx @@ -12,7 +12,7 @@ const DOMAttributeNames: Record = { } interface Props extends ScriptHTMLAttributes { - strategy?: 'after1P' | 'lazy' | 'renderBlocking' | 'before1P' + strategy?: 'defer' | 'lazy' | 'dangerousRenderBlocking' | 'eager' key?: string onLoad?: () => void onError?: () => void @@ -83,10 +83,10 @@ export default function Script(props: Props) { onLoad = () => {}, dangerouslySetInnerHTML, children = '', - strategy = 'after1P', + strategy = 'defer', key, onError, - preload = true, + preload = false, ...restProps } = props @@ -94,7 +94,7 @@ export default function Script(props: Props) { const { updateScripts, scripts } = useContext(HeadManagerContext) useEffect(() => { - if (strategy === 'after1P') { + if (strategy === 'defer') { loadScript(props) } else if (strategy === 'lazy') { window.addEventListener('load', () => { @@ -109,7 +109,7 @@ export default function Script(props: Props) { } }, [strategy, props]) - if (strategy === 'renderBlocking') { + if (strategy === 'dangerousRenderBlocking') { const syncProps: Props = { ...restProps } for (const [k, value] of Object.entries({ @@ -132,14 +132,14 @@ export default function Script(props: Props) { } return
index
diff --git a/test/integration/script-loader/pages/page1.js b/test/integration/script-loader/pages/page1.js index 45ec72bd9c797..590b9e37873f8 100644 --- a/test/integration/script-loader/pages/page1.js +++ b/test/integration/script-loader/pages/page1.js @@ -5,8 +5,8 @@ const Page = () => {
page1
diff --git a/test/integration/script-loader/pages/page2.js b/test/integration/script-loader/pages/page2.js index 1134c3254a09e..8c66f4a160dc6 100644 --- a/test/integration/script-loader/pages/page2.js +++ b/test/integration/script-loader/pages/page2.js @@ -5,8 +5,8 @@ const Page = () => {
page2
diff --git a/test/integration/script-loader/pages/page3.js b/test/integration/script-loader/pages/page3.js new file mode 100644 index 0000000000000..a285b76924d46 --- /dev/null +++ b/test/integration/script-loader/pages/page3.js @@ -0,0 +1,23 @@ +import Script from 'next/script' + +const Page = () => { + return ( +
+ + +
page3
+
+ ) +} + +export default Page diff --git a/test/integration/script-loader/styles/styles.css b/test/integration/script-loader/styles/styles.css new file mode 100644 index 0000000000000..301c8e48e6e18 --- /dev/null +++ b/test/integration/script-loader/styles/styles.css @@ -0,0 +1,6 @@ +body { + font-family: 'Arial', sans-serif; + padding: 20px 20px 60px; + max-width: 680px; + margin: 0 auto; +} diff --git a/test/integration/script-loader/test/index.test.js b/test/integration/script-loader/test/index.test.js index e07fd42efe773..6780c36222616 100644 --- a/test/integration/script-loader/test/index.test.js +++ b/test/integration/script-loader/test/index.test.js @@ -34,7 +34,7 @@ describe('Script Loader', () => { stopApp(server) }) - it('priority default', async () => { + it('priority defer', async () => { let browser try { browser = await webdriver(appPort, '/') @@ -55,7 +55,7 @@ describe('Script Loader', () => { // Renders script tag expect(script).toBeDefined() // Renders preload - expect(scriptPreload).toBeDefined() + expect(scriptPreload.length).toBeGreaterThan(0) // Script is inserted at the end expect(endScripts.length).toBe(0) //Preload is defined at the end @@ -65,7 +65,29 @@ describe('Script Loader', () => { } }) - it('priority beforeHydrate', async () => { + it('priority lazy', async () => { + let browser + try { + browser = await webdriver(appPort, '/page3') + + await browser.waitForElementByCss('#onload-div') + await waitFor(1000) + + const script = await browser.elementById('script') + const endScripts = await browser.elementsByCss( + '#script ~ script[src^="/_next/static/"]' + ) + + // Renders script tag + expect(script).toBeDefined() + // Script is inserted at the end + expect(endScripts.length).toBe(0) + } finally { + if (browser) await browser.close() + } + }) + + it('priority eager', async () => { const html = await renderViaHTTP(appPort, '/page1') const $ = cheerio.load(html) @@ -83,6 +105,19 @@ describe('Script Loader', () => { `link[rel=preload][href^="/_next/static/chunks/main"] ~ link[rel=preload][href="${src}"]` ).length ).toBeTruthy() + + // Preload is inserted after fonts and CSS + expect( + $( + `link[rel=stylesheet][href^="/_next/static/css"] ~ link[rel=preload][href="${src}"]` + ).length + ).toBeGreaterThan(0) + expect( + $( + `link[rel=stylesheet][href="https://fonts.googleapis.com/css?family=Voces"] ~ link[rel=preload][href="${src}"]` + ).length + ).toBeGreaterThan(0) + // Script is inserted before NextScripts expect( $('#__NEXT_DATA__ ~ #script ~ script[src^="/_next/static/chunks/main"]') @@ -90,7 +125,7 @@ describe('Script Loader', () => { ).toBeGreaterThan(0) }) - it('priority renderBlocking', async () => { + it('priority dangerousRenderBlocking', async () => { const html = await renderViaHTTP(appPort, '/page2') const $ = cheerio.load(html) From 2ed565626896b17a01b6ac5aaf1ed4a7b41e69a4 Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Fri, 6 Nov 2020 15:09:18 -0800 Subject: [PATCH 08/20] Add unstable prefix to script loader component --- packages/next/{script.d.ts => unstable-script.d.ts} | 0 packages/next/{script.js => unstable-script.js} | 0 test/integration/script-loader/pages/index.js | 2 +- test/integration/script-loader/pages/page1.js | 2 +- test/integration/script-loader/pages/page2.js | 2 +- test/integration/script-loader/pages/page3.js | 2 +- 6 files changed, 4 insertions(+), 4 deletions(-) rename packages/next/{script.d.ts => unstable-script.d.ts} (100%) rename packages/next/{script.js => unstable-script.js} (100%) diff --git a/packages/next/script.d.ts b/packages/next/unstable-script.d.ts similarity index 100% rename from packages/next/script.d.ts rename to packages/next/unstable-script.d.ts diff --git a/packages/next/script.js b/packages/next/unstable-script.js similarity index 100% rename from packages/next/script.js rename to packages/next/unstable-script.js diff --git a/test/integration/script-loader/pages/index.js b/test/integration/script-loader/pages/index.js index 0f426d33ec529..4619e0f2010b4 100644 --- a/test/integration/script-loader/pages/index.js +++ b/test/integration/script-loader/pages/index.js @@ -1,4 +1,4 @@ -import Script from 'next/script' +import Script from 'next/unstable-script' const Page = () => { return ( diff --git a/test/integration/script-loader/pages/page1.js b/test/integration/script-loader/pages/page1.js index 590b9e37873f8..07929f0bf4392 100644 --- a/test/integration/script-loader/pages/page1.js +++ b/test/integration/script-loader/pages/page1.js @@ -1,4 +1,4 @@ -import Script from 'next/script' +import Script from 'next/unstable-script' const Page = () => { return ( diff --git a/test/integration/script-loader/pages/page2.js b/test/integration/script-loader/pages/page2.js index 8c66f4a160dc6..ee127f5a790d7 100644 --- a/test/integration/script-loader/pages/page2.js +++ b/test/integration/script-loader/pages/page2.js @@ -1,4 +1,4 @@ -import Script from 'next/script' +import Script from 'next/unstable-script' const Page = () => { return ( diff --git a/test/integration/script-loader/pages/page3.js b/test/integration/script-loader/pages/page3.js index a285b76924d46..25db3152ceb67 100644 --- a/test/integration/script-loader/pages/page3.js +++ b/test/integration/script-loader/pages/page3.js @@ -1,4 +1,4 @@ -import Script from 'next/script' +import Script from 'next/unstable-script' const Page = () => { return ( From cea970b440861995921665dacf7cbfc28c2b5827 Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Tue, 10 Nov 2020 13:48:52 -0800 Subject: [PATCH 09/20] Execute onLoad each time the script is called --- packages/next/client/script.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/next/client/script.tsx b/packages/next/client/script.tsx index 23baa91a7a9a0..1fb32c87fee81 100644 --- a/packages/next/client/script.tsx +++ b/packages/next/client/script.tsx @@ -44,7 +44,8 @@ const loadScript = (props: Props) => { } = props if (ScriptCache.has(key || src)) { - return + // Execute onLoad since the script has already loaded + return onLoad() } const el = document.createElement('script') From 09093e102a53b2892ca8ca466f2670603f67f593 Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Tue, 17 Nov 2020 01:13:51 -0800 Subject: [PATCH 10/20] Resolve review comments. Fire onload correctly --- .../{script.tsx => experimental-script.tsx} | 47 ++++++++++++------- packages/next/experimental-script.d.ts | 2 + packages/next/experimental-script.js | 1 + packages/next/unstable-script.d.ts | 2 - packages/next/unstable-script.js | 1 - test/integration/script-loader/pages/index.js | 2 +- test/integration/script-loader/pages/page1.js | 2 +- test/integration/script-loader/pages/page2.js | 2 +- test/integration/script-loader/pages/page3.js | 2 +- test/integration/script-loader/pages/page4.js | 38 +++++++++++++++ .../script-loader/test/index.test.js | 14 ++++++ 11 files changed, 89 insertions(+), 24 deletions(-) rename packages/next/client/{script.tsx => experimental-script.tsx} (80%) create mode 100644 packages/next/experimental-script.d.ts create mode 100644 packages/next/experimental-script.js delete mode 100644 packages/next/unstable-script.d.ts delete mode 100644 packages/next/unstable-script.js create mode 100644 test/integration/script-loader/pages/page4.js diff --git a/packages/next/client/script.tsx b/packages/next/client/experimental-script.tsx similarity index 80% rename from packages/next/client/script.tsx rename to packages/next/client/experimental-script.tsx index 1fb32c87fee81..78f21e59cf5d9 100644 --- a/packages/next/client/script.tsx +++ b/packages/next/client/experimental-script.tsx @@ -2,7 +2,8 @@ import React, { useEffect, useContext } from 'react' import { ScriptHTMLAttributes } from 'react' import { HeadManagerContext } from '../next-server/lib/head-manager-context' -const ScriptCache = new Set() +const ScriptCache = new Map() +const LoadCache = new Set() const DOMAttributeNames: Record = { acceptCharset: 'accept-charset', @@ -20,13 +21,8 @@ interface Props extends ScriptHTMLAttributes { preload?: boolean } -const onErrorHandler: Function = ( - cacheKey: string, - onError: Function -) => () => { - if (cacheKey) { - ScriptCache.delete(cacheKey) - } +const onErrorHandler: Function = (src: string, onError: Function) => () => { + ScriptCache.delete(src) if (onError) { onError() @@ -43,17 +39,33 @@ const loadScript = (props: Props) => { onError, } = props - if (ScriptCache.has(key || src)) { - // Execute onLoad since the script has already loaded - return onLoad() + const cacheKey = key || src + if (ScriptCache.has(src)) { + if (!LoadCache.has(cacheKey)) { + // Execute onLoad since the script loading has begun + ScriptCache.get(src).then(() => { + LoadCache.add(cacheKey) + onLoad() + }) + } + return } const el = document.createElement('script') - const cacheKey = key || src - if (cacheKey) { - ScriptCache.add(cacheKey) - } + const loadPromise = new Promise((resolve, reject) => { + el.addEventListener('load', function () { + resolve() + if (onLoad) { + LoadCache.add(cacheKey) + onLoad.call(this) + } + }) + el.addEventListener('error', function () { + reject() + onErrorHandler(src, onError) + }) + }) if (dangerouslySetInnerHTML) { el.innerHTML = dangerouslySetInnerHTML.__html || '' @@ -63,8 +75,9 @@ const loadScript = (props: Props) => { el.src = src } - el.onload = onLoad - el.onerror = onErrorHandler(cacheKey, onError) + if (cacheKey) { + ScriptCache.set(src, loadPromise) + } for (const [k, value] of Object.entries(props)) { if (value === undefined) { diff --git a/packages/next/experimental-script.d.ts b/packages/next/experimental-script.d.ts new file mode 100644 index 0000000000000..7bf25c2a85804 --- /dev/null +++ b/packages/next/experimental-script.d.ts @@ -0,0 +1,2 @@ +export * from './dist/client/experimental-script' +export { default } from './dist/client/experimental-script' diff --git a/packages/next/experimental-script.js b/packages/next/experimental-script.js new file mode 100644 index 0000000000000..9ba204e606219 --- /dev/null +++ b/packages/next/experimental-script.js @@ -0,0 +1 @@ +module.exports = require('./dist/client/experimental-script') diff --git a/packages/next/unstable-script.d.ts b/packages/next/unstable-script.d.ts deleted file mode 100644 index 8863afb4c85e0..0000000000000 --- a/packages/next/unstable-script.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './dist/client/script' -export { default } from './dist/client/script' diff --git a/packages/next/unstable-script.js b/packages/next/unstable-script.js deleted file mode 100644 index 4e0f885ac60a3..0000000000000 --- a/packages/next/unstable-script.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/client/script') diff --git a/test/integration/script-loader/pages/index.js b/test/integration/script-loader/pages/index.js index 4619e0f2010b4..bac7e6eb91245 100644 --- a/test/integration/script-loader/pages/index.js +++ b/test/integration/script-loader/pages/index.js @@ -1,4 +1,4 @@ -import Script from 'next/unstable-script' +import Script from 'next/experimental-script' const Page = () => { return ( diff --git a/test/integration/script-loader/pages/page1.js b/test/integration/script-loader/pages/page1.js index 07929f0bf4392..9fea586b4a736 100644 --- a/test/integration/script-loader/pages/page1.js +++ b/test/integration/script-loader/pages/page1.js @@ -1,4 +1,4 @@ -import Script from 'next/unstable-script' +import Script from 'next/experimental-script' const Page = () => { return ( diff --git a/test/integration/script-loader/pages/page2.js b/test/integration/script-loader/pages/page2.js index ee127f5a790d7..48b4733f423b4 100644 --- a/test/integration/script-loader/pages/page2.js +++ b/test/integration/script-loader/pages/page2.js @@ -1,4 +1,4 @@ -import Script from 'next/unstable-script' +import Script from 'next/experimental-script' const Page = () => { return ( diff --git a/test/integration/script-loader/pages/page3.js b/test/integration/script-loader/pages/page3.js index 25db3152ceb67..5ee81d1a30dba 100644 --- a/test/integration/script-loader/pages/page3.js +++ b/test/integration/script-loader/pages/page3.js @@ -1,4 +1,4 @@ -import Script from 'next/unstable-script' +import Script from 'next/experimental-script' const Page = () => { return ( diff --git a/test/integration/script-loader/pages/page4.js b/test/integration/script-loader/pages/page4.js new file mode 100644 index 0000000000000..de2544606b07d --- /dev/null +++ b/test/integration/script-loader/pages/page4.js @@ -0,0 +1,38 @@ +import Script from 'next/experimental-script' + +const url = + 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js' + +const Page = () => { + return ( +
+ + + +
+
+ ) +} + +export default Page diff --git a/test/integration/script-loader/test/index.test.js b/test/integration/script-loader/test/index.test.js index 6780c36222616..21f0ffe1daa8a 100644 --- a/test/integration/script-loader/test/index.test.js +++ b/test/integration/script-loader/test/index.test.js @@ -132,4 +132,18 @@ describe('Script Loader', () => { // Script is inserted in place expect($('.container #script').length).toBeGreaterThan(0) }) + + it('onloads fire correctly', async () => { + let browser + try { + browser = await webdriver(appPort, '/page4') + await waitFor(3000) + + const text = await browser.elementById('text').text() + + expect(text).toBe('aaabbbccc') + } finally { + if (browser) await browser.close() + } + }) }) From c6455c2c62ba2d21aa907e73edc6f8ce756cca90 Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Fri, 20 Nov 2020 12:57:49 -0800 Subject: [PATCH 11/20] Resolving comments --- packages/next/client/experimental-script.tsx | 49 ++++++------------- packages/next/client/head-manager.ts | 2 +- test/integration/script-loader/pages/page2.js | 4 +- .../script-loader/test/index.test.js | 2 +- 4 files changed, 18 insertions(+), 39 deletions(-) diff --git a/packages/next/client/experimental-script.tsx b/packages/next/client/experimental-script.tsx index 78f21e59cf5d9..4b83f308c94c4 100644 --- a/packages/next/client/experimental-script.tsx +++ b/packages/next/client/experimental-script.tsx @@ -1,34 +1,21 @@ import React, { useEffect, useContext } from 'react' import { ScriptHTMLAttributes } from 'react' import { HeadManagerContext } from '../next-server/lib/head-manager-context' +import { DOMAttributeNames } from './head-manager' +import requestIdleCallback from './request-idle-callback' const ScriptCache = new Map() const LoadCache = new Set() -const DOMAttributeNames: Record = { - acceptCharset: 'accept-charset', - className: 'class', - htmlFor: 'for', - httpEquiv: 'http-equiv', -} - interface Props extends ScriptHTMLAttributes { - strategy?: 'defer' | 'lazy' | 'dangerousRenderBlocking' | 'eager' + strategy?: 'defer' | 'lazy' | 'dangerouslyBlockRendering' | 'eager' key?: string onLoad?: () => void onError?: () => void - children?: any + children?: React.ReactNode preload?: boolean } -const onErrorHandler: Function = (src: string, onError: Function) => () => { - ScriptCache.delete(src) - - if (onError) { - onError() - } -} - const loadScript = (props: Props) => { const { src = '', @@ -42,11 +29,9 @@ const loadScript = (props: Props) => { const cacheKey = key || src if (ScriptCache.has(src)) { if (!LoadCache.has(cacheKey)) { + LoadCache.add(cacheKey) // Execute onLoad since the script loading has begun - ScriptCache.get(src).then(() => { - LoadCache.add(cacheKey) - onLoad() - }) + ScriptCache.get(src).then(onLoad, onError) } return } @@ -57,16 +42,20 @@ const loadScript = (props: Props) => { el.addEventListener('load', function () { resolve() if (onLoad) { - LoadCache.add(cacheKey) onLoad.call(this) } }) el.addEventListener('error', function () { reject() - onErrorHandler(src, onError) + if (onError) { + onError() + } }) }) + ScriptCache.set(src, loadPromise) + LoadCache.add(cacheKey) + if (dangerouslySetInnerHTML) { el.innerHTML = dangerouslySetInnerHTML.__html || '' } else if (children) { @@ -75,10 +64,6 @@ const loadScript = (props: Props) => { el.src = src } - if (cacheKey) { - ScriptCache.set(src, loadPromise) - } - for (const [k, value] of Object.entries(props)) { if (value === undefined) { continue @@ -112,18 +97,12 @@ export default function Script(props: Props) { loadScript(props) } else if (strategy === 'lazy') { window.addEventListener('load', () => { - if ('requestIdleCallback' in window) { - ;(window as any).requestIdleCallback(() => loadScript(props), { - timeout: 3000, - }) - } else { - setTimeout(() => loadScript(props), 0) - } + requestIdleCallback(() => loadScript(props)) }) } }, [strategy, props]) - if (strategy === 'dangerousRenderBlocking') { + if (strategy === 'dangerouslyBlockRendering') { const syncProps: Props = { ...restProps } for (const [k, value] of Object.entries({ diff --git a/packages/next/client/head-manager.ts b/packages/next/client/head-manager.ts index fc3689b303c2c..31fa6a0c5f62e 100644 --- a/packages/next/client/head-manager.ts +++ b/packages/next/client/head-manager.ts @@ -1,4 +1,4 @@ -const DOMAttributeNames: Record = { +export const DOMAttributeNames: Record = { acceptCharset: 'accept-charset', className: 'class', htmlFor: 'for', diff --git a/test/integration/script-loader/pages/page2.js b/test/integration/script-loader/pages/page2.js index 48b4733f423b4..cb779f0a90f3d 100644 --- a/test/integration/script-loader/pages/page2.js +++ b/test/integration/script-loader/pages/page2.js @@ -5,8 +5,8 @@ const Page = () => {
page2
diff --git a/test/integration/script-loader/test/index.test.js b/test/integration/script-loader/test/index.test.js index 21f0ffe1daa8a..a2a764baad748 100644 --- a/test/integration/script-loader/test/index.test.js +++ b/test/integration/script-loader/test/index.test.js @@ -125,7 +125,7 @@ describe('Script Loader', () => { ).toBeGreaterThan(0) }) - it('priority dangerousRenderBlocking', async () => { + it('priority dangerouslyBlockRendering', async () => { const html = await renderViaHTTP(appPort, '/page2') const $ = cheerio.load(html) From 71d79c5b0f17aa42d3f2ceeaa0cb664fe9fabb82 Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Fri, 20 Nov 2020 13:11:36 -0800 Subject: [PATCH 12/20] Fix lint --- packages/next/client/experimental-script.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/next/client/experimental-script.tsx b/packages/next/client/experimental-script.tsx index 4b83f308c94c4..2a9aff3ab3af1 100644 --- a/packages/next/client/experimental-script.tsx +++ b/packages/next/client/experimental-script.tsx @@ -59,7 +59,12 @@ const loadScript = (props: Props) => { if (dangerouslySetInnerHTML) { el.innerHTML = dangerouslySetInnerHTML.__html || '' } else if (children) { - el.textContent = typeof children === 'string' ? children : children.join('') + el.textContent = + typeof children === 'string' + ? children + : Array.isArray(children) + ? children.join('') + : '' } else if (src) { el.src = src } @@ -117,7 +122,12 @@ export default function Script(props: Props) { } if (k === 'children') { syncProps.dangerouslySetInnerHTML = { - __html: typeof value === 'string' ? value : value.join(''), + __html: + typeof value === 'string' + ? value + : Array.isArray(value) + ? value.join('') + : '', } } else { ;(syncProps as any)[k] = value From b820597535af9794880cb237a50bc1a86d34104e Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Fri, 20 Nov 2020 14:34:40 -0800 Subject: [PATCH 13/20] Replace key prop with id --- packages/next/client/experimental-script.tsx | 13 +++++++------ test/integration/script-loader/pages/page4.js | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/next/client/experimental-script.tsx b/packages/next/client/experimental-script.tsx index 2a9aff3ab3af1..77e914f331bc7 100644 --- a/packages/next/client/experimental-script.tsx +++ b/packages/next/client/experimental-script.tsx @@ -9,7 +9,7 @@ const LoadCache = new Set() interface Props extends ScriptHTMLAttributes { strategy?: 'defer' | 'lazy' | 'dangerouslyBlockRendering' | 'eager' - key?: string + id?: string onLoad?: () => void onError?: () => void children?: React.ReactNode @@ -22,11 +22,11 @@ const loadScript = (props: Props) => { onLoad = () => {}, dangerouslySetInnerHTML, children = '', - key, + id, onError, } = props - const cacheKey = key || src + const cacheKey = id || src if (ScriptCache.has(src)) { if (!LoadCache.has(cacheKey)) { LoadCache.add(cacheKey) @@ -53,8 +53,10 @@ const loadScript = (props: Props) => { }) }) - ScriptCache.set(src, loadPromise) - LoadCache.add(cacheKey) + if (src) { + ScriptCache.set(src, loadPromise) + LoadCache.add(cacheKey) + } if (dangerouslySetInnerHTML) { el.innerHTML = dangerouslySetInnerHTML.__html || '' @@ -88,7 +90,6 @@ export default function Script(props: Props) { dangerouslySetInnerHTML, children = '', strategy = 'defer', - key, onError, preload = false, ...restProps diff --git a/test/integration/script-loader/pages/page4.js b/test/integration/script-loader/pages/page4.js index de2544606b07d..5e24bdfb784f9 100644 --- a/test/integration/script-loader/pages/page4.js +++ b/test/integration/script-loader/pages/page4.js @@ -8,7 +8,7 @@ const Page = () => {
+ + +
diff --git a/test/integration/script-loader/pages/index.js b/test/integration/script-loader/pages/index.js index bac7e6eb91245..0547af5020401 100644 --- a/test/integration/script-loader/pages/index.js +++ b/test/integration/script-loader/pages/index.js @@ -4,8 +4,8 @@ const Page = () => { return (
index
diff --git a/test/integration/script-loader/pages/page1.js b/test/integration/script-loader/pages/page1.js index 9fea586b4a736..728a77417dd14 100644 --- a/test/integration/script-loader/pages/page1.js +++ b/test/integration/script-loader/pages/page1.js @@ -4,8 +4,8 @@ const Page = () => { return (
page1
diff --git a/test/integration/script-loader/pages/page2.js b/test/integration/script-loader/pages/page2.js index cb779f0a90f3d..947ec448e6a47 100644 --- a/test/integration/script-loader/pages/page2.js +++ b/test/integration/script-loader/pages/page2.js @@ -4,7 +4,7 @@ const Page = () => { return (
diff --git a/test/integration/script-loader/pages/page3.js b/test/integration/script-loader/pages/page3.js index 15cc558c9417d..fb965da75d3ca 100644 --- a/test/integration/script-loader/pages/page3.js +++ b/test/integration/script-loader/pages/page3.js @@ -11,8 +11,8 @@ const Page = () => { })`}
page3
diff --git a/test/integration/script-loader/test/index.test.js b/test/integration/script-loader/test/index.test.js index 51b719f9511fe..344944fe32495 100644 --- a/test/integration/script-loader/test/index.test.js +++ b/test/integration/script-loader/test/index.test.js @@ -40,26 +40,28 @@ describe('Script Loader', () => { browser = await webdriver(appPort, '/') await waitFor(1000) - const script = await browser.elementById('script') - const src = await script.getAttribute('src') - const scriptPreload = await browser.elementsByCss( - `link[rel=preload][href="${src}"]` - ) - const endScripts = await browser.elementsByCss( - '#script ~ script[src^="/_next/static/"]' - ) - const endPreloads = await browser.elementsByCss( - `link[rel=preload][href="${src}"] ~ link[rel=preload][href^="/_next/static/"]` - ) - - // Renders script tag - expect(script).toBeDefined() - // Renders preload - expect(scriptPreload.length).toBeGreaterThan(0) - // Script is inserted at the end - expect(endScripts.length).toBe(0) - //Preload is defined at the end - expect(endPreloads.length).toBe(0) + async function test(id) { + const script = await browser.elementById(id) + const src = await script.getAttribute('src') + const endScripts = await browser.elementsByCss( + `#${id} ~ script[src^="/_next/static/"]` + ) + const endPreloads = await browser.elementsByCss( + `link[rel=preload][href="${src}"] ~ link[rel=preload][href^="/_next/static/"]` + ) + + // Renders script tag + expect(script).toBeDefined() + // Script is inserted at the end + expect(endScripts.length).toBe(0) + //Preload is defined at the end + expect(endPreloads.length).toBe(0) + } + + // Defer script in page + await test('scriptDefer') + // Defer script in _document + await test('documentDefer') } finally { if (browser) await browser.close() } @@ -73,15 +75,22 @@ describe('Script Loader', () => { await browser.waitForElementByCss('#onload-div') await waitFor(1000) - const script = await browser.elementById('script') - const endScripts = await browser.elementsByCss( - '#script ~ script[src^="/_next/static/"]' - ) - - // Renders script tag - expect(script).toBeDefined() - // Script is inserted at the end - expect(endScripts.length).toBe(0) + async function test(id) { + const script = await browser.elementById(id) + const endScripts = await browser.elementsByCss( + `#${id} ~ script[src^="/_next/static/"]` + ) + + // Renders script tag + expect(script).toBeDefined() + // Script is inserted at the end + expect(endScripts.length).toBe(0) + } + + // Lazy script in page + await test('scriptLazy') + // Lazy script in _document + await test('documentLazy') } finally { if (browser) await browser.close() } @@ -91,33 +100,38 @@ describe('Script Loader', () => { const html = await renderViaHTTP(appPort, '/page1') const $ = cheerio.load(html) - const script = $('#script') - const src = script.attr('src') - - // Renders script tag - expect(script).toBeDefined() - // Preload is inserted at the beginning - expect( - $( - `link[rel=preload][href="${src}"] ~ link[rel=preload][href^="/_next/static/"]` - ).length && - !$( - `link[rel=preload][href^="/_next/static/chunks/main"] ~ link[rel=preload][href="${src}"]` + function test(id) { + const script = $(`#${id}`) + const src = script.attr('src') + + // Renders script tag + expect(script).toBeDefined() + // Preload is inserted at the beginning + expect( + $( + `link[rel=preload][href="${src}"] ~ link[rel=preload][href^="/_next/static/"]` + ).length && + !$( + `link[rel=preload][href^="/_next/static/chunks/main"] ~ link[rel=preload][href="${src}"]` + ).length + ).toBeTruthy() + + // Preload is inserted after fonts and CSS + expect( + $( + `link[rel=stylesheet][href^="/_next/static/css"] ~ link[rel=preload][href="${src}"]` ).length - ).toBeTruthy() - - // Preload is inserted after fonts and CSS - expect( - $( - `link[rel=stylesheet][href^="/_next/static/css"] ~ link[rel=preload][href="${src}"]` - ).length - ).toBeGreaterThan(0) - - // Script is inserted before NextScripts - expect( - $('#__NEXT_DATA__ ~ #script ~ script[src^="/_next/static/chunks/main"]') - .length - ).toBeGreaterThan(0) + ).toBeGreaterThan(0) + + // Script is inserted before NextScripts + expect( + $(`#__NEXT_DATA__ ~ #${id} ~ script[src^="/_next/static/chunks/main"]`) + .length + ).toBeGreaterThan(0) + } + + test('scriptEager') + test('documentEager') }) it('priority dangerouslyBlockRendering', async () => { @@ -125,10 +139,11 @@ describe('Script Loader', () => { const $ = cheerio.load(html) // Script is inserted in place - expect($('.container #script').length).toBeGreaterThan(0) + expect($('.container #scriptBlock').length).toBeGreaterThan(0) + expect($('head #documentBlock').length).toBeGreaterThan(0) }) - it('onloads fire correctly', async () => { + it('onload fires correctly', async () => { let browser try { browser = await webdriver(appPort, '/page4') From 1e600e7d639e4b054fa947c3fa725a6846892b4d Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James Date: Wed, 10 Feb 2021 17:34:06 -0800 Subject: [PATCH 17/20] Remove console logs --- packages/next/next-server/server/render.tsx | 2 -- packages/next/pages/_document.tsx | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx index 64328847ea80c..2044373d6a447 100644 --- a/packages/next/next-server/server/render.tsx +++ b/packages/next/next-server/server/render.tsx @@ -583,8 +583,6 @@ export async function renderToHTML( let scriptLoader: any = {} - console.log(Document) - const AppContainer = ({ children }: any) => ( diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx index e1b43ca20fa1a..1e2b9f962c7f1 100644 --- a/packages/next/pages/_document.tsx +++ b/packages/next/pages/_document.tsx @@ -400,7 +400,6 @@ export class Head extends Component< // show a warning if Head contains (only in development) if (process.env.NODE_ENV !== 'production') { children = React.Children.map(children, (child: any) => { - console.log(child) const isReactHelmet = child?.props?.['data-react-helmet'] if (!isReactHelmet) { if (child?.type === 'title') { From 2d85777ed42b3dc8efc632c8d5e58b3c53c05861 Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James <janicklas@google.com> Date: Thu, 11 Feb 2021 12:38:59 -0800 Subject: [PATCH 18/20] Fix ts errors --- packages/next/client/experimental-script.tsx | 6 +++--- packages/next/client/index.tsx | 2 +- packages/next/next-server/lib/utils.ts | 1 + packages/next/pages/_document.tsx | 14 ++++++++------ 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/next/client/experimental-script.tsx b/packages/next/client/experimental-script.tsx index e0f6f2a0d3845..10a5e4eece709 100644 --- a/packages/next/client/experimental-script.tsx +++ b/packages/next/client/experimental-script.tsx @@ -7,7 +7,7 @@ import requestIdleCallback from './request-idle-callback' const ScriptCache = new Map() const LoadCache = new Set() -interface Props extends ScriptHTMLAttributes<HTMLScriptElement> { +export interface Props extends ScriptHTMLAttributes<HTMLScriptElement> { strategy?: 'defer' | 'lazy' | 'dangerouslyBlockRendering' | 'eager' id?: string onLoad?: () => void @@ -92,7 +92,7 @@ const loadScript = (props: Props): void => { document.body.appendChild(el) } -function handleClientScriptLoad(props) { +function handleClientScriptLoad(props: Props) { if (props.strategy === 'defer') { loadScript(props) } else if (props.strategy === 'lazy') { @@ -102,7 +102,7 @@ function handleClientScriptLoad(props) { } } -export function initScriptLoader(scriptLoaderItems = []) { +export function initScriptLoader(scriptLoaderItems: Props[]) { scriptLoaderItems.forEach(handleClientScriptLoad) } diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index bfb85b74eb56d..ce67835e07372 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -139,7 +139,7 @@ if (process.env.__NEXT_I18N_SUPPORT) { } } -if (process.env.__NEXT_SCRIPT_LOADER) { +if (process.env.__NEXT_SCRIPT_LOADER && data.scriptLoader) { initScriptLoader(data.scriptLoader) } diff --git a/packages/next/next-server/lib/utils.ts b/packages/next/next-server/lib/utils.ts index 8769f20ecc382..6321f748b4c52 100644 --- a/packages/next/next-server/lib/utils.ts +++ b/packages/next/next-server/lib/utils.ts @@ -103,6 +103,7 @@ export type NEXT_DATA = { locales?: string[] defaultLocale?: string domainLocales?: DomainLocales + scriptLoader?: any[] } /** diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx index 1e2b9f962c7f1..882fe217c0908 100644 --- a/packages/next/pages/_document.tsx +++ b/packages/next/pages/_document.tsx @@ -17,7 +17,9 @@ import { } from '../next-server/server/get-page-files' import { cleanAmpPath } from '../next-server/server/utils' import { htmlEscapeJsonString } from '../server/htmlescape' -import Script from '../client/experimental-script' +import Script, { + Props as ScriptLoaderProps, +} from '../client/experimental-script' export { DocumentContext, DocumentInitialProps, DocumentProps } @@ -314,12 +316,12 @@ export class Head extends Component< ] } - handleDocumentScriptLoaderItems(children) { + handleDocumentScriptLoaderItems(children: React.ReactNode): ReactNode[] { const { scriptLoader } = this.context - const scriptLoaderItems = [] - const filteredChildren = [] + const scriptLoaderItems: ScriptLoaderProps[] = [] + const filteredChildren: ReactNode[] = [] - React.Children.forEach(children, (child) => { + React.Children.forEach(children, (child: any) => { if (child.type === Script) { if (child.props.strategy === 'eager') { scriptLoader.eager = (scriptLoader.eager || []).concat([ @@ -682,7 +684,7 @@ export class NextScript extends Component<OriginProps> { getPreNextScripts() { const { scriptLoader } = this.context - return (scriptLoader.eager || []).map((file: string) => { + return (scriptLoader.eager || []).map((file: ScriptLoaderProps) => { const { strategy, ...props } = file return ( <script From 2acfb44425898e73506f934d205b3f220936b22b Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James <janicklas@google.com> Date: Thu, 11 Feb 2021 17:28:50 -0800 Subject: [PATCH 19/20] Fix failing test --- packages/next/client/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index ce67835e07372..28736303531f8 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -25,7 +25,6 @@ import initHeadManager from './head-manager' import PageLoader, { StyleSheetTuple } from './page-loader' import measureWebVitals from './performance-relayer' import { createRouter, makePublicRouterInstance } from './router' -import { initScriptLoader } from './experimental-script' /// <reference types="react-dom/experimental" /> @@ -140,6 +139,7 @@ if (process.env.__NEXT_I18N_SUPPORT) { } if (process.env.__NEXT_SCRIPT_LOADER && data.scriptLoader) { + const { initScriptLoader } = require('./experimental-script') initScriptLoader(data.scriptLoader) } From 7c319d3c7d0f0c46616179ae13d20a82151bb395 Mon Sep 17 00:00:00 2001 From: Janicklas Ralph James <janicklas@google.com> Date: Wed, 17 Feb 2021 14:11:17 -0800 Subject: [PATCH 20/20] Resolve PR comment --- packages/next/client/experimental-script.tsx | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/next/client/experimental-script.tsx b/packages/next/client/experimental-script.tsx index 2690ec30ab761..648a3c833af9c 100644 --- a/packages/next/client/experimental-script.tsx +++ b/packages/next/client/experimental-script.tsx @@ -93,9 +93,20 @@ const loadScript = (props: Props): void => { } function handleClientScriptLoad(props: Props) { - if (props.strategy === 'defer') { + const { strategy = 'defer' } = props + if (strategy === 'defer') { loadScript(props) - } else if (props.strategy === 'lazy') { + } else if (strategy === 'lazy') { + window.addEventListener('load', () => { + requestIdleCallback(() => loadScript(props)) + }) + } +} + +function loadLazyScript(props: Props) { + if (document.readyState === 'complete') { + requestIdleCallback(() => loadScript(props)) + } else { window.addEventListener('load', () => { requestIdleCallback(() => loadScript(props)) }) @@ -112,7 +123,7 @@ function Script(props: Props): JSX.Element | null { onLoad = () => {}, dangerouslySetInnerHTML, children = '', - strategy, + strategy = 'defer', onError, preload = false, ...restProps @@ -125,9 +136,7 @@ function Script(props: Props): JSX.Element | null { if (strategy === 'defer') { loadScript(props) } else if (strategy === 'lazy') { - window.addEventListener('load', () => { - requestIdleCallback(() => loadScript(props)) - }) + loadLazyScript(props) } }, [props, strategy]) @@ -185,8 +194,4 @@ function Script(props: Props): JSX.Element | null { return null } -Script.defaultProps = { - strategy: 'defer', -} - export default Script