From e236c603c621039e6b5edf1b294d3834d705e993 Mon Sep 17 00:00:00 2001 From: xun082 <73689580+xun082@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:29:40 +0800 Subject: [PATCH 1/8] fix(core): respect environment-specific output.assetPrefix in dev mode --- .../environments/assetprefix-override/src/node.js | 6 ++++++ packages/core/src/plugins/output.ts | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 e2e/cases/environments/assetprefix-override/src/node.js diff --git a/e2e/cases/environments/assetprefix-override/src/node.js b/e2e/cases/environments/assetprefix-override/src/node.js new file mode 100644 index 0000000000..70137d8b05 --- /dev/null +++ b/e2e/cases/environments/assetprefix-override/src/node.js @@ -0,0 +1,6 @@ +console.log('Node Bundle'); + +// Import an async chunk to test chunk loading path +import('./nodeAsync.js').then((module) => { + console.log(module.default); +}); diff --git a/packages/core/src/plugins/output.ts b/packages/core/src/plugins/output.ts index c1b02b7e1a..f562fdeadf 100644 --- a/packages/core/src/plugins/output.ts +++ b/packages/core/src/plugins/output.ts @@ -32,6 +32,13 @@ function getPublicPath({ if (typeof output.assetPrefix === 'string') { publicPath = output.assetPrefix; } + } else if ( + typeof output.assetPrefix === 'string' && + output.assetPrefix !== DEFAULT_ASSET_PREFIX + ) { + // In dev mode, prefer environment-specific `output.assetPrefix` over `dev.assetPrefix` + // but only if it's explicitly set to a non-default value + publicPath = output.assetPrefix; } else if (typeof dev.assetPrefix === 'string') { publicPath = dev.assetPrefix; } else if (dev.assetPrefix) { @@ -54,7 +61,9 @@ function getPublicPath({ const defaultPort = server.port ?? DEFAULT_PORT; const port = isDev ? (context.devServer?.port ?? defaultPort) : defaultPort; - return formatPublicPath(replacePortPlaceholder(publicPath, port)); + const replacedPath = replacePortPlaceholder(publicPath, port); + // For empty string, preserve it as-is to enable relative paths (important for node targets) + return replacedPath === '' ? replacedPath : formatPublicPath(replacedPath); } const getJsAsyncPath = ( From 1516428a17a5dc2e5865339055d213441c0b268a Mon Sep 17 00:00:00 2001 From: xun082 <73689580+xun082@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:49:10 +0800 Subject: [PATCH 2/8] fix(core): respect environment-specific output.assetPrefix in dev mode --- e2e/cases/environments/assetprefix-override/src/node.js | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 e2e/cases/environments/assetprefix-override/src/node.js diff --git a/e2e/cases/environments/assetprefix-override/src/node.js b/e2e/cases/environments/assetprefix-override/src/node.js deleted file mode 100644 index 70137d8b05..0000000000 --- a/e2e/cases/environments/assetprefix-override/src/node.js +++ /dev/null @@ -1,6 +0,0 @@ -console.log('Node Bundle'); - -// Import an async chunk to test chunk loading path -import('./nodeAsync.js').then((module) => { - console.log(module.default); -}); From a6a3644bb127061ad9a4e2b7c7faa793b559147d Mon Sep 17 00:00:00 2001 From: xun082 <73689580+xun082@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:55:56 +0800 Subject: [PATCH 3/8] fix(core): preserve empty string as valid relative path in public path handling --- packages/core/src/helpers/url.ts | 3 ++- packages/core/src/plugins/output.ts | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/src/helpers/url.ts b/packages/core/src/helpers/url.ts index 1db088395a..2219613dcf 100644 --- a/packages/core/src/helpers/url.ts +++ b/packages/core/src/helpers/url.ts @@ -62,7 +62,8 @@ export const formatPublicPath = ( withSlash = true, ): string => { // 'auto' is a magic value in Rspack and we should not add trailing slash - if (publicPath === 'auto') { + // Empty string is a valid value representing a relative path and should be preserved + if (publicPath === 'auto' || publicPath === '') { return publicPath; } diff --git a/packages/core/src/plugins/output.ts b/packages/core/src/plugins/output.ts index f562fdeadf..8dc56a24ee 100644 --- a/packages/core/src/plugins/output.ts +++ b/packages/core/src/plugins/output.ts @@ -61,9 +61,7 @@ function getPublicPath({ const defaultPort = server.port ?? DEFAULT_PORT; const port = isDev ? (context.devServer?.port ?? defaultPort) : defaultPort; - const replacedPath = replacePortPlaceholder(publicPath, port); - // For empty string, preserve it as-is to enable relative paths (important for node targets) - return replacedPath === '' ? replacedPath : formatPublicPath(replacedPath); + return formatPublicPath(replacePortPlaceholder(publicPath, port)); } const getJsAsyncPath = ( From f558b9bdec76410c356278bf848d81dea6e6f917 Mon Sep 17 00:00:00 2001 From: xun082 <73689580+xun082@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:26:24 +0800 Subject: [PATCH 4/8] fix(core): enhance public path handling to preserve empty string as valid relative path --- packages/core/src/helpers/compiler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/helpers/compiler.ts b/packages/core/src/helpers/compiler.ts index 9ca1950a1b..0e37a949a6 100644 --- a/packages/core/src/helpers/compiler.ts +++ b/packages/core/src/helpers/compiler.ts @@ -15,7 +15,8 @@ export const getPublicPathFromCompiler = ( if (typeof publicPath === 'string') { // 'auto' is a magic value in Rspack and behave like `publicPath: ""` - if (publicPath === 'auto') { + // Empty string is a valid value representing a relative path and should be preserved + if (publicPath === 'auto' || publicPath === '') { return ''; } return publicPath.endsWith('/') ? publicPath : `${publicPath}/`; From e20440af8d6fa2e23a93e7ce8ebae5bcefa13070 Mon Sep 17 00:00:00 2001 From: xun082 <73689580+xun082@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:43:39 +0800 Subject: [PATCH 5/8] fix(core): refine public path handling for server targets and clarify comments on empty string preservation --- packages/core/src/helpers/compiler.ts | 3 +-- packages/core/src/helpers/url.ts | 2 +- packages/core/src/plugins/output.ts | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/core/src/helpers/compiler.ts b/packages/core/src/helpers/compiler.ts index 0e37a949a6..9ca1950a1b 100644 --- a/packages/core/src/helpers/compiler.ts +++ b/packages/core/src/helpers/compiler.ts @@ -15,8 +15,7 @@ export const getPublicPathFromCompiler = ( if (typeof publicPath === 'string') { // 'auto' is a magic value in Rspack and behave like `publicPath: ""` - // Empty string is a valid value representing a relative path and should be preserved - if (publicPath === 'auto' || publicPath === '') { + if (publicPath === 'auto') { return ''; } return publicPath.endsWith('/') ? publicPath : `${publicPath}/`; diff --git a/packages/core/src/helpers/url.ts b/packages/core/src/helpers/url.ts index 2219613dcf..ece1d2e5d1 100644 --- a/packages/core/src/helpers/url.ts +++ b/packages/core/src/helpers/url.ts @@ -62,7 +62,7 @@ export const formatPublicPath = ( withSlash = true, ): string => { // 'auto' is a magic value in Rspack and we should not add trailing slash - // Empty string is a valid value representing a relative path and should be preserved + // Empty string is a valid value representing a relative path and should be preserved (important for node targets) if (publicPath === 'auto' || publicPath === '') { return publicPath; } diff --git a/packages/core/src/plugins/output.ts b/packages/core/src/plugins/output.ts index 8dc56a24ee..a4135d5c63 100644 --- a/packages/core/src/plugins/output.ts +++ b/packages/core/src/plugins/output.ts @@ -16,15 +16,28 @@ import type { function getPublicPath({ isDev, + isServer, config, context, }: { isDev: boolean; + isServer: boolean; config: NormalizedEnvironmentConfig; context: RsbuildContext; }) { const { dev, output, server } = config; + // For server targets (node), use empty string or explicit assetPrefix to enable relative paths + // This is important for worker_threads and other node-specific imports + // See: https://github.com/web-infra-dev/rsbuild/issues/6539 + if (isServer) { + if (!isDev && typeof output.assetPrefix === 'string') { + return output.assetPrefix; + } + // For node targets in dev mode, use empty string by default + return ''; + } + let publicPath = DEFAULT_ASSET_PREFIX; // If `mode` is `production` or `none`, use `output.assetPrefix` @@ -90,6 +103,7 @@ export const pluginOutput = (): RsbuildPlugin => ({ const publicPath = getPublicPath({ config, isDev, + isServer, context: api.context, }); From c8c78c7c18a8e2799ee71e5506254989a0c7d571 Mon Sep 17 00:00:00 2001 From: xun082 <73689580+xun082@users.noreply.github.com> Date: Fri, 16 Jan 2026 11:21:48 +0800 Subject: [PATCH 6/8] fix(core): improve public path handling for server targets and clarify dev mode behavior --- packages/core/src/helpers/compiler.ts | 5 ++++- packages/core/src/plugins/output.ts | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/core/src/helpers/compiler.ts b/packages/core/src/helpers/compiler.ts index 9ca1950a1b..36e9b63f43 100644 --- a/packages/core/src/helpers/compiler.ts +++ b/packages/core/src/helpers/compiler.ts @@ -15,7 +15,10 @@ export const getPublicPathFromCompiler = ( if (typeof publicPath === 'string') { // 'auto' is a magic value in Rspack and behave like `publicPath: ""` - if (publicPath === 'auto') { + // Empty string is a valid value representing a relative path and should be preserved + // This is important for server targets (node) to enable relative paths for worker_threads + // See: https://github.com/web-infra-dev/rsbuild/issues/6539 + if (publicPath === 'auto' || publicPath === '') { return ''; } return publicPath.endsWith('/') ? publicPath : `${publicPath}/`; diff --git a/packages/core/src/plugins/output.ts b/packages/core/src/plugins/output.ts index a4135d5c63..5ef6c43b01 100644 --- a/packages/core/src/plugins/output.ts +++ b/packages/core/src/plugins/output.ts @@ -34,7 +34,13 @@ function getPublicPath({ if (!isDev && typeof output.assetPrefix === 'string') { return output.assetPrefix; } - // For node targets in dev mode, use empty string by default + // In dev mode, if server.base is set, use it for SSR hydration compatibility + // Otherwise, use empty string by default for node targets + if (isDev && server.base && server.base !== '/') { + // Use server.base as a relative path (no protocol/hostname) for node targets + return formatPublicPath(server.base); + } + // For node targets in dev mode without server.base, use empty string return ''; } From 287fc12a20e20a1c3b1d3aa6acb9d2ff6cd1e2fe Mon Sep 17 00:00:00 2001 From: neverland Date: Sun, 8 Feb 2026 10:35:34 +0800 Subject: [PATCH 7/8] Revert some changes --- packages/core/src/plugins/output.ts | 32 ++++------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/packages/core/src/plugins/output.ts b/packages/core/src/plugins/output.ts index 5ef6c43b01..8f6f41e31d 100644 --- a/packages/core/src/plugins/output.ts +++ b/packages/core/src/plugins/output.ts @@ -16,34 +16,15 @@ import type { function getPublicPath({ isDev, - isServer, config, context, }: { isDev: boolean; - isServer: boolean; config: NormalizedEnvironmentConfig; context: RsbuildContext; }) { const { dev, output, server } = config; - // For server targets (node), use empty string or explicit assetPrefix to enable relative paths - // This is important for worker_threads and other node-specific imports - // See: https://github.com/web-infra-dev/rsbuild/issues/6539 - if (isServer) { - if (!isDev && typeof output.assetPrefix === 'string') { - return output.assetPrefix; - } - // In dev mode, if server.base is set, use it for SSR hydration compatibility - // Otherwise, use empty string by default for node targets - if (isDev && server.base && server.base !== '/') { - // Use server.base as a relative path (no protocol/hostname) for node targets - return formatPublicPath(server.base); - } - // For node targets in dev mode without server.base, use empty string - return ''; - } - let publicPath = DEFAULT_ASSET_PREFIX; // If `mode` is `production` or `none`, use `output.assetPrefix` @@ -51,13 +32,6 @@ function getPublicPath({ if (typeof output.assetPrefix === 'string') { publicPath = output.assetPrefix; } - } else if ( - typeof output.assetPrefix === 'string' && - output.assetPrefix !== DEFAULT_ASSET_PREFIX - ) { - // In dev mode, prefer environment-specific `output.assetPrefix` over `dev.assetPrefix` - // but only if it's explicitly set to a non-default value - publicPath = output.assetPrefix; } else if (typeof dev.assetPrefix === 'string') { publicPath = dev.assetPrefix; } else if (dev.assetPrefix) { @@ -109,7 +83,6 @@ export const pluginOutput = (): RsbuildPlugin => ({ const publicPath = getPublicPath({ config, isDev, - isServer, context: api.context, }); @@ -141,7 +114,10 @@ export const pluginOutput = (): RsbuildPlugin => ({ } : posix.join(jsAsyncPath, jsFilename), ) - .publicPath(publicPath); + .publicPath(publicPath) + .bundlerInfo({ + force: false, + }); if (isServer) { chain.output.library({ From 6648c05ac92aaa8f90ef9256dd070afed3d99414 Mon Sep 17 00:00:00 2001 From: neverland Date: Sun, 8 Feb 2026 10:36:02 +0800 Subject: [PATCH 8/8] fix --- packages/core/src/plugins/output.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core/src/plugins/output.ts b/packages/core/src/plugins/output.ts index 8f6f41e31d..c1b02b7e1a 100644 --- a/packages/core/src/plugins/output.ts +++ b/packages/core/src/plugins/output.ts @@ -114,10 +114,7 @@ export const pluginOutput = (): RsbuildPlugin => ({ } : posix.join(jsAsyncPath, jsFilename), ) - .publicPath(publicPath) - .bundlerInfo({ - force: false, - }); + .publicPath(publicPath); if (isServer) { chain.output.library({