diff --git a/.changeset/README.md b/.changeset/README.md deleted file mode 100644 index e5b6d8d6a67ad..0000000000000 --- a/.changeset/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Changesets - -Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works -with multi-package repos, or single-package repos to help you version and publish your code. You can -find the full documentation for it [in our repository](https://github.com/changesets/changesets) - -We have a quick list of common questions to get you started engaging with this project in -[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json deleted file mode 100644 index f61ae85885617..0000000000000 --- a/.changeset/config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", - "changelog": ["@changesets/changelog-github", { "repo": "vercel/next.js" }], - "commit": false, - "fixed": [["next", "@next/swc"]], - "linked": [], - "access": "public", - "baseBranch": "canary", - "updateInternalDependencies": "patch", - "ignore": [] -} diff --git a/.changeset/dry-roses-nail.md b/.changeset/dry-roses-nail.md deleted file mode 100644 index 7b4444acac23d..0000000000000 --- a/.changeset/dry-roses-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"next": patch ---- - -Enable `ppr` when `cacheComponents` is enabled diff --git a/.changeset/empty-paths-check.md b/.changeset/empty-paths-check.md deleted file mode 100644 index 0074f64bdaab5..0000000000000 --- a/.changeset/empty-paths-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'next': patch ---- - -Add `regions` to the function config manifest file diff --git a/.changeset/giant-bushes-sink.md b/.changeset/giant-bushes-sink.md deleted file mode 100644 index 12493bb3b955e..0000000000000 --- a/.changeset/giant-bushes-sink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'next': patch ---- - -Resolved bug where hitting the parameterized path directly would cause a fallback shell generation instead of just rendering the route with the parameterized placeholders. diff --git a/.changeset/lemon-islands-care.md b/.changeset/lemon-islands-care.md deleted file mode 100644 index 2bd38175d71f5..0000000000000 --- a/.changeset/lemon-islands-care.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'next': patch ---- - -Fix the unexpected clearing of symbolic link directories diff --git a/.changeset/loose-cows-pump.md b/.changeset/loose-cows-pump.md deleted file mode 100644 index 94a3f73c91175..0000000000000 --- a/.changeset/loose-cows-pump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'next': patch ---- - -Fix dangling promise in unstable_cache diff --git a/.changeset/lovely-bulldogs-dress.md b/.changeset/lovely-bulldogs-dress.md deleted file mode 100644 index cf8a76baebdbd..0000000000000 --- a/.changeset/lovely-bulldogs-dress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"next": patch ---- - -Always pass implicit/soft tags into the `CacheHandler.get` method diff --git a/.changeset/open-dodos-admire.md b/.changeset/open-dodos-admire.md deleted file mode 100644 index d596f86d830ef..0000000000000 --- a/.changeset/open-dodos-admire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'next': patch ---- - -Fix to use https urls in meta data images when using --experimental-https flag diff --git a/.changeset/pretty-suns-watch.md b/.changeset/pretty-suns-watch.md deleted file mode 100644 index 87c9353fccf6b..0000000000000 --- a/.changeset/pretty-suns-watch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"next": patch ---- - -Do not cache fetches with 'no-store' in "use cache" during SSG diff --git a/.changeset/seven-seas-run.md b/.changeset/seven-seas-run.md deleted file mode 100644 index 844d3ea79dcb2..0000000000000 --- a/.changeset/seven-seas-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"next": patch ---- - -[TypeScript Plugin] Match method signature (`someFunc(): void`) type for client boundary warnings. diff --git a/.changeset/shaggy-owls-visit.md b/.changeset/shaggy-owls-visit.md deleted file mode 100644 index 753479a46fb75..0000000000000 --- a/.changeset/shaggy-owls-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'next': patch ---- - -Fixed rewrite params of the interception routes not being parsed correctly in certain deployed environments diff --git a/.changeset/shaggy-pears-tell.md b/.changeset/shaggy-pears-tell.md deleted file mode 100644 index 4cd6e97ad2275..0000000000000 --- a/.changeset/shaggy-pears-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"next": patch ---- - -Use `onPostpone` to determine if segment prefetch is partial diff --git a/.changeset/shy-impalas-add.md b/.changeset/shy-impalas-add.md deleted file mode 100644 index e9f93508de5b1..0000000000000 --- a/.changeset/shy-impalas-add.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"next": patch ---- - -[TypeScript Plugin] Moved the diagnostics' positions to the prop's type instead of the value for client-boundary warnings. diff --git a/.changeset/silent-houses-lay.md b/.changeset/silent-houses-lay.md deleted file mode 100644 index 41d24de307ca3..0000000000000 --- a/.changeset/silent-houses-lay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@next/swc': patch ---- - -Added an experimental option for using the system CA store for fetching Google Fonts in Turbopack diff --git a/.changeset/smooth-bears-run.md b/.changeset/smooth-bears-run.md deleted file mode 100644 index 2a2ea3b1c0355..0000000000000 --- a/.changeset/smooth-bears-run.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'next': patch ---- - -[dev-overlay] Show error overlay on any thrown value - -We used to only show the error overlay on thrown values with a stack property. -On other thrown values we kept the overlay collapsed. diff --git a/.changeset/spotty-hotels-train.md b/.changeset/spotty-hotels-train.md deleted file mode 100644 index fea1a4e6b846c..0000000000000 --- a/.changeset/spotty-hotels-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"next": patch ---- - -[Segment Cache] Fix: Ensure server references can be prerendered diff --git a/.changeset/swift-socks-find.md b/.changeset/swift-socks-find.md deleted file mode 100644 index babf8d42cd199..0000000000000 --- a/.changeset/swift-socks-find.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'next': patch ---- - -Sourcemap errors during prerender if `experimental.enablePrerenderSourceMaps` is enabled diff --git a/.changeset/tough-peaches-burn.md b/.changeset/tough-peaches-burn.md deleted file mode 100644 index 5814def9a6b3d..0000000000000 --- a/.changeset/tough-peaches-burn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"next": patch ---- - -Fix name tracking for closures in server actions transform diff --git a/.changeset/tricky-planes-worry.md b/.changeset/tricky-planes-worry.md deleted file mode 100644 index 4b291fb9d32ad..0000000000000 --- a/.changeset/tricky-planes-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"next": patch ---- - -[cacheComponents] Avoid timeout errors with dynamic params in `"use cache"` diff --git a/.config/eslintignore.mjs b/.config/eslintignore.mjs new file mode 100644 index 0000000000000..b9bda80eb90d7 --- /dev/null +++ b/.config/eslintignore.mjs @@ -0,0 +1,58 @@ +import { globalIgnores } from 'eslint/config' + +export default globalIgnores([ + '**/.*/**/*', // Default of ESLint legacy config + '**/node_modules', + '**/.next/**/*', + '**/_next/**/*', + '**/.vscode/**/*', + '**/dist/**/*', + 'e2e-tests/**/*', + 'examples/cms-sanity/sanity.types.ts', + 'examples/with-eslint/**/*', + 'examples/with-typescript-eslint-jest/**/*', + 'examples/with-kea/**/*', + 'examples/with-custom-babel-config/**/*', + 'examples/with-flow/**/*', + 'examples/with-jest/**/*', + 'examples/with-mobx-state-tree/**/*', + 'examples/with-mobx/**/*', + 'examples/with-tigris/db/models/todoItems.ts', + 'packages/next/src/bundles/webpack/packages/*.runtime.js', + 'packages/next/src/bundles/webpack/packages/lazy-compilation-*.js', + 'packages/next/src/compiled/**/*', + 'packages/next/wasm/@next', + 'packages/react-refresh-utils/**/*.js', + 'packages/react-dev-overlay/lib/**/*', + '**/__tmp__/**/*', + '.github/actions/next-stats-action/.work', + 'packages/next-codemod/transforms/__testfixtures__/**/*', + 'packages/next-codemod/transforms/__tests__/**/*', + 'packages/next-codemod/bin/__testfixtures__/**/*', + 'packages/next-codemod/**/*.js', + 'packages/next-codemod/**/*.d.ts', + 'packages/next-env/**/*.d.ts', + 'packages/create-next-app/templates/**/*', + 'test/integration/eslint/**/*.js', + 'test/integration/script-loader/**/*.js', + 'test/development/basic/legacy-decorators/**/*.js', + 'test/production/emit-decorator-metadata/**/*.js', + '!test/**/*.test.*', + 'test/e2e/app-dir/rsc-errors/app/swc/use-client/page.js', + '**/test-timings.json', + 'crates/**/*', + 'bench/nested-deps/**/*', + 'bench/nested-deps-app-router/**/*', + 'bench/heavy-npm-deps/**/*', + 'packages/next-bundle-analyzer/index.d.ts', + 'examples/with-typescript-graphql/lib/gql/', + 'test/development/basic/hmr/components/parse-error.js', + 'test/development/mcp-server/fixtures/default-template/app/build-error/page.tsx', + 'packages/next-swc/docs/assets/**/*', + 'test/e2e/app-dir/server-source-maps/fixtures/default/internal-pkg/sourcemapped.js', + 'test/e2e/app-dir/server-source-maps/fixtures/default/external-pkg/sourcemapped.js', + 'test/development/next-lint-eslint-formatter-compact/**/*.js', + 'test/e2e/app-dir/app-external/app/mixed/import/mixed-mod.mjs', + 'turbopack/crates/*/tests/**/*', + 'turbopack/crates/*/js/src/compiled', +]) diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 0e3c720bc136b..0000000000000 --- a/.eslintignore +++ /dev/null @@ -1,55 +0,0 @@ -node_modules -**/.next/** -**/_next/** -**/.vscode/** -**/dist/** -e2e-tests/** -examples/cms-sanity/sanity.types.ts -examples/with-eslint/** -examples/with-typescript-eslint-jest/** -examples/with-kea/** -examples/with-custom-babel-config/** -examples/with-flow/** -examples/with-jest/** -examples/with-mobx-state-tree/** -examples/with-mobx/** -examples/with-tigris/db/models/todoItems.ts -packages/next/src/bundles/webpack/packages/*.runtime.js -packages/next/src/bundles/webpack/packages/lazy-compilation-*.js -packages/next/src/compiled/**/* -packages/next/wasm/@next -packages/react-refresh-utils/**/*.js -packages/react-dev-overlay/lib/** -**/__tmp__/** -.github/actions/next-stats-action/.work -packages/next-codemod/transforms/__testfixtures__/**/* -packages/next-codemod/transforms/__tests__/**/* -packages/next-codemod/bin/__testfixtures__/**/* -packages/next-codemod/**/*.js -packages/next-codemod/**/*.d.ts -packages/next-env/**/*.d.ts -packages/create-next-app/templates/** -test/integration/eslint/**/*.js -test/integration/script-loader/**/*.js -test/development/basic/legacy-decorators/**/*.js -test/production/emit-decorator-metadata/**/*.js -!test/**/*.test.* -test/e2e/app-dir/rsc-errors/app/swc/use-client/page.js -test-timings.json -crates/** -bench/nested-deps/** -bench/nested-deps-app-router/** -bench/heavy-npm-deps/** -packages/next-bundle-analyzer/index.d.ts -examples/with-typescript-graphql/lib/gql/ -test/development/basic/hmr/components/parse-error.js -test/development/mcp-server/fixtures/default-template/app/build-error/page.tsx -packages/next-swc/docs/assets/**/* -test/e2e/app-dir/server-source-maps/fixtures/default/internal-pkg/sourcemapped.js -test/e2e/app-dir/server-source-maps/fixtures/default/external-pkg/sourcemapped.js -test/development/next-lint-eslint-formatter-compact/**/*.js -test/e2e/app-dir/app-external/app/mixed/import/mixed-mod.mjs - -# turbopack crates -turbopack/crates/*/tests/** -turbopack/crates/*/js/src/compiled \ No newline at end of file diff --git a/.eslintrc.cli.json b/.eslintrc.cli.json deleted file mode 100644 index af6b69c5af120..0000000000000 --- a/.eslintrc.cli.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/eslintrc", - "extends": [".eslintrc.json"], - "overrides": [ - { - // This override adds type-checked rules. - "files": ["**/*.ts", "**/*.tsx"], - // Linting with type-checked rules is very slow and needs a lot of memory, - // so we exclude non-essential files. - "excludedFiles": [ - "examples/**/*", - "test/**/*", - "**/*.d.ts", - "turbopack/**/*" - ], - "parserOptions": { - "project": true - }, - // These rules are added on top of the rules that are declared in - // .eslintrc.json for the matching files. - "rules": { - // TODO: enable in follow-up PR - "@typescript-eslint/no-floating-promises": "off", - "@typescript-eslint/switch-exhaustiveness-check": "error" - } - } - ] -} diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 4796c91e2138c..0000000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,429 +0,0 @@ -{ - // This is the default eslint config that is used by IDEs. It does not use - // computation-heavy type-checked rules to ensure maximum responsiveness while - // writing code. In addition, there is .eslintrc.cli.json that does use - // type-checked rules in addition to the rules defined here, and it is used - // when running `pnpm lint-eslint` locally or in CI. - "$schema": "https://json.schemastore.org/eslintrc", - "root": true, - "parser": "@babel/eslint-parser", - "plugins": ["react", "react-hooks", "jest", "import", "jsdoc"], - "env": { - "browser": true, - "commonjs": true, - "es6": true, - "node": true, - "jest": true, - "es2020": true - }, - "parserOptions": { - "requireConfigFile": false, - "sourceType": "module", - "ecmaFeatures": { - "jsx": true - }, - "babelOptions": { - "presets": ["next/babel"], - "caller": { - // Eslint supports top level await when a parser for it is included. We enable the parser by default for Babel. - "supportsTopLevelAwait": true - } - } - }, - "settings": { - "react": { - "version": "detect" - }, - "import/internal-regex": "^next/" - }, - "overrides": [ - { - "files": [ - "test/**/*.js", - "test/**/*.ts", - "test/**/*.tsx", - "**/*.test.ts", - "**/*.test.tsx" - ], - "excludedFiles": ["test/tmp/**"], - "extends": ["plugin:jest/recommended"], - "rules": { - "jest/expect-expect": "off", - "jest/no-disabled-tests": "off", - "jest/no-conditional-expect": "off", - "jest/valid-title": "off", - "jest/no-interpolation-in-snapshots": "off", - "jest/no-export": "off", - "jest/no-standalone-expect": [ - "error", - { "additionalTestBlockFunctions": ["retry", "itCI", "itHeaded"] } - ] - } - }, - { "files": ["**/__tests__/**"], "env": { "jest": true } }, - { - "extends": [ - "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/stylistic" - ], - "files": ["**/*.ts", "**/*.tsx", "**/*.mts"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "sourceType": "module", - "warnOnUnsupportedTypeScriptVersion": false - }, - "plugins": ["@typescript-eslint"], - "rules": { - // Todo: investigate, for each of these rules, whether we want them. - "@typescript-eslint/array-type": "off", - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/ban-tslint-comment": "off", - "@typescript-eslint/no-empty-object-type": "off", - "@typescript-eslint/no-restricted-types": "off", - "@typescript-eslint/no-unsafe-function-type": "off", - "@typescript-eslint/no-wrapper-object-types": "off", - "@typescript-eslint/class-literal-property-style": "off", - "@typescript-eslint/consistent-generic-constructors": "off", - "@typescript-eslint/consistent-indexed-object-style": "off", - "@typescript-eslint/consistent-type-definitions": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-namespace": "off", - "@typescript-eslint/no-shadow": "off", - "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-require-imports": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/prefer-for-of": "off", - "@typescript-eslint/prefer-function-type": "off", - "@typescript-eslint/no-this-alias": "off", - "@typescript-eslint/triple-slash-reference": "off", - "no-var": "off", - "prefer-const": "off", - "prefer-rest-params": "off", - "prefer-spread": "off", - - // These off- or differently-configured rules work well for us. - "no-unused-expressions": "off", - "@typescript-eslint/no-unused-expressions": [ - "error", - { - "allowShortCircuit": true, - "allowTernary": true, - "allowTaggedTemplates": true - } - ], - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": [ - "error", - { - "args": "none", - "ignoreRestSiblings": true, - "argsIgnorePattern": "^_", - "caughtErrors": "none", - "caughtErrorsIgnorePattern": "^_", - "destructuredArrayIgnorePattern": "^_", - "varsIgnorePattern": "^_" - } - ], - "no-use-before-define": "off", - "no-useless-constructor": "off", - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/no-useless-constructor": "error", - "@typescript-eslint/prefer-literal-enum-member": "error" - } - }, - { - "files": ["packages/**/*.ts", "packages/**/*.tsx"], - "plugins": ["@next/eslint-plugin-internal"], - "rules": { - "@next/internal/typechecked-require": "error", - "jsdoc/no-types": "error", - "jsdoc/no-undefined-types": "error" - } - }, - { - "files": ["examples/**/*"], - "rules": { - "@typescript-eslint/no-use-before-define": [ - "error", - { - "functions": true, - "classes": true, - "variables": true, - "enums": true, - "typedefs": true - } - ], - "import/no-anonymous-default-export": [ - "error", - { - // React components: - "allowArrowFunction": false, - "allowAnonymousClass": false, - "allowAnonymousFunction": false, - - // Non-React stuff: - "allowArray": true, - "allowCallExpression": true, - "allowLiteral": true, - "allowObject": true - } - ] - } - }, - { - "files": ["packages/**"], - "excludedFiles": [ - "packages/next/taskfile*.js", - "packages/next/next-devtools.webpack-config.js", - "packages/next/next-runtime.webpack-config.js" - ], - "rules": { - "no-shadow": ["error", { "builtinGlobals": false }], - "import/no-extraneous-dependencies": [ - "error", - { "devDependencies": false } - ] - } - }, - { - "files": ["packages/**/*.tsx", "packages/**/*.ts"], - "rules": { - // Note: you must disable the base rule as it can report incorrect errors - "no-shadow": "off", - "@typescript-eslint/no-shadow": ["error", { "builtinGlobals": false }], - "@typescript-eslint/no-unused-vars": [ - "error", - { - "args": "all", - "argsIgnorePattern": "^_", - "caughtErrors": "none", - "ignoreRestSiblings": true, - "varsIgnorePattern": "^_" - } - ] - } - }, - { - "files": [ - "packages/eslint-plugin-next/**/*.js", - "test/unit/eslint-plugin-next/**/*.test.ts" - ], - "extends": ["plugin:eslint-plugin/recommended"], - "parserOptions": { - "sourceType": "script" - }, - "rules": { - "eslint-plugin/prefer-replace-text": "error", - "eslint-plugin/report-message-format": [ - "error", - ".+\\. See: https://nextjs.org/docs/messages/[a-z\\-]+$" - ], - "eslint-plugin/require-meta-docs-description": [ - "error", - { - "pattern": ".+" - } - ], - "eslint-plugin/require-meta-docs-url": "error" - } - }, - { - "files": ["packages/**/*.tsx", "packages/**/*.ts"], - "rules": { - "@typescript-eslint/consistent-type-imports": [ - "error", - { - "disallowTypeAnnotations": false - } - ], - "@typescript-eslint/no-import-type-side-effects": "error" - } - }, - { - "files": ["*.mdx"], - "extends": ["plugin:mdx/recommended"], - "parser": "eslint-mdx", - "rules": { - "react/jsx-no-undef": "off" - } - }, - { - "files": [ - "packages/next/src/next-devtools/dev-overlay/**/*.tsx", - "packages/next/src/next-devtools/dev-overlay/**/*.ts" - ], - "extends": ["plugin:react-hooks/recommended"], - "rules": { - "react-hooks/exhaustive-deps": "error" - } - } - ], - "rules": { - "array-callback-return": "error", - "default-case": ["error", { "commentPattern": "^no default$" }], - "dot-location": ["error", "property"], - "eqeqeq": ["error", "smart"], - "new-parens": "error", - "no-array-constructor": "error", - "no-caller": "error", - "no-cond-assign": ["error", "except-parens"], - "no-const-assign": "error", - "no-control-regex": "error", - "no-delete-var": "error", - "no-dupe-args": "error", - "no-dupe-class-members": "error", - "no-dupe-keys": "error", - "no-duplicate-case": "error", - "no-empty-character-class": "error", - "no-empty-pattern": "error", - "no-eval": "error", - "no-ex-assign": "error", - "no-extend-native": "error", - "no-extra-bind": "error", - "no-extra-label": "error", - "no-fallthrough": "error", - "no-func-assign": "error", - "no-implied-eval": "error", - "no-invalid-regexp": "error", - "no-iterator": "error", - "no-label-var": "error", - "no-labels": ["error", { "allowLoop": true, "allowSwitch": false }], - "no-lone-blocks": "error", - "no-loop-func": "error", - "no-mixed-operators": [ - "error", - { - "groups": [ - ["&", "|", "^", "~", "<<", ">>", ">>>"], - ["==", "!=", "===", "!==", ">", ">=", "<", "<="], - ["&&", "||"], - ["in", "instanceof"] - ], - "allowSamePrecedence": false - } - ], - "no-multi-str": "error", - "no-native-reassign": "error", - "no-negated-in-lhs": "error", - "no-new-func": "error", - "no-new-object": "error", - "no-new-symbol": "error", - "no-new-wrappers": "error", - "no-obj-calls": "error", - "no-octal": "error", - "no-octal-escape": "error", - "no-regex-spaces": "error", - "no-restricted-imports": [ - "error", - { - "patterns": [ - { - "group": ["*/next-devtools/dev-overlay*"], - "message": "Use `next/dist/compiled/next-devtools` (`src/next-devtools/dev-overlay/entrypoint.ts`) instead. Prefer `src/next-devtools/shared/` for shared utils." - } - ] - } - ], - "no-restricted-syntax": [ - "error", - "WithStatement", - { - "message": "substr() is deprecated, use slice() or substring() instead", - "selector": "MemberExpression > Identifier[name='substr']" - }, - // ban plain workUnitStore.type if statements and ternaries - { - "selector": "BinaryExpression[left.object.name='workUnitStore'][left.property.name='type'][operator=/^(?:===|!==)$/]", - "message": "Use an exhaustive switch on `workUnitStore.type` (with a `never`-based default) instead of using if statements or ternaries." - }, - // ban optional-chained workUnitStore?.type if statements and ternaries - { - "selector": "BinaryExpression[left.type='ChainExpression'][left.expression.object.name='workUnitStore'][left.expression.property.name='type'][operator=/^(?:===|!==)$/]", - "message": "Use an exhaustive switch on `workUnitStore.type` (with a `never`-based default) instead of using if statements or ternaries." - } - ], - "no-script-url": "error", - "no-self-assign": "error", - "no-self-compare": "error", - "no-sequences": "error", - "no-shadow-restricted-names": "error", - "no-sparse-arrays": "error", - "no-template-curly-in-string": "error", - "no-this-before-super": "error", - "no-throw-literal": "error", - "no-undef": "error", - "no-unexpected-multiline": "error", - "no-unreachable": "error", - "no-unused-expressions": [ - "error", - { - "allowShortCircuit": true, - "allowTernary": true, - "allowTaggedTemplates": true - } - ], - "no-unused-labels": "error", - "no-unused-vars": [ - "error", - { - "args": "none", - "caughtErrors": "none", - "ignoreRestSiblings": true - } - ], - "no-use-before-define": "off", - "no-useless-computed-key": "error", - "no-useless-concat": "error", - "no-useless-constructor": "error", - "no-useless-escape": "error", - "no-useless-rename": [ - "error", - { - "ignoreDestructuring": false, - "ignoreImport": false, - "ignoreExport": false - } - ], - "no-with": "error", - "no-whitespace-before-property": "error", - "react-hooks/exhaustive-deps": "error", - "require-yield": "error", - "rest-spread-spacing": ["error", "never"], - "strict": ["error", "never"], - "unicode-bom": ["error", "never"], - "use-isnan": "error", - "valid-typeof": "error", - "getter-return": "error", - "react/forbid-foreign-prop-types": ["error", { "allowInPropTypes": true }], - "react/jsx-no-comment-textnodes": "error", - "react/jsx-no-duplicate-props": "error", - "react/jsx-no-target-blank": "error", - "react/jsx-no-undef": "error", - "react/jsx-pascal-case": [ - "error", - { - "allowAllCaps": true, - "ignore": [] - } - ], - "react/jsx-uses-react": "error", - "react/jsx-uses-vars": "error", - "react/no-danger-with-children": "error", - "react/no-deprecated": "error", - "react/no-direct-mutation-state": "error", - "react/no-is-mounted": "error", - "react/no-typos": "error", - "react/react-in-jsx-scope": "off", - "react/require-render-return": "error", - "react/style-prop-object": "error", - "react-hooks/rules-of-hooks": "error", - // "@typescript-eslint/non-nullable-type-assertion-style": "error", - "@typescript-eslint/prefer-as-const": "error", - "@typescript-eslint/no-redeclare": [ - "error", - { "builtinGlobals": false, "ignoreDeclarationMerge": true } - ] - } -} diff --git a/.github/labeler.json b/.github/labeler.json index f1feeb69dcc2d..fe16f489847df 100644 --- a/.github/labeler.json +++ b/.github/labeler.json @@ -58,9 +58,8 @@ { "type": "user", "pattern": "ijjk" }, { "type": "user", "pattern": "lazarv" }, { "type": "user", "pattern": "lubieowoce" }, - { "type": "user", "pattern": "bgub" }, { "type": "user", "pattern": "RobPruzan" }, - { "type": "user", "pattern": "samcx" }, + { "type": "user", "pattern": "samselikoff" }, { "type": "user", "pattern": "sebmarkbage" }, { "type": "user", "pattern": "shuding" }, { "type": "user", "pattern": "styfle" }, @@ -71,7 +70,6 @@ "created-by: Next.js DevEx team": [ { "type": "user", "pattern": "delbaoliveira" }, { "type": "user", "pattern": "ismaelrumzan" }, - { "type": "user", "pattern": "leerob" }, { "type": "user", "pattern": "manovotny" }, { "type": "user", "pattern": "molebox" }, { "type": "user", "pattern": "timeyoutakeit" }, diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index d971d68c5e433..a77229e87eec5 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -19,11 +19,6 @@ env: # # See https://doc.rust-lang.org/rustc/platform-support/apple-darwin.html#os-version for more details MACOSX_DEPLOYMENT_TARGET: 11.0 - # This will become "true" if the latest commit (merged release PR) is either: - # - "Version Packages (#)" - # - "Version Pacakges (canary/rc) (#)" - # set from scripts/check-is-release.js - __NEW_RELEASE: 'false' jobs: deploy-target: @@ -53,13 +48,7 @@ jobs: # 'staging' for canary branch since that will eventually be published i.e. become the production build. id: deploy-target run: | - # TODO: Remove the new release check once the new release workflow is fully replaced. - RELEASE_CHECK=$(node ./scripts/check-is-release.js 2> /dev/null || :) - if [[ $RELEASE_CHECK == 'new-release' ]]; - then - echo "__NEW_RELEASE=true" >> $GITHUB_ENV - echo "value=production" >> $GITHUB_OUTPUT - elif [[ $RELEASE_CHECK == v* ]]; + if [[ $(node ./scripts/check-is-release.js 2> /dev/null || :) == v* ]]; then echo "value=production" >> $GITHUB_OUTPUT elif [ '${{ github.ref }}' == 'refs/heads/canary' ] @@ -68,7 +57,7 @@ jobs: elif [ '${{ github.event_name }}' == 'workflow_dispatch' ] then echo "value=force-preview" >> $GITHUB_OUTPUT - elif [[ $(node scripts/run-for-change.js --not --type docs --exec echo 'false') != 'false' ]]; + elif [[ $(node scripts/run-for-change.mjs --not --type docs --exec echo 'false') != 'false' ]]; then echo "value=skipped" >> $GITHUB_OUTPUT else @@ -611,41 +600,10 @@ jobs: - run: npm i -g npm@10.4.0 # need latest version for provenance (pinning to avoid bugs) - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc - run: ./scripts/publish-native.js - # Legacy release process - run: ./scripts/publish-release.js - if: ${{ env.__NEW_RELEASE == 'false' }} env: RELEASE_BOT_GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }} - # New release process - - name: Publish to NPM - id: changesets - # TODO: Change to IS_RELEASE condition when new release becomes stable. - if: ${{ env.__NEW_RELEASE == 'true' }} - uses: changesets/action@v1 - with: - publish: pnpm ci:publish - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }} - - - name: Send a Slack notification of the publish status - # TODO: Change to IS_RELEASE condition when new release becomes stable. - if: ${{ env.__NEW_RELEASE == 'true' && (steps.changesets.outputs.published == 'true' || steps.changesets.outputs.published == 'false') }} - run: pnpm tsx scripts/release/slack.ts - env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - RELEASE_STATUS: ${{ steps.changesets.outputs.published }} - WORKFLOW_LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - WORKFLOW_ACTOR: ${{ github.actor }} - - - name: Upload npm log artifact - if: steps.changesets.outputs.published == 'true' - uses: actions/upload-artifact@v4 - with: - name: npm-publish-logs - path: /home/runner/.npm/_logs/* - publish-turbopack-npm-packages: # Matches the commit message written by turbopack/xtask/src/publish.rs:377 if: "${{(github.ref == 'refs/heads/canary') && startsWith(github.event.head_commit.message, 'chore: release turbopack npm packages')}}" diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 1f4fb64e6b07d..8938abd10cd1b 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -33,14 +33,13 @@ jobs: id: docs-change run: | echo "DOCS_ONLY<> $GITHUB_OUTPUT; - echo "$(node scripts/run-for-change.js --not --type docs --exec echo 'false')" >> $GITHUB_OUTPUT; + echo "$(node scripts/run-for-change.mjs --not --type docs --exec echo 'false')" >> $GITHUB_OUTPUT; echo 'EOF' >> $GITHUB_OUTPUT - name: check for release id: is-release run: | - RELEASE_CHECK=$(node ./scripts/check-is-release.js 2> /dev/null || :) - if [[ $RELEASE_CHECK == "new-release" || $RELEASE_CHECK == v* ]]; + if [[ $(node ./scripts/check-is-release.js 2> /dev/null || :) == v* ]]; then echo "IS_RELEASE=true" >> $GITHUB_OUTPUT else @@ -195,7 +194,7 @@ jobs: devlow-bench: name: Run devlow benchmarks needs: ['optimize-ci', 'changes', 'build-next', 'build-native'] - if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' && !github.event.pull_request.head.repo.fork }} + if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' && github.event_name != 'pull_request' }} strategy: fail-fast: false @@ -240,7 +239,7 @@ jobs: exclude: # Excluding React 18 tests unless on `canary` branch until budget is approved. - react: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'run-react-18-tests') && '18.3.1' }} - group: [1/5, 2/5, 3/5, 4/5, 5/5] + group: [1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 7/7] # Empty value uses default react: ['', '18.3.1'] uses: ./.github/workflows/build_reusable.yml @@ -323,7 +322,7 @@ jobs: strategy: fail-fast: false matrix: - group: [1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 7/7] + group: [1/10, 2/10, 3/10, 4/10, 5/10, 6/10, 7/10, 8/10, 9/10, 10/10] # Empty value uses default # TODO: Run with React 18. # Integration tests use the installed React version in next/package.json. @@ -502,6 +501,7 @@ jobs: export NEXT_TEST_MODE=start export NEXT_TEST_WASM=true + export IS_WEBPACK_TEST=1 node run-tests.js \ test/production/pages-dir/production/test/index.test.ts \ test/e2e/streaming-ssr/index.test.ts @@ -561,6 +561,7 @@ jobs: with: nodeVersion: ${{ matrix.node }} afterBuild: | + export __NEXT_NODE_NATIVE_TS_LOADER_ENABLED=true NEXT_TEST_MODE=dev NODE_OPTIONS=--experimental-transform-types node run-tests.js test/e2e/app-dir/next-config-ts-native-ts/**/*.test.ts test/e2e/app-dir/next-config-ts-native-mts/**/*.test.ts stepName: 'test-next-config-ts-native-ts-dev-${{ matrix.node }}' @@ -581,6 +582,7 @@ jobs: with: nodeVersion: ${{ matrix.node }} afterBuild: | + export __NEXT_NODE_NATIVE_TS_LOADER_ENABLED=true NEXT_TEST_MODE=start NODE_OPTIONS=--experimental-transform-types node run-tests.js test/e2e/app-dir/next-config-ts-native-ts/**/*.test.ts test/e2e/app-dir/next-config-ts-native-mts/**/*.test.ts stepName: 'test-next-config-ts-native-ts-prod-${{ matrix.node }}' @@ -678,12 +680,12 @@ jobs: secrets: inherit - test-new-tests-deploy-experimental: - name: Test new tests when deployed (experimental) + test-new-tests-deploy-cache-components: + name: Test new tests when deployed (cache components) needs: [ 'optimize-ci', - 'test-experimental-prod', + 'test-cache-components-prod', 'test-new-tests-dev', 'test-new-tests-start', ] @@ -699,19 +701,19 @@ jobs: uses: ./.github/workflows/build_reusable.yml with: afterBuild: | - export __NEXT_EXPERIMENTAL_PPR=true # for compatibility with the existing tests - export __NEXT_EXPERIMENTAL_CACHE_COMPONENTS=true - export NEXT_EXTERNAL_TESTS_FILTERS="test/experimental-tests-manifest.json" + export __NEXT_CACHE_COMPONENTS=true + export __NEXT_EXPERIMENTAL_DEBUG_CHANNEL=true + export NEXT_EXTERNAL_TESTS_FILTERS="test/cache-components-tests-manifest.json" export NEXT_E2E_TEST_TIMEOUT=240000 export GH_PR_NUMBER=${{ github.event.pull_request && github.event.pull_request.number || '' }} node scripts/test-new-tests.mjs \ --mode deploy \ --group ${{ matrix.group }} - stepName: 'test-new-tests-deploy-experimental-${{matrix.group}}' + stepName: 'test-new-tests-deploy-cache-components-${{matrix.group}}' secrets: inherit - test-dev: + test-dev: # TODO: rename to include webpack name: test dev needs: ['optimize-ci', 'changes', 'build-native', 'build-next'] if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }} @@ -722,12 +724,13 @@ jobs: exclude: # Excluding React 18 tests unless on `canary` branch until budget is approved. - react: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'run-react-18-tests') && '18.3.1' }} - group: [1/6, 2/6, 3/6, 4/6, 5/6, 6/6] + group: [1/10, 2/10, 3/10, 4/10, 5/10, 6/10, 7/10, 8/10, 9/10, 10/10] # Empty value uses default react: ['', '18.3.1'] uses: ./.github/workflows/build_reusable.yml with: afterBuild: | + export IS_WEBPACK_TEST=1 export NEXT_TEST_MODE=dev export NEXT_TEST_REACT_VERSION="${{ matrix.react }}" @@ -752,12 +755,15 @@ jobs: uses: ./.github/workflows/build_reusable.yml with: + # Should this be using turbopack? a variation? afterBuild: | export NEXT_TEST_MODE=dev + export IS_WEBPACK_TEST=1 node run-tests.js \ test/e2e/app-dir/app/index.test.ts \ test/e2e/app-dir/app-edge/app-edge.test.ts \ + test/e2e/app-dir/proxy-runtime-nodejs/proxy-runtime-nodejs.test.ts \ test/development/app-dir/segment-explorer/segment-explorer.test.ts stepName: 'test-dev-windows' runs_on_labels: '["windows","self-hosted","x64"]' @@ -780,11 +786,12 @@ jobs: with: nodeVersion: 20.9.0 afterBuild: | + export IS_WEBPACK_TEST=1 node run-tests.js \ --concurrency 4 \ test/production/pages-dir/production/test/index.test.ts \ - test/integration/css-client-nav/test/index.test.js \ - test/integration/rewrites-has-condition/test/index.test.js \ + test/integration/css-client-nav/test/index.test.ts \ + test/integration/rewrites-has-condition/test/index.test.ts \ test/integration/create-next-app/index.test.ts \ test/integration/create-next-app/package-manager/pnpm.test.ts stepName: 'test-integration-windows' @@ -808,12 +815,14 @@ jobs: with: afterBuild: | export NEXT_TEST_MODE=start + export IS_WEBPACK_TEST=1 node run-tests.js --type production \ test/e2e/app-dir/app/index.test.ts \ test/e2e/app-dir/app-edge/app-edge.test.ts \ test/e2e/app-dir/metadata-edge/index.test.ts \ - test/e2e/app-dir/non-root-project-monorepo/non-root-project-monorepo.test.ts + test/e2e/app-dir/non-root-project-monorepo/non-root-project-monorepo.test.ts \ + test/e2e/app-dir/proxy-runtime-nodejs/proxy-runtime-nodejs.test.ts stepName: 'test-prod-windows' runs_on_labels: '["windows","self-hosted","x64"]' buildNativeTarget: 'x86_64-pc-windows-msvc' @@ -830,12 +839,13 @@ jobs: exclude: # Excluding React 18 tests unless on `canary` branch until budget is approved. - react: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'run-react-18-tests') && '18.3.1' }} - group: [1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 7/7] + group: [1/10, 2/10, 3/10, 4/10, 5/10, 6/10, 7/10, 8/10, 9/10, 10/10] # Empty value uses default react: ['', '18.3.1'] uses: ./.github/workflows/build_reusable.yml with: afterBuild: | + export IS_WEBPACK_TEST=1 export NEXT_TEST_MODE=start export NEXT_TEST_REACT_VERSION="${{ matrix.react }}" @@ -875,6 +885,7 @@ jobs: with: nodeVersion: 20.9.0 afterBuild: | + export IS_WEBPACK_TEST=1 export NEXT_TEST_REACT_VERSION="${{ matrix.react }}" node run-tests.js \ @@ -896,6 +907,7 @@ jobs: # these all run without concurrency because they're heavier export TEST_CONCURRENCY=1 + export IS_WEBPACK_TEST=1 BROWSER_NAME=firefox node run-tests.js \ test/production/pages-dir/production/test/index.test.ts @@ -910,76 +922,9 @@ jobs: stepName: 'test-firefox-safari' secrets: inherit - # TODO: remove these jobs once PPR is the default - # Manifest generated via: https://gist.github.com/wyattjoh/2ceaebd82a5bcff4819600fd60126431 - test-ppr-integration: - name: test ppr integration - needs: ['optimize-ci', 'changes', 'build-native', 'build-next'] - if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }} - - uses: ./.github/workflows/build_reusable.yml - with: - nodeVersion: 20.9.0 - afterBuild: | - export __NEXT_EXPERIMENTAL_PPR=true - export NEXT_EXTERNAL_TESTS_FILTERS="test/ppr-tests-manifest.json" - - node run-tests.js \ - --timings \ - --type integration - stepName: 'test-ppr-integration' - secrets: inherit - - test-ppr-dev: - name: test ppr 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_EXPERIMENTAL_PPR=true - export NEXT_EXTERNAL_TESTS_FILTERS="test/ppr-tests-manifest.json" - export NEXT_TEST_MODE=dev - - node run-tests.js \ - --timings \ - -g ${{ matrix.group }} \ - --type development - stepName: 'test-ppr-dev-${{ matrix.group }}' - secrets: inherit - - test-ppr-prod: - name: test ppr 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_EXPERIMENTAL_PPR=true - export NEXT_EXTERNAL_TESTS_FILTERS="test/ppr-tests-manifest.json" - export NEXT_TEST_MODE=start - - node run-tests.js \ - --timings \ - -g ${{ matrix.group }} \ - --type production - stepName: 'test-ppr-prod-${{ matrix.group }}' - secrets: inherit - - # TODO: Disable these when no more experiments are tested # Manifest generated via: https://gist.github.com/wyattjoh/2ceaebd82a5bcff4819600fd60126431 - test-experimental-integration: - name: test experimental integration + test-cache-components-integration: + name: test cache components integration needs: ['optimize-ci', 'changes', 'build-native', 'build-next'] if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }} @@ -987,18 +932,19 @@ jobs: with: nodeVersion: 20.9.0 afterBuild: | - export __NEXT_EXPERIMENTAL_PPR=true # for compatibility with the existing tests - export __NEXT_EXPERIMENTAL_CACHE_COMPONENTS=true - export NEXT_EXTERNAL_TESTS_FILTERS="test/experimental-tests-manifest.json" + export __NEXT_CACHE_COMPONENTS=true + export __NEXT_EXPERIMENTAL_DEBUG_CHANNEL=true + export NEXT_EXTERNAL_TESTS_FILTERS="test/cache-components-tests-manifest.json" + export IS_WEBPACK_TEST=1 node run-tests.js \ --timings \ --type integration - stepName: 'test-experimental-integration' + stepName: 'test-cache-components-integration' secrets: inherit - test-experimental-dev: - name: test experimental dev + test-cache-components-dev: + name: test cache components dev needs: ['optimize-ci', 'changes', 'build-native', 'build-next'] if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }} @@ -1009,21 +955,21 @@ jobs: uses: ./.github/workflows/build_reusable.yml with: afterBuild: | - export __NEXT_EXPERIMENTAL_PPR=true # for compatibility with the existing tests - export __NEXT_EXPERIMENTAL_CACHE_COMPONENTS=true - export NEXT_EXTERNAL_TESTS_FILTERS="test/experimental-tests-manifest.json" + export __NEXT_CACHE_COMPONENTS=true + export __NEXT_EXPERIMENTAL_DEBUG_CHANNEL=true + export NEXT_EXTERNAL_TESTS_FILTERS="test/cache-components-tests-manifest.json" export NEXT_TEST_MODE=dev - export __NEXT_EXPERIMENTAL_ISOLATED_DEV_BUILD=true + export IS_WEBPACK_TEST=1 node run-tests.js \ --timings \ -g ${{ matrix.group }} \ --type development - stepName: 'test-experimental-dev-${{ matrix.group }}' + stepName: 'test-cache-components-dev-${{ matrix.group }}' secrets: inherit - test-experimental-prod: - name: test experimental prod + test-cache-components-prod: + name: test cache components prod needs: ['optimize-ci', 'changes', 'build-native', 'build-next'] if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }} @@ -1034,16 +980,17 @@ jobs: uses: ./.github/workflows/build_reusable.yml with: afterBuild: | - export __NEXT_EXPERIMENTAL_PPR=true # for compatibility with the existing tests - export __NEXT_EXPERIMENTAL_CACHE_COMPONENTS=true - export NEXT_EXTERNAL_TESTS_FILTERS="test/experimental-tests-manifest.json" + export __NEXT_CACHE_COMPONENTS=true + export __NEXT_EXPERIMENTAL_DEBUG_CHANNEL=true + export NEXT_EXTERNAL_TESTS_FILTERS="test/cache-components-tests-manifest.json" export NEXT_TEST_MODE=start + export IS_WEBPACK_TEST=1 node run-tests.js \ --timings \ -g ${{ matrix.group }} \ --type production - stepName: 'test-experimental-prod-${{ matrix.group }}' + stepName: 'test-cache-components-prod-${{ matrix.group }}' secrets: inherit tests-pass: @@ -1061,12 +1008,9 @@ jobs: 'test-prod', 'test-integration', 'test-firefox-safari', - 'test-ppr-dev', - 'test-ppr-prod', - 'test-ppr-integration', - 'test-experimental-dev', - 'test-experimental-prod', - 'test-experimental-integration', + 'test-cache-components-dev', + 'test-cache-components-prod', + 'test-cache-components-integration', 'test-cargo-unit', 'rust-check', 'rustdoc-check', @@ -1076,7 +1020,7 @@ jobs: 'test-new-tests-dev', 'test-new-tests-start', 'test-new-tests-deploy', - 'test-new-tests-deploy-experimental', + 'test-new-tests-deploy-cache-components', 'test-turbopack-production', 'test-turbopack-production-integration', 'test-unit-windows', diff --git a/.github/workflows/build_reusable.yml b/.github/workflows/build_reusable.yml index 1a39146387766..47d30c63ba7af 100644 --- a/.github/workflows/build_reusable.yml +++ b/.github/workflows/build_reusable.yml @@ -92,8 +92,7 @@ env: TURBO_TEAM: 'vercel' TURBO_CACHE: 'remote:rw' - TURBO_API: ${{ secrets.HOSTED_TURBO_API }} - TURBO_TOKEN: ${{ secrets.HOSTED_TURBO_TOKEN }} + NEXT_TELEMETRY_DISABLED: 1 # allow not skipping install-native postinstall script if we don't have a binary available already NEXT_SKIP_NATIVE_POSTINSTALL: ${{ inputs.skipNativeInstall == 'yes' && '1' || '' }} @@ -110,6 +109,9 @@ env: NEXT_CI_RUNNER: ${{ inputs.runs_on_labels }} NEXT_TEST_PROXY_ADDRESS: ${{ inputs.overrideProxyAddress || '' }} + # defaults to 256, but we run a lot of tests in parallel, so the limit should be lower + NEXT_TURBOPACK_IO_CONCURRENCY: 64 + jobs: build: timeout-minutes: ${{ inputs.timeout_minutes }} diff --git a/.github/workflows/force_merge_canary_release_pr.yml b/.github/workflows/force_merge_canary_release_pr.yml deleted file mode 100644 index 1f22b160fcc57..0000000000000 --- a/.github/workflows/force_merge_canary_release_pr.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Force Merge Canary Release PR - -on: pull_request - -permissions: - # To bypass and merge PR - pull-requests: write - -jobs: - force-merge-canary-release-pr: - runs-on: ubuntu-latest - # Validate the login, PR title, and the label to ensure the PR is - # from the release PR and prevent spoofing. - if: | - github.event.pull_request.user.login == 'vercel-release-bot' && - github.event.pull_request.title == 'Version Packages (canary)' && - contains(github.event.pull_request.labels.*.name, 'created-by: CI') - steps: - - name: Bypass required status checks and merge PR - run: gh pr merge --admin --squash "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/integration_tests_reusable.yml b/.github/workflows/integration_tests_reusable.yml index bf201f5375f3a..596b8e2c41e74 100644 --- a/.github/workflows/integration_tests_reusable.yml +++ b/.github/workflows/integration_tests_reusable.yml @@ -91,7 +91,7 @@ jobs: afterBuild: | # e2e and ${{ inputs.test_type }} tests with `node run-tests.js` - export NEXT_TEST_CONTINUE_ON_ERROR=TRUE + export NEXT_TEST_CONTINUE_ON_ERROR=true export NEXT_TEST_MODE=${{ inputs.test_type == 'development' && 'dev' || 'start' }} @@ -123,7 +123,7 @@ jobs: afterBuild: | # legacy integration tests with `node run-tests.js` - export NEXT_TEST_CONTINUE_ON_ERROR=TRUE + export NEXT_TEST_CONTINUE_ON_ERROR=true # HACK: Despite the name, these environment variables are just used to # gate tests, so they're applicable to both turbopack and rspack tests diff --git a/.github/workflows/pull_request_stats.yml b/.github/workflows/pull_request_stats.yml index 3b3afeee5da3b..3425495177121 100644 --- a/.github/workflows/pull_request_stats.yml +++ b/.github/workflows/pull_request_stats.yml @@ -46,7 +46,7 @@ jobs: fetch-depth: 25 - name: Check non-docs only change - run: echo "DOCS_CHANGE<> $GITHUB_OUTPUT; echo "$(node scripts/run-for-change.js --not --type docs --exec echo 'nope')" >> $GITHUB_OUTPUT; echo 'EOF' >> $GITHUB_OUTPUT + run: echo "DOCS_CHANGE<> $GITHUB_OUTPUT; echo "$(node scripts/run-for-change.mjs --not --type docs --exec echo 'nope')" >> $GITHUB_OUTPUT; echo 'EOF' >> $GITHUB_OUTPUT id: docs-change - uses: actions/download-artifact@v4 @@ -60,3 +60,7 @@ jobs: - uses: ./.github/actions/next-stats-action if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }} + env: + # This uses the webpack bundle analyzer and for consistent results we need to use webpack. + # Once there is an equivalent analyzer for turbopack, we can remove this. + IS_WEBPACK_TEST: 1 diff --git a/.github/workflows/release-next-rspack.yml b/.github/workflows/release-next-rspack.yml index e80064a76796f..d6880491822a1 100644 --- a/.github/workflows/release-next-rspack.yml +++ b/.github/workflows/release-next-rspack.yml @@ -8,6 +8,11 @@ on: required: false default: false type: boolean + npm-name: + description: 'NPM package name to publish' + required: false + default: '@next/rspack-core' + type: string npm-tag: description: 'NPM tag for publishing' required: false @@ -48,13 +53,13 @@ jobs: run: | echo "🚀 Release Configuration:" echo " - Dry-run mode: ${{ inputs.dry-run }}" + echo " - NPM name: ${{ inputs.npm-name || '@next/rspack-core' }}" echo " - NPM tag: ${{ inputs.npm-tag || 'latest' }}" if [ "${{ inputs.dry-run }}" == "true" ]; then echo " - ⚠️ This is a DRY RUN - no packages will be published" else echo " - 📦 This will PUBLISH packages to npm" fi - - name: Setup Node.js uses: actions/setup-node@v4 with: @@ -66,6 +71,10 @@ jobs: - name: Setup pnpm run: corepack prepare + - name: Change npm name + run: pnpm run change-npm-name "${{ inputs.npm-name }}" + working-directory: ./rspack + - name: Cache pnpm dependencies uses: actions/cache@v3 with: @@ -109,7 +118,7 @@ jobs: run: | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }} - name: Release npm binding packages run: | diff --git a/.github/workflows/test_e2e_deploy_release.yml b/.github/workflows/test_e2e_deploy_release.yml index 631b0a2b43f46..3ad1f12e29ca5 100644 --- a/.github/workflows/test_e2e_deploy_release.yml +++ b/.github/workflows/test_e2e_deploy_release.yml @@ -19,18 +19,20 @@ on: description: Override the proxy address to use for the test default: '' type: string + continueOnError: + description: whether the tests should continue on failure + default: false + type: boolean env: + TURBO_TEAM: 'vercel' + TURBO_CACHE: 'remote:rw' VERCEL_TEST_TEAM: vtest314-next-e2e-tests VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }} DATADOG_API_KEY: ${{ secrets.DATA_DOG_API_KEY }} - TURBO_TEAM: 'vercel' - TURBO_CACHE: 'remote:rw' - TURBO_API: ${{ secrets.HOSTED_TURBO_API }} - TURBO_TOKEN: ${{ secrets.HOSTED_TURBO_TOKEN }} DD_ENV: 'ci' -run-name: test-e2e-deploy ${{ inputs.nextVersion || 'canary' }} +run-name: test-e2e-deploy ${{ inputs.nextVersion || (github.event_name == 'release' && github.event.release.tag_name) || 'canary' }} jobs: setup: @@ -78,7 +80,16 @@ jobs: matrix: group: [1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8, 8/8] with: - afterBuild: npm i -g vercel@latest && NEXT_E2E_TEST_TIMEOUT=240000 NEXT_TEST_MODE=deploy NEXT_EXTERNAL_TESTS_FILTERS="test/deploy-tests-manifest.json" NEXT_TEST_VERSION="${{ github.event.inputs.nextVersion || 'canary' }}" VERCEL_CLI_VERSION="${{ github.event.inputs.vercelCliVersion || 'vercel@latest' }}" node run-tests.js --timings -g ${{ matrix.group }} -c 2 --type e2e + afterBuild: | + npm i -g vercel@latest && \ + NEXT_E2E_TEST_TIMEOUT=240000 \ + NEXT_TEST_MODE=deploy \ + IS_WEBPACK_TEST=1 \ + NEXT_TEST_CONTINUE_ON_ERROR="${{ github.event.inputs.continueOnError || false }}" \ + NEXT_EXTERNAL_TESTS_FILTERS="test/deploy-tests-manifest.json" \ + NEXT_TEST_VERSION="${{ github.event.inputs.nextVersion || needs.setup.outputs.next-version || 'canary' }}" \ + VERCEL_CLI_VERSION="${{ github.event.inputs.vercelCliVersion || 'vercel@latest' }}" \ + node run-tests.js --timings -g ${{ matrix.group }} -c 2 --type e2e skipNativeBuild: 'yes' skipNativeInstall: 'no' stepName: 'test-deploy-${{ matrix.group }}' diff --git a/.github/workflows/test_e2e_project_reset_cron.yml b/.github/workflows/test_e2e_project_reset_cron.yml index 17adc0e1c97df..52e0234e1841b 100644 --- a/.github/workflows/test_e2e_project_reset_cron.yml +++ b/.github/workflows/test_e2e_project_reset_cron.yml @@ -10,11 +10,9 @@ on: env: VERCEL_TEST_TEAM: vtest314-next-e2e-tests VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }} + NODE_LTS_VERSION: 20 TURBO_TEAM: 'vercel' TURBO_CACHE: 'remote:rw' - TURBO_API: ${{ secrets.HOSTED_TURBO_API }} - TURBO_TOKEN: ${{ secrets.HOSTED_TURBO_TOKEN }} - NODE_LTS_VERSION: 20 run-name: test-e2e-project-reset (scheduled) diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 23d2d3421cd8c..c1ba7bd15a1c5 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -6,13 +6,14 @@ on: workflow_dispatch: inputs: releaseType: - description: stable, canary, or release candidate? + description: stable, canary, beta, or release candidate? required: true type: choice options: - canary - stable - release-candidate + - beta semverType: description: semver type? diff --git a/.github/workflows/trigger_release_new.yml b/.github/workflows/trigger_release_new.yml deleted file mode 100644 index 9e31cb4ec1bea..0000000000000 --- a/.github/workflows/trigger_release_new.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: Trigger Release (New) - -on: - # Run every day at 23:15 UTC - # TODO: Disabled cron for now, but uncomment - # once replaced the old release workflow. - # schedule: - # - cron: '15 23 * * *' - # Run manually - workflow_dispatch: - inputs: - releaseType: - description: Release Type - required: true - type: choice - # Cron job will run canary release - default: canary - options: - - canary - - stable - - release-candidate - - force: - description: Forced Release - default: false - type: boolean - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -env: - NAPI_CLI_VERSION: 2.18.4 - TURBO_VERSION: 2.5.5 - NODE_LTS_VERSION: 20 - -permissions: - # To create PR - pull-requests: write - -jobs: - start: - if: github.repository_owner == 'vercel' - runs-on: ubuntu-latest - env: - NEXT_TELEMETRY_DISABLED: 1 - # we build a dev binary for use in CI so skip downloading - # canary next-swc binaries in the monorepo - NEXT_SKIP_NATIVE_POSTINSTALL: 1 - - environment: release-${{ github.event.inputs.releaseType }} - steps: - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_LTS_VERSION }} - check-latest: true - - # Since actions/checkout won't include the latest tag information, - # use the old clone workflow while still preserving branch specific - # checkout behavior to support backports. - # x-ref: https://github.com/vercel/next.js/pull/63167 - - name: Clone Next.js repository - run: git clone https://github.com/vercel/next.js.git --depth=25 --single-branch --branch ${GITHUB_REF_NAME:-canary} . - - - name: Check token - run: gh auth status - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }} - - - name: Get commit of the latest tag - run: echo "LATEST_TAG_COMMIT=$(git rev-list -n 1 $(git describe --tags --abbrev=0))" >> $GITHUB_ENV - - - name: Get latest commit - run: echo "LATEST_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV - - - name: Check if new commits since last tag - if: ${{ github.event.inputs.releaseType != 'stable' && github.event.inputs.force != true }} - run: | - if [ "$LATEST_TAG_COMMIT" = "$LATEST_COMMIT" ]; then - echo "No new commits. Exiting..." - exit 1 - fi - - # https://github.com/actions/virtual-environments/issues/1187 - - name: tune linux network - run: sudo ethtool -K eth0 tx off rx off - - - name: Setup corepack - run: | - npm i -g corepack@0.31 - corepack enable - pnpm --version - - - id: get-store-path - run: echo STORE_PATH=$(pnpm store path) >> $GITHUB_OUTPUT - - - uses: actions/cache@v4 - timeout-minutes: 5 - id: cache-pnpm-store - with: - path: ${{ steps.get-store-path.outputs.STORE_PATH }} - key: pnpm-store-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - pnpm-store- - pnpm-store-${{ hashFiles('pnpm-lock.yaml') }} - - - run: pnpm install - - run: pnpm run build - - - name: Create Release Pull Request - id: changesets - uses: changesets/action@v1 - with: - version: pnpm ci:version - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }} - RELEASE_TYPE: ${{ github.event.inputs.releaseType }} - - # Add label to verify the PR is created from this workflow. - - name: Add label to PR - if: steps.changesets.outputs.pullRequestNumber - run: 'gh pr edit ${{ steps.changesets.outputs.pullRequestNumber }} --add-label "created-by: CI"' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/turbopack-benchmark.yml b/.github/workflows/turbopack-benchmark.yml index 9fd2be8c655e0..8d7f558ed1be4 100644 --- a/.github/workflows/turbopack-benchmark.yml +++ b/.github/workflows/turbopack-benchmark.yml @@ -24,7 +24,6 @@ env: RUST_LOG: 'off' TURBO_TEAM: 'vercel' TURBO_CACHE: 'remote:rw' - TURBO_TOKEN: ${{ secrets.HOSTED_TURBO_TOKEN }} jobs: benchmark-small-apps: diff --git a/.prettierignore b/.prettierignore index 53a38838ed32a..e608bfb93b132 100644 --- a/.prettierignore +++ b/.prettierignore @@ -17,7 +17,6 @@ packages/next/wasm/@next packages/next/errors.json .github/actions/next-stats-action/.work -.changeset/*.md crates/**/tests/**/output* crates/core/tests/loader/issue-32553/input.js @@ -65,6 +64,7 @@ test/development/mcp-server/fixtures/default-template/app/build-error/page.tsx /turbopack/crates/turbopack-tests/tests/execution/turbopack/basic/error/input/broken.js /turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/invalid-export-parse-error/input/invalid-export/broken.js /turbopack/crates/turbopack-tests/tests/execution/turbopack/**/not-compiled.js +/turbopack/crates/turbopack-tests/tests/snapshot/basic/ts-parse-error/input/other.ts /turbopack/crates/turbopack-tests/tests/snapshot/import-meta/cjs/input/mod.cjs /turbopack/crates/turbopack-tests/tests/snapshot/source_maps/* /turbopack/crates/turbopack-tests/tests/**/output* diff --git a/.vscode/settings.json b/.vscode/settings.json index a45a783474347..3049540a75fd3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,7 +16,7 @@ "typescript", "typescriptreact" ], - "eslint.useFlatConfig": false, + "eslint.useFlatConfig": true, // Set Jest runMode to on-demand as otherwise it will start running all tests the first time. // Equivalent to deprecated option "jest.autoRun": "off" "jest.runMode": "on-demand", @@ -62,7 +62,7 @@ "packages/next/src/server/app-render/dynamic-access-async-storage-instance.ts", "packages/next/src/server/app-render/work-async-storage-instance.ts", "packages/next/src/server/app-render/work-unit-async-storage-instance.ts", - "packages/next/src/server/app-render/dev-logs-async-storage-instance.ts", + "packages/next/src/server/app-render/console-async-storage-instance.ts", "packages/next/src/client/components/segment-cache-impl/*" ], // Disable TypeScript surveys. diff --git a/Cargo.lock b/Cargo.lock index d7c93ea14dcc8..6c61e61dba977 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,9 +300,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "ascii" @@ -322,9 +322,9 @@ dependencies = [ [[package]] name = "ast_node" -version = "3.0.4" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a184645bcc6f52d69d8e7639720699c6a99efb711f886e251ed1d16db8dd90e" +checksum = "c4902c7f39335a2390500ee791d6cb1778e742c7b97952497ec81449a5bfa3a7" dependencies = [ "quote", "swc_macros_common", @@ -441,7 +441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f6ca6f0c18c02c2fbfc119df551b8aeb8a385f6d5980f1475ba0255f1e97f1e" dependencies = [ "anyhow", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "itertools 0.10.5", "log", "nom 7.1.3", @@ -451,11 +451,11 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.1" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", ] [[package]] @@ -597,9 +597,9 @@ dependencies = [ [[package]] name = "binding_macros" -version = "40.0.0" +version = "43.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60c146e22990eb01e6675c558e17cffbe44bf6061ab4f3c2f14c6f9fd9badeb6" +checksum = "555840100e29306f39cf0cdd08410e6fafc17ded3452133c4b081d6efc72855b" dependencies = [ "anyhow", "console_error_panic_hook", @@ -685,7 +685,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "cc", "cfg-if", "constant_time_eq", @@ -836,6 +836,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.10.1" @@ -1009,7 +1015,7 @@ dependencies = [ "tracing", "url", "which", - "winreg 0.51.0", + "winreg", ] [[package]] @@ -1063,7 +1069,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -1408,6 +1414,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -2215,9 +2231,9 @@ checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fdeflate" -version = "0.3.0" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] @@ -2234,12 +2250,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "fixedbitset" version = "0.5.7" @@ -2268,6 +2278,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -2508,8 +2524,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata", + "regex-syntax", ] [[package]] @@ -2635,10 +2651,21 @@ checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", "serde", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + [[package]] name = "hdrhistogram" version = "7.5.4" @@ -2807,9 +2834,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -2831,12 +2858,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.6.0" -source = "git+https://github.com/bgw/hyper-rs.git?branch=v1.6.0-with-macos-intel-miscompilation-workaround#ab3544930722e6c270c16d3239dbb1d58f713393" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", @@ -2844,6 +2873,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -2856,7 +2886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.1.0", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "rustls", "rustls-native-certs", @@ -2873,26 +2903,13 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "pin-project-lite", "tokio", "tower-service", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper 0.14.28", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "hyper-tls" version = "0.6.0" @@ -2901,7 +2918,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "native-tls", "tokio", @@ -2915,7 +2932,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "880b8b1c98a5ec2a505c7c90db6d3f6f1f480af5655d9c5b55facc9382a5a5b5" dependencies = [ - "hyper 0.14.28", + "hyper 0.14.32", "pin-project", "tokio", "tokio-tungstenite", @@ -2924,9 +2941,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64 0.22.1", "bytes", @@ -2935,7 +2952,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.6.0", + "hyper 1.7.0", "ipnet", "libc", "percent-encoding", @@ -2948,16 +2965,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -3136,7 +3154,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.9", + "regex-automata", "same-file", "walkdir", "winapi-util", @@ -3144,15 +3162,16 @@ dependencies = [ [[package]] name = "image" -version = "0.25.0" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9b4f005360d32e9325029b38ba47ebd7a56f3316df09249368939562d518645" +checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" dependencies = [ "bytemuck", - "byteorder", + "byteorder-lite", "color_quant", "gif", "image-webp", + "moxcms", "num-traits", "png", "ravif", @@ -3163,19 +3182,19 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.1.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6107a25f04af48ceeb4093eebc9b405ee5a1813a0bab5ecf1805d3eabb3337" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" dependencies = [ - "byteorder", - "thiserror 1.0.69", + "byteorder-lite", + "quick-error", ] [[package]] name = "imgref" -version = "1.10.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" [[package]] name = "include_dir" @@ -3778,11 +3797,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.10.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03f1160296536f10c833a82dca22267d5486734230d47bf00bf435885814ba1e" +checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" dependencies = [ - "hashbrown 0.13.2", + "hashbrown 0.16.0", ] [[package]] @@ -3854,15 +3873,6 @@ dependencies = [ "zerocopy-derive", ] -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "managed" version = "0.8.0" @@ -3880,11 +3890,11 @@ dependencies = [ [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -3911,7 +3921,7 @@ dependencies = [ [[package]] name = "mdxjs" version = "1.0.4" -source = "git+https://github.com/mischnic/mdxjs-rs.git?branch=swc-core-32#1c774ac2df2c2eeaea0d81ba18675a1973f0ecea" +source = "git+https://github.com/mischnic/mdxjs-rs.git?branch=swc-core-32#cbeff4d812882805e581aa8245f0189090ab1336" dependencies = [ "markdown", "rustc-hash 2.1.1", @@ -4015,7 +4025,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", - "simd-adler32", ] [[package]] @@ -4025,6 +4034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -4073,7 +4083,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "log", "rand 0.9.0", @@ -4086,9 +4096,9 @@ dependencies = [ [[package]] name = "modularize_imports" -version = "0.97.0" +version = "0.99.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524a04bee752a8900bc582e7c4744a107fb63087bac2e87ae4d1cb027d914647" +checksum = "9bd567f0495a231885cb79c0a0314c4b5afe88f0a232e36afa816c829bb45477" dependencies = [ "convert_case", "handlebars", @@ -4103,16 +4113,20 @@ dependencies = [ ] [[package]] -name = "mopa" +name = "more-asserts" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a785740271256c230f57462d3b83e52f998433a7062fc18f96d5999474a9f915" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] -name = "more-asserts" -version = "0.2.2" +name = "moxcms" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +checksum = "c588e11a3082784af229e23e8e4ecf5bcc6fbe4f69101e0421ce8d79da7f0b40" +dependencies = [ + "num-traits", + "pxfm", +] [[package]] name = "munge" @@ -4208,7 +4222,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.8.2", "security-framework-sys", "tempfile", ] @@ -4239,6 +4253,7 @@ name = "next-api" version = "0.1.0" dependencies = [ "anyhow", + "byteorder", "either", "futures", "indexmap 2.9.0", @@ -4260,6 +4275,7 @@ dependencies = [ "turbo-tasks-malloc", "turbo-unix-path", "turbopack", + "turbopack-analyze", "turbopack-browser", "turbopack-core", "turbopack-ecmascript", @@ -4398,6 +4414,7 @@ dependencies = [ "console-subscriber", "dhat", "either", + "flate2", "futures-util", "getrandom 0.2.15", "iana-time-zone", @@ -4438,6 +4455,7 @@ dependencies = [ "url", "urlencoding", "vergen-gitcl", + "windows-sys 0.60.2", ] [[package]] @@ -4535,12 +4553,11 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.60.2", ] [[package]] @@ -4578,7 +4595,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "itoa", ] @@ -4655,12 +4672,28 @@ dependencies = [ ] [[package]] -name = "objc" -version = "0.2.7" +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "malloc_buf", + "bitflags 2.9.1", + "objc2", ] [[package]] @@ -4726,9 +4759,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" @@ -4779,12 +4812,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "owo-colors" version = "4.2.2" @@ -4882,7 +4909,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -4977,24 +5004,25 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ - "fixedbitset 0.4.2", - "indexmap 1.9.3", - "serde", - "serde_derive", + "fixedbitset", + "indexmap 2.9.0", ] [[package]] name = "petgraph" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ - "fixedbitset 0.5.7", + "fixedbitset", + "hashbrown 0.15.4", "indexmap 2.9.0", + "serde", + "serde_derive", ] [[package]] @@ -5128,15 +5156,15 @@ dependencies = [ [[package]] name = "png" -version = "0.17.8" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.1", + "miniz_oxide 0.8.2", ] [[package]] @@ -5424,6 +5452,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "pxfm" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" +dependencies = [ + "num-traits", +] + [[package]] name = "qfilter" version = "0.2.4" @@ -5621,7 +5658,7 @@ checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" dependencies = [ "arbitrary", "arg_enum_proc_macro", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "av1-grain", "bitstream-io", "built", @@ -5650,9 +5687,9 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.10" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" dependencies = [ "avif-serialize", "imgref", @@ -5662,12 +5699,6 @@ dependencies = [ "rgb", ] -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - [[package]] name = "rayon" version = "1.10.0" @@ -5690,9 +5721,9 @@ dependencies = [ [[package]] name = "react_remove_properties" -version = "0.51.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f13552767f97435513a84e97a53d8361b7f4ad6143bfcab99db258c74080f8" +checksum = "9206185f0bb1feedad3bb74096989add941cf4ea637d866037ddfadccdedff3c" dependencies = [ "serde", "swc_atoms", @@ -5752,17 +5783,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -5773,15 +5795,9 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.5" @@ -5818,9 +5834,9 @@ checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" [[package]] name = "remove_console" -version = "0.52.0" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ee193324f45141f19a8b80a0ad9801c744c9cba4a12b6367dd62a3160cb315" +checksum = "2f6e5cc263bc6d0d9284fe4f6f808470d94098f7bfdac7bdc4b55c2935782626" dependencies = [ "serde", "swc_atoms", @@ -5856,56 +5872,21 @@ checksum = "e3a8614ee435691de62bcffcf4a66d91b3594bf1428a5722e79103249a095690" [[package]] name = "reqwest" -version = "0.11.17" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91" -dependencies = [ - "base64 0.21.4", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.24", - "http 0.2.11", - "http-body 0.4.5", - "hyper 0.14.28", - "hyper-tls 0.5.0", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg 0.10.1", -] - -[[package]] -name = "reqwest" -version = "0.12.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", + "futures-channel", "futures-core", + "futures-util", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-rustls", - "hyper-tls 0.6.0", + "hyper-tls", "hyper-util", "js-sys", "log", @@ -5938,9 +5919,6 @@ name = "rgb" version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -dependencies = [ - "bytemuck", -] [[package]] name = "ring" @@ -6142,9 +6120,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" dependencies = [ "once_cell", "ring", @@ -6156,40 +6134,31 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" dependencies = [ "openssl-probe", - "rustls-pemfile", "rustls-pki-types", "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", + "security-framework 3.5.1", ] [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", @@ -6341,7 +6310,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -6349,9 +6331,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -6376,7 +6358,7 @@ dependencies = [ name = "send-trace-to-jaeger" version = "0.1.0" dependencies = [ - "reqwest 0.11.17", + "reqwest", "serde_json", ] @@ -6514,16 +6496,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "serde_regex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" -dependencies = [ - "regex", - "serde", -] - [[package]] name = "serde_repr" version = "0.1.20" @@ -6842,7 +6814,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f352d5d14be5a1f956d76ae0c8060c3487aaa2a080f10a4b4ff023c7c05a9047" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "static-map-macro", ] @@ -6940,9 +6912,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "styled_components" -version = "0.125.0" +version = "0.127.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c6e2cd95d46f8e6725e210765c27e649461c419c5bf455c68e995a8cb56d49" +checksum = "6994d5b6ba60327ccb08a9e4ef814c3a4660cf0dda7092a286d5db323393808c" dependencies = [ "Inflector", "once_cell", @@ -6959,9 +6931,9 @@ dependencies = [ [[package]] name = "styled_jsx" -version = "0.101.0" +version = "0.103.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b2b624c7f1acf24930cea5b9368e14b6857fec4f19452a032a7a1e819f8f78" +checksum = "513c153e0fba36b0fd4e04ca1d60617de172e7c38d18a9bd680484650d2de8de" dependencies = [ "anyhow", "lightningcss", @@ -7003,9 +6975,9 @@ checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" [[package]] name = "swc" -version = "40.0.0" +version = "43.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de6f9d757142d1378ba4a9fd0a7fef3a8f636502f3e0cc1cb51cf91e7c08bfcf" +checksum = "37aa6523fc9d76cbc14f5abafdf326860c5ad0b1195c1b11a5dd66623745ff43" dependencies = [ "anyhow", "base64 0.22.1", @@ -7109,9 +7081,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "14.0.4" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2bb772b3a26b8b71d4e8c112ced5b5867be2266364b58517407a270328a2696" +checksum = "13be0317490fc330a53ee9e64b26891503c35336b509eb69c4fc1dc4e0119ff9" dependencies = [ "anyhow", "ast_node", @@ -7142,9 +7114,9 @@ dependencies = [ [[package]] name = "swc_compiler_base" -version = "35.0.0" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d067cb3d44993f0953120a6048d7157a48fa7914f86ee1b98f6da41891e5b4" +checksum = "17c391731b37a8dc4ec47824b03a11cd835309250ac705c109a78ee75be727eb" dependencies = [ "anyhow", "base64 0.22.1", @@ -7201,9 +7173,9 @@ dependencies = [ [[package]] name = "swc_core" -version = "42.0.3" +version = "45.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5b0d7a8321f395373710f9d61766788c77704b1e532fcea34fe96cb4930422" +checksum = "c765366589d67d0883250b7aa66997da5de2cefd9081ee6ec881d50117b6e6a5" dependencies = [ "binding_macros", "par-core", @@ -7236,9 +7208,9 @@ dependencies = [ [[package]] name = "swc_css_ast" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fd83077e8da39e58c378ac25003ada5d1931b6823079aa2d48503f5166668d" +checksum = "145b3ee43b11a92a13bb319c66fed9e34d5953ef4be67e5667075aae68a394c7" dependencies = [ "is-macro", "string_enum", @@ -7248,9 +7220,9 @@ dependencies = [ [[package]] name = "swc_css_codegen" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddaef5c00a714a3c9348fc94fc4b6c755f689a1c36b4ec190870fadf240261aa" +checksum = "d385256478953a45a010fd759368ad4b9162f6f601d6e200e32024b6a879f2e9" dependencies = [ "auto_impl", "bitflags 2.9.1", @@ -7275,9 +7247,9 @@ dependencies = [ [[package]] name = "swc_css_compat" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "125f8be73b6f44e3b635dbfd93b9909680306a24e11216bd958ae438bc18c15b" +checksum = "4428f93e54915a676a22f8b04fcc19a77e3e35a5a8609635874d263a1227e22c" dependencies = [ "bitflags 2.9.1", "serde", @@ -7290,9 +7262,9 @@ dependencies = [ [[package]] name = "swc_css_minifier" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37838c088246ec2ca479b952b8168095178adb8eec0922bf055ba76275f387a7" +checksum = "d068270dd9b8f7f432ba8cc24fb51a7ebc0fd254a57f5cfc4f5a82f74e6ebcaf" dependencies = [ "rustc-hash 2.1.1", "serde", @@ -7305,9 +7277,9 @@ dependencies = [ [[package]] name = "swc_css_parser" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a87cd0258c1c28c592fe738d44e7ad4c7535d48c39df8546225747bdecc982fa" +checksum = "fa8e681f9c66208b1ec98e6f69cd068effaa7f0797581d11b88c772a2c312368" dependencies = [ "lexical", "serde", @@ -7318,9 +7290,9 @@ dependencies = [ [[package]] name = "swc_css_prefixer" -version = "17.0.0" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de1d974a1263f9a663fe614886a3409b8b60112ae0e1048093aa30c0acc71e22" +checksum = "9bcaacf911f12c0c946ea394b927715202ece653536a89628463b3498ae76c36" dependencies = [ "once_cell", "preset_env_base", @@ -7336,9 +7308,9 @@ dependencies = [ [[package]] name = "swc_css_utils" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd30a941e3c6edc245c5119344e4126e7e21a5a01a10496ff65db3bffeca226" +checksum = "bc7bd6d3ecd934a0ee3d057f78628cc1f058b4f13bed963e51112d85c86c9ebf" dependencies = [ "once_cell", "rustc-hash 2.1.1", @@ -7351,9 +7323,9 @@ dependencies = [ [[package]] name = "swc_css_visit" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92be458e0c9f037d928c3c2ec75a6cb41a68f77bdec4add1110f69a0e85cd70a" +checksum = "e023a02a0fd04354415d6dcdfcb39d1a37d35873732f801f7d2b67576500d96a" dependencies = [ "serde", "swc_atoms", @@ -7364,9 +7336,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "15.0.0" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c25af97d53cf8aab66a6c68f3418663313fc969ad267fc2a4d19402c329be1" +checksum = "add9298e06af471f29aea2f8d1b6232885bd2a634521d0e95dc9d5bde3d39d3d" dependencies = [ "bitflags 2.9.1", "bytecheck 0.8.1", @@ -7388,9 +7360,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "17.0.2" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf55c2d7555c93f4945e29f93b7529562be97ba16e60dd94c25724d746174ac" +checksum = "c70760095f23e70a295bc86dd52d454f5de4050621c9e27681ae26d166b77be4" dependencies = [ "ascii", "compact_str", @@ -7423,9 +7395,9 @@ dependencies = [ [[package]] name = "swc_ecma_compat_bugfixes" -version = "28.0.0" +version = "31.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0809b7e5a20d31ddaf051a19ea467378d3ea1e9ecee7f7e1866c192c781f00e1" +checksum = "ce408152ee3f1119ec91192381d7fa65dc65715ac52444fda51a74c29f62e8e7" dependencies = [ "rustc-hash 2.1.1", "swc_atoms", @@ -7441,9 +7413,9 @@ dependencies = [ [[package]] name = "swc_ecma_compat_common" -version = "21.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2949ac4924597be747348639eadedf8e54818fb26641f050d3d78361b15d1e0d" +checksum = "26c8e4400709546f3b62b23c3f777be41a068a972a153bab779f4976b9545c14" dependencies = [ "swc_common", "swc_ecma_ast", @@ -7453,11 +7425,11 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2015" -version = "28.0.1" +version = "31.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463a43bb2350ec3e68692d7e5f786ecf07382d057972dd5cc4fe02c9239fc5f3" +checksum = "c9d0917a1eca37bc184540d64685ce066201b96c9eb2c50f38c69ce296c2c686" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "indexmap 2.9.0", "is-macro", "rustc-hash 2.1.1", @@ -7480,9 +7452,9 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2016" -version = "26.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d08be3aaea9e0cb603a00b958f78c6149ce6fc98d0d9622935821a8dd2a99b" +checksum = "ead70453d65d02ce1d283c0159408dc4e5dd9a7b4635f2a3cc94d7346fdcf74a" dependencies = [ "swc_common", "swc_ecma_ast", @@ -7496,9 +7468,9 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2017" -version = "26.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b68fc5c6237cdb8bb450672443cd640c2acbc84edc3d097349db33de0051668" +checksum = "4c4549bc226e0f359d6906a4b67dcb1f28d377e38976fb959f2297dd3a9eb38f" dependencies = [ "serde", "swc_common", @@ -7512,9 +7484,9 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2018" -version = "26.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de471037ff0e178a678a852d232206049578dab258b4e4abc57a677f2d8322d" +checksum = "edc24a4b66caa22afcf5b42464f302675a38ce09a1e2eea0e0374c36923adbe0" dependencies = [ "serde", "swc_common", @@ -7530,9 +7502,9 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2019" -version = "26.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5cc26969456801ee879a9b79d69b82ddf3ac8ecd0c601d9960f867d3f91a7c" +checksum = "129658fc4634c8ad08cb039ddbc3ce3689aa269548cb8bac8f7b838c1ba5e953" dependencies = [ "swc_common", "swc_ecma_ast", @@ -7545,9 +7517,9 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2020" -version = "27.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ffd86caa05bc410105d05afe0c2fda17cb85ccba82d08fa72250d686a1ad4a3" +checksum = "a4cbb6cbba95100f8db2c0317cb8989ce58fc570bd4e1c9da204e52ac0b2d366" dependencies = [ "serde", "swc_common", @@ -7562,9 +7534,9 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2021" -version = "26.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b9c2e5183b794675e84c0543fe62a3ec3353bf461dd5b1a0e9396c1ef85101" +checksum = "c36529cd59e0855608b031fb6fe8b1d35f585fb5d361e97444763cc1386442fb" dependencies = [ "swc_ecma_ast", "swc_ecma_compiler", @@ -7575,9 +7547,9 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2022" -version = "27.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251f6791226538ac992067316e108b49c90e241e7eb33bc5632d6b0d08c20fd8" +checksum = "714b9b562308c78d60f9776a1b80160da2157968943481ff5c2d9c5982d4e951" dependencies = [ "rustc-hash 2.1.1", "swc_atoms", @@ -7596,9 +7568,9 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es3" -version = "22.0.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248256b4708793bc05ddd67a3e5f5096fcb10349ffb147697bddd368311928f3" +checksum = "e2e151f8a93d6ddba163ec700a25737089b207d4cd86b8916a0e750c52379576" dependencies = [ "swc_common", "swc_ecma_ast", @@ -7610,9 +7582,9 @@ dependencies = [ [[package]] name = "swc_ecma_compiler" -version = "4.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e2c5abb053281fa1dd99f4ce1e4c062bb18fed4cc24a2eada80d4160212e28" +checksum = "80f09a1096f454cc4685f75040ae90a7ea6d25aad7b4d6c3b6a947c595d8b23c" dependencies = [ "bitflags 2.9.1", "rustc-hash 2.1.1", @@ -7628,9 +7600,9 @@ dependencies = [ [[package]] name = "swc_ecma_ext_transforms" -version = "21.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf730dc1404ebc2fc6ccbb9e0aa5f25b3a89bd477f8ca79d4fe8257eb0c87742" +checksum = "5bf4fdcbb1431079b5c0cc008715e7e4856dad739754dd985e4aa9e0b6ffd857" dependencies = [ "phf", "swc_common", @@ -7641,11 +7613,11 @@ dependencies = [ [[package]] name = "swc_ecma_lexer" -version = "23.0.1" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c3bd958a5a67e2cc3f74abdd41fda688e54e7a25b866569260ef7018b67972" +checksum = "dfc7dca92e2239f09d177999b0fae2efe6e68a51eae1d9bae85b2efa78537924" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bitflags 2.9.1", "either", "num-bigint", @@ -7664,9 +7636,9 @@ dependencies = [ [[package]] name = "swc_ecma_lints" -version = "22.0.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80afc984b0863fdd058e2c1723530b43ea236001cd1a197cda888a241b801fbc" +checksum = "58f4eb9e2ee9676e6986194320d97dd28466db6d0852cadd9669e7ea5a687196" dependencies = [ "auto_impl", "dashmap 5.5.3", @@ -7685,9 +7657,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c675d14700c92f12585049b22b02356f1e142f4b0c32a4d0eb4b7a968a4c0c1e" +checksum = "5e63f947d3263a0a3f41b6dd4d3fceac3788268a7fac9888f2d78ac9c71cf676" dependencies = [ "anyhow", "dashmap 5.5.3", @@ -7707,11 +7679,11 @@ dependencies = [ [[package]] name = "swc_ecma_minifier" -version = "32.0.5" +version = "34.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80500b64911bb710aed7a0fa97e38abf8b7937426af133c27631c72ce22129ae" +checksum = "977ee617c78a4ace62abc8733222f6c3c51c6a9000c074a567b361377f6b756c" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bitflags 2.9.1", "indexmap 2.9.0", "num-bigint", @@ -7743,9 +7715,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "24.0.1" +version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8079e65c43d8f3e64e791321355f5864322425fce3a3ab7fc959bbddb531933" +checksum = "c8a43a89976cdbb42f152cfbd89a430b1fb693a9e0f2b5e2b1959466a88c3de0" dependencies = [ "either", "num-bigint", @@ -7759,12 +7731,12 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "34.0.0" +version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d8c89878e98728a1213843f627bf7e756dcf22b8db792c6278668b613e18e32" +checksum = "f823c2a9ff3576ba2b8bf498e371eb6e86ffdeeaa98ab151a1f9eaa589f4e360" dependencies = [ "anyhow", - "foldhash", + "foldhash 0.1.5", "indexmap 2.9.0", "once_cell", "precomputed-map", @@ -7784,9 +7756,9 @@ dependencies = [ [[package]] name = "swc_ecma_quote_macros" -version = "24.0.0" +version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c8c018ebafab9285b7e3dfd757f28c40345e2dfade4566cf3cd3da81fbd2963" +checksum = "d3b83953c928934f0b0b242c0bbda51a5895fec8c55409b3073c5fe851e11177" dependencies = [ "anyhow", "proc-macro2", @@ -7802,9 +7774,9 @@ dependencies = [ [[package]] name = "swc_ecma_testing" -version = "15.0.0" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd297865d417cf7e99bf36f4f29928e89ccf3446b56440e110b2f488f6d8d2d0" +checksum = "464be50bb5a907f43cf3559ccecec38bd69f91b580bcdbda9a65a35d8627b7f3" dependencies = [ "anyhow", "hex", @@ -7815,9 +7787,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "33.0.0" +version = "36.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17aad69f947105eae180fecbf17c5e494a962787c037e6facf1f50895b806b4a" +checksum = "932e4ff767145e75a64a837b589c87ad4abd133eef37abd9ca095fbd0721f6f4" dependencies = [ "par-core", "swc_common", @@ -7834,9 +7806,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "26.0.1" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0526b4e3d6cedb7e48c5026242809387676f836d4251235fa95165218bb8ce4" +checksum = "f72ecdabae8bccf20d9d030c787e9f2b7dbeddef3282c4732f5a28494d814764" dependencies = [ "better_scoped_tls", "indexmap 2.9.0", @@ -7856,9 +7828,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "26.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ad4c8c59a000e0bd587f94afb51eb9caad6a42d07f41b75c56d8bf6276e1bae" +checksum = "552e3f1541e74eef1d9c30c7eeeb0c5716a7655f3a2361f822d9c53291ddb98b" dependencies = [ "swc_common", "swc_ecma_ast", @@ -7869,9 +7841,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "29.0.0" +version = "32.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aed6ee500834a62375aede89f45404b95cd25b08418f6869eac8804bc98dbf47" +checksum = "d3a1edea2695a75dee6acf54eb4c32a91e4b28843bde8bbdc47fc928ae005440" dependencies = [ "indexmap 2.9.0", "par-core", @@ -7910,9 +7882,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "29.0.0" +version = "31.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4399b34a7d2c6b289252b1b271af1e79810aed7a06ef3b328de7534fd7a65f5" +checksum = "86153dade5ae62dc05e42a7298cb53ed13c2b61a907e6a2c5c1ed8d414f7eb3c" dependencies = [ "Inflector", "anyhow", @@ -7938,9 +7910,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "28.0.0" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb86ae16f150aa4fbc46bd37d6cce44612af59861afa987ab3053f17d343b1" +checksum = "b572cc5ef6532d006cdf5365b50802dfa94b030676be4164939f34c75b88dc9d" dependencies = [ "bytes-str", "dashmap 5.5.3", @@ -7962,9 +7934,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "26.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cd9f54f3e7b3efb0e30e80f9efeaf99cd4d66ff0b83fda6dcfcbc0e293a767" +checksum = "8ae20a71fad49a8e2784bacd146328c435854160ad2c7428529d5fa9d740acb6" dependencies = [ "either", "rustc-hash 2.1.1", @@ -7980,9 +7952,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "29.0.0" +version = "31.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9939e0a5a23529b63ac87d7a9981dba7f7021b7cb64ecf9039f3dfb0abb48c" +checksum = "46abf63b0ee1dd746d9069ce1eeacc257ff1ef514619acd69db06279669e58bd" dependencies = [ "base64 0.22.1", "bytes-str", @@ -8004,9 +7976,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_testing" -version = "29.0.0" +version = "31.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b394293ee1a70c986aac9282d606cfb05d0ee81f7f48958e3975e33886bd8745" +checksum = "ba723011d5a35faa99e61ca0de21f6c61c3449423b3709d7dce52b7ac4d10aa4" dependencies = [ "ansi_term", "anyhow", @@ -8030,9 +8002,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "29.0.0" +version = "31.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52079079848d95fdfe3634d06b40bdb47865ffbedd9b3c2cf63a8d91dec5eebf" +checksum = "2608b63e08955f0795de7e1dd1ab8b6e95f722fbc2eeb6902132c1702f61c131" dependencies = [ "bytes-str", "rustc-hash 2.1.1", @@ -8048,9 +8020,9 @@ dependencies = [ [[package]] name = "swc_ecma_usage_analyzer" -version = "22.0.1" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8031a4473e5366165f23766f5bc8361c45e8ed57f7475c0227147727cbaf3342" +checksum = "5e4faf08438c2ba8f48732eccc4630cf4ef7e0b998df8ba0ab20e1a8bebc05d2" dependencies = [ "bitflags 2.9.1", "indexmap 2.9.0", @@ -8066,9 +8038,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "21.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83259addd99ed4022aa9fc4d39428c008d3d42533769e1a005529da18cde4568" +checksum = "110befba503dcec034023f4ad3559b84991c7a4be977287300db9562955bda1d" dependencies = [ "indexmap 2.9.0", "num_cpus", @@ -8085,9 +8057,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "15.0.0" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a579aa8f9e212af521588df720ccead079c09fe5c8f61007cf724324aed3a0" +checksum = "e8763b91f52a54d5836c1f922dd393b157d47c121c7aab87308f582daf345f77" dependencies = [ "new_debug_unreachable", "num-bigint", @@ -8101,9 +8073,9 @@ dependencies = [ [[package]] name = "swc_emotion" -version = "0.101.0" +version = "0.103.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507e31b72fa6a56343380b48dc82544ada9dca859c21659437f016377e5dff6" +checksum = "fb43c7ccc87825b8b520962e8fa777808d8e4198b57af164fa920495b0f2187a" dependencies = [ "base64 0.22.1", "byteorder", @@ -8138,9 +8110,9 @@ dependencies = [ [[package]] name = "swc_error_reporters" -version = "16.0.1" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a16e3c08fd820735631820a7c220d5ce39bdc08b83eddbc73a645ef744511e" +checksum = "61f991c703dea8982ebfb955a0b1c35fdfe551b24d8d13039c5a0b66e381edbd" dependencies = [ "anyhow", "miette", @@ -8162,9 +8134,9 @@ dependencies = [ [[package]] name = "swc_node_comments" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf07db306bc7e19b8fc46702e8298419d12f587bd4724858bc9889fef8f3e72" +checksum = "8b71569a4f63edcf31b34c261a6062ec3ad8f1a7b5d3838e8b570f2b2a055a8f" dependencies = [ "dashmap 5.5.3", "rustc-hash 2.1.1", @@ -8174,9 +8146,9 @@ dependencies = [ [[package]] name = "swc_plugin_backend_wasmer" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d888829d5c88ed444eea30e63b18155fb0b4197bb6e43e6c50439bb5ad220a4b" +checksum = "3ee6799f2d74b7e12c7d5a69c434cb872bf7163e40affda2cb8147039e6e7fdb" dependencies = [ "anyhow", "enumset", @@ -8201,9 +8173,9 @@ dependencies = [ [[package]] name = "swc_plugin_proxy" -version = "15.0.0" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e78029030baf942203f11eae0ea47c07367d167060ba4c55a202a1341366c5" +checksum = "ef02609fd5dc946a13448833f8443173f45ff1477eb7e3683c0abb03018cdf20" dependencies = [ "better_scoped_tls", "bytecheck 0.8.1", @@ -8218,9 +8190,9 @@ dependencies = [ [[package]] name = "swc_plugin_runner" -version = "19.0.0" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b41ddf0ac2c0386802ca7646c8ae77bbdbe65ba32d33da0f3bf9e82430abc2" +checksum = "44d593f644b9803819cf40934aa6217aeafc787417eb91434cea7a1ee00bbca2" dependencies = [ "anyhow", "blake3", @@ -8239,9 +8211,9 @@ dependencies = [ [[package]] name = "swc_relay" -version = "0.71.0" +version = "0.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0085318ecea79ab708e28a8a0693856681f10491a375fa20f5de4f89be758cb" +checksum = "d82d6f54d53eb65b910f757f212053d891416c403dfd013dc2278d68ff29b6d0" dependencies = [ "once_cell", "regex", @@ -8295,9 +8267,9 @@ dependencies = [ [[package]] name = "swc_transform_common" -version = "8.0.0" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca33f282df60eefee05511c9aaf557696d2f9f0e22f4a5abca318da10c22f1cc" +checksum = "2b0ccbb3fe8a93a2bbe6df94c205a0ca909bb8786705031d355904efbb89a2a3" dependencies = [ "better_scoped_tls", "rustc-hash 2.1.1", @@ -8307,9 +8279,9 @@ dependencies = [ [[package]] name = "swc_typescript" -version = "20.0.0" +version = "21.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "118f743b98153dca6db0f68602523faebe25510e7d5ad4b1452f7e5d39e038a7" +checksum = "9ef4d6eace0eddb82337005a1fb9330f0eb5940b7f8211d7adeabf89bbe80098" dependencies = [ "bitflags 2.9.1", "petgraph 0.7.1", @@ -8499,9 +8471,9 @@ dependencies = [ [[package]] name = "testing" -version = "15.0.0" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb14720ff995a98916e7fafc6771242727ed1ac5f2725059f03f203586d8ca1b" +checksum = "59f0f5f27bd9f0c9429a9330a223bc654804dcd9797f3414a491ac5190bf3cbc" dependencies = [ "cargo_metadata 0.18.1", "difference", @@ -8853,7 +8825,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-timeout", "hyper-util", "percent-encoding", @@ -8935,9 +8907,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -8946,9 +8918,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -8968,9 +8940,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -8989,9 +8961,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", @@ -8999,14 +8971,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "serde", "serde_json", "sharded-slab", @@ -9221,7 +9193,6 @@ dependencies = [ "futures", "indexmap 2.9.0", "inventory", - "mopa", "once_cell", "parking_lot", "pin-project-lite", @@ -9230,7 +9201,6 @@ dependencies = [ "rustc-hash 2.1.1", "serde", "serde_json", - "serde_regex", "shrink-to-fit", "smallvec", "thiserror 1.0.69", @@ -9332,7 +9302,8 @@ dependencies = [ "anyhow", "mockito", "quick_cache", - "reqwest 0.12.22", + "reqwest", + "rustls", "serde", "tokio", "turbo-rcstr", @@ -9500,6 +9471,23 @@ dependencies = [ "turbopack-wasm", ] +[[package]] +name = "turbopack-analyze" +version = "0.1.0" +dependencies = [ + "anyhow", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "tokio", + "turbo-rcstr", + "turbo-tasks", + "turbo-tasks-backend", + "turbo-tasks-fs", + "turbo-tasks-testing", + "turbopack-core", +] + [[package]] name = "turbopack-bench" version = "0.1.0" @@ -9572,6 +9560,7 @@ dependencies = [ "turbo-tasks-env", "turbo-tasks-fs", "turbo-tasks-malloc", + "turbo-unix-path", "turbopack", "turbopack-bench", "turbopack-browser", @@ -9619,7 +9608,7 @@ dependencies = [ "indexmap 2.9.0", "once_cell", "patricia_tree", - "petgraph 0.6.3", + "petgraph 0.8.3", "ref-cast", "regex", "roaring", @@ -9691,7 +9680,7 @@ dependencies = [ "async-compression", "auto-hash-map", "futures", - "hyper 0.14.28", + "hyper 0.14.32", "hyper-tungstenite", "mime", "mime_guess", @@ -9738,7 +9727,7 @@ dependencies = [ "num-traits", "once_cell", "parking_lot", - "petgraph 0.6.3", + "petgraph 0.8.3", "regex", "rustc-hash 2.1.1", "serde", @@ -11089,17 +11078,16 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.15" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" +checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" dependencies = [ - "core-foundation", - "home", + "core-foundation 0.10.1", "jni", "log", "ndk-context", - "objc", - "raw-window-handle", + "objc2", + "objc2-foundation", "url", "web-sys", ] @@ -11191,12 +11179,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-targets 0.48.1", + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -11205,6 +11219,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -11235,7 +11273,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -11262,7 +11300,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.5", ] [[package]] @@ -11282,17 +11320,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -11313,10 +11351,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -11335,9 +11374,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" @@ -11359,9 +11398,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" @@ -11383,9 +11422,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" @@ -11419,9 +11458,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" @@ -11443,9 +11482,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" @@ -11467,9 +11506,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" @@ -11491,9 +11530,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" @@ -11525,15 +11564,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - [[package]] name = "winreg" version = "0.51.0" @@ -11759,9 +11789,9 @@ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-jpeg" -version = "0.4.11" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 63efe02ed915a..6b358486375dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -248,12 +248,41 @@ opt-level = 3 [profile.release.package.serde] opt-level = 3 +[profile.release.package.wasmer] +opt-level = "s" + +[profile.release.package.wasmer-vm] +opt-level = "s" + +[profile.release.package.wasmer-compiler-cranelift] +opt-level = "s" + +[profile.release.package.regalloc2] +opt-level = "s" + +[profile.release.package.swc_plugin_backend_wasmer] +opt-level = "s" + +[profile.release.package.globset] +opt-level = "s" + +[profile.release.package.toml_edit] +opt-level = "s" + +[profile.release.package.miette] +opt-level = "s" + + # Use a custom profile for CI where many tests are performance sensitive but we still want the additional validation of debug-assertions [profile.release-with-assertions] inherits = "release" debug-assertions = true overflow-checks = true +[profile.release-with-debug] +inherits = "release" +debug = true + [workspace.dependencies] # Workspace crates next-api = { path = "crates/next-api" } @@ -288,6 +317,7 @@ turbopack-cli-utils = { path = "turbopack/crates/turbopack-cli-utils" } turbopack-core = { path = "turbopack/crates/turbopack-core" } turbopack-create-test-app = { path = "turbopack/crates/turbopack-create-test-app" } turbopack-css = { path = "turbopack/crates/turbopack-css" } +turbopack-analyze = { path = "turbopack/crates/turbopack-analyze" } turbopack-browser = { path = "turbopack/crates/turbopack-browser" } turbopack-dev-server = { path = "turbopack/crates/turbopack-dev-server" } turbopack-ecmascript = { path = "turbopack/crates/turbopack-ecmascript" } @@ -308,24 +338,24 @@ turbopack-trace-utils = { path = "turbopack/crates/turbopack-trace-utils" } turbopack-wasm = { path = "turbopack/crates/turbopack-wasm" } # SWC crates -swc_core = { version = "42.0.3", features = [ +swc_core = { version = "45.0.1", features = [ "ecma_loader_lru", "ecma_loader_parking_lot", "parallel_rayon", ] } -swc_plugin_backend_wasmer = { version = "2.0.0" } -testing = "15.0.0" +swc_plugin_backend_wasmer = { version = "3.0.0" } +testing = "16.0.0" # Keep consistent with preset_env_base through swc_core browserslist-rs = "0.19.0" mdxjs = "1.0.3" -modularize_imports = "0.97.0" -styled_components = "0.125.0" -styled_jsx = "0.101.0" -swc_emotion = "0.101.0" -swc_relay = "0.71.0" -react_remove_properties = "0.51.0" -remove_console = "0.52.0" +modularize_imports = "0.99.0" +styled_components = "0.127.0" +styled_jsx = "0.103.0" +swc_emotion = "0.103.0" +swc_relay = "0.73.0" +react_remove_properties = "0.53.0" +remove_console = "0.54.0" preset_env_base = "5.0.0" @@ -348,6 +378,7 @@ async-compression = { version = "0.3.13", default-features = false, features = [ ] } async-trait = "0.1.64" bitfield = "0.18.0" +byteorder = "1.5.0" bytes = "1.1.0" bytes-str = "0.2.7" chrono = "0.4.23" @@ -363,11 +394,12 @@ dhat = { version = "0.3.2" } dunce = "1.0.3" either = "1.9.0" erased-serde = "0.4.5" +flate2 = "1.0.28" futures = "0.3.31" futures-util = "0.3.31" futures-retry = "0.6.0" hashbrown = "0.14.5" -image = { version = "0.25.0", default-features = false } +image = { version = "0.25.8", default-features = false } indexmap = "2.7.1" indoc = "2.0.0" itertools = "0.10.5" @@ -398,7 +430,7 @@ owo-colors = "4.2.2" parcel_selectors = "0.28.2" parking_lot = "0.12.1" pathdiff = "0.2.1" -petgraph = "0.6.3" +petgraph = "0.8.3" pin-project-lite = "0.2.9" postcard = "1.0.4" proc-macro2 = "1.0.79" @@ -409,7 +441,7 @@ rand = "0.9.0" rayon = "1.10.0" regex = "1.10.6" regress = "0.10.4" -reqwest = { version = "0.12.22", default-features = false } +reqwest = { version = "0.12.24", default-features = false } ringmap = "0.1.3" roaring = "0.10.10" rstest = "0.16.0" @@ -446,9 +478,8 @@ urlencoding = "2.1.2" uuid = "1.18.1" vergen = { version = "9.0.6", features = ["cargo"] } vergen-gitcl = { version = "1.0.8", features = ["cargo"] } -webbrowser = "0.8.7" +webbrowser = "1.0.6" inventory = "0.3.21" [patch.crates-io] -hyper = { git = "https://github.com/bgw/hyper-rs.git", branch = "v1.6.0-with-macos-intel-miscompilation-workaround" } mdxjs = { git = "https://github.com/mischnic/mdxjs-rs.git", branch = "swc-core-32" } diff --git a/apps/docs/app/page.tsx b/apps/docs/app/page.tsx index e88fc2290af99..2348f6dca76b5 100644 --- a/apps/docs/app/page.tsx +++ b/apps/docs/app/page.tsx @@ -1,34 +1,42 @@ import Image from 'next/image' -import Link from 'next/link' export default function Home() { return ( -
-
+
+
Next.js logo -
    -
  1. - Get started by editing{' '} - - app/page.tsx - - . -
  2. -
  3. - Save and see your changes instantly. -
  4. -
- -
+
+

+ To get started, edit the page.tsx file. +

+

+ Looking for a starting point or more instructions? Head over to{' '} + + Templates + {' '} + or the{' '} + + Learning + {' '} + center. +

+
+
- Deploy now + Deploy Now - - Read our docs - + Documentation +
-
) } diff --git a/apps/docs/package.json b/apps/docs/package.json index f887dd88ebc49..a93df5d7f18cd 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -23,7 +23,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "eslint": "^9", + "eslint": "9.37.0", "eslint-config-next": "canary", "tailwindcss": "4.1.13", "typescript": "^5" diff --git a/bench/heavy-npm-deps/next.config.mjs b/bench/heavy-npm-deps/next.config.mjs index c0132fd14cc1c..01768cb090ab7 100644 --- a/bench/heavy-npm-deps/next.config.mjs +++ b/bench/heavy-npm-deps/next.config.mjs @@ -7,8 +7,8 @@ const nextConfig = { ignoreBuildErrors: true, }, experimental: { - turbopackPersistentCachingForDev: process.env.TURBO_CACHE === '1', - turbopackPersistentCachingForBuild: process.env.TURBO_CACHE === '1', + turbopackFileSystemCacheForDev: process.env.TURBO_CACHE === '1', + turbopackFileSystemCacheForBuild: process.env.TURBO_CACHE === '1', }, } diff --git a/bench/heavy-npm-deps/package.json b/bench/heavy-npm-deps/package.json index 2428e307218e8..8bd442a10f8a5 100644 --- a/bench/heavy-npm-deps/package.json +++ b/bench/heavy-npm-deps/package.json @@ -4,9 +4,9 @@ "private": true, "scripts": { "dev-turbopack": "next dev --turbopack", - "dev-webpack": "next dev", + "dev-webpack": "next dev --webpack", "build-turbopack": "next build --turbopack", - "build-webpack": "next build", + "build-webpack": "next build --webpack", "start-turbopack": "next start", "start-webpack": "next start", "build-application": "next build", diff --git a/bench/module-cost/package.json b/bench/module-cost/package.json index eb6e8a7478d92..96814e0e6e875 100644 --- a/bench/module-cost/package.json +++ b/bench/module-cost/package.json @@ -3,10 +3,10 @@ "scripts": { "prepare-bench": "node scripts/prepare-bench.mjs", "benchmark": "node scripts/benchmark-runner.mjs", - "dev-webpack": "next dev", - "dev-turbopack": "next dev --turbo", - "build-webpack": "next build", - "build-turbopack": "next build --turbo", + "dev-webpack": "next dev --webpack", + "dev-turbopack": "next dev --turbopack", + "build-webpack": "next build --webpack", + "build-turbopack": "next build --turbopack", "start": "next start" }, "devDependencies": { diff --git a/bench/recursive-delete/README.md b/bench/recursive-delete/README.md new file mode 100644 index 0000000000000..fef339d428844 --- /dev/null +++ b/bench/recursive-delete/README.md @@ -0,0 +1,7 @@ +# Recursive Delete Benchmark + +```bash +pnpm bench +``` + +Run `pnpm bench --help` for options. diff --git a/bench/recursive-delete/nodejs-rm.js b/bench/recursive-delete/nodejs-rm.js new file mode 100644 index 0000000000000..a0d949a8d7b23 --- /dev/null +++ b/bench/recursive-delete/nodejs-rm.js @@ -0,0 +1,27 @@ +import { rm as rmPromises } from 'fs/promises' +import { rm as rmCallback, rmSync } from 'fs' +import { promisify } from 'util' + +const rmCallbackPromise = promisify(rmCallback) + +const targetDir = process.argv[2] +const method = process.argv[3] // 'promises', 'callback', or 'sync' + +async function test() { + const time = process.hrtime() + + if (method === 'promises') { + await rmPromises(targetDir, { recursive: true, force: true }) + } else if (method === 'callback') { + await rmCallbackPromise(targetDir, { recursive: true, force: true }) + } else if (method === 'sync') { + rmSync(targetDir, { recursive: true, force: true }) + } + + const hrtime = process.hrtime(time) + const nanoseconds = hrtime[0] * 1e9 + hrtime[1] + const milliseconds = nanoseconds / 1e6 + console.log(milliseconds) +} + +test() diff --git a/test/integration/eslint/empty-directory/components/.gitkeep b/bench/recursive-delete/output.txt similarity index 100% rename from test/integration/eslint/empty-directory/components/.gitkeep rename to bench/recursive-delete/output.txt diff --git a/bench/recursive-delete/package.json b/bench/recursive-delete/package.json new file mode 100644 index 0000000000000..68e20c110a5f7 --- /dev/null +++ b/bench/recursive-delete/package.json @@ -0,0 +1,12 @@ +{ + "name": "bench-recursive-delete", + "type": "module", + "scripts": { + "bench": "bash run.sh" + }, + "devDependencies": { + "fuzzponent": "workspace:*", + "next": "workspace:*", + "rimraf": "6.0.1" + } +} diff --git a/bench/recursive-delete/recursive-delete.js b/bench/recursive-delete/recursive-delete.js index e23ed3e6f26a6..2720b8f89fc44 100644 --- a/bench/recursive-delete/recursive-delete.js +++ b/bench/recursive-delete/recursive-delete.js @@ -1,10 +1,10 @@ -import { join } from 'path' -import { recursiveDelete } from 'next/dist/lib/recursive-delete' -const resolveDataDir = join(__dirname, `fixtures-${process.argv[2]}`) +import { recursiveDeleteSyncWithAsyncRetries } from 'next/dist/lib/recursive-delete.js' + +const targetDir = process.argv[2] async function test() { const time = process.hrtime() - await recursiveDelete(resolveDataDir) + await recursiveDeleteSyncWithAsyncRetries(targetDir) const hrtime = process.hrtime(time) const nanoseconds = hrtime[0] * 1e9 + hrtime[1] diff --git a/bench/recursive-delete/rimraf.js b/bench/recursive-delete/rimraf.js index 827cdaae77484..4a17c04ccd36a 100644 --- a/bench/recursive-delete/rimraf.js +++ b/bench/recursive-delete/rimraf.js @@ -1,13 +1,16 @@ -import { join } from 'path' -import { promisify } from 'util' -import rimrafMod from 'rimraf' +import { manual, manualSync } from 'rimraf' -const rimraf = promisify(rimrafMod) -const resolveDataDir = join(__dirname, `fixtures-${process.argv[2]}`, '**/*') +const targetDir = process.argv[2] +const method = process.argv[3] async function test() { const time = process.hrtime() - await rimraf(resolveDataDir) + + if (method === 'sync') { + manualSync(targetDir) + } else { + await manual(targetDir) + } const hrtime = process.hrtime(time) const nanoseconds = hrtime[0] * 1e9 + hrtime[1] diff --git a/bench/recursive-delete/run.sh b/bench/recursive-delete/run.sh index 40a11ea9c5347..170e1f3b894fd 100755 --- a/bench/recursive-delete/run.sh +++ b/bench/recursive-delete/run.sh @@ -1,69 +1,63 @@ -# Uses https://github.com/divmain/fuzzponent -mkdir fixtures-1 -cd fixtures-1 -fuzzponent -d 2 -s 20 > output.txt -cd .. -echo "rimraf 1" -node rimraf.js 1 - -mkdir fixtures-2 -cd fixtures-2 -fuzzponent -d 2 -s 20 > output.txt -cd .. -echo "rimraf 2" -node rimraf.js 2 - -mkdir fixtures-3 -cd fixtures-3 -fuzzponent -d 2 -s 20 > output.txt -cd .. -echo "rimraf 3" -node rimraf.js 3 - -mkdir fixtures-4 -cd fixtures-4 -fuzzponent -d 2 -s 20 > output.txt -cd .. -echo "rimraf 4" -node rimraf.js 4 - -mkdir fixtures-5 -cd fixtures-5 -fuzzponent -d 2 -s 20 > output.txt -cd .. -echo "rimraf 5" -node rimraf.js 5 - -echo "-----------" - -cd fixtures-1 -fuzzponent -d 2 -s 20 > output.txt -cd .. -echo "recursive delete 1" -node recursive-delete.js 1 - -cd fixtures-2 -fuzzponent -d 2 -s 20 > output.txt -cd .. -echo "recursive delete 2" -node recursive-delete.js 2 - -cd fixtures-3 -fuzzponent -d 2 -s 20 > output.txt -cd .. -echo "recursive delete 3" -node recursive-delete.js 3 - -cd fixtures-4 -fuzzponent -d 2 -s 20 > output.txt -cd .. -echo "recursive delete 4" -node recursive-delete.js 4 - -cd fixtures-5 -fuzzponent -d 2 -s 20 > output.txt -cd .. -echo "recursive delete 5" -node recursive-delete.js 5 - -rm -r fixtures-1 fixtures-2 fixtures-3 fixtures-4 fixtures-5 \ No newline at end of file +#!/usr/bin/env bash +set -euo pipefail + +ITERATIONS=5 + +show_help() { + echo "Usage: $(basename "$0") [-i|--iterations N] [-h|--help]" + exit "${1:-0}" +} + +if ! OPTS=$(getopt -o i:h --long iterations:,help -n "$(basename "$0")" -- "$@"); then + show_help 1 +fi +eval set -- "$OPTS" + +while true; do + case "$1" in + -i|--iterations) + ITERATIONS="$2" + shift 2 + ;; + -h|--help) + show_help + ;; + *) + break + ;; + esac +done + +cleanup() { + for i in $(seq 1 "$ITERATIONS"); do + rm -rf "fixtures-$i" + done +} + +trap cleanup EXIT +cleanup + +run_benchmark() { + local name=$1 + local script=$2 + shift 2 + + echo "-----------" + for i in $(seq 1 "$ITERATIONS"); do + local fixture="fixtures-$i" + mkdir "$fixture" + cd "fixtures-$i" + fuzzponent -d 2 -s 20 + cd .. + echo "$name $i" + node "$script" "$fixture" "$@" + if [[ -d "$fixture" ]]; then rmdir "$fixture"; fi + done +} + +run_benchmark "rimraf (async)" "rimraf.js" "async" +run_benchmark "rimraf (sync)" "rimraf.js" "sync" +run_benchmark "recursive delete" "recursive-delete.js" +run_benchmark "nodejs rm (promises)" "nodejs-rm.js" "promises" +run_benchmark "nodejs rm (callback)" "nodejs-rm.js" "callback" +run_benchmark "nodejs rm (sync)" "nodejs-rm.js" "sync" diff --git a/contributing/repository/linting.md b/contributing/repository/linting.md index 30535fdd6d0cf..7c198f240d93d 100644 --- a/contributing/repository/linting.md +++ b/contributing/repository/linting.md @@ -22,7 +22,7 @@ If you get a warning by alex, follow the instructions to correct the language. We recommend installing the [ESLint plugin for VS Code](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint). -You can find the enabled rules in the [ESLint config](../../.eslintrc.json). +You can find the enabled rules in the [ESLint config](../../eslint.config.mjs). ## Prettier diff --git a/crates/napi/Cargo.toml b/crates/napi/Cargo.toml index e9d106064685d..a0a3ddec1f039 100644 --- a/crates/napi/Cargo.toml +++ b/crates/napi/Cargo.toml @@ -55,10 +55,11 @@ ignored = [ ] [dependencies] -anyhow = "1.0.66" +anyhow = { workspace = true } console-subscriber = { workspace = true, optional = true } dhat = { workspace = true, optional = true } either = { workspace = true } +flate2 = { workspace = true } futures-util = { workspace = true } owo-colors = { workspace = true } napi = { workspace = true } @@ -123,6 +124,8 @@ turbopack-trace-utils = { workspace = true } turbopack-trace-server = { workspace = true } turbopack-ecmascript-plugins = { workspace = true, optional = true } +[target.'cfg(windows)'.dependencies] +windows-sys = "0.60" # Dependencies for the wasm32 build. [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/napi/build.rs b/crates/napi/build.rs index 18011b6fc3dba..475cb62e3be0d 100644 --- a/crates/napi/build.rs +++ b/crates/napi/build.rs @@ -2,8 +2,6 @@ use std::{env, fs, path::Path, process::Command, str}; use serde_json::Value; -extern crate napi_build; - fn main() -> anyhow::Result<()> { println!("cargo:rerun-if-env-changed=CI"); let is_ci = env::var("CI").is_ok_and(|value| !value.is_empty()); @@ -33,7 +31,7 @@ fn main() -> anyhow::Result<()> { let cargo = vergen_gitcl::CargoBuilder::default() .target_triple(true) .build()?; - // We use the git dirty state to disable persistent caching (persistent caching relies on a + // We use the git dirty state to disable filesystem cache (filesystem cache relies on a // commit hash to be safe). One tradeoff of this is that we must invalidate the rust build more // often. // @@ -46,7 +44,7 @@ fn main() -> anyhow::Result<()> { // // However, in practice that shouldn't be much of an issue: If no other dependency of this // top-level crate has changed (which would've triggered our rebuild), then the resulting binary - // must be equivalent to a clean build anyways. Therefore, persistent caching using the HEAD + // must be equivalent to a clean build anyways. Therefore, filesystem cache using the HEAD // commit hash as a version is okay. let git = vergen_gitcl::GitclBuilder::default() .dirty(/* include_untracked */ true) diff --git a/crates/napi/src/lib.rs b/crates/napi/src/lib.rs index 7529c09acf718..d2f7bfc10344f 100644 --- a/crates/napi/src/lib.rs +++ b/crates/napi/src/lib.rs @@ -30,9 +30,7 @@ DEALINGS IN THE SOFTWARE. //#![deny(clippy::all)] #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] - -#[macro_use] -extern crate napi_derive; +#![feature(iter_intersperse)] use std::sync::Arc; @@ -43,8 +41,10 @@ use swc_core::{ base::{Compiler, TransformOutput}, common::{FilePathMapping, SourceMap}, }; + #[cfg(not(target_arch = "wasm32"))] pub mod css; +pub mod lockfile; pub mod mdx; pub mod minify; #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/napi/src/lockfile.rs b/crates/napi/src/lockfile.rs new file mode 100644 index 0000000000000..9b44097a8a3df --- /dev/null +++ b/crates/napi/src/lockfile.rs @@ -0,0 +1,121 @@ +use std::{ + fs::{File, OpenOptions}, + mem::ManuallyDrop, + sync::Mutex, +}; + +use anyhow::Context; +use napi::bindgen_prelude::External; +use napi_derive::napi; + +/// A wrapper around [`File`] that is passed to JS, and is set to `None` when [`lockfile_unlock`] is +/// called. +/// +/// This uses [`ManuallyDrop`] to prevent exposing close-on-drop semantics to JS, as its not +/// idiomatic to rely on GC behaviors in JS. +/// +/// When the file is unlocked, the file at that path will be deleted (best-effort). +type JsLockfile = Mutex>>; + +pub struct LockfileInner { + file: File, + #[cfg(not(windows))] + path: std::path::PathBuf, +} + +#[napi(ts_return_type = "{ __napiType: \"Lockfile\" } | null")] +pub fn lockfile_try_acquire_sync(path: String) -> napi::Result>> { + let mut open_options = OpenOptions::new(); + open_options.write(true).create(true); + + // On Windows, we don't use `File::lock` because that grabs a mandatory lock. That can break + // tools or code that read the contents of the `.next` directory because the mandatory lock + // file will fail with EBUSY when read. Instead, we open a file with write mode, but without + // `FILE_SHARE_WRITE`. That gives us behavior closer to what we get on POSIX platforms. + // + // On POSIX platforms, Rust uses `flock` which creates an advisory lock, which can be + // read/written/deleted. + + #[cfg(windows)] + return { + use std::os::windows::fs::OpenOptionsExt; + + use windows_sys::Win32::{Foundation, Storage::FileSystem}; + + open_options + .share_mode(FileSystem::FILE_SHARE_READ | FileSystem::FILE_SHARE_DELETE) + .custom_flags(FileSystem::FILE_FLAG_DELETE_ON_CLOSE); + match open_options.open(&path) { + Ok(file) => Ok(Some(External::new(Mutex::new(ManuallyDrop::new(Some( + LockfileInner { file }, + )))))), + Err(err) + if err.raw_os_error() + == Some(Foundation::ERROR_SHARING_VIOLATION.try_into().unwrap()) => + { + Ok(None) + } + Err(err) => Err(err.into()), + } + }; + + #[cfg(not(windows))] + return { + use std::fs::TryLockError; + + let file = open_options.open(&path)?; + match file.try_lock() { + Ok(_) => Ok(Some(External::new(Mutex::new(ManuallyDrop::new(Some( + LockfileInner { + file, + path: path.into(), + }, + )))))), + Err(TryLockError::WouldBlock) => Ok(None), + Err(TryLockError::Error(err)) => Err(err.into()), + } + }; +} + +#[napi(ts_return_type = "Promise<{ __napiType: \"Lockfile\" } | null>")] +pub async fn lockfile_try_acquire(path: String) -> napi::Result>> { + tokio::task::spawn_blocking(move || lockfile_try_acquire_sync(path)) + .await + .context("panicked while attempting to acquire lockfile")? +} + +#[napi] +pub fn lockfile_unlock_sync( + #[napi(ts_arg_type = "{ __napiType: \"Lockfile\" }")] lockfile: External, +) { + // We don't need the file handle anymore, so we don't need to call `File::unlock`. Locks are + // released during `drop`. Remove it from the `ManuallyDrop` wrapper. + let Some(inner): Option = lockfile + .lock() + .expect("poisoned: another thread panicked during `lockfile_unlock_sync`?") + .take() + else { + return; + }; + + // - We use `FILE_FLAG_DELETE_ON_CLOSE` on Windows, so we don't need to delete the file there. + // - Ignore possible errors while removing the file, it only matters that we release the lock. + // - Delete *before* releasing the lock to avoid race conditions where we might accidentally + // delete another process's lockfile. This relies on POSIX semantics, letting us delete an + // open file. + #[cfg(not(windows))] + let _ = std::fs::remove_file(inner.path); + + drop(inner.file); +} + +#[napi] +pub async fn lockfile_unlock( + #[napi(ts_arg_type = "{ __napiType: \"Lockfile\" }")] lockfile: External, +) -> napi::Result<()> { + Ok( + tokio::task::spawn_blocking(move || lockfile_unlock_sync(lockfile)) + .await + .context("panicked while attempting to unlock lockfile")?, + ) +} diff --git a/crates/napi/src/mdx.rs b/crates/napi/src/mdx.rs index 6d4bd57ffa708..19d7f8ffa7fdc 100644 --- a/crates/napi/src/mdx.rs +++ b/crates/napi/src/mdx.rs @@ -1,5 +1,6 @@ use mdxjs::{Options, compile}; use napi::bindgen_prelude::*; +use napi_derive::napi; pub struct MdxCompileTask { pub input: String, diff --git a/crates/napi/src/minify.rs b/crates/napi/src/minify.rs index c00eb507db3d7..0049d6a0d8590 100644 --- a/crates/napi/src/minify.rs +++ b/crates/napi/src/minify.rs @@ -28,6 +28,7 @@ DEALINGS IN THE SOFTWARE. use anyhow::Context; use napi::bindgen_prelude::*; +use napi_derive::napi; use serde::Serialize; use swc_core::{ base::{config::JsMinifyOptions, try_with_handler}, diff --git a/crates/napi/src/next_api/analyze.rs b/crates/napi/src/next_api/analyze.rs new file mode 100644 index 0000000000000..b986a920619c1 --- /dev/null +++ b/crates/napi/src/next_api/analyze.rs @@ -0,0 +1,108 @@ +use std::{iter::once, sync::Arc}; + +use anyhow::Result; +use next_api::{ + analyze::{AnalyzeDataOutputAsset, ModulesDataOutputAsset}, + project::ProjectContainer, +}; +use turbo_tasks::{Effects, ReadRef, ResolvedVc, TryJoinIterExt, Vc}; +use turbopack_core::{diagnostics::PlainDiagnostic, issue::PlainIssue, output::OutputAssets}; + +use crate::next_api::utils::strongly_consistent_catch_collectables; + +#[turbo_tasks::value(serialization = "none")] +pub struct WriteAnalyzeResult { + pub issues: Arc>>, + pub diagnostics: Arc>>, + pub effects: Arc, +} + +#[turbo_tasks::function(operation)] +pub async fn write_analyze_data_with_issues_operation( + project: ResolvedVc, + app_dir_only: bool, +) -> Result> { + let analyze_data_op = write_analyze_data_with_issues_operation_inner(project, app_dir_only); + + let (_analyze_data, issues, diagnostics, effects) = + strongly_consistent_catch_collectables(analyze_data_op).await?; + + Ok(WriteAnalyzeResult { + issues, + diagnostics, + effects, + } + .cell()) +} + +#[turbo_tasks::function(operation)] +async fn write_analyze_data_with_issues_operation_inner( + project: ResolvedVc, + app_dir_only: bool, +) -> Result<()> { + let analyze_data_op = get_analyze_data_operation(project, app_dir_only); + + project + .project() + .emit_all_output_assets(analyze_data_op) + .as_side_effect() + .await?; + + Ok(()) +} + +#[turbo_tasks::function(operation)] +async fn get_analyze_data_operation( + container: ResolvedVc, + app_dir_only: bool, +) -> Result> { + let project = container.project(); + let project = + project.with_next_config(project.next_config().with_production_browser_source_maps()); + + let analyze_output_root = project + .node_root() + .owned() + .await? + .join("diagnostics/analyze")?; + let whole_app_module_graphs = project.whole_app_module_graphs(); + let analyze_output_root = &analyze_output_root; + let analyze_data = project + .get_all_endpoint_groups(app_dir_only) + .await? + .iter() + .map(|(key, endpoint_group)| async move { + let output_assets = endpoint_group.output_assets(); + let analyze_data = AnalyzeDataOutputAsset::new( + analyze_output_root + .join(&key.to_string())? + .join("analyze.data")?, + output_assets, + ) + .to_resolved() + .await?; + + Ok(ResolvedVc::upcast(analyze_data)) + }) + .try_join() + .await?; + + whole_app_module_graphs.as_side_effect().await?; + + let modules_data = ResolvedVc::upcast( + ModulesDataOutputAsset::new( + analyze_output_root.join("modules.data")?, + Vc::cell(vec![whole_app_module_graphs.await?.full]), + ) + .to_resolved() + .await?, + ); + + Ok(Vc::cell( + analyze_data + .iter() + .cloned() + .chain(once(modules_data)) + .collect(), + )) +} diff --git a/crates/napi/src/next_api/endpoint.rs b/crates/napi/src/next_api/endpoint.rs index bb41523648a96..3415925b4f005 100644 --- a/crates/napi/src/next_api/endpoint.rs +++ b/crates/napi/src/next_api/endpoint.rs @@ -3,6 +3,7 @@ use std::{ops::Deref, sync::Arc}; use anyhow::Result; use futures_util::TryFutureExt; use napi::{JsFunction, bindgen_prelude::External}; +use napi_derive::napi; use next_api::{ operation::OptionEndpoint, paths::ServerPath, @@ -15,7 +16,7 @@ use tracing::Instrument; use turbo_tasks::{Completion, Effects, OperationVc, ReadRef, Vc}; use turbopack_core::{diagnostics::PlainDiagnostic, issue::PlainIssue}; -use super::utils::{ +use crate::next_api::utils::{ DetachedVc, NapiDiagnostic, NapiIssue, RootTask, TurbopackResult, strongly_consistent_catch_collectables, subscribe, }; diff --git a/crates/napi/src/next_api/mod.rs b/crates/napi/src/next_api/mod.rs index 4897b2d3bf9ba..6dcf3d119c1c6 100644 --- a/crates/napi/src/next_api/mod.rs +++ b/crates/napi/src/next_api/mod.rs @@ -1,3 +1,4 @@ +pub mod analyze; pub mod endpoint; pub mod project; pub mod turbopack_ctx; diff --git a/crates/napi/src/next_api/project.rs b/crates/napi/src/next_api/project.rs index fdb0b866d22a5..1f785de2107d5 100644 --- a/crates/napi/src/next_api/project.rs +++ b/crates/napi/src/next_api/project.rs @@ -1,12 +1,14 @@ use std::{borrow::Cow, io::Write, path::PathBuf, sync::Arc, thread, time::Duration}; use anyhow::{Context, Result, anyhow, bail}; +use flate2::write::GzEncoder; use futures_util::TryFutureExt; use napi::{ Env, JsFunction, JsObject, Status, bindgen_prelude::{External, within_runtime_if_available}, threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; +use napi_derive::napi; use next_api::{ entrypoints::Entrypoints, next_server_nft::next_server_nft_assets, @@ -62,6 +64,7 @@ use url::Url; use crate::{ next_api::{ + analyze::{WriteAnalyzeResult, write_analyze_data_with_issues_operation}, endpoint::ExternalEndpoint, turbopack_ctx::{ NapiNextTurbopackCallbacks, NapiNextTurbopackCallbacksJsObject, NextTurboTasks, @@ -69,7 +72,7 @@ use crate::{ }, utils::{ DetachedVc, NapiDiagnostic, NapiIssue, RootTask, TurbopackResult, get_diagnostics, - get_issues, subscribe, + get_issues, strongly_consistent_catch_collectables, subscribe, }, }, util::DhatProfilerGuard, @@ -238,7 +241,7 @@ pub struct NapiDefineEnv { #[napi(object)] pub struct NapiTurboEngineOptions { - /// Use the new backend with persistent caching enabled. + /// Use the new backend with filesystem cache enabled. pub persistent_caching: Option, /// An upper bound of memory that turbopack will attempt to stay under. pub memory_limit: Option, @@ -362,23 +365,38 @@ pub fn project_new( trace = Some("overview".to_owned()); } + enum Compression { + None, + GzipFast, + GzipBest, + } + let mut compress = Compression::None; if let Some(mut trace) = trace { - // Trace presets - match trace.as_str() { - "overview" | "1" => { - trace = TRACING_NEXT_OVERVIEW_TARGETS.join(","); - } - "next" => { - trace = TRACING_NEXT_TARGETS.join(","); - } - "turbopack" => { - trace = TRACING_NEXT_TURBOPACK_TARGETS.join(","); - } - "turbo-tasks" => { - trace = TRACING_NEXT_TURBO_TASKS_TARGETS.join(","); - } - _ => {} - } + println!("Turbopack tracing enabled with targets: {trace}"); + println!(" Note that this might have a small performance impact."); + + trace = trace + .split(",") + .filter_map(|item| { + // Trace presets + Some(match item { + "overview" | "1" => Cow::Owned(TRACING_NEXT_OVERVIEW_TARGETS.join(",")), + "next" => Cow::Owned(TRACING_NEXT_TARGETS.join(",")), + "turbopack" => Cow::Owned(TRACING_NEXT_TURBOPACK_TARGETS.join(",")), + "turbo-tasks" => Cow::Owned(TRACING_NEXT_TURBO_TASKS_TARGETS.join(",")), + "gz" => { + compress = Compression::GzipFast; + return None; + } + "gz-best" => { + compress = Compression::GzipBest; + return None; + } + _ => Cow::Borrowed(item), + }) + }) + .intersperse_with(|| Cow::Borrowed(",")) + .collect::(); let subscriber = Registry::default(); @@ -396,9 +414,26 @@ pub fn project_new( std::fs::create_dir_all(&internal_dir) .context("Unable to create .next directory") .unwrap(); - let trace_file = internal_dir.join("trace-turbopack"); - let trace_writer = std::fs::File::create(trace_file.clone()).unwrap(); - let (trace_writer, trace_writer_guard) = TraceWriter::new(trace_writer); + let trace_file; + let (trace_writer, trace_writer_guard) = match compress { + Compression::None => { + trace_file = internal_dir.join("trace-turbopack"); + let trace_writer = std::fs::File::create(trace_file.clone()).unwrap(); + TraceWriter::new(trace_writer) + } + Compression::GzipFast => { + trace_file = internal_dir.join("trace-turbopack"); + let trace_writer = std::fs::File::create(trace_file.clone()).unwrap(); + let trace_writer = GzEncoder::new(trace_writer, flate2::Compression::fast()); + TraceWriter::new(trace_writer) + } + Compression::GzipBest => { + trace_file = internal_dir.join("trace-turbopack"); + let trace_writer = std::fs::File::create(trace_file.clone()).unwrap(); + let trace_writer = GzEncoder::new(trace_writer, flate2::Compression::best()); + TraceWriter::new(trace_writer) + } + }; let subscriber = subscriber.with(RawTraceLayer::new(trace_writer)); exit.on_exit(async move { @@ -455,9 +490,10 @@ pub fn project_new( } let options: ProjectOptions = options.into(); + let is_dev = options.dev; let container = turbo_tasks .run(async move { - let project = ProjectContainer::new(rcstr!("next.js"), options.dev); + let project = ProjectContainer::new(rcstr!("next.js"), is_dev); let project = project.to_resolved().await?; project.initialize(options).await?; Ok(project) @@ -465,24 +501,29 @@ pub fn project_new( .or_else(|e| turbopack_ctx.throw_turbopack_internal_result(&e.into())) .await?; - Handle::current().spawn({ - let tt = turbo_tasks.clone(); - async move { - let result = tt - .clone() - .run(async move { - benchmark_file_io(tt, container.project().node_root().owned().await?) + if is_dev { + Handle::current().spawn({ + let tt = turbo_tasks.clone(); + async move { + let result = tt + .clone() + .run(async move { + benchmark_file_io( + tt, + container.project().node_root().owned().await?, + ) .await - }) - .await; - if let Err(err) = result { - // TODO Not ideal to print directly to stdout. - // We should use a compilation event instead to report async errors. - println!("Failed to benchmark file I/O: {err}"); + }) + .await; + if let Err(err) = result { + // TODO Not ideal to print directly to stdout. + // We should use a compilation event instead to report async errors. + println!("Failed to benchmark file I/O: {err}"); + } } - } - .instrument(tracing::info_span!("benchmark file I/O")) - }); + .instrument(tracing::info_span!("benchmark file I/O")) + }); + } Ok(External::new(ProjectInstance { turbopack_ctx, @@ -538,7 +579,7 @@ async fn benchmark_file_io(turbo_tasks: NextTurboTasks, directory: FileSystemPat ))? .await?; - let directory = fs.to_sys_path(directory)?; + let directory = fs.to_sys_path(&directory); let temp_path = directory.join(format!( "tmp_file_io_benchmark_{:x}", rand::random::() @@ -596,10 +637,10 @@ pub async fn project_update( .await } -/// Invalidates the persistent cache so that it will be deleted next time that a turbopack project -/// is created with persistent caching enabled. +/// Invalidates the filesystem cache so that it will be deleted next time that a turbopack project +/// is created with filesystem cache enabled. #[napi] -pub async fn project_invalidate_persistent_cache( +pub async fn project_invalidate_file_system_cache( #[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External, ) -> napi::Result<()> { tokio::task::spawn_blocking(move || { @@ -613,7 +654,7 @@ pub async fn project_invalidate_persistent_cache( .invalidate(invalidation_reasons::USER_REQUEST) }) .await - .context("panicked while invalidating persistent cache")??; + .context("panicked while invalidating filesystem cache")??; Ok(()) } @@ -746,6 +787,7 @@ impl NapiRoute { #[napi(object)] pub struct NapiMiddleware { pub endpoint: External, + pub is_proxy: bool, } impl NapiMiddleware { @@ -758,6 +800,7 @@ impl NapiMiddleware { turbopack_ctx.clone(), value.endpoint, ))), + is_proxy: value.is_proxy, }) } } @@ -841,7 +884,7 @@ impl NapiEntrypoints { #[turbo_tasks::value(serialization = "none")] struct EntrypointsWithIssues { - entrypoints: ReadRef, + entrypoints: Option>, issues: Arc>>, diagnostics: Arc>>, effects: Arc, @@ -853,10 +896,8 @@ async fn get_entrypoints_with_issues_operation( ) -> Result> { let entrypoints_operation = EntrypointsOperation::new(project_container_entrypoints_operation(container)); - let entrypoints = entrypoints_operation.read_strongly_consistent().await?; - let issues = get_issues(entrypoints_operation).await?; - let diagnostics = get_diagnostics(entrypoints_operation).await?; - let effects = Arc::new(get_effects(entrypoints_operation).await?); + let (entrypoints, issues, diagnostics, effects) = + strongly_consistent_catch_collectables(entrypoints_operation).await?; Ok(EntrypointsWithIssues { entrypoints, issues, @@ -875,9 +916,16 @@ fn project_container_entrypoints_operation( container.entrypoints() } +#[turbo_tasks::value(serialization = "none")] +struct OperationResult { + issues: Arc>>, + diagnostics: Arc>>, + effects: Arc, +} + #[turbo_tasks::value(serialization = "none")] struct AllWrittenEntrypointsWithIssues { - entrypoints: Option>, + entrypoints: Option>, issues: Arc>>, diagnostics: Arc>>, effects: Arc, @@ -888,7 +936,7 @@ struct AllWrittenEntrypointsWithIssues { pub async fn project_write_all_entrypoints_to_disk( #[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External, app_dir_only: bool, -) -> napi::Result> { +) -> napi::Result>> { let ctx = &project.turbopack_ctx; let container = project.container; let tt = ctx.turbo_tasks(); @@ -899,7 +947,7 @@ pub async fn project_write_all_entrypoints_to_disk( get_all_written_entrypoints_with_issues_operation(container, app_dir_only); // Read and compile the files - let EntrypointsWithIssues { + let AllWrittenEntrypointsWithIssues { entrypoints, issues, diagnostics, @@ -917,7 +965,14 @@ pub async fn project_write_all_entrypoints_to_disk( .await?; Ok(TurbopackResult { - result: NapiEntrypoints::from_entrypoints_op(&entrypoints, &project.turbopack_ctx)?, + result: if let Some(entrypoints) = entrypoints { + Some(NapiEntrypoints::from_entrypoints_op( + &entrypoints, + &project.turbopack_ctx, + )?) + } else { + None + }, issues: issues.iter().map(|i| NapiIssue::from(&**i)).collect(), diagnostics: diags.iter().map(|d| NapiDiagnostic::from(d)).collect(), }) @@ -927,16 +982,14 @@ pub async fn project_write_all_entrypoints_to_disk( async fn get_all_written_entrypoints_with_issues_operation( container: ResolvedVc, app_dir_only: bool, -) -> Result> { +) -> Result> { let entrypoints_operation = EntrypointsOperation::new(all_entrypoints_write_to_disk_operation( container, app_dir_only, )); - let entrypoints = entrypoints_operation.read_strongly_consistent().await?; - let issues = get_issues(entrypoints_operation).await?; - let diagnostics = get_diagnostics(entrypoints_operation).await?; - let effects = Arc::new(get_effects(entrypoints_operation).await?); - Ok(EntrypointsWithIssues { + let (entrypoints, issues, diagnostics, effects) = + strongly_consistent_catch_collectables(entrypoints_operation).await?; + Ok(AllWrittenEntrypointsWithIssues { entrypoints, issues, diagnostics, @@ -950,9 +1003,10 @@ pub async fn all_entrypoints_write_to_disk_operation( project: ResolvedVc, app_dir_only: bool, ) -> Result> { + let output_assets_operation = output_assets_operation(project, app_dir_only); project .project() - .emit_all_output_assets(output_assets_operation(project, app_dir_only)) + .emit_all_output_assets(output_assets_operation) .as_side_effect() .await?; @@ -964,8 +1018,9 @@ async fn output_assets_operation( container: ResolvedVc, app_dir_only: bool, ) -> Result> { - let endpoint_assets = container - .project() + let project = container.project(); + let whole_app_module_graphs = project.whole_app_module_graphs(); + let endpoint_assets = project .get_all_endpoints(app_dir_only) .await? .iter() @@ -978,7 +1033,9 @@ async fn output_assets_operation( .flat_map(|assets| assets.iter().copied()) .collect(); - let nft = next_server_nft_assets(container.project()).await?; + let nft = next_server_nft_assets(project).await?; + + whole_app_module_graphs.as_side_effect().await?; Ok(Vc::cell( output_assets @@ -989,6 +1046,49 @@ async fn output_assets_operation( } #[tracing::instrument(level = "info", name = "get entrypoints", skip_all)] +#[napi] +pub async fn project_entrypoints( + #[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External, +) -> napi::Result>> { + let container = project.container; + + let (entrypoints, issues, diags) = project + .turbopack_ctx + .turbo_tasks() + .run_once(async move { + let entrypoints_with_issues_op = get_entrypoints_with_issues_operation(container); + + // Read and compile the files + let EntrypointsWithIssues { + entrypoints, + issues, + diagnostics, + effects: _, + } = &*entrypoints_with_issues_op + .read_strongly_consistent() + .await?; + + Ok((entrypoints.clone(), issues.clone(), diagnostics.clone())) + }) + .await + .map_err(|e| napi::Error::from_reason(PrettyPrintError(&e).to_string()))?; + + let result = match entrypoints { + Some(entrypoints) => Some(NapiEntrypoints::from_entrypoints_op( + &entrypoints, + &project.turbopack_ctx, + )?), + None => None, + }; + + Ok(TurbopackResult { + result, + issues: issues.iter().map(|i| NapiIssue::from(&**i)).collect(), + diagnostics: diags.iter().map(|d| NapiDiagnostic::from(d)).collect(), + }) +} + +#[tracing::instrument(level = "info", name = "subscribe to entrypoints", skip_all)] #[napi(ts_return_type = "{ __napiType: \"RootTask\" }")] pub fn project_entrypoints_subscribe( #[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External, @@ -1010,6 +1110,7 @@ pub fn project_entrypoints_subscribe( } = &*entrypoints_with_issues_op .read_strongly_consistent() .await?; + effects.apply().await?; Ok((entrypoints.clone(), issues.clone(), diagnostics.clone())) } @@ -1017,9 +1118,16 @@ pub fn project_entrypoints_subscribe( }, move |ctx| { let (entrypoints, issues, diags) = ctx.value; + let result = match entrypoints { + Some(entrypoints) => Some(NapiEntrypoints::from_entrypoints_op( + &entrypoints, + &turbopack_ctx, + )?), + None => None, + }; Ok(vec![TurbopackResult { - result: NapiEntrypoints::from_entrypoints_op(&entrypoints, &turbopack_ctx)?, + result, issues: issues .iter() .map(|issue| NapiIssue::from(&**issue)) @@ -1662,3 +1770,37 @@ pub fn project_get_source_map_sync( tokio::runtime::Handle::current().block_on(project_get_source_map(project, file_path)) }) } + +#[napi] +pub async fn project_write_analyze_data( + #[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External, + app_dir_only: bool, +) -> napi::Result> { + let container = project.container; + let (issues, diagnostics) = project + .turbopack_ctx + .turbo_tasks() + .run_once(async move { + let analyze_data_op = write_analyze_data_with_issues_operation(container, app_dir_only); + let WriteAnalyzeResult { + issues, + diagnostics, + effects, + } = &*analyze_data_op.read_strongly_consistent().await?; + + // Write the files to disk + effects.apply().await?; + Ok((issues.clone(), diagnostics.clone())) + }) + .await + .map_err(|e| napi::Error::from_reason(PrettyPrintError(&e).to_string()))?; + + Ok(TurbopackResult { + result: (), + issues: issues.iter().map(|i| NapiIssue::from(&**i)).collect(), + diagnostics: diagnostics + .iter() + .map(|d| NapiDiagnostic::from(d)) + .collect(), + }) +} diff --git a/crates/napi/src/next_api/turbopack_ctx.rs b/crates/napi/src/next_api/turbopack_ctx.rs index 5731d98607817..7712c853d919d 100644 --- a/crates/napi/src/next_api/turbopack_ctx.rs +++ b/crates/napi/src/next_api/turbopack_ctx.rs @@ -12,6 +12,7 @@ use std::{ use anyhow::Result; use either::Either; use napi::{JsFunction, threadsafe_function::ThreadsafeFunction}; +use napi_derive::napi; use once_cell::sync::Lazy; use owo_colors::OwoColorize; use serde::Serialize; @@ -207,6 +208,7 @@ pub fn create_turbo_tasks( turbo_tasks_backend::StorageMode::ReadWrite }), dependency_tracking, + num_workers: Some(tokio::runtime::Handle::current().metrics().num_workers()), ..Default::default() }, Either::Left(backing_storage), @@ -250,7 +252,7 @@ impl CompilationEvent for StartupCacheInvalidationEvent { _ => "", // ignore unknown reasons }; format!( - "Turbopack's persistent cache has been deleted{reason_msg}. Builds or page loads may \ + "Turbopack's filesystem cache has been deleted{reason_msg}. Builds or page loads may \ be slower as a result." ) } diff --git a/crates/napi/src/next_api/utils.rs b/crates/napi/src/next_api/utils.rs index 2eebcd45db85b..49d9846e80b60 100644 --- a/crates/napi/src/next_api/utils.rs +++ b/crates/napi/src/next_api/utils.rs @@ -7,6 +7,7 @@ use napi::{ bindgen_prelude::{Buffer, External, ToNapiValue}, threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; +use napi_derive::napi; use rustc_hash::FxHashMap; use serde::Serialize; use turbo_tasks::{ diff --git a/crates/napi/src/parse.rs b/crates/napi/src/parse.rs index 4ba37c725814f..7d59fadb1090b 100644 --- a/crates/napi/src/parse.rs +++ b/crates/napi/src/parse.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use anyhow::Context as _; use napi::bindgen_prelude::*; +use napi_derive::napi; use swc_core::{ base::{config::ParseOptions, try_with_handler}, common::{ diff --git a/crates/napi/src/react_compiler.rs b/crates/napi/src/react_compiler.rs index febde475e26bf..c788448305fde 100644 --- a/crates/napi/src/react_compiler.rs +++ b/crates/napi/src/react_compiler.rs @@ -1,6 +1,7 @@ use std::{path::PathBuf, sync::Arc}; use napi::bindgen_prelude::*; +use napi_derive::napi; use next_custom_transforms::react_compiler; use swc_core::{ common::{GLOBALS, SourceMap}, diff --git a/crates/napi/src/rspack.rs b/crates/napi/src/rspack.rs index 2fd5e81995279..521b047df6adf 100644 --- a/crates/napi/src/rspack.rs +++ b/crates/napi/src/rspack.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, fs, path::PathBuf, sync::Arc}; use napi::bindgen_prelude::*; +use napi_derive::napi; use swc_core::{ base::{ config::{IsModule, ParseOptions}, diff --git a/crates/napi/src/transform.rs b/crates/napi/src/transform.rs index e464cd208a993..4c48d5f19312a 100644 --- a/crates/napi/src/transform.rs +++ b/crates/napi/src/transform.rs @@ -35,6 +35,7 @@ use std::{ use anyhow::{Context as _, anyhow, bail}; use napi::bindgen_prelude::*; +use napi_derive::napi; use next_custom_transforms::chain_transforms::{TransformOptions, custom_before_pass}; use once_cell::sync::Lazy; use rustc_hash::{FxHashMap, FxHashSet}; diff --git a/crates/napi/src/turbo_trace_server.rs b/crates/napi/src/turbo_trace_server.rs index 96ebae24a1c26..129e37d60f034 100644 --- a/crates/napi/src/turbo_trace_server.rs +++ b/crates/napi/src/turbo_trace_server.rs @@ -1,5 +1,7 @@ use std::path::PathBuf; +use napi_derive::napi; + #[napi] pub fn start_turbopack_trace_server(path: String, port: Option) { let path_buf = PathBuf::from(path); diff --git a/crates/napi/src/turbopack.rs b/crates/napi/src/turbopack.rs index 0574beb26f092..7b88a1c90a611 100644 --- a/crates/napi/src/turbopack.rs +++ b/crates/napi/src/turbopack.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use anyhow::Context; use napi::bindgen_prelude::*; +use napi_derive::napi; use next_build::{ BuildOptions as NextBuildOptions, build_options::{BuildContext, DefineEnv}, diff --git a/crates/napi/src/util.rs b/crates/napi/src/util.rs index e1a9698cd7ada..dd45782986ff1 100644 --- a/crates/napi/src/util.rs +++ b/crates/napi/src/util.rs @@ -30,6 +30,7 @@ use std::{cell::RefCell, env, path::PathBuf}; use anyhow::anyhow; use napi::bindgen_prelude::{External, Status}; +use napi_derive::napi; use tracing_chrome::{ChromeLayerBuilder, FlushGuard}; use tracing_subscriber::{Layer, filter, layer::SubscriberExt, util::SubscriberInitExt}; diff --git a/crates/next-api/Cargo.toml b/crates/next-api/Cargo.toml index b7fab892310c0..6c6f878276c55 100644 --- a/crates/next-api/Cargo.toml +++ b/crates/next-api/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] anyhow = { workspace = true, features = ["backtrace"] } +byteorder = { workspace = true } either = { workspace = true } futures = { workspace = true } indexmap = { workspace = true } @@ -31,6 +32,7 @@ turbo-tasks-env = { workspace = true } turbo-tasks-fs = { workspace = true } turbo-unix-path = { workspace = true } turbopack = { workspace = true } +turbopack-analyze = { workspace = true } turbopack-browser = { workspace = true } turbopack-core = { workspace = true } turbopack-ecmascript = { workspace = true } diff --git a/crates/next-api/src/analyze.rs b/crates/next-api/src/analyze.rs new file mode 100644 index 0000000000000..65dd992b6acb3 --- /dev/null +++ b/crates/next-api/src/analyze.rs @@ -0,0 +1,610 @@ +use std::io::Write; + +use anyhow::Result; +use byteorder::{BE, WriteBytesExt}; +use rustc_hash::FxHashMap; +use serde::{Deserialize, Serialize}; +use turbo_rcstr::RcStr; +use turbo_tasks::{ + FxIndexSet, NonLocalValue, ResolvedVc, TryFlatJoinIterExt, ValueToString, Vc, + trace::TraceRawVcs, +}; +use turbo_tasks_fs::{ + File, FileContent, FileSystemPath, + rope::{Rope, RopeBuilder}, +}; +use turbopack_analyze::split_chunk::split_output_asset_into_parts; +use turbopack_core::{ + SOURCE_URL_PROTOCOL, + asset::{Asset, AssetContent}, + chunk::ChunkingType, + module::Module, + output::{OutputAsset, OutputAssets}, + reference::all_assets_from_entries, +}; + +use crate::route::{Endpoint, ModuleGraphs}; + +#[derive( + Default, Clone, Debug, Deserialize, Eq, NonLocalValue, PartialEq, Serialize, TraceRawVcs, +)] +pub struct EdgesData { + pub offsets: Vec, + pub data: Vec, +} + +impl EdgesData { + fn from_iterator<'a>(iterable: impl IntoIterator> + Clone) -> Self { + let mut current_offset = 0; + let sum: usize = iterable.clone().into_iter().map(|v| v.len()).sum(); + let mut data = Vec::with_capacity(sum); + let offsets = iterable + .into_iter() + .map(|edges| { + current_offset += edges.len() as u32; + data.extend(edges); + current_offset + }) + .collect(); + Self { offsets, data } + } + + fn write(&self, writer: &mut impl Write) -> Result<()> { + writer.write_u32::(self.offsets.len() as u32)?; + for &offset in &self.offsets { + writer.write_u32::(offset)?; + } + for &data in &self.data { + writer.write_u32::(data)?; + } + Ok(()) + } +} + +#[derive(Serialize)] +pub struct AnalyzeSource { + pub parent_source_index: Option, + /// Path. When there is a parent, this is concatenated to the parent's path. + /// Folders end with a slash. Might have multiple path segments when folders contain only a + /// single child. + pub path: RcStr, +} + +#[derive(Serialize)] +pub struct AnalyzeModule { + pub path: RcStr, + pub depth: u32, +} + +#[derive(Serialize)] +pub struct AnalyzeChunkPart { + pub source_index: u32, + pub output_file_index: u32, + pub size: u32, +} + +#[derive(Serialize)] +pub struct AnalyzeOutputFile { + pub filename: RcStr, +} + +#[derive(Serialize)] +struct EdgesDataReference { + pub offset: u32, + pub length: u32, +} + +#[derive(Serialize)] +struct AnalyzeDataHeader { + pub sources: Vec, + pub chunk_parts: Vec, + pub output_files: Vec, + /// Edges from chunks to chunk parts + pub output_file_chunk_parts: EdgesDataReference, + /// Edges from sources to chunk parts + pub source_chunk_parts: EdgesDataReference, + /// Edges from sources to their children sources + pub source_children: EdgesDataReference, + /// Root level sources, walking their children will reach all sources + pub source_roots: Vec, +} + +#[derive(Serialize)] +struct ModulesDataHeader { + pub modules: Vec, + /// Edges from modules to modules + pub module_dependents: EdgesDataReference, + /// Edges from modules to modules + pub async_module_dependents: EdgesDataReference, + /// Edges from modules to modules + pub module_dependencies: EdgesDataReference, + /// Edges from modules to modules + pub async_module_dependencies: EdgesDataReference, +} + +struct AnalyzeOutputFileBuilder { + output_file: AnalyzeOutputFile, + chunk_part_indices: Vec, +} + +struct AnalyzeSourceBuilder { + source: AnalyzeSource, + child_source_indices: Vec, + chunk_part_indices: Vec, +} + +struct AnalyzeModuleBuilder { + module: AnalyzeModule, + dependencies: FxIndexSet, + async_dependencies: FxIndexSet, + dependents: FxIndexSet, + async_dependents: FxIndexSet, +} + +struct AnalyzeDataBuilder { + sources: Vec, + source_index_map: FxHashMap, + chunk_parts: Vec, + output_files: Vec, +} + +struct ModulesDataBuilder { + modules: Vec, + module_index_map: FxHashMap, +} + +struct EdgesDataSectionBuilder { + data: Vec, +} + +impl EdgesDataSectionBuilder { + fn new() -> Self { + Self { data: vec![] } + } + + fn add_edges(&mut self, edges: &EdgesData) -> EdgesDataReference { + let offset = self.data.len().try_into().unwrap(); + edges.write(&mut self.data).unwrap(); + let length = (self.data.len() - offset as usize).try_into().unwrap(); + EdgesDataReference { offset, length } + } +} + +impl AnalyzeDataBuilder { + fn new() -> Self { + Self { + sources: vec![], + source_index_map: FxHashMap::default(), + chunk_parts: vec![], + output_files: vec![], + } + } + + fn ensure_source(&mut self, path: &str) -> (&mut AnalyzeSourceBuilder, u32) { + if let Some(&index) = self.source_index_map.get(path) { + return (&mut self.sources[index as usize], index); + } + let index = self.sources.len() as u32; + let path = RcStr::from(path); + self.source_index_map.insert(path.clone(), index); + self.sources.push(AnalyzeSourceBuilder { + source: AnalyzeSource { + parent_source_index: None, + path, + }, + child_source_indices: vec![], + chunk_part_indices: vec![], + }); + (&mut self.sources[index as usize], index) + } + + fn add_chunk_part(&mut self, chunk_part: AnalyzeChunkPart) -> u32 { + let i = self.chunk_parts.len() as u32; + self.chunk_parts.push(chunk_part); + i + } + + fn add_output_file(&mut self, output_file: AnalyzeOutputFile) -> u32 { + let i = self.output_files.len() as u32; + self.output_files.push(AnalyzeOutputFileBuilder { + output_file, + chunk_part_indices: vec![], + }); + i + } + + fn add_chunk_part_to_output_file(&mut self, output_file_index: u32, chunk_part_index: u32) { + self.output_files[output_file_index as usize] + .chunk_part_indices + .push(chunk_part_index); + } + + fn add_chunk_part_to_source(&mut self, source_index: u32, chunk_part_index: u32) { + self.sources[source_index as usize] + .chunk_part_indices + .push(chunk_part_index); + } + + fn build(self) -> Rope { + let source_roots = self + .sources + .iter() + .enumerate() + .filter_map(|(i, s)| { + if s.source.parent_source_index.is_none() { + Some(i as u32) + } else { + None + } + }) + .collect(); + + let source_children = + EdgesData::from_iterator(self.sources.iter().map(|s| &s.child_source_indices)); + + let source_chunk_parts = + EdgesData::from_iterator(self.sources.iter().map(|s| &s.chunk_part_indices)); + + let output_file_chunk_parts = + EdgesData::from_iterator(self.output_files.iter().map(|of| &of.chunk_part_indices)); + + let mut binary_section = EdgesDataSectionBuilder::new(); + + let header = AnalyzeDataHeader { + sources: self.sources.into_iter().map(|s| s.source).collect(), + chunk_parts: self.chunk_parts, + output_files: self + .output_files + .into_iter() + .map(|of| of.output_file) + .collect(), + output_file_chunk_parts: binary_section.add_edges(&output_file_chunk_parts), + source_chunk_parts: binary_section.add_edges(&source_chunk_parts), + source_children: binary_section.add_edges(&source_children), + source_roots, + }; + + let header_json = serde_json::to_vec(&header).unwrap(); + + let mut rope = RopeBuilder::default(); + rope.push_bytes(&(header_json.len() as u32).to_be_bytes()); + rope.reserve_bytes(header_json.len() + binary_section.data.len()); + rope.push_bytes(&header_json); + rope.push_bytes(&binary_section.data); + rope.build() + } +} + +impl ModulesDataBuilder { + fn new() -> Self { + Self { + modules: vec![], + module_index_map: FxHashMap::default(), + } + } + + fn ensure_module(&mut self, path: &str) -> (&mut AnalyzeModuleBuilder, u32) { + if let Some(&index) = self.module_index_map.get(path) { + return (&mut self.modules[index as usize], index); + } + let index = self.modules.len() as u32; + let path = RcStr::from(path); + self.module_index_map.insert(path.clone(), index); + self.modules.push(AnalyzeModuleBuilder { + module: AnalyzeModule { + path, + depth: u32::MAX, + }, + dependencies: FxIndexSet::default(), + async_dependencies: FxIndexSet::default(), + dependents: FxIndexSet::default(), + async_dependents: FxIndexSet::default(), + }); + (&mut self.modules[index as usize], index) + } + + fn build(self) -> Rope { + let module_dependencies_vecs: Vec> = self + .modules + .iter() + .map(|s| s.dependencies.iter().copied().collect()) + .collect(); + let async_module_dependencies_vecs: Vec> = self + .modules + .iter() + .map(|s| s.async_dependencies.iter().copied().collect()) + .collect(); + let module_dependents_vecs: Vec> = self + .modules + .iter() + .map(|s| s.dependents.iter().copied().collect()) + .collect(); + let async_module_dependents_vecs: Vec> = self + .modules + .iter() + .map(|s| s.async_dependents.iter().copied().collect()) + .collect(); + + let module_dependencies = EdgesData::from_iterator(&module_dependencies_vecs); + let async_module_dependencies = EdgesData::from_iterator(&async_module_dependencies_vecs); + let module_dependents = EdgesData::from_iterator(&module_dependents_vecs); + let async_module_dependents = EdgesData::from_iterator(&async_module_dependents_vecs); + + let mut binary_section = EdgesDataSectionBuilder::new(); + + let header = ModulesDataHeader { + modules: self.modules.into_iter().map(|s| s.module).collect(), + module_dependents: binary_section.add_edges(&module_dependents), + async_module_dependents: binary_section.add_edges(&async_module_dependents), + module_dependencies: binary_section.add_edges(&module_dependencies), + async_module_dependencies: binary_section.add_edges(&async_module_dependencies), + }; + + let header_json = serde_json::to_vec(&header).unwrap(); + + let mut rope = RopeBuilder::default(); + rope.push_bytes(&(header_json.len() as u32).to_be_bytes()); + rope.reserve_bytes(header_json.len() + binary_section.data.len()); + rope.push_bytes(&header_json); + rope.push_bytes(&binary_section.data); + rope.build() + } +} + +#[turbo_tasks::function] +pub async fn analyze_output_assets(output_assets: Vc) -> Result> { + let output_assets = all_assets_from_entries(output_assets); + + let mut builder = AnalyzeDataBuilder::new(); + + let prefix = format!("{SOURCE_URL_PROTOCOL}///"); + + // Process the output assets and extract chunk parts. + // Also creates sources for the chunk parts. + for &asset in output_assets.await? { + let filename = asset.path().to_string().owned().await?; + if filename.ends_with(".map") || filename.ends_with(".nft.json") { + // Skip source maps. + continue; + } + let output_file_index = builder.add_output_file(AnalyzeOutputFile { filename }); + let chunk_parts = split_output_asset_into_parts(*asset).await?; + for chunk_part in chunk_parts { + let source_index = builder + .ensure_source(chunk_part.source.trim_start_matches(&prefix)) + .1; + let chunk_part_index = builder.add_chunk_part(AnalyzeChunkPart { + source_index, + output_file_index, + size: chunk_part.real_size + chunk_part.unaccounted_size, + }); + builder.add_chunk_part_to_output_file(output_file_index, chunk_part_index); + builder.add_chunk_part_to_source(source_index, chunk_part_index); + } + } + + // Build a directory structure for the sources. + let mut i: u32 = 0; + while i < builder.sources.len().try_into().unwrap() { + let source = &builder.sources[i as usize]; + let path = source.source.path.as_str(); + if !path.is_empty() { + let (parent_path, path) = if let Some(pos) = path.trim_end_matches('/').rfind('/') { + (&path[..pos + 1], &path[pos + 1..]) + } else { + ("", path) + }; + let parent_path = parent_path.to_string(); + let path = path.into(); + let (parent_source, parent_index) = builder.ensure_source(&parent_path); + parent_source.child_source_indices.push(i); + builder.sources[i as usize].source.parent_source_index = Some(parent_index); + builder.sources[i as usize].source.path = path; + } + i += 1; + } + + let rope = builder.build(); + Ok(FileContent::Content(File::from(rope)).cell()) +} + +#[turbo_tasks::function] +pub async fn analyze_module_graphs(module_graphs: Vc) -> Result> { + let mut builder = ModulesDataBuilder::new(); + + let mut all_edges = FxIndexSet::default(); + let mut all_async_edges = FxIndexSet::default(); + for &module_graph in module_graphs.await? { + let module_graph = module_graph.read_graphs().await?; + module_graph.traverse_all_edges_unordered(|(parent_node, reference), node| { + match reference.chunking_type { + ChunkingType::Async => { + all_async_edges.insert((parent_node.module, node.module)); + } + _ => { + all_edges.insert((parent_node.module, node.module)); + } + } + Ok(()) + })?; + } + + type ModulePair = (ResolvedVc>, ResolvedVc>); + async fn mapper((from, to): ModulePair) -> Result> { + if from == to { + return Ok(None); + } + let from_path = from.ident().path().to_string().owned().await?; + let to_path = to.ident().path().to_string().owned().await?; + Ok(Some((from_path, to_path))) + } + + let all_edges = all_edges + .iter() + .copied() + .map(mapper) + .try_flat_join() + .await?; + let all_async_edges = all_async_edges + .iter() + .copied() + .map(mapper) + .try_flat_join() + .await?; + for (from_path, to_path) in all_edges { + let from_index = builder.ensure_module(&from_path).1; + let to_index = builder.ensure_module(&to_path).1; + if from_index == to_index { + continue; + } + builder.modules[from_index as usize] + .dependencies + .insert(to_index); + builder.modules[to_index as usize] + .dependents + .insert(from_index); + } + for (from_path, to_path) in all_async_edges { + let from_index = builder.ensure_module(&from_path).1; + let to_index = builder.ensure_module(&to_path).1; + if from_index == to_index { + continue; + } + builder.modules[from_index as usize] + .async_dependencies + .insert(to_index); + builder.modules[to_index as usize] + .async_dependents + .insert(from_index); + } + + // Compute depth using BFS from modules without incoming edges + let mut queue = std::collections::VecDeque::new(); + + // Find modules without incoming edges and set their depth to 0 + for (index, module) in builder.modules.iter_mut().enumerate() { + if module.dependents.is_empty() && module.async_dependents.is_empty() { + module.module.depth = 0; + queue.push_back(index as u32); + } + } + + // Process queue and propagate depth + while let Some(current_index) = queue.pop_front() { + let current_depth = builder.modules[current_index as usize].module.depth; + + // Collect dependencies to avoid borrow conflicts + let dependencies: Vec = builder.modules[current_index as usize] + .dependencies + .iter() + .copied() + .collect(); + + // Update dependencies + let new_depth = current_depth + 1; + for &dep_index in &dependencies { + let dep_module = &mut builder.modules[dep_index as usize]; + if new_depth < dep_module.module.depth { + dep_module.module.depth = new_depth; + queue.push_back(dep_index); + } + } + + // Collect async dependencies to avoid borrow conflicts + let async_dependencies: Vec = builder.modules[current_index as usize] + .async_dependencies + .iter() + .copied() + .collect(); + + // Update async dependencies + let new_depth = current_depth + 1000; + for &dep_index in &async_dependencies { + let dep_module = &mut builder.modules[dep_index as usize]; + if new_depth < dep_module.module.depth { + dep_module.module.depth = new_depth; + queue.push_back(dep_index); + } + } + } + + let rope = builder.build(); + Ok(FileContent::Content(File::from(rope)).cell()) +} + +#[turbo_tasks::function] +pub async fn analyze_endpoint(endpoint: Vc>) -> Result> { + Ok(analyze_output_assets( + *endpoint.output().await?.output_assets, + )) +} + +#[turbo_tasks::value] +pub struct AnalyzeDataOutputAsset { + pub path: FileSystemPath, + pub output_assets: ResolvedVc, +} + +#[turbo_tasks::value_impl] +impl AnalyzeDataOutputAsset { + #[turbo_tasks::function] + pub async fn new(path: FileSystemPath, output_assets: Vc) -> Result> { + Ok(Self { + path, + output_assets: output_assets.to_resolved().await?, + } + .cell()) + } +} + +#[turbo_tasks::value_impl] +impl Asset for AnalyzeDataOutputAsset { + #[turbo_tasks::function] + fn content(&self) -> Vc { + let file_content = analyze_output_assets(*self.output_assets); + AssetContent::file(file_content) + } +} + +#[turbo_tasks::value_impl] +impl OutputAsset for AnalyzeDataOutputAsset { + #[turbo_tasks::function] + fn path(&self) -> Vc { + self.path.clone().cell() + } +} + +#[turbo_tasks::value] +pub struct ModulesDataOutputAsset { + pub path: FileSystemPath, + pub module_graphs: ResolvedVc, +} + +#[turbo_tasks::value_impl] +impl ModulesDataOutputAsset { + #[turbo_tasks::function] + pub async fn new(path: FileSystemPath, module_graphs: Vc) -> Result> { + Ok(Self { + path, + module_graphs: module_graphs.to_resolved().await?, + } + .cell()) + } +} + +#[turbo_tasks::value_impl] +impl Asset for ModulesDataOutputAsset { + #[turbo_tasks::function] + fn content(&self) -> Vc { + let file_content = analyze_module_graphs(*self.module_graphs); + AssetContent::file(file_content) + } +} + +#[turbo_tasks::value_impl] +impl OutputAsset for ModulesDataOutputAsset { + #[turbo_tasks::function] + fn path(&self) -> Vc { + self.path.clone().cell() + } +} diff --git a/crates/next-api/src/app.rs b/crates/next-api/src/app.rs index 54cde1e7d7929..28b5fec7bbddb 100644 --- a/crates/next-api/src/app.rs +++ b/crates/next-api/src/app.rs @@ -23,9 +23,8 @@ use next_core::{ next_dynamic::NextDynamicTransition, next_edge::route_regex::get_named_middleware_regex, next_manifests::{ - AppPathsManifest, BuildManifest, EdgeFunctionDefinition, MiddlewareMatcher, - MiddlewaresManifestV2, PagesManifest, Regions, - client_reference_manifest::ClientReferenceManifest, + AppPathsManifest, BuildManifest, EdgeFunctionDefinition, MiddlewaresManifestV2, + PagesManifest, ProxyMatcher, Regions, client_reference_manifest::ClientReferenceManifest, }, next_server::{ ServerContextType, get_server_module_options_context, get_server_resolve_options_context, @@ -82,8 +81,10 @@ use crate::{ all_paths_in_root, all_server_paths, get_asset_paths_from_root, get_js_paths_from_root, get_wasm_paths_from_root, paths_to_bindings, wasm_paths_to_bindings, }, - project::{ModuleGraphs, Project}, - route::{AppPageRoute, Endpoint, EndpointOutput, EndpointOutputPaths, Route, Routes}, + project::{BaseAndFullModuleGraph, Project}, + route::{ + AppPageRoute, Endpoint, EndpointOutput, EndpointOutputPaths, ModuleGraphs, Route, Routes, + }, server_actions::{build_server_actions_loader, create_server_actions_manifest}, webpack_stats::generate_webpack_stats, }; @@ -851,7 +852,7 @@ impl AppProject { rsc_entry: ResolvedVc>, client_shared_entries: Vc, has_layout_segments: bool, - ) -> Result> { + ) -> Result> { if *self.project.per_page_module_graph().await? { let should_trace = self.project.next_mode().await?.is_production(); let client_shared_entries = client_shared_entries @@ -947,7 +948,7 @@ impl AppProject { graphs.push(additional_module_graph); let full = ModuleGraph::from_graphs(graphs); - Ok(ModuleGraphs { + Ok(BaseAndFullModuleGraph { base: base.to_resolved().await?, full: full.to_resolved().await?, } @@ -1561,7 +1562,7 @@ impl AppEndpoint { if emit_manifests != EmitManifests::None { // create middleware manifest let named_regex = get_named_middleware_regex(&app_entry.pathname); - let matchers = MiddlewareMatcher { + let matchers = ProxyMatcher { regexp: Some(named_regex.into()), original_source: app_entry.pathname.clone(), ..Default::default() @@ -2103,6 +2104,22 @@ impl Endpoint for AppEndpoint { server_actions_loader, ])])) } + + #[turbo_tasks::function] + async fn module_graphs(self: Vc) -> Result> { + let this = self.await?; + let app_entry = self.app_endpoint_entry().await?; + let module_graphs = this + .app_project + .app_module_graphs( + self, + *app_entry.rsc_entry, + this.app_project.client_runtime_entries(), + matches!(this.ty, AppEndpointType::Page { .. }), + ) + .await?; + Ok(Vc::cell(vec![module_graphs.full])) + } } #[turbo_tasks::value] diff --git a/crates/next-api/src/client_references.rs b/crates/next-api/src/client_references.rs index 0f87ce5eecb12..0a4a8ac0c4c69 100644 --- a/crates/next-api/src/client_references.rs +++ b/crates/next-api/src/client_references.rs @@ -3,21 +3,13 @@ use next_core::{ next_client_reference::{CssClientReferenceModule, EcmascriptClientReferenceModule}, next_server_component::server_component_module::NextServerComponentModule, }; -use roaring::RoaringBitmap; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use turbo_tasks::{ - FxIndexSet, NonLocalValue, ResolvedVc, TryFlatJoinIterExt, Vc, debug::ValueDebugFormat, - trace::TraceRawVcs, + NonLocalValue, ResolvedVc, TryFlatJoinIterExt, Vc, debug::ValueDebugFormat, trace::TraceRawVcs, }; use turbopack::css::chunk::CssChunkPlaceable; -use turbopack_core::{ - module::Module, - module_graph::{ - GraphTraversalAction, SingleModuleGraph, SingleModuleGraphModuleNode, - chunk_group_info::RoaringBitmapWrapper, - }, -}; +use turbopack_core::{module::Module, module_graph::SingleModuleGraph}; #[derive( Copy, Clone, Serialize, Deserialize, Eq, PartialEq, TraceRawVcs, ValueDebugFormat, NonLocalValue, @@ -31,42 +23,14 @@ pub enum ClientManifestEntryType { ServerComponent(ResolvedVc), } -/// Tracks information about all the css and js client references in the graph as well as how server -/// components depend on them. -#[turbo_tasks::value] -pub struct ClientReferenceManifest { - pub manifest: FxHashMap>, ClientManifestEntryType>, - // All the server components in the graph. - server_components: FxIndexSet>, - // All the server components that depend on each module - // This only includes mappings for modules with client references and the bitmaps reference - // indices into `[server_components]` - server_components_for_client_references: - FxHashMap>, RoaringBitmapWrapper>, -} - -impl ClientReferenceManifest { - /// Returns all the server components that depend on the given client reference - pub fn server_components_for_client_reference( - &self, - module: ResolvedVc>, - ) -> impl Iterator> { - let bitmap = &self - .server_components_for_client_references - .get(&module) - .expect("Module should be a client reference module") - .0; - - bitmap - .iter() - .map(|index| *self.server_components.get_index(index as usize).unwrap()) - } -} +/// Tracks information about all the css and js client references in the graph. +#[turbo_tasks::value(transparent)] +pub struct ClientReferenceData(FxHashMap>, ClientManifestEntryType>); #[turbo_tasks::function] pub async fn map_client_references( graph: Vc, -) -> Result> { +) -> Result> { let graph = graph.await?; let manifest = graph .iter_nodes() @@ -108,96 +72,5 @@ pub async fn map_client_references( .into_iter() .collect::>(); - let mut server_components = FxIndexSet::default(); - let mut module_to_server_component_bits = FxHashMap::default(); - if !manifest.is_empty() { - graph.traverse_edges_from_entries_fixed_point( - graph.entry_modules(), - |parent_info, node| { - let module = node.module(); - let module_type = manifest.get(&module); - let mut should_visit_children = match module_to_server_component_bits.entry(module) - { - std::collections::hash_map::Entry::Occupied(_) => false, - std::collections::hash_map::Entry::Vacant(vacant_entry) => { - // only do this the first time we visit the node. - let bits = vacant_entry.insert(RoaringBitmap::new()); - if let Some(ClientManifestEntryType::ServerComponent( - server_component_module, - )) = module_type - { - let index = server_components.insert_full(*server_component_module).0; - - bits.insert(index.try_into().unwrap()); - } - true - } - }; - if let Some((SingleModuleGraphModuleNode{module: parent_module}, _)) = parent_info - // Skip self cycles such as in - // test/e2e/app-dir/dynamic-import/app/page.tsx where a very-dynamic import induces a - // self cycle. They don't introduce new bits anyway. - && module != *parent_module - { - // Copy parent bits down. `traverse_edges_from_entries_fixed_point` always - // visits parents before children so we can simply assert - // that the parent it set. - let [Some(current), Some(parent)] = - module_to_server_component_bits.get_disjoint_mut([&module, parent_module]) - else { - unreachable!() - }; - // Check if we are adding new bits and thus need to revisit children unless we - // are already planning to because this is a new node. - if !should_visit_children { - let len = current.len(); - *current |= &*parent; - // did we find new bits? If so visit the children again - should_visit_children = len != current.len(); - } else { - *current |= &*parent; - } - } - - Ok(match module_type { - Some( - ClientManifestEntryType::EcmascriptClientReference { .. } - | ClientManifestEntryType::CssClientReference { .. }, - ) => { - // No need to explore these subgraphs ever, these are the leaves in the - // server component graph - GraphTraversalAction::Skip - } - // Continue on server components and through graphs of non-ClientReference - // modules, but only if our set of parent components has changed. - _ => { - if should_visit_children { - GraphTraversalAction::Continue - } else { - GraphTraversalAction::Skip - } - } - }) - }, - )?; - } - - // Filter down to just the client reference modules to reduce datastructure size - let server_components_for_client_references = module_to_server_component_bits - .into_iter() - .filter_map(|(k, v)| match manifest.get(&k) { - Some( - ClientManifestEntryType::CssClientReference(_) - | ClientManifestEntryType::EcmascriptClientReference { .. }, - ) => Some((k, RoaringBitmapWrapper(v))), - _ => None, - }) - .collect(); - - Ok(ClientReferenceManifest { - manifest, - server_components, - server_components_for_client_references, - } - .cell()) + Ok(Vc::cell(manifest)) } diff --git a/crates/next-api/src/dynamic_imports.rs b/crates/next-api/src/dynamic_imports.rs index 78bf12f41088c..799a275eb4b5a 100644 --- a/crates/next-api/src/dynamic_imports.rs +++ b/crates/next-api/src/dynamic_imports.rs @@ -19,7 +19,7 @@ //! to wait until all the dynamic components are being loaded, this ensures hydration mismatch //! won't occur -use anyhow::Result; +use anyhow::{Context, Result}; use next_core::{ next_app::ClientReferencesChunks, next_client_reference::EcmascriptClientReferenceModule, next_dynamic::NextDynamicEntryModule, @@ -66,8 +66,12 @@ pub(crate) async fn collect_next_dynamic_chunks( NextDynamicChunkAvailability::ClientReferences(client_reference_chunks) => { client_reference_chunks .client_component_client_chunks - .get(&parent_client_reference.unwrap()) - .unwrap() + .get( + &parent_client_reference.context( + "Parent client reference not found for next/dynamic import", + )?, + ) + .context("Client reference chunk group not found for next/dynamic import")? .availability_info } NextDynamicChunkAvailability::AvailabilityInfo(availability_info) => { diff --git a/crates/next-api/src/empty.rs b/crates/next-api/src/empty.rs index 7174aa69a1ca1..cc220d178458e 100644 --- a/crates/next-api/src/empty.rs +++ b/crates/next-api/src/empty.rs @@ -2,7 +2,7 @@ use anyhow::{Result, bail}; use turbo_tasks::{Completion, Vc}; use turbopack_core::module_graph::GraphEntries; -use crate::route::{Endpoint, EndpointOutput}; +use crate::route::{Endpoint, EndpointOutput, ModuleGraphs}; #[turbo_tasks::value] pub struct EmptyEndpoint; @@ -36,4 +36,9 @@ impl Endpoint for EmptyEndpoint { fn entries(self: Vc) -> Vc { GraphEntries::empty() } + + #[turbo_tasks::function] + fn module_graphs(self: Vc) -> Vc { + Vc::cell(vec![]) + } } diff --git a/crates/next-api/src/instrumentation.rs b/crates/next-api/src/instrumentation.rs index 51c07c7d3e919..a283a61150ea4 100644 --- a/crates/next-api/src/instrumentation.rs +++ b/crates/next-api/src/instrumentation.rs @@ -32,7 +32,7 @@ use crate::{ all_server_paths, get_js_paths_from_root, get_wasm_paths_from_root, wasm_paths_to_bindings, }, project::Project, - route::{Endpoint, EndpointOutput, EndpointOutputPaths}, + route::{Endpoint, EndpointOutput, EndpointOutputPaths, ModuleGraphs}, }; #[turbo_tasks::value] @@ -69,7 +69,7 @@ impl InstrumentationEndpoint { } #[turbo_tasks::function] - async fn core_modules(&self) -> Result> { + async fn entry_module(&self) -> Result>> { let userland_module = self .asset_context .process( @@ -80,6 +80,10 @@ impl InstrumentationEndpoint { .to_resolved() .await?; + if !self.is_edge { + return Ok(*userland_module); + } + let edge_entry_module = wrap_edge_entry( *self.asset_context, self.project.project_path().owned().await?, @@ -89,17 +93,13 @@ impl InstrumentationEndpoint { .to_resolved() .await?; - Ok(InstrumentationCoreModules { - userland_module, - edge_entry_module, - } - .cell()) + Ok(*edge_entry_module) } #[turbo_tasks::function] async fn edge_chunk_group(self: Vc) -> Result> { let this = self.await?; - let module = self.core_modules().await?.edge_entry_module; + let module = self.entry_module().to_resolved().await?; let module_graph = this.project.module_graph(*module); @@ -118,7 +118,7 @@ impl InstrumentationEndpoint { let chunking_context = this.project.server_chunking_context(false); - let userland_module = self.core_modules().await?.userland_module; + let userland_module = self.entry_module().to_resolved().await?; let module_graph = this.project.module_graph(*userland_module); let Some(module) = ResolvedVc::try_downcast(userland_module) else { @@ -200,12 +200,6 @@ impl InstrumentationEndpoint { } } -#[turbo_tasks::value] -struct InstrumentationCoreModules { - pub userland_module: ResolvedVc>, - pub edge_entry_module: ResolvedVc>, -} - #[turbo_tasks::value_impl] impl Endpoint for InstrumentationEndpoint { #[turbo_tasks::function] @@ -249,13 +243,15 @@ impl Endpoint for InstrumentationEndpoint { #[turbo_tasks::function] async fn entries(self: Vc) -> Result> { - let core_modules = self.core_modules().await?; - Ok(Vc::cell(vec![ChunkGroupEntry::Entry( - if self.await?.is_edge { - vec![core_modules.edge_entry_module] - } else { - vec![core_modules.userland_module] - }, - )])) + let entry_module = self.entry_module().to_resolved().await?; + Ok(Vc::cell(vec![ChunkGroupEntry::Entry(vec![entry_module])])) + } + + #[turbo_tasks::function] + async fn module_graphs(self: Vc) -> Result> { + let this = self.await?; + let module = self.entry_module(); + let module_graph = this.project.module_graph(module).to_resolved().await?; + Ok(Vc::cell(vec![module_graph])) } } diff --git a/crates/next-api/src/lib.rs b/crates/next-api/src/lib.rs index ba9df7726b5aa..9c8d2b4dc7249 100644 --- a/crates/next-api/src/lib.rs +++ b/crates/next-api/src/lib.rs @@ -3,6 +3,7 @@ #![feature(arbitrary_self_types_pointers)] #![feature(impl_trait_in_assoc_type)] +pub mod analyze; mod app; mod client_references; mod dynamic_imports; diff --git a/crates/next-api/src/loadable_manifest.rs b/crates/next-api/src/loadable_manifest.rs index cbf51d331a1c9..f1496ce1974f6 100644 --- a/crates/next-api/src/loadable_manifest.rs +++ b/crates/next-api/src/loadable_manifest.rs @@ -1,7 +1,6 @@ use anyhow::Result; use next_core::{next_manifests::LoadableManifest, util::NextRuntime}; -use rustc_hash::FxHashMap; -use turbo_tasks::{ResolvedVc, TryFlatJoinIterExt, Vc}; +use turbo_tasks::{FxIndexMap, ResolvedVc, TryFlatJoinIterExt, Vc}; use turbo_tasks_fs::{File, FileContent, FileSystemPath}; use turbopack_core::{ asset::AssetContent, @@ -21,7 +20,7 @@ pub async fn create_react_loadable_manifest( ) -> Result> { let dynamic_import_entries = &*dynamic_import_entries.await?; - let mut loadable_manifest: FxHashMap = FxHashMap::default(); + let mut loadable_manifest: FxIndexMap = FxIndexMap::default(); for (_, (module_id, chunk_output)) in dynamic_import_entries.into_iter() { let chunk_output = chunk_output.await?; diff --git a/crates/next-api/src/middleware.rs b/crates/next-api/src/middleware.rs index d5e75f30caf96..f90f093b7c199 100644 --- a/crates/next-api/src/middleware.rs +++ b/crates/next-api/src/middleware.rs @@ -5,9 +5,8 @@ use next_core::{ all_assets_from_entries, middleware::get_middleware_module, next_edge::entry::wrap_edge_entry, - next_manifests::{EdgeFunctionDefinition, MiddlewareMatcher, MiddlewaresManifestV2, Regions}, - parse_segment_config_from_source, - segment_config::ParseSegmentMode, + next_manifests::{EdgeFunctionDefinition, MiddlewaresManifestV2, ProxyMatcher, Regions}, + segment_config::NextSegmentConfig, util::{MiddlewareMatcherKind, NextRuntime}, }; use tracing::Instrument; @@ -39,7 +38,7 @@ use crate::{ get_wasm_paths_from_root, paths_to_bindings, wasm_paths_to_bindings, }, project::Project, - route::{Endpoint, EndpointOutput, EndpointOutputPaths}, + route::{Endpoint, EndpointOutput, EndpointOutputPaths, ModuleGraphs}, }; #[turbo_tasks::value] @@ -49,6 +48,8 @@ pub struct MiddlewareEndpoint { source: ResolvedVc>, app_dir: Option, ecmascript_client_reference_transition_name: Option, + config: ResolvedVc, + runtime: NextRuntime, } #[turbo_tasks::value_impl] @@ -60,6 +61,8 @@ impl MiddlewareEndpoint { source: ResolvedVc>, app_dir: Option, ecmascript_client_reference_transition_name: Option, + config: ResolvedVc, + runtime: NextRuntime, ) -> Vc { Self { project, @@ -67,6 +70,8 @@ impl MiddlewareEndpoint { source, app_dir, ecmascript_client_reference_transition_name, + config, + runtime, } .cell() } @@ -81,20 +86,20 @@ impl MiddlewareEndpoint { ) .module(); + let userland_path = userland_module.ident().path().await?; + let is_proxy = userland_path.file_stem() == Some("proxy"); + let module = get_middleware_module( *self.asset_context, self.project.project_path().owned().await?, userland_module, + is_proxy, ); - let runtime = parse_segment_config_from_source(*self.source, ParseSegmentMode::Base) - .await? - .runtime - .unwrap_or(NextRuntime::Edge); - - if matches!(runtime, NextRuntime::NodeJs) { + if matches!(self.runtime, NextRuntime::NodeJs) { return Ok(module); } + Ok(wrap_edge_entry( *self.asset_context, self.project.project_path().owned().await?, @@ -152,26 +157,23 @@ impl MiddlewareEndpoint { #[turbo_tasks::function] async fn output_assets(self: Vc) -> Result> { let this = self.await?; + let config = this.config.await?; - let config = - parse_segment_config_from_source(*self.await?.source, ParseSegmentMode::Base).await?; - let runtime = config.runtime.unwrap_or(NextRuntime::Edge); - - let next_config = this.project.next_config().await?; - let has_i18n = next_config.i18n.is_some(); - let has_i18n_locales = next_config - .i18n + let next_config = this.project.next_config(); + let i18n = next_config.i18n().await?; + let has_i18n = i18n.is_some(); + let has_i18n_locales = i18n .as_ref() .map(|i18n| i18n.locales.len() > 1) .unwrap_or(false); - let base_path = next_config.base_path.as_ref(); + let base_path = next_config.base_path().await?; let matchers = if let Some(matchers) = config.middleware_matcher.as_ref() { matchers .iter() .map(|matcher| { let mut matcher = match matcher { - MiddlewareMatcherKind::Str(matcher) => MiddlewareMatcher { + MiddlewareMatcherKind::Str(matcher) => ProxyMatcher { original_source: matcher.as_str().into(), ..Default::default() }, @@ -202,7 +204,7 @@ impl MiddlewareEndpoint { source.insert_str(0, "/:nextData(_next/data/[^/]{1,})?"); - if let Some(base_path) = base_path { + if let Some(base_path) = base_path.as_ref() { source.insert_str(0, base_path); } @@ -216,14 +218,14 @@ impl MiddlewareEndpoint { }) .collect() } else { - vec![MiddlewareMatcher { + vec![ProxyMatcher { regexp: Some(rcstr!("^/.*$")), original_source: rcstr!("/:path*"), ..Default::default() }] }; - if matches!(runtime, NextRuntime::NodeJs) { + if matches!(this.runtime, NextRuntime::NodeJs) { let chunk = self.node_chunk().to_resolved().await?; let mut output_assets = vec![chunk]; if this.project.next_mode().await?.is_production() { @@ -384,4 +386,15 @@ impl Endpoint for MiddlewareEndpoint { self.entry_module().to_resolved().await?, ])])) } + + #[turbo_tasks::function] + async fn module_graphs(self: Vc) -> Result> { + let this = self.await?; + let module_graph = this + .project + .module_graph(self.entry_module()) + .to_resolved() + .await?; + Ok(Vc::cell(vec![module_graph])) + } } diff --git a/crates/next-api/src/module_graph.rs b/crates/next-api/src/module_graph.rs index 8afabc9a64f0e..04464d9338e01 100644 --- a/crates/next-api/src/module_graph.rs +++ b/crates/next-api/src/module_graph.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::hash_map::Entry}; +use std::borrow::Cow; use anyhow::{Ok, Result}; use either::Either; @@ -28,7 +28,7 @@ use turbopack_core::{ }; use crate::{ - client_references::{ClientManifestEntryType, ClientReferenceManifest, map_client_references}, + client_references::{ClientManifestEntryType, ClientReferenceData, map_client_references}, dynamic_imports::{DynamicImportEntries, DynamicImportEntriesMapType, map_next_dynamic}, server_actions::{AllActions, AllModuleActions, map_server_actions, to_rsc_context}, }; @@ -232,7 +232,7 @@ impl ServerActionsGraph { }) .try_flat_join() .await?; - Ok(Vc::cell(actions.into_iter().collect())) + Ok(Vc::cell(actions)) } .instrument(span) .await @@ -244,7 +244,7 @@ pub struct ClientReferencesGraph { is_single_page: bool, graph: ResolvedVc, /// List of client references (modules that entries into the client graph) - data: ResolvedVc, + data: ResolvedVc, } #[turbo_tasks::value_impl] @@ -289,129 +289,116 @@ impl ClientReferencesGraph { // Because we care about 'evaluation order' we need to collect client references in the // post_order callbacks which is the same as evaluation order let mut client_references = Vec::new(); - let mut client_reference_modules = Vec::new(); - let mut server_components = FxIndexSet::default(); let mut server_utils = FxIndexSet::default(); - // Track how we reached each client reference. This way if a client reference is - // referenced by the root and by a server component we don't only associate it with the - // server component. - #[derive(PartialEq, Eq, Copy, Clone)] - enum ParentType { - ServerComponent, - Page, - Both, - } - impl ParentType { - fn merge(left: Self, right: Self) -> Self { - if left == right { - left - } else { - // One is Both or one is ServerComponent and the other is Page, which means - // Both - Self::Both - } - } - } - // Perform a DFS traversal to collect all client references and the set of server - // components for each module. - graph.traverse_edges_from_entries_dfs( - entries, - // state_map is `module -> ParentType` to track whether the module is reachable - // directly from an entry point. - &mut FxHashMap::default(), - |parent_info, node, state_map| { - let module = node.module(); - let module_type = data.manifest.get(&module); - - let parent_type = - if let Some(ClientManifestEntryType::ServerComponent(_)) = module_type { - ParentType::ServerComponent - } else if let Some((parent_node, _)) = parent_info { - *state_map.get(&parent_node.module).unwrap() - } else { - // a root node - ParentType::Page - }; - - match state_map.entry(module) { - Entry::Occupied(mut occupied_entry) => { - let current = occupied_entry.get_mut(); - let merged = ParentType::merge(*current, parent_type); - if merged != parent_type { - *current = merged; - } - } - Entry::Vacant(vacant_entry) => { - vacant_entry.insert(parent_type); - } - } + let mut server_components = FxIndexSet::default(); + // Perform a DFS traversal to find all server components included by this page. + graph.traverse_nodes_from_entries( + entries, + &mut (), + |node, _| { + let module_type = data.get(&node.module); Ok(match module_type { Some( ClientManifestEntryType::EcmascriptClientReference { .. } - | ClientManifestEntryType::CssClientReference { .. }, + | ClientManifestEntryType::CssClientReference { .. } + | ClientManifestEntryType::ServerComponent { .. }, ) => GraphTraversalAction::Skip, - _ => GraphTraversalAction::Continue, + None => GraphTraversalAction::Continue, }) }, - |_, node, state_map| { - let module = node.module(); + |node, _| { if let Some(server_util_module) = - ResolvedVc::try_downcast_type::(module) + ResolvedVc::try_downcast_type::(node.module) { + // Server utility used by the template, not a server component server_utils.insert(server_util_module); + return Ok(()); } - let Some(module_type) = data.manifest.get(&module) else { - return Ok(()); - }; + let module_type = data.get(&node.module); let ty = match module_type { - ClientManifestEntryType::EcmascriptClientReference { + Some(ClientManifestEntryType::EcmascriptClientReference { module, ssr_module: _, - } => ClientReferenceType::EcmascriptClientReference(*module), - ClientManifestEntryType::CssClientReference(module) => { + }) => ClientReferenceType::EcmascriptClientReference(*module), + Some(ClientManifestEntryType::CssClientReference(module)) => { ClientReferenceType::CssClientReference(*module) } - ClientManifestEntryType::ServerComponent(sc) => { + Some(ClientManifestEntryType::ServerComponent(sc)) => { server_components.insert(*sc); return Ok(()); } + None => { + return Ok(()); + } }; - if *state_map.get(&module).unwrap() == ParentType::ServerComponent { - // This is only reachable through server components, we need to wait to - // compute the client references until we have seen all server components - // reachable by this entrypoint, then we can intersect that with the set of - // server components that depend on this client reference - client_reference_modules.push((module, ty)); - } else { - // Otherwise there is some path from the root directly to the reference, - // just associate it with the root. - client_references.push(ClientReference { - server_component: None, - ty, - }) - } + // Client reference used by the template, not a server component + client_references.push(ClientReference { + server_component: None, + ty, + }); Ok(()) }, )?; - // Now compute all the parent components for each client reference module reachable from - // server components - client_references.extend(client_reference_modules.into_iter().flat_map( - |(module, ty)| { - data.server_components_for_client_reference(module) - .filter(|sc| server_components.contains(sc)) - .map(move |sc| ClientReference { + // Traverse each server component separately. Because not all server components are + // necessarily rendered at the same time (not-found, or parallel routes), we need to + // determine the order of client references individually for each server component. + for sc in server_components.iter().copied() { + graph.traverse_nodes_from_entries( + std::iter::once(ResolvedVc::upcast(sc)), + &mut (), + |node, _| { + let module = node.module; + let module_type = data.get(&module); + + Ok(match module_type { + Some( + ClientManifestEntryType::EcmascriptClientReference { .. } + | ClientManifestEntryType::CssClientReference { .. }, + ) => GraphTraversalAction::Skip, + _ => GraphTraversalAction::Continue, + }) + }, + |node, _| { + let module = node.module; + if let Some(server_util_module) = + ResolvedVc::try_downcast_type::(module) + { + server_utils.insert(server_util_module); + } + + let Some(module_type) = data.get(&module) else { + return Ok(()); + }; + + let ty = match module_type { + ClientManifestEntryType::EcmascriptClientReference { + module, + ssr_module: _, + } => ClientReferenceType::EcmascriptClientReference(*module), + ClientManifestEntryType::CssClientReference(module) => { + ClientReferenceType::CssClientReference(*module) + } + ClientManifestEntryType::ServerComponent(_) => { + return Ok(()); + } + }; + + client_references.push(ClientReference { server_component: Some(sc), ty, - }) - }, - )); + }); + + Ok(()) + }, + )?; + } Ok(ClientReferenceGraphResult { client_references: client_references.into_iter().collect(), @@ -500,7 +487,7 @@ impl Issue for CssGlobalImportIssue { #[turbo_tasks::function] fn stage(&self) -> Vc { - IssueStage::ProcessModule.into() + IssueStage::ProcessModule.cell() } // TODO(PACK-4879): compute the source information by following the module references @@ -581,14 +568,7 @@ async fn validate_pages_css_imports( .map(async |issue| { // We allow imports of global CSS files which are inside of `node_modules`. Ok( - if !issue - .module - .ident() - .path() - .await? - .path - .contains("/node_modules/") - { + if !issue.module.ident().path().await?.is_in_node_modules() { Some(issue) } else { None @@ -730,7 +710,7 @@ impl GlobalBuildInformation { .try_flat_join() .await?; - Ok(Vc::cell(result.into_iter().collect())) + Ok(Vc::cell(result)) } } .instrument(span) @@ -842,7 +822,7 @@ pub async fn get_global_information_for_endpoint( get_global_information_for_endpoint_inner_operation(module_graph, is_single_page); let result_vc = if !is_single_page { let result_vc = result_op.resolve_strongly_consistent().await?; - let _issues = result_op.take_collectibles::>(); + result_op.drop_collectibles::>(); *result_vc } else { result_op.connect() diff --git a/crates/next-api/src/next_server_nft.rs b/crates/next-api/src/next_server_nft.rs index ee576ff6b6d5a..c1d6e996bbef0 100644 --- a/crates/next-api/src/next_server_nft.rs +++ b/crates/next-api/src/next_server_nft.rs @@ -187,7 +187,7 @@ impl ServerNftJsonAsset { let cache_handlers = self .project .next_config() - .experimental_cache_handlers(project_path.clone()) + .cache_handlers(project_path.clone()) .await?; // These are used by packages/next/src/server/require-hook.ts diff --git a/crates/next-api/src/nft_json.rs b/crates/next-api/src/nft_json.rs index b156eda22d6f3..bfdaeecc2b52b 100644 --- a/crates/next-api/src/nft_json.rs +++ b/crates/next-api/src/nft_json.rs @@ -82,19 +82,17 @@ fn get_output_specifier( ident_folder_in_project_fs: &FileSystemPath, output_root: &FileSystemPath, project_root: &FileSystemPath, -) -> Result> { +) -> Result { // include assets in the outputs such as referenced chunks if path_ref.is_inside_ref(output_root) { - return Ok(Some(ident_folder.get_relative_path_to(path_ref).unwrap())); + return Ok(ident_folder.get_relative_path_to(path_ref).unwrap()); } // include assets in the project root such as images and traced references (externals) if path_ref.is_inside_ref(project_root) { - return Ok(Some( - ident_folder_in_project_fs - .get_relative_path_to(path_ref) - .unwrap(), - )); + return Ok(ident_folder_in_project_fs + .get_relative_path_to(path_ref) + .unwrap()); } // This should effectively be unreachable @@ -234,15 +232,20 @@ impl Asset for NftJsonAsset { }; // Collect base assets first - for referenced_chunk in - all_assets_from_entries_filtered(Vc::cell(entries), Some(client_root), exclude_glob) - .await? - { - if chunk.eq(referenced_chunk) { + let all_assets = all_assets_from_entries_filtered( + Vc::cell(entries), + Some(client_root), + exclude_glob, + ) + .await?; + + for referenced_chunk in all_assets.iter().copied() { + if chunk.eq(&referenced_chunk) { continue; } let referenced_chunk_path = referenced_chunk.path().await?; + if referenced_chunk_path.has_extension(".map") { continue; } @@ -279,16 +282,13 @@ impl Asset for NftJsonAsset { } } - let Some(specifier) = get_output_specifier( + let specifier = get_output_specifier( &referenced_chunk_path, &ident_folder, &ident_folder_in_project_fs, &output_root_ref, &project_root_ref, - )? - else { - continue; - }; + )?; result.insert(specifier); } @@ -357,7 +357,7 @@ impl Asset for NftJsonAsset { /// The glob walker in turbopack is somewhat naive so we handle relative path directives first so /// traversal doesn't need to consider them and can just traverse 'down' the tree. /// The main alternative is to merge glob evaluation with directory traversal which is what the npm -/// `glob` package does, but this would be a substantial rewrite.` +/// `glob` package does, but this would be a substantial rewrite. pub(crate) fn relativize_glob( glob: &str, relative_to: FileSystemPath, @@ -408,7 +408,9 @@ pub async fn all_assets_from_entries_filtered( Ok(( *asset, if emit_spans { - Some(asset.path().to_string().await?) + // INVALIDATION: we don't need to invalidate the list of assets when + // the span name changes + Some(asset.path_string().untracked().await?) } else { None }, @@ -476,12 +478,12 @@ async fn get_referenced_server_assets( client_root: Option, exclude_glob: Option>, ) -> Result>, Option>)>> { - asset - .references() - .await? - .iter() + let refs = asset.references().await?; + + refs.iter() .map(async |asset| { let asset_path = asset.path().await?; + if let Some(client_root) = &client_root && asset_path.is_inside_ref(client_root) { @@ -498,7 +500,9 @@ async fn get_referenced_server_assets( Ok(Some(( *asset, if emit_spans { - Some(asset.path().to_string().await?) + // INVALIDATION: we don't need to invalidate the list of assets when the span + // name changes + Some(asset.path_string().untracked().await?) } else { None }, diff --git a/crates/next-api/src/operation.rs b/crates/next-api/src/operation.rs index 5ad2784e172df..4056a1cf7e7f2 100644 --- a/crates/next-api/src/operation.rs +++ b/crates/next-api/src/operation.rs @@ -37,8 +37,8 @@ async fn entrypoints_without_collectibles_operation( entrypoints: OperationVc, ) -> Result> { let _ = entrypoints.resolve_strongly_consistent().await?; - let _ = entrypoints.take_collectibles::>(); - let _ = entrypoints.take_issues(); + entrypoints.drop_collectibles::>(); + entrypoints.drop_issues(); let _ = get_effects(entrypoints).await?; Ok(entrypoints.connect()) } @@ -55,8 +55,9 @@ impl EntrypointsOperation { .iter() .map(|(k, v)| (k.clone(), pick_route(entrypoints, k.clone(), v))) .collect(), - middleware: e.middleware.as_ref().map(|_| MiddlewareOperation { + middleware: e.middleware.as_ref().map(|m| MiddlewareOperation { endpoint: pick_endpoint(entrypoints, EndpointSelector::Middleware), + is_proxy: m.is_proxy, }), instrumentation: e .instrumentation @@ -212,6 +213,7 @@ pub struct InstrumentationOperation { #[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue)] pub struct MiddlewareOperation { pub endpoint: OperationVc, + pub is_proxy: bool, } #[turbo_tasks::value(shared)] diff --git a/crates/next-api/src/pages.rs b/crates/next-api/src/pages.rs index fb5d37c0771cf..7307d313e37ff 100644 --- a/crates/next-api/src/pages.rs +++ b/crates/next-api/src/pages.rs @@ -12,8 +12,8 @@ use next_core::{ next_dynamic::NextDynamicTransition, next_edge::route_regex::get_named_middleware_regex, next_manifests::{ - BuildManifest, ClientBuildManifest, EdgeFunctionDefinition, MiddlewareMatcher, - MiddlewaresManifestV2, PagesManifest, Regions, + BuildManifest, ClientBuildManifest, EdgeFunctionDefinition, MiddlewaresManifestV2, + PagesManifest, ProxyMatcher, Regions, }, next_pages::create_page_ssr_entry_module, next_server::{ @@ -78,7 +78,7 @@ use crate::{ get_wasm_paths_from_root, paths_to_bindings, wasm_paths_to_bindings, }, project::Project, - route::{Endpoint, EndpointOutput, EndpointOutputPaths, Route, Routes}, + route::{Endpoint, EndpointOutput, EndpointOutputPaths, ModuleGraphs, Route, Routes}, webpack_stats::generate_webpack_stats, }; @@ -1500,7 +1500,7 @@ impl PageEndpoint { }; let named_regex = get_named_middleware_regex(&this.pathname).into(); - let matchers = MiddlewareMatcher { + let matchers = ProxyMatcher { regexp: Some(named_regex), original_source: this.pathname.clone(), ..Default::default() @@ -1728,6 +1728,13 @@ impl Endpoint for PageEndpoint { Ok(Vc::cell(modules)) } + + #[turbo_tasks::function] + async fn module_graphs(self: Vc) -> Result> { + let client_module_graph = self.client_module_graph().to_resolved().await?; + let ssr_module_graph = self.ssr_module_graph().to_resolved().await?; + Ok(Vc::cell(vec![client_module_graph, ssr_module_graph])) + } } #[turbo_tasks::value] diff --git a/crates/next-api/src/project.rs b/crates/next-api/src/project.rs index d0aecf389a10f..e3b04f77b5353 100644 --- a/crates/next-api/src/project.rs +++ b/crates/next-api/src/project.rs @@ -84,7 +84,7 @@ use crate::{ instrumentation::InstrumentationEndpoint, middleware::MiddlewareEndpoint, pages::PagesProject, - route::{AppPageRoute, Endpoint, Endpoints, Route}, + route::{Endpoint, EndpointGroup, EndpointGroupKey, EndpointGroups, Endpoints, Route}, versioned_content_map::VersionedContentMap, }; @@ -255,6 +255,7 @@ pub struct DefineEnv { #[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue)] pub struct Middleware { pub endpoint: ResolvedVc>, + pub is_proxy: bool, } #[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue)] @@ -478,18 +479,15 @@ impl ProjectContainer { current_node_js_version = options.current_node_js_version.clone(); } - let dist_dir = next_config - .await? - .dist_dir - .as_ref() - .map_or_else(|| rcstr!(".next"), |d| d.clone()); - + let dist_dir = next_config.dist_dir().owned().await?; + let dist_dir_root = next_config.dist_dir_root().owned().await?; Ok(Project { root_path, project_path, watch, next_config: next_config.to_resolved().await?, dist_dir, + dist_dir_root, env: ResolvedVc::upcast(env_map.to_resolved().await?), define_env: define_env.to_resolved().await?, browserslist_query, @@ -536,6 +534,7 @@ impl ProjectContainer { } } +#[derive(Clone)] #[turbo_tasks::value] pub struct Project { /// An absolute root path (Windows or Unix path) from which all files must be nested under. @@ -553,6 +552,11 @@ pub struct Project { /// E.g. `.next` dist_dir: RcStr, + /// The root directory of the distDir. Generally the same as `distDir` but when + /// `isolatedDevBuild` is true it is the parent directory of `distDir`. This is used to + /// ensure that the bundler doesn't traverse into the output directory. + dist_dir_root: RcStr, + /// Filesystem watcher options. watch: WatchOptions, @@ -667,8 +671,23 @@ impl Project { } #[turbo_tasks::function] - pub fn project_fs(&self) -> Vc { - DiskFileSystem::new(PROJECT_FILESYSTEM_NAME.into(), self.root_path.clone()) + pub fn project_fs(&self) -> Result> { + let denied_path = match join_path(&self.project_path, &self.dist_dir_root) { + Some(dist_dir_root) => dist_dir_root.into(), + None => { + bail!( + "Invalid distDirRoot: {:?}. distDirRoot should not navigate out of the \ + projectPath.", + self.dist_dir_root + ); + } + }; + + Ok(DiskFileSystem::new_with_denied_path( + rcstr!(PROJECT_FILESYSTEM_NAME), + self.root_path.clone(), + denied_path, + )) } #[turbo_tasks::function] @@ -722,13 +741,17 @@ impl Project { #[turbo_tasks::function] pub async fn client_relative_path(self: Vc) -> Result> { - let next_config = self.next_config().await?; + let next_config = self.next_config(); Ok(self .client_root() .await? .join(&format!( "{}/_next", - next_config.base_path.clone().unwrap_or_default(), + next_config + .base_path() + .await? + .as_deref() + .unwrap_or_default(), ))? .cell()) } @@ -771,7 +794,7 @@ impl Project { } #[turbo_tasks::function] - pub(super) fn next_config(&self) -> Vc { + pub fn next_config(&self) -> Vc { *self.next_config } @@ -844,67 +867,77 @@ impl Project { } #[turbo_tasks::function] - pub async fn get_all_endpoints(self: Vc, app_dir_only: bool) -> Result> { - let mut endpoints = Vec::new(); + pub async fn get_all_endpoint_groups( + self: Vc, + app_dir_only: bool, + ) -> Result> { + let mut endpoint_groups = Vec::new(); let entrypoints = self.entrypoints().await?; - let mut is_pages_entries_added = false; + let mut add_pages_entries = false; if let Some(middleware) = &entrypoints.middleware { - endpoints.push(middleware.endpoint); + endpoint_groups.push(( + EndpointGroupKey::Middleware, + EndpointGroup::from(middleware.endpoint), + )); } if let Some(instrumentation) = &entrypoints.instrumentation { - endpoints.push(instrumentation.node_js); - endpoints.push(instrumentation.edge); + endpoint_groups.push(( + EndpointGroupKey::Instrumentation, + EndpointGroup::from(instrumentation.node_js), + )); + endpoint_groups.push(( + EndpointGroupKey::InstrumentationEdge, + EndpointGroup::from(instrumentation.edge), + )); } - for (_, route) in entrypoints.routes.iter() { + for (key, route) in entrypoints.routes.iter() { match route { Route::Page { html_endpoint, data_endpoint, } => { if !app_dir_only { - endpoints.push(*html_endpoint); - if !is_pages_entries_added { - endpoints.push(entrypoints.pages_error_endpoint); - endpoints.push(entrypoints.pages_app_endpoint); - endpoints.push(entrypoints.pages_document_endpoint); - is_pages_entries_added = true; - } - // This only exists in development mode for HMR - if let Some(data_endpoint) = data_endpoint { - endpoints.push(*data_endpoint); - } + endpoint_groups.push(( + EndpointGroupKey::Route(key.clone()), + EndpointGroup { + primary: vec![*html_endpoint], + // This only exists in development mode for HMR + additional: data_endpoint.iter().copied().collect(), + }, + )); + add_pages_entries = true; } } Route::PageApi { endpoint } => { if !app_dir_only { - endpoints.push(*endpoint); - if !is_pages_entries_added { - endpoints.push(entrypoints.pages_error_endpoint); - endpoints.push(entrypoints.pages_app_endpoint); - endpoints.push(entrypoints.pages_document_endpoint); - is_pages_entries_added = true; - } + endpoint_groups.push(( + EndpointGroupKey::Route(key.clone()), + EndpointGroup::from(*endpoint), + )); + add_pages_entries = true; } } Route::AppPage(page_routes) => { - for AppPageRoute { - original_name: _, - html_endpoint, - rsc_endpoint: _, - } in page_routes - { - endpoints.push(*html_endpoint); - } + endpoint_groups.push(( + EndpointGroupKey::Route(key.clone()), + EndpointGroup { + primary: page_routes.iter().map(|r| r.html_endpoint).collect(), + additional: Vec::new(), + }, + )); } Route::AppRoute { original_name: _, endpoint, } => { - endpoints.push(*endpoint); + endpoint_groups.push(( + EndpointGroupKey::Route(key.clone()), + EndpointGroup::from(*endpoint), + )); } Route::Conflict => { tracing::info!("WARN: conflict"); @@ -912,6 +945,36 @@ impl Project { } } + if add_pages_entries { + endpoint_groups.push(( + EndpointGroupKey::PagesError, + EndpointGroup::from(entrypoints.pages_error_endpoint), + )); + endpoint_groups.push(( + EndpointGroupKey::PagesApp, + EndpointGroup::from(entrypoints.pages_app_endpoint), + )); + endpoint_groups.push(( + EndpointGroupKey::PagesDocument, + EndpointGroup::from(entrypoints.pages_document_endpoint), + )); + } + + Ok(Vc::cell(endpoint_groups)) + } + + #[turbo_tasks::function] + pub async fn get_all_endpoints(self: Vc, app_dir_only: bool) -> Result> { + let mut endpoints = Vec::new(); + for (_key, group) in self.get_all_endpoint_groups(app_dir_only).await?.iter() { + for &endpoint in group.primary.iter() { + endpoints.push(endpoint); + } + for &endpoint in group.additional.iter() { + endpoints.push(endpoint); + } + } + Ok(Vc::cell(endpoints)) } @@ -977,11 +1040,20 @@ impl Project { } #[turbo_tasks::function] - pub async fn whole_app_module_graphs(self: ResolvedVc) -> Result> { + pub async fn whole_app_module_graphs( + self: ResolvedVc, + ) -> Result> { async move { let module_graphs_op = whole_app_module_graph_operation(self); - let module_graphs_vc = module_graphs_op.resolve_strongly_consistent().await?; - let _ = module_graphs_op.take_issues(); + let module_graphs_vc = if self.next_mode().await?.is_production() { + module_graphs_op.connect() + } else { + // In development mode, we need to to take and drop the issues, otherwise every + // route will report all issues. + let vc = module_graphs_op.resolve_strongly_consistent().await?; + module_graphs_op.drop_issues(); + *vc + }; // At this point all modules have been computed and we can get rid of the node.js // process pools @@ -991,7 +1063,7 @@ impl Project { turbopack_node::evaluate::scale_zero(); } - Ok(*module_graphs_vc) + Ok(module_graphs_vc) } .instrument(tracing::info_span!("module graph for app")) .await @@ -1049,6 +1121,7 @@ impl Project { no_mangling: self.no_mangling(), scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()), debug_ids: self.next_config().turbopack_debug_ids(), + should_use_absolute_url_references: self.next_config().inline_css(), })) } @@ -1069,13 +1142,12 @@ impl Project { turbo_source_maps: self.next_config().server_source_maps(), no_mangling: self.no_mangling(), scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()), + debug_ids: self.next_config().turbopack_debug_ids(), + client_root: self.client_relative_path().owned().await?, + asset_prefix: self.next_config().computed_asset_prefix().owned().await?, }; Ok(if client_assets { - get_server_chunking_context_with_client_assets( - options, - self.client_relative_path().owned().await?, - self.next_config().computed_asset_prefix().owned().await?, - ) + get_server_chunking_context_with_client_assets(options) } else { get_server_chunking_context(options) }) @@ -1098,13 +1170,11 @@ impl Project { turbo_source_maps: self.next_config().server_source_maps(), no_mangling: self.no_mangling(), scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()), + client_root: self.client_relative_path().owned().await?, + asset_prefix: self.next_config().computed_asset_prefix().owned().await?, }; Ok(if client_assets { - get_edge_chunking_context_with_client_assets( - options, - self.client_relative_path().owned().await?, - self.next_config().computed_asset_prefix(), - ) + get_edge_chunking_context_with_client_assets(options) } else { get_edge_chunking_context(options) }) @@ -1143,8 +1213,8 @@ impl Project { let config = self.next_config(); emit_event( - "skipMiddlewareUrlNormalize", - *config.skip_middleware_url_normalize().await?, + "skipProxyUrlNormalize", + *config.skip_proxy_url_normalize().await?, ); emit_event( @@ -1156,26 +1226,38 @@ impl Project { *config.persistent_caching_enabled().await?, ); - let config = &config.await?; - - emit_event("modularizeImports", config.modularize_imports.is_some()); - emit_event("transpilePackages", config.transpile_packages.is_some()); + emit_event( + "modularizeImports", + !config.modularize_imports().await?.is_empty(), + ); + emit_event( + "transpilePackages", + !config.transpile_packages().await?.is_empty(), + ); emit_event("turbotrace", false); // compiler options - let compiler_options = config.compiler.as_ref(); - let swc_relay_enabled = compiler_options.and_then(|c| c.relay.as_ref()).is_some(); + let compiler_options = config.compiler().await?; + let swc_relay_enabled = compiler_options.relay.is_some(); let styled_components_enabled = compiler_options - .and_then(|c| c.styled_components.as_ref().map(|sc| sc.is_enabled())) + .styled_components + .as_ref() + .map(|sc| sc.is_enabled()) .unwrap_or_default(); let react_remove_properties_enabled = compiler_options - .and_then(|c| c.react_remove_properties.as_ref().map(|rc| rc.is_enabled())) + .react_remove_properties + .as_ref() + .map(|rc| rc.is_enabled()) .unwrap_or_default(); let remove_console_enabled = compiler_options - .and_then(|c| c.remove_console.as_ref().map(|rc| rc.is_enabled())) + .remove_console + .as_ref() + .map(|rc| rc.is_enabled()) .unwrap_or_default(); let emotion_enabled = compiler_options - .and_then(|c| c.emotion.as_ref().map(|e| e.is_enabled())) + .emotion + .as_ref() + .map(|e| e.is_enabled()) .unwrap_or_default(); emit_event("swcRelay", swc_relay_enabled); @@ -1245,9 +1327,11 @@ impl Project { let pages_error_endpoint = self.pages_project().error_endpoint().to_resolved().await?; let middleware = self.find_middleware(); - let middleware = if let FindContextFileResult::Found(..) = *middleware.await? { + let middleware = if let FindContextFileResult::Found(fs_path, _) = &*middleware.await? { + let is_proxy = fs_path.file_stem() == Some("proxy"); Some(Middleware { endpoint: self.middleware_endpoint().to_resolved().await?, + is_proxy, }) } else { None @@ -1396,28 +1480,6 @@ impl Project { ))) } - #[turbo_tasks::function] - async fn middleware_context(self: Vc) -> Result>> { - let edge_module_context = self.edge_middleware_context(); - - let middleware = self.find_middleware(); - let FindContextFileResult::Found(fs_path, _) = &*middleware.await? else { - return Ok(edge_module_context); - }; - let source = Vc::upcast(FileSource::new(fs_path.clone())); - - let runtime = parse_segment_config_from_source(source, ParseSegmentMode::Base) - .await? - .runtime - .unwrap_or(NextRuntime::Edge); - - if matches!(runtime, NextRuntime::NodeJs) { - Ok(self.node_middleware_context()) - } else { - Ok(edge_module_context) - } - } - #[turbo_tasks::function] async fn find_middleware(self: Vc) -> Result> { Ok(find_context_file( @@ -1442,7 +1504,25 @@ impl Project { .as_ref() .map(|_| AppProject::client_transition_name()); - let middleware_asset_context = self.middleware_context(); + let is_proxy = fs_path.file_stem() == Some("proxy"); + let config = parse_segment_config_from_source( + source, + if is_proxy { + ParseSegmentMode::Proxy + } else { + ParseSegmentMode::Base + }, + ); + let runtime = config.await?.runtime.unwrap_or(if is_proxy { + NextRuntime::NodeJs + } else { + NextRuntime::Edge + }); + + let middleware_asset_context = match runtime { + NextRuntime::NodeJs => self.node_middleware_context(), + NextRuntime::Edge => self.edge_middleware_context(), + }; Ok(Vc::upcast(MiddlewareEndpoint::new( self, @@ -1450,6 +1530,8 @@ impl Project { source, app_dir.clone(), ecmascript_client_reference_transition_name, + config, + runtime, ))) } @@ -1804,6 +1886,15 @@ impl Project { Ok(Vc::cell(None)) } } + + #[turbo_tasks::function] + pub async fn with_next_config(&self, next_config: Vc) -> Result> { + Ok(Self { + next_config: next_config.to_resolved().await?, + ..(*self).clone() + } + .cell()) + } } // This is a performance optimization. This function is a root aggregation function that @@ -1811,7 +1902,7 @@ impl Project { #[turbo_tasks::function(operation)] async fn whole_app_module_graph_operation( project: ResolvedVc, -) -> Result> { +) -> Result> { mark_root(); let should_trace = project.next_mode().await?.is_production(); @@ -1829,7 +1920,7 @@ async fn whole_app_module_graph_operation( ); let full = ModuleGraph::from_graphs(vec![base_single_module_graph, additional_module_graph]); - Ok(ModuleGraphs { + Ok(BaseAndFullModuleGraph { base: base.to_resolved().await?, full: full.to_resolved().await?, } @@ -1837,7 +1928,7 @@ async fn whole_app_module_graph_operation( } #[turbo_tasks::value(shared)] -pub struct ModuleGraphs { +pub struct BaseAndFullModuleGraph { pub base: ResolvedVc, pub full: ResolvedVc, } diff --git a/crates/next-api/src/route.rs b/crates/next-api/src/route.rs index 8da029b73ba0e..2dd77f52dd19e 100644 --- a/crates/next-api/src/route.rs +++ b/crates/next-api/src/route.rs @@ -1,9 +1,11 @@ +use std::fmt::Display; + use anyhow::Result; use serde::{Deserialize, Serialize}; use turbo_rcstr::RcStr; use turbo_tasks::{ - Completion, FxIndexMap, NonLocalValue, OperationVc, ResolvedVc, Vc, debug::ValueDebugFormat, - trace::TraceRawVcs, + Completion, FxIndexMap, FxIndexSet, NonLocalValue, OperationVc, ResolvedVc, TryFlatJoinIterExt, + TryJoinIterExt, Vc, debug::ValueDebugFormat, trace::TraceRawVcs, }; use turbopack_core::{ module_graph::{GraphEntries, ModuleGraph}, @@ -47,6 +49,9 @@ pub enum Route { Conflict, } +#[turbo_tasks::value(transparent)] +pub struct ModuleGraphs(Vec>); + #[turbo_tasks::value_trait] pub trait Endpoint { #[turbo_tasks::function] @@ -65,8 +70,108 @@ pub trait Endpoint { fn additional_entries(self: Vc, _graph: Vc) -> Vc { GraphEntries::empty() } + #[turbo_tasks::function] + fn module_graphs(self: Vc) -> Vc; } +#[derive( + TraceRawVcs, + Serialize, + Deserialize, + PartialEq, + Eq, + ValueDebugFormat, + Clone, + Debug, + NonLocalValue, +)] +pub enum EndpointGroupKey { + Instrumentation, + InstrumentationEdge, + Middleware, + PagesError, + PagesApp, + PagesDocument, + Route(RcStr), +} + +impl Display for EndpointGroupKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + EndpointGroupKey::Instrumentation => write!(f, "instrumentation"), + EndpointGroupKey::InstrumentationEdge => write!(f, "instrumentation-edge"), + EndpointGroupKey::Middleware => write!(f, "middleware"), + EndpointGroupKey::PagesError => write!(f, "_error"), + EndpointGroupKey::PagesApp => write!(f, "_app"), + EndpointGroupKey::PagesDocument => write!(f, "_document"), + EndpointGroupKey::Route(route) => write!(f, "/{}", route), + } + } +} + +#[derive( + TraceRawVcs, + Serialize, + Deserialize, + PartialEq, + Eq, + ValueDebugFormat, + Clone, + Debug, + NonLocalValue, +)] +pub struct EndpointGroup { + pub primary: Vec>>, + pub additional: Vec>>, +} + +impl EndpointGroup { + pub fn from(endpoint: ResolvedVc>) -> Self { + Self { + primary: vec![endpoint], + additional: vec![], + } + } + + pub fn output_assets(&self) -> Vc { + output_of_endpoints(self.primary.iter().map(|endpoint| **endpoint).collect()) + } + + pub fn module_graphs(&self) -> Vc { + module_graphs_of_endpoints(self.primary.iter().map(|endpoint| **endpoint).collect()) + } +} + +#[turbo_tasks::function] +async fn output_of_endpoints(endpoints: Vec>>) -> Result> { + let assets = endpoints + .iter() + .map(async |endpoint| Ok(*endpoint.output().await?.output_assets)) + .try_join() + .await?; + Ok(OutputAssets::concat(assets)) +} + +#[turbo_tasks::function] +async fn module_graphs_of_endpoints( + endpoints: Vec>>, +) -> Result> { + let module_graphs = endpoints + .iter() + .map(async |endpoint| Ok(endpoint.module_graphs().await?.into_iter())) + .try_flat_join() + .await? + .into_iter() + .copied() + .collect::>() + .into_iter() + .collect::>(); + Ok(Vc::cell(module_graphs)) +} + +#[turbo_tasks::value(transparent)] +pub struct EndpointGroups(Vec<(EndpointGroupKey, EndpointGroup)>); + #[turbo_tasks::value(transparent)] pub struct Endpoints(Vec>>); diff --git a/crates/next-api/src/server_actions.rs b/crates/next-api/src/server_actions.rs index d177dc7c667b1..779c1ba180cd5 100644 --- a/crates/next-api/src/server_actions.rs +++ b/crates/next-api/src/server_actions.rs @@ -416,7 +416,7 @@ fn is_turbopack_internal_var(with: &Option>) -> bool { .unwrap_or(false) } -type HashToLayerNameModule = FxIndexMap>)>; +type HashToLayerNameModule = Vec<(String, (ActionLayer, String, ResolvedVc>))>; /// A mapping of every module which exports a Server Action, with the hashed id /// and exported name of each found action. @@ -427,7 +427,7 @@ pub struct AllActions(HashToLayerNameModule); impl AllActions { #[turbo_tasks::function] pub fn empty() -> Vc { - Vc::cell(FxIndexMap::default()) + Vc::cell(Default::default()) } } diff --git a/crates/next-build-test/nextConfig.json b/crates/next-build-test/nextConfig.json index 0cc9f3f2f0331..54ae7fac00260 100644 --- a/crates/next-build-test/nextConfig.json +++ b/crates/next-build-test/nextConfig.json @@ -22,7 +22,7 @@ "analyticsId": "", "images": { "deviceSizes": [640, 750, 828, 1080, 1200, 1920, 2048, 3840], - "imageSizes": [16, 32, 48, 64, 96, 128, 256, 384], + "imageSizes": [32, 48, 64, 96, 128, 256, 384], "path": "/_next/image", "loader": "default", "loaderFile": "", @@ -30,6 +30,8 @@ "disableStaticImages": false, "minimumCacheTTL": 60, "formats": ["image/avif", "image/webp"], + "maximumRedirects": 3, + "dangerouslyAllowLocalIP": false, "dangerouslyAllowSVG": false, "contentSecurityPolicy": "script-src 'none'; frame-src 'none'; sandbox;", "contentDispositionType": "inline", @@ -50,8 +52,6 @@ "productionBrowserSourceMaps": false, "optimizeFonts": true, "excludeDefaultMomentLocales": true, - "serverRuntimeConfig": {}, - "publicRuntimeConfig": {}, "reactProductionProfiling": false, "reactStrictMode": true, "httpAgentOptions": { @@ -69,7 +69,7 @@ "clientRouterFilter": true, "clientRouterFilterRedirects": false, "fetchCacheKeyPrefix": "", - "middlewarePrefetch": "flexible", + "proxyPrefetch": "flexible", "optimisticClientCache": true, "manualClientBasePath": false, "cpus": 2, diff --git a/crates/next-core/src/app_structure.rs b/crates/next-core/src/app_structure.rs index 7941dc840a319..0116c650fd4a8 100644 --- a/crates/next-core/src/app_structure.rs +++ b/crates/next-core/src/app_structure.rs @@ -840,6 +840,92 @@ impl Issue for DuplicateParallelRouteIssue { } } +#[turbo_tasks::value] +struct MissingDefaultParallelRouteIssue { + app_dir: FileSystemPath, + app_page: AppPage, + slot_name: RcStr, +} + +#[turbo_tasks::function] +fn missing_default_parallel_route_issue( + app_dir: FileSystemPath, + app_page: AppPage, + slot_name: RcStr, +) -> Vc { + MissingDefaultParallelRouteIssue { + app_dir, + app_page, + slot_name, + } + .cell() +} + +#[turbo_tasks::value_impl] +impl Issue for MissingDefaultParallelRouteIssue { + #[turbo_tasks::function] + fn file_path(&self) -> Result> { + Ok(self + .app_dir + .join(&self.app_page.to_string())? + .join(&format!("@{}", self.slot_name))? + .cell()) + } + + #[turbo_tasks::function] + fn stage(self: Vc) -> Vc { + IssueStage::AppStructure.cell() + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::Error + } + + #[turbo_tasks::function] + async fn title(&self) -> Vc { + StyledString::Text( + format!( + "Missing required default.js file for parallel route at {}/@{}", + self.app_page, self.slot_name + ) + .into(), + ) + .cell() + } + + #[turbo_tasks::function] + async fn description(&self) -> Vc { + Vc::cell(Some( + StyledString::Stack(vec![ + StyledString::Text( + format!( + "The parallel route slot \"@{}\" is missing a default.js file. When using \ + parallel routes, each slot must have a default.js file to serve as a \ + fallback.", + self.slot_name + ) + .into(), + ), + StyledString::Text( + format!( + "Create a default.js file at: {}/@{}/default.js", + self.app_page, self.slot_name + ) + .into(), + ), + ]) + .resolved_cell(), + )) + } + + #[turbo_tasks::function] + fn documentation_link(&self) -> Vc { + Vc::cell(rcstr!( + "https://nextjs.org/docs/messages/slot-missing-default" + )) + } +} + fn page_path_except_parallel(loader_tree: &AppPageLoaderTree) -> Option { if loader_tree.page.iter().any(|v| { matches!( @@ -863,6 +949,32 @@ fn page_path_except_parallel(loader_tree: &AppPageLoaderTree) -> Option None } +/// Checks if a directory tree has child routes (non-parallel, non-group routes). +/// Leaf segments don't need default.js because there are no child routes +/// that could cause the parallel slot to unmatch. +fn has_child_routes(directory_tree: &PlainDirectoryTree) -> bool { + for (name, subdirectory) in &directory_tree.subdirectories { + // Skip parallel routes (start with '@') + if is_parallel_route(name) { + continue; + } + + // Skip route groups, but check if they have pages inside + if is_group_route(name) { + // Recursively check if the group has child routes + if has_child_routes(subdirectory) { + return true; + } + continue; + } + + // If we get here, it's a regular route segment (child route) + return true; + } + + false +} + async fn check_duplicate( duplicate: &mut FxHashMap, loader_tree: &AppPageLoaderTree, @@ -1114,6 +1226,40 @@ async fn directory_tree_to_loader_tree_internal( if let Some(subtree) = subtree { if let Some(key) = parallel_route_key { + let is_inside_catchall = app_page.is_catchall(); + + // Validate that parallel routes (except "children") have a default.js file. + // Skip this validation if the slot is UNDER a catch-all route (i.e., the + // parallel route is a child of a catch-all segment). + // For example: + // /[...catchAll]/@slot - is_inside_catchall = true (skip validation) ✓ + // /@slot/[...catchAll] - is_inside_catchall = false (require default) ✓ + // The catch-all provides fallback behavior, so default.js is not required. + // + // Also skip validation if this is a leaf segment (no child routes). + // Leaf segments don't need default.js because there are no child routes + // that could cause the parallel slot to unmatch. For example: + // /repo-overview/@slot/page with no child routes - is_leaf_segment = true (skip + // validation) ✓ /repo-overview/@slot/page with + // /repo-overview/child/page - is_leaf_segment = false (require default) ✓ + // This also handles route groups correctly by filtering them out. + let is_leaf_segment = !has_child_routes(directory_tree); + + if key != "children" + && subdirectory.modules.default.is_none() + && !is_inside_catchall + && !is_leaf_segment + { + missing_default_parallel_route_issue( + app_dir.clone(), + app_page.clone(), + key.into(), + ) + .to_resolved() + .await? + .emit(); + } + tree.parallel_routes.insert(key.into(), subtree); continue; } @@ -1173,8 +1319,28 @@ async fn directory_tree_to_loader_tree_internal( None }; + let is_inside_catchall = app_page.is_catchall(); + + // Check if this is a leaf segment (no child routes). + let is_leaf_segment = !has_child_routes(directory_tree); + + // Only emit the issue if this is not the children slot and there's no default + // component. The children slot is implicit and doesn't require a default.js + // file. Also skip validation if the slot is UNDER a catch-all route or if + // this is a leaf segment (no child routes). + if default.is_none() && key != "children" && !is_inside_catchall && !is_leaf_segment { + missing_default_parallel_route_issue( + app_dir.clone(), + app_page.clone(), + key.clone(), + ) + .to_resolved() + .await? + .emit(); + } + tree.parallel_routes.insert( - key, + key.clone(), default_route_tree(app_dir.clone(), global_metadata, app_page.clone(), default) .await?, ); diff --git a/crates/next-core/src/emit.rs b/crates/next-core/src/emit.rs index 0e8f1b1f52190..d92a124d909e1 100644 --- a/crates/next-core/src/emit.rs +++ b/crates/next-core/src/emit.rs @@ -2,7 +2,7 @@ use anyhow::Result; use tracing::{Instrument, Level, Span}; use turbo_rcstr::RcStr; use turbo_tasks::{ - FxIndexSet, ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc, + FxIndexSet, ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, Vc, graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow}, }; use turbo_tasks_fs::{FileSystemPath, rebase}; @@ -161,7 +161,9 @@ pub async fn all_assets_from_entries(entries: Vc) -> Result>) -> Result>> { let extensions = page_extensions.await?; - let files = ["middleware.", "src/middleware."] + let files = ["middleware.", "src/middleware.", "proxy.", "src/proxy."] .into_iter() .flat_map(|f| { extensions @@ -26,17 +32,66 @@ pub async fn get_middleware_module( asset_context: Vc>, project_root: FileSystemPath, userland_module: ResolvedVc>, + is_proxy: bool, ) -> Result>> { const INNER: &str = "INNER_MIDDLEWARE_MODULE"; + // Determine if this is a proxy file by checking the module path + let userland_path = userland_module.ident().path().await?; + let (file_type, function_name, page_path) = if is_proxy { + ("Proxy", "proxy", "/proxy") + } else { + ("Middleware", "middleware", "/middleware") + }; + + // Validate that the module has the required exports + if let Some(ecma_module) = + Vc::try_resolve_sidecast::>(*userland_module).await? + { + let exports = ecma_module.get_exports().await?; + + // Check if the module has the required exports + let has_valid_export = match &*exports { + // ESM modules - check for named or default export + EcmascriptExports::EsmExports(esm_exports) => { + let esm_exports = esm_exports.await?; + let has_default = esm_exports.exports.contains_key("default"); + let expected_named = function_name; + let has_named = esm_exports.exports.contains_key(expected_named); + has_default || has_named + } + // CommonJS modules are valid (they can have module.exports or exports.default) + EcmascriptExports::CommonJs | EcmascriptExports::Value => true, + // DynamicNamespace might be valid for certain module types + EcmascriptExports::DynamicNamespace => true, + // None/Unknown likely indicate parsing errors - skip validation + // The parsing error will be emitted separately by Turbopack + EcmascriptExports::None | EcmascriptExports::Unknown => true, + // EmptyCommonJs is a legitimate case of missing exports + EcmascriptExports::EmptyCommonJs => false, + }; + + if !has_valid_export { + MiddlewareMissingExportIssue { + file_type: file_type.into(), + function_name: function_name.into(), + file_path: (*userland_path).clone(), + } + .resolved_cell() + .emit(); + + // Continue execution instead of bailing - let the module be processed anyway + // The runtime template will still catch this at runtime + } + } + // If we can't cast to EcmascriptChunkPlaceable, continue without validation + // (might be a special module type that doesn't support export checking) + // Load the file from the next.js codebase. let source = load_next_js_template( "middleware.js", project_root, - &[ - ("VAR_USERLAND", INNER), - ("VAR_DEFINITION_PAGE", "/middleware"), - ], + &[("VAR_USERLAND", INNER), ("VAR_DEFINITION_PAGE", page_path)], &[], &[], ) @@ -55,3 +110,73 @@ pub async fn get_middleware_module( Ok(module) } + +#[turbo_tasks::value] +struct MiddlewareMissingExportIssue { + file_type: RcStr, // "Proxy" or "Middleware" + function_name: RcStr, // "proxy" or "middleware" + file_path: FileSystemPath, +} + +#[turbo_tasks::value_impl] +impl Issue for MiddlewareMissingExportIssue { + #[turbo_tasks::function] + fn stage(&self) -> Vc { + IssueStage::Transform.cell() + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::Error + } + + #[turbo_tasks::function] + fn file_path(&self) -> Vc { + self.file_path.clone().cell() + } + + #[turbo_tasks::function] + async fn title(&self) -> Result> { + let title_text = format!( + "{} is missing expected function export name", + self.file_type + ); + + Ok(StyledString::Text(title_text.into()).cell()) + } + + #[turbo_tasks::function] + async fn description(&self) -> Result> { + let type_description = if self.file_type == "Proxy" { + "proxy (previously called middleware)" + } else { + "middleware" + }; + + let migration_bullet = if self.file_type == "Proxy" { + "- You are migrating from `middleware` to `proxy`, but haven't updated the exported \ + function.\n" + } else { + "" + }; + + // Rest of the message goes in description to avoid formatIssue indentation + let description_text = format!( + "This function is what Next.js runs for every request handled by this {}.\n\n\ + Why this happens:\n\ + {}\ + - The file exists but doesn't export a function.\n\ + - The export is not a function (e.g., an object or constant).\n\ + - There's a syntax error preventing the export from being recognized.\n\n\ + To fix it:\n\ + - Ensure this file has either a default or \"{}\" function export.\n\n\ + Learn more: https://nextjs.org/docs/messages/middleware-to-proxy", + type_description, + migration_bullet, + self.function_name + ); + + Ok(Vc::cell(Some( + StyledString::Text(description_text.into()).resolved_cell(), + ))) + } +} diff --git a/crates/next-core/src/mode.rs b/crates/next-core/src/mode.rs index 31db7ce19f762..f6a0496f86600 100644 --- a/crates/next-core/src/mode.rs +++ b/crates/next-core/src/mode.rs @@ -58,10 +58,7 @@ impl NextMode { } pub fn is_production(&self) -> bool { - match self { - NextMode::Development => false, - NextMode::Build => true, - } + !self.is_development() } pub fn runtime_type(&self) -> RuntimeType { diff --git a/crates/next-core/src/next_app/app_page_entry.rs b/crates/next-core/src/next_app/app_page_entry.rs index cf0de7746e95f..a95fd69c1820e 100644 --- a/crates/next-core/src/next_app/app_page_entry.rs +++ b/crates/next-core/src/next_app/app_page_entry.rs @@ -48,7 +48,7 @@ pub async fn get_app_page_entry( let server_component_transition = ResolvedVc::upcast(NextServerComponentTransition::new().to_resolved().await?); - let base_path = next_config.await?.base_path.clone(); + let base_path = next_config.base_path().owned().await?; let loader_tree = AppPageLoaderTreeModule::build( loader_tree, module_asset_context, diff --git a/crates/next-core/src/next_app/app_route_entry.rs b/crates/next-core/src/next_app/app_route_entry.rs index b8e6aeec242de..7d8d3df74eaea 100644 --- a/crates/next-core/src/next_app/app_route_entry.rs +++ b/crates/next-core/src/next_app/app_route_entry.rs @@ -40,7 +40,7 @@ pub async fn get_app_route_entry( let config = if let Some(original_segment_config) = original_segment_config { let mut segment_config = segment_from_source.owned().await?; segment_config.apply_parent_config(&*original_segment_config.await?); - segment_config.into() + segment_config.cell() } else { segment_from_source }; @@ -60,8 +60,8 @@ pub async fn get_app_route_entry( let inner = rcstr!("INNER_APP_ROUTE"); let output_type: &str = next_config + .output() .await? - .output .as_ref() .map(|o| match o { OutputType::Standalone => "\"standalone\"", diff --git a/crates/next-core/src/next_app/metadata/route.rs b/crates/next-core/src/next_app/metadata/route.rs index 533a3e5e3f605..a9312079b0469 100644 --- a/crates/next-core/src/next_app/metadata/route.rs +++ b/crates/next-core/src/next_app/metadata/route.rs @@ -291,26 +291,24 @@ async fn dynamic_sitemap_route_with_generate_source( const paramsPromise = ctx.params const idPromise = paramsPromise.then(params => params?.__metadata_id__) - if (process.env.NODE_ENV !== 'production') {{ - const id = await idPromise - const hasXmlExtension = id ? id.endsWith('.xml') : false - const sitemaps = await generateSitemaps() - let foundId - for (const item of sitemaps) {{ - if (item?.id == null) {{ - throw new Error('id property is required for every item returned from generateSitemaps') - }} - const baseId = id && hasXmlExtension ? id.slice(0, -4) : undefined - if (item.id.toString() === baseId) {{ - foundId = item.id - }} + const id = await idPromise + const hasXmlExtension = id ? id.endsWith('.xml') : false + const sitemaps = await generateSitemaps() + let foundId + for (const item of sitemaps) {{ + if (item?.id == null) {{ + throw new Error('id property is required for every item returned from generateSitemaps') }} - if (foundId == null) {{ - return new NextResponse('Not Found', {{ - status: 404, - }}) + const baseId = id && hasXmlExtension ? id.slice(0, -4) : undefined + if (item.id.toString() === baseId) {{ + foundId = item.id }} }} + if (foundId == null) {{ + return new NextResponse('Not Found', {{ + status: 404, + }}) + }} const targetIdPromise = idPromise.then(id => {{ const hasXmlExtension = id ? id.endsWith('.xml') : false @@ -329,7 +327,6 @@ async fn dynamic_sitemap_route_with_generate_source( export * from {resource_path} - export const dynamicParams = false export async function generateStaticParams() {{ const sitemaps = await generateSitemaps() const params = [] @@ -445,24 +442,22 @@ async fn dynamic_image_route_with_metadata_source( const {{ __metadata_id__, ...rest }} = params return rest }}) - - if (process.env.NODE_ENV !== 'production') {{ - const restParams = await restParamsPromise - const __metadata_id__ = await idPromise - const imageMetadata = await generateImageMetadata({{ params: restParams }}) - const id = imageMetadata.find((item) => {{ - if (item?.id == null) {{ - throw new Error('id property is required for every item returned from generateImageMetadata') - }} - - return item.id.toString() === __metadata_id__ - }})?.id - - if (id == null) {{ - return new NextResponse('Not Found', {{ - status: 404, - }}) + + const restParams = await restParamsPromise + const __metadata_id__ = await idPromise + const imageMetadata = await generateImageMetadata({{ params: restParams }}) + const id = imageMetadata.find((item) => {{ + if (item?.id == null) {{ + throw new Error('id property is required for every item returned from generateImageMetadata') }} + + return item.id.toString() === __metadata_id__ + }})?.id + + if (id == null) {{ + return new NextResponse('Not Found', {{ + status: 404, + }}) }} return handler({{ params: restParamsPromise, id: idPromise }}) @@ -470,7 +465,6 @@ async fn dynamic_image_route_with_metadata_source( export * from {resource_path} - export const dynamicParams = false export async function generateStaticParams({{ params }}) {{ const imageMetadata = await generateImageMetadata({{ params }}) const staticParams = [] @@ -563,7 +557,7 @@ impl Issue for StaticMetadataFileSizeIssue { #[turbo_tasks::function] fn stage(&self) -> Vc { - IssueStage::ProcessModule.into() + IssueStage::ProcessModule.cell() } #[turbo_tasks::function] diff --git a/crates/next-core/src/next_client/context.rs b/crates/next-core/src/next_client/context.rs index 2de13637dffe8..42d135ee5d976 100644 --- a/crates/next-core/src/next_client/context.rs +++ b/crates/next-core/src/next_client/context.rs @@ -19,8 +19,8 @@ use turbopack_browser::{ }; use turbopack_core::{ chunk::{ - ChunkingConfig, ChunkingContext, MangleType, MinifyType, SourceMapsType, - module_id_strategies::ModuleIdStrategy, + ChunkingConfig, ChunkingContext, MangleType, MinifyType, SourceMapSourceType, + SourceMapsType, module_id_strategies::ModuleIdStrategy, }, compile_time_info::{CompileTimeDefines, CompileTimeInfo, FreeVarReference, FreeVarReferences}, environment::{BrowserEnvironment, Environment, ExecutionEnvironment}, @@ -150,7 +150,12 @@ pub async fn get_client_resolve_options_context( get_next_client_resolved_map(project_path.clone(), project_path.clone(), *mode.await?) .to_resolved() .await?; - let custom_conditions = mode.await?.custom_resolve_conditions().collect(); + let mut custom_conditions: Vec<_> = mode.await?.custom_resolve_conditions().collect(); + + if *next_config.enable_cache_components().await? { + custom_conditions.push(rcstr!("next-js")); + }; + let resolve_options_context = ResolveOptionsContext { enable_node_modules: Some(project_path.root().owned().await?), custom_conditions, @@ -412,7 +417,7 @@ pub struct ClientChunkingContextOptions { pub root_path: FileSystemPath, pub client_root: FileSystemPath, pub client_root_to_root_path: RcStr, - pub asset_prefix: Vc>, + pub asset_prefix: Vc, pub chunk_suffix_path: Vc>, pub environment: Vc, pub module_id_strategy: Vc>, @@ -422,6 +427,7 @@ pub struct ClientChunkingContextOptions { pub no_mangling: Vc, pub scope_hoisting: Vc, pub debug_ids: Vc, + pub should_use_absolute_url_references: Vc, } #[turbo_tasks::function] @@ -443,11 +449,12 @@ pub async fn get_client_chunking_context( no_mangling, scope_hoisting, debug_ids, + should_use_absolute_url_references, } = options; let next_mode = mode.await?; let asset_prefix = asset_prefix.owned().await?; - let chunk_suffix_path = chunk_suffix_path.owned().await?; + let chunk_suffix_path = chunk_suffix_path.to_resolved().await?; let mut builder = BrowserChunkingContext::builder( root_path, client_root.clone(), @@ -458,7 +465,7 @@ pub async fn get_client_chunking_context( environment.to_resolved().await?, next_mode.runtime_type(), ) - .chunk_base_path(asset_prefix.clone()) + .chunk_base_path(Some(asset_prefix.clone())) .chunk_suffix_path(chunk_suffix_path) .minify_type(if *minify.await? { MinifyType::Minify { @@ -472,16 +479,17 @@ pub async fn get_client_chunking_context( } else { SourceMapsType::None }) - .asset_base_path(asset_prefix) + .asset_base_path(Some(asset_prefix)) .current_chunk_method(CurrentChunkMethod::DocumentCurrentScript) .export_usage(*export_usage.await?) .module_id_strategy(module_id_strategy.to_resolved().await?) - .debug_ids(*debug_ids.await?); + .debug_ids(*debug_ids.await?) + .should_use_absolute_url_references(*should_use_absolute_url_references.await?); if next_mode.is_development() { builder = builder .hot_module_replacement() - .use_file_source_map_uris() + .source_map_source_type(SourceMapSourceType::AbsoluteFileUri) .dynamic_chunk_content_loading(true); } else { builder = builder diff --git a/crates/next-core/src/next_client_reference/visit_client_reference.rs b/crates/next-core/src/next_client_reference/visit_client_reference.rs index f0a2862e2c2df..6b3be600ef030 100644 --- a/crates/next-core/src/next_client_reference/visit_client_reference.rs +++ b/crates/next-core/src/next_client_reference/visit_client_reference.rs @@ -3,10 +3,10 @@ use std::future::Future; use anyhow::Result; use rustc_hash::FxHashSet; use serde::{Deserialize, Serialize}; -use tracing::Instrument; +use tracing::{Instrument, Level, Span}; use turbo_rcstr::RcStr; use turbo_tasks::{ - FxIndexSet, NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt, ValueToString, Vc, + FxIndexSet, NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt, Vc, debug::ValueDebugFormat, graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow}, trace::TraceRawVcs, @@ -53,6 +53,9 @@ impl ClientReference { } } +#[turbo_tasks::value(transparent)] +pub struct ClientReferences(Vec); + #[derive( Copy, Clone, @@ -121,14 +124,23 @@ pub async fn find_server_entries( include_traced: bool, ) -> Result> { async move { + let emit_spans = tracing::enabled!(Level::INFO); let graph = AdjacencyMap::new() .skip_duplicates() .visit( vec![FindServerEntriesNode::Internal( entry, - entry.ident().to_string().await?, + if emit_spans { + // INVALIDATION: we don't need to invalidate when the span name changes + Some(entry.ident_string().untracked().await?) + } else { + None + }, )], - FindServerEntries { include_traced }, + FindServerEntries { + include_traced, + emit_spans, + }, ) .await .completed()? @@ -161,6 +173,7 @@ pub async fn find_server_entries( struct FindServerEntries { /// Whether to walk ChunkingType::Traced references include_traced: bool, + emit_spans: bool, } #[derive( @@ -177,9 +190,12 @@ struct FindServerEntries { )] enum FindServerEntriesNode { ClientReference, - ServerComponentEntry(ResolvedVc, ReadRef), - ServerUtilEntry(ResolvedVc, ReadRef), - Internal(ResolvedVc>, ReadRef), + ServerComponentEntry( + ResolvedVc, + Option>, + ), + ServerUtilEntry(ResolvedVc, Option>), + Internal(ResolvedVc>, Option>), } impl Visit for FindServerEntries { @@ -208,6 +224,7 @@ impl Visit for FindServerEntries { FindServerEntriesNode::ServerUtilEntry(module, _) => Vc::upcast(**module), FindServerEntriesNode::ServerComponentEntry(module, _) => Vc::upcast(**module), }; + let emit_spans = self.emit_spans; async move { // Pass include_traced to reuse the same cached `primary_chunkable_referenced_modules` // task result, but the traced references will be filtered out again afterwards. @@ -235,7 +252,13 @@ impl Visit for FindServerEntries { { return Ok(FindServerEntriesNode::ServerComponentEntry( server_component_asset, - server_component_asset.ident().to_string().await?, + if emit_spans { + // INVALIDATION: we don't need to invalidate when the span name + // changes + Some(server_component_asset.ident_string().untracked().await?) + } else { + None + }, )); } @@ -244,13 +267,24 @@ impl Visit for FindServerEntries { { return Ok(FindServerEntriesNode::ServerUtilEntry( server_util_module, - module.ident().to_string().await?, + if emit_spans { + // INVALIDATION: we don't need to invalidate when the span name + // changes + Some(module.ident_string().untracked().await?) + } else { + None + }, )); } Ok(FindServerEntriesNode::Internal( *module, - module.ident().to_string().await?, + if emit_spans { + // INVALIDATION: we don't need to invalidate when the span name changes + Some(module.ident_string().untracked().await?) + } else { + None + }, )) }); @@ -261,18 +295,21 @@ impl Visit for FindServerEntries { } fn span(&mut self, node: &FindServerEntriesNode) -> tracing::Span { + if !self.emit_spans { + return Span::current(); + } match node { FindServerEntriesNode::ClientReference => { tracing::info_span!("client reference") } FindServerEntriesNode::Internal(_, name) => { - tracing::info_span!("module", name = display(name)) + tracing::info_span!("module", name = display(name.as_ref().unwrap())) } FindServerEntriesNode::ServerUtilEntry(_, name) => { - tracing::info_span!("server util", name = display(name)) + tracing::info_span!("server util", name = display(name.as_ref().unwrap())) } FindServerEntriesNode::ServerComponentEntry(_, name) => { - tracing::info_span!("layout segment", name = display(name)) + tracing::info_span!("layout segment", name = display(name.as_ref().unwrap())) } } } diff --git a/crates/next-core/src/next_config.rs b/crates/next-core/src/next_config.rs index 9c102b9f6623a..ab9df2c0dec84 100644 --- a/crates/next-core/src/next_config.rs +++ b/crates/next-core/src/next_config.rs @@ -10,7 +10,7 @@ use turbo_tasks::{ trace::TraceRawVcs, }; use turbo_tasks_env::{EnvMap, ProcessEnv}; -use turbo_tasks_fetch::FetchClient; +use turbo_tasks_fetch::FetchClientConfig; use turbo_tasks_fs::FileSystemPath; use turbopack::module_options::{ ConditionItem, ConditionPath, LoaderRuleItem, WebpackRules, @@ -75,40 +75,41 @@ impl Default for CacheKinds { #[derive(Clone, Debug, Default, PartialEq)] #[serde(default, rename_all = "camelCase")] pub struct NextConfig { - // TODO all fields should be private and access should be wrapped within a turbo-tasks function - // Otherwise changing NextConfig will lead to invalidating all tasks accessing it. - pub config_file: Option, - pub config_file_name: RcStr, + // IMPORTANT: all fields should be private and access should be wrapped within a turbo-tasks + // function. Otherwise changing NextConfig will lead to invalidating all tasks accessing it. + config_file: Option, + config_file_name: RcStr, /// In-memory cache size in bytes. /// /// If `cache_max_memory_size: 0` disables in-memory caching. - pub cache_max_memory_size: Option, + cache_max_memory_size: Option, /// custom path to a cache handler to use - pub cache_handler: Option, - - pub env: FxIndexMap, - pub experimental: ExperimentalConfig, - pub images: ImageConfig, - pub page_extensions: Vec, - pub react_compiler: Option, - pub react_production_profiling: Option, - pub react_strict_mode: Option, - pub transpile_packages: Option>, - pub modularize_imports: Option>, - pub dist_dir: Option, - pub deployment_id: Option, + cache_handler: Option, + cache_handlers: Option>, + env: FxIndexMap, + experimental: ExperimentalConfig, + images: ImageConfig, + page_extensions: Vec, + react_compiler: Option, + react_production_profiling: Option, + react_strict_mode: Option, + transpile_packages: Option>, + modularize_imports: Option>, + dist_dir: RcStr, + dist_dir_root: RcStr, + deployment_id: Option, sass_options: Option, - pub trailing_slash: Option, - pub asset_prefix: Option, - pub base_path: Option, - pub skip_middleware_url_normalize: Option, - pub skip_trailing_slash_redirect: Option, - pub i18n: Option, - pub cross_origin: Option, - pub dev_indicators: Option, - pub output: Option, - pub turbopack: Option, + trailing_slash: Option, + asset_prefix: Option, + base_path: Option, + skip_proxy_url_normalize: Option, + skip_trailing_slash_redirect: Option, + i18n: Option, + cross_origin: Option, + dev_indicators: Option, + output: Option, + turbopack: Option, production_browser_source_maps: bool, output_file_tracing_includes: Option, output_file_tracing_excludes: Option, @@ -119,21 +120,21 @@ pub struct NextConfig { /// server-side bundles. /// /// [API Reference](https://nextjs.org/docs/pages/api-reference/next-config-js/bundlePagesRouterDependencies) - pub bundle_pages_router_dependencies: Option, + bundle_pages_router_dependencies: Option, /// A list of packages that should be treated as external on the server /// build. /// /// [API Reference](https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages) - pub server_external_packages: Option>, + server_external_packages: Option>, #[serde(rename = "_originalRedirects")] - pub original_redirects: Option>, + original_redirects: Option>, // Partially supported - pub compiler: Option, + compiler: Option, - pub optimize_fonts: Option, + optimize_fonts: Option, clean_dist_dir: bool, compress: bool, @@ -153,11 +154,24 @@ pub struct NextConfig { target: Option, typescript: TypeScriptConfig, use_file_system_public_routes: bool, + cache_components: Option, webpack: Option, } +#[turbo_tasks::value_impl] +impl NextConfig { + #[turbo_tasks::function] + pub fn with_production_browser_source_maps(&self) -> Vc { + Self { + production_browser_source_maps: true, + ..self.clone() + } + .cell() + } +} + #[derive( - Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, )] #[serde(rename_all = "kebab-case")] pub enum CrossOriginConfig { @@ -165,6 +179,9 @@ pub enum CrossOriginConfig { UseCredentials, } +#[turbo_tasks::value(transparent)] +pub struct OptionCrossOriginConfig(Option); + #[derive( Clone, Debug, @@ -262,7 +279,7 @@ struct HttpAgentConfig { } #[derive( - Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, )] #[serde(rename_all = "camelCase")] pub struct DomainLocale { @@ -273,7 +290,7 @@ pub struct DomainLocale { } #[derive( - Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, )] #[serde(rename_all = "camelCase")] pub struct I18NConfig { @@ -283,8 +300,11 @@ pub struct I18NConfig { pub locales: Vec, } +#[turbo_tasks::value(transparent)] +pub struct OptionI18NConfig(Option); + #[derive( - Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, )] #[serde(rename_all = "kebab-case")] pub enum OutputType { @@ -292,6 +312,9 @@ pub enum OutputType { Export, } +#[turbo_tasks::value(transparent)] +pub struct OptionOutputType(Option); + #[derive( Debug, Clone, @@ -456,7 +479,7 @@ impl Default for ImageConfig { // https://github.com/vercel/next.js/blob/327634eb/packages/next/shared/lib/image-config.ts#L100-L114 Self { device_sizes: vec![640, 750, 828, 1080, 1200, 1920, 2048, 3840], - image_sizes: vec![16, 32, 48, 64, 96, 128, 256, 384], + image_sizes: vec![32, 48, 64, 96, 128, 256, 384], path: "/_next/image".to_string(), loader: ImageLoader::Default, loader_file: None, @@ -800,6 +823,8 @@ pub struct ExperimentalConfig { web_vitals_attribution: Option>, server_actions: Option, sri: Option, + /// @deprecated - use top-level cache_components instead. + /// This field is kept for backwards compatibility during migration. cache_components: Option, use_cache: Option, root_params: Option, @@ -810,7 +835,6 @@ pub struct ExperimentalConfig { adjust_font_fallbacks_with_size_adjust: Option, after: Option, app_document_preloading: Option, - cache_handlers: Option>, cache_life: Option>, case_sensitive_routes: Option, cpus: Option, @@ -839,12 +863,7 @@ pub struct ExperimentalConfig { /// Automatically apply the "modularize_imports" optimization to imports of /// the specified packages. optimize_package_imports: Option>, - /// Using this feature will enable the `react@experimental` for the `app` - /// directory. - ppr: Option, taint: Option, - #[serde(rename = "routerBFCache")] - router_bfcache: Option, proxy_timeout: Option, /// enables the minification of server code. server_minification: Option, @@ -855,7 +874,6 @@ pub struct ExperimentalConfig { trust_host_header: Option, url_imports: Option, - view_transition: Option, /// This option is to enable running the Webpack build in a worker thread /// (doesn't apply to Turbopack). webpack_build_worker: Option, @@ -941,53 +959,6 @@ fn test_cache_life_profiles_invalid() { ); } -#[derive( - Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, -)] -#[serde(rename_all = "lowercase")] -pub enum ExperimentalPartialPrerenderingIncrementalValue { - Incremental, -} - -#[derive( - Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue, -)] -#[serde(untagged)] -pub enum ExperimentalPartialPrerendering { - Boolean(bool), - Incremental(ExperimentalPartialPrerenderingIncrementalValue), -} - -#[test] -fn test_parse_experimental_partial_prerendering() { - let json = serde_json::json!({ - "ppr": "incremental" - }); - let config: ExperimentalConfig = serde_json::from_value(json).unwrap(); - assert_eq!( - config.ppr, - Some(ExperimentalPartialPrerendering::Incremental( - ExperimentalPartialPrerenderingIncrementalValue::Incremental - )) - ); - - let json = serde_json::json!({ - "ppr": true - }); - let config: ExperimentalConfig = serde_json::from_value(json).unwrap(); - assert_eq!( - config.ppr, - Some(ExperimentalPartialPrerendering::Boolean(true)) - ); - - // Expect if we provide a random string, it will fail. - let json = serde_json::json!({ - "ppr": "random" - }); - let config = serde_json::from_value::(json); - assert!(config.is_err()); -} - #[derive( Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, )] @@ -1330,6 +1301,11 @@ impl NextConfig { Vc::cell(self.output == Some(OutputType::Standalone)) } + #[turbo_tasks::function] + pub fn base_path(&self) -> Vc> { + Vc::cell(self.base_path.clone()) + } + #[turbo_tasks::function] pub fn cache_handler(&self, project_path: FileSystemPath) -> Result> { if let Some(handler) = &self.cache_handler { @@ -1375,7 +1351,12 @@ impl NextConfig { #[turbo_tasks::function] pub fn page_extensions(&self) -> Vc> { - Vc::cell(self.page_extensions.clone()) + // Sort page extensions by length descending. This mirrors the Webpack behavior in Next.js, + // which just builds a regex alternative, which greedily matches the longest + // extension: https://github.com/vercel/next.js/blob/32476071fe331948d89a35c391eb578aed8de979/packages/next/src/build/entries.ts#L409 + let mut extensions = self.page_extensions.clone(); + extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len())); + Vc::cell(extensions) } #[turbo_tasks::function] @@ -1531,6 +1512,11 @@ impl NextConfig { })) } + #[turbo_tasks::function] + pub fn inline_css(&self) -> Vc { + Vc::cell(self.experimental.inline_css.unwrap_or(false)) + } + #[turbo_tasks::function] pub fn mdx_rs(&self) -> Vc { let options = &self.experimental.mdx_rs; @@ -1567,11 +1553,17 @@ impl NextConfig { } #[turbo_tasks::function] - pub fn experimental_cache_handlers( - &self, - project_path: FileSystemPath, - ) -> Result> { - if let Some(handlers) = &self.experimental.cache_handlers { + pub fn dist_dir(&self) -> Vc { + Vc::cell(self.dist_dir.clone()) + } + #[turbo_tasks::function] + pub fn dist_dir_root(&self) -> Vc { + Vc::cell(self.dist_dir_root.clone()) + } + + #[turbo_tasks::function] + pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result> { + if let Some(handlers) = &self.cache_handlers { Ok(Vc::cell( handlers .values() @@ -1637,8 +1629,8 @@ impl NextConfig { } #[turbo_tasks::function] - pub fn skip_middleware_url_normalize(&self) -> Vc { - Vc::cell(self.skip_middleware_url_normalize.unwrap_or(false)) + pub fn skip_proxy_url_normalize(&self) -> Vc { + Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false)) } #[turbo_tasks::function] @@ -1649,10 +1641,10 @@ impl NextConfig { /// Returns the final asset prefix. If an assetPrefix is set, it's used. /// Otherwise, the basePath is used. #[turbo_tasks::function] - pub async fn computed_asset_prefix(self: Vc) -> Result>> { + pub async fn computed_asset_prefix(self: Vc) -> Result> { let this = self.await?; - Ok(Vc::cell(Some( + Ok(Vc::cell( format!( "{}/_next/", if let Some(asset_prefix) = &this.asset_prefix { @@ -1663,7 +1655,7 @@ impl NextConfig { .trim_end_matches('/') ) .into(), - ))) + )) } /// Returns the suffix to use for chunk loading. @@ -1677,35 +1669,14 @@ impl NextConfig { } } - #[turbo_tasks::function] - pub fn enable_ppr(&self) -> Vc { - Vc::cell( - self.experimental - .ppr - .as_ref() - .map(|ppr| match ppr { - ExperimentalPartialPrerendering::Incremental( - ExperimentalPartialPrerenderingIncrementalValue::Incremental, - ) => true, - ExperimentalPartialPrerendering::Boolean(b) => *b, - }) - .unwrap_or(false), - ) - } - #[turbo_tasks::function] pub fn enable_taint(&self) -> Vc { Vc::cell(self.experimental.taint.unwrap_or(false)) } - #[turbo_tasks::function] - pub fn enable_view_transition(&self) -> Vc { - Vc::cell(self.experimental.view_transition.unwrap_or(false)) - } - #[turbo_tasks::function] pub fn enable_cache_components(&self) -> Vc { - Vc::cell(self.experimental.cache_components.unwrap_or(false)) + Vc::cell(self.cache_components.unwrap_or(false)) } #[turbo_tasks::function] @@ -1716,7 +1687,7 @@ impl NextConfig { // "use cache" was originally implicitly enabled with the // cacheComponents flag, so we transfer the value for cacheComponents to the // explicit useCache flag to ensure backwards compatibility. - .unwrap_or(self.experimental.cache_components.unwrap_or(false)), + .unwrap_or(self.cache_components.unwrap_or(false)), ) } @@ -1726,7 +1697,7 @@ impl NextConfig { self.experimental .root_params // rootParams should be enabled implicitly in cacheComponents. - .unwrap_or(self.experimental.cache_components.unwrap_or(false)), + .unwrap_or(self.cache_components.unwrap_or(false)), ) } @@ -1734,7 +1705,7 @@ impl NextConfig { pub fn cache_kinds(&self) -> Vc { let mut cache_kinds = CacheKinds::default(); - if let Some(handlers) = self.experimental.cache_handlers.as_ref() { + if let Some(handlers) = self.cache_handlers.as_ref() { cache_kinds.extend(handlers.keys().cloned()); } @@ -1857,6 +1828,21 @@ impl NextConfig { )) } + #[turbo_tasks::function] + pub fn cross_origin(&self) -> Vc { + Vc::cell(self.cross_origin.clone()) + } + + #[turbo_tasks::function] + pub fn i18n(&self) -> Vc { + Vc::cell(self.i18n.clone()) + } + + #[turbo_tasks::function] + pub fn output(&self) -> Vc { + Vc::cell(self.output.clone()) + } + #[turbo_tasks::function] pub fn output_file_tracing_includes(&self) -> Vc { Vc::cell(self.output_file_tracing_includes.clone()) @@ -1868,7 +1854,10 @@ impl NextConfig { } #[turbo_tasks::function] - pub async fn fetch_client(&self, env: Vc>) -> Result> { + pub async fn fetch_client( + &self, + env: Vc>, + ) -> Result> { // Support both an env var and the experimental flag to provide more flexibility to // developers on locked down systems, depending on if they want to configure this on a // per-system or per-project basis. @@ -1882,7 +1871,7 @@ impl NextConfig { }) .or(self.experimental.turbopack_use_system_tls_certs) .unwrap_or(false); - Ok(FetchClient { + Ok(FetchClientConfig { tls_built_in_webpki_certs: !use_system_tls_certs, tls_built_in_native_certs: use_system_tls_certs, } diff --git a/crates/next-core/src/next_edge/context.rs b/crates/next-core/src/next_edge/context.rs index cd780efb06015..2b5c06ced3b0d 100644 --- a/crates/next-core/src/next_edge/context.rs +++ b/crates/next-core/src/next_edge/context.rs @@ -153,6 +153,10 @@ pub async fn get_edge_resolve_options_context( custom_conditions.push(rcstr!("react-server")); }; + // Edge runtime is disabled for projects with Cache Components enabled except for Middleware + // but Middleware doesn't have all Next.js APIs so we omit the "next-js" condition for all edge + // entrypoints + let resolve_options_context = ResolveOptionsContext { enable_node_modules: Some(project_path.root().owned().await?), enable_edge_node_externals: true, @@ -204,13 +208,14 @@ pub struct EdgeChunkingContextOptions { pub turbo_source_maps: Vc, pub no_mangling: Vc, pub scope_hoisting: Vc, + pub client_root: FileSystemPath, + pub asset_prefix: RcStr, } +/// Like `get_edge_chunking_context` but all assets are emitted as client assets (so `/_next`) #[turbo_tasks::function] pub async fn get_edge_chunking_context_with_client_assets( options: EdgeChunkingContextOptions, - client_root: FileSystemPath, - asset_prefix: ResolvedVc>, ) -> Result>> { let EdgeChunkingContextOptions { mode, @@ -224,6 +229,8 @@ pub async fn get_edge_chunking_context_with_client_assets( turbo_source_maps, no_mangling, scope_hoisting, + client_root, + asset_prefix, } = options; let output_root = node_root.join("server/edge")?; let next_mode = mode.await?; @@ -237,7 +244,7 @@ pub async fn get_edge_chunking_context_with_client_assets( environment.to_resolved().await?, next_mode.runtime_type(), ) - .asset_base_path(asset_prefix.owned().await?) + .asset_base_path(Some(asset_prefix)) .minify_type(if *turbo_minify.await? { MinifyType::Minify { // React needs deterministic function names to work correctly. @@ -276,6 +283,7 @@ pub async fn get_edge_chunking_context_with_client_assets( Ok(Vc::upcast(builder.build())) } +// By default, assets are server assets, but the StructuredImageModuleType ones are on the client #[turbo_tasks::function] pub async fn get_edge_chunking_context( options: EdgeChunkingContextOptions, @@ -292,6 +300,8 @@ pub async fn get_edge_chunking_context( turbo_source_maps, no_mangling, scope_hoisting, + client_root, + asset_prefix, } = options; let output_root = node_root.join("server/edge")?; let next_mode = mode.await?; @@ -305,6 +315,9 @@ pub async fn get_edge_chunking_context( environment.to_resolved().await?, next_mode.runtime_type(), ) + .client_roots_override(rcstr!("client"), client_root.clone()) + .asset_root_path_override(rcstr!("client"), client_root.join("static/media")?) + .asset_base_path_override(rcstr!("client"), asset_prefix) // Since one can't read files in edge directly, any asset need to be fetched // instead. This special blob url is handled by the custom fetch // implementation in the edge sandbox. It will respond with the diff --git a/crates/next-core/src/next_font/google/mod.rs b/crates/next-core/src/next_font/google/mod.rs index 891a79322cdac..7a03e56d3e856 100644 --- a/crates/next-core/src/next_font/google/mod.rs +++ b/crates/next-core/src/next_font/google/mod.rs @@ -10,7 +10,7 @@ use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{Completion, FxIndexMap, ResolvedVc, Vc}; use turbo_tasks_bytes::stream::SingleValue; use turbo_tasks_env::{CommandLineProcessEnv, ProcessEnv}; -use turbo_tasks_fetch::{FetchClient, HttpResponseBody}; +use turbo_tasks_fetch::{FetchClientConfig, HttpResponseBody}; use turbo_tasks_fs::{ DiskFileSystem, File, FileContent, FileSystem, FileSystemPath, json::parse_json_with_source_context, @@ -146,7 +146,7 @@ impl NextFontGoogleReplacer { impl ImportMappingReplacement for NextFontGoogleReplacer { #[turbo_tasks::function] fn replace(&self, _capture: Vc) -> Vc { - ReplacedImportMapping::Ignore.into() + ReplacedImportMapping::Ignore.cell() } /// Intercepts requests for `next/font/google/target.css` and returns a @@ -166,14 +166,14 @@ impl ImportMappingReplacement for NextFontGoogleReplacer { fragment: _, } = request else { - return Ok(ImportMapResult::NoEntry.into()); + return Ok(ImportMapResult::NoEntry.cell()); }; let this = &*self.await?; if can_use_next_font(this.project_path.clone(), query).await? { Ok(self.import_map_result(query.clone())) } else { - Ok(ImportMapResult::NoEntry.into()) + Ok(ImportMapResult::NoEntry.cell()) } } } @@ -183,7 +183,7 @@ pub struct NextFontGoogleCssModuleReplacer { project_path: FileSystemPath, execution_context: ResolvedVc, next_mode: ResolvedVc, - fetch_client: ResolvedVc, + fetch_client: ResolvedVc, } #[turbo_tasks::value_impl] @@ -193,7 +193,7 @@ impl NextFontGoogleCssModuleReplacer { project_path: FileSystemPath, execution_context: ResolvedVc, next_mode: ResolvedVc, - fetch_client: ResolvedVc, + fetch_client: ResolvedVc, ) -> Vc { Self::cell(NextFontGoogleCssModuleReplacer { project_path, @@ -338,7 +338,7 @@ impl NextFontGoogleCssModuleReplacer { impl ImportMappingReplacement for NextFontGoogleCssModuleReplacer { #[turbo_tasks::function] fn replace(&self, _capture: Vc) -> Vc { - ReplacedImportMapping::Ignore.into() + ReplacedImportMapping::Ignore.cell() } /// Intercepts requests for the css module made by the virtual JavaScript @@ -376,13 +376,16 @@ struct NextFontGoogleFontFileOptions { #[turbo_tasks::value(shared)] pub struct NextFontGoogleFontFileReplacer { project_path: FileSystemPath, - fetch_client: ResolvedVc, + fetch_client: ResolvedVc, } #[turbo_tasks::value_impl] impl NextFontGoogleFontFileReplacer { #[turbo_tasks::function] - pub fn new(project_path: FileSystemPath, fetch_client: ResolvedVc) -> Vc { + pub fn new( + project_path: FileSystemPath, + fetch_client: ResolvedVc, + ) -> Vc { Self::cell(NextFontGoogleFontFileReplacer { project_path, fetch_client, @@ -670,7 +673,7 @@ fn font_file_options_from_query_map(query: &RcStr) -> Result, + fetch_client: Vc, stylesheet_url: RcStr, css_virtual_path: FileSystemPath, ) -> Result>> { @@ -680,7 +683,7 @@ async fn fetch_real_stylesheet( } async fn fetch_from_google_fonts( - fetch_client: Vc, + fetch_client: Vc, url: RcStr, virtual_path: FileSystemPath, ) -> Result>> { diff --git a/crates/next-core/src/next_font/issue.rs b/crates/next-core/src/next_font/issue.rs index 46389d04e23c2..e1dad5de23836 100644 --- a/crates/next-core/src/next_font/issue.rs +++ b/crates/next-core/src/next_font/issue.rs @@ -14,7 +14,7 @@ pub(crate) struct NextFontIssue { impl Issue for NextFontIssue { #[turbo_tasks::function] fn stage(&self) -> Vc { - IssueStage::CodeGen.into() + IssueStage::CodeGen.cell() } fn severity(&self) -> IssueSeverity { diff --git a/crates/next-core/src/next_font/local/mod.rs b/crates/next-core/src/next_font/local/mod.rs index a69f76db428f3..766fd8143ad05 100644 --- a/crates/next-core/src/next_font/local/mod.rs +++ b/crates/next-core/src/next_font/local/mod.rs @@ -166,7 +166,7 @@ impl BeforeResolvePlugin for NextFontLocalResolvePlugin { "{}.js", get_request_id(options_vc.font_family().await?, request_hash) ))?, - AssetContent::file(FileContent::Content(file_content.into()).into()), + AssetContent::file(FileContent::Content(file_content.into()).cell()), ) .to_resolved() .await?; diff --git a/crates/next-core/src/next_font/local/options.rs b/crates/next-core/src/next_font/local/options.rs index 92435d977a8f0..bb1aef545a303 100644 --- a/crates/next-core/src/next_font/local/options.rs +++ b/crates/next-core/src/next_font/local/options.rs @@ -9,6 +9,7 @@ use super::request::{ AdjustFontFallback, NextFontLocalRequest, NextFontLocalRequestArguments, SrcDescriptor, SrcRequest, }; +use crate::next_font::local::request::NextFontLocalDeclaration; /// A normalized, Vc-friendly struct derived from validating and transforming /// [[NextFontLocalRequest]] @@ -32,6 +33,8 @@ pub(super) struct NextFontLocalOptions { /// The name of the variable assigned to the results of calling the /// `localFont` function. This is used as the font family's base name. pub variable_name: RcStr, + /// A list of custom properties to be included in the @font-face declaration. + pub declarations: Option>, } impl NextFontLocalOptions { @@ -174,6 +177,7 @@ pub(super) fn options_from_request(request: &NextFontLocalRequest) -> Result Result, + pub declarations: Option>, } #[derive(Debug, Deserialize)] diff --git a/crates/next-core/src/next_font/local/stylesheet.rs b/crates/next-core/src/next_font/local/stylesheet.rs index 18661ff9de9f5..91a3fd070c5ef 100644 --- a/crates/next-core/src/next_font/local/stylesheet.rs +++ b/crates/next-core/src/next_font/local/stylesheet.rs @@ -1,5 +1,6 @@ use anyhow::{Result, bail}; use indoc::formatdoc; +use itertools::Itertools; use turbo_rcstr::RcStr; use turbo_tasks::Vc; @@ -64,12 +65,20 @@ pub(super) async fn build_font_face_definitions( definitions.push_str(&formatdoc!( r#" @font-face {{ + {} font-family: '{}'; src: url('@vercel/turbopack-next/internal/font/local/font?{}') format('{}'); font-display: {}; {}{} }} "#, + options.declarations.as_ref().map_or_else( + || "".to_owned(), + |declarations| declarations + .iter() + .map(|declaration| format!("{}: {};", declaration.prop, declaration.value)) + .join("\n") + ), scoped_font_family, query_str, ext_to_format(&font.ext)?, diff --git a/crates/next-core/src/next_image/module.rs b/crates/next-core/src/next_image/module.rs index 286748d7336b8..81c0ad1f2e310 100644 --- a/crates/next-core/src/next_image/module.rs +++ b/crates/next-core/src/next_image/module.rs @@ -56,7 +56,9 @@ impl StructuredImageModuleType { blur_placeholder_mode: BlurPlaceholderMode, module_asset_context: ResolvedVc, ) -> Result>> { - let static_asset = StaticUrlJsModule::new(*source).to_resolved().await?; + let static_asset = StaticUrlJsModule::new(*source, Some(rcstr!("client"))) + .to_resolved() + .await?; Ok(module_asset_context .process( Vc::upcast( diff --git a/crates/next-core/src/next_import_map.rs b/crates/next-core/src/next_import_map.rs index 39cd4ae2616cf..4fdc9a30c1b1a 100644 --- a/crates/next-core/src/next_import_map.rs +++ b/crates/next-core/src/next_import_map.rs @@ -5,8 +5,9 @@ use either::Either; use rustc_hash::FxHashMap; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{FxIndexMap, ResolvedVc, Vc, fxindexmap}; -use turbo_tasks_fs::{FileSystem, FileSystemPath}; +use turbo_tasks_fs::{FileSystem, FileSystemPath, to_sys_path}; use turbopack_core::{ + issue::{Issue, IssueExt, IssueSeverity, IssueStage, StyledString}, reference_type::{CommonJsReferenceSubType, ReferenceType}, resolve::{ AliasPattern, ExternalTraced, ExternalType, ResolveAliasMap, SubpathValue, @@ -126,10 +127,8 @@ pub async fn get_next_client_import_map( match &ty { ClientContextType::Pages { .. } => {} ClientContextType::App { app_dir } => { - let react_flavor = if *next_config.enable_ppr().await? - || *next_config.enable_taint().await? - || *next_config.enable_view_transition().await? - { + // Keep in sync with file:///./../../../packages/next/src/lib/needs-experimental-react.ts + let react_flavor = if *next_config.enable_taint().await? { "-experimental" } else { "" @@ -830,14 +829,8 @@ async fn apply_vendored_react_aliases_server( runtime: NextRuntime, next_config: Vc, ) -> Result<()> { - let ppr = *next_config.enable_ppr().await?; let taint = *next_config.enable_taint().await?; - let view_transition = *next_config.enable_view_transition().await?; - let react_channel = if ppr || taint || view_transition { - "-experimental" - } else { - "" - }; + let react_channel = if taint { "-experimental" } else { "" }; let react_condition = if ty.should_use_react_server_condition() { "server" } else { @@ -988,6 +981,7 @@ async fn apply_vendored_react_aliases_server( // This is used in the server runtime to import React Server Components. alias.extend(fxindexmap! { rcstr!("next/navigation") => rcstr!("next/dist/api/navigation.react-server"), + rcstr!("next/link") => rcstr!("next/dist/client/app-dir/link.react-server"), }); } @@ -1017,6 +1011,7 @@ async fn rsc_aliases( // This is used in the server runtime to import React Server Components. alias.extend(fxindexmap! { rcstr!("next/navigation") => rcstr!("next/dist/api/navigation.react-server"), + rcstr!("next/link") => rcstr!("next/dist/client/app-dir/link.react-server"), }); } @@ -1240,13 +1235,73 @@ pub async fn get_next_package(context_directory: FileSystemPath) -> Result Vc { + self.path.clone().cell() + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::Fatal + } + + #[turbo_tasks::function] + fn stage(&self) -> Vc { + IssueStage::Resolve.cell() + } + + #[turbo_tasks::function] + async fn title(&self) -> Result> { + let system_path = match to_sys_path(self.path.clone()).await? { + Some(path) => path.to_str().unwrap_or("{unknown}").to_string(), + _ => "{unknown}".to_string(), + }; + + Ok(StyledString::Stack(vec![ + StyledString::Line(vec![ + StyledString::Text( + "Error: Next.js inferred your workspace root, but it may not be correct.".into(), + ), + ]), + StyledString::Line(vec![ + StyledString::Text("We couldn't find the Next.js package (".into()), + StyledString::Strong("next/package.json".into()), + StyledString::Text(") from the project directory: ".into()), + StyledString::Strong(system_path.into()), + ]), + StyledString::Line(vec![ + StyledString::Text(" To fix this, set ".into()), + StyledString::Code("turbopack.root".into()), + StyledString::Text( + " in your Next.js config, or ensure the Next.js package is resolvable from this directory.".into(), + ), + ]), + StyledString::Line(vec![ + StyledString::Text("Note: For security and performance reasons, files outside of the project directory will not be compiled.".into()), + ]), + StyledString::Line(vec![ + StyledString::Text("See ".into()), + StyledString::Strong("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#root-directory".into()), + StyledString::Text(" for more information.".into()) + ]), + ]) + .cell()) + } +} + #[turbo_tasks::function] pub async fn try_get_next_package( context_directory: FileSystemPath, ) -> Result> { let root = context_directory.root().owned().await?; let result = resolve( - context_directory, + context_directory.clone(), ReferenceType::CommonJs(CommonJsReferenceSubType::Undefined), Request::parse(Pattern::Constant(rcstr!("next/package.json"))), node_cjs_resolve_options(root), @@ -1254,6 +1309,11 @@ pub async fn try_get_next_package( if let Some(source) = &*result.first_source().await? { Ok(Vc::cell(Some(source.ident().path().await?.parent()))) } else { + MissingNextFolderIssue { + path: context_directory, + } + .resolved_cell() + .emit(); Ok(Vc::cell(None)) } } diff --git a/crates/next-core/src/next_manifests/client_reference_manifest.rs b/crates/next-core/src/next_manifests/client_reference_manifest.rs index 532dc3af62451..52bb81572148a 100644 --- a/crates/next-core/src/next_manifests/client_reference_manifest.rs +++ b/crates/next-core/src/next_manifests/client_reference_manifest.rs @@ -165,21 +165,13 @@ async fn build_manifest( let mut entry_manifest: SerializedClientReferenceManifest = Default::default(); let mut references = FxIndexSet::default(); let chunk_suffix_path = next_config.chunk_suffix_path().owned().await?; - let prefix_path = next_config - .computed_asset_prefix() - .owned() - .await? - .unwrap_or_default(); + let prefix_path = next_config.computed_asset_prefix().owned().await?; let suffix_path = chunk_suffix_path.unwrap_or_default(); // TODO: Add `suffix` to the manifest for React to use. // entry_manifest.module_loading.prefix = prefix_path; - entry_manifest.module_loading.cross_origin = next_config - .await? - .cross_origin - .as_ref() - .map(|p| p.to_owned()); + entry_manifest.module_loading.cross_origin = next_config.cross_origin().owned().await?; let ClientReferencesChunks { client_component_client_chunks, layout_segment_client_chunks, @@ -442,8 +434,7 @@ async fn build_manifest( cached_chunk_paths(&mut client_chunk_path_cache, client_chunks.iter().copied()) .await?; // Inlining breaks HMR so it is always disabled in dev. - let inlined_css = - next_config.await?.experimental.inline_css.unwrap_or(false) && mode.is_production(); + let inlined_css = *next_config.inline_css().await? && mode.is_production(); for (chunk, chunk_path) in client_chunks_with_path { if let Some(path) = client_relative_path.get_path_to(&chunk_path) { diff --git a/crates/next-core/src/next_manifests/mod.rs b/crates/next-core/src/next_manifests/mod.rs index fe98c56dee0d6..c707279710814 100644 --- a/crates/next-core/src/next_manifests/mod.rs +++ b/crates/next-core/src/next_manifests/mod.rs @@ -238,7 +238,7 @@ impl Default for MiddlewaresManifest { NonLocalValue, )] #[serde(rename_all = "camelCase", default)] -pub struct MiddlewareMatcher { +pub struct ProxyMatcher { // When skipped next.js with fill that during merging. #[serde(skip_serializing_if = "Option::is_none")] pub regexp: Option, @@ -251,7 +251,7 @@ pub struct MiddlewareMatcher { pub original_source: RcStr, } -impl Default for MiddlewareMatcher { +impl Default for ProxyMatcher { fn default() -> Self { Self { regexp: None, @@ -272,7 +272,7 @@ pub struct EdgeFunctionDefinition { pub files: Vec, pub name: RcStr, pub page: RcStr, - pub matchers: Vec, + pub matchers: Vec, pub wasm: Vec, pub assets: Vec, #[serde(skip_serializing_if = "Option::is_none")] @@ -446,14 +446,14 @@ mod tests { #[test] fn test_middleware_matcher_serialization() { let matchers = vec![ - MiddlewareMatcher { + ProxyMatcher { regexp: None, locale: false, has: None, missing: None, original_source: rcstr!(""), }, - MiddlewareMatcher { + ProxyMatcher { regexp: Some(rcstr!(".*")), locale: true, has: Some(vec![RouteHas::Query { @@ -469,7 +469,7 @@ mod tests { ]; let serialized = serde_json::to_string(&matchers).unwrap(); - let deserialized: Vec = serde_json::from_str(&serialized).unwrap(); + let deserialized: Vec = serde_json::from_str(&serialized).unwrap(); assert_eq!(matchers, deserialized); } diff --git a/crates/next-core/src/next_route_matcher/all.rs b/crates/next-core/src/next_route_matcher/all.rs deleted file mode 100644 index 5839a948626e8..0000000000000 --- a/crates/next-core/src/next_route_matcher/all.rs +++ /dev/null @@ -1,15 +0,0 @@ -use serde::{Deserialize, Serialize}; -use turbopack_node::route_matcher::{Params, RouteMatcherRef}; - -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] -pub struct AllMatch; - -impl RouteMatcherRef for AllMatch { - fn matches(&self, _path: &str) -> bool { - true - } - - fn params(&self, _path: &str) -> Params { - Params(Some(Default::default())) - } -} diff --git a/crates/next-core/src/next_route_matcher/mod.rs b/crates/next-core/src/next_route_matcher/mod.rs deleted file mode 100644 index 9593930ee43c0..0000000000000 --- a/crates/next-core/src/next_route_matcher/mod.rs +++ /dev/null @@ -1,182 +0,0 @@ -use anyhow::{Result, bail}; -use turbo_rcstr::RcStr; -use turbo_tasks::{ResolvedVc, Vc}; -use turbopack_node::route_matcher::{Params, RouteMatcher, RouteMatcherRef}; - -use self::{ - all::AllMatch, - path_regex::{PathRegex, PathRegexBuilder}, - prefix_suffix::PrefixSuffixMatcher, -}; - -mod all; -mod path_regex; -mod prefix_suffix; - -/// A route matcher that matches a path against an exact route. -#[turbo_tasks::value] -pub(crate) struct NextExactMatcher { - path: ResolvedVc, -} - -#[turbo_tasks::value_impl] -impl NextExactMatcher { - #[turbo_tasks::function] - pub fn new(path: ResolvedVc) -> Vc { - Self::cell(NextExactMatcher { path }) - } -} - -#[turbo_tasks::value_impl] -impl RouteMatcher for NextExactMatcher { - #[turbo_tasks::function] - async fn matches(&self, path: RcStr) -> Result> { - Ok(Vc::cell(path == *self.path.await?)) - } - - #[turbo_tasks::function] - async fn params(&self, path: RcStr) -> Result> { - Ok(Vc::cell(if path == *self.path.await? { - Some(Default::default()) - } else { - None - })) - } -} - -/// A route matcher that matches a path against a route regex. -#[turbo_tasks::value] -pub(crate) struct NextParamsMatcher { - #[turbo_tasks(trace_ignore)] - matcher: PathRegex, -} - -#[turbo_tasks::value_impl] -impl NextParamsMatcher { - #[turbo_tasks::function] - pub async fn new(path: ResolvedVc) -> Result> { - Ok(Self::cell(NextParamsMatcher { - matcher: build_path_regex(path.await?.as_str())?, - })) - } -} - -#[turbo_tasks::value_impl] -impl RouteMatcher for NextParamsMatcher { - #[turbo_tasks::function] - fn matches(&self, path: RcStr) -> Vc { - Vc::cell(self.matcher.matches(&path)) - } - - #[turbo_tasks::function] - fn params(&self, path: RcStr) -> Vc { - Params::cell(self.matcher.params(&path)) - } -} - -/// A route matcher that strips a prefix and a suffix from a path before -/// matching it against a route regex. -#[turbo_tasks::value] -pub(crate) struct NextPrefixSuffixParamsMatcher { - #[turbo_tasks(trace_ignore)] - matcher: PrefixSuffixMatcher, -} - -#[turbo_tasks::value_impl] -impl NextPrefixSuffixParamsMatcher { - /// Converts a filename within the server root into a regular expression - /// with named capture groups for every dynamic segment. - #[turbo_tasks::function] - pub async fn new(path: ResolvedVc, prefix: RcStr, suffix: RcStr) -> Result> { - Ok(Self::cell(NextPrefixSuffixParamsMatcher { - matcher: PrefixSuffixMatcher::new( - prefix.to_string(), - suffix.to_string(), - build_path_regex(path.await?.as_str())?, - ), - })) - } -} - -#[turbo_tasks::value_impl] -impl RouteMatcher for NextPrefixSuffixParamsMatcher { - #[turbo_tasks::function] - fn matches(&self, path: RcStr) -> Vc { - Vc::cell(self.matcher.matches(&path)) - } - - #[turbo_tasks::function] - fn params(&self, path: RcStr) -> Vc { - Params::cell(self.matcher.params(&path)) - } -} - -/// A route matcher that matches against all paths. -#[turbo_tasks::value] -pub(crate) struct NextFallbackMatcher { - #[turbo_tasks(trace_ignore)] - matcher: AllMatch, -} - -#[turbo_tasks::value_impl] -impl NextFallbackMatcher { - #[turbo_tasks::function] - pub fn new() -> Vc { - Self::cell(NextFallbackMatcher { matcher: AllMatch }) - } -} - -#[turbo_tasks::value_impl] -impl RouteMatcher for NextFallbackMatcher { - #[turbo_tasks::function] - fn matches(&self, path: RcStr) -> Vc { - Vc::cell(self.matcher.matches(&path)) - } - - #[turbo_tasks::function] - fn params(&self, path: RcStr) -> Vc { - Params::cell(self.matcher.params(&path)) - } -} - -/// Converts a filename within the server root into a regular expression -/// with named capture groups for every dynamic segment. -fn build_path_regex(path: &str) -> Result { - let mut path_regex = PathRegexBuilder::new(); - for segment in path.split('/') { - if let Some(segment) = segment.strip_prefix('[') { - if let Some(segment) = segment.strip_prefix("[...") { - if let Some((placeholder, rem)) = segment.split_once("]]") { - path_regex.push_optional_catch_all(placeholder, rem); - } else { - bail!( - "path ({}) contains '[[' without matching ']]' at '[[...{}'", - path, - segment - ); - } - } else if let Some(segment) = segment.strip_prefix("...") { - if let Some((placeholder, rem)) = segment.split_once(']') { - path_regex.push_catch_all(placeholder, rem); - } else { - bail!( - "path ({}) contains '[' without matching ']' at '[...{}'", - path, - segment - ); - } - } else if let Some((placeholder, rem)) = segment.split_once(']') { - path_regex.push_dynamic_segment(placeholder, rem); - } else { - bail!( - "path ({}) contains '[' without matching ']' at '[{}'", - path, - segment - ); - } - } else { - path_regex.push_static_segment(segment); - } - } - path_regex.build() -} diff --git a/crates/next-core/src/next_route_matcher/path_regex.rs b/crates/next-core/src/next_route_matcher/path_regex.rs deleted file mode 100644 index ec5b5a3792580..0000000000000 --- a/crates/next-core/src/next_route_matcher/path_regex.rs +++ /dev/null @@ -1,166 +0,0 @@ -use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; -use turbo_tasks::primitives::Regex; -use turbopack_node::route_matcher::{Param, Params, RouteMatcherRef}; - -/// A regular expression that matches a path, with named capture groups for the -/// dynamic parts of the path. -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] -pub struct PathRegex { - regex: Regex, - named_params: Vec, -} - -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] -struct NamedParam { - name: String, - kind: NamedParamKind, -} - -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] -enum NamedParamKind { - Single, - Multi, -} - -impl std::fmt::Display for PathRegex { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.regex.as_str()) - } -} - -impl RouteMatcherRef for PathRegex { - fn matches(&self, path: &str) -> bool { - self.regex.is_match(path) - } - - fn params(&self, path: &str) -> Params { - Params(self.regex.captures(path).map(|capture| { - self.named_params - .iter() - .enumerate() - .filter_map(|(idx, param)| { - if param.name.is_empty() { - return None; - } - let value = capture.get(idx + 1)?; - Some(( - param.name.as_str().into(), - match param.kind { - NamedParamKind::Single => Param::Single(value.as_str().into()), - NamedParamKind::Multi => Param::Multi( - value - .as_str() - .split('/') - .map(|segment| segment.into()) - .collect(), - ), - }, - )) - }) - .collect() - })) - } -} - -/// Builder for [PathRegex]. -pub struct PathRegexBuilder { - regex_str: String, - named_params: Vec, -} - -impl PathRegexBuilder { - /// Creates a new [PathRegexBuilder]. - pub fn new() -> Self { - Self { - regex_str: "^".to_string(), - named_params: Default::default(), - } - } - - fn include_slash(&self) -> bool { - self.regex_str.len() > 1 - } - - fn push_str(&mut self, str: &str) { - self.regex_str.push_str(str); - } - - /// Pushes an optional catch all segment to the regex. - pub fn push_optional_catch_all(&mut self, name: N, rem: R) - where - N: Into, - R: AsRef, - { - self.push_str(if self.include_slash() { - "(?:/([^?]+))?" - } else { - "([^?]+)?" - }); - self.push_str(®ex::escape(rem.as_ref())); - self.named_params.push(NamedParam { - name: name.into(), - kind: NamedParamKind::Multi, - }); - } - - /// Pushes a catch all segment to the regex. - pub fn push_catch_all(&mut self, name: N, rem: R) - where - N: Into, - R: AsRef, - { - if self.include_slash() { - self.push_str("/"); - } - self.push_str("([^?]+)"); - self.push_str(®ex::escape(rem.as_ref())); - self.named_params.push(NamedParam { - name: name.into(), - kind: NamedParamKind::Multi, - }); - } - - /// Pushes a dynamic segment to the regex. - pub fn push_dynamic_segment(&mut self, name: N, rem: R) - where - N: Into, - R: AsRef, - { - if self.include_slash() { - self.push_str("/"); - } - self.push_str("([^?/]+)"); - self.push_str(®ex::escape(rem.as_ref())); - self.named_params.push(NamedParam { - name: name.into(), - kind: NamedParamKind::Single, - }); - } - - /// Pushes a static segment to the regex. - pub fn push_static_segment(&mut self, segment: S) - where - S: AsRef, - { - if self.include_slash() { - self.push_str("/"); - } - self.push_str(®ex::escape(segment.as_ref())); - } - - /// Builds and returns the [PathRegex]. - pub fn build(mut self) -> Result { - self.regex_str += "$"; - Ok(PathRegex { - regex: Regex(regex::Regex::new(&self.regex_str).with_context(|| "invalid path regex")?), - named_params: self.named_params, - }) - } -} - -impl Default for PathRegexBuilder { - fn default() -> Self { - Self::new() - } -} diff --git a/crates/next-core/src/next_route_matcher/prefix_suffix.rs b/crates/next-core/src/next_route_matcher/prefix_suffix.rs deleted file mode 100644 index a87271c3d564e..0000000000000 --- a/crates/next-core/src/next_route_matcher/prefix_suffix.rs +++ /dev/null @@ -1,54 +0,0 @@ -use serde::{Deserialize, Serialize}; -use turbopack_node::route_matcher::{Params, RouteMatcherRef}; - -/// A composite route matcher that matches a path if it has a given prefix and -/// suffix. -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] -pub struct PrefixSuffixMatcher -where - T: RouteMatcherRef, -{ - prefix: String, - suffix: String, - inner: T, -} - -impl PrefixSuffixMatcher -where - T: RouteMatcherRef, -{ - /// Creates a new [PrefixSuffixMatcher]. - pub fn new(prefix: String, suffix: String, inner: T) -> Self { - Self { - prefix, - suffix, - inner, - } - } - - fn strip_prefix_and_suffix<'b>(&self, path: &'b str) -> Option<&'b str> { - path.strip_prefix(self.prefix.as_str())? - .strip_suffix(self.suffix.as_str()) - } -} - -impl RouteMatcherRef for PrefixSuffixMatcher -where - T: RouteMatcherRef, -{ - fn matches(&self, path: &str) -> bool { - if let Some(path) = self.strip_prefix_and_suffix(path) { - self.inner.matches(path) - } else { - false - } - } - - fn params(&self, path: &str) -> Params { - if let Some(path) = self.strip_prefix_and_suffix(path) { - self.inner.params(path) - } else { - Params(None) - } - } -} diff --git a/crates/next-core/src/next_server/context.rs b/crates/next-core/src/next_server/context.rs index 109e635d184cb..bb308e6cb3153 100644 --- a/crates/next-core/src/next_server/context.rs +++ b/crates/next-core/src/next_server/context.rs @@ -16,7 +16,7 @@ use turbopack::{ }; use turbopack_core::{ chunk::{ - ChunkingConfig, MangleType, MinifyType, SourceMapsType, + ChunkingConfig, MangleType, MinifyType, SourceMapSourceType, SourceMapsType, module_id_strategies::ModuleIdStrategy, }, compile_time_defines, @@ -214,6 +214,13 @@ pub async fn get_server_resolve_options_context( custom_conditions.push(rcstr!("react-server")); }; + if *next_config.enable_cache_components().await? + // Middleware shouldn't use the "next-js" condition because it doesn't have all Next.js APIs available + && !matches!(ty, ServerContextType::Middleware { .. } | ServerContextType::Instrumentation { .. }) + { + custom_conditions.push(rcstr!("next-js")); + }; + let external_cjs_modules_plugin = if *next_config.bundle_pages_router_dependencies().await? { server_external_packages_plugin } else { @@ -993,13 +1000,15 @@ pub struct ServerChunkingContextOptions { pub turbo_source_maps: Vc, pub no_mangling: Vc, pub scope_hoisting: Vc, + pub debug_ids: Vc, + pub client_root: FileSystemPath, + pub asset_prefix: RcStr, } +/// Like `get_server_chunking_context` but all assets are emitted as client assets (so `/_next`) #[turbo_tasks::function] pub async fn get_server_chunking_context_with_client_assets( options: ServerChunkingContextOptions, - client_root: FileSystemPath, - asset_prefix: Option, ) -> Result> { let ServerChunkingContextOptions { mode, @@ -1013,6 +1022,9 @@ pub async fn get_server_chunking_context_with_client_assets( turbo_source_maps, no_mangling, scope_hoisting, + debug_ids, + client_root, + asset_prefix, } = options; let next_mode = mode.await?; @@ -1029,7 +1041,7 @@ pub async fn get_server_chunking_context_with_client_assets( environment.to_resolved().await?, next_mode.runtime_type(), ) - .asset_prefix(asset_prefix) + .asset_prefix(Some(asset_prefix)) .minify_type(if *turbo_minify.await? { MinifyType::Minify { // React needs deterministic function names to work correctly. @@ -1045,11 +1057,16 @@ pub async fn get_server_chunking_context_with_client_assets( }) .module_id_strategy(module_id_strategy.to_resolved().await?) .export_usage(*export_usage.await?) - .file_tracing(next_mode.is_production()); + .file_tracing(next_mode.is_production()) + .debug_ids(*debug_ids.await?); - if next_mode.is_development() { - builder = builder.use_file_source_map_uris(); + builder = builder.source_map_source_type(if next_mode.is_development() { + SourceMapSourceType::AbsoluteFileUri } else { + // TODO(lukesandberg): switch to relative once next is compatible. + SourceMapSourceType::TurbopackUri + }); + if next_mode.is_production() { builder = builder .chunking_config( Vc::::default().to_resolved().await?, @@ -1073,6 +1090,7 @@ pub async fn get_server_chunking_context_with_client_assets( Ok(builder.build()) } +// By default, assets are server assets, but the StructuredImageModuleType ones are on the client #[turbo_tasks::function] pub async fn get_server_chunking_context( options: ServerChunkingContextOptions, @@ -1089,6 +1107,9 @@ pub async fn get_server_chunking_context( turbo_source_maps, no_mangling, scope_hoisting, + debug_ids, + client_root, + asset_prefix, } = options; let next_mode = mode.await?; // TODO(alexkirsz) This should return a trait that can be implemented by the @@ -1104,6 +1125,9 @@ pub async fn get_server_chunking_context( environment.to_resolved().await?, next_mode.runtime_type(), ) + .client_roots_override(rcstr!("client"), client_root.clone()) + .asset_root_path_override(rcstr!("client"), client_root.join("static/media")?) + .asset_prefix_override(rcstr!("client"), asset_prefix) .minify_type(if *turbo_minify.await? { MinifyType::Minify { mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize), @@ -1118,12 +1142,15 @@ pub async fn get_server_chunking_context( }) .module_id_strategy(module_id_strategy.to_resolved().await?) .export_usage(*export_usage.await?) - .file_tracing(next_mode.is_production()); + .file_tracing(next_mode.is_production()) + .debug_ids(*debug_ids.await?); if next_mode.is_development() { - builder = builder.use_file_source_map_uris() + builder = builder.source_map_source_type(SourceMapSourceType::AbsoluteFileUri); } else { builder = builder + // TODO(lukesandberg): switch to relative once next is compatible. + .source_map_source_type(SourceMapSourceType::TurbopackUri) .chunking_config( Vc::::default().to_resolved().await?, ChunkingConfig { diff --git a/crates/next-core/src/next_server/resolve.rs b/crates/next-core/src/next_server/resolve.rs index 9e6c15cea4c21..456302e4b6b09 100644 --- a/crates/next-core/src/next_server/resolve.rs +++ b/crates/next-core/src/next_server/resolve.rs @@ -25,7 +25,7 @@ use turbopack_core::{ /// The predicated based on which the [ExternalCjsModulesResolvePlugin] decides /// whether to mark a module as external. -#[turbo_tasks::value(into = "shared")] +#[turbo_tasks::value(shared)] pub enum ExternalPredicate { /// Mark all modules as external if they're not listed in the list. /// Applies only to imports outside of node_modules. @@ -483,7 +483,7 @@ impl Issue for ExternalizeIssue { #[turbo_tasks::function] fn stage(&self) -> Vc { - IssueStage::Config.into() + IssueStage::Config.cell() } #[turbo_tasks::function] diff --git a/crates/next-core/src/next_shared/resolve.rs b/crates/next-core/src/next_shared/resolve.rs index 04156bf3766c4..05d14dc17cc59 100644 --- a/crates/next-core/src/next_shared/resolve.rs +++ b/crates/next-core/src/next_shared/resolve.rs @@ -64,7 +64,7 @@ impl Issue for InvalidImportModuleIssue { #[turbo_tasks::function] fn stage(&self) -> Vc { - IssueStage::Resolve.into() + IssueStage::Resolve.cell() } #[turbo_tasks::function] diff --git a/crates/next-core/src/next_shared/webpack_rules/babel.rs b/crates/next-core/src/next_shared/webpack_rules/babel.rs index 5280d5e5ba7a2..cb795fae2335f 100644 --- a/crates/next-core/src/next_shared/webpack_rules/babel.rs +++ b/crates/next-core/src/next_shared/webpack_rules/babel.rs @@ -304,7 +304,7 @@ struct BabelPluginReactCompilerResolutionIssue { impl Issue for BabelPluginReactCompilerResolutionIssue { #[turbo_tasks::function] fn stage(&self) -> Vc { - IssueStage::Transform.into() + IssueStage::Transform.cell() } fn severity(&self) -> IssueSeverity { diff --git a/crates/next-core/src/next_shared/webpack_rules/sass.rs b/crates/next-core/src/next_shared/webpack_rules/sass.rs index 0d394f3493f04..4ba8660627d66 100644 --- a/crates/next-core/src/next_shared/webpack_rules/sass.rs +++ b/crates/next-core/src/next_shared/webpack_rules/sass.rs @@ -71,19 +71,10 @@ pub async fn get_sass_loader_rules( } let sass_options = next_config.sass_config().await?; - let Some(mut sass_options) = sass_options.as_object().cloned() else { + let Some(sass_options) = sass_options.as_object() else { bail!("sass_options must be an object"); }; - // TODO: Remove this once we upgrade to sass-loader 16 - let silence_deprecations = if let Some(v) = sass_options.get("silenceDeprecations") { - v.clone() - } else { - serde_json::json!(["legacy-js-api"]) - }; - - sass_options.insert("silenceDeprecations".into(), silence_deprecations); - // additionalData is a loader option but Next.js has it under `sassOptions` in // `next.config.js` let additional_data = sass_options diff --git a/crates/next-core/src/raw_ecmascript_module.rs b/crates/next-core/src/raw_ecmascript_module.rs index 46c5303334231..0fb6e665c91be 100644 --- a/crates/next-core/src/raw_ecmascript_module.rs +++ b/crates/next-core/src/raw_ecmascript_module.rs @@ -309,7 +309,7 @@ impl EcmascriptChunkItem for RawEcmascriptChunkItem { }, ..Default::default() } - .into()) + .cell()) } .instrument(span) .await diff --git a/crates/next-core/src/segment_config.rs b/crates/next-core/src/segment_config.rs index acb5b96cfc693..af87e10ffc40a 100644 --- a/crates/next-core/src/segment_config.rs +++ b/crates/next-core/src/segment_config.rs @@ -37,7 +37,7 @@ use turbopack_ecmascript::{ use crate::{ app_structure::AppPageLoaderTree, next_config::RouteHas, - next_manifests::MiddlewareMatcher, + next_manifests::ProxyMatcher, util::{MiddlewareMatcherKind, NextRuntime}, }; @@ -89,7 +89,6 @@ pub struct NextSegmentConfig { pub fetch_cache: Option, pub runtime: Option, pub preferred_region: Option>, - pub experimental_ppr: Option, pub middleware_matcher: Option>, /// Whether these exports are defined in the source file. @@ -118,7 +117,6 @@ impl NextSegmentConfig { fetch_cache, runtime, preferred_region, - experimental_ppr, .. } = self; *dynamic = dynamic.or(parent.dynamic); @@ -127,7 +125,6 @@ impl NextSegmentConfig { *fetch_cache = fetch_cache.or(parent.fetch_cache); *runtime = runtime.or(parent.runtime); *preferred_region = preferred_region.take().or(parent.preferred_region.clone()); - *experimental_ppr = experimental_ppr.or(parent.experimental_ppr); } /// Applies a config from a parallel route to this config, returning an @@ -161,7 +158,6 @@ impl NextSegmentConfig { fetch_cache, runtime, preferred_region, - experimental_ppr, .. } = self; merge_parallel(dynamic, ¶llel_config.dynamic, "dynamic")?; @@ -178,11 +174,6 @@ impl NextSegmentConfig { ¶llel_config.preferred_region, "preferredRegion", )?; - merge_parallel( - experimental_ppr, - ¶llel_config.experimental_ppr, - "experimental_ppr", - )?; Ok(()) } } @@ -244,7 +235,7 @@ impl Issue for NextSegmentConfigParsingIssue { #[turbo_tasks::function] fn stage(&self) -> Vc { - IssueStage::Parse.into() + IssueStage::Parse.cell() } #[turbo_tasks::function] @@ -298,6 +289,8 @@ pub enum ParseSegmentMode { Base, // Disallows "use client + generateStatic" and ignores/warns about `export const config` App, + // Disallows config = { runtime: "edge" } + Proxy, } /// Parse the raw source code of a file to get the segment config local to that file. @@ -395,6 +388,8 @@ pub async fn parse_segment_config_from_source( EcmascriptModuleAssetType::Ecmascript }, EcmascriptInputTransforms::empty(), + false, + false, ) .await?; @@ -571,7 +566,7 @@ async fn parse_config_value( ) -> Result<()> { let get_value = || { let init = init.as_deref(); - // Unwrap `export const config = { .. } satisfies MiddlewareConfig`, usually this is already + // Unwrap `export const config = { .. } satisfies ProxyConfig`, usually this is already // transpiled away, but we are looking at the original source here. let init = if let Some(Expr::TsSatisfies(TsSatisfiesExpr { expr, .. })) = init { Some(&**expr) @@ -674,21 +669,35 @@ async fn parse_config_value( .await; }; - config.runtime = - match serde_json::from_value(Value::String(val.to_string())) { - Ok(runtime) => Some(runtime), - Err(err) => { - return invalid_config( - source, - "config", - span, - format!("`runtime` has an invalid value: {err}.").into(), - Some(value), - IssueSeverity::Error, - ) - .await; - } - }; + let runtime = match serde_json::from_value(Value::String(val.to_string())) { + Ok(runtime) => Some(runtime), + Err(err) => { + return invalid_config( + source, + "config", + span, + format!("`runtime` has an invalid value: {err}.").into(), + Some(value), + IssueSeverity::Error, + ) + .await; + } + }; + + if mode == ParseSegmentMode::Proxy && runtime == Some(NextRuntime::Edge) { + invalid_config( + source, + "config", + span, + rcstr!("Proxy does not support Edge runtime."), + Some(value), + IssueSeverity::Error, + ) + .await?; + continue; + } + + config.runtime = runtime } "matcher" => { config.middleware_matcher = @@ -929,35 +938,6 @@ async fn parse_config_value( "generateStaticParams" => { config.generate_static_params = Some(span); } - "experimental_ppr" => { - let Some(value) = get_value() else { - return invalid_config( - source, - "experimental_ppr", - span, - rcstr!("It mustn't be reexported."), - None, - IssueSeverity::Error, - ) - .await; - }; - if matches!(value, JsValue::Constant(ConstantValue::Undefined)) { - return Ok(()); - } - let Some(val) = value.as_bool() else { - return invalid_config( - source, - "experimental_ppr", - span, - rcstr!("`experimental_ppr` needs to be a static boolean."), - Some(&value), - IssueSeverity::Error, - ) - .await; - }; - - config.experimental_ppr = Some(val); - } _ => {} } @@ -1157,7 +1137,7 @@ async fn parse_route_matcher_from_js_value( if let Some(matcher) = item.as_str() { matchers.push(MiddlewareMatcherKind::Str(matcher.to_string())); } else if let JsValue::Object { parts, .. } = item { - let mut matcher = MiddlewareMatcher::default(); + let mut matcher = ProxyMatcher::default(); let mut had_source = false; for matcher_part in parts { if let ObjectPart::KeyValue(key, value) = matcher_part { diff --git a/crates/next-core/src/util.rs b/crates/next-core/src/util.rs index b6f0e323231e5..4689cde0f7d42 100644 --- a/crates/next-core/src/util.rs +++ b/crates/next-core/src/util.rs @@ -20,7 +20,7 @@ use turbopack_core::{ use crate::{ embed_js::next_js_fs, next_config::NextConfig, next_import_map::get_next_package, - next_manifests::MiddlewareMatcher, next_shared::webpack_rules::WebpackLoaderBuiltinCondition, + next_manifests::ProxyMatcher, next_shared::webpack_rules::WebpackLoaderBuiltinCondition, }; const NEXT_TEMPLATE_PATH: &str = "dist/esm/build/templates"; @@ -232,7 +232,7 @@ impl NextRuntime { #[derive(PartialEq, Eq, Clone, Debug, TraceRawVcs, Serialize, Deserialize, NonLocalValue)] pub enum MiddlewareMatcherKind { Str(String), - Matcher(MiddlewareMatcher), + Matcher(ProxyMatcher), } /// Loads a next.js template, replaces `replacements` and `injections` and makes diff --git a/crates/next-custom-transforms/src/transforms/named_import_transform.rs b/crates/next-custom-transforms/src/transforms/named_import_transform.rs index 048b015f30856..d690460c993e2 100644 --- a/crates/next-custom-transforms/src/transforms/named_import_transform.rs +++ b/crates/next-custom-transforms/src/transforms/named_import_transform.rs @@ -78,11 +78,11 @@ impl Fold for NamedImportTransform { // Create a new import declaration, keep everything the same except the source let mut new_decl = decl.clone(); - new_decl.src = Box::new(Str { + *new_decl.src = Str { span: DUMMY_SP, value: new_src.into(), raw: None, - }); + }; return new_decl; } diff --git a/crates/next-custom-transforms/src/transforms/react_server_components.rs b/crates/next-custom-transforms/src/transforms/react_server_components.rs index 98306014b48cc..55dead57fc944 100644 --- a/crates/next-custom-transforms/src/transforms/react_server_components.rs +++ b/crates/next-custom-transforms/src/transforms/react_server_components.rs @@ -101,7 +101,7 @@ enum NextConfigProperty { impl Display for NextConfigProperty { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - NextConfigProperty::CacheComponents => write!(f, "experimental.cacheComponents"), + NextConfigProperty::CacheComponents => write!(f, "cacheComponents"), NextConfigProperty::UseCache => write!(f, "experimental.useCache"), } } @@ -645,17 +645,17 @@ impl ReactServerComponentValidator { ], invalid_client_lib_apis_mapping: FxHashMap::from_iter([ - ("next/server", vec!["after", "unstable_rootParams"]), + ("next/server", vec!["after"]), ( "next/cache", vec![ "revalidatePath", "revalidateTag", // "unstable_cache", // useless in client, but doesn't technically error + "cacheLife", "unstable_cacheLife", + "cacheTag", "unstable_cacheTag", - "unstable_expirePath", - "unstable_expireTag", // "unstable_noStore" // no-op in client, but allowed for legacy reasons ], ), @@ -845,7 +845,8 @@ impl ReactServerComponentValidator { ); } } - "dynamicParams" | "dynamic" | "fetchCache" | "revalidate" => { + "dynamicParams" | "dynamic" | "fetchCache" | "revalidate" + | "experimental_ppr" => { if self.cache_components_enabled { possibly_invalid_exports.insert( export_name.clone(), diff --git a/crates/next-custom-transforms/src/transforms/server_actions.rs b/crates/next-custom-transforms/src/transforms/server_actions.rs index 4af9231b54f88..3da2d65b2c531 100644 --- a/crates/next-custom-transforms/src/transforms/server_actions.rs +++ b/crates/next-custom-transforms/src/transforms/server_actions.rs @@ -120,7 +120,7 @@ enum ServerActionsErrorKind { span: Span, cache_kind: RcStr, }, - UseCacheWithoutExperimentalFlag { + UseCacheWithoutCacheComponents { span: Span, directive: String, }, @@ -545,9 +545,6 @@ impl ServerActions { ..Default::default() }), }, - decorators: vec![], - span: DUMMY_SP, - is_generator: false, is_async: true, ..Default::default() }), @@ -752,44 +749,49 @@ impl ServerActions { }); } - // Create the action export decl from the arrow function - // export var cache_ident = async function() {} + let inner_fn_body = match *arrow.body.take() { + BlockStmtOrExpr::BlockStmt(body) => Some(body), + BlockStmtOrExpr::Expr(expr) => Some(BlockStmt { + stmts: vec![Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(expr), + })], + ..Default::default() + }), + }; + + let inner_fn = Box::new(Expr::Fn(FnExpr { + ident: None, + function: Box::new(Function { + params: new_params.clone(), + body: inner_fn_body, + span: arrow.span, + is_generator: false, + is_async: true, + ..Default::default() + }), + })); + + // Wrap with $$reactCache__(function foo() { return $$cache__(...) }) + let wrapper_fn = wrap_cache_expr( + cache_kind.as_str(), + reference_id.as_str(), + ids_from_closure.len(), + inner_fn, + self.arrow_or_fn_expr_ident.clone(), + arrow.span, + ); + + // Create the export: export var $$RSC_SERVER_CACHE_0 = ... self.hoisted_extra_items .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { span: DUMMY_SP, decl: VarDecl { - span: DUMMY_SP, kind: VarDeclKind::Var, decls: vec![VarDeclarator { span: arrow.span, name: Pat::Ident(cache_ident.clone().into()), - init: Some(wrap_cache_expr( - Box::new(Expr::Fn(FnExpr { - ident: None, - function: Box::new(Function { - params: new_params, - body: match *arrow.body.take() { - BlockStmtOrExpr::BlockStmt(body) => Some(body), - BlockStmtOrExpr::Expr(expr) => Some(BlockStmt { - span: DUMMY_SP, - stmts: vec![Stmt::Return(ReturnStmt { - span: DUMMY_SP, - arg: Some(expr), - })], - ..Default::default() - }), - }, - decorators: vec![], - span: DUMMY_SP, - is_generator: false, - is_async: true, - ..Default::default() - }), - })), - &cache_kind, - &reference_id, - ids_from_closure.len(), - )), + init: Some(wrapper_fn), definite: false, }], ..Default::default() @@ -866,28 +868,40 @@ impl ServerActions { private_ctxt: self.private_ctxt, }); - // export var cache_ident = async function() {} + let function_body = function.body.take(); + let function_span = function.span; + + let inner_fn = Box::new(Expr::Fn(FnExpr { + ident: fn_name.clone(), + function: Box::new(Function { + params: new_params.clone(), + body: function_body, + span: function_span, + is_async: true, + ..function.take() + }), + })); + + // Wrap with $$reactCache__(function foo() { return $$cache__(...) }) + let wrapper_fn = wrap_cache_expr( + cache_kind.as_str(), + reference_id.as_str(), + ids_from_closure.len(), + inner_fn, + fn_name.clone(), + function_span, + ); + + // Create the export: export var $$RSC_SERVER_CACHE_0 = ... self.hoisted_extra_items .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { span: DUMMY_SP, decl: VarDecl { - span: DUMMY_SP, kind: VarDeclKind::Var, decls: vec![VarDeclarator { - span: function.span, + span: function_span, name: Pat::Ident(cache_ident.clone().into()), - init: Some(wrap_cache_expr( - Box::new(Expr::Fn(FnExpr { - ident: fn_name.clone(), - function: Box::new(Function { - params: new_params, - ..function.take() - }), - })), - &cache_kind, - &reference_id, - ids_from_closure.len(), - )), + init: Some(wrapper_fn), definite: false, }], ..Default::default() @@ -901,7 +915,7 @@ impl ServerActions { expr: Box::new(annotate_ident_as_server_reference( cache_ident.clone(), reference_id.clone(), - function.span, + function_span, )), }))); @@ -1534,56 +1548,71 @@ impl VisitMut for ServerActions { } } ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => { - if named.src.is_some() { - disallowed_export_span = named.span; - } else { - for spec in &mut named.specifiers { - if let ExportSpecifier::Named(ExportNamedSpecifier { - orig: ModuleExportName::Ident(ident), - exported, - .. - }) = spec - { - if let Some(export_name) = exported { - if let ModuleExportName::Ident(Ident { sym, .. }) = - export_name - { - // export { foo as bar } - self.exported_idents.push(( - ident.clone(), - sym.clone(), - self.generate_server_reference_id( - sym.as_ref(), - in_cache_file, - None, - ), - )); - } else if let ModuleExportName::Str(str) = export_name { - // export { foo as "bar" } - self.exported_idents.push(( - ident.clone(), - str.value.clone(), - self.generate_server_reference_id( - str.value.as_ref(), - in_cache_file, - None, - ), - )); + if !named.type_only { + if named.src.is_some() { + if named.specifiers.iter().any(|s| match s { + ExportSpecifier::Namespace(_) | ExportSpecifier::Default(_) => { + true + } + ExportSpecifier::Named(s) => !s.is_type_only, + }) { + disallowed_export_span = named.span; + } + } else { + for spec in &mut named.specifiers { + if let ExportSpecifier::Named(ExportNamedSpecifier { + orig: ModuleExportName::Ident(ident), + exported, + is_type_only, + .. + }) = spec + { + if !*is_type_only { + if let Some(export_name) = exported { + if let ModuleExportName::Ident(Ident { + sym, .. + }) = export_name + { + // export { foo as bar } + self.exported_idents.push(( + ident.clone(), + sym.clone(), + self.generate_server_reference_id( + sym.as_ref(), + in_cache_file, + None, + ), + )); + } else if let ModuleExportName::Str(str) = + export_name + { + // export { foo as "bar" } + self.exported_idents.push(( + ident.clone(), + str.value.clone(), + self.generate_server_reference_id( + str.value.as_ref(), + in_cache_file, + None, + ), + )); + } + } else { + // export { foo } + self.exported_idents.push(( + ident.clone(), + ident.sym.clone(), + self.generate_server_reference_id( + ident.sym.as_ref(), + in_cache_file, + None, + ), + )); + } } } else { - // export { foo } - self.exported_idents.push(( - ident.clone(), - ident.sym.clone(), - self.generate_server_reference_id( - ident.sym.as_ref(), - in_cache_file, - None, - ), - )); + disallowed_export_span = named.span; } - } else { - disallowed_export_span = named.span; } } } @@ -1591,7 +1620,6 @@ impl VisitMut for ServerActions { ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl { decl, span, - .. })) => match decl { DefaultDecl::Fn(f) => { let (is_action_fn, is_cache_fn) = has_body_directive(&f.function.body); @@ -1760,8 +1788,14 @@ impl VisitMut for ServerActions { } } } - ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll { span, .. })) => { - disallowed_export_span = *span; + ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll { + span, + type_only, + .. + })) => { + if !*type_only { + disallowed_export_span = *span; + } } _ => {} } @@ -2029,6 +2063,7 @@ impl VisitMut for ServerActions { } // import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; + // import { cache as $$reactCache__ } from "react"; if self.has_cache && self.config.is_react_server_layer { new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { span: DUMMY_SP, @@ -2048,8 +2083,26 @@ impl VisitMut for ServerActions { phase: Default::default(), }))); - // Make it the first item - new.rotate_right(1); + new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { + span: DUMMY_SP, + specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier { + span: DUMMY_SP, + local: quote_ident!("$$reactCache__").into(), + imported: Some(quote_ident!("cache").into()), + is_type_only: false, + })], + src: Box::new(Str { + span: DUMMY_SP, + value: atom!("react"), + raw: None, + }), + type_only: false, + with: None, + phase: Default::default(), + }))); + + // Make them the first items + new.rotate_right(2); } if (self.has_action || self.has_cache) && self.config.is_react_server_layer { @@ -2359,24 +2412,64 @@ fn retain_names_from_declared_idents( *child_names = retained_names; } -fn wrap_cache_expr(expr: Box, name: &str, id: &str, bound_args_len: usize) -> Box { - // expr -> $$cache__("name", "id", 0, expr) - Box::new(Expr::Call(CallExpr { - span: DUMMY_SP, +fn wrap_cache_expr( + cache_kind: &str, + reference_id: &str, + bound_args_length: usize, + inner_fn: Box, + fn_ident: Option, + original_span: Span, +) -> Box { + let cache_call = CallExpr { + span: original_span, callee: quote_ident!("$$cache__").as_callee(), args: vec![ ExprOrSpread { spread: None, - expr: Box::new(name.into()), + expr: Box::new(cache_kind.into()), + }, + ExprOrSpread { + spread: None, + expr: Box::new(reference_id.into()), + }, + ExprOrSpread { + spread: None, + expr: Box::new(Expr::Lit(Lit::Num(Number { + span: DUMMY_SP, + value: bound_args_length as f64, + raw: None, + }))), }, + inner_fn.as_arg(), ExprOrSpread { spread: None, - expr: Box::new(id.into()), + expr: Box::new(Expr::Ident(private_ident!(DUMMY_SP, "arguments"))), }, - Number::from(bound_args_len).as_arg(), - expr.as_arg(), ], ..Default::default() + }; + + // This wrapper function ensures that we have a user-space call stack frame. + let wrapper_fn = Box::new(Expr::Fn(FnExpr { + ident: fn_ident, + function: Box::new(Function { + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(Box::new(Expr::Call(cache_call))), + })], + ..Default::default() + }), + span: original_span, + ..Default::default() + }), + })); + + Box::new(Expr::Call(CallExpr { + callee: quote_ident!("$$reactCache__").as_callee(), + args: vec![wrapper_fn.as_arg()], + ..Default::default() })) } @@ -2753,7 +2846,7 @@ impl DirectiveVisitor<'_> { }); } else if self.is_allowed_position { if !self.config.use_cache_enabled { - emit_error(ServerActionsErrorKind::UseCacheWithoutExperimentalFlag { + emit_error(ServerActionsErrorKind::UseCacheWithoutCacheComponents { span: *span, directive: value.to_string(), }); @@ -3022,6 +3115,7 @@ impl From for Box { optional, } in value.1.into_iter() { + #[allow(clippy::replace_box)] if is_member { expr = Box::new(Expr::Member(MemberExpr { span: DUMMY_SP, @@ -3191,15 +3285,15 @@ fn emit_error(error_kind: ServerActionsErrorKind) { span, formatdoc! { r#" - Unknown cache kind "{cache_kind}". Please configure a cache handler for this kind in the `experimental.cacheHandlers` object in your Next.js config. + Unknown cache kind "{cache_kind}". Please configure a cache handler for this kind in the `cacheHandlers` object in your Next.js config. "# }, ), - ServerActionsErrorKind::UseCacheWithoutExperimentalFlag { span, directive } => ( + ServerActionsErrorKind::UseCacheWithoutCacheComponents { span, directive } => ( span, formatdoc! { r#" - To use "{directive}", please enable the feature flag `experimental.cacheComponents` in your Next.js config. + To use "{directive}", please enable the feature flag `cacheComponents` in your Next.js config. Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage "# diff --git a/crates/next-custom-transforms/src/transforms/track_dynamic_imports.rs b/crates/next-custom-transforms/src/transforms/track_dynamic_imports.rs index fc2ba0fb441fb..60c8badf7dc5d 100644 --- a/crates/next-custom-transforms/src/transforms/track_dynamic_imports.rs +++ b/crates/next-custom-transforms/src/transforms/track_dynamic_imports.rs @@ -100,7 +100,7 @@ fn make_named_import_esm(args: MakeNamedImportArgs) -> ModuleItem { ); // the import source cannot be parametrized in `quote!()`, so patch it manually let decl = item.as_mut_module_decl().unwrap().as_mut_import().unwrap(); - decl.src = Box::new(source.into()); + *decl.src = source.into(); item } diff --git a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/input.js b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/input.js index 90bb710baa1d3..edde535f6212f 100644 --- a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/input.js +++ b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/input.js @@ -6,7 +6,7 @@ * This is a comment. */ -import { unstable_rootParams } from 'next/server' +import { lang } from 'next/root-params' export default function () { return null diff --git a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/output.js b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/output.js index ce04f4969bcce..25945c27bfb2e 100644 --- a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/output.js +++ b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/output.js @@ -2,7 +2,7 @@ 'use strict'; /** * This is a comment. - */ import { unstable_rootParams } from 'next/server'; + */ import { lang } from 'next/root-params'; export default function() { return null; } diff --git a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/output.stderr b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/output.stderr index 268d0f94149e5..12e2c99bfaafc 100644 --- a/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/output.stderr +++ b/crates/next-custom-transforms/tests/errors/react-server-components/client-graph/root-params/output.stderr @@ -1,9 +1,9 @@ - x You're importing a component that needs "unstable_rootParams". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/ - | building-your-application/rendering/server-components + x You're importing a component that needs "next/root-params". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building- + | your-application/rendering/server-components | | ,-[input.js:9:1] 8 | - 9 | import { unstable_rootParams } from 'next/server' - : ^^^^^^^^^^^^^^^^^^^ + 9 | import { lang } from 'next/root-params' + : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `---- diff --git a/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/cache-components/output.stderr b/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/cache-components/output.stderr index 492c1ced78a27..c1dac890e2473 100644 --- a/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/cache-components/output.stderr +++ b/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/cache-components/output.stderr @@ -1,31 +1,31 @@ - x Route segment config "runtime" is not compatible with `nextConfig.experimental.cacheComponents`. Please remove it. + x Route segment config "runtime" is not compatible with `nextConfig.cacheComponents`. Please remove it. ,-[input.js:1:1] 1 | export const runtime = 'edge' : ^^^^^^^ 2 | export const dynamic = 'force-dynamic' `---- - x Route segment config "dynamic" is not compatible with `nextConfig.experimental.cacheComponents`. Please remove it. + x Route segment config "dynamic" is not compatible with `nextConfig.cacheComponents`. Please remove it. ,-[input.js:2:1] 1 | export const runtime = 'edge' 2 | export const dynamic = 'force-dynamic' : ^^^^^^^ 3 | export const dynamicParams = false `---- - x Route segment config "dynamicParams" is not compatible with `nextConfig.experimental.cacheComponents`. Please remove it. + x Route segment config "dynamicParams" is not compatible with `nextConfig.cacheComponents`. Please remove it. ,-[input.js:3:1] 2 | export const dynamic = 'force-dynamic' 3 | export const dynamicParams = false : ^^^^^^^^^^^^^ 4 | export const fetchCache = 'force-no-store' `---- - x Route segment config "fetchCache" is not compatible with `nextConfig.experimental.cacheComponents`. Please remove it. + x Route segment config "fetchCache" is not compatible with `nextConfig.cacheComponents`. Please remove it. ,-[input.js:4:1] 3 | export const dynamicParams = false 4 | export const fetchCache = 'force-no-store' : ^^^^^^^^^^ 5 | export const revalidate = 1 `---- - x Route segment config "revalidate" is not compatible with `nextConfig.experimental.cacheComponents`. Please remove it. + x Route segment config "revalidate" is not compatible with `nextConfig.cacheComponents`. Please remove it. ,-[input.js:5:1] 4 | export const fetchCache = 'force-no-store' 5 | export const revalidate = 1 diff --git a/crates/next-custom-transforms/tests/errors/server-actions/server-graph/16/output.stderr b/crates/next-custom-transforms/tests/errors/server-actions/server-graph/16/output.stderr index b186314fd53e2..ce422b51b43e3 100644 --- a/crates/next-custom-transforms/tests/errors/server-actions/server-graph/16/output.stderr +++ b/crates/next-custom-transforms/tests/errors/server-actions/server-graph/16/output.stderr @@ -1,4 +1,4 @@ - x Unknown cache kind "x". Please configure a cache handler for this kind in the `experimental.cacheHandlers` object in your Next.js config. + x Unknown cache kind "x". Please configure a cache handler for this kind in the `cacheHandlers` object in your Next.js config. | ,-[input.js:1:1] 1 | 'use cache: x' diff --git a/crates/next-custom-transforms/tests/errors/server-actions/server-graph/17/output.stderr b/crates/next-custom-transforms/tests/errors/server-actions/server-graph/17/output.stderr index 7c7cbd53fb60c..30661c6996d47 100644 --- a/crates/next-custom-transforms/tests/errors/server-actions/server-graph/17/output.stderr +++ b/crates/next-custom-transforms/tests/errors/server-actions/server-graph/17/output.stderr @@ -1,4 +1,4 @@ - x Unknown cache kind "x". Please configure a cache handler for this kind in the `experimental.cacheHandlers` object in your Next.js config. + x Unknown cache kind "x". Please configure a cache handler for this kind in the `cacheHandlers` object in your Next.js config. | ,-[input.js:2:1] 1 | export async function foo() { diff --git a/crates/next-custom-transforms/tests/errors/use-cache-not-allowed/1/output.stderr b/crates/next-custom-transforms/tests/errors/use-cache-not-allowed/1/output.stderr index 16735abf31ad4..7a7edf839fe87 100644 --- a/crates/next-custom-transforms/tests/errors/use-cache-not-allowed/1/output.stderr +++ b/crates/next-custom-transforms/tests/errors/use-cache-not-allowed/1/output.stderr @@ -1,4 +1,4 @@ - x To use "use cache", please enable the feature flag `experimental.cacheComponents` in your Next.js config. + x To use "use cache", please enable the feature flag `cacheComponents` in your Next.js config. | | Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage | diff --git a/crates/next-custom-transforms/tests/errors/use-cache-not-allowed/2/output.stderr b/crates/next-custom-transforms/tests/errors/use-cache-not-allowed/2/output.stderr index 99d20a2dc4b49..4c9998a6f1f8b 100644 --- a/crates/next-custom-transforms/tests/errors/use-cache-not-allowed/2/output.stderr +++ b/crates/next-custom-transforms/tests/errors/use-cache-not-allowed/2/output.stderr @@ -1,4 +1,4 @@ - x To use "use cache: x", please enable the feature flag `experimental.cacheComponents` in your Next.js config. + x To use "use cache: x", please enable the feature flag `cacheComponents` in your Next.js config. | | Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage | diff --git a/crates/next-custom-transforms/tests/fixture/next-font-with-directive/use-cache/output.js b/crates/next-custom-transforms/tests/fixture/next-font-with-directive/use-cache/output.js index 6fd4ffd10ac18..fbc7d67790a2f 100644 --- a/crates/next-custom-transforms/tests/fixture/next-font-with-directive/use-cache/output.js +++ b/crates/next-custom-transforms/tests/fixture/next-font-with-directive/use-cache/output.js @@ -1,10 +1,13 @@ /* __next_internal_action_entry_do_not_use__ {"c0dd5bb6fef67f5ab84327f5164ac2c3111a159337":"$$RSC_SERVER_CACHE_0"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; +import { cache as $$reactCache__ } from "react"; import React from 'react'; import inter from '@next/font/google/target.css?{"path":"app/test.tsx","import":"Inter","arguments":[],"variableName":"inter"}'; -export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "c0dd5bb6fef67f5ab84327f5164ac2c3111a159337", 0, async function Cached({ children }) { - return
{children}
; +export var $$RSC_SERVER_CACHE_0 = $$reactCache__(function Cached() { + return $$cache__("default", "c0dd5bb6fef67f5ab84327f5164ac2c3111a159337", 0, async function Cached({ children }) { + return
{children}
; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_0, "c0dd5bb6fef67f5ab84327f5164ac2c3111a159337", null); Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/next.d.ts b/crates/next-custom-transforms/tests/fixture/server-actions/next.d.ts index befe558c27831..2e19662d526a8 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/next.d.ts +++ b/crates/next-custom-transforms/tests/fixture/server-actions/next.d.ts @@ -44,6 +44,7 @@ declare module 'private-next-rsc-cache-wrapper' { kind: string, id: string, boundArgsLength: number, - fn: TFn - ): TFn + fn: TFn, + argsObj: IArguments + ): Promise } diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/33/output.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/33/output.js index 3c6c43a7775b7..034ece8e19a61 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/33/output.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/33/output.js @@ -1,9 +1,12 @@ /* __next_internal_action_entry_do_not_use__ {"803128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; +import { cache as $$reactCache__ } from "react"; const v = 'world'; -export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function fn() { - return 'hello, ' + v; +export var $$RSC_SERVER_CACHE_0 = $$reactCache__(function fn() { + return $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function fn() { + return 'hello, ' + v; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_0, "803128060c414d59f8552e4788b846c0d2b7f74743", null); Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/34/output.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/34/output.js index 7d387cf83de75..2769b42858ad9 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/34/output.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/34/output.js @@ -1,8 +1,11 @@ /* __next_internal_action_entry_do_not_use__ {"8012a8d21b6362b4cc8f5b15560525095bc48dba80":"$$RSC_SERVER_CACHE_3","803128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0","8069348c79fce073bae2f70f139565a2fda1c74c74":"$$RSC_SERVER_CACHE_2","80951c375b4a6a6e89d67b743ec5808127cfde405d":"$$RSC_SERVER_CACHE_1"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; -export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function() { - return 'foo'; +import { cache as $$reactCache__ } from "react"; +export var $$RSC_SERVER_CACHE_0 = $$reactCache__(function foo() { + return $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function() { + return 'foo'; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_0, "803128060c414d59f8552e4788b846c0d2b7f74743", null); Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { @@ -11,8 +14,10 @@ Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { }); const foo = $$RSC_SERVER_CACHE_0; export { bar }; -export var $$RSC_SERVER_CACHE_1 = $$cache__("default", "80951c375b4a6a6e89d67b743ec5808127cfde405d", 0, async function bar() { - return 'bar'; +export var $$RSC_SERVER_CACHE_1 = $$reactCache__(function bar() { + return $$cache__("default", "80951c375b4a6a6e89d67b743ec5808127cfde405d", 0, async function bar() { + return 'bar'; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_1, "80951c375b4a6a6e89d67b743ec5808127cfde405d", null); Object["defineProperty"]($$RSC_SERVER_CACHE_1, "name", { @@ -24,8 +29,10 @@ var bar = $$RSC_SERVER_CACHE_1; const qux = async function qux() { return 'qux'; }; -export var $$RSC_SERVER_CACHE_2 = $$cache__("default", "8069348c79fce073bae2f70f139565a2fda1c74c74", 0, async function baz() { - return qux() + 'baz'; +export var $$RSC_SERVER_CACHE_2 = $$reactCache__(function baz() { + return $$cache__("default", "8069348c79fce073bae2f70f139565a2fda1c74c74", 0, async function baz() { + return qux() + 'baz'; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_2, "8069348c79fce073bae2f70f139565a2fda1c74c74", null); Object["defineProperty"]($$RSC_SERVER_CACHE_2, "name", { @@ -33,8 +40,10 @@ Object["defineProperty"]($$RSC_SERVER_CACHE_2, "name", { writable: false }); const baz = $$RSC_SERVER_CACHE_2; -export var $$RSC_SERVER_CACHE_3 = $$cache__("default", "8012a8d21b6362b4cc8f5b15560525095bc48dba80", 0, async function() { - return 'quux'; +export var $$RSC_SERVER_CACHE_3 = $$reactCache__(function quux() { + return $$cache__("default", "8012a8d21b6362b4cc8f5b15560525095bc48dba80", 0, async function() { + return 'quux'; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_3, "8012a8d21b6362b4cc8f5b15560525095bc48dba80", null); Object["defineProperty"]($$RSC_SERVER_CACHE_3, "name", { diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/35/output.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/35/output.js index 23c08a193de83..7e482e8c37937 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/35/output.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/35/output.js @@ -1,8 +1,11 @@ /* __next_internal_action_entry_do_not_use__ {"803128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; -export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function() { - return 'data'; +import { cache as $$reactCache__ } from "react"; +export var $$RSC_SERVER_CACHE_0 = $$reactCache__(function my_fn() { + return $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function() { + return 'data'; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_0, "803128060c414d59f8552e4788b846c0d2b7f74743", null); Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/36/output.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/36/output.js index 0640df39823e7..5c30e24ef6d67 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/36/output.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/36/output.js @@ -1,8 +1,11 @@ /* __next_internal_action_entry_do_not_use__ {"8012a8d21b6362b4cc8f5b15560525095bc48dba80":"$$RSC_SERVER_CACHE_3","803128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0","80951c375b4a6a6e89d67b743ec5808127cfde405d":"$$RSC_SERVER_CACHE_1","c069348c79fce073bae2f70f139565a2fda1c74c74":"$$RSC_SERVER_CACHE_2"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; -export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function foo() { - return 'data A'; +import { cache as $$reactCache__ } from "react"; +export var $$RSC_SERVER_CACHE_0 = $$reactCache__(function foo() { + return $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function foo() { + return 'data A'; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_0, "803128060c414d59f8552e4788b846c0d2b7f74743", null); Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { @@ -10,8 +13,10 @@ Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { writable: false }); export var foo = $$RSC_SERVER_CACHE_0; -export var $$RSC_SERVER_CACHE_1 = $$cache__("default", "80951c375b4a6a6e89d67b743ec5808127cfde405d", 0, async function bar() { - return 'data B'; +export var $$RSC_SERVER_CACHE_1 = $$reactCache__(function bar() { + return $$cache__("default", "80951c375b4a6a6e89d67b743ec5808127cfde405d", 0, async function bar() { + return 'data B'; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_1, "80951c375b4a6a6e89d67b743ec5808127cfde405d", null); Object["defineProperty"]($$RSC_SERVER_CACHE_1, "name", { @@ -19,8 +24,10 @@ Object["defineProperty"]($$RSC_SERVER_CACHE_1, "name", { writable: false }); export var bar = $$RSC_SERVER_CACHE_1; -export var $$RSC_SERVER_CACHE_2 = $$cache__("default", "c069348c79fce073bae2f70f139565a2fda1c74c74", 0, async function Cached({ children }) { - return children; +export var $$RSC_SERVER_CACHE_2 = $$reactCache__(function Cached() { + return $$cache__("default", "c069348c79fce073bae2f70f139565a2fda1c74c74", 0, async function Cached({ children }) { + return children; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_2, "c069348c79fce073bae2f70f139565a2fda1c74c74", null); Object["defineProperty"]($$RSC_SERVER_CACHE_2, "name", { @@ -28,8 +35,10 @@ Object["defineProperty"]($$RSC_SERVER_CACHE_2, "name", { writable: false }); export default $$RSC_SERVER_CACHE_2; -export var $$RSC_SERVER_CACHE_3 = $$cache__("default", "8012a8d21b6362b4cc8f5b15560525095bc48dba80", 0, async function baz() { - return 'data C'; +export var $$RSC_SERVER_CACHE_3 = $$reactCache__(function baz() { + return $$cache__("default", "8012a8d21b6362b4cc8f5b15560525095bc48dba80", 0, async function baz() { + return 'data C'; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_3, "8012a8d21b6362b4cc8f5b15560525095bc48dba80", null); Object["defineProperty"]($$RSC_SERVER_CACHE_3, "name", { diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/37/output.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/37/output.js index 30e6223c88ba2..6eb0199d1e85a 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/37/output.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/37/output.js @@ -1,8 +1,11 @@ /* __next_internal_action_entry_do_not_use__ {"803128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; -export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function fn() { - return 'foo'; +import { cache as $$reactCache__ } from "react"; +export var $$RSC_SERVER_CACHE_0 = $$reactCache__(function fn() { + return $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function fn() { + return 'foo'; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_0, "803128060c414d59f8552e4788b846c0d2b7f74743", null); Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/38/output.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/38/output.js index e0de6fb1bda5a..4e7e15e2dc3c6 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/38/output.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/38/output.js @@ -1,8 +1,11 @@ /* __next_internal_action_entry_do_not_use__ {"803128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; -export var $$RSC_SERVER_CACHE_0 = $$cache__("x", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function foo() { - return 'data'; +import { cache as $$reactCache__ } from "react"; +export var $$RSC_SERVER_CACHE_0 = $$reactCache__(function foo() { + return $$cache__("x", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function foo() { + return 'data'; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_0, "803128060c414d59f8552e4788b846c0d2b7f74743", null); Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/39/input.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/39/input.js index f3d9f13465bfa..9d1bf1061be00 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/39/input.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/39/input.js @@ -10,6 +10,6 @@ async function Component({ foo }) { } const data = await fn() - // @ts-expect-error: data is not a valid react child + // @ts-ignore: data is not a valid react child return
{data}
} diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/39/output.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/39/output.js index 3eb7a29401101..b57a82ca6e457 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/39/output.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/39/output.js @@ -1,11 +1,14 @@ /* __next_internal_action_entry_do_not_use__ {"c03128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; -export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "c03128060c414d59f8552e4788b846c0d2b7f74743", 2, async function fn([$$ACTION_ARG_0, $$ACTION_ARG_1]) { - console.log($$ACTION_ARG_0); - return { - foo: $$ACTION_ARG_1 - }; +import { cache as $$reactCache__ } from "react"; +export var $$RSC_SERVER_CACHE_0 = $$reactCache__(function fn() { + return $$cache__("default", "c03128060c414d59f8552e4788b846c0d2b7f74743", 2, async function fn([$$ACTION_ARG_0, $$ACTION_ARG_1]) { + console.log($$ACTION_ARG_0); + return { + foo: $$ACTION_ARG_1 + }; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_0, "c03128060c414d59f8552e4788b846c0d2b7f74743", null); Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { @@ -16,6 +19,6 @@ async function Component({ foo }) { const a = 123; var fn = $$RSC_SERVER_CACHE_0.bind(null, encryptActionBoundArgs("c03128060c414d59f8552e4788b846c0d2b7f74743", a, foo)); const data = await fn(); - // @ts-expect-error: data is not a valid react child + // @ts-ignore: data is not a valid react child return
{data}
; } diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/40/output.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/40/output.js index 8f6dc9e32f95f..abce8c606bc8b 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/40/output.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/40/output.js @@ -1,15 +1,18 @@ /* __next_internal_action_entry_do_not_use__ {"6090b5db271335765a4b0eab01f044b381b5ebd5cd":"$$RSC_SERVER_ACTION_1","e03128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; +import { cache as $$reactCache__ } from "react"; import { Form } from 'components'; -export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "e03128060c414d59f8552e4788b846c0d2b7f74743", 2, async function cache([$$ACTION_ARG_0, $$ACTION_ARG_1], e) { - const f = $$ACTION_ARG_0 + e; - return [ - f, - { - a: $$ACTION_ARG_1 - } - ]; +export var $$RSC_SERVER_CACHE_0 = $$reactCache__(function cache() { + return $$cache__("default", "e03128060c414d59f8552e4788b846c0d2b7f74743", 2, async function cache([$$ACTION_ARG_0, $$ACTION_ARG_1], e) { + const f = $$ACTION_ARG_0 + e; + return [ + f, + { + a: $$ACTION_ARG_1 + } + ]; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_0, "e03128060c414d59f8552e4788b846c0d2b7f74743", null); Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/41/output.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/41/output.js index e52be8748e992..f321824c6d8c8 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/41/output.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/41/output.js @@ -1,6 +1,7 @@ /* __next_internal_action_entry_do_not_use__ {"406a88810ecce4a4e8b59d53b8327d7e98bbf251d7":"$$RSC_SERVER_ACTION_0","c0951c375b4a6a6e89d67b743ec5808127cfde405d":"$$RSC_SERVER_CACHE_1"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; +import { cache as $$reactCache__ } from "react"; export const $$RSC_SERVER_ACTION_0 = async function fn($$ACTION_CLOSURE_BOUND) { var [$$ACTION_ARG_0, $$ACTION_ARG_1] = await decryptActionBoundArgs("406a88810ecce4a4e8b59d53b8327d7e98bbf251d7", $$ACTION_CLOSURE_BOUND); console.log($$ACTION_ARG_0); @@ -9,12 +10,14 @@ export const $$RSC_SERVER_ACTION_0 = async function fn($$ACTION_CLOSURE_BOUND) { }; }; registerServerReference($$RSC_SERVER_ACTION_0, "406a88810ecce4a4e8b59d53b8327d7e98bbf251d7", null); -export var $$RSC_SERVER_CACHE_1 = $$cache__("default", "c0951c375b4a6a6e89d67b743ec5808127cfde405d", 0, async function Component({ foo }) { - const a = 123; - var fn = $$RSC_SERVER_ACTION_0.bind(null, encryptActionBoundArgs("406a88810ecce4a4e8b59d53b8327d7e98bbf251d7", a, foo)); - const data = await fn(); - // @ts-expect-error: data is not a valid react child - return
{data}
; +export var $$RSC_SERVER_CACHE_1 = $$reactCache__(function Component() { + return $$cache__("default", "c0951c375b4a6a6e89d67b743ec5808127cfde405d", 0, async function Component({ foo }) { + const a = 123; + var fn = $$RSC_SERVER_ACTION_0.bind(null, encryptActionBoundArgs("406a88810ecce4a4e8b59d53b8327d7e98bbf251d7", a, foo)); + const data = await fn(); + // @ts-expect-error: data is not a valid react child + return
{data}
; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_1, "c0951c375b4a6a6e89d67b743ec5808127cfde405d", null); Object["defineProperty"]($$RSC_SERVER_CACHE_1, "name", { diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/42/input.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/42/input.js index a5d450374855f..6cd45bb82972e 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/42/input.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/42/input.js @@ -10,6 +10,6 @@ async function Component({ foo }) { } const data = await fn() - // @ts-expect-error: data is not a valid react child + // @ts-ignore: data is not a valid react child return
{data}
} diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/42/output.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/42/output.js index b22cae0e87eac..58b929d549c26 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/42/output.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/42/output.js @@ -1,11 +1,14 @@ /* __next_internal_action_entry_do_not_use__ {"c03128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; -export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "c03128060c414d59f8552e4788b846c0d2b7f74743", 2, async function([$$ACTION_ARG_0, $$ACTION_ARG_1]) { - console.log($$ACTION_ARG_0); - return { - foo: $$ACTION_ARG_1 - }; +import { cache as $$reactCache__ } from "react"; +export var $$RSC_SERVER_CACHE_0 = $$reactCache__(function fn() { + return $$cache__("default", "c03128060c414d59f8552e4788b846c0d2b7f74743", 2, async function([$$ACTION_ARG_0, $$ACTION_ARG_1]) { + console.log($$ACTION_ARG_0); + return { + foo: $$ACTION_ARG_1 + }; + }, arguments); }); registerServerReference($$RSC_SERVER_CACHE_0, "c03128060c414d59f8552e4788b846c0d2b7f74743", null); Object["defineProperty"]($$RSC_SERVER_CACHE_0, "name", { @@ -16,6 +19,6 @@ async function Component({ foo }) { const a = 123; const fn = $$RSC_SERVER_CACHE_0.bind(null, encryptActionBoundArgs("c03128060c414d59f8552e4788b846c0d2b7f74743", a, foo)); const data = await fn(); - // @ts-expect-error: data is not a valid react child + // @ts-ignore: data is not a valid react child return
{data}
; } diff --git a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/43/output.js b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/43/output.js index 44e8130c1e01a..9edbf417ade20 100644 --- a/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/43/output.js +++ b/crates/next-custom-transforms/tests/fixture/server-actions/server-graph/43/output.js @@ -1,6 +1,7 @@ /* __next_internal_action_entry_do_not_use__ {"406a88810ecce4a4e8b59d53b8327d7e98bbf251d7":"$$RSC_SERVER_ACTION_0","e0951c375b4a6a6e89d67b743ec5808127cfde405d":"$$RSC_SERVER_CACHE_1"} */ import { registerServerReference } from "private-next-rsc-server-reference"; import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption"; import { cache as $$cache__ } from "private-next-rsc-cache-wrapper"; +import { cache as $$reactCache__ } from "react"; import { Button } from 'components'; const secret = 'my password is qwerty123'; export const $$RSC_SERVER_ACTION_0 = async function action($$ACTION_CLOSURE_BOUND) { @@ -8,13 +9,15 @@ export const $$RSC_SERVER_ACTION_0 = async function action($$ACTION_CLOSURE_BOUN console.log(secret, $$ACTION_ARG_0); }; registerServerReference($$RSC_SERVER_ACTION_0, "406a88810ecce4a4e8b59d53b8327d7e98bbf251d7", null); -export var $$RSC_SERVER_CACHE_1 = $$cache__("default", "e0951c375b4a6a6e89d67b743ec5808127cfde405d", 0, async function getCachedRandom(x, children) { - return { - x, - y: Math.random(), - z: