diff --git a/.changeset/thirty-buses-exist.md b/.changeset/thirty-buses-exist.md new file mode 100644 index 0000000000..3c8f2de295 --- /dev/null +++ b/.changeset/thirty-buses-exist.md @@ -0,0 +1,19 @@ +--- +"@lynx-js/rspeedy": patch +--- + +Support configure the base path of the server. + +By default, the base path of the server is `/`, and users can access lynx bundle through `http://:/main.lynx.bundle` +If you want to access lynx bundle through `http://:/foo/main.lynx.bundle`, you can change `server.base` to `/foo` + +example: + +```js +import { defineConfig } from '@lynx-js/rspeedy'; +export default defineConfig({ + server: { + base: '/dist', + }, +}); +``` diff --git a/packages/rspeedy/core/etc/rspeedy.api.md b/packages/rspeedy/core/etc/rspeedy.api.md index ac78a4be14..4996477349 100644 --- a/packages/rspeedy/core/etc/rspeedy.api.md +++ b/packages/rspeedy/core/etc/rspeedy.api.md @@ -254,6 +254,7 @@ export type RspeedyInstance = RsbuildInstance & { // @public export interface Server { + base?: string | undefined; headers?: Record | undefined; host?: string | undefined; port?: number | undefined; diff --git a/packages/rspeedy/core/src/config/rsbuild/index.ts b/packages/rspeedy/core/src/config/rsbuild/index.ts index 72f40e50af..1edbcf1166 100644 --- a/packages/rspeedy/core/src/config/rsbuild/index.ts +++ b/packages/rspeedy/core/src/config/rsbuild/index.ts @@ -70,13 +70,18 @@ export function toRsbuildConfig( tsconfigPath: config.source?.tsconfigPath, }, server: { + base: config.server?.base, + headers: config.server?.headers, + host: config.server?.host, + port: config.server?.port, }, plugins: config.plugins, performance: { chunkSplit: config.performance?.chunkSplit, + removeConsole: toRsbuildRemoveConsole(config) as | ConsoleType[] | false diff --git a/packages/rspeedy/core/src/config/server/index.ts b/packages/rspeedy/core/src/config/server/index.ts index ae40885f4b..844fee4e37 100644 --- a/packages/rspeedy/core/src/config/server/index.ts +++ b/packages/rspeedy/core/src/config/server/index.ts @@ -7,6 +7,29 @@ * @public */ export interface Server { + /** + * Configure the base path of the server. + * + * @remarks + * By default, the base path of the server is `/`, and users can access lynx bundle through `http://:/main.lynx.bundle` + * + * If you want to access lynx bundle through `http://:/foo/main.lynx.bundle`, you can change `server.base` to `/foo` + * + * you can refer to {@link https://rsbuild.dev/config/server/base | server.base } for more information. + * + * @example + * + * ```js + * import { defineConfig } from '@lynx-js/rspeedy' + * export default defineConfig({ + * server: { + * base: '/dist' + * }, + * }) + * ``` + */ + base?: string | undefined + /** * Adds headers to all responses. * diff --git a/packages/rspeedy/core/src/plugins/dev.plugin.ts b/packages/rspeedy/core/src/plugins/dev.plugin.ts index 7c56f17510..4a1e7c2994 100644 --- a/packages/rspeedy/core/src/plugins/dev.plugin.ts +++ b/packages/rspeedy/core/src/plugins/dev.plugin.ts @@ -61,6 +61,13 @@ export function pluginDev( } } + if (server?.base) { + if ((assetPrefix as string).endsWith('/')) { + assetPrefix = (assetPrefix as string).slice(0, -1) + } + assetPrefix = `${assetPrefix}${server.base}/` + } + debug(`dev.assetPrefix is normalized to ${assetPrefix}`) api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) => { diff --git a/packages/rspeedy/core/test/config/server.test-d.ts b/packages/rspeedy/core/test/config/server.test-d.ts index 96a500be55..8c445a1b06 100644 --- a/packages/rspeedy/core/test/config/server.test-d.ts +++ b/packages/rspeedy/core/test/config/server.test-d.ts @@ -6,6 +6,11 @@ import { assertType, describe, test } from 'vitest' import type { Server } from '../../src/index.js' describe('Config - Server', () => { + test('server.base', () => { + assertType({}) + assertType({ base: '/foo' }) + }) + test('server.headers', () => { assertType({}) assertType({ diff --git a/packages/rspeedy/core/test/config/validate.test.ts b/packages/rspeedy/core/test/config/validate.test.ts index 1755bb99be..b5e58698c8 100644 --- a/packages/rspeedy/core/test/config/validate.test.ts +++ b/packages/rspeedy/core/test/config/validate.test.ts @@ -1553,6 +1553,8 @@ describe('Config Validation', () => { test('valid type', () => { const cases: Server[] = [ {}, + { base: '/foo' }, + { base: '/bar' }, { headers: {} }, { headers: { foo: 'bar' } }, { headers: { foo: [] } }, @@ -1568,6 +1570,26 @@ describe('Config Validation', () => { }) test('invalid type', () => { + expect(() => validate({ server: { base: 123 } })) + .toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid configuration. + + Invalid config on \`$input.server.base\`. + - Expect to be (string | undefined) + - Got: number + ] + `) + + expect(() => validate({ server: { base: null } })) + .toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid configuration. + + Invalid config on \`$input.server.base\`. + - Expect to be (string | undefined) + - Got: null + ] + `) + expect(() => validate({ server: { headers: null } })) .toThrowErrorMatchingInlineSnapshot(` [Error: Invalid configuration. diff --git a/packages/rspeedy/core/test/plugins/dev.plugin.test.ts b/packages/rspeedy/core/test/plugins/dev.plugin.test.ts index 65cec8930b..f7ee8eafc3 100644 --- a/packages/rspeedy/core/test/plugins/dev.plugin.test.ts +++ b/packages/rspeedy/core/test/plugins/dev.plugin.test.ts @@ -375,4 +375,45 @@ describe('Plugins - Dev', () => { ], }) }) + + test('server.base without /', async () => { + try { + const rsbuild = await createStubRspeedy({ + server: { + base: 'dist', + }, + }) + + await rsbuild.unwrapConfig() + } catch (error) { + expect(error).toMatchInlineSnapshot( + `[Error: [rsbuild:config] The "server.base" option should start with a slash, for example: "/base"]`, + ) + } + }) + + test('dev.assetPrefix with server.base', async () => { + const rsbuild = await createStubRspeedy({ + dev: { + assetPrefix: 'http://example.com/', + }, + server: { + base: '/dist', + }, + }) + + const config = await rsbuild.unwrapConfig() + + expect(typeof config.output?.publicPath).toBe('string') + + expect(config.output?.publicPath).toContain('http://example.com/') + expect(config.output?.publicPath).toContain('/dist/') + + const { port, hostname, pathname } = new URL(config.output!.publicPath!) + + expect(port).toBe('') + expect(isIP(hostname)).toBe(0) + expect(hostname).toBe('example.com') + expect(pathname).toBe('/dist/') + }) })