diff --git a/e2e/react-start/basic/package.json b/e2e/react-start/basic/package.json index 883627bb4f7..2e4d63558e2 100644 --- a/e2e/react-start/basic/package.json +++ b/e2e/react-start/basic/package.json @@ -4,12 +4,12 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "vite dev --port 3000", - "dev:e2e": "vite dev", - "build": "vite build && tsc --noEmit", - "build:spa": "MODE=spa vite build && tsc --noEmit", - "build:prerender": "MODE=prerender vite build && tsc --noEmit", - "preview": "vite preview", + "dev": "node scripts/run-bundler.mjs dev --port 3000", + "dev:e2e": "node scripts/run-bundler.mjs dev", + "build": "node scripts/run-bundler.mjs build", + "build:spa": "MODE=spa node scripts/run-bundler.mjs build", + "build:prerender": "MODE=prerender node scripts/run-bundler.mjs build", + "preview": "node scripts/run-bundler.mjs preview", "start": "node server.js", "test:e2e:startDummyServer": "node -e 'import(\"./tests/setup/global.setup.ts\").then(m => m.default())' &", "test:e2e:stopDummyServer": "node -e 'import(\"./tests/setup/global.teardown.ts\").then(m => m.default())'", @@ -33,6 +33,8 @@ }, "devDependencies": { "@playwright/test": "^1.50.1", + "@rsbuild/core": "^1.2.4", + "@rsbuild/plugin-react": "^1.1.0", "@tailwindcss/vite": "^4.1.18", "@tanstack/router-e2e-utils": "workspace:^", "@types/js-cookie": "^3.0.6", diff --git a/e2e/react-start/basic/rsbuild.config.ts b/e2e/react-start/basic/rsbuild.config.ts new file mode 100644 index 00000000000..e82c1c30367 --- /dev/null +++ b/e2e/react-start/basic/rsbuild.config.ts @@ -0,0 +1,49 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { defineConfig } from '@rsbuild/core' +import { pluginReact } from '@rsbuild/plugin-react' +import { tanstackStart } from '@tanstack/react-start/plugin/rsbuild' +import { isSpaMode } from './tests/utils/isSpaMode' +import { isPrerender } from './tests/utils/isPrerender' + +const currentDir = path.dirname(fileURLToPath(import.meta.url)) + +const spaModeConfiguration = { + enabled: true, + prerender: { + outputPath: 'index.html', + }, +} + +const prerenderConfiguration = { + enabled: true, + filter: (page: { path: string }) => + ![ + '/this-route-does-not-exist', + '/redirect', + '/i-do-not-exist', + '/not-found/via-beforeLoad', + '/not-found/via-loader', + '/specialChars/search', + '/specialChars/hash', + '/specialChars/malformed', + '/users', + ].some((p) => page.path.includes(p)), + maxRedirects: 100, +} + +export default defineConfig({ + plugins: [ + pluginReact(), + ...tanstackStart({ + spa: isSpaMode ? spaModeConfiguration : undefined, + prerender: isPrerender ? prerenderConfiguration : undefined, + }), + ], + tools: {}, + source: { + alias: { + '~': path.resolve(currentDir, 'src'), + }, + }, +}) diff --git a/e2e/react-start/basic/scripts/run-bundler.mjs b/e2e/react-start/basic/scripts/run-bundler.mjs new file mode 100644 index 00000000000..edfc8fcabbf --- /dev/null +++ b/e2e/react-start/basic/scripts/run-bundler.mjs @@ -0,0 +1,57 @@ +import { spawn } from 'node:child_process' + +const command = process.argv[2] +const args = process.argv.slice(3) + +if (!command) { + console.error('Missing bundler command') + process.exit(1) +} + +const bundler = process.env.BUNDLER === 'rsbuild' ? 'rsbuild' : 'vite' + +const extractPort = (args) => { + const portIndex = args.indexOf('--port') + if (portIndex >= 0 && args[portIndex + 1]) { + return args[portIndex + 1] + } + return null +} + +const run = (cmd, cmdArgs) => + new Promise((resolve, reject) => { + const child = spawn(cmd, cmdArgs, { + stdio: 'inherit', + env: process.env, + shell: process.platform === 'win32', + }) + child.on('error', (error) => { + reject(error) + }) + child.on('close', (code) => { + if (code === 0) { + resolve() + } else { + reject(new Error(`${cmd} exited with code ${code}`)) + } + }) + }) + +try { + if (bundler === 'rsbuild' && command === 'preview') { + const port = extractPort(args) + if (port) { + process.env.PORT = port + } + await run('node', ['server.js']) + } else { + await run(bundler, [command, ...args]) + + if (command === 'build') { + await run('tsc', ['--noEmit']) + } + } +} catch (error) { + console.error(error) + process.exit(1) +} diff --git a/e2e/react-start/basic/server.js b/e2e/react-start/basic/server.js index 83f5ff0079c..3c0a6668935 100644 --- a/e2e/react-start/basic/server.js +++ b/e2e/react-start/basic/server.js @@ -18,7 +18,12 @@ export async function createStartServer() { // to keep testing uniform stop express from redirecting /posts to /posts/ // when serving pre-rendered pages - app.use(express.static('./dist/client', { redirect: !isPrerender })) + app.use( + express.static('./dist/client', { + redirect: !isPrerender, + ...(isPrerender ? {} : { index: false }), + }), + ) app.use(async (req, res, next) => { try { diff --git a/e2e/react-start/basic/src/routeTree.gen.ts b/e2e/react-start/basic/src/routeTree.gen.ts index b5ecc4a50ff..48eaf8d7ca5 100644 --- a/e2e/react-start/basic/src/routeTree.gen.ts +++ b/e2e/react-start/basic/src/routeTree.gen.ts @@ -1,9 +1,3 @@ -/* 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. @@ -1365,6 +1359,7 @@ 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' { diff --git a/e2e/react-start/basic/src/routes/__root.tsx b/e2e/react-start/basic/src/routes/__root.tsx index e1862b499c6..7bf17f27c93 100644 --- a/e2e/react-start/basic/src/routes/__root.tsx +++ b/e2e/react-start/basic/src/routes/__root.tsx @@ -10,7 +10,7 @@ import { import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary' import { NotFound } from '~/components/NotFound' -import appCss from '~/styles/app.css?url' +import '~/styles/app.css' import { seo } from '~/utils/seo' export const Route = createRootRoute({ @@ -30,7 +30,6 @@ export const Route = createRootRoute({ }), ], links: [ - { rel: 'stylesheet', href: appCss }, { rel: 'apple-touch-icon', sizes: '180x180', diff --git a/e2e/react-start/basic/src/styles/app.css b/e2e/react-start/basic/src/styles/app.css index 37c1f5a6e2d..0a5d2c761a8 100644 --- a/e2e/react-start/basic/src/styles/app.css +++ b/e2e/react-start/basic/src/styles/app.css @@ -1,30 +1,57 @@ -@import 'tailwindcss' source('../'); - -@layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } -} - -@layer base { - html { - color-scheme: light dark; - } - - * { - @apply border-gray-200 dark:border-gray-800; - } - - html, - body { - @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200; - } - - .using-mouse * { - outline: none !important; - } +*, +*::before, +*::after, +::backdrop, +::file-selector-button { + box-sizing: border-box; + border-color: #e5e7eb; +} + +html { + color-scheme: light dark; +} + +body { + margin: 0; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + sans-serif; + background-color: #f9fafb; + color: #111827; +} + +.using-mouse * { + outline: none !important; +} + +.p-2 { + padding: 0.5rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.flex { + display: flex; +} + +.gap-2 { + gap: 0.5rem; +} + +.text-lg { + font-size: 1.125rem; +} + +.font-bold { + font-weight: 700; +} + +.italic { + font-style: italic; } diff --git a/e2e/react-start/module-federation-rsbuild-host/README.md b/e2e/react-start/module-federation-rsbuild-host/README.md new file mode 100644 index 00000000000..9ae79483877 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/README.md @@ -0,0 +1,100 @@ +# E2E Fixture: TanStack Start Module Federation Host (Rsbuild) + +This fixture validates host behavior for Rsbuild + Module Federation in: + +- `ssr` +- `spa` +- `prerender` + +It consumes the paired remote fixture at: + +- `../module-federation-rsbuild-remote` + +## Commands + +```sh +# SSR only +pnpm test:e2e:ssr + +# SPA only +pnpm test:e2e:spa + +# Prerender only +pnpm test:e2e:prerender + +# Full mode matrix +pnpm test:e2e +``` + +## Node SSR federation requirement + +Server remotes are loaded over HTTP from the remote SSR output and use: + +- `remoteType: 'script'` on the host SSR plugin config. +- `shared.react/react-dom.import: false` on the remote node-target config. + +The e2e suite also validates the remote SSR manifest contract: + +- `metaData.remoteEntry.type === 'commonjs-module'` +- `metaData.publicPath` points to `http:///ssr/` +- `metaData.types.zip === ''` and `metaData.types.api === ''` +- React/ReactDOM shared entries use wildcard metadata (`version: '*'`, `requiredVersion: '^*'`) in SSR manifest. +- React/ReactDOM shared fallback asset lists are empty in SSR manifest. +- Exposed module JS assets remain relative `static/js/...` paths. +- JS asset paths are expected to end with `.js`. + +It also validates the browser manifest contract for the remote web target: + +- `metaData.remoteEntry.type === 'global'` +- `metaData.publicPath` points to `http:///` +- `metaData.types.zip === '@mf-types.zip'` and `metaData.types.api === '@mf-types.d.ts'` +- React/ReactDOM shared entries use concrete non-wildcard versions in browser manifest. +- React/ReactDOM shared fallback asset lists are populated in browser manifest. +- Browser shared JS asset entries remain relative `static/js/...` paths (resolved via HTTP `publicPath`). +- Exposed module JS assets remain relative `static/js/...` paths. + +Federation stats contract is also verified: + +- SSR stats shared entries set `import: false` for `react` and `react-dom`. +- Browser stats shared entries omit `import` and keep standard web sharing metadata. +- Manifest and stats metadata stay aligned (identity, remote entry type/publicPath, types metadata, shared/expose names). +- Stats outputs keep exact expected cardinality (`shared: 2`, `exposes: 3`) and stable ids (`mf_remote:*`). +- Alignment also covers build/plugin metadata (`buildInfo`, `pluginVersion`) and remoteEntry name/path fields. +- Shared/expose JS asset lists are aligned between manifest and stats for both browser and SSR outputs. +- Types metadata parity includes `types.path` and `types.name`, and shared/expose CSS asset lists are also aligned. +- JSON endpoint checks also validate basic payload structure (`id`, `name`, `metaData.remoteEntry.name`, `pluginVersion`). +- Endpoint payloads also keep identity metadata stable (`metaData.name: 'mf_remote'`, `metaData.type: 'app'`, and `buildInfo.buildVersion: 'local'`). +- Endpoint payloads also preserve global metadata invariants (`metaData.globalName: 'mf_remote'`, `metaData.prefetchInterface: false`, `metaData.remoteEntry.path: ''`). +- JSON endpoint checks assert path-specific metadata values: + - `/dist/*` endpoints return `remoteEntry.type: 'global'`, browser `types` metadata, and root publicPath. + - `/ssr/*` endpoints return `remoteEntry.type: 'commonjs-module'`, empty SSR `types` metadata, and `/ssr/` publicPath. +- Endpoint `types.path` and `types.name` metadata are expected to stay empty strings (`''`) across both browser and SSR JSON payloads. +- Endpoint payloads also keep consistent plugin/build metadata across all JSON endpoints (`pluginVersion`, `buildVersion`, `buildName`). +- `pluginVersion` values are also expected to be SemVer-like strings on all JSON endpoints. +- JSON endpoint payloads also enforce mode-correct shared version semantics (`react`/`react-dom` wildcard only on SSR endpoints). +- Each JSON endpoint payload also keeps `remotes: []` and exactly two shared entries (`react`, `react-dom`). +- Shared identity in each endpoint payload remains stable (`mf_remote:react`, `mf_remote:react-dom`). +- Shared entries remain singleton on all endpoint payloads. +- Stats endpoints retain shared runtime flags (`shareScope: 'default'`, `eager: false`, and SSR-only `import: false`), while manifest endpoints omit those fields. +- Stats endpoints also keep shared usage arrays (`usedIn`, `usedExports`) as empty arrays, while manifest endpoints omit them. +- Endpoint shared JS asset semantics remain mode-correct (`[]` for SSR shared entries, non-empty relative `static/js/*.js` for browser shared entries). +- Shared endpoint payloads keep async JS asset arrays empty and CSS asset arrays empty. +- Endpoint payloads also retain expose identity/path contracts for `message`, `routes`, and `server-data`. +- Stats endpoint expose `requires` arrays remain empty; manifest endpoint payloads omit `requires`. +- Stats endpoint payloads also retain stable expose `file` metadata (`src/message.tsx`, `src/routes.tsx`, `src/server-data.ts`). +- Endpoint expose sync JS asset lists are non-empty and keep relative `static/js/*.js` paths. +- Endpoint expose async JS and CSS asset arrays remain empty. + +Remote entry payloads are also validated directly over HTTP at: + +- `/dist/remoteEntry.js` +- `/ssr/remoteEntry.js` + +and must return JavaScript content-types (not HTML fallback pages). + +The browser types artifact is also validated at: + +- `/dist/@mf-types.zip` + +and must be retrievable over HTTP as a non-HTML payload. + diff --git a/e2e/react-start/module-federation-rsbuild-host/package.json b/e2e/react-start/module-federation-rsbuild-host/package.json new file mode 100644 index 00000000000..fc2d8d0ab0e --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/package.json @@ -0,0 +1,36 @@ +{ + "name": "tanstack-react-start-e2e-module-federation-rsbuild-host", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "rsbuild dev --port 3000", + "build": "rsbuild build", + "preview": "rsbuild preview", + "start": "node server.js", + "test:e2e:ssr": "rm -rf dist; rm -rf port*.txt; HOST_MODE=ssr playwright test --project=chromium", + "test:e2e:spa": "rm -rf dist; rm -rf port*.txt; HOST_MODE=spa playwright test --project=chromium", + "test:e2e:prerender": "rm -rf dist; rm -rf port*.txt; HOST_MODE=prerender playwright test --project=chromium", + "test:e2e": "pnpm test:e2e:ssr && pnpm test:e2e:spa && pnpm test:e2e:prerender" + }, + "dependencies": { + "@tanstack/react-router": "workspace:^", + "@tanstack/react-start": "workspace:^", + "express": "^5.1.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@module-federation/node": "^2.7.32", + "@module-federation/rsbuild-plugin": "https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd", + "@playwright/test": "^1.50.1", + "@rsbuild/core": "^1.3.21", + "@rsbuild/plugin-react": "^1.1.0", + "@tanstack/router-e2e-utils": "workspace:^", + "@types/node": "^22.10.2", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "srvx": "^0.11.2", + "typescript": "^5.7.2" + } +} diff --git a/e2e/react-start/module-federation-rsbuild-host/playwright.config.ts b/e2e/react-start/module-federation-rsbuild-host/playwright.config.ts new file mode 100644 index 00000000000..e24fdf3577a --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/playwright.config.ts @@ -0,0 +1,52 @@ +import { defineConfig, devices } from '@playwright/test' +import { getTestServerPort } from '@tanstack/router-e2e-utils' +import packageJson from './package.json' with { type: 'json' } + +const HOST_PORT = await getTestServerPort(packageJson.name) +const REMOTE_PORT = await getTestServerPort(`${packageJson.name}-remote`) +const HOST_MODE = process.env.HOST_MODE || 'ssr' +const baseURL = `http://localhost:${HOST_PORT}` + +export default defineConfig({ + testDir: './tests', + workers: 1, + reporter: [['line']], + + use: { + baseURL, + }, + + webServer: [ + { + command: `pnpm --dir ../module-federation-rsbuild-remote build && pnpm --dir ../module-federation-rsbuild-remote preview --port ${REMOTE_PORT}`, + url: `http://localhost:${REMOTE_PORT}`, + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + env: { + REMOTE_PORT: String(REMOTE_PORT), + }, + timeout: 120_000, + }, + { + command: `pnpm build && pnpm start`, + url: baseURL, + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + env: { + PORT: String(HOST_PORT), + REMOTE_PORT: String(REMOTE_PORT), + HOST_MODE, + }, + timeout: 120_000, + }, + ], + + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + ], +}) diff --git a/e2e/react-start/module-federation-rsbuild-host/rsbuild.config.ts b/e2e/react-start/module-federation-rsbuild-host/rsbuild.config.ts new file mode 100644 index 00000000000..e17ce071bad --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/rsbuild.config.ts @@ -0,0 +1,92 @@ +import { defineConfig } from '@rsbuild/core' +import { createRequire } from 'node:module' +import { pluginReact } from '@rsbuild/plugin-react' +import { pluginModuleFederation } from '@module-federation/rsbuild-plugin' +import { tanstackStart } from '@tanstack/react-start/plugin/rsbuild' + +const require = createRequire(import.meta.url) +const remotePort = Number(process.env.REMOTE_PORT || 3001) +const remoteOrigin = `http://localhost:${remotePort}` +const hostMode = process.env.HOST_MODE || 'ssr' +const isSpaMode = hostMode === 'spa' +const isPrerenderMode = hostMode === 'prerender' +const enableServerFederationRuntime = hostMode === 'ssr' +const shared = { + react: { + singleton: true, + requiredVersion: false, + }, + 'react-dom': { + singleton: true, + requiredVersion: false, + }, +} +const startConfig = isSpaMode + ? { + spa: { + enabled: true, + }, + } + : isPrerenderMode + ? { + prerender: { + enabled: true, + crawlLinks: false, + autoStaticPathsDiscovery: false, + }, + pages: [ + { path: '/' }, + { path: '/selective-client-only' }, + ], + } + : undefined + +export default defineConfig({ + plugins: [ + pluginReact(), + pluginModuleFederation( + { + name: 'mf_host', + remotes: { + mf_remote: `mf_remote@${remoteOrigin}/remoteEntry.js`, + }, + dts: false, + experiments: { + asyncStartup: true, + }, + runtimePlugins: [require.resolve('@module-federation/node/runtimePlugin')], + shared, + }, + { + environment: 'client', + }, + ), + pluginModuleFederation( + { + name: 'mf_host_ssr', + remotes: { + // Server remotes are fetched via node runtime over HTTP from the remote SSR output. + mf_remote: `mf_remote@${remoteOrigin}/ssr/remoteEntry.js`, + }, + dts: false, + // Required by @module-federation/node runtime container loading. + remoteType: 'script', + experiments: { + asyncStartup: true, + }, + runtimePlugins: enableServerFederationRuntime + ? [require.resolve('@module-federation/node/runtimePlugin')] + : [], + shared, + }, + { + target: 'node', + environment: 'ssr', + }, + ), + ...tanstackStart(startConfig), + ], + environments: { + ssr: {}, + }, +}) diff --git a/e2e/react-start/module-federation-rsbuild-host/server.js b/e2e/react-start/module-federation-rsbuild-host/server.js new file mode 100644 index 00000000000..9fcfd2e772a --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/server.js @@ -0,0 +1,38 @@ +import fs from 'node:fs' +import path from 'node:path' +import { createRequire } from 'node:module' +import express from 'express' +import { toNodeHandler } from 'srvx/node' + +const require = createRequire(import.meta.url) +const port = process.env.PORT || 3000 + +if (!globalThis.self) { + globalThis.self = globalThis +} + +const bundledServerPath = path.resolve('./dist/server/server.js') +const commonJsServerPath = path.resolve('./dist/server/server.cjs') +fs.copyFileSync(bundledServerPath, commonJsServerPath) + +let imported = require(commonJsServerPath) +if (imported && typeof imported.then === 'function') { + imported = await imported +} +const server = imported?.default ?? imported +const nodeHandler = toNodeHandler(server.fetch) + +const app = express() + +app.use(express.static('./dist/client', { index: false })) +app.use(async (req, res, next) => { + try { + await nodeHandler(req, res) + } catch (error) { + next(error) + } +}) + +app.listen(port, () => { + console.info(`Start Server: http://localhost:${port}`) +}) diff --git a/e2e/react-start/module-federation-rsbuild-host/src/env.d.ts b/e2e/react-start/module-federation-rsbuild-host/src/env.d.ts new file mode 100644 index 00000000000..644ee55d5cc --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/src/env.d.ts @@ -0,0 +1,27 @@ +/// +/// + +declare module 'mf_remote/message' { + import type { ReactNode } from 'react' + + export function FederatedMessage(): ReactNode +} + +declare module 'mf_remote/routes' { + import type { ComponentType } from 'react' + + export type RemoteRouteRegistration = { + id: string + path: string + component: ComponentType + } + + export const remoteRouteRegistrations: Array +} + +declare module 'mf_remote/server-data' { + export function getFederatedServerData(source: string): { + source: string + message: string + } +} diff --git a/e2e/react-start/module-federation-rsbuild-host/src/routeTree.gen.ts b/e2e/react-start/module-federation-rsbuild-host/src/routeTree.gen.ts new file mode 100644 index 00000000000..ba164346a39 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/src/routeTree.gen.ts @@ -0,0 +1,131 @@ +/* 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 ServerFnMfRouteImport } from './routes/server-fn-mf' +import { Route as SelectiveClientOnlyRouteImport } from './routes/selective-client-only' +import { Route as IndexRouteImport } from './routes/index' +import { Route as ApiFederatedDataRouteImport } from './routes/api/federated-data' + +const ServerFnMfRoute = ServerFnMfRouteImport.update({ + id: '/server-fn-mf', + path: '/server-fn-mf', + getParentRoute: () => rootRouteImport, +} as any) +const SelectiveClientOnlyRoute = SelectiveClientOnlyRouteImport.update({ + id: '/selective-client-only', + path: '/selective-client-only', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const ApiFederatedDataRoute = ApiFederatedDataRouteImport.update({ + id: '/api/federated-data', + path: '/api/federated-data', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/selective-client-only': typeof SelectiveClientOnlyRoute + '/server-fn-mf': typeof ServerFnMfRoute + '/api/federated-data': typeof ApiFederatedDataRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/selective-client-only': typeof SelectiveClientOnlyRoute + '/server-fn-mf': typeof ServerFnMfRoute + '/api/federated-data': typeof ApiFederatedDataRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/selective-client-only': typeof SelectiveClientOnlyRoute + '/server-fn-mf': typeof ServerFnMfRoute + '/api/federated-data': typeof ApiFederatedDataRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/selective-client-only' + | '/server-fn-mf' + | '/api/federated-data' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/selective-client-only' | '/server-fn-mf' | '/api/federated-data' + id: + | '__root__' + | '/' + | '/selective-client-only' + | '/server-fn-mf' + | '/api/federated-data' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + SelectiveClientOnlyRoute: typeof SelectiveClientOnlyRoute + ServerFnMfRoute: typeof ServerFnMfRoute + ApiFederatedDataRoute: typeof ApiFederatedDataRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/server-fn-mf': { + id: '/server-fn-mf' + path: '/server-fn-mf' + fullPath: '/server-fn-mf' + preLoaderRoute: typeof ServerFnMfRouteImport + parentRoute: typeof rootRouteImport + } + '/selective-client-only': { + id: '/selective-client-only' + path: '/selective-client-only' + fullPath: '/selective-client-only' + preLoaderRoute: typeof SelectiveClientOnlyRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/api/federated-data': { + id: '/api/federated-data' + path: '/api/federated-data' + fullPath: '/api/federated-data' + preLoaderRoute: typeof ApiFederatedDataRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + SelectiveClientOnlyRoute: SelectiveClientOnlyRoute, + ServerFnMfRoute: ServerFnMfRoute, + ApiFederatedDataRoute: ApiFederatedDataRoute, +} +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/e2e/react-start/module-federation-rsbuild-host/src/router.tsx b/e2e/react-start/module-federation-rsbuild-host/src/router.tsx new file mode 100644 index 00000000000..5c435cf75fd --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/src/router.tsx @@ -0,0 +1,28 @@ +import { createRoute, createRouter } from '@tanstack/react-router' +import { routeTree as fileRouteTree } from './routeTree.gen' + +const shouldLoadRemoteRouteRegistrations = + typeof window !== 'undefined' || process.env.HOST_MODE === 'ssr' + +const remoteRouteRegistrations = shouldLoadRemoteRouteRegistrations + ? (await import('mf_remote/routes')).remoteRouteRegistrations + : [] + +const dynamicRemoteRoutes = remoteRouteRegistrations.map((registration) => + createRoute({ + getParentRoute: () => fileRouteTree, + path: registration.path, + component: registration.component, + }), +) + +const routeTree = fileRouteTree.addChildren( + [...(fileRouteTree.children ?? []), ...dynamicRemoteRoutes] as any, +) + +export function getRouter() { + return createRouter({ + routeTree, + defaultPreload: 'intent', + }) +} diff --git a/e2e/react-start/module-federation-rsbuild-host/src/routes/__root.tsx b/e2e/react-start/module-federation-rsbuild-host/src/routes/__root.tsx new file mode 100644 index 00000000000..ef6cc68bebb --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/src/routes/__root.tsx @@ -0,0 +1,25 @@ +import * as React from 'react' +import { + HeadContent, + Outlet, + Scripts, + createRootRoute, +} from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: RootComponent, +}) + +function RootComponent() { + return ( + + + + + + + + + + ) +} diff --git a/e2e/react-start/module-federation-rsbuild-host/src/routes/api/federated-data.ts b/e2e/react-start/module-federation-rsbuild-host/src/routes/api/federated-data.ts new file mode 100644 index 00000000000..b6e6a238e88 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/src/routes/api/federated-data.ts @@ -0,0 +1,12 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/api/federated-data')({ + server: { + handlers: { + GET: async () => { + const { getFederatedServerData } = await import('mf_remote/server-data') + return Response.json(getFederatedServerData('server-route')) + }, + }, + }, +}) diff --git a/e2e/react-start/module-federation-rsbuild-host/src/routes/index.tsx b/e2e/react-start/module-federation-rsbuild-host/src/routes/index.tsx new file mode 100644 index 00000000000..b03c6655871 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/src/routes/index.tsx @@ -0,0 +1,43 @@ +import { createFileRoute } from '@tanstack/react-router' + +const shouldLoadFederatedMessage = + typeof window !== 'undefined' || process.env.HOST_MODE === 'ssr' + +const FederatedMessage = shouldLoadFederatedMessage + ? (await import('mf_remote/message')).FederatedMessage + : function FederatedMessagePlaceholder() { + return ( +

+ Federated message renders on the client in this mode. +

+ ) + } + +export const Route = createFileRoute('/')({ + component: Home, +}) + +function Home() { + return ( +
+

Host application

+

+ This page renders a module from the remote app. +

+ +
+ +
+
+ ) +} diff --git a/e2e/react-start/module-federation-rsbuild-host/src/routes/selective-client-only.tsx b/e2e/react-start/module-federation-rsbuild-host/src/routes/selective-client-only.tsx new file mode 100644 index 00000000000..0b5f8d48886 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/src/routes/selective-client-only.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/selective-client-only')({ + ssr: false, + component: SelectiveClientOnlyRoute, +}) + +function SelectiveClientOnlyRoute() { + return ( +
+

Selective remote route content

+
+ ) +} diff --git a/e2e/react-start/module-federation-rsbuild-host/src/routes/server-fn-mf.tsx b/e2e/react-start/module-federation-rsbuild-host/src/routes/server-fn-mf.tsx new file mode 100644 index 00000000000..0c3b3484e63 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/src/routes/server-fn-mf.tsx @@ -0,0 +1,25 @@ +import { createFileRoute } from '@tanstack/react-router' +import { createServerFn } from '@tanstack/react-start' + +const getRemoteServerData = createServerFn({ method: 'GET' }).handler( + async () => { + const { getFederatedServerData } = await import('mf_remote/server-data') + return getFederatedServerData('server-function') + }, +) + +export const Route = createFileRoute('/server-fn-mf')({ + loader: () => getRemoteServerData(), + component: ServerFunctionFederationRoute, +}) + +function ServerFunctionFederationRoute() { + const response = Route.useLoaderData() + + return ( +
+

Server function federation route

+
{JSON.stringify(response)}
+
+ ) +} diff --git a/e2e/react-start/module-federation-rsbuild-host/tests/module-federation.spec.ts b/e2e/react-start/module-federation-rsbuild-host/tests/module-federation.spec.ts new file mode 100644 index 00000000000..9e94db1db4c --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/tests/module-federation.spec.ts @@ -0,0 +1,780 @@ +import { expect } from '@playwright/test' +import type { Page } from '@playwright/test' +import { getTestServerPort, test } from '@tanstack/router-e2e-utils' +import packageJson from '../package.json' with { type: 'json' } + +const REMOTE_PORT = await getTestServerPort(`${packageJson.name}-remote`) +const REMOTE_ORIGIN = `http://localhost:${REMOTE_PORT}` +const HOST_MODE = process.env.HOST_MODE || 'ssr' +const REMOTE_PACKAGE_NAME = packageJson.name.replace(/-host$/, '-remote') + +type SharedAssetGroup = { + sync?: Array + async?: Array +} + +type ManifestSharedEntry = { + id?: string + name?: string + version?: string + requiredVersion?: string + fallback?: string + import?: boolean + eager?: boolean + shareScope?: string + usedIn?: Array + usedExports?: Array + assets?: { + js?: SharedAssetGroup + css?: SharedAssetGroup + } +} + +type ManifestExposeEntry = { + id?: string + name?: string + path?: string + file?: string + requires?: Array + assets?: { + js?: SharedAssetGroup + css?: SharedAssetGroup + } +} + +type ManifestRemoteEntryMetadata = { + type?: string + name?: string + path?: string +} + +type ManifestTypesMetadata = { + path?: string + name?: string + zip?: string + api?: string +} + +type ManifestBuildInfo = { + buildVersion?: string + buildName?: string +} + +type MfManifest = { + id?: string + name?: string + metaData?: { + name?: string + type?: string + buildInfo?: ManifestBuildInfo + remoteEntry?: ManifestRemoteEntryMetadata + publicPath?: string + types?: ManifestTypesMetadata + globalName?: string + prefetchInterface?: boolean + pluginVersion?: string + } + shared?: Array + exposes?: Array + remotes?: Array +} + +async function fetchManifest( + page: Page, + paths: Array, +): Promise { + for (const path of paths) { + const response = await page.request.get(`${REMOTE_ORIGIN}${path}`) + if (!response.ok()) { + continue + } + + const body = await response.text() + if (!body.trim().startsWith('{')) { + continue + } + + return JSON.parse(body) as MfManifest + } + + throw new Error( + `Could not load JSON manifest from any path: ${paths.join(', ')}`, + ) +} + +function getSharedByName(manifest: MfManifest) { + return new Map( + (manifest.shared ?? []).map((shared) => [shared.name, shared] as const), + ) +} + +function getExposesByName(manifest: MfManifest) { + return new Map( + (manifest.exposes ?? []).map((expose) => [expose.name, expose] as const), + ) +} + +function getSortedEntryNames(entries: Array) { + return entries + .map((entry) => entry.name) + .filter((name): name is string => Boolean(name)) + .sort() +} + +function sortAssetPaths(assetPaths: Array) { + return [...assetPaths].sort() +} + +function normalizeShareScope(shareScope?: string) { + return shareScope ?? 'default' +} + +function normalizeEager(eager?: boolean) { + return eager ?? false +} + +function normalizeImport(importFlag?: boolean) { + return importFlag ?? false +} + +function isVersionLike(version?: string) { + return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(version ?? '') +} + +function assertRelativeJsAssetPaths(assetPaths: Array) { + for (const assetPath of assetPaths) { + expect(assetPath.startsWith('static/js/')).toBeTruthy() + expect(assetPath.endsWith('.js')).toBeTruthy() + expect(assetPath.startsWith('/')).toBeFalsy() + expect(assetPath.includes('file://')).toBeFalsy() + expect(assetPath.includes('/workspace/')).toBeFalsy() + } +} + +function assertRemoteEntryMeta( + manifest: MfManifest, + expectedType: 'global' | 'commonjs-module', + expectedPublicPath: string, +) { + expect(manifest?.metaData?.remoteEntry?.type).toBe(expectedType) + expect(manifest?.metaData?.remoteEntry?.name).toBe('remoteEntry.js') + expect(manifest?.metaData?.remoteEntry?.path).toBe('') + expect(manifest?.metaData?.publicPath).toBe(expectedPublicPath) +} + +function assertManifestIdentity(manifest: MfManifest) { + expect(manifest.id).toBe('mf_remote') + expect(manifest.name).toBe('mf_remote') + expect(manifest.metaData?.name).toBe('mf_remote') + expect(manifest.metaData?.type).toBe('app') + expect(manifest.metaData?.pluginVersion).toBeDefined() + expect((manifest.metaData?.pluginVersion?.length ?? 0) > 0).toBeTruthy() + expect(manifest.metaData?.globalName).toBe('mf_remote') + expect(manifest.metaData?.prefetchInterface).toBe(false) + expect(manifest.metaData?.buildInfo?.buildVersion).toBe('local') + expect( + manifest.metaData?.buildInfo?.buildName?.includes( + 'module-federation-rsbuild-remote', + ) ?? false, + ).toBeTruthy() +} + +async function assertAssetServedAsJavaScript( + page: Page, + basePath: '/dist' | '/ssr', + assetPath: string, +) { + const response = await page.request.get(`${REMOTE_ORIGIN}${basePath}/${assetPath}`) + expect(response.ok()).toBeTruthy() + + const contentType = (response.headers()['content-type'] ?? '').toLowerCase() + expect(contentType).not.toBe('') + expect(contentType.includes('text/html')).toBeFalsy() + expect(contentType.includes('javascript')).toBeTruthy() + + const body = await response.text() + expect(body.startsWith('')).toBeFalsy() +} + +async function assertExposeContracts( + page: Page, + manifest: MfManifest, + basePath: '/dist' | '/ssr', +) { + const expectedExposePaths = { + message: './message', + routes: './routes', + 'server-data': './server-data', + } as const + + const exposesByName = getExposesByName(manifest) + expect(exposesByName.size).toBe(Object.keys(expectedExposePaths).length) + for (const [exposeName, expectedPath] of Object.entries(expectedExposePaths)) { + const exposeEntry = exposesByName.get(exposeName) + expect(exposeEntry).toBeDefined() + expect(exposeEntry?.path).toBe(expectedPath) + expect(exposeEntry?.id).toBe(`mf_remote:${exposeName}`) + + const exposeSyncAssets = exposeEntry?.assets?.js?.sync ?? [] + expect(exposeSyncAssets.length).toBeGreaterThan(0) + assertRelativeJsAssetPaths(exposeSyncAssets) + for (const exposeAsset of exposeSyncAssets) { + await assertAssetServedAsJavaScript(page, basePath, exposeAsset) + } + } +} + +test('renders the remote module on the SSR response', async ({ page }) => { + const response = await page.request.get('/') + expect(response.ok()).toBeTruthy() + + const html = await response.text() + if (HOST_MODE === 'ssr') { + expect(html).toContain('Federated message from remote') + } else { + expect(html).not.toContain('Federated message from remote') + } +}) + +test('loads remote entry over http at runtime', async ({ + page, +}) => { + const remoteRequests: Array = [] + + page.on('request', (request) => { + const url = request.url() + if (!url.startsWith(REMOTE_ORIGIN)) { + return + } + if (url.endsWith('.js')) { + remoteRequests.push(url) + } + }) + + await page.goto('/') + await page.waitForLoadState('networkidle') + + expect( + remoteRequests.some( + (url) => url.includes('/remoteEntry.js') || url.includes('/dist/remoteEntry.js'), + ), + ).toBeTruthy() + + if (HOST_MODE === 'ssr') { + await expect(page.getByTestId('federated-button')).toContainText( + 'Federated message from remote', + ) + } else { + await expect(page.getByTestId('federated-placeholder')).toContainText( + 'Federated message renders on the client in this mode.', + ) + } +}) + +test('serves remote entries as javascript over HTTP', async ({ page }) => { + for (const remoteEntryPath of ['/dist/remoteEntry.js', '/ssr/remoteEntry.js']) { + const response = await page.request.get(`${REMOTE_ORIGIN}${remoteEntryPath}`) + expect(response.ok()).toBeTruthy() + + const contentType = (response.headers()['content-type'] ?? '').toLowerCase() + expect(contentType).not.toBe('') + expect(contentType.includes('text/html')).toBeFalsy() + expect(contentType.includes('javascript')).toBeTruthy() + + const body = await response.text() + expect(body.startsWith('')).toBeFalsy() + expect(body.includes('mf_remote')).toBeTruthy() + } +}) + +test('serves federation manifest and stats endpoints as JSON', async ({ page }) => { + const expectedExposePaths = { + message: './message', + routes: './routes', + 'server-data': './server-data', + } as const + const expectedExposeFiles = { + message: 'src/message.tsx', + routes: 'src/routes.tsx', + 'server-data': 'src/server-data.ts', + } as const + const expectedSharedIds = { + react: 'mf_remote:react', + 'react-dom': 'mf_remote:react-dom', + } as const + + for (const [ + path, + expectedRemoteType, + expectedTypesZip, + expectedTypesApi, + expectedTypesPath, + expectedTypesName, + expectedPublicPath, + expectedSharedRequiredVersion, + ] of [ + ['/dist/mf-manifest.json', 'global', '@mf-types.zip', '@mf-types.d.ts', '', '', `${REMOTE_ORIGIN}/`, '^19.2.3'], + ['/dist/mf-stats.json', 'global', '@mf-types.zip', '@mf-types.d.ts', '', '', `${REMOTE_ORIGIN}/`, '^19.2.3'], + ['/ssr/mf-manifest.json', 'commonjs-module', '', '', '', '', `${REMOTE_ORIGIN}/ssr/`, '^*'], + ['/ssr/mf-stats.json', 'commonjs-module', '', '', '', '', `${REMOTE_ORIGIN}/ssr/`, '^*'], + ] as const) { + const response = await page.request.get(`${REMOTE_ORIGIN}${path}`) + expect(response.ok()).toBeTruthy() + + const contentType = (response.headers()['content-type'] ?? '').toLowerCase() + expect(contentType.includes('text/html')).toBeFalsy() + expect(contentType.includes('application/json')).toBeTruthy() + + const body = await response.text() + expect(body.startsWith('{')).toBeTruthy() + const parsed = JSON.parse(body) as MfManifest + expect(parsed.id).toBe('mf_remote') + expect(parsed.name).toBe('mf_remote') + expect(parsed.metaData?.name).toBe('mf_remote') + expect(parsed.metaData?.type).toBe('app') + expect(parsed.remotes ?? []).toEqual([]) + expect(parsed.metaData?.globalName).toBe('mf_remote') + expect(parsed.metaData?.prefetchInterface).toBe(false) + expect(parsed.metaData?.remoteEntry?.type).toBe(expectedRemoteType) + expect(parsed.metaData?.publicPath).toBe(expectedPublicPath) + expect(parsed.metaData?.remoteEntry?.name).toBe('remoteEntry.js') + expect(parsed.metaData?.remoteEntry?.path).toBe('') + expect(parsed.metaData?.types?.zip).toBe(expectedTypesZip) + expect(parsed.metaData?.types?.api).toBe(expectedTypesApi) + expect(parsed.metaData?.types?.path).toBe(expectedTypesPath) + expect(parsed.metaData?.types?.name).toBe(expectedTypesName) + expect(parsed.metaData?.pluginVersion).toBeDefined() + expect(isVersionLike(parsed.metaData?.pluginVersion)).toBeTruthy() + expect(parsed.metaData?.buildInfo?.buildVersion).toBe('local') + expect( + parsed.metaData?.buildInfo?.buildName?.includes(REMOTE_PACKAGE_NAME) ?? false, + ).toBeTruthy() + expect(Array.isArray(parsed.shared)).toBeTruthy() + expect(Array.isArray(parsed.exposes)).toBeTruthy() + + const sharedByName = getSharedByName(parsed) + expect(sharedByName.size).toBe(2) + const react = sharedByName.get('react') + const reactDom = sharedByName.get('react-dom') + expect(react).toBeDefined() + expect(reactDom).toBeDefined() + expect(react?.id).toBe(expectedSharedIds.react) + expect(reactDom?.id).toBe(expectedSharedIds['react-dom']) + expect(react?.singleton).toBe(true) + expect(reactDom?.singleton).toBe(true) + expect(react?.requiredVersion).toBe(expectedSharedRequiredVersion) + expect(reactDom?.requiredVersion).toBe(expectedSharedRequiredVersion) + expect(react?.fallback).toBe('') + expect(reactDom?.fallback).toBe('') + expect(react?.assets?.js?.async ?? []).toEqual([]) + expect(reactDom?.assets?.js?.async ?? []).toEqual([]) + expect(react?.assets?.css?.sync ?? []).toEqual([]) + expect(reactDom?.assets?.css?.sync ?? []).toEqual([]) + expect(react?.assets?.css?.async ?? []).toEqual([]) + expect(reactDom?.assets?.css?.async ?? []).toEqual([]) + const isStatsEndpoint = path.endsWith('mf-stats.json') + if (isStatsEndpoint) { + expect(react?.shareScope).toBe('default') + expect(reactDom?.shareScope).toBe('default') + expect(react?.eager).toBe(false) + expect(reactDom?.eager).toBe(false) + expect(react?.usedIn ?? []).toEqual([]) + expect(reactDom?.usedIn ?? []).toEqual([]) + expect(react?.usedExports ?? []).toEqual([]) + expect(reactDom?.usedExports ?? []).toEqual([]) + if (path.startsWith('/ssr/')) { + expect(react?.import).toBe(false) + expect(reactDom?.import).toBe(false) + } else { + expect(react?.import).toBeUndefined() + expect(reactDom?.import).toBeUndefined() + } + } else { + expect(react?.shareScope).toBeUndefined() + expect(reactDom?.shareScope).toBeUndefined() + expect(react?.eager).toBeUndefined() + expect(reactDom?.eager).toBeUndefined() + expect(react?.import).toBeUndefined() + expect(reactDom?.import).toBeUndefined() + expect(react?.usedIn).toBeUndefined() + expect(reactDom?.usedIn).toBeUndefined() + expect(react?.usedExports).toBeUndefined() + expect(reactDom?.usedExports).toBeUndefined() + } + if (path.startsWith('/ssr/')) { + expect(react?.version).toBe('*') + expect(reactDom?.version).toBe('*') + expect(react?.assets?.js?.sync ?? []).toEqual([]) + expect(reactDom?.assets?.js?.sync ?? []).toEqual([]) + } else { + expect(react?.version).not.toBe('*') + expect(reactDom?.version).not.toBe('*') + expect((react?.assets?.js?.sync ?? []).length).toBeGreaterThan(0) + expect((reactDom?.assets?.js?.sync ?? []).length).toBeGreaterThan(0) + assertRelativeJsAssetPaths([ + ...(react?.assets?.js?.sync ?? []), + ...(reactDom?.assets?.js?.sync ?? []), + ]) + } + + const exposesByName = getExposesByName(parsed) + expect(exposesByName.size).toBe(3) + for (const [exposeName, exposePath] of Object.entries(expectedExposePaths)) { + const expose = exposesByName.get(exposeName) + expect(expose).toBeDefined() + expect(expose?.id).toBe(`mf_remote:${exposeName}`) + expect(expose?.path).toBe(exposePath) + const exposeSyncAssets = expose?.assets?.js?.sync ?? [] + expect(exposeSyncAssets.length).toBeGreaterThan(0) + assertRelativeJsAssetPaths(exposeSyncAssets) + expect(expose?.assets?.js?.async ?? []).toEqual([]) + expect(expose?.assets?.css?.sync ?? []).toEqual([]) + expect(expose?.assets?.css?.async ?? []).toEqual([]) + if (isStatsEndpoint) { + expect(expose?.requires ?? []).toEqual([]) + expect(expose?.file).toBe(expectedExposeFiles[exposeName]) + } else { + expect(expose?.requires).toBeUndefined() + expect(expose?.file).toBeUndefined() + } + } + } +}) + +test('keeps node shared ownership contract in federation stats', async ({ page }) => { + const ssrStats = await fetchManifest(page, ['/ssr/mf-stats.json']) + const browserStats = await fetchManifest(page, ['/dist/mf-stats.json']) + + const ssrSharedByName = getSharedByName(ssrStats) + const browserSharedByName = getSharedByName(browserStats) + + const ssrReact = ssrSharedByName.get('react') + const ssrReactDom = ssrSharedByName.get('react-dom') + const browserReact = browserSharedByName.get('react') + const browserReactDom = browserSharedByName.get('react-dom') + + expect(ssrReact?.import).toBe(false) + expect(ssrReactDom?.import).toBe(false) + expect(ssrReact?.shareScope).toBe('default') + expect(ssrReactDom?.shareScope).toBe('default') + expect(ssrReact?.eager).toBe(false) + expect(ssrReactDom?.eager).toBe(false) + + expect(browserReact?.import).toBeUndefined() + expect(browserReactDom?.import).toBeUndefined() + expect(browserReact?.shareScope).toBe('default') + expect(browserReactDom?.shareScope).toBe('default') + expect(browserReact?.eager).toBe(false) + expect(browserReactDom?.eager).toBe(false) + + const expectedExposeFiles = { + message: 'src/message.tsx', + routes: 'src/routes.tsx', + 'server-data': 'src/server-data.ts', + } as const + + const ssrExposes = getExposesByName(ssrStats) + const browserExposes = getExposesByName(browserStats) + expect(ssrExposes.size).toBe(Object.keys(expectedExposeFiles).length) + expect(browserExposes.size).toBe(Object.keys(expectedExposeFiles).length) + + for (const [name, file] of Object.entries(expectedExposeFiles)) { + const ssrExpose = ssrExposes.get(name) + const browserExpose = browserExposes.get(name) + + expect(ssrExpose?.file).toBe(file) + expect(browserExpose?.file).toBe(file) + expect(ssrExpose?.requires ?? []).toEqual([]) + expect(browserExpose?.requires ?? []).toEqual([]) + } +}) + +test('keeps federation manifest and stats metadata aligned', async ({ page }) => { + const browserManifest = await fetchManifest(page, ['/dist/mf-manifest.json']) + const browserStats = await fetchManifest(page, ['/dist/mf-stats.json']) + const ssrManifest = await fetchManifest(page, ['/ssr/mf-manifest.json']) + const ssrStats = await fetchManifest(page, ['/ssr/mf-stats.json']) + + expect(browserManifest.metaData?.pluginVersion).toBe(ssrManifest.metaData?.pluginVersion) + expect(browserStats.metaData?.pluginVersion).toBe(ssrStats.metaData?.pluginVersion) + + for (const [manifest, stats] of [ + [browserManifest, browserStats], + [ssrManifest, ssrStats], + ] as const) { + expect(stats.id).toBe(manifest.id) + expect(stats.name).toBe(manifest.name) + expect(stats.metaData?.name).toBe(manifest.metaData?.name) + expect(stats.metaData?.type).toBe(manifest.metaData?.type) + expect(stats.metaData?.remoteEntry?.name).toBe(manifest.metaData?.remoteEntry?.name) + expect(stats.metaData?.remoteEntry?.path).toBe(manifest.metaData?.remoteEntry?.path) + expect(stats.metaData?.remoteEntry?.type).toBe(manifest.metaData?.remoteEntry?.type) + expect(stats.metaData?.publicPath).toBe(manifest.metaData?.publicPath) + expect(stats.metaData?.types?.zip).toBe(manifest.metaData?.types?.zip) + expect(stats.metaData?.types?.api).toBe(manifest.metaData?.types?.api) + expect(stats.metaData?.types?.path).toBe(manifest.metaData?.types?.path) + expect(stats.metaData?.types?.name).toBe(manifest.metaData?.types?.name) + expect(stats.metaData?.buildInfo?.buildVersion).toBe( + manifest.metaData?.buildInfo?.buildVersion, + ) + expect(stats.metaData?.buildInfo?.buildName).toBe( + manifest.metaData?.buildInfo?.buildName, + ) + expect(stats.metaData?.globalName).toBe(manifest.metaData?.globalName) + expect(stats.metaData?.prefetchInterface).toBe(manifest.metaData?.prefetchInterface) + expect(stats.metaData?.pluginVersion).toBe(manifest.metaData?.pluginVersion) + + expect(getSortedEntryNames(stats.shared ?? [])).toEqual( + getSortedEntryNames(manifest.shared ?? []), + ) + expect(getSortedEntryNames(stats.exposes ?? [])).toEqual( + getSortedEntryNames(manifest.exposes ?? []), + ) + expect(stats.remotes ?? []).toEqual([]) + expect((stats.shared ?? []).length).toBe(2) + expect((stats.exposes ?? []).length).toBe(3) + + const manifestSharedByName = getSharedByName(manifest) + const statsSharedByName = getSharedByName(stats) + for (const sharedName of getSortedEntryNames(manifest.shared ?? [])) { + const manifestShared = manifestSharedByName.get(sharedName) + const statsShared = statsSharedByName.get(sharedName) + + expect(statsShared?.id).toBe(manifestShared?.id) + expect(statsShared?.version).toBe(manifestShared?.version) + expect(statsShared?.requiredVersion).toBe(manifestShared?.requiredVersion) + expect(statsShared?.fallback).toBe(manifestShared?.fallback) + expect(normalizeShareScope(statsShared?.shareScope)).toBe( + normalizeShareScope(manifestShared?.shareScope), + ) + expect(normalizeImport(statsShared?.import)).toBe( + normalizeImport(manifestShared?.import), + ) + expect(normalizeEager(statsShared?.eager)).toBe( + normalizeEager(manifestShared?.eager), + ) + expect( + sortAssetPaths(statsShared?.assets?.js?.sync ?? []), + ).toEqual(sortAssetPaths(manifestShared?.assets?.js?.sync ?? [])) + expect( + sortAssetPaths(statsShared?.assets?.js?.async ?? []), + ).toEqual(sortAssetPaths(manifestShared?.assets?.js?.async ?? [])) + expect( + sortAssetPaths(statsShared?.assets?.css?.sync ?? []), + ).toEqual(sortAssetPaths(manifestShared?.assets?.css?.sync ?? [])) + expect( + sortAssetPaths(statsShared?.assets?.css?.async ?? []), + ).toEqual(sortAssetPaths(manifestShared?.assets?.css?.async ?? [])) + } + + const manifestExposesByName = getExposesByName(manifest) + const statsExposesByName = getExposesByName(stats) + for (const exposeName of getSortedEntryNames(manifest.exposes ?? [])) { + const manifestExpose = manifestExposesByName.get(exposeName) + const statsExpose = statsExposesByName.get(exposeName) + + expect(statsExpose?.id).toBe(manifestExpose?.id) + expect(statsExpose?.path).toBe(manifestExpose?.path) + expect(statsExpose?.file).toBeDefined() + expect(statsExpose?.requires ?? []).toEqual(manifestExpose?.requires ?? []) + expect( + sortAssetPaths(statsExpose?.assets?.js?.sync ?? []), + ).toEqual(sortAssetPaths(manifestExpose?.assets?.js?.sync ?? [])) + expect( + sortAssetPaths(statsExpose?.assets?.js?.async ?? []), + ).toEqual(sortAssetPaths(manifestExpose?.assets?.js?.async ?? [])) + expect( + sortAssetPaths(statsExpose?.assets?.css?.sync ?? []), + ).toEqual(sortAssetPaths(manifestExpose?.assets?.css?.sync ?? [])) + expect( + sortAssetPaths(statsExpose?.assets?.css?.async ?? []), + ).toEqual(sortAssetPaths(manifestExpose?.assets?.css?.async ?? [])) + } + } + + for (const stats of [browserStats, ssrStats]) { + const sharedByName = getSharedByName(stats) + expect(sharedByName.get('react')?.id).toBe('mf_remote:react') + expect(sharedByName.get('react-dom')?.id).toBe('mf_remote:react-dom') + + const exposesByName = getExposesByName(stats) + expect(exposesByName.get('message')?.id).toBe('mf_remote:message') + expect(exposesByName.get('routes')?.id).toBe('mf_remote:routes') + expect(exposesByName.get('server-data')?.id).toBe('mf_remote:server-data') + } +}) + +test('keeps plugin and build metadata consistent across json endpoints', async ({ + page, +}) => { + const browserManifest = await fetchManifest(page, ['/dist/mf-manifest.json']) + const browserStats = await fetchManifest(page, ['/dist/mf-stats.json']) + const ssrManifest = await fetchManifest(page, ['/ssr/mf-manifest.json']) + const ssrStats = await fetchManifest(page, ['/ssr/mf-stats.json']) + + const endpointPayloads = [browserManifest, browserStats, ssrManifest, ssrStats] + for (const payload of endpointPayloads) { + expect(payload.metaData?.pluginVersion).toBeDefined() + expect((payload.metaData?.pluginVersion?.length ?? 0) > 0).toBeTruthy() + expect(isVersionLike(payload.metaData?.pluginVersion)).toBeTruthy() + expect(payload.metaData?.buildInfo?.buildVersion).toBe('local') + expect( + payload.metaData?.buildInfo?.buildName?.includes(REMOTE_PACKAGE_NAME) ?? false, + ).toBeTruthy() + } + + const [firstPayload] = endpointPayloads + for (const payload of endpointPayloads) { + expect(payload.metaData?.pluginVersion).toBe(firstPayload.metaData?.pluginVersion) + expect(payload.metaData?.buildInfo?.buildVersion).toBe( + firstPayload.metaData?.buildInfo?.buildVersion, + ) + expect(payload.metaData?.buildInfo?.buildName).toBe( + firstPayload.metaData?.buildInfo?.buildName, + ) + } +}) + +test('serves browser federated types zip over HTTP', async ({ page }) => { + const manifest = await fetchManifest(page, [ + '/mf-manifest.json', + '/dist/mf-manifest.json', + ]) + const typesZip = manifest?.metaData?.types?.zip + expect(typesZip).toBeDefined() + expect(typesZip?.startsWith('@')).toBeTruthy() + + const response = await page.request.get(`${REMOTE_ORIGIN}/dist/${typesZip}`) + expect(response.ok()).toBeTruthy() + + const contentType = (response.headers()['content-type'] ?? '').toLowerCase() + expect(contentType).not.toBe('') + expect(contentType.includes('text/html')).toBeFalsy() + + const body = await response.body() + expect(body.length).toBeGreaterThan(0) +}) + +test('serves node-compatible remote SSR manifest metadata', async ({ page }) => { + const manifest = await fetchManifest(page, ['/ssr/mf-manifest.json']) + assertManifestIdentity(manifest) + assertRemoteEntryMeta(manifest, 'commonjs-module', `${REMOTE_ORIGIN}/ssr/`) + expect(manifest?.metaData?.types?.zip).toBe('') + expect(manifest?.metaData?.types?.api).toBe('') + + const sharedByName = getSharedByName(manifest) + expect(sharedByName.size).toBe(2) + expect(manifest.remotes ?? []).toEqual([]) + const reactShared = sharedByName.get('react') + const reactDomShared = sharedByName.get('react-dom') + + expect(reactShared).toBeDefined() + expect(reactDomShared).toBeDefined() + expect(reactShared?.version).toBe('*') + expect(reactShared?.requiredVersion).toBe('^*') + expect(reactDomShared?.version).toBe('*') + expect(reactDomShared?.requiredVersion).toBe('^*') + expect(reactShared?.fallback).toBe('') + expect(reactDomShared?.fallback).toBe('') + expect(reactShared?.assets?.js?.sync ?? []).toEqual([]) + expect(reactDomShared?.assets?.js?.sync ?? []).toEqual([]) + + await assertExposeContracts(page, manifest, '/ssr') +}) + +test('serves browser manifest with shared fallback assets', async ({ page }) => { + const manifest = await fetchManifest(page, [ + '/mf-manifest.json', + '/dist/mf-manifest.json', + ]) + assertManifestIdentity(manifest) + assertRemoteEntryMeta(manifest, 'global', `${REMOTE_ORIGIN}/`) + expect(manifest?.metaData?.types?.zip).toBe('@mf-types.zip') + expect(manifest?.metaData?.types?.api).toBe('@mf-types.d.ts') + + const sharedByName = getSharedByName(manifest) + expect(sharedByName.size).toBe(2) + expect(manifest.remotes ?? []).toEqual([]) + const reactShared = sharedByName.get('react') + const reactDomShared = sharedByName.get('react-dom') + + expect(reactShared).toBeDefined() + expect(reactDomShared).toBeDefined() + expect(reactShared?.version).not.toBe('*') + expect(reactDomShared?.version).not.toBe('*') + expect(reactShared?.requiredVersion).not.toBe('^*') + expect(reactDomShared?.requiredVersion).not.toBe('^*') + expect(reactShared?.fallback).toBe('') + expect(reactDomShared?.fallback).toBe('') + const reactSyncAssets = reactShared?.assets?.js?.sync ?? [] + const reactDomSyncAssets = reactDomShared?.assets?.js?.sync ?? [] + + expect(reactSyncAssets.length).toBeGreaterThan(0) + expect(reactDomSyncAssets.length).toBeGreaterThan(0) + assertRelativeJsAssetPaths([...reactSyncAssets, ...reactDomSyncAssets]) + for (const sharedAsset of [...reactSyncAssets, ...reactDomSyncAssets]) { + await assertAssetServedAsJavaScript(page, '/dist', sharedAsset) + } + + await assertExposeContracts(page, manifest, '/dist') +}) + +test('dynamically registers and renders remote routes', async ({ page }) => { + test.skip( + HOST_MODE !== 'ssr', + 'Dynamic route registration is validated in SSR mode.', + ) + + const response = await page.request.get('/dynamic-remote') + expect(response.ok()).toBeTruthy() + const html = await response.text() + expect(html).toContain('Dynamic remote page from federation') + + await page.goto('/dynamic-remote') + await expect(page.getByTestId('dynamic-remote-heading')).toContainText( + 'Dynamic remote page from federation', + ) +}) + +test('does not SSR selective client-only route content', async ({ page }) => { + test.skip( + HOST_MODE === 'prerender', + 'Prerender mode serves selective route via static redirect semantics.', + ) + + const response = await page.request.get('/selective-client-only') + expect(response.ok()).toBeTruthy() + + const html = await response.text() + expect(html).not.toContain('Selective remote route content') +}) + +test('loads federated server route data', async ({ page }) => { + test.skip( + HOST_MODE !== 'ssr', + 'Server federation runtime is only enabled for SSR mode.', + ) + + const response = await page.request.get('/api/federated-data') + expect(response.ok()).toBeTruthy() + const json = await response.json() + expect(json).toEqual({ + source: 'server-route', + message: 'Federated server data from remote', + }) +}) + +test('loads federated server function data', async ({ page }) => { + test.skip( + HOST_MODE !== 'ssr', + 'Server federation runtime is only enabled for SSR mode.', + ) + + await page.goto('/server-fn-mf') + await expect(page.getByTestId('server-fn-result')).toContainText( + '"source":"server-function"', + ) + await expect(page.getByTestId('server-fn-result')).toContainText( + '"message":"Federated server data from remote"', + ) +}) diff --git a/e2e/react-start/module-federation-rsbuild-host/tsconfig.json b/e2e/react-start/module-federation-rsbuild-host/tsconfig.json new file mode 100644 index 00000000000..d95b47232e2 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-host/tsconfig.json @@ -0,0 +1,18 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "ES2022", + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "noEmit": true + } +} diff --git a/e2e/react-start/module-federation-rsbuild-remote/README.md b/e2e/react-start/module-federation-rsbuild-remote/README.md new file mode 100644 index 00000000000..d3b5a052373 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-remote/README.md @@ -0,0 +1,88 @@ +# E2E Fixture: TanStack Start Module Federation Remote (Rsbuild) + +Remote fixture for: + +- `../module-federation-rsbuild-host` + +It exposes: + +- `mf_remote/message` +- `mf_remote/routes` +- `mf_remote/server-data` + +## Commands + +```sh +pnpm build +pnpm preview --port 3001 +``` + +## Node SSR federation compatibility + +For the node-target federation config we use: + +- `library.type: 'commonjs-module'` +- `remoteType: 'script'` +- `shared.react.import: false` +- `shared.react-dom.import: false` +- SSR manifest `metaData.remoteEntry.type: 'commonjs-module'` +- SSR manifest `metaData.publicPath` points to `http:///ssr/` +- SSR manifest types metadata is empty (`zip: ''`, `api: ''`) +- SSR React/ReactDOM shared metadata uses wildcard versions (`*` / `^*`). +- empty SSR shared fallback JS assets for React/ReactDOM +- exposed module JS asset entries are relative `static/js/...` paths. +- JS asset entries are expected to end in `.js`. + +Web-target shared config remains standard singleton sharing (no `import: false`) +to preserve normal browser-side shared behavior. + +Expected browser manifest contract for the web target: + +- `metaData.remoteEntry.type: 'global'` +- `metaData.publicPath` points to `http:///` +- browser manifest types metadata points to emitted types (`@mf-types.zip`, `@mf-types.d.ts`) +- browser React/ReactDOM shared metadata uses concrete non-wildcard versions. +- React/ReactDOM browser shared fallback JS assets are present. +- Browser shared JS asset entries are relative `static/js/...` paths. +- Exposed module JS asset entries are also relative `static/js/...` paths. + +Federation stats contract expectations: + +- SSR stats include `import: false` on React/ReactDOM shared entries. +- Browser stats do not include `import` on React/ReactDOM shared entries. +- Manifest/stats metadata contracts remain aligned across `dist` and `ssr` outputs. +- Stats shared/expose id sets remain stable (`mf_remote:react`, `mf_remote:react-dom`, and expose ids for message/routes/server-data). +- Metadata alignment includes remoteEntry name/path and build/plugin version fields. +- Shared/expose asset lists remain aligned between manifest and stats outputs. +- Types metadata (`path`/`name`) and shared/expose CSS asset lists are also expected to match between outputs. +- JSON endpoint contract also checks identity fields and remote entry/plugin metadata presence. +- Endpoint payloads are expected to keep identity metadata stable (`metaData.name: mf_remote`, `metaData.type: app`, `buildInfo.buildVersion: local`). +- Endpoint payloads are expected to keep global metadata invariants (`globalName: mf_remote`, `prefetchInterface: false`, `remoteEntry.path: ''`). +- JSON endpoint checks also validate path-specific metadata: + - `/dist/*` endpoints use browser remote entry/type metadata and browser types metadata. + - `/ssr/*` endpoints use node remote entry/type metadata and empty SSR types metadata. +- `types.path` and `types.name` remain empty strings (`''`) across both browser and SSR endpoint payloads. +- `pluginVersion` and build metadata fields remain consistent across all JSON endpoints. +- `pluginVersion` is expected to remain SemVer-like on all JSON endpoints. +- Shared version semantics remain mode-correct across endpoints (`*` only for SSR node-target shared entries). +- JSON payloads also retain `remotes: []` and the two-entry shared set (`react`, `react-dom`). +- Shared entries stay singleton on all endpoint payloads. +- Stats endpoints include shared runtime flags (`shareScope: default`, `eager: false`, SSR-only `import: false`) while manifest endpoints omit those fields. +- Stats endpoints keep shared usage arrays (`usedIn`, `usedExports`) as empty arrays while manifest endpoints omit them. +- Endpoint shared JS asset semantics remain mode-correct (`[]` for SSR shared entries, non-empty relative `static/js/*.js` for browser shared entries). +- Shared endpoint payloads keep async JS asset arrays empty and CSS asset arrays empty. +- Expose ids/paths are expected to remain stable in endpoint payloads for `message`, `routes`, and `server-data`. +- Stats endpoint expose `requires` arrays remain empty; manifest endpoint payloads omit `requires`. +- Stats endpoints also keep stable expose `file` metadata (`src/message.tsx`, `src/routes.tsx`, `src/server-data.ts`). +- Endpoint expose sync JS asset lists stay non-empty and keep relative `static/js/*.js` paths. +- Endpoint expose async JS and CSS asset arrays remain empty. + +Both `/dist/remoteEntry.js` and `/ssr/remoteEntry.js` are expected to serve +JavaScript payloads over HTTP with JavaScript content-types (not HTML fallbacks). + +The browser types archive `/dist/@mf-types.zip` should also be retrievable as a +non-HTML payload. + +This keeps React ownership on the host side in node SSR runtime and avoids +remote shared fallback chunk loading incompatibilities. + diff --git a/e2e/react-start/module-federation-rsbuild-remote/index.html b/e2e/react-start/module-federation-rsbuild-remote/index.html new file mode 100644 index 00000000000..613abec7295 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-remote/index.html @@ -0,0 +1,12 @@ + + + + + + Module Federation Remote + + +
+ + + diff --git a/e2e/react-start/module-federation-rsbuild-remote/package.json b/e2e/react-start/module-federation-rsbuild-remote/package.json new file mode 100644 index 00000000000..adbe7a63e54 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-remote/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-react-start-e2e-module-federation-rsbuild-remote", + "private": true, + "type": "module", + "scripts": { + "dev": "rsbuild dev --port 3001", + "build": "rsbuild build", + "preview": "rsbuild preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@module-federation/node": "^2.7.32", + "@module-federation/rsbuild-plugin": "https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd", + "@rsbuild/core": "^1.3.21", + "@rsbuild/plugin-react": "^1.1.0", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "typescript": "^5.7.2" + } +} diff --git a/e2e/react-start/module-federation-rsbuild-remote/rsbuild.config.ts b/e2e/react-start/module-federation-rsbuild-remote/rsbuild.config.ts new file mode 100644 index 00000000000..35d4754ac69 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-remote/rsbuild.config.ts @@ -0,0 +1,115 @@ +import { defineConfig } from '@rsbuild/core' +import { createRequire } from 'node:module' +import { pluginReact } from '@rsbuild/plugin-react' +import { pluginModuleFederation } from '@module-federation/rsbuild-plugin' + +const require = createRequire(import.meta.url) +const remotePort = Number(process.env.REMOTE_PORT || 3001) +const remoteOrigin = `http://localhost:${remotePort}` +const sharedForWeb = { + react: { + singleton: true, + requiredVersion: false, + }, + 'react-dom': { + singleton: true, + requiredVersion: false, + }, +} +const sharedForNode = { + react: { + // Keep host-owned React in node runtime. + // With remoteType=script + node runtime plugin, remote shared fallbacks + // can otherwise trigger SSR chunk loading incompatibilities. + import: false, + singleton: true, + requiredVersion: false, + }, + 'react-dom': { + // Keep host-owned ReactDOM in node runtime. + import: false, + singleton: true, + requiredVersion: false, + }, +} + +export default defineConfig({ + plugins: [ + pluginReact(), + pluginModuleFederation( + { + name: 'mf_remote', + filename: 'remoteEntry.js', + exposes: { + './message': './src/message.tsx', + './routes': './src/routes.tsx', + './server-data': './src/server-data.ts', + }, + experiments: { + asyncStartup: true, + }, + runtimePlugins: [require.resolve('@module-federation/node/runtimePlugin')], + shared: sharedForWeb, + }, + { + environment: 'web', + }, + ), + pluginModuleFederation( + { + name: 'mf_remote', + filename: 'remoteEntry.js', + dts: false, + library: { + type: 'commonjs-module', + }, + remoteType: 'script', + exposes: { + './message': './src/message.tsx', + './routes': './src/routes.tsx', + './server-data': './src/server-data.ts', + }, + experiments: { + asyncStartup: true, + }, + runtimePlugins: [require.resolve('@module-federation/node/runtimePlugin')], + shared: sharedForNode, + }, + { + target: 'node', + environment: 'ssr', + }, + ), + ], + environments: { + web: { + output: { + assetPrefix: `${remoteOrigin}/`, + }, + }, + ssr: { + source: { + entry: {}, + }, + output: { + assetPrefix: `${remoteOrigin}/ssr/`, + cleanDistPath: false, + distPath: { + root: 'ssr', + }, + }, + tools: { + rspack: { + target: 'async-node', + output: { + chunkFormat: 'commonjs', + chunkLoading: 'async-node', + library: { + type: 'commonjs-module', + }, + }, + }, + }, + }, + }, +}) diff --git a/e2e/react-start/module-federation-rsbuild-remote/src/env.d.ts b/e2e/react-start/module-federation-rsbuild-remote/src/env.d.ts new file mode 100644 index 00000000000..b0ac762b091 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-remote/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/e2e/react-start/module-federation-rsbuild-remote/src/index.tsx b/e2e/react-start/module-federation-rsbuild-remote/src/index.tsx new file mode 100644 index 00000000000..0e778361332 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-remote/src/index.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' +import { createRoot } from 'react-dom/client' +import { FederatedMessage } from './message' + +function App() { + return ( +
+

Remote application

+ +
+ ) +} + +const container = document.getElementById('root') +if (container) { + const root = createRoot(container) + root.render( + + + , + ) +} diff --git a/e2e/react-start/module-federation-rsbuild-remote/src/message.tsx b/e2e/react-start/module-federation-rsbuild-remote/src/message.tsx new file mode 100644 index 00000000000..aabb061ed30 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-remote/src/message.tsx @@ -0,0 +1,15 @@ +import * as React from 'react' + +export function FederatedMessage() { + const [count, setCount] = React.useState(0) + + return ( + + ) +} diff --git a/e2e/react-start/module-federation-rsbuild-remote/src/routes.tsx b/e2e/react-start/module-federation-rsbuild-remote/src/routes.tsx new file mode 100644 index 00000000000..d0f8d8763c5 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-remote/src/routes.tsx @@ -0,0 +1,27 @@ +import type { ComponentType } from 'react' +import { FederatedMessage } from './message' + +export type RemoteRouteRegistration = { + id: string + path: string + component: ComponentType +} + +function DynamicRemotePage() { + return ( +
+

+ Dynamic remote page from federation +

+ +
+ ) +} + +export const remoteRouteRegistrations: Array = [ + { + id: 'dynamic-remote', + path: 'dynamic-remote', + component: DynamicRemotePage, + }, +] diff --git a/e2e/react-start/module-federation-rsbuild-remote/src/server-data.ts b/e2e/react-start/module-federation-rsbuild-remote/src/server-data.ts new file mode 100644 index 00000000000..6e95c99cd9f --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-remote/src/server-data.ts @@ -0,0 +1,6 @@ +export function getFederatedServerData(source: string) { + return { + source, + message: 'Federated server data from remote', + } +} diff --git a/e2e/react-start/module-federation-rsbuild-remote/tsconfig.json b/e2e/react-start/module-federation-rsbuild-remote/tsconfig.json new file mode 100644 index 00000000000..d95b47232e2 --- /dev/null +++ b/e2e/react-start/module-federation-rsbuild-remote/tsconfig.json @@ -0,0 +1,18 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "ES2022", + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "noEmit": true + } +} diff --git a/examples/react/start-module-federation-rsbuild-host/README.md b/examples/react/start-module-federation-rsbuild-host/README.md new file mode 100644 index 00000000000..b1b8a99f6e7 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/README.md @@ -0,0 +1,104 @@ +# TanStack Start - Module Federation Host (Rsbuild) + +This example demonstrates a **TanStack Start host app** consuming a remote module +using: + +- `@module-federation/rsbuild-plugin` +- `@module-federation/node/runtimePlugin` + +It also demonstrates: + +- Dynamic route registration from a remote module (Option B) +- Selective SSR (`ssr: false`) on a federated route +- Federated server route and server function handlers +- Start mode matrix via `HOST_MODE`: `ssr`, `spa`, `prerender` + +## SSR node runtime compatibility + +This host expects the paired remote's **node-target federation config** to use: + +- `library.type: 'commonjs-module'` +- `remoteType: 'script'` +- `shared.react/react-dom.import: false` +- SSR manifest `metaData.remoteEntry.type: 'commonjs-module'` +- SSR manifest `metaData.publicPath: 'http:///ssr/'` +- SSR manifest types metadata empty (`zip: ''`, `api: ''`) +- SSR manifest React/ReactDOM shared metadata uses wildcard versions (`*` / `^*`) +- SSR exposed module JS asset paths as relative `static/js/...` + +For the remote web target (browser manifest), expected contract is: + +- `metaData.remoteEntry.type: 'global'` +- `metaData.publicPath: 'http:///'` +- browser manifest types metadata points to emitted types (`@mf-types.zip`, `@mf-types.d.ts`) +- browser React/ReactDOM shared metadata uses concrete non-wildcard versions +- React/ReactDOM shared fallback JS asset lists are non-empty. +- Shared JS asset entries are relative `static/js/...` paths resolved via `publicPath`. +- Exposed module JS asset entries are also relative `static/js/...` paths. +- JS asset entries are expected to use `.js` suffixes. +- `/dist/remoteEntry.js` and `/ssr/remoteEntry.js` should serve JavaScript over HTTP with JavaScript content-types. +- `/dist/@mf-types.zip` should be retrievable over HTTP as a non-HTML payload. +- SSR stats shared entries should set `import: false` for React/ReactDOM; browser stats should omit `import`. +- Manifest and stats metadata should remain aligned across browser and SSR outputs. +- Stats entry ids and cardinality should remain stable (`shared: 2`, `exposes: 3`, ids under `mf_remote:*`). +- Metadata parity should include remoteEntry name/path plus build/plugin version fields. +- Shared/expose asset lists should also stay aligned between manifest and stats outputs. +- Types metadata (`path`/`name`) and CSS asset lists are expected to stay aligned as well. +- JSON endpoint validation should include identity fields and remote entry/plugin metadata presence. +- Endpoint payloads should keep identity metadata stable (`metaData.name: mf_remote`, `metaData.type: app`, `buildInfo.buildVersion: local`). +- Endpoint payloads should keep global metadata invariants (`globalName: mf_remote`, `prefetchInterface: false`, `remoteEntry.path: ''`). +- `/dist/*` and `/ssr/*` JSON endpoints should each report their expected remoteEntry type/types metadata/publicPath values. +- `types.path` and `types.name` should remain empty strings (`''`) across browser and SSR endpoint payloads. +- Build/plugin metadata fields are expected to remain consistent across all federation JSON endpoints. +- `pluginVersion` values should also remain SemVer-like across all federation JSON endpoints. +- Shared version metadata should stay mode-correct across endpoints (browser concrete versions, SSR wildcard versions). +- JSON endpoint payloads should keep `remotes` empty and include only `react`/`react-dom` shared entries. +- Shared endpoint ids should remain stable as `mf_remote:react` and `mf_remote:react-dom`. +- Shared entries should stay singleton on all endpoint payloads. +- Stats endpoints should keep shared runtime flags (`shareScope: default`, `eager: false`, SSR-only `import: false`) while manifest endpoints omit those fields. +- Stats endpoints should keep shared usage arrays (`usedIn`, `usedExports`) as empty arrays while manifest endpoints omit them. +- Endpoint shared JS asset semantics should stay mode-correct (`[]` for SSR shared entries, non-empty relative `static/js/*.js` for browser shared entries). +- Shared endpoint payloads should keep async JS asset arrays empty and CSS asset arrays empty. +- Expose ids/paths in endpoint payloads should remain stable for `message`, `routes`, and `server-data`. +- Stats endpoint expose `requires` arrays should remain empty; manifest endpoint payloads should omit `requires`. +- Stats endpoint payloads should also keep stable expose `file` values (`src/message.tsx`, `src/routes.tsx`, `src/server-data.ts`). +- Endpoint expose sync JS asset lists should stay non-empty and use relative `static/js/*.js` paths. +- Endpoint expose async JS and CSS asset arrays should remain empty. + +That combination keeps React shared ownership on the host and avoids SSR +runtime fallback chunk loading conflicts with `@module-federation/node`. + +## Run with the remote app + +1. Start the remote app first: + +```sh +cd ../start-module-federation-rsbuild-remote +pnpm install +pnpm build +pnpm preview --port 3001 +``` + +2. In another terminal, start the host app: + +```sh +pnpm install +HOST_MODE=ssr REMOTE_PORT=3001 pnpm build +PORT=3000 pnpm start +``` + +3. Open `http://localhost:3000`. + +The host renders the remote component during SSR and hydrates it on the client. + +## Try other Start modes + +```sh +# SPA mode +HOST_MODE=spa REMOTE_PORT=3001 pnpm build +PORT=3000 pnpm start + +# Static prerender mode +HOST_MODE=prerender REMOTE_PORT=3001 pnpm build +PORT=3000 pnpm start +``` diff --git a/examples/react/start-module-federation-rsbuild-host/package.json b/examples/react/start-module-federation-rsbuild-host/package.json new file mode 100644 index 00000000000..05dee88e5ab --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/package.json @@ -0,0 +1,30 @@ +{ + "name": "tanstack-react-example-start-module-federation-rsbuild-host", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "rsbuild dev --port 3000", + "build": "rsbuild build", + "preview": "rsbuild preview", + "start": "node server.js" + }, + "dependencies": { + "@tanstack/react-router": "workspace:^", + "@tanstack/react-start": "workspace:^", + "express": "^5.1.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@module-federation/node": "^2.7.32", + "@module-federation/rsbuild-plugin": "https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd", + "@rsbuild/core": "^1.3.21", + "@rsbuild/plugin-react": "^1.1.0", + "@types/node": "^22.10.2", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "srvx": "^0.11.2", + "typescript": "^5.7.2" + } +} diff --git a/examples/react/start-module-federation-rsbuild-host/rsbuild.config.ts b/examples/react/start-module-federation-rsbuild-host/rsbuild.config.ts new file mode 100644 index 00000000000..e17ce071bad --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/rsbuild.config.ts @@ -0,0 +1,92 @@ +import { defineConfig } from '@rsbuild/core' +import { createRequire } from 'node:module' +import { pluginReact } from '@rsbuild/plugin-react' +import { pluginModuleFederation } from '@module-federation/rsbuild-plugin' +import { tanstackStart } from '@tanstack/react-start/plugin/rsbuild' + +const require = createRequire(import.meta.url) +const remotePort = Number(process.env.REMOTE_PORT || 3001) +const remoteOrigin = `http://localhost:${remotePort}` +const hostMode = process.env.HOST_MODE || 'ssr' +const isSpaMode = hostMode === 'spa' +const isPrerenderMode = hostMode === 'prerender' +const enableServerFederationRuntime = hostMode === 'ssr' +const shared = { + react: { + singleton: true, + requiredVersion: false, + }, + 'react-dom': { + singleton: true, + requiredVersion: false, + }, +} +const startConfig = isSpaMode + ? { + spa: { + enabled: true, + }, + } + : isPrerenderMode + ? { + prerender: { + enabled: true, + crawlLinks: false, + autoStaticPathsDiscovery: false, + }, + pages: [ + { path: '/' }, + { path: '/selective-client-only' }, + ], + } + : undefined + +export default defineConfig({ + plugins: [ + pluginReact(), + pluginModuleFederation( + { + name: 'mf_host', + remotes: { + mf_remote: `mf_remote@${remoteOrigin}/remoteEntry.js`, + }, + dts: false, + experiments: { + asyncStartup: true, + }, + runtimePlugins: [require.resolve('@module-federation/node/runtimePlugin')], + shared, + }, + { + environment: 'client', + }, + ), + pluginModuleFederation( + { + name: 'mf_host_ssr', + remotes: { + // Server remotes are fetched via node runtime over HTTP from the remote SSR output. + mf_remote: `mf_remote@${remoteOrigin}/ssr/remoteEntry.js`, + }, + dts: false, + // Required by @module-federation/node runtime container loading. + remoteType: 'script', + experiments: { + asyncStartup: true, + }, + runtimePlugins: enableServerFederationRuntime + ? [require.resolve('@module-federation/node/runtimePlugin')] + : [], + shared, + }, + { + target: 'node', + environment: 'ssr', + }, + ), + ...tanstackStart(startConfig), + ], + environments: { + ssr: {}, + }, +}) diff --git a/examples/react/start-module-federation-rsbuild-host/server.js b/examples/react/start-module-federation-rsbuild-host/server.js new file mode 100644 index 00000000000..9fcfd2e772a --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/server.js @@ -0,0 +1,38 @@ +import fs from 'node:fs' +import path from 'node:path' +import { createRequire } from 'node:module' +import express from 'express' +import { toNodeHandler } from 'srvx/node' + +const require = createRequire(import.meta.url) +const port = process.env.PORT || 3000 + +if (!globalThis.self) { + globalThis.self = globalThis +} + +const bundledServerPath = path.resolve('./dist/server/server.js') +const commonJsServerPath = path.resolve('./dist/server/server.cjs') +fs.copyFileSync(bundledServerPath, commonJsServerPath) + +let imported = require(commonJsServerPath) +if (imported && typeof imported.then === 'function') { + imported = await imported +} +const server = imported?.default ?? imported +const nodeHandler = toNodeHandler(server.fetch) + +const app = express() + +app.use(express.static('./dist/client', { index: false })) +app.use(async (req, res, next) => { + try { + await nodeHandler(req, res) + } catch (error) { + next(error) + } +}) + +app.listen(port, () => { + console.info(`Start Server: http://localhost:${port}`) +}) diff --git a/examples/react/start-module-federation-rsbuild-host/src/env.d.ts b/examples/react/start-module-federation-rsbuild-host/src/env.d.ts new file mode 100644 index 00000000000..644ee55d5cc --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/src/env.d.ts @@ -0,0 +1,27 @@ +/// +/// + +declare module 'mf_remote/message' { + import type { ReactNode } from 'react' + + export function FederatedMessage(): ReactNode +} + +declare module 'mf_remote/routes' { + import type { ComponentType } from 'react' + + export type RemoteRouteRegistration = { + id: string + path: string + component: ComponentType + } + + export const remoteRouteRegistrations: Array +} + +declare module 'mf_remote/server-data' { + export function getFederatedServerData(source: string): { + source: string + message: string + } +} diff --git a/examples/react/start-module-federation-rsbuild-host/src/routeTree.gen.ts b/examples/react/start-module-federation-rsbuild-host/src/routeTree.gen.ts new file mode 100644 index 00000000000..ba164346a39 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/src/routeTree.gen.ts @@ -0,0 +1,131 @@ +/* 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 ServerFnMfRouteImport } from './routes/server-fn-mf' +import { Route as SelectiveClientOnlyRouteImport } from './routes/selective-client-only' +import { Route as IndexRouteImport } from './routes/index' +import { Route as ApiFederatedDataRouteImport } from './routes/api/federated-data' + +const ServerFnMfRoute = ServerFnMfRouteImport.update({ + id: '/server-fn-mf', + path: '/server-fn-mf', + getParentRoute: () => rootRouteImport, +} as any) +const SelectiveClientOnlyRoute = SelectiveClientOnlyRouteImport.update({ + id: '/selective-client-only', + path: '/selective-client-only', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const ApiFederatedDataRoute = ApiFederatedDataRouteImport.update({ + id: '/api/federated-data', + path: '/api/federated-data', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/selective-client-only': typeof SelectiveClientOnlyRoute + '/server-fn-mf': typeof ServerFnMfRoute + '/api/federated-data': typeof ApiFederatedDataRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/selective-client-only': typeof SelectiveClientOnlyRoute + '/server-fn-mf': typeof ServerFnMfRoute + '/api/federated-data': typeof ApiFederatedDataRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/selective-client-only': typeof SelectiveClientOnlyRoute + '/server-fn-mf': typeof ServerFnMfRoute + '/api/federated-data': typeof ApiFederatedDataRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/selective-client-only' + | '/server-fn-mf' + | '/api/federated-data' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/selective-client-only' | '/server-fn-mf' | '/api/federated-data' + id: + | '__root__' + | '/' + | '/selective-client-only' + | '/server-fn-mf' + | '/api/federated-data' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + SelectiveClientOnlyRoute: typeof SelectiveClientOnlyRoute + ServerFnMfRoute: typeof ServerFnMfRoute + ApiFederatedDataRoute: typeof ApiFederatedDataRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/server-fn-mf': { + id: '/server-fn-mf' + path: '/server-fn-mf' + fullPath: '/server-fn-mf' + preLoaderRoute: typeof ServerFnMfRouteImport + parentRoute: typeof rootRouteImport + } + '/selective-client-only': { + id: '/selective-client-only' + path: '/selective-client-only' + fullPath: '/selective-client-only' + preLoaderRoute: typeof SelectiveClientOnlyRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/api/federated-data': { + id: '/api/federated-data' + path: '/api/federated-data' + fullPath: '/api/federated-data' + preLoaderRoute: typeof ApiFederatedDataRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + SelectiveClientOnlyRoute: SelectiveClientOnlyRoute, + ServerFnMfRoute: ServerFnMfRoute, + ApiFederatedDataRoute: ApiFederatedDataRoute, +} +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/examples/react/start-module-federation-rsbuild-host/src/router.tsx b/examples/react/start-module-federation-rsbuild-host/src/router.tsx new file mode 100644 index 00000000000..5c435cf75fd --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/src/router.tsx @@ -0,0 +1,28 @@ +import { createRoute, createRouter } from '@tanstack/react-router' +import { routeTree as fileRouteTree } from './routeTree.gen' + +const shouldLoadRemoteRouteRegistrations = + typeof window !== 'undefined' || process.env.HOST_MODE === 'ssr' + +const remoteRouteRegistrations = shouldLoadRemoteRouteRegistrations + ? (await import('mf_remote/routes')).remoteRouteRegistrations + : [] + +const dynamicRemoteRoutes = remoteRouteRegistrations.map((registration) => + createRoute({ + getParentRoute: () => fileRouteTree, + path: registration.path, + component: registration.component, + }), +) + +const routeTree = fileRouteTree.addChildren( + [...(fileRouteTree.children ?? []), ...dynamicRemoteRoutes] as any, +) + +export function getRouter() { + return createRouter({ + routeTree, + defaultPreload: 'intent', + }) +} diff --git a/examples/react/start-module-federation-rsbuild-host/src/routes/__root.tsx b/examples/react/start-module-federation-rsbuild-host/src/routes/__root.tsx new file mode 100644 index 00000000000..ef6cc68bebb --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/src/routes/__root.tsx @@ -0,0 +1,25 @@ +import * as React from 'react' +import { + HeadContent, + Outlet, + Scripts, + createRootRoute, +} from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: RootComponent, +}) + +function RootComponent() { + return ( + + + + + + + + + + ) +} diff --git a/examples/react/start-module-federation-rsbuild-host/src/routes/api/federated-data.ts b/examples/react/start-module-federation-rsbuild-host/src/routes/api/federated-data.ts new file mode 100644 index 00000000000..b6e6a238e88 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/src/routes/api/federated-data.ts @@ -0,0 +1,12 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/api/federated-data')({ + server: { + handlers: { + GET: async () => { + const { getFederatedServerData } = await import('mf_remote/server-data') + return Response.json(getFederatedServerData('server-route')) + }, + }, + }, +}) diff --git a/examples/react/start-module-federation-rsbuild-host/src/routes/index.tsx b/examples/react/start-module-federation-rsbuild-host/src/routes/index.tsx new file mode 100644 index 00000000000..b104fe8a9ef --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/src/routes/index.tsx @@ -0,0 +1,35 @@ +import { createFileRoute } from '@tanstack/react-router' + +const shouldLoadFederatedMessage = + typeof window !== 'undefined' || process.env.HOST_MODE === 'ssr' + +const FederatedMessage = shouldLoadFederatedMessage + ? (await import('mf_remote/message')).FederatedMessage + : function FederatedMessagePlaceholder() { + return

Federated message renders on the client in this mode.

+ } + +export const Route = createFileRoute('/')({ + component: Home, +}) + +function Home() { + return ( +
+

Host application

+

This page renders a module exposed by the remote application.

+ + +
+ ) +} diff --git a/examples/react/start-module-federation-rsbuild-host/src/routes/selective-client-only.tsx b/examples/react/start-module-federation-rsbuild-host/src/routes/selective-client-only.tsx new file mode 100644 index 00000000000..5e68bdd7ff3 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/src/routes/selective-client-only.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/selective-client-only')({ + ssr: false, + component: SelectiveClientOnlyRoute, +}) + +function SelectiveClientOnlyRoute() { + return ( +
+

Selective remote route content

+
+ ) +} diff --git a/examples/react/start-module-federation-rsbuild-host/src/routes/server-fn-mf.tsx b/examples/react/start-module-federation-rsbuild-host/src/routes/server-fn-mf.tsx new file mode 100644 index 00000000000..632d2cebb25 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/src/routes/server-fn-mf.tsx @@ -0,0 +1,25 @@ +import { createFileRoute } from '@tanstack/react-router' +import { createServerFn } from '@tanstack/react-start' + +const getRemoteServerData = createServerFn({ method: 'GET' }).handler( + async () => { + const { getFederatedServerData } = await import('mf_remote/server-data') + return getFederatedServerData('server-function') + }, +) + +export const Route = createFileRoute('/server-fn-mf')({ + loader: () => getRemoteServerData(), + component: ServerFunctionFederationRoute, +}) + +function ServerFunctionFederationRoute() { + const response = Route.useLoaderData() + + return ( +
+

Server function federation route

+
{JSON.stringify(response)}
+
+ ) +} diff --git a/examples/react/start-module-federation-rsbuild-host/tsconfig.json b/examples/react/start-module-federation-rsbuild-host/tsconfig.json new file mode 100644 index 00000000000..d95b47232e2 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-host/tsconfig.json @@ -0,0 +1,18 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "ES2022", + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "noEmit": true + } +} diff --git a/examples/react/start-module-federation-rsbuild-remote/README.md b/examples/react/start-module-federation-rsbuild-remote/README.md new file mode 100644 index 00000000000..1fa31f31b4d --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-remote/README.md @@ -0,0 +1,78 @@ +# TanStack Start - Module Federation Remote (Rsbuild) + +This example is the **remote app** used by the host example: + +- `../start-module-federation-rsbuild-host` + +It exposes: + +- `mf_remote/message` +- `mf_remote/routes` (dynamic route registrations) +- `mf_remote/server-data` (server-side federated data helper) + +via `@module-federation/rsbuild-plugin`, and serves chunks over HTTP. + +## SSR node runtime note + +For node-target federation in this setup we use: + +- `library.type: 'commonjs-module'` +- `remoteType: 'script'` +- `shared.react/react-dom.import: false` in the node-target config +- SSR manifest `metaData.remoteEntry.type: 'commonjs-module'` +- SSR manifest `metaData.publicPath: 'http:///ssr/'` +- SSR manifest types metadata stays empty (`zip: ''`, `api: ''`) +- SSR React/ReactDOM shared metadata is wildcard (`*` / `^*`) +- SSR exposed module JS assets are emitted as relative `static/js/...` paths. + +The web-target config keeps normal singleton shared config (without +`import: false`) so client-side sharing remains unchanged. + +Expected browser manifest contract for web target: + +- `metaData.remoteEntry.type: 'global'` +- `metaData.publicPath: 'http:///'` +- browser manifest types metadata points to emitted types (`@mf-types.zip`, `@mf-types.d.ts`) +- browser React/ReactDOM shared metadata uses concrete non-wildcard versions +- React/ReactDOM shared fallback JS assets are present. +- Browser shared JS assets are emitted as relative `static/js/...` paths. +- Browser exposed module JS assets are also relative `static/js/...` paths. +- JS asset entries are expected to use `.js` suffixes. +- `/dist/remoteEntry.js` and `/ssr/remoteEntry.js` are served as JavaScript over HTTP with JavaScript content-types. +- `/dist/@mf-types.zip` is retrievable over HTTP as a non-HTML payload. +- SSR stats should show `import: false` for React/ReactDOM while browser stats keep `import` unset. +- Manifest/stats metadata is expected to stay aligned for both browser and SSR outputs. +- Stats shared/expose ids and counts are expected to remain stable (`shared: 2`, `exposes: 3`, ids prefixed with `mf_remote:`). +- Metadata alignment should also cover remoteEntry name/path and build/plugin version fields. +- Shared/expose asset lists are expected to match between manifest and stats outputs. +- Types metadata (`path`/`name`) and CSS asset list parity is expected between manifest and stats. +- JSON endpoint payloads are expected to include identity and remote entry/plugin metadata fields. +- Endpoint payloads should keep identity metadata stable (`metaData.name: mf_remote`, `metaData.type: app`, `buildInfo.buildVersion: local`). +- Endpoint payloads should preserve global metadata invariants (`globalName: mf_remote`, `prefetchInterface: false`, `remoteEntry.path: ''`). +- `/dist/*` vs `/ssr/*` endpoint metadata should remain mode-correct (remoteEntry type, types metadata, publicPath). +- `types.path` and `types.name` should remain empty strings (`''`) across browser and SSR endpoint payloads. +- Build/plugin metadata should stay consistent across all federation JSON endpoints. +- `pluginVersion` values are expected to stay SemVer-like across all federation JSON endpoints. +- Shared version metadata should remain mode-correct between browser and SSR endpoint payloads. +- JSON endpoint payloads should keep `remotes` empty and only include shared entries for React + ReactDOM. +- Shared entries are expected to stay singleton on all endpoint payloads. +- Stats endpoints should keep shared runtime flags (`shareScope: default`, `eager: false`, SSR-only `import: false`) while manifest endpoints omit those fields. +- Stats endpoints should keep shared usage arrays (`usedIn`, `usedExports`) as empty arrays while manifest endpoints omit them. +- Endpoint shared JS asset semantics should remain mode-correct (`[]` for SSR shared entries, non-empty relative `static/js/*.js` for browser shared entries). +- Shared endpoint payloads should keep async JS asset arrays empty and CSS asset arrays empty. +- Expose ids/paths should remain stable across endpoint payloads for `message`, `routes`, and `server-data`. +- Stats endpoint expose `requires` arrays are expected to remain empty; manifest endpoint payloads should omit `requires`. +- Stats endpoint payloads should also keep stable expose `file` metadata (`src/message.tsx`, `src/routes.tsx`, `src/server-data.ts`). +- Endpoint expose sync JS asset lists should stay non-empty and use relative `static/js/*.js` paths. +- Endpoint expose async JS and CSS asset arrays should remain empty. + +This keeps React shared ownership on the host side and avoids remote shared +fallback chunk loading issues in SSR node runtime. + +## Run + +```sh +pnpm install +pnpm build +pnpm preview --port 3001 +``` diff --git a/examples/react/start-module-federation-rsbuild-remote/index.html b/examples/react/start-module-federation-rsbuild-remote/index.html new file mode 100644 index 00000000000..613abec7295 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-remote/index.html @@ -0,0 +1,12 @@ + + + + + + Module Federation Remote + + +
+ + + diff --git a/examples/react/start-module-federation-rsbuild-remote/package.json b/examples/react/start-module-federation-rsbuild-remote/package.json new file mode 100644 index 00000000000..410885bb141 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-remote/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-react-example-start-module-federation-rsbuild-remote", + "private": true, + "type": "module", + "scripts": { + "dev": "rsbuild dev --port 3001", + "build": "rsbuild build", + "preview": "rsbuild preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@module-federation/node": "^2.7.32", + "@module-federation/rsbuild-plugin": "https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd", + "@rsbuild/core": "^1.3.21", + "@rsbuild/plugin-react": "^1.1.0", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "typescript": "^5.7.2" + } +} diff --git a/examples/react/start-module-federation-rsbuild-remote/rsbuild.config.ts b/examples/react/start-module-federation-rsbuild-remote/rsbuild.config.ts new file mode 100644 index 00000000000..35d4754ac69 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-remote/rsbuild.config.ts @@ -0,0 +1,115 @@ +import { defineConfig } from '@rsbuild/core' +import { createRequire } from 'node:module' +import { pluginReact } from '@rsbuild/plugin-react' +import { pluginModuleFederation } from '@module-federation/rsbuild-plugin' + +const require = createRequire(import.meta.url) +const remotePort = Number(process.env.REMOTE_PORT || 3001) +const remoteOrigin = `http://localhost:${remotePort}` +const sharedForWeb = { + react: { + singleton: true, + requiredVersion: false, + }, + 'react-dom': { + singleton: true, + requiredVersion: false, + }, +} +const sharedForNode = { + react: { + // Keep host-owned React in node runtime. + // With remoteType=script + node runtime plugin, remote shared fallbacks + // can otherwise trigger SSR chunk loading incompatibilities. + import: false, + singleton: true, + requiredVersion: false, + }, + 'react-dom': { + // Keep host-owned ReactDOM in node runtime. + import: false, + singleton: true, + requiredVersion: false, + }, +} + +export default defineConfig({ + plugins: [ + pluginReact(), + pluginModuleFederation( + { + name: 'mf_remote', + filename: 'remoteEntry.js', + exposes: { + './message': './src/message.tsx', + './routes': './src/routes.tsx', + './server-data': './src/server-data.ts', + }, + experiments: { + asyncStartup: true, + }, + runtimePlugins: [require.resolve('@module-federation/node/runtimePlugin')], + shared: sharedForWeb, + }, + { + environment: 'web', + }, + ), + pluginModuleFederation( + { + name: 'mf_remote', + filename: 'remoteEntry.js', + dts: false, + library: { + type: 'commonjs-module', + }, + remoteType: 'script', + exposes: { + './message': './src/message.tsx', + './routes': './src/routes.tsx', + './server-data': './src/server-data.ts', + }, + experiments: { + asyncStartup: true, + }, + runtimePlugins: [require.resolve('@module-federation/node/runtimePlugin')], + shared: sharedForNode, + }, + { + target: 'node', + environment: 'ssr', + }, + ), + ], + environments: { + web: { + output: { + assetPrefix: `${remoteOrigin}/`, + }, + }, + ssr: { + source: { + entry: {}, + }, + output: { + assetPrefix: `${remoteOrigin}/ssr/`, + cleanDistPath: false, + distPath: { + root: 'ssr', + }, + }, + tools: { + rspack: { + target: 'async-node', + output: { + chunkFormat: 'commonjs', + chunkLoading: 'async-node', + library: { + type: 'commonjs-module', + }, + }, + }, + }, + }, + }, +}) diff --git a/examples/react/start-module-federation-rsbuild-remote/src/env.d.ts b/examples/react/start-module-federation-rsbuild-remote/src/env.d.ts new file mode 100644 index 00000000000..b0ac762b091 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-remote/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/start-module-federation-rsbuild-remote/src/index.tsx b/examples/react/start-module-federation-rsbuild-remote/src/index.tsx new file mode 100644 index 00000000000..0e778361332 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-remote/src/index.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' +import { createRoot } from 'react-dom/client' +import { FederatedMessage } from './message' + +function App() { + return ( +
+

Remote application

+ +
+ ) +} + +const container = document.getElementById('root') +if (container) { + const root = createRoot(container) + root.render( + + + , + ) +} diff --git a/examples/react/start-module-federation-rsbuild-remote/src/message.tsx b/examples/react/start-module-federation-rsbuild-remote/src/message.tsx new file mode 100644 index 00000000000..91acae29847 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-remote/src/message.tsx @@ -0,0 +1,11 @@ +import * as React from 'react' + +export function FederatedMessage() { + const [count, setCount] = React.useState(0) + + return ( + + ) +} diff --git a/examples/react/start-module-federation-rsbuild-remote/src/routes.tsx b/examples/react/start-module-federation-rsbuild-remote/src/routes.tsx new file mode 100644 index 00000000000..58ed909d664 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-remote/src/routes.tsx @@ -0,0 +1,25 @@ +import type { ComponentType } from 'react' +import { FederatedMessage } from './message' + +export type RemoteRouteRegistration = { + id: string + path: string + component: ComponentType +} + +function DynamicRemotePage() { + return ( +
+

Dynamic remote page from federation

+ +
+ ) +} + +export const remoteRouteRegistrations: Array = [ + { + id: 'dynamic-remote', + path: 'dynamic-remote', + component: DynamicRemotePage, + }, +] diff --git a/examples/react/start-module-federation-rsbuild-remote/src/server-data.ts b/examples/react/start-module-federation-rsbuild-remote/src/server-data.ts new file mode 100644 index 00000000000..6e95c99cd9f --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-remote/src/server-data.ts @@ -0,0 +1,6 @@ +export function getFederatedServerData(source: string) { + return { + source, + message: 'Federated server data from remote', + } +} diff --git a/examples/react/start-module-federation-rsbuild-remote/tsconfig.json b/examples/react/start-module-federation-rsbuild-remote/tsconfig.json new file mode 100644 index 00000000000..d95b47232e2 --- /dev/null +++ b/examples/react/start-module-federation-rsbuild-remote/tsconfig.json @@ -0,0 +1,18 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "ES2022", + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "noEmit": true + } +} diff --git a/package.json b/package.json index 7325283b5ea..5cf794e3680 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,8 @@ "@eslint-react/eslint-plugin": "^1.26.2", "@playwright/test": "^1.57.0", "@tanstack/config": "0.22.0", - "@tanstack/react-query": "^5.90.19", "@tanstack/query-core": "^5.90.19", + "@tanstack/react-query": "^5.90.19", "@types/node": "25.0.9", "@types/react": "^19.2.8", "@types/react-dom": "^19.2.3", diff --git a/packages/react-router/eslint.config.ts b/packages/react-router/eslint.config.ts index 5d879181f79..d5fcd3aec86 100644 --- a/packages/react-router/eslint.config.ts +++ b/packages/react-router/eslint.config.ts @@ -8,6 +8,12 @@ export default [ { files: ['src/**/*.{ts,tsx}', 'tests/**/*.{ts,tsx}'], }, + { + files: ['llms/rules/**/*.ts'], + rules: { + 'no-useless-escape': 'off', + }, + }, { plugins: { 'react-hooks': pluginReactHooks, diff --git a/packages/react-start/package.json b/packages/react-start/package.json index 24bc307f3fa..727792d4139 100644 --- a/packages/react-start/package.json +++ b/packages/react-start/package.json @@ -74,6 +74,12 @@ "default": "./dist/esm/plugin/vite.js" } }, + "./plugin/rsbuild": { + "import": { + "types": "./dist/esm/plugin/rsbuild.d.ts", + "default": "./dist/esm/plugin/rsbuild.js" + } + }, "./server-entry": { "import": { "types": "./dist/default-entry/esm/server.d.ts", @@ -101,8 +107,17 @@ "pathe": "^2.0.3" }, "peerDependencies": { + "@rsbuild/core": ">=1.0.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": ">=7.0.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "vite": { + "optional": true + } } } diff --git a/packages/react-start/src/plugin/rsbuild.ts b/packages/react-start/src/plugin/rsbuild.ts new file mode 100644 index 00000000000..f02e273765a --- /dev/null +++ b/packages/react-start/src/plugin/rsbuild.ts @@ -0,0 +1,35 @@ +import { fileURLToPath } from 'node:url' +import path from 'pathe' +import { TanStackStartRsbuildPluginCore } from '@tanstack/start-plugin-core/rsbuild' +import type { TanStackStartInputConfig } from '@tanstack/start-plugin-core' + +type RsbuildPlugin = { + name: string + setup: (api: any) => void +} + +const currentDir = path.dirname(fileURLToPath(import.meta.url)) +const defaultEntryDir = path.resolve( + currentDir, + '..', + '..', + 'plugin', + 'default-entry', +) +const defaultEntryPaths = { + client: path.resolve(defaultEntryDir, 'client.tsx'), + server: path.resolve(defaultEntryDir, 'server.ts'), + start: path.resolve(defaultEntryDir, 'start.ts'), +} + +export function tanstackStart( + options?: TanStackStartInputConfig, +): Array { + return TanStackStartRsbuildPluginCore( + { + framework: 'react', + defaultEntryPaths, + }, + options, + ) +} diff --git a/packages/react-start/vite.config.ts b/packages/react-start/vite.config.ts index d7ab699a07b..129669ced14 100644 --- a/packages/react-start/vite.config.ts +++ b/packages/react-start/vite.config.ts @@ -31,6 +31,7 @@ export default mergeConfig( './src/server-rpc.ts', './src/ssr-rpc.ts', './src/plugin/vite.ts', + './src/plugin/rsbuild.ts', ], externalDeps: [ '@tanstack/react-start-client', diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index d677f5530ea..6e6a8297d88 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -2048,7 +2048,7 @@ export class RouterCore< * Commit a previously built location to history (push/replace), optionally * using view transitions and scroll restoration options. */ - commitLocation: CommitLocationFn = async ({ + commitLocation: CommitLocationFn = ({ viewTransition, ignoreBlocker, ...next @@ -2379,7 +2379,7 @@ export class RouterCore< onReady: async () => { // Wrap batch in framework-specific transition wrapper (e.g., Solid's startTransition) this.startTransition(() => { - this.startViewTransition(async () => { + this.startViewTransition(() => { // this.viewTransitionPromise = createControlledPromise() // Commit the pending matches. If a previous match was @@ -2442,6 +2442,8 @@ export class RouterCore< ) }) }) + + return Promise.resolve() }) }) }, diff --git a/packages/router-plugin/src/rspack.ts b/packages/router-plugin/src/rspack.ts index 9eef9f2221c..ab31067d68c 100644 --- a/packages/router-plugin/src/rspack.ts +++ b/packages/router-plugin/src/rspack.ts @@ -4,6 +4,7 @@ import { configSchema } from './core/config' import { unpluginRouterCodeSplitterFactory } from './core/router-code-splitter-plugin' import { unpluginRouterGeneratorFactory } from './core/router-generator-plugin' import { unpluginRouterComposedFactory } from './core/router-composed-plugin' +import { unpluginRouteAutoImportFactory } from './core/route-autoimport-plugin' import type { CodeSplittingOptions, Config } from './core/config' /** @@ -40,6 +41,13 @@ const TanStackRouterCodeSplitterRspack = /* #__PURE__ */ createRspackPlugin( unpluginRouterCodeSplitterFactory, ) +const tanstackRouterGenerator = TanStackRouterGeneratorRspack +const tanstackRouterCodeSplitter = TanStackRouterCodeSplitterRspack + +const TanStackRouterAutoImportRspack = /* #__PURE__ */ createRspackPlugin( + unpluginRouteAutoImportFactory, +) + /** * @example * ```ts @@ -57,12 +65,17 @@ const TanStackRouterRspack = /* #__PURE__ */ createRspackPlugin( unpluginRouterComposedFactory, ) const tanstackRouter = TanStackRouterRspack +const tanstackRouterAutoImport = TanStackRouterAutoImportRspack export default TanStackRouterRspack export { configSchema, TanStackRouterRspack, TanStackRouterGeneratorRspack, TanStackRouterCodeSplitterRspack, + TanStackRouterAutoImportRspack, + tanstackRouterGenerator, + tanstackRouterCodeSplitter, + tanstackRouterAutoImport, tanstackRouter, } export type { Config, CodeSplittingOptions } diff --git a/packages/router-plugin/src/vite.ts b/packages/router-plugin/src/vite.ts index 75b66f31aa2..c7bd6a5dcea 100644 --- a/packages/router-plugin/src/vite.ts +++ b/packages/router-plugin/src/vite.ts @@ -34,6 +34,7 @@ const tanstackRouterGenerator = createVitePlugin(unpluginRouterGeneratorFactory) const tanStackRouterCodeSplitter = createVitePlugin( unpluginRouterCodeSplitterFactory, ) +const tanstackRouterCodeSplitter = tanStackRouterCodeSplitter /** * @example @@ -57,6 +58,7 @@ export { getConfig, tanstackRouterAutoImport, tanStackRouterCodeSplitter, + tanstackRouterCodeSplitter, tanstackRouterGenerator, TanStackRouterVite, tanstackRouter, diff --git a/packages/router-plugin/src/webpack.ts b/packages/router-plugin/src/webpack.ts index a718e6056af..d3b19d730eb 100644 --- a/packages/router-plugin/src/webpack.ts +++ b/packages/router-plugin/src/webpack.ts @@ -4,6 +4,7 @@ import { configSchema } from './core/config' import { unpluginRouterCodeSplitterFactory } from './core/router-code-splitter-plugin' import { unpluginRouterGeneratorFactory } from './core/router-generator-plugin' import { unpluginRouterComposedFactory } from './core/router-composed-plugin' +import { unpluginRouteAutoImportFactory } from './core/route-autoimport-plugin' import type { CodeSplittingOptions, Config } from './core/config' /** @@ -32,6 +33,10 @@ const TanStackRouterCodeSplitterWebpack = /* #__PURE__ */ createWebpackPlugin( unpluginRouterCodeSplitterFactory, ) +const TanStackRouterAutoImportWebpack = /* #__PURE__ */ createWebpackPlugin( + unpluginRouteAutoImportFactory, +) + /** * @example * ```ts @@ -45,6 +50,9 @@ const TanStackRouterWebpack = /* #__PURE__ */ createWebpackPlugin( unpluginRouterComposedFactory, ) +const tanstackRouterGenerator = TanStackRouterGeneratorWebpack +const tanstackRouterCodeSplitter = TanStackRouterCodeSplitterWebpack +const tanstackRouterAutoImport = TanStackRouterAutoImportWebpack const tanstackRouter = TanStackRouterWebpack export default TanStackRouterWebpack export { @@ -52,6 +60,10 @@ export { TanStackRouterWebpack, TanStackRouterGeneratorWebpack, TanStackRouterCodeSplitterWebpack, + TanStackRouterAutoImportWebpack, + tanstackRouterGenerator, + tanstackRouterCodeSplitter, + tanstackRouterAutoImport, tanstackRouter, } export type { Config, CodeSplittingOptions } diff --git a/packages/solid-start/package.json b/packages/solid-start/package.json index 375fa862804..8712b09c40f 100644 --- a/packages/solid-start/package.json +++ b/packages/solid-start/package.json @@ -74,6 +74,12 @@ "default": "./dist/esm/plugin/vite.js" } }, + "./plugin/rsbuild": { + "import": { + "types": "./dist/esm/plugin/rsbuild.d.ts", + "default": "./dist/esm/plugin/rsbuild.js" + } + }, "./server-entry": { "import": { "types": "./dist/default-entry/esm/server.d.ts", @@ -104,7 +110,13 @@ "vite": "^7.3.1" }, "peerDependencies": { + "@rsbuild/core": ">=1.0.0", "solid-js": ">=1.0.0", "vite": ">=7.0.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + } } } diff --git a/packages/solid-start/src/plugin/rsbuild.ts b/packages/solid-start/src/plugin/rsbuild.ts new file mode 100644 index 00000000000..34bbc6542d5 --- /dev/null +++ b/packages/solid-start/src/plugin/rsbuild.ts @@ -0,0 +1,35 @@ +import { fileURLToPath } from 'node:url' +import path from 'pathe' +import { TanStackStartRsbuildPluginCore } from '@tanstack/start-plugin-core/rsbuild' +import type { TanStackStartInputConfig } from '@tanstack/start-plugin-core' + +type RsbuildPlugin = { + name: string + setup: (api: any) => void +} + +const currentDir = path.dirname(fileURLToPath(import.meta.url)) +const defaultEntryDir = path.resolve( + currentDir, + '..', + '..', + 'plugin', + 'default-entry', +) +const defaultEntryPaths = { + client: path.resolve(defaultEntryDir, 'client.tsx'), + server: path.resolve(defaultEntryDir, 'server.ts'), + start: path.resolve(defaultEntryDir, 'start.ts'), +} + +export function tanstackStart( + options?: TanStackStartInputConfig, +): Array { + return TanStackStartRsbuildPluginCore( + { + framework: 'solid', + defaultEntryPaths, + }, + options, + ) +} diff --git a/packages/solid-start/vite.config.ts b/packages/solid-start/vite.config.ts index 4004316d919..519844c729d 100644 --- a/packages/solid-start/vite.config.ts +++ b/packages/solid-start/vite.config.ts @@ -31,6 +31,7 @@ export default mergeConfig( './src/server-rpc.ts', './src/server.tsx', './src/plugin/vite.ts', + './src/plugin/rsbuild.ts', ], externalDeps: [ '@tanstack/solid-start-client', diff --git a/packages/start-client-core/src/client-rpc/serverFnFetcher.ts b/packages/start-client-core/src/client-rpc/serverFnFetcher.ts index 7d543b1df07..8d005a7d92e 100644 --- a/packages/start-client-core/src/client-rpc/serverFnFetcher.ts +++ b/packages/start-client-core/src/client-rpc/serverFnFetcher.ts @@ -3,6 +3,7 @@ import { encode, isNotFound, parseRedirect, + redirect, } from '@tanstack/router-core' import { fromCrossJSON, toJSONAsync } from 'seroval' import invariant from 'tiny-invariant' @@ -17,6 +18,7 @@ import { import { createFrameDecoder } from './frame-decoder' import type { FunctionMiddlewareClientFnOptions } from '../createMiddleware' import type { Plugin as SerovalPlugin } from 'seroval' +import type { RedirectOptions } from '@tanstack/router-core' let serovalPlugins: Array> | null = null @@ -33,6 +35,51 @@ function hasOwnProperties(obj: object): boolean { } return false } + +function isResponseLike(value: unknown): value is Response { + if (value instanceof Response) { + return true + } + if (value === null || typeof value !== 'object') { + return false + } + if (!('status' in value) || !('headers' in value)) { + return false + } + const candidate = value as { + headers?: { get?: unknown; set?: unknown } + json?: unknown + text?: unknown + body?: unknown + ok?: unknown + } + const headers = candidate.headers + return ( + !!headers && + typeof headers.get === 'function' && + typeof headers.set === 'function' && + typeof candidate.json === 'function' && + typeof candidate.text === 'function' && + 'body' in candidate && + typeof candidate.ok === 'boolean' + ) +} + +function parseRedirectFallback(payload: unknown) { + if (!payload || typeof payload !== 'object') { + return undefined + } + if (!('isSerializedRedirect' in payload)) { + return undefined + } + if ( + (payload as { isSerializedRedirect?: boolean }).isSerializedRedirect !== + true + ) { + return undefined + } + return redirect(payload as unknown as RedirectOptions) +} // caller => // serverFnFetcher => // client => @@ -172,7 +219,7 @@ async function getResponse(fn: () => Promise) { try { response = await fn() // client => server => fn => server => client } catch (error) { - if (error instanceof Response) { + if (isResponseLike(error)) { response = error } else { console.log(error) @@ -240,6 +287,14 @@ async function getResponse(fn: () => Promise) { } invariant(result, 'expected result to be resolved') + const serializedRedirect = + parseRedirect(result) ?? parseRedirectFallback(result) + if (serializedRedirect) { + throw serializedRedirect + } + if (isNotFound(result)) { + throw result + } if (result instanceof Error) { throw result } @@ -251,9 +306,10 @@ async function getResponse(fn: () => Promise) { // if it's JSON if (contentType.includes('application/json')) { const jsonPayload = await response.json() - const redirect = parseRedirect(jsonPayload) - if (redirect) { - throw redirect + const redirectResult = + parseRedirect(jsonPayload) ?? parseRedirectFallback(jsonPayload) + if (redirectResult) { + throw redirectResult } if (isNotFound(jsonPayload)) { throw jsonPayload diff --git a/packages/start-plugin-core/package.json b/packages/start-plugin-core/package.json index fa51a8b842e..5207a058c19 100644 --- a/packages/start-plugin-core/package.json +++ b/packages/start-plugin-core/package.json @@ -50,6 +50,12 @@ "default": "./dist/esm/index.js" } }, + "./rsbuild": { + "import": { + "types": "./dist/esm/rsbuild/index.d.ts", + "default": "./dist/esm/rsbuild/index.js" + } + }, "./package.json": "./package.json" }, "sideEffects": false, @@ -77,6 +83,7 @@ "srvx": "^0.11.2", "tinyglobby": "^0.2.15", "ufo": "^1.5.4", + "unplugin": "^2.3.11", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", "zod": "^3.24.2" @@ -87,6 +94,15 @@ "vite": "^7.3.1" }, "peerDependencies": { + "@rsbuild/core": ">=1.0.0", "vite": ">=7.0.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "vite": { + "optional": true + } } } diff --git a/packages/start-plugin-core/src/dev-server-plugin/plugin.ts b/packages/start-plugin-core/src/dev-server-plugin/plugin.ts index 533e4f634a2..9a72472da79 100644 --- a/packages/start-plugin-core/src/dev-server-plugin/plugin.ts +++ b/packages/start-plugin-core/src/dev-server-plugin/plugin.ts @@ -181,7 +181,7 @@ export function devServerPlugin({ console.error(e) try { viteDevServer.ssrFixStacktrace(e as Error) - } catch (_e) {} + } catch {} if ( webReq.headers.get('content-type')?.includes('application/json') diff --git a/packages/start-plugin-core/src/index.ts b/packages/start-plugin-core/src/index.ts index df946192ae1..777c7efeb11 100644 --- a/packages/start-plugin-core/src/index.ts +++ b/packages/start-plugin-core/src/index.ts @@ -1,6 +1,7 @@ export type { TanStackStartInputConfig } from './schema' export { TanStackStartVitePluginCore } from './plugin' +export { TanStackStartRsbuildPluginCore } from './rsbuild/plugin' export { resolveViteId } from './utils' export { VITE_ENVIRONMENT_NAMES } from './constants' diff --git a/packages/start-plugin-core/src/post-server-build.ts b/packages/start-plugin-core/src/post-server-build.ts index 478b2ef2104..036465b50af 100644 --- a/packages/start-plugin-core/src/post-server-build.ts +++ b/packages/start-plugin-core/src/post-server-build.ts @@ -43,7 +43,7 @@ export async function postServerBuild({ prerender: { ...startConfig.spa.prerender, headers: { - ...startConfig.spa.prerender.headers, + ...(startConfig.spa.prerender.headers ?? {}), [HEADERS.TSS_SHELL]: 'true', }, }, diff --git a/packages/start-plugin-core/src/rsbuild/index.ts b/packages/start-plugin-core/src/rsbuild/index.ts new file mode 100644 index 00000000000..0bf18566976 --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/index.ts @@ -0,0 +1,3 @@ +export { TanStackStartRsbuildPluginCore } from './plugin' +export { VITE_ENVIRONMENT_NAMES } from '../constants' +export { resolveViteId } from '../utils' diff --git a/packages/start-plugin-core/src/rsbuild/injected-head-scripts-plugin.ts b/packages/start-plugin-core/src/rsbuild/injected-head-scripts-plugin.ts new file mode 100644 index 00000000000..777f171edb1 --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/injected-head-scripts-plugin.ts @@ -0,0 +1,19 @@ +import { createRspackPlugin } from 'unplugin' +import { VIRTUAL_MODULES } from '@tanstack/start-server-core' + +export function createInjectedHeadScriptsPlugin() { + const pluginFactory = createRspackPlugin(() => ({ + name: 'tanstack-start:injected-head-scripts', + resolveId(id) { + if (id === VIRTUAL_MODULES.injectedHeadScripts) { + return id + } + return null + }, + load(id) { + if (id !== VIRTUAL_MODULES.injectedHeadScripts) return null + return `export const injectedHeadScripts = undefined` + }, + })) + return pluginFactory() +} diff --git a/packages/start-plugin-core/src/rsbuild/plugin.ts b/packages/start-plugin-core/src/rsbuild/plugin.ts new file mode 100644 index 00000000000..8bbbb80d2e3 --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/plugin.ts @@ -0,0 +1,781 @@ +import fs from 'node:fs' +import { pathToFileURL } from 'node:url' + +import { joinPaths } from '@tanstack/router-core' +import { NodeRequest, sendNodeResponse } from 'srvx/node' +import path from 'pathe' +import { joinURL } from 'ufo' +import { ENTRY_POINTS, VITE_ENVIRONMENT_NAMES } from '../constants' +import { resolveEntry } from '../resolve-entries' +import { parseStartConfig } from '../schema' +import { createRouteTreeModuleDeclaration } from '../start-router-plugin/route-tree-module-declaration' +import { createInjectedHeadScriptsPlugin } from './injected-head-scripts-plugin' +import { resolveLoaderPath } from './resolve-loader-path' +import { + SERVER_FN_MANIFEST_TEMP_FILE, + createServerFnManifestRspackPlugin, + createServerFnResolverPlugin, +} from './start-compiler-plugin' +import { + createStartManifestRspackPlugin, + createStartManifestVirtualModulePlugin, +} from './start-manifest-plugin' +import { postServerBuildRsbuild } from './post-server-build' +import { tanStackStartRouterRsbuild } from './start-router-plugin' +import type { TanStackStartInputConfig } from '../schema' +import type { + GetConfigFn, + ResolvedStartConfig, + TanStackStartVitePluginCoreOptions, +} from '../types' + +type RsbuildPlugin = { + name: string + setup: (api: any) => void +} + +const MODULE_FEDERATION_RSBUILD_PLUGIN_NAME = + 'rsbuild:module-federation-enhanced' + +function hasModuleFederationPlugin(config: any): boolean { + if (config?.moduleFederation?.options) { + return true + } + + if (!Array.isArray(config?.plugins)) { + return false + } + + return config.plugins.some((plugin: any) => { + return plugin?.name === MODULE_FEDERATION_RSBUILD_PLUGIN_NAME + }) +} + +function isFullUrl(str: string): boolean { + try { + new URL(str) + return true + } catch { + return false + } +} + +function defineReplaceEnv( + key: TKey, + value: TValue, +): { [P in `process.env.${TKey}` | `import.meta.env.${TKey}`]: TValue } { + return { + [`process.env.${key}`]: JSON.stringify(value), + [`import.meta.env.${key}`]: JSON.stringify(value), + } as { [P in `process.env.${TKey}` | `import.meta.env.${TKey}`]: TValue } +} + +function mergeRspackConfig(base: any, next: any) { + return { + ...base, + ...next, + plugins: [...(base?.plugins ?? []), ...(next?.plugins ?? [])], + module: { + ...base?.module, + ...next?.module, + rules: [...(base?.module?.rules ?? []), ...(next?.module?.rules ?? [])], + }, + resolve: { + ...base?.resolve, + ...next?.resolve, + alias: { + ...(base?.resolve?.alias ?? {}), + ...(next?.resolve?.alias ?? {}), + }, + }, + } +} + +function mergeEnvConfig(base: any, next: any) { + return { + ...base, + ...next, + source: { + ...base?.source, + ...next?.source, + alias: { + ...(base?.source?.alias ?? {}), + ...(next?.source?.alias ?? {}), + }, + define: { + ...(base?.source?.define ?? {}), + ...(next?.source?.define ?? {}), + }, + }, + output: { + ...base?.output, + ...next?.output, + distPath: { + ...(base?.output?.distPath ?? {}), + ...(next?.output?.distPath ?? {}), + }, + }, + tools: { + ...base?.tools, + ...next?.tools, + rspack: mergeRspackConfig(base?.tools?.rspack, next?.tools?.rspack), + }, + } +} + +function getOutputDirectory( + root: string, + config: any, + environmentName: string, + directoryName: string, +) { + const envDistPath = + config.environments?.[environmentName]?.output?.distPath?.root + if (envDistPath) { + return path.resolve(root, envDistPath) + } + const rootDistPath = config.output?.distPath?.root ?? 'dist' + return path.resolve(root, rootDistPath, directoryName) +} + +function toPluginArray(plugin: any) { + if (!plugin) return [] + return Array.isArray(plugin) ? plugin : [plugin] +} + +export function TanStackStartRsbuildPluginCore( + corePluginOpts: TanStackStartVitePluginCoreOptions, + startPluginOpts: TanStackStartInputConfig, +): Array { + const serverFnProviderEnv = + corePluginOpts.serverFn?.providerEnv || VITE_ENVIRONMENT_NAMES.server + const ssrIsProvider = serverFnProviderEnv === VITE_ENVIRONMENT_NAMES.server + + const resolvedStartConfig: ResolvedStartConfig = { + root: '', + startFilePath: undefined, + routerFilePath: '', + srcDirectory: '', + viteAppBase: '', + serverFnProviderEnv, + } + + let startConfig: ReturnType | null = null + const getConfig: GetConfigFn = () => { + if (!resolvedStartConfig.root) { + throw new Error(`Cannot get config before root is resolved`) + } + if (!startConfig) { + startConfig = parseStartConfig( + startPluginOpts, + corePluginOpts, + resolvedStartConfig.root, + ) + } + return { startConfig, resolvedStartConfig, corePluginOpts } + } + + let resolvedServerEntryPath: string | undefined + let resolvedServerOutputDir: string | undefined + let resolvedClientOutputDir: string | undefined + let routeTreeModuleDeclaration: string | null = null + let routeTreeGeneratedPath: string | null = null + + return [ + { + name: 'tanstack-start-core:rsbuild-config', + setup(api) { + api.modifyRsbuildConfig((config: any) => { + const root = config.root || process.cwd() + resolvedStartConfig.root = root + + const { startConfig } = getConfig() + const assetPrefix = config.output?.assetPrefix ?? '/' + resolvedStartConfig.viteAppBase = assetPrefix + if (!isFullUrl(resolvedStartConfig.viteAppBase)) { + resolvedStartConfig.viteAppBase = joinPaths([ + '/', + resolvedStartConfig.viteAppBase, + '/', + ]) + } + + if (startConfig.router.basepath === undefined) { + if (!isFullUrl(resolvedStartConfig.viteAppBase)) { + startConfig.router.basepath = + resolvedStartConfig.viteAppBase.replace(/^\/|\/$/g, '') + } else { + startConfig.router.basepath = '/' + } + } + + const TSS_SERVER_FN_BASE = joinPaths([ + '/', + startConfig.router.basepath, + startConfig.serverFns.base, + '/', + ]) + + const resolvedSrcDirectory = path.join(root, startConfig.srcDirectory) + resolvedStartConfig.srcDirectory = resolvedSrcDirectory + + const startFilePath = resolveEntry({ + type: 'start entry', + configuredEntry: startConfig.start.entry, + defaultEntry: 'start', + resolvedSrcDirectory, + required: false, + }) + resolvedStartConfig.startFilePath = startFilePath + + const routerFilePath = resolveEntry({ + type: 'router entry', + configuredEntry: startConfig.router.entry, + defaultEntry: 'router', + resolvedSrcDirectory, + required: true, + }) + resolvedStartConfig.routerFilePath = routerFilePath + + const clientEntryPath = resolveEntry({ + type: 'client entry', + configuredEntry: startConfig.client.entry, + defaultEntry: 'client', + resolvedSrcDirectory, + required: false, + }) + + const serverEntryPath = resolveEntry({ + type: 'server entry', + configuredEntry: startConfig.server.entry, + defaultEntry: 'server', + resolvedSrcDirectory, + required: false, + }) + resolvedServerEntryPath = + serverEntryPath ?? corePluginOpts.defaultEntryPaths.server + + const entryAliasConfiguration: Record< + (typeof ENTRY_POINTS)[keyof typeof ENTRY_POINTS], + string + > = { + [ENTRY_POINTS.client]: + clientEntryPath ?? corePluginOpts.defaultEntryPaths.client, + [ENTRY_POINTS.server]: + serverEntryPath ?? corePluginOpts.defaultEntryPaths.server, + [ENTRY_POINTS.start]: + startFilePath ?? corePluginOpts.defaultEntryPaths.start, + [ENTRY_POINTS.router]: routerFilePath, + } + const resolvedClientEntry = + entryAliasConfiguration[ENTRY_POINTS.client] + const resolvedServerEntry = + entryAliasConfiguration[ENTRY_POINTS.server] + + const clientOutputDir = getOutputDirectory( + root, + config, + VITE_ENVIRONMENT_NAMES.client, + 'client', + ) + resolvedClientOutputDir = clientOutputDir + const serverOutputDir = getOutputDirectory( + root, + config, + VITE_ENVIRONMENT_NAMES.server, + 'server', + ) + resolvedServerOutputDir = serverOutputDir + const serverFnManifestTempPath = path.join( + serverOutputDir, + SERVER_FN_MANIFEST_TEMP_FILE, + ) + const moduleFederationEnabled = hasModuleFederationPlugin(config) + + const isDev = api.context?.command === 'serve' + const isBuild = api.context?.command === 'build' + const defineViteEnv = (key: string, fallback = '') => { + const value = process.env[key] ?? fallback + return defineReplaceEnv(key, value) + } + const defineValues = { + ...defineReplaceEnv('TSS_SERVER_FN_BASE', TSS_SERVER_FN_BASE), + ...defineReplaceEnv('TSS_CLIENT_OUTPUT_DIR', clientOutputDir), + ...defineReplaceEnv( + 'TSS_ROUTER_BASEPATH', + startConfig.router.basepath, + ), + ...defineReplaceEnv('TSS_BUNDLER', 'rsbuild'), + ...defineReplaceEnv('TSS_DEV_SERVER', isDev ? 'true' : 'false'), + ...(isDev + ? defineReplaceEnv( + 'TSS_SHELL', + startConfig.spa?.enabled ? 'true' : 'false', + ) + : {}), + ...defineViteEnv('VITE_NODE_ENV', 'production'), + ...defineViteEnv('VITE_EXTERNAL_PORT', ''), + ...(isBuild && startConfig.server.build.staticNodeEnv + ? { + 'process.env.NODE_ENV': JSON.stringify( + process.env.NODE_ENV ?? 'production', + ), + } + : {}), + } + + const routerPlugins = tanStackStartRouterRsbuild( + startPluginOpts, + getConfig, + corePluginOpts, + ) + const clientRouterConfig = { + ...startConfig.router, + routeTreeFileHeader: [], + routeTreeFileFooter: [], + plugins: [], + } + const generatedRouteTreePath = + routerPlugins.getGeneratedRouteTreePath() + const routeTreeModuleDeclarationValue = + createRouteTreeModuleDeclaration({ + generatedRouteTreePath, + routerFilePath: resolvedStartConfig.routerFilePath, + startFilePath: resolvedStartConfig.startFilePath, + framework: corePluginOpts.framework, + }) + routeTreeModuleDeclaration = routeTreeModuleDeclarationValue + routeTreeGeneratedPath = generatedRouteTreePath + const registerDeclaration = `declare module '@tanstack/${corePluginOpts.framework}-start'` + if (fs.existsSync(generatedRouteTreePath)) { + const existingTree = fs.readFileSync( + generatedRouteTreePath, + 'utf-8', + ) + if (!existingTree.includes(registerDeclaration)) { + const staleRouteTreePath = `${generatedRouteTreePath}.stale` + try { + fs.renameSync(generatedRouteTreePath, staleRouteTreePath) + fs.rmSync(staleRouteTreePath) + } catch (error: any) { + // Ignore transient concurrent-generation races and continue. + if (!['ENOENT', 'EBUSY'].includes(error?.code)) { + throw error + } + } + } + } + + const startCompilerLoaderPath = resolveLoaderPath( + './start-compiler-loader', + ) + const startStorageContextStubPath = resolveLoaderPath( + './start-storage-context-stub', + ) + const clientAliasOverrides = { + '@tanstack/start-storage-context': startStorageContextStubPath, + } + + const startClientCoreDistPattern = + /[\\/]start-client-core[\\/]dist[\\/]esm[\\/]/ + const loaderIncludePaths: Array = [ + resolvedStartConfig.srcDirectory, + ] + loaderIncludePaths.push(startClientCoreDistPattern) + + const loaderRule = ( + env: 'client' | 'server', + envName: string, + manifestPath?: string, + ) => ({ + test: /\.[cm]?[jt]sx?$/, + include: loaderIncludePaths, + enforce: 'pre', + use: [ + { + loader: startCompilerLoaderPath, + options: { + env, + envName, + root, + framework: corePluginOpts.framework, + providerEnvName: serverFnProviderEnv, + generateFunctionId: + startPluginOpts?.serverFns?.generateFunctionId, + manifestPath, + }, + }, + ], + }) + + const autoImportPlugins = toPluginArray(routerPlugins.autoImport) + + const clientEnvConfig = { + source: { + entry: { index: resolvedClientEntry }, + alias: { + ...entryAliasConfiguration, + ...clientAliasOverrides, + }, + define: defineValues, + }, + output: { + target: 'web', + distPath: { + root: path.relative(root, clientOutputDir), + }, + }, + tools: { + rspack: { + plugins: [ + routerPlugins.generatorPlugin, + routerPlugins.clientCodeSplitter, + ...autoImportPlugins, + createStartManifestRspackPlugin({ + basePath: resolvedStartConfig.viteAppBase, + clientOutputDir, + }), + ], + module: { + rules: [ + loaderRule('client', VITE_ENVIRONMENT_NAMES.client), + { + include: [routerPlugins.getGeneratedRouteTreePath()], + use: [ + { + loader: routerPlugins.routeTreeLoaderPath, + options: { + routerConfig: clientRouterConfig, + root, + }, + }, + ], + }, + ], + }, + resolve: { + alias: { + ...entryAliasConfiguration, + ...clientAliasOverrides, + }, + }, + }, + }, + } + + const serverEnvConfig = { + source: { + entry: { server: resolvedServerEntry }, + alias: entryAliasConfiguration, + define: defineValues, + }, + output: { + target: 'node', + distPath: { + root: path.relative(root, serverOutputDir), + }, + }, + tools: { + rspack: { + ...(moduleFederationEnabled + ? { + target: 'async-node', + experiments: { + outputModule: false, + }, + output: { + module: false, + chunkFormat: 'commonjs', + chunkLoading: 'async-node', + library: { + type: 'commonjs-module', + }, + }, + } + : { + experiments: { + outputModule: true, + }, + output: { + module: true, + chunkFormat: 'module', + chunkLoading: 'import', + library: { + type: 'module', + }, + }, + }), + plugins: [ + routerPlugins.generatorPlugin, + routerPlugins.serverCodeSplitter, + ...autoImportPlugins, + createServerFnResolverPlugin({ + environmentName: VITE_ENVIRONMENT_NAMES.server, + providerEnvName: serverFnProviderEnv, + serverOutputDir, + }), + createServerFnManifestRspackPlugin({ + serverOutputDir, + }), + createInjectedHeadScriptsPlugin(), + createStartManifestVirtualModulePlugin({ + clientOutputDir, + }), + ], + module: { + rules: [ + loaderRule( + 'server', + VITE_ENVIRONMENT_NAMES.server, + serverFnManifestTempPath, + ), + ], + }, + resolve: { + alias: entryAliasConfiguration, + }, + }, + }, + } + + const setupMiddlewares = ( + middlewares: Array, + context: { environments?: Record }, + ) => { + if (startConfig.vite?.installDevServerMiddleware === false) { + return + } + const serverEnv = + context.environments?.[VITE_ENVIRONMENT_NAMES.server] + middlewares.push(async (req: any, res: any, next: any) => { + if (res.headersSent || res.writableEnded) { + return next() + } + if (!serverEnv?.loadBundle) { + return next() + } + try { + const serverBundle = await serverEnv.loadBundle() + const serverBuild = serverBundle?.default ?? serverBundle + if (!serverBuild?.fetch) { + return next() + } + const requestWithBaseUrl = Object.create(req) + requestWithBaseUrl.url = joinURL( + resolvedStartConfig.viteAppBase, + req.url ?? '/', + ) + const webReq = new NodeRequest({ req: requestWithBaseUrl, res }) + const webRes = await serverBuild.fetch(webReq) + return sendNodeResponse(res, webRes) + } catch (error) { + return next(error) + } + }) + } + + const existingSetupMiddlewares = config.dev?.setupMiddlewares + const mergedSetupMiddlewares = ( + middlewares: Array, + context: { environments?: Record }, + ) => { + if (typeof existingSetupMiddlewares === 'function') { + existingSetupMiddlewares(middlewares, context) + } else if (Array.isArray(existingSetupMiddlewares)) { + existingSetupMiddlewares.forEach((fn: any) => { + fn(middlewares, context) + }) + } + setupMiddlewares(middlewares, context) + } + + const nextConfig = { + ...config, + environments: { + ...config.environments, + [VITE_ENVIRONMENT_NAMES.client]: mergeEnvConfig( + config.environments?.[VITE_ENVIRONMENT_NAMES.client], + clientEnvConfig, + ), + [VITE_ENVIRONMENT_NAMES.server]: mergeEnvConfig( + config.environments?.[VITE_ENVIRONMENT_NAMES.server], + serverEnvConfig, + ), + }, + dev: { + ...config.dev, + setupMiddlewares: mergedSetupMiddlewares, + }, + } + + if (!ssrIsProvider) { + const providerOutputDir = getOutputDirectory( + root, + config, + serverFnProviderEnv, + serverFnProviderEnv, + ) + const providerManifestTempPath = path.join( + providerOutputDir, + SERVER_FN_MANIFEST_TEMP_FILE, + ) + nextConfig.environments = { + ...nextConfig.environments, + [serverFnProviderEnv]: mergeEnvConfig( + config.environments?.[serverFnProviderEnv], + { + source: { + entry: { provider: resolvedServerEntry }, + alias: entryAliasConfiguration, + define: defineValues, + }, + output: { + target: 'node', + distPath: { + root: path.relative(root, providerOutputDir), + }, + }, + tools: { + rspack: { + ...(moduleFederationEnabled + ? { + target: 'async-node', + experiments: { + outputModule: false, + }, + output: { + module: false, + chunkFormat: 'commonjs', + chunkLoading: 'async-node', + library: { + type: 'commonjs-module', + }, + }, + } + : { + experiments: { + outputModule: true, + }, + output: { + module: true, + chunkFormat: 'module', + chunkLoading: 'import', + library: { + type: 'module', + }, + }, + }), + plugins: [ + createServerFnResolverPlugin({ + environmentName: serverFnProviderEnv, + providerEnvName: serverFnProviderEnv, + serverOutputDir: providerOutputDir, + }), + createServerFnManifestRspackPlugin({ + serverOutputDir: providerOutputDir, + }), + createInjectedHeadScriptsPlugin(), + ], + module: { + rules: [ + loaderRule( + 'server', + serverFnProviderEnv, + providerManifestTempPath, + ), + ], + }, + resolve: { + alias: entryAliasConfiguration, + }, + }, + }, + }, + ), + } + } + + return nextConfig + }) + + api.onAfterStartProdServer?.(({ server }: { server: any }) => { + const serverOutputDir = resolvedServerOutputDir + if (!server?.middlewares?.use || !serverOutputDir) { + return + } + + let serverBuild: any = null + server.middlewares.use(async (req: any, res: any, next: any) => { + try { + if (res.headersSent || res.writableEnded) { + return next() + } + if (!resolvedServerEntryPath) { + return next() + } + + if (!serverBuild) { + const outputCandidates = ['server.js', 'server.mjs', 'index.js'] + const outputFilename = + outputCandidates.find((candidate) => + fs.existsSync(path.join(serverOutputDir, candidate)), + ) ?? 'server.js' + const serverEntryPath = path.join( + serverOutputDir, + outputFilename, + ) + const imported = await import( + pathToFileURL(serverEntryPath).toString() + ) + serverBuild = imported.default ?? imported + } + + if (!serverBuild?.fetch) { + return next() + } + const requestWithBaseUrl = Object.create(req) + requestWithBaseUrl.url = joinURL( + resolvedStartConfig.viteAppBase, + req.url ?? '/', + ) + + const webReq = new NodeRequest({ req: requestWithBaseUrl, res }) + const webRes: Response = await serverBuild.fetch(webReq) + return sendNodeResponse(res, webRes) + } catch (error) { + next(error) + } + }) + }) + + api.onAfterBuild?.(async () => { + const { startConfig } = getConfig() + const clientOutputDir = resolvedClientOutputDir + const serverOutputDir = resolvedServerOutputDir + if (!clientOutputDir || !serverOutputDir) { + return + } + await postServerBuildRsbuild({ + startConfig, + clientOutputDir, + serverOutputDir, + }) + if (routeTreeGeneratedPath && routeTreeModuleDeclaration) { + if (fs.existsSync(routeTreeGeneratedPath)) { + const existingTree = fs.readFileSync( + routeTreeGeneratedPath, + 'utf-8', + ) + if (!existingTree.includes(routeTreeModuleDeclaration)) { + fs.appendFileSync( + routeTreeGeneratedPath, + `\n\n${routeTreeModuleDeclaration}\n`, + ) + } + } + } + }) + }, + }, + ] +} diff --git a/packages/start-plugin-core/src/rsbuild/post-server-build.ts b/packages/start-plugin-core/src/rsbuild/post-server-build.ts new file mode 100644 index 00000000000..c8ccbbd0ac2 --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/post-server-build.ts @@ -0,0 +1,68 @@ +import { HEADERS } from '@tanstack/start-server-core' +import path from 'pathe' +import { buildSitemap } from '../build-sitemap' +import { prerender } from './prerender' +import type { TanStackStartOutputConfig } from '../schema' + +export async function postServerBuildRsbuild({ + startConfig, + clientOutputDir, + serverOutputDir, +}: { + startConfig: TanStackStartOutputConfig + clientOutputDir: string + serverOutputDir: string +}) { + if (startConfig.prerender?.enabled !== false) { + startConfig.prerender = { + ...startConfig.prerender, + enabled: + startConfig.prerender?.enabled ?? + startConfig.pages.some((d) => + typeof d === 'string' ? false : !!d.prerender?.enabled, + ), + } + } + + if (startConfig.spa?.enabled) { + startConfig.prerender = { + ...startConfig.prerender, + enabled: true, + } + + const maskUrl = new URL(startConfig.spa.maskPath, 'http://localhost') + if (maskUrl.origin !== 'http://localhost') { + throw new Error('spa.maskPath must be a path (no protocol/host)') + } + + startConfig.pages.push({ + path: maskUrl.toString().replace('http://localhost', ''), + prerender: { + ...startConfig.spa.prerender, + headers: { + ...(startConfig.spa.prerender.headers ?? {}), + [HEADERS.TSS_SHELL]: 'true', + }, + }, + sitemap: { + exclude: true, + }, + }) + } + + if (startConfig.prerender.enabled) { + const serverEntryPath = path.join(serverOutputDir, 'server.js') + await prerender({ + startConfig, + clientOutputDir, + serverEntryPath, + }) + } + + if (startConfig.sitemap?.enabled) { + buildSitemap({ + startConfig, + publicDir: clientOutputDir, + }) + } +} diff --git a/packages/start-plugin-core/src/rsbuild/prerender.ts b/packages/start-plugin-core/src/rsbuild/prerender.ts new file mode 100644 index 00000000000..5b7ddac41a1 --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/prerender.ts @@ -0,0 +1,295 @@ +import { createRequire } from 'node:module' +import { pathToFileURL } from 'node:url' +import { promises as fsp } from 'node:fs' +import os from 'node:os' + +import path from 'pathe' +import { joinURL, withBase, withTrailingSlash, withoutBase } from 'ufo' +import { Queue } from '../queue' +import { createLogger } from '../utils' +import type { Page, TanStackStartOutputConfig } from '../schema' + +export async function prerender({ + startConfig, + clientOutputDir, + serverEntryPath, +}: { + startConfig: TanStackStartOutputConfig + clientOutputDir: string + serverEntryPath: string +}) { + const logger = createLogger('prerender') + const require = createRequire(import.meta.url) + logger.info('Prerendering pages...') + + if (startConfig.prerender?.enabled) { + let pages = startConfig.pages.length ? startConfig.pages : [{ path: '/' }] + + if (startConfig.prerender.autoStaticPathsDiscovery ?? true) { + const pagesMap = new Map(pages.map((item) => [item.path, item])) + const discoveredPages = globalThis.TSS_PRERENDABLE_PATHS || [] + + for (const page of discoveredPages) { + if (!pagesMap.has(page.path)) { + pagesMap.set(page.path, page) + } + } + + pages = Array.from(pagesMap.values()) + } + + startConfig.pages = pages + } + + const routerBasePath = joinURL('/', startConfig.router.basepath ?? '') + const routerBaseUrl = new URL(routerBasePath, 'http://localhost') + + startConfig.pages = validateAndNormalizePrerenderPages( + startConfig.pages, + routerBaseUrl, + ) + + const previousPrerenderingEnv = process.env.TSS_PRERENDERING + process.env.TSS_PRERENDERING = 'true' + + let serverBuild: any + try { + const cjsServerEntryPath = `${serverEntryPath}.cjs` + await fsp.copyFile(serverEntryPath, cjsServerEntryPath) + serverBuild = require(cjsServerEntryPath) + if (serverBuild && typeof serverBuild.then === 'function') { + serverBuild = await serverBuild + } + await fsp.unlink(cjsServerEntryPath).catch(() => {}) + } catch { + serverBuild = await import(pathToFileURL(serverEntryPath).toString()) + } + const fetchHandler = serverBuild.default?.fetch ?? serverBuild.fetch + if (!fetchHandler) { + throw new Error('Server build does not export a fetch handler') + } + + const baseUrl = new URL('http://localhost') + + const isRedirectResponse = (res: Response) => { + return res.status >= 300 && res.status < 400 && res.headers.get('location') + } + + async function localFetch( + path: string, + options?: RequestInit, + maxRedirects: number = 5, + ): Promise { + const url = new URL(path, baseUrl) + const request = new Request(url, options) + const response = await fetchHandler(request) + + if (isRedirectResponse(response) && maxRedirects > 0) { + const location = response.headers.get('location')! + if (location.startsWith('http://localhost') || location.startsWith('/')) { + const newUrl = location.replace('http://localhost', '') + return localFetch(newUrl, options, maxRedirects - 1) + } else { + logger.warn(`Skipping redirect to external location: ${location}`) + } + } + + return response + } + + try { + const pages = await prerenderPages({ outputDir: clientOutputDir }) + + logger.info(`Prerendered ${pages.length} pages:`) + pages.forEach((page) => { + logger.info(`- ${page}`) + }) + } catch (error) { + logger.error(error) + throw error + } finally { + if (previousPrerenderingEnv === undefined) { + delete process.env.TSS_PRERENDERING + } else { + process.env.TSS_PRERENDERING = previousPrerenderingEnv + } + } + + function extractLinks(html: string): Array { + const linkRegex = /]+href=["']([^"']+)["'][^>]*>/g + const links: Array = [] + let match + + while ((match = linkRegex.exec(html)) !== null) { + const href = match[1] + if (href && (href.startsWith('/') || href.startsWith('./'))) { + links.push(href) + } + } + + return links + } + + async function prerenderPages({ outputDir }: { outputDir: string }) { + const seen = new Set() + const prerendered = new Set() + const retriesByPath = new Map() + const concurrency = startConfig.prerender?.concurrency ?? os.cpus().length + logger.info(`Concurrency: ${concurrency}`) + const queue = new Queue({ concurrency }) + startConfig.pages.forEach((page) => { + addCrawlPageTask(page) + }) + + await queue.start() + + return Array.from(prerendered) + + function addCrawlPageTask(page: Page) { + if (seen.has(page.path)) return + + seen.add(page.path) + + if (page.fromCrawl) { + startConfig.pages.push(page) + } + + if (!(page.prerender?.enabled ?? true)) return + + if (startConfig.prerender?.filter && !startConfig.prerender.filter(page)) + return + + const prerenderOptions = { + ...startConfig.prerender, + ...page.prerender, + } + + queue.add(async () => { + logger.info(`Crawling: ${page.path}`) + const retries = retriesByPath.get(page.path) || 0 + try { + const res = await localFetch( + withTrailingSlash(withBase(page.path, routerBasePath)), + { + headers: { + ...(prerenderOptions.headers ?? {}), + }, + }, + prerenderOptions.maxRedirects, + ) + + if (!res.ok) { + if (isRedirectResponse(res)) { + logger.warn(`Max redirects reached for ${page.path}`) + } + throw new Error(`Failed to fetch ${page.path}: ${res.statusText}`, { + cause: res, + }) + } + + const cleanPagePath = ( + prerenderOptions.outputPath || page.path + ).split(/[?#]/)[0]! + + const contentType = res.headers.get('content-type') || '' + const isImplicitHTML = + !cleanPagePath.endsWith('.html') && contentType.includes('html') + + const routeWithIndex = cleanPagePath.endsWith('/') + ? cleanPagePath + 'index' + : cleanPagePath + + const spaPrerender = startConfig.spa?.prerender + const isSpaShell = + !!spaPrerender && spaPrerender.outputPath === cleanPagePath + + let htmlPath: string + if (isSpaShell) { + htmlPath = cleanPagePath + '.html' + } else { + if ( + cleanPagePath.endsWith('/') || + (prerenderOptions.autoSubfolderIndex ?? true) + ) { + htmlPath = joinURL(cleanPagePath, 'index.html') + } else { + htmlPath = cleanPagePath + '.html' + } + } + + const filename = withoutBase( + isImplicitHTML ? htmlPath : routeWithIndex, + routerBasePath, + ) + + const html = await res.text() + + const filepath = path.join(outputDir, filename) + + await fsp.mkdir(path.dirname(filepath), { + recursive: true, + }) + + await fsp.writeFile(filepath, html) + + prerendered.add(page.path) + + const newPage = await prerenderOptions.onSuccess?.({ page, html }) + + if (newPage) { + Object.assign(page, newPage) + } + + if (prerenderOptions.crawlLinks ?? true) { + const links = extractLinks(html) + for (const link of links) { + addCrawlPageTask({ path: link, fromCrawl: true }) + } + } + } catch (error) { + if (retries < (prerenderOptions.retryCount ?? 0)) { + const resolvedDelay = prerenderOptions.retryDelay ?? 500 + logger.warn( + `Encountered error, retrying: ${page.path} in ${resolvedDelay}ms`, + ) + await new Promise((resolve) => setTimeout(resolve, resolvedDelay)) + retriesByPath.set(page.path, retries + 1) + seen.delete(page.path) + addCrawlPageTask(page) + } else { + if (prerenderOptions.failOnError ?? true) { + throw error + } + } + } + }) + } + } +} + +function validateAndNormalizePrerenderPages( + pages: Array, + routerBaseUrl: URL, +): Array { + return pages.map((page) => { + let url: URL + try { + url = new URL(page.path, routerBaseUrl) + } catch (err) { + throw new Error(`prerender page path must be relative: ${page.path}`, { + cause: err, + }) + } + + if (url.origin !== 'http://localhost') { + throw new Error(`prerender page path must be relative: ${page.path}`) + } + + const decodedPathname = decodeURIComponent(url.pathname) + + return { + ...page, + path: decodedPathname + url.search + url.hash, + } + }) +} diff --git a/packages/start-plugin-core/src/rsbuild/resolve-loader-path.ts b/packages/start-plugin-core/src/rsbuild/resolve-loader-path.ts new file mode 100644 index 00000000000..097150c687a --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/resolve-loader-path.ts @@ -0,0 +1,18 @@ +import { fileURLToPath } from 'node:url' +import fs from 'node:fs' +import path from 'pathe' + +/** + * Resolve a local loader path to emitted JS when present, otherwise TS source. + */ +export function resolveLoaderPath(relativePath: string): string { + const currentDir = path.dirname(fileURLToPath(import.meta.url)) + const basePath = path.resolve(currentDir, relativePath) + const jsPath = `${basePath}.js` + const tsPath = `${basePath}.ts` + + if (fs.existsSync(jsPath)) return jsPath + if (fs.existsSync(tsPath)) return tsPath + + return jsPath +} diff --git a/packages/start-plugin-core/src/rsbuild/route-tree-loader.ts b/packages/start-plugin-core/src/rsbuild/route-tree-loader.ts new file mode 100644 index 00000000000..36ad41d2682 --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/route-tree-loader.ts @@ -0,0 +1,15 @@ +import { getClientRouteTreeContent } from './route-tree-state' + +export default function routeTreeLoader(this: any) { + const callback = this.async() + const options = this.getOptions() as { + routerConfig?: any + root?: string + } + getClientRouteTreeContent({ + routerConfig: options.routerConfig, + root: options.root, + }) + .then((code) => callback(null, code)) + .catch((error) => callback(error)) +} diff --git a/packages/start-plugin-core/src/rsbuild/route-tree-state.ts b/packages/start-plugin-core/src/rsbuild/route-tree-state.ts new file mode 100644 index 00000000000..35756a64bdd --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/route-tree-state.ts @@ -0,0 +1,48 @@ +import { Generator } from '@tanstack/router-generator' +import { pruneServerOnlySubtrees } from '../start-router-plugin/pruneServerOnlySubtrees' +import type { Config } from '@tanstack/router-generator' + +let generatorInstance: Generator | null = null + +export function setGeneratorInstance(generator: Generator) { + generatorInstance = generator +} + +export async function getClientRouteTreeContent(options?: { + routerConfig?: Config + root?: string +}) { + let generator = generatorInstance + if (!generator) { + if (!options?.routerConfig || !options.root) { + throw new Error( + 'Generator instance not initialized for route tree loader', + ) + } + generator = new Generator({ + config: options.routerConfig, + root: options.root, + }) + await generator.run() + } + const crawlingResult = await generator.getCrawlingResult() + if (!crawlingResult) { + throw new Error('Crawling result not available') + } + const prunedAcc = pruneServerOnlySubtrees(crawlingResult) + const acc = { + ...crawlingResult.acc, + ...prunedAcc, + } + const buildResult = generator.buildRouteTree({ + ...crawlingResult, + acc, + config: { + disableTypes: true, + enableRouteTreeFormatting: false, + routeTreeFileHeader: [], + routeTreeFileFooter: [], + }, + }) + return buildResult.routeTreeContent +} diff --git a/packages/start-plugin-core/src/rsbuild/start-compiler-loader.ts b/packages/start-plugin-core/src/rsbuild/start-compiler-loader.ts new file mode 100644 index 00000000000..0479e1b0e15 --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/start-compiler-loader.ts @@ -0,0 +1,264 @@ +import fs, { promises as fsp } from 'node:fs' +import { createRequire } from 'node:module' +import path from 'node:path' +import { + KindDetectionPatterns, + LookupKindsPerEnv, + StartCompiler, + detectKindsInCode, +} from '../start-compiler-plugin/compiler' +import { cleanId } from '../start-compiler-plugin/utils' +import type { LookupConfig } from '../start-compiler-plugin/compiler' +import type { CompileStartFrameworkOptions } from '../types' +import type { + GenerateFunctionIdFnOptional, + ServerFn, +} from '../start-compiler-plugin/types' + +type LoaderOptions = { + env: 'client' | 'server' + envName: string + root: string + framework: CompileStartFrameworkOptions + providerEnvName: string + generateFunctionId?: GenerateFunctionIdFnOptional + manifestPath?: string +} + +const compilers = new Map() +const loaderContexts = new Map() +const serverFnsById: Record = {} +const require = createRequire(import.meta.url) +const appendServerFnsToManifest = ( + manifestPath: string, + data: Record, +) => { + if (!manifestPath || Object.keys(data).length === 0) return + fs.mkdirSync(path.dirname(manifestPath), { recursive: true }) + fs.appendFileSync(manifestPath, `${JSON.stringify(data)}\n`) +} + +export const getServerFnsById = () => serverFnsById +export const resetServerFnCompilerState = () => { + compilers.clear() + loaderContexts.clear() + for (const key of Object.keys(serverFnsById)) { + delete serverFnsById[key] + } +} + +// Derive transform code filter from KindDetectionPatterns (single source of truth) +function getTransformCodeFilterForEnv(env: 'client' | 'server'): Array { + const validKinds = LookupKindsPerEnv[env] + const patterns: Array = [] + for (const [kind, pattern] of Object.entries(KindDetectionPatterns) as Array< + [keyof typeof KindDetectionPatterns, RegExp] + >) { + if (validKinds.has(kind)) { + patterns.push(pattern) + } + } + return patterns +} + +const getLookupConfigurationsForEnv = ( + env: 'client' | 'server', + framework: CompileStartFrameworkOptions, +): Array => { + const commonConfigs: Array = [ + { + libName: `@tanstack/${framework}-start`, + rootExport: 'createServerFn', + kind: 'Root', + }, + { + libName: `@tanstack/${framework}-start`, + rootExport: 'createIsomorphicFn', + kind: 'Root', + }, + { + libName: `@tanstack/${framework}-start`, + rootExport: 'createServerOnlyFn', + kind: 'Root', + }, + { + libName: `@tanstack/${framework}-start`, + rootExport: 'createClientOnlyFn', + kind: 'Root', + }, + ] + + if (env === 'client') { + return [ + { + libName: `@tanstack/${framework}-start`, + rootExport: 'createMiddleware', + kind: 'Root', + }, + { + libName: `@tanstack/${framework}-start`, + rootExport: 'createStart', + kind: 'Root', + }, + ...commonConfigs, + ] + } + + return [ + ...commonConfigs, + { + libName: `@tanstack/${framework}-router`, + rootExport: 'ClientOnly', + kind: 'ClientOnlyJSX', + }, + ] +} + +function shouldTransformCode(code: string, env: 'client' | 'server') { + const patterns = getTransformCodeFilterForEnv(env) + return patterns.some((pattern) => pattern.test(code)) +} + +async function resolveId( + loaderContext: any, + source: string, + importer?: string, +): Promise { + const baseContext = importer + ? path.dirname(cleanId(importer)) + : loaderContext.context + const resolveContext = + source.startsWith('.') || source.startsWith('/') + ? baseContext + : loaderContext.rootContext || baseContext + + return new Promise((resolve) => { + const resolver = + loaderContext.getResolve?.({ + conditionNames: ['import', 'module', 'default'], + }) ?? loaderContext.resolve + + try { + resolver(resolveContext, source, (err: Error | null, result?: string) => { + if (!err && result) { + resolve(cleanId(result)) + return + } + try { + const resolved = require.resolve(source, { + paths: [ + baseContext, + loaderContext.rootContext || loaderContext.context, + ].filter(Boolean), + }) + resolve(cleanId(resolved)) + } catch { + resolve(null) + } + }) + } catch { + resolve(null) + } + }) +} + +async function loadModule( + compiler: StartCompiler, + loaderContext: any, + id: string, +) { + const cleaned = cleanId(id) + const resolvedPath = + (await resolveId(loaderContext, cleaned)) ?? + (path.isAbsolute(cleaned) ? cleaned : null) + + if (!resolvedPath || resolvedPath.includes('\0')) return + + try { + const code = await fsp.readFile(resolvedPath, 'utf-8') + compiler.ingestModule({ code, id: resolvedPath }) + } catch { + // ignore missing files + } +} + +export default function startCompilerLoader(this: any, code: string, map: any) { + const callback = this.async() + const options = this.getOptions() as LoaderOptions + + const env = options.env + const envName = options.envName + const root = options.root || process.cwd() + const framework = options.framework + const providerEnvName = options.providerEnvName + const manifestPath = options.manifestPath + + const shouldTransform = shouldTransformCode(code, env) + if (!shouldTransform) { + callback(null, code, map) + return + } + + loaderContexts.set(envName, this) + + let compiler = compilers.get(envName) + if (!compiler) { + const mode = + this.mode === 'production' || + this._compiler?.options?.mode === 'production' + ? 'build' + : 'dev' + const shouldPersistManifest = Boolean(manifestPath) && mode === 'build' + const onServerFnsById = (d: Record) => { + Object.assign(serverFnsById, d) + if (shouldPersistManifest && manifestPath) { + appendServerFnsToManifest(manifestPath, d) + } + } + + compiler = new StartCompiler({ + env, + envName, + root, + lookupKinds: LookupKindsPerEnv[env], + lookupConfigurations: getLookupConfigurationsForEnv(env, framework), + mode, + framework, + providerEnvName, + generateFunctionId: options.generateFunctionId, + onServerFnsById, + getKnownServerFns: () => serverFnsById, + loadModule: async (id: string) => + loadModule(compiler!, loaderContexts.get(envName), id), + resolveId: async (source: string, importer?: string) => + resolveId(loaderContexts.get(envName), source, importer), + }) + compilers.set(envName, compiler) + } + + const detectedKinds = detectKindsInCode(code, env) + const resourceQuery = + typeof this.resourceQuery === 'string' ? this.resourceQuery : '' + const baseResource = + typeof this.resource === 'string' ? this.resource : this.resourcePath + const resourceId = + resourceQuery && !baseResource.includes(resourceQuery) + ? `${baseResource}${resourceQuery}` + : baseResource + compiler + .compile({ + id: resourceId, + code, + detectedKinds, + }) + .then((result) => { + if (!result) { + callback(null, code, map) + return + } + callback(null, result.code, result.map ?? map) + }) + .catch((error) => { + callback(error) + }) +} diff --git a/packages/start-plugin-core/src/rsbuild/start-compiler-plugin.ts b/packages/start-plugin-core/src/rsbuild/start-compiler-plugin.ts new file mode 100644 index 00000000000..5859a5271a7 --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/start-compiler-plugin.ts @@ -0,0 +1,713 @@ +import { promises as fsp } from 'node:fs' +import path from 'node:path' +import { createRspackPlugin } from 'unplugin' +import { VIRTUAL_MODULES } from '@tanstack/start-server-core' +import { VITE_ENVIRONMENT_NAMES } from '../constants' +import { + getServerFnsById, + resetServerFnCompilerState, +} from './start-compiler-loader' +import type { ServerFn } from '../start-compiler-plugin/types' + +const SERVER_FN_MANIFEST_FILE = 'tanstack-start-server-fn-manifest.json' +export const SERVER_FN_MANIFEST_TEMP_FILE = + 'tanstack-start-server-fn-manifest.temp.jsonl' + +const readTempManifest = async (manifestPath: string) => { + try { + const raw = await fsp.readFile(manifestPath, 'utf-8') + return raw + .split('\n') + .filter(Boolean) + .reduce>((acc, line) => { + try { + const parsed = JSON.parse(line) as Record + Object.assign(acc, parsed) + } catch { + return acc + } + return acc + }, {}) + } catch { + return {} + } +} + +const normalizeIdentifier = (value: unknown) => + `${value ?? ''}`.replace(/\\/g, '/') + +const isJsFile = (file: unknown): file is string => + typeof file === 'string' && file.endsWith('.js') + +type CompilationModule = { + id?: string | number + [key: string]: any +} + +const getChunkFiles = (chunk: any) => { + const files = [ + ...Array.from(chunk?.files ?? []), + ...Array.from(chunk?.auxiliaryFiles ?? []), + ] + return files.filter((file) => typeof file === 'string') +} + +const getModuleIdentifiers = (module: any) => + [ + module.identifier, + module.name, + module.nameForCondition, + module.resource, + module.moduleIdentifier, + ] + .filter(Boolean) + .map((value) => normalizeIdentifier(value)) + +const getCompilationModuleIdentifiers = (module: any) => + [ + module.resource, + module.userRequest, + module.request, + typeof module.identifier === 'function' + ? module.identifier() + : module.identifier, + module.debugId, + ] + .filter(Boolean) + .map((value) => normalizeIdentifier(value)) + +function generateManifestModule( + serverFnsById: Record, + includeClientReferencedCheck: boolean, +): string { + const manifestEntries = Object.entries(serverFnsById) + .map(([id, fn]) => { + const baseEntry = `${JSON.stringify(id)}: { + functionName: ${JSON.stringify(fn.functionName)}, + importer: () => import(${JSON.stringify(fn.extractedFilename)})${ + includeClientReferencedCheck + ? `, + isClientReferenced: ${fn.isClientReferenced ?? true}` + : '' + } + }` + return baseEntry + }) + .join(',') + + const getServerFnByIdParams = includeClientReferencedCheck ? 'id, opts' : 'id' + const clientReferencedCheck = includeClientReferencedCheck + ? ` + if (opts?.fromClient && !serverFnInfo.isClientReferenced) { + throw new Error('Server function not accessible from client: ' + id) + } +` + : '' + + return ` + const manifest = {${manifestEntries}} + + export async function getServerFnById(${getServerFnByIdParams}) { + const serverFnInfo = manifest[id] + if (!serverFnInfo) { + throw new Error('Server function info not found for ' + id) + } +${clientReferencedCheck} + const fnModule = await serverFnInfo.importer() + + if (!fnModule) { + console.info('serverFnInfo', serverFnInfo) + throw new Error('Server function module not resolved for ' + id) + } + + let action = fnModule[serverFnInfo.functionName] + if (action?.serverFnMeta?.id && action.serverFnMeta.id !== id) { + action = undefined + } + if (!action) { + const fallbackAction = Object.values(fnModule).find( + (candidate) => + candidate?.serverFnMeta?.id && + candidate.serverFnMeta.id === id, + ) + if (fallbackAction) { + action = fallbackAction + } + } + if (Array.isArray(globalThis.__tssServerFnHandlers)) { + const globalMatch = globalThis.__tssServerFnHandlers.find( + (candidate) => + candidate?.serverFnMeta?.id && + candidate.serverFnMeta.id === id, + ) + if (globalMatch && (!action || action.__executeServer)) { + action = globalMatch + } + } + + if (!action) { + console.info('serverFnInfo', serverFnInfo) + console.info('fnModule', fnModule) + + throw new Error( + \`Server function module export not resolved for serverFn ID: \${id}\`, + ) + } + return action + } + ` +} + +function generateManifestModuleFromFile( + manifestPath: string, + includeClientReferencedCheck: boolean, +): string { + const getServerFnByIdParams = includeClientReferencedCheck ? 'id, opts' : 'id' + const clientReferencedCheck = includeClientReferencedCheck + ? ` + if (opts?.fromClient && !serverFnInfo.isClientReferenced) { + throw new Error('Server function not accessible from client: ' + id) + } +` + : '' + + return ` + import fs from 'node:fs' + import { pathToFileURL } from 'node:url' + + let cached + const getManifest = () => { + if (cached) return cached + try { + const raw = fs.readFileSync(${JSON.stringify(manifestPath)}, 'utf-8') + cached = JSON.parse(raw) + return cached + } catch (error) { + return {} + } + } + + export async function getServerFnById(${getServerFnByIdParams}) { + const manifest = getManifest() + const serverFnInfo = manifest[id] + if (!serverFnInfo) { + throw new Error('Server function info not found for ' + id) + } +${clientReferencedCheck} + let fnModule + if (typeof __webpack_require__ === 'function' && serverFnInfo.importerModuleId != null) { + const chunkIds = Array.isArray(serverFnInfo.importerChunkIds) + ? serverFnInfo.importerChunkIds + : [] + if (chunkIds.length > 0 && typeof __webpack_require__.e === 'function') { + await Promise.all(chunkIds.map((chunkId) => __webpack_require__.e(chunkId))) + } + fnModule = __webpack_require__(serverFnInfo.importerModuleId) + } else { + const importerPath = serverFnInfo.importerPath ?? serverFnInfo.extractedFilename + const importerHref = /^https?:\\/\\//.test(importerPath) + ? importerPath + : pathToFileURL(importerPath).href + fnModule = await import(/* webpackIgnore: true */ importerHref) + } + + if (!fnModule) { + console.info('serverFnInfo', serverFnInfo) + throw new Error('Server function module not resolved for ' + id) + } + + let action = fnModule[serverFnInfo.functionName] + if (action?.serverFnMeta?.id && action.serverFnMeta.id !== id) { + action = undefined + } + if (!action) { + const fallbackAction = Object.values(fnModule).find( + (candidate) => + candidate?.serverFnMeta?.id && + candidate.serverFnMeta.id === id, + ) + if (fallbackAction) { + action = fallbackAction + } + } + if (Array.isArray(globalThis.__tssServerFnHandlers)) { + const globalMatch = globalThis.__tssServerFnHandlers.find( + (candidate) => + candidate?.serverFnMeta?.id && + candidate.serverFnMeta.id === id, + ) + if (globalMatch && (!action || action.__executeServer)) { + action = globalMatch + } + } + + if (!action) { + console.info('serverFnInfo', serverFnInfo) + console.info('fnModule', fnModule) + + throw new Error( + \`Server function module export not resolved for serverFn ID: \${id}\`, + ) + } + return action + } + ` +} + +export function createServerFnManifestRspackPlugin(opts: { + serverOutputDir: string +}) { + const tempManifestPath = path.join( + opts.serverOutputDir, + SERVER_FN_MANIFEST_TEMP_FILE, + ) + + return { + apply(compiler: any) { + const resetManifestState = async () => { + resetServerFnCompilerState() + await fsp.rm(tempManifestPath, { force: true }) + } + compiler.hooks.beforeRun.tapPromise( + 'tanstack-start:server-fn-manifest', + resetManifestState, + ) + compiler.hooks.watchRun.tapPromise( + 'tanstack-start:server-fn-manifest', + resetManifestState, + ) + compiler.hooks.afterEmit.tapPromise( + 'tanstack-start:server-fn-manifest', + async (compilation: any) => { + const serverFnsById = getServerFnsById() + const fileServerFnsById = await readTempManifest(tempManifestPath) + const mergedServerFnsById = { + ...serverFnsById, + ...fileServerFnsById, + } + const stats = compilation?.getStats?.() + const statsJson = stats?.toJson?.({ + all: false, + assets: true, + chunks: true, + chunkModules: true, + moduleAssets: true, + modules: true, + }) + const chunks = statsJson?.chunks ?? [] + const chunksById = new Map( + chunks.map((chunk: any) => [chunk.id, getChunkFiles(chunk)]), + ) + const chunkModuleEntries = chunks.flatMap((chunk: any) => { + const chunkFiles = getChunkFiles(chunk) + const chunkModules = chunk.modules ?? [] + return chunkModules.flatMap((module: any) => + getModuleIdentifiers(module).map((identifier) => ({ + identifier, + files: chunkFiles, + })), + ) + }) + const modules = statsJson?.modules ?? [] + const compilationModules: Array = Array.from( + compilation?.modules ?? [], + ) + const chunkGraph = compilation?.chunkGraph + const moduleGraph = compilation?.moduleGraph + const compilationEntries = compilationModules.flatMap( + (module: any) => { + const identifiers = getCompilationModuleIdentifiers(module) + if (identifiers.length === 0) return [] + const chunkFiles = chunkGraph + ? Array.from( + chunkGraph.getModuleChunksIterable(module) ?? [], + ).flatMap((chunk: any) => getChunkFiles(chunk)) + : [] + return identifiers.map((identifier) => ({ + identifier, + files: chunkFiles, + })) + }, + ) + const assetFiles = (statsJson?.assets ?? []) + .map((asset: any) => asset.name ?? asset) + .filter((name: string) => typeof name === 'string') + .filter((name: string) => name.endsWith('.js')) + const getAssetContent = async (assetName: string) => { + const assetFromCompilation = + (typeof compilation?.getAsset === 'function' + ? compilation.getAsset(assetName)?.source + : undefined) ?? + compilation?.assets?.[assetName] ?? + (typeof compilation?.assets?.get === 'function' + ? compilation.assets.get(assetName) + : undefined) + const sourceValue = + assetFromCompilation && + typeof assetFromCompilation.source === 'function' + ? assetFromCompilation.source() + : assetFromCompilation + if (typeof sourceValue === 'string') return sourceValue + if (Buffer.isBuffer(sourceValue)) { + return sourceValue.toString('utf-8') + } + if (sourceValue && typeof sourceValue.toString === 'function') { + return sourceValue.toString() + } + try { + const assetPath = path.join(opts.serverOutputDir, assetName) + return await fsp.readFile(assetPath, 'utf-8') + } catch { + return undefined + } + } + const findAssetMatch = async (searchTokens: Array) => { + for (const assetName of assetFiles) { + const content = await getAssetContent(assetName) + if (!content) continue + if (searchTokens.some((token) => content.includes(token))) { + return assetName + } + } + return undefined + } + const manifestWithImporters: Record = {} + for (const [id, info] of Object.entries(mergedServerFnsById)) { + const normalizedExtracted = info.extractedFilename.replace( + /\\/g, + '/', + ) + const normalizedFilename = info.filename.replace(/\\/g, '/') + const searchTokens = [ + normalizedExtracted, + normalizedFilename, + path.basename(normalizedExtracted), + path.basename(normalizedFilename), + id, + info.functionName, + ].filter(Boolean) + const matchedModuleByExtracted = modules.find((module: any) => + getModuleIdentifiers(module).some((identifier) => + identifier.includes(normalizedExtracted), + ), + ) + const matchedModuleByFilename = modules.find((module: any) => + getModuleIdentifiers(module).some((identifier) => + identifier.includes(normalizedFilename), + ), + ) + const matchedModule = + matchedModuleByExtracted ?? matchedModuleByFilename + const chunkIds = + matchedModule?.chunks ?? matchedModule?.chunkIds ?? [] + const statsModuleId = matchedModule?.id ?? matchedModule?.moduleId + const chunkFiles = chunkIds.flatMap((chunkId: any) => { + return chunksById.get(chunkId) ?? [] + }) + const moduleAssets = Array.isArray(matchedModule?.assets) + ? matchedModule.assets + : matchedModule?.assets + ? Object.keys(matchedModule.assets) + : [] + const matchedCompilationModuleByExtracted = compilationModules.find( + (module) => + getCompilationModuleIdentifiers(module).some((identifier) => + identifier.includes(normalizedExtracted), + ), + ) + const matchedCompilationModuleByFilename = compilationModules.find( + (module) => + getCompilationModuleIdentifiers(module).some((identifier) => + identifier.includes(normalizedFilename), + ), + ) + const matchedCompilationModule = + matchedCompilationModuleByExtracted ?? + matchedCompilationModuleByFilename + const exportsInfo = matchedCompilationModule + ? moduleGraph?.getExportsInfo?.(matchedCompilationModule) + : null + const providedExports = + exportsInfo?.getProvidedExports?.() ?? + (Array.isArray(exportsInfo?.exports) + ? exportsInfo.exports + .map((exportInfo: any) => exportInfo.name) + .filter(Boolean) + : []) + const resolvedFunctionName = + Array.isArray(providedExports) && providedExports.length === 1 + ? providedExports[0] + : undefined + const compilationChunkIds = + matchedCompilationModule && chunkGraph + ? Array.from( + chunkGraph.getModuleChunksIterable( + matchedCompilationModule, + ), + ) + .map((chunk: any) => chunk.id) + .filter( + (chunkId: any) => + chunkId !== undefined && chunkId !== null, + ) + : [] + const compilationModuleId = + matchedCompilationModule?.id ?? + (matchedCompilationModule && chunkGraph?.getModuleId + ? chunkGraph.getModuleId(matchedCompilationModule) + : undefined) + const compilationFiles = + compilationEntries.find((entry) => { + return entry.identifier.includes(normalizedExtracted) + })?.files ?? + compilationEntries.find((entry) => { + return entry.identifier.includes(normalizedFilename) + })?.files ?? + [] + const chunkModuleFiles = + chunkFiles.length > 0 + ? chunkFiles + : (chunkModuleEntries.find((entry: any) => { + return ( + entry.identifier.includes(normalizedExtracted) || + entry.identifier.includes(normalizedFilename) + ) + })?.files ?? []) + const jsFile = chunkFiles.find(isJsFile) + const fallbackJsFile = + jsFile ?? + chunkModuleFiles.find(isJsFile) ?? + compilationFiles.find(isJsFile) ?? + moduleAssets.find(isJsFile) + let importerPath = jsFile + ? path.join(opts.serverOutputDir, jsFile) + : fallbackJsFile + ? path.join(opts.serverOutputDir, fallbackJsFile) + : undefined + if (!importerPath) { + const assetMatch = await findAssetMatch(searchTokens) + importerPath = assetMatch + ? path.join(opts.serverOutputDir, assetMatch) + : undefined + } + + manifestWithImporters[id] = { + ...info, + functionName: resolvedFunctionName ?? info.functionName, + importerPath, + importerChunkIds: + chunkIds.length > 0 + ? chunkIds + : compilationChunkIds.length > 0 + ? compilationChunkIds + : undefined, + importerModuleId: + statsModuleId ?? compilationModuleId ?? undefined, + } + } + const extractExportName = ( + content: string, + moduleId: string | number, + functionId: string, + ) => { + const marker = `${moduleId}:function` + const startIndex = content.indexOf(marker) + const scope = + startIndex === -1 + ? content + : content.slice(startIndex, startIndex + 4000) + const idIndex = scope.indexOf(functionId) + if (idIndex === -1) return undefined + const beforeId = scope.slice(Math.max(0, idIndex - 300), idIndex) + const assignmentMatches = Array.from( + beforeId.matchAll(/([A-Za-z_$][\w$]*)=(?!>)/g), + ) + const handlerVar = + assignmentMatches[assignmentMatches.length - 1]?.[1] + if (!handlerVar) return undefined + const escapedHandlerVar = handlerVar.replace( + /[.*+?^${}()|[\]\\]/g, + '\\$&', + ) + const exportMatch = scope.match( + new RegExp( + `([A-Za-z_$][\\\\w$]*):\\\\(\\\\)=>${escapedHandlerVar}`, + ), + ) + return exportMatch?.[1] + } + const findExportName = async ( + moduleId: string | number, + functionId: string, + preferredAssetName?: string, + ) => { + if (preferredAssetName) { + const content = await getAssetContent(preferredAssetName) + const resolved = content + ? extractExportName(content, moduleId, functionId) + : undefined + if (resolved) return resolved + } + for (const assetName of assetFiles) { + if (assetName === preferredAssetName) continue + const content = await getAssetContent(assetName) + if (!content) continue + const resolved = extractExportName(content, moduleId, functionId) + if (resolved) return resolved + } + return undefined + } + const extractModuleIdFromContent = ( + content: string, + functionId: string, + ) => { + const idIndex = content.indexOf(functionId) + if (idIndex === -1) return undefined + const beforeId = content.slice(Math.max(0, idIndex - 1500), idIndex) + const matches = Array.from( + beforeId.matchAll(/(?:^|[,{])\s*([0-9]+)\s*:\s*(?:function|\()/g), + ) + const moduleId = matches[matches.length - 1]?.[1] + if (!moduleId) return undefined + return Number.isNaN(Number(moduleId)) ? moduleId : Number(moduleId) + } + const parseChunkIdFromAssetName = (assetName: string) => { + const base = path.basename(assetName, path.extname(assetName)) + if (!base) return undefined + return /^\d+$/.test(base) ? Number(base) : undefined + } + const findModuleIdByFunctionId = async ( + functionId: string, + preferredAssetName?: string, + ) => { + if (preferredAssetName) { + const content = await getAssetContent(preferredAssetName) + if (content) { + const moduleId = extractModuleIdFromContent(content, functionId) + if (moduleId !== undefined) { + return { + moduleId, + assetName: preferredAssetName, + } + } + } + } + for (const assetName of assetFiles) { + if (assetName === preferredAssetName) continue + const content = await getAssetContent(assetName) + if (!content) continue + const moduleId = extractModuleIdFromContent(content, functionId) + if (moduleId !== undefined) { + return { moduleId, assetName } + } + } + return undefined + } + for (const info of Object.values(manifestWithImporters)) { + const importerAssetName = info.importerPath + ? path + .relative(opts.serverOutputDir, info.importerPath) + .replace(/\\/g, '/') + : undefined + if (info.importerModuleId == null) { + const moduleMatch = await findModuleIdByFunctionId( + info.functionId, + importerAssetName, + ) + if (moduleMatch) { + info.importerModuleId = moduleMatch.moduleId + if (!info.importerPath) { + info.importerPath = path.join( + opts.serverOutputDir, + moduleMatch.assetName, + ) + } + if (!info.importerChunkIds) { + const chunkId = parseChunkIdFromAssetName( + moduleMatch.assetName, + ) + if (chunkId !== undefined) { + info.importerChunkIds = [chunkId] + } + } + } + } + if (info.importerModuleId == null) continue + const resolvedName = await findExportName( + info.importerModuleId, + info.functionId, + importerAssetName, + ) + if (resolvedName) { + info.functionName = resolvedName + } + } + const manifestPath = path.join( + opts.serverOutputDir, + SERVER_FN_MANIFEST_FILE, + ) + await fsp.mkdir(path.dirname(manifestPath), { recursive: true }) + await fsp.writeFile( + manifestPath, + JSON.stringify(manifestWithImporters), + 'utf-8', + ) + await fsp.rm(tempManifestPath, { force: true }) + }, + ) + }, + } +} + +export function createServerFnResolverPlugin(opts: { + environmentName: string + providerEnvName: string + serverOutputDir?: string +}) { + const ssrIsProvider = opts.providerEnvName === VITE_ENVIRONMENT_NAMES.server + const includeClientReferencedCheck = !ssrIsProvider + const manifestPath = opts.serverOutputDir + ? path.join(opts.serverOutputDir, SERVER_FN_MANIFEST_FILE) + : null + const tempManifestPath = opts.serverOutputDir + ? path.join(opts.serverOutputDir, SERVER_FN_MANIFEST_TEMP_FILE) + : null + + const pluginFactory = createRspackPlugin(() => ({ + name: `tanstack-start-core:server-fn-resolver:${opts.environmentName}`, + resolveId(id) { + if (id === VIRTUAL_MODULES.serverFnResolver) { + return id + } + return null + }, + async load(id) { + if (id !== VIRTUAL_MODULES.serverFnResolver) return null + if (opts.environmentName !== opts.providerEnvName) { + return `export { getServerFnById } from '@tanstack/start-server-core/server-fn-ssr-caller'` + } + const serverFnsById = getServerFnsById() + const fileServerFnsById = tempManifestPath + ? await readTempManifest(tempManifestPath) + : {} + const mergedServerFnsById = { + ...serverFnsById, + ...fileServerFnsById, + } + if (Object.keys(mergedServerFnsById).length > 0) { + return generateManifestModule( + mergedServerFnsById, + includeClientReferencedCheck, + ) + } + if (manifestPath) { + return generateManifestModuleFromFile( + manifestPath, + includeClientReferencedCheck, + ) + } + return generateManifestModule(serverFnsById, includeClientReferencedCheck) + }, + })) + return pluginFactory() +} diff --git a/packages/start-plugin-core/src/rsbuild/start-manifest-plugin.ts b/packages/start-plugin-core/src/rsbuild/start-manifest-plugin.ts new file mode 100644 index 00000000000..14b8e2e1106 --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/start-manifest-plugin.ts @@ -0,0 +1,311 @@ +import { promises as fsp } from 'node:fs' +import path from 'node:path' +import { joinURL } from 'ufo' +import { rootRouteId } from '@tanstack/router-core' +import { VIRTUAL_MODULES } from '@tanstack/start-server-core' +import { tsrSplit } from '@tanstack/router-plugin' +import { createRspackPlugin } from 'unplugin' +import type { Manifest, RouterManagedTag } from '@tanstack/router-core' + +const START_MANIFEST_FILE = 'tanstack-start-manifest.json' + +type StatsAsset = string | { name?: string } + +type StatsChunk = { + files?: Array + auxiliaryFiles?: Array + modules?: Array + names?: Array + entry?: boolean + initial?: boolean +} + +type StatsModule = { + name?: string + identifier?: string + nameForCondition?: string +} + +type StatsJson = { + entrypoints?: Record }> + chunks?: Array +} + +function getAssetName(asset: StatsAsset): string | undefined { + if (!asset) return undefined + if (typeof asset === 'string') return asset + return asset.name +} + +function isJsAsset(asset: string) { + return asset.endsWith('.js') || asset.endsWith('.mjs') +} + +function isCssAsset(asset: string) { + return asset.endsWith('.css') +} + +function createCssTags( + basePath: string, + assets: Array, +): Array { + return assets.map((asset) => ({ + tag: 'link', + attrs: { + rel: 'stylesheet', + href: joinURL(basePath, asset), + type: 'text/css', + }, + })) +} + +function createEntryScriptTags( + basePath: string, + assets: Array, +): Array { + return assets.map((asset) => ({ + tag: 'script', + attrs: { + type: 'module', + async: true, + src: joinURL(basePath, asset), + }, + })) +} + +function unique(items: Array) { + return Array.from(new Set(items)) +} + +function getRouteModuleFilePath(module: StatsModule): string | undefined { + const moduleId = module.identifier ?? module.name ?? '' + if (!moduleId.includes(tsrSplit)) return undefined + + if (module.nameForCondition) { + return module.nameForCondition + } + + const resource = moduleId.split('!').pop() ?? moduleId + const cleanedResource = resource.startsWith('module|') + ? resource.slice('module|'.length) + : resource + const [resourcePath, queryString] = cleanedResource.split('?') + if (!queryString?.includes(tsrSplit)) return undefined + + return resourcePath +} + +function getStatsEntryPointName(statsJson: StatsJson): string | undefined { + const entrypoints = statsJson.entrypoints ?? {} + if (entrypoints['index']) return 'index' + if (entrypoints['main']) return 'main' + return Object.keys(entrypoints)[0] +} + +function getStatsEntryAssets(statsJson: StatsJson): { + entrypointName?: string + assets: Array +} { + const entrypoints = statsJson.entrypoints ?? {} + const entrypointName = getStatsEntryPointName(statsJson) + const entrypoint = entrypointName ? entrypoints[entrypointName] : undefined + + if (!entrypoint?.assets) { + return { entrypointName, assets: [] } + } + + return { + entrypointName, + assets: unique( + entrypoint.assets + .map(getAssetName) + .filter((asset): asset is string => Boolean(asset)), + ), + } +} + +function getEntryChunkAssets( + statsJson: StatsJson, + entrypointName?: string, +): Array { + if (!entrypointName) return [] + const chunks = statsJson.chunks ?? [] + const entryChunks = chunks.filter((chunk) => { + if (chunk.entry) return true + const names = chunk.names ?? [] + return names.includes(entrypointName) + }) + return unique( + entryChunks.flatMap((chunk) => chunk.files ?? []).filter(isJsAsset), + ) +} + +function pickEntryAsset( + assets: Array, + entrypointName?: string, +): string | undefined { + if (assets.length === 0) return undefined + if (entrypointName) { + const match = assets.find((asset) => { + const baseName = path.posix.basename(asset) + return ( + baseName === `${entrypointName}.js` || + baseName.startsWith(`${entrypointName}.`) + ) + }) + if (match) return match + } + return assets[assets.length - 1] +} + +function buildStartManifest({ + statsJson, + basePath, +}: { + statsJson: StatsJson + basePath: string +}): Manifest & { clientEntry: string } { + const { entrypointName, assets: entryAssets } = getStatsEntryAssets(statsJson) + const entryJsAssets = unique(entryAssets.filter(isJsAsset)) + const entryCssAssets = unique(entryAssets.filter(isCssAsset)) + + const entryFile = + pickEntryAsset(entryJsAssets, entrypointName) ?? + pickEntryAsset( + getEntryChunkAssets(statsJson, entrypointName), + entrypointName, + ) + if (!entryFile) { + throw new Error('No client entry file found in rsbuild stats') + } + + const routeTreeRoutes: Record = + globalThis.TSS_ROUTES_MANIFEST + + const routeChunks: Record> = {} + for (const chunk of statsJson.chunks ?? []) { + const modules = chunk.modules ?? [] + for (const mod of modules) { + const filePath = getRouteModuleFilePath(mod) + if (!filePath) continue + const normalizedPath = path.normalize(filePath) + const existingChunks = routeChunks[normalizedPath] + if (existingChunks) { + existingChunks.push(chunk) + } else { + routeChunks[normalizedPath] = [chunk] + } + } + } + + const manifest: Manifest = { routes: {} } + + Object.entries(routeTreeRoutes).forEach(([routeId, route]) => { + const chunks = routeChunks[path.normalize(route.filePath)] + if (!chunks?.length) { + manifest.routes[routeId] = {} + return + } + + const preloadAssets = unique( + chunks.flatMap((chunk) => chunk.files ?? []).filter(isJsAsset), + ) + const cssAssets = unique( + chunks + .flatMap((chunk) => [ + ...(chunk.files ?? []), + ...(chunk.auxiliaryFiles ?? []), + ]) + .filter(isCssAsset), + ) + + manifest.routes[routeId] = { + preloads: preloadAssets.map((asset) => joinURL(basePath, asset)), + assets: createCssTags(basePath, cssAssets), + } + }) + + const entryScriptAssets = entryJsAssets.filter((asset) => asset !== entryFile) + + manifest.routes[rootRouteId] = { + ...(manifest.routes[rootRouteId] ?? {}), + preloads: entryJsAssets.map((asset) => joinURL(basePath, asset)), + assets: [ + ...createCssTags(basePath, entryCssAssets), + ...createEntryScriptTags(basePath, entryScriptAssets), + ...(manifest.routes[rootRouteId]?.assets ?? []), + ], + } + + return { + routes: manifest.routes, + clientEntry: joinURL(basePath, entryFile), + } +} + +export function createStartManifestRspackPlugin(opts: { + basePath: string + clientOutputDir: string +}) { + return { + apply(compiler: any) { + compiler.hooks.done.tapPromise( + 'tanstack-start:manifest', + async (stats: any) => { + const statsJson: StatsJson = stats.toJson({ + all: false, + entrypoints: true, + chunks: true, + chunkModules: true, + modules: true, + }) + const manifest = buildStartManifest({ + statsJson, + basePath: opts.basePath, + }) + + const manifestPath = path.join( + opts.clientOutputDir, + START_MANIFEST_FILE, + ) + await fsp.mkdir(path.dirname(manifestPath), { recursive: true }) + await fsp.writeFile(manifestPath, JSON.stringify(manifest), 'utf-8') + }, + ) + }, + } +} + +export function createStartManifestVirtualModulePlugin(opts: { + clientOutputDir: string +}) { + const manifestPath = path.join(opts.clientOutputDir, START_MANIFEST_FILE) + const pluginFactory = createRspackPlugin(() => ({ + name: 'tanstack-start:manifest:virtual', + resolveId(id) { + if (id === VIRTUAL_MODULES.startManifest) { + return id + } + return null + }, + load(id) { + if (id !== VIRTUAL_MODULES.startManifest) return null + return ` +import fs from 'node:fs' + +let cached +export const tsrStartManifest = () => { + if (cached) return cached + try { + const raw = fs.readFileSync(${JSON.stringify(manifestPath)}, 'utf-8') + cached = JSON.parse(raw) + return cached + } catch (error) { + return { routes: {}, clientEntry: '' } + } +} +` + }, + })) + return pluginFactory() +} diff --git a/packages/start-plugin-core/src/rsbuild/start-router-plugin.ts b/packages/start-plugin-core/src/rsbuild/start-router-plugin.ts new file mode 100644 index 00000000000..7cf530d7146 --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/start-router-plugin.ts @@ -0,0 +1,115 @@ +import path from 'pathe' +import { + tanstackRouterAutoImport, + tanstackRouterCodeSplitter, + tanstackRouterGenerator, +} from '@tanstack/router-plugin/rspack' +import { routesManifestPlugin } from '../start-router-plugin/generator-plugins/routes-manifest-plugin' +import { prerenderRoutesPlugin } from '../start-router-plugin/generator-plugins/prerender-routes-plugin' +import { createRouteTreeModuleDeclaration } from '../start-router-plugin/route-tree-module-declaration' +import { VITE_ENVIRONMENT_NAMES } from '../constants' +import { setGeneratorInstance } from './route-tree-state' +import { resolveLoaderPath } from './resolve-loader-path' +import type { GetConfigFn, TanStackStartVitePluginCoreOptions } from '../types' +import type { GeneratorPlugin } from '@tanstack/router-generator' +import type { TanStackStartInputConfig } from '../schema' + +export function tanStackStartRouterRsbuild( + startPluginOpts: TanStackStartInputConfig, + getConfig: GetConfigFn, + corePluginOpts: TanStackStartVitePluginCoreOptions, +) { + const getGeneratedRouteTreePath = () => { + const { startConfig } = getConfig() + return path.resolve(startConfig.router.generatedRouteTree) + } + + const clientTreeGeneratorPlugin: GeneratorPlugin = { + name: 'start-client-tree-plugin', + init({ generator }) { + setGeneratorInstance(generator) + }, + } + + let routeTreeFileFooter: Array | null = null + function getRouteTreeFileFooter() { + if (routeTreeFileFooter) { + return routeTreeFileFooter + } + const { startConfig, resolvedStartConfig } = getConfig() + const ogRouteTreeFileFooter = startConfig.router.routeTreeFileFooter + if (ogRouteTreeFileFooter) { + if (Array.isArray(ogRouteTreeFileFooter)) { + routeTreeFileFooter = ogRouteTreeFileFooter + } else { + routeTreeFileFooter = ogRouteTreeFileFooter() + } + } + routeTreeFileFooter = [ + createRouteTreeModuleDeclaration({ + generatedRouteTreePath: getGeneratedRouteTreePath(), + framework: corePluginOpts.framework, + startFilePath: resolvedStartConfig.startFilePath, + routerFilePath: resolvedStartConfig.routerFilePath, + }), + ...(routeTreeFileFooter ?? []), + ] + return routeTreeFileFooter + } + + const routeTreeLoaderPath = resolveLoaderPath('./route-tree-loader') + + const generatorPlugin = tanstackRouterGenerator(() => { + const routerConfig = getConfig().startConfig.router + const plugins = [clientTreeGeneratorPlugin, routesManifestPlugin()] + if (startPluginOpts?.prerender?.enabled === true) { + plugins.push(prerenderRoutesPlugin()) + } + return { + ...routerConfig, + target: corePluginOpts.framework, + routeTreeFileFooter: getRouteTreeFileFooter(), + plugins, + } + }) + + const clientCodeSplitter = tanstackRouterCodeSplitter(() => { + const routerConfig = getConfig().startConfig.router + return { + ...routerConfig, + codeSplittingOptions: { + ...routerConfig.codeSplittingOptions, + deleteNodes: ['ssr', 'server', 'headers'], + addHmr: true, + }, + plugin: { + vite: { environmentName: VITE_ENVIRONMENT_NAMES.client }, + }, + } + }) + + const serverCodeSplitter = tanstackRouterCodeSplitter(() => { + const routerConfig = getConfig().startConfig.router + return { + ...routerConfig, + codeSplittingOptions: { + ...routerConfig.codeSplittingOptions, + addHmr: false, + }, + plugin: { + vite: { environmentName: VITE_ENVIRONMENT_NAMES.server }, + }, + } + }) + + const autoImport = tanstackRouterAutoImport(startPluginOpts?.router) + + return { + generatorPlugin, + clientCodeSplitter, + serverCodeSplitter, + autoImport, + routeTreeLoaderPath, + getGeneratedRouteTreePath, + } +} diff --git a/packages/start-plugin-core/src/rsbuild/start-storage-context-stub.ts b/packages/start-plugin-core/src/rsbuild/start-storage-context-stub.ts new file mode 100644 index 00000000000..a1662a4364e --- /dev/null +++ b/packages/start-plugin-core/src/rsbuild/start-storage-context-stub.ts @@ -0,0 +1,12 @@ +export function getStartContext() { + return { + startOptions: undefined, + } +} + +export async function runWithStartContext( + _context: unknown, + fn: () => T | Promise, +) { + return fn() +} diff --git a/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts b/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts index 36b9f429fba..cb1a257eaa1 100644 --- a/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts +++ b/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts @@ -7,7 +7,7 @@ import { generateFromAst, parseAst, } from '@tanstack/router-utils' -import babel from '@babel/core' +import * as babel from '@babel/core' import { handleCreateServerFn } from './handleCreateServerFn' import { handleCreateMiddleware } from './handleCreateMiddleware' import { handleCreateIsomorphicFn } from './handleCreateIsomorphicFn' diff --git a/packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts b/packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts index ddad1cf0611..284a3d32f32 100644 --- a/packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts +++ b/packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts @@ -1,5 +1,5 @@ import * as t from '@babel/types' -import babel from '@babel/core' +import * as babel from '@babel/core' import path from 'pathe' import { VITE_ENVIRONMENT_NAMES } from '../constants' import { cleanId, codeFrameError, stripMethodCall } from './utils' @@ -218,8 +218,11 @@ export function handleCreateServerFn( const exportNames = new Set() const serverFnsById: Record = {} + let providerImportPath: string | null = null + const providerImportNames = new Set() const [baseFilename] = context.id.split('?') as [string] + const baseDir = path.dirname(baseFilename) const extractedFilename = `${baseFilename}?${TSS_SERVERFN_SPLIT_PARAM}` const relativeFilename = path.relative(context.root, baseFilename) const knownFns = context.getKnownServerFns() @@ -309,6 +312,20 @@ export function handleCreateServerFn( // to avoid duplicates - provider files process the same functions if (!isProviderFile) { + if (!envConfig.isClientEnvironment && envConfig.ssrIsProvider) { + const [canonicalBase] = canonicalExtractedFilename.split('?') as [ + string, + ] + let relativeImportPath = path.relative(baseDir, canonicalBase) + if (!relativeImportPath.startsWith('.')) { + relativeImportPath = `./${relativeImportPath}` + } + relativeImportPath = relativeImportPath.split(path.sep).join('/') + providerImportPath = `${relativeImportPath}?${TSS_SERVERFN_SPLIT_PARAM}` + } + if (providerImportPath) { + providerImportNames.add(functionName) + } serverFnsById[functionId] = { functionName, functionId, @@ -452,12 +469,65 @@ export function handleCreateServerFn( context.onServerFnsById(serverFnsById) } - // Add runtime import using cached AST node const runtimeCode = getCachedRuntimeCode( context.framework, envConfig.runtimeCodeType, ) - context.ast.program.body.unshift(t.cloneNode(runtimeCode)) + + const importStatements: Array = [t.cloneNode(runtimeCode)] + if (providerImportPath && providerImportNames.size > 0) { + importStatements.push( + t.importDeclaration( + Array.from(providerImportNames).map((name) => + t.importSpecifier(t.identifier(name), t.identifier(name)), + ), + t.stringLiteral(providerImportPath), + ), + ) + } + + context.ast.program.body.unshift(...importStatements) + + if (providerImportPath && providerImportNames.size > 0) { + const globalHandlers = t.memberExpression( + t.identifier('globalThis'), + t.identifier('__tssServerFnHandlers'), + ) + const initHandlers = t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression( + t.identifier('globalThis'), + t.identifier('__tssServerFnHandlers'), + ), + t.logicalExpression( + '||', + t.memberExpression( + t.identifier('globalThis'), + t.identifier('__tssServerFnHandlers'), + ), + t.arrayExpression([]), + ), + ), + ) + const pushHandlers = t.expressionStatement( + t.callExpression( + t.memberExpression(globalHandlers, t.identifier('push')), + Array.from(providerImportNames).map((name) => t.identifier(name)), + ), + ) + const lastImportIndex = context.ast.program.body.reduce( + (lastIndex, node, index) => + t.isImportDeclaration(node) ? index : lastIndex, + -1, + ) + context.ast.program.body.splice( + lastImportIndex + 1, + 0, + initHandlers, + pushHandlers, + ) + } } /** diff --git a/packages/start-plugin-core/src/start-compiler-plugin/plugin.ts b/packages/start-plugin-core/src/start-compiler-plugin/plugin.ts index 973b2b192b6..dc86f545dd2 100644 --- a/packages/start-plugin-core/src/start-compiler-plugin/plugin.ts +++ b/packages/start-plugin-core/src/start-compiler-plugin/plugin.ts @@ -193,7 +193,6 @@ export function startCompilerPlugin( } let root = process.cwd() - let command: 'build' | 'serve' = 'build' const resolvedResolverVirtualImportId = resolveViteId( VIRTUAL_MODULES.serverFnResolver, @@ -227,7 +226,6 @@ export function startCompilerPlugin( }, configResolved(config) { root = config.root - command = config.command }, transform: { filter: { @@ -373,7 +371,6 @@ export function startCompilerPlugin( }, configResolved(config) { root = config.root - command = config.command }, resolveId: { filter: { id: new RegExp(VIRTUAL_MODULES.serverFnResolver) }, diff --git a/packages/start-plugin-core/src/start-compiler-plugin/types.ts b/packages/start-plugin-core/src/start-compiler-plugin/types.ts index 141cb4e28fb..19d4517ab8a 100644 --- a/packages/start-plugin-core/src/start-compiler-plugin/types.ts +++ b/packages/start-plugin-core/src/start-compiler-plugin/types.ts @@ -85,6 +85,12 @@ export interface ServerFn { extractedFilename: string /** The original source filename */ filename: string + /** The emitted chunk IDs for this function (rspack build) */ + importerChunkIds?: Array + /** The emitted module ID for this function (rspack build) */ + importerModuleId?: string | number + /** The emitted importer path for this function (rspack build fallback) */ + importerPath?: string /** * True when this function was discovered by the client build. * Used to restrict HTTP access to only client-referenced functions. diff --git a/packages/start-plugin-core/src/start-router-plugin/plugin.ts b/packages/start-plugin-core/src/start-router-plugin/plugin.ts index 1536d19c91e..a0e0ba6130f 100644 --- a/packages/start-plugin-core/src/start-router-plugin/plugin.ts +++ b/packages/start-plugin-core/src/start-router-plugin/plugin.ts @@ -9,6 +9,7 @@ import { VITE_ENVIRONMENT_NAMES } from '../constants' import { routesManifestPlugin } from './generator-plugins/routes-manifest-plugin' import { prerenderRoutesPlugin } from './generator-plugins/prerender-routes-plugin' import { pruneServerOnlySubtrees } from './pruneServerOnlySubtrees' +import { createRouteTreeModuleDeclaration } from './route-tree-module-declaration' import { SERVER_PROP } from './constants' import type { GetConfigFn, TanStackStartVitePluginCoreOptions } from '../types' import type { @@ -29,63 +30,6 @@ function isServerOnlyNode(node: RouteNode | undefined) { ) } -function moduleDeclaration({ - startFilePath, - routerFilePath, - corePluginOpts, - generatedRouteTreePath, -}: { - startFilePath: string | undefined - routerFilePath: string - corePluginOpts: TanStackStartVitePluginCoreOptions - generatedRouteTreePath: string -}): string { - function getImportPath(absolutePath: string) { - let relativePath = path.relative( - path.dirname(generatedRouteTreePath), - absolutePath, - ) - - if (!relativePath.startsWith('.')) { - relativePath = './' + relativePath - } - - // convert to POSIX-style for ESM imports (important on Windows) - relativePath = relativePath.split(path.sep).join('/') - return relativePath - } - - const result: Array = [ - `import type { getRouter } from '${getImportPath(routerFilePath)}'`, - ] - if (startFilePath) { - result.push( - `import type { startInstance } from '${getImportPath(startFilePath)}'`, - ) - } - // make sure we import something from start to get the server route declaration merge - else { - result.push( - `import type { createStart } from '@tanstack/${corePluginOpts.framework}-start'`, - ) - } - result.push( - `declare module '@tanstack/${corePluginOpts.framework}-start' { - interface Register { - ssr: true - router: Awaited>`, - ) - if (startFilePath) { - result.push( - ` config: Awaited>`, - ) - } - result.push(` } -}`) - - return result.join('\n') -} - export function tanStackStartRouter( startPluginOpts: TanStackStartInputConfig, getConfig: GetConfigFn, @@ -141,9 +85,9 @@ export function tanStackStartRouter( } } routeTreeFileFooter = [ - moduleDeclaration({ + createRouteTreeModuleDeclaration({ generatedRouteTreePath: getGeneratedRouteTreePath(), - corePluginOpts, + framework: corePluginOpts.framework, startFilePath: resolvedStartConfig.startFilePath, routerFilePath: resolvedStartConfig.routerFilePath, }), diff --git a/packages/start-plugin-core/src/start-router-plugin/route-tree-module-declaration.ts b/packages/start-plugin-core/src/start-router-plugin/route-tree-module-declaration.ts new file mode 100644 index 00000000000..22c665be7be --- /dev/null +++ b/packages/start-plugin-core/src/start-router-plugin/route-tree-module-declaration.ts @@ -0,0 +1,67 @@ +import path from 'pathe' + +type RouteTreeModuleDeclarationOptions = { + generatedRouteTreePath: string + routerFilePath: string + framework: string + startFilePath?: string +} + +/** + * Resolve an import path from the generated route tree file to an absolute file path. + */ +function getImportPath( + generatedRouteTreePath: string, + absolutePath: string, +): string { + let relativePath = path.relative( + path.dirname(generatedRouteTreePath), + absolutePath, + ) + + if (!relativePath.startsWith('.')) { + relativePath = `./${relativePath}` + } + + // Use POSIX import separators for generated module declarations. + return relativePath.split(path.sep).join('/') +} + +/** + * Build the framework-specific Register module augmentation appended to route trees. + */ +export function createRouteTreeModuleDeclaration( + options: RouteTreeModuleDeclarationOptions, +): string { + const result: Array = [ + `import type { getRouter } from '${getImportPath(options.generatedRouteTreePath, options.routerFilePath)}'`, + ] + + if (options.startFilePath) { + result.push( + `import type { startInstance } from '${getImportPath(options.generatedRouteTreePath, options.startFilePath)}'`, + ) + } else { + result.push( + `import type { createStart } from '@tanstack/${options.framework}-start'`, + ) + } + + result.push( + `declare module '@tanstack/${options.framework}-start' { + interface Register { + ssr: true + router: Awaited>`, + ) + + if (options.startFilePath) { + result.push( + ` config: Awaited>`, + ) + } + + result.push(` } +}`) + + return result.join('\n') +} diff --git a/packages/start-plugin-core/tests/createServerFn/createServerFn.test.ts b/packages/start-plugin-core/tests/createServerFn/createServerFn.test.ts index a99eed371ff..4ab0dbabd6d 100644 --- a/packages/start-plugin-core/tests/createServerFn/createServerFn.test.ts +++ b/packages/start-plugin-core/tests/createServerFn/createServerFn.test.ts @@ -128,7 +128,10 @@ describe('createServerFn compiles correctly', async () => { // Server caller: no second argument (implementation from extracted chunk) expect(compiledResultServerCaller!.code).toMatchInlineSnapshot(` "import { createSsrRpc } from '@tanstack/react-start/ssr-rpc'; + import { myServerFn_createServerFn_handler } from "./test.ts?tss-serverfn-split"; import { createServerFn } from '@tanstack/react-start'; + globalThis.__tssServerFnHandlers = globalThis.__tssServerFnHandlers || []; + globalThis.__tssServerFnHandlers.push(myServerFn_createServerFn_handler); const myServerFn = createServerFn().handler(createSsrRpc("eyJmaWxlIjoiL0BpZC9zcmMvdGVzdC50cz90c3Mtc2VydmVyZm4tc3BsaXQiLCJleHBvcnQiOiJteVNlcnZlckZuX2NyZWF0ZVNlcnZlckZuX2hhbmRsZXIifQ", () => import("/test/src/test.ts?tss-serverfn-split").then(m => m["myServerFn_createServerFn_handler"])));" `) @@ -184,7 +187,10 @@ describe('createServerFn compiles correctly', async () => { expect(compiledResultServerCaller!.code).toMatchInlineSnapshot(` "import { createSsrRpc } from '@tanstack/react-start/ssr-rpc'; + import { exportedFn_createServerFn_handler, nonExportedFn_createServerFn_handler } from "./test.ts?tss-serverfn-split"; import { createServerFn } from '@tanstack/react-start'; + globalThis.__tssServerFnHandlers = globalThis.__tssServerFnHandlers || []; + globalThis.__tssServerFnHandlers.push(exportedFn_createServerFn_handler, nonExportedFn_createServerFn_handler); export const exportedFn = createServerFn().handler(createSsrRpc("eyJmaWxlIjoiL0BpZC9zcmMvdGVzdC50cz90c3Mtc2VydmVyZm4tc3BsaXQiLCJleHBvcnQiOiJleHBvcnRlZEZuX2NyZWF0ZVNlcnZlckZuX2hhbmRsZXIifQ", () => import("/test/src/test.ts?tss-serverfn-split").then(m => m["exportedFn_createServerFn_handler"]))); const nonExportedFn = createServerFn().handler(createSsrRpc("eyJmaWxlIjoiL0BpZC9zcmMvdGVzdC50cz90c3Mtc2VydmVyZm4tc3BsaXQiLCJleHBvcnQiOiJub25FeHBvcnRlZEZuX2NyZWF0ZVNlcnZlckZuX2hhbmRsZXIifQ", () => import("/test/src/test.ts?tss-serverfn-split").then(m => m["nonExportedFn_createServerFn_handler"])));" `) diff --git a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnDestructured.tsx b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnDestructured.tsx index b8dbdcaae58..626d0f3ae7a 100644 --- a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnDestructured.tsx +++ b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnDestructured.tsx @@ -1,6 +1,9 @@ import { createSsrRpc } from '@tanstack/react-start/ssr-rpc'; +import { withUseServer_createServerFn_handler, withArrowFunction_createServerFn_handler, withArrowFunctionAndFunction_createServerFn_handler, withoutUseServer_createServerFn_handler, withVariable_createServerFn_handler, withZodValidator_createServerFn_handler, withValidatorFn_createServerFn_handler } from "./test.ts?tss-serverfn-split"; import { createServerFn } from '@tanstack/react-start'; import { z } from 'zod'; +globalThis.__tssServerFnHandlers = globalThis.__tssServerFnHandlers || []; +globalThis.__tssServerFnHandlers.push(withUseServer_createServerFn_handler, withArrowFunction_createServerFn_handler, withArrowFunctionAndFunction_createServerFn_handler, withoutUseServer_createServerFn_handler, withVariable_createServerFn_handler, withZodValidator_createServerFn_handler, withValidatorFn_createServerFn_handler); export const withUseServer = createServerFn({ method: 'GET' }).handler(createSsrRpc("eyJmaWxlIjoiL0BpZC9zcmMvdGVzdC50cz90c3Mtc2VydmVyZm4tc3BsaXQiLCJleHBvcnQiOiJ3aXRoVXNlU2VydmVyX2NyZWF0ZVNlcnZlckZuX2hhbmRsZXIifQ", () => import("/test/src/test.ts?tss-serverfn-split").then(m => m["withUseServer_createServerFn_handler"]))); diff --git a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnDestructuredRename.tsx b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnDestructuredRename.tsx index 815702d5d8f..cadb811cf37 100644 --- a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnDestructuredRename.tsx +++ b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnDestructuredRename.tsx @@ -1,5 +1,8 @@ import { createSsrRpc } from '@tanstack/react-start/ssr-rpc'; +import { withUseServer_createServerFn_handler, withoutUseServer_createServerFn_handler, withVariable_createServerFn_handler, withZodValidator_createServerFn_handler } from "./test.ts?tss-serverfn-split"; import { createServerFn as serverFn } from '@tanstack/react-start'; +globalThis.__tssServerFnHandlers = globalThis.__tssServerFnHandlers || []; +globalThis.__tssServerFnHandlers.push(withUseServer_createServerFn_handler, withoutUseServer_createServerFn_handler, withVariable_createServerFn_handler, withZodValidator_createServerFn_handler); export const withUseServer = serverFn({ method: 'GET' }).handler(createSsrRpc("eyJmaWxlIjoiL0BpZC9zcmMvdGVzdC50cz90c3Mtc2VydmVyZm4tc3BsaXQiLCJleHBvcnQiOiJ3aXRoVXNlU2VydmVyX2NyZWF0ZVNlcnZlckZuX2hhbmRsZXIifQ", () => import("/test/src/test.ts?tss-serverfn-split").then(m => m["withUseServer_createServerFn_handler"]))); diff --git a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnStarImport.tsx b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnStarImport.tsx index 2f68c8c276d..6452aa604d2 100644 --- a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnStarImport.tsx +++ b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnStarImport.tsx @@ -1,5 +1,8 @@ import { createSsrRpc } from '@tanstack/react-start/ssr-rpc'; +import { withUseServer_createServerFn_handler, withoutUseServer_createServerFn_handler, withVariable_createServerFn_handler, withZodValidator_createServerFn_handler } from "./test.ts?tss-serverfn-split"; import * as TanStackStart from '@tanstack/react-start'; +globalThis.__tssServerFnHandlers = globalThis.__tssServerFnHandlers || []; +globalThis.__tssServerFnHandlers.push(withUseServer_createServerFn_handler, withoutUseServer_createServerFn_handler, withVariable_createServerFn_handler, withZodValidator_createServerFn_handler); export const withUseServer = TanStackStart.createServerFn({ method: 'GET' }).handler(createSsrRpc("eyJmaWxlIjoiL0BpZC9zcmMvdGVzdC50cz90c3Mtc2VydmVyZm4tc3BsaXQiLCJleHBvcnQiOiJ3aXRoVXNlU2VydmVyX2NyZWF0ZVNlcnZlckZuX2hhbmRsZXIifQ", () => import("/test/src/test.ts?tss-serverfn-split").then(m => m["withUseServer_createServerFn_handler"]))); diff --git a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnValidator.tsx b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnValidator.tsx index ba0b4b577e2..b6a5abb5eb4 100644 --- a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnValidator.tsx +++ b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/createServerFnValidator.tsx @@ -1,6 +1,9 @@ import { createSsrRpc } from '@tanstack/react-start/ssr-rpc'; +import { withUseServer_createServerFn_handler } from "./test.ts?tss-serverfn-split"; import { createServerFn } from '@tanstack/react-start'; import { z } from 'zod'; +globalThis.__tssServerFnHandlers = globalThis.__tssServerFnHandlers || []; +globalThis.__tssServerFnHandlers.push(withUseServer_createServerFn_handler); export const withUseServer = createServerFn({ method: 'GET' }).inputValidator(z.number()).handler(createSsrRpc("eyJmaWxlIjoiL0BpZC9zcmMvdGVzdC50cz90c3Mtc2VydmVyZm4tc3BsaXQiLCJleHBvcnQiOiJ3aXRoVXNlU2VydmVyX2NyZWF0ZVNlcnZlckZuX2hhbmRsZXIifQ", () => import("/test/src/test.ts?tss-serverfn-split").then(m => m["withUseServer_createServerFn_handler"]))); \ No newline at end of file diff --git a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/factory.tsx b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/factory.tsx index dbc4e764d9a..874d4c8fd90 100644 --- a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/factory.tsx +++ b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/factory.tsx @@ -1,5 +1,8 @@ import { createSsrRpc } from '@tanstack/react-start/ssr-rpc'; +import { myAuthedFn_createServerFn_handler, deleteUserFn_createServerFn_handler } from "./test.ts?tss-serverfn-split"; import { createServerFn, createMiddleware } from '@tanstack/react-start'; +globalThis.__tssServerFnHandlers = globalThis.__tssServerFnHandlers || []; +globalThis.__tssServerFnHandlers.push(myAuthedFn_createServerFn_handler, deleteUserFn_createServerFn_handler); const authMiddleware = createMiddleware({ type: 'function' }).server(({ diff --git a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/isomorphic-fns.tsx b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/isomorphic-fns.tsx index 02675e9e956..aa574d6ff56 100644 --- a/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/isomorphic-fns.tsx +++ b/packages/start-plugin-core/tests/createServerFn/snapshots/server-caller/isomorphic-fns.tsx @@ -1,7 +1,10 @@ import { createSsrRpc } from '@tanstack/react-start/ssr-rpc'; +import { getServerEnv_createServerFn_handler, getServerEcho_createServerFn_handler } from "./test.ts?tss-serverfn-split"; import { createFileRoute } from '@tanstack/react-router'; import { createIsomorphicFn, createServerFn } from '@tanstack/react-start'; import { useState } from 'react'; +globalThis.__tssServerFnHandlers = globalThis.__tssServerFnHandlers || []; +globalThis.__tssServerFnHandlers.push(getServerEnv_createServerFn_handler, getServerEcho_createServerFn_handler); const getEnv = createIsomorphicFn().server(() => 'server').client(() => 'client'); const getServerEnv = createServerFn().handler(createSsrRpc("eyJmaWxlIjoiL0BpZC9zcmMvdGVzdC50cz90c3Mtc2VydmVyZm4tc3BsaXQiLCJleHBvcnQiOiJnZXRTZXJ2ZXJFbnZfY3JlYXRlU2VydmVyRm5faGFuZGxlciJ9", () => import("/test/src/test.ts?tss-serverfn-split").then(m => m["getServerEnv_createServerFn_handler"]))); const getEcho = createIsomorphicFn().server((input: string) => 'server received ' + input).client(input => 'client received ' + input); diff --git a/packages/start-plugin-core/vite.config.ts b/packages/start-plugin-core/vite.config.ts index d6650eebf65..dad7d4279ff 100644 --- a/packages/start-plugin-core/vite.config.ts +++ b/packages/start-plugin-core/vite.config.ts @@ -14,7 +14,13 @@ const config = defineConfig({ export default mergeConfig( config, tanstackViteConfig({ - entry: './src/index.ts', + entry: [ + './src/index.ts', + './src/rsbuild/index.ts', + './src/rsbuild/start-compiler-loader.ts', + './src/rsbuild/route-tree-loader.ts', + './src/rsbuild/start-storage-context-stub.ts', + ], srcDir: './src', outDir: './dist', cjs: false, diff --git a/packages/start-server-core/src/router-manifest.ts b/packages/start-server-core/src/router-manifest.ts index ceec7eaf4e8..925ae7893bf 100644 --- a/packages/start-server-core/src/router-manifest.ts +++ b/packages/start-server-core/src/router-manifest.ts @@ -20,7 +20,7 @@ const ROUTER_BASEPATH = process.env.TSS_ROUTER_BASEPATH || '/' export async function getStartManifest( matchedRoutes?: ReadonlyArray, ): Promise { - const { tsrStartManifest } = await import('tanstack-start-manifest:v') + const { tsrStartManifest } = await import('tanstack-start-manifest') const startManifest = tsrStartManifest() const rootRoute = (startManifest.routes[rootRouteId] = @@ -45,7 +45,7 @@ export async function getStartManifest( // build the client entry script tag after URL transforms are applied) let injectedHeadScripts: string | undefined if (process.env.TSS_DEV_SERVER === 'true') { - const mod = await import('tanstack-start-injected-head-scripts:v') + const mod = await import('tanstack-start-injected-head-scripts') if (mod.injectedHeadScripts) { injectedHeadScripts = mod.injectedHeadScripts } diff --git a/packages/start-server-core/src/server-functions-handler.ts b/packages/start-server-core/src/server-functions-handler.ts index fdc6fb06723..54d5d4c6e96 100644 --- a/packages/start-server-core/src/server-functions-handler.ts +++ b/packages/start-server-core/src/server-functions-handler.ts @@ -50,6 +50,10 @@ export const handleServerAction = async ({ const url = new URL(request.url) const action = await getServerFnById(serverFnId, { fromClient: true }) + const executableAction = + typeof (action as any)?.__executeServer === 'function' + ? (action as any).__executeServer.bind(action) + : action const isServerFn = request.headers.get('x-tsr-serverFn') === 'true' @@ -111,7 +115,8 @@ export const handleServerAction = async ({ } } - return await action(params) + const result = await executableAction(params) + return result } // Get requests use the query string @@ -129,7 +134,8 @@ export const handleServerAction = async ({ payload.context = safeObjectMerge(context, payload.context) payload.method = methodUpper // Send it through! - return await action(payload) + const result = await executableAction(payload) + return result } if (methodLower !== 'post') { @@ -144,7 +150,8 @@ export const handleServerAction = async ({ const payload = jsonPayload ? parsePayload(jsonPayload) : {} payload.context = safeObjectMerge(payload.context, context) payload.method = methodUpper - return await action(payload) + const result = await executableAction(payload) + return result })() const unwrapped = res.result || res.error @@ -157,8 +164,18 @@ export const handleServerAction = async ({ return unwrapped } - if (unwrapped instanceof Response) { - if (isRedirect(unwrapped)) { + const redirectOptions = getRedirectOptions(unwrapped) + if (redirectOptions) { + return Response.json( + { ...redirectOptions, isSerializedRedirect: true }, + { headers: getResponseHeaders(unwrapped) }, + ) + } + + if (isResponseLike(unwrapped)) { + const isRedirectResponse = + isRedirect(unwrapped) || Boolean(redirectOptions) + if (isRedirectResponse) { return unwrapped } unwrapped.headers.set(X_TSS_RAW_RESPONSE, 'true') @@ -305,7 +322,19 @@ export const handleServerAction = async ({ }) } } catch (error: any) { - if (error instanceof Response) { + if (isResponseLike(error)) { + const redirectOptions = getRedirectOptions(error) + if (redirectOptions && isServerFn) { + return Response.json( + { ...redirectOptions, isSerializedRedirect: true }, + { headers: getResponseHeaders(error) }, + ) + } + const isRedirectResponse = isRedirect(error) || Boolean(redirectOptions) + if (isRedirectResponse) { + return error + } + error.headers.set(X_TSS_RAW_RESPONSE, 'true') return error } // else if ( @@ -365,3 +394,42 @@ function isNotFoundResponse(error: any) { }, }) } + +function isResponseLike(value: unknown): value is Response { + if (value instanceof Response) { + return true + } + if (value === null || typeof value !== 'object') { + return false + } + if (!('status' in value) || !('headers' in value)) { + return false + } + const headers = (value as { headers?: { get?: unknown; set?: unknown } }) + .headers + return ( + !!headers && + typeof headers.get === 'function' && + typeof headers.set === 'function' + ) +} + +function getRedirectOptions( + value: unknown, +): Record | undefined { + if (!isRedirect(value)) { + return undefined + } + return value.options as Record +} + +function getResponseHeaders(value: unknown): Headers | undefined { + if (value === null || typeof value !== 'object') { + return undefined + } + if (!('headers' in value)) { + return undefined + } + const headers = (value as { headers?: Headers }).headers + return headers +} diff --git a/packages/start-server-core/src/tanstack-start.d.ts b/packages/start-server-core/src/tanstack-start.d.ts index 106375b4da9..49fb61c849a 100644 --- a/packages/start-server-core/src/tanstack-start.d.ts +++ b/packages/start-server-core/src/tanstack-start.d.ts @@ -1,4 +1,4 @@ -declare module 'tanstack-start-manifest:v' { +declare module 'tanstack-start-manifest' { import type { Manifest } from '@tanstack/router-core' export const tsrStartManifest: () => Manifest & { clientEntry: string } @@ -18,6 +18,6 @@ declare module '#tanstack-start-server-fn-resolver' { ): Promise } -declare module 'tanstack-start-injected-head-scripts:v' { +declare module 'tanstack-start-injected-head-scripts' { export const injectedHeadScripts: string | undefined } diff --git a/packages/start-server-core/src/virtual-modules.ts b/packages/start-server-core/src/virtual-modules.ts index 7280feacf26..3fd43ba4e9d 100644 --- a/packages/start-server-core/src/virtual-modules.ts +++ b/packages/start-server-core/src/virtual-modules.ts @@ -1,5 +1,5 @@ export const VIRTUAL_MODULES = { - startManifest: 'tanstack-start-manifest:v', - injectedHeadScripts: 'tanstack-start-injected-head-scripts:v', + startManifest: 'tanstack-start-manifest', + injectedHeadScripts: 'tanstack-start-injected-head-scripts', serverFnResolver: '#tanstack-start-server-fn-resolver', } as const diff --git a/packages/vue-start/package.json b/packages/vue-start/package.json index d515d54c85f..d732b138200 100644 --- a/packages/vue-start/package.json +++ b/packages/vue-start/package.json @@ -74,6 +74,12 @@ "default": "./dist/esm/plugin/vite.js" } }, + "./plugin/rsbuild": { + "import": { + "types": "./dist/esm/plugin/rsbuild.d.ts", + "default": "./dist/esm/plugin/rsbuild.js" + } + }, "./server-entry": { "import": { "types": "./dist/default-entry/esm/server.d.ts", @@ -106,7 +112,13 @@ "vue": "^3.5.25" }, "peerDependencies": { + "@rsbuild/core": ">=1.0.0", "vue": "^3.3.0", "vite": ">=7.0.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + } } } diff --git a/packages/vue-start/src/plugin/rsbuild.ts b/packages/vue-start/src/plugin/rsbuild.ts new file mode 100644 index 00000000000..55b4b13323d --- /dev/null +++ b/packages/vue-start/src/plugin/rsbuild.ts @@ -0,0 +1,35 @@ +import { fileURLToPath } from 'node:url' +import path from 'pathe' +import { TanStackStartRsbuildPluginCore } from '@tanstack/start-plugin-core/rsbuild' +import type { TanStackStartInputConfig } from '@tanstack/start-plugin-core' + +type RsbuildPlugin = { + name: string + setup: (api: any) => void +} + +const currentDir = path.dirname(fileURLToPath(import.meta.url)) +const defaultEntryDir = path.resolve( + currentDir, + '..', + '..', + 'plugin', + 'default-entry', +) +const defaultEntryPaths = { + client: path.resolve(defaultEntryDir, 'client.tsx'), + server: path.resolve(defaultEntryDir, 'server.ts'), + start: path.resolve(defaultEntryDir, 'start.ts'), +} + +export function tanstackStart( + options?: TanStackStartInputConfig, +): Array { + return TanStackStartRsbuildPluginCore( + { + framework: 'vue', + defaultEntryPaths, + }, + options, + ) +} diff --git a/packages/vue-start/vite.config.ts b/packages/vue-start/vite.config.ts index 3e1088b3ef3..b9c0ec1e262 100644 --- a/packages/vue-start/vite.config.ts +++ b/packages/vue-start/vite.config.ts @@ -33,6 +33,7 @@ export default mergeConfig( './src/server-rpc.ts', './src/server.tsx', './src/plugin/vite.ts', + './src/plugin/rsbuild.ts', ], externalDeps: ['@tanstack/vue-start-client', '@tanstack/vue-start-server'], cjs: false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9453614209..5a264b04406 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,7 +111,7 @@ importers: version: 4.0.3 nx: specifier: 22.3.3 - version: 22.3.3(@swc/core@1.10.15(@swc/helpers@0.5.15)) + version: 22.3.3(@swc/core@1.10.15(@swc/helpers@0.5.18)) prettier: specifier: ^3.8.0 version: 3.8.0 @@ -1119,6 +1119,12 @@ importers: '@playwright/test': specifier: ^1.57.0 version: 1.57.0 + '@rsbuild/core': + specifier: ^1.2.4 + version: 1.2.4 + '@rsbuild/plugin-react': + specifier: ^1.1.0 + version: 1.1.0(@rsbuild/core@1.2.4) '@tailwindcss/vite': specifier: ^4.1.18 version: 4.1.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) @@ -1594,7 +1600,7 @@ importers: version: 4.7.0(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) nitro: specifier: 3.0.1-alpha.2 - version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rolldown@1.0.0-rc.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) sass: specifier: ^1.97.2 version: 1.97.2 @@ -1716,7 +1722,7 @@ importers: version: 9.2.1 nitro: specifier: npm:nitro-nightly@latest - version: nitro-nightly@3.0.1-20260123-195236-c6b834cd(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + version: nitro-nightly@3.0.1-20260206-171553-bc737c0c(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) typescript: specifier: ^5.7.2 version: 5.9.3 @@ -1779,6 +1785,89 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.9.2)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + e2e/react-start/module-federation-rsbuild-host: + dependencies: + '@tanstack/react-router': + specifier: workspace:* + version: link:../../../packages/react-router + '@tanstack/react-start': + specifier: workspace:* + version: link:../../../packages/react-start + express: + specifier: ^5.1.0 + version: 5.1.0 + react: + specifier: ^19.2.3 + version: 19.2.3 + react-dom: + specifier: ^19.2.3 + version: 19.2.3(react@19.2.3) + devDependencies: + '@module-federation/node': + specifier: ^2.7.32 + version: 2.7.32(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + '@module-federation/rsbuild-plugin': + specifier: https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd + version: https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + '@playwright/test': + specifier: ^1.57.0 + version: 1.57.0 + '@rsbuild/core': + specifier: ^1.3.21 + version: 1.7.3 + '@rsbuild/plugin-react': + specifier: ^1.1.0 + version: 1.1.0(@rsbuild/core@1.7.3) + '@tanstack/router-e2e-utils': + specifier: workspace:^ + version: link:../../e2e-utils + '@types/node': + specifier: 25.0.9 + version: 25.0.9 + '@types/react': + specifier: ^19.2.8 + version: 19.2.8 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.8) + srvx: + specifier: ^0.11.2 + version: 0.11.2 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + + e2e/react-start/module-federation-rsbuild-remote: + dependencies: + react: + specifier: ^19.2.3 + version: 19.2.3 + react-dom: + specifier: ^19.2.3 + version: 19.2.3(react@19.2.3) + devDependencies: + '@module-federation/node': + specifier: ^2.7.32 + version: 2.7.32(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + '@module-federation/rsbuild-plugin': + specifier: https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd + version: https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + '@rsbuild/core': + specifier: ^1.3.21 + version: 1.7.3 + '@rsbuild/plugin-react': + specifier: ^1.1.0 + version: 1.1.0(@rsbuild/core@1.7.3) + '@types/react': + specifier: ^19.2.8 + version: 19.2.8 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.8) + typescript: + specifier: ^5.7.2 + version: 5.9.3 + e2e/react-start/query-integration: dependencies: '@tanstack/react-query': @@ -4825,7 +4914,7 @@ importers: version: 1.0.6(@rsbuild/core@1.2.4) '@rsbuild/plugin-vue': specifier: ^1.2.2 - version: 1.2.2(@rsbuild/core@1.2.4)(@swc/core@1.10.15(@swc/helpers@0.5.15))(vue@3.5.25(typescript@5.9.2)) + version: 1.2.2(@rsbuild/core@1.2.4)(@swc/core@1.10.15(@swc/helpers@0.5.18))(vue@3.5.25(typescript@5.9.2)) '@rsbuild/plugin-vue-jsx': specifier: ^1.1.1 version: 1.1.1(@babel/core@7.28.5)(@rsbuild/core@1.2.4) @@ -4880,7 +4969,7 @@ importers: version: 1.0.6(@rsbuild/core@1.2.4) '@rsbuild/plugin-vue': specifier: ^1.2.2 - version: 1.2.2(@rsbuild/core@1.2.4)(@swc/core@1.10.15(@swc/helpers@0.5.15))(vue@3.5.25(typescript@5.9.2)) + version: 1.2.2(@rsbuild/core@1.2.4)(@swc/core@1.10.15(@swc/helpers@0.5.18))(vue@3.5.25(typescript@5.9.2)) '@rsbuild/plugin-vue-jsx': specifier: ^1.1.1 version: 1.1.1(@babel/core@7.28.5)(@rsbuild/core@1.2.4) @@ -7209,7 +7298,7 @@ importers: devDependencies: '@swc/core': specifier: ^1.10.15 - version: 1.10.15(@swc/helpers@0.5.15) + version: 1.10.15(@swc/helpers@0.5.18) '@tanstack/router-plugin': specifier: workspace:* version: link:../../../packages/router-plugin @@ -7221,16 +7310,16 @@ importers: version: 19.2.3(@types/react@19.2.8) html-webpack-plugin: specifier: ^5.6.3 - version: 5.6.3(@rspack/core@1.2.2(@swc/helpers@0.5.15))(webpack@5.97.1) + version: 5.6.3(@rspack/core@1.7.6(@swc/helpers@0.5.18))(webpack@5.97.1) swc-loader: specifier: ^0.2.6 - version: 0.2.6(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack@5.97.1) + version: 0.2.6(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack@5.97.1) typescript: specifier: ^5.7.2 version: 5.8.2 webpack: specifier: ^5.97.1 - version: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + version: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) webpack-cli: specifier: ^5.1.4 version: 5.1.4(webpack-dev-server@5.2.0)(webpack@5.97.1) @@ -7955,7 +8044,7 @@ importers: version: 4.6.0(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) nitro: specifier: 3.0.1-alpha.2 - version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rolldown@1.0.0-rc.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -8738,6 +8827,83 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.8.2)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + examples/react/start-module-federation-rsbuild-host: + dependencies: + '@tanstack/react-router': + specifier: workspace:* + version: link:../../../packages/react-router + '@tanstack/react-start': + specifier: workspace:* + version: link:../../../packages/react-start + express: + specifier: ^5.1.0 + version: 5.1.0 + react: + specifier: ^19.2.3 + version: 19.2.3 + react-dom: + specifier: ^19.2.3 + version: 19.2.3(react@19.2.3) + devDependencies: + '@module-federation/node': + specifier: ^2.7.32 + version: 2.7.32(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + '@module-federation/rsbuild-plugin': + specifier: https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd + version: https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + '@rsbuild/core': + specifier: ^1.3.21 + version: 1.7.3 + '@rsbuild/plugin-react': + specifier: ^1.1.0 + version: 1.1.0(@rsbuild/core@1.7.3) + '@types/node': + specifier: 25.0.9 + version: 25.0.9 + '@types/react': + specifier: ^19.2.8 + version: 19.2.8 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.8) + srvx: + specifier: ^0.11.2 + version: 0.11.2 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + + examples/react/start-module-federation-rsbuild-remote: + dependencies: + react: + specifier: ^19.2.3 + version: 19.2.3 + react-dom: + specifier: ^19.2.3 + version: 19.2.3(react@19.2.3) + devDependencies: + '@module-federation/node': + specifier: ^2.7.32 + version: 2.7.32(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + '@module-federation/rsbuild-plugin': + specifier: https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd + version: https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + '@rsbuild/core': + specifier: ^1.3.21 + version: 1.7.3 + '@rsbuild/plugin-react': + specifier: ^1.1.0 + version: 1.1.0(@rsbuild/core@1.7.3) + '@types/react': + specifier: ^19.2.8 + version: 19.2.8 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.8) + typescript: + specifier: ^5.7.2 + version: 5.9.3 + examples/react/start-streaming-data-from-server-functions: dependencies: '@tanstack/react-router': @@ -10242,16 +10408,16 @@ importers: version: 1.9.10(@babel/core@7.28.5)(solid-js@1.9.10) css-loader: specifier: ^7.1.2 - version: 7.1.2(@rspack/core@1.2.2(@swc/helpers@0.5.15))(webpack@5.97.1) + version: 7.1.2(@rspack/core@1.7.6(@swc/helpers@0.5.18))(webpack@5.97.1) html-webpack-plugin: specifier: ^5.6.3 - version: 5.6.3(@rspack/core@1.2.2(@swc/helpers@0.5.15))(webpack@5.97.1) + version: 5.6.3(@rspack/core@1.7.6(@swc/helpers@0.5.18))(webpack@5.97.1) postcss: specifier: ^8.5.6 version: 8.5.6 postcss-loader: specifier: ^8.2.0 - version: 8.2.0(@rspack/core@1.2.2(@swc/helpers@0.5.15))(postcss@8.5.6)(typescript@5.9.2)(webpack@5.97.1) + version: 8.2.0(@rspack/core@1.7.6(@swc/helpers@0.5.18))(postcss@8.5.6)(typescript@5.9.2)(webpack@5.97.1) style-loader: specifier: ^4.0.0 version: 4.0.0(webpack@5.97.1) @@ -10260,7 +10426,7 @@ importers: version: 5.9.2 webpack: specifier: ^5.97.1 - version: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + version: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) webpack-cli: specifier: ^5.1.4 version: 5.1.4(webpack-dev-server@5.2.0)(webpack@5.97.1) @@ -10494,7 +10660,7 @@ importers: version: 25.0.9 nitro: specifier: 3.0.1-alpha.2 - version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rolldown@1.0.0-rc.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -10721,7 +10887,7 @@ importers: version: 25.0.9 nitro: specifier: 3.0.1-alpha.2 - version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rolldown@1.0.0-rc.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -11538,7 +11704,7 @@ importers: dependencies: nitropack: specifier: ^2.13.1 - version: 2.13.1(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(encoding@0.1.13)(mysql2@3.15.3) + version: 2.13.1(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(encoding@0.1.13)(mysql2@3.15.3)(rolldown@1.0.0-rc.3) pathe: specifier: ^2.0.3 version: 2.0.3 @@ -11644,6 +11810,9 @@ importers: packages/react-start: dependencies: + '@rsbuild/core': + specifier: '>=1.0.0' + version: 1.2.4 '@tanstack/react-router': specifier: workspace:* version: link:../react-router @@ -11926,7 +12095,7 @@ importers: version: 2.11.10(@testing-library/jest-dom@6.6.3)(solid-js@1.9.10)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) webpack: specifier: '>=5.92.0' - version: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15)) + version: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18)) zod: specifier: ^3.24.2 version: 3.25.57 @@ -12097,6 +12266,9 @@ importers: packages/solid-start: dependencies: + '@rsbuild/core': + specifier: '>=1.0.0' + version: 1.2.4 '@tanstack/solid-router': specifier: workspace:* version: link:../solid-router @@ -12228,6 +12400,9 @@ importers: '@rolldown/pluginutils': specifier: 1.0.0-beta.40 version: 1.0.0-beta.40 + '@rsbuild/core': + specifier: '>=1.0.0' + version: 1.2.4 '@tanstack/router-core': specifier: workspace:* version: link:../router-core @@ -12264,6 +12439,9 @@ importers: ufo: specifier: ^1.5.4 version: 1.6.1 + unplugin: + specifier: ^2.3.11 + version: 2.3.11 vitefu: specifier: ^1.1.1 version: 1.1.1(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) @@ -12300,7 +12478,7 @@ importers: version: link:../start-storage-context h3-v2: specifier: npm:h3@2.0.1-rc.14 - version: h3@2.0.1-rc.14(crossws@0.4.3(srvx@0.11.2)) + version: h3@2.0.1-rc.14(crossws@0.4.4(srvx@0.11.2)) seroval: specifier: ^1.4.2 version: 1.4.2 @@ -12460,6 +12638,9 @@ importers: packages/vue-start: dependencies: + '@rsbuild/core': + specifier: '>=1.0.0' + version: 1.2.4 '@tanstack/start-client-core': specifier: workspace:* version: link:../start-client-core @@ -14878,21 +15059,247 @@ packages: '@minimistjs/subarg@1.0.0': resolution: {integrity: sha512-Q/ONBiM2zNeYUy0mVSO44mWWKYM3UHuEK43PKIOzJCbvUnPoMH1K+gk3cf1kgnCVJFlWmddahQQCmrmBGlk9jQ==} + '@module-federation/bridge-react-webpack-plugin@2.0.1': + resolution: {integrity: sha512-D7LMW5EMAJShOMR1aZDAJ6s+MdsYDHaQyJADLQ3LaY0sne/BkVqkPikUwcO1IwOwKbXjYsDlQVOEvk9wZVRFhA==} + + '@module-federation/bridge-react-webpack-plugin@https://pkg.pr.new/module-federation/core/@module-federation/bridge-react-webpack-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-3H9DFdgTBZaJXSyRG+2atAs1vzHpmdN1kjvL0qtNlXnGD0doP23t5tS6fwXZWG0HITVCp5+2GffWL0CYcKEQGw==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/bridge-react-webpack-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + + '@module-federation/cli@2.0.1': + resolution: {integrity: sha512-2SL5Y8iODNX10y9T3CBLhHjSXo4afnA1BK82m4sNfZebuVO+o34bxewqwod9xfWq9xhTZmOSFZ+n+lgTKRv+CQ==} + engines: {node: '>=16.0.0'} + hasBin: true + + '@module-federation/cli@https://pkg.pr.new/module-federation/core/@module-federation/cli@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-tZKcV0//T5UPiQ2akTD2CBD1PcTPzUd0eMBsojEljqr3d1FZ8bBzr6isfxaORcuFUhInFYRF8w1WvhuLB8XVBg==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/cli@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + engines: {node: '>=16.0.0'} + hasBin: true + + '@module-federation/data-prefetch@2.0.1': + resolution: {integrity: sha512-Kq0P1OABGt6QAvs6TaE/zY9Ut9Y/oJFrzoSF3eWaCYbUAr2KD2SpTyMsPz4ssBzjeKXTgimugh6tHHd6mpCBIQ==} + peerDependencies: + react: ^19.2.3 + react-dom: ^19.2.3 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + + '@module-federation/data-prefetch@https://pkg.pr.new/module-federation/core/@module-federation/data-prefetch@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-Wip3+zxeTxg/ed2ACRJdDxhzLmTqyOGiYZEc04t17YNDV5xqHgiXQIFcAvUlkuM1qU/Z7cuOFpac7RlIXoS0wQ==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/data-prefetch@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + peerDependencies: + react: ^19.2.3 + react-dom: ^19.2.3 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + + '@module-federation/dts-plugin@2.0.1': + resolution: {integrity: sha512-PLneTsf1fQS5/RTBedtLAAmCPRdMfIlhfJkOa8QH3WDJaQsqm8Wb3r2cTUBf2aNj/bP3aH/y6Hs9JFB/4x0l5g==} + peerDependencies: + typescript: ^4.9.0 || ^5.0.0 + vue-tsc: '>=1.0.24' + peerDependenciesMeta: + vue-tsc: + optional: true + + '@module-federation/dts-plugin@https://pkg.pr.new/module-federation/core/@module-federation/dts-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-YcVnohC9wAUDv1f67MZo3V7MRew8RG5JgyoCyVgZaU5Nq2pd95BpLkb/AVbRry05lICDoQ4Gfx+41AKUnF4DGA==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/dts-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + peerDependencies: + typescript: ^4.9.0 || ^5.0.0 + vue-tsc: '>=1.0.24' + peerDependenciesMeta: + vue-tsc: + optional: true + + '@module-federation/enhanced@2.0.1': + resolution: {integrity: sha512-EZIARQ/8ScoTP6PV8+E4SsmMYWK4ErrikZJ0G/FX8wvK8mCtdoKatFtvDN9++P6Nl78kN9zHYgAV4AHKdBVjfQ==} + hasBin: true + peerDependencies: + typescript: ^4.9.0 || ^5.0.0 + vue-tsc: '>=1.0.24' + webpack: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + vue-tsc: + optional: true + webpack: + optional: true + + '@module-federation/enhanced@https://pkg.pr.new/module-federation/core/@module-federation/enhanced@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-l6lrvLlDy5njO/EQCH/WgOHbcmWSRSnl7TbL00iXExLTUsVatn72ZTdE3WLnj1vlMEzNN+UYoSAZ13NJeDi+iQ==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/enhanced@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + hasBin: true + peerDependencies: + typescript: ^4.9.0 || ^5.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@0.22.0': + resolution: {integrity: sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==} + '@module-federation/error-codes@0.8.4': resolution: {integrity: sha512-55LYmrDdKb4jt+qr8qE8U3al62ZANp3FhfVaNPOaAmdTh0jHdD8M3yf5HKFlr5xVkVO4eV/F/J2NCfpbh+pEXQ==} + '@module-federation/error-codes@2.0.1': + resolution: {integrity: sha512-2bJF/ft+qL9L6Zvq2t/G9/f/0wFL73cM8/NJ04uyYz9BjIgvx28K5qu8/6+IwgEEKATG7vOhBBVj6wH3S+5ASA==} + + '@module-federation/error-codes@https://pkg.pr.new/module-federation/core/@module-federation/error-codes@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-2bJF/ft+qL9L6Zvq2t/G9/f/0wFL73cM8/NJ04uyYz9BjIgvx28K5qu8/6+IwgEEKATG7vOhBBVj6wH3S+5ASA==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/error-codes@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + + '@module-federation/inject-external-runtime-core-plugin@2.0.1': + resolution: {integrity: sha512-oAA7G+4GCHM+WRYfscR/x4GwCyM9CEqfdD9/x2L6y8mtLWK9anRLKTocsI759AvzXsbT1m3EQ5ki1O6wlwDu3g==} + peerDependencies: + '@module-federation/runtime-tools': 2.0.1 + + '@module-federation/inject-external-runtime-core-plugin@https://pkg.pr.new/module-federation/core/@module-federation/inject-external-runtime-core-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-8Uxxa3r0xF7pXJYS70EdGnvxAQsWQdTIEXjVmLeB+GIlxwp3FGDtTtx5z1GFvyP1OSNTbmL3vxi9hL0wkaywhA==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/inject-external-runtime-core-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + peerDependencies: + '@module-federation/runtime-tools': 2.0.1 + + '@module-federation/managers@2.0.1': + resolution: {integrity: sha512-KR01lSlcYRQ9C6hW2a8CQQtAE0LvfTLgtV/6ZNUTagw8sRfeDln+ggrZsYilKu9zl0i8RPDgpv/kS60o4lcxCQ==} + + '@module-federation/managers@https://pkg.pr.new/module-federation/core/@module-federation/managers@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-EAv1OS960Y+YIkdiRMK6ChJsZ8tExkU1KQ9LgbJ+BaBwZFTBfuQiAjnvpGxUPboLPat+gHRNGHDUJ7Fz1ihtYA==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/managers@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + + '@module-federation/manifest@2.0.1': + resolution: {integrity: sha512-p8nYGjHWp17MsYdW/Vv0ogBDiTTsI1PHWPQbvVIqLQXDqwiesaRSRR1zziECXQoEL8lV5Bs+uSkcaJGhea9P+A==} + + '@module-federation/manifest@https://pkg.pr.new/module-federation/core/@module-federation/manifest@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-4QvReJp1YTP+erYngxwyBcCeC4pYiy78TvNRJaj/mQvipZbqKDGgTQJe/3FMJFP9OA5OVPoBRBl3n9HEX10/DQ==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/manifest@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + + '@module-federation/node@2.7.32': + resolution: {integrity: sha512-hUj5v2GGwpNzl2gaJS4AyzCYRzJBhN8875A+ucKF9tq3jaQb5zpy3izYMISqqbN2q9a7jz3nEUgwAh3pjri+rQ==} + peerDependencies: + webpack: ^5.40.0 + peerDependenciesMeta: + webpack: + optional: true + + '@module-federation/rsbuild-plugin@https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-u8l+89mSkQ6GSHKVmz36b4kWX9DiNnjsWYqW6m2Zs6Y/H2UgDsRyLtby41iX48MTwh3Qq8tWD/fHab/3fsi1rw==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + engines: {node: '>=16.0.0'} + peerDependencies: + '@rsbuild/core': 2.0.0-beta.2 + peerDependenciesMeta: + '@rsbuild/core': + optional: true + + '@module-federation/rspack@2.0.1': + resolution: {integrity: sha512-SAlNE8iclFmzrKtx3/C2GivXYx6nPzx4MgQV01QG/a4LpnLbwlxzdZu3rqQ2swp4NNWT/t/GT7Y+7gfhyVa7mg==} + peerDependencies: + '@rspack/core': ^0.7.0 || ^1.0.0 || ^2.0.0-0 + typescript: ^4.9.0 || ^5.0.0 + vue-tsc: '>=1.0.24' + peerDependenciesMeta: + typescript: + optional: true + vue-tsc: + optional: true + + '@module-federation/rspack@https://pkg.pr.new/module-federation/core/@module-federation/rspack@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-izSzYI7RK0iEyL3n1AIj0gw30ouF0ZZDo7Ziss1snEnfvt3jpAyTWBh1bsSXvuGrjqFA/VUDIeLKoVsgvO0HgQ==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/rspack@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + peerDependencies: + '@rspack/core': ^0.7.0 || ^1.0.0 || ^2.0.0-0 + typescript: ^4.9.0 || ^5.0.0 + vue-tsc: '>=1.0.24' + peerDependenciesMeta: + typescript: + optional: true + vue-tsc: + optional: true + + '@module-federation/runtime-core@0.22.0': + resolution: {integrity: sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==} + + '@module-federation/runtime-core@2.0.1': + resolution: {integrity: sha512-gOuCPSHoQGUGwlxfSTMInFX+QvLxdEWegGGMiLdU5vqbXuva4E9M+kXBBO7/0MkcBPMmVs0wOJGm0XOLeV2f1Q==} + + '@module-federation/runtime-core@https://pkg.pr.new/module-federation/core/@module-federation/runtime-core@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-2Z/+XPNw2zTYfXwbOcBHbnjI90umHSvTC9MlMGWpAPfv5lyjSB9Cg35kSeOhWU5H45U349gNYy5hi5ldmQ0yeA==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/runtime-core@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + + '@module-federation/runtime-tools@0.22.0': + resolution: {integrity: sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==} + '@module-federation/runtime-tools@0.8.4': resolution: {integrity: sha512-fjVOsItJ1u5YY6E9FnS56UDwZgqEQUrWFnouRiPtK123LUuqUI9FH4redZoKWlE1PB0ir1Z3tnqy8eFYzPO38Q==} + '@module-federation/runtime-tools@2.0.1': + resolution: {integrity: sha512-AStdwBtsGB3jIfDg9oP+KyVPsimdaeHsP855gqCxDp1hi2+GKjlZWZx9ThkS8NytVSXSUysxqoUL1ivDoKgcCQ==} + + '@module-federation/runtime-tools@https://pkg.pr.new/module-federation/core/@module-federation/runtime-tools@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-7QbkR+C5Wc4owFMf1JOKtU0TcVAWJRYqkKbfBHHXFQgYakBkYH3m0CzNYwMOGbjAgNRz/vjSrFH1HsKWevDYcg==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/runtime-tools@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + + '@module-federation/runtime@0.22.0': + resolution: {integrity: sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==} + '@module-federation/runtime@0.8.4': resolution: {integrity: sha512-yZeZ7z2Rx4gv/0E97oLTF3V6N25vglmwXGgoeju/W2YjsFvWzVtCDI7zRRb0mJhU6+jmSM8jP1DeQGbea/AiZQ==} + '@module-federation/runtime@2.0.1': + resolution: {integrity: sha512-UQ72P5Oo40dS6vdhHetwTtIsbGciEr+bjoYvDgh1WLPfFlTYd8zo9cLfqaf3juuPfV3cMVARAVPmh16lQYpUGA==} + + '@module-federation/runtime@https://pkg.pr.new/module-federation/core/@module-federation/runtime@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-BOoibg42RWyjiYvsbBPGXik3PKOxf3zRyI6W+McnXgA7GCertLMoxHBPe1amq6VEPEmg25zl+vw3Wl6e2M10jw==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/runtime@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + + '@module-federation/sdk@0.22.0': + resolution: {integrity: sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==} + '@module-federation/sdk@0.8.4': resolution: {integrity: sha512-waABomIjg/5m1rPDBWYG4KUhS5r7OUUY7S+avpaVIY/tkPWB3ibRDKy2dNLLAMaLKq0u+B1qIdEp4NIWkqhqpg==} + '@module-federation/sdk@2.0.1': + resolution: {integrity: sha512-32PwudojGjog51cwpTali7D6ud82oVgsyvOx9JjAzhvXBX96YI4mRsursuWcthDxmigJP9ZvUTXDuRUEDh1OQA==} + + '@module-federation/sdk@https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-ecZrEbQNo4CtsZzzrphNO9eQN+zeOCCpAyAAKD6f37bBOqmX11s5LNXhSbamDjbnwgGsZIoqBBsMs9+uUG5zoQ==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + + '@module-federation/third-party-dts-extractor@2.0.1': + resolution: {integrity: sha512-neKSr6FNUeGRh+YR57l/QZUzPytJXuJx+babF7j5iGJG3FP+kfizr6QD0hgVis5KEoXMVbQ8yyvG0slERizeyw==} + + '@module-federation/third-party-dts-extractor@https://pkg.pr.new/module-federation/core/@module-federation/third-party-dts-extractor@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-neKSr6FNUeGRh+YR57l/QZUzPytJXuJx+babF7j5iGJG3FP+kfizr6QD0hgVis5KEoXMVbQ8yyvG0slERizeyw==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/third-party-dts-extractor@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + + '@module-federation/webpack-bundler-runtime@0.22.0': + resolution: {integrity: sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==} + '@module-federation/webpack-bundler-runtime@0.8.4': resolution: {integrity: sha512-HggROJhvHPUX7uqBD/XlajGygMNM1DG0+4OAkk8MBQe4a18QzrRNzZt6XQbRTSG4OaEoyRWhQHvYD3Yps405tQ==} + '@module-federation/webpack-bundler-runtime@2.0.1': + resolution: {integrity: sha512-u1NId3SF4lHDTmD2CHFEszulmXmIq1TGw9JYvnLx5rKJL7xt3aNxcb1GvkaYbRNVBXhSMjJ75E5LsQlZzyBx9A==} + + '@module-federation/webpack-bundler-runtime@https://pkg.pr.new/module-federation/core/@module-federation/webpack-bundler-runtime@7acb6b2621a4960848ad00f3614dc0bb239775fd': + resolution: {integrity: sha512-y7rqu7WdfWjUY50OOIpemvJaHPRXyfLXCVCPqNtBp/3K3Ytj5z9Zf3NuH1WxNHIhhHQG5AKe8h34lDxjIhF2GQ==, tarball: https://pkg.pr.new/module-federation/core/@module-federation/webpack-bundler-runtime@7acb6b2621a4960848ad00f3614dc0bb239775fd} + version: 2.0.1 + '@motionone/animation@10.18.0': resolution: {integrity: sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==} @@ -15005,6 +15412,9 @@ packages: '@napi-rs/wasm-runtime@0.2.4': resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} + '@napi-rs/wasm-runtime@1.0.7': + resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} + '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} @@ -15350,6 +15760,9 @@ packages: cpu: [x64] os: [win32] + '@oxc-project/types@0.112.0': + resolution: {integrity: sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ==} + '@oxc-transform/binding-android-arm-eabi@0.110.0': resolution: {integrity: sha512-sE9dxvqqAax1YYJ3t7j+h5ZSI9jl6dYuDfngl6ieZUrIy5P89/8JKVgAzgp8o3wQSo7ndpJvYsi1K4ZqrmbP7w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -16388,6 +16801,83 @@ packages: '@remix-run/node-fetch-server@0.8.1': resolution: {integrity: sha512-J1dev372wtJqmqn9U/qbpbZxbJSQrogNN2+Qv1lKlpATpe/WQ9aCZfl/xSb9d2Rgh1IyLSvNxZAXPZxruO6Xig==} + '@rolldown/binding-android-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-0T1k9FinuBZ/t7rZ8jN6OpUKPnUjNdYHoj/cESWrQ3ZraAJ4OMm6z7QjSfCxqj8mOp9kTKc1zHK3kGz5vMu+nQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-JWWLzvcmc/3pe7qdJqPpuPk91SoE/N+f3PcWx/6ZwuyDVyungAEJPvKm/eEldiDdwTmaEzWfIR+HORxYWrCi1A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.3': + resolution: {integrity: sha512-MTakBxfx3tde5WSmbHxuqlDsIW0EzQym+PJYGF4P6lG2NmKzi128OGynoFUqoD5ryCySEY85dug4v+LWGBElIw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': + resolution: {integrity: sha512-jje3oopyOLs7IwfvXoS6Lxnmie5JJO7vW29fdGFu5YGY1EDbVDhD+P9vDihqS5X6fFiqL3ZQZCMBg6jyHkSVww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': + resolution: {integrity: sha512-A0n8P3hdLAaqzSFrQoA42p23ZKBYQOw+8EH5r15Sa9X1kD9/JXe0YT2gph2QTWvdr0CVK2BOXiK6ENfy6DXOag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': + resolution: {integrity: sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': + resolution: {integrity: sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': + resolution: {integrity: sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': + resolution: {integrity: sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-U662UnMETyjT65gFmG9ma+XziENrs7BBnENi/27swZPYagubfHRirXHG2oMl+pEax2WvO7Kb9gHZmMakpYqBHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': + resolution: {integrity: sha512-gekrQ3Q2HiC1T5njGyuUJoGpK/l6B/TNXKed3fZXNf9YRTJn3L5MOZsFBn4bN2+UX+8+7hgdlTcEsexX988G4g==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': + resolution: {integrity: sha512-85y5JifyMgs8m5K2XzR/VDsapKbiFiohl7s5lEj7nmNGO0pkTXE7q6TQScei96BNAsoK7JC3pA7ukA8WRHVJpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': + resolution: {integrity: sha512-a4VUQZH7LxGbUJ3qJ/TzQG8HxdHvf+jOnqf7B7oFx1TEBm+j2KNL2zr5SQ7wHkNAcaPevF6gf9tQnVBnC4mD+A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-beta.19': resolution: {integrity: sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==} @@ -16409,6 +16899,9 @@ packages: '@rolldown/pluginutils@1.0.0-beta.54': resolution: {integrity: sha512-AHgcZ+w7RIRZ65ihSQL8YuoKcpD9Scew4sEeP1BBUT9QdTo6KjwHrZZXjID6nL10fhKessCH6OPany2QKwAwTQ==} + '@rolldown/pluginutils@1.0.0-rc.3': + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} + '@rollup/plugin-alias@6.0.0': resolution: {integrity: sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==} engines: {node: '>=20.19.0'} @@ -16721,6 +17214,11 @@ packages: engines: {node: '>=16.7.0'} hasBin: true + '@rsbuild/core@1.7.3': + resolution: {integrity: sha512-kI1oQvCXbQYxUvQPnDLdjSX4gFsbrFNpuUj6jXEJ7IcJ74Q+n4oeFj74/8tKerhxhe0L90m/ZQfzLeN5ORGA9w==} + engines: {node: '>=18.12.0'} + hasBin: true + '@rsbuild/plugin-babel@1.0.3': resolution: {integrity: sha512-3S/ykXv7KRo0FxVpkjoHFUwB04nKINIET1kuv4xiRaDmeww1Tp0wl9h4u8a7d7gU/4FllyoUflY8TVhci/o05g==} peerDependencies: @@ -16759,49 +17257,101 @@ packages: cpu: [arm64] os: [darwin] + '@rspack/binding-darwin-arm64@1.7.6': + resolution: {integrity: sha512-NZ9AWtB1COLUX1tA9HQQvWpTy07NSFfKBU8A6ylWd5KH8AePZztpNgLLAVPTuNO4CZXYpwcoclf8jG/luJcQdQ==} + cpu: [arm64] + os: [darwin] + '@rspack/binding-darwin-x64@1.2.2': resolution: {integrity: sha512-vG5s7FkEvwrGLfksyDRHwKAHUkhZt1zHZZXJQn4gZKjTBonje8ezdc7IFlDiWpC4S+oBYp73nDWkUzkGRbSdcQ==} cpu: [x64] os: [darwin] + '@rspack/binding-darwin-x64@1.7.6': + resolution: {integrity: sha512-J2g6xk8ZS7uc024dNTGTHxoFzFovAZIRixUG7PiciLKTMP78svbSSWrmW6N8oAsAkzYfJWwQpVgWfFNRHvYxSw==} + cpu: [x64] + os: [darwin] + '@rspack/binding-linux-arm64-gnu@1.2.2': resolution: {integrity: sha512-VykY/kiYOzO8E1nYzfJ9+gQEHxb5B6lt5wa8M6xFi5B6jEGU+OsaGskmAZB9/GFImeFDHxDPvhUalI4R9p8O2Q==} cpu: [arm64] os: [linux] + '@rspack/binding-linux-arm64-gnu@1.7.6': + resolution: {integrity: sha512-eQfcsaxhFrv5FmtaA7+O1F9/2yFDNIoPZzV/ZvqvFz5bBXVc4FAm/1fVpBg8Po/kX1h0chBc7Xkpry3cabFW8w==} + cpu: [arm64] + os: [linux] + '@rspack/binding-linux-arm64-musl@1.2.2': resolution: {integrity: sha512-Z5vAC4wGfXi8XXZ6hs8Q06TYjr3zHf819HB4DI5i4C1eQTeKdZSyoFD0NHFG23bP4NWJffp8KhmoObcy9jBT5Q==} cpu: [arm64] os: [linux] + '@rspack/binding-linux-arm64-musl@1.7.6': + resolution: {integrity: sha512-DfQXKiyPIl7i1yECHy4eAkSmlUzzsSAbOjgMuKn7pudsWf483jg0UUYutNgXSlBjc/QSUp7906Cg8oty9OfwPA==} + cpu: [arm64] + os: [linux] + '@rspack/binding-linux-x64-gnu@1.2.2': resolution: {integrity: sha512-o3pDaL+cH5EeRbDE9gZcdZpBgp5iXvYZBBhe8vZQllYgI4zN5MJEuleV7WplG3UwTXlgZg3Kht4RORSOPn96vg==} cpu: [x64] os: [linux] + '@rspack/binding-linux-x64-gnu@1.7.6': + resolution: {integrity: sha512-NdA+2X3lk2GGrMMnTGyYTzM3pn+zNjaqXqlgKmFBXvjfZqzSsKq3pdD1KHZCd5QHN+Fwvoszj0JFsquEVhE1og==} + cpu: [x64] + os: [linux] + '@rspack/binding-linux-x64-musl@1.2.2': resolution: {integrity: sha512-RE3e0xe4DdchHssttKzryDwjLkbrNk/4H59TkkWeGYJcLw41tmcOZVFQUOwKLUvXWVyif/vjvV/w1SMlqB4wQg==} cpu: [x64] os: [linux] + '@rspack/binding-linux-x64-musl@1.7.6': + resolution: {integrity: sha512-rEy6MHKob02t/77YNgr6dREyJ0e0tv1X6Xsg8Z5E7rPXead06zefUbfazj4RELYySWnM38ovZyJAkPx/gOn3VA==} + cpu: [x64] + os: [linux] + + '@rspack/binding-wasm32-wasi@1.7.6': + resolution: {integrity: sha512-YupOrz0daSG+YBbCIgpDgzfMM38YpChv+afZpaxx5Ml7xPeAZIIdgWmLHnQ2rts73N2M1NspAiBwV00Xx0N4Vg==} + cpu: [wasm32] + '@rspack/binding-win32-arm64-msvc@1.2.2': resolution: {integrity: sha512-R+PKBYn6uzTaDdVqTHvjqiJPBr5ZHg1wg5UmFDLNH9OklzVFyQh1JInSdJRb7lzfzTRz6bEkkwUFBPQK/CGScw==} cpu: [arm64] os: [win32] + '@rspack/binding-win32-arm64-msvc@1.7.6': + resolution: {integrity: sha512-INj7aVXjBvlZ84kEhSK4kJ484ub0i+BzgnjDWOWM1K+eFYDZjLdAsQSS3fGGXwVc3qKbPIssFfnftATDMTEJHQ==} + cpu: [arm64] + os: [win32] + '@rspack/binding-win32-ia32-msvc@1.2.2': resolution: {integrity: sha512-dBqz3sRAGZ2f31FgzKLDvIRfq2haRP3X3XVCT0PsiMcvt7QJng+26aYYMy2THatd/nM8IwExYeitHWeiMBoruw==} cpu: [ia32] os: [win32] + '@rspack/binding-win32-ia32-msvc@1.7.6': + resolution: {integrity: sha512-lXGvC+z67UMcw58In12h8zCa9IyYRmuptUBMItQJzu+M278aMuD1nETyGLL7e4+OZ2lvrnnBIcjXN1hfw2yRzw==} + cpu: [ia32] + os: [win32] + '@rspack/binding-win32-x64-msvc@1.2.2': resolution: {integrity: sha512-eeAvaN831KG553cMSHkVldyk6YQn4ujgRHov6r1wtREq7CD3/ka9LMkJUepCN85K7XtwYT0N4KpFIQyf5GTGoA==} cpu: [x64] os: [win32] + '@rspack/binding-win32-x64-msvc@1.7.6': + resolution: {integrity: sha512-zeUxEc0ZaPpmaYlCeWcjSJUPuRRySiSHN23oJ2Xyw0jsQ01Qm4OScPdr0RhEOFuK/UE+ANyRtDo4zJsY52Hadw==} + cpu: [x64] + os: [win32] + '@rspack/binding@1.2.2': resolution: {integrity: sha512-GCZwpGFYlLTdJ2soPLwjw9z4LSZ+GdpbHNfBt3Cm/f/bAF8n6mZc7dHUqN893RFh7MPU17HNEL3fMw7XR+6pHg==} + '@rspack/binding@1.7.6': + resolution: {integrity: sha512-/NrEcfo8Gx22hLGysanrV6gHMuqZSxToSci/3M4kzEQtF5cPjfOv5pqeLK/+B6cr56ul/OmE96cCdWcXeVnFjQ==} + '@rspack/core@1.2.2': resolution: {integrity: sha512-EeHAmY65Uj62hSbUKesbrcWGE7jfUI887RD03G++Gj8jS4WPHEu1TFODXNOXg6pa7zyIvs2BK0Bm16Kwz8AEaQ==} engines: {node: '>=16.0.0'} @@ -16814,10 +17364,22 @@ packages: '@swc/helpers': optional: true + '@rspack/core@1.7.6': + resolution: {integrity: sha512-Iax6UhrfZqJajA778c1d5DBFbSIqPOSrI34kpNIiNpWd8Jq7mFIa+Z60SQb5ZQDZuUxcCZikjz5BxinFjTkg7Q==} + engines: {node: '>=18.12.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + '@rspack/lite-tapable@1.0.1': resolution: {integrity: sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==} engines: {node: '>=16.0.0'} + '@rspack/lite-tapable@1.1.0': + resolution: {integrity: sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==} + '@rspack/plugin-react-refresh@1.0.1': resolution: {integrity: sha512-KSBc3bsr3mrAPViv7w9MpE9KEWm6q87EyRXyHlRfJ9PpQ56NbX9KZ7AXo7jPeECb0q5sfpM2PSEf+syBiMgLSw==} peerDependencies: @@ -17300,6 +17862,9 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@swc/helpers@0.5.18': + resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==} + '@swc/types@0.1.17': resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} @@ -17399,6 +17964,7 @@ packages: '@tanstack/config@0.22.0': resolution: {integrity: sha512-7Wwfw6wBv2Kc+OBNIJQzBSJ6q7GABtwVT+VOQ/7/Gl7z8z1rtEYUZrxUrNvbbrHY+J5/WNZNZjJjTWDf8nTUBw==} engines: {node: '>=18'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. '@tanstack/devtools-client@0.0.4': resolution: {integrity: sha512-LefnH9KE9uRDEWifc3QDcooskA8ikfs41bybDTgpYQpyTUspZnaEdUdya9Hry0KYxZ8nos0S3nNbsP79KHqr6Q==} @@ -17814,6 +18380,9 @@ packages: '@types/retry@0.12.2': resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + '@types/send@0.17.4': resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} @@ -18494,6 +19063,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adm-zip@0.5.16: + resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} + engines: {node: '>=12.0'} + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -18676,6 +19249,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -18839,6 +19416,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + btoa@1.2.1: + resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==} + engines: {node: '>= 0.4.0'} + hasBin: true + buffer-builder@0.2.0: resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==} @@ -19301,6 +19883,10 @@ packages: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} + cookies@0.9.1: + resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} + engines: {node: '>= 0.8'} + copy-file@11.1.0: resolution: {integrity: sha512-X8XDzyvYaA6msMyAM575CUoygY5b44QzLcGRKsK3MFmXcOvQa518dNPLsKYwkYsn72g3EiW+LE0ytd/FlqWmyw==} engines: {node: '>=18'} @@ -19308,6 +19894,9 @@ packages: core-js@3.40.0: resolution: {integrity: sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==} + core-js@3.47.0: + resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -19364,6 +19953,14 @@ packages: srvx: optional: true + crossws@0.4.4: + resolution: {integrity: sha512-w6c4OdpRNnudVmcgr7brb/+/HmYjMQvYToO/oTrprTwxRUiom3LYWU1PMWuD006okbUWpII1Ea9/+kwpUfmyRg==} + peerDependencies: + srvx: '>=0.7.1' + peerDependenciesMeta: + srvx: + optional: true + css-loader@7.1.2: resolution: {integrity: sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==} engines: {node: '>= 18.12.0'} @@ -19439,6 +20036,10 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} + date-format@4.0.14: + resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==} + engines: {node: '>=4.0'} + db0@0.3.4: resolution: {integrity: sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw==} peerDependencies: @@ -19525,6 +20126,9 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + deep-equal@1.0.1: + resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} @@ -19578,6 +20182,9 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -20187,6 +20794,10 @@ packages: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + expect-type@1.2.2: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} @@ -20318,10 +20929,18 @@ packages: resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} engines: {node: '>= 0.8'} + find-file-up@2.0.1: + resolution: {integrity: sha512-qVdaUhYO39zmh28/JLQM5CoYN9byEOKEH4qfa8K1eNV17W0UUMJ9WgbR/hHFH+t5rcl+6RTb5UC7ck/I+uRkpQ==} + engines: {node: '>=8'} + find-my-way@9.4.0: resolution: {integrity: sha512-5Ye4vHsypZRYtS01ob/iwHzGRUDELlsoCftI/OZFhcLs1M0tkGPcXldE80TAZC5yYuJMBPJQQ43UHlqbJWiX2w==} engines: {node: '>=20'} + find-pkg@2.0.0: + resolution: {integrity: sha512-WgZ+nKbELDa6N3i/9nrHeNznm+lY3z4YfhDDWgW+5P0pdmMj26bxaxU11ookgY3NyP9GC7HvZ9etp0jRFqGEeQ==} + engines: {node: '>=8'} + find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} @@ -20352,9 +20971,6 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatted@3.3.2: - resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} - flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} @@ -20430,6 +21046,14 @@ packages: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -20517,12 +21141,21 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@13.0.0: resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} engines: {node: 20 || >=22} + global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} + + global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} + globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} @@ -20655,6 +21288,10 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + hono@4.7.10: resolution: {integrity: sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ==} engines: {node: '>=16.9.0'} @@ -20709,6 +21346,10 @@ packages: htmlparser2@9.1.0: resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + http-assert@1.5.0: + resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} + engines: {node: '>= 0.8'} + http-deceiver@1.2.7: resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} @@ -20716,6 +21357,10 @@ packages: resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} engines: {node: '>= 0.6'} + http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -21101,6 +21746,10 @@ packages: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -21134,6 +21783,11 @@ packages: resolution: {integrity: sha512-HM0q6XqQ93psDlqvuViNs/Ea3hAyGDkIdVAHlrEocjjAwGrs1fZ+EdQjS9eUPacnYB7Y8SoDdSY3H8p3ce205A==} engines: {node: '>=14.17.6'} + isomorphic-ws@5.0.0: + resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} + peerDependencies: + ws: '*' + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -21145,6 +21799,10 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -21249,9 +21907,6 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} @@ -21287,6 +21942,10 @@ packages: kebab-case@1.0.2: resolution: {integrity: sha512-7n6wXq4gNgBELfDCpzKc+mRrZFs7D+wgfF5WRFLNAr4DA/qtr9Js8uOAVAfHhuLMfAcQ0pRKqbpjx+TcJVdE1Q==} + keygrip@1.1.0: + resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} + engines: {node: '>= 0.6'} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -21308,6 +21967,13 @@ packages: known-css-properties@0.30.0: resolution: {integrity: sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==} + koa-compose@4.1.0: + resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} + + koa@3.0.3: + resolution: {integrity: sha512-MeuwbCoN1daWS32/Ni5qkzmrOtQO2qrnfdxDHjrm6s4b59yG4nexAJ0pTEFyzjLp0pBVO80CZp0vW8Ze30Ebow==} + engines: {node: '>= 18'} + kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -21484,6 +22150,9 @@ packages: lodash.clonedeep@4.5.0: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + lodash.clonedeepwith@4.5.0: + resolution: {integrity: sha512-QRBRSxhbtsX1nc0baxSkkK5WlVTTm/s48DSukcGcWZwIyI8Zz+lB+kFiELJXtzfH4Aj6kMWQ1VWW4U5uUDgZMA==} + lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} @@ -21524,10 +22193,17 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + log4js@6.9.1: + resolution: {integrity: sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==} + engines: {node: '>=8.0'} + logform@2.7.0: resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} engines: {node: '>= 12.0.0'} + long-timeout@0.1.1: + resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==} + long@5.3.1: resolution: {integrity: sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==} @@ -21885,24 +22561,21 @@ packages: netlify-redirector@0.5.0: resolution: {integrity: sha512-4zdzIP+6muqPCuE8avnrgDJ6KW/2+UpHTRcTbMXCIRxiRmyrX+IZ4WSJGZdHPWF3WmQpXpy603XxecZ9iygN7w==} + nf3@0.3.10: + resolution: {integrity: sha512-UlqmHkZiHGgSkRj17yrOXEsSu5ECvtlJ3Xm1W5WsWrTKgu9m7OjrMZh9H/ME2LcWrTlMD0/vmmNVpyBG4yRdGg==} + nf3@0.3.5: resolution: {integrity: sha512-1VozaVz0lVfGL3c2wZ4c6bmQCm340gDiIYUU3lcg8vVGL/WeuTdrd6OhJiUHZWofc7fFdquhS8Gm+13c3Tumcw==} - nf3@0.3.6: - resolution: {integrity: sha512-/XRUUILTAyuy1XunyVQuqGp8aEmZ2TfRTn8Rji+FA4xqv20qzL4jV7Reqbuey2XucKgPeRVcEYGScmJM0UnB6Q==} - - nitro-nightly@3.0.1-20260123-195236-c6b834cd: - resolution: {integrity: sha512-TjFlflqrAwl+jJcUwgXAq9qVSBRan3o6O4jR4SIt9J/8ipuoud8H+ERhvzUEZhunOJwjdbkp8B9X2Ik6cC1Yww==} + nitro-nightly@3.0.1-20260206-171553-bc737c0c: + resolution: {integrity: sha512-fqne2eTFStLkCODKJ2PWuN6mWv0HNL8mb0xYH/W14cNqbFPiwWQQPWPG9BWARfXm8q/QjN93kTyIYMwRgE5tag==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - rolldown: '>=1.0.0-beta.0' - rollup: ^4 + rollup: ^4.57.0 vite: ^7.3.1 xml2js: ^0.6.2 peerDependenciesMeta: - rolldown: - optional: true rollup: optional: true vite: @@ -21994,6 +22667,10 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + node-schedule@2.1.1: + resolution: {integrity: sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==} + engines: {node: '>=6'} + node-source-walk@7.0.1: resolution: {integrity: sha512-3VW/8JpPqPvnJvseXowjZcirPisssnBuDikk6JIZ8jQzF7KJQX52iPFX4RYYxLycYH7IbMRSPUOga/esVjy5Yg==} engines: {node: '>=18'} @@ -22233,6 +22910,10 @@ packages: resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} engines: {node: '>=18'} + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + parse5-htmlparser2-tree-adapter@6.0.1: resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} @@ -22639,6 +23320,9 @@ packages: radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + rambda@9.4.2: + resolution: {integrity: sha512-++euMfxnl7OgaEKwXh9QqThOjMeta2HH001N1v4mYQzBjJBnmXBh2BCK6dZAbICFVXOFUVD3xFG0R3ZPU0mxXw==} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -22865,6 +23549,10 @@ packages: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} + resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -22890,6 +23578,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + resolve@2.0.0-next.5: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true @@ -22929,6 +23621,11 @@ packages: engines: {node: 20 || >=22} hasBin: true + rolldown@1.0.0-rc.3: + resolution: {integrity: sha512-Po/YZECDOqVXjIXrtC5h++a5NLvKAQNrd9ggrIG3sbDfGO5BqTUsrI6l8zdniKRp3r5Tp/2JTrXqx4GIguFCMw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup-plugin-preserve-directives@0.4.0: resolution: {integrity: sha512-gx4nBxYm5BysmEQS+e2tAMrtFxrGvk+Pe5ppafRibQi0zlW7VYAbEGk6IKDw9sJGPdFWgVTE0o4BU4cdG0Fylg==} peerDependencies: @@ -23180,6 +23877,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -23361,6 +24063,9 @@ packages: sonic-boom@4.2.0: resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + sorted-array-functions@1.3.0: + resolution: {integrity: sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -23479,6 +24184,10 @@ packages: resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} engines: {node: '>=4', npm: '>=6'} + streamroller@3.1.5: + resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==} + engines: {node: '>=8.0'} + streamx@2.22.0: resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} @@ -23627,6 +24336,7 @@ packages: tar@7.4.3: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me terser-webpack-plugin@5.3.11: resolution: {integrity: sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==} @@ -23857,6 +24567,10 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + tsx@4.19.2: resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} engines: {node: '>=18.0.0'} @@ -24069,10 +24783,6 @@ packages: unplugin@1.0.1: resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==} - unplugin@2.3.10: - resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} - engines: {node: '>=18.12.0'} - unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -24681,6 +25391,7 @@ packages: whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} @@ -24709,6 +25420,10 @@ packages: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -26815,7 +27530,7 @@ snapshots: commander: 11.1.0 consola: 3.4.0 json5: 2.2.3 - unplugin: 2.3.10 + unplugin: 2.3.11 urlpattern-polyfill: 10.1.0 transitivePeerDependencies: - babel-plugin-macros @@ -27117,7 +27832,7 @@ snapshots: '@microsoft/tsdoc': 0.15.1 ajv: 8.12.0 jju: 1.4.0 - resolve: 1.22.10 + resolve: 1.22.11 '@microsoft/tsdoc@0.15.1': {} @@ -27125,27 +27840,416 @@ snapshots: dependencies: minimist: 1.2.8 + '@module-federation/bridge-react-webpack-plugin@2.0.1': + dependencies: + '@module-federation/sdk': 2.0.1 + '@types/semver': 7.5.8 + semver: 7.6.3 + + '@module-federation/bridge-react-webpack-plugin@https://pkg.pr.new/module-federation/core/@module-federation/bridge-react-webpack-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd': + dependencies: + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@types/semver': 7.5.8 + semver: 7.6.3 + + '@module-federation/cli@2.0.1(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))': + dependencies: + '@module-federation/dts-plugin': 2.0.1(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/sdk': 2.0.1 + chalk: 3.0.0 + commander: 11.1.0 + jiti: 2.4.2 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - typescript + - utf-8-validate + - vue-tsc + + '@module-federation/cli@https://pkg.pr.new/module-federation/core/@module-federation/cli@7acb6b2621a4960848ad00f3614dc0bb239775fd(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))': + dependencies: + '@module-federation/dts-plugin': https://pkg.pr.new/module-federation/core/@module-federation/dts-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + chalk: 3.0.0 + commander: 11.1.0 + jiti: 2.4.2 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - typescript + - utf-8-validate + - vue-tsc + + '@module-federation/data-prefetch@2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@module-federation/runtime': 2.0.1 + '@module-federation/sdk': 2.0.1 + fs-extra: 9.1.0 + optionalDependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + + '@module-federation/data-prefetch@https://pkg.pr.new/module-federation/core/@module-federation/data-prefetch@7acb6b2621a4960848ad00f3614dc0bb239775fd(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@module-federation/runtime': https://pkg.pr.new/module-federation/core/@module-federation/runtime@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + fs-extra: 9.1.0 + optionalDependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + + '@module-federation/dts-plugin@2.0.1(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))': + dependencies: + '@module-federation/error-codes': 2.0.1 + '@module-federation/managers': 2.0.1 + '@module-federation/sdk': 2.0.1 + '@module-federation/third-party-dts-extractor': 2.0.1 + adm-zip: 0.5.16 + ansi-colors: 4.1.3 + axios: 1.13.2 + chalk: 3.0.0 + fs-extra: 9.1.0 + isomorphic-ws: 5.0.0(ws@8.18.0) + koa: 3.0.3 + lodash.clonedeepwith: 4.5.0 + log4js: 6.9.1 + node-schedule: 2.1.1 + rambda: 9.4.2 + typescript: 5.9.3 + ws: 8.18.0 + optionalDependencies: + vue-tsc: 3.1.8(typescript@5.9.3) + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@module-federation/dts-plugin@https://pkg.pr.new/module-federation/core/@module-federation/dts-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))': + dependencies: + '@module-federation/error-codes': https://pkg.pr.new/module-federation/core/@module-federation/error-codes@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/managers': https://pkg.pr.new/module-federation/core/@module-federation/managers@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/third-party-dts-extractor': https://pkg.pr.new/module-federation/core/@module-federation/third-party-dts-extractor@7acb6b2621a4960848ad00f3614dc0bb239775fd + adm-zip: 0.5.16 + ansi-colors: 4.1.3 + axios: 1.13.2 + chalk: 3.0.0 + fs-extra: 9.1.0 + isomorphic-ws: 5.0.0(ws@8.18.0) + lodash.clonedeepwith: 4.5.0 + log4js: 6.9.1 + node-schedule: 2.1.1 + rambda: 9.4.2 + typescript: 5.9.3 + ws: 8.18.0 + optionalDependencies: + vue-tsc: 3.1.8(typescript@5.9.3) + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@module-federation/enhanced@2.0.1(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18)))': + dependencies: + '@module-federation/bridge-react-webpack-plugin': 2.0.1 + '@module-federation/cli': 2.0.1(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/data-prefetch': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@module-federation/dts-plugin': 2.0.1(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/error-codes': 2.0.1 + '@module-federation/inject-external-runtime-core-plugin': 2.0.1(@module-federation/runtime-tools@2.0.1) + '@module-federation/managers': 2.0.1 + '@module-federation/manifest': 2.0.1(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/rspack': 2.0.1(@rspack/core@1.7.6(@swc/helpers@0.5.18))(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/runtime-tools': 2.0.1 + '@module-federation/sdk': 2.0.1 + btoa: 1.2.1 + schema-utils: 4.3.3 + upath: 2.0.1 + optionalDependencies: + typescript: 5.9.3 + vue-tsc: 3.1.8(typescript@5.9.3) + webpack: 5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18)) + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - debug + - react + - react-dom + - supports-color + - utf-8-validate + + '@module-federation/enhanced@https://pkg.pr.new/module-federation/core/@module-federation/enhanced@7acb6b2621a4960848ad00f3614dc0bb239775fd(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18)))': + dependencies: + '@module-federation/bridge-react-webpack-plugin': https://pkg.pr.new/module-federation/core/@module-federation/bridge-react-webpack-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/cli': https://pkg.pr.new/module-federation/core/@module-federation/cli@7acb6b2621a4960848ad00f3614dc0bb239775fd(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/data-prefetch': https://pkg.pr.new/module-federation/core/@module-federation/data-prefetch@7acb6b2621a4960848ad00f3614dc0bb239775fd(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@module-federation/dts-plugin': https://pkg.pr.new/module-federation/core/@module-federation/dts-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/error-codes': https://pkg.pr.new/module-federation/core/@module-federation/error-codes@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/inject-external-runtime-core-plugin': https://pkg.pr.new/module-federation/core/@module-federation/inject-external-runtime-core-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(@module-federation/runtime-tools@2.0.1) + '@module-federation/managers': https://pkg.pr.new/module-federation/core/@module-federation/managers@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/manifest': https://pkg.pr.new/module-federation/core/@module-federation/manifest@7acb6b2621a4960848ad00f3614dc0bb239775fd(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/rspack': https://pkg.pr.new/module-federation/core/@module-federation/rspack@7acb6b2621a4960848ad00f3614dc0bb239775fd(@rspack/core@1.7.6(@swc/helpers@0.5.18))(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/runtime-tools': https://pkg.pr.new/module-federation/core/@module-federation/runtime-tools@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + btoa: 1.2.1 + schema-utils: 4.3.3 + upath: 2.0.1 + optionalDependencies: + typescript: 5.9.3 + vue-tsc: 3.1.8(typescript@5.9.3) + webpack: 5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18)) + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - debug + - react + - react-dom + - supports-color + - utf-8-validate + + '@module-federation/error-codes@0.22.0': {} + '@module-federation/error-codes@0.8.4': {} + '@module-federation/error-codes@2.0.1': {} + + '@module-federation/error-codes@https://pkg.pr.new/module-federation/core/@module-federation/error-codes@7acb6b2621a4960848ad00f3614dc0bb239775fd': {} + + '@module-federation/inject-external-runtime-core-plugin@2.0.1(@module-federation/runtime-tools@2.0.1)': + dependencies: + '@module-federation/runtime-tools': 2.0.1 + + '@module-federation/inject-external-runtime-core-plugin@https://pkg.pr.new/module-federation/core/@module-federation/inject-external-runtime-core-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(@module-federation/runtime-tools@2.0.1)': + dependencies: + '@module-federation/runtime-tools': 2.0.1 + + '@module-federation/managers@2.0.1': + dependencies: + '@module-federation/sdk': 2.0.1 + find-pkg: 2.0.0 + fs-extra: 9.1.0 + + '@module-federation/managers@https://pkg.pr.new/module-federation/core/@module-federation/managers@7acb6b2621a4960848ad00f3614dc0bb239775fd': + dependencies: + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + find-pkg: 2.0.0 + fs-extra: 9.1.0 + + '@module-federation/manifest@2.0.1(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))': + dependencies: + '@module-federation/dts-plugin': 2.0.1(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/managers': 2.0.1 + '@module-federation/sdk': 2.0.1 + chalk: 3.0.0 + find-pkg: 2.0.0 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - typescript + - utf-8-validate + - vue-tsc + + '@module-federation/manifest@https://pkg.pr.new/module-federation/core/@module-federation/manifest@7acb6b2621a4960848ad00f3614dc0bb239775fd(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))': + dependencies: + '@module-federation/dts-plugin': https://pkg.pr.new/module-federation/core/@module-federation/dts-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/managers': https://pkg.pr.new/module-federation/core/@module-federation/managers@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + chalk: 3.0.0 + find-pkg: 2.0.0 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - typescript + - utf-8-validate + - vue-tsc + + '@module-federation/node@2.7.32(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18)))': + dependencies: + '@module-federation/enhanced': 2.0.1(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + '@module-federation/runtime': 2.0.1 + '@module-federation/sdk': 2.0.1 + btoa: 1.2.1 + encoding: 0.1.13 + node-fetch: 2.7.0(encoding@0.1.13) + optionalDependencies: + webpack: 5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18)) + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - debug + - react + - react-dom + - supports-color + - typescript + - utf-8-validate + - vue-tsc + + '@module-federation/rsbuild-plugin@https://pkg.pr.new/module-federation/core/@module-federation/rsbuild-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18)))': + dependencies: + '@module-federation/enhanced': https://pkg.pr.new/module-federation/core/@module-federation/enhanced@7acb6b2621a4960848ad00f3614dc0bb239775fd(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + '@module-federation/node': 2.7.32(@rspack/core@1.7.6(@swc/helpers@0.5.18))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + fs-extra: 11.3.0 + optionalDependencies: + '@rsbuild/core': 1.7.3 + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - debug + - react + - react-dom + - supports-color + - typescript + - utf-8-validate + - vue-tsc + - webpack + + '@module-federation/rspack@2.0.1(@rspack/core@1.7.6(@swc/helpers@0.5.18))(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))': + dependencies: + '@module-federation/bridge-react-webpack-plugin': 2.0.1 + '@module-federation/dts-plugin': 2.0.1(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/inject-external-runtime-core-plugin': 2.0.1(@module-federation/runtime-tools@2.0.1) + '@module-federation/managers': 2.0.1 + '@module-federation/manifest': 2.0.1(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/runtime-tools': 2.0.1 + '@module-federation/sdk': 2.0.1 + '@rspack/core': 1.7.6(@swc/helpers@0.5.18) + btoa: 1.2.1 + optionalDependencies: + typescript: 5.9.3 + vue-tsc: 3.1.8(typescript@5.9.3) + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@module-federation/rspack@https://pkg.pr.new/module-federation/core/@module-federation/rspack@7acb6b2621a4960848ad00f3614dc0bb239775fd(@rspack/core@1.7.6(@swc/helpers@0.5.18))(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3))': + dependencies: + '@module-federation/bridge-react-webpack-plugin': https://pkg.pr.new/module-federation/core/@module-federation/bridge-react-webpack-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/dts-plugin': https://pkg.pr.new/module-federation/core/@module-federation/dts-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/inject-external-runtime-core-plugin': https://pkg.pr.new/module-federation/core/@module-federation/inject-external-runtime-core-plugin@7acb6b2621a4960848ad00f3614dc0bb239775fd(@module-federation/runtime-tools@2.0.1) + '@module-federation/managers': https://pkg.pr.new/module-federation/core/@module-federation/managers@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/manifest': https://pkg.pr.new/module-federation/core/@module-federation/manifest@7acb6b2621a4960848ad00f3614dc0bb239775fd(typescript@5.9.3)(vue-tsc@3.1.8(typescript@5.9.3)) + '@module-federation/runtime-tools': https://pkg.pr.new/module-federation/core/@module-federation/runtime-tools@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@rspack/core': 1.7.6(@swc/helpers@0.5.18) + btoa: 1.2.1 + optionalDependencies: + typescript: 5.9.3 + vue-tsc: 3.1.8(typescript@5.9.3) + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@module-federation/runtime-core@0.22.0': + dependencies: + '@module-federation/error-codes': 0.22.0 + '@module-federation/sdk': 0.22.0 + + '@module-federation/runtime-core@2.0.1': + dependencies: + '@module-federation/error-codes': 2.0.1 + '@module-federation/sdk': 2.0.1 + + '@module-federation/runtime-core@https://pkg.pr.new/module-federation/core/@module-federation/runtime-core@7acb6b2621a4960848ad00f3614dc0bb239775fd': + dependencies: + '@module-federation/error-codes': https://pkg.pr.new/module-federation/core/@module-federation/error-codes@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + + '@module-federation/runtime-tools@0.22.0': + dependencies: + '@module-federation/runtime': 0.22.0 + '@module-federation/webpack-bundler-runtime': 0.22.0 + '@module-federation/runtime-tools@0.8.4': dependencies: '@module-federation/runtime': 0.8.4 '@module-federation/webpack-bundler-runtime': 0.8.4 + '@module-federation/runtime-tools@2.0.1': + dependencies: + '@module-federation/runtime': 2.0.1 + '@module-federation/webpack-bundler-runtime': 2.0.1 + + '@module-federation/runtime-tools@https://pkg.pr.new/module-federation/core/@module-federation/runtime-tools@7acb6b2621a4960848ad00f3614dc0bb239775fd': + dependencies: + '@module-federation/runtime': https://pkg.pr.new/module-federation/core/@module-federation/runtime@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/webpack-bundler-runtime': https://pkg.pr.new/module-federation/core/@module-federation/webpack-bundler-runtime@7acb6b2621a4960848ad00f3614dc0bb239775fd + + '@module-federation/runtime@0.22.0': + dependencies: + '@module-federation/error-codes': 0.22.0 + '@module-federation/runtime-core': 0.22.0 + '@module-federation/sdk': 0.22.0 + '@module-federation/runtime@0.8.4': dependencies: '@module-federation/error-codes': 0.8.4 '@module-federation/sdk': 0.8.4 + '@module-federation/runtime@2.0.1': + dependencies: + '@module-federation/error-codes': 2.0.1 + '@module-federation/runtime-core': 2.0.1 + '@module-federation/sdk': 2.0.1 + + '@module-federation/runtime@https://pkg.pr.new/module-federation/core/@module-federation/runtime@7acb6b2621a4960848ad00f3614dc0bb239775fd': + dependencies: + '@module-federation/error-codes': https://pkg.pr.new/module-federation/core/@module-federation/error-codes@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/runtime-core': https://pkg.pr.new/module-federation/core/@module-federation/runtime-core@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + + '@module-federation/sdk@0.22.0': {} + '@module-federation/sdk@0.8.4': dependencies: isomorphic-rslog: 0.0.6 + '@module-federation/sdk@2.0.1': {} + + '@module-federation/sdk@https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd': {} + + '@module-federation/third-party-dts-extractor@2.0.1': + dependencies: + find-pkg: 2.0.0 + fs-extra: 9.1.0 + resolve: 1.22.8 + + '@module-federation/third-party-dts-extractor@https://pkg.pr.new/module-federation/core/@module-federation/third-party-dts-extractor@7acb6b2621a4960848ad00f3614dc0bb239775fd': + dependencies: + find-pkg: 2.0.0 + fs-extra: 9.1.0 + resolve: 1.22.8 + + '@module-federation/webpack-bundler-runtime@0.22.0': + dependencies: + '@module-federation/runtime': 0.22.0 + '@module-federation/sdk': 0.22.0 + '@module-federation/webpack-bundler-runtime@0.8.4': dependencies: '@module-federation/runtime': 0.8.4 '@module-federation/sdk': 0.8.4 + '@module-federation/webpack-bundler-runtime@2.0.1': + dependencies: + '@module-federation/runtime': 2.0.1 + '@module-federation/sdk': 2.0.1 + + '@module-federation/webpack-bundler-runtime@https://pkg.pr.new/module-federation/core/@module-federation/webpack-bundler-runtime@7acb6b2621a4960848ad00f3614dc0bb239775fd': + dependencies: + '@module-federation/runtime': https://pkg.pr.new/module-federation/core/@module-federation/runtime@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@module-federation/sdk': https://pkg.pr.new/module-federation/core/@module-federation/sdk@7acb6b2621a4960848ad00f3614dc0bb239775fd + '@motionone/animation@10.18.0': dependencies: '@motionone/easing': 10.18.0 @@ -27285,6 +28389,13 @@ snapshots: '@emnapi/runtime': 1.7.1 '@tybys/wasm-util': 0.9.0 + '@napi-rs/wasm-runtime@1.0.7': + dependencies: + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 + '@tybys/wasm-util': 0.10.1 + optional: true + '@napi-rs/wasm-runtime@1.1.1': dependencies: '@emnapi/core': 1.7.1 @@ -27763,6 +28874,8 @@ snapshots: '@oxc-minify/binding-win32-x64-msvc@0.110.0': optional: true + '@oxc-project/types@0.112.0': {} + '@oxc-transform/binding-android-arm-eabi@0.110.0': optional: true @@ -28880,6 +29993,47 @@ snapshots: '@remix-run/node-fetch-server@0.8.1': {} + '@rolldown/binding-android-arm64@1.0.0-rc.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': + optional: true + '@rolldown/pluginutils@1.0.0-beta.19': {} '@rolldown/pluginutils@1.0.0-beta.27': {} @@ -28894,6 +30048,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.54': {} + '@rolldown/pluginutils@1.0.0-rc.3': {} + '@rollup/plugin-alias@6.0.0(rollup@4.55.3)': optionalDependencies: rollup: 4.55.3 @@ -29107,6 +30263,14 @@ snapshots: transitivePeerDependencies: - '@rspack/tracing' + '@rsbuild/core@1.7.3': + dependencies: + '@rspack/core': 1.7.6(@swc/helpers@0.5.18) + '@rspack/lite-tapable': 1.1.0 + '@swc/helpers': 0.5.18 + core-js: 3.47.0 + jiti: 2.6.1 + '@rsbuild/plugin-babel@1.0.3(@rsbuild/core@1.2.4)': dependencies: '@babel/core': 7.28.5 @@ -29141,6 +30305,12 @@ snapshots: '@rspack/plugin-react-refresh': 1.0.1(react-refresh@0.16.0) react-refresh: 0.16.0 + '@rsbuild/plugin-react@1.1.0(@rsbuild/core@1.7.3)': + dependencies: + '@rsbuild/core': 1.7.3 + '@rspack/plugin-react-refresh': 1.0.1(react-refresh@0.16.0) + react-refresh: 0.16.0 + '@rsbuild/plugin-solid@1.0.6(@babel/core@7.28.5)(@rsbuild/core@1.2.4)(solid-js@1.9.10)': dependencies: '@rsbuild/core': 1.2.4 @@ -29163,11 +30333,11 @@ snapshots: - '@babel/core' - supports-color - '@rsbuild/plugin-vue@1.2.2(@rsbuild/core@1.2.4)(@swc/core@1.10.15(@swc/helpers@0.5.15))(vue@3.5.25(typescript@5.9.2))': + '@rsbuild/plugin-vue@1.2.2(@rsbuild/core@1.2.4)(@swc/core@1.10.15(@swc/helpers@0.5.18))(vue@3.5.25(typescript@5.9.2))': dependencies: '@rsbuild/core': 1.2.4 - rspack-vue-loader: 17.4.4(vue@3.5.25(typescript@5.9.2))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.15))) - webpack: 5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.15)) + rspack-vue-loader: 17.4.4(vue@3.5.25(typescript@5.9.2))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) + webpack: 5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18)) transitivePeerDependencies: - '@swc/core' - '@vue/compiler-sfc' @@ -29179,30 +30349,62 @@ snapshots: '@rspack/binding-darwin-arm64@1.2.2': optional: true + '@rspack/binding-darwin-arm64@1.7.6': + optional: true + '@rspack/binding-darwin-x64@1.2.2': optional: true + '@rspack/binding-darwin-x64@1.7.6': + optional: true + '@rspack/binding-linux-arm64-gnu@1.2.2': optional: true + '@rspack/binding-linux-arm64-gnu@1.7.6': + optional: true + '@rspack/binding-linux-arm64-musl@1.2.2': optional: true + '@rspack/binding-linux-arm64-musl@1.7.6': + optional: true + '@rspack/binding-linux-x64-gnu@1.2.2': optional: true + '@rspack/binding-linux-x64-gnu@1.7.6': + optional: true + '@rspack/binding-linux-x64-musl@1.2.2': optional: true + '@rspack/binding-linux-x64-musl@1.7.6': + optional: true + + '@rspack/binding-wasm32-wasi@1.7.6': + dependencies: + '@napi-rs/wasm-runtime': 1.0.7 + optional: true + '@rspack/binding-win32-arm64-msvc@1.2.2': optional: true + '@rspack/binding-win32-arm64-msvc@1.7.6': + optional: true + '@rspack/binding-win32-ia32-msvc@1.2.2': optional: true + '@rspack/binding-win32-ia32-msvc@1.7.6': + optional: true + '@rspack/binding-win32-x64-msvc@1.2.2': optional: true + '@rspack/binding-win32-x64-msvc@1.7.6': + optional: true + '@rspack/binding@1.2.2': optionalDependencies: '@rspack/binding-darwin-arm64': 1.2.2 @@ -29215,17 +30417,40 @@ snapshots: '@rspack/binding-win32-ia32-msvc': 1.2.2 '@rspack/binding-win32-x64-msvc': 1.2.2 + '@rspack/binding@1.7.6': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.7.6 + '@rspack/binding-darwin-x64': 1.7.6 + '@rspack/binding-linux-arm64-gnu': 1.7.6 + '@rspack/binding-linux-arm64-musl': 1.7.6 + '@rspack/binding-linux-x64-gnu': 1.7.6 + '@rspack/binding-linux-x64-musl': 1.7.6 + '@rspack/binding-wasm32-wasi': 1.7.6 + '@rspack/binding-win32-arm64-msvc': 1.7.6 + '@rspack/binding-win32-ia32-msvc': 1.7.6 + '@rspack/binding-win32-x64-msvc': 1.7.6 + '@rspack/core@1.2.2(@swc/helpers@0.5.15)': dependencies: '@module-federation/runtime-tools': 0.8.4 '@rspack/binding': 1.2.2 '@rspack/lite-tapable': 1.0.1 - caniuse-lite: 1.0.30001696 + caniuse-lite: 1.0.30001760 optionalDependencies: '@swc/helpers': 0.5.15 + '@rspack/core@1.7.6(@swc/helpers@0.5.18)': + dependencies: + '@module-federation/runtime-tools': 0.22.0 + '@rspack/binding': 1.7.6 + '@rspack/lite-tapable': 1.1.0 + optionalDependencies: + '@swc/helpers': 0.5.18 + '@rspack/lite-tapable@1.0.1': {} + '@rspack/lite-tapable@1.1.0': {} + '@rspack/plugin-react-refresh@1.0.1(react-refresh@0.16.0)': dependencies: error-stack-parser: 2.1.4 @@ -29241,14 +30466,14 @@ snapshots: fs-extra: 7.0.1 import-lazy: 4.0.0 jju: 1.4.0 - resolve: 1.22.10 + resolve: 1.22.11 semver: 7.5.4 optionalDependencies: '@types/node': 25.0.9 '@rushstack/rig-package@0.5.3': dependencies: - resolve: 1.22.10 + resolve: 1.22.11 strip-json-comments: 3.1.1 '@rushstack/terminal@0.13.3(@types/node@25.0.9)': @@ -29724,7 +30949,7 @@ snapshots: '@swc/core-win32-x64-msvc@1.10.15': optional: true - '@swc/core@1.10.15(@swc/helpers@0.5.15)': + '@swc/core@1.10.15(@swc/helpers@0.5.18)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.17 @@ -29739,7 +30964,7 @@ snapshots: '@swc/core-win32-arm64-msvc': 1.10.15 '@swc/core-win32-ia32-msvc': 1.10.15 '@swc/core-win32-x64-msvc': 1.10.15 - '@swc/helpers': 0.5.15 + '@swc/helpers': 0.5.18 '@swc/counter@0.1.3': {} @@ -29747,6 +30972,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@swc/helpers@0.5.18': + dependencies: + tslib: 2.8.1 + '@swc/types@0.1.17': dependencies: '@swc/counter': 0.1.3 @@ -30380,6 +31609,8 @@ snapshots: '@types/retry@0.12.2': {} + '@types/semver@7.5.8': {} + '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 @@ -31290,6 +32521,19 @@ snapshots: optionalDependencies: typescript: 5.9.2 + '@vue/language-core@3.1.8(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.26 + '@vue/compiler-dom': 3.5.25 + '@vue/shared': 3.5.25 + alien-signals: 3.1.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 + optionalDependencies: + typescript: 5.9.3 + optional: true + '@vue/reactivity@3.5.25': dependencies: '@vue/shared': 3.5.25 @@ -31411,17 +32655,17 @@ snapshots: '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.2.0)(webpack@5.97.1) '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.2.0)(webpack@5.97.1) '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.2.0)(webpack@5.97.1)': dependencies: - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.2.0)(webpack@5.97.1) optionalDependencies: webpack-dev-server: 5.2.0(webpack-cli@5.1.4)(webpack@5.97.1) @@ -31522,10 +32766,6 @@ snapshots: dependencies: acorn: 8.15.0 - acorn-jsx@5.3.2(acorn@8.14.1): - dependencies: - acorn: 8.14.1 - acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -31538,6 +32778,8 @@ snapshots: acorn@8.15.0: {} + adm-zip@0.5.16: {} + agent-base@6.0.2: dependencies: debug: 4.4.3 @@ -31718,6 +32960,8 @@ snapshots: asynckit@0.4.0: {} + at-least-node@1.0.0: {} + atomic-sleep@1.0.0: {} autocannon@8.0.0: @@ -31780,7 +33024,7 @@ snapshots: dependencies: '@babel/core': 7.28.5 find-up: 5.0.0 - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.28.5): dependencies: @@ -31795,7 +33039,7 @@ snapshots: dependencies: '@babel/runtime': 7.26.7 cosmiconfig: 7.1.0 - resolve: 1.22.10 + resolve: 1.22.11 babel-plugin-vue-jsx-hmr@1.0.0: dependencies: @@ -31945,6 +33189,8 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) + btoa@1.2.1: {} + buffer-builder@0.2.0: optional: true @@ -32412,6 +33658,11 @@ snapshots: cookie@1.0.2: {} + cookies@0.9.1: + dependencies: + depd: 2.0.0 + keygrip: 1.1.0 + copy-file@11.1.0: dependencies: graceful-fs: 4.2.11 @@ -32419,6 +33670,8 @@ snapshots: core-js@3.40.0: {} + core-js@3.47.0: {} + core-util-is@1.0.3: {} cosmiconfig@7.1.0: @@ -32472,12 +33725,11 @@ snapshots: optionalDependencies: srvx: 0.10.1 - crossws@0.4.3(srvx@0.11.2): + crossws@0.4.4(srvx@0.11.2): optionalDependencies: srvx: 0.11.2 - optional: true - css-loader@7.1.2(@rspack/core@1.2.2(@swc/helpers@0.5.15))(webpack@5.97.1): + css-loader@7.1.2(@rspack/core@1.7.6(@swc/helpers@0.5.18))(webpack@5.97.1): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -32488,8 +33740,8 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.3 optionalDependencies: - '@rspack/core': 1.2.2(@swc/helpers@0.5.15) - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + '@rspack/core': 1.7.6(@swc/helpers@0.5.18) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) css-select@4.3.0: dependencies: @@ -32562,6 +33814,8 @@ snapshots: dependencies: '@babel/runtime': 7.26.7 + date-format@4.0.14: {} + db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3): optionalDependencies: '@electric-sql/pglite': 0.3.2 @@ -32603,6 +33857,8 @@ snapshots: deep-eql@5.0.2: {} + deep-equal@1.0.1: {} + deep-equal@2.2.3: dependencies: array-buffer-byte-length: 1.0.2 @@ -32663,6 +33919,8 @@ snapshots: delayed-stream@1.0.0: {} + delegates@1.0.0: {} + denque@2.1.0: {} depd@1.1.2: {} @@ -32877,7 +34135,6 @@ snapshots: encoding@0.1.13: dependencies: iconv-lite: 0.6.3 - optional: true end-of-stream@1.4.4: dependencies: @@ -33439,8 +34696,8 @@ snapshots: espree@10.3.0: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 espree@10.4.0: @@ -33499,6 +34756,10 @@ snapshots: exit-hook@2.2.1: {} + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + expect-type@1.2.2: {} express@4.21.2: @@ -33714,12 +34975,20 @@ snapshots: transitivePeerDependencies: - supports-color + find-file-up@2.0.1: + dependencies: + resolve-dir: 1.0.1 + find-my-way@9.4.0: dependencies: fast-deep-equal: 3.1.3 fast-querystring: 1.1.2 safe-regex2: 5.0.0 + find-pkg@2.0.0: + dependencies: + find-file-up: 2.0.1 + find-root@1.1.0: {} find-up-simple@1.0.1: {} @@ -33775,13 +35044,11 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.2 + flatted: 3.3.3 keyv: 4.5.4 flat@5.0.2: {} - flatted@3.3.2: {} - flatted@3.3.3: {} fn.name@1.1.0: {} @@ -33842,7 +35109,7 @@ snapshots: fs-extra@11.3.0: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.1.0 + jsonfile: 6.2.0 universalify: 2.0.1 fs-extra@7.0.1: @@ -33851,6 +35118,19 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + fsevents@2.3.2: optional: true @@ -33950,6 +35230,20 @@ snapshots: minipass: 7.1.2 path-scurry: 2.0.0 + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -34016,12 +35310,12 @@ snapshots: optionalDependencies: crossws: 0.4.3(srvx@0.10.1) - h3@2.0.1-rc.14(crossws@0.4.3(srvx@0.11.2)): + h3@2.0.1-rc.14(crossws@0.4.4(srvx@0.11.2)): dependencies: rou3: 0.7.12 srvx: 0.11.2 optionalDependencies: - crossws: 0.4.3(srvx@0.11.2) + crossws: 0.4.4(srvx@0.11.2) handle-thing@2.0.1: {} @@ -34067,6 +35361,10 @@ snapshots: dependencies: react-is: 16.13.1 + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + hono@4.7.10: {} hookable@5.5.3: {} @@ -34106,7 +35404,7 @@ snapshots: html-tags@3.3.1: {} - html-webpack-plugin@5.6.3(@rspack/core@1.2.2(@swc/helpers@0.5.15))(webpack@5.97.1): + html-webpack-plugin@5.6.3(@rspack/core@1.7.6(@swc/helpers@0.5.18))(webpack@5.97.1): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -34114,8 +35412,8 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - '@rspack/core': 1.2.2(@swc/helpers@0.5.15) - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + '@rspack/core': 1.7.6(@swc/helpers@0.5.18) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) htmlparser2@6.1.0: dependencies: @@ -34131,6 +35429,11 @@ snapshots: domutils: 3.2.2 entities: 4.5.0 + http-assert@1.5.0: + dependencies: + deep-equal: 1.0.1 + http-errors: 1.8.1 + http-deceiver@1.2.7: {} http-errors@1.6.3: @@ -34140,6 +35443,14 @@ snapshots: setprototypeof: 1.1.0 statuses: 1.5.0 + http-errors@1.8.1: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -34516,6 +35827,8 @@ snapshots: is-what@4.1.16: {} + is-windows@1.0.2: {} + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 @@ -34540,6 +35853,10 @@ snapshots: isomorphic-rslog@0.0.6: {} + isomorphic-ws@5.0.0(ws@8.18.0): + dependencies: + ws: 8.18.0 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -34559,6 +35876,8 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 + jiti@2.4.2: {} + jiti@2.6.1: {} jju@1.4.0: {} @@ -34684,12 +36003,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - jsonfile@6.2.0: dependencies: universalify: 2.0.1 @@ -34732,6 +36045,10 @@ snapshots: kebab-case@1.0.2: {} + keygrip@1.1.0: + dependencies: + tsscmp: 1.0.6 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -34746,6 +36063,29 @@ snapshots: known-css-properties@0.30.0: {} + koa-compose@4.1.0: {} + + koa@3.0.3: + dependencies: + accepts: 1.3.8 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookies: 0.9.1 + delegates: 1.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + fresh: 0.5.2 + http-assert: 1.5.0 + http-errors: 2.0.0 + koa-compose: 4.1.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + type-is: 2.0.1 + vary: 1.1.2 + kolorist@1.8.0: {} kuler@2.0.0: {} @@ -34925,6 +36265,8 @@ snapshots: lodash.clonedeep@4.5.0: {} + lodash.clonedeepwith@4.5.0: {} + lodash.defaults@4.2.0: {} lodash.flatten@4.4.0: {} @@ -34954,6 +36296,16 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + log4js@6.9.1: + dependencies: + date-format: 4.0.14 + debug: 4.4.3 + flatted: 3.3.3 + rfdc: 1.4.1 + streamroller: 3.1.5 + transitivePeerDependencies: + - supports-color + logform@2.7.0: dependencies: '@colors/colors': 1.6.0 @@ -34963,6 +36315,8 @@ snapshots: safe-stable-stringify: 2.5.0 triple-beam: 1.4.1 + long-timeout@0.1.1: {} + long@5.3.1: {} loose-envify@1.4.0: @@ -35369,24 +36723,22 @@ snapshots: netlify-redirector@0.5.0: {} - nf3@0.3.5: {} + nf3@0.3.10: {} - nf3@0.3.6: {} + nf3@0.3.5: {} - nitro-nightly@3.0.1-20260123-195236-c6b834cd(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)): + nitro-nightly@3.0.1-20260206-171553-bc737c0c(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)): dependencies: consola: 3.4.2 - crossws: 0.4.3(srvx@0.10.1) + crossws: 0.4.4(srvx@0.11.2) db0: 0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3) - h3: 2.0.1-rc.11(crossws@0.4.3(srvx@0.10.1)) + h3: 2.0.1-rc.14(crossws@0.4.4(srvx@0.11.2)) jiti: 2.6.1 - nf3: 0.3.6 + nf3: 0.3.10 ofetch: 2.0.0-alpha.3 ohash: 2.0.11 - oxc-minify: 0.110.0 - oxc-transform: 0.110.0 - srvx: 0.10.1 - undici: 7.18.2 + rolldown: 1.0.0-rc.3 + srvx: 0.11.2 unenv: 2.0.0-rc.24 unstorage: 2.0.0-alpha.5(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.9.2)(lru-cache@11.2.2)(ofetch@2.0.0-alpha.3) optionalDependencies: @@ -35421,7 +36773,7 @@ snapshots: - sqlite3 - uploadthing - nitro@3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)): + nitro@3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.2)(mysql2@3.15.3)(rolldown@1.0.0-rc.3)(rollup@4.55.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)): dependencies: consola: 3.4.2 crossws: 0.4.3(srvx@0.10.1) @@ -35438,6 +36790,7 @@ snapshots: unenv: 2.0.0-rc.24 unstorage: 2.0.0-alpha.5(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.9.2)(lru-cache@11.2.2)(ofetch@2.0.0-alpha.3) optionalDependencies: + rolldown: 1.0.0-rc.3 rollup: 4.55.3 vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) transitivePeerDependencies: @@ -35469,7 +36822,7 @@ snapshots: - sqlite3 - uploadthing - nitropack@2.13.1(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(encoding@0.1.13)(mysql2@3.15.3): + nitropack@2.13.1(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(encoding@0.1.13)(mysql2@3.15.3)(rolldown@1.0.0-rc.3): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.55.3) @@ -35522,7 +36875,7 @@ snapshots: pretty-bytes: 7.1.0 radix3: 1.1.2 rollup: 4.55.3 - rollup-plugin-visualizer: 6.0.5(rollup@4.55.3) + rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-rc.3)(rollup@4.55.3) scule: 1.3.0 semver: 7.7.3 serve-placeholder: 2.0.2 @@ -35613,6 +36966,12 @@ snapshots: node-releases@2.0.27: {} + node-schedule@2.1.1: + dependencies: + cron-parser: 4.9.0 + long-timeout: 0.1.1 + sorted-array-functions: 1.3.0 + node-source-walk@7.0.1: dependencies: '@babel/parser': 7.28.5 @@ -35653,7 +37012,7 @@ snapshots: nwsapi@2.2.16: {} - nx@22.3.3(@swc/core@1.10.15(@swc/helpers@0.5.15)): + nx@22.3.3(@swc/core@1.10.15(@swc/helpers@0.5.18)): dependencies: '@napi-rs/wasm-runtime': 0.2.4 '@yarnpkg/lockfile': 1.1.0 @@ -35701,7 +37060,7 @@ snapshots: '@nx/nx-linux-x64-musl': 22.3.3 '@nx/nx-win32-arm64-msvc': 22.3.3 '@nx/nx-win32-x64-msvc': 22.3.3 - '@swc/core': 1.10.15(@swc/helpers@0.5.15) + '@swc/core': 1.10.15(@swc/helpers@0.5.18) transitivePeerDependencies: - debug @@ -35937,6 +37296,8 @@ snapshots: index-to-position: 1.2.0 type-fest: 4.41.0 + parse-passwd@1.0.0: {} + parse5-htmlparser2-tree-adapter@6.0.1: dependencies: parse5: 6.0.1 @@ -36071,15 +37432,15 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-loader@8.2.0(@rspack/core@1.2.2(@swc/helpers@0.5.15))(postcss@8.5.6)(typescript@5.9.2)(webpack@5.97.1): + postcss-loader@8.2.0(@rspack/core@1.7.6(@swc/helpers@0.5.18))(postcss@8.5.6)(typescript@5.9.2)(webpack@5.97.1): dependencies: cosmiconfig: 9.0.0(typescript@5.9.2) jiti: 2.6.1 postcss: 8.5.6 semver: 7.7.3 optionalDependencies: - '@rspack/core': 1.2.2(@swc/helpers@0.5.15) - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + '@rspack/core': 1.7.6(@swc/helpers@0.5.18) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) transitivePeerDependencies: - typescript @@ -36401,6 +37762,8 @@ snapshots: radix3@1.1.2: {} + rambda@9.4.2: {} + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -36562,7 +37925,7 @@ snapshots: rechoir@0.8.0: dependencies: - resolve: 1.22.10 + resolve: 1.22.11 redaxios@0.5.1: {} @@ -36634,6 +37997,11 @@ snapshots: dependencies: resolve-from: 5.0.0 + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -36654,6 +38022,12 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + resolve@1.22.8: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + resolve@2.0.0-next.5: dependencies: is-core-module: 2.16.1 @@ -36686,19 +38060,39 @@ snapshots: glob: 13.0.0 package-json-from-dist: 1.0.1 + rolldown@1.0.0-rc.3: + dependencies: + '@oxc-project/types': 0.112.0 + '@rolldown/pluginutils': 1.0.0-rc.3 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.3 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.3 + '@rolldown/binding-darwin-x64': 1.0.0-rc.3 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.3 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.3 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.3 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.3 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.3 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.3 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.3 + rollup-plugin-preserve-directives@0.4.0(rollup@4.55.3): dependencies: '@rollup/pluginutils': 5.1.4(rollup@4.55.3) magic-string: 0.30.21 rollup: 4.55.3 - rollup-plugin-visualizer@6.0.5(rollup@4.55.3): + rollup-plugin-visualizer@6.0.5(rolldown@1.0.0-rc.3)(rollup@4.55.3): dependencies: open: 8.4.2 picomatch: 4.0.3 source-map: 0.7.6 yargs: 17.7.2 optionalDependencies: + rolldown: 1.0.0-rc.3 rollup: 4.55.3 rollup@4.52.5: @@ -36778,11 +38172,11 @@ snapshots: rrweb-cssom@0.8.0: {} - rspack-vue-loader@17.4.4(vue@3.5.25(typescript@5.9.2))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.15))): + rspack-vue-loader@17.4.4(vue@3.5.25(typescript@5.9.2))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))): dependencies: chalk: 4.1.2 watchpack: 2.4.2 - webpack: 5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.15)) + webpack: 5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18)) optionalDependencies: vue: 3.5.25(typescript@5.9.2) @@ -36964,6 +38358,8 @@ snapshots: dependencies: lru-cache: 6.0.0 + semver@7.6.3: {} + semver@7.7.2: {} semver@7.7.3: {} @@ -37253,6 +38649,8 @@ snapshots: dependencies: atomic-sleep: 1.0.0 + sorted-array-functions@1.3.0: {} + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -37355,6 +38753,14 @@ snapshots: stoppable@1.1.0: {} + streamroller@3.1.5: + dependencies: + date-format: 4.0.14 + debug: 4.4.3 + fs-extra: 8.1.0 + transitivePeerDependencies: + - supports-color + streamx@2.22.0: dependencies: fast-fifo: 1.3.2 @@ -37416,7 +38822,7 @@ snapshots: style-loader@4.0.0(webpack@5.97.1): dependencies: - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) style-to-object@1.0.8: dependencies: @@ -37451,11 +38857,11 @@ snapshots: picocolors: 1.1.1 sax: 1.4.1 - swc-loader@0.2.6(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack@5.97.1): + swc-loader@0.2.6(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack@5.97.1): dependencies: - '@swc/core': 1.10.15(@swc/helpers@0.5.15) + '@swc/core': 1.10.15(@swc/helpers@0.5.18) '@swc/counter': 0.1.3 - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) swr@2.3.4(react@19.2.3): dependencies: @@ -37508,38 +38914,38 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 - terser-webpack-plugin@5.3.11(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))): + terser-webpack-plugin@5.3.11(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack@5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 - schema-utils: 4.3.0 + schema-utils: 4.3.3 serialize-javascript: 6.0.2 terser: 5.37.0 - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15)) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18)) optionalDependencies: - '@swc/core': 1.10.15(@swc/helpers@0.5.15) + '@swc/core': 1.10.15(@swc/helpers@0.5.18) - terser-webpack-plugin@5.3.11(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack@5.97.1): + terser-webpack-plugin@5.3.11(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack@5.97.1): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 - schema-utils: 4.3.0 + schema-utils: 4.3.3 serialize-javascript: 6.0.2 terser: 5.37.0 - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) optionalDependencies: - '@swc/core': 1.10.15(@swc/helpers@0.5.15) + '@swc/core': 1.10.15(@swc/helpers@0.5.18) - terser-webpack-plugin@5.3.16(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.15))): + terser-webpack-plugin@5.3.16(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 terser: 5.37.0 - webpack: 5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.15)) + webpack: 5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18)) optionalDependencies: - '@swc/core': 1.10.15(@swc/helpers@0.5.15) + '@swc/core': 1.10.15(@swc/helpers@0.5.18) terser@5.37.0: dependencies: @@ -37721,6 +39127,8 @@ snapshots: tslib@2.8.1: {} + tsscmp@1.0.6: {} + tsx@4.19.2: dependencies: esbuild: 0.23.1 @@ -37909,18 +39317,11 @@ snapshots: unplugin@1.0.1: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 chokidar: 3.6.0 webpack-sources: 3.2.3 webpack-virtual-modules: 0.5.0 - unplugin@2.3.10: - dependencies: - '@jridgewell/remapping': 2.3.5 - acorn: 8.15.0 - picomatch: 4.0.3 - webpack-virtual-modules: 0.6.2 - unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -38441,6 +39842,13 @@ snapshots: '@vue/language-core': 3.1.8(typescript@5.9.2) typescript: 5.9.2 + vue-tsc@3.1.8(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.26 + '@vue/language-core': 3.1.8(typescript@5.9.3) + typescript: 5.9.3 + optional: true + vue@3.5.25(typescript@5.8.3): dependencies: '@vue/compiler-dom': 3.5.25 @@ -38519,7 +39927,7 @@ snapshots: import-local: 3.2.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) webpack-merge: 5.10.0 optionalDependencies: webpack-dev-server: 5.2.0(webpack-cli@5.1.4)(webpack@5.97.1) @@ -38531,9 +39939,9 @@ snapshots: mime-types: 2.1.35 on-finished: 2.4.1 range-parser: 1.2.1 - schema-utils: 4.3.0 + schema-utils: 4.3.3 optionalDependencies: - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) webpack-dev-server@5.2.0(webpack-cli@5.1.4)(webpack@5.97.1): dependencies: @@ -38565,7 +39973,7 @@ snapshots: webpack-dev-middleware: 7.4.2(webpack@5.97.1) ws: 8.18.0 optionalDependencies: - webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.2.0)(webpack@5.97.1) transitivePeerDependencies: - bufferutil @@ -38587,7 +39995,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.15)): + webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -38611,7 +40019,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.15))) + terser-webpack-plugin: 5.3.16(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack@5.104.0(@swc/core@1.10.15(@swc/helpers@0.5.18))) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -38619,7 +40027,7 @@ snapshots: - esbuild - uglify-js - webpack@5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15)): + webpack@5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.7 @@ -38641,7 +40049,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.11(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))) + terser-webpack-plugin: 5.3.11(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack@5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -38649,7 +40057,7 @@ snapshots: - esbuild - uglify-js - webpack@5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack-cli@5.1.4): + webpack@5.97.1(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack-cli@5.1.4): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.7 @@ -38671,7 +40079,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.11(@swc/core@1.10.15(@swc/helpers@0.5.15))(webpack@5.97.1) + terser-webpack-plugin: 5.3.11(@swc/core@1.10.15(@swc/helpers@0.5.18))(webpack@5.97.1) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: @@ -38735,6 +40143,10 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0