From 58b5a9a2dbc1ca63e8949ee69cbd001d799e86f6 Mon Sep 17 00:00:00 2001 From: alexandre-marchina Date: Wed, 15 Jul 2026 13:56:43 -0300 Subject: [PATCH 1/2] feat: add assetPrefix option to createRsbuildEnvironmentPlan and corresponding tests --- .../start-plugin-core/src/rsbuild/planning.ts | 10 ++- .../start-plugin-core/src/rsbuild/plugin.ts | 9 +++ .../tests/rsbuild/output-directory.test.ts | 72 +++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/packages/start-plugin-core/src/rsbuild/planning.ts b/packages/start-plugin-core/src/rsbuild/planning.ts index e6518ad2e6..2146f8040b 100644 --- a/packages/start-plugin-core/src/rsbuild/planning.ts +++ b/packages/start-plugin-core/src/rsbuild/planning.ts @@ -73,6 +73,7 @@ export function createRsbuildEnvironmentPlan(opts: { clientOutputDirectory: string serverOutputDirectory: string publicBase: string + assetPrefix?: string | undefined serverFnProviderEnv: string environmentOverrides?: RsbuildEnvironmentOverrides scriptFormat?: ScriptFormat @@ -106,6 +107,13 @@ export function createRsbuildEnvironmentPlan(opts: { ) } + const clientAssetPrefix = + typeof opts.assetPrefix === 'string' && opts.assetPrefix !== 'auto' + ? opts.assetPrefix + : opts.publicBase !== '/' + ? opts.publicBase + : 'auto' + return { environments: { [RSBUILD_ENVIRONMENT_NAMES.client]: mergeRsbuildConfig( @@ -134,7 +142,7 @@ export function createRsbuildEnvironmentPlan(opts: { media: `${RSBUILD_CLIENT_ASSETS_DIR}/media`, assets: `${RSBUILD_CLIENT_ASSETS_DIR}/assets`, }, - assetPrefix: opts.publicBase, + assetPrefix: clientAssetPrefix, }, resolve: { alias, diff --git a/packages/start-plugin-core/src/rsbuild/plugin.ts b/packages/start-plugin-core/src/rsbuild/plugin.ts index 3de4ed8c2f..0cd6c8bf75 100644 --- a/packages/start-plugin-core/src/rsbuild/plugin.ts +++ b/packages/start-plugin-core/src/rsbuild/plugin.ts @@ -118,6 +118,14 @@ export function tanStackStartRsbuild( const serverBase = rsbuildConfig.server?.base const assetPrefix = rsbuildConfig.output?.assetPrefix + + // rsbuild normalizes output.assetPrefix to "/" when unset, same as + // server.base. Use the original config to distinguish user-set from + // default so createRsbuildEnvironmentPlan can choose the right fallback. + const originalConfig = api.getRsbuildConfig('original') + const userSetAssetPrefix = + originalConfig.output?.assetPrefix !== undefined + const publicBase = normalizePublicBase( typeof serverBase === 'string' ? serverBase @@ -171,6 +179,7 @@ export function tanStackStartRsbuild( clientOutputDirectory: resolvedStartConfig.outputDirectories.client, serverOutputDirectory: resolvedStartConfig.outputDirectories.server, publicBase: resolvedStartConfig.basePaths.publicBase, + assetPrefix: userSetAssetPrefix ? assetPrefix : undefined, serverFnProviderEnv, environmentOverrides: corePluginOpts.rsbuild?.environments, scriptFormat, diff --git a/packages/start-plugin-core/tests/rsbuild/output-directory.test.ts b/packages/start-plugin-core/tests/rsbuild/output-directory.test.ts index c0185a74eb..67203accf1 100644 --- a/packages/start-plugin-core/tests/rsbuild/output-directory.test.ts +++ b/packages/start-plugin-core/tests/rsbuild/output-directory.test.ts @@ -134,3 +134,75 @@ describe('createRsbuildEnvironmentPlan client output', () => { ) }) }) + +describe('createRsbuildEnvironmentPlan assetPrefix', () => { + const baseOptions = { + root: '/app', + entryAliases: { + client: '/app/src/client.tsx', + server: '/app/src/server.ts', + start: '/app/src/start.ts', + router: '/app/src/router.tsx', + alias: { + 'virtual:tanstack-start-client-entry': '/app/src/client.tsx', + 'virtual:tanstack-start-server-entry': '/app/src/server.ts', + '#tanstack-start-entry': '/app/src/start.ts', + '#tanstack-router-entry': '/app/src/router.tsx', + }, + }, + clientOutputDirectory: 'dist/client', + serverOutputDirectory: 'dist/server', + publicBase: '/app/', + serverFnProviderEnv: 'ssr', + } + + test('uses publicBase when assetPrefix is not provided and publicBase is not default', () => { + const plan = createRsbuildEnvironmentPlan(baseOptions) + expect(plan.environments.client!.output?.assetPrefix).toBe('/app/') + }) + + test('defaults to "auto" when neither configured and publicBase is default', () => { + const plan = createRsbuildEnvironmentPlan({ + ...baseOptions, + publicBase: '/', + }) + // '"auto"' lets rspack infer the public path from the document URL. + // For MF apps this enables inferAutoPublicPath; for non-MF apps it + // resolves identically to "/" (same-origin relative). + expect(plan.environments.client!.output?.assetPrefix).toBe('auto') + }) + + test('uses publicBase when assetPrefix is "auto"', () => { + const plan = createRsbuildEnvironmentPlan({ + ...baseOptions, + assetPrefix: 'auto', + }) + expect(plan.environments.client!.output?.assetPrefix).toBe('/app/') + }) + + test('uses assetPrefix when explicitly set to a path', () => { + const plan = createRsbuildEnvironmentPlan({ + ...baseOptions, + assetPrefix: '/cdn/', + }) + expect(plan.environments.client!.output?.assetPrefix).toBe('/cdn/') + }) + + test('uses assetPrefix when explicitly set to a full URL', () => { + const plan = createRsbuildEnvironmentPlan({ + ...baseOptions, + assetPrefix: 'https://cdn.example.com/', + }) + expect(plan.environments.client!.output?.assetPrefix).toBe( + 'https://cdn.example.com/', + ) + }) + + test('server environment never gets an assetPrefix', () => { + const plan = createRsbuildEnvironmentPlan({ + ...baseOptions, + assetPrefix: 'https://cdn.example.com/', + }) + expect(plan.environments.ssr!.output?.assetPrefix).toBeUndefined() + }) +}) From 087f2f0fb0ba19c6533aa582d941eec3baa639ea Mon Sep 17 00:00:00 2001 From: alexandre-marchina Date: Wed, 15 Jul 2026 14:53:08 -0300 Subject: [PATCH 2/2] feat: enhance assetPrefix handling in createRsbuildEnvironmentPlan and update tests --- packages/start-plugin-core/src/rsbuild/planning.ts | 9 +++++++-- packages/start-plugin-core/src/rsbuild/plugin.ts | 6 ++++-- .../tests/rsbuild/output-directory.test.ts | 13 ++++++------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/start-plugin-core/src/rsbuild/planning.ts b/packages/start-plugin-core/src/rsbuild/planning.ts index 2146f8040b..2f79d4d0c5 100644 --- a/packages/start-plugin-core/src/rsbuild/planning.ts +++ b/packages/start-plugin-core/src/rsbuild/planning.ts @@ -107,12 +107,17 @@ export function createRsbuildEnvironmentPlan(opts: { ) } + // When the user set output.assetPrefix, forward it. When they set + // server.base (and not assetPrefix), forward that via publicBase for + // backwards compatibility with the existing serverBase-precedence path. + // Otherwise leave assetPrefix unset so downstream plugins (e.g. Module + // Federation) and rsbuild's own default can take effect. const clientAssetPrefix = - typeof opts.assetPrefix === 'string' && opts.assetPrefix !== 'auto' + typeof opts.assetPrefix === 'string' ? opts.assetPrefix : opts.publicBase !== '/' ? opts.publicBase - : 'auto' + : undefined return { environments: { diff --git a/packages/start-plugin-core/src/rsbuild/plugin.ts b/packages/start-plugin-core/src/rsbuild/plugin.ts index 0cd6c8bf75..b59e330c9a 100644 --- a/packages/start-plugin-core/src/rsbuild/plugin.ts +++ b/packages/start-plugin-core/src/rsbuild/plugin.ts @@ -120,8 +120,10 @@ export function tanStackStartRsbuild( const assetPrefix = rsbuildConfig.output?.assetPrefix // rsbuild normalizes output.assetPrefix to "/" when unset, same as - // server.base. Use the original config to distinguish user-set from - // default so createRsbuildEnvironmentPlan can choose the right fallback. + // server.base. Read the original config to tell "user set /" from + // "rsbuild filled in /" — when the user didn't set it, we forward + // undefined so rsbuild's own default and downstream plugins remain in + // control of the client environment's assetPrefix. const originalConfig = api.getRsbuildConfig('original') const userSetAssetPrefix = originalConfig.output?.assetPrefix !== undefined diff --git a/packages/start-plugin-core/tests/rsbuild/output-directory.test.ts b/packages/start-plugin-core/tests/rsbuild/output-directory.test.ts index 67203accf1..9a16ab00db 100644 --- a/packages/start-plugin-core/tests/rsbuild/output-directory.test.ts +++ b/packages/start-plugin-core/tests/rsbuild/output-directory.test.ts @@ -161,23 +161,22 @@ describe('createRsbuildEnvironmentPlan assetPrefix', () => { expect(plan.environments.client!.output?.assetPrefix).toBe('/app/') }) - test('defaults to "auto" when neither configured and publicBase is default', () => { + test('leaves assetPrefix undefined when neither configured and publicBase is default', () => { const plan = createRsbuildEnvironmentPlan({ ...baseOptions, publicBase: '/', }) - // '"auto"' lets rspack infer the public path from the document URL. - // For MF apps this enables inferAutoPublicPath; for non-MF apps it - // resolves identically to "/" (same-origin relative). - expect(plan.environments.client!.output?.assetPrefix).toBe('auto') + // Start forwards nothing so rsbuild's own default and downstream plugins + // (e.g. Module Federation gating on `=== undefined`) stay in control. + expect(plan.environments.client!.output?.assetPrefix).toBeUndefined() }) - test('uses publicBase when assetPrefix is "auto"', () => { + test('forwards assetPrefix verbatim when explicitly set to "auto"', () => { const plan = createRsbuildEnvironmentPlan({ ...baseOptions, assetPrefix: 'auto', }) - expect(plan.environments.client!.output?.assetPrefix).toBe('/app/') + expect(plan.environments.client!.output?.assetPrefix).toBe('auto') }) test('uses assetPrefix when explicitly set to a path', () => {