fix(core): fix Docusaurus outDir for sites using baseUrl (#11434)

This commit is contained in:
Sébastien Lorber 2025-09-26 12:58:41 +02:00 committed by GitHub
parent 016b80b55d
commit e41fa2e191
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 2493 additions and 46 deletions

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
module.exports = {
title: 'Hello',
baseUrl: '/myBaseUrl/',
url: 'https://docusaurus.io',
i18n: {
defaultLocale: 'en',
locales: ['en','fr', 'es', 'de', 'it'],
localeConfigs: {
en: {
baseUrl: '/myBaseUrl/',
},
fr: {
baseUrl: 'myBaseUrl/fr',
},
es: {
url: 'https://es.docusaurus.io',
// TODO it's not clear what should be the inferred outDir in this case
baseUrl: 'es',
},
de: {
// TODO it's not clear what should be the inferred outDir in this case
baseUrl: 'WHATEVER/de',
},
it: {
url: 'https://it.docusaurus.io',
// TODO it's not clear what should be the inferred outDir in this case
baseUrl: '',
},
}
}
};

View File

@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
module.exports = {
title: 'Hello 2',
baseUrl: '/myBaseUrl-2/',
url: 'https://docusaurus.io',
};

View File

@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
module.exports = {
title: 'Hello',
baseUrl: '/myBaseUrl/',
url: 'https://docusaurus.io',
};

View File

@ -6,40 +6,138 @@
*/
import path from 'path';
import {loadSetup} from './testUtils';
import {loadSiteFixture} from './testUtils';
describe('load', () => {
it('loads props for site', async () => {
const site = await loadSetup('custom-i18n-site');
expect(site.props).toMatchSnapshot();
describe('loadSite', () => {
describe('simple-site-with-baseUrl', () => {
const siteFixture = 'loadSiteFixtures/simple-site-with-baseUrl';
it('loads site', async () => {
const site = await loadSiteFixture(siteFixture);
expect(site.props).toMatchSnapshot();
});
it('loads site - custom outDir', async () => {
const site = await loadSiteFixture(siteFixture, {
outDir: 'custom-out-dir',
});
expect(site.props).toMatchSnapshot();
});
it('loads site - custom config', async () => {
const site = await loadSiteFixture(siteFixture, {
config: 'docusaurus.config.custom.js',
});
expect(site.props).toMatchSnapshot();
});
it('loads site - non-existing config', async () => {
await expect(() =>
loadSiteFixture(siteFixture, {
config: 'docusaurus.config.doesNotExist.js',
}),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Config file at "<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/__fixtures__/loadSiteFixtures/simple-site-with-baseUrl/docusaurus.config.doesNotExist.js" not found."`,
);
});
});
it('loads props for site - custom i18n path', async () => {
const site = await loadSetup('custom-i18n-site', {locale: 'zh-Hans'});
expect(site.props).toEqual(
expect.objectContaining({
baseUrl: '/zh-Hans/',
i18n: expect.objectContaining({
currentLocale: 'zh-Hans',
}),
localizationDir: path.join(
__dirname,
'__fixtures__/custom-i18n-site/i18n/zh-Hans-custom',
),
outDir: path.join(
__dirname,
'__fixtures__/custom-i18n-site/build/zh-Hans/',
),
routesPaths: ['/zh-Hans/404.html'],
siteConfig: expect.objectContaining({
describe('simple-site-with-baseUrl-i18n', () => {
const siteFixture = 'loadSiteFixtures/simple-site-with-baseUrl-i18n';
it('loads site', async () => {
const site = await loadSiteFixture(siteFixture);
expect(site.props).toMatchSnapshot();
});
it('loads site - custom outDir', async () => {
const site = await loadSiteFixture(siteFixture, {
outDir: 'custom-out-dir',
});
expect(site.props).toMatchSnapshot();
});
it('loads site - locale en', async () => {
const site = await loadSiteFixture(siteFixture, {
locale: 'en',
});
expect(site.props).toMatchSnapshot();
});
it('loads site - locale fr', async () => {
const site = await loadSiteFixture(siteFixture, {
locale: 'fr',
});
expect(site.props).toMatchSnapshot();
});
it('loads site - locale fr + custom outDir', async () => {
const site = await loadSiteFixture(siteFixture, {
outDir: 'custom-out-dir',
locale: 'fr',
});
expect(site.props).toMatchSnapshot();
});
it('loads site - locale es', async () => {
const site = await loadSiteFixture(siteFixture, {
locale: 'es',
});
expect(site.props).toMatchSnapshot();
});
it('loads site - locale de', async () => {
const site = await loadSiteFixture(siteFixture, {
locale: 'de',
});
expect(site.props).toMatchSnapshot();
});
it('loads site - locale it', async () => {
const site = await loadSiteFixture(siteFixture, {
locale: 'it',
});
expect(site.props).toMatchSnapshot();
});
});
describe('custom-i18n-site', () => {
it('loads site', async () => {
const site = await loadSiteFixture('custom-i18n-site');
expect(site.props).toMatchSnapshot();
});
it('loads site - zh-Hans locale', async () => {
const site = await loadSiteFixture('custom-i18n-site', {
locale: 'zh-Hans',
});
expect(site.props).toEqual(
expect.objectContaining({
baseUrl: '/zh-Hans/',
i18n: expect.objectContaining({
currentLocale: 'zh-Hans',
}),
localizationDir: path.join(
__dirname,
'__fixtures__/custom-i18n-site/i18n/zh-Hans-custom',
),
outDir: path.join(
__dirname,
'__fixtures__/custom-i18n-site/build/zh-Hans/',
),
routesPaths: ['/zh-Hans/404.html'],
siteConfig: expect.objectContaining({
baseUrl: '/zh-Hans/',
}),
siteStorage: {
namespace: '',
type: 'localStorage',
},
plugins: site.props.plugins,
}),
siteStorage: {
namespace: '',
type: 'localStorage',
},
plugins: site.props.plugins,
}),
);
);
});
});
});

View File

@ -6,14 +6,14 @@
*/
import path from 'path';
import {loadSite, type LoadContextParams} from '../site';
import type {Site} from '@docusaurus/types';
import {loadSite, type LoadContextParams, type Site} from '../site';
// Helper methods to setup dummy/fake projects.
export async function loadSetup(
export async function loadSiteFixture(
name: string,
options?: Partial<LoadContextParams>,
): Promise<Site> {
const fixtures = path.join(__dirname, '__fixtures__');
return loadSite({siteDir: path.join(fixtures, name), ...options});
return loadSite({
siteDir: path.join(__dirname, '__fixtures__', name),
...options,
});
}

View File

@ -120,7 +120,17 @@ export async function loadContext(
// eventually including the /<locale>/ suffix
const baseUrl = localeConfig.baseUrl;
const outDir = path.join(path.resolve(siteDir, baseOutDir), baseUrl);
// TODO not ideal: we should allow configuring a custom outDir for each locale
// The site baseUrl should be 100% decoupled from the file system output shape
// We added this logic to restore v3 retro-compatibility, because by default
// Docusaurus always wrote to ./build for sites having a baseUrl
// See also https://github.com/facebook/docusaurus/issues/11433
// This logic assumes the locale baseUrl will start with the site baseUrl
// which is the case if an explicit locale baseUrl is not provided
// but in practice a custom locale baseUrl could be anything now
const outDirBaseUrl = baseUrl.replace(initialSiteConfig.baseUrl, '/');
const outDir = path.join(path.resolve(siteDir, baseOutDir), outDirBaseUrl);
const localizationDir = path.resolve(
siteDir,

View File

@ -8,7 +8,7 @@
import webpack from 'webpack';
import {createBuildClientConfig, createStartClientConfig} from '../client';
import {loadSetup} from '../../server/__tests__/testUtils';
import {loadSiteFixture} from '../../server/__tests__/testUtils';
import {createConfigureWebpackUtils} from '../configure';
import {
DEFAULT_FASTER_CONFIG,
@ -23,7 +23,7 @@ function createTestConfigureWebpackUtils() {
describe('webpack dev config', () => {
it('simple start', async () => {
const {props} = await loadSetup('simple-site');
const {props} = await loadSiteFixture('simple-site');
const {clientConfig} = await createStartClientConfig({
props,
faster: DEFAULT_FASTER_CONFIG,
@ -35,7 +35,7 @@ describe('webpack dev config', () => {
});
it('simple build', async () => {
const {props} = await loadSetup('simple-site');
const {props} = await loadSiteFixture('simple-site');
const {config} = await createBuildClientConfig({
props,
faster: DEFAULT_FASTER_CONFIG,
@ -47,7 +47,7 @@ describe('webpack dev config', () => {
});
it('custom start', async () => {
const {props} = await loadSetup('custom-site');
const {props} = await loadSiteFixture('custom-site');
const {clientConfig} = await createStartClientConfig({
props,
faster: DEFAULT_FASTER_CONFIG,
@ -59,7 +59,7 @@ describe('webpack dev config', () => {
});
it('custom build', async () => {
const {props} = await loadSetup('custom-site');
const {props} = await loadSiteFixture('custom-site');
const {config} = await createBuildClientConfig({
props,
faster: DEFAULT_FASTER_CONFIG,

View File

@ -9,7 +9,7 @@ import {jest} from '@jest/globals';
import webpack from 'webpack';
import createServerConfig from '../server';
import {loadSetup} from '../../server/__tests__/testUtils';
import {loadSiteFixture} from '../../server/__tests__/testUtils';
import {createConfigureWebpackUtils} from '../configure';
import {DEFAULT_FUTURE_CONFIG} from '../../server/configValidation';
@ -22,7 +22,7 @@ function createTestConfigureWebpackUtils() {
describe('webpack production config', () => {
it('simple', async () => {
jest.spyOn(console, 'log').mockImplementation(() => {});
const {props} = await loadSetup('simple-site');
const {props} = await loadSiteFixture('simple-site');
const {config} = await createServerConfig({
props,
configureWebpackUtils: await createTestConfigureWebpackUtils(),
@ -32,7 +32,7 @@ describe('webpack production config', () => {
it('custom', async () => {
jest.spyOn(console, 'log').mockImplementation(() => {});
const {props} = await loadSetup('custom-site');
const {props} = await loadSiteFixture('custom-site');
const {config} = await createServerConfig({
props,
configureWebpackUtils: await createTestConfigureWebpackUtils(),