diff --git a/packages/docusaurus-types/src/config.d.ts b/packages/docusaurus-types/src/config.d.ts index 8289e8b22282..4e646cc51fe8 100644 --- a/packages/docusaurus-types/src/config.d.ts +++ b/packages/docusaurus-types/src/config.d.ts @@ -132,7 +132,16 @@ export type FasterConfig = { rspackBundler: boolean; }; +export type FutureV4Config = { + removeLegacyPostBuildHeadAttribute: boolean; +}; + export type FutureConfig = { + /** + * Turns v4 future flags on + */ + v4: FutureV4Config; + experimental_faster: FasterConfig; experimental_storage: StorageConfig; @@ -451,6 +460,7 @@ export type Config = Overwrite< future?: Overwrite< DeepPartial, { + v4?: boolean | FutureV4Config; experimental_faster?: boolean | FasterConfig; } >; diff --git a/packages/docusaurus/src/server/__tests__/configValidation.test.ts b/packages/docusaurus/src/server/__tests__/configValidation.test.ts index c8784efc2cc8..63ea0c294d35 100644 --- a/packages/docusaurus/src/server/__tests__/configValidation.test.ts +++ b/packages/docusaurus/src/server/__tests__/configValidation.test.ts @@ -11,12 +11,15 @@ import { DEFAULT_FASTER_CONFIG, DEFAULT_FASTER_CONFIG_TRUE, DEFAULT_FUTURE_CONFIG, + DEFAULT_FUTURE_V4_CONFIG, + DEFAULT_FUTURE_V4_CONFIG_TRUE, DEFAULT_STORAGE_CONFIG, validateConfig, } from '../configValidation'; import type { FasterConfig, FutureConfig, + FutureV4Config, StorageConfig, } from '@docusaurus/types/src/config'; import type {Config, DocusaurusConfig, PluginConfig} from '@docusaurus/types'; @@ -45,6 +48,9 @@ describe('normalizeConfig', () => { ...DEFAULT_CONFIG, ...baseConfig, future: { + v4: { + removeLegacyPostBuildHeadAttribute: true, + }, experimental_faster: { swcJsLoader: true, swcJsMinimizer: true, @@ -744,6 +750,9 @@ describe('future', () => { it('accepts future - full', () => { const future: DocusaurusConfig['future'] = { + v4: { + removeLegacyPostBuildHeadAttribute: true, + }, experimental_faster: { swcJsLoader: true, swcJsMinimizer: true, @@ -1571,4 +1580,149 @@ describe('future', () => { }); }); }); + + describe('v4', () => { + function v4Containing(v4: Partial) { + return futureContaining({ + v4: expect.objectContaining(v4), + }); + } + + it('accepts v4 - undefined', () => { + expect( + normalizeConfig({ + future: { + v4: undefined, + }, + }), + ).toEqual(futureContaining(DEFAULT_FUTURE_CONFIG)); + }); + + it('accepts v4 - empty', () => { + expect( + normalizeConfig({ + future: {v4: {}}, + }), + ).toEqual(futureContaining(DEFAULT_FUTURE_CONFIG)); + }); + + it('accepts v4 - full', () => { + const v4: FutureV4Config = { + removeLegacyPostBuildHeadAttribute: true, + }; + expect( + normalizeConfig({ + future: { + v4, + }, + }), + ).toEqual(v4Containing(v4)); + }); + + it('accepts v4 - false', () => { + expect( + normalizeConfig({ + future: {v4: false}, + }), + ).toEqual(v4Containing(DEFAULT_FUTURE_V4_CONFIG)); + }); + + it('accepts v4 - true', () => { + expect( + normalizeConfig({ + future: {v4: true}, + }), + ).toEqual(v4Containing(DEFAULT_FUTURE_V4_CONFIG_TRUE)); + }); + + it('rejects v4 - number', () => { + // @ts-expect-error: invalid + const v4: Partial = 42; + expect(() => + normalizeConfig({ + future: { + v4, + }, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""future.v4" must be one of [object, boolean] + " + `); + }); + + describe('removeLegacyPostBuildHeadAttribute', () => { + it('accepts - undefined', () => { + const v4: Partial = { + removeLegacyPostBuildHeadAttribute: undefined, + }; + expect( + normalizeConfig({ + future: { + v4, + }, + }), + ).toEqual(v4Containing({removeLegacyPostBuildHeadAttribute: false})); + }); + + it('accepts - true', () => { + const v4: Partial = { + removeLegacyPostBuildHeadAttribute: true, + }; + expect( + normalizeConfig({ + future: { + v4, + }, + }), + ).toEqual(v4Containing({removeLegacyPostBuildHeadAttribute: true})); + }); + + it('accepts - false', () => { + const v4: Partial = { + removeLegacyPostBuildHeadAttribute: false, + }; + expect( + normalizeConfig({ + future: { + v4, + }, + }), + ).toEqual(v4Containing({removeLegacyPostBuildHeadAttribute: false})); + }); + + it('rejects - null', () => { + const v4: Partial = { + // @ts-expect-error: invalid + removeLegacyPostBuildHeadAttribute: 42, + }; + expect(() => + normalizeConfig({ + future: { + v4, + }, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""future.v4.removeLegacyPostBuildHeadAttribute" must be a boolean + " + `); + }); + + it('rejects - number', () => { + const v4: Partial = { + // @ts-expect-error: invalid + removeLegacyPostBuildHeadAttribute: 42, + }; + expect(() => + normalizeConfig({ + future: { + v4, + }, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""future.v4.removeLegacyPostBuildHeadAttribute" must be a boolean + " + `); + }); + }); + }); }); diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index 17682cd91471..049dd819c3c0 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -19,6 +19,7 @@ import { import type { FasterConfig, FutureConfig, + FutureV4Config, StorageConfig, } from '@docusaurus/types/src/config'; import type { @@ -60,7 +61,17 @@ export const DEFAULT_FASTER_CONFIG_TRUE: FasterConfig = { rspackBundler: true, }; +export const DEFAULT_FUTURE_V4_CONFIG: FutureV4Config = { + removeLegacyPostBuildHeadAttribute: false, +}; + +// When using the "v4: true" shortcut +export const DEFAULT_FUTURE_V4_CONFIG_TRUE: FutureV4Config = { + removeLegacyPostBuildHeadAttribute: true, +}; + export const DEFAULT_FUTURE_CONFIG: FutureConfig = { + v4: DEFAULT_FUTURE_V4_CONFIG, experimental_faster: DEFAULT_FASTER_CONFIG, experimental_storage: DEFAULT_STORAGE_CONFIG, experimental_router: 'browser', @@ -242,6 +253,22 @@ const FASTER_CONFIG_SCHEMA = Joi.alternatives() .optional() .default(DEFAULT_FASTER_CONFIG); +const FUTURE_V4_SCHEMA = Joi.alternatives() + .try( + Joi.object({ + removeLegacyPostBuildHeadAttribute: Joi.boolean().default( + DEFAULT_FUTURE_V4_CONFIG.removeLegacyPostBuildHeadAttribute, + ), + }), + Joi.boolean() + .required() + .custom((bool) => + bool ? DEFAULT_FUTURE_V4_CONFIG_TRUE : DEFAULT_FUTURE_V4_CONFIG, + ), + ) + .optional() + .default(DEFAULT_FUTURE_V4_CONFIG); + const STORAGE_CONFIG_SCHEMA = Joi.object({ type: Joi.string() .equal('localStorage', 'sessionStorage') @@ -254,6 +281,7 @@ const STORAGE_CONFIG_SCHEMA = Joi.object({ .default(DEFAULT_STORAGE_CONFIG); const FUTURE_CONFIG_SCHEMA = Joi.object({ + v4: FUTURE_V4_SCHEMA, experimental_faster: FASTER_CONFIG_SCHEMA, experimental_storage: STORAGE_CONFIG_SCHEMA, experimental_router: Joi.string()