diff --git a/packages/docusaurus-types/src/config.d.ts b/packages/docusaurus-types/src/config.d.ts index bc4180ca7b37..cc70ef21d218 100644 --- a/packages/docusaurus-types/src/config.d.ts +++ b/packages/docusaurus-types/src/config.d.ts @@ -39,6 +39,7 @@ export type FasterConfig = { export type FutureV4Config = { removeLegacyPostBuildHeadAttribute: boolean; useCssCascadeLayers: boolean; + siteStorageNamespacing: boolean; }; // VCS (Version Control System) info about a given change, e.g., a git commit. @@ -96,8 +97,6 @@ export type FutureConfig = { experimental_faster: FasterConfig; - experimental_storage: StorageConfig; - experimental_vcs: VcsConfig; /** @@ -180,6 +179,13 @@ export type DocusaurusConfig = { * Similar to Remix future flags, see https://remix.run/blog/future-flags */ future: FutureConfig; + /** + * Site-wide browser storage options that theme authors should strive to + * respect. + * + * @see https://docusaurus.io/docs/api/docusaurus-config#storage + */ + storage: StorageConfig; /** * This option adds `` to * every page to tell search engines to avoid indexing your site. diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap index 091d94e9ac18..704bb6aee625 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap @@ -20,10 +20,6 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -31,6 +27,7 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -73,6 +70,10 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -105,10 +106,6 @@ exports[`loadSiteConfig website with ts + js config 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -116,6 +113,7 @@ exports[`loadSiteConfig website with ts + js config 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -158,6 +156,10 @@ exports[`loadSiteConfig website with ts + js config 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -190,10 +192,6 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -201,6 +199,7 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -243,6 +242,10 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -275,10 +278,6 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -286,6 +285,7 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -328,6 +328,10 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -360,10 +364,6 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -371,6 +371,7 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -413,6 +414,10 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -445,10 +450,6 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -456,6 +457,7 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -498,6 +500,10 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -530,10 +536,6 @@ exports[`loadSiteConfig website with valid async config 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -541,6 +543,7 @@ exports[`loadSiteConfig website with valid async config 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -585,6 +588,10 @@ exports[`loadSiteConfig website with valid async config 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "Hello World", "themeConfig": {}, @@ -617,10 +624,6 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -628,6 +631,7 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -672,6 +676,10 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "Hello World", "themeConfig": {}, @@ -704,10 +712,6 @@ exports[`loadSiteConfig website with valid config creator function 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -715,6 +719,7 @@ exports[`loadSiteConfig website with valid config creator function 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -759,6 +764,10 @@ exports[`loadSiteConfig website with valid config creator function 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "Hello World", "themeConfig": {}, @@ -794,10 +803,6 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -805,6 +810,7 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -857,6 +863,10 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "Hello World", "themeConfig": {}, diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap index 6990d4d4a006..08fc5ae5ca38 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap @@ -100,10 +100,6 @@ exports[`loadSite custom-i18n-site loads site 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -111,6 +107,7 @@ exports[`loadSite custom-i18n-site loads site 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -161,6 +158,10 @@ exports[`loadSite custom-i18n-site loads site 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -273,10 +274,6 @@ exports[`loadSite simple-site-with-baseUrl loads site - custom config 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -284,6 +281,7 @@ exports[`loadSite simple-site-with-baseUrl loads site - custom config 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -326,6 +324,10 @@ exports[`loadSite simple-site-with-baseUrl loads site - custom config 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -438,10 +440,6 @@ exports[`loadSite simple-site-with-baseUrl loads site - custom outDir 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -449,6 +447,7 @@ exports[`loadSite simple-site-with-baseUrl loads site - custom outDir 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -491,6 +490,10 @@ exports[`loadSite simple-site-with-baseUrl loads site - custom outDir 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -603,10 +606,6 @@ exports[`loadSite simple-site-with-baseUrl loads site 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -614,6 +613,7 @@ exports[`loadSite simple-site-with-baseUrl loads site 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -656,6 +656,10 @@ exports[`loadSite simple-site-with-baseUrl loads site 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -812,10 +816,6 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale fr + custom "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -823,6 +823,7 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale fr + custom }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -887,6 +888,10 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale fr + custom "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -1043,10 +1048,6 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - custom outDir 1`] = "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -1054,6 +1055,7 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - custom outDir 1`] = }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -1118,6 +1120,10 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - custom outDir 1`] = "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -1274,10 +1280,6 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale de 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -1285,6 +1287,7 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale de 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -1349,6 +1352,10 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale de 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -1505,10 +1512,6 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale en 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -1516,6 +1519,7 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale en 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -1580,6 +1584,10 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale en 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -1736,10 +1744,6 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale es 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -1747,6 +1751,7 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale es 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -1811,6 +1816,10 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale es 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -1967,10 +1976,6 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale fr 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -1978,6 +1983,7 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale fr 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -2042,6 +2048,10 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale fr 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -2198,10 +2208,6 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale it 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -2209,6 +2215,7 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale it 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -2273,6 +2280,10 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site - locale it 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, @@ -2429,10 +2440,6 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site 1`] = ` "swcJsMinimizer": false, }, "experimental_router": "browser", - "experimental_storage": { - "namespace": false, - "type": "localStorage", - }, "experimental_vcs": { "getFileCreationInfo": [Function], "getFileLastUpdateInfo": [Function], @@ -2440,6 +2447,7 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site 1`] = ` }, "v4": { "removeLegacyPostBuildHeadAttribute": false, + "siteStorageNamespacing": false, "useCssCascadeLayers": false, }, }, @@ -2504,6 +2512,10 @@ exports[`loadSite simple-site-with-baseUrl-i18n loads site 1`] = ` "staticDirectories": [ "static", ], + "storage": { + "namespace": false, + "type": "localStorage", + }, "stylesheets": [], "tagline": "", "themeConfig": {}, diff --git a/packages/docusaurus/src/server/__tests__/configValidation.test.ts b/packages/docusaurus/src/server/__tests__/configValidation.test.ts index 379ce8af2898..42475f8bc26a 100644 --- a/packages/docusaurus/src/server/__tests__/configValidation.test.ts +++ b/packages/docusaurus/src/server/__tests__/configValidation.test.ts @@ -61,6 +61,7 @@ describe('normalizeConfig', () => { v4: { removeLegacyPostBuildHeadAttribute: true, useCssCascadeLayers: true, + siteStorageNamespacing: true, }, experimental_faster: { swcJsLoader: true, @@ -73,10 +74,6 @@ describe('normalizeConfig', () => { ssgWorkerThreads: true, gitEagerVcs: true, }, - experimental_storage: { - type: 'sessionStorage', - namespace: true, - }, experimental_vcs: { initialize: (_params) => {}, getFileCreationInfo: (_filePath) => null, @@ -84,6 +81,10 @@ describe('normalizeConfig', () => { }, experimental_router: 'hash', }, + storage: { + type: 'sessionStorage', + namespace: true, + }, tagline: 'my awesome site', organizationName: 'facebook', projectName: 'docusaurus', @@ -1060,6 +1061,303 @@ describe('presets', () => { }); }); +describe('storage', () => { + function storageContaining(storage: Partial) { + return expect.objectContaining({ + storage: expect.objectContaining(storage), + }); + } + + it('accepts storage - undefined', () => { + expect( + normalizeConfig({ + storage: undefined, + }), + ).toEqual(storageContaining(DEFAULT_STORAGE_CONFIG)); + }); + + it('accepts storage - empty', () => { + expect( + normalizeConfig({ + storage: {}, + }), + ).toEqual(storageContaining(DEFAULT_STORAGE_CONFIG)); + }); + + it('accepts storage - full', () => { + const storage: StorageConfig = { + type: 'sessionStorage', + namespace: 'myNamespace', + }; + expect( + normalizeConfig({ + storage, + }), + ).toEqual(storageContaining(storage)); + }); + + it('rejects storage - boolean', () => { + // @ts-expect-error: invalid + const storage: Partial = true; + expect(() => + normalizeConfig({ + storage, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""storage" must be of type object + " + `); + }); + + it('rejects storage - number', () => { + // @ts-expect-error: invalid + const storage: Partial = 42; + expect(() => + normalizeConfig({ + storage, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""storage" must be of type object + " + `); + }); + + it('rejects legacy future.experimental_storage', () => { + expect(() => + normalizeConfig({ + future: { + // @ts-expect-error: legacy config removed + experimental_storage: {namespace: true}, + }, + }), + ).toThrowErrorMatchingInlineSnapshot(` + "Docusaurus config \`future.experimental_storage\` has been promoted to stable and is no longer supported. + Please use the top-level \`storage\` option instead. + See https://docusaurus.io/docs/api/docusaurus-config#storage + " + `); + }); + + describe('type', () => { + it('accepts type', () => { + const storage: Partial = { + type: 'sessionStorage', + }; + expect( + normalizeConfig({ + storage, + }), + ).toEqual( + storageContaining({ + ...DEFAULT_STORAGE_CONFIG, + ...storage, + }), + ); + }); + + it('accepts type - undefined', () => { + const storage: Partial = { + type: undefined, + }; + expect( + normalizeConfig({ + storage, + }), + ).toEqual(storageContaining({type: 'localStorage'})); + }); + + it('rejects type - null', () => { + // @ts-expect-error: invalid + const storage: Partial = {type: 42}; + expect(() => + normalizeConfig({ + storage, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""storage.type" must be one of [localStorage, sessionStorage] + "storage.type" must be a string + " + `); + }); + + it('rejects type - number', () => { + // @ts-expect-error: invalid + const storage: Partial = {type: 42}; + expect(() => + normalizeConfig({ + storage, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""storage.type" must be one of [localStorage, sessionStorage] + "storage.type" must be a string + " + `); + }); + + it('rejects type - invalid enum value', () => { + // @ts-expect-error: invalid + const storage: Partial = {type: 'badType'}; + expect(() => + normalizeConfig({ + storage, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""storage.type" must be one of [localStorage, sessionStorage] + " + `); + }); + }); + + describe('namespace', () => { + it('defaults to true when future.v4.siteStorageNamespacing is enabled', () => { + expect( + normalizeConfig({ + future: { + v4: { + siteStorageNamespacing: true, + }, + }, + storage: { + type: 'localStorage', + }, + }), + ).toEqual(storageContaining({namespace: true})); + }); + + it('defaults to false when future.v4.siteStorageNamespacing is disabled', () => { + expect( + normalizeConfig({ + future: { + v4: { + siteStorageNamespacing: false, + }, + }, + storage: { + type: 'localStorage', + }, + }), + ).toEqual(storageContaining({namespace: false})); + }); + + it('defaults to false when namespace is not provided', () => { + expect( + normalizeConfig({ + storage: { + type: 'localStorage', + }, + }), + ).toEqual(storageContaining({namespace: false})); + }); + + it('respects explicit namespace when future.v4.siteStorageNamespacing is enabled', () => { + expect( + normalizeConfig({ + future: { + v4: { + siteStorageNamespacing: true, + }, + }, + storage: { + namespace: false, + }, + }), + ).toEqual(storageContaining({namespace: false})); + }); + + it('respects explicit namespace when future.v4.siteStorageNamespacing is disabled', () => { + expect( + normalizeConfig({ + future: { + v4: { + siteStorageNamespacing: false, + }, + }, + storage: { + namespace: false, + }, + }), + ).toEqual(storageContaining({namespace: false})); + }); + + it('respects explicit string namespace when future.v4.siteStorageNamespacing is enabled', () => { + expect( + normalizeConfig({ + future: { + v4: { + siteStorageNamespacing: true, + }, + }, + storage: { + namespace: 'myNamespace', + }, + }), + ).toEqual(storageContaining({namespace: 'myNamespace'})); + }); + + it('respects explicit string namespace when future.v4.siteStorageNamespacing is disabled', () => { + expect( + normalizeConfig({ + future: { + v4: { + siteStorageNamespacing: false, + }, + }, + storage: { + namespace: 'myNamespace', + }, + }), + ).toEqual(storageContaining({namespace: 'myNamespace'})); + }); + + it('accepts namespace - boolean', () => { + const storage: Partial = { + namespace: true, + }; + expect( + normalizeConfig({ + storage, + }), + ).toEqual(storageContaining(storage)); + }); + + it('accepts namespace - string', () => { + const storage: Partial = { + namespace: 'myNamespace', + }; + expect( + normalizeConfig({ + storage, + }), + ).toEqual(storageContaining(storage)); + }); + + it('rejects namespace - null', () => { + const storage: Partial = {namespace: null}; + expect(() => + normalizeConfig({ + storage, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""storage.namespace" must be one of [string, boolean] + " + `); + }); + + it('rejects namespace - number', () => { + // @ts-expect-error: invalid + const storage: Partial = {namespace: 42}; + expect(() => + normalizeConfig({ + storage, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""storage.namespace" must be one of [string, boolean] + " + `); + }); + }); +}); + describe('future', () => { function futureContaining(future: Partial) { return expect.objectContaining({ @@ -1088,6 +1386,7 @@ describe('future', () => { v4: { removeLegacyPostBuildHeadAttribute: true, useCssCascadeLayers: true, + siteStorageNamespacing: true, }, experimental_faster: { swcJsLoader: true, @@ -1105,10 +1404,6 @@ describe('future', () => { getFileCreationInfo: (_filePath) => null, getFileLastUpdateInfo: (_filePath) => null, }, - experimental_storage: { - type: 'sessionStorage', - namespace: 'myNamespace', - }, experimental_router: 'hash', }; expect( @@ -1215,213 +1510,6 @@ describe('future', () => { }); }); - describe('storage', () => { - function storageContaining(storage: Partial) { - return futureContaining({ - experimental_storage: expect.objectContaining(storage), - }); - } - - it('accepts storage - undefined', () => { - expect( - normalizeConfig({ - future: { - experimental_storage: undefined, - }, - }), - ).toEqual(futureContaining(DEFAULT_FUTURE_CONFIG)); - }); - - it('accepts storage - empty', () => { - expect( - normalizeConfig({ - future: {experimental_storage: {}}, - }), - ).toEqual(futureContaining(DEFAULT_FUTURE_CONFIG)); - }); - - it('accepts storage - full', () => { - const storage: StorageConfig = { - type: 'sessionStorage', - namespace: 'myNamespace', - }; - expect( - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toEqual(storageContaining(storage)); - }); - - it('rejects storage - boolean', () => { - // @ts-expect-error: invalid - const storage: Partial = true; - expect(() => - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toThrowErrorMatchingInlineSnapshot(` - ""future.experimental_storage" must be of type object - " - `); - }); - - it('rejects storage - number', () => { - // @ts-expect-error: invalid - const storage: Partial = 42; - expect(() => - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toThrowErrorMatchingInlineSnapshot(` - ""future.experimental_storage" must be of type object - " - `); - }); - - describe('type', () => { - it('accepts type', () => { - const storage: Partial = { - type: 'sessionStorage', - }; - expect( - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toEqual( - storageContaining({ - ...DEFAULT_STORAGE_CONFIG, - ...storage, - }), - ); - }); - - it('accepts type - undefined', () => { - const storage: Partial = { - type: undefined, - }; - expect( - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toEqual(storageContaining({type: 'localStorage'})); - }); - - it('rejects type - null', () => { - // @ts-expect-error: invalid - const storage: Partial = {type: 42}; - expect(() => - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toThrowErrorMatchingInlineSnapshot(` - ""future.experimental_storage.type" must be one of [localStorage, sessionStorage] - "future.experimental_storage.type" must be a string - " - `); - }); - - it('rejects type - number', () => { - // @ts-expect-error: invalid - const storage: Partial = {type: 42}; - expect(() => - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toThrowErrorMatchingInlineSnapshot(` - ""future.experimental_storage.type" must be one of [localStorage, sessionStorage] - "future.experimental_storage.type" must be a string - " - `); - }); - - it('rejects type - invalid enum value', () => { - // @ts-expect-error: invalid - const storage: Partial = {type: 'badType'}; - expect(() => - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toThrowErrorMatchingInlineSnapshot(` - ""future.experimental_storage.type" must be one of [localStorage, sessionStorage] - " - `); - }); - }); - - describe('namespace', () => { - it('accepts namespace - boolean', () => { - const storage: Partial = { - namespace: true, - }; - expect( - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toEqual(storageContaining(storage)); - }); - - it('accepts namespace - string', () => { - const storage: Partial = { - namespace: 'myNamespace', - }; - expect( - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toEqual(storageContaining(storage)); - }); - - it('rejects namespace - null', () => { - const storage: Partial = {namespace: null}; - expect(() => - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toThrowErrorMatchingInlineSnapshot(` - ""future.experimental_storage.namespace" must be one of [string, boolean] - " - `); - }); - - it('rejects namespace - number', () => { - // @ts-expect-error: invalid - const storage: Partial = {namespace: 42}; - expect(() => - normalizeConfig({ - future: { - experimental_storage: storage, - }, - }), - ).toThrowErrorMatchingInlineSnapshot(` - ""future.experimental_storage.namespace" must be one of [string, boolean] - " - `); - }); - }); - }); - describe('vcs', () => { function vcsContaining(vcs: Partial) { return futureContaining({ @@ -2472,6 +2560,7 @@ describe('future', () => { const v4: FutureV4Config = { removeLegacyPostBuildHeadAttribute: true, useCssCascadeLayers: true, + siteStorageNamespacing: true, }; expect( normalizeConfig({ @@ -2662,5 +2751,80 @@ describe('future', () => { `); }); }); + + describe('siteStorageNamespacing', () => { + it('accepts - undefined', () => { + const v4: Partial = { + siteStorageNamespacing: undefined, + }; + expect( + normalizeConfig({ + future: { + v4, + }, + }), + ).toEqual(v4Containing({siteStorageNamespacing: false})); + }); + + it('accepts - true', () => { + const v4: Partial = { + siteStorageNamespacing: true, + }; + expect( + normalizeConfig({ + future: { + v4, + }, + }), + ).toEqual(v4Containing({siteStorageNamespacing: true})); + }); + + it('accepts - false', () => { + const v4: Partial = { + siteStorageNamespacing: false, + }; + expect( + normalizeConfig({ + future: { + v4, + }, + }), + ).toEqual(v4Containing({siteStorageNamespacing: false})); + }); + + it('rejects - null', () => { + const v4: Partial = { + // @ts-expect-error: invalid + siteStorageNamespacing: null, + }; + expect(() => + normalizeConfig({ + future: { + v4, + }, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""future.v4.siteStorageNamespacing" must be a boolean + " + `); + }); + + it('rejects - number', () => { + const v4: Partial = { + // @ts-expect-error: invalid + siteStorageNamespacing: 42, + }; + expect(() => + normalizeConfig({ + future: { + v4, + }, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""future.v4.siteStorageNamespacing" must be a boolean + " + `); + }); + }); }); }); diff --git a/packages/docusaurus/src/server/__tests__/storage.test.ts b/packages/docusaurus/src/server/__tests__/storage.test.ts index f91b9ddbef3e..57999a6dc454 100644 --- a/packages/docusaurus/src/server/__tests__/storage.test.ts +++ b/packages/docusaurus/src/server/__tests__/storage.test.ts @@ -6,11 +6,8 @@ */ import {createSiteStorage} from '../storage'; -import { - DEFAULT_FUTURE_CONFIG, - DEFAULT_STORAGE_CONFIG, -} from '../configValidation'; -import type {FutureConfig, StorageConfig, SiteStorage} from '@docusaurus/types'; +import {DEFAULT_STORAGE_CONFIG} from '../configValidation'; +import type {StorageConfig, SiteStorage} from '@docusaurus/types'; function test({ url = 'https://docusaurus.io', @@ -21,15 +18,14 @@ function test({ baseUrl?: string; storage?: Partial; }): SiteStorage { - const future: FutureConfig = { - ...DEFAULT_FUTURE_CONFIG, - experimental_storage: { + return createSiteStorage({ + url, + baseUrl, + storage: { ...DEFAULT_STORAGE_CONFIG, ...storage, }, - }; - - return createSiteStorage({url, baseUrl, future}); + }); } const DefaultSiteStorage: SiteStorage = { diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index e57a7ba45abe..6ccde0034917 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -100,18 +100,19 @@ export const DEFAULT_FASTER_CONFIG_TRUE: FasterConfig = { export const DEFAULT_FUTURE_V4_CONFIG: FutureV4Config = { removeLegacyPostBuildHeadAttribute: false, useCssCascadeLayers: false, + siteStorageNamespacing: false, }; // When using the "v4: true" shortcut export const DEFAULT_FUTURE_V4_CONFIG_TRUE: FutureV4Config = { removeLegacyPostBuildHeadAttribute: true, useCssCascadeLayers: true, + siteStorageNamespacing: true, }; export const DEFAULT_FUTURE_CONFIG: FutureConfig = { v4: DEFAULT_FUTURE_V4_CONFIG, experimental_faster: DEFAULT_FASTER_CONFIG, - experimental_storage: DEFAULT_STORAGE_CONFIG, experimental_vcs: getVcsPreset('default-v1'), experimental_router: 'browser', }; @@ -143,6 +144,7 @@ export const DEFAULT_CONFIG: Pick< DocusaurusConfig, | 'i18n' | 'future' + | 'storage' | 'onBrokenLinks' | 'onBrokenAnchors' | 'onBrokenMarkdownLinks' @@ -165,6 +167,7 @@ export const DEFAULT_CONFIG: Pick< > = { i18n: DEFAULT_I18N_CONFIG, future: DEFAULT_FUTURE_CONFIG, + storage: DEFAULT_STORAGE_CONFIG, onBrokenLinks: 'throw', onBrokenAnchors: 'warn', // TODO Docusaurus v4: change to throw onBrokenMarkdownLinks: undefined, @@ -318,6 +321,9 @@ const FUTURE_V4_SCHEMA = Joi.alternatives() useCssCascadeLayers: Joi.boolean().default( DEFAULT_FUTURE_V4_CONFIG.useCssCascadeLayers, ), + siteStorageNamespacing: Joi.boolean().default( + DEFAULT_FUTURE_V4_CONFIG.siteStorageNamespacing, + ), }), Joi.boolean() .required() @@ -332,12 +338,11 @@ const STORAGE_CONFIG_SCHEMA = Joi.object({ type: Joi.string() .equal('localStorage', 'sessionStorage') .default(DEFAULT_STORAGE_CONFIG.type), - namespace: Joi.alternatives() - .try(Joi.string(), Joi.boolean()) - .default(DEFAULT_STORAGE_CONFIG.namespace), + // No schema default on purpose: we derive this from future.v4 flag later. + namespace: Joi.alternatives().try(Joi.string(), Joi.boolean()), }) .optional() - .default(DEFAULT_STORAGE_CONFIG); + .default({type: DEFAULT_STORAGE_CONFIG.type}); const VCS_CONFIG_OBJECT_SCHEMA = Joi.object({ // All the fields are required on purpose @@ -369,10 +374,20 @@ const VCS_CONFIG_SCHEMA = Joi.custom((input) => { return value; }).default(true); -const FUTURE_CONFIG_SCHEMA = Joi.object({ +const FUTURE_CONFIG_SCHEMA = Joi.object< + FutureConfig & {experimental_storage?: never} +>({ v4: FUTURE_V4_SCHEMA, experimental_faster: FASTER_CONFIG_SCHEMA, - experimental_storage: STORAGE_CONFIG_SCHEMA, + experimental_storage: Joi.any() + .forbidden() + .messages({ + 'any.unknown': `Docusaurus config ${logger.code( + 'future.experimental_storage', + )} has been promoted to stable and is no longer supported. +Please use the top-level ${logger.code('storage')} option instead. +See https://docusaurus.io/docs/api/docusaurus-config#storage`, + }), experimental_vcs: VCS_CONFIG_SCHEMA, experimental_router: Joi.string() .equal('browser', 'hash') @@ -391,6 +406,7 @@ export const ConfigSchema = Joi.object({ trailingSlash: Joi.boolean(), // No default value! undefined = retrocompatible legacy behavior! i18n: I18N_CONFIG_SCHEMA, future: FUTURE_CONFIG_SCHEMA, + storage: STORAGE_CONFIG_SCHEMA, onBrokenLinks: Joi.string() .equal('ignore', 'log', 'warn', 'throw') .default(DEFAULT_CONFIG.onBrokenLinks), @@ -533,6 +549,10 @@ export const ConfigSchema = Joi.object({ // Expressing this kind of logic in Joi is a pain // We also want to decouple logic from Joi: easier to remove it later! function postProcessDocusaurusConfig(config: DocusaurusConfig) { + if (config.storage.namespace === undefined) { + config.storage.namespace = config.future.v4.siteStorageNamespacing; + } + if (config.onBrokenMarkdownLinks) { logger.warn`The code=${'siteConfig.onBrokenMarkdownLinks'} config option is deprecated and will be removed in Docusaurus v4. Please migrate and move this option to code=${'siteConfig.markdown.hooks.onBrokenMarkdownLinks'} instead.`; diff --git a/packages/docusaurus/src/server/storage.ts b/packages/docusaurus/src/server/storage.ts index 657f4a359c2d..932db7df2695 100644 --- a/packages/docusaurus/src/server/storage.ts +++ b/packages/docusaurus/src/server/storage.ts @@ -9,11 +9,7 @@ import {normalizeUrl, simpleHash} from '@docusaurus/utils'; import {addTrailingSlash} from '@docusaurus/utils-common'; import type {DocusaurusConfig, SiteStorage} from '@docusaurus/types'; -type PartialFuture = Pick; - -type PartialConfig = Pick & { - future: PartialFuture; -}; +type PartialConfig = Pick; function automaticNamespace(config: PartialConfig): string { const normalizedUrl = addTrailingSlash( @@ -23,17 +19,17 @@ function automaticNamespace(config: PartialConfig): string { } function getNamespaceString(config: PartialConfig): string | null { - if (config.future.experimental_storage.namespace === true) { + if (config.storage.namespace === true) { return automaticNamespace(config); - } else if (config.future.experimental_storage.namespace === false) { + } else if (config.storage.namespace === false) { return null; } else { - return config.future.experimental_storage.namespace; + return config.storage.namespace; } } export function createSiteStorage(config: PartialConfig): SiteStorage { - const {type} = config.future.experimental_storage; + const {type} = config.storage; const namespaceString = getNamespaceString(config); const namespace = namespaceString ? `-${namespaceString}` : ''; diff --git a/website/docs/api/docusaurus.config.js.mdx b/website/docs/api/docusaurus.config.js.mdx index 5acad437ccc7..deec583c530b 100644 --- a/website/docs/api/docusaurus.config.js.mdx +++ b/website/docs/api/docusaurus.config.js.mdx @@ -234,6 +234,7 @@ export default { v4: { removeLegacyPostBuildHeadAttribute: true, useCssCascadeLayers: true, + siteStorageNamespacing: true, }, experimental_faster: { swcJsLoader: true, @@ -245,10 +246,6 @@ export default { ssgWorkerThreads: true, mdxCrossCompilerCache: true, }, - experimental_storage: { - type: 'localStorage', - namespace: true, - }, experimental_router: 'hash', }, }; @@ -257,6 +254,7 @@ export default { - `v4`: Permits to opt-in for upcoming Docusaurus v4 breaking changes and features, to prepare your site in advance for this new version. Use `true` as a shorthand to enable all the flags. - [`removeLegacyPostBuildHeadAttribute`](https://github.com/facebook/docusaurus/pull/10435): Removes the legacy `plugin.postBuild({head})` API that prevents us from applying useful SSG optimizations ([explanations](https://github.com/facebook/docusaurus/pull/10850)). - [`useCssCascadeLayers`](https://github.com/facebook/docusaurus/pull/11142): This enables the [Docusaurus CSS Cascade Layers plugin](./plugins/plugin-css-cascade-layers.mdx) with pre-configured layers that we plan to apply by default for Docusaurus v4. + - `siteStorageNamespacing`: Changes the default value of [`storage.namespace`](#storage) to `true`. This helps you prepare for the upcoming Docusaurus v4 default behavior. - `experimental_faster`: An object containing feature flags to make the Docusaurus build faster. This requires adding the `@docusaurus/faster` package to your site's dependencies. Use `true` as a shorthand to enable all flags. Read more on the [Docusaurus Faster](https://github.com/facebook/docusaurus/issues/10556) issue. Available feature flags: - [`swcJsLoader`](https://github.com/facebook/docusaurus/pull/10435): Use [SWC](https://swc.rs/) to transpile JS (instead of [Babel](https://babeljs.io/)). - [`swcJsMinimizer`](https://github.com/facebook/docusaurus/pull/10441): Use [SWC](https://swc.rs/) to minify JS (instead of [Terser](https://github.com/terser/terser)). @@ -267,9 +265,6 @@ export default { - [`mdxCrossCompilerCache`](https://github.com/facebook/docusaurus/pull/10479): Compile MDX files only once for both browser/Node.js environments instead of twice. - [`ssgWorkerThreads`](https://github.com/facebook/docusaurus/pull/10826): Using a Node.js worker thread pool to execute the static site generation phase faster. Requires `future.v4.removeLegacyPostBuildHeadAttribute` to be turned on. - [`gitEagerVcs`](https://github.com/facebook/docusaurus/pull/11512): Upgrades the default [VCS strategy](#vcs) to `default-v2`, that reads your whole Git repository at once instead of per-file, making Git operations faster on large repositories. -- `experimental_storage`: Site-wide browser storage options that theme authors should strive to respect. - - `type`: The browser storage theme authors should use. Possible values are `localStorage` and `sessionStorage`. Defaults to `localStorage`. - - `namespace`: Whether to namespace the browser storage keys to avoid storage key conflicts when Docusaurus sites are hosted under the same domain, or on localhost. Possible values are `string | boolean`. The namespace is appended at the end of the storage keys `key-namespace`. Use `true` to automatically generate a random namespace from your site `url + baseUrl`. Defaults to `false` (no namespace, historical behavior). - `experimental_router`: The router type to use. Possible values are `browser` and `hash`. Defaults to `browser`. The `hash` router is only useful for rare cases where you want to opt-out of static site generation, have a fully client-side app with a single `index.html` entrypoint file. This can be useful to distribute a Docusaurus site as a `.zip` archive that you can [browse locally without running a web server](https://github.com/facebook/docusaurus/issues/3825). - [`experimental_vcs`](#vcs): The Version Control System (VCS) implementation to use to read file info (creation/last update date/author). Read the [dedicated section](#vcs) below for details. @@ -361,6 +356,26 @@ type VcsPreset = | 'default-v2'; ``` +### `storage` {/* #storage */} + +- Type: `Object` + +Site-wide browser storage options that theme authors should strive to respect. + +- `type`: The browser storage theme authors should use. Possible values are `localStorage` and `sessionStorage`. Defaults to `localStorage`. +- `namespace`: Whether to namespace the browser storage keys to avoid storage key conflicts when Docusaurus sites are hosted under the same domain, or on localhost. Possible values are `string | boolean`. The namespace is appended at the end of the storage keys `key-namespace`. Use `true` to automatically generate a random namespace from your site `url + baseUrl`. Defaults to `false` (historical behavior), unless `future.v4.siteStorageNamespacing` is turned on. + +Example: + +```js title="docusaurus.config.js" +export default { + storage: { + type: 'localStorage', + namespace: true, + }, +}; +``` + ### `noIndex` {/* #noIndex */} - Type: `boolean` diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index f242e5aba33d..f49ef0c0247b 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -193,9 +193,6 @@ export default async function createConfigAsync() { ssgWorkerThreads: true, gitEagerVcs: true, }, - experimental_storage: { - namespace: true, - }, experimental_vcs: vcs, experimental_router: router, },