From 9c363241db66c63e9ab55fb71ad6495982c3ab21 Mon Sep 17 00:00:00 2001 From: Houssein Djirdeh Date: Mon, 6 Jun 2022 19:05:23 -0400 Subject: [PATCH 1/2] remove next/script from bundle unless used in a page --- packages/next/build/check-next-script-import.ts | 16 ++++++++++++++++ packages/next/build/index.ts | 8 ++++++++ packages/next/build/webpack-config.ts | 4 ++++ packages/next/client/index.tsx | 6 +++--- packages/next/client/initialize-script-loader.ts | 7 +++++++ packages/next/shared/lib/router/router.ts | 14 ++++++++++---- packages/next/shared/lib/utils.ts | 3 ++- 7 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 packages/next/build/check-next-script-import.ts create mode 100644 packages/next/client/initialize-script-loader.ts diff --git a/packages/next/build/check-next-script-import.ts b/packages/next/build/check-next-script-import.ts new file mode 100644 index 0000000000000..5005925a2ce19 --- /dev/null +++ b/packages/next/build/check-next-script-import.ts @@ -0,0 +1,16 @@ +import { promises } from 'fs' +import { join } from 'path' + +export async function checkNextScriptImport( + pagePaths: string[], + pagesDir: string +): Promise { + for (const page of pagePaths) { + const fileContent = await promises.readFile(join(pagesDir, page), 'utf8') + if (/import[^}]*.*('|")next\/script('|")/.test(fileContent)) { + return true + } + } + + return false +} diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index ee606e2699937..07f942eb0af6f 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -79,6 +79,7 @@ import { getPageStaticInfo } from './analysis/get-page-static-info' import { createEntrypoints, createPagesMapping } from './entries' import { generateBuildId } from './generate-build-id' import { isWriteable } from './is-writeable' +import { checkNextScriptImport } from './check-next-script-import' import * as Log from './output/log' import createSpinner from './spinner' import { trace, flushAllTraces, setGlobal } from '../trace' @@ -355,6 +356,12 @@ export default async function build( await flatReaddir(join(pagesDir, '..'), middlewareDetectionRegExp) ).map((absoluteFile) => absoluteFile.replace(dir, '')) + // Check if any page is importing next/script. + // Used in webpack config to define an env variable in order to only include next/script code into main bundle if needed + const isNextScriptImported: boolean = await nextBuildSpan + .traceChild('is-next-script-imported') + .traceAsyncFn(() => checkNextScriptImport(pagePaths, pagesDir)) + // needed for static exporting since we want to replace with HTML // files @@ -716,6 +723,7 @@ export default async function build( buildId, config, hasReactRoot, + isNextScriptImported, pagesDir, reactProductionProfiling, rewrites, diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 573f3ae6a7ccc..3a61d06305b91 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -324,6 +324,7 @@ export default async function getBaseWebpackConfig( dev = false, entrypoints, hasReactRoot, + isNextScriptImported = false, isDevFallback = false, pagesDir, reactProductionProfiling = false, @@ -338,6 +339,7 @@ export default async function getBaseWebpackConfig( dev?: boolean entrypoints: webpack5.EntryObject hasReactRoot: boolean + isNextScriptImported?: boolean isDevFallback?: boolean pagesDir: string reactProductionProfiling?: boolean @@ -1507,6 +1509,8 @@ export default async function getBaseWebpackConfig( 'process.env.__NEXT_SCRIPT_WORKERS': JSON.stringify( config.experimental.nextScriptWorkers && !dev ), + 'process.env.__NEXT_SCRIPT_IMPORTED': + JSON.stringify(isNextScriptImported), 'process.env.__NEXT_SCROLL_RESTORATION': JSON.stringify( config.experimental.scrollRestoration ), diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index eea7be78c20dd..a3856de457c63 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -34,6 +34,7 @@ import { ImageConfigContext } from '../shared/lib/image-config-context' import { ImageConfigComplete } from '../shared/lib/image-config' import { removeBasePath } from './remove-base-path' import { hasBasePath } from './has-base-path' +import { initalizeScriptLoader } from './initialize-script-loader' const ReactDOM = process.env.__NEXT_REACT_ROOT ? require('react-dom/client') @@ -252,9 +253,8 @@ export async function initialize(opts: { webpackHMR?: any } = {}): Promise<{ } } - if (initialData.scriptLoader) { - const { initScriptLoader } = require('./script') - initScriptLoader(initialData.scriptLoader) + if (initialData.scriptLoader && initialData.scriptLoader.length > 0) { + initalizeScriptLoader(initialData.scriptLoader) } pageLoader = new PageLoader(initialData.buildId, prefix) diff --git a/packages/next/client/initialize-script-loader.ts b/packages/next/client/initialize-script-loader.ts new file mode 100644 index 0000000000000..cbf5a731c58d2 --- /dev/null +++ b/packages/next/client/initialize-script-loader.ts @@ -0,0 +1,7 @@ +import type { initScriptLoader } from './script' + +export const initalizeScriptLoader: typeof initScriptLoader = (...args) => { + if (process.env.__NEXT_SCRIPT_IMPORTED) { + return require('./script').initScriptLoader(...args) + } +} diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index 90838d4fb5248..230629b06a160 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -14,7 +14,6 @@ import { isAssetError, markAssetError, } from '../../../client/route-loader' -import { handleClientScriptLoad } from '../../../client/script' import isError, { getProperError } from '../../../lib/is-error' import { denormalizePagePath } from '../page-path/denormalize-page-path' import { normalizeLocalePath } from '../i18n/normalize-locale-path' @@ -1255,9 +1254,16 @@ export default class Router implements BaseRouter { if (component && component.unstable_scriptLoader) { const scripts = [].concat(component.unstable_scriptLoader()) - scripts.forEach((script: any) => { - handleClientScriptLoad(script.props) - }) + if (scripts.length > 0) { + // Script code should already be present in main bundle if next/script is imported + const { handleClientScriptLoad } = await import( + /* webpackMode: "weak" */ + '../../../client/script' + ) + scripts.forEach((script: any) => { + handleClientScriptLoad(script.props) + }) + } } // handle redirect on client-transition diff --git a/packages/next/shared/lib/utils.ts b/packages/next/shared/lib/utils.ts index 787f18a1f8ccc..14c5719c02ad1 100644 --- a/packages/next/shared/lib/utils.ts +++ b/packages/next/shared/lib/utils.ts @@ -6,6 +6,7 @@ import type { IncomingMessage, ServerResponse } from 'http' import type { NextRouter } from './router/router' import type { ParsedUrlQuery } from 'querystring' import type { PreviewData } from 'next/types' +import type { ScriptProps } from '../../client/script' export type NextComponentType< C extends BaseContext = NextPageContext, @@ -102,7 +103,7 @@ export type NEXT_DATA = { locales?: string[] defaultLocale?: string domainLocales?: DomainLocale[] - scriptLoader?: any[] + scriptLoader?: ScriptProps[] isPreview?: boolean notFoundSrcPage?: string rsc?: boolean From 67a679e12afac549510cc47759644bf60242b253 Mon Sep 17 00:00:00 2001 From: Houssein Djirdeh Date: Thu, 9 Jun 2022 12:09:05 -0400 Subject: [PATCH 2/2] updates per review comments --- .../next/build/check-next-script-import.ts | 21 ++++++++++++------- packages/next/client/index.tsx | 6 ++++-- .../next/client/initialize-script-loader.ts | 4 +--- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/next/build/check-next-script-import.ts b/packages/next/build/check-next-script-import.ts index 5005925a2ce19..cd7d50d4cc1d9 100644 --- a/packages/next/build/check-next-script-import.ts +++ b/packages/next/build/check-next-script-import.ts @@ -5,12 +5,19 @@ export async function checkNextScriptImport( pagePaths: string[], pagesDir: string ): Promise { - for (const page of pagePaths) { - const fileContent = await promises.readFile(join(pagesDir, page), 'utf8') - if (/import[^}]*.*('|")next\/script('|")/.test(fileContent)) { - return true - } - } + let isNextScriptImported = false + await Promise.all( + pagePaths.map(async (page) => { + if (isNextScriptImported) return - return false + const fileContent = await promises.readFile(join(pagesDir, page), 'utf8') + if ( + fileContent.includes('next/script') || + fileContent.includes('next/dist/client/script') + ) { + isNextScriptImported = true + } + }) + ) + return isNextScriptImported } diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index a3856de457c63..9101510e8eb27 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -253,8 +253,10 @@ export async function initialize(opts: { webpackHMR?: any } = {}): Promise<{ } } - if (initialData.scriptLoader && initialData.scriptLoader.length > 0) { - initalizeScriptLoader(initialData.scriptLoader) + if (process.env.__NEXT_SCRIPT_IMPORTED) { + if (initialData.scriptLoader && initialData.scriptLoader.length > 0) { + initalizeScriptLoader(initialData.scriptLoader) + } } pageLoader = new PageLoader(initialData.buildId, prefix) diff --git a/packages/next/client/initialize-script-loader.ts b/packages/next/client/initialize-script-loader.ts index cbf5a731c58d2..541490153ba00 100644 --- a/packages/next/client/initialize-script-loader.ts +++ b/packages/next/client/initialize-script-loader.ts @@ -1,7 +1,5 @@ import type { initScriptLoader } from './script' export const initalizeScriptLoader: typeof initScriptLoader = (...args) => { - if (process.env.__NEXT_SCRIPT_IMPORTED) { - return require('./script').initScriptLoader(...args) - } + return require('./script').initScriptLoader(...args) }