From e1f33868e9ea14cee5e3d9c547eec14a2bde256a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Thu, 17 Sep 2026 15:38:41 +0100 Subject: [PATCH 01/18] feat: scaffold TanStack Module Federation plugin --- .changeset/config.json | 11 + .github/workflows/ci.yml | 38 + .github/workflows/pkg-pr-new.yml | 24 + .github/workflows/pr.yml | 21 + .github/workflows/release-pull-request.yml | 46 + .github/workflows/release.yml | 100 + .gitignore | 8 + .npmrc | 2 + .prettierignore | 4 + .prettierrc.json | 6 + LICENSE | 21 + README.md | 53 +- docs/RELEASING.md | 46 + package.json | 26 + packages/tanstack/CHANGELOG.md | 5 + packages/tanstack/LICENSE | 21 + packages/tanstack/README.md | 41 + packages/tanstack/package.json | 71 + packages/tanstack/src/index.ts | 48 + packages/tanstack/tsconfig.json | 22 + packages/tanstack/tsdown.config.ts | 19 + pnpm-lock.yaml | 2367 ++++++++++++++++++++ pnpm-workspace.yaml | 3 + test/release-artifacts.test.mjs | 44 + test/release-contracts.test.mjs | 116 + tsconfig.json | 5 + turbo.json | 19 + 27 files changed, 3186 insertions(+), 1 deletion(-) create mode 100644 .changeset/config.json create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/pkg-pr-new.yml create mode 100644 .github/workflows/pr.yml create mode 100644 .github/workflows/release-pull-request.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 LICENSE create mode 100644 docs/RELEASING.md create mode 100644 package.json create mode 100644 packages/tanstack/CHANGELOG.md create mode 100644 packages/tanstack/LICENSE create mode 100644 packages/tanstack/README.md create mode 100644 packages/tanstack/package.json create mode 100644 packages/tanstack/src/index.ts create mode 100644 packages/tanstack/tsconfig.json create mode 100644 packages/tanstack/tsdown.config.ts create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 test/release-artifacts.test.mjs create mode 100644 test/release-contracts.test.mjs create mode 100644 tsconfig.json create mode 100644 turbo.json diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..dbb2ecb --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [["@module-federation/tanstack"]], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3a5cd73 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + pull_request: + branches: [main] + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + verify: + name: Verify (Node ${{ matrix.node }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node: ["22.18.0", "24", "26"] + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + - uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6 + with: + version: 10.15.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version: ${{ matrix.node }} + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm format:check + - run: pnpm typecheck + - run: pnpm build + - run: pnpm test + - run: pnpm pack:tanstack diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml new file mode 100644 index 0000000..1dc317d --- /dev/null +++ b/.github/workflows/pkg-pr-new.yml @@ -0,0 +1,24 @@ +name: pkg-pr-new + +on: + push: + branches: [main] + pull_request: + +permissions: {} + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + - uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6 + with: + version: 10.15.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm build:package + - run: pnpm dlx pkg-pr-new@0.0.54 publish --compact ./packages/tanstack diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..c0e9964 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,21 @@ +name: Validate PR title + +on: + pull_request: + branches: [main] + +permissions: + pull-requests: read + +jobs: + semantic-pr: + runs-on: ubuntu-latest + steps: + - name: Accept generated release title + if: ${{ startsWith(github.event.pull_request.title, 'Release ') }} + run: echo "Generated release PR title accepted." + + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + if: ${{ !startsWith(github.event.pull_request.title, 'Release ') }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-pull-request.yml b/.github/workflows/release-pull-request.yml new file mode 100644 index 0000000..996f614 --- /dev/null +++ b/.github/workflows/release-pull-request.yml @@ -0,0 +1,46 @@ +name: Release Pull Request + +on: + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: release-pull-request + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Validate release request + env: + REPO_SCOPED_TOKEN: ${{ secrets.REPO_SCOPED_TOKEN }} + run: | + test "$GITHUB_REF" = "refs/heads/main" || { echo "Release PRs are main-only."; exit 1; } + test -n "$REPO_SCOPED_TOKEN" || { echo "REPO_SCOPED_TOKEN is required."; exit 1; } + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + fetch-depth: 10 + token: ${{ secrets.REPO_SCOPED_TOKEN }} + - uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6 + with: + version: 10.15.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile --ignore-scripts + - uses: module-federation/actions@842df3a7740093a5ff44de2b9a890106046b8e32 # v2 + with: + version: latest + versionNumber: auto + type: pull request + tools: changeset + coreNpmName: "@module-federation/tanstack" + env: + GITHUB_TOKEN: ${{ secrets.REPO_SCOPED_TOKEN }} + REPOSITORY: ${{ github.repository }} + REF: ${{ github.ref }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2043550 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,100 @@ +name: Release + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + type: choice + required: true + default: next + options: [latest, next] + branch: + required: true + default: main + +permissions: + contents: read + id-token: write + +concurrency: + group: publish-module-federation-tanstack + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + environment: Publish + steps: + - name: Validate manual target + if: github.event_name == 'workflow_dispatch' + env: + RELEASE_BRANCH: ${{ inputs.branch }} + run: test "$RELEASE_BRANCH" = "main" || { echo "Releases are main-only."; exit 1; } + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + fetch-depth: 0 + ref: ${{ github.event.release.tag_name || inputs.branch }} + - name: Verify source branch + run: | + git fetch --no-tags origin main:refs/remotes/origin/main + git merge-base --is-ancestor HEAD origin/main || { echo "Source is not on main."; exit 1; } + - uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6 + with: + version: 10.15.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version: 24 + cache: pnpm + registry-url: https://registry.npmjs.org/ + - run: pnpm install --frozen-lockfile + - name: Verify release tag + if: github.event_name == 'release' + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + test "${RELEASE_TAG#v}" = "$RELEASE_TAG" || { echo "Tags must not start with v."; exit 1; } + test "$(node -p "require('./packages/tanstack/package.json').version")" = "$RELEASE_TAG" || { echo "Tag/version mismatch."; exit 1; } + - name: Generate preview version + if: github.event_name == 'workflow_dispatch' && inputs.version == 'next' + run: | + pnpm changeset version --snapshot next + SNAPSHOT_VERSION="$(node -p "require('./packages/tanstack/package.json').version")" + BASE_VERSION="${SNAPSHOT_VERSION%%-*}" + npm pkg set version="${BASE_VERSION}-next.${GITHUB_RUN_ID}" --prefix packages/tanstack + - run: pnpm format:check + - run: pnpm typecheck + - run: pnpm build + - run: pnpm test + - run: pnpm pack:tanstack + - name: Compute dist-tag + id: dist + env: + INPUT_VERSION: ${{ inputs.version }} + PRERELEASE: ${{ github.event.release.prerelease || false }} + run: | + TAG=latest + if [ "$INPUT_VERSION" = "next" ] || [ "$PRERELEASE" = "true" ]; then TAG=next; fi + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + - name: Refuse conflicting republish + id: publish-check + env: + PACKAGE: "@module-federation/tanstack" + DIST_TAG: ${{ steps.dist.outputs.tag }} + run: | + VERSION="$(node -p "require('./packages/tanstack/package.json').version")" + EXISTING="$(npm view "$PACKAGE@$VERSION" version --json 2>/dev/null || true)" + EXISTING="${EXISTING#\"}"; EXISTING="${EXISTING%\"}" + if [ "$EXISTING" = "$VERSION" ]; then + CURRENT="$(npm view "$PACKAGE" "dist-tags.$DIST_TAG" --json 2>/dev/null || true)" + CURRENT="${CURRENT#\"}"; CURRENT="${CURRENT%\"}" + test "$CURRENT" = "$VERSION" || { echo "Existing version has conflicting dist-tag."; exit 1; } + echo "publish=false" >> "$GITHUB_OUTPUT" + else + echo "publish=true" >> "$GITHUB_OUTPUT" + fi + - name: Publish to npm + if: steps.publish-check.outputs.publish == 'true' + working-directory: packages/tanstack + run: npm publish --provenance --tag "${{ steps.dist.outputs.tag }}" --access public diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a62c8b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +.turbo/ +*.log +pnpm-debug.log* +coverage/ +*.tgz +.DS_Store diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..90edf5a --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +auto-install-peers=false +prefer-frozen-lockfile=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..b3bc6ae --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +dist +node_modules +pnpm-lock.yaml +.turbo diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..bccee91 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 100 +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b0cebd8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Module Federation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 5ad6652..87b7e89 100644 --- a/README.md +++ b/README.md @@ -1 +1,52 @@ -# tanstack \ No newline at end of file +# @module-federation/tanstack + +Module Federation support for TanStack Start, built on the Vite integration. + +## Install + +```bash +pnpm add @module-federation/tanstack @tanstack/react-start +``` + +## Configure + +The federation wrapper must come before `tanstackStart()` so Module Federation +can normalize TanStack's client and SSR entrypoints: + +```ts +import { tanstackStartModuleFederation } from "@module-federation/tanstack"; +import { tanstackStart } from "@tanstack/react-start/plugin/vite"; +import react from "@vitejs/plugin-react"; +import { nitro } from "nitro/vite"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + tanstackStartModuleFederation({ + name: "host", + remotes: { + remote: { + type: "module", + name: "remote", + entry: "https://example.test/remoteEntry.js", + }, + }, + }), + tanstackStart(), + react(), + nitro(), + ], +}); +``` + +Defaults are `remoteEntry.js`, `manifest: true`, and +`hostInitInjectLocation: "entry"`. React and `react-dom` are shared singleton +defaults; any explicit entries in `shared` take precedence. + +Do not set a global Module Federation `target`. TanStack Start builds client +and SSR environments from one Vite configuration, so the wrapper leaves target +selection to `@module-federation/vite` for each environment. + +The package supports Node 22.18+, 24.11+, and 26+, plus Vite 7/8 and TanStack +Start 1.x. Use Vite 8 when developing SSR hosts that load remotes; Vite 7 +remains supported for production builds. diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..e7538a7 --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,46 @@ +--- +title: Releasing +summary: Release and trusted npm publishing for @module-federation/tanstack. +read_when: + - Preparing a release + - Updating release automation +updated_at: 2026-09-16 +--- + +# Releasing + +Add a Changeset to every user-visible package change: + +```bash +pnpm changeset +``` + +Run the complete local gate before merging: + +```bash +pnpm install --frozen-lockfile +pnpm format:check +pnpm typecheck +pnpm build +pnpm test +pnpm pack:tanstack +``` + +The `Release Pull Request` workflow versions the package and updates its +changelog. After merging that PR, publish from `main` with a GitHub Release +whose tag exactly matches `packages/tanstack/package.json` (without a `v`), or +dispatch `Release` with `version=latest`. Preview dispatches use `version=next` +and publish to npm's `next` dist-tag. + +GitHub prerelease tags must match the prerelease version already committed in +`packages/tanstack/package.json`. The workflow publishes that exact version to +the `next` dist-tag. + +The publish workflow reruns the gate, checks that the source is reachable from +`main`, uses npm provenance, and refuses to republish an existing version under +a different dist-tag. Configure npm trusted publishing for repository +`module-federation/tanstack`, workflow `release.yml`, environment `Publish`. + +There are no committed app fixtures yet. The next integration matrix should +cover TanStack Start client and SSR builds on Vite 7 and 8, host/remote module +entries, manifest output, React singleton identity, and Nitro externalization. diff --git a/package.json b/package.json new file mode 100644 index 0000000..7ef2a35 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "mf-tanstack", + "private": true, + "packageManager": "pnpm@10.15.0", + "engines": { + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" + }, + "scripts": { + "build": "turbo run build", + "build:package": "pnpm --filter @module-federation/tanstack build", + "dev": "turbo run dev", + "test": "pnpm build:package && node --test --test-concurrency=1 test/*.test.mjs", + "typecheck": "turbo run typecheck", + "format": "prettier --write .", + "format:check": "prettier --check .", + "pack:tanstack": "pnpm --filter @module-federation/tanstack pack:dry", + "changeset": "changeset", + "changeset:status": "changeset status" + }, + "devDependencies": { + "@changesets/cli": "3.0.3", + "prettier": "3.9.7", + "turbo": "2.10.13", + "typescript": "6.0.3" + } +} diff --git a/packages/tanstack/CHANGELOG.md b/packages/tanstack/CHANGELOG.md new file mode 100644 index 0000000..ff942c3 --- /dev/null +++ b/packages/tanstack/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.1.0 + +- Initial TanStack Start integration for Module Federation. diff --git a/packages/tanstack/LICENSE b/packages/tanstack/LICENSE new file mode 100644 index 0000000..b0cebd8 --- /dev/null +++ b/packages/tanstack/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Module Federation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/tanstack/README.md b/packages/tanstack/README.md new file mode 100644 index 0000000..dad41f7 --- /dev/null +++ b/packages/tanstack/README.md @@ -0,0 +1,41 @@ +# @module-federation/tanstack + +TanStack Start integration for Module Federation. It delegates to +`@module-federation/vite` and supplies defaults needed by TanStack's client and +SSR environments. + +```ts +import { tanstackStartModuleFederation } from "@module-federation/tanstack"; +import { tanstackStart } from "@tanstack/react-start/plugin/vite"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + tanstackStartModuleFederation({ + name: "host", + remotes: { + remote: { + type: "module", + name: "remote", + entry: "https://example.test/remoteEntry.js", + }, + }, + }), + tanstackStart(), + ], +}); +``` + +Place the federation wrapper before `tanstackStart()`, followed by React and +Nitro plugins. Defaults: `filename: "remoteEntry.js"`, `manifest: true`, +`hostInitInjectLocation: "entry"`, and singleton `react`/`react-dom` shared +entries. User-provided `shared` entries override those defaults. + +The wrapper intentionally omits the global Module Federation `target` option. +TanStack Start's client and SSR environments need `@module-federation/vite` to +select `web` and `node` independently. + +Production builds support Vite 7 and 8. Vite 8 is required for SSR remote +loading during development. + +Supported Node releases are 22.18+, 24.11+, and 26+. diff --git a/packages/tanstack/package.json b/packages/tanstack/package.json new file mode 100644 index 0000000..4a637b3 --- /dev/null +++ b/packages/tanstack/package.json @@ -0,0 +1,71 @@ +{ + "name": "@module-federation/tanstack", + "version": "0.1.0", + "type": "module", + "description": "TanStack Start integration for Module Federation", + "license": "MIT", + "sideEffects": false, + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs", + "default": "./dist/index.mjs" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist/**/*", + "README.md", + "CHANGELOG.md", + "LICENSE" + ], + "publishConfig": { + "access": "public", + "provenance": true + }, + "repository": { + "type": "git", + "url": "git+https://github.com/module-federation/tanstack.git", + "directory": "packages/tanstack" + }, + "bugs": { + "url": "https://github.com/module-federation/tanstack/issues" + }, + "homepage": "https://github.com/module-federation/tanstack#readme", + "keywords": [ + "tanstack", + "tanstack-start", + "module-federation", + "microfrontend" + ], + "engines": { + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" + }, + "scripts": { + "build": "tsdown", + "dev": "tsdown --watch --no-clean", + "pack:dry": "npm pack --dry-run", + "prepack": "pnpm run build", + "prepublishOnly": "pnpm run typecheck && pnpm run build", + "typecheck": "tsc -p tsconfig.json --noEmit" + }, + "dependencies": { + "@module-federation/vite": "1.22.0" + }, + "peerDependencies": { + "@tanstack/react-start": "^1.0.0", + "vite": "^7.0.0 || ^8.0.0" + }, + "devDependencies": { + "@tanstack/react-start": "1.168.54", + "@types/node": "24.10.1", + "react": "19.3.0", + "react-dom": "19.3.0", + "tsdown": "0.23.0", + "typescript": "6.0.3", + "vite": "8.3.0" + } +} diff --git a/packages/tanstack/src/index.ts b/packages/tanstack/src/index.ts new file mode 100644 index 0000000..393d29b --- /dev/null +++ b/packages/tanstack/src/index.ts @@ -0,0 +1,48 @@ +import { federation, type ModuleFederationOptions } from "@module-federation/vite"; + +export type TanStackStartModuleFederationOptions = Omit; + +const defaultShared = { + react: { singleton: true }, + "react-dom": { singleton: true }, +} as const; + +type SharedOptions = NonNullable; +type SharedRecord = Exclude; + +function resolveShared(shared: SharedOptions | undefined): SharedRecord { + if (!Array.isArray(shared)) { + return { + ...defaultShared, + ...shared, + }; + } + + const additionalShared = Object.fromEntries( + shared + .filter((packageName) => !Object.hasOwn(defaultShared, packageName)) + .map((packageName) => [packageName, {}]), + ); + + return { + ...defaultShared, + ...additionalShared, + }; +} + +/** Creates one Module Federation plugin set for TanStack Start's client and SSR environments. */ +export function tanstackStartModuleFederation(options: TanStackStartModuleFederationOptions) { + const { + shared, + target: _environmentSpecificTarget, + ...configured + } = options as ModuleFederationOptions; + + return federation({ + ...configured, + filename: configured.filename ?? "remoteEntry.js", + manifest: configured.manifest ?? true, + hostInitInjectLocation: configured.hostInitInjectLocation ?? "entry", + shared: resolveShared(shared), + }); +} diff --git a/packages/tanstack/tsconfig.json b/packages/tanstack/tsconfig.json new file mode 100644 index 0000000..ae76cb7 --- /dev/null +++ b/packages/tanstack/tsconfig.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "allowImportingTsExtensions": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true, + "verbatimModuleSyntax": true + }, + "include": ["src/**/*.ts"], + "exclude": ["dist"] +} diff --git a/packages/tanstack/tsdown.config.ts b/packages/tanstack/tsdown.config.ts new file mode 100644 index 0000000..a6d6a0b --- /dev/null +++ b/packages/tanstack/tsdown.config.ts @@ -0,0 +1,19 @@ +import { builtinModules } from "node:module"; +import { defineConfig } from "tsdown"; + +const external = [ + ...builtinModules, + ...builtinModules.map((name) => `node:${name}`), + "@module-federation/vite", + "@module-federation/vite/*", +]; + +export default defineConfig({ + clean: true, + deps: { neverBundle: external }, + dts: { sourcemap: true }, + entry: { index: "./src/index.ts" }, + format: ["esm"], + outDir: "dist", + sourcemap: true, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..1e2b0d4 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2367 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: false + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@changesets/cli': + specifier: 3.0.3 + version: 3.0.3 + prettier: + specifier: 3.9.7 + version: 3.9.7 + turbo: + specifier: 2.10.13 + version: 2.10.13 + typescript: + specifier: 6.0.3 + version: 6.0.3 + + packages/tanstack: + dependencies: + '@module-federation/vite': + specifier: 1.22.0 + version: 1.22.0(typescript@6.0.3)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + devDependencies: + '@tanstack/react-start': + specifier: 1.168.54 + version: 1.168.54(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@types/node': + specifier: 24.10.1 + version: 24.10.1 + react: + specifier: 19.3.0 + version: 19.3.0 + react-dom: + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) + tsdown: + specifier: 0.23.0 + version: 0.23.0(typescript@6.0.3) + typescript: + specifier: 6.0.3 + version: 6.0.3 + vite: + specifier: 8.3.0 + version: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@changesets/apply-release-plan@8.1.1': + resolution: {integrity: sha512-nk2Hbq3M9NeHMdaIRBDvawKrQJzXB9sGyBUE5yWnIKtvdcNCPj75zBekJYr0gTT2ecJH3eFeTrc8eisPImcv0Q==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/assemble-release-plan@7.0.0': + resolution: {integrity: sha512-oEW8BxdA604kGGtDSCiHr5w9Tv4UWe9I2k61IBNZzCOE1kbYaJj4v+lFQNgcEZFkUc2pV/+hASErGDvpJOZCTg==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/changelog-git@1.0.0': + resolution: {integrity: sha512-3Dst2Ime2Op5nd4XmWJLPIgp11ZFqJqSkVug9izK6TDcIV4YlhPS4ECbEVR+eGI0bk0r1ItogD4j2Oli87bJrA==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/cli@3.0.3': + resolution: {integrity: sha512-L1i67bMfkK6iC/kDejuNO4pFgTRcWIObsD6Qof2woP1I4M8tj17DGlcpRaTzTv1DYSP5EMwWdEtR8X0Pj5Jg3Q==} + engines: {node: ^22.11 || ^24 || >=26, npm: '>=10.9.0', pnpm: '>=10.0.0', yarn: '>=4.5.2'} + hasBin: true + + '@changesets/config@4.0.1': + resolution: {integrity: sha512-PxpxSQLQSLDjceAgRq+4FWlU+HflDIw03mmPVipxoQx9xrXFUjmqTy3SE+n/2t+DqPdpPiLWVr5NRiLZvjyLwg==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/errors@1.0.0': + resolution: {integrity: sha512-ElN/mEzn6zmETgjwf5MclCMa9ef59sAR0lfO8VSYIsiRvbC2FbLB/92EoYw10Sl0kGixxHFiJZUSv7dA+YpR8g==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/format@0.1.2': + resolution: {integrity: sha512-Caez5XtNXCFS/G5bwyav3wuXL0tMxVd2ZGbaumWbzN08tyzO21asCw7JZhNtVsAZDCvDRUzZN+Iit9SyRITSYA==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/get-dependents-graph@3.0.0': + resolution: {integrity: sha512-ji/t5wFA1zREKXRUePE6Qi+Qu2UgxCeSSGQrphezwvQZrp49B7sJ+8+wvM0tA7zPeSxYKCojDy3WWgrl+s+awg==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/git@4.0.1': + resolution: {integrity: sha512-6vWpIAC4LkpmlqaIu37ViT5enyd9+uBwAiGqCGhASvkSHBgx4PrtTGXWTMFYpDmZbjgyonyVgMciPrW4MqtJsA==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/parse@1.0.0': + resolution: {integrity: sha512-P0iaMb9p9CRYZiTgAllEIF9AUMQHIy1G72tKlcIqJp61icZSsKQNiOPdxAMZG8m/DvZwt/oz5xEbTpike//dWg==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/pre@3.0.0': + resolution: {integrity: sha512-Zm/6YliV/a2oeWTqHJf6KxLrQwgcK1i/BRDl2m0EKZvbnxV5fG9QRhwJJGshjsZTUTS6dkfURQ2K6aAvgNw/3Q==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/read@1.0.1': + resolution: {integrity: sha512-nzvSC6RcTiWf/dsuwZFXpLzGGsRHYCL68U3uHV7srsulU93aVWY7u2GEJiDZ+4GIIElfaW5EqtKC0UHroY+LTQ==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/should-skip-package@1.0.0': + resolution: {integrity: sha512-pwqoJmbONn1XgXmZXPEExgAaT+HdZLjALFTDgIm+PnS5KeO2nLtzA2/Q+4aMFY14kFMuXKG30MObhvDzWgzDgg==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/types@7.0.0': + resolution: {integrity: sha512-c5GoiQyt3pxiXjrWSNoP8/GRf4kG+VnKzovx1OQM8dYYALlSwgedmkPmJ+ZqGxqwg9D3Bkj85Uo4KLd5BN3A0w==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/write@1.0.1': + resolution: {integrity: sha512-q/ThtP9gcnEP6xlv7LrY26C2mHqdGBti/MmOVngt/M/oIGYkssmQGxPK9WzBNt2juVcH/vml2WQ+ra8LXYOTaA==} + engines: {node: ^22.11 || ^24 || >=26} + + '@clack/core@1.5.1': + resolution: {integrity: sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==} + engines: {node: '>= 20.12.0'} + + '@clack/prompts@1.8.1': + resolution: {integrity: sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==} + engines: {node: '>= 20.12.0'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@manypkg/find-root@3.1.0': + resolution: {integrity: sha512-BcSqCyKhBVZ5YkSzOiheMCV41kqAFptW6xGqYSTjkVTl9XQpr+pqHhwgGCOHQtjDCv7Is6EFyA14Sm5GVbVABA==} + engines: {node: '>=20.0.0'} + + '@manypkg/get-packages@3.1.0': + resolution: {integrity: sha512-0TbBVyvPrP7xGYBI/cP8UP+yl/z+HtbTttAD7FMAJgn/kXOTwh5/60TsqP9ZYY710forNfyV0N8P/IE/ujGZJg==} + engines: {node: '>=20.0.0'} + + '@manypkg/tools@2.1.2': + resolution: {integrity: sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ==} + engines: {node: '>=20.0.0'} + + '@module-federation/dts-plugin@2.9.0': + resolution: {integrity: sha512-uMGqEG/p9odG2BVr7WRbBe2OgrvzBd3LPzcp5WP+bm3djBdUJ4PZ3ozqKp6qpy+NFnlh6vnM815Xn6AXkKy1Vw==} + peerDependencies: + typescript: ^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + vue-tsc: '>=1.0.24' + peerDependenciesMeta: + vue-tsc: + optional: true + + '@module-federation/error-codes@2.9.0': + resolution: {integrity: sha512-IGpd+VRlji3NyGOGGTsk7YEmPRKcDoBK4YHqIuP+OQwyb2YhHCUHO2vW9RXeQxCuKwi+c6xkTweRYG+Umy+0Zw==} + + '@module-federation/managers@2.9.0': + resolution: {integrity: sha512-8KhB2PF4g+M0hGaethU1H4GVWabLn5sF5TvnM6VxgAcDL4TfLY8aC/YobAXHG/SV0jpU5lsGe6s6xy6ccV0MQw==} + + '@module-federation/runtime-core@2.9.0': + resolution: {integrity: sha512-dLykRYfpbEJBTdk2NlbNoVVTB196O3qujawTBguLABJkPCWETJNk60NR1yZO34eI+DTH+eGwHU3m7FddAWnbnw==} + + '@module-federation/runtime@2.9.0': + resolution: {integrity: sha512-3cyAav0hWP+dNvB7qrcK9CKxQn+JvkbRvLtZz3zS2g0EyxtDFDjgbHY0Afcf7oHf8IHhKeEdzb/3Kjr1Pjmr7A==} + + '@module-federation/sdk@2.9.0': + resolution: {integrity: sha512-IMjObgBGQTXd33jTTCYcxvz8iOQGLsZ2QIPOj90rDxuBtJsN4WJwYyXqligrhY/e5p/BipUPdbIgKmWL7zFhTw==} + + '@module-federation/third-party-dts-extractor@2.9.0': + resolution: {integrity: sha512-R1Xuqnqzw6wQxWV3yU9fX1FydXAeZF2TNVFAVo1N1oLBA2j5y7aUO/Fc3VSNgd9/gxMzJgTVBdA+nMiD2SFCgg==} + + '@module-federation/vite@1.22.0': + resolution: {integrity: sha512-113KQtubRIg2ezPs8R2+q+sTKY/8IEePqaGsNAdeSHvQvtP8TUW0YvBvpNFgiPz/7VG0FhI7XYGraBekBZUzMA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@oozcitak/dom@2.0.2': + resolution: {integrity: sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==} + engines: {node: '>=20.0'} + + '@oozcitak/infra@2.0.2': + resolution: {integrity: sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA==} + engines: {node: '>=20.0'} + + '@oozcitak/url@3.0.0': + resolution: {integrity: sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ==} + engines: {node: '>=20.0'} + + '@oozcitak/util@10.0.0': + resolution: {integrity: sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==} + engines: {node: '>=20.0'} + + '@oxc-project/types@0.150.0': + resolution: {integrity: sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==} + + '@pnpm/deps.graph-sequencer@1100.0.1': + resolution: {integrity: sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A==} + engines: {node: '>=22.13'} + + '@quansync/fs@1.1.0': + resolution: {integrity: sha512-qAPG/t3HqML1TlN7sY/pTbEjzFVAKsMjNNMGheyDosro+kT4iw2KCUoHcVdmliwWjorm4elZbgNQyU2eD97eDg==} + + '@rolldown/binding-android-arm-eabi@1.2.9': + resolution: {integrity: sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.9': + resolution: {integrity: sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.9': + resolution: {integrity: sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.9': + resolution: {integrity: sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.9': + resolution: {integrity: sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.9': + resolution: {integrity: sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.9': + resolution: {integrity: sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.2.9': + resolution: {integrity: sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.2.9': + resolution: {integrity: sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.2.9': + resolution: {integrity: sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.2.9': + resolution: {integrity: sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.2.9': + resolution: {integrity: sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.2.9': + resolution: {integrity: sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.9': + resolution: {integrity: sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.9': + resolution: {integrity: sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@tanstack/history@1.162.4': + resolution: {integrity: sha512-utTS5L2OkeYUzXGohL1Z8sefu1GLNOJcxe8Hd6iIdc/Xo1K1nDB2JEp4iSFhvYh33xKC9V91TxrS8qfrpoKobQ==} + engines: {node: '>=20.19'} + + '@tanstack/react-router@1.170.36': + resolution: {integrity: sha512-AbgcI/xO8AJY1r6y5tF7TZyHH0VKm0XcfY3QVIVCrHq8k04Hkj1N4nfLL+1xCGC3VnMI3AYGAVWkatnqE2dxAQ==} + engines: {node: '>=20.19'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-start-client@1.168.34': + resolution: {integrity: sha512-rNwogcDo5A9xeKmGHIocI50nlqtlEr5lcgbcdW9gCtDEloXALdwFtXriEj3kHZe7BLvn9FItZESZcC3xo4/71w==} + engines: {node: '>=22.12.0'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-start-rsc@0.1.53': + resolution: {integrity: sha512-Hzwpd74HyvTFU4rEY9PpbUaYJ7BA3deCg0p+Dyj3NTFsTnULD+/Eba87u1QPl5UEVsKWyIG9J7g4J6JZpxFWSg==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@rspack/core': '>=2.0.0-0' + '@vitejs/plugin-rsc': '>=0.5.30' + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + react-server-dom-rspack: '>=0.0.2' + peerDependenciesMeta: + '@rspack/core': + optional: true + '@vitejs/plugin-rsc': + optional: true + react-server-dom-rspack: + optional: true + + '@tanstack/react-start-server@1.167.41': + resolution: {integrity: sha512-ng4M05+vPug8rJH4OzF6u45JRSn+MThpjGCzkXmBgW3w/6TRkzWHQMO5/tBzpERJhUHp+uk178ZfFJVw0SWYpA==} + engines: {node: '>=22.12.0'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-start@1.168.54': + resolution: {integrity: sha512-x0GWvFm1e4WtgETxDE0xVWa39HdEnoinKQ9SU8pvO187SDsSpudYuhr8StfWdLE5iRhziGchLqlPl6UccUZ46w==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@rsbuild/core': ^2.0.0 + '@vitejs/plugin-rsc': '*' + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + vite: '>=7.0.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@vitejs/plugin-rsc': + optional: true + vite: + optional: true + + '@tanstack/react-store@0.11.1': + resolution: {integrity: sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/router-core@1.171.30': + resolution: {integrity: sha512-wtS1yCAb/k5CJBauvrCyqf2n4/kRc3aiz81tFBJ1QXH4Utsu81xFTMq15AdmNWgWmt2UstUSMfWi4Ua7ph2F+w==} + engines: {node: '>=20.19'} + + '@tanstack/router-generator@1.167.36': + resolution: {integrity: sha512-9Sjm1j8fkurviVIcbAXVdoyqAT8tlurY1tD9Na9Y59/BW5uWT5/5U+eU4Nv/d5grBKyosiJCP7/pcADvstnEgg==} + engines: {node: '>=20.19'} + + '@tanstack/router-plugin@1.168.38': + resolution: {integrity: sha512-0SWwASONOiy7biNBvzdmfhKWwhxa+oJUZHIAdsAEAK+H1sftLWcE3b4sil5omjIIAlPmBJLk2JuUnfBWrn0iIg==} + engines: {node: '>=20.19'} + peerDependencies: + '@rsbuild/core': '>=1.0.2 || ^2.0.0' + '@tanstack/react-router': ^1.170.36 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' + vite-plugin-solid: ^2.11.10 || ^3.0.0-0 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.162.3': + resolution: {integrity: sha512-Icb0xGuG1+54IV0WMLRcc3ErTx2HeJWyPG661FkQ8UT6guoBq1FJjFRqlG/JS0xi4mFFkBhvwO7tbLa32f3yxA==} + engines: {node: '>=20.19'} + + '@tanstack/start-client-core@1.170.30': + resolution: {integrity: sha512-eyNrOJumsx68Od+//eVWgQdQAdleY9bzbJ/N9/q5x/pqHdtaSxHYqMERPAmB7XcYrMoF0Bo9LcP1TfXqvqk69g==} + engines: {node: '>=22.12.0'} + + '@tanstack/start-fn-stubs@1.162.0': + resolution: {integrity: sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==} + engines: {node: '>=22.12.0'} + + '@tanstack/start-plugin-core@1.171.44': + resolution: {integrity: sha512-xSUlXNpA/k1cNHoIcPFqBw8kehQelUn7qN8dCVMvNEjSRA63jWbFVZNrkMXAKsY0sTMM+i4qoOmCleKT1ha7nA==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@rsbuild/core': ^2.0.0 + vite: '>=7.0.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + vite: + optional: true + + '@tanstack/start-server-core@1.169.35': + resolution: {integrity: sha512-xy6DB1EFznF9d7r+lqj35+Gz0alreCh8tO9+yPepANab+WZagQEDjFwHSY9vrv3lzBWbMlRrkkGG1OZWOSL48w==} + engines: {node: '>=22.12.0'} + + '@tanstack/start-storage-context@1.167.32': + resolution: {integrity: sha512-Di2mLQPXy1oPFU0o9jb0oMA/XXX9sJc+FEMJo3PMiSyB6cPnbAjsR1VgvavfsCBMi7kOE+ctdK+ELDX2MwKvIA==} + engines: {node: '>=22.12.0'} + + '@tanstack/store@0.11.1': + resolution: {integrity: sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA==} + + '@tanstack/virtual-file-routes@1.162.0': + resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==} + engines: {node: '>=20.19'} + + '@turbo/darwin-64@2.10.13': + resolution: {integrity: sha512-m5DBcpkxmcKpEJHHFUfqf9GLUjSmw3cDnBJM9nghPW3kjCNb5OyNKmzG7/yT5x5AwzOO+HMwKN+UI4iVKL87YA==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.10.13': + resolution: {integrity: sha512-vfKPvSuoY8BJGj8M8yN+a7aq1g+CejbEacDSDlJLDkSbPN3J/U1DbYAmPG8Ype5XvImI0SgfvUhIorNnFj67zw==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.10.13': + resolution: {integrity: sha512-Zp0N5M6j9iEjJtFLTA2L+52TLuYy0VbXT3zLZzfMeqqdKSUJ7pDo2pA1NhqjoQcF2dIsktZ8xZRW3pGI6T81fw==} + cpu: [x64] + os: [android, linux] + + '@turbo/linux-arm64@2.10.13': + resolution: {integrity: sha512-M8XcfWdS2D4EP3wzd5ehUyCANucMIOsRE6gZuV0Mr2NIn+FpsUjXFCh7r9HY7GnNcjXH4tUnT76OsCDg2c0Fng==} + cpu: [arm64] + os: [android, linux] + + '@turbo/windows-64@2.10.13': + resolution: {integrity: sha512-QFd3KBMpBBCc31TbxMnxgdBivie8lxJXV8EqmanH8fg8l2e1BdCLWpZJbxk48VNvcF4LHUnwO0FJOq+veKN1cw==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.10.13': + resolution: {integrity: sha512-rM+cFO1P8iAHXdasOdMyK9nDk0UGII7VEFLIA8+jwv/+rh48pnVBbF2OSjJDBOw57pxNLIOEDA0JyKcarTBcIw==} + cpu: [arm64] + os: [win32] + + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + + '@yuku-codegen/binding-android-arm64@0.10.2': + resolution: {integrity: sha512-Oc2KInVkPfUjEB4PeV6X0NvIoyYTzDe/WmqFrTwBqKs6YKnz4A7XwWQvJTb7M0rLz9a6WgpUUiiS5QKaCi4O/g==} + cpu: [arm64] + os: [android] + + '@yuku-codegen/binding-darwin-arm64@0.10.2': + resolution: {integrity: sha512-3H7eNPIHJndUJXZ4QUGBPq1zC6RGcUZfm7bymJes+/N7wTVWUn1bxZ9JcozYHpkWQNm9f23G8YWbaHD9aYH72A==} + cpu: [arm64] + os: [darwin] + + '@yuku-codegen/binding-darwin-x64@0.10.2': + resolution: {integrity: sha512-AJOUpR2s3LF9AWiiub/Uyc/UoXNTWcq8hK3cVI6fUSXtwh34dG21J6hRuNlO6JVJFTo4o3ScVvZOrh/YFfUEAA==} + cpu: [x64] + os: [darwin] + + '@yuku-codegen/binding-freebsd-x64@0.10.2': + resolution: {integrity: sha512-ms7DcZu87u5CiK/wMwvpKAvAmtaqKjCQIXNweMLypG6qbuiaOMQf1jpbB5+j46xBcgCovo8TQMcv0bCQLKIL9w==} + cpu: [x64] + os: [freebsd] + + '@yuku-codegen/binding-linux-arm-gnu@0.10.2': + resolution: {integrity: sha512-xJDpYAsKV5+eaiBhTYl05fvT6sst4PsCeuIFMRu9b0/WCSrNQPfCDtAQ5/MS6xNpsdujdZEPR+gmfWk3ZG5i3A==} + cpu: [arm] + os: [linux] + + '@yuku-codegen/binding-linux-arm-musl@0.10.2': + resolution: {integrity: sha512-qfTkgd7AEx61l4K9VtXWCGTxc/fmXJ4ZheDz3i+/AAJUtg4sa0bPrc0VBxPggB/rayR8Z3+JfrBItuyanBJJ9Q==} + cpu: [arm] + os: [linux] + + '@yuku-codegen/binding-linux-arm64-gnu@0.10.2': + resolution: {integrity: sha512-4e6Mifm/4UdjtU3D8mATIrvgP+xEiH8xtQOjd0zpd72XKWT7ug3sdyhq2utkkZFP6BvYp7SgZrI+aR4VeIOHdw==} + cpu: [arm64] + os: [linux] + + '@yuku-codegen/binding-linux-arm64-musl@0.10.2': + resolution: {integrity: sha512-RdsJrUfDYFVV3JOmWFUWwSu31fa1APn12xDeIKxgl/YWxrabwtxsDBNjF2561c7tmcbiewdCUvngqRs8AyGVLA==} + cpu: [arm64] + os: [linux] + + '@yuku-codegen/binding-linux-x64-gnu@0.10.2': + resolution: {integrity: sha512-mVzWimEPPreaPyXIUvxsHoJzvO7ckZ5j0LgQFHz04zQIRwt9A9T2hA7K5/jYjJKkMxWG/U2VjhGNwVhtyU+uqg==} + cpu: [x64] + os: [linux] + + '@yuku-codegen/binding-linux-x64-musl@0.10.2': + resolution: {integrity: sha512-Y77fyISurmr2mvO1yeq3u+x/WJbACgPR4w+lzEVx30ViCxZzIGrZPRN1yEdZ9xUNPNsIO5thBHIdRNG+UGUG+Q==} + cpu: [x64] + os: [linux] + + '@yuku-codegen/binding-win32-arm64@0.10.2': + resolution: {integrity: sha512-1+tGLyG0u5YYy0lev4ck7/0sd8xJ9SZde0dLut7qLDPZV5WVTV8x6OxNXUne/PGuHZyO1vK+txPQzzHOyXHGSw==} + cpu: [arm64] + os: [win32] + + '@yuku-codegen/binding-win32-x64@0.10.2': + resolution: {integrity: sha512-8a2SExRohRbwC0MY4VpOF0/RSckrJ2ASZqAor5/RYSJJaeadR93n10gZzcKT6pY9ukiSOIZCRKVRD2tH73T0qA==} + cpu: [x64] + os: [win32] + + '@yuku-parser/binding-android-arm64@0.10.2': + resolution: {integrity: sha512-2VPBU9fRGRAQ2xPAvghnec3oou5Nrxm2Bezhf+13UMspAxQSkF/32r+ySKXSwIPA5/RivumDJDxAxc301Sgicw==} + cpu: [arm64] + os: [android] + + '@yuku-parser/binding-darwin-arm64@0.10.2': + resolution: {integrity: sha512-LD+PMZE51tCYTOss5HBkm3/AE39MvcMDBfWJx7A4yDcjfNbAQDnHZKtzSOuqrswx+TQHY0ws5xn6+fWOwtmfBA==} + cpu: [arm64] + os: [darwin] + + '@yuku-parser/binding-darwin-x64@0.10.2': + resolution: {integrity: sha512-mmZ8cND+AoIIMRERyMinlg5ApHxP23B9jH2B5wT7T+dliPa9rubLxneB/SUjFwyUjGaFnB5G7t4YvpfbO5zbkQ==} + cpu: [x64] + os: [darwin] + + '@yuku-parser/binding-freebsd-x64@0.10.2': + resolution: {integrity: sha512-gVIjaaIddbRfAhHlC8N809wQWml7mxfSVnzaLtzGXObTeFTPAg/YVnQXt1UOhPM1ah5eG/8Y0RUlNpB4GVe7eQ==} + cpu: [x64] + os: [freebsd] + + '@yuku-parser/binding-linux-arm-gnu@0.10.2': + resolution: {integrity: sha512-q/XPPQQAPdlw05aPj30ygBhekmQryGOwxVraBgApjKK8yY1kNQgqq6XCYLF1WHSee+lhxclrTO7W0/bt7dR1GA==} + cpu: [arm] + os: [linux] + + '@yuku-parser/binding-linux-arm-musl@0.10.2': + resolution: {integrity: sha512-A+Cb0I1hFF4wilTQpWs79k1aNnj4B2tkrHx3zsuUNF9BtVY2zPZ4yeQEM/zjXrS/qJlNMZVK9NrWJjwdpnTrfg==} + cpu: [arm] + os: [linux] + + '@yuku-parser/binding-linux-arm64-gnu@0.10.2': + resolution: {integrity: sha512-aGNSzqIqqphFwAdIFwVvKIyXD1Iy3CxrEJVIZAT87Ecyi4vCmmDD2v2l9h+Gd3/wSy3JZlOI792LJbKAjyy9rQ==} + cpu: [arm64] + os: [linux] + + '@yuku-parser/binding-linux-arm64-musl@0.10.2': + resolution: {integrity: sha512-Ddl1sF0rtuCXyHGSOV6dcmdx+ETv1iD+IVFqQuOjzkJZWclxJxdBWX6ZJMRtTiGqGUTbqLEKiTXxpR/Bvqk+2A==} + cpu: [arm64] + os: [linux] + + '@yuku-parser/binding-linux-x64-gnu@0.10.2': + resolution: {integrity: sha512-/nlcpR6IF5U+0m5L9wvAIXeQa2DT+BXuvqKDsTcOTlcc0B4dHNyqE5hTXsS4hAyimpy2ZK8A1zMNF5hTrjg2cg==} + cpu: [x64] + os: [linux] + + '@yuku-parser/binding-linux-x64-musl@0.10.2': + resolution: {integrity: sha512-GX80dxTQD/M/OryyuxJcFzFENzry3cDFPvCFTyWogNWQH3fSHfMrNfpwpl1YzMos1eV9NygK5GSDG7VArQlb4w==} + cpu: [x64] + os: [linux] + + '@yuku-parser/binding-win32-arm64@0.10.2': + resolution: {integrity: sha512-agePQBV4VHewiGU0ACSjscZ/hJd283f9JfAF19Gf1rI5+wy5tTgx4GS36i0b3xim15AQ4RCpDRosEvhLZ4zAOw==} + cpu: [arm64] + os: [win32] + + '@yuku-parser/binding-win32-x64@0.10.2': + resolution: {integrity: sha512-s8//CMpgL5+y1lvDCdyh1rwGI5+ytDJywFJRe1vnhI7n0j+caxshNucurikYLLVeQ2SYFex6aPrzgQxkabBQRA==} + cpu: [x64] + os: [win32] + + '@yuku-toolchain/types@0.10.2': + resolution: {integrity: sha512-sSeo4SSSToiS+sSD+bwn/s94EEcaLJ7tG5LCp8gFYC1G5VzxzX7fqB6m9RU1yMD8KTpu6zdU/I1NlQf8hVBJ9Q==} + + adm-zip@0.6.0: + resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} + engines: {node: '>=14.0'} + + ansis@4.4.0: + resolution: {integrity: sha512-9k3v7xcHwgdO/DruxGIg4HtjvlAZlcnsX/mzqUb1t3NkYnl9kK2UJ+Gq0io+vQf7iT//BD/HB/NBkUR1LWxoeA==} + engines: {node: '>=14'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + babel-dead-code-elimination@1.0.12: + resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} + + baseline-browser-mapping@2.11.24: + resolution: {integrity: sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==} + engines: {node: '>=6.0.0'} + hasBin: true + + browserslist@4.29.0: + resolution: {integrity: sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + + caniuse-lite@1.0.30001810: + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + dts-resolver@3.0.0: + resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + + electron-to-chromium@1.5.430: + resolution: {integrity: sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ==} + + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + exsolve@1.1.1: + resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fetchdts@0.1.7: + resolution: {integrity: sha512-YoZjBdafyLIop9lSxXVI33oLD5kN31q4Td+CasofLLYeLXRFeOsuOw0Uo+XNRi9PZlbfdlN2GmRtm4tCEQ9/KA==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-tsconfig@5.0.0-beta.6: + resolution: {integrity: sha512-X6fBC0pmImC70gvX2zm56go9hx0MyoGVdG0tUCkg/D+Xnh5TJsOZ7iDbOdI3PvmtrDxnu1YdDufpK2QJX1Meqw==} + engines: {node: '>=20.20.0'} + + h3@2.0.1-rc.20: + resolution: {integrity: sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==} + engines: {node: '>=20.11.1'} + hasBin: true + peerDependencies: + crossws: ^0.4.1 + peerDependenciesMeta: + crossws: + optional: true + + hookable@6.1.1: + resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + + human-id@4.2.1: + resolution: {integrity: sha512-zPGsiS+dWoTZtZ4AtpA9Y+BdSFSNWvnouNlWNoUFyAM6xHOHmdCvqO3k8AIbdamCOv4gUFUVNPf6rJFfc4UiJw==} + hasBin: true + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + import-without-cache@0.4.1: + resolution: {integrity: sha512-vXoV9PjKHEednCUu01e98TkImxy67e3BJXbTIOmIq4Hyzw3IAwYRcuPkfeTzJzwyyts4kjuA73x+pWJLc4f86A==} + engines: {node: ^22.18.0 || >=24.0.0} + + isbot@5.2.2: + resolution: {integrity: sha512-iQcBXcd+Rv/pkubRyGh2utW2j1oPG5hZY6TUhVPpqK4G+o3IbxpJNx04hgksjc/N7GK5pEorUxDeg31cFgEk/w==} + engines: {node: '>=18'} + + isomorphic-ws@5.0.0: + resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} + peerDependencies: + ws: '*' + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + launch-editor@2.14.1: + resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.55: + resolution: {integrity: sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==} + engines: {node: '>=18'} + + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + + obug@3.0.0: + resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} + engines: {node: '>=12.20.0'} + + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.9.7: + resolution: {integrity: sha512-T3mF5P7HFzZVLDQuCUNtJj6WK1pcpLMweMNUVwGb01bLNkH9AkKxtZvmInw8K6bnn2O3d6/5RHl6zSHiBAD0Og==} + engines: {node: '>=14'} + hasBin: true + + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + + react-dom@19.3.0: + resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} + peerDependencies: + react: ^19.3.0 + + react@19.3.0: + resolution: {integrity: sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==} + engines: {node: '>=0.10.0'} + + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rolldown-plugin-dts@0.28.6: + resolution: {integrity: sha512-qKrFtBfRfR2hP233m7Ic9zf3wv6MSYdZghWKMdEO6dRYZGlNfINJAa1P5ZVvyHh3mrcd7IJQvIY8L9vnm+v5rQ==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} + peerDependencies: + '@typescript/native-preview': '*' + '@volar/typescript': ~2.4.0 + '@vue/language-core': ~3.2.0 || ~3.3.0 + rolldown: ^1.2.0 + typescript: ^5.0.0 || ^6.0.0 || ~7.0.0 + vue-tsc: ~3.2.0 || ~3.3.0 + peerDependenciesMeta: + '@typescript/native-preview': + optional: true + '@volar/typescript': + optional: true + '@vue/language-core': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.2.9: + resolution: {integrity: sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rou3@0.8.1: + resolution: {integrity: sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==} + + scheduler@0.28.0: + resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + seroval-plugins@1.6.7: + resolution: {integrity: sha512-4Nk35ttD3DTDJW4hgw5StsVAPeU6qnDFnULAouw6tQ7oLTV/ICXrWpsXo2EE52eSP2joUMazbVf52mFEcADqRw==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.6.7: + resolution: {integrity: sha512-AeDcLh0yO2SFm9W71essgnSzLV9DI8ZH0x0knXn2DMnUZj728mpLbxjlbB6IqKCmqh8JA3cEqRyGoNkt584JcQ==} + engines: {node: '>=10'} + + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + engines: {node: '>= 0.4'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + srvx@0.11.22: + resolution: {integrity: sha512-LqZxxBDMKuMAZzFzJnDCkFOrs9MZQZr0LvHiO/SuSZVdQaXD7xQ5UWTUxheJrQPve1qk9MG2B/yttUvJxw8egQ==} + engines: {node: '>=20.16.0'} + hasBin: true + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + tsdown@0.23.0: + resolution: {integrity: sha512-BaT+ep1xnj5hdyICLd5r5SYYE+TXnI1ATePLykv9vcKpHpFTWUWRH+x1AF/E2qcu3YB6+vI8IdyxIIIffEqw5Q==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.23.0 + '@tsdown/exe': 0.23.0 + '@vitejs/devtools': '*' + publint: ^0.3.8 + tsx: '*' + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 + unplugin-unused: '>=0.5.0' + unrun: '*' + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + + turbo@2.10.13: + resolution: {integrity: sha512-69MkPbjk+G8oyC7pC2DeBk0rPdbLz51wuH5eAT0Q4BVJkzq/b1XM4kUUidsARbmDjwIELSJwd1ezITULY9kWNw==} + hasBin: true + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + undici@7.29.0: + resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} + engines: {node: '>=20.18.1'} + + unplugin@3.4.0: + resolution: {integrity: sha512-9skdIFlCsPdFV7wUfZxNsFInlW+7nJmGu2gkTu0OUhF56aXGsHab9x52/QhdJ4lC7ZDPWTxbiC4ANqVlsuaW3w==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@farmfe/core': '*' + '@rsbuild/core': '*' + '@rspack/core': '*' + bun-types-no-globals: '*' + esbuild: '*' + rolldown: '*' + rollup: '*' + unloader: '*' + vite: '*' + webpack: '*' + peerDependenciesMeta: + '@farmfe/core': + optional: true + '@rsbuild/core': + optional: true + '@rspack/core': + optional: true + bun-types-no-globals: + optional: true + esbuild: + optional: true + rolldown: + optional: true + rollup: + optional: true + unloader: + optional: true + vite: + optional: true + webpack: + optional: true + + update-browserslist-db@1.3.3: + resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-sync-external-store@1.7.0: + resolution: {integrity: sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + verkit@0.4.0: + resolution: {integrity: sha512-sXMwN6DMHeouPfCxkxWkKAmxphWKEenHYY5H1nIBzU3PmDsmJp6kBXJdshjVdpMZuWCmL9SH7KFRx29AylpP6g==} + engines: {node: '>=18.12.0'} + + vite@8.3.0: + resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xmlbuilder2@4.0.3: + resolution: {integrity: sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==} + engines: {node: '>=20.0'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true + + yuku-ast@0.10.2: + resolution: {integrity: sha512-UnG9mA6giglCvSErft2/40TVFX750Sj1xgwddPLpG7J7rlr/P1wPADg9G2RK+d/1tNLtRVoLdMMOMVBB9561TQ==} + + yuku-codegen@0.10.2: + resolution: {integrity: sha512-hentl2dtrF6cPjiAirtkfxfFZBA6Hdm61UqRJ7cA0qrlDNMk9PZGOjw5w1mx3E0hu/6pHcJF4dJW+D0SXHPZOA==} + + yuku-parser@0.10.2: + resolution: {integrity: sha512-CgaU0/PPjCAIEZ3WQroosOxTY3eeKldAN3h+vk8pMNz4+jl1CZzBR4pW+K/rRPF112VooCL5FdjJoiMNjDrL2A==} + + zod@4.6.5: + resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.29.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@changesets/apply-release-plan@8.1.1': + dependencies: + '@changesets/config': 4.0.1 + '@changesets/format': 0.1.2 + '@changesets/git': 4.0.1 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + import-meta-resolve: 4.2.0 + jsonc-parser: 3.3.1 + semver: 7.8.5 + + '@changesets/assemble-release-plan@7.0.0': + dependencies: + '@changesets/errors': 1.0.0 + '@changesets/get-dependents-graph': 3.0.0 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + semver: 7.8.5 + + '@changesets/changelog-git@1.0.0': + dependencies: + '@changesets/types': 7.0.0 + + '@changesets/cli@3.0.3': + dependencies: + '@changesets/apply-release-plan': 8.1.1 + '@changesets/assemble-release-plan': 7.0.0 + '@changesets/changelog-git': 1.0.0 + '@changesets/config': 4.0.1 + '@changesets/errors': 1.0.0 + '@changesets/get-dependents-graph': 3.0.0 + '@changesets/git': 4.0.1 + '@changesets/pre': 3.0.0 + '@changesets/read': 1.0.1 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + '@changesets/write': 1.0.1 + '@clack/prompts': 1.8.1 + '@manypkg/get-packages': 3.1.0 + '@pnpm/deps.graph-sequencer': 1100.0.1 + cac: 7.0.0 + import-meta-resolve: 4.2.0 + launch-editor: 2.14.1 + package-manager-detector: 1.8.0 + semver: 7.8.5 + tinyexec: 1.3.1 + + '@changesets/config@4.0.1': + dependencies: + '@changesets/get-dependents-graph': 3.0.0 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + '@manypkg/get-packages': 3.1.0 + picomatch: 4.0.7 + + '@changesets/errors@1.0.0': {} + + '@changesets/format@0.1.2': + dependencies: + package-manager-detector: 1.8.0 + tinyexec: 1.3.1 + + '@changesets/get-dependents-graph@3.0.0': + dependencies: + '@changesets/types': 7.0.0 + semver: 7.8.5 + + '@changesets/git@4.0.1': + dependencies: + '@changesets/errors': 1.0.0 + '@changesets/types': 7.0.0 + '@manypkg/get-packages': 3.1.0 + picomatch: 4.0.7 + tinyexec: 1.3.1 + + '@changesets/parse@1.0.0': + dependencies: + '@changesets/types': 7.0.0 + yaml: 2.9.1 + + '@changesets/pre@3.0.0': + dependencies: + '@changesets/errors': 1.0.0 + '@changesets/types': 7.0.0 + '@manypkg/get-packages': 3.1.0 + + '@changesets/read@1.0.1': + dependencies: + '@changesets/git': 4.0.1 + '@changesets/parse': 1.0.0 + '@changesets/types': 7.0.0 + + '@changesets/should-skip-package@1.0.0': + dependencies: + '@changesets/types': 7.0.0 + + '@changesets/types@7.0.0': {} + + '@changesets/write@1.0.1': + dependencies: + '@changesets/format': 0.1.2 + '@changesets/types': 7.0.0 + human-id: 4.2.1 + + '@clack/core@1.5.1': + dependencies: + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@clack/prompts@1.8.1': + dependencies: + '@clack/core': 1.5.1 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@manypkg/find-root@3.1.0': + dependencies: + '@manypkg/tools': 2.1.2 + + '@manypkg/get-packages@3.1.0': + dependencies: + '@manypkg/find-root': 3.1.0 + '@manypkg/tools': 2.1.2 + + '@manypkg/tools@2.1.2': + dependencies: + jju: 1.4.0 + tinyglobby: 0.2.17 + yaml: 2.9.1 + + '@module-federation/dts-plugin@2.9.0(typescript@6.0.3)': + dependencies: + '@module-federation/error-codes': 2.9.0 + '@module-federation/managers': 2.9.0 + '@module-federation/sdk': 2.9.0 + '@module-federation/third-party-dts-extractor': 2.9.0 + adm-zip: 0.6.0 + isomorphic-ws: 5.0.0(ws@8.21.0) + typescript: 6.0.3 + undici: 7.29.0 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@module-federation/error-codes@2.9.0': {} + + '@module-federation/managers@2.9.0': + dependencies: + '@module-federation/sdk': 2.9.0 + + '@module-federation/runtime-core@2.9.0': + dependencies: + '@module-federation/error-codes': 2.9.0 + '@module-federation/sdk': 2.9.0 + + '@module-federation/runtime@2.9.0': + dependencies: + '@module-federation/error-codes': 2.9.0 + '@module-federation/runtime-core': 2.9.0 + '@module-federation/sdk': 2.9.0 + + '@module-federation/sdk@2.9.0': {} + + '@module-federation/third-party-dts-extractor@2.9.0': {} + + '@module-federation/vite@1.22.0(typescript@6.0.3)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + '@module-federation/dts-plugin': 2.9.0(typescript@6.0.3) + '@module-federation/runtime': 2.9.0 + '@module-federation/sdk': 2.9.0 + vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - vue-tsc + + '@oozcitak/dom@2.0.2': + dependencies: + '@oozcitak/infra': 2.0.2 + '@oozcitak/url': 3.0.0 + '@oozcitak/util': 10.0.0 + + '@oozcitak/infra@2.0.2': + dependencies: + '@oozcitak/util': 10.0.0 + + '@oozcitak/url@3.0.0': + dependencies: + '@oozcitak/infra': 2.0.2 + '@oozcitak/util': 10.0.0 + + '@oozcitak/util@10.0.0': {} + + '@oxc-project/types@0.150.0': {} + + '@pnpm/deps.graph-sequencer@1100.0.1': {} + + '@quansync/fs@1.1.0': + dependencies: + quansync: 1.0.0 + + '@rolldown/binding-android-arm-eabi@1.2.9': + optional: true + + '@rolldown/binding-android-arm64@1.2.9': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.9': + optional: true + + '@rolldown/binding-darwin-x64@1.2.9': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.9': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.9': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.9': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.9': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.9': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.9': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.9': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.9': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.9': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.9': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.9': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@tanstack/history@1.162.4': {} + + '@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@tanstack/history': 1.162.4 + '@tanstack/react-store': 0.11.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.30 + isbot: 5.2.2 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + + '@tanstack/react-start-client@1.168.34(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.30 + '@tanstack/start-client-core': 1.170.30 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + + '@tanstack/react-start-rsc@0.1.53(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.30 + '@tanstack/router-utils': 1.162.3 + '@tanstack/start-client-core': 1.170.30 + '@tanstack/start-fn-stubs': 1.162.0 + '@tanstack/start-plugin-core': 1.171.44(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/start-storage-context': 1.167.32 + pathe: 2.0.3 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + transitivePeerDependencies: + - '@farmfe/core' + - '@rsbuild/core' + - bun-types-no-globals + - crossws + - esbuild + - rolldown + - rollup + - supports-color + - unloader + - vite + - vite-plugin-solid + - webpack + + '@tanstack/react-start-server@1.167.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.30 + '@tanstack/start-server-core': 1.169.35 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + transitivePeerDependencies: + - crossws + + '@tanstack/react-start@1.168.54(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-client': 1.168.34(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-rsc': 0.1.53(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/react-start-server': 1.167.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-utils': 1.162.3 + '@tanstack/start-client-core': 1.170.30 + '@tanstack/start-plugin-core': 1.171.44(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/start-server-core': 1.169.35 + pathe: 2.0.3 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - crossws + - esbuild + - react-server-dom-rspack + - rolldown + - rollup + - supports-color + - unloader + - vite-plugin-solid + - webpack + + '@tanstack/react-store@0.11.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@tanstack/store': 0.11.1 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + use-sync-external-store: 1.7.0(react@19.3.0) + + '@tanstack/router-core@1.171.30': + dependencies: + '@tanstack/history': 1.162.4 + cookie-es: 3.1.1 + seroval: 1.6.7 + seroval-plugins: 1.6.7(seroval@1.6.7) + + '@tanstack/router-generator@1.167.36': + dependencies: + '@babel/types': 7.29.8 + '@tanstack/router-core': 1.171.30 + '@tanstack/router-utils': 1.162.3 + '@tanstack/virtual-file-routes': 1.162.0 + jiti: 2.7.0 + magic-string: 0.30.21 + prettier: 3.9.7 + zod: 4.6.5 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-plugin@1.168.38(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + '@babel/core': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + '@tanstack/router-core': 1.171.30 + '@tanstack/router-generator': 1.167.36 + '@tanstack/router-utils': 1.162.3 + chokidar: 5.0.0 + unplugin: 3.4.0(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + zod: 4.6.5 + optionalDependencies: + '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - rolldown + - rollup + - supports-color + - unloader + + '@tanstack/router-utils@1.162.3': + dependencies: + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + ansis: 4.4.0 + babel-dead-code-elimination: 1.0.12 + diff: 8.0.4 + pathe: 2.0.3 + tinyglobby: 0.2.17 + transitivePeerDependencies: + - supports-color + + '@tanstack/start-client-core@1.170.30': + dependencies: + '@tanstack/router-core': 1.171.30 + '@tanstack/start-fn-stubs': 1.162.0 + '@tanstack/start-storage-context': 1.167.32 + seroval: 1.6.7 + + '@tanstack/start-fn-stubs@1.162.0': {} + + '@tanstack/start-plugin-core@1.171.44(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + '@tanstack/router-core': 1.171.30 + '@tanstack/router-generator': 1.167.36 + '@tanstack/router-plugin': 1.168.38(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/router-utils': 1.162.3 + '@tanstack/start-server-core': 1.169.35 + exsolve: 1.1.1 + lightningcss: 1.33.0 + pathe: 2.0.3 + picomatch: 4.0.7 + seroval: 1.6.7 + source-map: 0.7.6 + srvx: 0.11.22 + tinyglobby: 0.2.17 + ufo: 1.6.4 + vitefu: 1.1.3(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + xmlbuilder2: 4.0.3 + zod: 4.6.5 + optionalDependencies: + vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - '@tanstack/react-router' + - bun-types-no-globals + - crossws + - esbuild + - rolldown + - rollup + - supports-color + - unloader + - vite-plugin-solid + - webpack + + '@tanstack/start-server-core@1.169.35': + dependencies: + '@tanstack/history': 1.162.4 + '@tanstack/router-core': 1.171.30 + '@tanstack/start-client-core': 1.170.30 + '@tanstack/start-storage-context': 1.167.32 + fetchdts: 0.1.7 + h3-v2: h3@2.0.1-rc.20 + seroval: 1.6.7 + transitivePeerDependencies: + - crossws + + '@tanstack/start-storage-context@1.167.32': + dependencies: + '@tanstack/router-core': 1.171.30 + + '@tanstack/store@0.11.1': {} + + '@tanstack/virtual-file-routes@1.162.0': {} + + '@turbo/darwin-64@2.10.13': + optional: true + + '@turbo/darwin-arm64@2.10.13': + optional: true + + '@turbo/linux-64@2.10.13': + optional: true + + '@turbo/linux-arm64@2.10.13': + optional: true + + '@turbo/windows-64@2.10.13': + optional: true + + '@turbo/windows-arm64@2.10.13': + optional: true + + '@types/node@24.10.1': + dependencies: + undici-types: 7.16.0 + + '@yuku-codegen/binding-android-arm64@0.10.2': + optional: true + + '@yuku-codegen/binding-darwin-arm64@0.10.2': + optional: true + + '@yuku-codegen/binding-darwin-x64@0.10.2': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.10.2': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.10.2': + optional: true + + '@yuku-codegen/binding-win32-x64@0.10.2': + optional: true + + '@yuku-parser/binding-android-arm64@0.10.2': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.10.2': + optional: true + + '@yuku-parser/binding-darwin-x64@0.10.2': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.10.2': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.10.2': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.10.2': + optional: true + + '@yuku-parser/binding-win32-arm64@0.10.2': + optional: true + + '@yuku-parser/binding-win32-x64@0.10.2': + optional: true + + '@yuku-toolchain/types@0.10.2': {} + + adm-zip@0.6.0: {} + + ansis@4.4.0: {} + + argparse@2.0.1: {} + + babel-dead-code-elimination@1.0.12: + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + baseline-browser-mapping@2.11.24: {} + + browserslist@4.29.0: + dependencies: + baseline-browser-mapping: 2.11.24 + caniuse-lite: 1.0.30001810 + electron-to-chromium: 1.5.430 + node-releases: 2.0.55 + update-browserslist-db: 1.3.3(browserslist@4.29.0) + + cac@7.0.0: {} + + caniuse-lite@1.0.30001810: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.1.1 + + convert-source-map@2.0.0: {} + + cookie-es@3.1.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + defu@6.1.7: {} + + detect-libc@2.1.2: {} + + diff@8.0.4: {} + + dts-resolver@3.0.0: {} + + electron-to-chromium@1.5.430: {} + + empathic@2.0.1: {} + + escalade@3.2.0: {} + + exsolve@1.1.1: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fetchdts@0.1.7: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-tsconfig@5.0.0-beta.6: + dependencies: + resolve-pkg-maps: 1.0.0 + + h3@2.0.1-rc.20: + dependencies: + rou3: 0.8.1 + srvx: 0.11.22 + + hookable@6.1.1: {} + + human-id@4.2.1: {} + + import-meta-resolve@4.2.0: {} + + import-without-cache@0.4.1: {} + + isbot@5.2.2: {} + + isomorphic-ws@5.0.0(ws@8.21.0): + dependencies: + ws: 8.21.0 + + jiti@2.7.0: {} + + jju@1.4.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + + launch-editor@2.14.1: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.10.0 + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + ms@2.1.3: {} + + nanoid@3.3.19: {} + + node-releases@2.0.55: {} + + obug@2.2.1: {} + + obug@3.0.0: {} + + package-manager-detector@1.8.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + postcss@8.5.28: + dependencies: + nanoid: 3.3.19 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.9.7: {} + + quansync@1.0.0: {} + + react-dom@19.3.0(react@19.3.0): + dependencies: + react: 19.3.0 + scheduler: 0.28.0 + + react@19.3.0: {} + + readdirp@5.1.1: {} + + resolve-pkg-maps@1.0.0: {} + + rolldown-plugin-dts@0.28.6(rolldown@1.2.9)(typescript@6.0.3): + dependencies: + dts-resolver: 3.0.0 + get-tsconfig: 5.0.0-beta.6 + obug: 3.0.0 + rolldown: 1.2.9 + yuku-ast: 0.10.2 + yuku-codegen: 0.10.2 + yuku-parser: 0.10.2 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.2.9: + dependencies: + '@oxc-project/types': 0.150.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.9 + '@rolldown/binding-android-arm64': 1.2.9 + '@rolldown/binding-darwin-arm64': 1.2.9 + '@rolldown/binding-darwin-x64': 1.2.9 + '@rolldown/binding-freebsd-x64': 1.2.9 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.9 + '@rolldown/binding-linux-arm64-gnu': 1.2.9 + '@rolldown/binding-linux-arm64-musl': 1.2.9 + '@rolldown/binding-linux-ppc64-gnu': 1.2.9 + '@rolldown/binding-linux-s390x-gnu': 1.2.9 + '@rolldown/binding-linux-x64-gnu': 1.2.9 + '@rolldown/binding-linux-x64-musl': 1.2.9 + '@rolldown/binding-openharmony-arm64': 1.2.9 + '@rolldown/binding-win32-arm64-msvc': 1.2.9 + '@rolldown/binding-win32-x64-msvc': 1.2.9 + + rou3@0.8.1: {} + + scheduler@0.28.0: {} + + semver@6.3.1: {} + + semver@7.8.5: {} + + seroval-plugins@1.6.7(seroval@1.6.7): + dependencies: + seroval: 1.6.7 + + seroval@1.6.7: {} + + shell-quote@1.10.0: {} + + sisteransi@1.0.5: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + srvx@0.11.22: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tree-kill@1.2.2: {} + + tsdown@0.23.0(typescript@6.0.3): + dependencies: + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.0.1 + hookable: 6.1.1 + import-without-cache: 0.4.1 + obug: 2.2.1 + picomatch: 4.0.7 + rolldown: 1.2.9 + rolldown-plugin-dts: 0.28.6(rolldown@1.2.9)(typescript@6.0.3) + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + verkit: 0.4.0 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - '@typescript/native-preview' + - '@volar/typescript' + - '@vue/language-core' + - oxc-resolver + - vue-tsc + + turbo@2.10.13: + optionalDependencies: + '@turbo/darwin-64': 2.10.13 + '@turbo/darwin-arm64': 2.10.13 + '@turbo/linux-64': 2.10.13 + '@turbo/linux-arm64': 2.10.13 + '@turbo/windows-64': 2.10.13 + '@turbo/windows-arm64': 2.10.13 + + typescript@6.0.3: {} + + ufo@1.6.4: {} + + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.1.0 + quansync: 1.0.0 + + undici-types@7.16.0: {} + + undici@7.29.0: {} + + unplugin@3.4.0(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)): + dependencies: + '@jridgewell/remapping': 2.3.5 + picomatch: 4.0.7 + webpack-virtual-modules: 0.6.2 + optionalDependencies: + rolldown: 1.2.9 + vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + + update-browserslist-db@1.3.3(browserslist@4.29.0): + dependencies: + browserslist: 4.29.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-sync-external-store@1.7.0(react@19.3.0): + dependencies: + react: 19.3.0 + + verkit@0.4.0: {} + + vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.2.9 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.10.1 + fsevents: 2.3.3 + jiti: 2.7.0 + yaml: 2.9.1 + + vitefu@1.1.3(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)): + optionalDependencies: + vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + + webpack-virtual-modules@0.6.2: {} + + ws@8.21.0: {} + + xmlbuilder2@4.0.3: + dependencies: + '@oozcitak/dom': 2.0.2 + '@oozcitak/infra': 2.0.2 + '@oozcitak/util': 10.0.0 + js-yaml: 4.3.2 + + yallist@3.1.1: {} + + yaml@2.9.1: {} + + yuku-ast@0.10.2: + dependencies: + '@yuku-toolchain/types': 0.10.2 + + yuku-codegen@0.10.2: + dependencies: + '@yuku-toolchain/types': 0.10.2 + optionalDependencies: + '@yuku-codegen/binding-android-arm64': 0.10.2 + '@yuku-codegen/binding-darwin-arm64': 0.10.2 + '@yuku-codegen/binding-darwin-x64': 0.10.2 + '@yuku-codegen/binding-freebsd-x64': 0.10.2 + '@yuku-codegen/binding-linux-arm-gnu': 0.10.2 + '@yuku-codegen/binding-linux-arm-musl': 0.10.2 + '@yuku-codegen/binding-linux-arm64-gnu': 0.10.2 + '@yuku-codegen/binding-linux-arm64-musl': 0.10.2 + '@yuku-codegen/binding-linux-x64-gnu': 0.10.2 + '@yuku-codegen/binding-linux-x64-musl': 0.10.2 + '@yuku-codegen/binding-win32-arm64': 0.10.2 + '@yuku-codegen/binding-win32-x64': 0.10.2 + + yuku-parser@0.10.2: + dependencies: + '@yuku-toolchain/types': 0.10.2 + yuku-ast: 0.10.2 + optionalDependencies: + '@yuku-parser/binding-android-arm64': 0.10.2 + '@yuku-parser/binding-darwin-arm64': 0.10.2 + '@yuku-parser/binding-darwin-x64': 0.10.2 + '@yuku-parser/binding-freebsd-x64': 0.10.2 + '@yuku-parser/binding-linux-arm-gnu': 0.10.2 + '@yuku-parser/binding-linux-arm-musl': 0.10.2 + '@yuku-parser/binding-linux-arm64-gnu': 0.10.2 + '@yuku-parser/binding-linux-arm64-musl': 0.10.2 + '@yuku-parser/binding-linux-x64-gnu': 0.10.2 + '@yuku-parser/binding-linux-x64-musl': 0.10.2 + '@yuku-parser/binding-win32-arm64': 0.10.2 + '@yuku-parser/binding-win32-x64': 0.10.2 + + zod@4.6.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..286cf7f --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - apps/* + - packages/* diff --git a/test/release-artifacts.test.mjs b/test/release-artifacts.test.mjs new file mode 100644 index 0000000..e8f6555 --- /dev/null +++ b/test/release-artifacts.test.mjs @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { existsSync, readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import test from "node:test"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const packageRoot = join(root, "packages", "tanstack"); +const dist = join(packageRoot, "dist"); + +test("published declarations reference an included source map", () => { + const declarationName = "index.d.mts"; + const declaration = readFileSync(join(dist, declarationName), "utf8"); + const sourceMap = declaration.match(/\/\/[#@]\s*sourceMappingURL=([^\s]+)\s*$/m)?.[1]; + + assert.ok(sourceMap, `${declarationName} has no source map reference`); + assert.ok( + existsSync(join(dist, sourceMap)), + `${declarationName} references missing source map ${sourceMap}`, + ); +}); + +test("npm package contains only runtime, metadata, and documentation files", () => { + const packed = spawnSync("npm", ["pack", "--dry-run", "--json", "--ignore-scripts"], { + cwd: packageRoot, + encoding: "utf8", + }); + + assert.equal(packed.status, 0, packed.stderr); + const [manifest] = JSON.parse(packed.stdout); + const files = manifest.files.map(({ path }) => path).sort(); + + assert.deepEqual(files, [ + "CHANGELOG.md", + "LICENSE", + "README.md", + "dist/index.d.mts", + "dist/index.d.mts.map", + "dist/index.mjs", + "dist/index.mjs.map", + "package.json", + ]); +}); diff --git a/test/release-contracts.test.mjs b/test/release-contracts.test.mjs new file mode 100644 index 0000000..d7739ed --- /dev/null +++ b/test/release-contracts.test.mjs @@ -0,0 +1,116 @@ +import assert from "node:assert/strict"; +import { existsSync, readFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import test from "node:test"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const packageRoot = join(root, "packages", "tanstack"); +const packageRequire = createRequire(join(packageRoot, "package.json")); +const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")); +const rootPackageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); +const ciWorkflow = readFileSync(join(root, ".github", "workflows", "ci.yml"), "utf8"); +const releaseWorkflow = readFileSync(join(root, ".github", "workflows", "release.yml"), "utf8"); +const { tanstackStartModuleFederation } = await import(join(packageRoot, "dist", "index.mjs")); + +function upstreamOptions(options) { + const plugins = tanstackStartModuleFederation(options); + assert.ok(Array.isArray(plugins), "federation wrapper returns Vite plugins"); + const upstream = plugins.filter((plugin) => plugin.name === "module-federation-vite"); + assert.equal(upstream.length, 1, "wrapper delegates to one federation instance"); + assert.ok(upstream[0]._options, "upstream plugin exposes normalized options"); + return upstream[0]._options; +} + +test("applies TanStack-safe defaults", () => { + const options = upstreamOptions({ + name: "host", + filename: undefined, + manifest: undefined, + hostInitInjectLocation: undefined, + }); + assert.equal(options.filename, "remoteEntry.js"); + assert.equal(options.manifest, true); + assert.equal(options.hostInitInjectLocation, "entry"); + assert.equal(options.shared.react.shareConfig.singleton, true); + assert.equal(options.shared["react-dom"].shareConfig.singleton, true); +}); + +test("preserves explicit federation settings and shared entries", () => { + const options = upstreamOptions({ + name: "remote", + filename: "custom-entry.js", + manifest: false, + hostInitInjectLocation: "html", + shared: { + react: { singleton: false, requiredVersion: "^19.0.0" }, + lodash: { singleton: true }, + }, + }); + assert.equal(options.filename, "custom-entry.js"); + assert.equal(options.manifest, false); + assert.equal(options.hostInitInjectLocation, "html"); + assert.equal(options.shared.react.shareConfig.singleton, false); + assert.equal(options.shared.react.shareConfig.requiredVersion, "^19.0.0"); + assert.equal(options.shared.lodash.shareConfig.singleton, true); + assert.ok(options.shared["react-dom"], "unconfigured React DOM remains shared"); +}); + +test("does not mutate the caller's options", () => { + const options = { + name: "host", + shared: { react: { singleton: false } }, + remotes: { remote: { type: "module", name: "remote", entry: "remoteEntry.js" } }, + }; + const before = structuredClone(options); + upstreamOptions(options); + assert.deepEqual(options, before); +}); + +test("accepts the upstream array form", () => { + const options = upstreamOptions({ name: "remote", shared: ["lodash", "react"] }); + assert.deepEqual(Object.keys(options.shared).sort(), ["lodash", "react", "react-dom"]); + assert.equal(options.shared.react.shareConfig.singleton, true); + assert.equal(options.shared["react-dom"].shareConfig.singleton, true); +}); + +test("leaves the build target to each TanStack environment", () => { + const options = upstreamOptions({ name: "host", target: "node" }); + assert.equal(options.target, undefined); +}); + +test("published metadata points to ESM build and declaration map", () => { + assert.equal(packageJson.type, "module"); + assert.equal(packageJson.exports["."].import, "./dist/index.mjs"); + assert.equal(packageJson.exports["."].types, "./dist/index.d.mts"); + assert.equal( + packageRequire.resolve("@module-federation/tanstack"), + join(packageRoot, "dist", "index.mjs"), + ); + for (const file of ["index.mjs", "index.mjs.map", "index.d.mts", "index.d.mts.map"]) { + assert.ok(existsSync(join(packageRoot, "dist", file)), `dist/${file} exists`); + } +}); + +test("CI uses Node versions supported by the package build toolchain", () => { + const supportedNode = "^22.18.0 || ^24.11.0 || >=26.0.0"; + assert.equal(rootPackageJson.engines.node, supportedNode); + assert.equal(packageJson.engines.node, supportedNode); + assert.match(ciWorkflow, /node: \["22\.18\.0", "24", "26"\]/); + assert.doesNotMatch(ciWorkflow, /22\.12\.0/); +}); + +test("GitHub prereleases publish the version validated against their tag", () => { + assert.match(releaseWorkflow, /Tag\/version mismatch/); + assert.doesNotMatch(releaseWorkflow, /Set deterministic prerelease version/); + assert.doesNotMatch( + releaseWorkflow, + /github\.event\.release\.prerelease[\s\S]*npm pkg set version/, + ); +}); + +test("npm publications share one package-wide concurrency queue", () => { + assert.match(releaseWorkflow, /group: publish-module-federation-tanstack/); + assert.doesNotMatch(releaseWorkflow, /group:.*github\.(?:ref|run)/); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8de1016 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "files": [], + "references": [{ "path": "./packages/tanstack/tsconfig.json" }] +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..882620b --- /dev/null +++ b/turbo.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://turborepo.com/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$", ".env*"], + "outputs": [".output/**", "dist/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "typecheck": { + "dependsOn": ["^build", "^typecheck"], + "inputs": ["$TURBO_DEFAULT$", ".env*"], + "outputs": [] + } + } +} From cd00f8d0b6603dab6fa4c481fb9247a8a1be0d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Fri, 18 Sep 2026 09:44:41 +0200 Subject: [PATCH 02/18] feat: add federated TanStack Start examples --- .github/workflows/pkg-pr-new.yml | 2 +- .gitignore | 1 + .prettierignore | 1 + README.md | 13 + apps/README.md | 20 ++ apps/host/package.json | 28 ++ apps/host/src/remote.d.ts | 6 + apps/host/src/routeTree.gen.ts | 68 ++++ apps/host/src/router.tsx | 16 + apps/host/src/routes/__root.tsx | 37 ++ apps/host/src/routes/index.tsx | 37 ++ apps/host/src/styles.css | 139 +++++++ apps/host/tsconfig.json | 20 ++ apps/host/vite.config.ts | 30 ++ apps/remote/package.json | 28 ++ apps/remote/src/components/StatusCard.css | 77 ++++ apps/remote/src/components/StatusCard.tsx | 27 ++ apps/remote/src/routeTree.gen.ts | 68 ++++ apps/remote/src/router.tsx | 16 + apps/remote/src/routes/__root.tsx | 37 ++ apps/remote/src/routes/index.tsx | 22 ++ apps/remote/src/styles.css | 59 +++ apps/remote/tsconfig.json | 20 ++ apps/remote/vite.config.ts | 32 ++ docs/RELEASING.md | 6 +- package.json | 5 +- pnpm-lock.yaml | 418 ++++++++++++++++++++++ test/example-build.test.mjs | 53 +++ test/example-runtime.test.mjs | 99 +++++ test/release-contracts.test.mjs | 6 + tsconfig.json | 6 +- turbo.json | 4 + 32 files changed, 1394 insertions(+), 7 deletions(-) create mode 100644 apps/README.md create mode 100644 apps/host/package.json create mode 100644 apps/host/src/remote.d.ts create mode 100644 apps/host/src/routeTree.gen.ts create mode 100644 apps/host/src/router.tsx create mode 100644 apps/host/src/routes/__root.tsx create mode 100644 apps/host/src/routes/index.tsx create mode 100644 apps/host/src/styles.css create mode 100644 apps/host/tsconfig.json create mode 100644 apps/host/vite.config.ts create mode 100644 apps/remote/package.json create mode 100644 apps/remote/src/components/StatusCard.css create mode 100644 apps/remote/src/components/StatusCard.tsx create mode 100644 apps/remote/src/routeTree.gen.ts create mode 100644 apps/remote/src/router.tsx create mode 100644 apps/remote/src/routes/__root.tsx create mode 100644 apps/remote/src/routes/index.tsx create mode 100644 apps/remote/src/styles.css create mode 100644 apps/remote/tsconfig.json create mode 100644 apps/remote/vite.config.ts create mode 100644 test/example-build.test.mjs create mode 100644 test/example-runtime.test.mjs diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml index 1dc317d..5fad24d 100644 --- a/.github/workflows/pkg-pr-new.yml +++ b/.github/workflows/pkg-pr-new.yml @@ -21,4 +21,4 @@ jobs: cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm build:package - - run: pnpm dlx pkg-pr-new@0.0.54 publish --compact ./packages/tanstack + - run: pnpm dlx pkg-pr-new@0.0.54 publish ./packages/tanstack diff --git a/.gitignore b/.gitignore index a62c8b7..09af92d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ dist/ pnpm-debug.log* coverage/ *.tgz +*.tsbuildinfo .DS_Store diff --git a/.prettierignore b/.prettierignore index b3bc6ae..77c3854 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,4 @@ dist node_modules pnpm-lock.yaml .turbo +**/routeTree.gen.ts diff --git a/README.md b/README.md index 87b7e89..514a256 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,16 @@ selection to `@module-federation/vite` for each environment. The package supports Node 22.18+, 24.11+, and 26+, plus Vite 7/8 and TanStack Start 1.x. Use Vite 8 when developing SSR hosts that load remotes; Vite 7 remains supported for production builds. + +## Example + +The workspace includes two TanStack Start apps. `apps/remote` exposes a +stateful React card, and `apps/host` consumes it through this package. + +```bash +pnpm install +pnpm start +``` + +Open the host at http://localhost:3000. The remote also runs as its own +TanStack Start application at http://localhost:3001. diff --git a/apps/README.md b/apps/README.md new file mode 100644 index 0000000..113a75b --- /dev/null +++ b/apps/README.md @@ -0,0 +1,20 @@ +# TanStack Start example + +Both sides of this example are TanStack Start applications. + +- `remote` exposes `./StatusCard` from its Module Federation container and also + renders the component on its own `/` route. +- `host` loads `tanstack_remote/StatusCard` from the remote running on port 3001. Its own application runs on port 3000. + +From the repository root: + +```bash +pnpm start +``` + +The federation wrapper must precede `tanstackStart()` in both Vite configs. +React and React DOM use the wrapper's singleton defaults. + +Each package's `start` script runs Vite's development server. In Vite's CLI, +the bare `vite` command means “start”; the literal `vite start` command would +treat `start` as a project directory. diff --git a/apps/host/package.json b/apps/host/package.json new file mode 100644 index 0000000..1dbba92 --- /dev/null +++ b/apps/host/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-start-host", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite dev --port 3000 --strictPort", + "start": "vite --host 127.0.0.1 --port 3000 --strictPort", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@module-federation/runtime": "2.9.0", + "@tanstack/react-router": "1.170.38", + "@tanstack/react-start": "1.168.56", + "react": "19.3.0", + "react-dom": "19.3.0" + }, + "devDependencies": { + "@module-federation/tanstack": "workspace:*", + "@types/node": "24.10.1", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@vitejs/plugin-react": "6.1.1", + "typescript": "6.0.3", + "vite": "8.3.0" + } +} diff --git a/apps/host/src/remote.d.ts b/apps/host/src/remote.d.ts new file mode 100644 index 0000000..e950d84 --- /dev/null +++ b/apps/host/src/remote.d.ts @@ -0,0 +1,6 @@ +declare module "tanstack_remote/StatusCard" { + import type { ComponentType } from "react"; + + const StatusCard: ComponentType; + export default StatusCard; +} diff --git a/apps/host/src/routeTree.gen.ts b/apps/host/src/routeTree.gen.ts new file mode 100644 index 0000000..dceedff --- /dev/null +++ b/apps/host/src/routeTree.gen.ts @@ -0,0 +1,68 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' + fileRoutesByTo: FileRoutesByTo + to: '/' + id: '__root__' | '/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/apps/host/src/router.tsx b/apps/host/src/router.tsx new file mode 100644 index 0000000..f8bf919 --- /dev/null +++ b/apps/host/src/router.tsx @@ -0,0 +1,16 @@ +import { createRouter } from "@tanstack/react-router"; +import { routeTree } from "./routeTree.gen"; + +export function getRouter() { + return createRouter({ + routeTree, + defaultPreload: "intent", + scrollRestoration: true, + }); +} + +declare module "@tanstack/react-router" { + interface Register { + router: ReturnType; + } +} diff --git a/apps/host/src/routes/__root.tsx b/apps/host/src/routes/__root.tsx new file mode 100644 index 0000000..ab2e206 --- /dev/null +++ b/apps/host/src/routes/__root.tsx @@ -0,0 +1,37 @@ +import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"; +import type { ReactNode } from "react"; +import stylesHref from "../styles.css?url"; + +export const Route = createRootRoute({ + head: () => ({ + links: [{ rel: "stylesheet", href: stylesHref }], + meta: [ + { charSet: "utf-8" }, + { name: "viewport", content: "width=device-width, initial-scale=1" }, + { title: "TanStack Start federation host" }, + ], + }), + component: RootComponent, +}); + +function RootComponent() { + return ( + + + + ); +} + +function RootDocument({ children }: { children: ReactNode }) { + return ( + + + + + + {children} + + + + ); +} diff --git a/apps/host/src/routes/index.tsx b/apps/host/src/routes/index.tsx new file mode 100644 index 0000000..bacdfcd --- /dev/null +++ b/apps/host/src/routes/index.tsx @@ -0,0 +1,37 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { lazy, Suspense } from "react"; + +const RemoteStatusCard = lazy(() => import("tanstack_remote/StatusCard")); + +export const Route = createFileRoute("/")({ + loader: () => import("tanstack_remote/StatusCard").then(() => null), + component: Home, +}); + +function Home() { + return ( +
+
+

TanStack Start × Module Federation

+

Two full-stack apps. One React tree.

+

+ This page belongs to the host on port 3000. The interactive card below is owned and built + by a second TanStack Start application on port 3001. +

+
+ +
+
+ 01 +

Host route

+

Rendered by TanStack Start

+

The route, document shell, and server response come from the host app.

+
+ + Loading the remote app…}> + + +
+
+ ); +} diff --git a/apps/host/src/styles.css b/apps/host/src/styles.css new file mode 100644 index 0000000..47b0470 --- /dev/null +++ b/apps/host/src/styles.css @@ -0,0 +1,139 @@ +:root { + color: #e9efe9; + background: #101411; + font-family: "Avenir Next", "Trebuchet MS", sans-serif; + font-synthesis: none; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-width: 320px; + min-height: 100vh; + background: + linear-gradient(rgba(143, 255, 184, 0.045) 1px, transparent 1px), + linear-gradient(90deg, rgba(143, 255, 184, 0.045) 1px, transparent 1px), + radial-gradient(circle at 82% 8%, rgba(62, 133, 91, 0.24), transparent 35%), #101411; + background-size: + 40px 40px, + 40px 40px, + auto; +} + +button { + font: inherit; +} + +.page-shell { + width: min(1120px, calc(100% - 40px)); + margin: 0 auto; + padding: 88px 0 72px; +} + +.hero { + max-width: 820px; + animation: rise 520ms ease-out both; +} + +.eyebrow, +.card-label, +.card-index { + font-family: "SFMono-Regular", Consolas, monospace; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.eyebrow { + color: #8fffb8; + font-size: 0.78rem; +} + +h1 { + margin: 18px 0 20px; + max-width: 760px; + font-size: clamp(3rem, 8vw, 6.8rem); + line-height: 0.94; + letter-spacing: -0.065em; +} + +.lede { + max-width: 650px; + color: #aeb9b1; + font-size: 1.08rem; + line-height: 1.7; +} + +.proof-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; + margin-top: 64px; +} + +.host-card, +.remote-loading { + min-height: 300px; + border: 1px solid #344239; + padding: 30px; +} + +.host-card { + position: relative; + background: rgba(20, 28, 23, 0.82); +} + +.card-index { + position: absolute; + top: 24px; + right: 26px; + color: #617066; + font-size: 0.72rem; +} + +.card-label { + margin: 0 0 70px; + color: #8fffb8; + font-size: 0.72rem; +} + +.host-card h2 { + margin: 0 0 14px; + font-size: 1.8rem; +} + +.host-card p:last-child { + max-width: 390px; + color: #96a39a; + line-height: 1.65; +} + +.remote-loading { + display: grid; + place-items: center; + color: #8fffb8; + font-family: "SFMono-Regular", Consolas, monospace; +} + +@keyframes rise { + from { + opacity: 0; + transform: translateY(16px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 760px) { + .page-shell { + padding-top: 56px; + } + + .proof-grid { + grid-template-columns: 1fr; + } +} diff --git a/apps/host/tsconfig.json b/apps/host/tsconfig.json new file mode 100644 index 0000000..189cae2 --- /dev/null +++ b/apps/host/tsconfig.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts"], + "compilerOptions": { + "allowImportingTsExtensions": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "types": ["node", "vite/client"], + "verbatimModuleSyntax": true + } +} diff --git a/apps/host/vite.config.ts b/apps/host/vite.config.ts new file mode 100644 index 0000000..03ecd37 --- /dev/null +++ b/apps/host/vite.config.ts @@ -0,0 +1,30 @@ +import { tanstackStartModuleFederation } from "@module-federation/tanstack"; +import { tanstackStart } from "@tanstack/react-start/plugin/vite"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + tanstackStartModuleFederation({ + name: "tanstack_host", + dts: false, + remotes: { + tanstack_remote: { + type: "module", + name: "tanstack_remote", + entry: "http://127.0.0.1:3001/remoteEntry.js", + }, + }, + }), + tanstackStart(), + react(), + ], + server: { + port: 3000, + }, + ssr: { + optimizeDeps: { + include: ["react", "react-dom"], + }, + }, +}); diff --git a/apps/remote/package.json b/apps/remote/package.json new file mode 100644 index 0000000..3a5c6a8 --- /dev/null +++ b/apps/remote/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-start-remote", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite dev --port 3001 --strictPort", + "start": "vite --host 127.0.0.1 --port 3001 --strictPort", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@module-federation/runtime": "2.9.0", + "@tanstack/react-router": "1.170.38", + "@tanstack/react-start": "1.168.56", + "react": "19.3.0", + "react-dom": "19.3.0" + }, + "devDependencies": { + "@module-federation/tanstack": "workspace:*", + "@types/node": "24.10.1", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@vitejs/plugin-react": "6.1.1", + "typescript": "6.0.3", + "vite": "8.3.0" + } +} diff --git a/apps/remote/src/components/StatusCard.css b/apps/remote/src/components/StatusCard.css new file mode 100644 index 0000000..c8ab085 --- /dev/null +++ b/apps/remote/src/components/StatusCard.css @@ -0,0 +1,77 @@ +.remote-card { + position: relative; + min-height: 300px; + border: 1px solid #7b4d26; + background: #241b14; + color: #e9efe9; + padding: 30px; + box-shadow: 14px 14px 0 #0a0d0b; + font-family: "Avenir Next", "Trebuchet MS", sans-serif; +} + +.remote-card .card-index, +.remote-card .card-label, +.remote-card .remote-status, +.remote-card button { + font-family: "SFMono-Regular", Consolas, monospace; +} + +.remote-card .card-index, +.remote-card .card-label, +.remote-card .remote-status { + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.remote-card .card-index { + position: absolute; + top: 24px; + right: 26px; + color: #8f6d4e; + font-size: 0.72rem; +} + +.remote-card .card-label { + margin: 0 0 58px; + color: #f4a95f; + font-size: 0.75rem; +} + +.remote-card .remote-status { + display: flex; + align-items: center; + gap: 8px; + color: #c4cfca; + font-size: 0.66rem; +} + +.remote-card .status-dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: #8fffb8; + box-shadow: 0 0 10px rgba(143, 255, 184, 0.72); +} + +.remote-card h2 { + margin: 22px 0 12px; + font-size: 1.8rem; +} + +.remote-card > p:not(.card-label) { + color: #aeb9b1; + line-height: 1.65; +} + +.remote-card button { + margin-top: 24px; + border: 0; + background: #f4a95f; + color: #17120e; + cursor: pointer; + padding: 12px 16px; +} + +.remote-card button:hover { + background: #ffc27f; +} diff --git a/apps/remote/src/components/StatusCard.tsx b/apps/remote/src/components/StatusCard.tsx new file mode 100644 index 0000000..bb5efaf --- /dev/null +++ b/apps/remote/src/components/StatusCard.tsx @@ -0,0 +1,27 @@ +import { useEffect, useState } from "react"; +import "./StatusCard.css"; + +export default function StatusCard() { + const [count, setCount] = useState(0); + const [hydrated, setHydrated] = useState(false); + + useEffect(() => { + setHydrated(true); + }, []); + + return ( +
+ 02 +

Federated route module

+
+ + {hydrated ? "Hydrated on the host" : "Rendered on the server"} +
+

Owned by the remote app

+

This component crossed an application boundary while React stayed a shared singleton.

+ +
+ ); +} diff --git a/apps/remote/src/routeTree.gen.ts b/apps/remote/src/routeTree.gen.ts new file mode 100644 index 0000000..dceedff --- /dev/null +++ b/apps/remote/src/routeTree.gen.ts @@ -0,0 +1,68 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' + fileRoutesByTo: FileRoutesByTo + to: '/' + id: '__root__' | '/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/apps/remote/src/router.tsx b/apps/remote/src/router.tsx new file mode 100644 index 0000000..f8bf919 --- /dev/null +++ b/apps/remote/src/router.tsx @@ -0,0 +1,16 @@ +import { createRouter } from "@tanstack/react-router"; +import { routeTree } from "./routeTree.gen"; + +export function getRouter() { + return createRouter({ + routeTree, + defaultPreload: "intent", + scrollRestoration: true, + }); +} + +declare module "@tanstack/react-router" { + interface Register { + router: ReturnType; + } +} diff --git a/apps/remote/src/routes/__root.tsx b/apps/remote/src/routes/__root.tsx new file mode 100644 index 0000000..ad43dab --- /dev/null +++ b/apps/remote/src/routes/__root.tsx @@ -0,0 +1,37 @@ +import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"; +import type { ReactNode } from "react"; +import stylesHref from "../styles.css?url"; + +export const Route = createRootRoute({ + head: () => ({ + links: [{ rel: "stylesheet", href: stylesHref }], + meta: [ + { charSet: "utf-8" }, + { name: "viewport", content: "width=device-width, initial-scale=1" }, + { title: "TanStack Start federation remote" }, + ], + }), + component: RootComponent, +}); + +function RootComponent() { + return ( + + + + ); +} + +function RootDocument({ children }: { children: ReactNode }) { + return ( + + + + + + {children} + + + + ); +} diff --git a/apps/remote/src/routes/index.tsx b/apps/remote/src/routes/index.tsx new file mode 100644 index 0000000..b1a59cd --- /dev/null +++ b/apps/remote/src/routes/index.tsx @@ -0,0 +1,22 @@ +import { createFileRoute } from "@tanstack/react-router"; +import StatusCard from "../components/StatusCard"; + +export const Route = createFileRoute("/")({ + component: Home, +}); + +function Home() { + return ( +
+
+

Standalone remote

+

This is a complete TanStack Start app.

+

+ It owns a route and a server build, then exposes the same component to the host through + Module Federation. +

+
+ +
+ ); +} diff --git a/apps/remote/src/styles.css b/apps/remote/src/styles.css new file mode 100644 index 0000000..2db3a3d --- /dev/null +++ b/apps/remote/src/styles.css @@ -0,0 +1,59 @@ +:root { + color: #e9efe9; + background: #101411; + font-family: "Avenir Next", "Trebuchet MS", sans-serif; + font-synthesis: none; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-width: 320px; + min-height: 100vh; + background: radial-gradient(circle at 75% 12%, rgba(231, 155, 76, 0.2), transparent 32%), #101411; +} + +button { + font: inherit; +} + +.remote-page { + display: grid; + grid-template-columns: minmax(0, 1.2fr) minmax(320px, 0.8fr); + align-items: center; + gap: 70px; + width: min(1050px, calc(100% - 40px)); + min-height: 100vh; + margin: 0 auto; +} + +.eyebrow { + font-family: "SFMono-Regular", Consolas, monospace; + color: #f4a95f; + font-size: 0.75rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +h1 { + margin: 18px 0 20px; + font-size: clamp(3rem, 6vw, 5.8rem); + line-height: 0.96; + letter-spacing: -0.06em; +} + +.lede { + color: #aeb9b1; + line-height: 1.65; +} + +@media (max-width: 780px) { + .remote-page { + grid-template-columns: 1fr; + gap: 40px; + padding: 56px 0; + } +} diff --git a/apps/remote/tsconfig.json b/apps/remote/tsconfig.json new file mode 100644 index 0000000..189cae2 --- /dev/null +++ b/apps/remote/tsconfig.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts"], + "compilerOptions": { + "allowImportingTsExtensions": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "types": ["node", "vite/client"], + "verbatimModuleSyntax": true + } +} diff --git a/apps/remote/vite.config.ts b/apps/remote/vite.config.ts new file mode 100644 index 0000000..146faf2 --- /dev/null +++ b/apps/remote/vite.config.ts @@ -0,0 +1,32 @@ +import { tanstackStartModuleFederation } from "@module-federation/tanstack"; +import { tanstackStart } from "@tanstack/react-start/plugin/vite"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + tanstackStartModuleFederation({ + name: "tanstack_remote", + dts: false, + exposes: { + "./StatusCard": "./src/components/StatusCard.tsx", + }, + }), + tanstackStart(), + react(), + ], + server: { + port: 3001, + cors: true, + origin: "http://127.0.0.1:3001", + }, + preview: { + port: 3001, + cors: true, + }, + ssr: { + optimizeDeps: { + include: ["react", "react-dom"], + }, + }, +}); diff --git a/docs/RELEASING.md b/docs/RELEASING.md index e7538a7..1119725 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -41,6 +41,6 @@ The publish workflow reruns the gate, checks that the source is reachable from a different dist-tag. Configure npm trusted publishing for repository `module-federation/tanstack`, workflow `release.yml`, environment `Publish`. -There are no committed app fixtures yet. The next integration matrix should -cover TanStack Start client and SSR builds on Vite 7 and 8, host/remote module -entries, manifest output, React singleton identity, and Nitro externalization. +The release gate builds both TanStack Start examples and checks the host server +bundle plus the remote federation manifest and entries. Browser-level coverage +for SSR markup, hydration, and remote failure recovery remains follow-up work. diff --git a/package.json b/package.json index 7ef2a35..f423342 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,13 @@ "scripts": { "build": "turbo run build", "build:package": "pnpm --filter @module-federation/tanstack build", - "dev": "turbo run dev", - "test": "pnpm build:package && node --test --test-concurrency=1 test/*.test.mjs", + "dev": "pnpm build:package && turbo run dev", + "test": "pnpm build && node --test --test-concurrency=1 test/*.test.mjs", "typecheck": "turbo run typecheck", "format": "prettier --write .", "format:check": "prettier --check .", "pack:tanstack": "pnpm --filter @module-federation/tanstack pack:dry", + "start": "pnpm build:package && turbo run start", "changeset": "changeset", "changeset:status": "changeset status" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e2b0d4..8c8b656 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,6 +21,86 @@ importers: specifier: 6.0.3 version: 6.0.3 + apps/host: + dependencies: + '@module-federation/runtime': + specifier: 2.9.0 + version: 2.9.0 + '@tanstack/react-router': + specifier: 1.170.38 + version: 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start': + specifier: 1.168.56 + version: 1.168.56(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + react: + specifier: 19.3.0 + version: 19.3.0 + react-dom: + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) + devDependencies: + '@module-federation/tanstack': + specifier: workspace:* + version: link:../../packages/tanstack + '@types/node': + specifier: 24.10.1 + version: 24.10.1 + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) + '@vitejs/plugin-react': + specifier: 6.1.1 + version: 6.1.1(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + typescript: + specifier: 6.0.3 + version: 6.0.3 + vite: + specifier: 8.3.0 + version: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + + apps/remote: + dependencies: + '@module-federation/runtime': + specifier: 2.9.0 + version: 2.9.0 + '@tanstack/react-router': + specifier: 1.170.38 + version: 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start': + specifier: 1.168.56 + version: 1.168.56(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + react: + specifier: 19.3.0 + version: 19.3.0 + react-dom: + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) + devDependencies: + '@module-federation/tanstack': + specifier: workspace:* + version: link:../../packages/tanstack + '@types/node': + specifier: 24.10.1 + version: 24.10.1 + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) + '@vitejs/plugin-react': + specifier: 6.1.1 + version: 6.1.1(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + typescript: + specifier: 6.0.3 + version: 6.0.3 + vite: + specifier: 8.3.0 + version: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + packages/tanstack: dependencies: '@module-federation/vite': @@ -378,6 +458,13 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' + '@tanstack/react-router@1.170.38': + resolution: {integrity: sha512-iHM9b0aDJbuvftmkiQXawzoqsdV68p2Re2safbVLCnxafe+iLKV++2i3hI/BMAP6uR92/Mjw94JKJJfD7pbwHQ==} + engines: {node: '>=20.19'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + '@tanstack/react-start-client@1.168.34': resolution: {integrity: sha512-rNwogcDo5A9xeKmGHIocI50nlqtlEr5lcgbcdW9gCtDEloXALdwFtXriEj3kHZe7BLvn9FItZESZcC3xo4/71w==} engines: {node: '>=22.12.0'} @@ -385,6 +472,13 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' + '@tanstack/react-start-client@1.168.36': + resolution: {integrity: sha512-TRXZKag/Ng0TqVfcN4cbEDVJhEZYosHZ9/VAEhK3PK/YqznytK+bcoonE4jP5NCXMdH2YRTMsrWKrApEuWz0Cg==} + engines: {node: '>=22.12.0'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + '@tanstack/react-start-rsc@0.1.53': resolution: {integrity: sha512-Hzwpd74HyvTFU4rEY9PpbUaYJ7BA3deCg0p+Dyj3NTFsTnULD+/Eba87u1QPl5UEVsKWyIG9J7g4J6JZpxFWSg==} engines: {node: '>=22.12.0'} @@ -402,6 +496,23 @@ packages: react-server-dom-rspack: optional: true + '@tanstack/react-start-rsc@0.1.55': + resolution: {integrity: sha512-qfW89LMrewtMLHMCumMP3+3mf2gPezFE7FZ7CHPJis4paxt2A6Jl7eCpE6whYgc5DFiifUv9qdfbC22HIP0HAw==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@rspack/core': '>=2.0.0-0' + '@vitejs/plugin-rsc': '>=0.5.30' + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + react-server-dom-rspack: '>=0.0.2' + peerDependenciesMeta: + '@rspack/core': + optional: true + '@vitejs/plugin-rsc': + optional: true + react-server-dom-rspack: + optional: true + '@tanstack/react-start-server@1.167.41': resolution: {integrity: sha512-ng4M05+vPug8rJH4OzF6u45JRSn+MThpjGCzkXmBgW3w/6TRkzWHQMO5/tBzpERJhUHp+uk178ZfFJVw0SWYpA==} engines: {node: '>=22.12.0'} @@ -409,6 +520,13 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' + '@tanstack/react-start-server@1.167.43': + resolution: {integrity: sha512-/Fc1mOEBAyXmqxnihIkS1DrCcJX8ffrJ+xyc8nnFmO9YuCdrnZJskd48R1ZiOhM7u3vCPzDxUycIhKo8HlKn2g==} + engines: {node: '>=22.12.0'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + '@tanstack/react-start@1.168.54': resolution: {integrity: sha512-x0GWvFm1e4WtgETxDE0xVWa39HdEnoinKQ9SU8pvO187SDsSpudYuhr8StfWdLE5iRhziGchLqlPl6UccUZ46w==} engines: {node: '>=22.12.0'} @@ -426,6 +544,23 @@ packages: vite: optional: true + '@tanstack/react-start@1.168.56': + resolution: {integrity: sha512-b/jVJS+yt7J0IKwXfVoA2mlhIi92sWfwCYrCVked0WERzgbYAad6xrOwNTljz9OKyeajofGrmMdKJgRzK1Lt2w==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@rsbuild/core': ^2.0.0 + '@vitejs/plugin-rsc': '*' + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + vite: '>=7.0.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@vitejs/plugin-rsc': + optional: true + vite: + optional: true + '@tanstack/react-store@0.11.1': resolution: {integrity: sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ==} peerDependencies: @@ -436,10 +571,18 @@ packages: resolution: {integrity: sha512-wtS1yCAb/k5CJBauvrCyqf2n4/kRc3aiz81tFBJ1QXH4Utsu81xFTMq15AdmNWgWmt2UstUSMfWi4Ua7ph2F+w==} engines: {node: '>=20.19'} + '@tanstack/router-core@1.171.32': + resolution: {integrity: sha512-X86Jqk3vB2KJcUfS6oLi7B7yxbaa59QEhZRPPP1fRx3k+Jw/Fu1UYVBcRtdwK/OccQnrlQdVoKvNO3QXmyEBOw==} + engines: {node: '>=20.19'} + '@tanstack/router-generator@1.167.36': resolution: {integrity: sha512-9Sjm1j8fkurviVIcbAXVdoyqAT8tlurY1tD9Na9Y59/BW5uWT5/5U+eU4Nv/d5grBKyosiJCP7/pcADvstnEgg==} engines: {node: '>=20.19'} + '@tanstack/router-generator@1.167.38': + resolution: {integrity: sha512-lkqgFleDgfkW+QONVMKBs+ZGDUF0v9R9FkplS/GZvKDFpfnu+tZxmGlV2pryU1TDxyuSyjh56AtIla3NMrlScw==} + engines: {node: '>=20.19'} + '@tanstack/router-plugin@1.168.38': resolution: {integrity: sha512-0SWwASONOiy7biNBvzdmfhKWwhxa+oJUZHIAdsAEAK+H1sftLWcE3b4sil5omjIIAlPmBJLk2JuUnfBWrn0iIg==} engines: {node: '>=20.19'} @@ -461,6 +604,27 @@ packages: webpack: optional: true + '@tanstack/router-plugin@1.168.40': + resolution: {integrity: sha512-ifiXjjR4uivxwRDhgYAcfyrcu+Mwx+vlG0QjjQ7N5C5sKmzpt5UtsL21TNVvZDSZl2Vae8DcL84Z02fOs8ZZMQ==} + engines: {node: '>=20.19'} + peerDependencies: + '@rsbuild/core': '>=1.0.2 || ^2.0.0' + '@tanstack/react-router': ^1.170.38 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' + vite-plugin-solid: ^2.11.10 || ^3.0.0-0 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + '@tanstack/router-utils@1.162.3': resolution: {integrity: sha512-Icb0xGuG1+54IV0WMLRcc3ErTx2HeJWyPG661FkQ8UT6guoBq1FJjFRqlG/JS0xi4mFFkBhvwO7tbLa32f3yxA==} engines: {node: '>=20.19'} @@ -469,6 +633,10 @@ packages: resolution: {integrity: sha512-eyNrOJumsx68Od+//eVWgQdQAdleY9bzbJ/N9/q5x/pqHdtaSxHYqMERPAmB7XcYrMoF0Bo9LcP1TfXqvqk69g==} engines: {node: '>=22.12.0'} + '@tanstack/start-client-core@1.170.32': + resolution: {integrity: sha512-kawgeg3Ej/JvGeEN7txFdVN9kXOST9wl7yPojOGESiomwiubHKRdYUS6sBskW91j44H+HirQUppP4+Ha7kjb4A==} + engines: {node: '>=22.12.0'} + '@tanstack/start-fn-stubs@1.162.0': resolution: {integrity: sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==} engines: {node: '>=22.12.0'} @@ -485,14 +653,34 @@ packages: vite: optional: true + '@tanstack/start-plugin-core@1.171.46': + resolution: {integrity: sha512-d/cDPZNDdRl824mOamt6USO1ja5JAaIwkuT2FIBRkBdrKZgQ8GJWv5pqpkXMbKANc49V8bUBBOTjgiCdC7YsPw==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@rsbuild/core': ^2.0.0 + vite: '>=7.0.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + vite: + optional: true + '@tanstack/start-server-core@1.169.35': resolution: {integrity: sha512-xy6DB1EFznF9d7r+lqj35+Gz0alreCh8tO9+yPepANab+WZagQEDjFwHSY9vrv3lzBWbMlRrkkGG1OZWOSL48w==} engines: {node: '>=22.12.0'} + '@tanstack/start-server-core@1.169.37': + resolution: {integrity: sha512-CjYMXg2XISMEJt9UVR4lbMRsnRyWPrdyuF8iuJ4gxTc1sVNRciwMVpMPVGQG2KuIma3ewlNxw8+Tpx2tnS3zRA==} + engines: {node: '>=22.12.0'} + '@tanstack/start-storage-context@1.167.32': resolution: {integrity: sha512-Di2mLQPXy1oPFU0o9jb0oMA/XXX9sJc+FEMJo3PMiSyB6cPnbAjsR1VgvavfsCBMi7kOE+ctdK+ELDX2MwKvIA==} engines: {node: '>=22.12.0'} + '@tanstack/start-storage-context@1.167.34': + resolution: {integrity: sha512-cRPE0kjt1CnOIhepmNOvRFwCGhDAnCRohPD3HmzqJJThPkixLBRuLDvIYp4hRPtM1TJ9hiKbYl24IxAhjZO5TA==} + engines: {node: '>=22.12.0'} + '@tanstack/store@0.11.1': resolution: {integrity: sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA==} @@ -533,6 +721,30 @@ packages: '@types/node@24.10.1': resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/react-dom@19.3.0': + resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} + peerDependencies: + '@types/react': ^19.3.0 + + '@types/react@19.3.0': + resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} + + '@vitejs/plugin-react@6.1.1': + resolution: {integrity: sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + oxc-transform-react: ^0.145.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + oxc-transform-react: + optional: true + '@yuku-codegen/binding-android-arm64@0.10.2': resolution: {integrity: sha512-Oc2KInVkPfUjEB4PeV6X0NvIoyYTzDe/WmqFrTwBqKs6YKnz4A7XwWQvJTb7M0rLz9a6WgpUUiiS5QKaCi4O/g==} cpu: [arm64] @@ -697,6 +909,9 @@ packages: cookie-es@3.1.1: resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1638,6 +1853,15 @@ snapshots: react: 19.3.0 react-dom: 19.3.0(react@19.3.0) + '@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@tanstack/history': 1.162.4 + '@tanstack/react-store': 0.11.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.32 + isbot: 5.2.2 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + '@tanstack/react-start-client@1.168.34(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -1646,6 +1870,14 @@ snapshots: react: 19.3.0 react-dom: 19.3.0(react@19.3.0) + '@tanstack/react-start-client@1.168.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.32 + '@tanstack/start-client-core': 1.170.32 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + '@tanstack/react-start-rsc@0.1.53(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -1672,6 +1904,32 @@ snapshots: - vite-plugin-solid - webpack + '@tanstack/react-start-rsc@0.1.55(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.32 + '@tanstack/router-utils': 1.162.3 + '@tanstack/start-client-core': 1.170.32 + '@tanstack/start-fn-stubs': 1.162.0 + '@tanstack/start-plugin-core': 1.171.46(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/start-storage-context': 1.167.34 + pathe: 2.0.3 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + transitivePeerDependencies: + - '@farmfe/core' + - '@rsbuild/core' + - bun-types-no-globals + - crossws + - esbuild + - rolldown + - rollup + - supports-color + - unloader + - vite + - vite-plugin-solid + - webpack + '@tanstack/react-start-server@1.167.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -1682,6 +1940,16 @@ snapshots: transitivePeerDependencies: - crossws + '@tanstack/react-start-server@1.167.43(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.32 + '@tanstack/start-server-core': 1.169.37 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + transitivePeerDependencies: + - crossws + '@tanstack/react-start@1.168.54(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -1711,6 +1979,35 @@ snapshots: - vite-plugin-solid - webpack + '@tanstack/react-start@1.168.56(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-client': 1.168.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-rsc': 0.1.55(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/react-start-server': 1.167.43(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-utils': 1.162.3 + '@tanstack/start-client-core': 1.170.32 + '@tanstack/start-plugin-core': 1.171.46(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/start-server-core': 1.169.37 + pathe: 2.0.3 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - crossws + - esbuild + - react-server-dom-rspack + - rolldown + - rollup + - supports-color + - unloader + - vite-plugin-solid + - webpack + '@tanstack/react-store@0.11.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/store': 0.11.1 @@ -1725,6 +2022,13 @@ snapshots: seroval: 1.6.7 seroval-plugins: 1.6.7(seroval@1.6.7) + '@tanstack/router-core@1.171.32': + dependencies: + '@tanstack/history': 1.162.4 + cookie-es: 3.1.1 + seroval: 1.6.7 + seroval-plugins: 1.6.7(seroval@1.6.7) + '@tanstack/router-generator@1.167.36': dependencies: '@babel/types': 7.29.8 @@ -1738,6 +2042,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@tanstack/router-generator@1.167.38': + dependencies: + '@babel/types': 7.29.8 + '@tanstack/router-core': 1.171.32 + '@tanstack/router-utils': 1.162.3 + '@tanstack/virtual-file-routes': 1.162.0 + jiti: 2.7.0 + magic-string: 0.30.21 + prettier: 3.9.7 + zod: 4.6.5 + transitivePeerDependencies: + - supports-color + '@tanstack/router-plugin@1.168.38(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@babel/core': 7.29.7 @@ -1762,6 +2079,30 @@ snapshots: - supports-color - unloader + '@tanstack/router-plugin@1.168.40(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + '@babel/core': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + '@tanstack/router-core': 1.171.32 + '@tanstack/router-generator': 1.167.38 + '@tanstack/router-utils': 1.162.3 + chokidar: 5.0.0 + unplugin: 3.4.0(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + zod: 4.6.5 + optionalDependencies: + '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - rolldown + - rollup + - supports-color + - unloader + '@tanstack/router-utils@1.162.3': dependencies: '@babel/generator': 7.29.8 @@ -1782,6 +2123,13 @@ snapshots: '@tanstack/start-storage-context': 1.167.32 seroval: 1.6.7 + '@tanstack/start-client-core@1.170.32': + dependencies: + '@tanstack/router-core': 1.171.32 + '@tanstack/start-fn-stubs': 1.162.0 + '@tanstack/start-storage-context': 1.167.34 + seroval: 1.6.7 + '@tanstack/start-fn-stubs@1.162.0': {} '@tanstack/start-plugin-core@1.171.44(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': @@ -1823,6 +2171,45 @@ snapshots: - vite-plugin-solid - webpack + '@tanstack/start-plugin-core@1.171.46(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + '@tanstack/router-core': 1.171.32 + '@tanstack/router-generator': 1.167.38 + '@tanstack/router-plugin': 1.168.40(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/router-utils': 1.162.3 + '@tanstack/start-server-core': 1.169.37 + exsolve: 1.1.1 + lightningcss: 1.33.0 + pathe: 2.0.3 + picomatch: 4.0.7 + seroval: 1.6.7 + source-map: 0.7.6 + srvx: 0.11.22 + tinyglobby: 0.2.17 + ufo: 1.6.4 + vitefu: 1.1.3(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + xmlbuilder2: 4.0.3 + zod: 4.6.5 + optionalDependencies: + vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - '@tanstack/react-router' + - bun-types-no-globals + - crossws + - esbuild + - rolldown + - rollup + - supports-color + - unloader + - vite-plugin-solid + - webpack + '@tanstack/start-server-core@1.169.35': dependencies: '@tanstack/history': 1.162.4 @@ -1835,10 +2222,26 @@ snapshots: transitivePeerDependencies: - crossws + '@tanstack/start-server-core@1.169.37': + dependencies: + '@tanstack/history': 1.162.4 + '@tanstack/router-core': 1.171.32 + '@tanstack/start-client-core': 1.170.32 + '@tanstack/start-storage-context': 1.167.34 + fetchdts: 0.1.7 + h3-v2: h3@2.0.1-rc.20 + seroval: 1.6.7 + transitivePeerDependencies: + - crossws + '@tanstack/start-storage-context@1.167.32': dependencies: '@tanstack/router-core': 1.171.30 + '@tanstack/start-storage-context@1.167.34': + dependencies: + '@tanstack/router-core': 1.171.32 + '@tanstack/store@0.11.1': {} '@tanstack/virtual-file-routes@1.162.0': {} @@ -1865,6 +2268,19 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/react-dom@19.3.0(@types/react@19.3.0)': + dependencies: + '@types/react': 19.3.0 + + '@types/react@19.3.0': + dependencies: + csstype: 3.2.3 + + '@vitejs/plugin-react@6.1.1(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + '@yuku-codegen/binding-android-arm64@0.10.2': optional: true @@ -1976,6 +2392,8 @@ snapshots: cookie-es@3.1.1: {} + csstype@3.2.3: {} + debug@4.4.3: dependencies: ms: 2.1.3 diff --git a/test/example-build.test.mjs b/test/example-build.test.mjs new file mode 100644 index 0000000..7ed7d7d --- /dev/null +++ b/test/example-build.test.mjs @@ -0,0 +1,53 @@ +import assert from "node:assert/strict"; +import { existsSync, readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import test from "node:test"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const hostDist = join(root, "apps", "host", "dist"); +const remoteDist = join(root, "apps", "remote", "dist"); + +function readJson(path) { + return JSON.parse(readFileSync(path, "utf8")); +} + +test("both TanStack Start apps produce client and server builds", () => { + for (const [name, dist] of [ + ["host", hostDist], + ["remote", remoteDist], + ]) { + assert.ok(existsSync(join(dist, "client", ".vite", "manifest.json")), `${name} client`); + assert.ok(existsSync(join(dist, "server", "server.js")), `${name} server`); + } +}); + +test("remote publishes browser and server federation entries", () => { + const client = join(remoteDist, "client"); + assert.ok(existsSync(join(client, "remoteEntry.js"))); + assert.ok(existsSync(join(client, "remoteEntry.ssr.js"))); + + const manifest = readJson(join(client, "mf-manifest.json")); + assert.equal(manifest.name, "tanstack_remote"); + assert.equal(manifest.metaData.remoteEntry.name, "remoteEntry.js"); + assert.equal(manifest.metaData.ssrRemoteEntry.name, "remoteEntry.ssr.js"); + + const statusCard = manifest.exposes.find(({ path }) => path === "./StatusCard"); + assert.ok(statusCard, "StatusCard expose is listed"); + assert.ok(statusCard.assets.css.sync.length > 0, "StatusCard CSS is published"); + + for (const dependency of ["react", "react-dom"]) { + const shared = manifest.shared.find(({ name }) => name === dependency); + assert.equal(shared?.singleton, true, `${dependency} is a singleton`); + } +}); + +test("host records the TanStack remote and server-side loader", () => { + const manifest = readJson(join(hostDist, "client", "mf-manifest.json")); + const remote = manifest.remotes.find(({ alias }) => alias === "tanstack_remote"); + assert.equal(remote?.moduleName, "StatusCard"); + + const serverFiles = readFileSync(join(hostDist, "server", ".vite", "manifest.json"), "utf8"); + assert.match(serverFiles, /ssrEntryLoader/); + assert.match(serverFiles, /tanstack_remote/); +}); diff --git a/test/example-runtime.test.mjs b/test/example-runtime.test.mjs new file mode 100644 index 0000000..b4f92cd --- /dev/null +++ b/test/example-runtime.test.mjs @@ -0,0 +1,99 @@ +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import test from "node:test"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); + +function startApp(name) { + const child = spawn("pnpm", ["--filter", name, "start"], { + cwd: root, + detached: true, + env: { ...process.env, NODE_ENV: "development" }, + stdio: ["ignore", "pipe", "pipe"], + }); + child.output = ""; + + for (const stream of [child.stdout, child.stderr]) { + stream.on("data", (chunk) => { + child.output = `${child.output}${chunk}`.slice(-12_000); + }); + } + + return child; +} + +async function waitForResponse(url, child, predicate = () => true) { + const deadline = Date.now() + 20_000; + let lastError; + let lastResponse; + + while (Date.now() < deadline) { + if (child.exitCode !== null || child.signalCode !== null) { + assert.fail(`server stopped while waiting for ${url}\n${child.output}`); + } + + try { + const response = await fetch(url, { signal: AbortSignal.timeout(2_000) }); + lastResponse = { body: await response.text(), status: response.status }; + if (predicate(lastResponse)) return lastResponse; + } catch (error) { + lastError = error; + } + + await new Promise((resolve) => setTimeout(resolve, 150)); + } + + assert.fail( + [ + `timed out waiting for ${url}`, + lastResponse ? `last status: ${lastResponse.status}` : undefined, + lastError ? `last error: ${lastError.message}` : undefined, + child.output, + ] + .filter(Boolean) + .join("\n"), + ); +} + +async function stopApp(child) { + if (child.exitCode !== null || child.signalCode !== null) return; + process.kill(-child.pid, "SIGTERM"); + + await Promise.race([ + new Promise((resolve) => child.once("exit", resolve)), + new Promise((resolve) => setTimeout(resolve, 3_000)), + ]); + + if (child.exitCode === null && child.signalCode === null) { + process.kill(-child.pid, "SIGKILL"); + } +} + +test("host server-renders a component from the TanStack remote", async () => { + const remote = startApp("tanstack-start-remote"); + let host; + + try { + const remoteEntry = await waitForResponse( + "http://127.0.0.1:3001/remoteEntry.js", + remote, + ({ status }) => status === 200, + ); + assert.match(remoteEntry.body, /virtual:mf-exposes/); + + host = startApp("tanstack-start-host"); + const response = await waitForResponse( + "http://127.0.0.1:3000/", + host, + ({ body, status }) => status === 200 && body.includes("Owned by the remote app"), + ); + + assert.match(response.body, /Two full-stack apps\. One React tree\./); + assert.match(response.body, /Rendered on the server/); + } finally { + if (host) await stopApp(host); + await stopApp(remote); + } +}); diff --git a/test/release-contracts.test.mjs b/test/release-contracts.test.mjs index d7739ed..9413310 100644 --- a/test/release-contracts.test.mjs +++ b/test/release-contracts.test.mjs @@ -11,6 +11,7 @@ const packageRequire = createRequire(join(packageRoot, "package.json")); const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")); const rootPackageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); const ciWorkflow = readFileSync(join(root, ".github", "workflows", "ci.yml"), "utf8"); +const previewWorkflow = readFileSync(join(root, ".github", "workflows", "pkg-pr-new.yml"), "utf8"); const releaseWorkflow = readFileSync(join(root, ".github", "workflows", "release.yml"), "utf8"); const { tanstackStartModuleFederation } = await import(join(packageRoot, "dist", "index.mjs")); @@ -114,3 +115,8 @@ test("npm publications share one package-wide concurrency queue", () => { assert.match(releaseWorkflow, /group: publish-module-federation-tanstack/); assert.doesNotMatch(releaseWorkflow, /group:.*github\.(?:ref|run)/); }); + +test("first package preview does not require an existing npm release", () => { + assert.match(previewWorkflow, /pkg-pr-new@0\.0\.54 publish \.\/packages\/tanstack/); + assert.doesNotMatch(previewWorkflow, /--compact/); +}); diff --git a/tsconfig.json b/tsconfig.json index 8de1016..2aa37eb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,9 @@ { "$schema": "https://json.schemastore.org/tsconfig", "files": [], - "references": [{ "path": "./packages/tanstack/tsconfig.json" }] + "references": [ + { "path": "./packages/tanstack/tsconfig.json" }, + { "path": "./apps/host/tsconfig.json" }, + { "path": "./apps/remote/tsconfig.json" } + ] } diff --git a/turbo.json b/turbo.json index 882620b..5c6afdd 100644 --- a/turbo.json +++ b/turbo.json @@ -10,6 +10,10 @@ "cache": false, "persistent": true }, + "start": { + "cache": false, + "persistent": true + }, "typecheck": { "dependsOn": ["^build", "^typecheck"], "inputs": ["$TURBO_DEFAULT$", ".env*"], From 895417c527f03dd75d32c4147dccedfd80338dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Fri, 18 Sep 2026 10:34:02 +0200 Subject: [PATCH 03/18] feat: add Rsbuild adapter and interop examples --- .gitignore | 2 + README.md | 60 +- apps/README.md | 9 +- apps/host/src/remote.d.ts | 7 + apps/host/src/routes/index.tsx | 21 +- apps/host/vite.config.ts | 5 + apps/rsbuild-host/package.json | 29 + apps/rsbuild-host/rsbuild.config.ts | 24 + apps/rsbuild-host/src/assets.d.ts | 4 + .../src/components/RemoteCardSlot.tsx | 38 + apps/rsbuild-host/src/routeTree.gen.ts | 68 + apps/rsbuild-host/src/router.tsx | 16 + apps/rsbuild-host/src/routes/__root.tsx | 36 + apps/rsbuild-host/src/routes/index.tsx | 21 + apps/rsbuild-host/src/styles.css | 32 + apps/rsbuild-host/tsconfig.json | 20 + apps/rsbuild-remote/package.json | 29 + apps/rsbuild-remote/rsbuild.config.ts | 24 + apps/rsbuild-remote/src/assets.d.ts | 6 + .../src/components/StatusCard.css | 23 + .../src/components/StatusCard.tsx | 16 + apps/rsbuild-remote/src/routeTree.gen.ts | 68 + apps/rsbuild-remote/src/router.tsx | 12 + apps/rsbuild-remote/src/routes/__root.tsx | 36 + apps/rsbuild-remote/src/routes/index.tsx | 14 + apps/rsbuild-remote/src/styles.css | 22 + apps/rsbuild-remote/tsconfig.json | 20 + docs/RELEASING.md | 8 +- packages/tanstack/README.md | 50 +- packages/tanstack/package.json | 71 +- packages/tanstack/rslib.config.ts | 36 + packages/tanstack/src/index.ts | 52 +- packages/tanstack/src/rsbuild.ts | 177 ++ packages/tanstack/src/shared.ts | 31 + packages/tanstack/src/vite.ts | 21 + packages/tanstack/tsconfig.json | 1 + packages/tanstack/tsdown.config.ts | 19 - pnpm-lock.yaml | 1602 +++++++++-------- test/example-build.test.mjs | 54 +- test/example-runtime.test.mjs | 45 + test/release-artifacts.test.mjs | 54 +- test/release-contracts.test.mjs | 217 ++- tsconfig.json | 4 +- 43 files changed, 2218 insertions(+), 886 deletions(-) create mode 100644 apps/rsbuild-host/package.json create mode 100644 apps/rsbuild-host/rsbuild.config.ts create mode 100644 apps/rsbuild-host/src/assets.d.ts create mode 100644 apps/rsbuild-host/src/components/RemoteCardSlot.tsx create mode 100644 apps/rsbuild-host/src/routeTree.gen.ts create mode 100644 apps/rsbuild-host/src/router.tsx create mode 100644 apps/rsbuild-host/src/routes/__root.tsx create mode 100644 apps/rsbuild-host/src/routes/index.tsx create mode 100644 apps/rsbuild-host/src/styles.css create mode 100644 apps/rsbuild-host/tsconfig.json create mode 100644 apps/rsbuild-remote/package.json create mode 100644 apps/rsbuild-remote/rsbuild.config.ts create mode 100644 apps/rsbuild-remote/src/assets.d.ts create mode 100644 apps/rsbuild-remote/src/components/StatusCard.css create mode 100644 apps/rsbuild-remote/src/components/StatusCard.tsx create mode 100644 apps/rsbuild-remote/src/routeTree.gen.ts create mode 100644 apps/rsbuild-remote/src/router.tsx create mode 100644 apps/rsbuild-remote/src/routes/__root.tsx create mode 100644 apps/rsbuild-remote/src/routes/index.tsx create mode 100644 apps/rsbuild-remote/src/styles.css create mode 100644 apps/rsbuild-remote/tsconfig.json create mode 100644 packages/tanstack/rslib.config.ts create mode 100644 packages/tanstack/src/rsbuild.ts create mode 100644 packages/tanstack/src/shared.ts create mode 100644 packages/tanstack/src/vite.ts delete mode 100644 packages/tanstack/tsdown.config.ts diff --git a/.gitignore b/.gitignore index 09af92d..cc0b184 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ node_modules/ dist/ .turbo/ +.mf/ +@mf-types/ *.log pnpm-debug.log* coverage/ diff --git a/README.md b/README.md index 514a256..daa6e7c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # @module-federation/tanstack -Module Federation support for TanStack Start, built on the Vite integration. +Module Federation support for TanStack Start on Vite and Rsbuild. Rsbuild uses +Rspack under the hood. ## Install @@ -8,16 +9,24 @@ Module Federation support for TanStack Start, built on the Vite integration. pnpm add @module-federation/tanstack @tanstack/react-start ``` +Install the adapter for the bundler you use. They are optional peers so an +Rsbuild-only application does not install or resolve the Vite adapter, and a +Vite-only application does not install or resolve the Rsbuild adapter. + +```bash +pnpm add @module-federation/vite vite # Vite +pnpm add @module-federation/rsbuild-plugin @rsbuild/core # Rsbuild +``` + ## Configure The federation wrapper must come before `tanstackStart()` so Module Federation can normalize TanStack's client and SSR entrypoints: ```ts -import { tanstackStartModuleFederation } from "@module-federation/tanstack"; +import { tanstackStartModuleFederation } from "@module-federation/tanstack/vite"; import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import react from "@vitejs/plugin-react"; -import { nitro } from "nitro/vite"; import { defineConfig } from "vite"; export default defineConfig({ @@ -34,7 +43,6 @@ export default defineConfig({ }), tanstackStart(), react(), - nitro(), ], }); ``` @@ -51,15 +59,51 @@ The package supports Node 22.18+, 24.11+, and 26+, plus Vite 7/8 and TanStack Start 1.x. Use Vite 8 when developing SSR hosts that load remotes; Vite 7 remains supported for production builds. +The package root remains a Vite compatibility export. New Vite projects can +import `/vite` explicitly. Rsbuild projects must import `/rsbuild`: + +```ts +import { defineConfig } from "@rsbuild/core"; +import { pluginReact } from "@rsbuild/plugin-react"; +import { tanstackStart } from "@tanstack/react-start/plugin/rsbuild"; +import { tanstackStartModuleFederation } from "@module-federation/tanstack/rsbuild"; + +export default defineConfig({ + plugins: [ + pluginReact(), + tanstackStart(), + ...tanstackStartModuleFederation({ + federation: { + name: "host", + remotes: { remote: "remote@https://example.test/mf-manifest.json" }, + }, + }), + ], +}); +``` + +The Rsbuild adapter registers browser and SSR federation plugins by default. It +emits script-compatible browser output and async-node CommonJS SSR output so +its manifest can be consumed by Vite and Rsbuild hosts. The wrapper adds eager, +singleton `react` and `react-dom` entries without changing the caller's config +object. Set `server: false` for browser-only remotes. + +Server federation mode emits `dist/server/index.cjs`; browser-only mode keeps +TanStack Start's standard `dist/server/index.js` entry. + ## Example -The workspace includes two TanStack Start apps. `apps/remote` exposes a -stateful React card, and `apps/host` consumes it through this package. +The workspace includes Vite and Rsbuild pairs. `apps/remote` exposes a +stateful React card to `apps/host`. `apps/rsbuild-remote` exposes a second card +to the Vite host, while `apps/rsbuild-host` consumes the Vite remote. Both +cross-bundler links use manifest URLs. They render after hydration, so this is +browser interop coverage, not an unverified SSR interoperability claim. ```bash pnpm install pnpm start ``` -Open the host at http://localhost:3000. The remote also runs as its own -TanStack Start application at http://localhost:3001. +This starts all four applications so both same-bundler and cross-bundler paths +are available. Open the Vite host at http://localhost:3000 or the Rsbuild host +at http://localhost:3003. diff --git a/apps/README.md b/apps/README.md index 113a75b..7f41b71 100644 --- a/apps/README.md +++ b/apps/README.md @@ -1,4 +1,4 @@ -# TanStack Start example +# TanStack Start examples Both sides of this example are TanStack Start applications. @@ -18,3 +18,10 @@ React and React DOM use the wrapper's singleton defaults. Each package's `start` script runs Vite's development server. In Vite's CLI, the bare `vite` command means “start”; the literal `vite start` command would treat `start` as a project directory. + +`rsbuild-remote` exposes the same kind of card through Rspack with a browser +manifest. Both hosts consume both remotes by manifest URL after hydration. +These examples exercise same-bundler and cross-bundler client interoperability; +they do not claim that cross-bundler SSR remote rendering is supported. + +`pnpm start` runs all four applications on ports 3000 through 3003. diff --git a/apps/host/src/remote.d.ts b/apps/host/src/remote.d.ts index e950d84..fe82049 100644 --- a/apps/host/src/remote.d.ts +++ b/apps/host/src/remote.d.ts @@ -4,3 +4,10 @@ declare module "tanstack_remote/StatusCard" { const StatusCard: ComponentType; export default StatusCard; } + +declare module "tanstack_rsbuild_remote/StatusCard" { + import type { ComponentType } from "react"; + + const StatusCard: ComponentType; + export default StatusCard; +} diff --git a/apps/host/src/routes/index.tsx b/apps/host/src/routes/index.tsx index bacdfcd..7a03a21 100644 --- a/apps/host/src/routes/index.tsx +++ b/apps/host/src/routes/index.tsx @@ -1,7 +1,8 @@ import { createFileRoute } from "@tanstack/react-router"; -import { lazy, Suspense } from "react"; +import { lazy, Suspense, useEffect, useState } from "react"; const RemoteStatusCard = lazy(() => import("tanstack_remote/StatusCard")); +const RsbuildStatusCard = lazy(() => import("tanstack_rsbuild_remote/StatusCard")); export const Route = createFileRoute("/")({ loader: () => import("tanstack_remote/StatusCard").then(() => null), @@ -31,7 +32,25 @@ function Home() { Loading the remote app…}> + + ); } + +function RsbuildRemoteSlot() { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) return
Rsbuild remote loads after hydration
; + + return ( + Loading the Rsbuild remote…}> + + + ); +} diff --git a/apps/host/vite.config.ts b/apps/host/vite.config.ts index 03ecd37..6832e5a 100644 --- a/apps/host/vite.config.ts +++ b/apps/host/vite.config.ts @@ -14,6 +14,11 @@ export default defineConfig({ name: "tanstack_remote", entry: "http://127.0.0.1:3001/remoteEntry.js", }, + tanstack_rsbuild_remote: { + type: "global", + name: "tanstack_rsbuild_remote", + entry: "http://127.0.0.1:3002/mf-manifest.json", + }, }, }), tanstackStart(), diff --git a/apps/rsbuild-host/package.json b/apps/rsbuild-host/package.json new file mode 100644 index 0000000..7491972 --- /dev/null +++ b/apps/rsbuild-host/package.json @@ -0,0 +1,29 @@ +{ + "name": "tanstack-start-rsbuild-host", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "build": "rsbuild build", + "dev": "rsbuild dev --port 3003 --strict-port", + "start": "rsbuild dev --host 127.0.0.1 --port 3003 --strict-port", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@module-federation/runtime": "2.9.0", + "@tanstack/react-router": "1.170.38", + "@tanstack/react-start": "1.168.56", + "react": "19.3.0", + "react-dom": "19.3.0" + }, + "devDependencies": { + "@module-federation/rsbuild-plugin": "2.9.0", + "@module-federation/tanstack": "workspace:*", + "@rsbuild/core": "2.2.8", + "@rsbuild/plugin-react": "2.1.0", + "@types/node": "24.10.1", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "typescript": "6.0.3" + } +} diff --git a/apps/rsbuild-host/rsbuild.config.ts b/apps/rsbuild-host/rsbuild.config.ts new file mode 100644 index 0000000..713e8b7 --- /dev/null +++ b/apps/rsbuild-host/rsbuild.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from "@rsbuild/core"; +import { pluginReact } from "@rsbuild/plugin-react"; +import { tanstackStart } from "@tanstack/react-start/plugin/rsbuild"; +import { tanstackStartModuleFederation } from "@module-federation/tanstack/rsbuild"; + +export default defineConfig({ + dev: { lazyCompilation: false }, + server: { host: "127.0.0.1", port: 3003 }, + plugins: [ + pluginReact(), + tanstackStart(), + ...tanstackStartModuleFederation({ + federation: { + dts: false, + name: "tanstack_rsbuild_host", + remotes: { + tanstack_remote: "tanstack_remote@http://127.0.0.1:3001/mf-manifest.json", + tanstack_rsbuild_remote: "tanstack_rsbuild_remote@http://127.0.0.1:3002/mf-manifest.json", + }, + }, + server: false, + }), + ], +}); diff --git a/apps/rsbuild-host/src/assets.d.ts b/apps/rsbuild-host/src/assets.d.ts new file mode 100644 index 0000000..596a189 --- /dev/null +++ b/apps/rsbuild-host/src/assets.d.ts @@ -0,0 +1,4 @@ +declare module "*.css?url" { + const href: string; + export default href; +} diff --git a/apps/rsbuild-host/src/components/RemoteCardSlot.tsx b/apps/rsbuild-host/src/components/RemoteCardSlot.tsx new file mode 100644 index 0000000..c99449f --- /dev/null +++ b/apps/rsbuild-host/src/components/RemoteCardSlot.tsx @@ -0,0 +1,38 @@ +import { loadRemote } from "@module-federation/runtime"; +import { useEffect, useState, type ComponentType } from "react"; + +type RemoteModule = { + default: ComponentType; +}; + +export function RemoteCardSlot({ fallback, remote }: { fallback: string; remote: string }) { + const [RemoteCard, setRemoteCard] = useState(null); + const [failed, setFailed] = useState(false); + + useEffect(() => { + let active = true; + setRemoteCard(null); + setFailed(false); + + loadRemote(remote) + .then((remoteModule) => { + if (!active) return; + if (remoteModule?.default) { + setRemoteCard(() => remoteModule.default); + } else { + setFailed(true); + } + }) + .catch(() => { + if (active) setFailed(true); + }); + + return () => { + active = false; + }; + }, [remote]); + + if (failed) return
Remote unavailable
; + if (!RemoteCard) return
{fallback}
; + return ; +} diff --git a/apps/rsbuild-host/src/routeTree.gen.ts b/apps/rsbuild-host/src/routeTree.gen.ts new file mode 100644 index 0000000..dceedff --- /dev/null +++ b/apps/rsbuild-host/src/routeTree.gen.ts @@ -0,0 +1,68 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' + fileRoutesByTo: FileRoutesByTo + to: '/' + id: '__root__' | '/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/apps/rsbuild-host/src/router.tsx b/apps/rsbuild-host/src/router.tsx new file mode 100644 index 0000000..6982ebb --- /dev/null +++ b/apps/rsbuild-host/src/router.tsx @@ -0,0 +1,16 @@ +import { createRouter } from "@tanstack/react-router"; +import { routeTree } from "./routeTree.gen"; + +export function getRouter() { + return createRouter({ + defaultPreload: "intent", + routeTree, + scrollRestoration: true, + }); +} + +declare module "@tanstack/react-router" { + interface Register { + router: ReturnType; + } +} diff --git a/apps/rsbuild-host/src/routes/__root.tsx b/apps/rsbuild-host/src/routes/__root.tsx new file mode 100644 index 0000000..2eb5fe8 --- /dev/null +++ b/apps/rsbuild-host/src/routes/__root.tsx @@ -0,0 +1,36 @@ +import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"; +import type { ReactNode } from "react"; +import stylesHref from "../styles.css?url"; + +export const Route = createRootRoute({ + head: () => ({ + links: [{ href: stylesHref, rel: "stylesheet" }], + meta: [ + { charSet: "utf-8" }, + { name: "viewport", content: "width=device-width, initial-scale=1" }, + ], + }), + component: Root, +}); + +function Root() { + return ( + + + + ); +} + +function Document({ children }: { children: ReactNode }) { + return ( + + + + + + {children} + + + + ); +} diff --git a/apps/rsbuild-host/src/routes/index.tsx b/apps/rsbuild-host/src/routes/index.tsx new file mode 100644 index 0000000..8a5c075 --- /dev/null +++ b/apps/rsbuild-host/src/routes/index.tsx @@ -0,0 +1,21 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { RemoteCardSlot } from "../components/RemoteCardSlot"; + +export const Route = createFileRoute("/")({ component: Home }); + +function Home() { + return ( +
+

Rsbuild host

+

Rspack host, two remote formats.

+

This TanStack Start app loads both remotes through the browser runtime.

+
+ + +
+
+ ); +} diff --git a/apps/rsbuild-host/src/styles.css b/apps/rsbuild-host/src/styles.css new file mode 100644 index 0000000..e379e6a --- /dev/null +++ b/apps/rsbuild-host/src/styles.css @@ -0,0 +1,32 @@ +:root { + background: #111; + color: #edf6ef; + font-family: system-ui, sans-serif; +} +body { + margin: 0; +} +.page-shell { + width: min(760px, calc(100% - 40px)); + margin: 0 auto; + padding: 80px 0; +} +.eyebrow { + color: #91f1b0; + font-family: monospace; + text-transform: uppercase; +} +h1 { + font-size: clamp(3rem, 10vw, 6rem); + margin: 12px 0; +} +.remote-loading { + border: 1px solid #4b6653; + margin-top: 36px; + padding: 30px; +} +.remote-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 20px; +} diff --git a/apps/rsbuild-host/tsconfig.json b/apps/rsbuild-host/tsconfig.json new file mode 100644 index 0000000..0416574 --- /dev/null +++ b/apps/rsbuild-host/tsconfig.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "include": ["src/**/*.ts", "src/**/*.tsx", "rsbuild.config.ts"], + "compilerOptions": { + "allowImportingTsExtensions": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/apps/rsbuild-remote/package.json b/apps/rsbuild-remote/package.json new file mode 100644 index 0000000..82947ab --- /dev/null +++ b/apps/rsbuild-remote/package.json @@ -0,0 +1,29 @@ +{ + "name": "tanstack-start-rsbuild-remote", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "build": "rsbuild build", + "dev": "rsbuild dev --port 3002 --strict-port", + "start": "rsbuild dev --host 127.0.0.1 --port 3002 --strict-port", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@module-federation/runtime": "2.9.0", + "@tanstack/react-router": "1.170.38", + "@tanstack/react-start": "1.168.56", + "react": "19.3.0", + "react-dom": "19.3.0" + }, + "devDependencies": { + "@module-federation/rsbuild-plugin": "2.9.0", + "@module-federation/tanstack": "workspace:*", + "@rsbuild/core": "2.2.8", + "@rsbuild/plugin-react": "2.1.0", + "@types/node": "24.10.1", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "typescript": "6.0.3" + } +} diff --git a/apps/rsbuild-remote/rsbuild.config.ts b/apps/rsbuild-remote/rsbuild.config.ts new file mode 100644 index 0000000..73bbc22 --- /dev/null +++ b/apps/rsbuild-remote/rsbuild.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from "@rsbuild/core"; +import { pluginReact } from "@rsbuild/plugin-react"; +import { tanstackStart } from "@tanstack/react-start/plugin/rsbuild"; +import { tanstackStartModuleFederation } from "@module-federation/tanstack/rsbuild"; + +export default defineConfig({ + dev: { lazyCompilation: false }, + server: { + cors: { origin: ["http://127.0.0.1:3000", "http://127.0.0.1:3003"] }, + host: "127.0.0.1", + port: 3002, + }, + plugins: [ + pluginReact(), + tanstackStart(), + ...tanstackStartModuleFederation({ + federation: { + exposes: { "./StatusCard": "./src/components/StatusCard.tsx" }, + name: "tanstack_rsbuild_remote", + }, + server: false, + }), + ], +}); diff --git a/apps/rsbuild-remote/src/assets.d.ts b/apps/rsbuild-remote/src/assets.d.ts new file mode 100644 index 0000000..18e9eca --- /dev/null +++ b/apps/rsbuild-remote/src/assets.d.ts @@ -0,0 +1,6 @@ +declare module "*.css"; + +declare module "*.css?url" { + const href: string; + export default href; +} diff --git a/apps/rsbuild-remote/src/components/StatusCard.css b/apps/rsbuild-remote/src/components/StatusCard.css new file mode 100644 index 0000000..f055223 --- /dev/null +++ b/apps/rsbuild-remote/src/components/StatusCard.css @@ -0,0 +1,23 @@ +.remote-card { + border: 1px solid #83502a; + background: #291c13; + color: #fff3e8; + padding: 30px; +} + +.remote-card .eyebrow { + color: #ffbc7c; + font-family: monospace; + text-transform: uppercase; +} + +.remote-card h2 { + font-size: 2rem; +} + +.remote-card button { + background: #ffbc7c; + border: 0; + cursor: pointer; + padding: 12px; +} diff --git a/apps/rsbuild-remote/src/components/StatusCard.tsx b/apps/rsbuild-remote/src/components/StatusCard.tsx new file mode 100644 index 0000000..20fedfb --- /dev/null +++ b/apps/rsbuild-remote/src/components/StatusCard.tsx @@ -0,0 +1,16 @@ +import { useState } from "react"; +import "./StatusCard.css"; + +export default function StatusCard() { + const [count, setCount] = useState(0); + return ( +
+

Federated from Rsbuild

+

One component, either host.

+

This card is built by Rspack and shared with the Vite host.

+ +
+ ); +} diff --git a/apps/rsbuild-remote/src/routeTree.gen.ts b/apps/rsbuild-remote/src/routeTree.gen.ts new file mode 100644 index 0000000..dceedff --- /dev/null +++ b/apps/rsbuild-remote/src/routeTree.gen.ts @@ -0,0 +1,68 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' + fileRoutesByTo: FileRoutesByTo + to: '/' + id: '__root__' | '/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/apps/rsbuild-remote/src/router.tsx b/apps/rsbuild-remote/src/router.tsx new file mode 100644 index 0000000..673839e --- /dev/null +++ b/apps/rsbuild-remote/src/router.tsx @@ -0,0 +1,12 @@ +import { createRouter } from "@tanstack/react-router"; +import { routeTree } from "./routeTree.gen"; + +export function getRouter() { + return createRouter({ defaultPreload: "intent", routeTree, scrollRestoration: true }); +} + +declare module "@tanstack/react-router" { + interface Register { + router: ReturnType; + } +} diff --git a/apps/rsbuild-remote/src/routes/__root.tsx b/apps/rsbuild-remote/src/routes/__root.tsx new file mode 100644 index 0000000..2eb5fe8 --- /dev/null +++ b/apps/rsbuild-remote/src/routes/__root.tsx @@ -0,0 +1,36 @@ +import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"; +import type { ReactNode } from "react"; +import stylesHref from "../styles.css?url"; + +export const Route = createRootRoute({ + head: () => ({ + links: [{ href: stylesHref, rel: "stylesheet" }], + meta: [ + { charSet: "utf-8" }, + { name: "viewport", content: "width=device-width, initial-scale=1" }, + ], + }), + component: Root, +}); + +function Root() { + return ( + + + + ); +} + +function Document({ children }: { children: ReactNode }) { + return ( + + + + + + {children} + + + + ); +} diff --git a/apps/rsbuild-remote/src/routes/index.tsx b/apps/rsbuild-remote/src/routes/index.tsx new file mode 100644 index 0000000..9cfe99e --- /dev/null +++ b/apps/rsbuild-remote/src/routes/index.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from "@tanstack/react-router"; +import StatusCard from "../components/StatusCard"; + +export const Route = createFileRoute("/")({ component: Home }); + +function Home() { + return ( +
+

Rsbuild remote

+

Rspack-built remote.

+ +
+ ); +} diff --git a/apps/rsbuild-remote/src/styles.css b/apps/rsbuild-remote/src/styles.css new file mode 100644 index 0000000..fbab7d3 --- /dev/null +++ b/apps/rsbuild-remote/src/styles.css @@ -0,0 +1,22 @@ +:root { + background: #16110d; + color: #fff3e8; + font-family: system-ui, sans-serif; +} +body { + margin: 0; +} +.page-shell { + width: min(760px, calc(100% - 40px)); + margin: 0 auto; + padding: 80px 0; +} +.eyebrow { + color: #ffbc7c; + font-family: monospace; + text-transform: uppercase; +} +h1 { + font-size: clamp(3rem, 10vw, 6rem); + margin: 12px 0 48px; +} diff --git a/apps/rsbuild-remote/tsconfig.json b/apps/rsbuild-remote/tsconfig.json new file mode 100644 index 0000000..0416574 --- /dev/null +++ b/apps/rsbuild-remote/tsconfig.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "include": ["src/**/*.ts", "src/**/*.tsx", "rsbuild.config.ts"], + "compilerOptions": { + "allowImportingTsExtensions": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 1119725..909b926 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -41,6 +41,8 @@ The publish workflow reruns the gate, checks that the source is reachable from a different dist-tag. Configure npm trusted publishing for repository `module-federation/tanstack`, workflow `release.yml`, environment `Publish`. -The release gate builds both TanStack Start examples and checks the host server -bundle plus the remote federation manifest and entries. Browser-level coverage -for SSR markup, hydration, and remote failure recovery remains follow-up work. +The release gate builds the Vite and Rsbuild example pairs. It checks the Vite +host server bundle and both directions of client manifest interoperability. The +Rsbuild example remote is browser-only; package tests cover the adapter's +async-node CommonJS SSR configuration. Browser-level coverage for hydrated +cross-bundler rendering remains follow-up work. diff --git a/packages/tanstack/README.md b/packages/tanstack/README.md index dad41f7..fee8af5 100644 --- a/packages/tanstack/README.md +++ b/packages/tanstack/README.md @@ -1,11 +1,14 @@ # @module-federation/tanstack -TanStack Start integration for Module Federation. It delegates to -`@module-federation/vite` and supplies defaults needed by TanStack's client and -SSR environments. +TanStack Start integration for Module Federation on Vite and Rsbuild. The +adapter packages are optional peers, so importing one adapter does not resolve +the other. + +The root import remains compatible with existing Vite configurations. New Vite +projects can use `/vite` explicitly: ```ts -import { tanstackStartModuleFederation } from "@module-federation/tanstack"; +import { tanstackStartModuleFederation } from "@module-federation/tanstack/vite"; import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import { defineConfig } from "vite"; @@ -26,8 +29,8 @@ export default defineConfig({ }); ``` -Place the federation wrapper before `tanstackStart()`, followed by React and -Nitro plugins. Defaults: `filename: "remoteEntry.js"`, `manifest: true`, +Place the federation wrapper before `tanstackStart()`, followed by the React +plugin. Defaults: `filename: "remoteEntry.js"`, `manifest: true`, `hostInitInjectLocation: "entry"`, and singleton `react`/`react-dom` shared entries. User-provided `shared` entries override those defaults. @@ -38,4 +41,39 @@ select `web` and `node` independently. Production builds support Vite 7 and 8. Vite 8 is required for SSR remote loading during development. +For TanStack Start's Rsbuild integration, use the dedicated adapter. It adds +separate browser and SSR Module Federation plugins so Rspack emits a browser +script remote and async-node CommonJS SSR remote. For a browser-only remote, +pass `server: false`: + +```ts +import { defineConfig } from "@rsbuild/core"; +import { pluginReact } from "@rsbuild/plugin-react"; +import { tanstackStart } from "@tanstack/react-start/plugin/rsbuild"; +import { tanstackStartModuleFederation } from "@module-federation/tanstack/rsbuild"; + +export default defineConfig({ + plugins: [ + pluginReact(), + tanstackStart(), + ...tanstackStartModuleFederation({ + federation: { + name: "remote", + exposes: { "./StatusCard": "./src/StatusCard.tsx" }, + }, + }), + ], +}); +``` + +Install `@module-federation/vite` with Vite projects, or +`@module-federation/rsbuild-plugin` and `@rsbuild/core` with Rsbuild projects. +Those packages, plus `@tanstack/react-start`, are optional peers because each +adapter is loaded only by its matching subpath. + +The default Rsbuild server adapter changes TanStack Start's server output to +async-node CommonJS. Start that build with `node dist/server/index.cjs`. Passing +`server: false` keeps TanStack Start's standard `dist/server/index.js` output +for browser-only federation. + Supported Node releases are 22.18+, 24.11+, and 26+. diff --git a/packages/tanstack/package.json b/packages/tanstack/package.json index 4a637b3..660843a 100644 --- a/packages/tanstack/package.json +++ b/packages/tanstack/package.json @@ -5,14 +5,39 @@ "description": "TanStack Start integration for Module Federation", "license": "MIT", "sideEffects": false, - "main": "./dist/index.mjs", - "module": "./dist/index.mjs", - "types": "./dist/index.d.mts", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.mts", - "import": "./dist/index.mjs", - "default": "./dist/index.mjs" + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./vite": { + "import": { + "types": "./dist/vite.d.ts", + "default": "./dist/vite.js" + }, + "require": { + "types": "./dist/vite.d.cts", + "default": "./dist/vite.cjs" + } + }, + "./rsbuild": { + "import": { + "types": "./dist/rsbuild.d.ts", + "default": "./dist/rsbuild.js" + }, + "require": { + "types": "./dist/rsbuild.d.cts", + "default": "./dist/rsbuild.cjs" + } }, "./package.json": "./package.json" }, @@ -45,26 +70,46 @@ "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "scripts": { - "build": "tsdown", - "dev": "tsdown --watch --no-clean", + "build": "rslib build --lib esm && rslib build --lib cjs --no-clean", + "dev": "rslib build --watch", "pack:dry": "npm pack --dry-run", "prepack": "pnpm run build", "prepublishOnly": "pnpm run typecheck && pnpm run build", "typecheck": "tsc -p tsconfig.json --noEmit" }, - "dependencies": { - "@module-federation/vite": "1.22.0" - }, "peerDependencies": { "@tanstack/react-start": "^1.0.0", + "@module-federation/rsbuild-plugin": "^2.9.0", + "@module-federation/vite": "^1.22.0", + "@rsbuild/core": "^2.0.0", "vite": "^7.0.0 || ^8.0.0" }, + "peerDependenciesMeta": { + "@tanstack/react-start": { + "optional": true + }, + "@module-federation/rsbuild-plugin": { + "optional": true + }, + "@module-federation/vite": { + "optional": true + }, + "@rsbuild/core": { + "optional": true + }, + "vite": { + "optional": true + } + }, "devDependencies": { - "@tanstack/react-start": "1.168.54", + "@module-federation/rsbuild-plugin": "2.9.0", + "@module-federation/vite": "1.22.0", + "@rsbuild/core": "2.2.8", + "@rslib/core": "1.0.1", + "@tanstack/react-start": "1.168.56", "@types/node": "24.10.1", "react": "19.3.0", "react-dom": "19.3.0", - "tsdown": "0.23.0", "typescript": "6.0.3", "vite": "8.3.0" } diff --git a/packages/tanstack/rslib.config.ts b/packages/tanstack/rslib.config.ts new file mode 100644 index 0000000..a0cfbe7 --- /dev/null +++ b/packages/tanstack/rslib.config.ts @@ -0,0 +1,36 @@ +import { defineConfig } from "@rslib/core"; + +const entry = { + index: "./src/index.ts", + rsbuild: "./src/rsbuild.ts", + shared: "./src/shared.ts", + vite: "./src/vite.ts", +}; + +const output = { + distPath: { root: "./dist" }, + sourceMap: { js: "source-map" }, + target: "node", +} as const; + +export default defineConfig({ + lib: [ + { + bundle: false, + dts: { autoExtension: true }, + format: "esm", + output, + redirect: { dts: { extension: true } }, + source: { entry }, + syntax: "es2022", + }, + { + bundle: false, + dts: { autoExtension: true }, + format: "cjs", + output, + source: { entry }, + syntax: "es2022", + }, + ], +}); diff --git a/packages/tanstack/src/index.ts b/packages/tanstack/src/index.ts index 393d29b..24f205b 100644 --- a/packages/tanstack/src/index.ts +++ b/packages/tanstack/src/index.ts @@ -1,48 +1,4 @@ -import { federation, type ModuleFederationOptions } from "@module-federation/vite"; - -export type TanStackStartModuleFederationOptions = Omit; - -const defaultShared = { - react: { singleton: true }, - "react-dom": { singleton: true }, -} as const; - -type SharedOptions = NonNullable; -type SharedRecord = Exclude; - -function resolveShared(shared: SharedOptions | undefined): SharedRecord { - if (!Array.isArray(shared)) { - return { - ...defaultShared, - ...shared, - }; - } - - const additionalShared = Object.fromEntries( - shared - .filter((packageName) => !Object.hasOwn(defaultShared, packageName)) - .map((packageName) => [packageName, {}]), - ); - - return { - ...defaultShared, - ...additionalShared, - }; -} - -/** Creates one Module Federation plugin set for TanStack Start's client and SSR environments. */ -export function tanstackStartModuleFederation(options: TanStackStartModuleFederationOptions) { - const { - shared, - target: _environmentSpecificTarget, - ...configured - } = options as ModuleFederationOptions; - - return federation({ - ...configured, - filename: configured.filename ?? "remoteEntry.js", - manifest: configured.manifest ?? true, - hostInitInjectLocation: configured.hostInitInjectLocation ?? "entry", - shared: resolveShared(shared), - }); -} +// The root entrypoint remains Vite-compatible. Rsbuild users should import +// `@module-federation/tanstack/rsbuild` so their installation never resolves +// the Vite adapter. +export { tanstackStartModuleFederation, type TanStackStartModuleFederationOptions } from "./vite"; diff --git a/packages/tanstack/src/rsbuild.ts b/packages/tanstack/src/rsbuild.ts new file mode 100644 index 0000000..96d78f3 --- /dev/null +++ b/packages/tanstack/src/rsbuild.ts @@ -0,0 +1,177 @@ +import { + pluginModuleFederation, + type ModuleFederationOptions, +} from "@module-federation/rsbuild-plugin"; +import type { RsbuildPlugin } from "@rsbuild/core"; +import { eagerShared, resolveShared } from "./shared"; + +type ModuleFederationOverride = Partial; + +type ClientFederationOptions = ModuleFederationOverride & { + chunkLoadingGlobal?: string; + environment?: string; + forceScriptOutput?: boolean; +}; + +type ServerFederationOptions = + | false + | (ModuleFederationOverride & { + chunkFilename?: string; + entryFilename?: string; + environment?: string; + forceCommonJsOutput?: boolean; + }); + +export type TanStackStartRsbuildModuleFederationOptions = { + client?: ClientFederationOptions; + federation: ModuleFederationOptions; + server?: ServerFederationOptions; +}; + +/** Creates browser and SSR federation adapters for TanStack Start's Rsbuild integration. */ +export function tanstackStartModuleFederation({ + client = {}, + federation, + server = {}, +}: TanStackStartRsbuildModuleFederationOptions): RsbuildPlugin[] { + const { + chunkLoadingGlobal, + environment: clientEnvironment = "client", + forceScriptOutput = true, + ...clientOverrides + } = client; + const clientFederationOptions = withDefaults({ ...federation, ...clientOverrides }); + const federationName = clientFederationOptions.name; + + if (!federationName) throw new Error("federation.name is required"); + + const plugins: RsbuildPlugin[] = [ + pluginModuleFederation(clientFederationOptions, { + environment: clientEnvironment, + target: "web", + }), + clientCompatibilityPlugin({ + chunkLoadingGlobal: chunkLoadingGlobal ?? `chunk_${federationName}`, + environment: clientEnvironment, + forceScriptOutput, + name: federationName, + }), + ]; + + if (server === false) return plugins; + + const { + chunkFilename, + entryFilename, + environment: serverEnvironment = "ssr", + forceCommonJsOutput = true, + ...serverOverrides + } = server; + const serverFederationOptions = withDefaults( + { ...federation, ...serverOverrides }, + forceCommonJsOutput ? "serverRemoteEntry.cjs" : "serverRemoteEntry.js", + ); + + return [ + ...plugins, + pluginModuleFederation(serverFederationOptions, { + environment: serverEnvironment, + target: "node", + }), + serverCompatibilityPlugin({ + chunkFilename: + chunkFilename ?? (forceCommonJsOutput ? `[id].${federationName}.cjs` : undefined), + entryFilename: entryFilename ?? (forceCommonJsOutput ? "[name].cjs" : undefined), + environment: serverEnvironment, + forceCommonJsOutput, + }), + ]; +} + +export const pluginTanStackStartModuleFederation = tanstackStartModuleFederation; + +function withDefaults( + options: ModuleFederationOptions, + filename = "remoteEntry.js", +): ModuleFederationOptions { + return { + ...options, + filename: options.filename ?? filename, + manifest: options.manifest ?? true, + shared: resolveShared(options.shared, eagerShared) as ModuleFederationOptions["shared"], + }; +} + +function clientCompatibilityPlugin({ + chunkLoadingGlobal, + environment, + forceScriptOutput, + name, +}: { + chunkLoadingGlobal: string; + environment: string; + forceScriptOutput: boolean; + name: string; +}): RsbuildPlugin { + return { + name: "tanstack-start-federation-client-compat", + setup(api) { + api.onBeforeCreateCompiler(({ bundlerConfigs }) => { + for (const config of bundlerConfigs ?? []) { + if (config.name !== environment) continue; + + config.output ||= {}; + config.output.chunkFormat = "array-push"; + config.output.chunkLoading = "jsonp"; + config.output.chunkLoadingGlobal = chunkLoadingGlobal; + config.output.uniqueName ??= name; + + if (forceScriptOutput) { + config.output.module = false; + const experiments = (config.experiments ??= {}) as { outputModule?: boolean }; + experiments.outputModule = false; + } + } + }); + }, + }; +} + +function serverCompatibilityPlugin({ + chunkFilename, + entryFilename, + environment, + forceCommonJsOutput, +}: { + chunkFilename?: string; + entryFilename?: string; + environment: string; + forceCommonJsOutput: boolean; +}): RsbuildPlugin { + return { + name: "tanstack-start-federation-ssr-compat", + setup(api) { + api.onBeforeCreateCompiler(({ bundlerConfigs }) => { + for (const config of bundlerConfigs ?? []) { + if (config.name !== environment) continue; + + config.output ||= {}; + if (forceCommonJsOutput) { + config.output.filename = entryFilename ?? "[name].cjs"; + config.output.chunkFilename = + chunkFilename ?? `[id].${config.output.uniqueName ?? "server"}.cjs`; + config.target = "async-node"; + config.output.module = false; + config.output.chunkFormat = "commonjs"; + config.output.chunkLoading = "async-node"; + config.output.library = { type: "commonjs2" }; + delete config.output.chunkLoadingGlobal; + } else { + if (entryFilename) config.output.filename = entryFilename; + if (chunkFilename) config.output.chunkFilename = chunkFilename; + } + } + }); + }, + }; +} diff --git a/packages/tanstack/src/shared.ts b/packages/tanstack/src/shared.ts new file mode 100644 index 0000000..20c04ec --- /dev/null +++ b/packages/tanstack/src/shared.ts @@ -0,0 +1,31 @@ +export const defaultShared = { + react: { singleton: true }, + "react-dom": { singleton: true }, +} as const; + +export const eagerShared = { + react: { eager: true, singleton: true }, + "react-dom": { eager: true, singleton: true }, +} as const; + +export function resolveShared( + shared: unknown, + defaults: Record = defaultShared, +): Record { + if (Array.isArray(shared)) { + return { + ...defaults, + ...Object.fromEntries( + shared + .filter((packageName): packageName is string => typeof packageName === "string") + .filter((packageName) => !Object.hasOwn(defaults, packageName)) + .map((packageName) => [packageName, {}]), + ), + }; + } + + return { + ...defaults, + ...(shared && typeof shared === "object" ? shared : {}), + }; +} diff --git a/packages/tanstack/src/vite.ts b/packages/tanstack/src/vite.ts new file mode 100644 index 0000000..ca1b162 --- /dev/null +++ b/packages/tanstack/src/vite.ts @@ -0,0 +1,21 @@ +import { federation, type ModuleFederationOptions } from "@module-federation/vite"; +import { resolveShared } from "./shared"; + +export type TanStackStartModuleFederationOptions = Omit; + +/** Creates one Vite federation plugin set for TanStack Start's client and SSR environments. */ +export function tanstackStartModuleFederation(options: TanStackStartModuleFederationOptions) { + const { + shared, + target: _environmentSpecificTarget, + ...configured + } = options as ModuleFederationOptions; + + return federation({ + ...configured, + filename: configured.filename ?? "remoteEntry.js", + manifest: configured.manifest ?? true, + hostInitInjectLocation: configured.hostInitInjectLocation ?? "entry", + shared: resolveShared(shared) as ModuleFederationOptions["shared"], + }); +} diff --git a/packages/tanstack/tsconfig.json b/packages/tanstack/tsconfig.json index ae76cb7..058e070 100644 --- a/packages/tanstack/tsconfig.json +++ b/packages/tanstack/tsconfig.json @@ -9,6 +9,7 @@ "moduleResolution": "Bundler", "strict": true, "noUncheckedIndexedAccess": true, + "rootDir": "./src", "declaration": true, "declarationMap": true, "sourceMap": true, diff --git a/packages/tanstack/tsdown.config.ts b/packages/tanstack/tsdown.config.ts deleted file mode 100644 index a6d6a0b..0000000 --- a/packages/tanstack/tsdown.config.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { builtinModules } from "node:module"; -import { defineConfig } from "tsdown"; - -const external = [ - ...builtinModules, - ...builtinModules.map((name) => `node:${name}`), - "@module-federation/vite", - "@module-federation/vite/*", -]; - -export default defineConfig({ - clean: true, - deps: { neverBundle: external }, - dts: { sourcemap: true }, - entry: { index: "./src/index.ts" }, - format: ["esm"], - outDir: "dist", - sourcemap: true, -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c8b656..38a72c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,7 +31,7 @@ importers: version: 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/react-start': specifier: 1.168.56 - version: 1.168.56(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + version: 1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) react: specifier: 19.3.0 version: 19.3.0 @@ -71,7 +71,7 @@ importers: version: 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/react-start': specifier: 1.168.56 - version: 1.168.56(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + version: 1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) react: specifier: 19.3.0 version: 19.3.0 @@ -101,15 +101,109 @@ importers: specifier: 8.3.0 version: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) - packages/tanstack: + apps/rsbuild-host: + dependencies: + '@module-federation/runtime': + specifier: 2.9.0 + version: 2.9.0 + '@tanstack/react-router': + specifier: 1.170.38 + version: 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start': + specifier: 1.168.56 + version: 1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + react: + specifier: 19.3.0 + version: 19.3.0 + react-dom: + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) + devDependencies: + '@module-federation/rsbuild-plugin': + specifier: 2.9.0 + version: 2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) + '@module-federation/tanstack': + specifier: workspace:* + version: link:../../packages/tanstack + '@rsbuild/core': + specifier: 2.2.8 + version: 2.2.8(@module-federation/runtime-tools@2.9.0) + '@rsbuild/plugin-react': + specifier: 2.1.0 + version: 2.1.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23)) + '@types/node': + specifier: 24.10.1 + version: 24.10.1 + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) + typescript: + specifier: 6.0.3 + version: 6.0.3 + + apps/rsbuild-remote: dependencies: + '@module-federation/runtime': + specifier: 2.9.0 + version: 2.9.0 + '@tanstack/react-router': + specifier: 1.170.38 + version: 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start': + specifier: 1.168.56 + version: 1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + react: + specifier: 19.3.0 + version: 19.3.0 + react-dom: + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) + devDependencies: + '@module-federation/rsbuild-plugin': + specifier: 2.9.0 + version: 2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) + '@module-federation/tanstack': + specifier: workspace:* + version: link:../../packages/tanstack + '@rsbuild/core': + specifier: 2.2.8 + version: 2.2.8(@module-federation/runtime-tools@2.9.0) + '@rsbuild/plugin-react': + specifier: 2.1.0 + version: 2.1.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23)) + '@types/node': + specifier: 24.10.1 + version: 24.10.1 + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) + typescript: + specifier: 6.0.3 + version: 6.0.3 + + packages/tanstack: + devDependencies: + '@module-federation/rsbuild-plugin': + specifier: 2.9.0 + version: 2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) '@module-federation/vite': specifier: 1.22.0 version: 1.22.0(typescript@6.0.3)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) - devDependencies: + '@rsbuild/core': + specifier: 2.2.8 + version: 2.2.8(@module-federation/runtime-tools@2.9.0) + '@rslib/core': + specifier: 1.0.1 + version: 1.0.1(@module-federation/runtime-tools@2.9.0)(typescript@6.0.3) '@tanstack/react-start': - specifier: 1.168.54 - version: 1.168.54(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 1.168.56 + version: 1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) '@types/node': specifier: 24.10.1 version: 24.10.1 @@ -119,9 +213,6 @@ importers: react-dom: specifier: 19.3.0 version: 19.3.0(react@19.3.0) - tsdown: - specifier: 0.23.0 - version: 0.23.0(typescript@6.0.3) typescript: specifier: 6.0.3 version: 6.0.3 @@ -131,6 +222,64 @@ importers: packages: + '@ast-grep/napi-darwin-arm64@0.45.3': + resolution: {integrity: sha512-rMZJ6frSpR/xSbeV4hiA9WR5hHqk3rVYX8lOqEoUCaUnjEuFXvHdGN4WawJ8pVDq/IqM1UnTtsDSYgUdVmO5DA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@ast-grep/napi-darwin-x64@0.45.3': + resolution: {integrity: sha512-tIclZ5pM+YSgk8NNTBoTkaclbS39cU5lbVfSrFWX4LUnxXfUPG+F+ppxyFjKwJTBELsGHEO4LdX3/Lc+FDwPNA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@ast-grep/napi-linux-arm64-gnu@0.45.3': + resolution: {integrity: sha512-Bm8F7/oRHQkDXbjH+gBi9oB5huJ0q12r/AIOMIypf5PWlAVvh206Tsw468phw4Oh6kd4Ad1Z+gflNW1MutgK2g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@ast-grep/napi-linux-arm64-musl@0.45.3': + resolution: {integrity: sha512-yr5xcRerV41GeRxmZ8SKbfGeyZRhQePoNNCgTAb4OrWMelWwbMI80aY7/IlF2bo1sN40BSW4hZdGIO/akTd1RQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@ast-grep/napi-linux-x64-gnu@0.45.3': + resolution: {integrity: sha512-lluRK3hxfHMQdFuVID6seAKVx4FH0kEwF6rCh5/I40Sf3EtNR2UCBXAqd6RBA6hqlSckktvBxCfw+h4aescDmg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@ast-grep/napi-linux-x64-musl@0.45.3': + resolution: {integrity: sha512-09wJQKq86LqILqQtw0+X09SzhcPwUnMVFKyia7S3XLbLqSeQpAMF55Y+zll53JRQ1dCzFEZJyvTT/7j9vwfE3w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@ast-grep/napi-win32-arm64-msvc@0.45.3': + resolution: {integrity: sha512-/HiVgw2cdtzXLDLoG4eBBcauekNXewfagy7qiVbjkVu/KcCH8dJ9lpj8DzeWwuBNtZxuD8jpiwP0M1pz3Bc3uw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@ast-grep/napi-win32-ia32-msvc@0.45.3': + resolution: {integrity: sha512-/gwJrJQUw65uNtAFcG2rtSTr117Gqqy3bfjaljCPKsa65dTeRcMMnjKLlZ6V9/g8z+4Xf78kdhp5ECbir4yfSw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@ast-grep/napi-win32-x64-msvc@0.45.3': + resolution: {integrity: sha512-CEZAMAsaDoQoVE3MT2c/ry3/ydfyaqlTjxv8DK5PG355SrdAYPgevGC2Me6tz4dhphJin89Y20VsnrS5UrYV5Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@ast-grep/napi@0.45.3': + resolution: {integrity: sha512-bjJsUt0g3UCwbn9bjv7MnoR15rszSm79yPCXn4DWWK0NuYUUqBU/srtNMR4WnpfR7TnfBRdYFoqSNx0pGCNWJg==} + engines: {node: '>= 10'} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -267,6 +416,15 @@ packages: resolution: {integrity: sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==} engines: {node: '>= 20.12.0'} + '@emnapi/core@1.11.3': + resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@emnapi/wasi-threads@1.2.3': + resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -295,6 +453,14 @@ packages: resolution: {integrity: sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ==} engines: {node: '>=20.0.0'} + '@module-federation/bridge-react-webpack-plugin@2.9.0': + resolution: {integrity: sha512-hv3fuQkGERQ/COBKTVbFV1GWovReSg/bzJzDuxWR1F84h6NYtbYMWQqX8Egvb7HKMtn9Rflzw0GeaLr8F08vDg==} + + '@module-federation/cli@2.9.0': + resolution: {integrity: sha512-r9RdlLRy3zWxuWQRonif48xdDu1reBGx18QpkZwLQ4JfSrOARjycNIGY2Y7QaUw7dedzxyee4FtKFeX/kOVLYQ==} + engines: {node: '>=16.0.0'} + hasBin: true + '@module-federation/dts-plugin@2.9.0': resolution: {integrity: sha512-uMGqEG/p9odG2BVr7WRbBe2OgrvzBd3LPzcp5WP+bm3djBdUJ4PZ3ozqKp6qpy+NFnlh6vnM815Xn6AXkKy1Vw==} peerDependencies: @@ -304,15 +470,70 @@ packages: vue-tsc: optional: true + '@module-federation/enhanced@2.9.0': + resolution: {integrity: sha512-Jd/JHoFL9fNKL4Nnzo/9hf6FD/oQo3gKpE8xt6EGuhxmrvg6wM0radPtqgHiSSWLMW7CiXr2agNjKvquKd83rw==} + hasBin: true + peerDependencies: + typescript: ^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + vue-tsc: '>=1.0.24' + webpack: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + vue-tsc: + optional: true + webpack: + optional: true + '@module-federation/error-codes@2.9.0': resolution: {integrity: sha512-IGpd+VRlji3NyGOGGTsk7YEmPRKcDoBK4YHqIuP+OQwyb2YhHCUHO2vW9RXeQxCuKwi+c6xkTweRYG+Umy+0Zw==} + '@module-federation/inject-external-runtime-core-plugin@2.9.0': + resolution: {integrity: sha512-k3wCSZsY21HjYQ61wmIJUQleOgcHgqx/sX4qXYw0XnJnzHuFo64rzMvgr+TuBIgZ1peAQtfILLlTnWyTRjtZHw==} + peerDependencies: + '@module-federation/runtime-tools': 2.9.0 + '@module-federation/managers@2.9.0': resolution: {integrity: sha512-8KhB2PF4g+M0hGaethU1H4GVWabLn5sF5TvnM6VxgAcDL4TfLY8aC/YobAXHG/SV0jpU5lsGe6s6xy6ccV0MQw==} + '@module-federation/manifest@2.9.0': + resolution: {integrity: sha512-Zhm9luVOw9XPou50eTwsvdgr208CszoQ6cGORQDCPAMhjpGb5oYFtilJ+LKW7hQ1LktD15bEZmGhC1anvgXEiQ==} + + '@module-federation/node@2.7.50': + resolution: {integrity: sha512-mbpQRdafyeWgsmYoJfdhOQf76zS6onOGpC2X1ELpWXB1Y4BcZGloL0CLjNMNon9m3ucfpc99tOGAQqFzQVkSBQ==} + peerDependencies: + webpack: ^5.40.0 + peerDependenciesMeta: + webpack: + optional: true + + '@module-federation/rsbuild-plugin@2.9.0': + resolution: {integrity: sha512-CaWAxZg+zOMw/BfgRXQdNBQZ/e5U7DBZvE13LzG13GEclt7yxaMQgKH0si3uOsq9f/X2NC8UnuhNHLIkloqlqA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@rsbuild/core': ^1.3.21 || ^2.0.0-0 + peerDependenciesMeta: + '@rsbuild/core': + optional: true + + '@module-federation/rspack@2.9.0': + resolution: {integrity: sha512-9zSlmQYKRHKVWqhlZSMyjstp2A3VVrQV2Of8mrF5NXhngFemNx7Hw35+MTo+gsRfF5glpN8bAzLyIokRUo1Cog==} + peerDependencies: + '@rspack/core': ^0.7.0 || ^1.0.0 || ^2.0.0-0 + typescript: ^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + vue-tsc: '>=1.0.24' + peerDependenciesMeta: + typescript: + optional: true + vue-tsc: + optional: true + '@module-federation/runtime-core@2.9.0': resolution: {integrity: sha512-dLykRYfpbEJBTdk2NlbNoVVTB196O3qujawTBguLABJkPCWETJNk60NR1yZO34eI+DTH+eGwHU3m7FddAWnbnw==} + '@module-federation/runtime-tools@2.9.0': + resolution: {integrity: sha512-u2puqsaHiw1bVvLNk1uh98iPo6UzaBuci/aTvOFwKvbT/RF18C3vGGnm+ShHPKQEnoN6ctPtnygyOZDPR+8cgg==} + '@module-federation/runtime@2.9.0': resolution: {integrity: sha512-3cyAav0hWP+dNvB7qrcK9CKxQn+JvkbRvLtZz3zS2g0EyxtDFDjgbHY0Afcf7oHf8IHhKeEdzb/3Kjr1Pjmr7A==} @@ -328,6 +549,15 @@ packages: peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@module-federation/webpack-bundler-runtime@2.9.0': + resolution: {integrity: sha512-MdU6NQibT57MaJG3KPBjC30IVBrz5eU7IcjHoVDYnMh7OmASw3stagBu7hYjdMTP31luNdtzUn85s9axvdwTlw==} + + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@oozcitak/dom@2.0.2': resolution: {integrity: sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==} engines: {node: '>=20.0'} @@ -351,9 +581,6 @@ packages: resolution: {integrity: sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A==} engines: {node: '>=22.13'} - '@quansync/fs@1.1.0': - resolution: {integrity: sha512-qAPG/t3HqML1TlN7sY/pTbEjzFVAKsMjNNMGheyDosro+kT4iw2KCUoHcVdmliwWjorm4elZbgNQyU2eD97eDg==} - '@rolldown/binding-android-arm-eabi@1.2.9': resolution: {integrity: sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -447,17 +674,137 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rsbuild/core@2.2.8': + resolution: {integrity: sha512-/htjYpZiqZFB/fELguE8nkqKDT/DqHXeXUgdf+X4UlP5BJc1EeolZ9ksDpCw9WNTlcn3zsPOj7UZ0dUUB4FAFA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + core-js: '>= 3.0.0' + peerDependenciesMeta: + core-js: + optional: true + + '@rsbuild/plugin-react@2.1.0': + resolution: {integrity: sha512-RQTIAWB/CwPjoWt9iAl+8HixeQVgZ7kEIBrWPCixfITyHdiD84h0YpUTpEUuz6kGHw1KXT9mHZ3Rwy6WG7aRDA==} + peerDependencies: + '@rsbuild/core': ^2.0.0 + peerDependenciesMeta: + '@rsbuild/core': + optional: true + + '@rslib/core@1.0.1': + resolution: {integrity: sha512-FsUgiFALdgPQDM41OEYmt18J4061Vcm/fW2RXkZcwlO56PNaAEu2vtk+i8iH0lPEpCK79OaJPYjUzygOj4pPpA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7 + typescript: ^5 || ^6 || ^7 + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + typescript: + optional: true + + '@rspack/binding-darwin-arm64@2.2.6': + resolution: {integrity: sha512-y8I8MAeKAOYuLqEKYQSmNFcW4dI1u5O+xYQcjT1L3Gby02KEt0YxlmSPvzElSQJUjsJ8PkKqU3U1suDjA6xvkg==} + cpu: [arm64] + os: [darwin] + + '@rspack/binding-darwin-x64@2.2.6': + resolution: {integrity: sha512-+zhtUAP+nZON1lSlqOFJWW2VpIXRZ4/NQskYe0xohELI4Fr6xun/Oh8dIMt+Nviamvo7lbO9XURrFio1+Fziog==} + cpu: [x64] + os: [darwin] + + '@rspack/binding-linux-arm64-gnu@2.2.6': + resolution: {integrity: sha512-7GI9xlkzVMAp9dbNvynvAreMjQQNw01b+khYYfmoQOgWXTUwrtzR7/JhJ3tijVQTXkyiYAKJ0RPvz57yD+p1vQ==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-arm64-musl@2.2.6': + resolution: {integrity: sha512-klkjpGdeROzRiGSk2amfeZBAh0vWu0GjX5xKKQfRR4IqKQxURGCQafbvl3VX5XrHEUnKFzag89Lyv+ZVx5eTCw==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-ppc64-gnu@2.2.6': + resolution: {integrity: sha512-TpLZr49oU31EuEqVDczpbfLZ6c976zu8HspBuUWYj1Ra4SNeOHhINKE1i42OVw2DCEMpsz8Tv3tYyLVACGC8JA==} + cpu: [ppc64] + os: [linux] + + '@rspack/binding-linux-riscv64-gnu@2.2.6': + resolution: {integrity: sha512-gU3Ki8Lt6cE4qRM62959/28GG3XQUZsnvstgDULJpkHR5QFGYXUhOoPe2fpyWYrLw6+t9lWXBuI/gUez1zc8Sg==} + cpu: [riscv64] + os: [linux] + + '@rspack/binding-linux-riscv64-musl@2.2.6': + resolution: {integrity: sha512-OQRvU8HT9rGmtgdpieO722IrGViDlR0D6yjqbt7S51b+saBPwJ8Nf3fwMtpZnBo1djV2BGXK00L34jYypeChrw==} + cpu: [riscv64] + os: [linux] + + '@rspack/binding-linux-s390x-gnu@2.2.6': + resolution: {integrity: sha512-pus4Z76+J1iRO5Gutc6pq1LkyVhXqi0Sw6o5Dxwtl3sSbVy+F5PJkxtAT+DEg07pQy5m3dCymz2CIpiFlwYLTQ==} + cpu: [s390x] + os: [linux] + + '@rspack/binding-linux-x64-gnu@2.2.6': + resolution: {integrity: sha512-M0Efi9ny2cJ6VdhHxsZbcwdRCQ0HqBAsnPxPB8Ulknj+VtRlWnTlX/DlqMaHFuCb0MOWS6DXGGEAZ8veEil0BA==} + cpu: [x64] + os: [linux] + + '@rspack/binding-linux-x64-musl@2.2.6': + resolution: {integrity: sha512-614vRMr6VAqUXQm9TY7FCamcB08XJOv51WAwIlfEpoKTYxzNiQvrTYBYPgStUH20ywLwVoN6EJ++WCmHaebGWw==} + cpu: [x64] + os: [linux] + + '@rspack/binding-wasm32-wasi@2.2.6': + resolution: {integrity: sha512-p995VmOEcHWPKw/Eek0CYUjmXWhfyWOPoKtE51Dy0/jqHcs7uRy53igDd277yHTuKCHibmwU+cjSKUe6b/aBZg==} + cpu: [wasm32] + + '@rspack/binding-win32-arm64-msvc@2.2.6': + resolution: {integrity: sha512-xC8nHMqVzQWqVW95HOcf9qTsM445ui7QNfUTDui6oTiHcBtsyfK1CEdXsc50bdi/FrJ2ueOps3M5gxbM8Ttl/A==} + cpu: [arm64] + os: [win32] + + '@rspack/binding-win32-ia32-msvc@2.2.6': + resolution: {integrity: sha512-sfqP/LSnMwAHpTA+Cfbkhvx/IvUr23Z7S+miOkaMT/j2XJYCBrPZK0ZYm8+LUNDQ2ij8/XCJT4pVpQbtc/SMPw==} + cpu: [ia32] + os: [win32] + + '@rspack/binding-win32-x64-msvc@2.2.6': + resolution: {integrity: sha512-b1ERY5QydZWJKA13AYsoMPZycG5AGoUc3sOMqLszO0gs84aMnLbK7lBiyeZCvEcHEa7gSFQlLQr9p+Xk7HPO4Q==} + cpu: [x64] + os: [win32] + + '@rspack/binding@2.2.6': + resolution: {integrity: sha512-bCB4a7KKaQpPNlkExf27AmLJabeGjpjXlhnCdvmSIHHlT3YfpYz2CSKOLLLI/BcBez8zUBNDVmvswm3ggCCRqA==} + + '@rspack/core@2.2.6': + resolution: {integrity: sha512-sqN75Fgf6v3tCmt8GAG/rdcYHOUDv/YZbQf24YCGDoMLmdNlXCosCHkBYWI8qdLs9IqSnGt7XgbcA39Zilg6UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@module-federation/runtime-tools': ^0.24.1 || ^2.0.0 + '@swc/helpers': ^0.5.23 + peerDependenciesMeta: + '@module-federation/runtime-tools': + optional: true + '@swc/helpers': + optional: true + + '@rspack/plugin-react-refresh@2.0.2': + resolution: {integrity: sha512-dGNZiCxQxgAUI9sah7gd8u+O7OJZRCmqtEJNDOd8xW5RqcieC86F7p5qcShyw6onH5pKf57evpr2VjGbaFGkZg==} + peerDependencies: + '@rspack/core': ^2.0.0 + react-refresh: '>=0.10.0 <1.0.0' + peerDependenciesMeta: + '@rspack/core': + optional: true + + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} + '@tanstack/history@1.162.4': resolution: {integrity: sha512-utTS5L2OkeYUzXGohL1Z8sefu1GLNOJcxe8Hd6iIdc/Xo1K1nDB2JEp4iSFhvYh33xKC9V91TxrS8qfrpoKobQ==} engines: {node: '>=20.19'} - '@tanstack/react-router@1.170.36': - resolution: {integrity: sha512-AbgcI/xO8AJY1r6y5tF7TZyHH0VKm0XcfY3QVIVCrHq8k04Hkj1N4nfLL+1xCGC3VnMI3AYGAVWkatnqE2dxAQ==} - engines: {node: '>=20.19'} - peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router@1.170.38': resolution: {integrity: sha512-iHM9b0aDJbuvftmkiQXawzoqsdV68p2Re2safbVLCnxafe+iLKV++2i3hI/BMAP6uR92/Mjw94JKJJfD7pbwHQ==} engines: {node: '>=20.19'} @@ -465,13 +812,6 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-client@1.168.34': - resolution: {integrity: sha512-rNwogcDo5A9xeKmGHIocI50nlqtlEr5lcgbcdW9gCtDEloXALdwFtXriEj3kHZe7BLvn9FItZESZcC3xo4/71w==} - engines: {node: '>=22.12.0'} - peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-client@1.168.36': resolution: {integrity: sha512-TRXZKag/Ng0TqVfcN4cbEDVJhEZYosHZ9/VAEhK3PK/YqznytK+bcoonE4jP5NCXMdH2YRTMsrWKrApEuWz0Cg==} engines: {node: '>=22.12.0'} @@ -479,23 +819,6 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-rsc@0.1.53': - resolution: {integrity: sha512-Hzwpd74HyvTFU4rEY9PpbUaYJ7BA3deCg0p+Dyj3NTFsTnULD+/Eba87u1QPl5UEVsKWyIG9J7g4J6JZpxFWSg==} - engines: {node: '>=22.12.0'} - peerDependencies: - '@rspack/core': '>=2.0.0-0' - '@vitejs/plugin-rsc': '>=0.5.30' - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - react-server-dom-rspack: '>=0.0.2' - peerDependenciesMeta: - '@rspack/core': - optional: true - '@vitejs/plugin-rsc': - optional: true - react-server-dom-rspack: - optional: true - '@tanstack/react-start-rsc@0.1.55': resolution: {integrity: sha512-qfW89LMrewtMLHMCumMP3+3mf2gPezFE7FZ7CHPJis4paxt2A6Jl7eCpE6whYgc5DFiifUv9qdfbC22HIP0HAw==} engines: {node: '>=22.12.0'} @@ -513,13 +836,6 @@ packages: react-server-dom-rspack: optional: true - '@tanstack/react-start-server@1.167.41': - resolution: {integrity: sha512-ng4M05+vPug8rJH4OzF6u45JRSn+MThpjGCzkXmBgW3w/6TRkzWHQMO5/tBzpERJhUHp+uk178ZfFJVw0SWYpA==} - engines: {node: '>=22.12.0'} - peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-server@1.167.43': resolution: {integrity: sha512-/Fc1mOEBAyXmqxnihIkS1DrCcJX8ffrJ+xyc8nnFmO9YuCdrnZJskd48R1ZiOhM7u3vCPzDxUycIhKo8HlKn2g==} engines: {node: '>=22.12.0'} @@ -527,23 +843,6 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start@1.168.54': - resolution: {integrity: sha512-x0GWvFm1e4WtgETxDE0xVWa39HdEnoinKQ9SU8pvO187SDsSpudYuhr8StfWdLE5iRhziGchLqlPl6UccUZ46w==} - engines: {node: '>=22.12.0'} - peerDependencies: - '@rsbuild/core': ^2.0.0 - '@vitejs/plugin-rsc': '*' - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - vite: '>=7.0.0' - peerDependenciesMeta: - '@rsbuild/core': - optional: true - '@vitejs/plugin-rsc': - optional: true - vite: - optional: true - '@tanstack/react-start@1.168.56': resolution: {integrity: sha512-b/jVJS+yt7J0IKwXfVoA2mlhIi92sWfwCYrCVked0WERzgbYAad6xrOwNTljz9OKyeajofGrmMdKJgRzK1Lt2w==} engines: {node: '>=22.12.0'} @@ -567,43 +866,14 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.171.30': - resolution: {integrity: sha512-wtS1yCAb/k5CJBauvrCyqf2n4/kRc3aiz81tFBJ1QXH4Utsu81xFTMq15AdmNWgWmt2UstUSMfWi4Ua7ph2F+w==} - engines: {node: '>=20.19'} - '@tanstack/router-core@1.171.32': resolution: {integrity: sha512-X86Jqk3vB2KJcUfS6oLi7B7yxbaa59QEhZRPPP1fRx3k+Jw/Fu1UYVBcRtdwK/OccQnrlQdVoKvNO3QXmyEBOw==} engines: {node: '>=20.19'} - '@tanstack/router-generator@1.167.36': - resolution: {integrity: sha512-9Sjm1j8fkurviVIcbAXVdoyqAT8tlurY1tD9Na9Y59/BW5uWT5/5U+eU4Nv/d5grBKyosiJCP7/pcADvstnEgg==} - engines: {node: '>=20.19'} - '@tanstack/router-generator@1.167.38': resolution: {integrity: sha512-lkqgFleDgfkW+QONVMKBs+ZGDUF0v9R9FkplS/GZvKDFpfnu+tZxmGlV2pryU1TDxyuSyjh56AtIla3NMrlScw==} engines: {node: '>=20.19'} - '@tanstack/router-plugin@1.168.38': - resolution: {integrity: sha512-0SWwASONOiy7biNBvzdmfhKWwhxa+oJUZHIAdsAEAK+H1sftLWcE3b4sil5omjIIAlPmBJLk2JuUnfBWrn0iIg==} - engines: {node: '>=20.19'} - peerDependencies: - '@rsbuild/core': '>=1.0.2 || ^2.0.0' - '@tanstack/react-router': ^1.170.36 - vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' - vite-plugin-solid: ^2.11.10 || ^3.0.0-0 - webpack: '>=5.92.0' - peerDependenciesMeta: - '@rsbuild/core': - optional: true - '@tanstack/react-router': - optional: true - vite: - optional: true - vite-plugin-solid: - optional: true - webpack: - optional: true - '@tanstack/router-plugin@1.168.40': resolution: {integrity: sha512-ifiXjjR4uivxwRDhgYAcfyrcu+Mwx+vlG0QjjQ7N5C5sKmzpt5UtsL21TNVvZDSZl2Vae8DcL84Z02fOs8ZZMQ==} engines: {node: '>=20.19'} @@ -629,10 +899,6 @@ packages: resolution: {integrity: sha512-Icb0xGuG1+54IV0WMLRcc3ErTx2HeJWyPG661FkQ8UT6guoBq1FJjFRqlG/JS0xi4mFFkBhvwO7tbLa32f3yxA==} engines: {node: '>=20.19'} - '@tanstack/start-client-core@1.170.30': - resolution: {integrity: sha512-eyNrOJumsx68Od+//eVWgQdQAdleY9bzbJ/N9/q5x/pqHdtaSxHYqMERPAmB7XcYrMoF0Bo9LcP1TfXqvqk69g==} - engines: {node: '>=22.12.0'} - '@tanstack/start-client-core@1.170.32': resolution: {integrity: sha512-kawgeg3Ej/JvGeEN7txFdVN9kXOST9wl7yPojOGESiomwiubHKRdYUS6sBskW91j44H+HirQUppP4+Ha7kjb4A==} engines: {node: '>=22.12.0'} @@ -641,18 +907,6 @@ packages: resolution: {integrity: sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==} engines: {node: '>=22.12.0'} - '@tanstack/start-plugin-core@1.171.44': - resolution: {integrity: sha512-xSUlXNpA/k1cNHoIcPFqBw8kehQelUn7qN8dCVMvNEjSRA63jWbFVZNrkMXAKsY0sTMM+i4qoOmCleKT1ha7nA==} - engines: {node: '>=22.12.0'} - peerDependencies: - '@rsbuild/core': ^2.0.0 - vite: '>=7.0.0' - peerDependenciesMeta: - '@rsbuild/core': - optional: true - vite: - optional: true - '@tanstack/start-plugin-core@1.171.46': resolution: {integrity: sha512-d/cDPZNDdRl824mOamt6USO1ja5JAaIwkuT2FIBRkBdrKZgQ8GJWv5pqpkXMbKANc49V8bUBBOTjgiCdC7YsPw==} engines: {node: '>=22.12.0'} @@ -665,18 +919,10 @@ packages: vite: optional: true - '@tanstack/start-server-core@1.169.35': - resolution: {integrity: sha512-xy6DB1EFznF9d7r+lqj35+Gz0alreCh8tO9+yPepANab+WZagQEDjFwHSY9vrv3lzBWbMlRrkkGG1OZWOSL48w==} - engines: {node: '>=22.12.0'} - '@tanstack/start-server-core@1.169.37': resolution: {integrity: sha512-CjYMXg2XISMEJt9UVR4lbMRsnRyWPrdyuF8iuJ4gxTc1sVNRciwMVpMPVGQG2KuIma3ewlNxw8+Tpx2tnS3zRA==} engines: {node: '>=22.12.0'} - '@tanstack/start-storage-context@1.167.32': - resolution: {integrity: sha512-Di2mLQPXy1oPFU0o9jb0oMA/XXX9sJc+FEMJo3PMiSyB6cPnbAjsR1VgvavfsCBMi7kOE+ctdK+ELDX2MwKvIA==} - engines: {node: '>=22.12.0'} - '@tanstack/start-storage-context@1.167.34': resolution: {integrity: sha512-cRPE0kjt1CnOIhepmNOvRFwCGhDAnCRohPD3HmzqJJThPkixLBRuLDvIYp4hRPtM1TJ9hiKbYl24IxAhjZO5TA==} engines: {node: '>=22.12.0'} @@ -718,160 +964,54 @@ packages: cpu: [arm64] os: [win32] - '@types/node@24.10.1': - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} - - '@types/react-dom@19.3.0': - resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} - peerDependencies: - '@types/react': ^19.3.0 - - '@types/react@19.3.0': - resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} - - '@vitejs/plugin-react@6.1.1': - resolution: {integrity: sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 - babel-plugin-react-compiler: ^1.0.0 - oxc-transform-react: ^0.145.0 - vite: ^8.0.0 - peerDependenciesMeta: - '@rolldown/plugin-babel': - optional: true - babel-plugin-react-compiler: - optional: true - oxc-transform-react: - optional: true - - '@yuku-codegen/binding-android-arm64@0.10.2': - resolution: {integrity: sha512-Oc2KInVkPfUjEB4PeV6X0NvIoyYTzDe/WmqFrTwBqKs6YKnz4A7XwWQvJTb7M0rLz9a6WgpUUiiS5QKaCi4O/g==} - cpu: [arm64] - os: [android] - - '@yuku-codegen/binding-darwin-arm64@0.10.2': - resolution: {integrity: sha512-3H7eNPIHJndUJXZ4QUGBPq1zC6RGcUZfm7bymJes+/N7wTVWUn1bxZ9JcozYHpkWQNm9f23G8YWbaHD9aYH72A==} - cpu: [arm64] - os: [darwin] - - '@yuku-codegen/binding-darwin-x64@0.10.2': - resolution: {integrity: sha512-AJOUpR2s3LF9AWiiub/Uyc/UoXNTWcq8hK3cVI6fUSXtwh34dG21J6hRuNlO6JVJFTo4o3ScVvZOrh/YFfUEAA==} - cpu: [x64] - os: [darwin] - - '@yuku-codegen/binding-freebsd-x64@0.10.2': - resolution: {integrity: sha512-ms7DcZu87u5CiK/wMwvpKAvAmtaqKjCQIXNweMLypG6qbuiaOMQf1jpbB5+j46xBcgCovo8TQMcv0bCQLKIL9w==} - cpu: [x64] - os: [freebsd] - - '@yuku-codegen/binding-linux-arm-gnu@0.10.2': - resolution: {integrity: sha512-xJDpYAsKV5+eaiBhTYl05fvT6sst4PsCeuIFMRu9b0/WCSrNQPfCDtAQ5/MS6xNpsdujdZEPR+gmfWk3ZG5i3A==} - cpu: [arm] - os: [linux] - - '@yuku-codegen/binding-linux-arm-musl@0.10.2': - resolution: {integrity: sha512-qfTkgd7AEx61l4K9VtXWCGTxc/fmXJ4ZheDz3i+/AAJUtg4sa0bPrc0VBxPggB/rayR8Z3+JfrBItuyanBJJ9Q==} - cpu: [arm] - os: [linux] - - '@yuku-codegen/binding-linux-arm64-gnu@0.10.2': - resolution: {integrity: sha512-4e6Mifm/4UdjtU3D8mATIrvgP+xEiH8xtQOjd0zpd72XKWT7ug3sdyhq2utkkZFP6BvYp7SgZrI+aR4VeIOHdw==} - cpu: [arm64] - os: [linux] - - '@yuku-codegen/binding-linux-arm64-musl@0.10.2': - resolution: {integrity: sha512-RdsJrUfDYFVV3JOmWFUWwSu31fa1APn12xDeIKxgl/YWxrabwtxsDBNjF2561c7tmcbiewdCUvngqRs8AyGVLA==} - cpu: [arm64] - os: [linux] - - '@yuku-codegen/binding-linux-x64-gnu@0.10.2': - resolution: {integrity: sha512-mVzWimEPPreaPyXIUvxsHoJzvO7ckZ5j0LgQFHz04zQIRwt9A9T2hA7K5/jYjJKkMxWG/U2VjhGNwVhtyU+uqg==} - cpu: [x64] - os: [linux] - - '@yuku-codegen/binding-linux-x64-musl@0.10.2': - resolution: {integrity: sha512-Y77fyISurmr2mvO1yeq3u+x/WJbACgPR4w+lzEVx30ViCxZzIGrZPRN1yEdZ9xUNPNsIO5thBHIdRNG+UGUG+Q==} - cpu: [x64] - os: [linux] - - '@yuku-codegen/binding-win32-arm64@0.10.2': - resolution: {integrity: sha512-1+tGLyG0u5YYy0lev4ck7/0sd8xJ9SZde0dLut7qLDPZV5WVTV8x6OxNXUne/PGuHZyO1vK+txPQzzHOyXHGSw==} - cpu: [arm64] - os: [win32] - - '@yuku-codegen/binding-win32-x64@0.10.2': - resolution: {integrity: sha512-8a2SExRohRbwC0MY4VpOF0/RSckrJ2ASZqAor5/RYSJJaeadR93n10gZzcKT6pY9ukiSOIZCRKVRD2tH73T0qA==} - cpu: [x64] - os: [win32] - - '@yuku-parser/binding-android-arm64@0.10.2': - resolution: {integrity: sha512-2VPBU9fRGRAQ2xPAvghnec3oou5Nrxm2Bezhf+13UMspAxQSkF/32r+ySKXSwIPA5/RivumDJDxAxc301Sgicw==} - cpu: [arm64] - os: [android] - - '@yuku-parser/binding-darwin-arm64@0.10.2': - resolution: {integrity: sha512-LD+PMZE51tCYTOss5HBkm3/AE39MvcMDBfWJx7A4yDcjfNbAQDnHZKtzSOuqrswx+TQHY0ws5xn6+fWOwtmfBA==} - cpu: [arm64] - os: [darwin] + '@tybys/wasm-util@0.10.4': + resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} - '@yuku-parser/binding-darwin-x64@0.10.2': - resolution: {integrity: sha512-mmZ8cND+AoIIMRERyMinlg5ApHxP23B9jH2B5wT7T+dliPa9rubLxneB/SUjFwyUjGaFnB5G7t4YvpfbO5zbkQ==} - cpu: [x64] - os: [darwin] + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@yuku-parser/binding-freebsd-x64@0.10.2': - resolution: {integrity: sha512-gVIjaaIddbRfAhHlC8N809wQWml7mxfSVnzaLtzGXObTeFTPAg/YVnQXt1UOhPM1ah5eG/8Y0RUlNpB4GVe7eQ==} - cpu: [x64] - os: [freebsd] - - '@yuku-parser/binding-linux-arm-gnu@0.10.2': - resolution: {integrity: sha512-q/XPPQQAPdlw05aPj30ygBhekmQryGOwxVraBgApjKK8yY1kNQgqq6XCYLF1WHSee+lhxclrTO7W0/bt7dR1GA==} - cpu: [arm] - os: [linux] - - '@yuku-parser/binding-linux-arm-musl@0.10.2': - resolution: {integrity: sha512-A+Cb0I1hFF4wilTQpWs79k1aNnj4B2tkrHx3zsuUNF9BtVY2zPZ4yeQEM/zjXrS/qJlNMZVK9NrWJjwdpnTrfg==} - cpu: [arm] - os: [linux] - - '@yuku-parser/binding-linux-arm64-gnu@0.10.2': - resolution: {integrity: sha512-aGNSzqIqqphFwAdIFwVvKIyXD1Iy3CxrEJVIZAT87Ecyi4vCmmDD2v2l9h+Gd3/wSy3JZlOI792LJbKAjyy9rQ==} - cpu: [arm64] - os: [linux] - - '@yuku-parser/binding-linux-arm64-musl@0.10.2': - resolution: {integrity: sha512-Ddl1sF0rtuCXyHGSOV6dcmdx+ETv1iD+IVFqQuOjzkJZWclxJxdBWX6ZJMRtTiGqGUTbqLEKiTXxpR/Bvqk+2A==} - cpu: [arm64] - os: [linux] - - '@yuku-parser/binding-linux-x64-gnu@0.10.2': - resolution: {integrity: sha512-/nlcpR6IF5U+0m5L9wvAIXeQa2DT+BXuvqKDsTcOTlcc0B4dHNyqE5hTXsS4hAyimpy2ZK8A1zMNF5hTrjg2cg==} - cpu: [x64] - os: [linux] - - '@yuku-parser/binding-linux-x64-musl@0.10.2': - resolution: {integrity: sha512-GX80dxTQD/M/OryyuxJcFzFENzry3cDFPvCFTyWogNWQH3fSHfMrNfpwpl1YzMos1eV9NygK5GSDG7VArQlb4w==} - cpu: [x64] - os: [linux] - - '@yuku-parser/binding-win32-arm64@0.10.2': - resolution: {integrity: sha512-agePQBV4VHewiGU0ACSjscZ/hJd283f9JfAF19Gf1rI5+wy5tTgx4GS36i0b3xim15AQ4RCpDRosEvhLZ4zAOw==} - cpu: [arm64] - os: [win32] + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} - '@yuku-parser/binding-win32-x64@0.10.2': - resolution: {integrity: sha512-s8//CMpgL5+y1lvDCdyh1rwGI5+ytDJywFJRe1vnhI7n0j+caxshNucurikYLLVeQ2SYFex6aPrzgQxkabBQRA==} - cpu: [x64] - os: [win32] + '@types/react-dom@19.3.0': + resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} + peerDependencies: + '@types/react': ^19.3.0 + + '@types/react@19.3.0': + resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} - '@yuku-toolchain/types@0.10.2': - resolution: {integrity: sha512-sSeo4SSSToiS+sSD+bwn/s94EEcaLJ7tG5LCp8gFYC1G5VzxzX7fqB6m9RU1yMD8KTpu6zdU/I1NlQf8hVBJ9Q==} + '@vitejs/plugin-react@6.1.1': + resolution: {integrity: sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + oxc-transform-react: ^0.145.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + oxc-transform-react: + optional: true adm-zip@0.6.0: resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} engines: {node: '>=14.0'} + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansis@4.4.0: resolution: {integrity: sha512-9k3v7xcHwgdO/DruxGIg4HtjvlAZlcnsX/mzqUb1t3NkYnl9kK2UJ+Gq0io+vQf7iT//BD/HB/NBkUR1LWxoeA==} engines: {node: '>=14'} @@ -903,6 +1043,10 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -921,9 +1065,6 @@ packages: supports-color: optional: true - defu@6.1.7: - resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -932,21 +1073,11 @@ packages: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} - dts-resolver@3.0.0: - resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} - engines: {node: ^22.18.0 || >=24.0.0} - peerDependencies: - oxc-resolver: '>=11.0.0' - peerDependenciesMeta: - oxc-resolver: - optional: true - electron-to-chromium@1.5.430: resolution: {integrity: sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ==} - empathic@2.0.1: - resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} - engines: {node: '>=14'} + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} @@ -955,12 +1086,18 @@ packages: exsolve@1.1.1: resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-string-truncated-width@3.0.3: resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.8: + resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} + fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -985,10 +1122,6 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - get-tsconfig@5.0.0-beta.6: - resolution: {integrity: sha512-X6fBC0pmImC70gvX2zm56go9hx0MyoGVdG0tUCkg/D+Xnh5TJsOZ7iDbOdI3PvmtrDxnu1YdDufpK2QJX1Meqw==} - engines: {node: '>=20.20.0'} - h3@2.0.1-rc.20: resolution: {integrity: sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==} engines: {node: '>=20.11.1'} @@ -999,20 +1132,17 @@ packages: crossws: optional: true - hookable@6.1.1: - resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} - human-id@4.2.1: resolution: {integrity: sha512-zPGsiS+dWoTZtZ4AtpA9Y+BdSFSNWvnouNlWNoUFyAM6xHOHmdCvqO3k8AIbdamCOv4gUFUVNPf6rJFfc4UiJw==} hasBin: true + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - import-without-cache@0.4.1: - resolution: {integrity: sha512-vXoV9PjKHEednCUu01e98TkImxy67e3BJXbTIOmIq4Hyzw3IAwYRcuPkfeTzJzwyyts4kjuA73x+pWJLc4f86A==} - engines: {node: ^22.18.0 || >=24.0.0} - isbot@5.2.2: resolution: {integrity: sha512-iQcBXcd+Rv/pkubRyGh2utW2j1oPG5hZY6TUhVPpqK4G+o3IbxpJNx04hgksjc/N7GK5pEorUxDeg31cFgEk/w==} engines: {node: '>=18'} @@ -1022,6 +1152,10 @@ packages: peerDependencies: ws: '*' + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -1041,6 +1175,9 @@ packages: engines: {node: '>=6'} hasBin: true + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1136,18 +1273,19 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-releases@2.0.55: resolution: {integrity: sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==} engines: {node: '>=18'} - obug@2.2.1: - resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} - engines: {node: '>=12.20.0'} - - obug@3.0.0: - resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} - engines: {node: '>=12.20.0'} - package-manager-detector@1.8.0: resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} @@ -1170,14 +1308,15 @@ packages: engines: {node: '>=14'} hasBin: true - quansync@1.0.0: - resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} - react-dom@19.3.0: resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} peerDependencies: react: ^19.3.0 + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + react@19.3.0: resolution: {integrity: sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==} engines: {node: '>=0.10.0'} @@ -1186,30 +1325,9 @@ packages: resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} engines: {node: '>= 20.19.0'} - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - rolldown-plugin-dts@0.28.6: - resolution: {integrity: sha512-qKrFtBfRfR2hP233m7Ic9zf3wv6MSYdZghWKMdEO6dRYZGlNfINJAa1P5ZVvyHh3mrcd7IJQvIY8L9vnm+v5rQ==} - engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} - peerDependencies: - '@typescript/native-preview': '*' - '@volar/typescript': ~2.4.0 - '@vue/language-core': ~3.2.0 || ~3.3.0 - rolldown: ^1.2.0 - typescript: ^5.0.0 || ^6.0.0 || ~7.0.0 - vue-tsc: ~3.2.0 || ~3.3.0 - peerDependenciesMeta: - '@typescript/native-preview': - optional: true - '@volar/typescript': - optional: true - '@vue/language-core': - optional: true - typescript: - optional: true - vue-tsc: - optional: true + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} rolldown@1.2.9: resolution: {integrity: sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==} @@ -1219,9 +1337,29 @@ packages: rou3@0.8.1: resolution: {integrity: sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==} + rsbuild-plugin-dts@1.0.1: + resolution: {integrity: sha512-vzLhs8v1SuRd0qyIWue5caaM2A1lGImuH+cQAX71KnNpS9BZHyHRBHHXBFp9jp610XJnLlkBATyPclqUfGtCAA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@microsoft/api-extractor': ^7 + '@rsbuild/core': ^2.0.0 + typescript: ^5 || ^6 || ^7 + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + typescript: + optional: true + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.28.0: resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} + schema-utils@4.3.0: + resolution: {integrity: sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==} + engines: {node: '>= 10.13.0'} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -1261,6 +1399,10 @@ packages: engines: {node: '>=20.16.0'} hasBin: true + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + tinyexec@1.3.1: resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} @@ -1269,43 +1411,11 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tsdown@0.23.0: - resolution: {integrity: sha512-BaT+ep1xnj5hdyICLd5r5SYYE+TXnI1ATePLykv9vcKpHpFTWUWRH+x1AF/E2qcu3YB6+vI8IdyxIIIffEqw5Q==} - engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} - hasBin: true - peerDependencies: - '@arethetypeswrong/core': ^0.18.1 - '@tsdown/css': 0.23.0 - '@tsdown/exe': 0.23.0 - '@vitejs/devtools': '*' - publint: ^0.3.8 - tsx: '*' - typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 - unplugin-unused: '>=0.5.0' - unrun: '*' - peerDependenciesMeta: - '@arethetypeswrong/core': - optional: true - '@tsdown/css': - optional: true - '@tsdown/exe': - optional: true - '@vitejs/devtools': - optional: true - publint: - optional: true - tsx: - optional: true - typescript: - optional: true - unplugin-unused: - optional: true - unrun: - optional: true + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} turbo@2.10.13: resolution: {integrity: sha512-69MkPbjk+G8oyC7pC2DeBk0rPdbLz51wuH5eAT0Q4BVJkzq/b1XM4kUUidsARbmDjwIELSJwd1ezITULY9kWNw==} @@ -1319,9 +1429,6 @@ packages: ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - unconfig-core@7.5.0: - resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} - undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -1376,10 +1483,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - verkit@0.4.0: - resolution: {integrity: sha512-sXMwN6DMHeouPfCxkxWkKAmxphWKEenHYY5H1nIBzU3PmDsmJp6kBXJdshjVdpMZuWCmL9SH7KFRx29AylpP6g==} - engines: {node: '>=18.12.0'} - vite@8.3.0: resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1431,9 +1534,15 @@ packages: vite: optional: true + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + ws@8.21.0: resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} @@ -1458,20 +1567,50 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yuku-ast@0.10.2: - resolution: {integrity: sha512-UnG9mA6giglCvSErft2/40TVFX750Sj1xgwddPLpG7J7rlr/P1wPADg9G2RK+d/1tNLtRVoLdMMOMVBB9561TQ==} - - yuku-codegen@0.10.2: - resolution: {integrity: sha512-hentl2dtrF6cPjiAirtkfxfFZBA6Hdm61UqRJ7cA0qrlDNMk9PZGOjw5w1mx3E0hu/6pHcJF4dJW+D0SXHPZOA==} - - yuku-parser@0.10.2: - resolution: {integrity: sha512-CgaU0/PPjCAIEZ3WQroosOxTY3eeKldAN3h+vk8pMNz4+jl1CZzBR4pW+K/rRPF112VooCL5FdjJoiMNjDrL2A==} - zod@4.6.5: resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} snapshots: + '@ast-grep/napi-darwin-arm64@0.45.3': + optional: true + + '@ast-grep/napi-darwin-x64@0.45.3': + optional: true + + '@ast-grep/napi-linux-arm64-gnu@0.45.3': + optional: true + + '@ast-grep/napi-linux-arm64-musl@0.45.3': + optional: true + + '@ast-grep/napi-linux-x64-gnu@0.45.3': + optional: true + + '@ast-grep/napi-linux-x64-musl@0.45.3': + optional: true + + '@ast-grep/napi-win32-arm64-msvc@0.45.3': + optional: true + + '@ast-grep/napi-win32-ia32-msvc@0.45.3': + optional: true + + '@ast-grep/napi-win32-x64-msvc@0.45.3': + optional: true + + '@ast-grep/napi@0.45.3': + optionalDependencies: + '@ast-grep/napi-darwin-arm64': 0.45.3 + '@ast-grep/napi-darwin-x64': 0.45.3 + '@ast-grep/napi-linux-arm64-gnu': 0.45.3 + '@ast-grep/napi-linux-arm64-musl': 0.45.3 + '@ast-grep/napi-linux-x64-gnu': 0.45.3 + '@ast-grep/napi-linux-x64-musl': 0.45.3 + '@ast-grep/napi-win32-arm64-msvc': 0.45.3 + '@ast-grep/napi-win32-ia32-msvc': 0.45.3 + '@ast-grep/napi-win32-x64-msvc': 0.45.3 + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -1688,6 +1827,22 @@ snapshots: fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 + '@emnapi/core@1.11.3': + dependencies: + '@emnapi/wasi-threads': 1.2.3 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.3': + dependencies: + tslib: 2.8.1 + optional: true + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -1722,6 +1877,22 @@ snapshots: tinyglobby: 0.2.17 yaml: 2.9.1 + '@module-federation/bridge-react-webpack-plugin@2.9.0': + dependencies: + '@module-federation/sdk': 2.9.0 + + '@module-federation/cli@2.9.0(typescript@6.0.3)': + dependencies: + '@module-federation/dts-plugin': 2.9.0(typescript@6.0.3) + '@module-federation/sdk': 2.9.0 + commander: 11.1.0 + jiti: 2.4.2 + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - vue-tsc + '@module-federation/dts-plugin@2.9.0(typescript@6.0.3)': dependencies: '@module-federation/error-codes': 2.9.0 @@ -1737,17 +1908,105 @@ snapshots: - bufferutil - utf-8-validate + '@module-federation/enhanced@2.9.0(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3)': + dependencies: + '@module-federation/bridge-react-webpack-plugin': 2.9.0 + '@module-federation/cli': 2.9.0(typescript@6.0.3) + '@module-federation/dts-plugin': 2.9.0(typescript@6.0.3) + '@module-federation/error-codes': 2.9.0 + '@module-federation/inject-external-runtime-core-plugin': 2.9.0(@module-federation/runtime-tools@2.9.0) + '@module-federation/managers': 2.9.0 + '@module-federation/manifest': 2.9.0(typescript@6.0.3) + '@module-federation/rspack': 2.9.0(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) + '@module-federation/runtime-tools': 2.9.0 + '@module-federation/sdk': 2.9.0 + '@module-federation/webpack-bundler-runtime': 2.9.0 + schema-utils: 4.3.0 + tapable: 2.3.0 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - utf-8-validate + '@module-federation/error-codes@2.9.0': {} + '@module-federation/inject-external-runtime-core-plugin@2.9.0(@module-federation/runtime-tools@2.9.0)': + dependencies: + '@module-federation/runtime-tools': 2.9.0 + '@module-federation/managers@2.9.0': dependencies: '@module-federation/sdk': 2.9.0 + '@module-federation/manifest@2.9.0(typescript@6.0.3)': + dependencies: + '@module-federation/dts-plugin': 2.9.0(typescript@6.0.3) + '@module-federation/managers': 2.9.0 + '@module-federation/sdk': 2.9.0 + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - vue-tsc + + '@module-federation/node@2.7.50(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3)': + dependencies: + '@module-federation/enhanced': 2.9.0(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) + '@module-federation/runtime': 2.9.0 + '@module-federation/sdk': 2.9.0 + encoding: 0.1.13 + node-fetch: 2.7.0(encoding@0.1.13) + tapable: 2.3.0 + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - typescript + - utf-8-validate + - vue-tsc + + '@module-federation/rsbuild-plugin@2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3)': + dependencies: + '@module-federation/enhanced': 2.9.0(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) + '@module-federation/node': 2.7.50(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) + '@module-federation/sdk': 2.9.0 + optionalDependencies: + '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - typescript + - utf-8-validate + - vue-tsc + - webpack + + '@module-federation/rspack@2.9.0(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3)': + dependencies: + '@module-federation/bridge-react-webpack-plugin': 2.9.0 + '@module-federation/dts-plugin': 2.9.0(typescript@6.0.3) + '@module-federation/inject-external-runtime-core-plugin': 2.9.0(@module-federation/runtime-tools@2.9.0) + '@module-federation/managers': 2.9.0 + '@module-federation/manifest': 2.9.0(typescript@6.0.3) + '@module-federation/runtime-tools': 2.9.0 + '@module-federation/sdk': 2.9.0 + '@rspack/core': 2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23) + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@module-federation/runtime-core@2.9.0': dependencies: '@module-federation/error-codes': 2.9.0 '@module-federation/sdk': 2.9.0 + '@module-federation/runtime-tools@2.9.0': + dependencies: + '@module-federation/runtime': 2.9.0 + '@module-federation/webpack-bundler-runtime': 2.9.0 + '@module-federation/runtime@2.9.0': dependencies: '@module-federation/error-codes': 2.9.0 @@ -1770,6 +2029,19 @@ snapshots: - utf-8-validate - vue-tsc + '@module-federation/webpack-bundler-runtime@2.9.0': + dependencies: + '@module-federation/error-codes': 2.9.0 + '@module-federation/runtime': 2.9.0 + '@module-federation/sdk': 2.9.0 + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + dependencies: + '@emnapi/core': 1.11.3 + '@emnapi/runtime': 1.11.3 + '@tybys/wasm-util': 0.10.4 + optional: true + '@oozcitak/dom@2.0.2': dependencies: '@oozcitak/infra': 2.0.2 @@ -1791,10 +2063,6 @@ snapshots: '@pnpm/deps.graph-sequencer@1100.0.1': {} - '@quansync/fs@1.1.0': - dependencies: - quansync: 1.0.0 - '@rolldown/binding-android-arm-eabi@1.2.9': optional: true @@ -1842,16 +2110,113 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@tanstack/history@1.162.4': {} + '@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0)': + dependencies: + '@rspack/core': 2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23) + '@swc/helpers': 0.5.23 + transitivePeerDependencies: + - '@module-federation/runtime-tools' + + '@rsbuild/plugin-react@2.1.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))': + dependencies: + '@rspack/plugin-react-refresh': 2.0.2(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-refresh@0.18.0) + react-refresh: 0.18.0 + optionalDependencies: + '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) + transitivePeerDependencies: + - '@rspack/core' + + '@rslib/core@1.0.1(@module-federation/runtime-tools@2.9.0)(typescript@6.0.3)': + dependencies: + '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) + rsbuild-plugin-dts: 1.0.1(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(typescript@6.0.3) + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - '@module-federation/runtime-tools' + - core-js + + '@rspack/binding-darwin-arm64@2.2.6': + optional: true + + '@rspack/binding-darwin-x64@2.2.6': + optional: true + + '@rspack/binding-linux-arm64-gnu@2.2.6': + optional: true + + '@rspack/binding-linux-arm64-musl@2.2.6': + optional: true + + '@rspack/binding-linux-ppc64-gnu@2.2.6': + optional: true + + '@rspack/binding-linux-riscv64-gnu@2.2.6': + optional: true + + '@rspack/binding-linux-riscv64-musl@2.2.6': + optional: true + + '@rspack/binding-linux-s390x-gnu@2.2.6': + optional: true + + '@rspack/binding-linux-x64-gnu@2.2.6': + optional: true + + '@rspack/binding-linux-x64-musl@2.2.6': + optional: true + + '@rspack/binding-wasm32-wasi@2.2.6': + dependencies: + '@emnapi/core': 1.11.3 + '@emnapi/runtime': 1.11.3 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + optional: true + + '@rspack/binding-win32-arm64-msvc@2.2.6': + optional: true + + '@rspack/binding-win32-ia32-msvc@2.2.6': + optional: true + + '@rspack/binding-win32-x64-msvc@2.2.6': + optional: true + + '@rspack/binding@2.2.6': + optionalDependencies: + '@rspack/binding-darwin-arm64': 2.2.6 + '@rspack/binding-darwin-x64': 2.2.6 + '@rspack/binding-linux-arm64-gnu': 2.2.6 + '@rspack/binding-linux-arm64-musl': 2.2.6 + '@rspack/binding-linux-ppc64-gnu': 2.2.6 + '@rspack/binding-linux-riscv64-gnu': 2.2.6 + '@rspack/binding-linux-riscv64-musl': 2.2.6 + '@rspack/binding-linux-s390x-gnu': 2.2.6 + '@rspack/binding-linux-x64-gnu': 2.2.6 + '@rspack/binding-linux-x64-musl': 2.2.6 + '@rspack/binding-wasm32-wasi': 2.2.6 + '@rspack/binding-win32-arm64-msvc': 2.2.6 + '@rspack/binding-win32-ia32-msvc': 2.2.6 + '@rspack/binding-win32-x64-msvc': 2.2.6 + + '@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23)': + dependencies: + '@rspack/binding': 2.2.6 + optionalDependencies: + '@module-federation/runtime-tools': 2.9.0 + '@swc/helpers': 0.5.23 + + '@rspack/plugin-react-refresh@2.0.2(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-refresh@0.18.0)': + dependencies: + react-refresh: 0.18.0 + optionalDependencies: + '@rspack/core': 2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23) - '@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@swc/helpers@0.5.23': dependencies: - '@tanstack/history': 1.162.4 - '@tanstack/react-store': 0.11.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.30 - isbot: 5.2.2 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + tslib: 2.8.1 + + '@tanstack/history@1.162.4': {} '@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: @@ -1862,14 +2227,6 @@ snapshots: react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - '@tanstack/react-start-client@1.168.34(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': - dependencies: - '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.30 - '@tanstack/start-client-core': 1.170.30 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) - '@tanstack/react-start-client@1.168.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -1878,44 +2235,20 @@ snapshots: react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - '@tanstack/react-start-rsc@0.1.53(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': - dependencies: - '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.30 - '@tanstack/router-utils': 1.162.3 - '@tanstack/start-client-core': 1.170.30 - '@tanstack/start-fn-stubs': 1.162.0 - '@tanstack/start-plugin-core': 1.171.44(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) - '@tanstack/start-storage-context': 1.167.32 - pathe: 2.0.3 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) - transitivePeerDependencies: - - '@farmfe/core' - - '@rsbuild/core' - - bun-types-no-globals - - crossws - - esbuild - - rolldown - - rollup - - supports-color - - unloader - - vite - - vite-plugin-solid - - webpack - - '@tanstack/react-start-rsc@0.1.55(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + '@tanstack/react-start-rsc@0.1.55(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/router-core': 1.171.32 '@tanstack/router-utils': 1.162.3 '@tanstack/start-client-core': 1.170.32 '@tanstack/start-fn-stubs': 1.162.0 - '@tanstack/start-plugin-core': 1.171.46(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/start-plugin-core': 1.171.46(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) '@tanstack/start-storage-context': 1.167.34 pathe: 2.0.3 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@rspack/core': 2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23) transitivePeerDependencies: - '@farmfe/core' - '@rsbuild/core' @@ -1930,16 +2263,6 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/react-start-server@1.167.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': - dependencies: - '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.30 - '@tanstack/start-server-core': 1.169.35 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) - transitivePeerDependencies: - - crossws - '@tanstack/react-start-server@1.167.43(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -1950,49 +2273,21 @@ snapshots: transitivePeerDependencies: - crossws - '@tanstack/react-start@1.168.54(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': - dependencies: - '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/react-start-client': 1.168.34(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/react-start-rsc': 0.1.53(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) - '@tanstack/react-start-server': 1.167.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-utils': 1.162.3 - '@tanstack/start-client-core': 1.170.30 - '@tanstack/start-plugin-core': 1.171.44(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) - '@tanstack/start-server-core': 1.169.35 - pathe: 2.0.3 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) - optionalDependencies: - vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) - transitivePeerDependencies: - - '@farmfe/core' - - '@rspack/core' - - bun-types-no-globals - - crossws - - esbuild - - react-server-dom-rspack - - rolldown - - rollup - - supports-color - - unloader - - vite-plugin-solid - - webpack - - '@tanstack/react-start@1.168.56(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + '@tanstack/react-start@1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/react-start-client': 1.168.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/react-start-rsc': 0.1.55(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/react-start-rsc': 0.1.55(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) '@tanstack/react-start-server': 1.167.43(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/router-utils': 1.162.3 '@tanstack/start-client-core': 1.170.32 - '@tanstack/start-plugin-core': 1.171.46(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/start-plugin-core': 1.171.46(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) '@tanstack/start-server-core': 1.169.37 pathe: 2.0.3 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) optionalDependencies: + '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) transitivePeerDependencies: - '@farmfe/core' @@ -2015,13 +2310,6 @@ snapshots: react-dom: 19.3.0(react@19.3.0) use-sync-external-store: 1.7.0(react@19.3.0) - '@tanstack/router-core@1.171.30': - dependencies: - '@tanstack/history': 1.162.4 - cookie-es: 3.1.1 - seroval: 1.6.7 - seroval-plugins: 1.6.7(seroval@1.6.7) - '@tanstack/router-core@1.171.32': dependencies: '@tanstack/history': 1.162.4 @@ -2029,19 +2317,6 @@ snapshots: seroval: 1.6.7 seroval-plugins: 1.6.7(seroval@1.6.7) - '@tanstack/router-generator@1.167.36': - dependencies: - '@babel/types': 7.29.8 - '@tanstack/router-core': 1.171.30 - '@tanstack/router-utils': 1.162.3 - '@tanstack/virtual-file-routes': 1.162.0 - jiti: 2.7.0 - magic-string: 0.30.21 - prettier: 3.9.7 - zod: 4.6.5 - transitivePeerDependencies: - - supports-color - '@tanstack/router-generator@1.167.38': dependencies: '@babel/types': 7.29.8 @@ -2055,31 +2330,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.168.38(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': - dependencies: - '@babel/core': 7.29.7 - '@babel/template': 7.29.7 - '@babel/types': 7.29.8 - '@tanstack/router-core': 1.171.30 - '@tanstack/router-generator': 1.167.36 - '@tanstack/router-utils': 1.162.3 - chokidar: 5.0.0 - unplugin: 3.4.0(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) - zod: 4.6.5 - optionalDependencies: - '@tanstack/react-router': 1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) - transitivePeerDependencies: - - '@farmfe/core' - - '@rspack/core' - - bun-types-no-globals - - esbuild - - rolldown - - rollup - - supports-color - - unloader - - '@tanstack/router-plugin@1.168.40(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + '@tanstack/router-plugin@1.168.40(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@babel/core': 7.29.7 '@babel/template': 7.29.7 @@ -2088,9 +2339,10 @@ snapshots: '@tanstack/router-generator': 1.167.38 '@tanstack/router-utils': 1.162.3 chokidar: 5.0.0 - unplugin: 3.4.0(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + unplugin: 3.4.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) zod: 4.6.5 optionalDependencies: + '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) transitivePeerDependencies: @@ -2116,13 +2368,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/start-client-core@1.170.30': - dependencies: - '@tanstack/router-core': 1.171.30 - '@tanstack/start-fn-stubs': 1.162.0 - '@tanstack/start-storage-context': 1.167.32 - seroval: 1.6.7 - '@tanstack/start-client-core@1.170.32': dependencies: '@tanstack/router-core': 1.171.32 @@ -2132,46 +2377,7 @@ snapshots: '@tanstack/start-fn-stubs@1.162.0': {} - '@tanstack/start-plugin-core@1.171.44(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/core': 7.29.7 - '@babel/types': 7.29.8 - '@jridgewell/remapping': 2.3.5 - '@tanstack/router-core': 1.171.30 - '@tanstack/router-generator': 1.167.36 - '@tanstack/router-plugin': 1.168.38(@tanstack/react-router@1.170.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) - '@tanstack/router-utils': 1.162.3 - '@tanstack/start-server-core': 1.169.35 - exsolve: 1.1.1 - lightningcss: 1.33.0 - pathe: 2.0.3 - picomatch: 4.0.7 - seroval: 1.6.7 - source-map: 0.7.6 - srvx: 0.11.22 - tinyglobby: 0.2.17 - ufo: 1.6.4 - vitefu: 1.1.3(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) - xmlbuilder2: 4.0.3 - zod: 4.6.5 - optionalDependencies: - vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) - transitivePeerDependencies: - - '@farmfe/core' - - '@rspack/core' - - '@tanstack/react-router' - - bun-types-no-globals - - crossws - - esbuild - - rolldown - - rollup - - supports-color - - unloader - - vite-plugin-solid - - webpack - - '@tanstack/start-plugin-core@1.171.46(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + '@tanstack/start-plugin-core@1.171.46(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@babel/code-frame': 7.29.7 '@babel/core': 7.29.7 @@ -2179,7 +2385,7 @@ snapshots: '@jridgewell/remapping': 2.3.5 '@tanstack/router-core': 1.171.32 '@tanstack/router-generator': 1.167.38 - '@tanstack/router-plugin': 1.168.40(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/router-plugin': 1.168.40(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) '@tanstack/router-utils': 1.162.3 '@tanstack/start-server-core': 1.169.37 exsolve: 1.1.1 @@ -2195,6 +2401,7 @@ snapshots: xmlbuilder2: 4.0.3 zod: 4.6.5 optionalDependencies: + '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) transitivePeerDependencies: - '@farmfe/core' @@ -2210,18 +2417,6 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/start-server-core@1.169.35': - dependencies: - '@tanstack/history': 1.162.4 - '@tanstack/router-core': 1.171.30 - '@tanstack/start-client-core': 1.170.30 - '@tanstack/start-storage-context': 1.167.32 - fetchdts: 0.1.7 - h3-v2: h3@2.0.1-rc.20 - seroval: 1.6.7 - transitivePeerDependencies: - - crossws - '@tanstack/start-server-core@1.169.37': dependencies: '@tanstack/history': 1.162.4 @@ -2234,10 +2429,6 @@ snapshots: transitivePeerDependencies: - crossws - '@tanstack/start-storage-context@1.167.32': - dependencies: - '@tanstack/router-core': 1.171.30 - '@tanstack/start-storage-context@1.167.34': dependencies: '@tanstack/router-core': 1.171.32 @@ -2264,6 +2455,13 @@ snapshots: '@turbo/windows-arm64@2.10.13': optional: true + '@tybys/wasm-util@0.10.4': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/json-schema@7.0.15': {} + '@types/node@24.10.1': dependencies: undici-types: 7.16.0 @@ -2281,81 +2479,23 @@ snapshots: '@rolldown/pluginutils': 1.0.1 vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) - '@yuku-codegen/binding-android-arm64@0.10.2': - optional: true - - '@yuku-codegen/binding-darwin-arm64@0.10.2': - optional: true - - '@yuku-codegen/binding-darwin-x64@0.10.2': - optional: true - - '@yuku-codegen/binding-freebsd-x64@0.10.2': - optional: true - - '@yuku-codegen/binding-linux-arm-gnu@0.10.2': - optional: true - - '@yuku-codegen/binding-linux-arm-musl@0.10.2': - optional: true - - '@yuku-codegen/binding-linux-arm64-gnu@0.10.2': - optional: true - - '@yuku-codegen/binding-linux-arm64-musl@0.10.2': - optional: true - - '@yuku-codegen/binding-linux-x64-gnu@0.10.2': - optional: true - - '@yuku-codegen/binding-linux-x64-musl@0.10.2': - optional: true - - '@yuku-codegen/binding-win32-arm64@0.10.2': - optional: true - - '@yuku-codegen/binding-win32-x64@0.10.2': - optional: true - - '@yuku-parser/binding-android-arm64@0.10.2': - optional: true - - '@yuku-parser/binding-darwin-arm64@0.10.2': - optional: true - - '@yuku-parser/binding-darwin-x64@0.10.2': - optional: true - - '@yuku-parser/binding-freebsd-x64@0.10.2': - optional: true - - '@yuku-parser/binding-linux-arm-gnu@0.10.2': - optional: true - - '@yuku-parser/binding-linux-arm-musl@0.10.2': - optional: true - - '@yuku-parser/binding-linux-arm64-gnu@0.10.2': - optional: true - - '@yuku-parser/binding-linux-arm64-musl@0.10.2': - optional: true - - '@yuku-parser/binding-linux-x64-gnu@0.10.2': - optional: true - - '@yuku-parser/binding-linux-x64-musl@0.10.2': - optional: true - - '@yuku-parser/binding-win32-arm64@0.10.2': - optional: true + adm-zip@0.6.0: {} - '@yuku-parser/binding-win32-x64@0.10.2': - optional: true + ajv-formats@2.1.1: + dependencies: + ajv: 8.20.0 - '@yuku-toolchain/types@0.10.2': {} + ajv-keywords@5.1.0(ajv@8.20.0): + dependencies: + ajv: 8.20.0 + fast-deep-equal: 3.1.3 - adm-zip@0.6.0: {} + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.8 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 ansis@4.4.0: {} @@ -2388,6 +2528,8 @@ snapshots: dependencies: readdirp: 5.1.1 + commander@11.1.0: {} + convert-source-map@2.0.0: {} cookie-es@3.1.1: {} @@ -2398,28 +2540,30 @@ snapshots: dependencies: ms: 2.1.3 - defu@6.1.7: {} - detect-libc@2.1.2: {} diff@8.0.4: {} - dts-resolver@3.0.0: {} - electron-to-chromium@1.5.430: {} - empathic@2.0.1: {} + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 escalade@3.2.0: {} exsolve@1.1.1: {} + fast-deep-equal@3.1.3: {} + fast-string-truncated-width@3.0.3: {} fast-string-width@3.0.2: dependencies: fast-string-truncated-width: 3.0.3 + fast-uri@3.1.8: {} + fast-wrap-ansi@0.2.2: dependencies: fast-string-width: 3.0.2 @@ -2435,22 +2579,18 @@ snapshots: gensync@1.0.0-beta.2: {} - get-tsconfig@5.0.0-beta.6: - dependencies: - resolve-pkg-maps: 1.0.0 - h3@2.0.1-rc.20: dependencies: rou3: 0.8.1 srvx: 0.11.22 - hookable@6.1.1: {} - human-id@4.2.1: {} - import-meta-resolve@4.2.0: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 - import-without-cache@0.4.1: {} + import-meta-resolve@4.2.0: {} isbot@5.2.2: {} @@ -2458,6 +2598,8 @@ snapshots: dependencies: ws: 8.21.0 + jiti@2.4.2: {} + jiti@2.7.0: {} jju@1.4.0: {} @@ -2470,6 +2612,8 @@ snapshots: jsesc@3.1.0: {} + json-schema-traverse@1.0.0: {} + json5@2.2.3: {} jsonc-parser@3.3.1: {} @@ -2540,11 +2684,13 @@ snapshots: nanoid@3.3.19: {} - node-releases@2.0.55: {} - - obug@2.2.1: {} + node-fetch@2.7.0(encoding@0.1.13): + dependencies: + whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 - obug@3.0.0: {} + node-releases@2.0.55: {} package-manager-detector@1.8.0: {} @@ -2562,32 +2708,18 @@ snapshots: prettier@3.9.7: {} - quansync@1.0.0: {} - react-dom@19.3.0(react@19.3.0): dependencies: react: 19.3.0 scheduler: 0.28.0 + react-refresh@0.18.0: {} + react@19.3.0: {} readdirp@5.1.1: {} - resolve-pkg-maps@1.0.0: {} - - rolldown-plugin-dts@0.28.6(rolldown@1.2.9)(typescript@6.0.3): - dependencies: - dts-resolver: 3.0.0 - get-tsconfig: 5.0.0-beta.6 - obug: 3.0.0 - rolldown: 1.2.9 - yuku-ast: 0.10.2 - yuku-codegen: 0.10.2 - yuku-parser: 0.10.2 - optionalDependencies: - typescript: 6.0.3 - transitivePeerDependencies: - - oxc-resolver + require-from-string@2.0.2: {} rolldown@1.2.9: dependencies: @@ -2612,8 +2744,24 @@ snapshots: rou3@0.8.1: {} + rsbuild-plugin-dts@1.0.1(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(typescript@6.0.3): + dependencies: + '@ast-grep/napi': 0.45.3 + '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) + optionalDependencies: + typescript: 6.0.3 + + safer-buffer@2.1.2: {} + scheduler@0.28.0: {} + schema-utils@4.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.20.0 + ajv-formats: 2.1.1 + ajv-keywords: 5.1.0(ajv@8.20.0) + semver@6.3.1: {} semver@7.8.5: {} @@ -2634,6 +2782,8 @@ snapshots: srvx@0.11.22: {} + tapable@2.3.0: {} + tinyexec@1.3.1: {} tinyglobby@0.2.17: @@ -2641,32 +2791,9 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 - tree-kill@1.2.2: {} + tr46@0.0.3: {} - tsdown@0.23.0(typescript@6.0.3): - dependencies: - cac: 7.0.0 - defu: 6.1.7 - empathic: 2.0.1 - hookable: 6.1.1 - import-without-cache: 0.4.1 - obug: 2.2.1 - picomatch: 4.0.7 - rolldown: 1.2.9 - rolldown-plugin-dts: 0.28.6(rolldown@1.2.9)(typescript@6.0.3) - tinyexec: 1.3.1 - tinyglobby: 0.2.17 - tree-kill: 1.2.2 - unconfig-core: 7.5.0 - verkit: 0.4.0 - optionalDependencies: - typescript: 6.0.3 - transitivePeerDependencies: - - '@typescript/native-preview' - - '@volar/typescript' - - '@vue/language-core' - - oxc-resolver - - vue-tsc + tslib@2.8.1: {} turbo@2.10.13: optionalDependencies: @@ -2681,21 +2808,18 @@ snapshots: ufo@1.6.4: {} - unconfig-core@7.5.0: - dependencies: - '@quansync/fs': 1.1.0 - quansync: 1.0.0 - undici-types@7.16.0: {} undici@7.29.0: {} - unplugin@3.4.0(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)): + unplugin@3.4.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 optionalDependencies: + '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) + '@rspack/core': 2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23) rolldown: 1.2.9 vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) @@ -2709,8 +2833,6 @@ snapshots: dependencies: react: 19.3.0 - verkit@0.4.0: {} - vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1): dependencies: lightningcss: 1.33.0 @@ -2728,8 +2850,15 @@ snapshots: optionalDependencies: vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + webidl-conversions@3.0.1: {} + webpack-virtual-modules@0.6.2: {} + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + ws@8.21.0: {} xmlbuilder2@4.0.3: @@ -2743,43 +2872,4 @@ snapshots: yaml@2.9.1: {} - yuku-ast@0.10.2: - dependencies: - '@yuku-toolchain/types': 0.10.2 - - yuku-codegen@0.10.2: - dependencies: - '@yuku-toolchain/types': 0.10.2 - optionalDependencies: - '@yuku-codegen/binding-android-arm64': 0.10.2 - '@yuku-codegen/binding-darwin-arm64': 0.10.2 - '@yuku-codegen/binding-darwin-x64': 0.10.2 - '@yuku-codegen/binding-freebsd-x64': 0.10.2 - '@yuku-codegen/binding-linux-arm-gnu': 0.10.2 - '@yuku-codegen/binding-linux-arm-musl': 0.10.2 - '@yuku-codegen/binding-linux-arm64-gnu': 0.10.2 - '@yuku-codegen/binding-linux-arm64-musl': 0.10.2 - '@yuku-codegen/binding-linux-x64-gnu': 0.10.2 - '@yuku-codegen/binding-linux-x64-musl': 0.10.2 - '@yuku-codegen/binding-win32-arm64': 0.10.2 - '@yuku-codegen/binding-win32-x64': 0.10.2 - - yuku-parser@0.10.2: - dependencies: - '@yuku-toolchain/types': 0.10.2 - yuku-ast: 0.10.2 - optionalDependencies: - '@yuku-parser/binding-android-arm64': 0.10.2 - '@yuku-parser/binding-darwin-arm64': 0.10.2 - '@yuku-parser/binding-darwin-x64': 0.10.2 - '@yuku-parser/binding-freebsd-x64': 0.10.2 - '@yuku-parser/binding-linux-arm-gnu': 0.10.2 - '@yuku-parser/binding-linux-arm-musl': 0.10.2 - '@yuku-parser/binding-linux-arm64-gnu': 0.10.2 - '@yuku-parser/binding-linux-arm64-musl': 0.10.2 - '@yuku-parser/binding-linux-x64-gnu': 0.10.2 - '@yuku-parser/binding-linux-x64-musl': 0.10.2 - '@yuku-parser/binding-win32-arm64': 0.10.2 - '@yuku-parser/binding-win32-x64': 0.10.2 - zod@4.6.5: {} diff --git a/test/example-build.test.mjs b/test/example-build.test.mjs index 7ed7d7d..7d05edc 100644 --- a/test/example-build.test.mjs +++ b/test/example-build.test.mjs @@ -7,18 +7,23 @@ import test from "node:test"; const root = join(dirname(fileURLToPath(import.meta.url)), ".."); const hostDist = join(root, "apps", "host", "dist"); const remoteDist = join(root, "apps", "remote", "dist"); +const rsbuildHostDist = join(root, "apps", "rsbuild-host", "dist"); +const rsbuildRemoteDist = join(root, "apps", "rsbuild-remote", "dist"); function readJson(path) { return JSON.parse(readFileSync(path, "utf8")); } -test("both TanStack Start apps produce client and server builds", () => { +test("all TanStack Start apps produce client and server builds", () => { for (const [name, dist] of [ ["host", hostDist], ["remote", remoteDist], + ["rsbuild host", rsbuildHostDist], + ["rsbuild remote", rsbuildRemoteDist], ]) { - assert.ok(existsSync(join(dist, "client", ".vite", "manifest.json")), `${name} client`); - assert.ok(existsSync(join(dist, "server", "server.js")), `${name} server`); + assert.ok(existsSync(join(dist, "client", "mf-manifest.json")), `${name} client`); + const serverEntry = name.startsWith("rsbuild") ? "index.js" : "server.js"; + assert.ok(existsSync(join(dist, "server", serverEntry)), `${name} server`); } }); @@ -51,3 +56,46 @@ test("host records the TanStack remote and server-side loader", () => { assert.match(serverFiles, /ssrEntryLoader/); assert.match(serverFiles, /tanstack_remote/); }); + +test("Vite and Rsbuild publish reciprocal client interoperability contracts", () => { + const viteHostManifest = readJson(join(hostDist, "client", "mf-manifest.json")); + const rsbuildHostManifest = readJson(join(rsbuildHostDist, "client", "mf-manifest.json")); + const rsbuildRemoteClient = readJson(join(rsbuildRemoteDist, "client", "mf-manifest.json")); + + assert.equal( + viteHostManifest.remotes.find(({ alias }) => alias === "tanstack_rsbuild_remote")?.moduleName, + "StatusCard", + ); + assert.equal( + rsbuildHostManifest.remotes.find(({ alias }) => alias === "tanstack_remote")?.entry, + "http://127.0.0.1:3001/mf-manifest.json", + ); + assert.equal( + rsbuildHostManifest.remotes.find(({ alias }) => alias === "tanstack_rsbuild_remote")?.entry, + "http://127.0.0.1:3002/mf-manifest.json", + ); + assert.equal(rsbuildRemoteClient.metaData.remoteEntry.type, "global"); + assert.equal(rsbuildRemoteClient.metaData.remoteEntry.name, "remoteEntry.js"); + const statusCard = rsbuildRemoteClient.exposes.find(({ path }) => path === "./StatusCard"); + assert.ok(statusCard?.assets.css.sync.length > 0, "Rsbuild StatusCard CSS is published"); + assert.equal( + existsSync(join(rsbuildRemoteDist, "server", "serverRemoteEntry.cjs")), + false, + "client-only cross-bundler remote does not publish an unused SSR container", + ); + assert.equal( + existsSync(join(rsbuildHostDist, "server", "mf-manifest.json")), + false, + "client-only cross-bundler host does not initialize remotes during SSR", + ); + + for (const manifest of [rsbuildRemoteClient]) { + for (const dependency of ["react", "react-dom"]) { + assert.equal( + manifest.shared.find(({ name }) => name === dependency)?.singleton, + true, + `${dependency} is a singleton`, + ); + } + } +}); diff --git a/test/example-runtime.test.mjs b/test/example-runtime.test.mjs index b4f92cd..54b5a67 100644 --- a/test/example-runtime.test.mjs +++ b/test/example-runtime.test.mjs @@ -97,3 +97,48 @@ test("host server-renders a component from the TanStack remote", async () => { await stopApp(remote); } }); + +test("Vite and Rsbuild hosts serve reciprocal browser-interoperability shells", async () => { + const viteRemote = startApp("tanstack-start-remote"); + const rsbuildRemote = startApp("tanstack-start-rsbuild-remote"); + let viteHost; + let rsbuildHost; + + try { + await waitForResponse( + "http://127.0.0.1:3001/remoteEntry.js", + viteRemote, + ({ status }) => status === 200, + ); + const manifest = await waitForResponse( + "http://127.0.0.1:3002/mf-manifest.json", + rsbuildRemote, + ({ status }) => status === 200, + ); + assert.match(manifest.body, /tanstack_rsbuild_remote/); + + viteHost = startApp("tanstack-start-host"); + rsbuildHost = startApp("tanstack-start-rsbuild-host"); + + const viteResponse = await waitForResponse( + "http://127.0.0.1:3000/", + viteHost, + ({ status }) => status === 200, + ); + const rsbuildResponse = await waitForResponse( + "http://127.0.0.1:3003/", + rsbuildHost, + ({ status }) => status === 200, + ); + + assert.match(viteResponse.body, /Rsbuild remote loads after hydration/); + assert.match(rsbuildResponse.body, /Rspack host, two remote formats/); + assert.match(rsbuildResponse.body, /Loading the Rsbuild remote/); + assert.match(rsbuildResponse.body, /Loading the Vite remote/); + } finally { + if (rsbuildHost) await stopApp(rsbuildHost); + if (viteHost) await stopApp(viteHost); + await stopApp(rsbuildRemote); + await stopApp(viteRemote); + } +}); diff --git a/test/release-artifacts.test.mjs b/test/release-artifacts.test.mjs index e8f6555..f87a4b4 100644 --- a/test/release-artifacts.test.mjs +++ b/test/release-artifacts.test.mjs @@ -9,16 +9,24 @@ const root = join(dirname(fileURLToPath(import.meta.url)), ".."); const packageRoot = join(root, "packages", "tanstack"); const dist = join(packageRoot, "dist"); -test("published declarations reference an included source map", () => { - const declarationName = "index.d.mts"; - const declaration = readFileSync(join(dist, declarationName), "utf8"); - const sourceMap = declaration.match(/\/\/[#@]\s*sourceMappingURL=([^\s]+)\s*$/m)?.[1]; +test("published declarations reference included source maps", () => { + for (const declarationName of [ + "index.d.cts", + "index.d.ts", + "rsbuild.d.cts", + "rsbuild.d.ts", + "vite.d.cts", + "vite.d.ts", + ]) { + const declaration = readFileSync(join(dist, declarationName), "utf8"); + const sourceMap = declaration.match(/\/\/[#@]\s*sourceMappingURL=([^\s]+)\s*$/m)?.[1]; - assert.ok(sourceMap, `${declarationName} has no source map reference`); - assert.ok( - existsSync(join(dist, sourceMap)), - `${declarationName} references missing source map ${sourceMap}`, - ); + assert.ok(sourceMap, `${declarationName} has no source map reference`); + assert.ok( + existsSync(join(dist, sourceMap)), + `${declarationName} references missing source map ${sourceMap}`, + ); + } }); test("npm package contains only runtime, metadata, and documentation files", () => { @@ -31,14 +39,22 @@ test("npm package contains only runtime, metadata, and documentation files", () const [manifest] = JSON.parse(packed.stdout); const files = manifest.files.map(({ path }) => path).sort(); - assert.deepEqual(files, [ - "CHANGELOG.md", - "LICENSE", - "README.md", - "dist/index.d.mts", - "dist/index.d.mts.map", - "dist/index.mjs", - "dist/index.mjs.map", - "package.json", - ]); + const runtimeFiles = ["index", "rsbuild", "shared", "vite"].flatMap((entry) => { + const sourceMaps = entry === "index" ? [] : [`dist/${entry}.js.map`]; + return [ + `dist/${entry}.cjs`, + `dist/${entry}.cjs.map`, + `dist/${entry}.d.cts`, + `dist/${entry}.d.cts.map`, + `dist/${entry}.d.ts`, + `dist/${entry}.d.ts.map`, + `dist/${entry}.js`, + ...sourceMaps, + ]; + }); + + assert.deepEqual( + files, + ["CHANGELOG.md", "LICENSE", "README.md", ...runtimeFiles, "package.json"].sort(), + ); }); diff --git a/test/release-contracts.test.mjs b/test/release-contracts.test.mjs index 9413310..b38f9e3 100644 --- a/test/release-contracts.test.mjs +++ b/test/release-contracts.test.mjs @@ -1,8 +1,19 @@ import assert from "node:assert/strict"; -import { existsSync, readFileSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { + cpSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + symlinkSync, + writeFileSync, +} from "node:fs"; import { createRequire } from "node:module"; import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; +import { tmpdir } from "node:os"; +import { fileURLToPath, pathToFileURL } from "node:url"; import test from "node:test"; const root = join(dirname(fileURLToPath(import.meta.url)), ".."); @@ -13,7 +24,15 @@ const rootPackageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf const ciWorkflow = readFileSync(join(root, ".github", "workflows", "ci.yml"), "utf8"); const previewWorkflow = readFileSync(join(root, ".github", "workflows", "pkg-pr-new.yml"), "utf8"); const releaseWorkflow = readFileSync(join(root, ".github", "workflows", "release.yml"), "utf8"); -const { tanstackStartModuleFederation } = await import(join(packageRoot, "dist", "index.mjs")); +const { tanstackStartModuleFederation } = await import( + pathToFileURL(join(packageRoot, "dist", "index.js")).href +); +const { eagerShared, resolveShared } = await import( + pathToFileURL(join(packageRoot, "dist", "shared.js")).href +); +const { tanstackStartModuleFederation: tanstackStartRsbuildModuleFederation } = await import( + pathToFileURL(join(packageRoot, "dist", "rsbuild.js")).href +); function upstreamOptions(options) { const plugins = tanstackStartModuleFederation(options); @@ -35,6 +54,7 @@ test("applies TanStack-safe defaults", () => { assert.equal(options.manifest, true); assert.equal(options.hostInitInjectLocation, "entry"); assert.equal(options.shared.react.shareConfig.singleton, true); + assert.equal(options.shared.react.shareConfig.eager, false, "Vite defaults stay non-eager"); assert.equal(options.shared["react-dom"].shareConfig.singleton, true); }); @@ -81,19 +101,187 @@ test("leaves the build target to each TanStack environment", () => { assert.equal(options.target, undefined); }); -test("published metadata points to ESM build and declaration map", () => { +test("Rsbuild defaults make synchronous React shares eager without mutating overrides", () => { + const callerShared = { react: { eager: false, singleton: false } }; + const before = structuredClone(callerShared); + const shared = resolveShared(callerShared, eagerShared); + + assert.deepEqual(callerShared, before); + assert.deepEqual(shared.react, { eager: false, singleton: false }); + assert.deepEqual(shared["react-dom"], { eager: true, singleton: true }); +}); + +test("Rsbuild configures separate browser and async-node federation environments", () => { + const plugins = tanstackStartRsbuildModuleFederation({ + federation: { name: "test_remote" }, + }); + + assert.deepEqual( + plugins.map(({ name }) => name), + [ + "rsbuild:module-federation-enhanced", + "tanstack-start-federation-client-compat", + "rsbuild:module-federation-enhanced", + "tanstack-start-federation-ssr-compat", + ], + ); + + const clientConfig = { name: "client", output: {} }; + const serverConfig = { + name: "ssr", + output: { chunkLoadingGlobal: "stale" }, + }; + applyCompilerHook( + plugins.find(({ name }) => name === "tanstack-start-federation-client-compat"), + [clientConfig, serverConfig], + ); + applyCompilerHook( + plugins.find(({ name }) => name === "tanstack-start-federation-ssr-compat"), + [clientConfig, serverConfig], + ); + + assert.deepEqual(clientConfig.output, { + chunkFormat: "array-push", + chunkLoading: "jsonp", + chunkLoadingGlobal: "chunk_test_remote", + module: false, + uniqueName: "test_remote", + }); + assert.equal(serverConfig.target, "async-node"); + assert.deepEqual(serverConfig.output, { + chunkFilename: "[id].test_remote.cjs", + chunkFormat: "commonjs", + chunkLoading: "async-node", + filename: "[name].cjs", + library: { type: "commonjs2" }, + module: false, + }); + + assert.equal( + tanstackStartRsbuildModuleFederation({ + federation: { name: "browser_only" }, + server: false, + }).length, + 2, + ); + + const esmPlugins = tanstackStartRsbuildModuleFederation({ + federation: { name: "esm_server" }, + server: { forceCommonJsOutput: false }, + }); + const esmServerConfig = { + name: "ssr", + output: { chunkFilename: "[id].js", filename: "[name].js" }, + }; + applyCompilerHook( + esmPlugins.find(({ name }) => name === "tanstack-start-federation-ssr-compat"), + [esmServerConfig], + ); + assert.deepEqual(esmServerConfig, { + name: "ssr", + output: { chunkFilename: "[id].js", filename: "[name].js" }, + }); +}); + +function applyCompilerHook(plugin, bundlerConfigs) { + let hook; + plugin.setup({ + onBeforeCreateCompiler(callback) { + hook = callback; + }, + }); + assert.ok(hook, `${plugin.name} registers a compiler hook`); + hook({ bundlerConfigs }); +} + +test("published metadata selects ESM and CommonJS builds with matching types", () => { assert.equal(packageJson.type, "module"); - assert.equal(packageJson.exports["."].import, "./dist/index.mjs"); - assert.equal(packageJson.exports["."].types, "./dist/index.d.mts"); + assert.equal(packageJson.exports["."].import.default, "./dist/index.js"); + assert.equal(packageJson.exports["."].import.types, "./dist/index.d.ts"); + assert.equal(packageJson.exports["."].require.default, "./dist/index.cjs"); + assert.equal(packageJson.exports["."].require.types, "./dist/index.d.cts"); assert.equal( packageRequire.resolve("@module-federation/tanstack"), - join(packageRoot, "dist", "index.mjs"), + join(packageRoot, "dist", "index.cjs"), ); - for (const file of ["index.mjs", "index.mjs.map", "index.d.mts", "index.d.mts.map"]) { + for (const file of ["index.cjs", "index.js", "index.d.cts", "index.d.ts"]) { assert.ok(existsSync(join(packageRoot, "dist", file)), `dist/${file} exists`); } }); +test("clean consumers resolve CJS and ESM entrypoints without the other adapter", () => { + const viteConsumer = createConsumer(["@module-federation/vite"]); + const rsbuildConsumer = createConsumer(["@module-federation/rsbuild-plugin", "@rsbuild/core"]); + + try { + const viteRequire = createRequire(join(viteConsumer, "consumer.cjs")); + const rsbuildRequire = createRequire(join(rsbuildConsumer, "consumer.cjs")); + const cjsRoot = viteRequire("@module-federation/tanstack"); + const cjsRsbuild = rsbuildRequire("@module-federation/tanstack/rsbuild"); + + assert.equal(typeof cjsRoot.tanstackStartModuleFederation, "function"); + assert.equal(typeof cjsRsbuild.tanstackStartModuleFederation, "function"); + assertConsumerImport(viteConsumer, "@module-federation/tanstack"); + assertConsumerImport(viteConsumer, "@module-federation/tanstack/vite"); + assertConsumerImport(rsbuildConsumer, "@module-federation/tanstack/rsbuild"); + } finally { + rmSync(viteConsumer, { force: true, recursive: true }); + rmSync(rsbuildConsumer, { force: true, recursive: true }); + } + + assert.doesNotMatch( + readFileSync(join(packageRoot, "dist", "rsbuild.js"), "utf8"), + /@module-federation\/vite/, + ); + assert.doesNotMatch( + readFileSync(join(packageRoot, "dist", "vite.js"), "utf8"), + /@module-federation\/rsbuild-plugin/, + ); +}); + +function assertConsumerImport(consumer, specifier) { + const entry = join(consumer, "consumer.mjs"); + writeFileSync( + entry, + `import { tanstackStartModuleFederation } from ${JSON.stringify(specifier)};\n` + + `if (typeof tanstackStartModuleFederation !== "function") process.exit(2);\n`, + ); + const result = spawnSync(process.execPath, [entry], { + cwd: consumer, + encoding: "utf8", + }); + assert.equal(result.status, 0, result.stderr); +} + +function createConsumer(adapterPackages) { + const consumer = mkdtempSync(join(tmpdir(), "mf-tanstack-consumer-")); + const packageTarget = join(consumer, "node_modules", "@module-federation", "tanstack"); + mkdirSync(dirname(packageTarget), { recursive: true }); + cpSync(packageRoot, packageTarget, { + filter: (source) => !source.includes("/node_modules/") && !source.includes("/.turbo/"), + recursive: true, + }); + + for (const packageName of adapterPackages) { + const adapterRoot = findPackageRoot(packageRequire.resolve(packageName)); + const adapterTarget = join(consumer, "node_modules", ...packageName.split("/")); + mkdirSync(dirname(adapterTarget), { recursive: true }); + symlinkSync(adapterRoot, adapterTarget); + } + + return consumer; +} + +function findPackageRoot(entryPoint) { + let directory = dirname(entryPoint); + while (!existsSync(join(directory, "package.json"))) { + const parent = dirname(directory); + if (parent === directory) throw new Error(`Could not find package root for ${entryPoint}`); + directory = parent; + } + return directory; +} + test("CI uses Node versions supported by the package build toolchain", () => { const supportedNode = "^22.18.0 || ^24.11.0 || >=26.0.0"; assert.equal(rootPackageJson.engines.node, supportedNode); @@ -102,6 +290,19 @@ test("CI uses Node versions supported by the package build toolchain", () => { assert.doesNotMatch(ciWorkflow, /22\.12\.0/); }); +test("bundler adapters are isolated optional peers", () => { + for (const dependency of [ + "@module-federation/rsbuild-plugin", + "@module-federation/vite", + "@rsbuild/core", + "vite", + ]) { + assert.ok(packageJson.peerDependencies[dependency], `${dependency} has a peer range`); + assert.equal(packageJson.peerDependenciesMeta[dependency]?.optional, true); + } + assert.equal(packageJson.optionalDependencies, undefined); +}); + test("GitHub prereleases publish the version validated against their tag", () => { assert.match(releaseWorkflow, /Tag\/version mismatch/); assert.doesNotMatch(releaseWorkflow, /Set deterministic prerelease version/); diff --git a/tsconfig.json b/tsconfig.json index 2aa37eb..2292037 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,8 @@ "references": [ { "path": "./packages/tanstack/tsconfig.json" }, { "path": "./apps/host/tsconfig.json" }, - { "path": "./apps/remote/tsconfig.json" } + { "path": "./apps/remote/tsconfig.json" }, + { "path": "./apps/rsbuild-host/tsconfig.json" }, + { "path": "./apps/rsbuild-remote/tsconfig.json" } ] } From 6f60ba934bc3bd457301012899ef3a052af29937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Fri, 18 Sep 2026 10:43:44 +0200 Subject: [PATCH 04/18] refactor: rename Vite examples --- README.md | 4 +- apps/README.md | 5 +- apps/rsbuild-host/rsbuild.config.ts | 2 +- apps/rsbuild-host/src/routes/index.tsx | 5 +- apps/{host => vite-host}/package.json | 2 +- apps/{host => vite-host}/src/remote.d.ts | 2 +- apps/{host => vite-host}/src/routeTree.gen.ts | 0 apps/{host => vite-host}/src/router.tsx | 0 .../src/routes/__root.tsx | 2 +- apps/{host => vite-host}/src/routes/index.tsx | 8 +-- apps/{host => vite-host}/src/styles.css | 0 apps/{host => vite-host}/tsconfig.json | 0 apps/{host => vite-host}/vite.config.ts | 6 +- apps/{remote => vite-remote}/package.json | 2 +- .../src/components/StatusCard.css | 0 .../src/components/StatusCard.tsx | 0 .../src/routeTree.gen.ts | 0 apps/{remote => vite-remote}/src/router.tsx | 0 .../src/routes/__root.tsx | 2 +- .../src/routes/index.tsx | 0 apps/{remote => vite-remote}/src/styles.css | 0 apps/{remote => vite-remote}/tsconfig.json | 0 apps/{remote => vite-remote}/vite.config.ts | 2 +- docs/RELEASING.md | 2 +- pnpm-lock.yaml | 68 +++++++++---------- test/example-build.test.mjs | 26 +++---- test/example-runtime.test.mjs | 10 +-- tsconfig.json | 4 +- 28 files changed, 78 insertions(+), 74 deletions(-) rename apps/{host => vite-host}/package.json (94%) rename apps/{host => vite-host}/src/remote.d.ts (84%) rename apps/{host => vite-host}/src/routeTree.gen.ts (100%) rename apps/{host => vite-host}/src/router.tsx (100%) rename apps/{remote => vite-host}/src/routes/__root.tsx (93%) rename apps/{host => vite-host}/src/routes/index.tsx (82%) rename apps/{host => vite-host}/src/styles.css (100%) rename apps/{host => vite-host}/tsconfig.json (100%) rename apps/{host => vite-host}/vite.config.ts (88%) rename apps/{remote => vite-remote}/package.json (94%) rename apps/{remote => vite-remote}/src/components/StatusCard.css (100%) rename apps/{remote => vite-remote}/src/components/StatusCard.tsx (100%) rename apps/{remote => vite-remote}/src/routeTree.gen.ts (100%) rename apps/{remote => vite-remote}/src/router.tsx (100%) rename apps/{host => vite-remote}/src/routes/__root.tsx (93%) rename apps/{remote => vite-remote}/src/routes/index.tsx (100%) rename apps/{remote => vite-remote}/src/styles.css (100%) rename apps/{remote => vite-remote}/tsconfig.json (100%) rename apps/{remote => vite-remote}/vite.config.ts (94%) diff --git a/README.md b/README.md index daa6e7c..a49299a 100644 --- a/README.md +++ b/README.md @@ -93,8 +93,8 @@ TanStack Start's standard `dist/server/index.js` entry. ## Example -The workspace includes Vite and Rsbuild pairs. `apps/remote` exposes a -stateful React card to `apps/host`. `apps/rsbuild-remote` exposes a second card +The workspace includes Vite and Rsbuild pairs. `apps/vite-remote` exposes a +stateful React card to `apps/vite-host`. `apps/rsbuild-remote` exposes a second card to the Vite host, while `apps/rsbuild-host` consumes the Vite remote. Both cross-bundler links use manifest URLs. They render after hydration, so this is browser interop coverage, not an unverified SSR interoperability claim. diff --git a/apps/README.md b/apps/README.md index 7f41b71..dbce32d 100644 --- a/apps/README.md +++ b/apps/README.md @@ -2,9 +2,10 @@ Both sides of this example are TanStack Start applications. -- `remote` exposes `./StatusCard` from its Module Federation container and also +- `vite-remote` exposes `./StatusCard` from its Module Federation container and also renders the component on its own `/` route. -- `host` loads `tanstack_remote/StatusCard` from the remote running on port 3001. Its own application runs on port 3000. +- `vite-host` loads `tanstack_vite_remote/StatusCard` from the remote running on + port 3001. Its own application runs on port 3000. From the repository root: diff --git a/apps/rsbuild-host/rsbuild.config.ts b/apps/rsbuild-host/rsbuild.config.ts index 713e8b7..73ad455 100644 --- a/apps/rsbuild-host/rsbuild.config.ts +++ b/apps/rsbuild-host/rsbuild.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ dts: false, name: "tanstack_rsbuild_host", remotes: { - tanstack_remote: "tanstack_remote@http://127.0.0.1:3001/mf-manifest.json", + tanstack_vite_remote: "tanstack_vite_remote@http://127.0.0.1:3001/mf-manifest.json", tanstack_rsbuild_remote: "tanstack_rsbuild_remote@http://127.0.0.1:3002/mf-manifest.json", }, }, diff --git a/apps/rsbuild-host/src/routes/index.tsx b/apps/rsbuild-host/src/routes/index.tsx index 8a5c075..41b643c 100644 --- a/apps/rsbuild-host/src/routes/index.tsx +++ b/apps/rsbuild-host/src/routes/index.tsx @@ -14,7 +14,10 @@ function Home() { fallback="Loading the Rsbuild remote…" remote="tanstack_rsbuild_remote/StatusCard" /> - + ); diff --git a/apps/host/package.json b/apps/vite-host/package.json similarity index 94% rename from apps/host/package.json rename to apps/vite-host/package.json index 1dbba92..65015fb 100644 --- a/apps/host/package.json +++ b/apps/vite-host/package.json @@ -1,5 +1,5 @@ { - "name": "tanstack-start-host", + "name": "tanstack-start-vite-host", "private": true, "sideEffects": false, "type": "module", diff --git a/apps/host/src/remote.d.ts b/apps/vite-host/src/remote.d.ts similarity index 84% rename from apps/host/src/remote.d.ts rename to apps/vite-host/src/remote.d.ts index fe82049..a2c6621 100644 --- a/apps/host/src/remote.d.ts +++ b/apps/vite-host/src/remote.d.ts @@ -1,4 +1,4 @@ -declare module "tanstack_remote/StatusCard" { +declare module "tanstack_vite_remote/StatusCard" { import type { ComponentType } from "react"; const StatusCard: ComponentType; diff --git a/apps/host/src/routeTree.gen.ts b/apps/vite-host/src/routeTree.gen.ts similarity index 100% rename from apps/host/src/routeTree.gen.ts rename to apps/vite-host/src/routeTree.gen.ts diff --git a/apps/host/src/router.tsx b/apps/vite-host/src/router.tsx similarity index 100% rename from apps/host/src/router.tsx rename to apps/vite-host/src/router.tsx diff --git a/apps/remote/src/routes/__root.tsx b/apps/vite-host/src/routes/__root.tsx similarity index 93% rename from apps/remote/src/routes/__root.tsx rename to apps/vite-host/src/routes/__root.tsx index ad43dab..0571218 100644 --- a/apps/remote/src/routes/__root.tsx +++ b/apps/vite-host/src/routes/__root.tsx @@ -8,7 +8,7 @@ export const Route = createRootRoute({ meta: [ { charSet: "utf-8" }, { name: "viewport", content: "width=device-width, initial-scale=1" }, - { title: "TanStack Start federation remote" }, + { title: "TanStack Start federation Vite host" }, ], }), component: RootComponent, diff --git a/apps/host/src/routes/index.tsx b/apps/vite-host/src/routes/index.tsx similarity index 82% rename from apps/host/src/routes/index.tsx rename to apps/vite-host/src/routes/index.tsx index 7a03a21..f124c7c 100644 --- a/apps/host/src/routes/index.tsx +++ b/apps/vite-host/src/routes/index.tsx @@ -1,11 +1,11 @@ import { createFileRoute } from "@tanstack/react-router"; import { lazy, Suspense, useEffect, useState } from "react"; -const RemoteStatusCard = lazy(() => import("tanstack_remote/StatusCard")); +const RemoteStatusCard = lazy(() => import("tanstack_vite_remote/StatusCard")); const RsbuildStatusCard = lazy(() => import("tanstack_rsbuild_remote/StatusCard")); export const Route = createFileRoute("/")({ - loader: () => import("tanstack_remote/StatusCard").then(() => null), + loader: () => import("tanstack_vite_remote/StatusCard").then(() => null), component: Home, }); @@ -16,8 +16,8 @@ function Home() {

TanStack Start × Module Federation

Two full-stack apps. One React tree.

- This page belongs to the host on port 3000. The interactive card below is owned and built - by a second TanStack Start application on port 3001. + This page belongs to the Vite host on port 3000. The interactive card below is owned and + built by a second TanStack Start application on port 3001.

diff --git a/apps/host/src/styles.css b/apps/vite-host/src/styles.css similarity index 100% rename from apps/host/src/styles.css rename to apps/vite-host/src/styles.css diff --git a/apps/host/tsconfig.json b/apps/vite-host/tsconfig.json similarity index 100% rename from apps/host/tsconfig.json rename to apps/vite-host/tsconfig.json diff --git a/apps/host/vite.config.ts b/apps/vite-host/vite.config.ts similarity index 88% rename from apps/host/vite.config.ts rename to apps/vite-host/vite.config.ts index 6832e5a..20fbedc 100644 --- a/apps/host/vite.config.ts +++ b/apps/vite-host/vite.config.ts @@ -6,12 +6,12 @@ import { defineConfig } from "vite"; export default defineConfig({ plugins: [ tanstackStartModuleFederation({ - name: "tanstack_host", + name: "tanstack_vite_host", dts: false, remotes: { - tanstack_remote: { + tanstack_vite_remote: { type: "module", - name: "tanstack_remote", + name: "tanstack_vite_remote", entry: "http://127.0.0.1:3001/remoteEntry.js", }, tanstack_rsbuild_remote: { diff --git a/apps/remote/package.json b/apps/vite-remote/package.json similarity index 94% rename from apps/remote/package.json rename to apps/vite-remote/package.json index 3a5c6a8..74045fe 100644 --- a/apps/remote/package.json +++ b/apps/vite-remote/package.json @@ -1,5 +1,5 @@ { - "name": "tanstack-start-remote", + "name": "tanstack-start-vite-remote", "private": true, "sideEffects": false, "type": "module", diff --git a/apps/remote/src/components/StatusCard.css b/apps/vite-remote/src/components/StatusCard.css similarity index 100% rename from apps/remote/src/components/StatusCard.css rename to apps/vite-remote/src/components/StatusCard.css diff --git a/apps/remote/src/components/StatusCard.tsx b/apps/vite-remote/src/components/StatusCard.tsx similarity index 100% rename from apps/remote/src/components/StatusCard.tsx rename to apps/vite-remote/src/components/StatusCard.tsx diff --git a/apps/remote/src/routeTree.gen.ts b/apps/vite-remote/src/routeTree.gen.ts similarity index 100% rename from apps/remote/src/routeTree.gen.ts rename to apps/vite-remote/src/routeTree.gen.ts diff --git a/apps/remote/src/router.tsx b/apps/vite-remote/src/router.tsx similarity index 100% rename from apps/remote/src/router.tsx rename to apps/vite-remote/src/router.tsx diff --git a/apps/host/src/routes/__root.tsx b/apps/vite-remote/src/routes/__root.tsx similarity index 93% rename from apps/host/src/routes/__root.tsx rename to apps/vite-remote/src/routes/__root.tsx index ab2e206..987b3fc 100644 --- a/apps/host/src/routes/__root.tsx +++ b/apps/vite-remote/src/routes/__root.tsx @@ -8,7 +8,7 @@ export const Route = createRootRoute({ meta: [ { charSet: "utf-8" }, { name: "viewport", content: "width=device-width, initial-scale=1" }, - { title: "TanStack Start federation host" }, + { title: "TanStack Start federation Vite remote" }, ], }), component: RootComponent, diff --git a/apps/remote/src/routes/index.tsx b/apps/vite-remote/src/routes/index.tsx similarity index 100% rename from apps/remote/src/routes/index.tsx rename to apps/vite-remote/src/routes/index.tsx diff --git a/apps/remote/src/styles.css b/apps/vite-remote/src/styles.css similarity index 100% rename from apps/remote/src/styles.css rename to apps/vite-remote/src/styles.css diff --git a/apps/remote/tsconfig.json b/apps/vite-remote/tsconfig.json similarity index 100% rename from apps/remote/tsconfig.json rename to apps/vite-remote/tsconfig.json diff --git a/apps/remote/vite.config.ts b/apps/vite-remote/vite.config.ts similarity index 94% rename from apps/remote/vite.config.ts rename to apps/vite-remote/vite.config.ts index 146faf2..9fd7628 100644 --- a/apps/remote/vite.config.ts +++ b/apps/vite-remote/vite.config.ts @@ -6,7 +6,7 @@ import { defineConfig } from "vite"; export default defineConfig({ plugins: [ tanstackStartModuleFederation({ - name: "tanstack_remote", + name: "tanstack_vite_remote", dts: false, exposes: { "./StatusCard": "./src/components/StatusCard.tsx", diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 909b926..7f536c6 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -42,7 +42,7 @@ a different dist-tag. Configure npm trusted publishing for repository `module-federation/tanstack`, workflow `release.yml`, environment `Publish`. The release gate builds the Vite and Rsbuild example pairs. It checks the Vite -host server bundle and both directions of client manifest interoperability. The +Vite host server bundle and both directions of client manifest interoperability. The Rsbuild example remote is browser-only; package tests cover the adapter's async-node CommonJS SSR configuration. Browser-level coverage for hydrated cross-bundler rendering remains follow-up work. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38a72c0..0262cbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,7 +21,7 @@ importers: specifier: 6.0.3 version: 6.0.3 - apps/host: + apps/rsbuild-host: dependencies: '@module-federation/runtime': specifier: 2.9.0 @@ -39,9 +39,18 @@ importers: specifier: 19.3.0 version: 19.3.0(react@19.3.0) devDependencies: + '@module-federation/rsbuild-plugin': + specifier: 2.9.0 + version: 2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) '@module-federation/tanstack': specifier: workspace:* version: link:../../packages/tanstack + '@rsbuild/core': + specifier: 2.2.8 + version: 2.2.8(@module-federation/runtime-tools@2.9.0) + '@rsbuild/plugin-react': + specifier: 2.1.0 + version: 2.1.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23)) '@types/node': specifier: 24.10.1 version: 24.10.1 @@ -51,17 +60,11 @@ importers: '@types/react-dom': specifier: 19.3.0 version: 19.3.0(@types/react@19.3.0) - '@vitejs/plugin-react': - specifier: 6.1.1 - version: 6.1.1(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) typescript: specifier: 6.0.3 version: 6.0.3 - vite: - specifier: 8.3.0 - version: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) - apps/remote: + apps/rsbuild-remote: dependencies: '@module-federation/runtime': specifier: 2.9.0 @@ -79,9 +82,18 @@ importers: specifier: 19.3.0 version: 19.3.0(react@19.3.0) devDependencies: + '@module-federation/rsbuild-plugin': + specifier: 2.9.0 + version: 2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) '@module-federation/tanstack': specifier: workspace:* version: link:../../packages/tanstack + '@rsbuild/core': + specifier: 2.2.8 + version: 2.2.8(@module-federation/runtime-tools@2.9.0) + '@rsbuild/plugin-react': + specifier: 2.1.0 + version: 2.1.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23)) '@types/node': specifier: 24.10.1 version: 24.10.1 @@ -91,17 +103,11 @@ importers: '@types/react-dom': specifier: 19.3.0 version: 19.3.0(@types/react@19.3.0) - '@vitejs/plugin-react': - specifier: 6.1.1 - version: 6.1.1(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) typescript: specifier: 6.0.3 version: 6.0.3 - vite: - specifier: 8.3.0 - version: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) - apps/rsbuild-host: + apps/vite-host: dependencies: '@module-federation/runtime': specifier: 2.9.0 @@ -119,18 +125,9 @@ importers: specifier: 19.3.0 version: 19.3.0(react@19.3.0) devDependencies: - '@module-federation/rsbuild-plugin': - specifier: 2.9.0 - version: 2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) '@module-federation/tanstack': specifier: workspace:* version: link:../../packages/tanstack - '@rsbuild/core': - specifier: 2.2.8 - version: 2.2.8(@module-federation/runtime-tools@2.9.0) - '@rsbuild/plugin-react': - specifier: 2.1.0 - version: 2.1.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23)) '@types/node': specifier: 24.10.1 version: 24.10.1 @@ -140,11 +137,17 @@ importers: '@types/react-dom': specifier: 19.3.0 version: 19.3.0(@types/react@19.3.0) + '@vitejs/plugin-react': + specifier: 6.1.1 + version: 6.1.1(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) typescript: specifier: 6.0.3 version: 6.0.3 + vite: + specifier: 8.3.0 + version: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) - apps/rsbuild-remote: + apps/vite-remote: dependencies: '@module-federation/runtime': specifier: 2.9.0 @@ -162,18 +165,9 @@ importers: specifier: 19.3.0 version: 19.3.0(react@19.3.0) devDependencies: - '@module-federation/rsbuild-plugin': - specifier: 2.9.0 - version: 2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) '@module-federation/tanstack': specifier: workspace:* version: link:../../packages/tanstack - '@rsbuild/core': - specifier: 2.2.8 - version: 2.2.8(@module-federation/runtime-tools@2.9.0) - '@rsbuild/plugin-react': - specifier: 2.1.0 - version: 2.1.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23)) '@types/node': specifier: 24.10.1 version: 24.10.1 @@ -183,9 +177,15 @@ importers: '@types/react-dom': specifier: 19.3.0 version: 19.3.0(@types/react@19.3.0) + '@vitejs/plugin-react': + specifier: 6.1.1 + version: 6.1.1(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) typescript: specifier: 6.0.3 version: 6.0.3 + vite: + specifier: 8.3.0 + version: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) packages/tanstack: devDependencies: diff --git a/test/example-build.test.mjs b/test/example-build.test.mjs index 7d05edc..d362d90 100644 --- a/test/example-build.test.mjs +++ b/test/example-build.test.mjs @@ -5,8 +5,8 @@ import { fileURLToPath } from "node:url"; import test from "node:test"; const root = join(dirname(fileURLToPath(import.meta.url)), ".."); -const hostDist = join(root, "apps", "host", "dist"); -const remoteDist = join(root, "apps", "remote", "dist"); +const viteHostDist = join(root, "apps", "vite-host", "dist"); +const viteRemoteDist = join(root, "apps", "vite-remote", "dist"); const rsbuildHostDist = join(root, "apps", "rsbuild-host", "dist"); const rsbuildRemoteDist = join(root, "apps", "rsbuild-remote", "dist"); @@ -16,8 +16,8 @@ function readJson(path) { test("all TanStack Start apps produce client and server builds", () => { for (const [name, dist] of [ - ["host", hostDist], - ["remote", remoteDist], + ["Vite host", viteHostDist], + ["Vite remote", viteRemoteDist], ["rsbuild host", rsbuildHostDist], ["rsbuild remote", rsbuildRemoteDist], ]) { @@ -28,12 +28,12 @@ test("all TanStack Start apps produce client and server builds", () => { }); test("remote publishes browser and server federation entries", () => { - const client = join(remoteDist, "client"); + const client = join(viteRemoteDist, "client"); assert.ok(existsSync(join(client, "remoteEntry.js"))); assert.ok(existsSync(join(client, "remoteEntry.ssr.js"))); const manifest = readJson(join(client, "mf-manifest.json")); - assert.equal(manifest.name, "tanstack_remote"); + assert.equal(manifest.name, "tanstack_vite_remote"); assert.equal(manifest.metaData.remoteEntry.name, "remoteEntry.js"); assert.equal(manifest.metaData.ssrRemoteEntry.name, "remoteEntry.ssr.js"); @@ -47,18 +47,18 @@ test("remote publishes browser and server federation entries", () => { } }); -test("host records the TanStack remote and server-side loader", () => { - const manifest = readJson(join(hostDist, "client", "mf-manifest.json")); - const remote = manifest.remotes.find(({ alias }) => alias === "tanstack_remote"); +test("Vite host records the TanStack remote and server-side loader", () => { + const manifest = readJson(join(viteHostDist, "client", "mf-manifest.json")); + const remote = manifest.remotes.find(({ alias }) => alias === "tanstack_vite_remote"); assert.equal(remote?.moduleName, "StatusCard"); - const serverFiles = readFileSync(join(hostDist, "server", ".vite", "manifest.json"), "utf8"); + const serverFiles = readFileSync(join(viteHostDist, "server", ".vite", "manifest.json"), "utf8"); assert.match(serverFiles, /ssrEntryLoader/); - assert.match(serverFiles, /tanstack_remote/); + assert.match(serverFiles, /tanstack_vite_remote/); }); test("Vite and Rsbuild publish reciprocal client interoperability contracts", () => { - const viteHostManifest = readJson(join(hostDist, "client", "mf-manifest.json")); + const viteHostManifest = readJson(join(viteHostDist, "client", "mf-manifest.json")); const rsbuildHostManifest = readJson(join(rsbuildHostDist, "client", "mf-manifest.json")); const rsbuildRemoteClient = readJson(join(rsbuildRemoteDist, "client", "mf-manifest.json")); @@ -67,7 +67,7 @@ test("Vite and Rsbuild publish reciprocal client interoperability contracts", () "StatusCard", ); assert.equal( - rsbuildHostManifest.remotes.find(({ alias }) => alias === "tanstack_remote")?.entry, + rsbuildHostManifest.remotes.find(({ alias }) => alias === "tanstack_vite_remote")?.entry, "http://127.0.0.1:3001/mf-manifest.json", ); assert.equal( diff --git a/test/example-runtime.test.mjs b/test/example-runtime.test.mjs index 54b5a67..a09ba30 100644 --- a/test/example-runtime.test.mjs +++ b/test/example-runtime.test.mjs @@ -71,8 +71,8 @@ async function stopApp(child) { } } -test("host server-renders a component from the TanStack remote", async () => { - const remote = startApp("tanstack-start-remote"); +test("Vite host server-renders a component from the Vite remote", async () => { + const remote = startApp("tanstack-start-vite-remote"); let host; try { @@ -83,7 +83,7 @@ test("host server-renders a component from the TanStack remote", async () => { ); assert.match(remoteEntry.body, /virtual:mf-exposes/); - host = startApp("tanstack-start-host"); + host = startApp("tanstack-start-vite-host"); const response = await waitForResponse( "http://127.0.0.1:3000/", host, @@ -99,7 +99,7 @@ test("host server-renders a component from the TanStack remote", async () => { }); test("Vite and Rsbuild hosts serve reciprocal browser-interoperability shells", async () => { - const viteRemote = startApp("tanstack-start-remote"); + const viteRemote = startApp("tanstack-start-vite-remote"); const rsbuildRemote = startApp("tanstack-start-rsbuild-remote"); let viteHost; let rsbuildHost; @@ -117,7 +117,7 @@ test("Vite and Rsbuild hosts serve reciprocal browser-interoperability shells", ); assert.match(manifest.body, /tanstack_rsbuild_remote/); - viteHost = startApp("tanstack-start-host"); + viteHost = startApp("tanstack-start-vite-host"); rsbuildHost = startApp("tanstack-start-rsbuild-host"); const viteResponse = await waitForResponse( diff --git a/tsconfig.json b/tsconfig.json index 2292037..50148eb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,8 @@ "files": [], "references": [ { "path": "./packages/tanstack/tsconfig.json" }, - { "path": "./apps/host/tsconfig.json" }, - { "path": "./apps/remote/tsconfig.json" }, + { "path": "./apps/vite-host/tsconfig.json" }, + { "path": "./apps/vite-remote/tsconfig.json" }, { "path": "./apps/rsbuild-host/tsconfig.json" }, { "path": "./apps/rsbuild-remote/tsconfig.json" } ] From c73cf94bda433eb90cbfffa4471857b91a1a40ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Fri, 18 Sep 2026 11:16:19 +0200 Subject: [PATCH 05/18] docs: detail SSR completion work --- TODO.md | 285 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..47d2940 --- /dev/null +++ b/TODO.md @@ -0,0 +1,285 @@ +# SSR completion TODO + +This file tracks the work required before `@module-federation/tanstack` can +claim SSR support across both Vite and Rsbuild/Rspack. + +Assessment date: 2026-09-18. + +## Current support boundary + +| Host | Remote | Federated SSR status | Current proof | +| ------- | ------- | -------------------- | ------------------------------------------------------------------------------- | +| Vite | Vite | Supported | The Vite host response contains remote markup before hydration. | +| Vite | Rsbuild | Not supported | The Rsbuild remote loads in the browser after hydration. | +| Rsbuild | Vite | Not supported | The Vite remote loads through the browser runtime after hydration. | +| Rsbuild | Rsbuild | Not yet supported | The adapter can build a server container, but no end-to-end SSR fixture exists. | + +The Rsbuild examples are TanStack Start SSR applications, so their local route +shells render on the server. Their Module Federation configuration passes +`server: false`; federated components are client-only. Do not describe that as +federated SSR. + +The supported claim today is: + +> Vite-to-Vite federated SSR, plus Vite/Rsbuild browser interoperability. + +## Evidence behind this boundary + +### Vite-to-Vite SSR works + +- [`packages/tanstack/src/vite.ts`](packages/tanstack/src/vite.ts) registers one + Vite federation plugin for TanStack Start's client and server environments. +- The wrapper leaves `target` unset so `@module-federation/vite` selects `web` + for the client and `node` for SSR. +- `hostInitInjectLocation: "entry"` initializes federation before TanStack + hydrates an application without an `index.html` entry. +- `module-federation/vite` enables its SSR entry loader for every production + build and for Vite 8 development. Vite 7 development does not have the + ModuleRunner support required by this path. +- Vite remotes emit a separate ESM `remoteEntry.ssr.js` and include it in + `mf-manifest.json` as `metaData.ssrRemoteEntry`. +- The Vite SSR loader resolves the manifest entry, links shared packages to the + host, and evaluates the remote through Vite ModuleRunner, `vm.SourceTextModule`, + or a temporary ESM graph. +- [`test/example-runtime.test.mjs`](test/example-runtime.test.mjs) verifies that + the Vite host's initial HTML contains `Owned by the remote app` and + `Rendered on the server`. + +Reference implementation paths: + +- `module-federation/vite/src/utils/ssrCapabilities.ts` +- `module-federation/vite/src/plugins/pluginSSRRemoteEntry.ts` +- `module-federation/vite/src/utils/ssrEntryLoader.ts` +- `module-federation/vite/src/plugins/pluginAddEntry.ts` +- `module-federation/vite/src/virtualModules/virtualRuntimeInitStatus.ts` + +### Rsbuild has the required pieces, but not the proof + +- [`packages/tanstack/src/rsbuild.ts`](packages/tanstack/src/rsbuild.ts) installs + separate `web` and `node` federation plugins for TanStack Start's `client` + and `ssr` environments. +- The browser compiler emits script/JSONP-compatible output. +- The server compiler can emit async-node CommonJS output with + `serverRemoteEntry.cjs` and `dist/server/index.cjs`. +- React and React DOM default to eager singletons for Rsbuild. TanStack's server + bundle imports React synchronously, and Core reports `RUNTIME-006` when those + shares are lazy. +- Core's `target: "node"` path changes the compiler target to `async-node`, + creates a CommonJS server container, and injects + `@module-federation/node/runtimePlugin`. +- Core only installs its startup dependency handling when + `experiments.asyncStartup` is enabled. The TanStack adapter does not default + this option today. +- Both committed Rsbuild examples pass `server: false`, so the server adapter is + covered by compiler-contract tests, not by a running host/remote pair. + +Reference implementation paths: + +- `module-federation/core/packages/rsbuild-plugin/src/cli/index.ts` +- `module-federation/core/packages/rsbuild-plugin/src/utils/ssr.ts` +- `module-federation/core/packages/node/src/runtimePlugin.ts` +- `module-federation/core/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts` +- `module-federation/core/packages/webpack-bundler-runtime/src/installInitialConsumes.ts` + +### Cross-bundler SSR needs an explicit transport contract + +The two server entry formats differ: + +- Vite emits an ESM SSR entry and an ESM dependency graph. +- The current Rsbuild adapter emits a CommonJS container and async-node chunks. + +The Vite loader can `require()` a local CommonJS entry. For an HTTP CommonJS +entry, `createRequire()` cannot load the URL, and the loader falls through to +paths designed around ESM source. That is not a reliable Rsbuild remote loader. + +In the other direction, Core's Node runtime has ESM loading support, but this +repository does not prove that an Rsbuild host can load Vite's manifest-declared +ESM SSR entry, preserve the host React instance, and hydrate the result. + +## Phase 1: finish Rsbuild-to-Rsbuild SSR + +### Adapter defaults + +- [ ] Merge `experiments.asyncStartup: true` into the Rsbuild browser and server + federation options. +- [ ] Preserve an explicit `experiments.asyncStartup: false` override. +- [ ] Keep `react` and `react-dom` eager singletons by default. +- [ ] Cover React subpaths used by the compiled application, including + `react/jsx-runtime` and `react-dom/client`, without creating a second React + instance. +- [ ] Assert that Core's node target injects the Node runtime plugin exactly + once. +- [ ] Confirm that two environment-scoped Rsbuild plugins continue to install + one browser container and one server container. + +### Server-enabled fixtures + +- [ ] Add a dedicated Rsbuild remote with the server adapter enabled. +- [ ] Expose a stateful component from both `remoteEntry.js` and + `serverRemoteEntry.cjs`. +- [ ] Add a dedicated Rsbuild host with the server adapter enabled. +- [ ] Import the remote through a route loader or another real async boundary. +- [ ] Start the production remote from `dist/server/index.cjs`. +- [ ] Serve the client manifest, server entry, server chunks, JavaScript, CSS, + and federated type archive from stable URLs. +- [ ] Start the production host from `dist/server/index.cjs`. +- [ ] Ensure development and production use the same remote naming and share + scope. + +### Rsbuild SSR acceptance criteria + +- [ ] The host's initial HTML contains remote component markup. +- [ ] The initial HTML does not contain only a loading placeholder. +- [ ] Hydration completes without warnings or subtree replacement. +- [ ] A remote button remains interactive after hydration. +- [ ] Host context and hooks work inside the remote component. +- [ ] React and React DOM resolve to one physical host-owned instance. +- [ ] No `RUNTIME-006`, `RUNTIME-001`, or missing chunk error appears. +- [ ] The remote manifest declares a `commonjs-module` server entry. +- [ ] Every server entry import and async chunk is reachable in production. +- [ ] The built server output contains no build-machine absolute paths. + +## Phase 2: support a Vite host with an Rsbuild SSR remote + +Choose and document one server-entry strategy before implementing this path. + +### Option A: emit an ESM SSR entry from Rsbuild + +This aligns the remote with the existing Vite SSR loader. + +- [ ] Determine whether the Rsbuild server compiler can emit a second ESM + container without changing the CommonJS entry used by Rsbuild hosts. +- [ ] Decide how the manifest identifies both server formats. The current + manifest has one `ssrRemoteEntry` slot. +- [ ] Keep server chunks portable and reachable over HTTP. +- [ ] Verify that Vite's temporary-file and VM strategies can link the emitted + graph. + +### Option B: teach the Vite loader to evaluate HTTP CommonJS containers + +This aligns the host with the existing Rsbuild server output. + +- [ ] Add a bounded HTTP CommonJS loader rather than passing an HTTP URL to + `createRequire()`. +- [ ] Reuse Core's Node chunk-loading behavior where possible. +- [ ] Resolve relative async chunks from the remote public path. +- [ ] Preserve Node module-cache identity for React and other singletons. +- [ ] Apply the same fetch timeout and maximum-body limits used by the ESM + loader. +- [ ] Avoid unrestricted `eval` or unbounded code fetching. + +### Vite-host acceptance criteria + +- [ ] Vite 8 development renders the Rsbuild remote into initial HTML. +- [ ] Vite 8 production renders and hydrates the same remote. +- [ ] Vite 7 production works, or the package documents a narrower requirement. +- [ ] Remote CSS appears before or during hydration without a layout flash. +- [ ] Manifest revalidation replaces a changed remote without restarting the + host process. + +## Phase 3: support an Rsbuild host with a Vite SSR remote + +- [ ] Confirm that Core's Node runtime selects `metaData.ssrRemoteEntry` instead + of the browser entry in a Vite manifest. +- [ ] Confirm support for a manifest entry whose type is `module`. +- [ ] Load Vite's transitive ESM chunks without Node network-import flags. +- [ ] Map shared package imports to the Rsbuild host share scope. +- [ ] Remove any dependency on `--experimental-vm-modules` from the supported + default path. +- [ ] Verify development against Vite's `/__mf_ssr__/` and ModuleRunner + endpoints. +- [ ] Verify production against the built `remoteEntry.ssr.js` graph. + +### Rsbuild-host acceptance criteria + +- [ ] The Rsbuild host's initial HTML contains Vite remote markup. +- [ ] Hydration preserves that markup and remote state. +- [ ] The Node runtime reports no missing module, VM import, or chunk execution + error. +- [ ] A remote outage returns a controlled fallback instead of a 500 response. + +## Phase 4: shared identity and recovery hardening + +### Shared dependencies + +- [ ] Test React context created by the host and consumed by every remote. +- [ ] Test `useState`, `useEffect`, Suspense, and error boundaries across the + federation boundary. +- [ ] Test React 18 and React 19 if both remain in the support range. +- [ ] Validate version mismatch warnings for incompatible React majors. +- [ ] Ensure `import: false` fails at startup when the host cannot provide the + required package. + +### Remote lifecycle + +- [ ] Start the host while the remote is unavailable. +- [ ] Bring the remote online and prove that a later request recovers. +- [ ] Restart the remote with a new manifest and server entry. +- [ ] Revalidate without serving a stale container or stale shared module. +- [ ] Test concurrent first requests while the remote entry is loading. +- [ ] Test cyclic imports in the server remote graph. + +### Error reporting + +- [ ] Preserve the original network or execution error. +- [ ] Distinguish manifest failure, entry failure, chunk failure, and shared + dependency failure. +- [ ] Add optional observability reports for raw runtime codes that do not + contain enough context. + +## Required test matrix + +| Host | Remote | Development | Production | Initial remote HTML | Hydration and interaction | +| --------- | --------- | ------------ | ---------- | ------------------- | ------------------------- | +| Vite 8 | Vite 8 | Existing | Existing | Existing | Add browser assertion | +| Vite 7 | Vite 7 | Not required | Add | Add | Add | +| Rsbuild 2 | Rsbuild 2 | Add | Add | Add | Add | +| Vite 8 | Rsbuild 2 | Add | Add | Add | Add | +| Rsbuild 2 | Vite 8 | Add | Add | Add | Add | + +Run the production matrix on every supported Node line: + +- [ ] Node 22.18 +- [ ] Node 24 +- [ ] Node 26 + +Each matrix row must verify: + +- [ ] manifest and remote entry return 200; +- [ ] every manifest-referenced asset returns 200; +- [ ] initial HTML contains the expected remote marker; +- [ ] hydration produces no console errors or mismatch warnings; +- [ ] remote interaction updates state; +- [ ] host and remote use one React dispatcher; +- [ ] shutting down the remote produces the documented fallback; +- [ ] restarting the remote allows recovery. + +## Documentation and release work + +- [ ] Update the root README only after a matrix row satisfies every acceptance + criterion. +- [ ] Document `server: false` as browser-only federation, not SSR federation. +- [ ] Document `dist/server/index.cjs` when the CommonJS Rsbuild server adapter + is enabled. +- [ ] Document the chosen cross-bundler server-entry format. +- [ ] Keep Vite 8 as the development SSR requirement unless Vite 7 gains an + equivalent ModuleRunner path. +- [ ] Add a Changeset for any new supported SSR combination. +- [ ] Include the tested bundler and Node version matrix in the release notes. + +## Definition of done + +SSR support for a host/remote combination is complete only when: + +- [ ] the remote is rendered into the host's initial HTML; +- [ ] the same markup hydrates without replacement; +- [ ] the remote remains interactive; +- [ ] shared React identity is proven; +- [ ] development and production both pass where claimed; +- [ ] remote outage and recovery behavior are tested; +- [ ] server entries and all chunks are deployable without local paths; +- [ ] the supported versions are documented; +- [ ] CI runs the proof on every supported Node line. + +Until then, keep the package description precise: Vite-to-Vite SSR is supported; +Rsbuild and cross-bundler SSR are in progress. From 281d5fd9b34b2f80089d9f3d4291fcfeb306fd36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 6 Oct 2026 20:43:04 +0200 Subject: [PATCH 06/18] feat(tanstack): add browser federation coverage across hosts - Make Rsbuild SSR federation opt-in and eager React host shares - Add browser tests for hydration, remote interaction, and offline fallbacks - Document support boundaries and update release workflows --- .github/workflows/ci.yml | 1 + .github/workflows/release.yml | 1 + README.md | 52 +++-- TODO.md | 30 ++- apps/README.md | 58 +++-- apps/rsbuild-host/rsbuild.config.ts | 3 +- apps/rsbuild-remote/rsbuild.config.ts | 1 - .../src/components/RemoteBoundary.tsx | 28 +++ apps/vite-host/src/routes/index.tsx | 15 +- docs/RELEASING.md | 16 +- package.json | 1 + packages/tanstack/CHANGELOG.md | 16 +- packages/tanstack/README.md | 82 +++++-- packages/tanstack/package.json | 2 +- packages/tanstack/src/rsbuild.ts | 12 +- packages/tanstack/src/vite.ts | 12 +- pnpm-lock.yaml | 19 ++ test/example-runtime.test.mjs | 218 +++++++++++++----- test/release-contracts.test.mjs | 25 +- 19 files changed, 438 insertions(+), 154 deletions(-) create mode 100644 apps/vite-host/src/components/RemoteBoundary.tsx diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a5cd73..f16fdc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,5 +34,6 @@ jobs: - run: pnpm format:check - run: pnpm typecheck - run: pnpm build + - run: pnpm exec playwright install --with-deps chromium - run: pnpm test - run: pnpm pack:tanstack diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2043550..4ea8ab6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,6 +66,7 @@ jobs: - run: pnpm format:check - run: pnpm typecheck - run: pnpm build + - run: pnpm exec playwright install --with-deps chromium - run: pnpm test - run: pnpm pack:tanstack - name: Compute dist-tag diff --git a/README.md b/README.md index a49299a..285115d 100644 --- a/README.md +++ b/README.md @@ -48,15 +48,17 @@ export default defineConfig({ ``` Defaults are `remoteEntry.js`, `manifest: true`, and -`hostInitInjectLocation: "entry"`. React and `react-dom` are shared singleton -defaults; any explicit entries in `shared` take precedence. +`hostInitInjectLocation: "entry"`. React and `react-dom` are shared +singletons; when the config declares `remotes`, they are also eager so the +host's own React stays in the share scope. Explicit entries in `shared` take +precedence. Do not set a global Module Federation `target`. TanStack Start builds client and SSR environments from one Vite configuration, so the wrapper leaves target selection to `@module-federation/vite` for each environment. The package supports Node 22.18+, 24.11+, and 26+, plus Vite 7/8 and TanStack -Start 1.x. Use Vite 8 when developing SSR hosts that load remotes; Vite 7 +Start 1.167.43+. Use Vite 8 when developing SSR hosts that load remotes; Vite 7 remains supported for production builds. The package root remains a Vite compatibility export. New Vite projects can @@ -75,6 +77,7 @@ export default defineConfig({ ...tanstackStartModuleFederation({ federation: { name: "host", + shareStrategy: "loaded-first", remotes: { remote: "remote@https://example.test/mf-manifest.json" }, }, }), @@ -82,28 +85,39 @@ export default defineConfig({ }); ``` -The Rsbuild adapter registers browser and SSR federation plugins by default. It -emits script-compatible browser output and async-node CommonJS SSR output so -its manifest can be consumed by Vite and Rsbuild hosts. The wrapper adds eager, -singleton `react` and `react-dom` entries without changing the caller's config -object. Set `server: false` for browser-only remotes. +The Rsbuild adapter emits script-compatible browser output, so its manifest can +be consumed by Vite and Rsbuild hosts. It adds eager, singleton `react` and +`react-dom` entries without changing the caller's config object. Hosts should +use `shareStrategy: "loaded-first"` so one offline remote cannot fail startup. -Server federation mode emits `dist/server/index.cjs`; browser-only mode keeps -TanStack Start's standard `dist/server/index.js` entry. +SSR federation for Rsbuild is experimental and opt-in: `server: true` adds an +async-node CommonJS server container and changes the server entry to +`dist/server/index.cjs`. -## Example +## Support status -The workspace includes Vite and Rsbuild pairs. `apps/vite-remote` exposes a -stateful React card to `apps/vite-host`. `apps/rsbuild-remote` exposes a second card -to the Vite host, while `apps/rsbuild-host` consumes the Vite remote. Both -cross-bundler links use manifest URLs. They render after hydration, so this is -browser interop coverage, not an unverified SSR interoperability claim. +| Host | Remote | Browser federation | Federated SSR | +| ------- | ------- | ------------------ | ----------------------------- | +| Vite | Vite | Supported | Supported | +| Vite | Rsbuild | Supported | Not supported | +| Rsbuild | Vite | Supported | Not supported | +| Rsbuild | Rsbuild | Supported | Experimental (`server: true`) | + +[`TODO.md`](TODO.md) tracks the remaining SSR work. + +## Examples + +The workspace has four TanStack Start apps; see [`apps/README.md`](apps/README.md). ```bash pnpm install pnpm start ``` -This starts all four applications so both same-bundler and cross-bundler paths -are available. Open the Vite host at http://localhost:3000 or the Rsbuild host -at http://localhost:3003. +Open the Vite host at http://127.0.0.1:3000 or the Rsbuild host at +http://127.0.0.1:3003. Each host renders a card from both remotes. + +`pnpm test` builds everything, starts all four apps, and drives both hosts in +headless Chromium. It checks hydration, remote interactivity, console errors, +and the fallback when a remote is offline. Run +`pnpm exec playwright install chromium` once before the first local test run. diff --git a/TODO.md b/TODO.md index 47d2940..435b8be 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,9 @@ This file tracks the work required before `@module-federation/tanstack` can claim SSR support across both Vite and Rsbuild/Rspack. -Assessment date: 2026-09-18. +Assessment date: 2026-09-18. Updated 2026-10-06: the Rsbuild server adapter is now +opt-in (`server: true`), and browser tests cover hydration, shared React +identity, and remote outages for every host/remote pair. ## Current support boundary @@ -12,11 +14,11 @@ Assessment date: 2026-09-18. | Vite | Vite | Supported | The Vite host response contains remote markup before hydration. | | Vite | Rsbuild | Not supported | The Rsbuild remote loads in the browser after hydration. | | Rsbuild | Vite | Not supported | The Vite remote loads through the browser runtime after hydration. | -| Rsbuild | Rsbuild | Not yet supported | The adapter can build a server container, but no end-to-end SSR fixture exists. | +| Rsbuild | Rsbuild | Experimental | `server: true` builds a server container, but no end-to-end SSR fixture exists. | The Rsbuild examples are TanStack Start SSR applications, so their local route -shells render on the server. Their Module Federation configuration passes -`server: false`; federated components are client-only. Do not describe that as +shells render on the server. Their Module Federation configuration uses the +default browser-only mode; federated components are client-only. Do not describe that as federated SSR. The supported claim today is: @@ -70,8 +72,8 @@ Reference implementation paths: - Core only installs its startup dependency handling when `experiments.asyncStartup` is enabled. The TanStack adapter does not default this option today. -- Both committed Rsbuild examples pass `server: false`, so the server adapter is - covered by compiler-contract tests, not by a running host/remote pair. +- Both committed Rsbuild examples use the default browser-only mode, so the + server adapter is covered by compiler-contract tests, not by a running host/remote pair. Reference implementation paths: @@ -231,7 +233,7 @@ This aligns the host with the existing Rsbuild server output. | Host | Remote | Development | Production | Initial remote HTML | Hydration and interaction | | --------- | --------- | ------------ | ---------- | ------------------- | ------------------------- | -| Vite 8 | Vite 8 | Existing | Existing | Existing | Add browser assertion | +| Vite 8 | Vite 8 | Existing | Add | Existing | Existing (development) | | Vite 7 | Vite 7 | Not required | Add | Add | Add | | Rsbuild 2 | Rsbuild 2 | Add | Add | Add | Add | | Vite 8 | Rsbuild 2 | Add | Add | Add | Add | @@ -258,7 +260,7 @@ Each matrix row must verify: - [ ] Update the root README only after a matrix row satisfies every acceptance criterion. -- [ ] Document `server: false` as browser-only federation, not SSR federation. +- [x] Document browser-only federation as the Rsbuild default and SSR as opt-in. - [ ] Document `dist/server/index.cjs` when the CommonJS Rsbuild server adapter is enabled. - [ ] Document the chosen cross-bundler server-entry format. @@ -283,3 +285,15 @@ SSR support for a host/remote combination is complete only when: Until then, keep the package description precise: Vite-to-Vite SSR is supported; Rsbuild and cross-bundler SSR are in progress. + +## Example and development gaps + +- [ ] Run the browser tests against production builds, not only development + servers. +- [ ] A Vite remote with `exposes` does not hydrate when opened directly in + development: `@module-federation/vite` 1.22 forces host-driven init for + exposing containers (`forceClientInjected`). Report upstream, or add a + standalone init path. +- [ ] Decide whether the Rsbuild adapter should default hosts to + `shareStrategy: "loaded-first"`. With `version-first`, one offline remote + fails host startup with `RUNTIME-003`; the example sets it explicitly. diff --git a/apps/README.md b/apps/README.md index dbce32d..7e0c6cd 100644 --- a/apps/README.md +++ b/apps/README.md @@ -1,11 +1,14 @@ # TanStack Start examples -Both sides of this example are TanStack Start applications. +Four TanStack Start applications: a host and a remote for each bundler. Each +host consumes both remotes, so every host/remote combination runs. -- `vite-remote` exposes `./StatusCard` from its Module Federation container and also - renders the component on its own `/` route. -- `vite-host` loads `tanstack_vite_remote/StatusCard` from the remote running on - port 3001. Its own application runs on port 3000. +| App | Port | Role | +| ---------------- | ---- | ---------------------------------------------------------------------- | +| `vite-host` | 3000 | Server-renders the Vite remote; loads the Rsbuild remote on the client | +| `vite-remote` | 3001 | Exposes `./StatusCard`, rendered into the Vite host's initial HTML | +| `rsbuild-remote` | 3002 | Exposes `./StatusCard` through a browser manifest | +| `rsbuild-host` | 3003 | Loads both remotes with `loadRemote()` after hydration | From the repository root: @@ -13,16 +16,35 @@ From the repository root: pnpm start ``` -The federation wrapper must precede `tanstackStart()` in both Vite configs. -React and React DOM use the wrapper's singleton defaults. - -Each package's `start` script runs Vite's development server. In Vite's CLI, -the bare `vite` command means “start”; the literal `vite start` command would -treat `start` as a project directory. - -`rsbuild-remote` exposes the same kind of card through Rspack with a browser -manifest. Both hosts consume both remotes by manifest URL after hydration. -These examples exercise same-bundler and cross-bundler client interoperability; -they do not claim that cross-bundler SSR remote rendering is supported. - -`pnpm start` runs all four applications on ports 3000 through 3003. +Then open http://127.0.0.1:3000 or http://127.0.0.1:3003. Every remote card has +a counter button; clicking it proves the remote shares the host's React +instance. + +## What each host shows + +- **Vite host.** The route loader imports the Vite remote, so its card is in + the server response ("Rendered on the server") and hydrates in place. The + Rsbuild card mounts after hydration. Both remotes sit behind + `RemoteBoundary`, an error boundary with a fallback card, so a failed remote + never blanks the page. +- **Rsbuild host.** `RemoteCardSlot` calls `loadRemote()` in an effect and + shows "Remote unavailable" when a remote fails. The host uses + `shareStrategy: "loaded-first"`, so it does not fetch every remote's + manifest at startup. + +Stop the Rsbuild remote while both hosts are open and reload them: each host +keeps rendering and shows its fallback card. + +## Notes + +- The federation wrapper must come before `tanstackStart()` in the Vite + configs. +- Each `start` script runs the bundler's development server. In Vite's CLI the + bare `vite` command starts the dev server; `vite start` would treat `start` + as a project directory. +- In development, opening the Vite remote directly (port 3001) server-renders + the page but does not hydrate: `@module-federation/vite` waits for a host to + initialize a container that has `exposes`. It hydrates when loaded through a + host, and its production build hydrates standalone. +- Cross-bundler remotes render on the client only. Cross-bundler SSR is not + supported yet. diff --git a/apps/rsbuild-host/rsbuild.config.ts b/apps/rsbuild-host/rsbuild.config.ts index 73ad455..194206a 100644 --- a/apps/rsbuild-host/rsbuild.config.ts +++ b/apps/rsbuild-host/rsbuild.config.ts @@ -13,12 +13,13 @@ export default defineConfig({ federation: { dts: false, name: "tanstack_rsbuild_host", + // Resolve remotes on demand so one offline remote cannot block host startup. + shareStrategy: "loaded-first", remotes: { tanstack_vite_remote: "tanstack_vite_remote@http://127.0.0.1:3001/mf-manifest.json", tanstack_rsbuild_remote: "tanstack_rsbuild_remote@http://127.0.0.1:3002/mf-manifest.json", }, }, - server: false, }), ], }); diff --git a/apps/rsbuild-remote/rsbuild.config.ts b/apps/rsbuild-remote/rsbuild.config.ts index 73bbc22..57daa7a 100644 --- a/apps/rsbuild-remote/rsbuild.config.ts +++ b/apps/rsbuild-remote/rsbuild.config.ts @@ -18,7 +18,6 @@ export default defineConfig({ exposes: { "./StatusCard": "./src/components/StatusCard.tsx" }, name: "tanstack_rsbuild_remote", }, - server: false, }), ], }); diff --git a/apps/vite-host/src/components/RemoteBoundary.tsx b/apps/vite-host/src/components/RemoteBoundary.tsx new file mode 100644 index 0000000..7e953b5 --- /dev/null +++ b/apps/vite-host/src/components/RemoteBoundary.tsx @@ -0,0 +1,28 @@ +import { Component, Suspense, type ReactNode } from "react"; + +type Props = { children: ReactNode; fallback: string; name: string }; + +/** Keeps a failed remote from taking down the host route. */ +export class RemoteBoundary extends Component { + state = { failed: false }; + + static getDerivedStateFromError() { + return { failed: true }; + } + + componentDidCatch(error: unknown) { + console.warn(`[${this.props.name}] remote failed to render`, error); + } + + render() { + if (this.state.failed) { + return
{this.props.name} is unavailable
; + } + + return ( + {this.props.fallback}}> + {this.props.children} + + ); + } +} diff --git a/apps/vite-host/src/routes/index.tsx b/apps/vite-host/src/routes/index.tsx index f124c7c..5e7c0d5 100644 --- a/apps/vite-host/src/routes/index.tsx +++ b/apps/vite-host/src/routes/index.tsx @@ -1,5 +1,6 @@ import { createFileRoute } from "@tanstack/react-router"; -import { lazy, Suspense, useEffect, useState } from "react"; +import { lazy, useEffect, useState } from "react"; +import { RemoteBoundary } from "../components/RemoteBoundary"; const RemoteStatusCard = lazy(() => import("tanstack_vite_remote/StatusCard")); const RsbuildStatusCard = lazy(() => import("tanstack_rsbuild_remote/StatusCard")); @@ -16,8 +17,8 @@ function Home() {

TanStack Start × Module Federation

Two full-stack apps. One React tree.

- This page belongs to the Vite host on port 3000. The interactive card below is owned and - built by a second TanStack Start application on port 3001. + This page belongs to the Vite host on port 3000. The Vite remote on port 3001 renders its + card on the server; the Rsbuild remote on port 3002 loads in the browser after hydration.

@@ -29,9 +30,9 @@ function Home() {

The route, document shell, and server response come from the host app.

- Loading the remote app…}> + - + @@ -49,8 +50,8 @@ function RsbuildRemoteSlot() { if (!mounted) return
Rsbuild remote loads after hydration
; return ( - Loading the Rsbuild remote…}> + - + ); } diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 7f536c6..67cf10e 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -4,7 +4,7 @@ summary: Release and trusted npm publishing for @module-federation/tanstack. read_when: - Preparing a release - Updating release automation -updated_at: 2026-09-16 +updated_at: 2026-10-06 --- # Releasing @@ -41,8 +41,12 @@ The publish workflow reruns the gate, checks that the source is reachable from a different dist-tag. Configure npm trusted publishing for repository `module-federation/tanstack`, workflow `release.yml`, environment `Publish`. -The release gate builds the Vite and Rsbuild example pairs. It checks the Vite -Vite host server bundle and both directions of client manifest interoperability. The -Rsbuild example remote is browser-only; package tests cover the adapter's -async-node CommonJS SSR configuration. Browser-level coverage for hydrated -cross-bundler rendering remains follow-up work. +The release gate builds the Vite and Rsbuild example pairs and checks their +manifests and server bundles. It then starts all four apps and drives both hosts +in headless Chromium: Vite-to-Vite SSR markup, hydration, remote interactivity, +one shared React instance, no console errors, and the fallback when a remote is +offline. Package tests cover the opt-in Rsbuild async-node CommonJS SSR +configuration. + +The test suite needs Chromium. Locally, run `pnpm exec playwright install +chromium` once; the workflows install it before `pnpm test`. diff --git a/package.json b/package.json index f423342..38f4cb8 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "devDependencies": { "@changesets/cli": "3.0.3", + "playwright": "1.63.0", "prettier": "3.9.7", "turbo": "2.10.13", "typescript": "6.0.3" diff --git a/packages/tanstack/CHANGELOG.md b/packages/tanstack/CHANGELOG.md index ff942c3..477e138 100644 --- a/packages/tanstack/CHANGELOG.md +++ b/packages/tanstack/CHANGELOG.md @@ -2,4 +2,18 @@ ## 0.1.0 -- Initial TanStack Start integration for Module Federation. +Initial release: Module Federation for TanStack Start on Vite and Rsbuild. + +- `@module-federation/tanstack/vite` (also the package root) wraps + `@module-federation/vite` with TanStack Start defaults: `remoteEntry.js`, a + manifest, entry-level host init, and environment-specific build targets. +- Vite hosts share eager singleton `react` and `react-dom`, so a remote built + by another bundler cannot replace the host's React instance. +- `@module-federation/tanstack/rsbuild` wraps + `@module-federation/rsbuild-plugin` with script-compatible browser output and + eager singleton React shares. SSR federation is experimental and opt-in + through `server: true`. +- Supported: Vite-to-Vite federated SSR, plus browser federation for every + Vite/Rsbuild host and remote combination. +- Tested with Node 22.18, 24, and 26; Vite 8.3; Rsbuild 2.2; TanStack Start + 1.168; React 19.3. diff --git a/packages/tanstack/README.md b/packages/tanstack/README.md index fee8af5..d021415 100644 --- a/packages/tanstack/README.md +++ b/packages/tanstack/README.md @@ -4,12 +4,25 @@ TanStack Start integration for Module Federation on Vite and Rsbuild. The adapter packages are optional peers, so importing one adapter does not resolve the other. -The root import remains compatible with existing Vite configurations. New Vite -projects can use `/vite` explicitly: +## Support status + +| Host | Remote | Browser federation | Federated SSR | +| ------- | ------- | ------------------ | ----------------------------- | +| Vite | Vite | Supported | Supported | +| Vite | Rsbuild | Supported | Not supported | +| Rsbuild | Vite | Supported | Not supported | +| Rsbuild | Rsbuild | Supported | Experimental (`server: true`) | + +"Federated SSR" means the remote component is rendered into the host's initial +HTML. Every supported row is covered by a browser test that hydrates the remote, +clicks it, and checks for console errors. + +## Vite ```ts import { tanstackStartModuleFederation } from "@module-federation/tanstack/vite"; import { tanstackStart } from "@tanstack/react-start/plugin/vite"; +import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; export default defineConfig({ @@ -25,26 +38,33 @@ export default defineConfig({ }, }), tanstackStart(), + react(), ], }); ``` Place the federation wrapper before `tanstackStart()`, followed by the React -plugin. Defaults: `filename: "remoteEntry.js"`, `manifest: true`, -`hostInitInjectLocation: "entry"`, and singleton `react`/`react-dom` shared -entries. User-provided `shared` entries override those defaults. +plugin. The root import (`@module-federation/tanstack`) is the same Vite +adapter. + +Defaults: + +- `filename: "remoteEntry.js"`, `manifest: true`, and + `hostInitInjectLocation: "entry"`. +- `react` and `react-dom` are shared singletons. When the config declares + `remotes`, they are also `eager`, so the host's own React stays in the share + scope and a remote built by another bundler cannot replace it. Remote-only + builds stay lazy. +- Any entry you pass in `shared` replaces the default for that package. -The wrapper intentionally omits the global Module Federation `target` option. -TanStack Start's client and SSR environments need `@module-federation/vite` to -select `web` and `node` independently. +The wrapper omits the global Module Federation `target` option. TanStack +Start's client and SSR environments need `@module-federation/vite` to select +`web` and `node` independently. Production builds support Vite 7 and 8. Vite 8 is required for SSR remote loading during development. -For TanStack Start's Rsbuild integration, use the dedicated adapter. It adds -separate browser and SSR Module Federation plugins so Rspack emits a browser -script remote and async-node CommonJS SSR remote. For a browser-only remote, -pass `server: false`: +## Rsbuild ```ts import { defineConfig } from "@rsbuild/core"; @@ -66,14 +86,34 @@ export default defineConfig({ }); ``` -Install `@module-federation/vite` with Vite projects, or -`@module-federation/rsbuild-plugin` and `@rsbuild/core` with Rsbuild projects. -Those packages, plus `@tanstack/react-start`, are optional peers because each -adapter is loaded only by its matching subpath. +The adapter returns an array of Rsbuild plugins; spread it into `plugins`. It +emits script-compatible browser output, so the manifest can be consumed by Vite +and Rsbuild hosts. `react` and `react-dom` default to eager singletons because +TanStack Start imports React synchronously. + +For hosts, set `shareStrategy: "loaded-first"`. With the default +`version-first` strategy, the host fetches every remote's manifest during +startup, and a single offline remote fails the whole page. + +### Experimental SSR federation + +Pass `server: true` (or an options object) to add an async-node CommonJS server +container. This changes TanStack Start's server output to +`dist/server/index.cjs`; start it with `node dist/server/index.cjs`. No +end-to-end SSR test covers this mode yet, and Vite hosts cannot load the +CommonJS server container. + +## Known limitations + +- In development, a Vite remote with `exposes` does not hydrate when opened + directly in the browser: `@module-federation/vite` waits for a host to + initialize it. Open it through a host, or use a production build. +- Cross-bundler SSR is not supported. Cross-bundler remotes render after + hydration. -The default Rsbuild server adapter changes TanStack Start's server output to -async-node CommonJS. Start that build with `node dist/server/index.cjs`. Passing -`server: false` keeps TanStack Start's standard `dist/server/index.js` output -for browser-only federation. +## Requirements -Supported Node releases are 22.18+, 24.11+, and 26+. +- Node 22.18+, 24.11+, or 26+. +- TanStack Start 1.167.43+ (the first release with the Rsbuild integration). +- Vite: `@module-federation/vite` and `vite`. +- Rsbuild: `@module-federation/rsbuild-plugin` and `@rsbuild/core`. diff --git a/packages/tanstack/package.json b/packages/tanstack/package.json index 660843a..98c602c 100644 --- a/packages/tanstack/package.json +++ b/packages/tanstack/package.json @@ -78,7 +78,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "peerDependencies": { - "@tanstack/react-start": "^1.0.0", + "@tanstack/react-start": "^1.167.43", "@module-federation/rsbuild-plugin": "^2.9.0", "@module-federation/vite": "^1.22.0", "@rsbuild/core": "^2.0.0", diff --git a/packages/tanstack/src/rsbuild.ts b/packages/tanstack/src/rsbuild.ts index 96d78f3..a17c4e6 100644 --- a/packages/tanstack/src/rsbuild.ts +++ b/packages/tanstack/src/rsbuild.ts @@ -25,14 +25,18 @@ type ServerFederationOptions = export type TanStackStartRsbuildModuleFederationOptions = { client?: ClientFederationOptions; federation: ModuleFederationOptions; - server?: ServerFederationOptions; + /** + * Experimental SSR federation. Pass `true` or an options object to add an async-node + * CommonJS server container. Defaults to `false`: browser-only federation. + */ + server?: ServerFederationOptions | true; }; -/** Creates browser and SSR federation adapters for TanStack Start's Rsbuild integration. */ +/** Creates browser and optional SSR federation adapters for TanStack Start's Rsbuild integration. */ export function tanstackStartModuleFederation({ client = {}, federation, - server = {}, + server = false, }: TanStackStartRsbuildModuleFederationOptions): RsbuildPlugin[] { const { chunkLoadingGlobal, @@ -66,7 +70,7 @@ export function tanstackStartModuleFederation({ environment: serverEnvironment = "ssr", forceCommonJsOutput = true, ...serverOverrides - } = server; + } = server === true ? {} : server; const serverFederationOptions = withDefaults( { ...federation, ...serverOverrides }, forceCommonJsOutput ? "serverRemoteEntry.cjs" : "serverRemoteEntry.js", diff --git a/packages/tanstack/src/vite.ts b/packages/tanstack/src/vite.ts index ca1b162..b361e40 100644 --- a/packages/tanstack/src/vite.ts +++ b/packages/tanstack/src/vite.ts @@ -1,5 +1,5 @@ import { federation, type ModuleFederationOptions } from "@module-federation/vite"; -import { resolveShared } from "./shared"; +import { defaultShared, eagerShared, resolveShared } from "./shared"; export type TanStackStartModuleFederationOptions = Omit; @@ -11,11 +11,19 @@ export function tanstackStartModuleFederation(options: TanStackStartModuleFedera ...configured } = options as ModuleFederationOptions; + // A host renders with its own React before any remote registers a provider. Eager host + // shares keep that instance in the share scope, so remotes from other bundlers cannot + // replace it. Remote-only builds stay lazy and defer to the host. + const isHost = Object.keys(configured.remotes ?? {}).length > 0; + return federation({ ...configured, filename: configured.filename ?? "remoteEntry.js", manifest: configured.manifest ?? true, hostInitInjectLocation: configured.hostInitInjectLocation ?? "entry", - shared: resolveShared(shared) as ModuleFederationOptions["shared"], + shared: resolveShared( + shared, + isHost ? eagerShared : defaultShared, + ) as ModuleFederationOptions["shared"], }); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0262cbd..487ef81 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@changesets/cli': specifier: 3.0.3 version: 3.0.3 + playwright: + specifier: 1.63.0 + version: 1.63.0 prettier: specifier: 3.9.7 version: 3.9.7 @@ -1299,6 +1302,16 @@ packages: resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} + playwright-core@1.63.0: + resolution: {integrity: sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.63.0: + resolution: {integrity: sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==} + engines: {node: '>=20'} + hasBin: true + postcss@8.5.28: resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} @@ -2700,6 +2713,12 @@ snapshots: picomatch@4.0.7: {} + playwright-core@1.63.0: {} + + playwright@1.63.0: + dependencies: + playwright-core: 1.63.0 + postcss@8.5.28: dependencies: nanoid: 3.3.19 diff --git a/test/example-runtime.test.mjs b/test/example-runtime.test.mjs index a09ba30..8a4a5d8 100644 --- a/test/example-runtime.test.mjs +++ b/test/example-runtime.test.mjs @@ -2,10 +2,21 @@ import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import test from "node:test"; +import { after, before, test } from "node:test"; +import { chromium } from "playwright"; const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const urls = { + viteHost: "http://127.0.0.1:3000/", + viteRemote: "http://127.0.0.1:3001/", + rsbuildRemote: "http://127.0.0.1:3002/", + rsbuildHost: "http://127.0.0.1:3003/", +}; + +const apps = {}; +let browser; + function startApp(name) { const child = spawn("pnpm", ["--filter", name, "start"], { cwd: root, @@ -25,7 +36,7 @@ function startApp(name) { } async function waitForResponse(url, child, predicate = () => true) { - const deadline = Date.now() + 20_000; + const deadline = Date.now() + 30_000; let lastError; let lastResponse; @@ -58,7 +69,7 @@ async function waitForResponse(url, child, predicate = () => true) { } async function stopApp(child) { - if (child.exitCode !== null || child.signalCode !== null) return; + if (!child || child.exitCode !== null || child.signalCode !== null) return; process.kill(-child.pid, "SIGTERM"); await Promise.race([ @@ -71,74 +82,157 @@ async function stopApp(child) { } } +/** Opens a page and records uncaught errors and React warnings logged as console errors. */ +async function openPage(url) { + const page = await browser.newPage(); + const errors = []; + page.on("pageerror", (error) => errors.push(error.stack ?? error.message)); + page.on("console", (message) => { + if (message.type() === "error") errors.push(message.text()); + }); + await page.goto(url); + return { errors, page }; +} + +/** Clicks a remote card's counter and proves the remote's state updates on the host. */ +async function assertInteractive(page, cardText) { + const button = page + .locator("article", { hasText: cardText }) + .getByRole("button", { name: /Remote count/ }); + await button.waitFor({ timeout: 20_000 }); + const count = async () => Number((await button.textContent()).match(/\d+/)?.[0]); + + // A server-rendered button ignores clicks until React hydrates it. + const deadline = Date.now() + 20_000; + while ((await count()) === 0) { + assert.ok(Date.now() < deadline, `${cardText} never became interactive`); + await button.click(); + await page.waitForTimeout(100); + } + + const before = await count(); + await button.click(); + await page.waitForFunction( + ([text, expected]) => + [...document.querySelectorAll("article")] + .find((article) => article.textContent.includes(text)) + ?.querySelector("button") + ?.textContent.includes(String(expected)), + [cardText, before + 1], + { timeout: 5_000 }, + ); +} + +before(async () => { + apps.viteRemote = startApp("tanstack-start-vite-remote"); + apps.rsbuildRemote = startApp("tanstack-start-rsbuild-remote"); + await waitForResponse( + `${urls.viteRemote}remoteEntry.js`, + apps.viteRemote, + ({ status }) => status === 200, + ); + await waitForResponse( + `${urls.rsbuildRemote}mf-manifest.json`, + apps.rsbuildRemote, + ({ status }) => status === 200, + ); + + apps.viteHost = startApp("tanstack-start-vite-host"); + apps.rsbuildHost = startApp("tanstack-start-rsbuild-host"); + await waitForResponse(urls.viteHost, apps.viteHost, ({ status }) => status === 200); + await waitForResponse(urls.rsbuildHost, apps.rsbuildHost, ({ status }) => status === 200); + + browser = await chromium.launch(); +}); + +after(async () => { + await browser?.close(); + await Promise.all(Object.values(apps).map(stopApp)); +}); + test("Vite host server-renders a component from the Vite remote", async () => { - const remote = startApp("tanstack-start-vite-remote"); - let host; + const response = await waitForResponse( + urls.viteHost, + apps.viteHost, + ({ body, status }) => status === 200 && body.includes("Owned by the remote app"), + ); + + assert.match(response.body, /Two full-stack apps\. One React tree\./); + assert.match(response.body, /Rendered on the server/); + assert.match(response.body, /Rsbuild remote loads after hydration/); +}); + +test("Rsbuild remote serves a browser manifest", async () => { + const manifest = await waitForResponse( + `${urls.rsbuildRemote}mf-manifest.json`, + apps.rsbuildRemote, + ({ status }) => status === 200, + ); + assert.match(manifest.body, /tanstack_rsbuild_remote/); +}); + +test("Rsbuild host serves loading placeholders for both remotes", async () => { + const response = await waitForResponse(urls.rsbuildHost, apps.rsbuildHost); + assert.match(response.body, /Rspack host, two remote formats/); + assert.match(response.body, /Loading the Rsbuild remote/); + assert.match(response.body, /Loading the Vite remote/); +}); +test("Vite host hydrates the Vite and Rsbuild remotes with one React instance", async () => { + const { errors, page } = await openPage(urls.viteHost); try { - const remoteEntry = await waitForResponse( - "http://127.0.0.1:3001/remoteEntry.js", - remote, - ({ status }) => status === 200, - ); - assert.match(remoteEntry.body, /virtual:mf-exposes/); - - host = startApp("tanstack-start-vite-host"); - const response = await waitForResponse( - "http://127.0.0.1:3000/", - host, - ({ body, status }) => status === 200 && body.includes("Owned by the remote app"), - ); - - assert.match(response.body, /Two full-stack apps\. One React tree\./); - assert.match(response.body, /Rendered on the server/); + await page.getByText("Hydrated on the host").waitFor({ timeout: 20_000 }); + await assertInteractive(page, "Owned by the remote app"); + await assertInteractive(page, "Federated from Rsbuild"); + assert.deepEqual(errors, []); } finally { - if (host) await stopApp(host); - await stopApp(remote); + await page.close(); } }); -test("Vite and Rsbuild hosts serve reciprocal browser-interoperability shells", async () => { - const viteRemote = startApp("tanstack-start-vite-remote"); - const rsbuildRemote = startApp("tanstack-start-rsbuild-remote"); - let viteHost; - let rsbuildHost; +test("Rsbuild host loads the Vite and Rsbuild remotes with one React instance", async () => { + const { errors, page } = await openPage(urls.rsbuildHost); + try { + await assertInteractive(page, "Owned by the remote app"); + await assertInteractive(page, "Federated from Rsbuild"); + assert.deepEqual(errors, []); + } finally { + await page.close(); + } +}); +test("Rsbuild remote works as a standalone TanStack Start app", async () => { + const { errors, page } = await openPage(urls.rsbuildRemote); try { - await waitForResponse( - "http://127.0.0.1:3001/remoteEntry.js", - viteRemote, - ({ status }) => status === 200, - ); - const manifest = await waitForResponse( - "http://127.0.0.1:3002/mf-manifest.json", - rsbuildRemote, - ({ status }) => status === 200, - ); - assert.match(manifest.body, /tanstack_rsbuild_remote/); - - viteHost = startApp("tanstack-start-vite-host"); - rsbuildHost = startApp("tanstack-start-rsbuild-host"); - - const viteResponse = await waitForResponse( - "http://127.0.0.1:3000/", - viteHost, - ({ status }) => status === 200, - ); - const rsbuildResponse = await waitForResponse( - "http://127.0.0.1:3003/", - rsbuildHost, - ({ status }) => status === 200, - ); - - assert.match(viteResponse.body, /Rsbuild remote loads after hydration/); - assert.match(rsbuildResponse.body, /Rspack host, two remote formats/); - assert.match(rsbuildResponse.body, /Loading the Rsbuild remote/); - assert.match(rsbuildResponse.body, /Loading the Vite remote/); + await assertInteractive(page, "Federated from Rsbuild"); + assert.deepEqual(errors, []); } finally { - if (rsbuildHost) await stopApp(rsbuildHost); - if (viteHost) await stopApp(viteHost); - await stopApp(rsbuildRemote); - await stopApp(viteRemote); + await page.close(); + } +}); + +// @module-federation/vite 1.22 never self-initializes a container with `exposes` in +// development, so its React share waits for a host. Production builds hydrate standalone. +test.todo("Vite remote hydrates as a standalone app in development"); + +// Runs last: it stops the Rsbuild remote. +test("hosts keep working when the Rsbuild remote is offline", async () => { + await stopApp(apps.rsbuildRemote); + + for (const [url, fallback] of [ + [urls.viteHost, "Rsbuild remote is unavailable"], + [urls.rsbuildHost, "Remote unavailable"], + ]) { + const page = await browser.newPage(); + const uncaught = []; + page.on("pageerror", (error) => uncaught.push(error.stack ?? error.message)); + try { + await page.goto(url); + await page.getByText(fallback).waitFor({ timeout: 20_000 }); + await assertInteractive(page, "Owned by the remote app"); + assert.deepEqual(uncaught, [], url); + } finally { + await page.close(); + } } }); diff --git a/test/release-contracts.test.mjs b/test/release-contracts.test.mjs index b38f9e3..9f002f0 100644 --- a/test/release-contracts.test.mjs +++ b/test/release-contracts.test.mjs @@ -54,10 +54,28 @@ test("applies TanStack-safe defaults", () => { assert.equal(options.manifest, true); assert.equal(options.hostInitInjectLocation, "entry"); assert.equal(options.shared.react.shareConfig.singleton, true); - assert.equal(options.shared.react.shareConfig.eager, false, "Vite defaults stay non-eager"); + assert.equal(options.shared.react.shareConfig.eager, false, "remote-only builds stay lazy"); assert.equal(options.shared["react-dom"].shareConfig.singleton, true); }); +test("Vite hosts provide their own React eagerly so remotes cannot replace it", () => { + const options = upstreamOptions({ + name: "host", + remotes: { remote: { type: "module", name: "remote", entry: "remoteEntry.js" } }, + }); + for (const dependency of ["react", "react-dom"]) { + assert.equal(options.shared[dependency].shareConfig.singleton, true); + assert.equal(options.shared[dependency].shareConfig.eager, true, `${dependency} is eager`); + } + + const overridden = upstreamOptions({ + name: "host", + remotes: { remote: { type: "module", name: "remote", entry: "remoteEntry.js" } }, + shared: { react: { singleton: true } }, + }); + assert.equal(overridden.shared.react.shareConfig.eager, false, "explicit entries win"); +}); + test("preserves explicit federation settings and shared entries", () => { const options = upstreamOptions({ name: "remote", @@ -111,9 +129,10 @@ test("Rsbuild defaults make synchronous React shares eager without mutating over assert.deepEqual(shared["react-dom"], { eager: true, singleton: true }); }); -test("Rsbuild configures separate browser and async-node federation environments", () => { +test("Rsbuild opt-in SSR configures separate browser and async-node environments", () => { const plugins = tanstackStartRsbuildModuleFederation({ federation: { name: "test_remote" }, + server: true, }); assert.deepEqual( @@ -160,9 +179,9 @@ test("Rsbuild configures separate browser and async-node federation environments assert.equal( tanstackStartRsbuildModuleFederation({ federation: { name: "browser_only" }, - server: false, }).length, 2, + "SSR federation is opt-in", ); const esmPlugins = tanstackStartRsbuildModuleFederation({ From 4a1c1d57b737b98b4fa8128bcadc64e545a632e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 6 Oct 2026 20:53:20 +0200 Subject: [PATCH 07/18] fix(examples): disable remote type hints in the Rsbuild remote The dev-only dynamic-remote-type-hints runtime plugin opens a WebSocket to the federated type server. On CI runners the socket races host startup and logs console errors, which fails the browser tests on every Node version. Type generation is unchanged. --- .npmrc | 2 -- apps/rsbuild-remote/rsbuild.config.ts | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 .npmrc diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 90edf5a..0000000 --- a/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -auto-install-peers=false -prefer-frozen-lockfile=true diff --git a/apps/rsbuild-remote/rsbuild.config.ts b/apps/rsbuild-remote/rsbuild.config.ts index 57daa7a..5895726 100644 --- a/apps/rsbuild-remote/rsbuild.config.ts +++ b/apps/rsbuild-remote/rsbuild.config.ts @@ -15,6 +15,9 @@ export default defineConfig({ tanstackStart(), ...tanstackStartModuleFederation({ federation: { + // The type-hints runtime plugin opens a WebSocket to the dev type server, which + // races host startup and logs errors in the browser. Types are still generated. + dev: { disableDynamicRemoteTypeHints: true }, exposes: { "./StatusCard": "./src/components/StatusCard.tsx" }, name: "tanstack_rsbuild_remote", }, From b73c413852a8b65c6047a276b4c3bd5accb2216d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 6 Oct 2026 20:53:20 +0200 Subject: [PATCH 08/18] chore(deps): upgrade toolchain to latest releases - pnpm 12.9.1: move settings from .npmrc to pnpm-workspace.yaml and let pnpm/action-setup read the version from packageManager - TypeScript 7.0.2, @types/node 26, turbo 2.11, prettier 3.9.9, Rslib 1.0.3 - Module Federation 2.9.2 / @module-federation/vite 1.23.2, TanStack Start 1.168.60, Router 1.170.41, Vite 8.3.2, Rsbuild 2.2.11 - Bump actions/checkout to v7.0.1 and pnpm/action-setup to v6.1.0 - Build the CJS library before ESM: under TypeScript 7 the CJS declaration pass renames every .d.ts in dist, including the ESM declarations --- .github/workflows/ci.yml | 8 +- .github/workflows/pkg-pr-new.yml | 8 +- .github/workflows/release-pull-request.yml | 8 +- .github/workflows/release.yml | 8 +- README.md | 5 + TODO.md | 7 +- apps/rsbuild-host/package.json | 16 +- apps/rsbuild-remote/package.json | 16 +- apps/vite-host/package.json | 14 +- apps/vite-remote/package.json | 14 +- package.json | 8 +- packages/tanstack/package.json | 20 +- packages/tanstack/rslib.config.ts | 3 + pnpm-lock.yaml | 1584 ++++++++++++-------- pnpm-workspace.yaml | 3 + test/example-runtime.test.mjs | 5 +- 16 files changed, 1069 insertions(+), 658 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f16fdc3..1ce0e02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,11 +22,9 @@ jobs: matrix: node: ["22.18.0", "24", "26"] steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - - uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6 - with: - version: 10.15.0 - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: ${{ matrix.node }} cache: pnpm diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml index 5fad24d..0e0a314 100644 --- a/.github/workflows/pkg-pr-new.yml +++ b/.github/workflows/pkg-pr-new.yml @@ -11,11 +11,9 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - - uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6 - with: - version: 10.15.0 - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 cache: pnpm diff --git a/.github/workflows/release-pull-request.yml b/.github/workflows/release-pull-request.yml index 996f614..a48475c 100644 --- a/.github/workflows/release-pull-request.yml +++ b/.github/workflows/release-pull-request.yml @@ -21,14 +21,12 @@ jobs: run: | test "$GITHUB_REF" = "refs/heads/main" || { echo "Release PRs are main-only."; exit 1; } test -n "$REPO_SCOPED_TOKEN" || { echo "REPO_SCOPED_TOKEN is required."; exit 1; } - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 10 token: ${{ secrets.REPO_SCOPED_TOKEN }} - - uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6 - with: - version: 10.15.0 - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 cache: pnpm diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4ea8ab6..09a12cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: env: RELEASE_BRANCH: ${{ inputs.branch }} run: test "$RELEASE_BRANCH" = "main" || { echo "Releases are main-only."; exit 1; } - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 ref: ${{ github.event.release.tag_name || inputs.branch }} @@ -40,10 +40,8 @@ jobs: run: | git fetch --no-tags origin main:refs/remotes/origin/main git merge-base --is-ancestor HEAD origin/main || { echo "Source is not on main."; exit 1; } - - uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6 - with: - version: 10.15.0 - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 cache: pnpm diff --git a/README.md b/README.md index 285115d..40ddf2d 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,11 @@ async-node CommonJS server container and changes the server entry to The workspace has four TanStack Start apps; see [`apps/README.md`](apps/README.md). +The repository uses pnpm 12 (pinned in `packageManager`). pnpm 12 ships as a +native binary, so an older global pnpm cannot switch to it automatically; +install pnpm 12 itself (see https://pnpm.io/installation) before running these +commands. + ```bash pnpm install pnpm start diff --git a/TODO.md b/TODO.md index 435b8be..9dfe95a 100644 --- a/TODO.md +++ b/TODO.md @@ -291,9 +291,10 @@ Rsbuild and cross-bundler SSR are in progress. - [ ] Run the browser tests against production builds, not only development servers. - [ ] A Vite remote with `exposes` does not hydrate when opened directly in - development: `@module-federation/vite` 1.22 forces host-driven init for - exposing containers (`forceClientInjected`). Report upstream, or add a - standalone init path. + development: `@module-federation/vite` forces host-driven init for + exposing containers (`forceClientInjected`). 1.22 hangs; 1.23.2 throws + `_jsxDEV is not a function`. Report upstream, or add a standalone init + path. - [ ] Decide whether the Rsbuild adapter should default hosts to `shareStrategy: "loaded-first"`. With `version-first`, one offline remote fails host startup with `RUNTIME-003`; the example sets it explicitly. diff --git a/apps/rsbuild-host/package.json b/apps/rsbuild-host/package.json index 7491972..d4f7af6 100644 --- a/apps/rsbuild-host/package.json +++ b/apps/rsbuild-host/package.json @@ -10,20 +10,20 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@module-federation/runtime": "2.9.0", - "@tanstack/react-router": "1.170.38", - "@tanstack/react-start": "1.168.56", + "@module-federation/runtime": "2.9.2", + "@tanstack/react-router": "1.170.41", + "@tanstack/react-start": "1.168.60", "react": "19.3.0", "react-dom": "19.3.0" }, "devDependencies": { - "@module-federation/rsbuild-plugin": "2.9.0", + "@module-federation/rsbuild-plugin": "2.9.2", "@module-federation/tanstack": "workspace:*", - "@rsbuild/core": "2.2.8", - "@rsbuild/plugin-react": "2.1.0", - "@types/node": "24.10.1", + "@rsbuild/core": "2.2.11", + "@rsbuild/plugin-react": "2.1.1", + "@types/node": "26.6.4", "@types/react": "19.3.0", "@types/react-dom": "19.3.0", - "typescript": "6.0.3" + "typescript": "7.0.2" } } diff --git a/apps/rsbuild-remote/package.json b/apps/rsbuild-remote/package.json index 82947ab..f7caf14 100644 --- a/apps/rsbuild-remote/package.json +++ b/apps/rsbuild-remote/package.json @@ -10,20 +10,20 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@module-federation/runtime": "2.9.0", - "@tanstack/react-router": "1.170.38", - "@tanstack/react-start": "1.168.56", + "@module-federation/runtime": "2.9.2", + "@tanstack/react-router": "1.170.41", + "@tanstack/react-start": "1.168.60", "react": "19.3.0", "react-dom": "19.3.0" }, "devDependencies": { - "@module-federation/rsbuild-plugin": "2.9.0", + "@module-federation/rsbuild-plugin": "2.9.2", "@module-federation/tanstack": "workspace:*", - "@rsbuild/core": "2.2.8", - "@rsbuild/plugin-react": "2.1.0", - "@types/node": "24.10.1", + "@rsbuild/core": "2.2.11", + "@rsbuild/plugin-react": "2.1.1", + "@types/node": "26.6.4", "@types/react": "19.3.0", "@types/react-dom": "19.3.0", - "typescript": "6.0.3" + "typescript": "7.0.2" } } diff --git a/apps/vite-host/package.json b/apps/vite-host/package.json index 65015fb..b0fe9ad 100644 --- a/apps/vite-host/package.json +++ b/apps/vite-host/package.json @@ -10,19 +10,19 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@module-federation/runtime": "2.9.0", - "@tanstack/react-router": "1.170.38", - "@tanstack/react-start": "1.168.56", + "@module-federation/runtime": "2.9.2", + "@tanstack/react-router": "1.170.41", + "@tanstack/react-start": "1.168.60", "react": "19.3.0", "react-dom": "19.3.0" }, "devDependencies": { "@module-federation/tanstack": "workspace:*", - "@types/node": "24.10.1", + "@types/node": "26.6.4", "@types/react": "19.3.0", "@types/react-dom": "19.3.0", - "@vitejs/plugin-react": "6.1.1", - "typescript": "6.0.3", - "vite": "8.3.0" + "@vitejs/plugin-react": "6.1.2", + "typescript": "7.0.2", + "vite": "8.3.2" } } diff --git a/apps/vite-remote/package.json b/apps/vite-remote/package.json index 74045fe..6c824d4 100644 --- a/apps/vite-remote/package.json +++ b/apps/vite-remote/package.json @@ -10,19 +10,19 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@module-federation/runtime": "2.9.0", - "@tanstack/react-router": "1.170.38", - "@tanstack/react-start": "1.168.56", + "@module-federation/runtime": "2.9.2", + "@tanstack/react-router": "1.170.41", + "@tanstack/react-start": "1.168.60", "react": "19.3.0", "react-dom": "19.3.0" }, "devDependencies": { "@module-federation/tanstack": "workspace:*", - "@types/node": "24.10.1", + "@types/node": "26.6.4", "@types/react": "19.3.0", "@types/react-dom": "19.3.0", - "@vitejs/plugin-react": "6.1.1", - "typescript": "6.0.3", - "vite": "8.3.0" + "@vitejs/plugin-react": "6.1.2", + "typescript": "7.0.2", + "vite": "8.3.2" } } diff --git a/package.json b/package.json index 38f4cb8..a00aec4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mf-tanstack", "private": true, - "packageManager": "pnpm@10.15.0", + "packageManager": "pnpm@12.9.1", "engines": { "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, @@ -21,8 +21,8 @@ "devDependencies": { "@changesets/cli": "3.0.3", "playwright": "1.63.0", - "prettier": "3.9.7", - "turbo": "2.10.13", - "typescript": "6.0.3" + "prettier": "3.9.9", + "turbo": "2.11.7", + "typescript": "7.0.2" } } diff --git a/packages/tanstack/package.json b/packages/tanstack/package.json index 98c602c..c682501 100644 --- a/packages/tanstack/package.json +++ b/packages/tanstack/package.json @@ -70,7 +70,7 @@ "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "scripts": { - "build": "rslib build --lib esm && rslib build --lib cjs --no-clean", + "build": "rslib build --lib cjs && rslib build --lib esm --no-clean", "dev": "rslib build --watch", "pack:dry": "npm pack --dry-run", "prepack": "pnpm run build", @@ -78,10 +78,10 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "peerDependencies": { - "@tanstack/react-start": "^1.167.43", "@module-federation/rsbuild-plugin": "^2.9.0", "@module-federation/vite": "^1.22.0", "@rsbuild/core": "^2.0.0", + "@tanstack/react-start": "^1.167.43", "vite": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { @@ -102,15 +102,15 @@ } }, "devDependencies": { - "@module-federation/rsbuild-plugin": "2.9.0", - "@module-federation/vite": "1.22.0", - "@rsbuild/core": "2.2.8", - "@rslib/core": "1.0.1", - "@tanstack/react-start": "1.168.56", - "@types/node": "24.10.1", + "@module-federation/rsbuild-plugin": "2.9.2", + "@module-federation/vite": "1.23.2", + "@rsbuild/core": "2.2.11", + "@rslib/core": "1.0.3", + "@tanstack/react-start": "1.168.60", + "@types/node": "26.6.4", "react": "19.3.0", "react-dom": "19.3.0", - "typescript": "6.0.3", - "vite": "8.3.0" + "typescript": "7.0.2", + "vite": "8.3.2" } } diff --git a/packages/tanstack/rslib.config.ts b/packages/tanstack/rslib.config.ts index a0cfbe7..08b6857 100644 --- a/packages/tanstack/rslib.config.ts +++ b/packages/tanstack/rslib.config.ts @@ -13,6 +13,9 @@ const output = { target: "node", } as const; +// The `build` script runs the CJS library first. With TypeScript 7, rsbuild-plugin-dts +// renames every `.d.ts` in `dist` to `.d.cts` during the CJS pass, which would also +// consume the ESM declarations if they were emitted first. export default defineConfig({ lib: [ { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 487ef81..bba98a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,3 +1,186 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + '@pnpm/exe': + specifier: 12.9.1 + version: 12.9.1 + pnpm: + specifier: 12.9.1 + version: 12.9.1 + +packages: + + '@pnpm/exe.android-arm64@12.9.1': + resolution: {integrity: sha512-Hgm1XdqMTfBC8PdGLL+NjZWv4xyi7D6XNrN9fb24XGYlyTBmUtdCefKnCLmHujShRD8BEO1zqM30SNcKBDLysw==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.9.1': + resolution: {integrity: sha512-WDGCe6jZRVZP3RC/q6hOkZylkPkIQIi1/GcrR/d/YzbihhAHXCczrk+UYnV5TgbsEnAYO7Cx+2rX3nfhHvy24w==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.9.1': + resolution: {integrity: sha512-+7iiEEBgYN9szPWUnAuN6eOVMTI6Hrki5bcuC9n9xl9tTnT53Tpxpo45Hml+W4IrAU0d+Tmd0sPnVIbX59PD6w==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.9.1': + resolution: {integrity: sha512-SK7TxJiR12GbJZGffRFHItkc+Mx+99v6xMMJ9/X//8NwWV1wo17a2bwfGI8qZpl17YwsrmPEFOOqhzE1GIlrtQ==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.freebsd-x64@12.9.1': + resolution: {integrity: sha512-4AjLk6uvTs2iO6wMit2hcxJ2M32xb7OV5SNFk+w6G8/NzcjcMB7ENEpyeXyLiP0kcnp8uaCGUct2NfrLCJ35eQ==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.9.1': + resolution: {integrity: sha512-dZL2ES1ckDWM9v+EtxoOklZ0WNdtayooi9iLiDUubopudX3zXPNR2mlrxBRgjbDgECOndQen2SmyxL5eZiBuKw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.9.1': + resolution: {integrity: sha512-cx+uF6xqwEtm7B8NV+1ve0xBg9gcuzQZ5R+0WgOWvKG/h978UljS6BgWs1ipIGuGgUdeVhm3PKmBltXIcczLXA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-ppc64@12.9.1': + resolution: {integrity: sha512-MpjPsy4XR17zAtLziQZfsN+wveWa/bUJggZtwfo3+SuyRmXnSvVkFD4+v8/3RTWyYaM6mwLUTjZDJ+g3urL+ew==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.9.1': + resolution: {integrity: sha512-Oo67TKqqH+Bno+L6X7Xyorwp1QG82zKIkL0vbo1x2kcPTDiyPbdM+d6lIs4+hzeB1KHT5FDoGzZj2dad7Ft+eg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.9.1': + resolution: {integrity: sha512-QAD5FRCdW36HfD58BV7DoDm6z8yv+GgSqcUV1+ERwYjpVQuG6ZTpf5o0x3KDas8eTLIW7MLYVdDdlAurWeArEg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.9.1': + resolution: {integrity: sha512-mQDhBsGqcTkrLQM4q5igmRFcr6EAl4UM/FGawY+0CWGcqPpUmYUvDy9LHy80xxkHIWVJ886gvvso2XOjfav1Jw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.9.1': + resolution: {integrity: sha512-+M5N74hM4fc87AAckxx/UIW+dUnWf2eko+HchJs/NZUxehLhekNyMI/GNaNXlvd+kWR+97XXBD51XRkkNiUtrA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.9.1': + resolution: {integrity: sha512-mI28C2wxobrDi6x1OYhBRyG10hnDq/4sEIyy5RzoX6LB4kLuv6K/d25HOpMwlW20cuM4M72k9NZpOdcmUxMbEA==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.9.1': + resolution: {integrity: sha512-SWoDMT0oD7NrDSIoPpkKD09D9fAQKNjwFZ3lezlCUhAfXxMv70zsfrNT2/P+GoCK2kefXF9OysUZkH1l5vvvfA==} + cpu: [x64] + os: [win32] + + '@pnpm/exe@12.9.1': + resolution: {integrity: sha512-TZOVvYq8nwzmw8IR3P1GKZk87tV7vDPCT3oJlL7IhgYNZfLGov4jX0J8RJxuAXxIT3uxg/uiconrLLBv+X5KyA==} + engines: {node: '>=18.*'} + hasBin: true + + pnpm@12.9.1: + resolution: {integrity: sha512-BrBV//XNINwSeB3hc87TMQzglRvy7GICEaFgRdVETVyTpmMhB2TvKaZTphBFm6A2jw50+E2AxUcjz5Wq57wNbQ==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.android-arm64@12.9.1': + optional: true + + '@pnpm/exe.android-x64@12.9.1': + optional: true + + '@pnpm/exe.darwin-arm64@12.9.1': + optional: true + + '@pnpm/exe.darwin-x64@12.9.1': + optional: true + + '@pnpm/exe.freebsd-x64@12.9.1': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.9.1': + optional: true + + '@pnpm/exe.linux-arm64@12.9.1': + optional: true + + '@pnpm/exe.linux-ppc64@12.9.1': + optional: true + + '@pnpm/exe.linux-riscv64@12.9.1': + optional: true + + '@pnpm/exe.linux-s390x@12.9.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.9.1': + optional: true + + '@pnpm/exe.linux-x64@12.9.1': + optional: true + + '@pnpm/exe.win32-arm64@12.9.1': + optional: true + + '@pnpm/exe.win32-x64@12.9.1': + optional: true + + '@pnpm/exe@12.9.1': + optionalDependencies: + '@pnpm/exe.android-arm64': 12.9.1 + '@pnpm/exe.android-x64': 12.9.1 + '@pnpm/exe.darwin-arm64': 12.9.1 + '@pnpm/exe.darwin-x64': 12.9.1 + '@pnpm/exe.freebsd-x64': 12.9.1 + '@pnpm/exe.linux-arm64': 12.9.1 + '@pnpm/exe.linux-arm64-musl': 12.9.1 + '@pnpm/exe.linux-ppc64': 12.9.1 + '@pnpm/exe.linux-riscv64': 12.9.1 + '@pnpm/exe.linux-s390x': 12.9.1 + '@pnpm/exe.linux-x64': 12.9.1 + '@pnpm/exe.linux-x64-musl': 12.9.1 + '@pnpm/exe.win32-arm64': 12.9.1 + '@pnpm/exe.win32-x64': 12.9.1 + + pnpm@12.9.1: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.9.1 + '@pnpm/exe.android-x64': 12.9.1 + '@pnpm/exe.darwin-arm64': 12.9.1 + '@pnpm/exe.darwin-x64': 12.9.1 + '@pnpm/exe.freebsd-x64': 12.9.1 + '@pnpm/exe.linux-arm64': 12.9.1 + '@pnpm/exe.linux-arm64-musl': 12.9.1 + '@pnpm/exe.linux-ppc64': 12.9.1 + '@pnpm/exe.linux-riscv64': 12.9.1 + '@pnpm/exe.linux-s390x': 12.9.1 + '@pnpm/exe.linux-x64': 12.9.1 + '@pnpm/exe.linux-x64-musl': 12.9.1 + '@pnpm/exe.win32-arm64': 12.9.1 + '@pnpm/exe.win32-x64': 12.9.1 + +--- lockfileVersion: '9.0' settings: @@ -15,26 +198,26 @@ importers: specifier: 1.63.0 version: 1.63.0 prettier: - specifier: 3.9.7 - version: 3.9.7 + specifier: 3.9.9 + version: 3.9.9 turbo: - specifier: 2.10.13 - version: 2.10.13 + specifier: 2.11.7 + version: 2.11.7 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 apps/rsbuild-host: dependencies: '@module-federation/runtime': - specifier: 2.9.0 - version: 2.9.0 + specifier: 2.9.2 + version: 2.9.2 '@tanstack/react-router': - specifier: 1.170.38 - version: 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + specifier: 1.170.41 + version: 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/react-start': - specifier: 1.168.56 - version: 1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 1.168.60 + version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) react: specifier: 19.3.0 version: 19.3.0 @@ -43,20 +226,20 @@ importers: version: 19.3.0(react@19.3.0) devDependencies: '@module-federation/rsbuild-plugin': - specifier: 2.9.0 - version: 2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) + specifier: 2.9.2 + version: 2.9.2(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2) '@module-federation/tanstack': specifier: workspace:* version: link:../../packages/tanstack '@rsbuild/core': - specifier: 2.2.8 - version: 2.2.8(@module-federation/runtime-tools@2.9.0) + specifier: 2.2.11 + version: 2.2.11(@module-federation/runtime-tools@2.9.2) '@rsbuild/plugin-react': - specifier: 2.1.0 - version: 2.1.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23)) + specifier: 2.1.1 + version: 2.1.1(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23)) '@types/node': - specifier: 24.10.1 - version: 24.10.1 + specifier: 26.6.4 + version: 26.6.4 '@types/react': specifier: 19.3.0 version: 19.3.0 @@ -64,20 +247,20 @@ importers: specifier: 19.3.0 version: 19.3.0(@types/react@19.3.0) typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 apps/rsbuild-remote: dependencies: '@module-federation/runtime': - specifier: 2.9.0 - version: 2.9.0 + specifier: 2.9.2 + version: 2.9.2 '@tanstack/react-router': - specifier: 1.170.38 - version: 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + specifier: 1.170.41 + version: 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/react-start': - specifier: 1.168.56 - version: 1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 1.168.60 + version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) react: specifier: 19.3.0 version: 19.3.0 @@ -86,20 +269,20 @@ importers: version: 19.3.0(react@19.3.0) devDependencies: '@module-federation/rsbuild-plugin': - specifier: 2.9.0 - version: 2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) + specifier: 2.9.2 + version: 2.9.2(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2) '@module-federation/tanstack': specifier: workspace:* version: link:../../packages/tanstack '@rsbuild/core': - specifier: 2.2.8 - version: 2.2.8(@module-federation/runtime-tools@2.9.0) + specifier: 2.2.11 + version: 2.2.11(@module-federation/runtime-tools@2.9.2) '@rsbuild/plugin-react': - specifier: 2.1.0 - version: 2.1.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23)) + specifier: 2.1.1 + version: 2.1.1(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23)) '@types/node': - specifier: 24.10.1 - version: 24.10.1 + specifier: 26.6.4 + version: 26.6.4 '@types/react': specifier: 19.3.0 version: 19.3.0 @@ -107,20 +290,20 @@ importers: specifier: 19.3.0 version: 19.3.0(@types/react@19.3.0) typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 apps/vite-host: dependencies: '@module-federation/runtime': - specifier: 2.9.0 - version: 2.9.0 + specifier: 2.9.2 + version: 2.9.2 '@tanstack/react-router': - specifier: 1.170.38 - version: 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + specifier: 1.170.41 + version: 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/react-start': - specifier: 1.168.56 - version: 1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 1.168.60 + version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) react: specifier: 19.3.0 version: 19.3.0 @@ -132,8 +315,8 @@ importers: specifier: workspace:* version: link:../../packages/tanstack '@types/node': - specifier: 24.10.1 - version: 24.10.1 + specifier: 26.6.4 + version: 26.6.4 '@types/react': specifier: 19.3.0 version: 19.3.0 @@ -141,26 +324,26 @@ importers: specifier: 19.3.0 version: 19.3.0(@types/react@19.3.0) '@vitejs/plugin-react': - specifier: 6.1.1 - version: 6.1.1(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 6.1.2 + version: 6.1.2(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 vite: - specifier: 8.3.0 - version: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + specifier: 8.3.2 + version: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) apps/vite-remote: dependencies: '@module-federation/runtime': - specifier: 2.9.0 - version: 2.9.0 + specifier: 2.9.2 + version: 2.9.2 '@tanstack/react-router': - specifier: 1.170.38 - version: 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + specifier: 1.170.41 + version: 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/react-start': - specifier: 1.168.56 - version: 1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 1.168.60 + version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) react: specifier: 19.3.0 version: 19.3.0 @@ -172,8 +355,8 @@ importers: specifier: workspace:* version: link:../../packages/tanstack '@types/node': - specifier: 24.10.1 - version: 24.10.1 + specifier: 26.6.4 + version: 26.6.4 '@types/react': specifier: 19.3.0 version: 19.3.0 @@ -181,35 +364,35 @@ importers: specifier: 19.3.0 version: 19.3.0(@types/react@19.3.0) '@vitejs/plugin-react': - specifier: 6.1.1 - version: 6.1.1(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 6.1.2 + version: 6.1.2(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 vite: - specifier: 8.3.0 - version: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + specifier: 8.3.2 + version: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) packages/tanstack: devDependencies: '@module-federation/rsbuild-plugin': - specifier: 2.9.0 - version: 2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) + specifier: 2.9.2 + version: 2.9.2(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2) '@module-federation/vite': - specifier: 1.22.0 - version: 1.22.0(typescript@6.0.3)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 1.23.2 + version: 1.23.2(typescript@7.0.2)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) '@rsbuild/core': - specifier: 2.2.8 - version: 2.2.8(@module-federation/runtime-tools@2.9.0) + specifier: 2.2.11 + version: 2.2.11(@module-federation/runtime-tools@2.9.2) '@rslib/core': - specifier: 1.0.1 - version: 1.0.1(@module-federation/runtime-tools@2.9.0)(typescript@6.0.3) + specifier: 1.0.3 + version: 1.0.3(@module-federation/runtime-tools@2.9.2)(typescript@7.0.2) '@tanstack/react-start': - specifier: 1.168.56 - version: 1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 1.168.60 + version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) '@types/node': - specifier: 24.10.1 - version: 24.10.1 + specifier: 26.6.4 + version: 26.6.4 react: specifier: 19.3.0 version: 19.3.0 @@ -217,11 +400,11 @@ importers: specifier: 19.3.0 version: 19.3.0(react@19.3.0) typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 vite: - specifier: 8.3.0 - version: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + specifier: 8.3.2 + version: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) packages: @@ -242,24 +425,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@ast-grep/napi-linux-arm64-musl@0.45.3': resolution: {integrity: sha512-yr5xcRerV41GeRxmZ8SKbfGeyZRhQePoNNCgTAb4OrWMelWwbMI80aY7/IlF2bo1sN40BSW4hZdGIO/akTd1RQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@ast-grep/napi-linux-x64-gnu@0.45.3': resolution: {integrity: sha512-lluRK3hxfHMQdFuVID6seAKVx4FH0kEwF6rCh5/I40Sf3EtNR2UCBXAqd6RBA6hqlSckktvBxCfw+h4aescDmg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@ast-grep/napi-linux-x64-musl@0.45.3': resolution: {integrity: sha512-09wJQKq86LqILqQtw0+X09SzhcPwUnMVFKyia7S3XLbLqSeQpAMF55Y+zll53JRQ1dCzFEZJyvTT/7j9vwfE3w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@ast-grep/napi-win32-arm64-msvc@0.45.3': resolution: {integrity: sha512-/HiVgw2cdtzXLDLoG4eBBcauekNXewfagy7qiVbjkVu/KcCH8dJ9lpj8DzeWwuBNtZxuD8jpiwP0M1pz3Bc3uw==} @@ -333,8 +520,8 @@ packages: resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.8': - resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} engines: {node: '>=6.0.0'} hasBin: true @@ -456,16 +643,16 @@ packages: resolution: {integrity: sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ==} engines: {node: '>=20.0.0'} - '@module-federation/bridge-react-webpack-plugin@2.9.0': - resolution: {integrity: sha512-hv3fuQkGERQ/COBKTVbFV1GWovReSg/bzJzDuxWR1F84h6NYtbYMWQqX8Egvb7HKMtn9Rflzw0GeaLr8F08vDg==} + '@module-federation/bridge-react-webpack-plugin@2.9.2': + resolution: {integrity: sha512-6ikuvP1639g8jzSxveDLz/BOk0VTIkO7aSuvrZgq0waB0h553zFtkTJAXQETezzO+s1taHGhEvFoCtl6DJWu6A==} - '@module-federation/cli@2.9.0': - resolution: {integrity: sha512-r9RdlLRy3zWxuWQRonif48xdDu1reBGx18QpkZwLQ4JfSrOARjycNIGY2Y7QaUw7dedzxyee4FtKFeX/kOVLYQ==} + '@module-federation/cli@2.9.2': + resolution: {integrity: sha512-1+rNHFGC07lQq7CtgcF9ibtgMcAau4TzdOcLfCv13+o5XWWdM8X1yhPbqQWvj3MO/C/Js7Xwon1sTKjF69Do9g==} engines: {node: '>=16.0.0'} hasBin: true - '@module-federation/dts-plugin@2.9.0': - resolution: {integrity: sha512-uMGqEG/p9odG2BVr7WRbBe2OgrvzBd3LPzcp5WP+bm3djBdUJ4PZ3ozqKp6qpy+NFnlh6vnM815Xn6AXkKy1Vw==} + '@module-federation/dts-plugin@2.9.2': + resolution: {integrity: sha512-J84cRrewtUFnFdMeD/91mHtW+Toh3mAWvGnhsFAc9oEbGwqfzJEfgPJri65xdtzWcNhs1d6FImPMTvnjzOnYvA==} peerDependencies: typescript: ^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 vue-tsc: '>=1.0.24' @@ -473,8 +660,8 @@ packages: vue-tsc: optional: true - '@module-federation/enhanced@2.9.0': - resolution: {integrity: sha512-Jd/JHoFL9fNKL4Nnzo/9hf6FD/oQo3gKpE8xt6EGuhxmrvg6wM0radPtqgHiSSWLMW7CiXr2agNjKvquKd83rw==} + '@module-federation/enhanced@2.9.2': + resolution: {integrity: sha512-m1/kzb97Eu1v7FUenvwMpJiF6Vc8RNDjFxHS+YNgxy3WjI8s/mQrLJBrGK66qpK3wuP9kx0qB65kRiurB9IjUA==} hasBin: true peerDependencies: typescript: ^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -488,30 +675,30 @@ packages: webpack: optional: true - '@module-federation/error-codes@2.9.0': - resolution: {integrity: sha512-IGpd+VRlji3NyGOGGTsk7YEmPRKcDoBK4YHqIuP+OQwyb2YhHCUHO2vW9RXeQxCuKwi+c6xkTweRYG+Umy+0Zw==} + '@module-federation/error-codes@2.9.2': + resolution: {integrity: sha512-MTpKubqsJSBgnuw+2Pli1kHL4INeHD5cGnHL84oRANwQAWK7b6lN3ZZTmL3FL6lYn65ImineqZNqGWBv5GQVLQ==} - '@module-federation/inject-external-runtime-core-plugin@2.9.0': - resolution: {integrity: sha512-k3wCSZsY21HjYQ61wmIJUQleOgcHgqx/sX4qXYw0XnJnzHuFo64rzMvgr+TuBIgZ1peAQtfILLlTnWyTRjtZHw==} + '@module-federation/inject-external-runtime-core-plugin@2.9.2': + resolution: {integrity: sha512-e8F/+fNwVgwhJVhLURAa7orYe1WFojamf4myf/G4jszGZ6Yx5DoIUjFBMrkLQgY2nuM/cw2/itIY4ZMGL1CJbA==} peerDependencies: - '@module-federation/runtime-tools': 2.9.0 + '@module-federation/runtime-tools': 2.9.2 - '@module-federation/managers@2.9.0': - resolution: {integrity: sha512-8KhB2PF4g+M0hGaethU1H4GVWabLn5sF5TvnM6VxgAcDL4TfLY8aC/YobAXHG/SV0jpU5lsGe6s6xy6ccV0MQw==} + '@module-federation/managers@2.9.2': + resolution: {integrity: sha512-VtSKLkT2EA1wqCMFXNiklC+XF+5bZPYY3sUQ9LCFHqKS7v7NOJDrwMV6Qtw3fxH4Y6Pe6P28MiU8D9dlzknCKQ==} - '@module-federation/manifest@2.9.0': - resolution: {integrity: sha512-Zhm9luVOw9XPou50eTwsvdgr208CszoQ6cGORQDCPAMhjpGb5oYFtilJ+LKW7hQ1LktD15bEZmGhC1anvgXEiQ==} + '@module-federation/manifest@2.9.2': + resolution: {integrity: sha512-l9X1g2nHqjdumz9U9jawKo8rKoWXVbSlzLOqs+GYiHfzqSB2/ZQ+4AX0CaJ7orLz2varpGK4qdnQ0saptGuEVw==} - '@module-federation/node@2.7.50': - resolution: {integrity: sha512-mbpQRdafyeWgsmYoJfdhOQf76zS6onOGpC2X1ELpWXB1Y4BcZGloL0CLjNMNon9m3ucfpc99tOGAQqFzQVkSBQ==} + '@module-federation/node@2.7.52': + resolution: {integrity: sha512-K6R3+UoLK7SCxL0bxRs3J17AZeOEOY8ytyqxP3Y1jtO/drVqu1GaB0t+N1hvmle2zfAe9AmSG49cUiO03T5aEQ==} peerDependencies: webpack: ^5.40.0 peerDependenciesMeta: webpack: optional: true - '@module-federation/rsbuild-plugin@2.9.0': - resolution: {integrity: sha512-CaWAxZg+zOMw/BfgRXQdNBQZ/e5U7DBZvE13LzG13GEclt7yxaMQgKH0si3uOsq9f/X2NC8UnuhNHLIkloqlqA==} + '@module-federation/rsbuild-plugin@2.9.2': + resolution: {integrity: sha512-E6VekVYmFdKWhL/ip75ukhAm/9gbK7f/cRmzS1BxTmw+8EFrrnvScU7qoj1MTSnMvViTtmXS42D49JWZ3SFw3g==} engines: {node: '>=16.0.0'} peerDependencies: '@rsbuild/core': ^1.3.21 || ^2.0.0-0 @@ -519,8 +706,8 @@ packages: '@rsbuild/core': optional: true - '@module-federation/rspack@2.9.0': - resolution: {integrity: sha512-9zSlmQYKRHKVWqhlZSMyjstp2A3VVrQV2Of8mrF5NXhngFemNx7Hw35+MTo+gsRfF5glpN8bAzLyIokRUo1Cog==} + '@module-federation/rspack@2.9.2': + resolution: {integrity: sha512-RobMJ/msRzJ94L9k1CAsPuhed2N2rvHBRBJhtk7t3pK/UoQM95Ks6KAcmVKBrxTVHWMhsKTCatxC7u7X19LYdA==} peerDependencies: '@rspack/core': ^0.7.0 || ^1.0.0 || ^2.0.0-0 typescript: ^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -531,29 +718,29 @@ packages: vue-tsc: optional: true - '@module-federation/runtime-core@2.9.0': - resolution: {integrity: sha512-dLykRYfpbEJBTdk2NlbNoVVTB196O3qujawTBguLABJkPCWETJNk60NR1yZO34eI+DTH+eGwHU3m7FddAWnbnw==} + '@module-federation/runtime-core@2.9.2': + resolution: {integrity: sha512-S9roTDqLtAWaaDBuug69ZnXXGEx0pUq/yG06J759bjRgfAg2P9+dWEv1q7rB2eMIp7HH4L6s3ColDRYdL6tJ9w==} - '@module-federation/runtime-tools@2.9.0': - resolution: {integrity: sha512-u2puqsaHiw1bVvLNk1uh98iPo6UzaBuci/aTvOFwKvbT/RF18C3vGGnm+ShHPKQEnoN6ctPtnygyOZDPR+8cgg==} + '@module-federation/runtime-tools@2.9.2': + resolution: {integrity: sha512-rhOvimSLLnnocfOVUfAzH2xDq1pHMw4f/nSC+JQhSztEgP/1UslPPvcBcenx3B8/6CoYmBbGa2OIBTJ225ZHAg==} - '@module-federation/runtime@2.9.0': - resolution: {integrity: sha512-3cyAav0hWP+dNvB7qrcK9CKxQn+JvkbRvLtZz3zS2g0EyxtDFDjgbHY0Afcf7oHf8IHhKeEdzb/3Kjr1Pjmr7A==} + '@module-federation/runtime@2.9.2': + resolution: {integrity: sha512-1lPZVy7TkMTTBMkkjgQt7jA+9FAx226yQqEz+X0lFAIbpxIWfpzNQLD+7IydEe83ZhDJpK3cv/9bn3jjQBZeqQ==} - '@module-federation/sdk@2.9.0': - resolution: {integrity: sha512-IMjObgBGQTXd33jTTCYcxvz8iOQGLsZ2QIPOj90rDxuBtJsN4WJwYyXqligrhY/e5p/BipUPdbIgKmWL7zFhTw==} + '@module-federation/sdk@2.9.2': + resolution: {integrity: sha512-ILtBFPuYpVcRA/Qay1lJ2hMtZI8fDE/v1n7xiN17e5qx5rSBR3FaamTsmRpN1hIt+gVnoa76CzOP/Y73d8hHNg==} - '@module-federation/third-party-dts-extractor@2.9.0': - resolution: {integrity: sha512-R1Xuqnqzw6wQxWV3yU9fX1FydXAeZF2TNVFAVo1N1oLBA2j5y7aUO/Fc3VSNgd9/gxMzJgTVBdA+nMiD2SFCgg==} + '@module-federation/third-party-dts-extractor@2.9.2': + resolution: {integrity: sha512-FKJVGx+/jIe16RfmwoyIDVMPAttIFJv6AJCLkKf05IRNvI3RJFqQ1tLP1YjcfGuumCbYuWGa4QKnMla7YjVpqg==} - '@module-federation/vite@1.22.0': - resolution: {integrity: sha512-113KQtubRIg2ezPs8R2+q+sTKY/8IEePqaGsNAdeSHvQvtP8TUW0YvBvpNFgiPz/7VG0FhI7XYGraBekBZUzMA==} + '@module-federation/vite@1.23.2': + resolution: {integrity: sha512-LlfbC+IDFicoPMR/kELhl/1BzSI79SP7dWON6gX/Gm/8SkFXxpHpdQGaJSLSmJvtLbQMTPx0bqZDj4huB2Du3g==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - '@module-federation/webpack-bundler-runtime@2.9.0': - resolution: {integrity: sha512-MdU6NQibT57MaJG3KPBjC30IVBrz5eU7IcjHoVDYnMh7OmASw3stagBu7hYjdMTP31luNdtzUn85s9axvdwTlw==} + '@module-federation/webpack-bundler-runtime@2.9.2': + resolution: {integrity: sha512-dDtWiYcCWWgm/mCS7L4KfDHsyr7HwDRnHHS2bgl1H7RQfH4WAi706gT2hhzmV272PI33V7qJHN7Go1d/BamN0Q==} '@napi-rs/wasm-runtime@1.1.6': resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} @@ -577,99 +764,105 @@ packages: resolution: {integrity: sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==} engines: {node: '>=20.0'} - '@oxc-project/types@0.150.0': - resolution: {integrity: sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==} + '@oxc-project/types@0.152.0': + resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} '@pnpm/deps.graph-sequencer@1100.0.1': resolution: {integrity: sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A==} engines: {node: '>=22.13'} - '@rolldown/binding-android-arm-eabi@1.2.9': - resolution: {integrity: sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==} + '@rolldown/binding-android-arm-eabi@1.2.12': + resolution: {integrity: sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rolldown/binding-android-arm64@1.2.9': - resolution: {integrity: sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==} + '@rolldown/binding-android-arm64@1.2.12': + resolution: {integrity: sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.2.9': - resolution: {integrity: sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==} + '@rolldown/binding-darwin-arm64@1.2.12': + resolution: {integrity: sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.9': - resolution: {integrity: sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==} + '@rolldown/binding-darwin-x64@1.2.12': + resolution: {integrity: sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.2.9': - resolution: {integrity: sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==} + '@rolldown/binding-freebsd-x64@1.2.12': + resolution: {integrity: sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.2.9': - resolution: {integrity: sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': + resolution: {integrity: sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.2.9': - resolution: {integrity: sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==} + '@rolldown/binding-linux-arm64-gnu@1.2.12': + resolution: {integrity: sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.2.9': - resolution: {integrity: sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==} + '@rolldown/binding-linux-arm64-musl@1.2.12': + resolution: {integrity: sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.2.9': - resolution: {integrity: sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==} + '@rolldown/binding-linux-ppc64-gnu@1.2.12': + resolution: {integrity: sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.9': - resolution: {integrity: sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==} + '@rolldown/binding-linux-s390x-gnu@1.2.12': + resolution: {integrity: sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.9': - resolution: {integrity: sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==} + '@rolldown/binding-linux-x64-gnu@1.2.12': + resolution: {integrity: sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.2.9': - resolution: {integrity: sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==} + '@rolldown/binding-linux-x64-musl@1.2.12': + resolution: {integrity: sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] - '@rolldown/binding-openharmony-arm64@1.2.9': - resolution: {integrity: sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==} + '@rolldown/binding-openharmony-arm64@1.2.12': + resolution: {integrity: sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-win32-arm64-msvc@1.2.9': - resolution: {integrity: sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==} + '@rolldown/binding-win32-arm64-msvc@1.2.12': + resolution: {integrity: sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.9': - resolution: {integrity: sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==} + '@rolldown/binding-win32-x64-msvc@1.2.12': + resolution: {integrity: sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -677,8 +870,8 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@rsbuild/core@2.2.8': - resolution: {integrity: sha512-/htjYpZiqZFB/fELguE8nkqKDT/DqHXeXUgdf+X4UlP5BJc1EeolZ9ksDpCw9WNTlcn3zsPOj7UZ0dUUB4FAFA==} + '@rsbuild/core@2.2.11': + resolution: {integrity: sha512-avPW0j6RaEbtV2mfl0AzTAJEimPX4Xj0pCcZyxNCrE9vkS91HmweM5V7XQj3kI5iTYfHRp9Y6To8RRHYLEvG7A==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -687,16 +880,16 @@ packages: core-js: optional: true - '@rsbuild/plugin-react@2.1.0': - resolution: {integrity: sha512-RQTIAWB/CwPjoWt9iAl+8HixeQVgZ7kEIBrWPCixfITyHdiD84h0YpUTpEUuz6kGHw1KXT9mHZ3Rwy6WG7aRDA==} + '@rsbuild/plugin-react@2.1.1': + resolution: {integrity: sha512-tLtUedsRtTLS6M4VtwzI6aakFLOFdgl9Sh1ZNBWvxb0Te3EnHrppaE7UiYEmDUXQDJ50gfr6yyirtxY5N6moyg==} peerDependencies: '@rsbuild/core': ^2.0.0 peerDependenciesMeta: '@rsbuild/core': optional: true - '@rslib/core@1.0.1': - resolution: {integrity: sha512-FsUgiFALdgPQDM41OEYmt18J4061Vcm/fW2RXkZcwlO56PNaAEu2vtk+i8iH0lPEpCK79OaJPYjUzygOj4pPpA==} + '@rslib/core@1.0.3': + resolution: {integrity: sha512-a5IZiyf9sNeeE9jcTsTmz16vxcThpbOSJg0BZXzJAxrUFOZX5xG25PJua9hr0LDWGryBX2UQTruVsLQ8BCpqng==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -708,80 +901,88 @@ packages: typescript: optional: true - '@rspack/binding-darwin-arm64@2.2.6': - resolution: {integrity: sha512-y8I8MAeKAOYuLqEKYQSmNFcW4dI1u5O+xYQcjT1L3Gby02KEt0YxlmSPvzElSQJUjsJ8PkKqU3U1suDjA6xvkg==} + '@rspack/binding-darwin-arm64@2.2.8': + resolution: {integrity: sha512-d6GuY/k7kJk7SOXmruIz0m+2fIXaLv2SnBHEYsbvBzE7ZO6QXq7fSABWFWNnSnNu+brYHA5Dr/skV81bXgTTCw==} cpu: [arm64] os: [darwin] - '@rspack/binding-darwin-x64@2.2.6': - resolution: {integrity: sha512-+zhtUAP+nZON1lSlqOFJWW2VpIXRZ4/NQskYe0xohELI4Fr6xun/Oh8dIMt+Nviamvo7lbO9XURrFio1+Fziog==} + '@rspack/binding-darwin-x64@2.2.8': + resolution: {integrity: sha512-xCVou/jJASjEAjwy1T3hPJcJqA5QZASJfUwHqEk+nA0v44MagwH8tM8lsOvRfhzR7N2+YFac1rfZOUT8uM3awg==} cpu: [x64] os: [darwin] - '@rspack/binding-linux-arm64-gnu@2.2.6': - resolution: {integrity: sha512-7GI9xlkzVMAp9dbNvynvAreMjQQNw01b+khYYfmoQOgWXTUwrtzR7/JhJ3tijVQTXkyiYAKJ0RPvz57yD+p1vQ==} + '@rspack/binding-linux-arm64-gnu@2.2.8': + resolution: {integrity: sha512-Zy/ss6YAQTjOWKDkcWrbKtu+pySltqT22wn68nlrWKygPWLNmMdU5anlsnwRTIuQ3MF0YJRS42ARauVtlqvRQw==} cpu: [arm64] os: [linux] + libc: [glibc] - '@rspack/binding-linux-arm64-musl@2.2.6': - resolution: {integrity: sha512-klkjpGdeROzRiGSk2amfeZBAh0vWu0GjX5xKKQfRR4IqKQxURGCQafbvl3VX5XrHEUnKFzag89Lyv+ZVx5eTCw==} + '@rspack/binding-linux-arm64-musl@2.2.8': + resolution: {integrity: sha512-lxgp5O8PhL3St9p82llHoNoeWzp9jltW5S9F00p00XPJZHUIuxbwfLSzn/6Wg86USEmafLoBkyD9CM8rBd3SNQ==} cpu: [arm64] os: [linux] + libc: [musl] - '@rspack/binding-linux-ppc64-gnu@2.2.6': - resolution: {integrity: sha512-TpLZr49oU31EuEqVDczpbfLZ6c976zu8HspBuUWYj1Ra4SNeOHhINKE1i42OVw2DCEMpsz8Tv3tYyLVACGC8JA==} + '@rspack/binding-linux-ppc64-gnu@2.2.8': + resolution: {integrity: sha512-MY+YqpT02tcDCMgHy+IewQsaz7ql4mbJaAqqMlTIqvQwAUl9dXEP7BL352OgCxm9VRebTldE5lK99A3wVd3zqA==} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rspack/binding-linux-riscv64-gnu@2.2.6': - resolution: {integrity: sha512-gU3Ki8Lt6cE4qRM62959/28GG3XQUZsnvstgDULJpkHR5QFGYXUhOoPe2fpyWYrLw6+t9lWXBuI/gUez1zc8Sg==} + '@rspack/binding-linux-riscv64-gnu@2.2.8': + resolution: {integrity: sha512-wmN9OigSGNGCKY+AOYaWI8LJcIi8qvs5KjueBueCKOnTV6MPIEujA75QZH1pehTTYKvdGv7r2EtUXUmWpn7/SQ==} cpu: [riscv64] os: [linux] + libc: [glibc] - '@rspack/binding-linux-riscv64-musl@2.2.6': - resolution: {integrity: sha512-OQRvU8HT9rGmtgdpieO722IrGViDlR0D6yjqbt7S51b+saBPwJ8Nf3fwMtpZnBo1djV2BGXK00L34jYypeChrw==} + '@rspack/binding-linux-riscv64-musl@2.2.8': + resolution: {integrity: sha512-ZcQMYR10hLh581zs9MFnFFn0F7wyt5fkDffyukeMHJEoPJrbkfl6cQtsUdpQnZ+mYGYS5KydD1DT9/eMH65jjw==} cpu: [riscv64] os: [linux] + libc: [musl] - '@rspack/binding-linux-s390x-gnu@2.2.6': - resolution: {integrity: sha512-pus4Z76+J1iRO5Gutc6pq1LkyVhXqi0Sw6o5Dxwtl3sSbVy+F5PJkxtAT+DEg07pQy5m3dCymz2CIpiFlwYLTQ==} + '@rspack/binding-linux-s390x-gnu@2.2.8': + resolution: {integrity: sha512-ahBiE+Ir1FMYvStYicihhSJlS2YRagxtry0jWH8zW287o7C0eiHJ+pteJHHC15T5RJqm0j6w5TuwYe+F9AYxxg==} cpu: [s390x] os: [linux] + libc: [glibc] - '@rspack/binding-linux-x64-gnu@2.2.6': - resolution: {integrity: sha512-M0Efi9ny2cJ6VdhHxsZbcwdRCQ0HqBAsnPxPB8Ulknj+VtRlWnTlX/DlqMaHFuCb0MOWS6DXGGEAZ8veEil0BA==} + '@rspack/binding-linux-x64-gnu@2.2.8': + resolution: {integrity: sha512-dncEI515NoPXh3zzWk06Rb3mhSV76myiFKqXiJ8K4fVAe5x/RlweMhvPhHhm7RnIMeBld7BeStzOqj38XFsWGA==} cpu: [x64] os: [linux] + libc: [glibc] - '@rspack/binding-linux-x64-musl@2.2.6': - resolution: {integrity: sha512-614vRMr6VAqUXQm9TY7FCamcB08XJOv51WAwIlfEpoKTYxzNiQvrTYBYPgStUH20ywLwVoN6EJ++WCmHaebGWw==} + '@rspack/binding-linux-x64-musl@2.2.8': + resolution: {integrity: sha512-pfFyU03PjD0GaQG8zOQOHwq51rrJZetXjTGpMmz7iA1hq/f9lChJsDWrC8/7VXeQLwBofLZlOODqe8OM8OBzgA==} cpu: [x64] os: [linux] + libc: [musl] - '@rspack/binding-wasm32-wasi@2.2.6': - resolution: {integrity: sha512-p995VmOEcHWPKw/Eek0CYUjmXWhfyWOPoKtE51Dy0/jqHcs7uRy53igDd277yHTuKCHibmwU+cjSKUe6b/aBZg==} + '@rspack/binding-wasm32-wasi@2.2.8': + resolution: {integrity: sha512-3mOJuZTrF56jOh/GD9chLMJKbzPZ6Ty2OIML7+lLz0Yk2XS/xWRfLn85Dmnkb/O6UF65x7+p8VPAIZlBtLTclw==} cpu: [wasm32] - '@rspack/binding-win32-arm64-msvc@2.2.6': - resolution: {integrity: sha512-xC8nHMqVzQWqVW95HOcf9qTsM445ui7QNfUTDui6oTiHcBtsyfK1CEdXsc50bdi/FrJ2ueOps3M5gxbM8Ttl/A==} + '@rspack/binding-win32-arm64-msvc@2.2.8': + resolution: {integrity: sha512-Cu/RMZT/16s+67xhl6X1pswetL3GLSQl+LnugKRh08Ul21A/tR7vxC4g4aih4u9k5SXCAngxFUt8zAjKnHIBqA==} cpu: [arm64] os: [win32] - '@rspack/binding-win32-ia32-msvc@2.2.6': - resolution: {integrity: sha512-sfqP/LSnMwAHpTA+Cfbkhvx/IvUr23Z7S+miOkaMT/j2XJYCBrPZK0ZYm8+LUNDQ2ij8/XCJT4pVpQbtc/SMPw==} + '@rspack/binding-win32-ia32-msvc@2.2.8': + resolution: {integrity: sha512-H/erd6di8OapZhhM0raA/7XduPn9jusfLMeO1XF+AmF9WF3HzHgMjha1j+KwtPgQnaH69w6AhY+G2472VIwQmw==} cpu: [ia32] os: [win32] - '@rspack/binding-win32-x64-msvc@2.2.6': - resolution: {integrity: sha512-b1ERY5QydZWJKA13AYsoMPZycG5AGoUc3sOMqLszO0gs84aMnLbK7lBiyeZCvEcHEa7gSFQlLQr9p+Xk7HPO4Q==} + '@rspack/binding-win32-x64-msvc@2.2.8': + resolution: {integrity: sha512-BziHSUlIQM0MliCqdcHuBXSkQGtPoHQTXJce7fsgnTAzQFgPeog92qTNezYI4IrQ7pQouyiVJ2HPieUbVPRKQQ==} cpu: [x64] os: [win32] - '@rspack/binding@2.2.6': - resolution: {integrity: sha512-bCB4a7KKaQpPNlkExf27AmLJabeGjpjXlhnCdvmSIHHlT3YfpYz2CSKOLLLI/BcBez8zUBNDVmvswm3ggCCRqA==} + '@rspack/binding@2.2.8': + resolution: {integrity: sha512-c9flkSua3khHnOKBCRk8BkHGOwU9YB7niv+5JfQXiLZIvJmgKsZ7PD8QrFklMhsJT3oVAc6c9GN7/wy1YkACjA==} - '@rspack/core@2.2.6': - resolution: {integrity: sha512-sqN75Fgf6v3tCmt8GAG/rdcYHOUDv/YZbQf24YCGDoMLmdNlXCosCHkBYWI8qdLs9IqSnGt7XgbcA39Zilg6UQ==} + '@rspack/core@2.2.8': + resolution: {integrity: sha512-JugsVrnbMGNYzMH++7gR9enygpP/lAo9PbVRq3XmJg0ryIvCpST0XJjBcccaSjYaaPamsjrddihmgVLMFp+yXw==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@module-federation/runtime-tools': ^0.24.1 || ^2.0.0 @@ -808,22 +1009,22 @@ packages: resolution: {integrity: sha512-utTS5L2OkeYUzXGohL1Z8sefu1GLNOJcxe8Hd6iIdc/Xo1K1nDB2JEp4iSFhvYh33xKC9V91TxrS8qfrpoKobQ==} engines: {node: '>=20.19'} - '@tanstack/react-router@1.170.38': - resolution: {integrity: sha512-iHM9b0aDJbuvftmkiQXawzoqsdV68p2Re2safbVLCnxafe+iLKV++2i3hI/BMAP6uR92/Mjw94JKJJfD7pbwHQ==} + '@tanstack/react-router@1.170.41': + resolution: {integrity: sha512-Mpvw8Wm5MGDbTqI8Yc9flLKxbOHwoWocv0H2aGKcgw2a017oK5nZWqa954P+DrUx/8fI98YNywnHP5XcD3tQ9w==} engines: {node: '>=20.19'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-client@1.168.36': - resolution: {integrity: sha512-TRXZKag/Ng0TqVfcN4cbEDVJhEZYosHZ9/VAEhK3PK/YqznytK+bcoonE4jP5NCXMdH2YRTMsrWKrApEuWz0Cg==} + '@tanstack/react-start-client@1.168.39': + resolution: {integrity: sha512-Uby0uo37JlQgUKHU0snDwH9nYiEeAjm9+7pUOFgycU/UszEILC2SO2DSCoNnCMuHKj9z+4URKzBYIP2exT9+wg==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-rsc@0.1.55': - resolution: {integrity: sha512-qfW89LMrewtMLHMCumMP3+3mf2gPezFE7FZ7CHPJis4paxt2A6Jl7eCpE6whYgc5DFiifUv9qdfbC22HIP0HAw==} + '@tanstack/react-start-rsc@0.1.59': + resolution: {integrity: sha512-M2Lqxkk5C1zQncQs5paqX+/OLdOht51pg6D84YfLMrNsjhU/nzb6wd92MIja0mi5i23+L+fxLLjQYR2DObjBlw==} engines: {node: '>=22.12.0'} peerDependencies: '@rspack/core': '>=2.0.0-0' @@ -839,15 +1040,15 @@ packages: react-server-dom-rspack: optional: true - '@tanstack/react-start-server@1.167.43': - resolution: {integrity: sha512-/Fc1mOEBAyXmqxnihIkS1DrCcJX8ffrJ+xyc8nnFmO9YuCdrnZJskd48R1ZiOhM7u3vCPzDxUycIhKo8HlKn2g==} + '@tanstack/react-start-server@1.167.46': + resolution: {integrity: sha512-K7z8xBOEdp8u5CZCxWrbS0tFtk5XZGMdhssLeZlRZKlLDbX7BKLdMtJ7th5sR6QVt9MTUAg3LcoqvJMPSFF+lg==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start@1.168.56': - resolution: {integrity: sha512-b/jVJS+yt7J0IKwXfVoA2mlhIi92sWfwCYrCVked0WERzgbYAad6xrOwNTljz9OKyeajofGrmMdKJgRzK1Lt2w==} + '@tanstack/react-start@1.168.60': + resolution: {integrity: sha512-WMOM0Xlh2lPbVlbjYM6uOxOpiaWRPvODm/dwkLsGKq5tjyHOHZmKckJgwlrZl7OQUqFlOZHYj8bKUTgoeg8cUQ==} engines: {node: '>=22.12.0'} peerDependencies: '@rsbuild/core': ^2.0.0 @@ -863,26 +1064,26 @@ packages: vite: optional: true - '@tanstack/react-store@0.11.1': - resolution: {integrity: sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ==} + '@tanstack/react-store@0.11.2': + resolution: {integrity: sha512-oj5O5DmiMWE+ih+SioX2ayC1kbK7d3fpLx0B75fPJr9nY3zU+hoW010yr64qXsY/9fyiLVE2bn5mGNV3owm8RQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.171.32': - resolution: {integrity: sha512-X86Jqk3vB2KJcUfS6oLi7B7yxbaa59QEhZRPPP1fRx3k+Jw/Fu1UYVBcRtdwK/OccQnrlQdVoKvNO3QXmyEBOw==} + '@tanstack/router-core@1.171.34': + resolution: {integrity: sha512-X+19pGuuLVPvvAvPYw9v/+ktRk5cCll8AtfpBpOhDRmVgUMrfqze0467xtq8YRVVs/mmo5rVtWn/7Tp32RDBKw==} engines: {node: '>=20.19'} - '@tanstack/router-generator@1.167.38': - resolution: {integrity: sha512-lkqgFleDgfkW+QONVMKBs+ZGDUF0v9R9FkplS/GZvKDFpfnu+tZxmGlV2pryU1TDxyuSyjh56AtIla3NMrlScw==} + '@tanstack/router-generator@1.167.40': + resolution: {integrity: sha512-WkEueqEvsBBudzO0fx1nWfOmRNW8OAvBoVABTGls9bAWzjdC+KDGHvjCNrvn88ofs1F6gqL8aWnzKCbxW3dCyQ==} engines: {node: '>=20.19'} - '@tanstack/router-plugin@1.168.40': - resolution: {integrity: sha512-ifiXjjR4uivxwRDhgYAcfyrcu+Mwx+vlG0QjjQ7N5C5sKmzpt5UtsL21TNVvZDSZl2Vae8DcL84Z02fOs8ZZMQ==} + '@tanstack/router-plugin@1.168.42': + resolution: {integrity: sha512-vAR5WXJh/ErseEWQftDhsJcr2c++ucRQ6FbqN3ygMO44+ZxxRfW3sIpZmddHROVSmBk8jc6MrwjTPNjVhd62Vw==} engines: {node: '>=20.19'} peerDependencies: '@rsbuild/core': '>=1.0.2 || ^2.0.0' - '@tanstack/react-router': ^1.170.38 + '@tanstack/react-router': ^1.170.41 vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' vite-plugin-solid: ^2.11.10 || ^3.0.0-0 webpack: '>=5.92.0' @@ -902,16 +1103,16 @@ packages: resolution: {integrity: sha512-Icb0xGuG1+54IV0WMLRcc3ErTx2HeJWyPG661FkQ8UT6guoBq1FJjFRqlG/JS0xi4mFFkBhvwO7tbLa32f3yxA==} engines: {node: '>=20.19'} - '@tanstack/start-client-core@1.170.32': - resolution: {integrity: sha512-kawgeg3Ej/JvGeEN7txFdVN9kXOST9wl7yPojOGESiomwiubHKRdYUS6sBskW91j44H+HirQUppP4+Ha7kjb4A==} + '@tanstack/start-client-core@1.170.34': + resolution: {integrity: sha512-LfwZSbau2MLLZ/uPAoO+NC9fK3J66c0wLhnNQqIa+Goqw7bXPjkHwlsjY7evlUz1AnyhhQ1Qab95v+VmP0TZ9A==} engines: {node: '>=22.12.0'} '@tanstack/start-fn-stubs@1.162.0': resolution: {integrity: sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==} engines: {node: '>=22.12.0'} - '@tanstack/start-plugin-core@1.171.46': - resolution: {integrity: sha512-d/cDPZNDdRl824mOamt6USO1ja5JAaIwkuT2FIBRkBdrKZgQ8GJWv5pqpkXMbKANc49V8bUBBOTjgiCdC7YsPw==} + '@tanstack/start-plugin-core@1.171.49': + resolution: {integrity: sha512-HPOb7LnMc71k38cd5uvMybPPUU/Q/56KvSqQzSWz7cps1ZDFXaxUM6EFaFHLCdgTWqGexNGDdRE+sUVPVBM4Xg==} engines: {node: '>=22.12.0'} peerDependencies: '@rsbuild/core': ^2.0.0 @@ -922,48 +1123,48 @@ packages: vite: optional: true - '@tanstack/start-server-core@1.169.37': - resolution: {integrity: sha512-CjYMXg2XISMEJt9UVR4lbMRsnRyWPrdyuF8iuJ4gxTc1sVNRciwMVpMPVGQG2KuIma3ewlNxw8+Tpx2tnS3zRA==} + '@tanstack/start-server-core@1.169.39': + resolution: {integrity: sha512-pqqkxhXY46nuFSYt6TsMGyABiplUzHFHndKAlRRGC0fWDrJCH1koWxVMoxySa51ZETV79f/z73b5UZ1jfIkXwQ==} engines: {node: '>=22.12.0'} - '@tanstack/start-storage-context@1.167.34': - resolution: {integrity: sha512-cRPE0kjt1CnOIhepmNOvRFwCGhDAnCRohPD3HmzqJJThPkixLBRuLDvIYp4hRPtM1TJ9hiKbYl24IxAhjZO5TA==} + '@tanstack/start-storage-context@1.167.36': + resolution: {integrity: sha512-x+xCXK2GtFzY6NbXqAg8YpU6cwAjDfMeYSNzxfGzntpjbwBRHHrXeC/kYUnBBQCV7wK4kJ8vBBQGP0OyYTasUg==} engines: {node: '>=22.12.0'} - '@tanstack/store@0.11.1': - resolution: {integrity: sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA==} + '@tanstack/store@0.11.2': + resolution: {integrity: sha512-sJ4mjol8uQsHV0gOJzzjwXfh2Fwm+Sz0+8deqiTm4jGbMdjzNSW+xZCFm0kUa870uhd8yi+DpKnZb5Kc4apa0Q==} '@tanstack/virtual-file-routes@1.162.0': resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==} engines: {node: '>=20.19'} - '@turbo/darwin-64@2.10.13': - resolution: {integrity: sha512-m5DBcpkxmcKpEJHHFUfqf9GLUjSmw3cDnBJM9nghPW3kjCNb5OyNKmzG7/yT5x5AwzOO+HMwKN+UI4iVKL87YA==} + '@turbo/darwin-64@2.11.7': + resolution: {integrity: sha512-Z2LFOxwbiJueD1miByXJS/BrW21vv6YaQXZustty5jEX4vQYw6zyh101Sz1QO7WG0mqBzKagZ/DoAeeujI0/Sw==} cpu: [x64] os: [darwin] - '@turbo/darwin-arm64@2.10.13': - resolution: {integrity: sha512-vfKPvSuoY8BJGj8M8yN+a7aq1g+CejbEacDSDlJLDkSbPN3J/U1DbYAmPG8Ype5XvImI0SgfvUhIorNnFj67zw==} + '@turbo/darwin-arm64@2.11.7': + resolution: {integrity: sha512-Vhv8ybgIflosNDSSRD9fNXHlCFA60dsGyTqQmc7l28SEX5jOaweCCD4BBYNd8/p0WGkw3e7tZbfA9w6FsujGvw==} cpu: [arm64] os: [darwin] - '@turbo/linux-64@2.10.13': - resolution: {integrity: sha512-Zp0N5M6j9iEjJtFLTA2L+52TLuYy0VbXT3zLZzfMeqqdKSUJ7pDo2pA1NhqjoQcF2dIsktZ8xZRW3pGI6T81fw==} + '@turbo/linux-64@2.11.7': + resolution: {integrity: sha512-rAqRp3blQKk1+ylMGlZHAW0eJjVNuuGUm4ZrC6RU1Im8tLryvaO4a2zLQ/FQi1TRlmKqKV9YXBki2YeytlLbXA==} cpu: [x64] os: [android, linux] - '@turbo/linux-arm64@2.10.13': - resolution: {integrity: sha512-M8XcfWdS2D4EP3wzd5ehUyCANucMIOsRE6gZuV0Mr2NIn+FpsUjXFCh7r9HY7GnNcjXH4tUnT76OsCDg2c0Fng==} + '@turbo/linux-arm64@2.11.7': + resolution: {integrity: sha512-thVYGov7S+llXAxrl3kBb/efalav0xhrjl5NHCR6AfKx14JkbqaetvbVwyss4FvZ9rt+Pv3xsTb+8SIYix8DTg==} cpu: [arm64] os: [android, linux] - '@turbo/windows-64@2.10.13': - resolution: {integrity: sha512-QFd3KBMpBBCc31TbxMnxgdBivie8lxJXV8EqmanH8fg8l2e1BdCLWpZJbxk48VNvcF4LHUnwO0FJOq+veKN1cw==} + '@turbo/windows-64@2.11.7': + resolution: {integrity: sha512-ZIoKT7J7KutgYl4bGxmmgeUQjG+7E0nY8BF9dxUOiMH2vdTsM4JQ2uSwTYFkTR2Llbg6ghGjSmuZ8LQtkLEYjg==} cpu: [x64] os: [win32] - '@turbo/windows-arm64@2.10.13': - resolution: {integrity: sha512-rM+cFO1P8iAHXdasOdMyK9nDk0UGII7VEFLIA8+jwv/+rh48pnVBbF2OSjJDBOw57pxNLIOEDA0JyKcarTBcIw==} + '@turbo/windows-arm64@2.11.7': + resolution: {integrity: sha512-88Xv2f7SLGVJ68lRRx8o375dKmnZYNdDtvlKcPA0kZpcF93eAQd8l96BKQ0/AMHG0bMJA0Z5QqoqtetxzNsC+g==} cpu: [arm64] os: [win32] @@ -973,8 +1174,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@24.10.1': - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/node@26.6.4': + resolution: {integrity: sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==} '@types/react-dom@19.3.0': resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} @@ -984,13 +1185,133 @@ packages: '@types/react@19.3.0': resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} - '@vitejs/plugin-react@6.1.1': - resolution: {integrity: sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@vitejs/plugin-react@6.1.2': + resolution: {integrity: sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 babel-plugin-react-compiler: ^1.0.0 - oxc-transform-react: ^0.145.0 + oxc-transform-react: ^0.152.0 vite: ^8.0.0 peerDependenciesMeta: '@rolldown/plugin-babel': @@ -1000,8 +1321,8 @@ packages: oxc-transform-react: optional: true - adm-zip@0.6.0: - resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} + adm-zip@0.6.1: + resolution: {integrity: sha512-Xwrja8nx9e5o2N1my4DsKCeKpdrnACyr1wtbPxBDgGzKzKyE9kRtBFA8mWldI+RVlD7CBZNWY/wQ2+ydwOR6kQ==} engines: {node: '>=14.0'} ajv-formats@2.1.1: @@ -1025,13 +1346,13 @@ packages: babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} - baseline-browser-mapping@2.11.24: - resolution: {integrity: sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==} + baseline-browser-mapping@2.11.27: + resolution: {integrity: sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==} engines: {node: '>=6.0.0'} hasBin: true - browserslist@4.29.0: - resolution: {integrity: sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==} + browserslist@4.29.3: + resolution: {integrity: sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1039,8 +1360,8 @@ packages: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} - caniuse-lite@1.0.30001810: - resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + caniuse-lite@1.0.30001814: + resolution: {integrity: sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==} chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} @@ -1076,8 +1397,8 @@ packages: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} - electron-to-chromium@1.5.430: - resolution: {integrity: sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ==} + electron-to-chromium@1.5.444: + resolution: {integrity: sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ==} encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} @@ -1086,8 +1407,8 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - exsolve@1.1.1: - resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} + exsolve@1.1.3: + resolution: {integrity: sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1135,8 +1456,8 @@ packages: crossws: optional: true - human-id@4.2.1: - resolution: {integrity: sha512-zPGsiS+dWoTZtZ4AtpA9Y+BdSFSNWvnouNlWNoUFyAM6xHOHmdCvqO3k8AIbdamCOv4gUFUVNPf6rJFfc4UiJw==} + human-id@4.2.2: + resolution: {integrity: sha512-Z+8unD6em/MJwyrkWQ2M3wygRGI1+MztbutiNwk1DCuEPXOZRWGjtcMPZYP1exY9iNZMhd7ZOSMQSo9WYtiQRw==} hasBin: true iconv-lite@0.6.3: @@ -1227,24 +1548,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.33.0: resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.33.0: resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.33.0: resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.33.0: resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} @@ -1271,8 +1596,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.19: - resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -1285,12 +1610,12 @@ packages: encoding: optional: true - node-releases@2.0.55: - resolution: {integrity: sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==} + node-releases@2.0.57: + resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} engines: {node: '>=18'} - package-manager-detector@1.8.0: - resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + package-manager-detector@1.9.0: + resolution: {integrity: sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==} pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1312,12 +1637,12 @@ packages: engines: {node: '>=20'} hasBin: true - postcss@8.5.28: - resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + postcss@8.5.29: + resolution: {integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==} engines: {node: ^10 || ^12 || >=14} - prettier@3.9.7: - resolution: {integrity: sha512-T3mF5P7HFzZVLDQuCUNtJj6WK1pcpLMweMNUVwGb01bLNkH9AkKxtZvmInw8K6bnn2O3d6/5RHl6zSHiBAD0Og==} + prettier@3.9.9: + resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==} engines: {node: '>=14'} hasBin: true @@ -1326,8 +1651,8 @@ packages: peerDependencies: react: ^19.3.0 - react-refresh@0.18.0: - resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + react-refresh@0.19.0: + resolution: {integrity: sha512-5HdstYgoJBbBofjapO5qCPcovECJCBfvjLb+iTcsmqDFVMRdqy0qs7iiInBJTUXmIFc2bjoc5b2HVLp2zRtYhQ==} engines: {node: '>=0.10.0'} react@19.3.0: @@ -1342,16 +1667,16 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - rolldown@1.2.9: - resolution: {integrity: sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==} + rolldown@1.2.12: + resolution: {integrity: sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true rou3@0.8.1: resolution: {integrity: sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==} - rsbuild-plugin-dts@1.0.1: - resolution: {integrity: sha512-vzLhs8v1SuRd0qyIWue5caaM2A1lGImuH+cQAX71KnNpS9BZHyHRBHHXBFp9jp610XJnLlkBATyPclqUfGtCAA==} + rsbuild-plugin-dts@1.0.3: + resolution: {integrity: sha512-wVxMYvWmVPam6SeZsz8OixHm+4JPfLJAAMTD0Q2te2SxksvjuHC3wuvU12teSmloQ3nFX1YC6guNzuYqDYw+vQ==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@microsoft/api-extractor': ^7 @@ -1382,25 +1707,25 @@ packages: engines: {node: '>=10'} hasBin: true - seroval-plugins@1.6.7: - resolution: {integrity: sha512-4Nk35ttD3DTDJW4hgw5StsVAPeU6qnDFnULAouw6tQ7oLTV/ICXrWpsXo2EE52eSP2joUMazbVf52mFEcADqRw==} + seroval-plugins@1.6.8: + resolution: {integrity: sha512-N7mWAMydj89EnYTHtRpS3LBrAz3J9O6oVrqpuXuvlAHfAUG8WEKqtbAd7SBiJNY5NfCAFhSdyAh7wI4Shjqy4g==} engines: {node: '>=10'} peerDependencies: seroval: ^1.0 - seroval@1.6.7: - resolution: {integrity: sha512-AeDcLh0yO2SFm9W71essgnSzLV9DI8ZH0x0knXn2DMnUZj728mpLbxjlbB6IqKCmqh8JA3cEqRyGoNkt584JcQ==} + seroval@1.6.8: + resolution: {integrity: sha512-HlSgSAkTk4EqHcje1ptJjfZi1YDv5KbhVJ/d3P7T/nAXua2VmDu+AKDX5VTdFfZf48nDkWB2TKYt0DrCSa+3wg==} engines: {node: '>=10'} - shell-quote@1.10.0: - resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + shell-quote@1.12.0: + resolution: {integrity: sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==} engines: {node: '>= 0.4'} sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} source-map@0.7.6: @@ -1430,23 +1755,23 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - turbo@2.10.13: - resolution: {integrity: sha512-69MkPbjk+G8oyC7pC2DeBk0rPdbLz51wuH5eAT0Q4BVJkzq/b1XM4kUUidsARbmDjwIELSJwd1ezITULY9kWNw==} + turbo@2.11.7: + resolution: {integrity: sha512-RhXHbqfvY6o3xt/zSHCwSNwLrZlXiqHzewi4eAj9PtZ1T1CqgMJ2yIDb7SKPTG9IikTm925mfzL6hldQVdyNWw==} hasBin: true - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} - undici@7.29.0: - resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} + undici@7.29.1: + resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==} engines: {node: '>=20.18.1'} unplugin@3.4.0: @@ -1496,8 +1821,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - vite@8.3.0: - resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==} + vite@8.3.2: + resolution: {integrity: sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1556,8 +1881,8 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - ws@8.21.0: - resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1639,7 +1964,7 @@ snapshots: '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.9 '@babel/template': 7.29.7 '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 @@ -1654,7 +1979,7 @@ snapshots: '@babel/generator@7.29.8': dependencies: - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.9 '@babel/types': 7.29.8 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 @@ -1664,7 +1989,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.29.0 + browserslist: 4.29.3 lru-cache: 5.1.1 semver: 6.3.1 @@ -1697,14 +2022,14 @@ snapshots: '@babel/template': 7.29.7 '@babel/types': 7.29.8 - '@babel/parser@7.29.8': + '@babel/parser@7.29.9': dependencies: '@babel/types': 7.29.8 '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.9 '@babel/types': 7.29.8 '@babel/traverse@7.29.8': @@ -1712,7 +2037,7 @@ snapshots: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.9 '@babel/template': 7.29.7 '@babel/types': 7.29.8 debug: 4.4.3 @@ -1767,7 +2092,7 @@ snapshots: cac: 7.0.0 import-meta-resolve: 4.2.0 launch-editor: 2.14.1 - package-manager-detector: 1.8.0 + package-manager-detector: 1.9.0 semver: 7.8.5 tinyexec: 1.3.1 @@ -1783,7 +2108,7 @@ snapshots: '@changesets/format@0.1.2': dependencies: - package-manager-detector: 1.8.0 + package-manager-detector: 1.9.0 tinyexec: 1.3.1 '@changesets/get-dependents-graph@3.0.0': @@ -1826,7 +2151,7 @@ snapshots: dependencies: '@changesets/format': 0.1.2 '@changesets/types': 7.0.0 - human-id: 4.2.1 + human-id: 4.2.2 '@clack/core@1.5.1': dependencies: @@ -1890,14 +2215,14 @@ snapshots: tinyglobby: 0.2.17 yaml: 2.9.1 - '@module-federation/bridge-react-webpack-plugin@2.9.0': + '@module-federation/bridge-react-webpack-plugin@2.9.2': dependencies: - '@module-federation/sdk': 2.9.0 + '@module-federation/sdk': 2.9.2 - '@module-federation/cli@2.9.0(typescript@6.0.3)': + '@module-federation/cli@2.9.2(typescript@7.0.2)': dependencies: - '@module-federation/dts-plugin': 2.9.0(typescript@6.0.3) - '@module-federation/sdk': 2.9.0 + '@module-federation/dts-plugin': 2.9.2(typescript@7.0.2) + '@module-federation/sdk': 2.9.2 commander: 11.1.0 jiti: 2.4.2 transitivePeerDependencies: @@ -1906,69 +2231,69 @@ snapshots: - utf-8-validate - vue-tsc - '@module-federation/dts-plugin@2.9.0(typescript@6.0.3)': - dependencies: - '@module-federation/error-codes': 2.9.0 - '@module-federation/managers': 2.9.0 - '@module-federation/sdk': 2.9.0 - '@module-federation/third-party-dts-extractor': 2.9.0 - adm-zip: 0.6.0 - isomorphic-ws: 5.0.0(ws@8.21.0) - typescript: 6.0.3 - undici: 7.29.0 - ws: 8.21.0 + '@module-federation/dts-plugin@2.9.2(typescript@7.0.2)': + dependencies: + '@module-federation/error-codes': 2.9.2 + '@module-federation/managers': 2.9.2 + '@module-federation/sdk': 2.9.2 + '@module-federation/third-party-dts-extractor': 2.9.2 + adm-zip: 0.6.1 + isomorphic-ws: 5.0.0(ws@8.21.3) + typescript: 7.0.2 + undici: 7.29.1 + ws: 8.21.3 transitivePeerDependencies: - bufferutil - utf-8-validate - '@module-federation/enhanced@2.9.0(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3)': - dependencies: - '@module-federation/bridge-react-webpack-plugin': 2.9.0 - '@module-federation/cli': 2.9.0(typescript@6.0.3) - '@module-federation/dts-plugin': 2.9.0(typescript@6.0.3) - '@module-federation/error-codes': 2.9.0 - '@module-federation/inject-external-runtime-core-plugin': 2.9.0(@module-federation/runtime-tools@2.9.0) - '@module-federation/managers': 2.9.0 - '@module-federation/manifest': 2.9.0(typescript@6.0.3) - '@module-federation/rspack': 2.9.0(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) - '@module-federation/runtime-tools': 2.9.0 - '@module-federation/sdk': 2.9.0 - '@module-federation/webpack-bundler-runtime': 2.9.0 + '@module-federation/enhanced@2.9.2(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2)': + dependencies: + '@module-federation/bridge-react-webpack-plugin': 2.9.2 + '@module-federation/cli': 2.9.2(typescript@7.0.2) + '@module-federation/dts-plugin': 2.9.2(typescript@7.0.2) + '@module-federation/error-codes': 2.9.2 + '@module-federation/inject-external-runtime-core-plugin': 2.9.2(@module-federation/runtime-tools@2.9.2) + '@module-federation/managers': 2.9.2 + '@module-federation/manifest': 2.9.2(typescript@7.0.2) + '@module-federation/rspack': 2.9.2(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2) + '@module-federation/runtime-tools': 2.9.2 + '@module-federation/sdk': 2.9.2 + '@module-federation/webpack-bundler-runtime': 2.9.2 schema-utils: 4.3.0 tapable: 2.3.0 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - '@rspack/core' - bufferutil - utf-8-validate - '@module-federation/error-codes@2.9.0': {} + '@module-federation/error-codes@2.9.2': {} - '@module-federation/inject-external-runtime-core-plugin@2.9.0(@module-federation/runtime-tools@2.9.0)': + '@module-federation/inject-external-runtime-core-plugin@2.9.2(@module-federation/runtime-tools@2.9.2)': dependencies: - '@module-federation/runtime-tools': 2.9.0 + '@module-federation/runtime-tools': 2.9.2 - '@module-federation/managers@2.9.0': + '@module-federation/managers@2.9.2': dependencies: - '@module-federation/sdk': 2.9.0 + '@module-federation/sdk': 2.9.2 - '@module-federation/manifest@2.9.0(typescript@6.0.3)': + '@module-federation/manifest@2.9.2(typescript@7.0.2)': dependencies: - '@module-federation/dts-plugin': 2.9.0(typescript@6.0.3) - '@module-federation/managers': 2.9.0 - '@module-federation/sdk': 2.9.0 + '@module-federation/dts-plugin': 2.9.2(typescript@7.0.2) + '@module-federation/managers': 2.9.2 + '@module-federation/sdk': 2.9.2 transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - vue-tsc - '@module-federation/node@2.7.50(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3)': + '@module-federation/node@2.7.52(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2)': dependencies: - '@module-federation/enhanced': 2.9.0(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) - '@module-federation/runtime': 2.9.0 - '@module-federation/sdk': 2.9.0 + '@module-federation/enhanced': 2.9.2(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2) + '@module-federation/runtime': 2.9.2 + '@module-federation/sdk': 2.9.2 encoding: 0.1.13 node-fetch: 2.7.0(encoding@0.1.13) tapable: 2.3.0 @@ -1979,13 +2304,13 @@ snapshots: - utf-8-validate - vue-tsc - '@module-federation/rsbuild-plugin@2.9.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3)': + '@module-federation/rsbuild-plugin@2.9.2(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2)': dependencies: - '@module-federation/enhanced': 2.9.0(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) - '@module-federation/node': 2.7.50(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3) - '@module-federation/sdk': 2.9.0 + '@module-federation/enhanced': 2.9.2(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2) + '@module-federation/node': 2.7.52(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2) + '@module-federation/sdk': 2.9.2 optionalDependencies: - '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) + '@rsbuild/core': 2.2.11(@module-federation/runtime-tools@2.9.2) transitivePeerDependencies: - '@rspack/core' - bufferutil @@ -1994,59 +2319,59 @@ snapshots: - vue-tsc - webpack - '@module-federation/rspack@2.9.0(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(typescript@6.0.3)': + '@module-federation/rspack@2.9.2(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2)': dependencies: - '@module-federation/bridge-react-webpack-plugin': 2.9.0 - '@module-federation/dts-plugin': 2.9.0(typescript@6.0.3) - '@module-federation/inject-external-runtime-core-plugin': 2.9.0(@module-federation/runtime-tools@2.9.0) - '@module-federation/managers': 2.9.0 - '@module-federation/manifest': 2.9.0(typescript@6.0.3) - '@module-federation/runtime-tools': 2.9.0 - '@module-federation/sdk': 2.9.0 - '@rspack/core': 2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23) + '@module-federation/bridge-react-webpack-plugin': 2.9.2 + '@module-federation/dts-plugin': 2.9.2(typescript@7.0.2) + '@module-federation/inject-external-runtime-core-plugin': 2.9.2(@module-federation/runtime-tools@2.9.2) + '@module-federation/managers': 2.9.2 + '@module-federation/manifest': 2.9.2(typescript@7.0.2) + '@module-federation/runtime-tools': 2.9.2 + '@module-federation/sdk': 2.9.2 + '@rspack/core': 2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23) optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - bufferutil - utf-8-validate - '@module-federation/runtime-core@2.9.0': + '@module-federation/runtime-core@2.9.2': dependencies: - '@module-federation/error-codes': 2.9.0 - '@module-federation/sdk': 2.9.0 + '@module-federation/error-codes': 2.9.2 + '@module-federation/sdk': 2.9.2 - '@module-federation/runtime-tools@2.9.0': + '@module-federation/runtime-tools@2.9.2': dependencies: - '@module-federation/runtime': 2.9.0 - '@module-federation/webpack-bundler-runtime': 2.9.0 + '@module-federation/runtime': 2.9.2 + '@module-federation/webpack-bundler-runtime': 2.9.2 - '@module-federation/runtime@2.9.0': + '@module-federation/runtime@2.9.2': dependencies: - '@module-federation/error-codes': 2.9.0 - '@module-federation/runtime-core': 2.9.0 - '@module-federation/sdk': 2.9.0 + '@module-federation/error-codes': 2.9.2 + '@module-federation/runtime-core': 2.9.2 + '@module-federation/sdk': 2.9.2 - '@module-federation/sdk@2.9.0': {} + '@module-federation/sdk@2.9.2': {} - '@module-federation/third-party-dts-extractor@2.9.0': {} + '@module-federation/third-party-dts-extractor@2.9.2': {} - '@module-federation/vite@1.22.0(typescript@6.0.3)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + '@module-federation/vite@1.23.2(typescript@7.0.2)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))': dependencies: - '@module-federation/dts-plugin': 2.9.0(typescript@6.0.3) - '@module-federation/runtime': 2.9.0 - '@module-federation/sdk': 2.9.0 - vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + '@module-federation/dts-plugin': 2.9.2(typescript@7.0.2) + '@module-federation/runtime': 2.9.2 + '@module-federation/sdk': 2.9.2 + vite: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - vue-tsc - '@module-federation/webpack-bundler-runtime@2.9.0': + '@module-federation/webpack-bundler-runtime@2.9.2': dependencies: - '@module-federation/error-codes': 2.9.0 - '@module-federation/runtime': 2.9.0 - '@module-federation/sdk': 2.9.0 + '@module-federation/error-codes': 2.9.2 + '@module-federation/runtime': 2.9.2 + '@module-federation/sdk': 2.9.2 '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': dependencies: @@ -2072,158 +2397,158 @@ snapshots: '@oozcitak/util@10.0.0': {} - '@oxc-project/types@0.150.0': {} + '@oxc-project/types@0.152.0': {} '@pnpm/deps.graph-sequencer@1100.0.1': {} - '@rolldown/binding-android-arm-eabi@1.2.9': + '@rolldown/binding-android-arm-eabi@1.2.12': optional: true - '@rolldown/binding-android-arm64@1.2.9': + '@rolldown/binding-android-arm64@1.2.12': optional: true - '@rolldown/binding-darwin-arm64@1.2.9': + '@rolldown/binding-darwin-arm64@1.2.12': optional: true - '@rolldown/binding-darwin-x64@1.2.9': + '@rolldown/binding-darwin-x64@1.2.12': optional: true - '@rolldown/binding-freebsd-x64@1.2.9': + '@rolldown/binding-freebsd-x64@1.2.12': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.9': + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.9': + '@rolldown/binding-linux-arm64-gnu@1.2.12': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.9': + '@rolldown/binding-linux-arm64-musl@1.2.12': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.9': + '@rolldown/binding-linux-ppc64-gnu@1.2.12': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.9': + '@rolldown/binding-linux-s390x-gnu@1.2.12': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.9': + '@rolldown/binding-linux-x64-gnu@1.2.12': optional: true - '@rolldown/binding-linux-x64-musl@1.2.9': + '@rolldown/binding-linux-x64-musl@1.2.12': optional: true - '@rolldown/binding-openharmony-arm64@1.2.9': + '@rolldown/binding-openharmony-arm64@1.2.12': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.9': + '@rolldown/binding-win32-arm64-msvc@1.2.12': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.9': + '@rolldown/binding-win32-x64-msvc@1.2.12': optional: true '@rolldown/pluginutils@1.0.1': {} - '@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0)': + '@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2)': dependencies: - '@rspack/core': 2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23) + '@rspack/core': 2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23) '@swc/helpers': 0.5.23 transitivePeerDependencies: - '@module-federation/runtime-tools' - '@rsbuild/plugin-react@2.1.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))': + '@rsbuild/plugin-react@2.1.1(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))': dependencies: - '@rspack/plugin-react-refresh': 2.0.2(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-refresh@0.18.0) - react-refresh: 0.18.0 + '@rspack/plugin-react-refresh': 2.0.2(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-refresh@0.19.0) + react-refresh: 0.19.0 optionalDependencies: - '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) + '@rsbuild/core': 2.2.11(@module-federation/runtime-tools@2.9.2) transitivePeerDependencies: - '@rspack/core' - '@rslib/core@1.0.1(@module-federation/runtime-tools@2.9.0)(typescript@6.0.3)': + '@rslib/core@1.0.3(@module-federation/runtime-tools@2.9.2)(typescript@7.0.2)': dependencies: - '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) - rsbuild-plugin-dts: 1.0.1(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(typescript@6.0.3) + '@rsbuild/core': 2.2.11(@module-federation/runtime-tools@2.9.2) + rsbuild-plugin-dts: 1.0.3(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(typescript@7.0.2) optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - '@module-federation/runtime-tools' - core-js - '@rspack/binding-darwin-arm64@2.2.6': + '@rspack/binding-darwin-arm64@2.2.8': optional: true - '@rspack/binding-darwin-x64@2.2.6': + '@rspack/binding-darwin-x64@2.2.8': optional: true - '@rspack/binding-linux-arm64-gnu@2.2.6': + '@rspack/binding-linux-arm64-gnu@2.2.8': optional: true - '@rspack/binding-linux-arm64-musl@2.2.6': + '@rspack/binding-linux-arm64-musl@2.2.8': optional: true - '@rspack/binding-linux-ppc64-gnu@2.2.6': + '@rspack/binding-linux-ppc64-gnu@2.2.8': optional: true - '@rspack/binding-linux-riscv64-gnu@2.2.6': + '@rspack/binding-linux-riscv64-gnu@2.2.8': optional: true - '@rspack/binding-linux-riscv64-musl@2.2.6': + '@rspack/binding-linux-riscv64-musl@2.2.8': optional: true - '@rspack/binding-linux-s390x-gnu@2.2.6': + '@rspack/binding-linux-s390x-gnu@2.2.8': optional: true - '@rspack/binding-linux-x64-gnu@2.2.6': + '@rspack/binding-linux-x64-gnu@2.2.8': optional: true - '@rspack/binding-linux-x64-musl@2.2.6': + '@rspack/binding-linux-x64-musl@2.2.8': optional: true - '@rspack/binding-wasm32-wasi@2.2.6': + '@rspack/binding-wasm32-wasi@2.2.8': dependencies: '@emnapi/core': 1.11.3 '@emnapi/runtime': 1.11.3 '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) optional: true - '@rspack/binding-win32-arm64-msvc@2.2.6': + '@rspack/binding-win32-arm64-msvc@2.2.8': optional: true - '@rspack/binding-win32-ia32-msvc@2.2.6': + '@rspack/binding-win32-ia32-msvc@2.2.8': optional: true - '@rspack/binding-win32-x64-msvc@2.2.6': + '@rspack/binding-win32-x64-msvc@2.2.8': optional: true - '@rspack/binding@2.2.6': + '@rspack/binding@2.2.8': optionalDependencies: - '@rspack/binding-darwin-arm64': 2.2.6 - '@rspack/binding-darwin-x64': 2.2.6 - '@rspack/binding-linux-arm64-gnu': 2.2.6 - '@rspack/binding-linux-arm64-musl': 2.2.6 - '@rspack/binding-linux-ppc64-gnu': 2.2.6 - '@rspack/binding-linux-riscv64-gnu': 2.2.6 - '@rspack/binding-linux-riscv64-musl': 2.2.6 - '@rspack/binding-linux-s390x-gnu': 2.2.6 - '@rspack/binding-linux-x64-gnu': 2.2.6 - '@rspack/binding-linux-x64-musl': 2.2.6 - '@rspack/binding-wasm32-wasi': 2.2.6 - '@rspack/binding-win32-arm64-msvc': 2.2.6 - '@rspack/binding-win32-ia32-msvc': 2.2.6 - '@rspack/binding-win32-x64-msvc': 2.2.6 - - '@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23)': - dependencies: - '@rspack/binding': 2.2.6 + '@rspack/binding-darwin-arm64': 2.2.8 + '@rspack/binding-darwin-x64': 2.2.8 + '@rspack/binding-linux-arm64-gnu': 2.2.8 + '@rspack/binding-linux-arm64-musl': 2.2.8 + '@rspack/binding-linux-ppc64-gnu': 2.2.8 + '@rspack/binding-linux-riscv64-gnu': 2.2.8 + '@rspack/binding-linux-riscv64-musl': 2.2.8 + '@rspack/binding-linux-s390x-gnu': 2.2.8 + '@rspack/binding-linux-x64-gnu': 2.2.8 + '@rspack/binding-linux-x64-musl': 2.2.8 + '@rspack/binding-wasm32-wasi': 2.2.8 + '@rspack/binding-win32-arm64-msvc': 2.2.8 + '@rspack/binding-win32-ia32-msvc': 2.2.8 + '@rspack/binding-win32-x64-msvc': 2.2.8 + + '@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23)': + dependencies: + '@rspack/binding': 2.2.8 optionalDependencies: - '@module-federation/runtime-tools': 2.9.0 + '@module-federation/runtime-tools': 2.9.2 '@swc/helpers': 0.5.23 - '@rspack/plugin-react-refresh@2.0.2(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-refresh@0.18.0)': + '@rspack/plugin-react-refresh@2.0.2(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-refresh@0.19.0)': dependencies: - react-refresh: 0.18.0 + react-refresh: 0.19.0 optionalDependencies: - '@rspack/core': 2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23) + '@rspack/core': 2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23) '@swc/helpers@0.5.23': dependencies: @@ -2231,37 +2556,37 @@ snapshots: '@tanstack/history@1.162.4': {} - '@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/history': 1.162.4 - '@tanstack/react-store': 0.11.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.32 + '@tanstack/react-store': 0.11.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.34 isbot: 5.2.2 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - '@tanstack/react-start-client@1.168.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@tanstack/react-start-client@1.168.39(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.32 - '@tanstack/start-client-core': 1.170.32 + '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.34 + '@tanstack/start-client-core': 1.170.34 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - '@tanstack/react-start-rsc@0.1.55(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + '@tanstack/react-start-rsc@0.1.59(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))': dependencies: - '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.32 + '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.34 '@tanstack/router-utils': 1.162.3 - '@tanstack/start-client-core': 1.170.32 + '@tanstack/start-client-core': 1.170.34 '@tanstack/start-fn-stubs': 1.162.0 - '@tanstack/start-plugin-core': 1.171.46(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) - '@tanstack/start-storage-context': 1.167.34 + '@tanstack/start-plugin-core': 1.171.49(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/start-storage-context': 1.167.36 pathe: 2.0.3 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@rspack/core': 2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23) + '@rspack/core': 2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23) transitivePeerDependencies: - '@farmfe/core' - '@rsbuild/core' @@ -2276,32 +2601,32 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/react-start-server@1.167.43(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@tanstack/react-start-server@1.167.46(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.32 - '@tanstack/start-server-core': 1.169.37 + '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.34 + '@tanstack/start-server-core': 1.169.39 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) transitivePeerDependencies: - crossws - '@tanstack/react-start@1.168.56(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + '@tanstack/react-start@1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))': dependencies: - '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/react-start-client': 1.168.36(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/react-start-rsc': 0.1.55(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) - '@tanstack/react-start-server': 1.167.43(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-client': 1.168.39(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-rsc': 0.1.59(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/react-start-server': 1.167.46(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/router-utils': 1.162.3 - '@tanstack/start-client-core': 1.170.32 - '@tanstack/start-plugin-core': 1.171.46(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) - '@tanstack/start-server-core': 1.169.37 + '@tanstack/start-client-core': 1.170.34 + '@tanstack/start-plugin-core': 1.171.49(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/start-server-core': 1.169.39 pathe: 2.0.3 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) - vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + '@rsbuild/core': 2.2.11(@module-federation/runtime-tools@2.9.2) + vite: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -2316,48 +2641,48 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/react-store@0.11.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@tanstack/react-store@0.11.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@tanstack/store': 0.11.1 + '@tanstack/store': 0.11.2 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) use-sync-external-store: 1.7.0(react@19.3.0) - '@tanstack/router-core@1.171.32': + '@tanstack/router-core@1.171.34': dependencies: '@tanstack/history': 1.162.4 cookie-es: 3.1.1 - seroval: 1.6.7 - seroval-plugins: 1.6.7(seroval@1.6.7) + seroval: 1.6.8 + seroval-plugins: 1.6.8(seroval@1.6.8) - '@tanstack/router-generator@1.167.38': + '@tanstack/router-generator@1.167.40': dependencies: '@babel/types': 7.29.8 - '@tanstack/router-core': 1.171.32 + '@tanstack/router-core': 1.171.34 '@tanstack/router-utils': 1.162.3 '@tanstack/virtual-file-routes': 1.162.0 jiti: 2.7.0 magic-string: 0.30.21 - prettier: 3.9.7 + prettier: 3.9.9 zod: 4.6.5 transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.168.40(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + '@tanstack/router-plugin@1.168.42(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@babel/core': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.8 - '@tanstack/router-core': 1.171.32 - '@tanstack/router-generator': 1.167.38 + '@tanstack/router-core': 1.171.34 + '@tanstack/router-generator': 1.167.40 '@tanstack/router-utils': 1.162.3 chokidar: 5.0.0 - unplugin: 3.4.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + unplugin: 3.4.0(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) zod: 4.6.5 optionalDependencies: - '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) - '@tanstack/react-router': 1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + '@rsbuild/core': 2.2.11(@module-federation/runtime-tools@2.9.2) + '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + vite: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -2371,7 +2696,7 @@ snapshots: '@tanstack/router-utils@1.162.3': dependencies: '@babel/generator': 7.29.8 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.9 '@babel/types': 7.29.8 ansis: 4.4.0 babel-dead-code-elimination: 1.0.12 @@ -2381,41 +2706,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/start-client-core@1.170.32': + '@tanstack/start-client-core@1.170.34': dependencies: - '@tanstack/router-core': 1.171.32 + '@tanstack/router-core': 1.171.34 '@tanstack/start-fn-stubs': 1.162.0 - '@tanstack/start-storage-context': 1.167.34 - seroval: 1.6.7 + '@tanstack/start-storage-context': 1.167.36 + seroval: 1.6.8 '@tanstack/start-fn-stubs@1.162.0': {} - '@tanstack/start-plugin-core@1.171.46(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + '@tanstack/start-plugin-core@1.171.49(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@babel/code-frame': 7.29.7 '@babel/core': 7.29.7 '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 - '@tanstack/router-core': 1.171.32 - '@tanstack/router-generator': 1.167.38 - '@tanstack/router-plugin': 1.168.40(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + '@tanstack/router-core': 1.171.34 + '@tanstack/router-generator': 1.167.40 + '@tanstack/router-plugin': 1.168.42(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) '@tanstack/router-utils': 1.162.3 - '@tanstack/start-server-core': 1.169.37 - exsolve: 1.1.1 + '@tanstack/start-server-core': 1.169.39 + exsolve: 1.1.3 lightningcss: 1.33.0 pathe: 2.0.3 picomatch: 4.0.7 - seroval: 1.6.7 + seroval: 1.6.8 source-map: 0.7.6 srvx: 0.11.22 tinyglobby: 0.2.17 ufo: 1.6.4 - vitefu: 1.1.3(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)) + vitefu: 1.1.3(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) xmlbuilder2: 4.0.3 zod: 4.6.5 optionalDependencies: - '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) - vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + '@rsbuild/core': 2.2.11(@module-federation/runtime-tools@2.9.2) + vite: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -2430,42 +2755,42 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/start-server-core@1.169.37': + '@tanstack/start-server-core@1.169.39': dependencies: '@tanstack/history': 1.162.4 - '@tanstack/router-core': 1.171.32 - '@tanstack/start-client-core': 1.170.32 - '@tanstack/start-storage-context': 1.167.34 + '@tanstack/router-core': 1.171.34 + '@tanstack/start-client-core': 1.170.34 + '@tanstack/start-storage-context': 1.167.36 fetchdts: 0.1.7 h3-v2: h3@2.0.1-rc.20 - seroval: 1.6.7 + seroval: 1.6.8 transitivePeerDependencies: - crossws - '@tanstack/start-storage-context@1.167.34': + '@tanstack/start-storage-context@1.167.36': dependencies: - '@tanstack/router-core': 1.171.32 + '@tanstack/router-core': 1.171.34 - '@tanstack/store@0.11.1': {} + '@tanstack/store@0.11.2': {} '@tanstack/virtual-file-routes@1.162.0': {} - '@turbo/darwin-64@2.10.13': + '@turbo/darwin-64@2.11.7': optional: true - '@turbo/darwin-arm64@2.10.13': + '@turbo/darwin-arm64@2.11.7': optional: true - '@turbo/linux-64@2.10.13': + '@turbo/linux-64@2.11.7': optional: true - '@turbo/linux-arm64@2.10.13': + '@turbo/linux-arm64@2.11.7': optional: true - '@turbo/windows-64@2.10.13': + '@turbo/windows-64@2.11.7': optional: true - '@turbo/windows-arm64@2.10.13': + '@turbo/windows-arm64@2.11.7': optional: true '@tybys/wasm-util@0.10.4': @@ -2475,9 +2800,9 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@24.10.1': + '@types/node@26.6.4': dependencies: - undici-types: 7.16.0 + undici-types: 8.9.0 '@types/react-dom@19.3.0(@types/react@19.3.0)': dependencies: @@ -2487,12 +2812,72 @@ snapshots: dependencies: csstype: 3.2.3 - '@vitejs/plugin-react@6.1.1(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1))': + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@vitejs/plugin-react@6.1.2(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + vite: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) - adm-zip@0.6.0: {} + adm-zip@0.6.1: {} ajv-formats@2.1.1: dependencies: @@ -2517,25 +2902,25 @@ snapshots: babel-dead-code-elimination@1.0.12: dependencies: '@babel/core': 7.29.7 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.9 '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - baseline-browser-mapping@2.11.24: {} + baseline-browser-mapping@2.11.27: {} - browserslist@4.29.0: + browserslist@4.29.3: dependencies: - baseline-browser-mapping: 2.11.24 - caniuse-lite: 1.0.30001810 - electron-to-chromium: 1.5.430 - node-releases: 2.0.55 - update-browserslist-db: 1.3.3(browserslist@4.29.0) + baseline-browser-mapping: 2.11.27 + caniuse-lite: 1.0.30001814 + electron-to-chromium: 1.5.444 + node-releases: 2.0.57 + update-browserslist-db: 1.3.3(browserslist@4.29.3) cac@7.0.0: {} - caniuse-lite@1.0.30001810: {} + caniuse-lite@1.0.30001814: {} chokidar@5.0.0: dependencies: @@ -2557,7 +2942,7 @@ snapshots: diff@8.0.4: {} - electron-to-chromium@1.5.430: {} + electron-to-chromium@1.5.444: {} encoding@0.1.13: dependencies: @@ -2565,7 +2950,7 @@ snapshots: escalade@3.2.0: {} - exsolve@1.1.1: {} + exsolve@1.1.3: {} fast-deep-equal@3.1.3: {} @@ -2597,7 +2982,7 @@ snapshots: rou3: 0.8.1 srvx: 0.11.22 - human-id@4.2.1: {} + human-id@4.2.2: {} iconv-lite@0.6.3: dependencies: @@ -2607,9 +2992,9 @@ snapshots: isbot@5.2.2: {} - isomorphic-ws@5.0.0(ws@8.21.0): + isomorphic-ws@5.0.0(ws@8.21.3): dependencies: - ws: 8.21.0 + ws: 8.21.3 jiti@2.4.2: {} @@ -2634,7 +3019,7 @@ snapshots: launch-editor@2.14.1: dependencies: picocolors: 1.1.1 - shell-quote: 1.10.0 + shell-quote: 1.12.0 lightningcss-android-arm64@1.33.0: optional: true @@ -2695,7 +3080,7 @@ snapshots: ms@2.1.3: {} - nanoid@3.3.19: {} + nanoid@3.3.20: {} node-fetch@2.7.0(encoding@0.1.13): dependencies: @@ -2703,9 +3088,9 @@ snapshots: optionalDependencies: encoding: 0.1.13 - node-releases@2.0.55: {} + node-releases@2.0.57: {} - package-manager-detector@1.8.0: {} + package-manager-detector@1.9.0: {} pathe@2.0.3: {} @@ -2719,20 +3104,20 @@ snapshots: dependencies: playwright-core: 1.63.0 - postcss@8.5.28: + postcss@8.5.29: dependencies: - nanoid: 3.3.19 + nanoid: 3.3.20 picocolors: 1.1.1 - source-map-js: 1.2.1 + source-map-js: 1.2.2 - prettier@3.9.7: {} + prettier@3.9.9: {} react-dom@19.3.0(react@19.3.0): dependencies: react: 19.3.0 scheduler: 0.28.0 - react-refresh@0.18.0: {} + react-refresh@0.19.0: {} react@19.3.0: {} @@ -2740,35 +3125,35 @@ snapshots: require-from-string@2.0.2: {} - rolldown@1.2.9: + rolldown@1.2.12: dependencies: - '@oxc-project/types': 0.150.0 + '@oxc-project/types': 0.152.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.9 - '@rolldown/binding-android-arm64': 1.2.9 - '@rolldown/binding-darwin-arm64': 1.2.9 - '@rolldown/binding-darwin-x64': 1.2.9 - '@rolldown/binding-freebsd-x64': 1.2.9 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.9 - '@rolldown/binding-linux-arm64-gnu': 1.2.9 - '@rolldown/binding-linux-arm64-musl': 1.2.9 - '@rolldown/binding-linux-ppc64-gnu': 1.2.9 - '@rolldown/binding-linux-s390x-gnu': 1.2.9 - '@rolldown/binding-linux-x64-gnu': 1.2.9 - '@rolldown/binding-linux-x64-musl': 1.2.9 - '@rolldown/binding-openharmony-arm64': 1.2.9 - '@rolldown/binding-win32-arm64-msvc': 1.2.9 - '@rolldown/binding-win32-x64-msvc': 1.2.9 + '@rolldown/binding-android-arm-eabi': 1.2.12 + '@rolldown/binding-android-arm64': 1.2.12 + '@rolldown/binding-darwin-arm64': 1.2.12 + '@rolldown/binding-darwin-x64': 1.2.12 + '@rolldown/binding-freebsd-x64': 1.2.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.12 + '@rolldown/binding-linux-arm64-gnu': 1.2.12 + '@rolldown/binding-linux-arm64-musl': 1.2.12 + '@rolldown/binding-linux-ppc64-gnu': 1.2.12 + '@rolldown/binding-linux-s390x-gnu': 1.2.12 + '@rolldown/binding-linux-x64-gnu': 1.2.12 + '@rolldown/binding-linux-x64-musl': 1.2.12 + '@rolldown/binding-openharmony-arm64': 1.2.12 + '@rolldown/binding-win32-arm64-msvc': 1.2.12 + '@rolldown/binding-win32-x64-msvc': 1.2.12 rou3@0.8.1: {} - rsbuild-plugin-dts@1.0.1(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(typescript@6.0.3): + rsbuild-plugin-dts@1.0.3(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(typescript@7.0.2): dependencies: '@ast-grep/napi': 0.45.3 - '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) + '@rsbuild/core': 2.2.11(@module-federation/runtime-tools@2.9.2) optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 safer-buffer@2.1.2: {} @@ -2785,17 +3170,17 @@ snapshots: semver@7.8.5: {} - seroval-plugins@1.6.7(seroval@1.6.7): + seroval-plugins@1.6.8(seroval@1.6.8): dependencies: - seroval: 1.6.7 + seroval: 1.6.8 - seroval@1.6.7: {} + seroval@1.6.8: {} - shell-quote@1.10.0: {} + shell-quote@1.12.0: {} sisteransi@1.0.5: {} - source-map-js@1.2.1: {} + source-map-js@1.2.2: {} source-map@0.7.6: {} @@ -2814,37 +3199,58 @@ snapshots: tslib@2.8.1: {} - turbo@2.10.13: + turbo@2.11.7: optionalDependencies: - '@turbo/darwin-64': 2.10.13 - '@turbo/darwin-arm64': 2.10.13 - '@turbo/linux-64': 2.10.13 - '@turbo/linux-arm64': 2.10.13 - '@turbo/windows-64': 2.10.13 - '@turbo/windows-arm64': 2.10.13 - - typescript@6.0.3: {} + '@turbo/darwin-64': 2.11.7 + '@turbo/darwin-arm64': 2.11.7 + '@turbo/linux-64': 2.11.7 + '@turbo/linux-arm64': 2.11.7 + '@turbo/windows-64': 2.11.7 + '@turbo/windows-arm64': 2.11.7 + + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 ufo@1.6.4: {} - undici-types@7.16.0: {} + undici-types@8.9.0: {} - undici@7.29.0: {} + undici@7.29.1: {} - unplugin@3.4.0(@rsbuild/core@2.2.8(@module-federation/runtime-tools@2.9.0))(@rspack/core@2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23))(rolldown@1.2.9)(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)): + unplugin@3.4.0(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 optionalDependencies: - '@rsbuild/core': 2.2.8(@module-federation/runtime-tools@2.9.0) - '@rspack/core': 2.2.6(@module-federation/runtime-tools@2.9.0)(@swc/helpers@0.5.23) - rolldown: 1.2.9 - vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + '@rsbuild/core': 2.2.11(@module-federation/runtime-tools@2.9.2) + '@rspack/core': 2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23) + rolldown: 1.2.12 + vite: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) - update-browserslist-db@1.3.3(browserslist@4.29.0): + update-browserslist-db@1.3.3(browserslist@4.29.3): dependencies: - browserslist: 4.29.0 + browserslist: 4.29.3 escalade: 3.2.0 picocolors: 1.1.1 @@ -2852,22 +3258,22 @@ snapshots: dependencies: react: 19.3.0 - vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1): + vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 - postcss: 8.5.28 - rolldown: 1.2.9 + postcss: 8.5.29 + rolldown: 1.2.12 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 26.6.4 fsevents: 2.3.3 jiti: 2.7.0 yaml: 2.9.1 - vitefu@1.1.3(vite@8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1)): + vitefu@1.1.3(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)): optionalDependencies: - vite: 8.3.0(@types/node@24.10.1)(jiti@2.7.0)(yaml@2.9.1) + vite: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) webidl-conversions@3.0.1: {} @@ -2878,7 +3284,7 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - ws@8.21.0: {} + ws@8.21.3: {} xmlbuilder2@4.0.3: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 286cf7f..7d115c0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,6 @@ packages: - apps/* - packages/* + +autoInstallPeers: false +preferFrozenLockfile: true diff --git a/test/example-runtime.test.mjs b/test/example-runtime.test.mjs index 8a4a5d8..e4d4616 100644 --- a/test/example-runtime.test.mjs +++ b/test/example-runtime.test.mjs @@ -211,8 +211,9 @@ test("Rsbuild remote works as a standalone TanStack Start app", async () => { } }); -// @module-federation/vite 1.22 never self-initializes a container with `exposes` in -// development, so its React share waits for a host. Production builds hydrate standalone. +// @module-federation/vite (1.22 through 1.23.2) never self-initializes a container with +// `exposes` in development, so its React shares are only wired up through a host. +// Production builds hydrate standalone. test.todo("Vite remote hydrates as a standalone app in development"); // Runs last: it stops the Rsbuild remote. From 28cbac830ca9d4eee5114b196130006a392cc7a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 6 Oct 2026 20:57:25 +0200 Subject: [PATCH 09/18] docs: add Turborepo agent guidance --- AGENTS.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3bad16e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,10 @@ + + +# This is NOT the Turborepo you know + +Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the `turbo` package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run `node -p "require.resolve('turbo/package.json')"` from a workspace that depends on `turbo`. + +Read `docs/README.md` inside that installed package first, then read the relevant pages from its `docs/` directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access. + +This block is written and re-added by `turbo` before repository-scoped commands when an AI agent is detected. In the Turborepo source repository, its template is defined in `crates/turborepo-cli/src/cli/agent_guidance.rs`. Removing the managed block while updates are enabled means a later qualifying invocation will add it again. Set `"agentGuidance": false` in the root `turbo.json` or `turbo.jsonc` to opt out; this does not remove an existing block. Keep the block committed with your work to avoid an uncommitted change on the next agent invocation. + From 0e0981b11bf877c93159adb37a421c53ac4d1e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 6 Oct 2026 21:33:45 +0200 Subject: [PATCH 10/18] feat(rsbuild): federate server rendering between Rsbuild apps With server: true, a remote builds its Node container in a dedicated environment, emitted to dist/client/ssr/ with only the exposed modules, and advertises it in the browser manifest as ssrRemoteEntry. A host loads remotes on the server from an async-node CommonJS build that keeps dist/server/index.js as its entry. - default experiments.asyncStartup and role-aware React sharing (eager for hosts, lazy for remotes and containers) - give remotes publicPath "auto": TanStack Start derived "/" from server.base, so production remotes loaded chunks from the host's origin - write host server output to disk in development, where async-node chunks are read from disk while the entry runs from memory - add @module-federation/tanstack/node-entry-loader, which loads CommonJS remote entries without import(), unavailable in Rsbuild's dev runner - add rsbuild-ssr-remote and rsbuild-ssr-host fixtures --- apps/rsbuild-ssr-host/package.json | 30 +++ apps/rsbuild-ssr-host/rsbuild.config.ts | 26 ++ apps/rsbuild-ssr-host/src/assets.d.ts | 4 + .../src/components/RemoteBoundary.tsx | 28 +++ apps/rsbuild-ssr-host/src/remote.d.ts | 6 + apps/rsbuild-ssr-host/src/routeTree.gen.ts | 68 +++++ apps/rsbuild-ssr-host/src/router.tsx | 16 ++ apps/rsbuild-ssr-host/src/routes/__root.tsx | 36 +++ apps/rsbuild-ssr-host/src/routes/index.tsx | 27 ++ apps/rsbuild-ssr-host/src/styles.css | 32 +++ apps/rsbuild-ssr-host/tsconfig.json | 20 ++ apps/rsbuild-ssr-remote/package.json | 30 +++ apps/rsbuild-ssr-remote/rsbuild.config.ts | 28 +++ apps/rsbuild-ssr-remote/src/assets.d.ts | 6 + .../src/components/StatusCard.css | 23 ++ .../src/components/StatusCard.tsx | 25 ++ apps/rsbuild-ssr-remote/src/routeTree.gen.ts | 68 +++++ apps/rsbuild-ssr-remote/src/router.tsx | 12 + apps/rsbuild-ssr-remote/src/routes/__root.tsx | 36 +++ apps/rsbuild-ssr-remote/src/routes/index.tsx | 14 ++ apps/rsbuild-ssr-remote/src/styles.css | 22 ++ apps/rsbuild-ssr-remote/tsconfig.json | 20 ++ packages/tanstack/package.json | 11 + packages/tanstack/rslib.config.ts | 1 + packages/tanstack/src/node-entry-loader.ts | 65 +++++ packages/tanstack/src/rsbuild.ts | 225 ++++++++++++++--- pnpm-lock.yaml | 89 +++++++ test/release-artifacts.test.mjs | 30 ++- test/release-contracts.test.mjs | 234 ++++++++++++++---- tsconfig.json | 26 +- 30 files changed, 1164 insertions(+), 94 deletions(-) create mode 100644 apps/rsbuild-ssr-host/package.json create mode 100644 apps/rsbuild-ssr-host/rsbuild.config.ts create mode 100644 apps/rsbuild-ssr-host/src/assets.d.ts create mode 100644 apps/rsbuild-ssr-host/src/components/RemoteBoundary.tsx create mode 100644 apps/rsbuild-ssr-host/src/remote.d.ts create mode 100644 apps/rsbuild-ssr-host/src/routeTree.gen.ts create mode 100644 apps/rsbuild-ssr-host/src/router.tsx create mode 100644 apps/rsbuild-ssr-host/src/routes/__root.tsx create mode 100644 apps/rsbuild-ssr-host/src/routes/index.tsx create mode 100644 apps/rsbuild-ssr-host/src/styles.css create mode 100644 apps/rsbuild-ssr-host/tsconfig.json create mode 100644 apps/rsbuild-ssr-remote/package.json create mode 100644 apps/rsbuild-ssr-remote/rsbuild.config.ts create mode 100644 apps/rsbuild-ssr-remote/src/assets.d.ts create mode 100644 apps/rsbuild-ssr-remote/src/components/StatusCard.css create mode 100644 apps/rsbuild-ssr-remote/src/components/StatusCard.tsx create mode 100644 apps/rsbuild-ssr-remote/src/routeTree.gen.ts create mode 100644 apps/rsbuild-ssr-remote/src/router.tsx create mode 100644 apps/rsbuild-ssr-remote/src/routes/__root.tsx create mode 100644 apps/rsbuild-ssr-remote/src/routes/index.tsx create mode 100644 apps/rsbuild-ssr-remote/src/styles.css create mode 100644 apps/rsbuild-ssr-remote/tsconfig.json create mode 100644 packages/tanstack/src/node-entry-loader.ts diff --git a/apps/rsbuild-ssr-host/package.json b/apps/rsbuild-ssr-host/package.json new file mode 100644 index 0000000..8d97ca8 --- /dev/null +++ b/apps/rsbuild-ssr-host/package.json @@ -0,0 +1,30 @@ +{ + "name": "tanstack-start-rsbuild-ssr-host", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "build": "rsbuild build", + "dev": "rsbuild dev --port 3005 --strict-port", + "start": "rsbuild dev --host 127.0.0.1 --port 3005 --strict-port", + "preview": "rsbuild preview --host 127.0.0.1 --port 3005", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@module-federation/runtime": "2.9.2", + "@tanstack/react-router": "1.170.41", + "@tanstack/react-start": "1.168.60", + "react": "19.3.0", + "react-dom": "19.3.0" + }, + "devDependencies": { + "@module-federation/rsbuild-plugin": "2.9.2", + "@module-federation/tanstack": "workspace:*", + "@rsbuild/core": "2.2.11", + "@rsbuild/plugin-react": "2.1.1", + "@types/node": "26.6.4", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "typescript": "7.0.2" + } +} diff --git a/apps/rsbuild-ssr-host/rsbuild.config.ts b/apps/rsbuild-ssr-host/rsbuild.config.ts new file mode 100644 index 0000000..0ddc71d --- /dev/null +++ b/apps/rsbuild-ssr-host/rsbuild.config.ts @@ -0,0 +1,26 @@ +import { defineConfig } from "@rsbuild/core"; +import { pluginReact } from "@rsbuild/plugin-react"; +import { tanstackStart } from "@tanstack/react-start/plugin/rsbuild"; +import { tanstackStartModuleFederation } from "@module-federation/tanstack/rsbuild"; + +export default defineConfig({ + dev: { lazyCompilation: false }, + server: { host: "127.0.0.1", port: 3005 }, + plugins: [ + pluginReact(), + tanstackStart(), + ...tanstackStartModuleFederation({ + federation: { + dts: false, + name: "tanstack_rsbuild_ssr_host", + shareStrategy: "loaded-first", + remotes: { + tanstack_rsbuild_ssr_remote: + "tanstack_rsbuild_ssr_remote@http://127.0.0.1:3004/mf-manifest.json", + }, + }, + // Loads remotes on the server through the Module Federation Node runtime. + server: true, + }), + ], +}); diff --git a/apps/rsbuild-ssr-host/src/assets.d.ts b/apps/rsbuild-ssr-host/src/assets.d.ts new file mode 100644 index 0000000..596a189 --- /dev/null +++ b/apps/rsbuild-ssr-host/src/assets.d.ts @@ -0,0 +1,4 @@ +declare module "*.css?url" { + const href: string; + export default href; +} diff --git a/apps/rsbuild-ssr-host/src/components/RemoteBoundary.tsx b/apps/rsbuild-ssr-host/src/components/RemoteBoundary.tsx new file mode 100644 index 0000000..7e953b5 --- /dev/null +++ b/apps/rsbuild-ssr-host/src/components/RemoteBoundary.tsx @@ -0,0 +1,28 @@ +import { Component, Suspense, type ReactNode } from "react"; + +type Props = { children: ReactNode; fallback: string; name: string }; + +/** Keeps a failed remote from taking down the host route. */ +export class RemoteBoundary extends Component { + state = { failed: false }; + + static getDerivedStateFromError() { + return { failed: true }; + } + + componentDidCatch(error: unknown) { + console.warn(`[${this.props.name}] remote failed to render`, error); + } + + render() { + if (this.state.failed) { + return
{this.props.name} is unavailable
; + } + + return ( + {this.props.fallback}}> + {this.props.children} + + ); + } +} diff --git a/apps/rsbuild-ssr-host/src/remote.d.ts b/apps/rsbuild-ssr-host/src/remote.d.ts new file mode 100644 index 0000000..9d8ea20 --- /dev/null +++ b/apps/rsbuild-ssr-host/src/remote.d.ts @@ -0,0 +1,6 @@ +declare module "tanstack_rsbuild_ssr_remote/StatusCard" { + import type { ComponentType } from "react"; + + const StatusCard: ComponentType; + export default StatusCard; +} diff --git a/apps/rsbuild-ssr-host/src/routeTree.gen.ts b/apps/rsbuild-ssr-host/src/routeTree.gen.ts new file mode 100644 index 0000000..dceedff --- /dev/null +++ b/apps/rsbuild-ssr-host/src/routeTree.gen.ts @@ -0,0 +1,68 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' + fileRoutesByTo: FileRoutesByTo + to: '/' + id: '__root__' | '/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/apps/rsbuild-ssr-host/src/router.tsx b/apps/rsbuild-ssr-host/src/router.tsx new file mode 100644 index 0000000..6982ebb --- /dev/null +++ b/apps/rsbuild-ssr-host/src/router.tsx @@ -0,0 +1,16 @@ +import { createRouter } from "@tanstack/react-router"; +import { routeTree } from "./routeTree.gen"; + +export function getRouter() { + return createRouter({ + defaultPreload: "intent", + routeTree, + scrollRestoration: true, + }); +} + +declare module "@tanstack/react-router" { + interface Register { + router: ReturnType; + } +} diff --git a/apps/rsbuild-ssr-host/src/routes/__root.tsx b/apps/rsbuild-ssr-host/src/routes/__root.tsx new file mode 100644 index 0000000..2eb5fe8 --- /dev/null +++ b/apps/rsbuild-ssr-host/src/routes/__root.tsx @@ -0,0 +1,36 @@ +import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"; +import type { ReactNode } from "react"; +import stylesHref from "../styles.css?url"; + +export const Route = createRootRoute({ + head: () => ({ + links: [{ href: stylesHref, rel: "stylesheet" }], + meta: [ + { charSet: "utf-8" }, + { name: "viewport", content: "width=device-width, initial-scale=1" }, + ], + }), + component: Root, +}); + +function Root() { + return ( + + + + ); +} + +function Document({ children }: { children: ReactNode }) { + return ( + + + + + + {children} + + + + ); +} diff --git a/apps/rsbuild-ssr-host/src/routes/index.tsx b/apps/rsbuild-ssr-host/src/routes/index.tsx new file mode 100644 index 0000000..25d8f4a --- /dev/null +++ b/apps/rsbuild-ssr-host/src/routes/index.tsx @@ -0,0 +1,27 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { lazy } from "react"; +import { RemoteBoundary } from "../components/RemoteBoundary"; + +const RemoteStatusCard = lazy(() => import("tanstack_rsbuild_ssr_remote/StatusCard")); + +export const Route = createFileRoute("/")({ + // Loading the remote before render puts its markup in the server response. + // An unavailable remote must not fail the route: the boundary below renders a fallback. + loader: () => import("tanstack_rsbuild_ssr_remote/StatusCard").then(() => null).catch(() => null), + component: Home, +}); + +function Home() { + return ( +
+

Rsbuild SSR host

+

Server-rendered across Rsbuild apps.

+

This TanStack Start server renders the remote card before the page reaches the browser.

+
+ + + +
+
+ ); +} diff --git a/apps/rsbuild-ssr-host/src/styles.css b/apps/rsbuild-ssr-host/src/styles.css new file mode 100644 index 0000000..e379e6a --- /dev/null +++ b/apps/rsbuild-ssr-host/src/styles.css @@ -0,0 +1,32 @@ +:root { + background: #111; + color: #edf6ef; + font-family: system-ui, sans-serif; +} +body { + margin: 0; +} +.page-shell { + width: min(760px, calc(100% - 40px)); + margin: 0 auto; + padding: 80px 0; +} +.eyebrow { + color: #91f1b0; + font-family: monospace; + text-transform: uppercase; +} +h1 { + font-size: clamp(3rem, 10vw, 6rem); + margin: 12px 0; +} +.remote-loading { + border: 1px solid #4b6653; + margin-top: 36px; + padding: 30px; +} +.remote-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 20px; +} diff --git a/apps/rsbuild-ssr-host/tsconfig.json b/apps/rsbuild-ssr-host/tsconfig.json new file mode 100644 index 0000000..0416574 --- /dev/null +++ b/apps/rsbuild-ssr-host/tsconfig.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "include": ["src/**/*.ts", "src/**/*.tsx", "rsbuild.config.ts"], + "compilerOptions": { + "allowImportingTsExtensions": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/apps/rsbuild-ssr-remote/package.json b/apps/rsbuild-ssr-remote/package.json new file mode 100644 index 0000000..cb00d74 --- /dev/null +++ b/apps/rsbuild-ssr-remote/package.json @@ -0,0 +1,30 @@ +{ + "name": "tanstack-start-rsbuild-ssr-remote", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "build": "rsbuild build", + "dev": "rsbuild dev --port 3004 --strict-port", + "start": "rsbuild dev --host 127.0.0.1 --port 3004 --strict-port", + "preview": "rsbuild preview --host 127.0.0.1 --port 3004", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@module-federation/runtime": "2.9.2", + "@tanstack/react-router": "1.170.41", + "@tanstack/react-start": "1.168.60", + "react": "19.3.0", + "react-dom": "19.3.0" + }, + "devDependencies": { + "@module-federation/rsbuild-plugin": "2.9.2", + "@module-federation/tanstack": "workspace:*", + "@rsbuild/core": "2.2.11", + "@rsbuild/plugin-react": "2.1.1", + "@types/node": "26.6.4", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "typescript": "7.0.2" + } +} diff --git a/apps/rsbuild-ssr-remote/rsbuild.config.ts b/apps/rsbuild-ssr-remote/rsbuild.config.ts new file mode 100644 index 0000000..67f4701 --- /dev/null +++ b/apps/rsbuild-ssr-remote/rsbuild.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from "@rsbuild/core"; +import { pluginReact } from "@rsbuild/plugin-react"; +import { tanstackStart } from "@tanstack/react-start/plugin/rsbuild"; +import { tanstackStartModuleFederation } from "@module-federation/tanstack/rsbuild"; + +export default defineConfig({ + dev: { lazyCompilation: false }, + server: { + cors: { origin: ["http://127.0.0.1:3005"] }, + host: "127.0.0.1", + port: 3004, + }, + plugins: [ + pluginReact(), + tanstackStart(), + ...tanstackStartModuleFederation({ + federation: { + dev: { disableDynamicRemoteTypeHints: true }, + dts: false, + exposes: { "./StatusCard": "./src/components/StatusCard.tsx" }, + name: "tanstack_rsbuild_ssr_remote", + }, + // Builds an async-node server container next to the browser container, so an + // Rsbuild host can render this card on the server. + server: true, + }), + ], +}); diff --git a/apps/rsbuild-ssr-remote/src/assets.d.ts b/apps/rsbuild-ssr-remote/src/assets.d.ts new file mode 100644 index 0000000..18e9eca --- /dev/null +++ b/apps/rsbuild-ssr-remote/src/assets.d.ts @@ -0,0 +1,6 @@ +declare module "*.css"; + +declare module "*.css?url" { + const href: string; + export default href; +} diff --git a/apps/rsbuild-ssr-remote/src/components/StatusCard.css b/apps/rsbuild-ssr-remote/src/components/StatusCard.css new file mode 100644 index 0000000..f055223 --- /dev/null +++ b/apps/rsbuild-ssr-remote/src/components/StatusCard.css @@ -0,0 +1,23 @@ +.remote-card { + border: 1px solid #83502a; + background: #291c13; + color: #fff3e8; + padding: 30px; +} + +.remote-card .eyebrow { + color: #ffbc7c; + font-family: monospace; + text-transform: uppercase; +} + +.remote-card h2 { + font-size: 2rem; +} + +.remote-card button { + background: #ffbc7c; + border: 0; + cursor: pointer; + padding: 12px; +} diff --git a/apps/rsbuild-ssr-remote/src/components/StatusCard.tsx b/apps/rsbuild-ssr-remote/src/components/StatusCard.tsx new file mode 100644 index 0000000..e2810f2 --- /dev/null +++ b/apps/rsbuild-ssr-remote/src/components/StatusCard.tsx @@ -0,0 +1,25 @@ +import { useEffect, useState } from "react"; +import "./StatusCard.css"; + +export default function StatusCard() { + const [count, setCount] = useState(0); + const [hydrated, setHydrated] = useState(false); + + useEffect(() => { + setHydrated(true); + }, []); + + return ( +
+

Federated SSR from Rsbuild

+

+ {hydrated ? "Hydrated on the host" : "Rendered on the server"} +

+

Server-rendered by an Rsbuild remote.

+

The host's server loaded this card from the remote's async-node container.

+ +
+ ); +} diff --git a/apps/rsbuild-ssr-remote/src/routeTree.gen.ts b/apps/rsbuild-ssr-remote/src/routeTree.gen.ts new file mode 100644 index 0000000..dceedff --- /dev/null +++ b/apps/rsbuild-ssr-remote/src/routeTree.gen.ts @@ -0,0 +1,68 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' + fileRoutesByTo: FileRoutesByTo + to: '/' + id: '__root__' | '/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/apps/rsbuild-ssr-remote/src/router.tsx b/apps/rsbuild-ssr-remote/src/router.tsx new file mode 100644 index 0000000..673839e --- /dev/null +++ b/apps/rsbuild-ssr-remote/src/router.tsx @@ -0,0 +1,12 @@ +import { createRouter } from "@tanstack/react-router"; +import { routeTree } from "./routeTree.gen"; + +export function getRouter() { + return createRouter({ defaultPreload: "intent", routeTree, scrollRestoration: true }); +} + +declare module "@tanstack/react-router" { + interface Register { + router: ReturnType; + } +} diff --git a/apps/rsbuild-ssr-remote/src/routes/__root.tsx b/apps/rsbuild-ssr-remote/src/routes/__root.tsx new file mode 100644 index 0000000..2eb5fe8 --- /dev/null +++ b/apps/rsbuild-ssr-remote/src/routes/__root.tsx @@ -0,0 +1,36 @@ +import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"; +import type { ReactNode } from "react"; +import stylesHref from "../styles.css?url"; + +export const Route = createRootRoute({ + head: () => ({ + links: [{ href: stylesHref, rel: "stylesheet" }], + meta: [ + { charSet: "utf-8" }, + { name: "viewport", content: "width=device-width, initial-scale=1" }, + ], + }), + component: Root, +}); + +function Root() { + return ( + + + + ); +} + +function Document({ children }: { children: ReactNode }) { + return ( + + + + + + {children} + + + + ); +} diff --git a/apps/rsbuild-ssr-remote/src/routes/index.tsx b/apps/rsbuild-ssr-remote/src/routes/index.tsx new file mode 100644 index 0000000..badc535 --- /dev/null +++ b/apps/rsbuild-ssr-remote/src/routes/index.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from "@tanstack/react-router"; +import StatusCard from "../components/StatusCard"; + +export const Route = createFileRoute("/")({ component: Home }); + +function Home() { + return ( +
+

Rsbuild SSR remote

+

Rspack-built SSR remote.

+ +
+ ); +} diff --git a/apps/rsbuild-ssr-remote/src/styles.css b/apps/rsbuild-ssr-remote/src/styles.css new file mode 100644 index 0000000..fbab7d3 --- /dev/null +++ b/apps/rsbuild-ssr-remote/src/styles.css @@ -0,0 +1,22 @@ +:root { + background: #16110d; + color: #fff3e8; + font-family: system-ui, sans-serif; +} +body { + margin: 0; +} +.page-shell { + width: min(760px, calc(100% - 40px)); + margin: 0 auto; + padding: 80px 0; +} +.eyebrow { + color: #ffbc7c; + font-family: monospace; + text-transform: uppercase; +} +h1 { + font-size: clamp(3rem, 10vw, 6rem); + margin: 12px 0 48px; +} diff --git a/apps/rsbuild-ssr-remote/tsconfig.json b/apps/rsbuild-ssr-remote/tsconfig.json new file mode 100644 index 0000000..0416574 --- /dev/null +++ b/apps/rsbuild-ssr-remote/tsconfig.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "include": ["src/**/*.ts", "src/**/*.tsx", "rsbuild.config.ts"], + "compilerOptions": { + "allowImportingTsExtensions": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/packages/tanstack/package.json b/packages/tanstack/package.json index c682501..2f5afb9 100644 --- a/packages/tanstack/package.json +++ b/packages/tanstack/package.json @@ -39,6 +39,16 @@ "default": "./dist/rsbuild.cjs" } }, + "./node-entry-loader": { + "import": { + "types": "./dist/node-entry-loader.d.ts", + "default": "./dist/node-entry-loader.js" + }, + "require": { + "types": "./dist/node-entry-loader.d.cts", + "default": "./dist/node-entry-loader.cjs" + } + }, "./package.json": "./package.json" }, "files": [ @@ -103,6 +113,7 @@ }, "devDependencies": { "@module-federation/rsbuild-plugin": "2.9.2", + "@module-federation/runtime": "2.9.2", "@module-federation/vite": "1.23.2", "@rsbuild/core": "2.2.11", "@rslib/core": "1.0.3", diff --git a/packages/tanstack/rslib.config.ts b/packages/tanstack/rslib.config.ts index 08b6857..8a27916 100644 --- a/packages/tanstack/rslib.config.ts +++ b/packages/tanstack/rslib.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from "@rslib/core"; const entry = { index: "./src/index.ts", + "node-entry-loader": "./src/node-entry-loader.ts", rsbuild: "./src/rsbuild.ts", shared: "./src/shared.ts", vite: "./src/vite.ts", diff --git a/packages/tanstack/src/node-entry-loader.ts b/packages/tanstack/src/node-entry-loader.ts new file mode 100644 index 0000000..b8d3e48 --- /dev/null +++ b/packages/tanstack/src/node-entry-loader.ts @@ -0,0 +1,65 @@ +import type { ModuleFederationRuntimePlugin } from "@module-federation/runtime"; + +declare const __non_webpack_require__: NodeJS.Require; + +type RemoteEntryExports = NonNullable< + Parameters>[0]["remoteEntryExports"] +>; + +/** + * Loads CommonJS remote entries over HTTP for a host's server bundle. + * + * Module Federation's default Node loader reaches `vm` and `path` through `import()`. + * Rsbuild's development runner executes server bundles in a `vm` function without a + * dynamic-import callback, so that `import()` throws. Node built-ins are loaded with + * `require` here instead, matching how the federation chunk loader already works. + */ +export default function nodeEntryLoaderPlugin(): ModuleFederationRuntimePlugin { + return { + name: "tanstack-start-node-entry-loader", + async loadEntry({ remoteInfo }) { + const { entry, entryGlobalName, type } = remoteInfo; + if (type !== "commonjs-module" || !/^https?:\/\//.test(entry)) return undefined; + + const response = await fetch(entry); + if (!response.ok) { + throw new Error(`Failed to fetch remote entry ${entry}: HTTP ${response.status}`); + } + const source = await response.text(); + + const nodeRequire = __non_webpack_require__; + const vm: typeof import("node:vm") = nodeRequire("node:vm"); + const { createRequire }: typeof import("node:module") = nodeRequire("node:module"); + const path: typeof import("node:path") = nodeRequire("node:path"); + + const { pathname } = new URL(entry); + const container = { exports: {} as Record }; + const run = new vm.Script( + `(function (exports, module, require, __dirname, __filename) {${source}\n})`, + { + filename: entry, + importModuleDynamically: vm.constants?.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }, + ).runInThisContext(); + run( + container.exports, + container, + createRequire(path.join(process.cwd(), "__mf_require_base__.js")), + path.posix.dirname(pathname), + path.posix.basename(pathname), + ); + + const exports = container.exports; + const remoteEntry = (entryGlobalName && exports[entryGlobalName]) || exports; + if (!isRemoteEntry(remoteEntry)) { + throw new Error(`Remote entry ${entry} did not export a federation container.`); + } + return remoteEntry; + }, + }; +} + +function isRemoteEntry(value: unknown): value is RemoteEntryExports { + const candidate = value as Partial | undefined; + return typeof candidate?.get === "function" && typeof candidate.init === "function"; +} diff --git a/packages/tanstack/src/rsbuild.ts b/packages/tanstack/src/rsbuild.ts index a17c4e6..6395deb 100644 --- a/packages/tanstack/src/rsbuild.ts +++ b/packages/tanstack/src/rsbuild.ts @@ -1,9 +1,10 @@ +import { join } from "node:path"; import { pluginModuleFederation, type ModuleFederationOptions, } from "@module-federation/rsbuild-plugin"; import type { RsbuildPlugin } from "@rsbuild/core"; -import { eagerShared, resolveShared } from "./shared"; +import { defaultShared, eagerShared, resolveShared } from "./shared"; type ModuleFederationOverride = Partial; @@ -26,12 +27,24 @@ export type TanStackStartRsbuildModuleFederationOptions = { client?: ClientFederationOptions; federation: ModuleFederationOptions; /** - * Experimental SSR federation. Pass `true` or an options object to add an async-node - * CommonJS server container. Defaults to `false`: browser-only federation. + * Server-side federation. Pass `true` or an options object to enable it. Defaults to + * `false`: browser-only federation. + * + * - With `exposes`, the build adds a Node container in `/ssr/` and + * advertises it in the browser manifest as `ssrRemoteEntry`. + * - With `remotes`, TanStack Start's server environment loads remotes through the + * Module Federation Node runtime, as async-node CommonJS (`dist/server/index.cjs`). */ server?: ServerFederationOptions | true; }; +/** Directory, inside the browser output, that holds the remote's Node container. */ +export const SERVER_CONTAINER_DIR = "ssr"; + +const SERVER_CONTAINER_ENVIRONMENT = "mf-server"; +const NODE_ENTRY_LOADER = "@module-federation/tanstack/node-entry-loader"; +const SERVER_CONTAINER_FILENAME = "remoteEntry.js"; + /** Creates browser and optional SSR federation adapters for TanStack Start's Rsbuild integration. */ export function tanstackStartModuleFederation({ client = {}, @@ -44,11 +57,20 @@ export function tanstackStartModuleFederation({ forceScriptOutput = true, ...clientOverrides } = client; - const clientFederationOptions = withDefaults({ ...federation, ...clientOverrides }); - const federationName = clientFederationOptions.name; - + const federationName = federation.name; if (!federationName) throw new Error("federation.name is required"); + const isHost = hasEntries(federation.remotes); + const isRemote = hasEntries(federation.exposes); + const serverEnabled = server !== false; + const exposesServerContainer = serverEnabled && isRemote; + + const clientFederationOptions = withDefaults( + { ...federation, ...clientOverrides }, + isHost ? eagerShared : defaultShared, + ); + if (exposesServerContainer) advertiseServerContainer(clientFederationOptions); + const plugins: RsbuildPlugin[] = [ pluginModuleFederation(clientFederationOptions, { environment: clientEnvironment, @@ -58,11 +80,12 @@ export function tanstackStartModuleFederation({ chunkLoadingGlobal: chunkLoadingGlobal ?? `chunk_${federationName}`, environment: clientEnvironment, forceScriptOutput, + isRemote, name: federationName, }), ]; - if (server === false) return plugins; + if (!serverEnabled) return plugins; const { chunkFilename, @@ -71,38 +94,145 @@ export function tanstackStartModuleFederation({ forceCommonJsOutput = true, ...serverOverrides } = server === true ? {} : server; - const serverFederationOptions = withDefaults( - { ...federation, ...serverOverrides }, - forceCommonJsOutput ? "serverRemoteEntry.cjs" : "serverRemoteEntry.js", - ); - return [ - ...plugins, - pluginModuleFederation(serverFederationOptions, { - environment: serverEnvironment, - target: "node", - }), - serverCompatibilityPlugin({ - chunkFilename: - chunkFilename ?? (forceCommonJsOutput ? `[id].${federationName}.cjs` : undefined), - entryFilename: entryFilename ?? (forceCommonJsOutput ? "[name].cjs" : undefined), - environment: serverEnvironment, - forceCommonJsOutput, - }), - ]; + if (exposesServerContainer) { + const { remotes: _remotes, ...containerOptions } = { ...federation, ...serverOverrides }; + plugins.push( + serverContainerEnvironmentPlugin({ clientEnvironment }), + pluginModuleFederation( + withDefaults({ ...containerOptions, filename: SERVER_CONTAINER_FILENAME }, defaultShared), + { environment: SERVER_CONTAINER_ENVIRONMENT, target: "node" }, + ), + ); + } + + if (isHost) { + // The host's own server bundle consumes remotes. Its container, if any, is built by the + // dedicated environment above, so TanStack's server output never contains one. + const { exposes: _exposes, ...hostOptions } = { ...federation, ...serverOverrides }; + plugins.push( + pluginModuleFederation( + { + ...withDefaults(hostOptions, eagerShared), + manifest: false, + runtimePlugins: [...(hostOptions.runtimePlugins ?? []), NODE_ENTRY_LOADER], + }, + { environment: serverEnvironment, target: "node" }, + ), + serverCompatibilityPlugin({ + chunkFilename: + chunkFilename ?? (forceCommonJsOutput ? `[id].${federationName}.cjs` : undefined), + entryFilename: entryFilename ?? (forceCommonJsOutput ? "[name].cjs" : undefined), + environment: serverEnvironment, + forceCommonJsOutput, + }), + ); + } + + return plugins; } export const pluginTanStackStartModuleFederation = tanstackStartModuleFederation; +function isAbsoluteUrl(value: unknown): boolean { + return typeof value === "string" && /^[a-z][a-z\d+.-]*:\/\//i.test(value); +} + +function hasEntries(value: unknown): boolean { + if (Array.isArray(value)) return value.length > 0; + return Boolean(value) && typeof value === "object" && Object.keys(value as object).length > 0; +} + function withDefaults( options: ModuleFederationOptions, - filename = "remoteEntry.js", + sharedDefaults: Record, ): ModuleFederationOptions { return { ...options, - filename: options.filename ?? filename, + filename: options.filename ?? "remoteEntry.js", manifest: options.manifest ?? true, - shared: resolveShared(options.shared, eagerShared) as ModuleFederationOptions["shared"], + shared: resolveShared(options.shared, sharedDefaults) as ModuleFederationOptions["shared"], + // TanStack Start imports React synchronously from its entries. Async startup waits for + // shared modules before running them, so lazy shares cannot fail with RUNTIME-006. + experiments: { asyncStartup: true, ...options.experiments }, + }; +} + +/** + * Adds the Node container to the browser manifest, as Module Federation's dual-target + * build does. A Node consumer reads `ssrRemoteEntry` and fetches the container from + * `ssr/`. + */ +function advertiseServerContainer(options: ModuleFederationOptions) { + const manifest = options.manifest === true ? {} : options.manifest; + if (!manifest) return; + + const userAdditionalData = manifest.additionalData; + options.manifest = { + ...manifest, + async additionalData(context) { + const stats = (await userAdditionalData?.(context)) ?? context.stats; + Object.assign(stats.metaData, { + ssrRemoteEntry: { + name: SERVER_CONTAINER_FILENAME, + path: SERVER_CONTAINER_DIR, + type: "commonjs-module", + }, + }); + return stats; + }, + }; +} + +/** Builds the remote's Node container in its own environment, inside the browser output. */ +function serverContainerEnvironmentPlugin({ + clientEnvironment, +}: { + clientEnvironment: string; +}): RsbuildPlugin { + return { + name: "tanstack-start-federation-server-container", + setup(api) { + // Module Federation validates its node environment in a `pre` hook, before TanStack + // Start defines its own environments, so this one is created bare and placed next to + // the browser output once the bundler configs exist. + api.modifyRsbuildConfig({ + order: "pre", + handler(config) { + config.environments ??= {}; + config.environments[SERVER_CONTAINER_ENVIRONMENT] = { + // Module Federation adds the container entry; the placeholder keeps the + // environment from bundling the application. + source: { entry: { [SERVER_CONTAINER_ENVIRONMENT]: "data:text/javascript," } }, + output: { emitAssets: true, target: "node" }, + }; + }, + }); + + api.onBeforeCreateCompiler(({ bundlerConfigs }) => { + const clientConfig = bundlerConfigs?.find(({ name }) => name === clientEnvironment); + const containerConfig = bundlerConfigs?.find( + ({ name }) => name === SERVER_CONTAINER_ENVIRONMENT, + ); + if (!containerConfig) return; + if (!clientConfig?.output?.path) { + throw new Error( + `TanStack Start's "${clientEnvironment}" environment was not found. ` + + "Register tanstackStart() before tanstackStartModuleFederation().", + ); + } + + // The container ships with the browser assets and loads its chunks over HTTP from + // the same origin. + const publicPath = clientConfig.output.publicPath; + containerConfig.output ||= {}; + containerConfig.output.path = join(clientConfig.output.path, SERVER_CONTAINER_DIR); + containerConfig.output.publicPath = + typeof publicPath === "string" && publicPath !== "auto" + ? `${publicPath}${SERVER_CONTAINER_DIR}/` + : "auto"; + }); + }, }; } @@ -110,11 +240,13 @@ function clientCompatibilityPlugin({ chunkLoadingGlobal, environment, forceScriptOutput, + isRemote, name, }: { chunkLoadingGlobal: string; environment: string; forceScriptOutput: boolean; + isRemote: boolean; name: string; }): RsbuildPlugin { return { @@ -130,6 +262,14 @@ function clientCompatibilityPlugin({ config.output.chunkLoadingGlobal = chunkLoadingGlobal; config.output.uniqueName ??= name; + // TanStack Start derives the public path from `server.base`, which Rsbuild always + // sets, so a remote ends up with a host-relative "/" in production. Hosts on other + // origins would then request its chunks from themselves. "auto" resolves them from + // the remote's own script and manifest URLs; an absolute URL (such as a CDN) is kept. + if (isRemote && !isAbsoluteUrl(config.output.publicPath)) { + config.output.publicPath = "auto"; + } + if (forceScriptOutput) { config.output.module = false; const experiments = (config.experiments ??= {}) as { outputModule?: boolean }; @@ -176,6 +316,35 @@ function serverCompatibilityPlugin({ } } }); + + if (!forceCommonJsOutput) return; + + // async-node chunk loading reads chunks from disk, while the dev server runs the entry + // from memory. Writing the server output keeps both from the same compilation. + api.modifyEnvironmentConfig((config, { name }) => { + if (name !== environment) return; + config.dev.writeToDisk = true; + }); + + // TanStack Start's preview server, and deployments built against it, import + // `dist/server/index.js`. Keep that entry as an ES module that re-exports the CommonJS + // bundle. With async startup, the bundle exports a promise that settles once + // federation has initialized. + api.processAssets( + { stage: "additional", environments: [environment] }, + ({ assets, compilation, sources }) => { + if (!assets["index.cjs"] || assets["index.js"]) return; + compilation.emitAsset("index.js", new sources.RawSource(SERVER_ENTRY_SHIM)); + }, + ); }, }; } + +const SERVER_ENTRY_SHIM = `import { createRequire } from "node:module"; + +const server = await createRequire(import.meta.url)("./index.cjs"); + +export const createServerEntry = server.createServerEntry; +export default server.default; +`; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bba98a8..8a3eb42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -293,6 +293,92 @@ importers: specifier: 7.0.2 version: 7.0.2 + apps/rsbuild-ssr-host: + dependencies: + '@module-federation/runtime': + specifier: 2.9.2 + version: 2.9.2 + '@tanstack/react-router': + specifier: 1.170.41 + version: 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start': + specifier: 1.168.60 + version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + react: + specifier: 19.3.0 + version: 19.3.0 + react-dom: + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) + devDependencies: + '@module-federation/rsbuild-plugin': + specifier: 2.9.2 + version: 2.9.2(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2) + '@module-federation/tanstack': + specifier: workspace:* + version: link:../../packages/tanstack + '@rsbuild/core': + specifier: 2.2.11 + version: 2.2.11(@module-federation/runtime-tools@2.9.2) + '@rsbuild/plugin-react': + specifier: 2.1.1 + version: 2.1.1(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23)) + '@types/node': + specifier: 26.6.4 + version: 26.6.4 + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) + typescript: + specifier: 7.0.2 + version: 7.0.2 + + apps/rsbuild-ssr-remote: + dependencies: + '@module-federation/runtime': + specifier: 2.9.2 + version: 2.9.2 + '@tanstack/react-router': + specifier: 1.170.41 + version: 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start': + specifier: 1.168.60 + version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + react: + specifier: 19.3.0 + version: 19.3.0 + react-dom: + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) + devDependencies: + '@module-federation/rsbuild-plugin': + specifier: 2.9.2 + version: 2.9.2(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2) + '@module-federation/tanstack': + specifier: workspace:* + version: link:../../packages/tanstack + '@rsbuild/core': + specifier: 2.2.11 + version: 2.2.11(@module-federation/runtime-tools@2.9.2) + '@rsbuild/plugin-react': + specifier: 2.1.1 + version: 2.1.1(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23)) + '@types/node': + specifier: 26.6.4 + version: 26.6.4 + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) + typescript: + specifier: 7.0.2 + version: 7.0.2 + apps/vite-host: dependencies: '@module-federation/runtime': @@ -378,6 +464,9 @@ importers: '@module-federation/rsbuild-plugin': specifier: 2.9.2 version: 2.9.2(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(typescript@7.0.2) + '@module-federation/runtime': + specifier: 2.9.2 + version: 2.9.2 '@module-federation/vite': specifier: 1.23.2 version: 1.23.2(typescript@7.0.2)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) diff --git a/test/release-artifacts.test.mjs b/test/release-artifacts.test.mjs index f87a4b4..f6e18e3 100644 --- a/test/release-artifacts.test.mjs +++ b/test/release-artifacts.test.mjs @@ -13,6 +13,8 @@ test("published declarations reference included source maps", () => { for (const declarationName of [ "index.d.cts", "index.d.ts", + "node-entry-loader.d.cts", + "node-entry-loader.d.ts", "rsbuild.d.cts", "rsbuild.d.ts", "vite.d.cts", @@ -39,19 +41,21 @@ test("npm package contains only runtime, metadata, and documentation files", () const [manifest] = JSON.parse(packed.stdout); const files = manifest.files.map(({ path }) => path).sort(); - const runtimeFiles = ["index", "rsbuild", "shared", "vite"].flatMap((entry) => { - const sourceMaps = entry === "index" ? [] : [`dist/${entry}.js.map`]; - return [ - `dist/${entry}.cjs`, - `dist/${entry}.cjs.map`, - `dist/${entry}.d.cts`, - `dist/${entry}.d.cts.map`, - `dist/${entry}.d.ts`, - `dist/${entry}.d.ts.map`, - `dist/${entry}.js`, - ...sourceMaps, - ]; - }); + const runtimeFiles = ["index", "node-entry-loader", "rsbuild", "shared", "vite"].flatMap( + (entry) => { + const sourceMaps = entry === "index" ? [] : [`dist/${entry}.js.map`]; + return [ + `dist/${entry}.cjs`, + `dist/${entry}.cjs.map`, + `dist/${entry}.d.cts`, + `dist/${entry}.d.cts.map`, + `dist/${entry}.d.ts`, + `dist/${entry}.d.ts.map`, + `dist/${entry}.js`, + ...sourceMaps, + ]; + }, + ); assert.deepEqual( files, diff --git a/test/release-contracts.test.mjs b/test/release-contracts.test.mjs index 9f002f0..8039d86 100644 --- a/test/release-contracts.test.mjs +++ b/test/release-contracts.test.mjs @@ -119,7 +119,7 @@ test("leaves the build target to each TanStack environment", () => { assert.equal(options.target, undefined); }); -test("Rsbuild defaults make synchronous React shares eager without mutating overrides", () => { +test("Rsbuild shared defaults never mutate caller overrides", () => { const callerShared = { react: { eager: false, singleton: false } }; const before = structuredClone(callerShared); const shared = resolveShared(callerShared, eagerShared); @@ -129,46 +129,139 @@ test("Rsbuild defaults make synchronous React shares eager without mutating over assert.deepEqual(shared["react-dom"], { eager: true, singleton: true }); }); -test("Rsbuild opt-in SSR configures separate browser and async-node environments", () => { - const plugins = tanstackStartRsbuildModuleFederation({ - federation: { name: "test_remote" }, +const rsbuildRemote = { name: "test_remote", exposes: { "./Card": "./src/Card.tsx" } }; +const rsbuildHost = { + name: "test_host", + remotes: { remote: "remote@http://remote/mf-manifest.json" }, +}; + +test("Rsbuild hosts share eager React; remotes and server containers stay lazy", () => { + const [hostClient, hostServer] = federationOptions({ federation: rsbuildHost, server: true }); + const [remoteClient, remoteContainer] = federationOptions({ + federation: rsbuildRemote, server: true, }); + for (const options of [hostClient, hostServer]) { + assert.deepEqual(options.shared.react, { eager: true, singleton: true }); + assert.deepEqual(options.shared["react-dom"], { eager: true, singleton: true }); + } + for (const options of [remoteClient, remoteContainer]) { + assert.deepEqual(options.shared.react, { singleton: true }); + assert.deepEqual(options.shared["react-dom"], { singleton: true }); + } +}); + +test("Rsbuild enables async startup unless the caller disables it", () => { + const [enabled] = federationOptions({ federation: rsbuildRemote }); + assert.equal(enabled.experiments.asyncStartup, true); + + const [disabled] = federationOptions({ + federation: { ...rsbuildRemote, experiments: { asyncStartup: false } }, + }); + assert.equal(disabled.experiments.asyncStartup, false); +}); + +test("Rsbuild federation is browser-only by default", () => { + for (const federation of [rsbuildRemote, rsbuildHost]) { + assert.deepEqual( + tanstackStartRsbuildModuleFederation({ federation }).map(({ name }) => name), + ["rsbuild:module-federation-enhanced", "tanstack-start-federation-client-compat"], + ); + } +}); + +test("Rsbuild remotes resolve browser chunks from their own origin", () => { + const remoteClient = { name: "client", output: { publicPath: "/" } }; + runHooks(clientCompat(rsbuildRemote)).onBeforeCreateCompiler({ bundlerConfigs: [remoteClient] }); + assert.equal(remoteClient.output.publicPath, "auto"); + + const cdnClient = { name: "client", output: { publicPath: "https://cdn.example/remote/" } }; + runHooks(clientCompat(rsbuildRemote)).onBeforeCreateCompiler({ bundlerConfigs: [cdnClient] }); + assert.equal(cdnClient.output.publicPath, "https://cdn.example/remote/"); + + const hostClient = { name: "client", output: { publicPath: "/" } }; + runHooks(clientCompat(rsbuildHost)).onBeforeCreateCompiler({ bundlerConfigs: [hostClient] }); + assert.deepEqual(hostClient.output, { + chunkFormat: "array-push", + chunkLoading: "jsonp", + chunkLoadingGlobal: "chunk_test_host", + module: false, + publicPath: "/", + uniqueName: "test_host", + }); +}); + +test("Rsbuild SSR remotes ship a Node container with the browser assets", async () => { + const plugins = tanstackStartRsbuildModuleFederation({ federation: rsbuildRemote, server: true }); assert.deepEqual( plugins.map(({ name }) => name), [ "rsbuild:module-federation-enhanced", "tanstack-start-federation-client-compat", + "tanstack-start-federation-server-container", "rsbuild:module-federation-enhanced", - "tanstack-start-federation-ssr-compat", ], ); - const clientConfig = { name: "client", output: {} }; - const serverConfig = { - name: "ssr", - output: { chunkLoadingGlobal: "stale" }, - }; - applyCompilerHook( - plugins.find(({ name }) => name === "tanstack-start-federation-client-compat"), - [clientConfig, serverConfig], - ); - applyCompilerHook( - plugins.find(({ name }) => name === "tanstack-start-federation-ssr-compat"), - [clientConfig, serverConfig], + const hooks = runHooks( + plugins.find(({ name }) => name === "tanstack-start-federation-server-container"), ); + const rsbuildConfig = { environments: { client: {}, ssr: {} } }; + assert.equal(hooks.modifyRsbuildConfig.order, "pre"); + hooks.modifyRsbuildConfig.handler(rsbuildConfig); + assert.deepEqual(rsbuildConfig.environments["mf-server"].output, { + emitAssets: true, + target: "node", + }); - assert.deepEqual(clientConfig.output, { - chunkFormat: "array-push", - chunkLoading: "jsonp", - chunkLoadingGlobal: "chunk_test_remote", - module: false, - uniqueName: "test_remote", + for (const [publicPath, expected] of [ + ["http://127.0.0.1:3004/", "http://127.0.0.1:3004/ssr/"], + ["auto", "auto"], + ]) { + const client = { name: "client", output: { path: "/app/dist/client", publicPath } }; + const container = { name: "mf-server", output: {} }; + hooks.onBeforeCreateCompiler({ bundlerConfigs: [client, container] }); + assert.equal(container.output.path, join("/app/dist/client", "ssr")); + assert.equal(container.output.publicPath, expected); + } + + const [client, container] = federationOptions({ federation: rsbuildRemote, server: true }); + assert.equal(container.filename, "remoteEntry.js"); + assert.equal(container.remotes, undefined); + const stats = await client.manifest.additionalData({ stats: { metaData: {} } }); + assert.deepEqual(stats.metaData.ssrRemoteEntry, { + name: "remoteEntry.js", + path: "ssr", + type: "commonjs-module", }); +}); + +test("Rsbuild SSR hosts load remotes from an async-node CommonJS server", () => { + const plugins = tanstackStartRsbuildModuleFederation({ federation: rsbuildHost, server: true }); + assert.deepEqual( + plugins.map(({ name }) => name), + [ + "rsbuild:module-federation-enhanced", + "tanstack-start-federation-client-compat", + "rsbuild:module-federation-enhanced", + "tanstack-start-federation-ssr-compat", + ], + ); + + const [, server] = federationOptions({ federation: rsbuildHost, server: true }); + assert.equal(server.manifest, false); + assert.equal(server.exposes, undefined); + assert.ok(server.runtimePlugins.includes("@module-federation/tanstack/node-entry-loader")); + + const hooks = runHooks( + plugins.find(({ name }) => name === "tanstack-start-federation-ssr-compat"), + ); + const serverConfig = { name: "ssr", output: { chunkLoadingGlobal: "stale" } }; + hooks.onBeforeCreateCompiler({ bundlerConfigs: [{ name: "client", output: {} }, serverConfig] }); assert.equal(serverConfig.target, "async-node"); assert.deepEqual(serverConfig.output, { - chunkFilename: "[id].test_remote.cjs", + chunkFilename: "[id].test_host.cjs", chunkFormat: "commonjs", chunkLoading: "async-node", filename: "[name].cjs", @@ -176,41 +269,86 @@ test("Rsbuild opt-in SSR configures separate browser and async-node environments module: false, }); - assert.equal( - tanstackStartRsbuildModuleFederation({ - federation: { name: "browser_only" }, - }).length, - 2, - "SSR federation is opt-in", - ); + const environmentConfig = { dev: {} }; + hooks.modifyEnvironmentConfig(environmentConfig, { name: "ssr" }); + assert.equal(environmentConfig.dev.writeToDisk, true, "async-node chunks are read from disk"); + + const emitted = {}; + hooks.processAssets.handler({ + assets: { "index.cjs": {} }, + compilation: { emitAsset: (name, source) => (emitted[name] = source.source()) }, + sources: { + RawSource: class { + constructor(value) { + this.value = value; + } + source() { + return this.value; + } + }, + }, + }); + assert.deepEqual(hooks.processAssets.options, { stage: "additional", environments: ["ssr"] }); + assert.match(emitted["index.js"], /await createRequire\(import\.meta\.url\)\("\.\/index\.cjs"\)/); +}); - const esmPlugins = tanstackStartRsbuildModuleFederation({ - federation: { name: "esm_server" }, +test("Rsbuild ESM server output is left to TanStack Start", () => { + const plugins = tanstackStartRsbuildModuleFederation({ + federation: rsbuildHost, server: { forceCommonJsOutput: false }, }); - const esmServerConfig = { - name: "ssr", - output: { chunkFilename: "[id].js", filename: "[name].js" }, - }; - applyCompilerHook( - esmPlugins.find(({ name }) => name === "tanstack-start-federation-ssr-compat"), - [esmServerConfig], + const hooks = runHooks( + plugins.find(({ name }) => name === "tanstack-start-federation-ssr-compat"), ); - assert.deepEqual(esmServerConfig, { + const serverConfig = { name: "ssr", output: { chunkFilename: "[id].js", filename: "[name].js" } }; + hooks.onBeforeCreateCompiler({ bundlerConfigs: [serverConfig] }); + assert.deepEqual(serverConfig, { name: "ssr", output: { chunkFilename: "[id].js", filename: "[name].js" }, }); + assert.equal(hooks.processAssets, undefined); + assert.equal(hooks.modifyEnvironmentConfig, undefined); }); -function applyCompilerHook(plugin, bundlerConfigs) { - let hook; +function clientCompat(federation) { + return tanstackStartRsbuildModuleFederation({ federation }).find( + ({ name }) => name === "tanstack-start-federation-client-compat", + ); +} + +/** Returns the Module Federation options each enhanced plugin receives, in plugin order. */ +function federationOptions(options) { + return tanstackStartRsbuildModuleFederation(options) + .filter(({ name }) => name === "rsbuild:module-federation-enhanced") + .map((plugin) => { + let exposed; + plugin.setup( + new Proxy( + {}, + { + get(_, key) { + if (key === "context") return { callerName: "rsbuild" }; + if (key === "expose") return (_name, api) => (exposed = api); + if (key === "getRsbuildConfig") return () => ({}); + return () => {}; + }, + }, + ), + ); + return exposed.getOptions(); + }); +} + +/** Runs a plugin's setup against a recording API and returns the hooks it registered. */ +function runHooks(plugin) { + const hooks = {}; plugin.setup({ - onBeforeCreateCompiler(callback) { - hook = callback; - }, + modifyEnvironmentConfig: (handler) => (hooks.modifyEnvironmentConfig = handler), + modifyRsbuildConfig: (hook) => (hooks.modifyRsbuildConfig = hook), + onBeforeCreateCompiler: (handler) => (hooks.onBeforeCreateCompiler = handler), + processAssets: (options, handler) => (hooks.processAssets = { handler, options }), }); - assert.ok(hook, `${plugin.name} registers a compiler hook`); - hook({ bundlerConfigs }); + return hooks; } test("published metadata selects ESM and CommonJS builds with matching types", () => { diff --git a/tsconfig.json b/tsconfig.json index 50148eb..3c0eb91 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,10 +2,26 @@ "$schema": "https://json.schemastore.org/tsconfig", "files": [], "references": [ - { "path": "./packages/tanstack/tsconfig.json" }, - { "path": "./apps/vite-host/tsconfig.json" }, - { "path": "./apps/vite-remote/tsconfig.json" }, - { "path": "./apps/rsbuild-host/tsconfig.json" }, - { "path": "./apps/rsbuild-remote/tsconfig.json" } + { + "path": "./packages/tanstack/tsconfig.json" + }, + { + "path": "./apps/vite-host/tsconfig.json" + }, + { + "path": "./apps/vite-remote/tsconfig.json" + }, + { + "path": "./apps/rsbuild-host/tsconfig.json" + }, + { + "path": "./apps/rsbuild-remote/tsconfig.json" + }, + { + "path": "./apps/rsbuild-ssr-host/tsconfig.json" + }, + { + "path": "./apps/rsbuild-ssr-remote/tsconfig.json" + } ] } From b40f0e6d2c0bdcf4aa27257bac5085b34f5475a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 6 Oct 2026 21:33:46 +0200 Subject: [PATCH 11/18] test: run example scenarios against production builds The six apps now run twice, as development servers and through vite preview and rsbuild preview. Each run checks server-rendered remote markup for concurrent first requests, reachable manifest assets, hydration, remote interactivity, console errors, and fallback and recovery while remotes are offline. Host loaders ignore a failed remote import, so an offline remote renders the boundary fallback instead of failing the route. The development run does not stop the Vite remote: @module-federation/vite's dev SSR transport then exits the Vite host with an unhandled rejection. --- apps/rsbuild-host/package.json | 1 + apps/rsbuild-remote/package.json | 1 + apps/vite-host/package.json | 1 + apps/vite-host/src/routes/index.tsx | 3 +- apps/vite-remote/package.json | 1 + package.json | 1 + test/example-production.test.mjs | 4 + test/example-runtime.test.mjs | 245 +--------------------- test/support/examples.mjs | 310 ++++++++++++++++++++++++++++ turbo.json | 4 + 10 files changed, 334 insertions(+), 237 deletions(-) create mode 100644 test/example-production.test.mjs create mode 100644 test/support/examples.mjs diff --git a/apps/rsbuild-host/package.json b/apps/rsbuild-host/package.json index d4f7af6..b0f585b 100644 --- a/apps/rsbuild-host/package.json +++ b/apps/rsbuild-host/package.json @@ -7,6 +7,7 @@ "build": "rsbuild build", "dev": "rsbuild dev --port 3003 --strict-port", "start": "rsbuild dev --host 127.0.0.1 --port 3003 --strict-port", + "preview": "rsbuild preview --host 127.0.0.1 --port 3003", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/apps/rsbuild-remote/package.json b/apps/rsbuild-remote/package.json index f7caf14..5249402 100644 --- a/apps/rsbuild-remote/package.json +++ b/apps/rsbuild-remote/package.json @@ -7,6 +7,7 @@ "build": "rsbuild build", "dev": "rsbuild dev --port 3002 --strict-port", "start": "rsbuild dev --host 127.0.0.1 --port 3002 --strict-port", + "preview": "rsbuild preview --host 127.0.0.1 --port 3002", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/apps/vite-host/package.json b/apps/vite-host/package.json index b0fe9ad..212c91b 100644 --- a/apps/vite-host/package.json +++ b/apps/vite-host/package.json @@ -7,6 +7,7 @@ "build": "vite build", "dev": "vite dev --port 3000 --strictPort", "start": "vite --host 127.0.0.1 --port 3000 --strictPort", + "preview": "vite preview --host 127.0.0.1 --port 3000 --strictPort", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/apps/vite-host/src/routes/index.tsx b/apps/vite-host/src/routes/index.tsx index 5e7c0d5..7fecd19 100644 --- a/apps/vite-host/src/routes/index.tsx +++ b/apps/vite-host/src/routes/index.tsx @@ -6,7 +6,8 @@ const RemoteStatusCard = lazy(() => import("tanstack_vite_remote/StatusCard")); const RsbuildStatusCard = lazy(() => import("tanstack_rsbuild_remote/StatusCard")); export const Route = createFileRoute("/")({ - loader: () => import("tanstack_vite_remote/StatusCard").then(() => null), + // An unavailable remote must not fail the route: the boundary below renders a fallback. + loader: () => import("tanstack_vite_remote/StatusCard").then(() => null).catch(() => null), component: Home, }); diff --git a/apps/vite-remote/package.json b/apps/vite-remote/package.json index 6c824d4..756a067 100644 --- a/apps/vite-remote/package.json +++ b/apps/vite-remote/package.json @@ -7,6 +7,7 @@ "build": "vite build", "dev": "vite dev --port 3001 --strictPort", "start": "vite --host 127.0.0.1 --port 3001 --strictPort", + "preview": "vite preview --host 127.0.0.1 --port 3001 --strictPort", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/package.json b/package.json index a00aec4..6ad1669 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "format:check": "prettier --check .", "pack:tanstack": "pnpm --filter @module-federation/tanstack pack:dry", "start": "pnpm build:package && turbo run start", + "preview": "pnpm build && turbo run preview", "changeset": "changeset", "changeset:status": "changeset status" }, diff --git a/test/example-production.test.mjs b/test/example-production.test.mjs new file mode 100644 index 0000000..e2686d5 --- /dev/null +++ b/test/example-production.test.mjs @@ -0,0 +1,4 @@ +// Production builds served by `vite preview` and `rsbuild preview`. +import { describeExamples } from "./support/examples.mjs"; + +describeExamples("preview"); diff --git a/test/example-runtime.test.mjs b/test/example-runtime.test.mjs index e4d4616..5c2f619 100644 --- a/test/example-runtime.test.mjs +++ b/test/example-runtime.test.mjs @@ -1,239 +1,12 @@ -import assert from "node:assert/strict"; -import { spawn } from "node:child_process"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { after, before, test } from "node:test"; -import { chromium } from "playwright"; +// Development servers: Vite and Rsbuild dev servers with in-memory builds. +import { test } from "node:test"; +import { describeExamples } from "./support/examples.mjs"; -const root = join(dirname(fileURLToPath(import.meta.url)), ".."); - -const urls = { - viteHost: "http://127.0.0.1:3000/", - viteRemote: "http://127.0.0.1:3001/", - rsbuildRemote: "http://127.0.0.1:3002/", - rsbuildHost: "http://127.0.0.1:3003/", -}; - -const apps = {}; -let browser; - -function startApp(name) { - const child = spawn("pnpm", ["--filter", name, "start"], { - cwd: root, - detached: true, - env: { ...process.env, NODE_ENV: "development" }, - stdio: ["ignore", "pipe", "pipe"], - }); - child.output = ""; - - for (const stream of [child.stdout, child.stderr]) { - stream.on("data", (chunk) => { - child.output = `${child.output}${chunk}`.slice(-12_000); - }); - } - - return child; -} - -async function waitForResponse(url, child, predicate = () => true) { - const deadline = Date.now() + 30_000; - let lastError; - let lastResponse; - - while (Date.now() < deadline) { - if (child.exitCode !== null || child.signalCode !== null) { - assert.fail(`server stopped while waiting for ${url}\n${child.output}`); - } - - try { - const response = await fetch(url, { signal: AbortSignal.timeout(2_000) }); - lastResponse = { body: await response.text(), status: response.status }; - if (predicate(lastResponse)) return lastResponse; - } catch (error) { - lastError = error; - } - - await new Promise((resolve) => setTimeout(resolve, 150)); - } - - assert.fail( - [ - `timed out waiting for ${url}`, - lastResponse ? `last status: ${lastResponse.status}` : undefined, - lastError ? `last error: ${lastError.message}` : undefined, - child.output, - ] - .filter(Boolean) - .join("\n"), - ); -} - -async function stopApp(child) { - if (!child || child.exitCode !== null || child.signalCode !== null) return; - process.kill(-child.pid, "SIGTERM"); - - await Promise.race([ - new Promise((resolve) => child.once("exit", resolve)), - new Promise((resolve) => setTimeout(resolve, 3_000)), - ]); - - if (child.exitCode === null && child.signalCode === null) { - process.kill(-child.pid, "SIGKILL"); - } -} - -/** Opens a page and records uncaught errors and React warnings logged as console errors. */ -async function openPage(url) { - const page = await browser.newPage(); - const errors = []; - page.on("pageerror", (error) => errors.push(error.stack ?? error.message)); - page.on("console", (message) => { - if (message.type() === "error") errors.push(message.text()); - }); - await page.goto(url); - return { errors, page }; -} - -/** Clicks a remote card's counter and proves the remote's state updates on the host. */ -async function assertInteractive(page, cardText) { - const button = page - .locator("article", { hasText: cardText }) - .getByRole("button", { name: /Remote count/ }); - await button.waitFor({ timeout: 20_000 }); - const count = async () => Number((await button.textContent()).match(/\d+/)?.[0]); - - // A server-rendered button ignores clicks until React hydrates it. - const deadline = Date.now() + 20_000; - while ((await count()) === 0) { - assert.ok(Date.now() < deadline, `${cardText} never became interactive`); - await button.click(); - await page.waitForTimeout(100); - } - - const before = await count(); - await button.click(); - await page.waitForFunction( - ([text, expected]) => - [...document.querySelectorAll("article")] - .find((article) => article.textContent.includes(text)) - ?.querySelector("button") - ?.textContent.includes(String(expected)), - [cardText, before + 1], - { timeout: 5_000 }, - ); -} - -before(async () => { - apps.viteRemote = startApp("tanstack-start-vite-remote"); - apps.rsbuildRemote = startApp("tanstack-start-rsbuild-remote"); - await waitForResponse( - `${urls.viteRemote}remoteEntry.js`, - apps.viteRemote, - ({ status }) => status === 200, - ); - await waitForResponse( - `${urls.rsbuildRemote}mf-manifest.json`, - apps.rsbuildRemote, - ({ status }) => status === 200, - ); - - apps.viteHost = startApp("tanstack-start-vite-host"); - apps.rsbuildHost = startApp("tanstack-start-rsbuild-host"); - await waitForResponse(urls.viteHost, apps.viteHost, ({ status }) => status === 200); - await waitForResponse(urls.rsbuildHost, apps.rsbuildHost, ({ status }) => status === 200); - - browser = await chromium.launch(); -}); - -after(async () => { - await browser?.close(); - await Promise.all(Object.values(apps).map(stopApp)); -}); - -test("Vite host server-renders a component from the Vite remote", async () => { - const response = await waitForResponse( - urls.viteHost, - apps.viteHost, - ({ body, status }) => status === 200 && body.includes("Owned by the remote app"), - ); - - assert.match(response.body, /Two full-stack apps\. One React tree\./); - assert.match(response.body, /Rendered on the server/); - assert.match(response.body, /Rsbuild remote loads after hydration/); -}); - -test("Rsbuild remote serves a browser manifest", async () => { - const manifest = await waitForResponse( - `${urls.rsbuildRemote}mf-manifest.json`, - apps.rsbuildRemote, - ({ status }) => status === 200, - ); - assert.match(manifest.body, /tanstack_rsbuild_remote/); -}); - -test("Rsbuild host serves loading placeholders for both remotes", async () => { - const response = await waitForResponse(urls.rsbuildHost, apps.rsbuildHost); - assert.match(response.body, /Rspack host, two remote formats/); - assert.match(response.body, /Loading the Rsbuild remote/); - assert.match(response.body, /Loading the Vite remote/); +describeExamples("start", { + // In development, @module-federation/vite's ModuleRunner transport leaves a rejected + // fetch unhandled once a server-rendered Vite remote goes away, which exits the Vite + // host's dev server. Production builds survive the same outage. + outageRemotes: ["rsbuildRemote", "rsbuildSsrRemote"], }); -test("Vite host hydrates the Vite and Rsbuild remotes with one React instance", async () => { - const { errors, page } = await openPage(urls.viteHost); - try { - await page.getByText("Hydrated on the host").waitFor({ timeout: 20_000 }); - await assertInteractive(page, "Owned by the remote app"); - await assertInteractive(page, "Federated from Rsbuild"); - assert.deepEqual(errors, []); - } finally { - await page.close(); - } -}); - -test("Rsbuild host loads the Vite and Rsbuild remotes with one React instance", async () => { - const { errors, page } = await openPage(urls.rsbuildHost); - try { - await assertInteractive(page, "Owned by the remote app"); - await assertInteractive(page, "Federated from Rsbuild"); - assert.deepEqual(errors, []); - } finally { - await page.close(); - } -}); - -test("Rsbuild remote works as a standalone TanStack Start app", async () => { - const { errors, page } = await openPage(urls.rsbuildRemote); - try { - await assertInteractive(page, "Federated from Rsbuild"); - assert.deepEqual(errors, []); - } finally { - await page.close(); - } -}); - -// @module-federation/vite (1.22 through 1.23.2) never self-initializes a container with -// `exposes` in development, so its React shares are only wired up through a host. -// Production builds hydrate standalone. -test.todo("Vite remote hydrates as a standalone app in development"); - -// Runs last: it stops the Rsbuild remote. -test("hosts keep working when the Rsbuild remote is offline", async () => { - await stopApp(apps.rsbuildRemote); - - for (const [url, fallback] of [ - [urls.viteHost, "Rsbuild remote is unavailable"], - [urls.rsbuildHost, "Remote unavailable"], - ]) { - const page = await browser.newPage(); - const uncaught = []; - page.on("pageerror", (error) => uncaught.push(error.stack ?? error.message)); - try { - await page.goto(url); - await page.getByText(fallback).waitFor({ timeout: 20_000 }); - await assertInteractive(page, "Owned by the remote app"); - assert.deepEqual(uncaught, [], url); - } finally { - await page.close(); - } - } -}); +test.todo("Vite host dev server survives its server-rendered Vite remote going offline"); diff --git a/test/support/examples.mjs b/test/support/examples.mjs new file mode 100644 index 0000000..1368b99 --- /dev/null +++ b/test/support/examples.mjs @@ -0,0 +1,310 @@ +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { after, before, test } from "node:test"; +import { chromium } from "playwright"; + +const root = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); + +export const apps = { + viteHost: { name: "tanstack-start-vite-host", url: "http://127.0.0.1:3000/" }, + viteRemote: { name: "tanstack-start-vite-remote", url: "http://127.0.0.1:3001/" }, + rsbuildRemote: { name: "tanstack-start-rsbuild-remote", url: "http://127.0.0.1:3002/" }, + rsbuildHost: { name: "tanstack-start-rsbuild-host", url: "http://127.0.0.1:3003/" }, + rsbuildSsrRemote: { name: "tanstack-start-rsbuild-ssr-remote", url: "http://127.0.0.1:3004/" }, + rsbuildSsrHost: { name: "tanstack-start-rsbuild-ssr-host", url: "http://127.0.0.1:3005/" }, +}; + +const remotes = ["viteRemote", "rsbuildRemote", "rsbuildSsrRemote"]; +const hosts = ["viteHost", "rsbuildHost", "rsbuildSsrHost"]; + +const cards = { + vite: "Owned by the remote app", + rsbuild: "Federated from Rsbuild", + rsbuildSsr: "Federated SSR from Rsbuild", +}; + +/** + * Starts the six example apps with `script` ("start" for development servers, "preview" + * for production builds) and registers the shared federation scenarios against them. + * `outageRemotes` lists the remotes the outage scenario stops. + */ +export function describeExamples(script, { outageRemotes = remotes } = {}) { + const processes = {}; + let browser; + + const start = async (key) => { + processes[key] = startApp(apps[key].name, script); + // A missing route renders TanStack's not-found page without touching any remote. + await waitForResponse(`${apps[key].url}__ready`, processes[key], ({ status }) => status < 500); + }; + + before(async () => { + await Promise.all(remotes.map(start)); + await Promise.all(hosts.map(start)); + browser = await chromium.launch(); + }); + + after(async () => { + await browser?.close(); + await Promise.all(Object.values(processes).map(stopApp)); + }); + + test("SSR hosts render remote markup for concurrent first requests", async () => { + for (const [host, marker] of [ + ["viteHost", cards.vite], + ["rsbuildSsrHost", cards.rsbuildSsr], + ]) { + const responses = await Promise.all( + Array.from({ length: 8 }, () => fetch(apps[host].url).then(readResponse)), + ); + for (const { body, status } of responses) { + assert.equal(status, 200, host); + assert.ok(body.includes(marker), `${host} initial HTML contains the remote card`); + assert.ok( + body.includes("Rendered on the server"), + `${host} renders the remote on the server`, + ); + } + } + }); + + test("remote manifests reference reachable assets", async () => { + for (const key of remotes) { + const manifestUrl = new URL("mf-manifest.json", apps[key].url).href; + const manifest = await (await fetch(manifestUrl)).json(); + const assets = manifestAssets(manifest); + assert.ok(assets.length > 0, `${key} lists assets`); + for (const asset of assets) { + const url = new URL(asset, manifestUrl).href; + const { status } = await fetch(url); + assert.equal(status, 200, url); + } + } + }); + + test("Vite host hydrates the Vite and Rsbuild remotes with one React instance", async () => { + await withPage(apps.viteHost.url, async (page) => { + await page.getByText("Hydrated on the host").waitFor({ timeout: 20_000 }); + await assertInteractive(page, cards.vite); + await assertInteractive(page, cards.rsbuild); + }); + }); + + test("Rsbuild host loads the Vite and Rsbuild remotes with one React instance", async () => { + await withPage(apps.rsbuildHost.url, async (page) => { + await assertInteractive(page, cards.vite); + await assertInteractive(page, cards.rsbuild); + }); + }); + + test("Rsbuild SSR host hydrates the server-rendered Rsbuild remote", async () => { + await withPage(apps.rsbuildSsrHost.url, async (page) => { + await page.getByText("Hydrated on the host").waitFor({ timeout: 20_000 }); + await assertInteractive(page, cards.rsbuildSsr); + }); + }); + + test("Rsbuild remotes work as standalone TanStack Start apps", async () => { + await withPage(apps.rsbuildRemote.url, (page) => assertInteractive(page, cards.rsbuild)); + await withPage(apps.rsbuildSsrRemote.url, (page) => assertInteractive(page, cards.rsbuildSsr)); + }); + + // @module-federation/vite never initializes a container with `exposes` when it is opened + // directly in development, and 1.23.0 broke the production path too (1.22.x works). + test.todo("Vite remote hydrates as a standalone app"); + + // Runs last: it stops remotes, then starts them again. + test("hosts fall back while remotes are offline and recover when they return", async () => { + await Promise.all(outageRemotes.map((key) => stopApp(processes[key]))); + const offline = (key) => outageRemotes.includes(key); + + for (const [host, fallbacks] of [ + [ + "viteHost", + [ + offline("viteRemote") && "Vite remote is unavailable", + offline("rsbuildRemote") && "Rsbuild remote is unavailable", + ], + ], + [ + "rsbuildHost", + [(offline("viteRemote") || offline("rsbuildRemote")) && "Remote unavailable"], + ], + ["rsbuildSsrHost", [offline("rsbuildSsrRemote") && "Rsbuild SSR remote is unavailable"]], + ]) { + const expected = fallbacks.filter(Boolean); + if (expected.length === 0) continue; + + const { status } = await fetch(apps[host].url); + assert.equal(status, 200, `${host} still serves its page`); + await withPage( + apps[host].url, + async (page) => { + for (const fallback of expected) { + await page.getByText(fallback).first().waitFor({ timeout: 20_000 }); + } + }, + // React reports the failed server render as recoverable and renders the + // boundary on the client, which is the fallback under test. + { allowErrors: true }, + ); + } + + await Promise.all(outageRemotes.map(start)); + + for (const [host, remote, marker] of [ + ["viteHost", "viteRemote", cards.vite], + ["rsbuildSsrHost", "rsbuildSsrRemote", cards.rsbuildSsr], + ]) { + if (!offline(remote)) continue; + const { body } = await waitForResponse(apps[host].url, processes[host], ({ body }) => + body.includes(marker), + ); + assert.ok(body.includes("Rendered on the server"), `${host} renders the remote again`); + } + await withPage(apps.rsbuildHost.url, async (page) => { + await assertInteractive(page, cards.vite); + await assertInteractive(page, cards.rsbuild); + }); + await withPage(apps.viteHost.url, async (page) => { + await page.getByText("Hydrated on the host").waitFor({ timeout: 20_000 }); + await assertInteractive(page, cards.vite); + await assertInteractive(page, cards.rsbuild); + }); + }); + + /** Opens a page, runs `check`, and fails on uncaught errors or console errors and warnings. */ + async function withPage(url, check, { allowErrors = false } = {}) { + const page = await browser.newPage(); + const errors = []; + if (!allowErrors) { + page.on("pageerror", (error) => errors.push(error.stack ?? error.message)); + page.on("console", (message) => { + if (["error", "warning"].includes(message.type())) { + errors.push(`${message.type()}: ${message.text()}`); + } + }); + } + try { + await page.goto(url); + await check(page); + assert.deepEqual(errors, [], url); + } finally { + await page.close(); + } + } +} + +/** Lists every file a manifest points to, relative to the manifest URL. */ +function manifestAssets(manifest) { + const { metaData } = manifest; + const entries = [metaData.remoteEntry, metaData.ssrRemoteEntry] + .filter(Boolean) + .map(({ path, name }) => (path ? `${path.replace(/^\/|\/$/g, "")}/${name}` : name)); + const files = [...manifest.exposes, ...manifest.shared].flatMap(({ assets }) => [ + ...assets.js.sync, + ...assets.js.async, + ...assets.css.sync, + ...assets.css.async, + ]); + return [...new Set([...entries, ...files])]; +} + +/** Clicks a remote card's counter and proves the remote's state updates on the host. */ +async function assertInteractive(page, cardText) { + const button = page + .locator("article", { hasText: cardText }) + .getByRole("button", { name: /Remote count/ }); + await button.waitFor({ timeout: 20_000 }); + const count = async () => Number((await button.textContent()).match(/\d+/)?.[0]); + + // A server-rendered button ignores clicks until React hydrates it. + const deadline = Date.now() + 20_000; + while ((await count()) === 0) { + assert.ok(Date.now() < deadline, `${cardText} never became interactive`); + await button.click(); + await page.waitForTimeout(100); + } + + const before = await count(); + await button.click(); + await page.waitForFunction( + ([text, expected]) => + [...document.querySelectorAll("article")] + .find((article) => article.textContent.includes(text)) + ?.querySelector("button") + ?.textContent.includes(String(expected)), + [cardText, before + 1], + { timeout: 5_000 }, + ); +} + +function startApp(name, script) { + const child = spawn("pnpm", ["--filter", name, script], { + cwd: root, + detached: true, + env: { ...process.env, NODE_ENV: script === "start" ? "development" : "production" }, + stdio: ["ignore", "pipe", "pipe"], + }); + child.output = ""; + + for (const stream of [child.stdout, child.stderr]) { + stream.on("data", (chunk) => { + child.output = `${child.output}${chunk}`.slice(-12_000); + }); + } + + return child; +} + +async function readResponse(response) { + return { body: await response.text(), status: response.status }; +} + +async function waitForResponse(url, child, predicate = () => true) { + const deadline = Date.now() + 60_000; + let lastError; + let lastResponse; + + while (Date.now() < deadline) { + if (child.exitCode !== null || child.signalCode !== null) { + assert.fail(`server stopped while waiting for ${url}\n${child.output}`); + } + + try { + lastResponse = await readResponse(await fetch(url, { signal: AbortSignal.timeout(5_000) })); + if (predicate(lastResponse)) return lastResponse; + } catch (error) { + lastError = error; + } + + await new Promise((resolve) => setTimeout(resolve, 250)); + } + + assert.fail( + [ + `timed out waiting for ${url}`, + lastResponse ? `last status: ${lastResponse.status}` : undefined, + lastError ? `last error: ${lastError.message}` : undefined, + child.output, + ] + .filter(Boolean) + .join("\n"), + ); +} + +async function stopApp(child) { + if (!child || child.exitCode !== null || child.signalCode !== null) return; + process.kill(-child.pid, "SIGTERM"); + + await Promise.race([ + new Promise((resolve) => child.once("exit", resolve)), + new Promise((resolve) => setTimeout(resolve, 3_000)), + ]); + + if (child.exitCode === null && child.signalCode === null) { + process.kill(-child.pid, "SIGKILL"); + } +} diff --git a/turbo.json b/turbo.json index 5c6afdd..f26e535 100644 --- a/turbo.json +++ b/turbo.json @@ -18,6 +18,10 @@ "dependsOn": ["^build", "^typecheck"], "inputs": ["$TURBO_DEFAULT$", ".env*"], "outputs": [] + }, + "preview": { + "cache": false, + "persistent": true } } } From 1164748d79438fd2d9f5c73aa1448bb00ce6925b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 6 Oct 2026 21:33:47 +0200 Subject: [PATCH 12/18] docs: document Rsbuild SSR federation and the remaining work Mark Rsbuild-to-Rsbuild SSR as supported, describe the server: true layout, list known upstream limitations (standalone Vite remotes, the dev SSR crash, remote CSS in SSR responses), narrow the documented Vite support to the tested Vite 8, and restructure TODO.md around what 0.1.0 proves. --- README.md | 53 ++-- TODO.md | 444 ++++++++++++--------------------- apps/README.md | 58 +++-- docs/RELEASING.md | 13 +- packages/tanstack/CHANGELOG.md | 24 +- packages/tanstack/README.md | 73 ++++-- 6 files changed, 290 insertions(+), 375 deletions(-) diff --git a/README.md b/README.md index 40ddf2d..ceefa4d 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,8 @@ Do not set a global Module Federation `target`. TanStack Start builds client and SSR environments from one Vite configuration, so the wrapper leaves target selection to `@module-federation/vite` for each environment. -The package supports Node 22.18+, 24.11+, and 26+, plus Vite 7/8 and TanStack -Start 1.167.43+. Use Vite 8 when developing SSR hosts that load remotes; Vite 7 -remains supported for production builds. +The package supports Node 22.18+, 24.11+, and 26+, plus TanStack Start +1.167.43+. It is tested with Vite 8, which development SSR hosts require. The package root remains a Vite compatibility export. New Vite projects can import `/vite` explicitly. Rsbuild projects must import `/rsbuild`: @@ -86,28 +85,33 @@ export default defineConfig({ ``` The Rsbuild adapter emits script-compatible browser output, so its manifest can -be consumed by Vite and Rsbuild hosts. It adds eager, singleton `react` and -`react-dom` entries without changing the caller's config object. Hosts should -use `shareStrategy: "loaded-first"` so one offline remote cannot fail startup. +be consumed by Vite and Rsbuild hosts. It shares singleton `react` and +`react-dom` (eager for hosts, lazy for remotes), enables async startup, and +gives remotes `publicPath: "auto"` so other origins load their chunks from the +remote. Hosts should use `shareStrategy: "loaded-first"` so one offline remote +cannot fail startup. -SSR federation for Rsbuild is experimental and opt-in: `server: true` adds an -async-node CommonJS server container and changes the server entry to -`dist/server/index.cjs`. +SSR federation is opt-in with `server: true` on both sides. A remote builds a +Node container into `dist/client/ssr/` and advertises it in its manifest; a host +loads remotes on the server from an async-node CommonJS server build. See the +[package README](packages/tanstack/README.md#ssr-federation). ## Support status -| Host | Remote | Browser federation | Federated SSR | -| ------- | ------- | ------------------ | ----------------------------- | -| Vite | Vite | Supported | Supported | -| Vite | Rsbuild | Supported | Not supported | -| Rsbuild | Vite | Supported | Not supported | -| Rsbuild | Rsbuild | Supported | Experimental (`server: true`) | +| Host | Remote | Browser federation | Federated SSR | +| ------- | ------- | ------------------ | -------------------------- | +| Vite | Vite | Supported | Supported | +| Vite | Rsbuild | Supported | Not supported | +| Rsbuild | Vite | Supported | Not supported | +| Rsbuild | Rsbuild | Supported | Supported (`server: true`) | -[`TODO.md`](TODO.md) tracks the remaining SSR work. +Known limitations are listed in the +[package README](packages/tanstack/README.md#known-limitations), and +[`TODO.md`](TODO.md) tracks the remaining work. ## Examples -The workspace has four TanStack Start apps; see [`apps/README.md`](apps/README.md). +The workspace has six TanStack Start apps; see [`apps/README.md`](apps/README.md). The repository uses pnpm 12 (pinned in `packageManager`). pnpm 12 ships as a native binary, so an older global pnpm cannot switch to it automatically; @@ -116,13 +120,16 @@ commands. ```bash pnpm install -pnpm start +pnpm start # development servers +pnpm preview # production builds ``` -Open the Vite host at http://127.0.0.1:3000 or the Rsbuild host at -http://127.0.0.1:3003. Each host renders a card from both remotes. +Open the Vite host at http://127.0.0.1:3000, the Rsbuild host at +http://127.0.0.1:3003, or the Rsbuild SSR host at http://127.0.0.1:3005. -`pnpm test` builds everything, starts all four apps, and drives both hosts in -headless Chromium. It checks hydration, remote interactivity, console errors, -and the fallback when a remote is offline. Run +`pnpm test` builds everything, then runs the six apps twice, as development +servers and as production builds, and drives every host in headless Chromium. +It checks server-rendered remote markup (including concurrent first requests), +hydration, remote interactivity, console errors, manifest asset reachability, +and the fallback and recovery when remotes go offline. Run `pnpm exec playwright install chromium` once before the first local test run. diff --git a/TODO.md b/TODO.md index 9dfe95a..650f668 100644 --- a/TODO.md +++ b/TODO.md @@ -1,300 +1,168 @@ -# SSR completion TODO - -This file tracks the work required before `@module-federation/tanstack` can -claim SSR support across both Vite and Rsbuild/Rspack. - -Assessment date: 2026-09-18. Updated 2026-10-06: the Rsbuild server adapter is now -opt-in (`server: true`), and browser tests cover hydration, shared React -identity, and remote outages for every host/remote pair. - -## Current support boundary - -| Host | Remote | Federated SSR status | Current proof | -| ------- | ------- | -------------------- | ------------------------------------------------------------------------------- | -| Vite | Vite | Supported | The Vite host response contains remote markup before hydration. | -| Vite | Rsbuild | Not supported | The Rsbuild remote loads in the browser after hydration. | -| Rsbuild | Vite | Not supported | The Vite remote loads through the browser runtime after hydration. | -| Rsbuild | Rsbuild | Experimental | `server: true` builds a server container, but no end-to-end SSR fixture exists. | - -The Rsbuild examples are TanStack Start SSR applications, so their local route -shells render on the server. Their Module Federation configuration uses the -default browser-only mode; federated components are client-only. Do not describe that as -federated SSR. - -The supported claim today is: - -> Vite-to-Vite federated SSR, plus Vite/Rsbuild browser interoperability. - -## Evidence behind this boundary - -### Vite-to-Vite SSR works - -- [`packages/tanstack/src/vite.ts`](packages/tanstack/src/vite.ts) registers one - Vite federation plugin for TanStack Start's client and server environments. -- The wrapper leaves `target` unset so `@module-federation/vite` selects `web` - for the client and `node` for SSR. -- `hostInitInjectLocation: "entry"` initializes federation before TanStack - hydrates an application without an `index.html` entry. -- `module-federation/vite` enables its SSR entry loader for every production - build and for Vite 8 development. Vite 7 development does not have the - ModuleRunner support required by this path. -- Vite remotes emit a separate ESM `remoteEntry.ssr.js` and include it in - `mf-manifest.json` as `metaData.ssrRemoteEntry`. -- The Vite SSR loader resolves the manifest entry, links shared packages to the - host, and evaluates the remote through Vite ModuleRunner, `vm.SourceTextModule`, - or a temporary ESM graph. -- [`test/example-runtime.test.mjs`](test/example-runtime.test.mjs) verifies that - the Vite host's initial HTML contains `Owned by the remote app` and - `Rendered on the server`. - -Reference implementation paths: - -- `module-federation/vite/src/utils/ssrCapabilities.ts` -- `module-federation/vite/src/plugins/pluginSSRRemoteEntry.ts` -- `module-federation/vite/src/utils/ssrEntryLoader.ts` -- `module-federation/vite/src/plugins/pluginAddEntry.ts` -- `module-federation/vite/src/virtualModules/virtualRuntimeInitStatus.ts` - -### Rsbuild has the required pieces, but not the proof - -- [`packages/tanstack/src/rsbuild.ts`](packages/tanstack/src/rsbuild.ts) installs - separate `web` and `node` federation plugins for TanStack Start's `client` - and `ssr` environments. -- The browser compiler emits script/JSONP-compatible output. -- The server compiler can emit async-node CommonJS output with - `serverRemoteEntry.cjs` and `dist/server/index.cjs`. -- React and React DOM default to eager singletons for Rsbuild. TanStack's server - bundle imports React synchronously, and Core reports `RUNTIME-006` when those - shares are lazy. -- Core's `target: "node"` path changes the compiler target to `async-node`, - creates a CommonJS server container, and injects - `@module-federation/node/runtimePlugin`. -- Core only installs its startup dependency handling when - `experiments.asyncStartup` is enabled. The TanStack adapter does not default - this option today. -- Both committed Rsbuild examples use the default browser-only mode, so the - server adapter is covered by compiler-contract tests, not by a running host/remote pair. - -Reference implementation paths: - -- `module-federation/core/packages/rsbuild-plugin/src/cli/index.ts` -- `module-federation/core/packages/rsbuild-plugin/src/utils/ssr.ts` -- `module-federation/core/packages/node/src/runtimePlugin.ts` -- `module-federation/core/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts` -- `module-federation/core/packages/webpack-bundler-runtime/src/installInitialConsumes.ts` - -### Cross-bundler SSR needs an explicit transport contract +# Federation TODO + +This file tracks what `@module-federation/tanstack` proves today and the work +left after 0.1.0. + +Assessment date: 2026-10-06. + +## Support boundary + +| Host | Remote | Browser federation | Federated SSR | Proof (development and production) | +| ------- | ------- | ------------------ | ------------- | -------------------------------------------------------------------- | +| Vite | Vite | Supported | Supported | Remote markup in the host's initial HTML, hydration, outage/recovery | +| Vite | Rsbuild | Supported | Not supported | The Rsbuild remote loads in the browser after hydration | +| Rsbuild | Vite | Supported | Not supported | The Vite remote loads through the browser runtime | +| Rsbuild | Rsbuild | Supported | Supported | `rsbuild-ssr-host` renders `rsbuild-ssr-remote` on the server | + +`test/example-runtime.test.mjs` (development servers) and +`test/example-production.test.mjs` (production builds) run the same scenarios +through `test/support/examples.mjs` on every supported Node line in CI. + +## Done for 0.1.0 + +### Rsbuild-to-Rsbuild SSR + +- [x] Default `experiments.asyncStartup: true` for browser and server + federation, preserving an explicit `false`. +- [x] Share `react` and `react-dom` as singletons: eager for hosts, lazy for + remotes and server containers, so remotes cannot replace a host's React. +- [x] Build a remote's Node container in a dedicated `mf-server` environment, + emitted to `dist/client/ssr/` with the browser assets. It contains only + the exposed modules; the app's server bundle is never published. +- [x] Advertise the container in the browser manifest as `ssrRemoteEntry`, + which Module Federation otherwise only does for Rslib's `dual` target. +- [x] Give remotes `publicPath: "auto"`. TanStack Start derives `/` from + `server.base`, which made production remotes request their chunks from + the host's origin, and made Node hosts fail to fetch them. +- [x] Load remotes in a host's server bundle as async-node CommonJS, keep + `dist/server/index.js` as an ES module that re-exports it, and write the + server output to disk in development so async-node chunks match the + in-memory entry. +- [x] Load CommonJS remote entries with `require`-based Node built-ins + (`@module-federation/tanstack/node-entry-loader`). Rsbuild's development + runner cannot evaluate Module Federation's `import("vm")`. +- [x] Dedicated fixtures: `apps/rsbuild-ssr-remote` and `apps/rsbuild-ssr-host`, + importing the remote in a route loader. +- [x] Acceptance: initial HTML contains remote markup (not a placeholder), for + eight concurrent first requests; hydration without warnings; the remote + stays interactive; one React instance; no `RUNTIME-*` or chunk errors; + the manifest declares a `commonjs-module` server entry; every + manifest-referenced asset returns 200; the container has no + build-machine paths. + +### Hardening + +- [x] Production builds (`vite preview`, `rsbuild preview`) run the same browser + scenarios as development servers. +- [x] Hosts render a fallback, not a 500 or a blank page, while remotes are + offline, and render remote markup again once they return. +- [x] `useState`, `useEffect`, Suspense, and error boundaries across the + federation boundary (counter, hydration status, lazy remote components, + `RemoteBoundary`). +- [x] Document `server: true`, the `ssr/` container, and the server entries. + +## Open + +### Upstream issues + +Report these to `module-federation/vite`; they are documented as known +limitations. -The two server entry formats differ: - -- Vite emits an ESM SSR entry and an ESM dependency graph. -- The current Rsbuild adapter emits a CommonJS container and async-node chunks. - -The Vite loader can `require()` a local CommonJS entry. For an HTTP CommonJS -entry, `createRequire()` cannot load the URL, and the loader falls through to -paths designed around ESM source. That is not a reliable Rsbuild remote loader. - -In the other direction, Core's Node runtime has ESM loading support, but this -repository does not prove that an Rsbuild host can load Vite's manifest-declared -ESM SSR entry, preserve the host React instance, and hydrate the result. - -## Phase 1: finish Rsbuild-to-Rsbuild SSR - -### Adapter defaults - -- [ ] Merge `experiments.asyncStartup: true` into the Rsbuild browser and server - federation options. -- [ ] Preserve an explicit `experiments.asyncStartup: false` override. -- [ ] Keep `react` and `react-dom` eager singletons by default. -- [ ] Cover React subpaths used by the compiled application, including - `react/jsx-runtime` and `react-dom/client`, without creating a second React - instance. -- [ ] Assert that Core's node target injects the Node runtime plugin exactly - once. -- [ ] Confirm that two environment-scoped Rsbuild plugins continue to install - one browser container and one server container. - -### Server-enabled fixtures - -- [ ] Add a dedicated Rsbuild remote with the server adapter enabled. -- [ ] Expose a stateful component from both `remoteEntry.js` and - `serverRemoteEntry.cjs`. -- [ ] Add a dedicated Rsbuild host with the server adapter enabled. -- [ ] Import the remote through a route loader or another real async boundary. -- [ ] Start the production remote from `dist/server/index.cjs`. -- [ ] Serve the client manifest, server entry, server chunks, JavaScript, CSS, - and federated type archive from stable URLs. -- [ ] Start the production host from `dist/server/index.cjs`. -- [ ] Ensure development and production use the same remote naming and share - scope. - -### Rsbuild SSR acceptance criteria - -- [ ] The host's initial HTML contains remote component markup. -- [ ] The initial HTML does not contain only a loading placeholder. -- [ ] Hydration completes without warnings or subtree replacement. -- [ ] A remote button remains interactive after hydration. -- [ ] Host context and hooks work inside the remote component. -- [ ] React and React DOM resolve to one physical host-owned instance. -- [ ] No `RUNTIME-006`, `RUNTIME-001`, or missing chunk error appears. -- [ ] The remote manifest declares a `commonjs-module` server entry. -- [ ] Every server entry import and async chunk is reachable in production. -- [ ] The built server output contains no build-machine absolute paths. - -## Phase 2: support a Vite host with an Rsbuild SSR remote - -Choose and document one server-entry strategy before implementing this path. - -### Option A: emit an ESM SSR entry from Rsbuild - -This aligns the remote with the existing Vite SSR loader. - -- [ ] Determine whether the Rsbuild server compiler can emit a second ESM - container without changing the CommonJS entry used by Rsbuild hosts. -- [ ] Decide how the manifest identifies both server formats. The current - manifest has one `ssrRemoteEntry` slot. -- [ ] Keep server chunks portable and reachable over HTTP. -- [ ] Verify that Vite's temporary-file and VM strategies can link the emitted - graph. - -### Option B: teach the Vite loader to evaluate HTTP CommonJS containers - -This aligns the host with the existing Rsbuild server output. - -- [ ] Add a bounded HTTP CommonJS loader rather than passing an HTTP URL to - `createRequire()`. -- [ ] Reuse Core's Node chunk-loading behavior where possible. -- [ ] Resolve relative async chunks from the remote public path. -- [ ] Preserve Node module-cache identity for React and other singletons. -- [ ] Apply the same fetch timeout and maximum-body limits used by the ESM - loader. -- [ ] Avoid unrestricted `eval` or unbounded code fetching. - -### Vite-host acceptance criteria - -- [ ] Vite 8 development renders the Rsbuild remote into initial HTML. -- [ ] Vite 8 production renders and hydrates the same remote. -- [ ] Vite 7 production works, or the package documents a narrower requirement. -- [ ] Remote CSS appears before or during hydration without a layout flash. -- [ ] Manifest revalidation replaces a changed remote without restarting the - host process. - -## Phase 3: support an Rsbuild host with a Vite SSR remote - -- [ ] Confirm that Core's Node runtime selects `metaData.ssrRemoteEntry` instead - of the browser entry in a Vite manifest. -- [ ] Confirm support for a manifest entry whose type is `module`. -- [ ] Load Vite's transitive ESM chunks without Node network-import flags. -- [ ] Map shared package imports to the Rsbuild host share scope. -- [ ] Remove any dependency on `--experimental-vm-modules` from the supported - default path. -- [ ] Verify development against Vite's `/__mf_ssr__/` and ModuleRunner - endpoints. -- [ ] Verify production against the built `remoteEntry.ssr.js` graph. - -### Rsbuild-host acceptance criteria - -- [ ] The Rsbuild host's initial HTML contains Vite remote markup. -- [ ] Hydration preserves that markup and remote state. -- [ ] The Node runtime reports no missing module, VM import, or chunk execution - error. -- [ ] A remote outage returns a controlled fallback instead of a 500 response. - -## Phase 4: shared identity and recovery hardening +- [ ] A Vite remote with `exposes` does not hydrate when opened directly in + development: the plugin forces host-driven init for exposing containers + (`forceClientInjected`), so the remote's own React shares never resolve. + 1.22 hangs; 1.23.2 throws `_jsxDEV is not a function`. +- [ ] Since 1.23.0, the same remote also fails standalone in production + (`x is not a function` from the `react-dom/client` share). Bisected: + 1.22.0 and 1.22.1 hydrate; 1.23.0, 1.23.1, and 1.23.2 do not. +- [ ] In development, a Vite host's dev server exits with an unhandled + `TypeError: fetch failed` (ECONNREFUSED) after a Vite remote it has + server-rendered through the ModuleRunner transport (`/__mf_runner__`) + goes offline. Production hosts are unaffected. + +### SSR quality + +- [ ] Include the remote's stylesheets in SSR responses. Neither SSR host emits + the remote's CSS links, so remote markup is unstyled until its JavaScript + loads. This likely needs a helper that reads `exposes[].assets.css` from + the remote snapshot and feeds TanStack Start's route `head`. +- [ ] Revalidate a changed remote manifest and server entry without restarting + the host, and prove no stale container or shared module is served. +- [ ] Test cyclic imports in a server remote graph. ### Shared dependencies -- [ ] Test React context created by the host and consumed by every remote. -- [ ] Test `useState`, `useEffect`, Suspense, and error boundaries across the - federation boundary. -- [ ] Test React 18 and React 19 if both remain in the support range. -- [ ] Validate version mismatch warnings for incompatible React majors. -- [ ] Ensure `import: false` fails at startup when the host cannot provide the - required package. +- [ ] Test React context created by the host and consumed by a remote, through + a shared singleton package. +- [ ] Decide the React support range. TanStack Start allows React 18 and 19; + only React 19 is tested. +- [ ] Validate version-mismatch warnings for incompatible React majors, and + that `import: false` fails at startup when the host cannot provide a + package. -### Remote lifecycle +### Versions -- [ ] Start the host while the remote is unavailable. -- [ ] Bring the remote online and prove that a later request recovers. -- [ ] Restart the remote with a new manifest and server entry. -- [ ] Revalidate without serving a stale container or stale shared module. -- [ ] Test concurrent first requests while the remote entry is loading. -- [ ] Test cyclic imports in the server remote graph. +- [ ] Vite 7: `@vitejs/plugin-react` 6 requires Vite 8, so a Vite 7 run needs + its own dependency set (`@vitejs/plugin-react` 5). Until then the docs + claim Vite 8 only, while the peer range still allows Vite 7. ### Error reporting -- [ ] Preserve the original network or execution error. -- [ ] Distinguish manifest failure, entry failure, chunk failure, and shared - dependency failure. +- [ ] Preserve the original network or execution error, and distinguish + manifest, entry, chunk, and shared-dependency failures. - [ ] Add optional observability reports for raw runtime codes that do not contain enough context. -## Required test matrix - -| Host | Remote | Development | Production | Initial remote HTML | Hydration and interaction | -| --------- | --------- | ------------ | ---------- | ------------------- | ------------------------- | -| Vite 8 | Vite 8 | Existing | Add | Existing | Existing (development) | -| Vite 7 | Vite 7 | Not required | Add | Add | Add | -| Rsbuild 2 | Rsbuild 2 | Add | Add | Add | Add | -| Vite 8 | Rsbuild 2 | Add | Add | Add | Add | -| Rsbuild 2 | Vite 8 | Add | Add | Add | Add | - -Run the production matrix on every supported Node line: - -- [ ] Node 22.18 -- [ ] Node 24 -- [ ] Node 26 - -Each matrix row must verify: - -- [ ] manifest and remote entry return 200; -- [ ] every manifest-referenced asset returns 200; -- [ ] initial HTML contains the expected remote marker; -- [ ] hydration produces no console errors or mismatch warnings; -- [ ] remote interaction updates state; -- [ ] host and remote use one React dispatcher; -- [ ] shutting down the remote produces the documented fallback; -- [ ] restarting the remote allows recovery. - -## Documentation and release work - -- [ ] Update the root README only after a matrix row satisfies every acceptance - criterion. -- [x] Document browser-only federation as the Rsbuild default and SSR as opt-in. -- [ ] Document `dist/server/index.cjs` when the CommonJS Rsbuild server adapter - is enabled. -- [ ] Document the chosen cross-bundler server-entry format. -- [ ] Keep Vite 8 as the development SSR requirement unless Vite 7 gains an - equivalent ModuleRunner path. -- [ ] Add a Changeset for any new supported SSR combination. -- [ ] Include the tested bundler and Node version matrix in the release notes. - -## Definition of done - -SSR support for a host/remote combination is complete only when: - -- [ ] the remote is rendered into the host's initial HTML; -- [ ] the same markup hydrates without replacement; -- [ ] the remote remains interactive; -- [ ] shared React identity is proven; -- [ ] development and production both pass where claimed; -- [ ] remote outage and recovery behavior are tested; -- [ ] server entries and all chunks are deployable without local paths; -- [ ] the supported versions are documented; -- [ ] CI runs the proof on every supported Node line. - -Until then, keep the package description precise: Vite-to-Vite SSR is supported; -Rsbuild and cross-bundler SSR are in progress. - -## Example and development gaps - -- [ ] Run the browser tests against production builds, not only development - servers. -- [ ] A Vite remote with `exposes` does not hydrate when opened directly in - development: `@module-federation/vite` forces host-driven init for - exposing containers (`forceClientInjected`). 1.22 hangs; 1.23.2 throws - `_jsxDEV is not a function`. Report upstream, or add a standalone init - path. -- [ ] Decide whether the Rsbuild adapter should default hosts to - `shareStrategy: "loaded-first"`. With `version-first`, one offline remote - fails host startup with `RUNTIME-003`; the example sets it explicitly. +## Cross-bundler SSR (after 0.1.0) + +The two server entry formats differ: + +- Vite emits an ESM SSR entry and an ESM dependency graph. +- The Rsbuild adapter emits a CommonJS container (`ssr/remoteEntry.js`) with + async-node chunks. + +The Vite SSR loader can `require()` a local CommonJS entry. For an HTTP +CommonJS entry, `createRequire()` cannot load the URL, and the loader falls +through to paths designed around ESM source. In the other direction, Core's Node +runtime has ESM loading support, but nothing proves that an Rsbuild host can +load Vite's manifest-declared ESM SSR entry, preserve the host's React +instance, and hydrate the result. Both directions need changes in +`@module-federation/vite` or Core. + +### Vite host with an Rsbuild SSR remote + +Choose one server-entry strategy: + +- Option A, emit an ESM SSR entry from Rsbuild: decide how the manifest + identifies both server formats (it has one `ssrRemoteEntry` slot), keep the + chunks reachable over HTTP, and verify Vite's temporary-file and VM + strategies can link the graph. +- Option B, teach the Vite loader to evaluate HTTP CommonJS containers: a + bounded loader that reuses Core's Node chunk loading, resolves async chunks + from the remote public path, preserves module-cache identity for singletons, + applies the ESM loader's fetch timeout and body limits, and avoids unbounded + `eval`. + +Acceptance: Vite 8 development and production render and hydrate the Rsbuild +remote, remote CSS arrives without a flash, and manifest revalidation replaces +a changed remote without restarting the host. + +### Rsbuild host with a Vite SSR remote + +- [ ] Confirm that Core's Node runtime selects `metaData.ssrRemoteEntry` with + type `module`. +- [ ] Load Vite's transitive ESM chunks without Node network-import flags or + `--experimental-vm-modules`. +- [ ] Map shared package imports to the Rsbuild host share scope. +- [ ] Verify development against Vite's `/__mf_ssr__/` and ModuleRunner + endpoints, and production against the built `remoteEntry.ssr.js` graph. + +Acceptance: the Rsbuild host's initial HTML contains the Vite remote, hydration +preserves it, and a remote outage returns a fallback instead of a 500. + +## Release work for each new SSR combination + +- [ ] Update the support table only after the combination passes the + development and production scenarios on every supported Node line. +- [ ] Add a Changeset and list the tested bundler and Node versions in the + release notes. diff --git a/apps/README.md b/apps/README.md index 7e0c6cd..43fe82a 100644 --- a/apps/README.md +++ b/apps/README.md @@ -1,50 +1,56 @@ # TanStack Start examples -Four TanStack Start applications: a host and a remote for each bundler. Each -host consumes both remotes, so every host/remote combination runs. - -| App | Port | Role | -| ---------------- | ---- | ---------------------------------------------------------------------- | -| `vite-host` | 3000 | Server-renders the Vite remote; loads the Rsbuild remote on the client | -| `vite-remote` | 3001 | Exposes `./StatusCard`, rendered into the Vite host's initial HTML | -| `rsbuild-remote` | 3002 | Exposes `./StatusCard` through a browser manifest | -| `rsbuild-host` | 3003 | Loads both remotes with `loadRemote()` after hydration | +Six TanStack Start applications: a browser-federation host and remote for each +bundler, plus an Rsbuild pair that federates on the server. + +| App | Port | Role | +| -------------------- | ---- | ---------------------------------------------------------------------- | +| `vite-host` | 3000 | Server-renders the Vite remote; loads the Rsbuild remote on the client | +| `vite-remote` | 3001 | Exposes `./StatusCard`, rendered into the Vite host's initial HTML | +| `rsbuild-remote` | 3002 | Exposes `./StatusCard` through a browser manifest | +| `rsbuild-host` | 3003 | Loads both remotes with `loadRemote()` after hydration | +| `rsbuild-ssr-remote` | 3004 | Exposes `./StatusCard` with a Node container (`server: true`) | +| `rsbuild-ssr-host` | 3005 | Server-renders the Rsbuild SSR remote (`server: true`) | From the repository root: ```bash -pnpm start +pnpm start # development servers +pnpm preview # production builds (`vite preview` / `rsbuild preview`) ``` -Then open http://127.0.0.1:3000 or http://127.0.0.1:3003. Every remote card has -a counter button; clicking it proves the remote shares the host's React -instance. +Then open http://127.0.0.1:3000, http://127.0.0.1:3003, or +http://127.0.0.1:3005. Every remote card has a counter button; clicking it +proves the remote shares the host's React instance. Cards rendered on the +server show "Rendered on the server" until they hydrate. ## What each host shows - **Vite host.** The route loader imports the Vite remote, so its card is in - the server response ("Rendered on the server") and hydrates in place. The - Rsbuild card mounts after hydration. Both remotes sit behind - `RemoteBoundary`, an error boundary with a fallback card, so a failed remote - never blanks the page. + the server response and hydrates in place. The Rsbuild card mounts after + hydration. Both remotes sit behind `RemoteBoundary`, an error boundary with a + fallback card, and the loader ignores a failed import, so an offline remote + never fails the route. - **Rsbuild host.** `RemoteCardSlot` calls `loadRemote()` in an effect and shows "Remote unavailable" when a remote fails. The host uses `shareStrategy: "loaded-first"`, so it does not fetch every remote's manifest at startup. +- **Rsbuild SSR host.** Same pattern as the Vite host, on Rsbuild: the loader + imports the remote on the server through the Module Federation Node runtime, + which fetches the container from the remote's `ssr/` directory. -Stop the Rsbuild remote while both hosts are open and reload them: each host -keeps rendering and shows its fallback card. +Stop a remote while the hosts are open and reload them: each host keeps +rendering and shows its fallback, then renders the remote again once it is +back. ## Notes - The federation wrapper must come before `tanstackStart()` in the Vite - configs. + configs, and after it in the Rsbuild configs. - Each `start` script runs the bundler's development server. In Vite's CLI the bare `vite` command starts the dev server; `vite start` would treat `start` as a project directory. -- In development, opening the Vite remote directly (port 3001) server-renders - the page but does not hydrate: `@module-federation/vite` waits for a host to - initialize a container that has `exposes`. It hydrates when loaded through a - host, and its production build hydrates standalone. -- Cross-bundler remotes render on the client only. Cross-bundler SSR is not - supported yet. +- Opening the Vite remote directly (port 3001) server-renders the page but does + not hydrate it: `@module-federation/vite` waits for a host to initialize a + container with `exposes`. It hydrates when loaded through a host. +- Cross-bundler remotes render on the client only. diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 67cf10e..a52b675 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -41,12 +41,13 @@ The publish workflow reruns the gate, checks that the source is reachable from a different dist-tag. Configure npm trusted publishing for repository `module-federation/tanstack`, workflow `release.yml`, environment `Publish`. -The release gate builds the Vite and Rsbuild example pairs and checks their -manifests and server bundles. It then starts all four apps and drives both hosts -in headless Chromium: Vite-to-Vite SSR markup, hydration, remote interactivity, -one shared React instance, no console errors, and the fallback when a remote is -offline. Package tests cover the opt-in Rsbuild async-node CommonJS SSR -configuration. +The release gate builds the six example apps and checks their manifests and +server bundles. It then runs the apps twice, as development servers and as +production builds, and drives every host in headless Chromium: server-rendered +remote markup for Vite to Vite and Rsbuild to Rsbuild (including concurrent +first requests), hydration, remote interactivity, one shared React instance, no +console errors, reachable manifest assets, and the fallback and recovery when +remotes go offline. The test suite needs Chromium. Locally, run `pnpm exec playwright install chromium` once; the workflows install it before `pnpm test`. diff --git a/packages/tanstack/CHANGELOG.md b/packages/tanstack/CHANGELOG.md index 477e138..e63c494 100644 --- a/packages/tanstack/CHANGELOG.md +++ b/packages/tanstack/CHANGELOG.md @@ -7,13 +7,19 @@ Initial release: Module Federation for TanStack Start on Vite and Rsbuild. - `@module-federation/tanstack/vite` (also the package root) wraps `@module-federation/vite` with TanStack Start defaults: `remoteEntry.js`, a manifest, entry-level host init, and environment-specific build targets. -- Vite hosts share eager singleton `react` and `react-dom`, so a remote built - by another bundler cannot replace the host's React instance. + Hosts share eager singleton `react` and `react-dom`, so a remote built by + another bundler cannot replace the host's React instance. - `@module-federation/tanstack/rsbuild` wraps - `@module-federation/rsbuild-plugin` with script-compatible browser output and - eager singleton React shares. SSR federation is experimental and opt-in - through `server: true`. -- Supported: Vite-to-Vite federated SSR, plus browser federation for every - Vite/Rsbuild host and remote combination. -- Tested with Node 22.18, 24, and 26; Vite 8.3; Rsbuild 2.2; TanStack Start - 1.168; React 19.3. + `@module-federation/rsbuild-plugin` with script-compatible browser output, + async startup, role-aware React sharing, and `publicPath: "auto"` for + remotes, so production remotes load from their own origin. +- Rsbuild SSR federation with `server: true`: remotes ship a Node container in + `dist/client/ssr/` and advertise it in their manifest; hosts load remotes on + the server from an async-node CommonJS build that keeps `dist/server/index.js` + as its entry. +- `@module-federation/tanstack/node-entry-loader`: the runtime plugin Rsbuild + hosts use to load CommonJS remote entries on the server. +- Supported: federated SSR for Vite to Vite and Rsbuild to Rsbuild, and browser + federation for every Vite/Rsbuild host and remote combination. +- Tested in development and production builds on Node 22.18, 24, and 26, with + Vite 8.3, Rsbuild 2.2, TanStack Start 1.168, and React 19.3. diff --git a/packages/tanstack/README.md b/packages/tanstack/README.md index d021415..1c86e0d 100644 --- a/packages/tanstack/README.md +++ b/packages/tanstack/README.md @@ -6,16 +6,17 @@ the other. ## Support status -| Host | Remote | Browser federation | Federated SSR | -| ------- | ------- | ------------------ | ----------------------------- | -| Vite | Vite | Supported | Supported | -| Vite | Rsbuild | Supported | Not supported | -| Rsbuild | Vite | Supported | Not supported | -| Rsbuild | Rsbuild | Supported | Experimental (`server: true`) | +| Host | Remote | Browser federation | Federated SSR | +| ------- | ------- | ------------------ | -------------------------- | +| Vite | Vite | Supported | Supported | +| Vite | Rsbuild | Supported | Not supported | +| Rsbuild | Vite | Supported | Not supported | +| Rsbuild | Rsbuild | Supported | Supported (`server: true`) | "Federated SSR" means the remote component is rendered into the host's initial -HTML. Every supported row is covered by a browser test that hydrates the remote, -clicks it, and checks for console errors. +HTML. Every supported row is covered by browser tests against both development +servers and production builds: the remote hydrates, responds to clicks, logs no +errors, and the host falls back and recovers when the remote goes offline. ## Vite @@ -61,8 +62,7 @@ The wrapper omits the global Module Federation `target` option. TanStack Start's client and SSR environments need `@module-federation/vite` to select `web` and `node` independently. -Production builds support Vite 7 and 8. Vite 8 is required for SSR remote -loading during development. +Vite 8 is required for SSR remote loading during development. ## Rsbuild @@ -86,28 +86,55 @@ export default defineConfig({ }); ``` -The adapter returns an array of Rsbuild plugins; spread it into `plugins`. It -emits script-compatible browser output, so the manifest can be consumed by Vite -and Rsbuild hosts. `react` and `react-dom` default to eager singletons because -TanStack Start imports React synchronously. +The adapter returns an array of Rsbuild plugins; spread it into `plugins`, after +`tanstackStart()`. It emits script-compatible browser output, so the manifest +can be consumed by Vite and Rsbuild hosts. + +Defaults: + +- `react` and `react-dom` are shared singletons, `eager` for hosts (configs with + `remotes`) and lazy for remote-only builds. +- `experiments.asyncStartup: true`, so entries wait for shared modules before + running. Pass `experiments: { asyncStartup: false }` to opt out. +- A remote's browser build uses `publicPath: "auto"`, so hosts on other origins + load its chunks from the remote. TanStack Start otherwise derives `/` from + `server.base`. An absolute asset prefix, such as a CDN URL, is kept. For hosts, set `shareStrategy: "loaded-first"`. With the default `version-first` strategy, the host fetches every remote's manifest during startup, and a single offline remote fails the whole page. -### Experimental SSR federation +### SSR federation + +Pass `server: true` (or an options object) to federate on the server. Enable it +on both sides: + +- **Remotes** (`exposes`) build a Node container into `dist/client/ssr/`, next + to the browser assets, and advertise it in `mf-manifest.json` as + `ssrRemoteEntry`. Deploy `dist/client` as usual; the container holds only the + exposed modules, never the app's server code. The remote's own server output + is unchanged. +- **Hosts** (`remotes`) load remotes on the server through the Module + Federation Node runtime. TanStack Start's server output becomes async-node + CommonJS (`dist/server/index.cjs`), with `dist/server/index.js` re-exporting + it, so `rsbuild preview` and deployments that import `index.js` keep working. -Pass `server: true` (or an options object) to add an async-node CommonJS server -container. This changes TanStack Start's server output to -`dist/server/index.cjs`; start it with `node dist/server/index.cjs`. No -end-to-end SSR test covers this mode yet, and Vite hosts cannot load the -CommonJS server container. +Import the remote in the route `loader` and render it inside a Suspense or +error boundary; see `apps/rsbuild-ssr-host` in the repository. ## Known limitations -- In development, a Vite remote with `exposes` does not hydrate when opened - directly in the browser: `@module-federation/vite` waits for a host to - initialize it. Open it through a host, or use a production build. +- Server-rendered remotes arrive without their stylesheets: the remote's CSS + loads with its JavaScript, after the HTML. Expect a short unstyled flash for + remote markup in SSR responses. +- A Vite remote with `exposes` does not hydrate when opened directly as an app: + `@module-federation/vite` waits for a host to initialize it. Production builds + worked standalone up to `@module-federation/vite` 1.22.1; 1.23.0 broke them. + Loading the remote through a host is unaffected. +- In development, a Vite host's dev server exits when a Vite remote it has + server-rendered goes offline (an unhandled rejection in + `@module-federation/vite`'s dev SSR transport). Production hosts fall back and + recover. - Cross-bundler SSR is not supported. Cross-bundler remotes render after hydration. From ec50c6e186568ca69ac57a4a10489760227d38f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 6 Oct 2026 21:39:55 +0200 Subject: [PATCH 13/18] test: prove host context reaches remotes through a shared singleton Each host provides its name through a React context from the shared example-host-context package, and every remote card renders it. The browser and server scenarios assert the host's name in each card, including the Vite remote inside the Rsbuild host and server-rendered HTML. A Vite host renders before lazy shares resolve, so the Vite host provides the package eagerly; the README now states that rule for Vite hosts. --- TODO.md | 5 ++-- apps/README.md | 5 ++++ apps/host-context/package.json | 17 +++++++++++ apps/host-context/src/index.ts | 11 ++++++++ apps/rsbuild-host/package.json | 1 + apps/rsbuild-host/rsbuild.config.ts | 2 ++ apps/rsbuild-host/src/routes/__root.tsx | 5 +++- apps/rsbuild-remote/package.json | 1 + apps/rsbuild-remote/rsbuild.config.ts | 2 ++ .../src/components/StatusCard.tsx | 3 ++ apps/rsbuild-ssr-host/package.json | 1 + apps/rsbuild-ssr-host/rsbuild.config.ts | 2 ++ apps/rsbuild-ssr-host/src/routes/__root.tsx | 5 +++- apps/rsbuild-ssr-remote/package.json | 1 + apps/rsbuild-ssr-remote/rsbuild.config.ts | 2 ++ .../src/components/StatusCard.tsx | 3 ++ apps/vite-host/package.json | 1 + apps/vite-host/src/routes/__root.tsx | 5 +++- apps/vite-host/vite.config.ts | 5 ++++ apps/vite-remote/package.json | 1 + .../vite-remote/src/components/StatusCard.tsx | 3 ++ apps/vite-remote/vite.config.ts | 2 ++ packages/tanstack/README.md | 4 +++ pnpm-lock.yaml | 27 ++++++++++++++++++ test/support/examples.mjs | 28 +++++++++++++++---- 25 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 apps/host-context/package.json create mode 100644 apps/host-context/src/index.ts diff --git a/TODO.md b/TODO.md index 650f668..811ac57 100644 --- a/TODO.md +++ b/TODO.md @@ -59,6 +59,9 @@ through `test/support/examples.mjs` on every supported Node line in CI. - [x] `useState`, `useEffect`, Suspense, and error boundaries across the federation boundary (counter, hydration status, lazy remote components, `RemoteBoundary`). +- [x] React context created by each host and read by every remote, including + across bundlers and in server-rendered HTML, through the singleton + `example-host-context` package. - [x] Document `server: true`, the `ssr/` container, and the server entries. ## Open @@ -92,8 +95,6 @@ limitations. ### Shared dependencies -- [ ] Test React context created by the host and consumed by a remote, through - a shared singleton package. - [ ] Decide the React support range. TanStack Start allows React 18 and 19; only React 19 is tested. - [ ] Validate version-mismatch warnings for incompatible React majors, and diff --git a/apps/README.md b/apps/README.md index 43fe82a..930c184 100644 --- a/apps/README.md +++ b/apps/README.md @@ -3,6 +3,11 @@ Six TanStack Start applications: a browser-federation host and remote for each bundler, plus an Rsbuild pair that federates on the server. +`host-context` is a small shared package: each host provides its name through a +React context, and every remote card shows "Host: …". It is shared as a +singleton, so the right name only appears when host and remote resolve one +module instance. + | App | Port | Role | | -------------------- | ---- | ---------------------------------------------------------------------- | | `vite-host` | 3000 | Server-renders the Vite remote; loads the Rsbuild remote on the client | diff --git a/apps/host-context/package.json b/apps/host-context/package.json new file mode 100644 index 0000000..b832dfa --- /dev/null +++ b/apps/host-context/package.json @@ -0,0 +1,17 @@ +{ + "name": "example-host-context", + "version": "0.0.0", + "private": true, + "sideEffects": false, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "peerDependencies": { + "react": "^19.0.0" + }, + "devDependencies": { + "@types/react": "19.3.0", + "react": "19.3.0" + } +} diff --git a/apps/host-context/src/index.ts b/apps/host-context/src/index.ts new file mode 100644 index 0000000..806c0f5 --- /dev/null +++ b/apps/host-context/src/index.ts @@ -0,0 +1,11 @@ +import { createContext, useContext } from "react"; + +/** + * Provided by each host and read by every remote card. Shared as a Module Federation + * singleton, so a remote only sees the host's value if both resolve one module instance. + */ +export const HostContext = createContext("standalone"); + +export function useHostName() { + return useContext(HostContext); +} diff --git a/apps/rsbuild-host/package.json b/apps/rsbuild-host/package.json index b0f585b..fad3a00 100644 --- a/apps/rsbuild-host/package.json +++ b/apps/rsbuild-host/package.json @@ -14,6 +14,7 @@ "@module-federation/runtime": "2.9.2", "@tanstack/react-router": "1.170.41", "@tanstack/react-start": "1.168.60", + "example-host-context": "workspace:*", "react": "19.3.0", "react-dom": "19.3.0" }, diff --git a/apps/rsbuild-host/rsbuild.config.ts b/apps/rsbuild-host/rsbuild.config.ts index 194206a..caec5e4 100644 --- a/apps/rsbuild-host/rsbuild.config.ts +++ b/apps/rsbuild-host/rsbuild.config.ts @@ -13,6 +13,8 @@ export default defineConfig({ federation: { dts: false, name: "tanstack_rsbuild_host", + // Workspace dependencies declare "workspace:*", which is not a semver range. + shared: { "example-host-context": { requiredVersion: "*", singleton: true } }, // Resolve remotes on demand so one offline remote cannot block host startup. shareStrategy: "loaded-first", remotes: { diff --git a/apps/rsbuild-host/src/routes/__root.tsx b/apps/rsbuild-host/src/routes/__root.tsx index 2eb5fe8..c235e44 100644 --- a/apps/rsbuild-host/src/routes/__root.tsx +++ b/apps/rsbuild-host/src/routes/__root.tsx @@ -1,4 +1,5 @@ import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"; +import { HostContext } from "example-host-context"; import type { ReactNode } from "react"; import stylesHref from "../styles.css?url"; @@ -16,7 +17,9 @@ export const Route = createRootRoute({ function Root() { return ( - + + + ); } diff --git a/apps/rsbuild-remote/package.json b/apps/rsbuild-remote/package.json index 5249402..4aee25b 100644 --- a/apps/rsbuild-remote/package.json +++ b/apps/rsbuild-remote/package.json @@ -14,6 +14,7 @@ "@module-federation/runtime": "2.9.2", "@tanstack/react-router": "1.170.41", "@tanstack/react-start": "1.168.60", + "example-host-context": "workspace:*", "react": "19.3.0", "react-dom": "19.3.0" }, diff --git a/apps/rsbuild-remote/rsbuild.config.ts b/apps/rsbuild-remote/rsbuild.config.ts index 5895726..34d992e 100644 --- a/apps/rsbuild-remote/rsbuild.config.ts +++ b/apps/rsbuild-remote/rsbuild.config.ts @@ -20,6 +20,8 @@ export default defineConfig({ dev: { disableDynamicRemoteTypeHints: true }, exposes: { "./StatusCard": "./src/components/StatusCard.tsx" }, name: "tanstack_rsbuild_remote", + // Workspace dependencies declare "workspace:*", which is not a semver range. + shared: { "example-host-context": { requiredVersion: "*", singleton: true } }, }, }), ], diff --git a/apps/rsbuild-remote/src/components/StatusCard.tsx b/apps/rsbuild-remote/src/components/StatusCard.tsx index 20fedfb..ca7ea2d 100644 --- a/apps/rsbuild-remote/src/components/StatusCard.tsx +++ b/apps/rsbuild-remote/src/components/StatusCard.tsx @@ -1,13 +1,16 @@ +import { useHostName } from "example-host-context"; import { useState } from "react"; import "./StatusCard.css"; export default function StatusCard() { + const hostName = useHostName(); const [count, setCount] = useState(0); return (

Federated from Rsbuild

One component, either host.

This card is built by Rspack and shared with the Vite host.

+

Host: {hostName}

diff --git a/apps/rsbuild-ssr-host/package.json b/apps/rsbuild-ssr-host/package.json index 8d97ca8..36957bb 100644 --- a/apps/rsbuild-ssr-host/package.json +++ b/apps/rsbuild-ssr-host/package.json @@ -14,6 +14,7 @@ "@module-federation/runtime": "2.9.2", "@tanstack/react-router": "1.170.41", "@tanstack/react-start": "1.168.60", + "example-host-context": "workspace:*", "react": "19.3.0", "react-dom": "19.3.0" }, diff --git a/apps/rsbuild-ssr-host/rsbuild.config.ts b/apps/rsbuild-ssr-host/rsbuild.config.ts index 0ddc71d..04912e4 100644 --- a/apps/rsbuild-ssr-host/rsbuild.config.ts +++ b/apps/rsbuild-ssr-host/rsbuild.config.ts @@ -13,6 +13,8 @@ export default defineConfig({ federation: { dts: false, name: "tanstack_rsbuild_ssr_host", + // Workspace dependencies declare "workspace:*", which is not a semver range. + shared: { "example-host-context": { requiredVersion: "*", singleton: true } }, shareStrategy: "loaded-first", remotes: { tanstack_rsbuild_ssr_remote: diff --git a/apps/rsbuild-ssr-host/src/routes/__root.tsx b/apps/rsbuild-ssr-host/src/routes/__root.tsx index 2eb5fe8..cc68b8a 100644 --- a/apps/rsbuild-ssr-host/src/routes/__root.tsx +++ b/apps/rsbuild-ssr-host/src/routes/__root.tsx @@ -1,4 +1,5 @@ import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"; +import { HostContext } from "example-host-context"; import type { ReactNode } from "react"; import stylesHref from "../styles.css?url"; @@ -16,7 +17,9 @@ export const Route = createRootRoute({ function Root() { return ( - + + + ); } diff --git a/apps/rsbuild-ssr-remote/package.json b/apps/rsbuild-ssr-remote/package.json index cb00d74..db721b7 100644 --- a/apps/rsbuild-ssr-remote/package.json +++ b/apps/rsbuild-ssr-remote/package.json @@ -14,6 +14,7 @@ "@module-federation/runtime": "2.9.2", "@tanstack/react-router": "1.170.41", "@tanstack/react-start": "1.168.60", + "example-host-context": "workspace:*", "react": "19.3.0", "react-dom": "19.3.0" }, diff --git a/apps/rsbuild-ssr-remote/rsbuild.config.ts b/apps/rsbuild-ssr-remote/rsbuild.config.ts index 67f4701..aa7c0dc 100644 --- a/apps/rsbuild-ssr-remote/rsbuild.config.ts +++ b/apps/rsbuild-ssr-remote/rsbuild.config.ts @@ -19,6 +19,8 @@ export default defineConfig({ dts: false, exposes: { "./StatusCard": "./src/components/StatusCard.tsx" }, name: "tanstack_rsbuild_ssr_remote", + // Workspace dependencies declare "workspace:*", which is not a semver range. + shared: { "example-host-context": { requiredVersion: "*", singleton: true } }, }, // Builds an async-node server container next to the browser container, so an // Rsbuild host can render this card on the server. diff --git a/apps/rsbuild-ssr-remote/src/components/StatusCard.tsx b/apps/rsbuild-ssr-remote/src/components/StatusCard.tsx index e2810f2..5dce0b9 100644 --- a/apps/rsbuild-ssr-remote/src/components/StatusCard.tsx +++ b/apps/rsbuild-ssr-remote/src/components/StatusCard.tsx @@ -1,7 +1,9 @@ +import { useHostName } from "example-host-context"; import { useEffect, useState } from "react"; import "./StatusCard.css"; export default function StatusCard() { + const hostName = useHostName(); const [count, setCount] = useState(0); const [hydrated, setHydrated] = useState(false); @@ -17,6 +19,7 @@ export default function StatusCard() {

Server-rendered by an Rsbuild remote.

The host's server loaded this card from the remote's async-node container.

+

Host: {hostName}

diff --git a/apps/vite-host/package.json b/apps/vite-host/package.json index 212c91b..c822690 100644 --- a/apps/vite-host/package.json +++ b/apps/vite-host/package.json @@ -14,6 +14,7 @@ "@module-federation/runtime": "2.9.2", "@tanstack/react-router": "1.170.41", "@tanstack/react-start": "1.168.60", + "example-host-context": "workspace:*", "react": "19.3.0", "react-dom": "19.3.0" }, diff --git a/apps/vite-host/src/routes/__root.tsx b/apps/vite-host/src/routes/__root.tsx index 0571218..44dbaf4 100644 --- a/apps/vite-host/src/routes/__root.tsx +++ b/apps/vite-host/src/routes/__root.tsx @@ -1,4 +1,5 @@ import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"; +import { HostContext } from "example-host-context"; import type { ReactNode } from "react"; import stylesHref from "../styles.css?url"; @@ -17,7 +18,9 @@ export const Route = createRootRoute({ function RootComponent() { return ( - + + + ); } diff --git a/apps/vite-host/vite.config.ts b/apps/vite-host/vite.config.ts index 20fbedc..f87cd1d 100644 --- a/apps/vite-host/vite.config.ts +++ b/apps/vite-host/vite.config.ts @@ -8,6 +8,11 @@ export default defineConfig({ tanstackStartModuleFederation({ name: "tanstack_vite_host", dts: false, + shared: { + // The root route renders the provider synchronously, so a Vite host must provide + // the package eagerly. "workspace:*" is not a semver range, hence requiredVersion. + "example-host-context": { eager: true, requiredVersion: "*", singleton: true }, + }, remotes: { tanstack_vite_remote: { type: "module", diff --git a/apps/vite-remote/package.json b/apps/vite-remote/package.json index 756a067..87f7d6a 100644 --- a/apps/vite-remote/package.json +++ b/apps/vite-remote/package.json @@ -14,6 +14,7 @@ "@module-federation/runtime": "2.9.2", "@tanstack/react-router": "1.170.41", "@tanstack/react-start": "1.168.60", + "example-host-context": "workspace:*", "react": "19.3.0", "react-dom": "19.3.0" }, diff --git a/apps/vite-remote/src/components/StatusCard.tsx b/apps/vite-remote/src/components/StatusCard.tsx index bb5efaf..d60e898 100644 --- a/apps/vite-remote/src/components/StatusCard.tsx +++ b/apps/vite-remote/src/components/StatusCard.tsx @@ -1,7 +1,9 @@ +import { useHostName } from "example-host-context"; import { useEffect, useState } from "react"; import "./StatusCard.css"; export default function StatusCard() { + const hostName = useHostName(); const [count, setCount] = useState(0); const [hydrated, setHydrated] = useState(false); @@ -19,6 +21,7 @@ export default function StatusCard() {

Owned by the remote app

This component crossed an application boundary while React stayed a shared singleton.

+

Host: {hostName}

diff --git a/apps/vite-remote/vite.config.ts b/apps/vite-remote/vite.config.ts index 9fd7628..ca36c2a 100644 --- a/apps/vite-remote/vite.config.ts +++ b/apps/vite-remote/vite.config.ts @@ -8,6 +8,8 @@ export default defineConfig({ tanstackStartModuleFederation({ name: "tanstack_vite_remote", dts: false, + // Workspace dependencies declare "workspace:*", which is not a semver range. + shared: { "example-host-context": { requiredVersion: "*", singleton: true } }, exposes: { "./StatusCard": "./src/components/StatusCard.tsx", }, diff --git a/packages/tanstack/README.md b/packages/tanstack/README.md index 1c86e0d..4c144f8 100644 --- a/packages/tanstack/README.md +++ b/packages/tanstack/README.md @@ -58,6 +58,10 @@ Defaults: builds stay lazy. - Any entry you pass in `shared` replaces the default for that package. +A Vite host renders before lazy shared modules resolve, so mark any other shared +package the host imports from its entry or root route as `eager: true`, as the +wrapper does for React. Remotes can keep their shares lazy. + The wrapper omits the global Module Federation `target` option. TanStack Start's client and SSR environments need `@module-federation/vite` to select `web` and `node` independently. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a3eb42..4d6a437 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -207,6 +207,15 @@ importers: specifier: 7.0.2 version: 7.0.2 + apps/host-context: + devDependencies: + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + react: + specifier: 19.3.0 + version: 19.3.0 + apps/rsbuild-host: dependencies: '@module-federation/runtime': @@ -218,6 +227,9 @@ importers: '@tanstack/react-start': specifier: 1.168.60 version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + example-host-context: + specifier: workspace:* + version: link:../host-context react: specifier: 19.3.0 version: 19.3.0 @@ -261,6 +273,9 @@ importers: '@tanstack/react-start': specifier: 1.168.60 version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + example-host-context: + specifier: workspace:* + version: link:../host-context react: specifier: 19.3.0 version: 19.3.0 @@ -304,6 +319,9 @@ importers: '@tanstack/react-start': specifier: 1.168.60 version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + example-host-context: + specifier: workspace:* + version: link:../host-context react: specifier: 19.3.0 version: 19.3.0 @@ -347,6 +365,9 @@ importers: '@tanstack/react-start': specifier: 1.168.60 version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + example-host-context: + specifier: workspace:* + version: link:../host-context react: specifier: 19.3.0 version: 19.3.0 @@ -390,6 +411,9 @@ importers: '@tanstack/react-start': specifier: 1.168.60 version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + example-host-context: + specifier: workspace:* + version: link:../host-context react: specifier: 19.3.0 version: 19.3.0 @@ -430,6 +454,9 @@ importers: '@tanstack/react-start': specifier: 1.168.60 version: 1.168.60(@rsbuild/core@2.2.11(@module-federation/runtime-tools@2.9.2))(@rspack/core@2.2.8(@module-federation/runtime-tools@2.9.2)(@swc/helpers@0.5.23))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + example-host-context: + specifier: workspace:* + version: link:../host-context react: specifier: 19.3.0 version: 19.3.0 diff --git a/test/support/examples.mjs b/test/support/examples.mjs index 1368b99..a5620d8 100644 --- a/test/support/examples.mjs +++ b/test/support/examples.mjs @@ -52,9 +52,9 @@ export function describeExamples(script, { outageRemotes = remotes } = {}) { }); test("SSR hosts render remote markup for concurrent first requests", async () => { - for (const [host, marker] of [ - ["viteHost", cards.vite], - ["rsbuildSsrHost", cards.rsbuildSsr], + for (const [host, marker, hostName] of [ + ["viteHost", cards.vite, "Vite host"], + ["rsbuildSsrHost", cards.rsbuildSsr, "Rsbuild SSR host"], ]) { const responses = await Promise.all( Array.from({ length: 8 }, () => fetch(apps[host].url).then(readResponse)), @@ -89,6 +89,7 @@ export function describeExamples(script, { outageRemotes = remotes } = {}) { await page.getByText("Hydrated on the host").waitFor({ timeout: 20_000 }); await assertInteractive(page, cards.vite); await assertInteractive(page, cards.rsbuild); + await assertHostContext(page, [cards.vite, cards.rsbuild], "Vite host"); }); }); @@ -96,6 +97,7 @@ export function describeExamples(script, { outageRemotes = remotes } = {}) { await withPage(apps.rsbuildHost.url, async (page) => { await assertInteractive(page, cards.vite); await assertInteractive(page, cards.rsbuild); + await assertHostContext(page, [cards.vite, cards.rsbuild], "Rsbuild host"); }); }); @@ -103,12 +105,20 @@ export function describeExamples(script, { outageRemotes = remotes } = {}) { await withPage(apps.rsbuildSsrHost.url, async (page) => { await page.getByText("Hydrated on the host").waitFor({ timeout: 20_000 }); await assertInteractive(page, cards.rsbuildSsr); + await assertHostContext(page, [cards.rsbuildSsr], "Rsbuild SSR host"); }); }); test("Rsbuild remotes work as standalone TanStack Start apps", async () => { - await withPage(apps.rsbuildRemote.url, (page) => assertInteractive(page, cards.rsbuild)); - await withPage(apps.rsbuildSsrRemote.url, (page) => assertInteractive(page, cards.rsbuildSsr)); + for (const [key, card] of [ + ["rsbuildRemote", cards.rsbuild], + ["rsbuildSsrRemote", cards.rsbuildSsr], + ]) { + await withPage(apps[key].url, async (page) => { + await assertInteractive(page, card); + await assertHostContext(page, [card], "standalone"); + }); + } }); // @module-federation/vite never initializes a container with `exposes` when it is opened @@ -212,6 +222,14 @@ function manifestAssets(manifest) { return [...new Set([...entries, ...files])]; } +/** Proves each remote card reads the context its host provides through a shared module. */ +async function assertHostContext(page, cardTexts, hostName) { + for (const cardText of cardTexts) { + const card = page.locator("article", { hasText: cardText }); + await card.getByText(`Host: ${hostName}`).waitFor({ timeout: 5_000 }); + } +} + /** Clicks a remote card's counter and proves the remote's state updates on the host. */ async function assertInteractive(page, cardText) { const button = page From 9ad37c1a697ab7408544299d3908393065eb7c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 6 Oct 2026 21:42:10 +0200 Subject: [PATCH 14/18] docs: require Vite 8 for federated SSR Checked by hand with Vite 7.3.7 and @vitejs/plugin-react 5.2: browser federation works, but production SSR hosts fail to load remotes because the server bundle keeps a virtual: import that Node rejects. --- README.md | 2 +- TODO.md | 11 ++++++++--- packages/tanstack/README.md | 5 ++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ceefa4d..0149281 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ and SSR environments from one Vite configuration, so the wrapper leaves target selection to `@module-federation/vite` for each environment. The package supports Node 22.18+, 24.11+, and 26+, plus TanStack Start -1.167.43+. It is tested with Vite 8, which development SSR hosts require. +1.167.43+. It is tested with Vite 8, which federated SSR requires; Vite 7 supports browser federation only. The package root remains a Vite compatibility export. New Vite projects can import `/vite` explicitly. Rsbuild projects must import `/rsbuild`: diff --git a/TODO.md b/TODO.md index 811ac57..d398f9d 100644 --- a/TODO.md +++ b/TODO.md @@ -103,9 +103,14 @@ limitations. ### Versions -- [ ] Vite 7: `@vitejs/plugin-react` 6 requires Vite 8, so a Vite 7 run needs - its own dependency set (`@vitejs/plugin-react` 5). Until then the docs - claim Vite 8 only, while the peer range still allows Vite 7. +- [x] Vite 7 checked by hand with `@vitejs/plugin-react` 5.2 and Vite 7.3.7: + browser federation works (hydration, interaction, host context). +- [ ] Vite 7 production SSR: the host's server bundle keeps a `virtual:` import + that Node rejects (`ERR_UNSUPPORTED_ESM_URL_SCHEME`), so the remote falls + back to client rendering. Report to `@module-federation/vite`; until then + the docs require Vite 8 for federated SSR. +- [ ] Add a CI job with Vite 7 dependencies once the SSR path works, or narrow + the `vite` peer range. ### Error reporting diff --git a/packages/tanstack/README.md b/packages/tanstack/README.md index 4c144f8..bb40112 100644 --- a/packages/tanstack/README.md +++ b/packages/tanstack/README.md @@ -66,7 +66,10 @@ The wrapper omits the global Module Federation `target` option. TanStack Start's client and SSR environments need `@module-federation/vite` to select `web` and `node` independently. -Vite 8 is required for SSR remote loading during development. +Federated SSR requires Vite 8. With Vite 7 (and `@vitejs/plugin-react` 5), +browser federation works, but production SSR hosts fail to load remotes +(`ERR_UNSUPPORTED_ESM_URL_SCHEME` for a `virtual:` import) and fall back to +client rendering. CI tests Vite 8 only. ## Rsbuild From d8c47c021dd353ce996f6d7be060e090f04d0a77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Wed, 7 Oct 2026 00:26:33 +0200 Subject: [PATCH 15/18] feat: federate SSR across bundlers and recover from remote outages Every Vite and Rsbuild host and remote pair now renders on the server, in development and production: - Rsbuild hosts hand Vite remotes' ES module entries to @module-federation/vite's SSR loader, loaded with Node's require so it also works in Rsbuild's dev runner. The host's shared modules are registered under their node_modules paths, so a Vite dev server's externalized React is the host's React. - Vite hosts load Rsbuild server containers, which move to remoteEntry.ssr.cjs next to the manifest (chunks stay in ssr/). In development, saved remote entries are imported through Node instead of Vite's module runner, which evaluated CommonJS as ES modules. Hosts now render a remote again after a failed first load, such as a host started during an outage. Four caches kept the failure: the Vite server wrapper's single load attempt, Vite's dev module runner caching the wrapper's rejected then export, Rspack's module cache keeping a failed remote module's empty exports, and React.lazy. The adapters patch the first three; @module-federation/tanstack/runtime adds lazyRemote for the last. The runtime subpath also exports getRemoteStylesheets, which reads an expose's CSS from the remote manifest for the route head, so server-rendered remote markup is styled before JavaScript loads. The node entry loader throws RemoteEntryError with the failing phase and the original cause, and bounds its fetches. The Vite adapter requires Vite 8 and stops with an error on older versions, and handles the unawaited __mf_remote_pending export that exited dev servers when a remote went offline. The example scenarios cover cross-bundler SSR, styling with JavaScript disabled, and hosts started during an outage. The docs describe the server rendering pattern, the React 19 requirement, and the first npm publish. --- README.md | 34 +- TODO.md | 226 ++++++------- apps/README.md | 45 +-- .../src/components/StatusCard.css | 8 +- .../src/components/StatusCard.tsx | 2 +- apps/rsbuild-ssr-host/package.json | 4 +- apps/rsbuild-ssr-host/rsbuild.config.ts | 3 +- apps/rsbuild-ssr-host/src/remote.d.ts | 7 + apps/rsbuild-ssr-host/src/routes/index.tsx | 46 ++- apps/rsbuild-ssr-remote/rsbuild.config.ts | 2 +- .../src/components/StatusCard.css | 8 +- .../src/components/StatusCard.tsx | 2 +- apps/vite-host/src/remote.d.ts | 7 + apps/vite-host/src/routes/index.tsx | 41 ++- apps/vite-host/vite.config.ts | 5 + docs/RELEASING.md | 38 ++- packages/tanstack/CHANGELOG.md | 25 +- packages/tanstack/README.md | 136 ++++++-- packages/tanstack/package.json | 17 +- packages/tanstack/rslib.config.ts | 2 + packages/tanstack/src/node-entry-loader.ts | 298 ++++++++++++++--- packages/tanstack/src/rsbuild.ts | 102 ++++-- packages/tanstack/src/runtime.ts | 96 ++++++ packages/tanstack/src/vite-compat.ts | 149 +++++++++ packages/tanstack/src/vite.ts | 35 +- pnpm-lock.yaml | 9 + test/example-runtime.test.mjs | 10 +- test/release-artifacts.test.mjs | 39 ++- test/release-contracts.test.mjs | 293 ++++++++++++++++- test/runtime.test.mjs | 307 ++++++++++++++++++ test/support/examples.mjs | 182 ++++++++--- 31 files changed, 1791 insertions(+), 387 deletions(-) create mode 100644 packages/tanstack/src/runtime.ts create mode 100644 packages/tanstack/src/vite-compat.ts create mode 100644 test/runtime.test.mjs diff --git a/README.md b/README.md index 0149281..5d8b7d7 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ and SSR environments from one Vite configuration, so the wrapper leaves target selection to `@module-federation/vite` for each environment. The package supports Node 22.18+, 24.11+, and 26+, plus TanStack Start -1.167.43+. It is tested with Vite 8, which federated SSR requires; Vite 7 supports browser federation only. +1.167.43+. The Vite adapter requires Vite 8. The package root remains a Vite compatibility export. New Vite projects can import `/vite` explicitly. Rsbuild projects must import `/rsbuild`: @@ -91,19 +91,25 @@ gives remotes `publicPath: "auto"` so other origins load their chunks from the remote. Hosts should use `shareStrategy: "loaded-first"` so one offline remote cannot fail startup. -SSR federation is opt-in with `server: true` on both sides. A remote builds a -Node container into `dist/client/ssr/` and advertises it in its manifest; a host -loads remotes on the server from an async-node CommonJS server build. See the +Rsbuild SSR federation is opt-in with `server: true`. A remote builds a Node +container, `remoteEntry.ssr.cjs`, next to its browser entry and advertises it in +its manifest, so Vite and Rsbuild hosts can render it on the server. A host +loads remotes on the server from an async-node CommonJS server build, and +renders Vite remotes too when `@module-federation/vite` is installed. See the [package README](packages/tanstack/README.md#ssr-federation). +`@module-federation/tanstack/runtime` exports `getRemoteStylesheets`, which +reads a remote's stylesheets from its manifest so a route can put them in the +server response's ``. + ## Support status -| Host | Remote | Browser federation | Federated SSR | -| ------- | ------- | ------------------ | -------------------------- | -| Vite | Vite | Supported | Supported | -| Vite | Rsbuild | Supported | Not supported | -| Rsbuild | Vite | Supported | Not supported | -| Rsbuild | Rsbuild | Supported | Supported (`server: true`) | +| Host | Remote | Browser federation | Federated SSR | +| ------- | ------- | ------------------ | ---------------------------------- | +| Vite | Vite | Supported | Supported | +| Vite | Rsbuild | Supported | Supported (remote: `server: true`) | +| Rsbuild | Vite | Supported | Supported (host: `server: true`) | +| Rsbuild | Rsbuild | Supported | Supported (both: `server: true`) | Known limitations are listed in the [package README](packages/tanstack/README.md#known-limitations), and @@ -129,7 +135,9 @@ http://127.0.0.1:3003, or the Rsbuild SSR host at http://127.0.0.1:3005. `pnpm test` builds everything, then runs the six apps twice, as development servers and as production builds, and drives every host in headless Chromium. -It checks server-rendered remote markup (including concurrent first requests), -hydration, remote interactivity, console errors, manifest asset reachability, -and the fallback and recovery when remotes go offline. Run +It checks server-rendered remote markup from both bundlers (including +concurrent first requests), remote stylesheets in the server response, +hydration, remote interactivity, shared React context, console errors, +manifest asset reachability, and the fallback and recovery when remotes go +offline. Run `pnpm exec playwright install chromium` once before the first local test run. diff --git a/TODO.md b/TODO.md index d398f9d..a513544 100644 --- a/TODO.md +++ b/TODO.md @@ -7,165 +7,169 @@ Assessment date: 2026-10-06. ## Support boundary -| Host | Remote | Browser federation | Federated SSR | Proof (development and production) | -| ------- | ------- | ------------------ | ------------- | -------------------------------------------------------------------- | -| Vite | Vite | Supported | Supported | Remote markup in the host's initial HTML, hydration, outage/recovery | -| Vite | Rsbuild | Supported | Not supported | The Rsbuild remote loads in the browser after hydration | -| Rsbuild | Vite | Supported | Not supported | The Vite remote loads through the browser runtime | -| Rsbuild | Rsbuild | Supported | Supported | `rsbuild-ssr-host` renders `rsbuild-ssr-remote` on the server | +| Host | Remote | Browser federation | Federated SSR | Proof (development and production) | +| ------- | ------- | ------------------ | ------------- | --------------------------------------------------------------------- | +| Vite | Vite | Supported | Supported | `vite-host` renders `vite-remote` on the server | +| Vite | Rsbuild | Supported | Supported | `vite-host` renders `rsbuild-ssr-remote` on the server | +| Rsbuild | Vite | Supported | Supported | `rsbuild-ssr-host` renders `vite-remote` on the server | +| Rsbuild | Rsbuild | Supported | Supported | `rsbuild-ssr-host` renders `rsbuild-ssr-remote` on the server | +| Any | Any | Supported | — | `rsbuild-host` and `vite-host` load browser-only remotes on hydration | `test/example-runtime.test.mjs` (development servers) and `test/example-production.test.mjs` (production builds) run the same scenarios -through `test/support/examples.mjs` on every supported Node line in CI. +through `test/support/examples.mjs` on every supported Node line in CI: remote +markup in the initial HTML for eight concurrent first requests, remote +stylesheets in the response (builds), hydration without console errors, remote +interactivity, one React instance and shared context, fallback and recovery +while every remote is offline, and hosts that start during an outage rendering +the remotes once they return. ## Done for 0.1.0 -### Rsbuild-to-Rsbuild SSR +### Rsbuild remotes on the server - [x] Default `experiments.asyncStartup: true` for browser and server federation, preserving an explicit `false`. - [x] Share `react` and `react-dom` as singletons: eager for hosts, lazy for remotes and server containers, so remotes cannot replace a host's React. -- [x] Build a remote's Node container in a dedicated `mf-server` environment, - emitted to `dist/client/ssr/` with the browser assets. It contains only - the exposed modules; the app's server bundle is never published. -- [x] Advertise the container in the browser manifest as `ssrRemoteEntry`, - which Module Federation otherwise only does for Rslib's `dual` target. +- [x] Build a remote's Node container in a dedicated `mf-server` environment: + `remoteEntry.ssr.cjs` next to `remoteEntry.js`, chunks in `ssr/`. It + contains only the exposed modules; the app's server bundle is never + published. +- [x] Advertise the container in the browser manifest as a `commonjs-module` + `ssrRemoteEntry`, which Module Federation otherwise only does for + Rslib's `dual` target. - [x] Give remotes `publicPath: "auto"`. TanStack Start derives `/` from `server.base`, which made production remotes request their chunks from the host's origin, and made Node hosts fail to fetch them. +- [x] Make the container loadable by `@module-federation/vite`: a root-level + file (its SSR loader joins `path` and `name` without a separator), a + `.cjs` name (it treats every `.ssr.js` URL as an ES module), and static + `init`/`get` re-exports so ES module importers see named exports. + +### Rsbuild hosts on the server + - [x] Load remotes in a host's server bundle as async-node CommonJS, keep `dist/server/index.js` as an ES module that re-exports it, and write the server output to disk in development so async-node chunks match the in-memory entry. -- [x] Load CommonJS remote entries with `require`-based Node built-ins - (`@module-federation/tanstack/node-entry-loader`). Rsbuild's development - runner cannot evaluate Module Federation's `import("vm")`. -- [x] Dedicated fixtures: `apps/rsbuild-ssr-remote` and `apps/rsbuild-ssr-host`, - importing the remote in a route loader. -- [x] Acceptance: initial HTML contains remote markup (not a placeholder), for - eight concurrent first requests; hydration without warnings; the remote - stays interactive; one React instance; no `RUNTIME-*` or chunk errors; - the manifest declares a `commonjs-module` server entry; every - manifest-referenced asset returns 200; the container has no - build-machine paths. - -### Hardening - +- [x] `@module-federation/tanstack/node-entry-loader` compiles CommonJS + containers as CommonJS modules, so their `import()` calls work in + Rsbuild's development runner without experimental Node flags or + warnings. +- [x] Vite remotes: the loader hands ES module entries to + `@module-federation/vite`'s SSR loader, loaded with Node's `require` so + its imports bypass Rsbuild's runner. It registers the host's loaded + shared modules under their node_modules paths, so React imported from + node_modules by a Vite dev server's module runner is the host's React. +- [x] Failures throw `RemoteEntryError` with `phase` (`fetch`, `evaluate`, + `loader`), the remote, the URL, and the original `cause`, instead of + falling through to Module Federation's Node loader, which cannot load + ES modules without Node flags. + +### Vite hosts + +- [x] Support Vite 8 only, like TanStack Start's examples (Vite ^8.0.14 with + `@vitejs/plugin-react` 6, which requires Vite 8). The `vite` peer range + is `^8.0.0`, and the adapter stops with an error on older versions: on + Vite 7, `@module-federation/vite` hangs server-side remote loads in + development and emits an unresolved `virtual:` import in production. +- [x] Work around an unhandled rejection in `@module-federation/vite` that + exited a Vite host's dev server when a server-rendered remote went + offline: the adapter handles the `__mf_remote_pending` export. +- [x] Work around `@module-federation/vite` importing saved remote entries + through Vite's dev module runner, which evaluates CommonJS containers as + ES modules: the adapter imports them through Node in development. + +### SSR quality and hardening + +- [x] `getRemoteStylesheets` (`@module-federation/tanstack/runtime`) reads an + expose's stylesheets from the remote manifest for the route `head`, so + server-rendered remote markup is styled before JavaScript loads. The + production suite checks it with JavaScript disabled. - [x] Production builds (`vite preview`, `rsbuild preview`) run the same browser scenarios as development servers. - [x] Hosts render a fallback, not a 500 or a blank page, while remotes are - offline, and render remote markup again once they return. + offline, and render remote markup again once they return, in development + and production. +- [x] Hosts recover from a remote that failed its first load, such as a host + started during an outage. Four caches kept the failure, each now + cleared: the Vite server wrapper's single load attempt, Vite's dev module + runner caching the wrapper's rejected `then` export, Rspack's module + cache (a failed remote module kept empty exports; Rspack's + `strictModuleExceptionHandling` caches the error instead, so the adapter + removes the module from the cache), and `React.lazy` (`lazyRemote`). - [x] `useState`, `useEffect`, Suspense, and error boundaries across the federation boundary (counter, hydration status, lazy remote components, `RemoteBoundary`). - [x] React context created by each host and read by every remote, including across bundlers and in server-rendered HTML, through the singleton `example-host-context` package. -- [x] Document `server: true`, the `ssr/` container, and the server entries. ## Open ### Upstream issues -Report these to `module-federation/vite`; they are documented as known -limitations. - -- [ ] A Vite remote with `exposes` does not hydrate when opened directly in - development: the plugin forces host-driven init for exposing containers - (`forceClientInjected`), so the remote's own React shares never resolve. - 1.22 hangs; 1.23.2 throws `_jsxDEV is not a function`. -- [ ] Since 1.23.0, the same remote also fails standalone in production - (`x is not a function` from the `react-dom/client` share). Bisected: - 1.22.0 and 1.22.1 hydrate; 1.23.0, 1.23.1, and 1.23.2 do not. -- [ ] In development, a Vite host's dev server exits with an unhandled - `TypeError: fetch failed` (ECONNREFUSED) after a Vite remote it has - server-rendered through the ModuleRunner transport (`/__mf_runner__`) - goes offline. Production hosts are unaffected. +Report these to `module-federation/vite` (the first three are worked around +here) and `module-federation/core`. + +- [ ] `@module-federation/vite`: `__mf_remote_pending` is exported but never + awaited, so a remote outage becomes an unhandled rejection in a dev + server. Worked around by `remotePendingPlugin`. +- [ ] `@module-federation/vite`: `getSSREntry` returns `type: "module"` for any + `.ssr.js` URL, ignoring the remote's declared format. Worked around with + the `.cjs` container name. +- [ ] `@module-federation/vite`: in development, the SSR loader imports saved + remote entries through Vite's module runner. Worked around by + `nativeTempModuleImportPlugin`. +- [ ] `@module-federation/vite`: a server wrapper makes one load attempt, so a + remote that fails its first load fails for the life of the process. In + development, the wrapper's `then` export also makes Vite's module runner + cache the rejection. Worked around by `remotePendingPlugin`. Browser + wrappers make one attempt too, which a page reload clears. +- [ ] `@module-federation/webpack-bundler-runtime`: a remote module that + failed stays in the module cache with empty exports, although the load + itself is retried. Worked around by `remoteRetryPlugin`. +- [ ] `@module-federation/vite`: a Vite remote with `exposes` does not hydrate + when opened directly in development (`forceClientInjected`), and since + 1.23.0 not in production either (`x is not a function` from the + `react-dom/client` share; 1.22.0 and 1.22.1 work). Fixed on `main` + (#1413, #1415, #1420); update when a release ships. +- [ ] `@module-federation/node`: a container without an absolute public path + logs `Backup remote entry found` for every chunk it loads through a Vite + host. ### SSR quality -- [ ] Include the remote's stylesheets in SSR responses. Neither SSR host emits - the remote's CSS links, so remote markup is unstyled until its JavaScript - loads. This likely needs a helper that reads `exposes[].assets.css` from - the remote snapshot and feeds TanStack Start's route `head`. - [ ] Revalidate a changed remote manifest and server entry without restarting the host, and prove no stale container or shared module is served. -- [ ] Test cyclic imports in a server remote graph. + `@module-federation/vite` has `revalidate()` and `maxAgeMs` for its own + loader; the Module Federation runtime caches containers for the life of + the process. +- [ ] Test cyclic imports across chunks in a server remote graph. +- [ ] In development, a Vite remote's server modules import React from the files + its dev server resolves, so host and remote must resolve React to the + same files (true in a monorepo). Production builds take React from the + share scope. ### Shared dependencies -- [ ] Decide the React support range. TanStack Start allows React 18 and 19; - only React 19 is tested. +- [x] React range: React 19. TanStack Start 1.168 accepts React 18, but its + Rsbuild server build fails with it: TanStack Router imports React's + `use`, which React 18 lacks. Vite apps build with React 18; revisit when + TanStack Start supports it on both bundlers. - [ ] Validate version-mismatch warnings for incompatible React majors, and that `import: false` fails at startup when the host cannot provide a - package. - -### Versions - -- [x] Vite 7 checked by hand with `@vitejs/plugin-react` 5.2 and Vite 7.3.7: - browser federation works (hydration, interaction, host context). -- [ ] Vite 7 production SSR: the host's server bundle keeps a `virtual:` import - that Node rejects (`ERR_UNSUPPORTED_ESM_URL_SCHEME`), so the remote falls - back to client rendering. Report to `@module-federation/vite`; until then - the docs require Vite 8 for federated SSR. -- [ ] Add a CI job with Vite 7 dependencies once the SSR path works, or narrow - the `vite` peer range. + package. With mismatched React majors, the federation runtime already + warns (`Version 18.3.1 ... does not satisfy the requirement ... 19.3.0`). ### Error reporting -- [ ] Preserve the original network or execution error, and distinguish - manifest, entry, chunk, and shared-dependency failures. +- [ ] Distinguish manifest, chunk, and shared-dependency failures the way + `RemoteEntryError` does for server entries. - [ ] Add optional observability reports for raw runtime codes that do not contain enough context. -## Cross-bundler SSR (after 0.1.0) - -The two server entry formats differ: - -- Vite emits an ESM SSR entry and an ESM dependency graph. -- The Rsbuild adapter emits a CommonJS container (`ssr/remoteEntry.js`) with - async-node chunks. - -The Vite SSR loader can `require()` a local CommonJS entry. For an HTTP -CommonJS entry, `createRequire()` cannot load the URL, and the loader falls -through to paths designed around ESM source. In the other direction, Core's Node -runtime has ESM loading support, but nothing proves that an Rsbuild host can -load Vite's manifest-declared ESM SSR entry, preserve the host's React -instance, and hydrate the result. Both directions need changes in -`@module-federation/vite` or Core. - -### Vite host with an Rsbuild SSR remote - -Choose one server-entry strategy: - -- Option A, emit an ESM SSR entry from Rsbuild: decide how the manifest - identifies both server formats (it has one `ssrRemoteEntry` slot), keep the - chunks reachable over HTTP, and verify Vite's temporary-file and VM - strategies can link the graph. -- Option B, teach the Vite loader to evaluate HTTP CommonJS containers: a - bounded loader that reuses Core's Node chunk loading, resolves async chunks - from the remote public path, preserves module-cache identity for singletons, - applies the ESM loader's fetch timeout and body limits, and avoids unbounded - `eval`. - -Acceptance: Vite 8 development and production render and hydrate the Rsbuild -remote, remote CSS arrives without a flash, and manifest revalidation replaces -a changed remote without restarting the host. - -### Rsbuild host with a Vite SSR remote - -- [ ] Confirm that Core's Node runtime selects `metaData.ssrRemoteEntry` with - type `module`. -- [ ] Load Vite's transitive ESM chunks without Node network-import flags or - `--experimental-vm-modules`. -- [ ] Map shared package imports to the Rsbuild host share scope. -- [ ] Verify development against Vite's `/__mf_ssr__/` and ModuleRunner - endpoints, and production against the built `remoteEntry.ssr.js` graph. - -Acceptance: the Rsbuild host's initial HTML contains the Vite remote, hydration -preserves it, and a remote outage returns a fallback instead of a 500. - ## Release work for each new SSR combination - [ ] Update the support table only after the combination passes the diff --git a/apps/README.md b/apps/README.md index 930c184..25b1084 100644 --- a/apps/README.md +++ b/apps/README.md @@ -1,21 +1,22 @@ # TanStack Start examples -Six TanStack Start applications: a browser-federation host and remote for each -bundler, plus an Rsbuild pair that federates on the server. +Six TanStack Start applications: a host and a remote for each bundler, plus an +Rsbuild pair that federates on the server. Both SSR hosts render a Vite remote +and an Rsbuild remote into their initial HTML. `host-context` is a small shared package: each host provides its name through a React context, and every remote card shows "Host: …". It is shared as a singleton, so the right name only appears when host and remote resolve one module instance. -| App | Port | Role | -| -------------------- | ---- | ---------------------------------------------------------------------- | -| `vite-host` | 3000 | Server-renders the Vite remote; loads the Rsbuild remote on the client | -| `vite-remote` | 3001 | Exposes `./StatusCard`, rendered into the Vite host's initial HTML | -| `rsbuild-remote` | 3002 | Exposes `./StatusCard` through a browser manifest | -| `rsbuild-host` | 3003 | Loads both remotes with `loadRemote()` after hydration | -| `rsbuild-ssr-remote` | 3004 | Exposes `./StatusCard` with a Node container (`server: true`) | -| `rsbuild-ssr-host` | 3005 | Server-renders the Rsbuild SSR remote (`server: true`) | +| App | Port | Role | +| -------------------- | ---- | -------------------------------------------------------------------------------- | +| `vite-host` | 3000 | Server-renders the Vite and Rsbuild SSR remotes; loads `rsbuild-remote` later | +| `vite-remote` | 3001 | Exposes `./StatusCard`, rendered into both SSR hosts' initial HTML | +| `rsbuild-remote` | 3002 | Exposes `./StatusCard` through a browser manifest | +| `rsbuild-host` | 3003 | Loads both browser remotes with `loadRemote()` after hydration | +| `rsbuild-ssr-remote` | 3004 | Exposes `./StatusCard` with a Node container (`server: true`) for both SSR hosts | +| `rsbuild-ssr-host` | 3005 | Server-renders the Rsbuild SSR and Vite remotes (`server: true`) | From the repository root: @@ -31,22 +32,27 @@ server show "Rendered on the server" until they hydrate. ## What each host shows -- **Vite host.** The route loader imports the Vite remote, so its card is in - the server response and hydrates in place. The Rsbuild card mounts after - hydration. Both remotes sit behind `RemoteBoundary`, an error boundary with a - fallback card, and the loader ignores a failed import, so an offline remote - never fails the route. +- **Vite host.** The route loader imports the Vite remote and the Rsbuild SSR + remote, so both cards are in the server response and hydrate in place. The + loader also reads each remote's stylesheets with `getRemoteStylesheets`, and + the route's `head` links them, so the cards are styled before JavaScript + runs. The `rsbuild-remote` card mounts after hydration. The cards are + `lazyRemote` components, which load again after a failure. Every remote sits + behind `RemoteBoundary`, an error boundary with a fallback card, and the + loader ignores failed imports, so an offline remote never fails the route. - **Rsbuild host.** `RemoteCardSlot` calls `loadRemote()` in an effect and shows "Remote unavailable" when a remote fails. The host uses `shareStrategy: "loaded-first"`, so it does not fetch every remote's manifest at startup. -- **Rsbuild SSR host.** Same pattern as the Vite host, on Rsbuild: the loader - imports the remote on the server through the Module Federation Node runtime, - which fetches the container from the remote's `ssr/` directory. +- **Rsbuild SSR host.** Same pattern as the Vite host, on Rsbuild. The + Module Federation Node runtime loads the Rsbuild remote's + `remoteEntry.ssr.cjs` container, and hands the Vite remote's ES module entry + to `@module-federation/vite`'s SSR loader, which is why this app depends on + `@module-federation/vite` and `vite`. Stop a remote while the hosts are open and reload them: each host keeps rendering and shows its fallback, then renders the remote again once it is -back. +back. The same holds for a host started before its remotes. ## Notes @@ -58,4 +64,3 @@ back. - Opening the Vite remote directly (port 3001) server-renders the page but does not hydrate it: `@module-federation/vite` waits for a host to initialize a container with `exposes`. It hydrates when loaded through a host. -- Cross-bundler remotes render on the client only. diff --git a/apps/rsbuild-remote/src/components/StatusCard.css b/apps/rsbuild-remote/src/components/StatusCard.css index f055223..42e1cc1 100644 --- a/apps/rsbuild-remote/src/components/StatusCard.css +++ b/apps/rsbuild-remote/src/components/StatusCard.css @@ -1,21 +1,21 @@ -.remote-card { +.rsbuild-card { border: 1px solid #83502a; background: #291c13; color: #fff3e8; padding: 30px; } -.remote-card .eyebrow { +.rsbuild-card .eyebrow { color: #ffbc7c; font-family: monospace; text-transform: uppercase; } -.remote-card h2 { +.rsbuild-card h2 { font-size: 2rem; } -.remote-card button { +.rsbuild-card button { background: #ffbc7c; border: 0; cursor: pointer; diff --git a/apps/rsbuild-remote/src/components/StatusCard.tsx b/apps/rsbuild-remote/src/components/StatusCard.tsx index ca7ea2d..b5b37cc 100644 --- a/apps/rsbuild-remote/src/components/StatusCard.tsx +++ b/apps/rsbuild-remote/src/components/StatusCard.tsx @@ -6,7 +6,7 @@ export default function StatusCard() { const hostName = useHostName(); const [count, setCount] = useState(0); return ( -
+

Federated from Rsbuild

One component, either host.

This card is built by Rspack and shared with the Vite host.

diff --git a/apps/rsbuild-ssr-host/package.json b/apps/rsbuild-ssr-host/package.json index 36957bb..60773f7 100644 --- a/apps/rsbuild-ssr-host/package.json +++ b/apps/rsbuild-ssr-host/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@module-federation/runtime": "2.9.2", + "@module-federation/vite": "1.23.2", "@tanstack/react-router": "1.170.41", "@tanstack/react-start": "1.168.60", "example-host-context": "workspace:*", @@ -26,6 +27,7 @@ "@types/node": "26.6.4", "@types/react": "19.3.0", "@types/react-dom": "19.3.0", - "typescript": "7.0.2" + "typescript": "7.0.2", + "vite": "8.3.2" } } diff --git a/apps/rsbuild-ssr-host/rsbuild.config.ts b/apps/rsbuild-ssr-host/rsbuild.config.ts index 04912e4..f02e28e 100644 --- a/apps/rsbuild-ssr-host/rsbuild.config.ts +++ b/apps/rsbuild-ssr-host/rsbuild.config.ts @@ -19,9 +19,10 @@ export default defineConfig({ remotes: { tanstack_rsbuild_ssr_remote: "tanstack_rsbuild_ssr_remote@http://127.0.0.1:3004/mf-manifest.json", + tanstack_vite_remote: "tanstack_vite_remote@http://127.0.0.1:3001/mf-manifest.json", }, }, - // Loads remotes on the server through the Module Federation Node runtime. + // Loads remotes on the server. Vite remotes also need @module-federation/vite installed. server: true, }), ], diff --git a/apps/rsbuild-ssr-host/src/remote.d.ts b/apps/rsbuild-ssr-host/src/remote.d.ts index 9d8ea20..c46c4d0 100644 --- a/apps/rsbuild-ssr-host/src/remote.d.ts +++ b/apps/rsbuild-ssr-host/src/remote.d.ts @@ -4,3 +4,10 @@ declare module "tanstack_rsbuild_ssr_remote/StatusCard" { const StatusCard: ComponentType; export default StatusCard; } + +declare module "tanstack_vite_remote/StatusCard" { + import type { ComponentType } from "react"; + + const StatusCard: ComponentType; + export default StatusCard; +} diff --git a/apps/rsbuild-ssr-host/src/routes/index.tsx b/apps/rsbuild-ssr-host/src/routes/index.tsx index 25d8f4a..fa5dc6e 100644 --- a/apps/rsbuild-ssr-host/src/routes/index.tsx +++ b/apps/rsbuild-ssr-host/src/routes/index.tsx @@ -1,13 +1,38 @@ +import { getRemoteStylesheets, lazyRemote } from "@module-federation/tanstack/runtime"; import { createFileRoute } from "@tanstack/react-router"; -import { lazy } from "react"; import { RemoteBoundary } from "../components/RemoteBoundary"; -const RemoteStatusCard = lazy(() => import("tanstack_rsbuild_ssr_remote/StatusCard")); +// Unlike React.lazy, lazyRemote loads again after a failure, so a server that hit a remote +// outage renders the remote once it is back. +const RsbuildStatusCard = lazyRemote(() => import("tanstack_rsbuild_ssr_remote/StatusCard")); +const ViteStatusCard = lazyRemote(() => import("tanstack_vite_remote/StatusCard")); + +// Manifests of the remotes this route renders on the server. +const serverRenderedManifests = [ + "http://127.0.0.1:3004/mf-manifest.json", + "http://127.0.0.1:3001/mf-manifest.json", +]; export const Route = createFileRoute("/")({ - // Loading the remote before render puts its markup in the server response. - // An unavailable remote must not fail the route: the boundary below renders a fallback. - loader: () => import("tanstack_rsbuild_ssr_remote/StatusCard").then(() => null).catch(() => null), + // Loading the remotes before render puts their markup in the server response. + // An unavailable remote must not fail the route: its boundary renders a fallback. + loader: async () => { + const [stylesheets] = await Promise.all([ + Promise.all( + serverRenderedManifests.map((manifest) => + getRemoteStylesheets(manifest, "./StatusCard").catch(() => []), + ), + ).then((lists) => lists.flat()), + import("tanstack_rsbuild_ssr_remote/StatusCard").catch(() => null), + import("tanstack_vite_remote/StatusCard").catch(() => null), + ]); + return { stylesheets }; + }, + // The remote markup arrives with the HTML, so its stylesheets must too. Otherwise it stays + // unstyled until the remote's JavaScript loads. + head: ({ loaderData }) => ({ + links: loaderData?.stylesheets.map((href) => ({ href, rel: "stylesheet" })), + }), component: Home, }); @@ -15,11 +40,16 @@ function Home() { return (

Rsbuild SSR host

-

Server-rendered across Rsbuild apps.

-

This TanStack Start server renders the remote card before the page reaches the browser.

+

Server-rendered from Rsbuild and Vite.

+

+ This TanStack Start server renders both remote cards before the page reaches the browser. +

- + + + +
diff --git a/apps/rsbuild-ssr-remote/rsbuild.config.ts b/apps/rsbuild-ssr-remote/rsbuild.config.ts index aa7c0dc..8e302bf 100644 --- a/apps/rsbuild-ssr-remote/rsbuild.config.ts +++ b/apps/rsbuild-ssr-remote/rsbuild.config.ts @@ -6,7 +6,7 @@ import { tanstackStartModuleFederation } from "@module-federation/tanstack/rsbui export default defineConfig({ dev: { lazyCompilation: false }, server: { - cors: { origin: ["http://127.0.0.1:3005"] }, + cors: { origin: ["http://127.0.0.1:3000", "http://127.0.0.1:3005"] }, host: "127.0.0.1", port: 3004, }, diff --git a/apps/rsbuild-ssr-remote/src/components/StatusCard.css b/apps/rsbuild-ssr-remote/src/components/StatusCard.css index f055223..a0dfe22 100644 --- a/apps/rsbuild-ssr-remote/src/components/StatusCard.css +++ b/apps/rsbuild-ssr-remote/src/components/StatusCard.css @@ -1,21 +1,21 @@ -.remote-card { +.rsbuild-ssr-card { border: 1px solid #83502a; background: #291c13; color: #fff3e8; padding: 30px; } -.remote-card .eyebrow { +.rsbuild-ssr-card .eyebrow { color: #ffbc7c; font-family: monospace; text-transform: uppercase; } -.remote-card h2 { +.rsbuild-ssr-card h2 { font-size: 2rem; } -.remote-card button { +.rsbuild-ssr-card button { background: #ffbc7c; border: 0; cursor: pointer; diff --git a/apps/rsbuild-ssr-remote/src/components/StatusCard.tsx b/apps/rsbuild-ssr-remote/src/components/StatusCard.tsx index 5dce0b9..68c0080 100644 --- a/apps/rsbuild-ssr-remote/src/components/StatusCard.tsx +++ b/apps/rsbuild-ssr-remote/src/components/StatusCard.tsx @@ -12,7 +12,7 @@ export default function StatusCard() { }, []); return ( -
+

Federated SSR from Rsbuild

{hydrated ? "Hydrated on the host" : "Rendered on the server"} diff --git a/apps/vite-host/src/remote.d.ts b/apps/vite-host/src/remote.d.ts index a2c6621..9342b12 100644 --- a/apps/vite-host/src/remote.d.ts +++ b/apps/vite-host/src/remote.d.ts @@ -11,3 +11,10 @@ declare module "tanstack_rsbuild_remote/StatusCard" { const StatusCard: ComponentType; export default StatusCard; } + +declare module "tanstack_rsbuild_ssr_remote/StatusCard" { + import type { ComponentType } from "react"; + + const StatusCard: ComponentType; + export default StatusCard; +} diff --git a/apps/vite-host/src/routes/index.tsx b/apps/vite-host/src/routes/index.tsx index 7fecd19..d889520 100644 --- a/apps/vite-host/src/routes/index.tsx +++ b/apps/vite-host/src/routes/index.tsx @@ -1,13 +1,40 @@ +import { getRemoteStylesheets, lazyRemote } from "@module-federation/tanstack/runtime"; import { createFileRoute } from "@tanstack/react-router"; -import { lazy, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { RemoteBoundary } from "../components/RemoteBoundary"; -const RemoteStatusCard = lazy(() => import("tanstack_vite_remote/StatusCard")); -const RsbuildStatusCard = lazy(() => import("tanstack_rsbuild_remote/StatusCard")); +// Unlike React.lazy, lazyRemote loads again after a failure, so a server that hit a remote +// outage renders the remote once it is back. +const RemoteStatusCard = lazyRemote(() => import("tanstack_vite_remote/StatusCard")); +const RsbuildStatusCard = lazyRemote(() => import("tanstack_rsbuild_remote/StatusCard")); +const RsbuildSsrStatusCard = lazyRemote(() => import("tanstack_rsbuild_ssr_remote/StatusCard")); + +// Manifests of the remotes this route renders on the server. +const serverRenderedManifests = [ + "http://127.0.0.1:3001/mf-manifest.json", + "http://127.0.0.1:3004/mf-manifest.json", +]; export const Route = createFileRoute("/")({ - // An unavailable remote must not fail the route: the boundary below renders a fallback. - loader: () => import("tanstack_vite_remote/StatusCard").then(() => null).catch(() => null), + // Loading the server-rendered remotes before render puts their markup in the response. + // An unavailable remote must not fail the route: its boundary renders a fallback. + loader: async () => { + const [stylesheets] = await Promise.all([ + Promise.all( + serverRenderedManifests.map((manifest) => + getRemoteStylesheets(manifest, "./StatusCard").catch(() => []), + ), + ).then((lists) => lists.flat()), + import("tanstack_vite_remote/StatusCard").catch(() => null), + import("tanstack_rsbuild_ssr_remote/StatusCard").catch(() => null), + ]); + return { stylesheets }; + }, + // The remote markup arrives with the HTML, so its stylesheets must too. Otherwise it stays + // unstyled until the remote's JavaScript loads. + head: ({ loaderData }) => ({ + links: loaderData?.stylesheets.map((href) => ({ href, rel: "stylesheet" })), + }), component: Home, }); @@ -35,6 +62,10 @@ function Home() { + + + + diff --git a/apps/vite-host/vite.config.ts b/apps/vite-host/vite.config.ts index f87cd1d..805fee3 100644 --- a/apps/vite-host/vite.config.ts +++ b/apps/vite-host/vite.config.ts @@ -24,6 +24,11 @@ export default defineConfig({ name: "tanstack_rsbuild_remote", entry: "http://127.0.0.1:3002/mf-manifest.json", }, + tanstack_rsbuild_ssr_remote: { + type: "global", + name: "tanstack_rsbuild_ssr_remote", + entry: "http://127.0.0.1:3004/mf-manifest.json", + }, }, }), tanstackStart(), diff --git a/docs/RELEASING.md b/docs/RELEASING.md index a52b675..1909bc4 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -4,7 +4,7 @@ summary: Release and trusted npm publishing for @module-federation/tanstack. read_when: - Preparing a release - Updating release automation -updated_at: 2026-10-06 +updated_at: 2026-10-07 --- # Releasing @@ -38,16 +38,40 @@ the `next` dist-tag. The publish workflow reruns the gate, checks that the source is reachable from `main`, uses npm provenance, and refuses to republish an existing version under -a different dist-tag. Configure npm trusted publishing for repository -`module-federation/tanstack`, workflow `release.yml`, environment `Publish`. +a different dist-tag. It publishes through npm trusted publishing for +repository `module-federation/tanstack`, workflow `release.yml`, environment +`Publish`, so the repository needs a `Publish` environment. + +## First publish + +npm only lets a package that already exists name a trusted publisher, so the +workflow cannot publish `@module-federation/tanstack` the first time. Bootstrap +it once, as a maintainer with publish rights in the `@module-federation` npm +organization: + +1. Publish a placeholder from an empty directory: a `package.json` with + `"name": "@module-federation/tanstack"` and `"version": "0.0.0"`, then + `npm publish --access public`. +2. Add the trusted publisher on npmjs.com (package settings, "Trusted + Publisher", GitHub Actions) or with npm 11.10+: + `npm trust github @module-federation/tanstack --file release.yml --repo module-federation/tanstack --env Publish --allow-publish`. +3. Release `0.1.0` through the workflow within two days: npm discards a trusted + publisher configuration that has not published by then. +4. Deprecate the placeholder: + `npm deprecate @module-federation/tanstack@0.0.0 "Placeholder; use 0.1.0 or later."` + +Trusted publishing needs npm 11.5.1+ on Node 22.14+; the workflow's Node 24 +includes it. Releases after `0.1.0` need no manual step. The release gate builds the six example apps and checks their manifests and server bundles. It then runs the apps twice, as development servers and as production builds, and drives every host in headless Chromium: server-rendered -remote markup for Vite to Vite and Rsbuild to Rsbuild (including concurrent -first requests), hydration, remote interactivity, one shared React instance, no -console errors, reachable manifest assets, and the fallback and recovery when -remotes go offline. +remote markup for every Vite and Rsbuild host and remote pair (including +concurrent first requests), remote stylesheets in the server response, +hydration, remote interactivity, one shared React instance and context, no +console errors, reachable manifest assets, the fallback and recovery when +remotes go offline, and hosts that start during an outage rendering the remotes +once they return. The test suite needs Chromium. Locally, run `pnpm exec playwright install chromium` once; the workflows install it before `pnpm test`. diff --git a/packages/tanstack/CHANGELOG.md b/packages/tanstack/CHANGELOG.md index e63c494..77d5b7e 100644 --- a/packages/tanstack/CHANGELOG.md +++ b/packages/tanstack/CHANGELOG.md @@ -8,18 +8,27 @@ Initial release: Module Federation for TanStack Start on Vite and Rsbuild. `@module-federation/vite` with TanStack Start defaults: `remoteEntry.js`, a manifest, entry-level host init, and environment-specific build targets. Hosts share eager singleton `react` and `react-dom`, so a remote built by - another bundler cannot replace the host's React instance. + another bundler cannot replace the host's React instance. It requires Vite 8 + and stops with an error on older versions. - `@module-federation/tanstack/rsbuild` wraps `@module-federation/rsbuild-plugin` with script-compatible browser output, async startup, role-aware React sharing, and `publicPath: "auto"` for remotes, so production remotes load from their own origin. -- Rsbuild SSR federation with `server: true`: remotes ship a Node container in - `dist/client/ssr/` and advertise it in their manifest; hosts load remotes on - the server from an async-node CommonJS build that keeps `dist/server/index.js` - as its entry. +- Rsbuild SSR federation with `server: true`: remotes ship a Node container, + `remoteEntry.ssr.cjs`, with their browser assets and advertise it in their + manifest; hosts load remotes on the server from an async-node CommonJS build + that keeps `dist/server/index.js` as its entry. +- Federated SSR for every host and remote pair: Vite hosts render Rsbuild + remotes built with `server: true`, and Rsbuild hosts render Vite remotes + through `@module-federation/vite`'s SSR loader. - `@module-federation/tanstack/node-entry-loader`: the runtime plugin Rsbuild - hosts use to load CommonJS remote entries on the server. -- Supported: federated SSR for Vite to Vite and Rsbuild to Rsbuild, and browser - federation for every Vite/Rsbuild host and remote combination. + hosts use to load remote server entries. Failures throw `RemoteEntryError` + with the failing phase and the original cause. +- `@module-federation/tanstack/runtime`: `lazyRemote`, a `React.lazy` that + loads a failed remote again instead of failing until the server restarts, and + `getRemoteStylesheets`, which reads a remote's stylesheets from its manifest + so server-rendered remote markup is styled before JavaScript loads. +- Hosts that hit a remote outage, including at startup, render the remote again + once it returns. - Tested in development and production builds on Node 22.18, 24, and 26, with Vite 8.3, Rsbuild 2.2, TanStack Start 1.168, and React 19.3. diff --git a/packages/tanstack/README.md b/packages/tanstack/README.md index bb40112..5008356 100644 --- a/packages/tanstack/README.md +++ b/packages/tanstack/README.md @@ -6,17 +6,18 @@ the other. ## Support status -| Host | Remote | Browser federation | Federated SSR | -| ------- | ------- | ------------------ | -------------------------- | -| Vite | Vite | Supported | Supported | -| Vite | Rsbuild | Supported | Not supported | -| Rsbuild | Vite | Supported | Not supported | -| Rsbuild | Rsbuild | Supported | Supported (`server: true`) | +| Host | Remote | Browser federation | Federated SSR | +| ------- | ------- | ------------------ | ---------------------------------- | +| Vite | Vite | Supported | Supported | +| Vite | Rsbuild | Supported | Supported (remote: `server: true`) | +| Rsbuild | Vite | Supported | Supported (host: `server: true`) | +| Rsbuild | Rsbuild | Supported | Supported (both: `server: true`) | "Federated SSR" means the remote component is rendered into the host's initial -HTML. Every supported row is covered by browser tests against both development -servers and production builds: the remote hydrates, responds to clicks, logs no -errors, and the host falls back and recovers when the remote goes offline. +HTML. Every row is covered by browser tests against both development servers +and production builds: the remote hydrates, shares the host's React and +context, responds to clicks, logs no errors, and the host falls back and +recovers when the remote goes offline. ## Vite @@ -66,10 +67,23 @@ The wrapper omits the global Module Federation `target` option. TanStack Start's client and SSR environments need `@module-federation/vite` to select `web` and `node` independently. -Federated SSR requires Vite 8. With Vite 7 (and `@vitejs/plugin-react` 5), -browser federation works, but production SSR hosts fail to load remotes -(`ERR_UNSUPPORTED_ESM_URL_SCHEME` for a `virtual:` import) and fall back to -client rendering. CI tests Vite 8 only. +The Vite adapter requires Vite 8, like TanStack Start's own examples and +`@vitejs/plugin-react` 6. It stops with an error on older versions, where +`@module-federation/vite` cannot load remotes on the server. + +A Vite host renders Rsbuild remotes on the server when they are built with +`server: true` (see below). Declare such a remote by its manifest, so the +server can find the Node container: + +```ts +remotes: { + rsbuild_remote: { + type: "global", + name: "rsbuild_remote", + entry: "https://example.test/mf-manifest.json", + }, +}, +``` ## Rsbuild @@ -113,41 +127,99 @@ startup, and a single offline remote fails the whole page. ### SSR federation -Pass `server: true` (or an options object) to federate on the server. Enable it -on both sides: +Pass `server: true` (or an options object) to federate on the server: -- **Remotes** (`exposes`) build a Node container into `dist/client/ssr/`, next - to the browser assets, and advertise it in `mf-manifest.json` as - `ssrRemoteEntry`. Deploy `dist/client` as usual; the container holds only the - exposed modules, never the app's server code. The remote's own server output - is unchanged. +- **Remotes** (`exposes`) build a Node container, `remoteEntry.ssr.cjs`, next to + `remoteEntry.js` in `dist/client` (its chunks go in `dist/client/ssr/`), and + advertise it in `mf-manifest.json` as `ssrRemoteEntry`. Deploy `dist/client` + as usual; the container holds only the exposed modules, never the app's + server code. The remote's own server output is unchanged. Vite and Rsbuild + hosts can both render it. - **Hosts** (`remotes`) load remotes on the server through the Module Federation Node runtime. TanStack Start's server output becomes async-node CommonJS (`dist/server/index.cjs`), with `dist/server/index.js` re-exporting it, so `rsbuild preview` and deployments that import `index.js` keep working. + Rsbuild remotes need `server: true` too. Vite remotes need nothing extra on + their side, but the host must have `@module-federation/vite` (and its `vite` + peer) installed: their server entries are ES modules, loaded by that + package's SSR loader. + +When a remote's server entry cannot load, the host's runtime plugin throws a +`RemoteEntryError` whose `phase` is `fetch`, `evaluate`, or `loader` and whose +`cause` is the original error. + +## Rendering remotes on the server + +Hosts on either bundler render remotes the same way: load the remote in the +route `loader`, render it with `lazyRemote` inside a Suspense and error +boundary, and link its stylesheets from the route's `head`: + +```tsx +import { getRemoteStylesheets, lazyRemote } from "@module-federation/tanstack/runtime"; +import { createFileRoute } from "@tanstack/react-router"; + +const StatusCard = lazyRemote(() => import("remote/StatusCard")); + +export const Route = createFileRoute("/")({ + // Loading the remote before rendering puts its markup in the server response. + // A failed load must not fail the route: the boundary renders a fallback. + loader: async () => { + const [stylesheets] = await Promise.all([ + getRemoteStylesheets("https://remote.test/mf-manifest.json", "./StatusCard").catch(() => []), + import("remote/StatusCard").catch(() => null), + ]); + return { stylesheets }; + }, + head: ({ loaderData }) => ({ + links: loaderData?.stylesheets.map((href) => ({ href, rel: "stylesheet" })), + }), + component: () => ( + + + + ), +}); +``` -Import the remote in the route `loader` and render it inside a Suspense or -error boundary; see `apps/rsbuild-ssr-host` in the repository. +`RemoteBoundary` stands for any error boundary around a `Suspense`; the +examples in `apps/` include one. + +- **`lazyRemote(load, { retryAfterMs })`** is `React.lazy` for remotes. A + `React.lazy` component that fails once stays failed, so a server that hit a + remote outage would render the fallback until it restarts. `lazyRemote` loads + again on the first render after `retryAfterMs` (5 seconds by default). The + adapters make the import itself load again after a failure too, in + development and production: on its own, each bundler keeps the first failure + for the life of the process. +- **`getRemoteStylesheets(manifestUrl, expose, { maxAgeMs })`** lists the + stylesheets the remote's manifest declares for an expose, as absolute URLs. A + remote's CSS otherwise loads with its JavaScript, after the HTML, so the + server-rendered markup would be unstyled until then. It works in the browser + and on the server, reuses a fetched manifest for 30 seconds (`maxAgeMs`), and + rejects when the manifest is unreachable or does not list the expose. ## Known limitations -- Server-rendered remotes arrive without their stylesheets: the remote's CSS - loads with its JavaScript, after the HTML. Expect a short unstyled flash for - remote markup in SSR responses. - A Vite remote with `exposes` does not hydrate when opened directly as an app: `@module-federation/vite` waits for a host to initialize it. Production builds worked standalone up to `@module-federation/vite` 1.22.1; 1.23.0 broke them. Loading the remote through a host is unaffected. -- In development, a Vite host's dev server exits when a Vite remote it has - server-rendered goes offline (an unhandled rejection in - `@module-federation/vite`'s dev SSR transport). Production hosts fall back and - recover. -- Cross-bundler SSR is not supported. Cross-bundler remotes render after - hydration. +- In development, a Vite remote's server modules import React from the files + its dev server resolves, so host and remote must resolve React to the same + files, as they do in a monorepo. Production builds take React from the + host's share scope. +- A Vite dev server injects CSS from JavaScript, so its manifest lists no + stylesheets and `getRemoteStylesheets` returns none for it in development. ## Requirements - Node 22.18+, 24.11+, or 26+. - TanStack Start 1.167.43+ (the first release with the Rsbuild integration). +- React 19, which the tests use. TanStack Start also accepts React 18, but as + of 1.168 its Rsbuild server build fails with it: TanStack Router imports + React's `use`, which React 18 lacks. React 18 with Vite builds but is + untested. - Vite: `@module-federation/vite` and `vite`. -- Rsbuild: `@module-federation/rsbuild-plugin` and `@rsbuild/core`. +- Rsbuild: `@module-federation/rsbuild-plugin` and `@rsbuild/core`. An Rsbuild + host that renders Vite remotes on the server also needs + `@module-federation/vite` and `vite`. diff --git a/packages/tanstack/package.json b/packages/tanstack/package.json index 2f5afb9..9387fbe 100644 --- a/packages/tanstack/package.json +++ b/packages/tanstack/package.json @@ -49,6 +49,16 @@ "default": "./dist/node-entry-loader.cjs" } }, + "./runtime": { + "import": { + "types": "./dist/runtime.d.ts", + "default": "./dist/runtime.js" + }, + "require": { + "types": "./dist/runtime.d.cts", + "default": "./dist/runtime.cjs" + } + }, "./package.json": "./package.json" }, "files": [ @@ -92,7 +102,8 @@ "@module-federation/vite": "^1.22.0", "@rsbuild/core": "^2.0.0", "@tanstack/react-start": "^1.167.43", - "vite": "^7.0.0 || ^8.0.0" + "react": ">=18.0.0", + "vite": "^8.0.0" }, "peerDependenciesMeta": { "@tanstack/react-start": { @@ -109,6 +120,9 @@ }, "vite": { "optional": true + }, + "react": { + "optional": true } }, "devDependencies": { @@ -119,6 +133,7 @@ "@rslib/core": "1.0.3", "@tanstack/react-start": "1.168.60", "@types/node": "26.6.4", + "@types/react": "19.3.0", "react": "19.3.0", "react-dom": "19.3.0", "typescript": "7.0.2", diff --git a/packages/tanstack/rslib.config.ts b/packages/tanstack/rslib.config.ts index 8a27916..6ab4262 100644 --- a/packages/tanstack/rslib.config.ts +++ b/packages/tanstack/rslib.config.ts @@ -4,8 +4,10 @@ const entry = { index: "./src/index.ts", "node-entry-loader": "./src/node-entry-loader.ts", rsbuild: "./src/rsbuild.ts", + runtime: "./src/runtime.ts", shared: "./src/shared.ts", vite: "./src/vite.ts", + "vite-compat": "./src/vite-compat.ts", }; const output = { diff --git a/packages/tanstack/src/node-entry-loader.ts b/packages/tanstack/src/node-entry-loader.ts index b8d3e48..8d48f45 100644 --- a/packages/tanstack/src/node-entry-loader.ts +++ b/packages/tanstack/src/node-entry-loader.ts @@ -1,65 +1,269 @@ import type { ModuleFederationRuntimePlugin } from "@module-federation/runtime"; -declare const __non_webpack_require__: NodeJS.Require; +type LoadEntryArgs = Parameters>[0]; +type RemoteEntryExports = NonNullable; +type RemoteInfo = LoadEntryArgs["remoteInfo"]; +type FederationHost = LoadEntryArgs["origin"]; +type NodeModule = typeof import("node:module"); -type RemoteEntryExports = NonNullable< - Parameters>[0]["remoteEntryExports"] ->; +/** A remote's server entry failed to load. `phase` says which step failed; `cause` keeps the original error. */ +export class RemoteEntryError extends Error { + readonly phase: "fetch" | "evaluate" | "loader"; + readonly remote: string; + readonly url: string; + + constructor( + message: string, + options: { cause?: unknown; phase: RemoteEntryError["phase"]; remote: string; url: string }, + ) { + super(message, { cause: options.cause }); + this.name = "RemoteEntryError"; + this.phase = options.phase; + this.remote = options.remote; + this.url = options.url; + } +} /** - * Loads CommonJS remote entries over HTTP for a host's server bundle. + * Loads remote server entries for an Rsbuild host's server bundle. * - * Module Federation's default Node loader reaches `vm` and `path` through `import()`. - * Rsbuild's development runner executes server bundles in a `vm` function without a - * dynamic-import callback, so that `import()` throws. Node built-ins are loaded with - * `require` here instead, matching how the federation chunk loader already works. + * - CommonJS containers (Rsbuild remotes) are fetched and compiled here as CommonJS + * modules, so their own `import()` calls work. Module Federation's default Node loader + * evaluates them with `vm`, which Rsbuild's development runner cannot support. + * - ES module entries (Vite remotes) go to `@module-federation/vite`'s SSR loader, which + * handles both production builds and Vite dev servers. It is loaded with Node's + * `require`, not the bundle's, so its `import()` calls work in Rsbuild's runner too. */ export default function nodeEntryLoaderPlugin(): ModuleFederationRuntimePlugin { return { name: "tanstack-start-node-entry-loader", - async loadEntry({ remoteInfo }) { - const { entry, entryGlobalName, type } = remoteInfo; - if (type !== "commonjs-module" || !/^https?:\/\//.test(entry)) return undefined; - - const response = await fetch(entry); - if (!response.ok) { - throw new Error(`Failed to fetch remote entry ${entry}: HTTP ${response.status}`); - } - const source = await response.text(); - - const nodeRequire = __non_webpack_require__; - const vm: typeof import("node:vm") = nodeRequire("node:vm"); - const { createRequire }: typeof import("node:module") = nodeRequire("node:module"); - const path: typeof import("node:path") = nodeRequire("node:path"); - - const { pathname } = new URL(entry); - const container = { exports: {} as Record }; - const run = new vm.Script( - `(function (exports, module, require, __dirname, __filename) {${source}\n})`, - { - filename: entry, - importModuleDynamically: vm.constants?.USE_MAIN_CONTEXT_DEFAULT_LOADER, - }, - ).runInThisContext(); - run( - container.exports, - container, - createRequire(path.join(process.cwd(), "__mf_require_base__.js")), - path.posix.dirname(pathname), - path.posix.basename(pathname), - ); - - const exports = container.exports; - const remoteEntry = (entryGlobalName && exports[entryGlobalName]) || exports; - if (!isRemoteEntry(remoteEntry)) { - throw new Error(`Remote entry ${entry} did not export a federation container.`); - } - return remoteEntry; + async loadEntry(args) { + const { remoteInfo } = args; + if (!isNodeServer() || !/^https?:\/\//.test(remoteInfo.entry)) return undefined; + if (remoteInfo.type === "commonjs-module") return loadCommonJsEntry(remoteInfo); + if (remoteInfo.type === "module" || remoteInfo.type === "esm") return loadModuleEntry(args); + return undefined; }, }; } +// Matches @module-federation/vite's SSR fetch timeout. +const FETCH_TIMEOUT_MS = 10_000; + +function isNodeServer() { + return typeof process !== "undefined" && typeof process.getBuiltinModule === "function"; +} + +function nodeModule() { + return process.getBuiltinModule("node:module") as NodeModule; +} + +/** Requires from the app, because the host's server bundle has no node_modules of its own. */ +function appRequires() { + const { createRequire } = nodeModule(); + const path = process.getBuiltinModule("node:path") as typeof import("node:path"); + // The app root first, then the entry script for servers started from elsewhere. + return [path.join(process.cwd(), "package.json"), process.argv[1]] + .filter((base): base is string => Boolean(base)) + .map((base) => createRequire(base)); +} + +async function loadCommonJsEntry({ entry, entryGlobalName, name }: RemoteInfo) { + let source: string; + try { + const response = await fetch(entry, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) }); + if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`.trim()); + source = await response.text(); + } catch (cause) { + throw new RemoteEntryError( + `Could not fetch the server entry of remote "${name}" from ${entry}.`, + { + cause, + phase: "fetch", + remote: name, + url: entry, + }, + ); + } + + let exports: Record; + try { + const Module = nodeModule() as unknown as new (id: string) => CompilableModule; + const container = new Module(entry); + container.filename = entry; + container.paths = appRequires()[0]?.resolve.paths("react") ?? []; + // What `require()` does with a file's source: Node compiles it as a CommonJS module + // whose `import()` calls use Node's own loader. + container._compile(source, entry, "commonjs"); + exports = container.exports as Record; + } catch (cause) { + throw new RemoteEntryError(`The server entry of remote "${name}" threw while loading.`, { + cause, + phase: "evaluate", + remote: name, + url: entry, + }); + } + + const remoteEntry = (entryGlobalName && exports[entryGlobalName]) || exports; + if (!isRemoteEntry(remoteEntry)) { + throw new RemoteEntryError( + `The server entry of remote "${name}" did not export a federation container.`, + { phase: "evaluate", remote: name, url: entry }, + ); + } + return remoteEntry; +} + +interface CompilableModule { + exports: unknown; + filename: string; + paths: string[]; + _compile(source: string, filename: string, format?: "commonjs"): void; +} + function isRemoteEntry(value: unknown): value is RemoteEntryExports { const candidate = value as Partial | undefined; return typeof candidate?.get === "function" && typeof candidate.init === "function"; } + +// Packages a Vite remote's server code may import directly, besides the host's shared +// packages. Resolving them from the host keeps one copy of each in the process. +const VITE_SHARED_PACKAGES = [ + "@module-federation/runtime", + "@module-federation/runtime-core", + "@module-federation/sdk", + "react", + "react-dom", + "react/compiler-runtime", + "react/jsx-dev-runtime", + "react/jsx-runtime", +]; + +type ViteSsrEntryLoaderModule = { + default: (options: { resolvedShared: Record }) => ModuleFederationRuntimePlugin; + SsrEntryHttpError?: new (...args: never[]) => Error; +}; + +type ViteLoader = { + httpError?: ViteSsrEntryLoaderModule["SsrEntryHttpError"]; + plugin: ModuleFederationRuntimePlugin; + require: NodeJS.Require; + resolvedShared: Record; +}; + +let viteLoader: ViteLoader | undefined; + +async function loadModuleEntry(args: LoadEntryArgs) { + const { entry, name } = args.remoteInfo; + viteLoader ??= createViteLoader(args); + // Shared modules the host loaded since the last remote are aligned too. + provideHostModules(args.origin, viteLoader.resolvedShared, viteLoader.require); + + let remoteEntry: RemoteEntryExports | void; + try { + remoteEntry = await viteLoader.plugin.loadEntry?.(args); + } catch (cause) { + const { httpError } = viteLoader; + throw new RemoteEntryError( + `Could not load the server entry of remote "${name}" from ${entry}.`, + { + cause, + phase: httpError && cause instanceof httpError ? "fetch" : "evaluate", + remote: name, + url: entry, + }, + ); + } + // Without an entry the runtime would try its own Node loader, which cannot load ES + // modules without Node flags. Report the failure instead. The Vite loader returns + // nothing for an unreachable remote, so tell that case apart. + if (!remoteEntry) { + const reachable = await fetch(entry, { + method: "HEAD", + signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), + }).then( + (response) => response.ok, + () => false, + ); + throw new RemoteEntryError( + reachable + ? `@module-federation/vite could not load the server entry of remote "${name}" ` + + `from ${entry}; its warnings have the cause.` + : `Could not reach remote "${name}" at ${entry}.`, + { phase: reachable ? "loader" : "fetch", remote: name, url: entry }, + ); + } + return remoteEntry; +} + +function createViteLoader({ origin, remoteInfo }: LoadEntryArgs): ViteLoader { + const requires = appRequires(); + const resolve = (specifier: string) => { + for (const require of requires) { + try { + return { path: require.resolve(specifier), require }; + } catch { + // Try the next base. + } + } + return undefined; + }; + + const loader = resolve("@module-federation/vite/ssrEntryLoader"); + if (!loader) { + throw new RemoteEntryError( + `Remote "${remoteInfo.name}" is a Vite remote. Add @module-federation/vite to the ` + + "host's dependencies to render it on the server.", + { phase: "loader", remote: remoteInfo.name, url: remoteInfo.entry }, + ); + } + + const resolvedShared: Record = {}; + for (const specifier of [...VITE_SHARED_PACKAGES, ...Object.keys(origin.options.shared)]) { + const resolved = resolve(specifier); + if (resolved) resolvedShared[specifier] = resolved.path; + } + + // The loader is an ES module; Node 22.12+ requires it synchronously. + const loaderModule = loader.require(loader.path) as ViteSsrEntryLoaderModule; + return { + httpError: loaderModule.SsrEntryHttpError, + plugin: loaderModule.default({ resolvedShared }), + require: loader.require, + resolvedShared, + }; +} + +/** + * Vite remotes can import shared packages from node_modules instead of the share scope: + * a Vite dev server's module runner externalizes them, and resolved imports in production + * builds point there. The host bundles its own copies, so this registers them in Node's + * module cache under their node_modules paths. The process then keeps one React. + */ +function provideHostModules( + origin: FederationHost, + resolvedShared: Record, + require: NodeJS.Require, +) { + const Module = nodeModule() as unknown as new (id: string) => CompilableModule & { + loaded: boolean; + }; + for (const scope of Object.values(origin.shareScopeMap)) { + for (const [packageName, versions] of Object.entries(scope)) { + const filename = resolvedShared[packageName]; + // Something already loaded this package from node_modules; nothing to align. + if (!filename || require.cache[filename]) continue; + const provided = Object.values(versions).find( + (shared) => shared.from === origin.name && shared.loaded && shared.lib, + ); + if (!provided?.lib) continue; + + const cached = new Module(filename); + cached.filename = filename; + cached.exports = provided.lib(); + cached.loaded = true; + require.cache[filename] = cached as unknown as NodeJS.Module; + } + } +} diff --git a/packages/tanstack/src/rsbuild.ts b/packages/tanstack/src/rsbuild.ts index 6395deb..b8847ce 100644 --- a/packages/tanstack/src/rsbuild.ts +++ b/packages/tanstack/src/rsbuild.ts @@ -1,4 +1,3 @@ -import { join } from "node:path"; import { pluginModuleFederation, type ModuleFederationOptions, @@ -30,20 +29,24 @@ export type TanStackStartRsbuildModuleFederationOptions = { * Server-side federation. Pass `true` or an options object to enable it. Defaults to * `false`: browser-only federation. * - * - With `exposes`, the build adds a Node container in `/ssr/` and - * advertises it in the browser manifest as `ssrRemoteEntry`. + * - With `exposes`, the build adds a Node container, `remoteEntry.ssr.cjs`, next to the + * browser entry (its chunks go in `ssr/`) and advertises it in the browser manifest + * as `ssrRemoteEntry`. * - With `remotes`, TanStack Start's server environment loads remotes through the * Module Federation Node runtime, as async-node CommonJS (`dist/server/index.cjs`). */ server?: ServerFederationOptions | true; }; -/** Directory, inside the browser output, that holds the remote's Node container. */ -export const SERVER_CONTAINER_DIR = "ssr"; - +// The server container sits next to the manifest, like a Vite remote's SSR entry. Hosts +// resolve its chunks from either the container URL (Module Federation's Node runtime) or +// the manifest URL (`@module-federation/vite`), so both must share a directory. The +// `.cjs` extension matters: `@module-federation/vite` treats any `.ssr.js` URL as an ES +// module, while for other names it reads the format from the manifest. +const SERVER_CONTAINER_FILENAME = "remoteEntry.ssr.cjs"; +const SERVER_CHUNK_DIR = "ssr"; const SERVER_CONTAINER_ENVIRONMENT = "mf-server"; const NODE_ENTRY_LOADER = "@module-federation/tanstack/node-entry-loader"; -const SERVER_CONTAINER_FILENAME = "remoteEntry.js"; /** Creates browser and optional SSR federation adapters for TanStack Start's Rsbuild integration. */ export function tanstackStartModuleFederation({ @@ -76,6 +79,7 @@ export function tanstackStartModuleFederation({ environment: clientEnvironment, target: "web", }), + ...(isHost ? [remoteRetryPlugin()] : []), clientCompatibilityPlugin({ chunkLoadingGlobal: chunkLoadingGlobal ?? `chunk_${federationName}`, environment: clientEnvironment, @@ -100,7 +104,17 @@ export function tanstackStartModuleFederation({ plugins.push( serverContainerEnvironmentPlugin({ clientEnvironment }), pluginModuleFederation( - withDefaults({ ...containerOptions, filename: SERVER_CONTAINER_FILENAME }, defaultShared), + withDefaults( + { + ...containerOptions, + // The browser build owns the manifest and the federated types. + dev: false, + dts: false, + filename: SERVER_CONTAINER_FILENAME, + manifest: false, + }, + defaultShared, + ), { environment: SERVER_CONTAINER_ENVIRONMENT, target: "node" }, ), ); @@ -160,8 +174,8 @@ function withDefaults( /** * Adds the Node container to the browser manifest, as Module Federation's dual-target - * build does. A Node consumer reads `ssrRemoteEntry` and fetches the container from - * `ssr/`. + * build does. A Node consumer reads `ssrRemoteEntry` and fetches the container from the + * manifest's public path. */ function advertiseServerContainer(options: ModuleFederationOptions) { const manifest = options.manifest === true ? {} : options.manifest; @@ -173,18 +187,14 @@ function advertiseServerContainer(options: ModuleFederationOptions) { async additionalData(context) { const stats = (await userAdditionalData?.(context)) ?? context.stats; Object.assign(stats.metaData, { - ssrRemoteEntry: { - name: SERVER_CONTAINER_FILENAME, - path: SERVER_CONTAINER_DIR, - type: "commonjs-module", - }, + ssrRemoteEntry: { name: SERVER_CONTAINER_FILENAME, path: "", type: "commonjs-module" }, }); return stats; }, }; } -/** Builds the remote's Node container in its own environment, inside the browser output. */ +/** Builds the remote's Node container in its own environment, into the browser output. */ function serverContainerEnvironmentPlugin({ clientEnvironment, }: { @@ -204,7 +214,9 @@ function serverContainerEnvironmentPlugin({ // Module Federation adds the container entry; the placeholder keeps the // environment from bundling the application. source: { entry: { [SERVER_CONTAINER_ENVIRONMENT]: "data:text/javascript," } }, - output: { emitAssets: true, target: "node" }, + // The output directory is shared with the browser build, which cleans it and + // emits the stylesheets. + output: { cleanDistPath: false, emitAssets: false, target: "node" }, }; }, }); @@ -223,15 +235,61 @@ function serverContainerEnvironmentPlugin({ } // The container ships with the browser assets and loads its chunks over HTTP from - // the same origin. + // the same public path. const publicPath = clientConfig.output.publicPath; containerConfig.output ||= {}; - containerConfig.output.path = join(clientConfig.output.path, SERVER_CONTAINER_DIR); + containerConfig.output.path = clientConfig.output.path; + containerConfig.output.filename = `${SERVER_CHUNK_DIR}/[name].js`; + containerConfig.output.chunkFilename = `${SERVER_CHUNK_DIR}/[id].[contenthash:8].js`; containerConfig.output.publicPath = - typeof publicPath === "string" && publicPath !== "auto" - ? `${publicPath}${SERVER_CONTAINER_DIR}/` - : "auto"; + typeof publicPath === "string" && publicPath !== "auto" ? publicPath : "auto"; }); + + // The container assigns `module.exports` at runtime, so ES module importers see only + // a default export. Static re-assignments make `init` and `get` detectable named + // exports, which `@module-federation/vite` hosts read when they import the container. + api.processAssets( + { stage: "summarize", environments: [SERVER_CONTAINER_ENVIRONMENT] }, + ({ assets, compilation, sources }) => { + if (!assets[SERVER_CONTAINER_FILENAME]) return; + compilation.updateAsset( + SERVER_CONTAINER_FILENAME, + (source) => new sources.ConcatSource(source, CONTAINER_NAMED_EXPORTS), + ); + }, + ); + }, + }; +} + +const CONTAINER_NAMED_EXPORTS = ` +module.exports.init = module.exports.init; +module.exports.get = module.exports.get; +`; + +const REMOTE_FAILURE_FACTORY = /(webpackRequire\.m\[id\]\s*=\s*\(\)\s*=>\s*\{)(\s*throw error;)/; + +/** + * Lets a host load a remote again after a failed load. Module Federation's bundler runtime + * replaces a remote module that failed with one that throws, and retries the load on the + * next import, but the module cache keeps the first failure's empty exports. A server + * that hit a remote outage, or started during one, would then never render that remote. + * Rspack's `strictModuleExceptionHandling` caches the error instead, so this removes the + * failed module from the cache. + */ +function remoteRetryPlugin(): RsbuildPlugin { + return { + name: "tanstack-start-federation-remote-retry", + setup(api) { + api.transform( + { test: /[\\/]@module-federation[\\/]webpack-bundler-runtime[\\/]dist[\\/]remotes\.c?js$/ }, + ({ code }) => + // One line, so the rest of the file keeps its positions. + code.replace( + REMOTE_FAILURE_FACTORY, + "$1 if (webpackRequire.c) delete webpackRequire.c[id];$2", + ), + ); }, }; } diff --git a/packages/tanstack/src/runtime.ts b/packages/tanstack/src/runtime.ts new file mode 100644 index 0000000..1c9ba22 --- /dev/null +++ b/packages/tanstack/src/runtime.ts @@ -0,0 +1,96 @@ +import { createElement, lazy, type ComponentProps, type ComponentType } from "react"; + +export type LazyRemoteOptions = { + /** How long a failed load stays failed before a render tries again, in milliseconds. Defaults to 5 seconds. */ + retryAfterMs?: number; +}; + +/** + * `React.lazy` for remote modules. A component from `React.lazy` that fails to load stays + * failed: on a server, every later request renders the same error, even once the remote + * is back. This one loads again on the first render after `retryAfterMs`. + * + * ```tsx + * const StatusCard = lazyRemote(() => import("remote/StatusCard")); + * ``` + */ +export function lazyRemote>( + load: () => Promise<{ default: T }>, + { retryAfterMs = 5_000 }: LazyRemoteOptions = {}, +): ComponentType> { + let failedAt: number | undefined; + const create = () => + lazy(() => + load().catch((error: unknown) => { + failedAt = Date.now(); + throw error; + }), + ); + let current = create(); + + return function RemoteComponent(props: ComponentProps) { + // React renders again right after a load fails, to report the error. Waiting before a + // new attempt keeps that render from starting another load. + if (failedAt !== undefined && Date.now() - failedAt >= retryAfterMs) { + failedAt = undefined; + current = create(); + } + return createElement(current as ComponentType>, props); + }; +} + +export type RemoteStylesheetOptions = { + /** How long a fetched manifest is reused, in milliseconds. Defaults to 30 seconds. */ + maxAgeMs?: number; +}; + +type CssAssets = { async?: string[]; sync?: string[] }; + +type RemoteManifest = { + exposes?: Array<{ assets?: { css?: CssAssets }; name?: string; path?: string }>; + metaData?: { publicPath?: string }; +}; + +const manifests = new Map }>(); + +/** + * Lists the stylesheets a remote's manifest declares for an exposed module, as absolute + * URLs. Return them from a route loader and render them in the route's `head`, so markup + * a remote rendered on the server is styled before the remote's JavaScript loads. + * + * Rejects when the manifest cannot be fetched or does not list `expose`. + */ +export async function getRemoteStylesheets( + manifestUrl: string, + expose: string, + { maxAgeMs = 30_000 }: RemoteStylesheetOptions = {}, +): Promise { + const manifest = await fetchManifest(manifestUrl, maxAgeMs); + const name = expose.replace(/^\.\//, ""); + const exposed = manifest.exposes?.find( + (candidate) => candidate.path === expose || candidate.name === name, + ); + if (!exposed) throw new Error(`${manifestUrl} does not expose "${expose}".`); + + // "auto" means the assets sit next to the manifest. + const publicPath = manifest.metaData?.publicPath; + const base = new URL(publicPath && publicPath !== "auto" ? publicPath : ".", manifestUrl); + const { async = [], sync = [] } = exposed.assets?.css ?? {}; + return [...new Set([...sync, ...async])].map((asset) => new URL(asset, base).href); +} + +function fetchManifest(url: string, maxAgeMs: number) { + const cached = manifests.get(url); + if (cached && Date.now() - cached.fetchedAt < maxAgeMs) return cached.manifest; + + const manifest = fetch(url).then(async (response) => { + if (!response.ok) throw new Error(`${url} responded with HTTP ${response.status}.`); + return (await response.json()) as RemoteManifest; + }); + manifests.set(url, { fetchedAt: Date.now(), manifest }); + // A failed fetch is not reused: the next call retries. + manifest.catch(() => { + if (manifests.get(url)?.manifest === manifest) manifests.delete(url); + }); + return manifest; +} diff --git a/packages/tanstack/src/vite-compat.ts b/packages/tanstack/src/vite-compat.ts new file mode 100644 index 0000000..e9c4fda --- /dev/null +++ b/packages/tanstack/src/vite-compat.ts @@ -0,0 +1,149 @@ +import type { Plugin } from "vite"; + +/** + * Fails fast on Vite 7 and older. `@module-federation/vite` loads remotes on the server + * only through Vite 8's module runner and Rolldown output, so federated SSR breaks + * silently on older versions. + */ +export function viteVersionPlugin(): Plugin { + return { + name: "tanstack-start-federation:vite-version", + enforce: "pre", + config() { + const version = this.meta.viteVersion; + const major = Number(version?.split(".")[0]); + if (major < 8) { + throw new Error( + `@module-federation/tanstack requires Vite 8; this project runs Vite ${version}.`, + ); + } + }, + }; +} + +const REMOTE_PENDING_EXPORT = "export const __mf_remote_pending ="; +const HANDLED_REMOTE_PENDING = /__mf_remote_pending\??\.catch/; +// The export server wrappers use: one load, started when the wrapper is evaluated. +const EAGER_REMOTE_PENDING_EXPORT = + /export const( __mf_remote_pending =\s*__mfRemotePending\s*\?\?\s*__mfStartRemoteLoad\(\)\.then\(__mfAssignRemoteModule\);)/; +// "let" padded to the length of "const", so the incoming source map stays valid. +const RETRYABLE_REMOTE_PENDING_EXPORT = "export let $1"; + +const REMOTE_LOAD_RETRY = ` +function __mfTanstackRetryAfterFailure(pending) { + pending.then(undefined, () => { + const retry = __mfTanstackNextAttempt(); + if (__mfRemotePending === pending) __mfRemotePending = retry; + if (__mf_remote_pending === pending) __mf_remote_pending = retry; + }); +} +function __mfTanstackNextAttempt() { + let attempt; + const retry = { + then(onFulfilled, onRejected) { + if (!attempt) { + attempt = __mfStartRemoteLoad().then(__mfAssignRemoteModule); + if (__mfRemotePending === retry) __mfRemotePending = attempt; + if (__mf_remote_pending === retry) __mf_remote_pending = attempt; + __mfTanstackRetryAfterFailure(attempt); + } + return attempt.then(onFulfilled, onRejected); + }, + }; + return retry; +} +if (__mfRemotePending) __mfTanstackRetryAfterFailure(__mfRemotePending); +if (__mf_remote_pending !== __mfRemotePending) __mfTanstackRetryAfterFailure(__mf_remote_pending); +`; + +// Development server wrappers export `then`, so Vite's module runner waits for the remote +// when it imports them, and caches the outcome for every later import. +const DEV_THEN_EXPORT = "export function then(onFulfilled, onRejected) {"; +// Same length, so the incoming source map stays valid. +const RENAMED_DEV_THEN = "function __mfThen1st(onFulfilled, onRejected) {"; + +const DEV_THEN_RETRY = ` +export function then(onFulfilled, onRejected) { + return __mfThen1st() + .then(undefined, () => ({ + get default() { + return __mfDefaultExport; + }, + get __moduleExports() { + return exportModule; + }, + __mf_remote_pending: { then: (resolve, reject) => __mf_remote_pending.then(resolve, reject) }, + })) + .then(onFulfilled, onRejected); +} +`; + +/** + * Works around two `@module-federation/vite` defects in the wrappers that load remotes. + * + * - On the server, a wrapper is evaluated once and makes one load attempt, exported as + * `__mf_remote_pending`. When that attempt fails, every later import fails with the same + * error, so a server that started during a remote outage never renders that remote. The + * adapter makes the next import after a failure start a new attempt. + * In development, the wrapper's `then` export also resolves after a failed load, with + * exports that read the retried load: Vite's module runner would otherwise cache the + * rejection for every later import. + * - A wrapper re-evaluated after its remote was cached exports a fresh load that nothing + * awaits, so a remote outage becomes an unhandled rejection that exits the Vite dev + * server. Awaiting the export still rejects. Wrappers that handle it are left alone. + */ +export function remotePendingPlugin(): Plugin { + return { + name: "tanstack-start-federation:remote-pending", + enforce: "post", + transform: { + filter: { id: /__loadRemote__/ }, + handler(code) { + if (EAGER_REMOTE_PENDING_EXPORT.test(code)) { + // The retry handles every rejection, so it also prevents the unhandled one. + let retrying = + code.replace(EAGER_REMOTE_PENDING_EXPORT, RETRYABLE_REMOTE_PENDING_EXPORT) + + REMOTE_LOAD_RETRY; + if (retrying.includes(DEV_THEN_EXPORT)) { + retrying = retrying.replace(DEV_THEN_EXPORT, RENAMED_DEV_THEN) + DEV_THEN_RETRY; + } + return { code: retrying, map: null }; + } + if (!code.includes(REMOTE_PENDING_EXPORT) || HANDLED_REMOTE_PENDING.test(code)) return; + // Appending moves no code, so the incoming source map stays valid. + return { code: `${code}\n__mf_remote_pending?.catch?.(() => {});\n`, map: null }; + }, + }, + }; +} + +const TEMP_MODULE_IMPORT = + /(async function importTempModule\([^)]*\)\s*\{\s*return await )import\(/; + +/** + * Works around a `@module-federation/vite` defect in development. Its SSR loader saves a + * remote's server entry to a temporary file and imports it, but in a dev server that + * import goes through Vite's module runner, which evaluates every file as an ES module. + * CommonJS entries, such as Rsbuild server containers, then fail with "module is not + * defined". Importing through Node lets it choose the format, as production builds do. + */ +export function nativeTempModuleImportPlugin(): Plugin { + return { + name: "tanstack-start-federation:native-temp-module-import", + apply: "serve", + transform: { + filter: { id: /@module-federation[\\/]vite[\\/]lib[\\/]ssrEntryLoader-[^\\/?]+\.js(?:\?|$)/ }, + handler(code) { + if (!TEMP_MODULE_IMPORT.test(code)) return; + // `Function` code imports through Node's loader, not the module runner. The helper + // goes at the end so earlier lines keep their positions for the source map. + return { + code: + code.replace(TEMP_MODULE_IMPORT, "$1__mfNativeImport(") + + '\nconst __mfNativeImport = new Function("specifier", "return import(specifier)");\n', + map: null, + }; + }, + }, + }; +} diff --git a/packages/tanstack/src/vite.ts b/packages/tanstack/src/vite.ts index b361e40..3aabefd 100644 --- a/packages/tanstack/src/vite.ts +++ b/packages/tanstack/src/vite.ts @@ -1,10 +1,18 @@ import { federation, type ModuleFederationOptions } from "@module-federation/vite"; +import type { Plugin } from "vite"; import { defaultShared, eagerShared, resolveShared } from "./shared"; +import { + nativeTempModuleImportPlugin, + remotePendingPlugin, + viteVersionPlugin, +} from "./vite-compat"; export type TanStackStartModuleFederationOptions = Omit; /** Creates one Vite federation plugin set for TanStack Start's client and SSR environments. */ -export function tanstackStartModuleFederation(options: TanStackStartModuleFederationOptions) { +export function tanstackStartModuleFederation( + options: TanStackStartModuleFederationOptions, +): Plugin[] { const { shared, target: _environmentSpecificTarget, @@ -16,14 +24,19 @@ export function tanstackStartModuleFederation(options: TanStackStartModuleFedera // replace it. Remote-only builds stay lazy and defer to the host. const isHost = Object.keys(configured.remotes ?? {}).length > 0; - return federation({ - ...configured, - filename: configured.filename ?? "remoteEntry.js", - manifest: configured.manifest ?? true, - hostInitInjectLocation: configured.hostInitInjectLocation ?? "entry", - shared: resolveShared( - shared, - isHost ? eagerShared : defaultShared, - ) as ModuleFederationOptions["shared"], - }); + return [ + viteVersionPlugin(), + ...federation({ + ...configured, + filename: configured.filename ?? "remoteEntry.js", + manifest: configured.manifest ?? true, + hostInitInjectLocation: configured.hostInitInjectLocation ?? "entry", + shared: resolveShared( + shared, + isHost ? eagerShared : defaultShared, + ) as ModuleFederationOptions["shared"], + }), + remotePendingPlugin(), + nativeTempModuleImportPlugin(), + ]; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d6a437..5458aad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -313,6 +313,9 @@ importers: '@module-federation/runtime': specifier: 2.9.2 version: 2.9.2 + '@module-federation/vite': + specifier: 1.23.2 + version: 1.23.2(typescript@7.0.2)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) '@tanstack/react-router': specifier: 1.170.41 version: 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -353,6 +356,9 @@ importers: typescript: specifier: 7.0.2 version: 7.0.2 + vite: + specifier: 8.3.2 + version: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) apps/rsbuild-ssr-remote: dependencies: @@ -509,6 +515,9 @@ importers: '@types/node': specifier: 26.6.4 version: 26.6.4 + '@types/react': + specifier: 19.3.0 + version: 19.3.0 react: specifier: 19.3.0 version: 19.3.0 diff --git a/test/example-runtime.test.mjs b/test/example-runtime.test.mjs index 5c2f619..2b0b102 100644 --- a/test/example-runtime.test.mjs +++ b/test/example-runtime.test.mjs @@ -1,12 +1,4 @@ // Development servers: Vite and Rsbuild dev servers with in-memory builds. -import { test } from "node:test"; import { describeExamples } from "./support/examples.mjs"; -describeExamples("start", { - // In development, @module-federation/vite's ModuleRunner transport leaves a rejected - // fetch unhandled once a server-rendered Vite remote goes away, which exits the Vite - // host's dev server. Production builds survive the same outage. - outageRemotes: ["rsbuildRemote", "rsbuildSsrRemote"], -}); - -test.todo("Vite host dev server survives its server-rendered Vite remote going offline"); +describeExamples("start"); diff --git a/test/release-artifacts.test.mjs b/test/release-artifacts.test.mjs index f6e18e3..f7399af 100644 --- a/test/release-artifacts.test.mjs +++ b/test/release-artifacts.test.mjs @@ -17,6 +17,8 @@ test("published declarations reference included source maps", () => { "node-entry-loader.d.ts", "rsbuild.d.cts", "rsbuild.d.ts", + "runtime.d.cts", + "runtime.d.ts", "vite.d.cts", "vite.d.ts", ]) { @@ -41,21 +43,28 @@ test("npm package contains only runtime, metadata, and documentation files", () const [manifest] = JSON.parse(packed.stdout); const files = manifest.files.map(({ path }) => path).sort(); - const runtimeFiles = ["index", "node-entry-loader", "rsbuild", "shared", "vite"].flatMap( - (entry) => { - const sourceMaps = entry === "index" ? [] : [`dist/${entry}.js.map`]; - return [ - `dist/${entry}.cjs`, - `dist/${entry}.cjs.map`, - `dist/${entry}.d.cts`, - `dist/${entry}.d.cts.map`, - `dist/${entry}.d.ts`, - `dist/${entry}.d.ts.map`, - `dist/${entry}.js`, - ...sourceMaps, - ]; - }, - ); + const entries = [ + "index", + "node-entry-loader", + "rsbuild", + "runtime", + "shared", + "vite", + "vite-compat", + ]; + const runtimeFiles = entries.flatMap((entry) => { + const sourceMaps = entry === "index" ? [] : [`dist/${entry}.js.map`]; + return [ + `dist/${entry}.cjs`, + `dist/${entry}.cjs.map`, + `dist/${entry}.d.cts`, + `dist/${entry}.d.cts.map`, + `dist/${entry}.d.ts`, + `dist/${entry}.d.ts.map`, + `dist/${entry}.js`, + ...sourceMaps, + ]; + }); assert.deepEqual( files, diff --git a/test/release-contracts.test.mjs b/test/release-contracts.test.mjs index 8039d86..eb93ccc 100644 --- a/test/release-contracts.test.mjs +++ b/test/release-contracts.test.mjs @@ -119,6 +119,182 @@ test("leaves the build target to each TanStack environment", () => { assert.equal(options.target, undefined); }); +function vitePlugin(name) { + const plugin = tanstackStartModuleFederation({ name: "host" }).find( + (candidate) => candidate.name === name, + ); + assert.ok(plugin, `${name} is registered`); + return plugin; +} + +test("the Vite adapter fails fast on Vite 7", () => { + const plugin = vitePlugin("tanstack-start-federation:vite-version"); + assert.throws( + () => plugin.config.call({ meta: { viteVersion: "7.3.6" } }), + /requires Vite 8; this project runs Vite 7\.3\.6/, + ); + assert.doesNotThrow(() => plugin.config.call({ meta: { viteVersion: "8.3.2" } })); +}); + +test("unawaited remote pending exports cannot crash the dev server", () => { + const plugin = vitePlugin("tanstack-start-federation:remote-pending"); + assert.equal(plugin.enforce, "post"); + assert.ok(plugin.transform.filter.id.test("\0virtual:mf:host__loadRemote__remote_mf_1_Card")); + + const wrapper = "export const __mf_remote_pending =\n __mfStartRemoteLoad();"; + const { code, map } = plugin.transform.handler(wrapper); + assert.equal(map, null); + assert.ok(code.startsWith(wrapper), "the generated code is unchanged"); + assert.match(code, /__mf_remote_pending\?\.catch\?\.\(\(\) => \{\}\);\n$/); + + // Wrappers that already handle the export, or have none, are left alone. + assert.equal(plugin.transform.handler(code), undefined); + assert.equal(plugin.transform.handler("export default 1;"), undefined); +}); + +/** + * The shape of @module-federation/vite's server wrapper, with loads that follow `outcomes`. + * `dev` adds the `then` export development server wrappers have. + */ +function remoteWrapper({ dev = false } = {}) { + return [ + "let attempts = 0;", + "const outcomes = ['offline', 'online'];", + "function __mfStartRemoteLoad() {", + " attempts++;", + " return outcomes.shift() === 'online'", + " ? Promise.resolve({ default: 'Card' })", + " : new Promise((_, reject) => setTimeout(() => reject(new Error('offline')), 10));", + "}", + "let exportModule;", + "let __mfDefaultExport;", + "function __mfAssignRemoteModule(mod) {", + " if (mod !== undefined) exportModule = mod;", + " __mfDefaultExport = exportModule?.default;", + " return exportModule;", + "}", + "let __mfRemotePending;", + "if (exportModule === undefined) {", + " __mfRemotePending = __mfStartRemoteLoad().then(__mfAssignRemoteModule);", + "}", + "export { exportModule as __moduleExports };", + "export const __mf_remote_pending =", + " __mfRemotePending ??", + " __mfStartRemoteLoad().then(__mfAssignRemoteModule);", + ...(dev + ? [ + "export function then(onFulfilled, onRejected) {", + " return (__mfRemotePending ?? Promise.resolve(exportModule))", + " .then(__mfAssignRemoteModule)", + " .then(() => ({ default: __mfDefaultExport, __mf_remote_pending: __mfRemotePending }))", + " .then(onFulfilled, onRejected);", + "}", + ] + : []), + "export const getAttempts = () => attempts;", + ].join("\n"); +} + +/** Writes a transformed wrapper and passes its URL and its raw module namespace to `check`. */ +async function importTransformed(code, check) { + const directory = mkdtempSync(join(tmpdir(), "mf-tanstack-wrapper-")); + try { + const file = join(directory, "wrapper.mjs"); + writeFileSync(file, code); + // `import()` adopts a namespace with a `then` export; a static import does not. + const namespaceFile = join(directory, "namespace.mjs"); + writeFileSync( + namespaceFile, + 'import * as wrapper from "./wrapper.mjs";\nexport { wrapper };\n', + ); + const { wrapper } = await import(pathToFileURL(namespaceFile).href); + await check(pathToFileURL(file).href, wrapper); + } finally { + rmSync(directory, { force: true, recursive: true }); + } +} + +test("server remote wrappers load again after a failed attempt", async () => { + const plugin = vitePlugin("tanstack-start-federation:remote-pending"); + const wrapper = remoteWrapper(); + const { code, map } = plugin.transform.handler(wrapper); + assert.equal(map, null); + const lines = code.split("\n"); + const exportLine = wrapper.split("\n").findIndex((line) => line.includes("__mf_remote_pending")); + assert.equal(lines[exportLine], "export let __mf_remote_pending =", "columns stay in place"); + + await importTransformed(code, async (_url, wrapperModule) => { + // The first importer sees the failure, without an unhandled rejection... + await assert.rejects(Promise.resolve(wrapperModule.__mf_remote_pending), /offline/); + await new Promise((resolve) => setImmediate(resolve)); + assert.equal(wrapperModule.getAttempts(), 1, "nothing retries until the next import"); + + // ...and the next one loads again. + assert.deepEqual(await wrapperModule.__mf_remote_pending, { default: "Card" }); + assert.equal(wrapperModule.getAttempts(), 2); + assert.deepEqual(wrapperModule.__moduleExports, { default: "Card" }); + assert.deepEqual(await wrapperModule.__mf_remote_pending, { default: "Card" }); + assert.equal(wrapperModule.getAttempts(), 2, "a successful load is reused"); + }); +}); + +test("development wrappers resolve after a failed load, so Vite does not cache it", async () => { + const plugin = vitePlugin("tanstack-start-federation:remote-pending"); + const wrapper = remoteWrapper({ dev: true }); + const { code } = plugin.transform.handler(wrapper); + assert.equal( + code.split("\n")[ + wrapper.split("\n").findIndex((line) => line.startsWith("export function then")) + ], + "function __mfThen1st(onFulfilled, onRejected) {", + "columns stay in place", + ); + + await importTransformed(code, async (url, wrapperModule) => { + // Like Vite's module runner, `import()` awaits the wrapper's `then` export once. + const cached = await import(url); + assert.equal(cached.default, undefined, "the first load failed"); + assert.equal(wrapperModule.getAttempts(), 1); + + // Importers read the load through `__mf_remote_pending`, which loads again. + assert.deepEqual(await cached.__mf_remote_pending, { default: "Card" }); + assert.equal(cached.default, "Card", "the cached exports are live"); + assert.deepEqual(cached.__moduleExports, { default: "Card" }); + assert.equal(wrapperModule.getAttempts(), 2); + }); +}); + +test("development SSR imports saved remote entries through Node", async () => { + const plugin = vitePlugin("tanstack-start-federation:native-temp-module-import"); + assert.equal(plugin.apply, "serve"); + const { filter } = plugin.transform; + const loaderPath = "/app/node_modules/@module-federation/vite/lib/ssrEntryLoader-DEH3eV1e.js"; + assert.ok(filter.id.test(loaderPath)); + assert.ok(filter.id.test(`${loaderPath}?v=b50fefa8`), "Vite's dependency query is allowed"); + assert.equal(filter.id.test("/app/node_modules/@module-federation/vite/lib/index.js"), false); + + const loader = [ + "async function importTempModule(filePath, versionKey) {", + "\treturn await import(", + "\t\t/* @vite-ignore */", + "\t\t`${filePath}?v=${encodeURIComponent(versionKey)}`", + ");", + "}", + "const other = () => import('./chunk.js');", + ].join("\n"); + const { code, map } = plugin.transform.handler(loader); + assert.equal(map, null); + assert.equal(code.split("\n").length, loader.split("\n").length + 2, "only appends lines"); + assert.match(code, /return await __mfNativeImport\(/); + assert.match(code, /const other = \(\) => import\('\.\/chunk\.js'\);/); + + // The helper imports through Node's loader. + const nativeImport = new Function(`${code}\nreturn __mfNativeImport;`)(); + assert.equal((await nativeImport("node:path")).sep, "/"); + + assert.equal(plugin.transform.handler("export default 1;"), undefined); +}); + test("Rsbuild shared defaults never mutate caller overrides", () => { const callerShared = { react: { eager: false, singleton: false } }; const before = structuredClone(callerShared); @@ -163,12 +339,17 @@ test("Rsbuild enables async startup unless the caller disables it", () => { }); test("Rsbuild federation is browser-only by default", () => { - for (const federation of [rsbuildRemote, rsbuildHost]) { - assert.deepEqual( - tanstackStartRsbuildModuleFederation({ federation }).map(({ name }) => name), - ["rsbuild:module-federation-enhanced", "tanstack-start-federation-client-compat"], - ); - } + const pluginNames = (federation) => + tanstackStartRsbuildModuleFederation({ federation }).map(({ name }) => name); + assert.deepEqual(pluginNames(rsbuildRemote), [ + "rsbuild:module-federation-enhanced", + "tanstack-start-federation-client-compat", + ]); + assert.deepEqual(pluginNames(rsbuildHost), [ + "rsbuild:module-federation-enhanced", + "tanstack-start-federation-remote-retry", + "tanstack-start-federation-client-compat", + ]); }); test("Rsbuild remotes resolve browser chunks from their own origin", () => { @@ -192,6 +373,33 @@ test("Rsbuild remotes resolve browser chunks from their own origin", () => { }); }); +test("Rsbuild hosts can load a remote again after it failed", () => { + const plugin = tanstackStartRsbuildModuleFederation({ federation: rsbuildHost }).find( + ({ name }) => name === "tanstack-start-federation-remote-retry", + ); + let transform; + plugin.setup({ transform: (options, handler) => (transform = { handler, options }) }); + const runtimeRoot = findPackageRoot( + createRequire(packageRequire.resolve("@module-federation/rsbuild-plugin")).resolve( + "@module-federation/webpack-bundler-runtime", + ), + ); + for (const file of ["remotes.cjs", "remotes.js"]) { + const path = join(runtimeRoot, "dist", file); + assert.ok(transform.options.test.test(path), file); + const code = readFileSync(path, "utf8"); + const patched = transform.handler({ code }); + assert.equal(patched.split("\n").length, code.split("\n").length, `${file} keeps its lines`); + // Without strict handling, a module that threw stays cached with empty exports; Rspack's + // strict handling caches the error instead. The failed module must leave the cache. + assert.match( + patched, + /webpackRequire\.m\[id\] = \(\) => \{ if \(webpackRequire\.c\) delete webpackRequire\.c\[id\];\s*throw error;/, + file, + ); + } +}); + test("Rsbuild SSR remotes ship a Node container with the browser assets", async () => { const plugins = tanstackStartRsbuildModuleFederation({ federation: rsbuildRemote, server: true }); assert.deepEqual( @@ -210,29 +418,52 @@ test("Rsbuild SSR remotes ship a Node container with the browser assets", async const rsbuildConfig = { environments: { client: {}, ssr: {} } }; assert.equal(hooks.modifyRsbuildConfig.order, "pre"); hooks.modifyRsbuildConfig.handler(rsbuildConfig); - assert.deepEqual(rsbuildConfig.environments["mf-server"].output, { - emitAssets: true, - target: "node", + assert.deepEqual(rsbuildConfig.environments["mf-server"], { + source: { entry: { "mf-server": "data:text/javascript," } }, + output: { cleanDistPath: false, emitAssets: false, target: "node" }, }); - for (const [publicPath, expected] of [ - ["http://127.0.0.1:3004/", "http://127.0.0.1:3004/ssr/"], - ["auto", "auto"], - ]) { + // The container sits next to the manifest; its chunks load over HTTP from the same + // public path as the browser assets. + for (const publicPath of ["http://127.0.0.1:3004/", "auto"]) { const client = { name: "client", output: { path: "/app/dist/client", publicPath } }; const container = { name: "mf-server", output: {} }; hooks.onBeforeCreateCompiler({ bundlerConfigs: [client, container] }); - assert.equal(container.output.path, join("/app/dist/client", "ssr")); - assert.equal(container.output.publicPath, expected); + assert.deepEqual(container.output, { + chunkFilename: "ssr/[id].[contenthash:8].js", + filename: "ssr/[name].js", + path: "/app/dist/client", + publicPath, + }); } + assert.throws( + () => hooks.onBeforeCreateCompiler({ bundlerConfigs: [{ name: "mf-server", output: {} }] }), + /Register tanstackStart\(\) before tanstackStartModuleFederation\(\)/, + ); + + // ES module importers of the container need `init` and `get` as detectable named exports. + let updated; + hooks.processAssets.handler({ + assets: { "remoteEntry.ssr.cjs": {} }, + compilation: { updateAsset: (name, update) => (updated = [name, update("container;")]) }, + sources: { ConcatSource: class extends Array {} }, + }); + assert.deepEqual(hooks.processAssets.options, { + stage: "summarize", + environments: ["mf-server"], + }); + assert.equal(updated[0], "remoteEntry.ssr.cjs"); + assert.match(updated[1].join(""), /module\.exports\.init = module\.exports\.init;/); + assert.match(updated[1].join(""), /module\.exports\.get = module\.exports\.get;/); const [client, container] = federationOptions({ federation: rsbuildRemote, server: true }); - assert.equal(container.filename, "remoteEntry.js"); + assert.equal(container.filename, "remoteEntry.ssr.cjs"); + assert.equal(container.manifest, false); assert.equal(container.remotes, undefined); const stats = await client.manifest.additionalData({ stats: { metaData: {} } }); assert.deepEqual(stats.metaData.ssrRemoteEntry, { - name: "remoteEntry.js", - path: "ssr", + name: "remoteEntry.ssr.cjs", + path: "", type: "commonjs-module", }); }); @@ -243,6 +474,7 @@ test("Rsbuild SSR hosts load remotes from an async-node CommonJS server", () => plugins.map(({ name }) => name), [ "rsbuild:module-federation-enhanced", + "tanstack-start-federation-remote-retry", "tanstack-start-federation-client-compat", "rsbuild:module-federation-enhanced", "tanstack-start-federation-ssr-compat", @@ -396,6 +628,31 @@ test("clean consumers resolve CJS and ESM entrypoints without the other adapter" ); }); +test("runtime subpaths load without a bundler adapter", () => { + // Route code imports the runtime helpers, so React is always installed next to them. + const consumer = createConsumer(["react"]); + try { + const consumerRequire = createRequire(join(consumer, "consumer.cjs")); + const runtime = consumerRequire("@module-federation/tanstack/runtime"); + const loader = consumerRequire("@module-federation/tanstack/node-entry-loader"); + assert.equal(typeof runtime.getRemoteStylesheets, "function"); + assert.equal(typeof runtime.lazyRemote, "function"); + assert.equal(typeof loader.default, "function"); + assert.equal(typeof loader.RemoteEntryError, "function"); + } finally { + rmSync(consumer, { force: true, recursive: true }); + } + + // Route code imports the runtime helpers in the browser too. + for (const file of ["runtime.js", "runtime.cjs"]) { + assert.doesNotMatch( + readFileSync(join(packageRoot, "dist", file), "utf8"), + /node:|@module-federation\/(?:vite|rsbuild-plugin)|@rsbuild\/core/, + file, + ); + } +}); + function assertConsumerImport(consumer, specifier) { const entry = join(consumer, "consumer.mjs"); writeFileSync( diff --git a/test/runtime.test.mjs b/test/runtime.test.mjs new file mode 100644 index 0000000..9cd4e95 --- /dev/null +++ b/test/runtime.test.mjs @@ -0,0 +1,307 @@ +import assert from "node:assert/strict"; +import { execFile, spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { createServer } from "node:http"; +import { createRequire } from "node:module"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { promisify } from "node:util"; +import { after, before, test } from "node:test"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const dist = join(root, "packages", "tanstack", "dist"); +const loaderUrl = pathToFileURL(join(dist, "node-entry-loader.js")).href; +const { default: nodeEntryLoader, RemoteEntryError } = await import(loaderUrl); +const { getRemoteStylesheets, lazyRemote } = await import( + pathToFileURL(join(dist, "runtime.js")).href +); +// The React copy the runtime resolves. +const packageRequire = createRequire(join(root, "packages", "tanstack", "package.json")); +const { createElement, Suspense } = packageRequire("react"); +const { renderToString } = packageRequire("react-dom/server"); +const { prerenderToNodeStream } = packageRequire("react-dom/static"); + +const files = new Map(); +const requests = []; +let server; +let origin; + +before(async () => { + server = createServer((request, response) => { + requests.push(request.url); + const file = files.get(request.url); + if (!file) { + response.writeHead(404).end(); + return; + } + response.writeHead(200, { "content-type": file.type }).end(file.body); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + origin = `http://127.0.0.1:${server.address().port}`; +}); + +after(() => server.close()); + +function serve(path, body, type = "text/javascript") { + files.set(path, { body, type }); + return `${origin}${path}`; +} + +function loadEntry(entry, type = "commonjs-module") { + const remoteInfo = { entry, entryGlobalName: "remote", name: "remote", type }; + return nodeEntryLoader().loadEntry({ origin: {}, remoteInfo }); +} + +test("loads HTTP CommonJS containers as CommonJS modules", async () => { + // Rsbuild containers import Node built-ins through `new Function` at runtime. + const entry = serve( + "/cjs/remoteEntry.ssr.cjs", + ` + const importNode = new Function("name", "return import(name)"); + module.exports = { + init() {}, + async get(request) { + const path = await importNode("node:path"); + return () => ({ request, separator: path.sep, filename: __filename }); + }, + }; + `, + ); + + const container = await loadEntry(entry); + const factory = await container.get("./Card"); + assert.deepEqual(factory(), { request: "./Card", separator: "/", filename: entry }); +}); + +test("reports which step failed and keeps the cause", async () => { + await assert.rejects(loadEntry(`${origin}/missing/remoteEntry.ssr.cjs`), (error) => { + assert.ok(error instanceof RemoteEntryError); + assert.equal(error.phase, "fetch"); + assert.equal(error.remote, "remote"); + assert.equal(error.url, `${origin}/missing/remoteEntry.ssr.cjs`); + assert.match(error.cause.message, /HTTP 404/); + return true; + }); + + const closed = createServer(); + await new Promise((resolve) => closed.listen(0, "127.0.0.1", resolve)); + const offline = `http://127.0.0.1:${closed.address().port}/remoteEntry.ssr.cjs`; + await new Promise((resolve) => closed.close(resolve)); + await assert.rejects(loadEntry(offline), { name: "RemoteEntryError", phase: "fetch" }); + + const throwing = serve("/throws/remoteEntry.ssr.cjs", 'throw new Error("container failed");'); + await assert.rejects(loadEntry(throwing), (error) => { + assert.equal(error.phase, "evaluate"); + assert.equal(error.cause.message, "container failed"); + return true; + }); + + const empty = serve("/empty/remoteEntry.ssr.cjs", "module.exports = {};"); + await assert.rejects(loadEntry(empty), { + message: /did not export a federation container/, + phase: "evaluate", + }); +}); + +test("leaves other entries to the federation runtime", async () => { + assert.equal(await loadEntry(`${origin}/remoteEntry.js`, "global"), undefined); + assert.equal(await loadEntry("/local/remoteEntry.ssr.cjs"), undefined); +}); + +test("Vite remotes ask for @module-federation/vite when the host lacks it", () => { + const app = mkdtempSync(join(tmpdir(), "mf-tanstack-host-")); + try { + const script = join(app, "server.mjs"); + writeFileSync( + script, + ` + const { default: loader } = await import(${JSON.stringify(loaderUrl)}); + const remoteInfo = { entry: "http://127.0.0.1:9/remoteEntry.ssr.js", name: "vite_remote", type: "module" }; + const origin = { name: "host", options: { shared: {} }, shareScopeMap: {} }; + try { + await loader().loadEntry({ origin, remoteInfo }); + } catch (error) { + console.log(JSON.stringify({ message: error.message, phase: error.phase })); + } + `, + ); + const result = spawnSync(process.execPath, [script], { cwd: app, encoding: "utf8" }); + assert.equal(result.status, 0, result.stderr); + const error = JSON.parse(result.stdout); + assert.equal(error.phase, "loader"); + assert.match(error.message, /Add @module-federation\/vite to the host's dependencies/); + } finally { + rmSync(app, { force: true, recursive: true }); + } +}); + +/** + * Runs `body` as an ES module in the Rsbuild SSR example host, which depends on + * @module-federation/vite and React, and returns the JSON it logs last. It runs in a child + * process so the loader's module registrations stay out of this one. + */ +async function runInHost(body) { + const app = join(root, "apps", "rsbuild-ssr-host"); + const directory = mkdtempSync(join(tmpdir(), "mf-tanstack-host-")); + try { + const script = join(directory, "server.mjs"); + writeFileSync( + script, + `const { default: loader } = await import(${JSON.stringify(loaderUrl)});\n${body}`, + ); + // Asynchronous, so this process's test server can answer the child. + const { stdout } = await promisify(execFile)(process.execPath, [script], { cwd: app }); + return JSON.parse(stdout.trim().split("\n").at(-1)); + } finally { + rmSync(directory, { force: true, recursive: true }); + } +} + +test("Vite remotes resolve node_modules React to the host's own instance", async () => { + const outcome = await runInHost(` + import { createRequire } from "node:module"; + const hostReact = { version: "host" }; + const unused = { version: "not loaded" }; + const origin = { + name: "host", + options: { shared: {} }, + shareScopeMap: { + default: { + react: { "19.3.0": { from: "host", loaded: true, lib: () => hostReact } }, + "react-dom": { "19.3.0": { from: "host", lib: () => unused } }, + }, + }, + }; + // A closed port: the load fails, after the host's modules are registered. + const remoteInfo = { entry: "http://127.0.0.1:9/remoteEntry.js", name: "vite_remote", type: "module" }; + const phase = await loader().loadEntry({ origin, remoteInfo }).catch((error) => error.phase); + const appRequire = createRequire(process.cwd() + "/package.json"); + console.log(JSON.stringify({ + phase, + react: appRequire("react") === hostReact, + reactDom: appRequire("react-dom") === unused, + })); + `); + assert.deepEqual(outcome, { phase: "fetch", react: true, reactDom: false }); +}); + +test("Vite remotes report a reachable remote without a server entry", async () => { + // A Vite remote built without a server entry: its manifest lists no ssrRemoteEntry. + const entry = serve("/no-ssr/remoteEntry.js", "export {};"); + serve( + "/no-ssr/mf-manifest.json", + JSON.stringify({ + exposes: [], + metaData: { remoteEntry: { name: "remoteEntry.js", path: "" } }, + }), + "application/json", + ); + const outcome = await runInHost(` + const origin = { name: "host", options: { shared: {} }, shareScopeMap: {} }; + const remoteInfo = { entry: ${JSON.stringify(entry)}, name: "vite_remote", type: "module" }; + const error = await loader().loadEntry({ origin, remoteInfo }).catch((error) => error); + console.log(JSON.stringify({ message: error.message, phase: error.phase })); + `); + assert.equal(outcome.phase, "loader"); + assert.match(outcome.message, /could not load the server entry of remote "vite_remote"/); +}); + +test("getRemoteStylesheets resolves an expose's CSS from its manifest", async () => { + const manifest = (publicPath) => + JSON.stringify({ + exposes: [ + { + assets: { css: { async: ["assets/lazy.css"], sync: ["assets/card.css"] } }, + name: "Card", + path: "./Card", + }, + ], + metaData: { publicPath }, + }); + + const auto = serve("/auto/mf-manifest.json", manifest("auto"), "application/json"); + assert.deepEqual(await getRemoteStylesheets(auto, "./Card"), [ + `${origin}/auto/assets/card.css`, + `${origin}/auto/assets/lazy.css`, + ]); + assert.deepEqual(await getRemoteStylesheets(auto, "Card"), [ + `${origin}/auto/assets/card.css`, + `${origin}/auto/assets/lazy.css`, + ]); + await assert.rejects(getRemoteStylesheets(auto, "./Missing"), /does not expose "\.\/Missing"/); + + const cdn = serve( + "/cdn/mf-manifest.json", + manifest("https://cdn.test/remote/"), + "application/json", + ); + assert.deepEqual(await getRemoteStylesheets(cdn, "./Card"), [ + "https://cdn.test/remote/assets/card.css", + "https://cdn.test/remote/assets/lazy.css", + ]); +}); + +test("getRemoteStylesheets reuses manifests briefly and retries failures", async () => { + const path = "/cache/mf-manifest.json"; + const url = `${origin}${path}`; + const fetches = () => requests.filter((request) => request === path).length; + + await assert.rejects(getRemoteStylesheets(url, "./Card"), /HTTP 404/); + serve(path, JSON.stringify({ exposes: [{ name: "Card", path: "./Card" }] }), "application/json"); + assert.deepEqual(await getRemoteStylesheets(url, "./Card"), []); + assert.deepEqual(await getRemoteStylesheets(url, "./Card"), []); + assert.equal(fetches(), 2, "the failure was retried, the success reused"); + + assert.deepEqual(await getRemoteStylesheets(url, "./Card", { maxAgeMs: 0 }), []); + assert.equal(fetches(), 3, "an expired manifest is fetched again"); +}); + +/** Server-renders `element` to HTML once every Suspense boundary has settled. */ +async function prerender(element) { + const errors = []; + const { prelude } = await prerenderToNodeStream( + createElement(Suspense, { fallback: "fallback" }, element), + { onError: (error) => void errors.push(error.message) }, + ); + let html = ""; + for await (const chunk of prelude) html += chunk; + return { errors, html }; +} + +test("lazyRemote renders the remote, and loads again only after retryAfterMs", async () => { + let online = false; + let loads = 0; + const Card = ({ label }) => createElement("p", null, `card ${label}`); + const RemoteCard = lazyRemote( + async () => { + loads++; + if (!online) throw new Error("remote offline"); + return { default: Card }; + }, + { retryAfterMs: 100 }, + ); + + const offline = await prerender(createElement(RemoteCard, { label: "one" })); + assert.deepEqual(offline.errors, ["remote offline"]); + assert.doesNotMatch(offline.html, /card/); + assert.equal(loads, 1); + + // The remote is back, but the failure stands until retryAfterMs passes. + online = true; + const tooSoon = await prerender(createElement(RemoteCard, { label: "two" })); + assert.deepEqual(tooSoon.errors, ["remote offline"]); + assert.equal(loads, 1); + + await new Promise((resolve) => setTimeout(resolve, 120)); + const recovered = await prerender(createElement(RemoteCard, { label: "three" })); + assert.deepEqual(recovered.errors, []); + assert.match(recovered.html, /card three/); + assert.equal(loads, 2); + + // A loaded remote is never loaded again. + online = false; + await new Promise((resolve) => setTimeout(resolve, 120)); + assert.match(renderToString(createElement(RemoteCard, { label: "four" })), /card four/); + assert.equal(loads, 2); +}); diff --git a/test/support/examples.mjs b/test/support/examples.mjs index a5620d8..6eeb2b0 100644 --- a/test/support/examples.mjs +++ b/test/support/examples.mjs @@ -4,6 +4,7 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { after, before, test } from "node:test"; import { chromium } from "playwright"; +import { getRemoteStylesheets } from "../../packages/tanstack/dist/runtime.js"; const root = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); @@ -25,12 +26,19 @@ const cards = { rsbuildSsr: "Federated SSR from Rsbuild", }; +// The remotes each SSR host renders on the server, across both bundlers. +const serverRendered = { + viteHost: ["viteRemote", "rsbuildSsrRemote"], + rsbuildSsrHost: ["rsbuildSsrRemote", "viteRemote"], +}; + +const remoteCards = { viteRemote: cards.vite, rsbuildSsrRemote: cards.rsbuildSsr }; + /** * Starts the six example apps with `script` ("start" for development servers, "preview" * for production builds) and registers the shared federation scenarios against them. - * `outageRemotes` lists the remotes the outage scenario stops. */ -export function describeExamples(script, { outageRemotes = remotes } = {}) { +export function describeExamples(script) { const processes = {}; let browser; @@ -51,25 +59,69 @@ export function describeExamples(script, { outageRemotes = remotes } = {}) { await Promise.all(Object.values(processes).map(stopApp)); }); - test("SSR hosts render remote markup for concurrent first requests", async () => { - for (const [host, marker, hostName] of [ - ["viteHost", cards.vite, "Vite host"], - ["rsbuildSsrHost", cards.rsbuildSsr, "Rsbuild SSR host"], - ]) { + test("SSR hosts render Vite and Rsbuild remote markup for concurrent first requests", async () => { + for (const [host, remoteKeys] of Object.entries(serverRendered)) { const responses = await Promise.all( - Array.from({ length: 8 }, () => fetch(apps[host].url).then(readResponse)), + Array.from({ length: 8 }, () => + fetch(apps[host].url, { signal: AbortSignal.timeout(60_000) }).then(readResponse), + ), ); for (const { body, status } of responses) { assert.equal(status, 200, host); - assert.ok(body.includes(marker), `${host} initial HTML contains the remote card`); - assert.ok( - body.includes("Rendered on the server"), - `${host} renders the remote on the server`, + for (const remote of remoteKeys) { + assert.ok(body.includes(remoteCards[remote]), `${host} initial HTML contains ${remote}`); + } + assert.equal( + body.match(/Rendered on the server/g)?.length, + remoteKeys.length, + `${host} renders every remote on the server`, ); } } }); + test("server-rendered remotes are styled before JavaScript runs", async () => { + for (const [host, remoteKeys] of Object.entries(serverRendered)) { + const stylesheets = Object.fromEntries( + await Promise.all( + remoteKeys.map(async (remote) => [ + remote, + await getRemoteStylesheets( + new URL("mf-manifest.json", apps[remote].url).href, + "./StatusCard", + ), + ]), + ), + ); + // A Vite dev server injects CSS from JavaScript, so its manifest lists none. Builds + // must list every remote's stylesheet. + const styled = remoteKeys.filter((remote) => stylesheets[remote].length > 0); + if (script === "preview") assert.deepEqual(styled, remoteKeys, host); + + const context = await browser.newContext({ javaScriptEnabled: false }); + try { + const page = await context.newPage(); + // Without JavaScript, streamed Suspense content stays hidden, so warm the host up + // first: once its remotes are loaded, it renders them inline. + await fetch(apps[host].url, { signal: AbortSignal.timeout(60_000) }); + await page.goto(apps[host].url); + const loaded = await page.evaluate(() => [...document.styleSheets].map(({ href }) => href)); + + for (const remote of styled) { + for (const href of stylesheets[remote]) { + assert.ok(loaded.includes(href), `${host} loads ${href}`); + } + const background = await page + .locator("article", { hasText: remoteCards[remote] }) + .evaluate((card) => getComputedStyle(card).backgroundColor); + assert.notEqual(background, "rgba(0, 0, 0, 0)", `${host} styles ${remote}`); + } + } finally { + await context.close(); + } + } + }); + test("remote manifests reference reachable assets", async () => { for (const key of remotes) { const manifestUrl = new URL("mf-manifest.json", apps[key].url).href; @@ -86,10 +138,11 @@ export function describeExamples(script, { outageRemotes = remotes } = {}) { test("Vite host hydrates the Vite and Rsbuild remotes with one React instance", async () => { await withPage(apps.viteHost.url, async (page) => { - await page.getByText("Hydrated on the host").waitFor({ timeout: 20_000 }); - await assertInteractive(page, cards.vite); - await assertInteractive(page, cards.rsbuild); - await assertHostContext(page, [cards.vite, cards.rsbuild], "Vite host"); + await page.getByText("Hydrated on the host").first().waitFor({ timeout: 20_000 }); + for (const card of [cards.vite, cards.rsbuildSsr, cards.rsbuild]) { + await assertInteractive(page, card); + } + await assertHostContext(page, [cards.vite, cards.rsbuildSsr, cards.rsbuild], "Vite host"); }); }); @@ -101,11 +154,12 @@ export function describeExamples(script, { outageRemotes = remotes } = {}) { }); }); - test("Rsbuild SSR host hydrates the server-rendered Rsbuild remote", async () => { + test("Rsbuild SSR host hydrates the server-rendered Rsbuild and Vite remotes", async () => { await withPage(apps.rsbuildSsrHost.url, async (page) => { - await page.getByText("Hydrated on the host").waitFor({ timeout: 20_000 }); + await page.getByText("Hydrated on the host").first().waitFor({ timeout: 20_000 }); await assertInteractive(page, cards.rsbuildSsr); - await assertHostContext(page, [cards.rsbuildSsr], "Rsbuild SSR host"); + await assertInteractive(page, cards.vite); + await assertHostContext(page, [cards.rsbuildSsr, cards.vite], "Rsbuild SSR host"); }); }); @@ -125,28 +179,22 @@ export function describeExamples(script, { outageRemotes = remotes } = {}) { // directly in development, and 1.23.0 broke the production path too (1.22.x works). test.todo("Vite remote hydrates as a standalone app"); - // Runs last: it stops remotes, then starts them again. + // The last two scenarios stop remotes, then start them again. test("hosts fall back while remotes are offline and recover when they return", async () => { - await Promise.all(outageRemotes.map((key) => stopApp(processes[key]))); - const offline = (key) => outageRemotes.includes(key); + await Promise.all(remotes.map((key) => stopApp(processes[key]))); - for (const [host, fallbacks] of [ + for (const [host, expected] of [ [ "viteHost", [ - offline("viteRemote") && "Vite remote is unavailable", - offline("rsbuildRemote") && "Rsbuild remote is unavailable", + "Vite remote is unavailable", + "Rsbuild SSR remote is unavailable", + "Rsbuild remote is unavailable", ], ], - [ - "rsbuildHost", - [(offline("viteRemote") || offline("rsbuildRemote")) && "Remote unavailable"], - ], - ["rsbuildSsrHost", [offline("rsbuildSsrRemote") && "Rsbuild SSR remote is unavailable"]], + ["rsbuildHost", ["Remote unavailable"]], + ["rsbuildSsrHost", ["Rsbuild SSR remote is unavailable", "Vite remote is unavailable"]], ]) { - const expected = fallbacks.filter(Boolean); - if (expected.length === 0) continue; - const { status } = await fetch(apps[host].url); assert.equal(status, 200, `${host} still serves its page`); await withPage( @@ -162,29 +210,69 @@ export function describeExamples(script, { outageRemotes = remotes } = {}) { ); } - await Promise.all(outageRemotes.map(start)); - - for (const [host, remote, marker] of [ - ["viteHost", "viteRemote", cards.vite], - ["rsbuildSsrHost", "rsbuildSsrRemote", cards.rsbuildSsr], - ]) { - if (!offline(remote)) continue; - const { body } = await waitForResponse(apps[host].url, processes[host], ({ body }) => - body.includes(marker), - ); - assert.ok(body.includes("Rendered on the server"), `${host} renders the remote again`); - } + await Promise.all(remotes.map(start)); + await assertServerRendersRemotes("again"); await withPage(apps.rsbuildHost.url, async (page) => { await assertInteractive(page, cards.vite); await assertInteractive(page, cards.rsbuild); }); await withPage(apps.viteHost.url, async (page) => { - await page.getByText("Hydrated on the host").waitFor({ timeout: 20_000 }); + await page.getByText("Hydrated on the host").first().waitFor({ timeout: 20_000 }); + for (const card of [cards.vite, cards.rsbuildSsr, cards.rsbuild]) { + await assertInteractive(page, card); + } + }); + await withPage(apps.rsbuildSsrHost.url, async (page) => { + await assertInteractive(page, cards.rsbuildSsr); await assertInteractive(page, cards.vite); - await assertInteractive(page, cards.rsbuild); }); }); + test("SSR hosts that start during an outage render the remotes once they return", async () => { + await Promise.all(remotes.map((key) => stopApp(processes[key]))); + const ssrHosts = Object.keys(serverRendered); + await Promise.all(ssrHosts.map((key) => stopApp(processes[key]))); + await Promise.all(ssrHosts.map(start)); + + // The first requests fail to load every remote. + for (const host of ssrHosts) { + const { body, status } = await readResponse( + await fetch(apps[host].url, { signal: AbortSignal.timeout(60_000) }), + ); + assert.equal(status, 200, `${host} serves its page while remotes are offline`); + assert.equal(body.includes("Rendered on the server"), false, host); + } + + await Promise.all(remotes.map(start)); + await assertServerRendersRemotes("after starting during an outage"); + await withPage(apps.viteHost.url, async (page) => { + await page.getByText("Hydrated on the host").first().waitFor({ timeout: 20_000 }); + for (const card of [cards.vite, cards.rsbuildSsr, cards.rsbuild]) { + await assertInteractive(page, card); + } + await assertHostContext(page, [cards.vite, cards.rsbuildSsr, cards.rsbuild], "Vite host"); + }); + await withPage(apps.rsbuildSsrHost.url, async (page) => { + await assertInteractive(page, cards.rsbuildSsr); + await assertInteractive(page, cards.vite); + await assertHostContext(page, [cards.rsbuildSsr, cards.vite], "Rsbuild SSR host"); + }); + }); + + /** Waits until every SSR host renders all of its remotes on the server. */ + async function assertServerRendersRemotes(when) { + for (const [host, remoteKeys] of Object.entries(serverRendered)) { + const { body } = await waitForResponse(apps[host].url, processes[host], ({ body }) => + remoteKeys.every((remote) => body.includes(remoteCards[remote])), + ); + assert.equal( + body.match(/Rendered on the server/g)?.length, + remoteKeys.length, + `${host} renders its remotes on the server ${when}`, + ); + } + } + /** Opens a page, runs `check`, and fails on uncaught errors or console errors and warnings. */ async function withPage(url, check, { allowErrors = false } = {}) { const page = await browser.newPage(); From 529951c5059bc487bf413324f2a64e34dc303f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Wed, 7 Oct 2026 00:29:15 +0200 Subject: [PATCH 16/18] docs: mention lazyRemote in the root README --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5d8b7d7..628ee7f 100644 --- a/README.md +++ b/README.md @@ -98,9 +98,12 @@ loads remotes on the server from an async-node CommonJS server build, and renders Vite remotes too when `@module-federation/vite` is installed. See the [package README](packages/tanstack/README.md#ssr-federation). -`@module-federation/tanstack/runtime` exports `getRemoteStylesheets`, which -reads a remote's stylesheets from its manifest so a route can put them in the -server response's ``. +`@module-federation/tanstack/runtime` has two helpers for route code: +`lazyRemote`, a `React.lazy` that loads a failed remote again instead of +failing until the server restarts, and `getRemoteStylesheets`, which reads a +remote's stylesheets from its manifest so a route can put them in the server +response's ``. See +[rendering remotes on the server](packages/tanstack/README.md#rendering-remotes-on-the-server). ## Support status From 131ec2b8251d76f6bb845382cdbc470285aae2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Wed, 7 Oct 2026 00:58:58 +0200 Subject: [PATCH 17/18] docs: link the upstream reports behind each workaround The Vite adapter's workarounds and TODO.md now point at module-federation/vite#1421-#1424, so each workaround can go once its fix ships. --- TODO.md | 55 +++++++++++++++++----------- packages/tanstack/src/rsbuild.ts | 3 +- packages/tanstack/src/vite-compat.ts | 3 ++ 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/TODO.md b/TODO.md index a513544..468fc32 100644 --- a/TODO.md +++ b/TODO.md @@ -110,31 +110,42 @@ the remotes once they return. ### Upstream issues -Report these to `module-federation/vite` (the first three are worked around -here) and `module-federation/core`. - -- [ ] `@module-federation/vite`: `__mf_remote_pending` is exported but never - awaited, so a remote outage becomes an unhandled rejection in a dev - server. Worked around by `remotePendingPlugin`. -- [ ] `@module-federation/vite`: `getSSREntry` returns `type: "module"` for any - `.ssr.js` URL, ignoring the remote's declared format. Worked around with - the `.cjs` container name. -- [ ] `@module-federation/vite`: in development, the SSR loader imports saved - remote entries through Vite's module runner. Worked around by - `nativeTempModuleImportPlugin`. -- [ ] `@module-federation/vite`: a server wrapper makes one load attempt, so a - remote that fails its first load fails for the life of the process. In - development, the wrapper's `then` export also makes Vite's module runner - cache the rejection. Worked around by `remotePendingPlugin`. Browser - wrappers make one attempt too, which a page reload clears. +Reported to `module-federation/vite`; the adapters work around each one until a +release ships the fix. Remove the workaround when it does. + +- [ ] An unawaited `__mf_remote_pending` turns a remote outage into an + unhandled rejection that exits a dev server. + [vite#1421](https://github.com/module-federation/vite/pull/1421) (PR). + Worked around by `remotePendingPlugin`. +- [ ] A server wrapper makes one load attempt, so a remote that fails its + first load fails for the life of the process; in development, Vite's + module runner also caches the wrapper's rejected `then` export. + [vite#1424](https://github.com/module-federation/vite/issues/1424). + Worked around by `remotePendingPlugin`. Browser wrappers make one + attempt too, which a page reload clears. +- [ ] The SSR loader cannot load a CommonJS server container from a manifest: + it treats any `.ssr.js` URL as an ES module, joins `path` and `name` + without a separator, returns the CommonJS namespace instead of + `namespace.default`, and in development imports the saved entry through + Vite's module runner. + [vite#1423](https://github.com/module-federation/vite/issues/1423). + Worked around by the root-level `remoteEntry.ssr.cjs` container with + named-export re-assignments, and by `nativeTempModuleImportPlugin`. +- [ ] Rollup builds (Vite 5 to 7) keep an unresolved + `virtual:mf-exposes-ssr:` import in the SSR entry outside Nuxt. + [vite#1422](https://github.com/module-federation/vite/pull/1422) (PR). + Not worked around: this package requires Vite 8. +- [ ] A Vite remote with `exposes` does not hydrate when opened directly in + development (`forceClientInjected`), and since 1.23.0 not in production + either (`x is not a function` from the `react-dom/client` share; 1.22.0 + and 1.22.1 work). Fixed on `main` (#1413, #1415, #1420); update when a + release ships. + +Still to report to `module-federation/core`: + - [ ] `@module-federation/webpack-bundler-runtime`: a remote module that failed stays in the module cache with empty exports, although the load itself is retried. Worked around by `remoteRetryPlugin`. -- [ ] `@module-federation/vite`: a Vite remote with `exposes` does not hydrate - when opened directly in development (`forceClientInjected`), and since - 1.23.0 not in production either (`x is not a function` from the - `react-dom/client` share; 1.22.0 and 1.22.1 work). Fixed on `main` - (#1413, #1415, #1420); update when a release ships. - [ ] `@module-federation/node`: a container without an absolute public path logs `Backup remote entry found` for every chunk it loads through a Vite host. diff --git a/packages/tanstack/src/rsbuild.ts b/packages/tanstack/src/rsbuild.ts index b8847ce..274f33e 100644 --- a/packages/tanstack/src/rsbuild.ts +++ b/packages/tanstack/src/rsbuild.ts @@ -42,7 +42,8 @@ export type TanStackStartRsbuildModuleFederationOptions = { // resolve its chunks from either the container URL (Module Federation's Node runtime) or // the manifest URL (`@module-federation/vite`), so both must share a directory. The // `.cjs` extension matters: `@module-federation/vite` treats any `.ssr.js` URL as an ES -// module, while for other names it reads the format from the manifest. +// module, while for other names it reads the format from the manifest +// (module-federation/vite#1423). const SERVER_CONTAINER_FILENAME = "remoteEntry.ssr.cjs"; const SERVER_CHUNK_DIR = "ssr"; const SERVER_CONTAINER_ENVIRONMENT = "mf-server"; diff --git a/packages/tanstack/src/vite-compat.ts b/packages/tanstack/src/vite-compat.ts index e9c4fda..63e3ea1 100644 --- a/packages/tanstack/src/vite-compat.ts +++ b/packages/tanstack/src/vite-compat.ts @@ -91,6 +91,8 @@ export function then(onFulfilled, onRejected) { * - A wrapper re-evaluated after its remote was cached exports a fresh load that nothing * awaits, so a remote outage becomes an unhandled rejection that exits the Vite dev * server. Awaiting the export still rejects. Wrappers that handle it are left alone. + * + * Reported as module-federation/vite#1424 and fixed for the rejection in #1421. */ export function remotePendingPlugin(): Plugin { return { @@ -126,6 +128,7 @@ const TEMP_MODULE_IMPORT = * import goes through Vite's module runner, which evaluates every file as an ES module. * CommonJS entries, such as Rsbuild server containers, then fail with "module is not * defined". Importing through Node lets it choose the format, as production builds do. + * Reported as module-federation/vite#1423. */ export function nativeTempModuleImportPlugin(): Plugin { return { From 94601dadc42c9aa822a453e278bef900a944e1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Wed, 7 Oct 2026 17:34:21 +0200 Subject: [PATCH 18/18] fix: require @module-federation/vite 1.23.3 and keep dev bundles out of dist @module-federation/vite 1.23.3 ships the fixes reported from this package (module-federation/vite#1421, #1422, #1425, #1426), so the Vite adapter drops its workarounds: the remote wrapper retry and the dev temp-file import. The Rsbuild server container no longer needs static named-export re-assignments. Every remote now also runs as a standalone app, so the example scenarios test the Vite remote standalone too, instead of marking it todo. A host's Rsbuild dev server wrote its server bundle to dist/server, replacing a production build that `rsbuild preview` serves. The dev bundle now goes to node_modules/.cache/tanstack-start-federation/, and the development scenarios check that dist/server is left alone. --- README.md | 3 +- TODO.md | 87 +++++------- apps/README.md | 5 +- apps/rsbuild-ssr-host/package.json | 2 +- packages/tanstack/CHANGELOG.md | 4 +- packages/tanstack/README.md | 19 ++- packages/tanstack/package.json | 4 +- packages/tanstack/src/rsbuild.ts | 37 ++--- packages/tanstack/src/vite-compat.ts | 130 ----------------- packages/tanstack/src/vite.ts | 8 +- pnpm-lock.yaml | 14 +- pnpm-workspace.yaml | 2 + test/release-contracts.test.mjs | 199 +++------------------------ test/support/examples.mjs | 24 +++- 14 files changed, 122 insertions(+), 416 deletions(-) diff --git a/README.md b/README.md index 628ee7f..ad5f4af 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,8 @@ and SSR environments from one Vite configuration, so the wrapper leaves target selection to `@module-federation/vite` for each environment. The package supports Node 22.18+, 24.11+, and 26+, plus TanStack Start -1.167.43+. The Vite adapter requires Vite 8. +1.167.43+. The Vite adapter requires Vite 8 and `@module-federation/vite` +1.23.3+. The package root remains a Vite compatibility export. New Vite projects can import `/vite` explicitly. Rsbuild projects must import `/rsbuild`: diff --git a/TODO.md b/TODO.md index 468fc32..ef738f2 100644 --- a/TODO.md +++ b/TODO.md @@ -42,17 +42,18 @@ the remotes once they return. - [x] Give remotes `publicPath: "auto"`. TanStack Start derives `/` from `server.base`, which made production remotes request their chunks from the host's origin, and made Node hosts fail to fetch them. -- [x] Make the container loadable by `@module-federation/vite`: a root-level - file (its SSR loader joins `path` and `name` without a separator), a - `.cjs` name (it treats every `.ssr.js` URL as an ES module), and static - `init`/`get` re-exports so ES module importers see named exports. +- [x] Keep the container next to the manifest, so its chunks resolve the same + way from the container URL (Module Federation's Node runtime) and from + the manifest URL (`@module-federation/vite`). ### Rsbuild hosts on the server - [x] Load remotes in a host's server bundle as async-node CommonJS, keep `dist/server/index.js` as an ES module that re-exports it, and write the server output to disk in development so async-node chunks match the - in-memory entry. + in-memory entry. The dev bundle goes to + `node_modules/.cache/tanstack-start-federation/`, so it never replaces a + production build in `dist/server`. - [x] `@module-federation/tanstack/node-entry-loader` compiles CommonJS containers as CommonJS modules, so their `import()` calls work in Rsbuild's development runner without experimental Node flags or @@ -74,12 +75,9 @@ the remotes once they return. is `^8.0.0`, and the adapter stops with an error on older versions: on Vite 7, `@module-federation/vite` hangs server-side remote loads in development and emits an unresolved `virtual:` import in production. -- [x] Work around an unhandled rejection in `@module-federation/vite` that - exited a Vite host's dev server when a server-rendered remote went - offline: the adapter handles the `__mf_remote_pending` export. -- [x] Work around `@module-federation/vite` importing saved remote entries - through Vite's dev module runner, which evaluates CommonJS containers as - ES modules: the adapter imports them through Node in development. +- [x] Require `@module-federation/vite` 1.23.3, which ships the fixes for the + problems found here (see "Fixed upstream" below), so the adapter needs + no workarounds for it. ### SSR quality and hardening @@ -93,12 +91,14 @@ the remotes once they return. offline, and render remote markup again once they return, in development and production. - [x] Hosts recover from a remote that failed its first load, such as a host - started during an outage. Four caches kept the failure, each now - cleared: the Vite server wrapper's single load attempt, Vite's dev module - runner caching the wrapper's rejected `then` export, Rspack's module - cache (a failed remote module kept empty exports; Rspack's - `strictModuleExceptionHandling` caches the error instead, so the adapter - removes the module from the cache), and `React.lazy` (`lazyRemote`). + started during an outage. Three layers kept the failure: + `@module-federation/vite`'s single load attempt (fixed in 1.23.3), + Rspack's module cache (a failed remote module kept empty exports; + Rspack's `strictModuleExceptionHandling` caches the error instead, so + the adapter removes the module from the cache), and `React.lazy` + (`lazyRemote`). +- [x] Every remote, including the Vite remote, works as a standalone TanStack + Start app (`@module-federation/vite` 1.23.3). - [x] `useState`, `useEffect`, Suspense, and error boundaries across the federation boundary (counter, hydration status, lazy remote components, `RemoteBoundary`). @@ -110,38 +110,7 @@ the remotes once they return. ### Upstream issues -Reported to `module-federation/vite`; the adapters work around each one until a -release ships the fix. Remove the workaround when it does. - -- [ ] An unawaited `__mf_remote_pending` turns a remote outage into an - unhandled rejection that exits a dev server. - [vite#1421](https://github.com/module-federation/vite/pull/1421) (PR). - Worked around by `remotePendingPlugin`. -- [ ] A server wrapper makes one load attempt, so a remote that fails its - first load fails for the life of the process; in development, Vite's - module runner also caches the wrapper's rejected `then` export. - [vite#1424](https://github.com/module-federation/vite/issues/1424). - Worked around by `remotePendingPlugin`. Browser wrappers make one - attempt too, which a page reload clears. -- [ ] The SSR loader cannot load a CommonJS server container from a manifest: - it treats any `.ssr.js` URL as an ES module, joins `path` and `name` - without a separator, returns the CommonJS namespace instead of - `namespace.default`, and in development imports the saved entry through - Vite's module runner. - [vite#1423](https://github.com/module-federation/vite/issues/1423). - Worked around by the root-level `remoteEntry.ssr.cjs` container with - named-export re-assignments, and by `nativeTempModuleImportPlugin`. -- [ ] Rollup builds (Vite 5 to 7) keep an unresolved - `virtual:mf-exposes-ssr:` import in the SSR entry outside Nuxt. - [vite#1422](https://github.com/module-federation/vite/pull/1422) (PR). - Not worked around: this package requires Vite 8. -- [ ] A Vite remote with `exposes` does not hydrate when opened directly in - development (`forceClientInjected`), and since 1.23.0 not in production - either (`x is not a function` from the `react-dom/client` share; 1.22.0 - and 1.22.1 work). Fixed on `main` (#1413, #1415, #1420); update when a - release ships. - -Still to report to `module-federation/core`: +To report to `module-federation/core`: - [ ] `@module-federation/webpack-bundler-runtime`: a remote module that failed stays in the module cache with empty exports, although the load @@ -150,6 +119,26 @@ Still to report to `module-federation/core`: logs `Backup remote entry found` for every chunk it loads through a Vite host. +Fixed upstream in `@module-federation/vite` 1.23.3: + +- [x] An unawaited `__mf_remote_pending` turned a remote outage into an + unhandled rejection that exited a dev server + ([vite#1421](https://github.com/module-federation/vite/pull/1421)). +- [x] A server wrapper made one load attempt, so a remote that failed its + first load failed for the life of the process + ([vite#1424](https://github.com/module-federation/vite/issues/1424), + fixed by [vite#1426](https://github.com/module-federation/vite/pull/1426)). +- [x] The SSR loader could not load a CommonJS server container from a + manifest ([vite#1423](https://github.com/module-federation/vite/issues/1423), + fixed by [vite#1425](https://github.com/module-federation/vite/pull/1425)). +- [x] Rollup builds (Vite 5 to 7) kept an unresolved `virtual:mf-exposes-ssr:` + import in the SSR entry outside Nuxt + ([vite#1422](https://github.com/module-federation/vite/pull/1422)). +- [x] A Vite remote with `exposes` did not hydrate when opened directly + ([vite#1413](https://github.com/module-federation/vite/issues/1413), + fixed by [vite#1415](https://github.com/module-federation/vite/pull/1415) + and [vite#1420](https://github.com/module-federation/vite/pull/1420)). + ### SSR quality - [ ] Revalidate a changed remote manifest and server entry without restarting diff --git a/apps/README.md b/apps/README.md index 25b1084..3fde10c 100644 --- a/apps/README.md +++ b/apps/README.md @@ -61,6 +61,5 @@ back. The same holds for a host started before its remotes. - Each `start` script runs the bundler's development server. In Vite's CLI the bare `vite` command starts the dev server; `vite start` would treat `start` as a project directory. -- Opening the Vite remote directly (port 3001) server-renders the page but does - not hydrate it: `@module-federation/vite` waits for a host to initialize a - container with `exposes`. It hydrates when loaded through a host. +- Every remote also works as a standalone TanStack Start app: open port 3001, + 3002, or 3004 directly, and its card shows "Host: standalone". diff --git a/apps/rsbuild-ssr-host/package.json b/apps/rsbuild-ssr-host/package.json index 60773f7..c8e67d3 100644 --- a/apps/rsbuild-ssr-host/package.json +++ b/apps/rsbuild-ssr-host/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@module-federation/runtime": "2.9.2", - "@module-federation/vite": "1.23.2", + "@module-federation/vite": "1.23.3", "@tanstack/react-router": "1.170.41", "@tanstack/react-start": "1.168.60", "example-host-context": "workspace:*", diff --git a/packages/tanstack/CHANGELOG.md b/packages/tanstack/CHANGELOG.md index 77d5b7e..8486c6e 100644 --- a/packages/tanstack/CHANGELOG.md +++ b/packages/tanstack/CHANGELOG.md @@ -14,6 +14,8 @@ Initial release: Module Federation for TanStack Start on Vite and Rsbuild. `@module-federation/rsbuild-plugin` with script-compatible browser output, async startup, role-aware React sharing, and `publicPath: "auto"` for remotes, so production remotes load from their own origin. +- Requires `@module-federation/vite` 1.23.3, which fixes federated SSR of + CommonJS server containers and retries failed server-side remote loads. - Rsbuild SSR federation with `server: true`: remotes ship a Node container, `remoteEntry.ssr.cjs`, with their browser assets and advertise it in their manifest; hosts load remotes on the server from an async-node CommonJS build @@ -29,6 +31,6 @@ Initial release: Module Federation for TanStack Start on Vite and Rsbuild. `getRemoteStylesheets`, which reads a remote's stylesheets from its manifest so server-rendered remote markup is styled before JavaScript loads. - Hosts that hit a remote outage, including at startup, render the remote again - once it returns. + once it returns, and every remote also runs as a standalone app. - Tested in development and production builds on Node 22.18, 24, and 26, with Vite 8.3, Rsbuild 2.2, TanStack Start 1.168, and React 19.3. diff --git a/packages/tanstack/README.md b/packages/tanstack/README.md index 5008356..f54143b 100644 --- a/packages/tanstack/README.md +++ b/packages/tanstack/README.md @@ -139,7 +139,10 @@ Pass `server: true` (or an options object) to federate on the server: Federation Node runtime. TanStack Start's server output becomes async-node CommonJS (`dist/server/index.cjs`), with `dist/server/index.js` re-exporting it, so `rsbuild preview` and deployments that import `index.js` keep working. - Rsbuild remotes need `server: true` too. Vite remotes need nothing extra on + The dev server writes its server bundle to + `node_modules/.cache/tanstack-start-federation/` instead, so a production + build in `dist/server` survives development sessions. Rsbuild remotes need + `server: true` too. Vite remotes need nothing extra on their side, but the host must have `@module-federation/vite` (and its `vite` peer) installed: their server entries are ES modules, loaded by that package's SSR loader. @@ -188,9 +191,9 @@ examples in `apps/` include one. `React.lazy` component that fails once stays failed, so a server that hit a remote outage would render the fallback until it restarts. `lazyRemote` loads again on the first render after `retryAfterMs` (5 seconds by default). The - adapters make the import itself load again after a failure too, in - development and production: on its own, each bundler keeps the first failure - for the life of the process. + import itself loads again after a failure too: `@module-federation/vite` + 1.23.3+ retries failed server loads, and the Rsbuild adapter removes a failed + remote module from Rspack's module cache. - **`getRemoteStylesheets(manifestUrl, expose, { maxAgeMs })`** lists the stylesheets the remote's manifest declares for an expose, as absolute URLs. A remote's CSS otherwise loads with its JavaScript, after the HTML, so the @@ -200,10 +203,6 @@ examples in `apps/` include one. ## Known limitations -- A Vite remote with `exposes` does not hydrate when opened directly as an app: - `@module-federation/vite` waits for a host to initialize it. Production builds - worked standalone up to `@module-federation/vite` 1.22.1; 1.23.0 broke them. - Loading the remote through a host is unaffected. - In development, a Vite remote's server modules import React from the files its dev server resolves, so host and remote must resolve React to the same files, as they do in a monorepo. Production builds take React from the @@ -219,7 +218,7 @@ examples in `apps/` include one. of 1.168 its Rsbuild server build fails with it: TanStack Router imports React's `use`, which React 18 lacks. React 18 with Vite builds but is untested. -- Vite: `@module-federation/vite` and `vite`. +- Vite: `@module-federation/vite` 1.23.3+ and `vite` 8. - Rsbuild: `@module-federation/rsbuild-plugin` and `@rsbuild/core`. An Rsbuild host that renders Vite remotes on the server also needs - `@module-federation/vite` and `vite`. + `@module-federation/vite` 1.23.3+ and `vite`. diff --git a/packages/tanstack/package.json b/packages/tanstack/package.json index 9387fbe..a9734d1 100644 --- a/packages/tanstack/package.json +++ b/packages/tanstack/package.json @@ -99,7 +99,7 @@ }, "peerDependencies": { "@module-federation/rsbuild-plugin": "^2.9.0", - "@module-federation/vite": "^1.22.0", + "@module-federation/vite": "^1.23.3", "@rsbuild/core": "^2.0.0", "@tanstack/react-start": "^1.167.43", "react": ">=18.0.0", @@ -128,7 +128,7 @@ "devDependencies": { "@module-federation/rsbuild-plugin": "2.9.2", "@module-federation/runtime": "2.9.2", - "@module-federation/vite": "1.23.2", + "@module-federation/vite": "1.23.3", "@rsbuild/core": "2.2.11", "@rslib/core": "1.0.3", "@tanstack/react-start": "1.168.60", diff --git a/packages/tanstack/src/rsbuild.ts b/packages/tanstack/src/rsbuild.ts index 274f33e..4852b64 100644 --- a/packages/tanstack/src/rsbuild.ts +++ b/packages/tanstack/src/rsbuild.ts @@ -1,3 +1,4 @@ +import { join } from "node:path"; import { pluginModuleFederation, type ModuleFederationOptions, @@ -41,9 +42,7 @@ export type TanStackStartRsbuildModuleFederationOptions = { // The server container sits next to the manifest, like a Vite remote's SSR entry. Hosts // resolve its chunks from either the container URL (Module Federation's Node runtime) or // the manifest URL (`@module-federation/vite`), so both must share a directory. The -// `.cjs` extension matters: `@module-federation/vite` treats any `.ssr.js` URL as an ES -// module, while for other names it reads the format from the manifest -// (module-federation/vite#1423). +// extension says the container is CommonJS. const SERVER_CONTAINER_FILENAME = "remoteEntry.ssr.cjs"; const SERVER_CHUNK_DIR = "ssr"; const SERVER_CONTAINER_ENVIRONMENT = "mf-server"; @@ -245,29 +244,10 @@ function serverContainerEnvironmentPlugin({ containerConfig.output.publicPath = typeof publicPath === "string" && publicPath !== "auto" ? publicPath : "auto"; }); - - // The container assigns `module.exports` at runtime, so ES module importers see only - // a default export. Static re-assignments make `init` and `get` detectable named - // exports, which `@module-federation/vite` hosts read when they import the container. - api.processAssets( - { stage: "summarize", environments: [SERVER_CONTAINER_ENVIRONMENT] }, - ({ assets, compilation, sources }) => { - if (!assets[SERVER_CONTAINER_FILENAME]) return; - compilation.updateAsset( - SERVER_CONTAINER_FILENAME, - (source) => new sources.ConcatSource(source, CONTAINER_NAMED_EXPORTS), - ); - }, - ); }, }; } -const CONTAINER_NAMED_EXPORTS = ` -module.exports.init = module.exports.init; -module.exports.get = module.exports.get; -`; - const REMOTE_FAILURE_FACTORY = /(webpackRequire\.m\[id\]\s*=\s*\(\)\s*=>\s*\{)(\s*throw error;)/; /** @@ -379,10 +359,21 @@ function serverCompatibilityPlugin({ if (!forceCommonJsOutput) return; // async-node chunk loading reads chunks from disk, while the dev server runs the entry - // from memory. Writing the server output keeps both from the same compilation. + // from memory. Writing the server output keeps both from the same compilation. The + // dev output goes to a cache directory: in `dist/server` it would replace a production + // build that `rsbuild preview` serves, and dev chunks share its chunk names. api.modifyEnvironmentConfig((config, { name }) => { if (name !== environment) return; config.dev.writeToDisk = true; + if (api.context.action === "dev") { + config.output.distPath.root = join( + api.context.rootPath, + "node_modules", + ".cache", + "tanstack-start-federation", + environment, + ); + } }); // TanStack Start's preview server, and deployments built against it, import diff --git a/packages/tanstack/src/vite-compat.ts b/packages/tanstack/src/vite-compat.ts index 63e3ea1..ec82983 100644 --- a/packages/tanstack/src/vite-compat.ts +++ b/packages/tanstack/src/vite-compat.ts @@ -20,133 +20,3 @@ export function viteVersionPlugin(): Plugin { }, }; } - -const REMOTE_PENDING_EXPORT = "export const __mf_remote_pending ="; -const HANDLED_REMOTE_PENDING = /__mf_remote_pending\??\.catch/; -// The export server wrappers use: one load, started when the wrapper is evaluated. -const EAGER_REMOTE_PENDING_EXPORT = - /export const( __mf_remote_pending =\s*__mfRemotePending\s*\?\?\s*__mfStartRemoteLoad\(\)\.then\(__mfAssignRemoteModule\);)/; -// "let" padded to the length of "const", so the incoming source map stays valid. -const RETRYABLE_REMOTE_PENDING_EXPORT = "export let $1"; - -const REMOTE_LOAD_RETRY = ` -function __mfTanstackRetryAfterFailure(pending) { - pending.then(undefined, () => { - const retry = __mfTanstackNextAttempt(); - if (__mfRemotePending === pending) __mfRemotePending = retry; - if (__mf_remote_pending === pending) __mf_remote_pending = retry; - }); -} -function __mfTanstackNextAttempt() { - let attempt; - const retry = { - then(onFulfilled, onRejected) { - if (!attempt) { - attempt = __mfStartRemoteLoad().then(__mfAssignRemoteModule); - if (__mfRemotePending === retry) __mfRemotePending = attempt; - if (__mf_remote_pending === retry) __mf_remote_pending = attempt; - __mfTanstackRetryAfterFailure(attempt); - } - return attempt.then(onFulfilled, onRejected); - }, - }; - return retry; -} -if (__mfRemotePending) __mfTanstackRetryAfterFailure(__mfRemotePending); -if (__mf_remote_pending !== __mfRemotePending) __mfTanstackRetryAfterFailure(__mf_remote_pending); -`; - -// Development server wrappers export `then`, so Vite's module runner waits for the remote -// when it imports them, and caches the outcome for every later import. -const DEV_THEN_EXPORT = "export function then(onFulfilled, onRejected) {"; -// Same length, so the incoming source map stays valid. -const RENAMED_DEV_THEN = "function __mfThen1st(onFulfilled, onRejected) {"; - -const DEV_THEN_RETRY = ` -export function then(onFulfilled, onRejected) { - return __mfThen1st() - .then(undefined, () => ({ - get default() { - return __mfDefaultExport; - }, - get __moduleExports() { - return exportModule; - }, - __mf_remote_pending: { then: (resolve, reject) => __mf_remote_pending.then(resolve, reject) }, - })) - .then(onFulfilled, onRejected); -} -`; - -/** - * Works around two `@module-federation/vite` defects in the wrappers that load remotes. - * - * - On the server, a wrapper is evaluated once and makes one load attempt, exported as - * `__mf_remote_pending`. When that attempt fails, every later import fails with the same - * error, so a server that started during a remote outage never renders that remote. The - * adapter makes the next import after a failure start a new attempt. - * In development, the wrapper's `then` export also resolves after a failed load, with - * exports that read the retried load: Vite's module runner would otherwise cache the - * rejection for every later import. - * - A wrapper re-evaluated after its remote was cached exports a fresh load that nothing - * awaits, so a remote outage becomes an unhandled rejection that exits the Vite dev - * server. Awaiting the export still rejects. Wrappers that handle it are left alone. - * - * Reported as module-federation/vite#1424 and fixed for the rejection in #1421. - */ -export function remotePendingPlugin(): Plugin { - return { - name: "tanstack-start-federation:remote-pending", - enforce: "post", - transform: { - filter: { id: /__loadRemote__/ }, - handler(code) { - if (EAGER_REMOTE_PENDING_EXPORT.test(code)) { - // The retry handles every rejection, so it also prevents the unhandled one. - let retrying = - code.replace(EAGER_REMOTE_PENDING_EXPORT, RETRYABLE_REMOTE_PENDING_EXPORT) + - REMOTE_LOAD_RETRY; - if (retrying.includes(DEV_THEN_EXPORT)) { - retrying = retrying.replace(DEV_THEN_EXPORT, RENAMED_DEV_THEN) + DEV_THEN_RETRY; - } - return { code: retrying, map: null }; - } - if (!code.includes(REMOTE_PENDING_EXPORT) || HANDLED_REMOTE_PENDING.test(code)) return; - // Appending moves no code, so the incoming source map stays valid. - return { code: `${code}\n__mf_remote_pending?.catch?.(() => {});\n`, map: null }; - }, - }, - }; -} - -const TEMP_MODULE_IMPORT = - /(async function importTempModule\([^)]*\)\s*\{\s*return await )import\(/; - -/** - * Works around a `@module-federation/vite` defect in development. Its SSR loader saves a - * remote's server entry to a temporary file and imports it, but in a dev server that - * import goes through Vite's module runner, which evaluates every file as an ES module. - * CommonJS entries, such as Rsbuild server containers, then fail with "module is not - * defined". Importing through Node lets it choose the format, as production builds do. - * Reported as module-federation/vite#1423. - */ -export function nativeTempModuleImportPlugin(): Plugin { - return { - name: "tanstack-start-federation:native-temp-module-import", - apply: "serve", - transform: { - filter: { id: /@module-federation[\\/]vite[\\/]lib[\\/]ssrEntryLoader-[^\\/?]+\.js(?:\?|$)/ }, - handler(code) { - if (!TEMP_MODULE_IMPORT.test(code)) return; - // `Function` code imports through Node's loader, not the module runner. The helper - // goes at the end so earlier lines keep their positions for the source map. - return { - code: - code.replace(TEMP_MODULE_IMPORT, "$1__mfNativeImport(") + - '\nconst __mfNativeImport = new Function("specifier", "return import(specifier)");\n', - map: null, - }; - }, - }, - }; -} diff --git a/packages/tanstack/src/vite.ts b/packages/tanstack/src/vite.ts index 3aabefd..ed974e1 100644 --- a/packages/tanstack/src/vite.ts +++ b/packages/tanstack/src/vite.ts @@ -1,11 +1,7 @@ import { federation, type ModuleFederationOptions } from "@module-federation/vite"; import type { Plugin } from "vite"; import { defaultShared, eagerShared, resolveShared } from "./shared"; -import { - nativeTempModuleImportPlugin, - remotePendingPlugin, - viteVersionPlugin, -} from "./vite-compat"; +import { viteVersionPlugin } from "./vite-compat"; export type TanStackStartModuleFederationOptions = Omit; @@ -36,7 +32,5 @@ export function tanstackStartModuleFederation( isHost ? eagerShared : defaultShared, ) as ModuleFederationOptions["shared"], }), - remotePendingPlugin(), - nativeTempModuleImportPlugin(), ]; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5458aad..b0c1c85 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -314,8 +314,8 @@ importers: specifier: 2.9.2 version: 2.9.2 '@module-federation/vite': - specifier: 1.23.2 - version: 1.23.2(typescript@7.0.2)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 1.23.3 + version: 1.23.3(typescript@7.0.2)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) '@tanstack/react-router': specifier: 1.170.41 version: 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -501,8 +501,8 @@ importers: specifier: 2.9.2 version: 2.9.2 '@module-federation/vite': - specifier: 1.23.2 - version: 1.23.2(typescript@7.0.2)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + specifier: 1.23.3 + version: 1.23.3(typescript@7.0.2)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) '@rsbuild/core': specifier: 2.2.11 version: 2.2.11(@module-federation/runtime-tools@2.9.2) @@ -858,8 +858,8 @@ packages: '@module-federation/third-party-dts-extractor@2.9.2': resolution: {integrity: sha512-FKJVGx+/jIe16RfmwoyIDVMPAttIFJv6AJCLkKf05IRNvI3RJFqQ1tLP1YjcfGuumCbYuWGa4QKnMla7YjVpqg==} - '@module-federation/vite@1.23.2': - resolution: {integrity: sha512-LlfbC+IDFicoPMR/kELhl/1BzSI79SP7dWON6gX/Gm/8SkFXxpHpdQGaJSLSmJvtLbQMTPx0bqZDj4huB2Du3g==} + '@module-federation/vite@1.23.3': + resolution: {integrity: sha512-XYW7ePFmrSIIvQR4PBHBrujRlFI7TBP+g0QZkw9AsqW/A/x/tT97ypWe+osX4YCkCy57/48AQ0B/3bkl/wZXXQ==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2480,7 +2480,7 @@ snapshots: '@module-federation/third-party-dts-extractor@2.9.2': {} - '@module-federation/vite@1.23.2(typescript@7.0.2)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))': + '@module-federation/vite@1.23.3(typescript@7.0.2)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@module-federation/dts-plugin': 2.9.2(typescript@7.0.2) '@module-federation/runtime': 2.9.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7d115c0..1cc58b6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,4 +3,6 @@ packages: - packages/* autoInstallPeers: false +minimumReleaseAgeExclude: + - "@module-federation/vite@1.23.3" preferFrozenLockfile: true diff --git a/test/release-contracts.test.mjs b/test/release-contracts.test.mjs index eb93ccc..90ba1f0 100644 --- a/test/release-contracts.test.mjs +++ b/test/release-contracts.test.mjs @@ -136,165 +136,6 @@ test("the Vite adapter fails fast on Vite 7", () => { assert.doesNotThrow(() => plugin.config.call({ meta: { viteVersion: "8.3.2" } })); }); -test("unawaited remote pending exports cannot crash the dev server", () => { - const plugin = vitePlugin("tanstack-start-federation:remote-pending"); - assert.equal(plugin.enforce, "post"); - assert.ok(plugin.transform.filter.id.test("\0virtual:mf:host__loadRemote__remote_mf_1_Card")); - - const wrapper = "export const __mf_remote_pending =\n __mfStartRemoteLoad();"; - const { code, map } = plugin.transform.handler(wrapper); - assert.equal(map, null); - assert.ok(code.startsWith(wrapper), "the generated code is unchanged"); - assert.match(code, /__mf_remote_pending\?\.catch\?\.\(\(\) => \{\}\);\n$/); - - // Wrappers that already handle the export, or have none, are left alone. - assert.equal(plugin.transform.handler(code), undefined); - assert.equal(plugin.transform.handler("export default 1;"), undefined); -}); - -/** - * The shape of @module-federation/vite's server wrapper, with loads that follow `outcomes`. - * `dev` adds the `then` export development server wrappers have. - */ -function remoteWrapper({ dev = false } = {}) { - return [ - "let attempts = 0;", - "const outcomes = ['offline', 'online'];", - "function __mfStartRemoteLoad() {", - " attempts++;", - " return outcomes.shift() === 'online'", - " ? Promise.resolve({ default: 'Card' })", - " : new Promise((_, reject) => setTimeout(() => reject(new Error('offline')), 10));", - "}", - "let exportModule;", - "let __mfDefaultExport;", - "function __mfAssignRemoteModule(mod) {", - " if (mod !== undefined) exportModule = mod;", - " __mfDefaultExport = exportModule?.default;", - " return exportModule;", - "}", - "let __mfRemotePending;", - "if (exportModule === undefined) {", - " __mfRemotePending = __mfStartRemoteLoad().then(__mfAssignRemoteModule);", - "}", - "export { exportModule as __moduleExports };", - "export const __mf_remote_pending =", - " __mfRemotePending ??", - " __mfStartRemoteLoad().then(__mfAssignRemoteModule);", - ...(dev - ? [ - "export function then(onFulfilled, onRejected) {", - " return (__mfRemotePending ?? Promise.resolve(exportModule))", - " .then(__mfAssignRemoteModule)", - " .then(() => ({ default: __mfDefaultExport, __mf_remote_pending: __mfRemotePending }))", - " .then(onFulfilled, onRejected);", - "}", - ] - : []), - "export const getAttempts = () => attempts;", - ].join("\n"); -} - -/** Writes a transformed wrapper and passes its URL and its raw module namespace to `check`. */ -async function importTransformed(code, check) { - const directory = mkdtempSync(join(tmpdir(), "mf-tanstack-wrapper-")); - try { - const file = join(directory, "wrapper.mjs"); - writeFileSync(file, code); - // `import()` adopts a namespace with a `then` export; a static import does not. - const namespaceFile = join(directory, "namespace.mjs"); - writeFileSync( - namespaceFile, - 'import * as wrapper from "./wrapper.mjs";\nexport { wrapper };\n', - ); - const { wrapper } = await import(pathToFileURL(namespaceFile).href); - await check(pathToFileURL(file).href, wrapper); - } finally { - rmSync(directory, { force: true, recursive: true }); - } -} - -test("server remote wrappers load again after a failed attempt", async () => { - const plugin = vitePlugin("tanstack-start-federation:remote-pending"); - const wrapper = remoteWrapper(); - const { code, map } = plugin.transform.handler(wrapper); - assert.equal(map, null); - const lines = code.split("\n"); - const exportLine = wrapper.split("\n").findIndex((line) => line.includes("__mf_remote_pending")); - assert.equal(lines[exportLine], "export let __mf_remote_pending =", "columns stay in place"); - - await importTransformed(code, async (_url, wrapperModule) => { - // The first importer sees the failure, without an unhandled rejection... - await assert.rejects(Promise.resolve(wrapperModule.__mf_remote_pending), /offline/); - await new Promise((resolve) => setImmediate(resolve)); - assert.equal(wrapperModule.getAttempts(), 1, "nothing retries until the next import"); - - // ...and the next one loads again. - assert.deepEqual(await wrapperModule.__mf_remote_pending, { default: "Card" }); - assert.equal(wrapperModule.getAttempts(), 2); - assert.deepEqual(wrapperModule.__moduleExports, { default: "Card" }); - assert.deepEqual(await wrapperModule.__mf_remote_pending, { default: "Card" }); - assert.equal(wrapperModule.getAttempts(), 2, "a successful load is reused"); - }); -}); - -test("development wrappers resolve after a failed load, so Vite does not cache it", async () => { - const plugin = vitePlugin("tanstack-start-federation:remote-pending"); - const wrapper = remoteWrapper({ dev: true }); - const { code } = plugin.transform.handler(wrapper); - assert.equal( - code.split("\n")[ - wrapper.split("\n").findIndex((line) => line.startsWith("export function then")) - ], - "function __mfThen1st(onFulfilled, onRejected) {", - "columns stay in place", - ); - - await importTransformed(code, async (url, wrapperModule) => { - // Like Vite's module runner, `import()` awaits the wrapper's `then` export once. - const cached = await import(url); - assert.equal(cached.default, undefined, "the first load failed"); - assert.equal(wrapperModule.getAttempts(), 1); - - // Importers read the load through `__mf_remote_pending`, which loads again. - assert.deepEqual(await cached.__mf_remote_pending, { default: "Card" }); - assert.equal(cached.default, "Card", "the cached exports are live"); - assert.deepEqual(cached.__moduleExports, { default: "Card" }); - assert.equal(wrapperModule.getAttempts(), 2); - }); -}); - -test("development SSR imports saved remote entries through Node", async () => { - const plugin = vitePlugin("tanstack-start-federation:native-temp-module-import"); - assert.equal(plugin.apply, "serve"); - const { filter } = plugin.transform; - const loaderPath = "/app/node_modules/@module-federation/vite/lib/ssrEntryLoader-DEH3eV1e.js"; - assert.ok(filter.id.test(loaderPath)); - assert.ok(filter.id.test(`${loaderPath}?v=b50fefa8`), "Vite's dependency query is allowed"); - assert.equal(filter.id.test("/app/node_modules/@module-federation/vite/lib/index.js"), false); - - const loader = [ - "async function importTempModule(filePath, versionKey) {", - "\treturn await import(", - "\t\t/* @vite-ignore */", - "\t\t`${filePath}?v=${encodeURIComponent(versionKey)}`", - ");", - "}", - "const other = () => import('./chunk.js');", - ].join("\n"); - const { code, map } = plugin.transform.handler(loader); - assert.equal(map, null); - assert.equal(code.split("\n").length, loader.split("\n").length + 2, "only appends lines"); - assert.match(code, /return await __mfNativeImport\(/); - assert.match(code, /const other = \(\) => import\('\.\/chunk\.js'\);/); - - // The helper imports through Node's loader. - const nativeImport = new Function(`${code}\nreturn __mfNativeImport;`)(); - assert.equal((await nativeImport("node:path")).sep, "/"); - - assert.equal(plugin.transform.handler("export default 1;"), undefined); -}); - test("Rsbuild shared defaults never mutate caller overrides", () => { const callerShared = { react: { eager: false, singleton: false } }; const before = structuredClone(callerShared); @@ -441,20 +282,8 @@ test("Rsbuild SSR remotes ship a Node container with the browser assets", async /Register tanstackStart\(\) before tanstackStartModuleFederation\(\)/, ); - // ES module importers of the container need `init` and `get` as detectable named exports. - let updated; - hooks.processAssets.handler({ - assets: { "remoteEntry.ssr.cjs": {} }, - compilation: { updateAsset: (name, update) => (updated = [name, update("container;")]) }, - sources: { ConcatSource: class extends Array {} }, - }); - assert.deepEqual(hooks.processAssets.options, { - stage: "summarize", - environments: ["mf-server"], - }); - assert.equal(updated[0], "remoteEntry.ssr.cjs"); - assert.match(updated[1].join(""), /module\.exports\.init = module\.exports\.init;/); - assert.match(updated[1].join(""), /module\.exports\.get = module\.exports\.get;/); + // The container is emitted as Module Federation builds it. + assert.equal(hooks.processAssets, undefined); const [client, container] = federationOptions({ federation: rsbuildRemote, server: true }); assert.equal(container.filename, "remoteEntry.ssr.cjs"); @@ -501,9 +330,24 @@ test("Rsbuild SSR hosts load remotes from an async-node CommonJS server", () => module: false, }); - const environmentConfig = { dev: {} }; - hooks.modifyEnvironmentConfig(environmentConfig, { name: "ssr" }); - assert.equal(environmentConfig.dev.writeToDisk, true, "async-node chunks are read from disk"); + const environmentConfig = () => ({ dev: {}, output: { distPath: { root: "dist/server" } } }); + const buildConfig = environmentConfig(); + hooks.modifyEnvironmentConfig(buildConfig, { name: "ssr" }); + assert.equal(buildConfig.dev.writeToDisk, true, "async-node chunks are read from disk"); + assert.equal(buildConfig.output.distPath.root, "dist/server"); + + // A dev server writes its bundle elsewhere, so a production build in dist/server survives. + const devHooks = runHooks( + plugins.find(({ name }) => name === "tanstack-start-federation-ssr-compat"), + { action: "dev" }, + ); + const devConfig = environmentConfig(); + devHooks.modifyEnvironmentConfig(devConfig, { name: "ssr" }); + assert.equal(devConfig.dev.writeToDisk, true); + assert.equal( + devConfig.output.distPath.root, + join("/app", "node_modules", ".cache", "tanstack-start-federation", "ssr"), + ); const emitted = {}; hooks.processAssets.handler({ @@ -572,9 +416,10 @@ function federationOptions(options) { } /** Runs a plugin's setup against a recording API and returns the hooks it registered. */ -function runHooks(plugin) { +function runHooks(plugin, { action = "build" } = {}) { const hooks = {}; plugin.setup({ + context: { action, rootPath: "/app" }, modifyEnvironmentConfig: (handler) => (hooks.modifyEnvironmentConfig = handler), modifyRsbuildConfig: (hook) => (hooks.modifyRsbuildConfig = hook), onBeforeCreateCompiler: (handler) => (hooks.onBeforeCreateCompiler = handler), diff --git a/test/support/examples.mjs b/test/support/examples.mjs index 6eeb2b0..94057a3 100644 --- a/test/support/examples.mjs +++ b/test/support/examples.mjs @@ -1,5 +1,6 @@ import assert from "node:assert/strict"; import { spawn } from "node:child_process"; +import { existsSync, readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { after, before, test } from "node:test"; @@ -48,7 +49,12 @@ export function describeExamples(script) { await waitForResponse(`${apps[key].url}__ready`, processes[key], ({ status }) => status < 500); }; + // Production server entries of the Rsbuild SSR host, read before its dev server starts. + const serverBuild = join(root, "apps", "rsbuild-ssr-host", "dist", "server", "index.cjs"); + let productionServerEntry; + before(async () => { + if (existsSync(serverBuild)) productionServerEntry = readFileSync(serverBuild, "utf8"); await Promise.all(remotes.map(start)); await Promise.all(hosts.map(start)); browser = await chromium.launch(); @@ -122,6 +128,17 @@ export function describeExamples(script) { } }); + if (script === "start") { + test("development servers leave production server builds alone", (t) => { + if (productionServerEntry === undefined) { + t.skip("needs a production build from before the development servers started"); + return; + } + // `rsbuild preview` serves dist/server, so a dev server must not write its bundle there. + assert.equal(readFileSync(serverBuild, "utf8"), productionServerEntry); + }); + } + test("remote manifests reference reachable assets", async () => { for (const key of remotes) { const manifestUrl = new URL("mf-manifest.json", apps[key].url).href; @@ -163,8 +180,9 @@ export function describeExamples(script) { }); }); - test("Rsbuild remotes work as standalone TanStack Start apps", async () => { + test("remotes work as standalone TanStack Start apps", async () => { for (const [key, card] of [ + ["viteRemote", cards.vite], ["rsbuildRemote", cards.rsbuild], ["rsbuildSsrRemote", cards.rsbuildSsr], ]) { @@ -175,10 +193,6 @@ export function describeExamples(script) { } }); - // @module-federation/vite never initializes a container with `exposes` when it is opened - // directly in development, and 1.23.0 broke the production path too (1.22.x works). - test.todo("Vite remote hydrates as a standalone app"); - // The last two scenarios stop remotes, then start them again. test("hosts fall back while remotes are offline and recover when they return", async () => { await Promise.all(remotes.map((key) => stopApp(processes[key])));