diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 799678988275..9fef923b17a4 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -1013,134 +1013,6 @@ jobs: stepName: 'test-cache-components-prod-${{ matrix.group }}' secrets: inherit - test-node-streams-cache-components-dev: - name: test node streams cache components dev - needs: - [ - 'optimize-ci', - 'changes', - 'build-native', - 'build-next', - 'fetch-test-timings', - ] - if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }} - - strategy: - fail-fast: false - matrix: - group: [1/6, 2/6, 3/6, 4/6, 5/6, 6/6] - uses: ./.github/workflows/build_reusable.yml - with: - afterBuild: | - export __NEXT_USE_NODE_STREAMS=true - export __NEXT_CACHE_COMPONENTS=true - export __NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS=true - export __NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER=true - export NEXT_EXTERNAL_TESTS_FILTERS="test/cache-components-tests-manifest.json" - export NEXT_TEST_MODE=dev - export IS_TURBOPACK_TEST=1 - export TURBOPACK_DEV=1 - - node run-tests.js \ - --timings \ - --require-timings \ - -g ${{ matrix.group }} \ - --type development - testTimingsArtifact: 'test-timings' - stepName: 'test-node-streams-cache-components-dev-${{ matrix.group }}' - secrets: inherit - - test-node-streams-cache-components-prod: - name: test node streams cache components prod - needs: - [ - 'optimize-ci', - 'changes', - 'build-native', - 'build-next', - 'fetch-test-timings', - ] - if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }} - - strategy: - fail-fast: false - matrix: - group: [1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 7/7] - uses: ./.github/workflows/build_reusable.yml - with: - afterBuild: | - export __NEXT_USE_NODE_STREAMS=true - export __NEXT_CACHE_COMPONENTS=true - export __NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS=true - export __NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER=true - export NEXT_EXTERNAL_TESTS_FILTERS="test/cache-components-tests-manifest.json" - export NEXT_TEST_MODE=start - export IS_TURBOPACK_TEST=1 - export TURBOPACK_BUILD=1 - - node run-tests.js \ - --timings \ - --require-timings \ - -g ${{ matrix.group }} \ - --type production - testTimingsArtifact: 'test-timings' - stepName: 'test-node-streams-cache-components-prod-${{ matrix.group }}' - secrets: inherit - - test-node-streams-dev: - name: test node streams dev - needs: ['optimize-ci', 'changes', 'build-native', 'build-next'] - if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }} - - strategy: - fail-fast: false - matrix: - group: [1/6, 2/6, 3/6, 4/6, 5/6, 6/6] - uses: ./.github/workflows/build_reusable.yml - with: - afterBuild: | - export __NEXT_USE_NODE_STREAMS=true - export NEXT_EXTERNAL_TESTS_FILTERS="test/cache-components-tests-manifest.json,test/use-node-streams-tests-manifest.json" - export NEXT_TEST_MODE=dev - export IS_TURBOPACK_TEST=1 - export TURBOPACK_DEV=1 - export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true - export RUST_BACKTRACE=1 - - node run-tests.js \ - --timings \ - -g ${{ matrix.group }} \ - --type development - stepName: 'test-node-streams-dev-${{ matrix.group }}' - secrets: inherit - - test-node-streams-prod: - name: test node streams prod - needs: ['optimize-ci', 'changes', 'build-native', 'build-next'] - if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }} - - strategy: - fail-fast: false - matrix: - group: [1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 7/7] - uses: ./.github/workflows/build_reusable.yml - with: - afterBuild: | - export __NEXT_USE_NODE_STREAMS=true - export NEXT_EXTERNAL_TESTS_FILTERS="test/cache-components-tests-manifest.json,test/use-node-streams-tests-manifest.json" - export NEXT_TEST_MODE=start - export IS_TURBOPACK_TEST=1 - export TURBOPACK_BUILD=1 - export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true - export RUST_BACKTRACE=1 - - node run-tests.js \ - --timings \ - -g ${{ matrix.group }} \ - --type production - stepName: 'test-node-streams-prod-${{ matrix.group }}' - secrets: inherit - tests-pass: needs: [ @@ -1157,10 +1029,6 @@ jobs: 'test-firefox-safari', 'test-cache-components-dev', 'test-cache-components-prod', - 'test-node-streams-cache-components-dev', - 'test-node-streams-cache-components-prod', - 'test-node-streams-dev', - 'test-node-streams-prod', 'test-cargo-unit', 'rust-check', 'rustdoc-check', diff --git a/lerna.json b/lerna.json index 9acef58d795f..98302bb874e3 100644 --- a/lerna.json +++ b/lerna.json @@ -15,5 +15,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "16.3.0-canary.37" + "version": "16.3.0-canary.38" } \ No newline at end of file diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 0552ff3a1317..c31434c22d2a 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 5ae58b51f2dc..492fe300daaa 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "description": "ESLint configuration used by Next.js.", "license": "MIT", "repository": { @@ -12,7 +12,7 @@ "dist" ], "dependencies": { - "@next/eslint-plugin-next": "16.3.0-canary.37", + "@next/eslint-plugin-next": "16.3.0-canary.38", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index 071fa49c464c..225325e6d68d 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,7 +1,7 @@ { "name": "@next/eslint-plugin-internal", "private": true, - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "description": "ESLint plugin for working on Next.js.", "exports": { ".": "./src/eslint-plugin-internal.js" diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index bb64794693b0..a63a8afaa447 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": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "description": "ESLint plugin for Next.js.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/font/package.json b/packages/font/package.json index 7e5e92b07790..d2e8ac784459 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,7 +1,7 @@ { "name": "@next/font", "private": true, - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "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 d100769667b2..a985d1222498 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index fba401d9fa72..f875fac75015 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/index.ts b/packages/next-env/index.ts index 392670c7817e..5b34e68be666 100644 --- a/packages/next-env/index.ts +++ b/packages/next-env/index.ts @@ -18,7 +18,17 @@ let cachedLoadedEnvFiles: LoadedEnvFiles = [] let previousLoadedEnvFiles: LoadedEnvFiles = [] export function updateInitialEnv(newEnv: Env) { - Object.assign(initialEnv || {}, newEnv) + if (!initialEnv) { + return + } + + for (const [key, value] of Object.entries(newEnv)) { + if (value === undefined) { + delete initialEnv[key] + } else { + initialEnv[key] = value + } + } } type Log = { diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 91f7abeb9ed2..faa73740af0f 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index b133ae0ceda5..68afc5d7341a 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-playwright/package.json b/packages/next-playwright/package.json index fe0bbd9562f7..c8e23bf0f234 100644 --- a/packages/next-playwright/package.json +++ b/packages/next-playwright/package.json @@ -1,6 +1,6 @@ { "name": "@next/playwright", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "repository": { "url": "vercel/next.js", "directory": "packages/next-playwright" diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 6ec1529c33a4..06c2fd30ae63 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "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 68e02644914d..8596f6300312 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "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 f2f21d15b13c..752e7b5128eb 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-routing/package.json b/packages/next-routing/package.json index 5ab5910f388d..b5110dd01451 100644 --- a/packages/next-routing/package.json +++ b/packages/next-routing/package.json @@ -1,6 +1,6 @@ { "name": "@next/routing", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "keywords": [ "react", "next", diff --git a/packages/next-rspack/package.json b/packages/next-rspack/package.json index 91a5a17105fe..f81659571ee0 100644 --- a/packages/next-rspack/package.json +++ b/packages/next-rspack/package.json @@ -1,6 +1,6 @@ { "name": "next-rspack", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "repository": { "url": "vercel/next.js", "directory": "packages/next-rspack" diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index b7073cd5208c..e1fc35d0380c 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "private": true, "files": [ "native/" diff --git a/packages/next/package.json b/packages/next/package.json index 0c06e3f121de..495b02117b35 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -101,7 +101,7 @@ ] }, "dependencies": { - "@next/env": "16.3.0-canary.37", + "@next/env": "16.3.0-canary.38", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -165,11 +165,11 @@ "@modelcontextprotocol/sdk": "1.18.1", "@mswjs/interceptors": "0.23.0", "@napi-rs/triples": "1.2.0", - "@next/font": "16.3.0-canary.37", - "@next/polyfill-module": "16.3.0-canary.37", - "@next/polyfill-nomodule": "16.3.0-canary.37", - "@next/react-refresh-utils": "16.3.0-canary.37", - "@next/swc": "16.3.0-canary.37", + "@next/font": "16.3.0-canary.38", + "@next/polyfill-module": "16.3.0-canary.38", + "@next/polyfill-nomodule": "16.3.0-canary.38", + "@next/react-refresh-utils": "16.3.0-canary.38", + "@next/swc": "16.3.0-canary.38", "@opentelemetry/api": "1.6.0", "@playwright/test": "1.58.2", "@rspack/core": "1.6.7", diff --git a/packages/next/src/server/app-render/stream-ops.node.ts b/packages/next/src/server/app-render/stream-ops.node.ts index 5e4e80b26253..90dc34d9aadb 100644 --- a/packages/next/src/server/app-render/stream-ops.node.ts +++ b/packages/next/src/server/app-render/stream-ops.node.ts @@ -614,9 +614,6 @@ export async function renderToNodeFizzStream( onHeaders: streamOptions?.onHeaders, onShellReady() { streamOptions?.onShellReady?.() - if (!deferPipe) { - pipeable.pipe(pt) - } shellReady.resolve() }, onShellError(error: unknown) { @@ -636,6 +633,11 @@ export async function renderToNodeFizzStream( await shellReady.promise + if (!deferPipe) { + await waitAtLeastOneReactRenderTask() + pipeable.pipe(pt) + } + return { stream: pt, allReady: allReady.promise, diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index 8da5f855611e..077f90d8eca5 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -36,7 +36,7 @@ export type NextConfigComplete = Required> & { prefetchInlining?: PrefetchInliningConfig // Normalized by config.ts: defaulted to 90% of staticPageGenerationTimeout useCacheTimeout: number - // Normalized by config.ts `finalizeConfig`: defaulted to `'manual-warning'` + // Normalized by config.ts `finalizeConfig`: defaulted to `'warning'` instantInsights: { validationLevel: ValidationLevel } } // The root directory of the distDir. In development mode, this is the parent directory of `distDir` @@ -1080,10 +1080,10 @@ export interface ExperimentalConfig { /** * Controls the validation behavior of Instant Insights * - * - `'warning'`: Validates all navigations for Instant UI in development - * - `'manual-warning'`: Validates navigations for Instant UI in development when configured with `unstable_instant` in Pages and Layouts + * - `'warning'` (default): Validates all navigations for Instant UI in development + * - `'manual-warning'`: Validates navigations for Instant UI in development only when configured with `unstable_instant` in Pages and Layouts * - `'experimental-error'`: Validates all navigations for Instant in development and build. Use with caution. - * - `'experimental-manual-error'`: Validates navigations for Instant UI in developement and build when configured with `unstable_instant` in Pages and Layouts. Use with caution. + * - `'experimental-manual-error'`: Validates navigations for Instant UI in development and build when configured with `unstable_instant` in Pages and Layouts. Use with caution. */ validationLevel?: ValidationLevel } @@ -2040,6 +2040,7 @@ export const defaultConfig = Object.freeze({ gestureTransition: false, inlineCss: false, useCache: undefined, + useNodeStreams: true, slowModuleDetection: undefined, globalNotFound: false, browserDebugInfoInTerminal: 'warn', diff --git a/packages/next/src/server/config.ts b/packages/next/src/server/config.ts index c61f17984c65..3d1e14ffb0a5 100644 --- a/packages/next/src/server/config.ts +++ b/packages/next/src/server/config.ts @@ -1618,11 +1618,30 @@ function assignDefaultsAndValidate( function finalizeConfig(config: NextConfigComplete): NextConfigComplete { config.experimental.instantInsights = { validationLevel: - config.experimental.instantInsights?.validationLevel ?? 'manual-warning', + config.experimental.instantInsights?.validationLevel ?? 'warning', } + syncUseNodeStreamsEnv(config) return config } +function syncUseNodeStreamsEnv(config: NextConfig): void { + // This must use resolved config: user configs are inspected before defaults + // are merged, while runtime bundles must select the default implementation. + const useNodeStreams = config.experimental?.useNodeStreams + ? 'true' + : undefined + + if (useNodeStreams) { + process.env.__NEXT_USE_NODE_STREAMS = useNodeStreams + } else { + delete process.env.__NEXT_USE_NODE_STREAMS + } + + // Dev env reloads restore process.env from this snapshot. Preserve the + // resolved runtime selection so a reload cannot mix stream implementations. + updateInitialEnv({ __NEXT_USE_NODE_STREAMS: useNodeStreams }) +} + async function applyModifyConfig( config: NextConfigComplete, phase: PHASE_TYPE, @@ -1750,6 +1769,7 @@ export default async function loadConfig( return cachedResult.rawConfig } + syncUseNodeStreamsEnv(cachedResult.config) return cachedResult.config } else { // Reset next.config errors before loading config @@ -1777,6 +1797,8 @@ export default async function loadConfig( process.env.__NEXT_PRIVATE_STANDALONE_CONFIG ) + syncUseNodeStreamsEnv(standaloneConfig) + // Cache the standalone config configCache.set(cacheKey, { config: standaloneConfig, @@ -2229,16 +2251,6 @@ function enforceExperimentalFeatures( config.experimental.useNodeStreams = true } - // Keep runtime bundle selection env in sync with the resolved config. - // Explicit user config (e.g. useNodeStreams: false) should win over an - // inherited shell env var to avoid selecting nodestream runtime bundles - // while define-env compiled user bundles with node streams disabled. - if (config.experimental.useNodeStreams) { - process.env.__NEXT_USE_NODE_STREAMS = 'true' - } else { - delete process.env.__NEXT_USE_NODE_STREAMS - } - // TODO: Remove this once strictRouteTypes is the default. if ( process.env.__NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES === 'true' && diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 9a7664759eeb..423e598566e3 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": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "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 16793b4f18ce..9f486741dc3e 100644 --- a/packages/third-parties/package.json +++ b/packages/third-parties/package.json @@ -1,6 +1,6 @@ { "name": "@next/third-parties", - "version": "16.3.0-canary.37", + "version": "16.3.0-canary.38", "repository": { "url": "vercel/next.js", "directory": "packages/third-parties" @@ -27,7 +27,7 @@ "third-party-capital": "1.0.20" }, "devDependencies": { - "next": "16.3.0-canary.37", + "next": "16.3.0-canary.38", "outdent": "0.8.0", "prettier": "2.5.1", "typescript": "6.0.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 636661a0b069..ef3d6815d204 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -986,7 +986,7 @@ importers: packages/eslint-config-next: dependencies: '@next/eslint-plugin-next': - specifier: 16.3.0-canary.37 + specifier: 16.3.0-canary.38 version: link:../eslint-plugin-next eslint: specifier: '>=9.0.0' @@ -1063,7 +1063,7 @@ importers: packages/next: dependencies: '@next/env': - specifier: 16.3.0-canary.37 + specifier: 16.3.0-canary.38 version: link:../next-env '@swc/helpers': specifier: 0.5.15 @@ -1184,19 +1184,19 @@ importers: specifier: 1.2.0 version: 1.2.0 '@next/font': - specifier: 16.3.0-canary.37 + specifier: 16.3.0-canary.38 version: link:../font '@next/polyfill-module': - specifier: 16.3.0-canary.37 + specifier: 16.3.0-canary.38 version: link:../next-polyfill-module '@next/polyfill-nomodule': - specifier: 16.3.0-canary.37 + specifier: 16.3.0-canary.38 version: link:../next-polyfill-nomodule '@next/react-refresh-utils': - specifier: 16.3.0-canary.37 + specifier: 16.3.0-canary.38 version: link:../react-refresh-utils '@next/swc': - specifier: 16.3.0-canary.37 + specifier: 16.3.0-canary.38 version: link:../next-swc '@opentelemetry/api': specifier: 1.6.0 @@ -1930,7 +1930,7 @@ importers: version: 1.0.20 devDependencies: next: - specifier: 16.3.0-canary.37 + specifier: 16.3.0-canary.38 version: link:../next outdent: specifier: 0.8.0 diff --git a/test/development/app-dir/cache-components-dev-errors/next.config.js b/test/development/app-dir/cache-components-dev-errors/next.config.js index e64bae22d658..94fcd455df28 100644 --- a/test/development/app-dir/cache-components-dev-errors/next.config.js +++ b/test/development/app-dir/cache-components-dev-errors/next.config.js @@ -3,6 +3,11 @@ */ const nextConfig = { cacheComponents: true, + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, } module.exports = nextConfig diff --git a/test/development/app-dir/error-overlay/error-ignored-frames/next.config.js b/test/development/app-dir/error-overlay/error-ignored-frames/next.config.js index 807126e4cf0b..3f1322dc956a 100644 --- a/test/development/app-dir/error-overlay/error-ignored-frames/next.config.js +++ b/test/development/app-dir/error-overlay/error-ignored-frames/next.config.js @@ -1,6 +1,12 @@ /** * @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, +} module.exports = nextConfig diff --git a/test/development/app-dir/hmr-iframe/next.config.js b/test/development/app-dir/hmr-iframe/next.config.js new file mode 100644 index 000000000000..3f1322dc956a --- /dev/null +++ b/test/development/app-dir/hmr-iframe/next.config.js @@ -0,0 +1,12 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, +} + +module.exports = nextConfig diff --git a/test/development/app-dir/owner-stack-invalid-element-type/next.config.js b/test/development/app-dir/owner-stack-invalid-element-type/next.config.js index 807126e4cf0b..3f1322dc956a 100644 --- a/test/development/app-dir/owner-stack-invalid-element-type/next.config.js +++ b/test/development/app-dir/owner-stack-invalid-element-type/next.config.js @@ -1,6 +1,12 @@ /** * @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, +} module.exports = nextConfig diff --git a/test/development/app-dir/owner-stack/next.config.js b/test/development/app-dir/owner-stack/next.config.js index 807126e4cf0b..3f1322dc956a 100644 --- a/test/development/app-dir/owner-stack/next.config.js +++ b/test/development/app-dir/owner-stack/next.config.js @@ -1,6 +1,12 @@ /** * @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, +} module.exports = nextConfig diff --git a/test/e2e/app-dir/cache-components-errors/fixtures/default/next.config.js b/test/e2e/app-dir/cache-components-errors/fixtures/default/next.config.js index e64bae22d658..94fcd455df28 100644 --- a/test/e2e/app-dir/cache-components-errors/fixtures/default/next.config.js +++ b/test/e2e/app-dir/cache-components-errors/fixtures/default/next.config.js @@ -3,6 +3,11 @@ */ const nextConfig = { cacheComponents: true, + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, } module.exports = nextConfig diff --git a/test/e2e/app-dir/cache-components-errors/fixtures/http-access-fallback-prerender/next.config.js b/test/e2e/app-dir/cache-components-errors/fixtures/http-access-fallback-prerender/next.config.js index 34aa2b5aed26..e561e049a1eb 100644 --- a/test/e2e/app-dir/cache-components-errors/fixtures/http-access-fallback-prerender/next.config.js +++ b/test/e2e/app-dir/cache-components-errors/fixtures/http-access-fallback-prerender/next.config.js @@ -5,6 +5,9 @@ const nextConfig = { cacheComponents: true, experimental: { authInterrupts: true, + instantInsights: { + validationLevel: 'manual-warning', + }, }, } diff --git a/test/e2e/app-dir/cache-components/next.config.js b/test/e2e/app-dir/cache-components/next.config.js index 86f364bdd422..e83f1cf6305a 100644 --- a/test/e2e/app-dir/cache-components/next.config.js +++ b/test/e2e/app-dir/cache-components/next.config.js @@ -5,6 +5,11 @@ const nextConfig = { cacheComponents: true, adapterPath: process.env.NEXT_ADAPTER_PATH ?? require.resolve('./my-adapter.mjs'), + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, } module.exports = nextConfig diff --git a/test/e2e/app-dir/instant-validation-level-default/app/bare/page.tsx b/test/e2e/app-dir/instant-validation-level-default/app/bare/page.tsx new file mode 100644 index 000000000000..307c49df3a08 --- /dev/null +++ b/test/e2e/app-dir/instant-validation-level-default/app/bare/page.tsx @@ -0,0 +1,16 @@ +// Bare page (no `unstable_instant` config). Under the framework default +// (`'warning'`), implicit validation fires on this page in dev. The runtime +// data accessed at the top of the page is the "Suspense too high for instant +// navigation" violation that instant validation specifically flags. The root +// layout's Suspense satisfies static-shell validation, so the only error in +// dev is the instant one. +import { connection } from 'next/server' + +export default async function Page() { + await connection() + return ( +
+

bare page (no unstable_instant), runtime data at the top.

+
+ ) +} diff --git a/test/e2e/app-dir/instant-validation-level-default/app/explicit-false/page.tsx b/test/e2e/app-dir/instant-validation-level-default/app/explicit-false/page.tsx new file mode 100644 index 000000000000..27e70e2c2631 --- /dev/null +++ b/test/e2e/app-dir/instant-validation-level-default/app/explicit-false/page.tsx @@ -0,0 +1,16 @@ +// Page explicitly opts out of instant validation. Under the framework +// default (`'warning'`), this segment-level override suppresses the +// implicit validation that would otherwise fire on a bare page, so no +// redbox appears. +import { connection } from 'next/server' + +export const unstable_instant = false + +export default async function Page() { + await connection() + return ( +
+

explicit-false page (segment opts out of validation).

+
+ ) +} diff --git a/test/e2e/app-dir/instant-validation-level-default/app/layout.tsx b/test/e2e/app-dir/instant-validation-level-default/app/layout.tsx new file mode 100644 index 000000000000..a32d71748e1c --- /dev/null +++ b/test/e2e/app-dir/instant-validation-level-default/app/layout.tsx @@ -0,0 +1,23 @@ +import { Suspense, type ReactNode } from 'react' + +// Validation level is not set in next.config.ts, so the framework default +// applies. The default is 'warning' — implicit validation fires on bare +// page/default segments in dev only (build is unaffected unless a segment +// explicitly escalates with `level: 'experimental-error'`). +// +// Children are wrapped in Suspense so that pages with runtime data +// accessed at the top of the page don't fail static-shell validation +// (the Suspense fallback renders into the static shell). Instant +// validation flags "Suspense too high for instant navigation" as an +// instant-specific violation when it runs. +export const unstable_instant = false + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + + loading…

}>{children}
+ + + ) +} diff --git a/test/e2e/app-dir/instant-validation-level-default/instant-validation-level-default.test.ts b/test/e2e/app-dir/instant-validation-level-default/instant-validation-level-default.test.ts new file mode 100644 index 000000000000..461bf9067e0d --- /dev/null +++ b/test/e2e/app-dir/instant-validation-level-default/instant-validation-level-default.test.ts @@ -0,0 +1,93 @@ +import { nextTestSetup } from 'e2e-utils' +import { expectBuildValidationSkipped } from 'e2e-utils/instant-validation' +import { waitForNoErrorToast } from '../../../lib/next-test-utils' + +// This fixture intentionally omits `experimental.instantInsights` from +// next.config.ts. It pins the framework default for `validationLevel` — +// the framework should resolve the default to `'warning'`, which means +// implicit validation fires on bare pages in dev. If the framework default +// ever changes, this test should fail, alerting whoever changes it. +// +// For exhaustive coverage of explicit levels and per-segment overrides, +// see the sibling `instant-validation-level-{warning,manual-warning,error, +// manual-error}` fixtures. +describe('instant validation - default level', () => { + const { next, skipped, isNextDev, isNextStart, isTurbopack } = nextTestSetup({ + files: __dirname, + skipStart: true, + skipDeployment: true, + env: { + NEXT_TEST_LOG_VALIDATION: '1', + }, + }) + if (skipped) return + + if (isNextStart && !isTurbopack) { + it.skip('TODO: snapshot tests for webpack', () => {}) + return + } + + if (isNextStart) { + beforeAll(async () => { + await next.build({ args: ['--experimental-build-mode', 'compile'] }) + }) + afterEach(async () => { + await next.stop() + }) + } else { + beforeAll(async () => { + await next.start() + }) + } + + const prerender = async (pathname: string) => { + return await next.build({ + args: [ + '--experimental-build-mode', + 'generate', + '--debug-build-paths', + `app${pathname}/page.tsx`, + ], + }) + } + + if (isNextDev) { + describe('dev', () => { + it('bare page: framework default matches `warning`, implicit validation fires', async () => { + const browser = await next.browser('/bare') + await expect(browser).toDisplayCollapsedRedbox(` + { + "code": "E1264", + "description": "Next.js encountered uncached data during a navigation.", + "environmentLabel": "Server", + "label": "Instant", + "source": "app/bare/page.tsx (10:19) @ Page + > 10 | await connection() + | ^", + "stack": [ + "Page app/bare/page.tsx (10:19)", + ], + } + `) + }) + + it('explicit-false page: per-segment opt-out still works under default', async () => { + const browser = await next.browser('/explicit-false') + await browser.elementByCss('main') + await waitForNoErrorToast(browser, { waitInMs: 500 }) + }) + }) + } else { + describe('build', () => { + it('bare page: framework default is dev-only, build skips validation', async () => { + const result = await prerender('/bare') + expectBuildValidationSkipped(result) + }) + + it('explicit-false page: per-segment opt-out keeps build clean', async () => { + const result = await prerender('/explicit-false') + expectBuildValidationSkipped(result) + }) + }) + } +}) diff --git a/test/e2e/app-dir/instant-validation-level-default/next.config.ts b/test/e2e/app-dir/instant-validation-level-default/next.config.ts new file mode 100644 index 000000000000..9fd0c7a083d4 --- /dev/null +++ b/test/e2e/app-dir/instant-validation-level-default/next.config.ts @@ -0,0 +1,10 @@ +import type { NextConfig } from 'next' + +const nextConfig: NextConfig = { + cacheComponents: true, + typescript: { + ignoreBuildErrors: true, + }, +} + +export default nextConfig diff --git a/test/e2e/app-dir/loader-file-named-export-custom-loader-error/next.config.js b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/next.config.js index 77961243627b..b625c77b5dca 100644 --- a/test/e2e/app-dir/loader-file-named-export-custom-loader-error/next.config.js +++ b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/next.config.js @@ -5,6 +5,11 @@ const nextConfig = { images: { loaderFile: '/dummy-loader.ts', }, + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, } module.exports = nextConfig diff --git a/test/e2e/app-dir/non-root-project-monorepo/apps/web/next.config.js b/test/e2e/app-dir/non-root-project-monorepo/apps/web/next.config.js index 807126e4cf0b..3f1322dc956a 100644 --- a/test/e2e/app-dir/non-root-project-monorepo/apps/web/next.config.js +++ b/test/e2e/app-dir/non-root-project-monorepo/apps/web/next.config.js @@ -1,6 +1,12 @@ /** * @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, +} module.exports = nextConfig diff --git a/test/e2e/app-dir/router-autoscroll/next.config.js b/test/e2e/app-dir/router-autoscroll/next.config.js index 36e8d82ea1af..ad60a12885cd 100644 --- a/test/e2e/app-dir/router-autoscroll/next.config.js +++ b/test/e2e/app-dir/router-autoscroll/next.config.js @@ -1,6 +1,12 @@ /** * @type {import('next').NextConfig} */ -const config = {} +const config = { + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, +} module.exports = config diff --git a/test/e2e/app-dir/server-source-maps/fixtures/default/next.config.js b/test/e2e/app-dir/server-source-maps/fixtures/default/next.config.js index 8a61ee02d1f4..19c0893004fa 100644 --- a/test/e2e/app-dir/server-source-maps/fixtures/default/next.config.js +++ b/test/e2e/app-dir/server-source-maps/fixtures/default/next.config.js @@ -6,6 +6,9 @@ const nextConfig = { experimental: { cpus: 1, serverSourceMaps: true, + instantInsights: { + validationLevel: 'manual-warning', + }, }, serverExternalPackages: ['external-pkg'], } diff --git a/test/e2e/app-dir/server-source-maps/fixtures/edge/next.config.js b/test/e2e/app-dir/server-source-maps/fixtures/edge/next.config.js index 86773e2ae9a6..0341e872150f 100644 --- a/test/e2e/app-dir/server-source-maps/fixtures/edge/next.config.js +++ b/test/e2e/app-dir/server-source-maps/fixtures/edge/next.config.js @@ -5,6 +5,9 @@ const nextConfig = { experimental: { cpus: 1, serverSourceMaps: true, + instantInsights: { + validationLevel: 'manual-warning', + }, }, } diff --git a/test/e2e/legacy-link-behavior/next.config.js b/test/e2e/legacy-link-behavior/next.config.js index e64bae22d658..94fcd455df28 100644 --- a/test/e2e/legacy-link-behavior/next.config.js +++ b/test/e2e/legacy-link-behavior/next.config.js @@ -3,6 +3,11 @@ */ const nextConfig = { cacheComponents: true, + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, } module.exports = nextConfig diff --git a/test/e2e/next-image-new/app-dir-localpatterns/next.config.js b/test/e2e/next-image-new/app-dir-localpatterns/next.config.js index 10c28b1a185c..635050bf29c1 100644 --- a/test/e2e/next-image-new/app-dir-localpatterns/next.config.js +++ b/test/e2e/next-image-new/app-dir-localpatterns/next.config.js @@ -7,4 +7,9 @@ module.exports = { }, ], }, + experimental: { + instantInsights: { + validationLevel: 'manual-warning', + }, + }, } diff --git a/test/production/debug-build-path/debug-build-paths.test.ts b/test/production/debug-build-path/debug-build-paths.test.ts index ac382337ebd2..a70ab6827bb3 100644 --- a/test/production/debug-build-path/debug-build-paths.test.ts +++ b/test/production/debug-build-path/debug-build-paths.test.ts @@ -1,11 +1,33 @@ import path from 'path' import { nextTestSetup } from 'e2e-utils' +function getTreeView(cliOutput: string): string { + let foundStart = false + const lines: string[] = [] + + for (const line of cliOutput.split('\n')) { + foundStart ||= line.startsWith('Route ') + + if (foundStart) { + lines.push(line) + } + + if (line.startsWith('└')) { + foundStart = false + } + } + + return lines.join('\n').trim() +} + describe('debug-build-paths', () => { describe('default fixture', () => { const { next } = nextTestSetup({ files: path.join(__dirname, 'fixtures/default'), skipStart: true, + env: { + __NEXT_PRIVATE_DETERMINISTIC_BUILD_OUTPUT: '1', + }, }) describe('explicit path formats', () => { @@ -17,12 +39,11 @@ describe('debug-build-paths', () => { expect(buildResult.cliOutput).toBeDefined() // Should only build the specified page - expect(buildResult.cliOutput).toContain('Route (pages)') - expect(buildResult.cliOutput).toContain('○ /foo') - // Should not build other pages - expect(buildResult.cliOutput).not.toContain('○ /bar') - // Should not build app routes - expect(buildResult.cliOutput).not.toContain('Route (app)') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (pages) + ┌ ○ /404 + └ ○ /foo" + `) }) it('should build multiple pages routes', async () => { @@ -33,11 +54,12 @@ describe('debug-build-paths', () => { expect(buildResult.cliOutput).toBeDefined() // Should build both specified pages - expect(buildResult.cliOutput).toContain('Route (pages)') - expect(buildResult.cliOutput).toContain('○ /foo') - expect(buildResult.cliOutput).toContain('○ /bar') - // Should not build app routes - expect(buildResult.cliOutput).not.toContain('Route (app)') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (pages) + ┌ ○ /404 + ├ ○ /bar + └ ○ /foo" + `) }) it('should build dynamic route with literal [slug] path', async () => { @@ -50,14 +72,11 @@ describe('debug-build-paths', () => { expect(buildResult.cliOutput).toBeDefined() // Should build only the blog/[slug] route - expect(buildResult.cliOutput).toContain('Route (app)') - expect(buildResult.cliOutput).toContain('/blog/[slug]') - // Should not build other app routes - expect(buildResult.cliOutput).not.toMatch(/○ \/\n/) - expect(buildResult.cliOutput).not.toContain('○ /about') - expect(buildResult.cliOutput).not.toContain('○ /dashboard') - // Should not build pages routes - expect(buildResult.cliOutput).not.toContain('Route (pages)') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ /_not-found + └ ƒ /blog/[slug]" + `) }) }) @@ -69,17 +88,14 @@ describe('debug-build-paths', () => { expect(buildResult.exitCode).toBe(0) expect(buildResult.cliOutput).toBeDefined() - // Should build pages matching the glob - expect(buildResult.cliOutput).toContain('Route (pages)') - expect(buildResult.cliOutput).toContain('○ /foo') - expect(buildResult.cliOutput).toContain('○ /bar') - - // Should build the specified app route - expect(buildResult.cliOutput).toContain('Route (app)') - expect(buildResult.cliOutput).toContain('○ /') - // Should not build other app routes - expect(buildResult.cliOutput).not.toContain('○ /about') - expect(buildResult.cliOutput).not.toContain('○ /dashboard') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ / + └ ○ /_not-found + Route (pages) + ┌ ○ /bar + └ ○ /foo" + `) }) it('should match nested routes with app/blog/**/page.tsx pattern', async () => { @@ -90,14 +106,12 @@ describe('debug-build-paths', () => { expect(buildResult.cliOutput).toBeDefined() // Should build the blog route - expect(buildResult.cliOutput).toContain('Route (app)') - expect(buildResult.cliOutput).toContain('/blog/[slug]') - // Should not build other app routes (check for exact route, not substring) - expect(buildResult.cliOutput).not.toMatch(/○ \/\n/) - expect(buildResult.cliOutput).not.toContain('○ /about') - expect(buildResult.cliOutput).not.toContain('○ /dashboard') - // Should not build pages routes - expect(buildResult.cliOutput).not.toContain('Route (pages)') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ /_not-found + ├ ƒ /blog/[slug] + └ ƒ /blog/[slug]/comments" + `) }) it('should match dynamic routes with glob before brackets like app/**/[slug]/page.tsx', async () => { @@ -108,14 +122,11 @@ describe('debug-build-paths', () => { expect(buildResult.cliOutput).toBeDefined() // Should build the blog/[slug] route - expect(buildResult.cliOutput).toContain('Route (app)') - expect(buildResult.cliOutput).toContain('/blog/[slug]') - // Should not build other app routes - expect(buildResult.cliOutput).not.toMatch(/○ \/\n/) - expect(buildResult.cliOutput).not.toContain('○ /about') - expect(buildResult.cliOutput).not.toContain('○ /dashboard') - // Should not build pages routes - expect(buildResult.cliOutput).not.toContain('Route (pages)') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ /_not-found + └ ƒ /blog/[slug]" + `) }) it('should match hybrid pattern with literal [slug] and glob **', async () => { @@ -129,15 +140,12 @@ describe('debug-build-paths', () => { expect(buildResult.cliOutput).toBeDefined() // Should build both blog/[slug] and blog/[slug]/comments routes - expect(buildResult.cliOutput).toContain('Route (app)') - expect(buildResult.cliOutput).toContain('/blog/[slug]') - expect(buildResult.cliOutput).toContain('/blog/[slug]/comments') - // Should not build other app routes - expect(buildResult.cliOutput).not.toMatch(/○ \/\n/) - expect(buildResult.cliOutput).not.toContain('○ /about') - expect(buildResult.cliOutput).not.toContain('○ /dashboard') - // Should not build pages routes - expect(buildResult.cliOutput).not.toContain('Route (pages)') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ /_not-found + ├ ƒ /blog/[slug] + └ ƒ /blog/[slug]/comments" + `) }) it('should match multiple app routes with explicit patterns', async () => { @@ -151,15 +159,15 @@ describe('debug-build-paths', () => { expect(buildResult.cliOutput).toBeDefined() // Should build specified app routes - expect(buildResult.cliOutput).toContain('Route (app)') - expect(buildResult.cliOutput).toContain('○ /') - expect(buildResult.cliOutput).toContain('○ /about') - expect(buildResult.cliOutput).toContain('○ /dashboard') - expect(buildResult.cliOutput).toContain('/blog/[slug]') - // Should not build routes not specified - expect(buildResult.cliOutput).not.toContain('/with-type-error') - // Should not build pages routes - expect(buildResult.cliOutput).not.toContain('Route (pages)') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ / + ├ ○ /_not-found + ├ ○ /about + ├ ƒ /blog/[slug] + ├ ƒ /blog/[slug]/comments + └ ○ /dashboard" + `) }) it('should exclude paths matching negation patterns', async () => { @@ -188,9 +196,11 @@ describe('debug-build-paths', () => { }) expect(buildResult.exitCode).toBe(0) - expect(buildResult.cliOutput).toContain('Route (app)') - expect(buildResult.cliOutput).toContain('/blog/[slug]') - expect(buildResult.cliOutput).not.toContain('/blog/[slug]/comments') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ /_not-found + └ ƒ /blog/[slug]" + `) }) it('should support multiple negation patterns', async () => { @@ -228,12 +238,13 @@ describe('debug-build-paths', () => { args: ['--debug-build-paths', 'app/(group)/**/page.tsx'], }) expect(buildResult.exitCode).toBe(0) - expect(buildResult.cliOutput).toContain('Route (app)') + // Route groups are stripped from the path, so /nested instead of /(group)/nested - expect(buildResult.cliOutput).toContain('/nested') - // Should not build other routes - expect(buildResult.cliOutput).not.toContain('○ /about') - expect(buildResult.cliOutput).not.toContain('○ /dashboard') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ /_not-found + └ ○ /nested" + `) }) it('should build routes with parallel routes', async () => { @@ -241,12 +252,12 @@ describe('debug-build-paths', () => { args: ['--debug-build-paths', 'app/parallel-test/**/page.tsx'], }) expect(buildResult.exitCode).toBe(0) - expect(buildResult.cliOutput).toContain('Route (app)') // Parallel route segments (@sidebar) are stripped from the path - expect(buildResult.cliOutput).toContain('/parallel-test') - // Should not build other routes - expect(buildResult.cliOutput).not.toContain('○ /about') - expect(buildResult.cliOutput).not.toContain('○ /dashboard') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ /_not-found + └ ○ /parallel-test" + `) }) }) @@ -258,10 +269,11 @@ describe('debug-build-paths', () => { }) // Build should succeed because the file with type error is not checked expect(buildResult.exitCode).toBe(0) - expect(buildResult.cliOutput).toContain('Route (pages)') - expect(buildResult.cliOutput).toContain('○ /foo') - // Should not include app routes - expect(buildResult.cliOutput).not.toContain('Route (app)') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (pages) + ┌ ○ /404 + └ ○ /foo" + `) }) it('should fail typechecking when route with type error is included', async () => { @@ -281,6 +293,9 @@ describe('debug-build-paths', () => { const { next } = nextTestSetup({ files: path.join(__dirname, 'fixtures/src-dir'), skipStart: true, + env: { + __NEXT_PRIVATE_DETERMINISTIC_BUILD_OUTPUT: '1', + }, }) it('should resolve app patterns with explicit src/ prefix', async () => { @@ -288,11 +303,11 @@ describe('debug-build-paths', () => { args: ['--debug-build-paths', 'src/app/blog/[slug]/page.tsx'], }) expect(buildResult.exitCode).toBe(0) - expect(buildResult.cliOutput).toContain('Route (app)') - expect(buildResult.cliOutput).toContain('/blog/[slug]') - // Should not build other app routes - expect(buildResult.cliOutput).not.toMatch(/○ \/\n/) - expect(buildResult.cliOutput).not.toContain('Route (pages)') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ /_not-found + └ ƒ /blog/[slug]" + `) }) it('should resolve app patterns without src/ prefix when project uses src/app', async () => { @@ -300,10 +315,11 @@ describe('debug-build-paths', () => { args: ['--debug-build-paths', 'app/blog/[slug]/page.tsx'], }) expect(buildResult.exitCode).toBe(0) - expect(buildResult.cliOutput).toContain('Route (app)') - expect(buildResult.cliOutput).toContain('/blog/[slug]') - expect(buildResult.cliOutput).not.toMatch(/○ \/\n/) - expect(buildResult.cliOutput).not.toContain('Route (pages)') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ /_not-found + └ ƒ /blog/[slug]" + `) }) it('should resolve pages patterns without src/ prefix when project uses src/pages', async () => { @@ -311,9 +327,11 @@ describe('debug-build-paths', () => { args: ['--debug-build-paths', 'pages/foo.tsx'], }) expect(buildResult.exitCode).toBe(0) - expect(buildResult.cliOutput).toContain('Route (pages)') - expect(buildResult.cliOutput).toContain('○ /foo') - expect(buildResult.cliOutput).not.toContain('Route (app)') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (pages) + ┌ ○ /404 + └ ○ /foo" + `) }) it('should resolve glob patterns without src/ prefix when project uses src/app', async () => { @@ -321,10 +339,11 @@ describe('debug-build-paths', () => { args: ['--debug-build-paths', 'app/(group)/**/page.tsx'], }) expect(buildResult.exitCode).toBe(0) - expect(buildResult.cliOutput).toContain('Route (app)') - // Route groups are stripped from the path - expect(buildResult.cliOutput).toContain('/nested') - expect(buildResult.cliOutput).not.toContain('/blog/[slug]') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ /_not-found + └ ○ /nested" + `) }) }) @@ -332,6 +351,9 @@ describe('debug-build-paths', () => { const { next } = nextTestSetup({ files: path.join(__dirname, 'fixtures/with-compile-error'), skipStart: true, + env: { + __NEXT_PRIVATE_DETERMINISTIC_BUILD_OUTPUT: '1', + }, }) it('should skip compilation of excluded routes with compile errors', async () => { @@ -340,11 +362,11 @@ describe('debug-build-paths', () => { args: ['--debug-build-paths', 'app/valid/page.tsx'], }) // Build should succeed because the broken page is not compiled - expect(buildResult.exitCode).toBe(0) - expect(buildResult.cliOutput).toContain('Route (app)') - expect(buildResult.cliOutput).toContain('○ /valid') - // Should not include the broken route - expect(buildResult.cliOutput).not.toContain('/broken') + expect(getTreeView(buildResult.cliOutput)).toMatchInlineSnapshot(` + "Route (app) + ┌ ○ /_not-found + └ ○ /valid" + `) }) it('should fail compilation when route with compile error is included', async () => { diff --git a/test/unit/instant-config-normalization.test.ts b/test/unit/instant-config-normalization.test.ts index e33a8c7811db..670a6d0bbe12 100644 --- a/test/unit/instant-config-normalization.test.ts +++ b/test/unit/instant-config-normalization.test.ts @@ -14,7 +14,7 @@ function uniqueDir(tag: string) { // `experimental.instantInsights.validationLevel` to a concrete value in // one place so consumers don't each need to know the current framework default. describe('experimental.instantInsights validationLevel normalization', () => { - it('defaults to manual-warning when the instantInsights config is absent', async () => { + it('defaults to warning when the instantInsights config is absent', async () => { const config = await loadConfig( PHASE_PRODUCTION_SERVER, uniqueDir('absent'), @@ -23,11 +23,11 @@ describe('experimental.instantInsights validationLevel normalization', () => { } ) expect(config.experimental.instantInsights).toEqual({ - validationLevel: 'manual-warning', + validationLevel: 'warning', }) }) - it('defaults to manual-warning when experimental.instantInsights is an empty object', async () => { + it('defaults to warning when experimental.instantInsights is an empty object', async () => { const config = await loadConfig( PHASE_PRODUCTION_SERVER, uniqueDir('empty'), @@ -36,7 +36,7 @@ describe('experimental.instantInsights validationLevel normalization', () => { } ) expect(config.experimental.instantInsights).toEqual({ - validationLevel: 'manual-warning', + validationLevel: 'warning', }) }) diff --git a/test/unit/preserve-process-env.test.ts b/test/unit/preserve-process-env.test.ts index cbddc233040e..174ea9f764d2 100644 --- a/test/unit/preserve-process-env.test.ts +++ b/test/unit/preserve-process-env.test.ts @@ -1,4 +1,8 @@ -import { loadEnvConfig } from '../../packages/next-env/' +import { + loadEnvConfig, + resetEnv, + updateInitialEnv, +} from '../../packages/next-env/' describe('preserve process env', () => { it('should not reassign `process.env`', () => { @@ -6,4 +10,20 @@ describe('preserve process env', () => { loadEnvConfig('.') expect(Object.is(originalProcessEnv, process.env)).toBeTrue() }) + + it('should remove values unset in the initial env snapshot', () => { + const key = '__NEXT_TEST_UNSET_INITIAL_ENV' + + try { + loadEnvConfig('.') + process.env[key] = 'changed' + updateInitialEnv({ [key]: undefined }) + + resetEnv() + + expect(process.env[key]).toBeUndefined() + } finally { + delete process.env[key] + } + }) }) diff --git a/test/use-node-streams-tests-manifest.json b/test/use-node-streams-tests-manifest.json deleted file mode 100644 index 46c1c103b37e..000000000000 --- a/test/use-node-streams-tests-manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": 2, - "suites": {}, - "rules": { - "include": [ - "test/e2e/**/*.test.{t,j}s{,x}", - "test/production/app-*/**/*.test.{t,j}s{,x}", - "test/development/app-*/**/*.test.{t,j}s{,x}", - "test/development/acceptance-app/**/*.test.{t,j}s{,x}" - ], - "exclude": [] - } -} diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 03cfd954f508..9ba4c465d2a3 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -31,8 +31,8 @@ use turbo_bincode::{TurboBincodeBuffer, new_turbo_bincode_decoder, new_turbo_bin use turbo_tasks::{ CellId, RawVc, ReadCellOptions, ReadCellTracking, ReadConsistency, ReadOutputOptions, ReadTracking, SharedReference, StackDynTaskInputs, TRANSIENT_TASK_BIT, TaskExecutionReason, - TaskId, TaskPersistence, TaskPriority, TraitTypeId, TurboTasksBackendApi, TurboTasksPanic, - ValueTypeId, + TaskId, TaskPersistence, TaskPriority, TraitTypeId, TurboTasks, TurboTasksCallApi, + TurboTasksPanic, ValueTypeId, backend::{ Backend, CachedTaskType, CachedTaskTypeArc, CellContent, CellHash, TaskExecutionSpec, TransientTaskType, TurboTaskContextError, TurboTaskLocalContextError, TurboTasksError, @@ -225,7 +225,7 @@ impl TurboTasksBackend { /// Returns `(snapshot_had_new_data, eviction_counts)`. pub fn snapshot_and_evict_for_testing( &self, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) -> (bool, EvictionCounts) { self.0.snapshot_and_evict_for_testing(turbo_tasks) } @@ -270,7 +270,7 @@ impl TurboTasksBackendInner { fn execute_context<'a>( &'a self, - turbo_tasks: &'a dyn TurboTasksBackendApi>, + turbo_tasks: &'a TurboTasks>, ) -> impl ExecuteContext<'a> { ExecuteContextImpl::new(self, turbo_tasks) } @@ -312,7 +312,7 @@ impl TurboTasksBackendInner { #[doc(hidden)] pub fn snapshot_and_evict_for_testing( &self, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) -> (bool, EvictionCounts) { assert!( self.should_persist(), @@ -439,7 +439,7 @@ impl TurboTasksBackendInner { task_id: TaskId, reader: Option, options: ReadOutputOptions, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) -> Result> { self.assert_not_persistent_calling_transient(reader, task_id, /* cell_id */ None); @@ -558,8 +558,7 @@ impl TurboTasksBackendInner { let this = self.clone(); let tt = turbo_tasks.pin(); move || { - let tt: &dyn TurboTasksBackendApi> = &*tt; - let mut ctx = this.execute_context(tt); + let mut ctx = this.execute_context(&tt); let mut visited = FxHashSet::default(); fn indent(s: &str) -> String { s.split_inclusive('\n') @@ -771,7 +770,7 @@ impl TurboTasksBackendInner { reader: Option, cell: CellId, options: ReadCellOptions, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) -> Result> { self.assert_not_persistent_calling_transient(reader, task_id, Some(cell)); @@ -938,7 +937,7 @@ impl TurboTasksBackendInner { &self, parent_span: Option, reason: &str, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) -> Result<(Instant, bool), anyhow::Error> { let snapshot_span = tracing::trace_span!(parent: parent_span.clone(), "snapshot", reason = reason) @@ -1376,7 +1375,7 @@ impl TurboTasksBackendInner { Ok((snapshot_time, true)) } - fn startup(&self, turbo_tasks: &dyn TurboTasksBackendApi>) { + fn startup(&self, turbo_tasks: &TurboTasks>) { if self.should_restore() { // Continue all uncompleted operations // They can't be interrupted by a snapshot since the snapshotting job has not been @@ -1409,7 +1408,7 @@ impl TurboTasksBackendInner { } #[allow(unused_variables)] - fn stop(&self, turbo_tasks: &dyn TurboTasksBackendApi>) { + fn stop(&self, turbo_tasks: &TurboTasks>) { #[cfg(feature = "verify_aggregation_graph")] { self.is_idle.store(false, Ordering::Release); @@ -1427,7 +1426,7 @@ impl TurboTasksBackendInner { } #[allow(unused_variables)] - fn idle_start(self: &Arc, turbo_tasks: &dyn TurboTasksBackendApi>) { + fn idle_start(self: &Arc, turbo_tasks: &TurboTasks>) { self.idle_start_event.notify(usize::MAX); #[cfg(feature = "verify_aggregation_graph")] @@ -1467,7 +1466,7 @@ impl TurboTasksBackendInner { arg: &mut dyn StackDynTaskInputs, parent_task: Option, persistence: TaskPersistence, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) -> TaskId { let transient = matches!(persistence, TaskPersistence::Transient); @@ -1692,11 +1691,7 @@ impl TurboTasksBackendInner { ) } - fn invalidate_task( - &self, - task_id: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi>, - ) { + fn invalidate_task(&self, task_id: TaskId, turbo_tasks: &TurboTasks>) { if !self.should_track_dependencies() { panic!("Dependency tracking is disabled so invalidation is not allowed"); } @@ -1708,11 +1703,7 @@ impl TurboTasksBackendInner { ); } - fn invalidate_tasks( - &self, - tasks: &[TaskId], - turbo_tasks: &dyn TurboTasksBackendApi>, - ) { + fn invalidate_tasks(&self, tasks: &[TaskId], turbo_tasks: &TurboTasks>) { if !self.should_track_dependencies() { panic!("Dependency tracking is disabled so invalidation is not allowed"); } @@ -1727,7 +1718,7 @@ impl TurboTasksBackendInner { fn invalidate_tasks_set( &self, tasks: &AutoSet, 2>, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) { if !self.should_track_dependencies() { panic!("Dependency tracking is disabled so invalidation is not allowed"); @@ -1743,7 +1734,7 @@ impl TurboTasksBackendInner { fn invalidate_serialization( &self, task_id: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) { if task_id.is_transient() { return; @@ -1767,7 +1758,7 @@ impl TurboTasksBackendInner { fn get_task_name( &self, task_id: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) -> String { let mut ctx = self.execute_context(turbo_tasks); let task = ctx.task(task_id, TaskDataCategory::Data); @@ -1788,7 +1779,7 @@ impl TurboTasksBackendInner { fn task_execution_canceled( &self, task_id: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) { let mut ctx = self.execute_context(turbo_tasks); let mut task = ctx.task(task_id, TaskDataCategory::All); @@ -1839,7 +1830,7 @@ impl TurboTasksBackendInner { &self, task_id: TaskId, priority: TaskPriority, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) -> Option> { let execution_reason; let task_type; @@ -1959,7 +1950,7 @@ impl TurboTasksBackendInner { cell_counters: &AutoMap, 8>, #[cfg(feature = "verify_determinism")] stateful: bool, has_invalidator: bool, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) -> Option { // Task completion is a 4 step process: // 1. Remove old edges (dependencies, collectibles, children, cells) and update the @@ -2793,7 +2784,7 @@ impl TurboTasksBackendInner { fn run_backend_job<'a>( self: &'a Arc, job: TurboTasksBackendJob, - turbo_tasks: &'a dyn TurboTasksBackendApi>, + turbo_tasks: &'a TurboTasks>, ) -> Pin + Send + 'a>> { Box::pin(async move { match job { @@ -2977,7 +2968,7 @@ impl TurboTasksBackendInner { &self, task_id: TaskId, cell: CellId, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) -> Result { let mut ctx = self.execute_context(turbo_tasks); let task = ctx.task(task_id, TaskDataCategory::Data); @@ -2993,7 +2984,7 @@ impl TurboTasksBackendInner { task_id: TaskId, collectible_type: TraitTypeId, reader_id: Option, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) -> AutoMap, 1> { let mut ctx = self.execute_context(turbo_tasks); let mut collectibles = AutoMap::default(); @@ -3056,7 +3047,7 @@ impl TurboTasksBackendInner { collectible_type: TraitTypeId, collectible: RawVc, task_id: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) { self.assert_valid_collectible(task_id, collectible); @@ -3084,7 +3075,7 @@ impl TurboTasksBackendInner { collectible: RawVc, count: u32, task_id: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) { self.assert_valid_collectible(task_id, collectible); @@ -3114,7 +3105,7 @@ impl TurboTasksBackendInner { updated_key_hashes: Option>, content_hash: Option, verification_mode: VerificationMode, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) { operation::UpdateCellOperation::run( task_id, @@ -3130,7 +3121,7 @@ impl TurboTasksBackendInner { fn mark_own_task_as_finished( &self, task: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) { let mut ctx = self.execute_context(turbo_tasks); let mut task = ctx.task(task, TaskDataCategory::Data); @@ -3151,7 +3142,7 @@ impl TurboTasksBackendInner { &self, task: TaskId, parent_task: Option, - turbo_tasks: &dyn TurboTasksBackendApi>, + turbo_tasks: &TurboTasks>, ) { self.assert_not_persistent_calling_transient(parent_task, task, None); ConnectChildOperation::run(parent_task, task, self.execute_context(turbo_tasks)); @@ -3168,11 +3159,7 @@ impl TurboTasksBackendInner { task_id } - fn dispose_root_task( - &self, - task_id: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi>, - ) { + fn dispose_root_task(&self, task_id: TaskId, turbo_tasks: &TurboTasks>) { #[cfg(feature = "verify_aggregation_graph")] self.root_tasks.lock().remove(&task_id); @@ -3194,11 +3181,7 @@ impl TurboTasksBackendInner { } #[cfg(feature = "verify_aggregation_graph")] - fn verify_aggregation_graph( - &self, - turbo_tasks: &dyn TurboTasksBackendApi>, - idle: bool, - ) { + fn verify_aggregation_graph(&self, turbo_tasks: &TurboTasks>, idle: bool) { if env::var("TURBO_ENGINE_VERIFY_GRAPH").ok().as_deref() == Some("0") { return; } @@ -3456,23 +3439,23 @@ impl TurboTasksBackendInner { } impl Backend for TurboTasksBackend { - fn startup(&self, turbo_tasks: &dyn TurboTasksBackendApi) { + fn startup(&self, turbo_tasks: &TurboTasks) { self.0.startup(turbo_tasks); } - fn stopping(&self, _turbo_tasks: &dyn TurboTasksBackendApi) { + fn stopping(&self, _turbo_tasks: &TurboTasks) { self.0.stopping(); } - fn stop(&self, turbo_tasks: &dyn TurboTasksBackendApi) { + fn stop(&self, turbo_tasks: &TurboTasks) { self.0.stop(turbo_tasks); } - fn idle_start(&self, turbo_tasks: &dyn TurboTasksBackendApi) { + fn idle_start(&self, turbo_tasks: &TurboTasks) { self.0.idle_start(turbo_tasks); } - fn idle_end(&self, _turbo_tasks: &dyn TurboTasksBackendApi) { + fn idle_end(&self, _turbo_tasks: &TurboTasks) { self.0.idle_end(); } @@ -3483,37 +3466,33 @@ impl Backend for TurboTasksBackend { arg: &mut dyn StackDynTaskInputs, parent_task: Option, persistence: TaskPersistence, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> TaskId { self.0 .get_or_create_task(native_fn, this, arg, parent_task, persistence, turbo_tasks) } - fn invalidate_task(&self, task_id: TaskId, turbo_tasks: &dyn TurboTasksBackendApi) { + fn invalidate_task(&self, task_id: TaskId, turbo_tasks: &TurboTasks) { self.0.invalidate_task(task_id, turbo_tasks); } - fn invalidate_tasks(&self, tasks: &[TaskId], turbo_tasks: &dyn TurboTasksBackendApi) { + fn invalidate_tasks(&self, tasks: &[TaskId], turbo_tasks: &TurboTasks) { self.0.invalidate_tasks(tasks, turbo_tasks); } fn invalidate_tasks_set( &self, tasks: &AutoSet, 2>, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) { self.0.invalidate_tasks_set(tasks, turbo_tasks); } - fn invalidate_serialization( - &self, - task_id: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi, - ) { + fn invalidate_serialization(&self, task_id: TaskId, turbo_tasks: &TurboTasks) { self.0.invalidate_serialization(task_id, turbo_tasks); } - fn task_execution_canceled(&self, task: TaskId, turbo_tasks: &dyn TurboTasksBackendApi) { + fn task_execution_canceled(&self, task: TaskId, turbo_tasks: &TurboTasks) { self.0.task_execution_canceled(task, turbo_tasks) } @@ -3521,7 +3500,7 @@ impl Backend for TurboTasksBackend { &self, task_id: TaskId, priority: TaskPriority, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> Option> { self.0 .try_start_task_execution(task_id, priority, turbo_tasks) @@ -3534,7 +3513,7 @@ impl Backend for TurboTasksBackend { cell_counters: &AutoMap, 8>, #[cfg(feature = "verify_determinism")] stateful: bool, has_invalidator: bool, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> Option { self.0.task_execution_completed( task_id, @@ -3552,7 +3531,7 @@ impl Backend for TurboTasksBackend { fn run_backend_job<'a>( &'a self, job: Self::BackendJob, - turbo_tasks: &'a dyn TurboTasksBackendApi, + turbo_tasks: &'a TurboTasks, ) -> Pin + Send + 'a>> { self.0.run_backend_job(job, turbo_tasks) } @@ -3562,7 +3541,7 @@ impl Backend for TurboTasksBackend { task_id: TaskId, reader: Option, options: ReadOutputOptions, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> Result> { self.0 .try_read_task_output(task_id, reader, options, turbo_tasks) @@ -3574,7 +3553,7 @@ impl Backend for TurboTasksBackend { cell: CellId, reader: Option, options: ReadCellOptions, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> Result> { self.0 .try_read_task_cell(task_id, reader, cell, options, turbo_tasks) @@ -3584,7 +3563,7 @@ impl Backend for TurboTasksBackend { &self, task_id: TaskId, cell: CellId, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> Result { self.0.try_read_own_task_cell(task_id, cell, turbo_tasks) } @@ -3594,7 +3573,7 @@ impl Backend for TurboTasksBackend { task_id: TaskId, collectible_type: TraitTypeId, reader: Option, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> AutoMap, 1> { self.0 .read_task_collectibles(task_id, collectible_type, reader, turbo_tasks) @@ -3605,7 +3584,7 @@ impl Backend for TurboTasksBackend { collectible_type: TraitTypeId, collectible: RawVc, task_id: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) { self.0 .emit_collectible(collectible_type, collectible, task_id, turbo_tasks) @@ -3617,7 +3596,7 @@ impl Backend for TurboTasksBackend { collectible: RawVc, count: u32, task_id: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) { self.0 .unemit_collectible(collectible_type, collectible, count, task_id, turbo_tasks) @@ -3631,7 +3610,7 @@ impl Backend for TurboTasksBackend { updated_key_hashes: Option>, content_hash: Option, verification_mode: VerificationMode, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) { self.0.update_task_cell( task_id, @@ -3644,11 +3623,7 @@ impl Backend for TurboTasksBackend { ); } - fn mark_own_task_as_finished( - &self, - task_id: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi, - ) { + fn mark_own_task_as_finished(&self, task_id: TaskId, turbo_tasks: &TurboTasks) { self.0.mark_own_task_as_finished(task_id, turbo_tasks); } @@ -3656,7 +3631,7 @@ impl Backend for TurboTasksBackend { &self, task: TaskId, parent_task: Option, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) { self.0.connect_task(task, parent_task, turbo_tasks); } @@ -3664,12 +3639,12 @@ impl Backend for TurboTasksBackend { fn create_transient_task( &self, task_type: TransientTaskType, - _turbo_tasks: &dyn TurboTasksBackendApi, + _turbo_tasks: &TurboTasks, ) -> TaskId { self.0.create_transient_task(task_type) } - fn dispose_root_task(&self, task_id: TaskId, turbo_tasks: &dyn TurboTasksBackendApi) { + fn dispose_root_task(&self, task_id: TaskId, turbo_tasks: &TurboTasks) { self.0.dispose_root_task(task_id, turbo_tasks); } @@ -3681,7 +3656,7 @@ impl Backend for TurboTasksBackend { self.0.options.dependency_tracking } - fn get_task_name(&self, task: TaskId, turbo_tasks: &dyn TurboTasksBackendApi) -> String { + fn get_task_name(&self, task: TaskId, turbo_tasks: &TurboTasks) -> String { self.0.get_task_name(task, turbo_tasks) } } diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs index fa686e558957..bedb94e8e38b 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs @@ -19,7 +19,7 @@ use tracing::info_span; use tracing::trace_span; use turbo_tasks::{ CellId, DynTaskInputs, FxIndexMap, RawVc, SharedReference, TaskExecutionReason, TaskId, - TaskPriority, TurboTasksBackendApi, TurboTasksCallApi, backend::CachedTaskTypeArc, + TaskPriority, TurboTasks, TurboTasksCallApi, backend::CachedTaskTypeArc, macro_helpers::NativeFunction, }; @@ -168,7 +168,7 @@ impl TaskLockCounter { pub struct ExecuteContextImpl<'e, B: BackingStorage> { backend: &'e TurboTasksBackendInner, - turbo_tasks: &'e dyn TurboTasksBackendApi>, + turbo_tasks: &'e TurboTasks>, _operation_guard: Option>, task_lock_counter: TaskLockCounter, } @@ -176,7 +176,7 @@ pub struct ExecuteContextImpl<'e, B: BackingStorage> { impl<'e, B: BackingStorage> ExecuteContextImpl<'e, B> { pub(super) fn new( backend: &'e TurboTasksBackendInner, - turbo_tasks: &'e dyn TurboTasksBackendApi>, + turbo_tasks: &'e TurboTasks>, ) -> Self { Self { backend, @@ -1018,7 +1018,7 @@ impl<'e, B: BackingStorage> ExecuteContext<'e> for ExecuteContextImpl<'e, B> { struct ChildExecuteContextImpl<'e, B: BackingStorage> { backend: &'e TurboTasksBackendInner, - turbo_tasks: &'e dyn TurboTasksBackendApi>, + turbo_tasks: &'e TurboTasks>, } impl<'e, B: BackingStorage> ChildExecuteContext<'e> for ChildExecuteContextImpl<'e, B> { diff --git a/turbopack/crates/turbo-tasks/src/backend.rs b/turbopack/crates/turbo-tasks/src/backend.rs index a23282829a27..904015babf77 100644 --- a/turbopack/crates/turbo-tasks/src/backend.rs +++ b/turbopack/crates/turbo-tasks/src/backend.rs @@ -29,14 +29,13 @@ use turbo_rcstr::RcStr; use turbo_tasks_hash::DeterministicHasher; use crate::{ - RawVc, ReadCellOptions, ReadOutputOptions, ReadRef, SharedReference, TaskId, TaskIdSet, + CellId, RawVc, ReadCellOptions, ReadOutputOptions, ReadRef, SharedReference, TaskId, TaskIdSet, TaskPriority, TraitRef, TraitTypeId, TurboTasksCallApi, TurboTasksPanic, ValueTypeId, ValueTypePersistence, VcValueTrait, VcValueType, dyn_task_inputs::{DynTaskInputs, StackDynTaskInputs}, event::EventListener, macro_helpers::NativeFunction, - manager::{TaskPersistence, TurboTasksBackendApi}, - raw_vc::CellId, + manager::{TaskPersistence, TurboTasks}, registry, task::shared_reference::TypedSharedReference, task_statistics::TaskStatisticsApi, @@ -594,40 +593,35 @@ pub enum VerificationMode { Skip, } -pub trait Backend: Sync + Send { +pub trait Backend: Sized + Sync + Send { #[allow(unused_variables)] - fn startup(&self, turbo_tasks: &dyn TurboTasksBackendApi) {} + fn startup(&self, turbo_tasks: &TurboTasks) {} #[allow(unused_variables)] - fn stop(&self, turbo_tasks: &dyn TurboTasksBackendApi) {} + fn stop(&self, turbo_tasks: &TurboTasks) {} #[allow(unused_variables)] - fn stopping(&self, turbo_tasks: &dyn TurboTasksBackendApi) {} + fn stopping(&self, turbo_tasks: &TurboTasks) {} #[allow(unused_variables)] - fn idle_start(&self, turbo_tasks: &dyn TurboTasksBackendApi) {} + fn idle_start(&self, turbo_tasks: &TurboTasks) {} #[allow(unused_variables)] - fn idle_end(&self, turbo_tasks: &dyn TurboTasksBackendApi) {} + fn idle_end(&self, turbo_tasks: &TurboTasks) {} - fn invalidate_task(&self, task: TaskId, turbo_tasks: &dyn TurboTasksBackendApi); + fn invalidate_task(&self, task: TaskId, turbo_tasks: &TurboTasks); - fn invalidate_tasks(&self, tasks: &[TaskId], turbo_tasks: &dyn TurboTasksBackendApi); - fn invalidate_tasks_set(&self, tasks: &TaskIdSet, turbo_tasks: &dyn TurboTasksBackendApi); + fn invalidate_tasks(&self, tasks: &[TaskId], turbo_tasks: &TurboTasks); + fn invalidate_tasks_set(&self, tasks: &TaskIdSet, turbo_tasks: &TurboTasks); - fn invalidate_serialization( - &self, - _task: TaskId, - _turbo_tasks: &dyn TurboTasksBackendApi, - ) { - } + fn invalidate_serialization(&self, _task: TaskId, _turbo_tasks: &TurboTasks) {} fn try_start_task_execution<'a>( &'a self, task: TaskId, priority: TaskPriority, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> Option>; - fn task_execution_canceled(&self, task: TaskId, turbo_tasks: &dyn TurboTasksBackendApi); + fn task_execution_canceled(&self, task: TaskId, turbo_tasks: &TurboTasks); /// Called when a task's execution finishes. /// @@ -641,7 +635,7 @@ pub trait Backend: Sync + Send { cell_counters: &AutoMap, 8>, #[cfg(feature = "verify_determinism")] stateful: bool, has_invalidator: bool, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> Option; type BackendJob: Send + 'static; @@ -649,7 +643,7 @@ pub trait Backend: Sync + Send { fn run_backend_job<'a>( &'a self, job: Self::BackendJob, - turbo_tasks: &'a dyn TurboTasksBackendApi, + turbo_tasks: &'a TurboTasks, ) -> Pin + Send + 'a>>; /// INVALIDATION: Be careful with this, when reader is None, it will not track dependencies, so @@ -659,7 +653,7 @@ pub trait Backend: Sync + Send { task: TaskId, reader: Option, options: ReadOutputOptions, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> Result>; /// INVALIDATION: Be careful with this, when reader is None, it will not track dependencies, so @@ -670,7 +664,7 @@ pub trait Backend: Sync + Send { index: CellId, reader: Option, options: ReadCellOptions, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> Result>; /// INVALIDATION: Be careful with this, it will not track dependencies, so @@ -679,7 +673,7 @@ pub trait Backend: Sync + Send { &self, current_task: TaskId, index: CellId, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> Result; /// INVALIDATION: Be careful with this, when reader is None, it will not track dependencies, so @@ -689,7 +683,7 @@ pub trait Backend: Sync + Send { task: TaskId, trait_id: TraitTypeId, reader: Option, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> TaskCollectiblesMap; fn emit_collectible( @@ -697,7 +691,7 @@ pub trait Backend: Sync + Send { trait_type: TraitTypeId, collectible: RawVc, task: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ); fn unemit_collectible( @@ -706,7 +700,7 @@ pub trait Backend: Sync + Send { collectible: RawVc, count: u32, task: TaskId, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ); fn update_task_cell( @@ -717,7 +711,7 @@ pub trait Backend: Sync + Send { updated_key_hashes: Option>, content_hash: Option, verification_mode: VerificationMode, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ); fn get_or_create_task( @@ -727,31 +721,27 @@ pub trait Backend: Sync + Send { arg: &mut dyn StackDynTaskInputs, parent_task: Option, persistence: TaskPersistence, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> TaskId; fn connect_task( &self, task: TaskId, parent_task: Option, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ); - fn mark_own_task_as_finished( - &self, - _task: TaskId, - _turbo_tasks: &dyn TurboTasksBackendApi, - ) { + fn mark_own_task_as_finished(&self, _task: TaskId, _turbo_tasks: &TurboTasks) { // Do nothing by default } fn create_transient_task( &self, task_type: TransientTaskType, - turbo_tasks: &dyn TurboTasksBackendApi, + turbo_tasks: &TurboTasks, ) -> TaskId; - fn dispose_root_task(&self, task: TaskId, turbo_tasks: &dyn TurboTasksBackendApi); + fn dispose_root_task(&self, task: TaskId, turbo_tasks: &TurboTasks); fn task_statistics(&self) -> &TaskStatisticsApi; @@ -759,7 +749,7 @@ pub trait Backend: Sync + Send { /// Returns a human-readable name for the given task. Used by error display formatting /// to lazily resolve task names instead of storing them eagerly in error objects. - fn get_task_name(&self, task: TaskId, turbo_tasks: &dyn TurboTasksBackendApi) -> String; + fn get_task_name(&self, task: TaskId, turbo_tasks: &TurboTasks) -> String; } #[cfg(test)] diff --git a/turbopack/crates/turbo-tasks/src/lib.rs b/turbopack/crates/turbo-tasks/src/lib.rs index 4c5a4bee6a6d..592d1c9ed276 100644 --- a/turbopack/crates/turbo-tasks/src/lib.rs +++ b/turbopack/crates/turbo-tasks/src/lib.rs @@ -43,7 +43,6 @@ pub mod panic_hooks; pub mod parallel; pub mod primitives; mod priority_runner; -mod raw_vc; mod read_options; mod read_ref; pub mod registry; @@ -96,15 +95,14 @@ pub use crate::{ join_iter_ext::{JoinIterExt, TryFlatJoinIterExt, TryJoinIterExt}, manager::{ CurrentCellRef, ReadCellTracking, ReadConsistency, ReadTracking, TaskPersistence, - TaskPriority, TurboTasks, TurboTasksApi, TurboTasksBackendApi, TurboTasksCallApi, Unused, - UpdateInfo, dynamic_call, emit, get_serialization_invalidator, mark_finished, - mark_stateful, mark_top_level_task, prevent_gc, run, run_once, run_once_with_reason, - trait_call, turbo_tasks, turbo_tasks_scope, turbo_tasks_weak, + TaskPriority, TurboTasks, TurboTasksApi, TurboTasksCallApi, Unused, UpdateInfo, + dynamic_call, emit, get_serialization_invalidator, mark_finished, mark_stateful, + mark_top_level_task, prevent_gc, run, run_once, run_once_with_reason, trait_call, + turbo_tasks, turbo_tasks_scope, turbo_tasks_weak, unmark_top_level_task_may_leak_eventually_consistent_state, with_turbo_tasks, }, mapped_read_ref::MappedReadRef, output::OutputContent, - raw_vc::{CellId, RawVc, ReadRawVcFuture, ResolveRawVcFuture}, read_options::{ReadCellOptions, ReadOutputOptions}, read_ref::ReadRef, serialization_invalidation::SerializationInvalidator, @@ -120,11 +118,12 @@ pub use crate::{ value::{TransientInstance, TransientValue}, value_type::{Evictability, TraitMethod, TraitType, ValueType, ValueTypePersistence}, vc::{ - Dynamic, NonLocalValue, OperationValue, OperationVc, OptionVcExt, ReadVcFuture, - ResolveOperationVcFuture, ResolveVcFuture, ResolvedVc, ToResolvedVcFuture, Upcast, - UpcastStrict, ValueDefault, Vc, VcCast, VcCellCompareMode, VcCellHashedCompareMode, - VcCellKeyedCompareMode, VcCellNewMode, VcDefaultRead, VcRead, VcTransparentRead, - VcValueTrait, VcValueTraitCast, VcValueType, VcValueTypeCast, + CellId, Dynamic, NonLocalValue, OperationValue, OperationVc, OptionVcExt, RawVc, + ReadRawVcFuture, ReadVcFuture, ResolveOperationVcFuture, ResolveRawVcFuture, + ResolveVcFuture, ResolvedVc, ToResolvedVcFuture, Upcast, UpcastStrict, ValueDefault, Vc, + VcCast, VcCellCompareMode, VcCellHashedCompareMode, VcCellKeyedCompareMode, VcCellNewMode, + VcDefaultRead, VcRead, VcTransparentRead, VcValueTrait, VcValueTraitCast, VcValueType, + VcValueTypeCast, }, }; diff --git a/turbopack/crates/turbo-tasks/src/local_task_tracker.rs b/turbopack/crates/turbo-tasks/src/local_task_tracker.rs index b9fe571b74b8..5380c6cdf83a 100644 --- a/turbopack/crates/turbo-tasks/src/local_task_tracker.rs +++ b/turbopack/crates/turbo-tasks/src/local_task_tracker.rs @@ -119,7 +119,7 @@ mod tests { use super::*; fn dummy_output() -> OutputContent { - OutputContent::Link(crate::raw_vc::RawVc::TaskOutput( + OutputContent::Link(crate::RawVc::TaskOutput( crate::TaskId::try_from(1).unwrap(), )) } diff --git a/turbopack/crates/turbo-tasks/src/manager.rs b/turbopack/crates/turbo-tasks/src/manager.rs index 58e6b3a3edf6..153fa1c278b6 100644 --- a/turbopack/crates/turbo-tasks/src/manager.rs +++ b/turbopack/crates/turbo-tasks/src/manager.rs @@ -27,9 +27,9 @@ use tracing::{Instrument, Span, instrument}; use turbo_tasks_hash::{DeterministicHash, hash_xxh3_hash128}; use crate::{ - Completion, InvalidationReason, InvalidationReasonSet, OutputContent, ReadCellOptions, - ReadOutputOptions, ResolvedVc, SharedReference, TaskId, TraitMethod, ValueTypeId, Vc, VcRead, - VcValueTrait, VcValueType, + CellId, Completion, InvalidationReason, InvalidationReasonSet, OutputContent, RawVc, + ReadCellOptions, ReadOutputOptions, ResolvedVc, SharedReference, TaskId, TraitMethod, + ValueTypeId, Vc, VcRead, VcValueTrait, VcValueType, backend::{ Backend, CellContent, CellHash, TaskCollectiblesMap, TaskExecutionSpec, TransientTaskType, TurboTasksExecutionError, TypedCellContent, VerificationMode, @@ -37,14 +37,12 @@ use crate::{ capture_future::CaptureFuture, dyn_task_inputs::StackDynTaskInputs, event::{Event, EventListener}, - id::{ExecutionId, LocalTaskId, TRANSIENT_TASK_BIT, TraitTypeId}, - id_factory::IdFactoryWithReuse, + id::{ExecutionId, LocalTaskId, TraitTypeId}, keyed::KeyedEq, local_task_tracker::LocalTaskTracker, macro_helpers::NativeFunction, message_queue::{CompilationEvent, CompilationEventQueue}, priority_runner::{Executor, PriorityRunner}, - raw_vc::{CellId, RawVc}, registry, serialization_invalidation::SerializationInvalidator, task::local_task::{LocalTask, LocalTaskSpec, LocalTaskType}, @@ -53,8 +51,8 @@ use crate::{ util::{IdFactory, StaticOrArc}, }; -/// Common base trait for [`TurboTasksApi`] and [`TurboTasksBackendApi`]. Provides APIs for creating -/// tasks from function calls. +/// Common base trait for [`TurboTasksApi`] and [`TurboTasks`]. Provides APIs for creating tasks +/// from function calls. pub trait TurboTasksCallApi: Sync + Send { /// Calls a native function with arguments. Resolves arguments when needed /// with a wrapper task. @@ -227,46 +225,6 @@ impl Unused { } } -/// A subset of the [`TurboTasks`] API that's exposed to [`Backend`] implementations. -pub trait TurboTasksBackendApi: TurboTasksCallApi + Sync + Send { - fn pin(&self) -> Arc>; - - fn get_fresh_persistent_task_id(&self) -> Unused; - fn get_fresh_transient_task_id(&self) -> Unused; - /// # Safety - /// - /// The caller must ensure that the task id is not used anymore. - unsafe fn reuse_persistent_task_id(&self, id: Unused); - /// # Safety - /// - /// The caller must ensure that the task id is not used anymore. - unsafe fn reuse_transient_task_id(&self, id: Unused); - - /// Schedule a task for execution. - fn schedule(&self, task: TaskId, priority: TaskPriority); - - /// Returns the priority of the current task. - fn get_current_task_priority(&self) -> TaskPriority; - - /// Schedule a foreground backend job for execution. - fn schedule_backend_foreground_job(&self, job: B::BackendJob); - - /// Schedule a background backend job for execution. - /// - /// Background jobs are not counted towards activeness of the system. The system is considered - /// idle even with active background jobs. - fn schedule_backend_background_job(&self, job: B::BackendJob); - - /// Returns the duration from the start of the program to the given instant. - fn program_duration_until(&self, instant: Instant) -> Duration; - - /// Returns true if the system is idle. - fn is_idle(&self) -> bool; - - /// Returns a reference to the backend. - fn backend(&self) -> &B; -} - #[allow(clippy::manual_non_exhaustive)] pub struct UpdateInfo { pub duration: Duration, @@ -478,8 +436,6 @@ enum ScheduledTask { pub struct TurboTasks { this: Weak, backend: B, - task_id_factory: IdFactoryWithReuse, - transient_task_id_factory: IdFactoryWithReuse, execution_id_factory: IdFactory, stopped: AtomicBool, currently_scheduled_foreground_jobs: AtomicUsize, @@ -496,7 +452,6 @@ pub struct TurboTasks { event_foreground_done: Event, /// Event that is triggered when all background jobs are done event_background_done: Event, - program_start: Instant, compilation_events: CompilationEventQueue, } @@ -605,18 +560,10 @@ impl TurboTasks { // so we probably want to make sure that all tasks are joined // when trying to drop turbo tasks pub fn new(backend: B) -> Arc { - let task_id_factory = IdFactoryWithReuse::new( - TaskId::MIN, - TaskId::try_from(TRANSIENT_TASK_BIT - 1).unwrap(), - ); - let transient_task_id_factory = - IdFactoryWithReuse::new(TaskId::try_from(TRANSIENT_TASK_BIT).unwrap(), TaskId::MAX); let execution_id_factory = IdFactory::new(ExecutionId::MIN, ExecutionId::MAX); let this = Arc::new_cyclic(|this| Self { this: this.clone(), backend, - task_id_factory, - transient_task_id_factory, execution_id_factory, stopped: AtomicBool::new(false), currently_scheduled_foreground_jobs: AtomicUsize::new(0), @@ -634,7 +581,6 @@ impl TurboTasks { event_background_done: Event::new(|| { || "TurboTasks::event_background_done".to_string() }), - program_start: Instant::now(), compilation_events: CompilationEventQueue::default(), }); this.backend.startup(&*this); @@ -835,7 +781,7 @@ impl TurboTasks { } #[track_caller] - pub(crate) fn schedule(&self, task_id: TaskId, priority: TaskPriority) { + pub fn schedule(&self, task_id: TaskId, priority: TaskPriority) { self.begin_foreground_job(); self.scheduled_tasks.fetch_add(1, Ordering::AcqRel); @@ -1095,26 +1041,6 @@ impl TurboTasks { .await; } - #[track_caller] - pub(crate) fn schedule_foreground_job(&self, func: T) - where - T: AsyncFnOnce(Arc>) -> Arc> + Send + 'static, - T::CallOnceFuture: Send, - { - let mut this = self.pin(); - this.begin_foreground_job(); - tokio::spawn( - TURBO_TASKS - .scope(this.clone(), async move { - if !this.stopped.load(Ordering::Acquire) { - this = func(this.clone()).await; - } - this.finish_foreground_job(); - }) - .in_current_span(), - ); - } - #[track_caller] pub(crate) fn schedule_background_job(&self, func: T) where @@ -1149,6 +1075,26 @@ impl TurboTasks { pub fn backend(&self) -> &B { &self.backend } + + pub fn get_current_task_priority(&self) -> TaskPriority { + CURRENT_TASK_STATE + .try_with(|task_state| task_state.read().unwrap().priority) + .unwrap_or(TaskPriority::initial()) + } + + pub fn is_idle(&self) -> bool { + self.currently_scheduled_foreground_jobs + .load(Ordering::Acquire) + == 0 + } + + #[track_caller] + pub fn schedule_backend_background_job(&self, job: B::BackendJob) { + self.schedule_background_job(async move |this| { + this.backend.run_backend_job(job, &*this).await; + this + }) + } } struct TurboTasksExecutor; @@ -1622,70 +1568,6 @@ impl TurboTasksApi for TurboTasks { } } -impl TurboTasksBackendApi for TurboTasks { - fn pin(&self) -> Arc> { - self.pin() - } - fn backend(&self) -> &B { - &self.backend - } - - #[track_caller] - fn schedule_backend_background_job(&self, job: B::BackendJob) { - self.schedule_background_job(async move |this| { - this.backend.run_backend_job(job, &*this).await; - this - }) - } - - #[track_caller] - fn schedule_backend_foreground_job(&self, job: B::BackendJob) { - self.schedule_foreground_job(async move |this| { - this.backend.run_backend_job(job, &*this).await; - this - }) - } - - #[track_caller] - fn schedule(&self, task: TaskId, priority: TaskPriority) { - self.schedule(task, priority) - } - - fn get_current_task_priority(&self) -> TaskPriority { - CURRENT_TASK_STATE - .try_with(|task_state| task_state.read().unwrap().priority) - .unwrap_or(TaskPriority::initial()) - } - - fn program_duration_until(&self, instant: Instant) -> Duration { - instant - self.program_start - } - - fn get_fresh_persistent_task_id(&self) -> Unused { - // SAFETY: This is a fresh id from the factory - unsafe { Unused::new_unchecked(self.task_id_factory.get()) } - } - - fn get_fresh_transient_task_id(&self) -> Unused { - // SAFETY: This is a fresh id from the factory - unsafe { Unused::new_unchecked(self.transient_task_id_factory.get()) } - } - - unsafe fn reuse_persistent_task_id(&self, id: Unused) { - unsafe { self.task_id_factory.reuse(id.into()) } - } - - unsafe fn reuse_transient_task_id(&self, id: Unused) { - unsafe { self.transient_task_id_factory.reuse(id.into()) } - } - - fn is_idle(&self) -> bool { - self.currently_scheduled_foreground_jobs - .load(Ordering::Acquire) - == 0 - } -} - async fn wait_for_local_tasks() { let listener = CURRENT_TASK_STATE.with(|ts| ts.read().unwrap().local_tasks.listen_for_in_flight()); diff --git a/turbopack/crates/turbo-tasks/src/task/local_task.rs b/turbopack/crates/turbo-tasks/src/task/local_task.rs index 8aab78684890..0e58472f0862 100644 --- a/turbopack/crates/turbo-tasks/src/task/local_task.rs +++ b/turbopack/crates/turbo-tasks/src/task/local_task.rs @@ -4,7 +4,7 @@ use anyhow::{Result, bail}; use crate::{ CellId, DynTaskInputs, OutputContent, OwnedStackDynTaskInputs, RawVc, TaskPersistence, - TraitMethod, TurboTasksBackendApi, ValueTypeId, backend::Backend, event::Event, + TraitMethod, TurboTasks, ValueTypeId, backend::Backend, event::Event, macro_helpers::NativeFunction, registry, }; @@ -55,7 +55,7 @@ impl LocalTaskType { mut this: Option, arg: &dyn DynTaskInputs, persistence: TaskPersistence, - turbo_tasks: Arc>, + turbo_tasks: Arc>, ) -> Result { if let Some(this) = this.as_mut() { *this = this.resolve().await?; @@ -70,7 +70,7 @@ impl LocalTaskType { this: RawVc, arg: &dyn DynTaskInputs, persistence: TaskPersistence, - turbo_tasks: Arc>, + turbo_tasks: Arc>, ) -> Result { let this = this.resolve().await?; let RawVc::TaskCell(_, CellId { type_id, .. }) = this else { diff --git a/turbopack/crates/turbo-tasks/src/vc/mod.rs b/turbopack/crates/turbo-tasks/src/vc/mod.rs index 4b2d6ecb522f..0f708f8b0898 100644 --- a/turbopack/crates/turbo-tasks/src/vc/mod.rs +++ b/turbopack/crates/turbo-tasks/src/vc/mod.rs @@ -3,6 +3,7 @@ mod cell_mode; pub(crate) mod default; mod local; pub(crate) mod operation; +mod raw; mod read; pub(crate) mod resolved; mod traits; @@ -32,6 +33,7 @@ pub use self::{ default::ValueDefault, local::NonLocalValue, operation::{OperationValue, OperationVc, ResolveOperationVcFuture}, + raw::{CellId, RawVc, ReadRawVcFuture, ResolveRawVcFuture}, read::{ReadOwnedVcFuture, ReadVcFuture, VcDefaultRead, VcRead, VcTransparentRead}, resolved::ResolvedVc, traits::{Dynamic, Upcast, UpcastStrict, VcValueTrait, VcValueType}, @@ -39,7 +41,6 @@ pub use self::{ #[cfg(debug_assertions)] use crate::debug::{ValueDebug, ValueDebugFormat, ValueDebugFormatString}; use crate::{ - CellId, RawVc, ResolveRawVcFuture, keyed::{KeyedAccess, KeyedEq}, registry, trace::{TraceRawVcs, TraceRawVcsContext}, diff --git a/turbopack/crates/turbo-tasks/src/raw_vc.rs b/turbopack/crates/turbo-tasks/src/vc/raw.rs similarity index 100% rename from turbopack/crates/turbo-tasks/src/raw_vc.rs rename to turbopack/crates/turbo-tasks/src/vc/raw.rs diff --git a/turbopack/crates/turbopack-ecmascript/src/analyzer/side_effects.rs b/turbopack/crates/turbopack-ecmascript/src/analyzer/side_effects.rs index 74af9fd40d62..95c3e5929b08 100644 --- a/turbopack/crates/turbopack-ecmascript/src/analyzer/side_effects.rs +++ b/turbopack/crates/turbopack-ecmascript/src/analyzer/side_effects.rs @@ -267,13 +267,161 @@ static KNOWN_PURE_REGEXP_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! { "test", "exec", }; +/// True if `prop` is the non-computed member property `.`. +fn prop_is(prop: &MemberProp, name: &str) -> bool { + matches!(prop, MemberProp::Ident(i) if i.sym.as_ref() == name) +} + +/// True if `identifier` is the named, unshadowed module-scope (unresolved) +/// binding — e.g. the real CommonJS `module`/`exports`/`require`, not a local +/// shadow. Prevents `let exports = {}; exports.foo = 'a'` from being treated as +/// a write to the global `exports`. +fn is_global(identifier: &Ident, name: &str, unresolved_mark: Mark) -> bool { + identifier.ctxt.outer() == unresolved_mark && identifier.sym.as_ref() == name +} + +/// A freshly-allocated object/array literal (evaluates to a brand-new value). +fn is_object_or_array_literal(expr: &Expr) -> bool { + matches!(unparen(expr), Expr::Object(_) | Expr::Array(_)) +} + +/// Whether `expr`'s object graph contains a getter or setter. An accessor makes +/// member access (read *or* write) potentially effectful — e.g. `o.foo = 1` +/// invokes a `set foo` — so a value carrying one can't be attached to the +/// exports object and then mutated as if it were plain data. Function/method +/// bodies are not descended into: an accessor declared inside a nested function +/// isn't part of this value's own shape. +fn contains_getters_or_setters(expr: &Expr) -> bool { + match unparen(expr) { + Expr::Object(obj) => obj.props.iter().any(|prop| match prop { + PropOrSpread::Prop(prop) => match &**prop { + Prop::Getter(_) | Prop::Setter(_) => true, + Prop::KeyValue(kv) => contains_getters_or_setters(&kv.value), + Prop::Method(_) | Prop::Shorthand(_) | Prop::Assign(_) => false, + }, + PropOrSpread::Spread(spread) => contains_getters_or_setters(&spread.expr), + }), + Expr::Array(arr) => arr + .elems + .iter() + .flatten() + .any(|elem| contains_getters_or_setters(&elem.expr)), + _ => false, + } +} + +/// `module.exports` (the real, unshadowed `module` binding). +fn is_module_dot_exports(member: &MemberExpr, unresolved_mark: Mark) -> bool { + matches!(unparen(&member.obj), Expr::Ident(o) if is_global(o, "module", unresolved_mark)) + && prop_is(&member.prop, "exports") +} + +/// `module.exports` or a property chain rooted at it (`module.exports.a.b`). +fn is_module_exports_chain(expr: &Expr, unresolved_mark: Mark) -> bool { + match unparen(expr) { + Expr::Member(m) => { + is_module_dot_exports(m, unresolved_mark) + || is_module_exports_chain(&m.obj, unresolved_mark) + } + _ => false, + } +} + +/// Whether `member` writes the module's own CommonJS exports: `exports.`, +/// `module.exports`, or `module.exports.`. +fn is_cjs_export_member(member: &MemberExpr, unresolved_mark: Mark) -> bool { + match unparen(&member.obj) { + // `exports.`, or `module.exports` + Expr::Ident(obj) => { + is_global(obj, "exports", unresolved_mark) + || is_module_dot_exports(member, unresolved_mark) + } + // `module.exports.` + Expr::Member(inner) => is_cjs_export_member(inner, unresolved_mark), + _ => false, + } +} + +/// Calls `f` for every assignment that executes during module evaluation. +/// +/// This descends through all expressions — conditionals, logical/binary +/// operators, sequences, assignment chains, call arguments, etc. — so an +/// assignment hidden in `cond && (module.exports = …)` or `a ? (b = …) : c` is +/// still seen. It does *not* descend into function/method bodies: those don't +/// run at module-evaluation time (calling such a function would itself be a side +/// effect), so assignments inside them are irrelevant here. +fn for_each_top_level_assign(program: &Program, f: impl FnMut(&AssignExpr)) { + struct Collector { + f: F, + } + impl Visit for Collector { + noop_visit_type!(); + fn visit_assign_expr(&mut self, n: &AssignExpr) { + (self.f)(n); + n.visit_children_with(self); + } + // Function/method bodies do not execute during module evaluation. + fn visit_function(&mut self, _: &Function) {} + fn visit_arrow_expr(&mut self, _: &ArrowExpr) {} + fn visit_constructor(&mut self, _: &Constructor) {} + } + program.visit_with(&mut Collector { f }); +} + +/// Whether `module.exports` is ever reassigned to a value that isn't safe. +/// +/// A reassignment to an alias (`module.exports = require('./x')`, +/// `module.exports = other`) would make later changes to properties on +/// `module.exports` have side effects. +fn module_exports_is_tainted(program: &Program, unresolved_mark: Mark) -> bool { + let mut tainted = false; + for_each_top_level_assign(program, |assign| { + if assign.op == AssignOp::Assign + && let AssignTarget::Simple(SimpleAssignTarget::Member(member)) = &assign.left + && is_module_dot_exports(member, unresolved_mark) + && !is_object_or_array_literal(&assign.right) + { + tainted = true; + } + }); + tainted +} + +/// Whether any value assigned to the module's CommonJS exports carries a getter +/// or setter. +/// +/// Attaching an accessor to the exports object makes later member access (read or +/// write) potentially effectful — a subsequent `module.exports.foo = 1` could +/// invoke a `set foo` — so once one is present, writes to the exports can no +/// longer be treated as plain data assignments. +fn module_exports_has_accessor(program: &Program, unresolved_mark: Mark) -> bool { + let mut found = false; + for_each_top_level_assign(program, |assign| { + if assign.op == AssignOp::Assign + && let AssignTarget::Simple(SimpleAssignTarget::Member(member)) = &assign.left + && is_cjs_export_member(member, unresolved_mark) + && contains_getters_or_setters(&assign.right) + { + found = true; + } + }); + found +} + /// Analyzes a program to determine if it contains side effects at the top level. pub fn compute_module_evaluation_side_effects( program: &Program, comments: &dyn Comments, unresolved_mark: Mark, ) -> ModuleSideEffects { - let mut visitor = SideEffectVisitor::new(comments, unresolved_mark); + let module_exports_tainted = module_exports_is_tainted(program, unresolved_mark); + let module_exports_has_accessor = module_exports_has_accessor(program, unresolved_mark); + let mut visitor = SideEffectVisitor::new( + comments, + unresolved_mark, + module_exports_tainted, + module_exports_has_accessor, + ); program.visit_with(&mut visitor); if visitor.has_side_effects { ModuleSideEffects::SideEffectful @@ -287,16 +435,29 @@ pub fn compute_module_evaluation_side_effects( struct SideEffectVisitor<'a> { comments: &'a dyn Comments, unresolved_mark: Mark, + /// Whether `module.exports` was reassigned to a non-safe value, making member + /// writes to `module.exports.*` potentially observable. + module_exports_tainted: bool, + /// Whether a getter or setter is attached to the exports object, making any + /// write to the CommonJS exports potentially observable. + module_exports_has_accessor: bool, has_side_effects: bool, will_invoke_fn_exprs: bool, has_imports: bool, } impl<'a> SideEffectVisitor<'a> { - fn new(comments: &'a dyn Comments, unresolved_mark: Mark) -> Self { + fn new( + comments: &'a dyn Comments, + unresolved_mark: Mark, + module_exports_tainted: bool, + module_exports_has_accessor: bool, + ) -> Self { Self { comments, unresolved_mark, + module_exports_tainted, + module_exports_has_accessor, has_side_effects: false, will_invoke_fn_exprs: false, has_imports: false, @@ -345,7 +506,7 @@ impl<'a> SideEffectVisitor<'a> { Callee::Expr(expr) => { let expr = unparen(expr); if let Expr::Ident(ident) = expr { - ident.ctxt.outer() == self.unresolved_mark && ident.sym.as_ref() == "require" + is_global(ident, "require", self.unresolved_mark) } else { false } @@ -355,6 +516,32 @@ impl<'a> SideEffectVisitor<'a> { _ => false, } } + + /// Returns true if `target` writes to the module's own CommonJS exports + /// (`exports.x`, `module.exports`, or `module.exports.x`) in a way that is + /// the CJS equivalent of an ESM `export`, rather than a side effect. + fn is_safe_cjs_export_target(&self, target: &AssignTarget) -> bool { + let AssignTarget::Simple(SimpleAssignTarget::Member(member)) = target else { + return false; + }; + if !is_cjs_export_member(member, self.unresolved_mark) { + return false; + } + // If a getter/setter is attached to the exports object, any write to its + // members could invoke an accessor, so conservatively none are safe. + if self.module_exports_has_accessor { + return false; + } + // If `module.exports` was reassigned to a non-safe value, writing its + // members may invoke a setter or mutate another module's object, so it + // is not safe even though it targets the CJS exports. + if self.module_exports_tainted && is_module_exports_chain(&member.obj, self.unresolved_mark) + { + return false; + } + true + } + /// Check if an expression is a known pure built-in function. /// /// This checks for: @@ -735,10 +922,23 @@ impl<'a> Visit for SideEffectVisitor<'a> { self.mark_side_effect(); } } - Expr::Assign(_) => { - // Assignments have side effects - // TODO: allow assignments to module level variables - self.mark_side_effect(); + Expr::Assign(assign) => { + // Assigning to the module's own CommonJS exports (`exports.x`, + // `module.exports`, `module.exports.x`) is the CJS equivalent of an + // ESM `export` declaration. + // + // If a getter/setter is attached to the exports object (detected + // by the `module_exports_has_accessor` pass), `is_safe_cjs_export_target` + // conservatively rejects every write to the exports, since a + // member write could invoke the accessor. + if assign.op == AssignOp::Assign && self.is_safe_cjs_export_target(&assign.left) { + // Still check the assigned value, and the target's computed + // property keys (e.g. `exports[sideEffect()] = …`). + assign.left.visit_with(self); + assign.right.visit_with(self); + } else { + self.mark_side_effect(); + } } Expr::Update(_) => { // Updates (++, --) have side effects @@ -2372,8 +2572,119 @@ mod tests { mod common_js_modules_tests { use super::*; - side_effects!(test_common_js_exports, "exports.foo = 'a'"); - side_effects!(test_common_js_exports_module, "module.exports.foo = 'a'"); - side_effects!(test_common_js_exports_assignment, "module.exports = {}"); + // Writing the module's own CommonJS exports with a pure value is the CJS + // equivalent of an ESM `export` and is not a module-evaluation side effect. + no_side_effects!(test_common_js_exports, "exports.foo = 'a'"); + no_side_effects!(test_common_js_exports_module, "module.exports.foo = 'a'"); + no_side_effects!(test_common_js_exports_assignment, "module.exports = {}"); + no_side_effects!( + test_common_js_function_exports, + "exports.foo = function () { return 1; }; exports.bar = 2;" + ); + + module_evaluation_is_side_effect_free!( + test_common_js_reexport, + "module.exports = require('./other');" + ); + module_evaluation_is_side_effect_free!( + test_common_js_named_reexports, + "exports.a = require('./a'); exports.b = require('./b');" + ); + + // a side effect in a computed value + side_effects!( + test_common_js_export_impure_value, + "exports.foo = sideEffect();" + ); + // a side effect in a computed export key, + side_effects!( + test_common_js_export_computed_side_effect, + "exports[sideEffect()] = 'a';" + ); + // writing a non-`exports` property of `module`, + side_effects!(test_module_non_export_assignment, "module.foo = 'a';"); + // and a locally-shadowed `exports`. + side_effects!( + test_shadowed_exports_assignment, + "let exports = {}; exports.foo = 'a';" + ); + + // A getter/setter attached to the exports object makes member writes + // potentially invoke an accessor, so it is conservatively flagged as a + // side effect as soon as the accessor is attached. + side_effects!( + test_cjs_export_setter_invoked, + "module.exports = { set foo(v) { sideEffect() } }; module.exports.foo = 1;" + ); + + // Reassigning `module.exports` to a fresh literal keeps later member + // writes pure (the common incremental-exports pattern). + no_side_effects!( + test_cjs_export_fresh_then_write, + "module.exports = {}; module.exports.foo = 1;" + ); + // But reassigning it to an alias (a re-export, or any non-literal) taints + // it: a later `module.exports.*` write may mutate that other object, so + // it is a side effect. + side_effects!( + test_cjs_export_reexport_then_write, + "module.exports = require('./other'); module.exports.extra = 1;" + ); + side_effects!( + test_cjs_export_alias_then_write, + "module.exports = other; module.exports.foo = 1;" + ); + // The reassignment is also detected when hidden inside a top-level + // comma-sequence expression rather than a standalone statement. + side_effects!( + test_cjs_export_reexport_then_write_sequence, + "module.exports = require('./other'), module.exports.extra = 1;" + ); + // …and when nested in a conditional/logical expression that still runs at + // module evaluation (the scan descends every evaluated expression, just + // not function bodies). + side_effects!( + test_cjs_export_reexport_in_logical_then_write, + "x && (module.exports = require('./other')); module.exports.extra = 1;" + ); + side_effects!( + test_cjs_export_setter_attached_in_conditional, + "x ? (module.exports = { set foo(v) { sideEffect() } }) : 0;" + ); + // A reassignment inside a function body does not run during module + // evaluation, so it is not a taint on its own. + no_side_effects!( + test_cjs_export_reassign_in_function_body_is_pure, + "function f() { module.exports = require('./other'); } module.exports.foo = 1;" + ); + + // A class `static` block executes at module evaluation (when the class + // definition is evaluated), so an accessor attached to the exports object + // inside one is detected — mirroring the "top level" assignment in: + // class C { static { foo = bar; } } + side_effects!( + test_cjs_export_setter_in_static_block, + "class C { static { module.exports = { set foo(v) { sideEffect() } }; \ + module.exports.foo = 1; } }" + ); + // …but a constructor body only runs when the class is instantiated, not at + // module evaluation, so the same attachment there is *not* a top-level + // assignment and is not detected — mirroring the "not top level" + // assignment in: + // class C { constructor() { baz = quux; } } + no_side_effects!( + test_cjs_export_setter_in_constructor_is_pure, + "class C { constructor() { module.exports = { set foo(v) { sideEffect() } }; } } \ + module.exports.foo = 1;" + ); + + // A `static` property initializer also runs at module evaluation (when the + // class definition is evaluated), so an accessor attached to the exports + // object inside one is detected. + side_effects!( + test_cjs_export_setter_in_static_property, + "class C { static x = (module.exports = { set foo(v) { sideEffect() } }); } \ + module.exports.foo = 1;" + ); } } diff --git a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs index e6085bda4888..a2335d8d8220 100644 --- a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs @@ -3541,7 +3541,10 @@ async fn value_visitor_inner( JsValue::WellKnownFunction( WellKnownFunctionKind::PathJoin | WellKnownFunctionKind::PathResolve(_) - | WellKnownFunctionKind::FsReadMethod(_), + | WellKnownFunctionKind::FsReadMethod(_) + | WellKnownFunctionKind::FsReadDir + | WellKnownFunctionKind::ChildProcessSpawnMethod(_) + | WellKnownFunctionKind::ChildProcessFork, ) => { if ignore { return Ok(( diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/0_9x_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_1j3m-pr.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/0_9x_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_0nlrkdv.js.map similarity index 100% rename from turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/0_9x_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_1j3m-pr.js.map rename to turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/0_9x_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_0nlrkdv.js.map diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/0rv8_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_1j3m-pr.js b/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/0rv8_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_0nlrkdv.js similarity index 77% rename from turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/0rv8_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_1j3m-pr.js rename to turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/0rv8_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_0nlrkdv.js index b849a8714b65..f64366809d5d 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/0rv8_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_1j3m-pr.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/0rv8_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_0nlrkdv.js @@ -1,5 +1,5 @@ (globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push([ - "output/0rv8_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_1j3m-pr.js", - {"otherChunks":["output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_0b1pxfd._.js"],"runtimeModuleIds":["[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/index.js [test] (ecmascript)"]} + "output/0rv8_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_index_0nlrkdv.js", + {"otherChunks":["output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_1tq4mpj._.js"],"runtimeModuleIds":["[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/index.js [test] (ecmascript)"]} ]); // Dummy runtime \ No newline at end of file diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_0b1pxfd._.js b/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_1tq4mpj._.js similarity index 85% rename from turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_0b1pxfd._.js rename to turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_1tq4mpj._.js index ab4a0fb57e28..c0a9b5a47c29 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_0b1pxfd._.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_1tq4mpj._.js @@ -1,8 +1,4 @@ -(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push(["output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_0b1pxfd._.js", -"[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/jsx-runtime.js [test] (ecmascript)", ((__turbopack_context__, module, exports) => { - -module.exports = {}; -}), +(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push(["output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_1tq4mpj._.js", "[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/entry-base.js [test] (ecmascript)", ((__turbopack_context__) => { "use strict"; @@ -14,7 +10,6 @@ __turbopack_context__.s([], "[project]/turbopack/crates/turbopack-tests/tests/sn ; // MERGED MODULE: [project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/collect-segment-data.js [test] (ecmascript) ; -var __TURBOPACK__imported__module__$5b$project$5d2f$turbopack$2f$crates$2f$turbopack$2d$tests$2f$tests$2f$snapshot$2f$source_maps$2f$merged$2d$unicode$2f$input$2f$jsx$2d$runtime$2e$js__$5b$test$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/jsx-runtime.js [test] (ecmascript)"); ; // eslint-disable-next-line import/no-extraneous-dependencies const createFromReadableStream = 123; //import { createFromReadableStream } from 'react-server-dom-webpack/client.edge'; @@ -63,4 +58,4 @@ if (Date.now() > 0) { }), ]); -//# sourceMappingURL=1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_0b1pxfd._.js.map \ No newline at end of file +//# sourceMappingURL=1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_1tq4mpj._.js.map \ No newline at end of file diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_0b1pxfd._.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_1tq4mpj._.js.map similarity index 76% rename from turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_0b1pxfd._.js.map rename to turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_1tq4mpj._.js.map index 686d92189782..b719ef6f5a9d 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_0b1pxfd._.js.map +++ b/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/output/1do3_crates_turbopack-tests_tests_snapshot_source_maps_merged-unicode_input_1tq4mpj._.js.map @@ -2,9 +2,8 @@ "version": 3, "sources": [], "sections": [ - {"offset": {"line": 3, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/jsx-runtime.js"],"sourcesContent":["module.exports = {};\n"],"names":["module","exports"],"mappings":"AAAAA,OAAOC,OAAO,GAAG,CAAC"}}, - {"offset": {"line": 8, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/collect-segment-data.js"],"sourcesContent":["import { jsx as _jsx } from \"./jsx-runtime\";\n// eslint-disable-next-line import/no-extraneous-dependencies\nconst createFromReadableStream = 123; //import { createFromReadableStream } from 'react-server-dom-webpack/client.edge';\n// eslint-disable-next-line import/no-extraneous-dependencies\nconst prerender = 123; // import { prerender } from 'react-server-dom-webpack/static.edge';\nconst streamFromBuffer=1,streamToBuffer=1; // import { streamFromBuffer, streamToBuffer } from '../stream-utils/node-web-streams-helper';\nconst waitAtLeastOneReactRenderTask=1; //import { waitAtLeastOneReactRenderTask } from '../../lib/scheduler';\n// import './segment-value-encoding';\n"],"names":[],"mappings":";;;;;;;;AAAA;;AACA,6DAA6D;AAC7D,MAAM,2BAA2B,KAAK,kFAAkF;AACxH,6DAA6D;AAC7D,MAAM,YAAY,KAAK,oEAAoE;AAC3F,MAAM,mBAAiB,GAAE,iBAAe,GAAG,8FAA8F;AACzI,MAAM,gCAA8B,GAAG,sEAAsE;CAC7G,sCAAsC"}}, - {"offset": {"line": 31, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/index1.js"],"sourcesContent":["import \"./entry-base.js\";\n"],"names":[],"mappings":";AAAA"}}, - {"offset": {"line": 38, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/index2.js"],"sourcesContent":["import \"./entry-base.js\";\n"],"names":[],"mappings":";AAAA"}}, - {"offset": {"line": 44, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/index.js"],"sourcesContent":["// \"entry-base.js [app-rsc] (ecmascript) \" with\n// [\"reflect-utils.js [app-rsc] (ecmascript)\",\n// \"params.js [app-rsc] (ecmascript)\",\n// \"segment-value-encoding.js [app-rsc] (ecmascript)\",\n// \"collect-segment-data.js [app-rsc] (ecmascript)\",\n// \"entry-base.js [app-rsc] (ecmascript) \"]\n\n// \"entry-base.js [test] (ecmascript) \" with\n// [\"reflect-utils.js [test] (ecmascript)\",\n// \"params.js [test] (ecmascript)\",\n// \"segment-value-encoding.js [test] (ecmascript)\",\n// \"collect-segment-data.js [test] (ecmascript)\",\n// \"entry-base.js [test] (ecmascript) \"]\n\nif (Date.now() > 0) {\n require('./index1.js')\n}\nif (Date.now() > 0) {\n require('./index2.js')\n}\n\n"],"names":["Date","now"],"mappings":"AAAA,kEAAkE;AAClE,8CAA8C;AAC9C,sCAAsC;AACtC,sDAAsD;AACtD,oDAAoD;AACpD,mDAAmD;AAEnD,+DAA+D;AAC/D,2CAA2C;AAC3C,mCAAmC;AACnC,mDAAmD;AACnD,kDAAkD;AAClD,iDAAiD;AAEjD,IAAIA,KAAKC,GAAG,KAAK,GAAG;;AAEpB;AACA,IAAID,KAAKC,GAAG,KAAK,GAAG;;AAEpB"}}] + {"offset": {"line": 4, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/collect-segment-data.js"],"sourcesContent":["import { jsx as _jsx } from \"./jsx-runtime\";\n// eslint-disable-next-line import/no-extraneous-dependencies\nconst createFromReadableStream = 123; //import { createFromReadableStream } from 'react-server-dom-webpack/client.edge';\n// eslint-disable-next-line import/no-extraneous-dependencies\nconst prerender = 123; // import { prerender } from 'react-server-dom-webpack/static.edge';\nconst streamFromBuffer=1,streamToBuffer=1; // import { streamFromBuffer, streamToBuffer } from '../stream-utils/node-web-streams-helper';\nconst waitAtLeastOneReactRenderTask=1; //import { waitAtLeastOneReactRenderTask } from '../../lib/scheduler';\n// import './segment-value-encoding';\n"],"names":[],"mappings":";;;;;;;;;AACA,6DAA6D;AAC7D,MAAM,2BAA2B,KAAK,kFAAkF;AACxH,6DAA6D;AAC7D,MAAM,YAAY,KAAK,oEAAoE;AAC3F,MAAM,mBAAiB,GAAE,iBAAe,GAAG,8FAA8F;AACzI,MAAM,gCAA8B,GAAG,sEAAsE;CAC7G,sCAAsC"}}, + {"offset": {"line": 26, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/index1.js"],"sourcesContent":["import \"./entry-base.js\";\n"],"names":[],"mappings":";AAAA"}}, + {"offset": {"line": 33, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/index2.js"],"sourcesContent":["import \"./entry-base.js\";\n"],"names":[],"mappings":";AAAA"}}, + {"offset": {"line": 39, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/source_maps/merged-unicode/input/index.js"],"sourcesContent":["// \"entry-base.js [app-rsc] (ecmascript) \" with\n// [\"reflect-utils.js [app-rsc] (ecmascript)\",\n// \"params.js [app-rsc] (ecmascript)\",\n// \"segment-value-encoding.js [app-rsc] (ecmascript)\",\n// \"collect-segment-data.js [app-rsc] (ecmascript)\",\n// \"entry-base.js [app-rsc] (ecmascript) \"]\n\n// \"entry-base.js [test] (ecmascript) \" with\n// [\"reflect-utils.js [test] (ecmascript)\",\n// \"params.js [test] (ecmascript)\",\n// \"segment-value-encoding.js [test] (ecmascript)\",\n// \"collect-segment-data.js [test] (ecmascript)\",\n// \"entry-base.js [test] (ecmascript) \"]\n\nif (Date.now() > 0) {\n require('./index1.js')\n}\nif (Date.now() > 0) {\n require('./index2.js')\n}\n\n"],"names":["Date","now"],"mappings":"AAAA,kEAAkE;AAClE,8CAA8C;AAC9C,sCAAsC;AACtC,sDAAsD;AACtD,oDAAoD;AACpD,mDAAmD;AAEnD,+DAA+D;AAC/D,2CAA2C;AAC3C,mCAAmC;AACnC,mDAAmD;AACnD,kDAAkD;AAClD,iDAAiD;AAEjD,IAAIA,KAAKC,GAAG,KAAK,GAAG;;AAEpB;AACA,IAAID,KAAKC,GAAG,KAAK,GAAG;;AAEpB"}}] } \ No newline at end of file