From 7498fd4b2e885256ca2d29ae9f5c0efbb25d0cb5 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 24 Jul 2024 16:48:04 -0700 Subject: [PATCH 1/2] Restructure trace experiment manifest (#68131) This restructures the experimental trace manifest to output the values instead of just hashes for better debugging experience and removes public env from the manifest for now. --- .../flying-shuttle/detect-changed-entries.ts | 61 ++++++++++----- .../src/build/flying-shuttle/store-shuttle.ts | 75 ++++++------------- test/e2e/app-dir/app/flying-shuttle.test.ts | 17 +---- 3 files changed, 68 insertions(+), 85 deletions(-) diff --git a/packages/next/src/build/flying-shuttle/detect-changed-entries.ts b/packages/next/src/build/flying-shuttle/detect-changed-entries.ts index 1089c9c08b9e9..a4db8e6be3183 100644 --- a/packages/next/src/build/flying-shuttle/detect-changed-entries.ts +++ b/packages/next/src/build/flying-shuttle/detect-changed-entries.ts @@ -3,7 +3,7 @@ import path from 'path' import crypto from 'crypto' import { getPageFromPath } from '../entries' import { Sema } from 'next/dist/compiled/async-sema' -import { generateShuttleManifest } from './store-shuttle' +import { generateShuttleManifest, type ShuttleManifest } from './store-shuttle' import type { NextConfigComplete } from '../../server/config-shared' export interface DetectedEntriesResult { @@ -11,6 +11,32 @@ export interface DetectedEntriesResult { pages: string[] } +function deepEqual(obj1: any, obj2: any) { + if (obj1 === obj2) return true + + if ( + typeof obj1 !== 'object' || + obj1 === null || + typeof obj2 !== 'object' || + obj2 === null + ) { + return false + } + + let keys1 = Object.keys(obj1) + let keys2 = Object.keys(obj2) + + if (keys1.length !== keys2.length) return false + + for (let key of keys1) { + if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { + return false + } + } + + return true +} + let _hasShuttle: undefined | boolean = undefined export async function hasShuttle( config: NextConfigComplete, @@ -19,44 +45,41 @@ export async function hasShuttle( if (typeof _hasShuttle === 'boolean') { return _hasShuttle } - let foundShuttleManifest: - | ReturnType - | undefined + let foundShuttleManifest: ShuttleManifest try { foundShuttleManifest = JSON.parse( - await fs.promises - .readFile(path.join(shuttleDir, 'shuttle-manifest.json'), 'utf8') - .catch(() => '{}') + await fs.promises.readFile( + path.join(shuttleDir, 'shuttle-manifest.json'), + 'utf8' + ) ) _hasShuttle = true } catch (err: unknown) { _hasShuttle = false + console.log(`Failed to read shuttle manifest`) return _hasShuttle } - const currentShuttleManifest = generateShuttleManifest(config) + const currentShuttleManifest = JSON.parse(generateShuttleManifest(config)) - if ( - currentShuttleManifest.nextVersion !== foundShuttleManifest?.nextVersion - ) { + if (currentShuttleManifest.nextVersion !== foundShuttleManifest.nextVersion) { // we don't allow using shuttle from differing Next.js version // as it could have internal changes console.log( - `shuttle has differing Next.js version ${foundShuttleManifest?.nextVersion} versus current ${currentShuttleManifest.nextVersion}, skipping.` + `shuttle has differing Next.js version ${foundShuttleManifest.nextVersion} versus current ${currentShuttleManifest.nextVersion}, skipping.` ) _hasShuttle = false } - if ( - _hasShuttle !== false && - currentShuttleManifest.globalHash !== foundShuttleManifest?.globalHash - ) { - // if the global hash was invalidated we bypass the cache to be safe + if (!deepEqual(currentShuttleManifest.config, foundShuttleManifest.config)) { + _hasShuttle = false console.log( - `shuttle has differing global hash ${foundShuttleManifest?.globalHash} versus current ${currentShuttleManifest.globalHash}, skipping.` + `Mismatching shuttle configs`, + currentShuttleManifest.config, + foundShuttleManifest.config ) - _hasShuttle = false } + return _hasShuttle } diff --git a/packages/next/src/build/flying-shuttle/store-shuttle.ts b/packages/next/src/build/flying-shuttle/store-shuttle.ts index 7a5253545c116..9aa0859a8a8d0 100644 --- a/packages/next/src/build/flying-shuttle/store-shuttle.ts +++ b/packages/next/src/build/flying-shuttle/store-shuttle.ts @@ -1,6 +1,5 @@ import fs from 'fs' import path from 'path' -import crypto from 'crypto' import { recursiveCopy } from '../../lib/recursive-copy' import { version as nextVersion } from 'next/package.json' import type { NextConfigComplete } from '../../server/config-shared' @@ -12,58 +11,31 @@ import { PAGES_MANIFEST, ROUTES_MANIFEST, } from '../../shared/lib/constants' -import { getNextPublicEnvironmentVariables } from '../webpack/plugins/define-env-plugin' -export function generateShuttleManifest(config: NextConfigComplete) { - // NEXT_PUBLIC_ changes for now since they are inlined - // and specific next config values that can impact the build - const globalHash = crypto.createHash('sha256') - const nextPublicEnv = getNextPublicEnvironmentVariables() - // sort for deterministic order - const publicEnvKeys = Object.keys(nextPublicEnv).sort() - - for (const key of publicEnvKeys) { - globalHash.update(`${key}=${nextPublicEnv[key]}`) - } - - // TODO: make this opt-out list instead? - // also ensure this list is complete this is minimal set - const configsToInvalidateOn = [ - 'basePath', - 'env', - 'i18n', - 'images', - 'productionBrowserSourceMaps', - 'webpack', - 'sassOptions', - 'trailingSlash', - 'experimental.flyingShuttle', - 'experimental.ppr', - 'experimental.reactCompiler', - ].sort() - - for (const key of configsToInvalidateOn) { - let value = config[key] - - if (key.includes('.')) { - value = config - - const keyParts = key.split('.') - for (let i = 0; i < keyParts.length; i++) { - value = value[keyParts[i]] - } - } - - let serializedConfig = - typeof value === 'function' ? value.toString() : JSON.stringify(value) - - globalHash.update(`${key}=${serializedConfig}`) - } +export interface ShuttleManifest { + nextVersion: string + config: Record +} - return { +export function generateShuttleManifest(config: NextConfigComplete) { + return JSON.stringify({ nextVersion, - globalHash: globalHash.digest('hex'), - } + config: { + env: config.env, + i18n: config.i18n, + basePath: config.basePath, + sassOptions: config.sassOptions, + trailingSlash: config.trailingSlash, + productionBrowserSourceMaps: config.productionBrowserSourceMaps, + + experimental: { + ppr: config.experimental.ppr, + reactCompiler: config.experimental.reactCompiler, + serverSourceMaps: config.experimental.serverSourceMaps, + serverMinification: config.experimental.serverMinification, + }, + }, + } satisfies ShuttleManifest) } // we can create a new shuttle with the outputs before env values have @@ -80,10 +52,9 @@ export async function storeShuttle({ await fs.promises.rm(shuttleDir, { force: true, recursive: true }) await fs.promises.mkdir(shuttleDir, { recursive: true }) - const shuttleManifest = generateShuttleManifest(config) await fs.promises.writeFile( path.join(shuttleDir, 'shuttle-manifest.json'), - JSON.stringify(shuttleManifest) + generateShuttleManifest(config) ) // copy all server entries diff --git a/test/e2e/app-dir/app/flying-shuttle.test.ts b/test/e2e/app-dir/app/flying-shuttle.test.ts index aa174dff18e95..cb5a97493bc93 100644 --- a/test/e2e/app-dir/app/flying-shuttle.test.ts +++ b/test/e2e/app-dir/app/flying-shuttle.test.ts @@ -23,13 +23,13 @@ import { nextTestSetup, isNextStart } from 'e2e-utils' NEXT_PRIVATE_FLYING_SHUTTLE: 'true', }, }) - let initialGlobalHash: string = '' + let initialConfig: Record = {} beforeAll(async () => { const manifest = await next.readJSON( '.next/cache/shuttle/shuttle-manifest.json' ) - initialGlobalHash = manifest.globalHash + initialConfig = manifest.config }) async function checkShuttleManifest() { @@ -39,7 +39,7 @@ import { nextTestSetup, isNextStart } from 'e2e-utils' expect(manifest).toEqual({ nextVersion, - globalHash: initialGlobalHash, + config: initialConfig, }) } @@ -93,17 +93,6 @@ import { nextTestSetup, isNextStart } from 'e2e-utils' } }) - it('should have shuttle-manifest.json', async () => { - const manifest = await next.readJSON( - '.next/cache/shuttle/shuttle-manifest.json' - ) - - expect(manifest).toEqual({ - nextVersion, - globalHash: expect.stringMatching(/[\w\d]{1,}/), - }) - }) - it('should hard navigate on chunk load failure', async () => { let blockChunks = false const browser = await next.browser('/dashboard', { From c7b6759e39bd3bba8586ac5eab5026e636c790cd Mon Sep 17 00:00:00 2001 From: vercel-release-bot Date: Wed, 24 Jul 2024 23:51:28 +0000 Subject: [PATCH 2/2] v15.0.0-canary.82 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-next/package.json | 2 +- packages/font/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-env/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next-swc/package.json | 2 +- packages/next/package.json | 12 ++++++------ packages/react-refresh-utils/package.json | 2 +- packages/third-parties/package.json | 4 ++-- pnpm-lock.yaml | 14 +++++++------- 17 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lerna.json b/lerna.json index 4429bea962050..6fa94a7b2e6cb 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "15.0.0-canary.81" + "version": "15.0.0-canary.82" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 7d448e6e3d626..9de43429c8c93 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index d90478a91ef99..0cc5f81e4eb5d 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "description": "ESLint configuration used by Next.js.", "main": "index.js", "license": "MIT", @@ -10,7 +10,7 @@ }, "homepage": "https://nextjs.org/docs/app/building-your-application/configuring/eslint#eslint-config", "dependencies": { - "@next/eslint-plugin-next": "15.0.0-canary.81", + "@next/eslint-plugin-next": "15.0.0-canary.82", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 23fd66d3db708..e378b0a9a48ad 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "description": "ESLint plugin for Next.js.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index d35234634dc03..58d6c5ffb9a2f 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,7 +1,7 @@ { "name": "@next/font", "private": true, - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 2ee3e64b73304..b85c9107a525b 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 784b9fd3de33f..4bf52310b1e2b 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index d8ce8df7cc6ba..7ffdf1e3101be 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index b789344471912..fab8dac04e6a6 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 843e99f8e74ad..867d58bc9e50b 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index c8a281386f254..f4a92fa8fb42b 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index 76951d1cde9a5..e6f6c21eeecc8 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 1677372372b44..98ccbb80284d2 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "private": true, "scripts": { "clean": "node ../../scripts/rm.mjs native", diff --git a/packages/next/package.json b/packages/next/package.json index c3bfebf315483..c4e2014e32d2f 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -93,7 +93,7 @@ ] }, "dependencies": { - "@next/env": "15.0.0-canary.81", + "@next/env": "15.0.0-canary.82", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.12", "busboy": "1.6.0", @@ -158,10 +158,10 @@ "@jest/types": "29.5.0", "@mswjs/interceptors": "0.23.0", "@napi-rs/triples": "1.2.0", - "@next/polyfill-module": "15.0.0-canary.81", - "@next/polyfill-nomodule": "15.0.0-canary.81", - "@next/react-refresh-utils": "15.0.0-canary.81", - "@next/swc": "15.0.0-canary.81", + "@next/polyfill-module": "15.0.0-canary.82", + "@next/polyfill-nomodule": "15.0.0-canary.82", + "@next/react-refresh-utils": "15.0.0-canary.82", + "@next/swc": "15.0.0-canary.82", "@opentelemetry/api": "1.6.0", "@playwright/test": "1.41.2", "@swc/core": "1.7.0-nightly-20240714.1", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 14b3cf5136065..1a659273508a1 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/packages/third-parties/package.json b/packages/third-parties/package.json index 1bb30aae16d8f..560b59edc796c 100644 --- a/packages/third-parties/package.json +++ b/packages/third-parties/package.json @@ -1,6 +1,6 @@ { "name": "@next/third-parties", - "version": "15.0.0-canary.81", + "version": "15.0.0-canary.82", "repository": { "url": "vercel/next.js", "directory": "packages/third-parties" @@ -26,7 +26,7 @@ "third-party-capital": "1.0.20" }, "devDependencies": { - "next": "15.0.0-canary.81", + "next": "15.0.0-canary.82", "outdent": "0.8.0", "prettier": "2.5.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92a1c53860268..c8726261c6643 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -783,7 +783,7 @@ importers: packages/eslint-config-next: dependencies: '@next/eslint-plugin-next': - specifier: 15.0.0-canary.81 + specifier: 15.0.0-canary.82 version: link:../eslint-plugin-next '@rushstack/eslint-patch': specifier: ^1.3.3 @@ -844,7 +844,7 @@ importers: packages/next: dependencies: '@next/env': - specifier: 15.0.0-canary.81 + specifier: 15.0.0-canary.82 version: link:../next-env '@swc/counter': specifier: 0.1.3 @@ -975,16 +975,16 @@ importers: specifier: 1.2.0 version: 1.2.0 '@next/polyfill-module': - specifier: 15.0.0-canary.81 + specifier: 15.0.0-canary.82 version: link:../next-polyfill-module '@next/polyfill-nomodule': - specifier: 15.0.0-canary.81 + specifier: 15.0.0-canary.82 version: link:../next-polyfill-nomodule '@next/react-refresh-utils': - specifier: 15.0.0-canary.81 + specifier: 15.0.0-canary.82 version: link:../react-refresh-utils '@next/swc': - specifier: 15.0.0-canary.81 + specifier: 15.0.0-canary.82 version: link:../next-swc '@opentelemetry/api': specifier: 1.6.0 @@ -1597,7 +1597,7 @@ importers: version: 1.0.20 devDependencies: next: - specifier: 15.0.0-canary.81 + specifier: 15.0.0-canary.82 version: link:../next outdent: specifier: 0.8.0