Skip to content

Commit

Permalink
feat(core): site storage config options (experimental) (#10121)
Browse files Browse the repository at this point in the history
  • Loading branch information
slorber authored May 10, 2024
1 parent cb68951 commit 620e463
Show file tree
Hide file tree
Showing 20 changed files with 824 additions and 54 deletions.
7 changes: 7 additions & 0 deletions packages/docusaurus-module-type-aliases/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ declare module '@generated/site-metadata' {
export = siteMetadata;
}

declare module '@generated/site-storage' {
import type {SiteStorage} from '@docusaurus/types';

const siteStorage: SiteStorage;
export = siteStorage;
}

declare module '@generated/registry' {
import type {Registry} from '@docusaurus/types';

Expand Down
91 changes: 50 additions & 41 deletions packages/docusaurus-theme-classic/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {createRequire} from 'module';
import rtlcss from 'rtlcss';
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
import {getTranslationFiles, translateThemeConfig} from './translations';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {LoadContext, Plugin, SiteStorage} from '@docusaurus/types';
import type {ThemeConfig} from '@docusaurus/theme-common';
import type {Plugin as PostCssPlugin} from 'postcss';
import type {PluginOptions} from '@docusaurus/theme-classic';
Expand All @@ -23,58 +23,66 @@ const ContextReplacementPlugin = requireFromDocusaurusCore(
'webpack/lib/ContextReplacementPlugin',
) as typeof webpack.ContextReplacementPlugin;

// Need to be inlined to prevent dark mode FOUC
// Make sure the key is the same as the one in `/theme/hooks/useTheme.js`
const ThemeStorageKey = 'theme';
// Support for ?docusaurus-theme=dark
const ThemeQueryStringKey = 'docusaurus-theme';
// Support for ?docusaurus-data-mode=embed&docusaurus-data-myAttr=42
const DataQueryStringPrefixKey = 'docusaurus-data-';

const noFlashColorMode = ({
defaultMode,
respectPrefersColorScheme,
}: ThemeConfig['colorMode']) =>
colorMode: {defaultMode, respectPrefersColorScheme},
siteStorage,
}: {
colorMode: ThemeConfig['colorMode'];
siteStorage: SiteStorage;
}) => {
// Need to be inlined to prevent dark mode FOUC
// Make sure the key is the same as the one in the color mode React context
// Currently defined in: `docusaurus-theme-common/src/contexts/colorMode.tsx`
const themeStorageKey = `theme${siteStorage.namespace}`;

/* language=js */
`(function() {
var defaultMode = '${defaultMode}';
var respectPrefersColorScheme = ${respectPrefersColorScheme};
return `(function() {
var defaultMode = '${defaultMode}';
var respectPrefersColorScheme = ${respectPrefersColorScheme};
function setDataThemeAttribute(theme) {
document.documentElement.setAttribute('data-theme', theme);
}
function setDataThemeAttribute(theme) {
document.documentElement.setAttribute('data-theme', theme);
}
function getQueryStringTheme() {
try {
return new URLSearchParams(window.location.search).get('${ThemeQueryStringKey}')
} catch(e) {}
}
function getQueryStringTheme() {
try {
return new URLSearchParams(window.location.search).get('${ThemeQueryStringKey}')
} catch (e) {
}
}
function getStoredTheme() {
try {
return localStorage.getItem('${ThemeStorageKey}');
} catch (err) {}
}
function getStoredTheme() {
try {
return window['${siteStorage.type}'].getItem('${themeStorageKey}');
} catch (err) {
}
}
var initialTheme = getQueryStringTheme() || getStoredTheme();
if (initialTheme !== null) {
setDataThemeAttribute(initialTheme);
} else {
if (
respectPrefersColorScheme &&
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
setDataThemeAttribute('dark');
} else if (
respectPrefersColorScheme &&
window.matchMedia('(prefers-color-scheme: light)').matches
) {
setDataThemeAttribute('light');
var initialTheme = getQueryStringTheme() || getStoredTheme();
if (initialTheme !== null) {
setDataThemeAttribute(initialTheme);
} else {
setDataThemeAttribute(defaultMode === 'dark' ? 'dark' : 'light');
if (
respectPrefersColorScheme &&
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
setDataThemeAttribute('dark');
} else if (
respectPrefersColorScheme &&
window.matchMedia('(prefers-color-scheme: light)').matches
) {
setDataThemeAttribute('light');
} else {
setDataThemeAttribute(defaultMode === 'dark' ? 'dark' : 'light');
}
}
}
})();`;
})();`;
};

/* language=js */
const DataAttributeQueryStringInlineJavaScript = `
Expand Down Expand Up @@ -126,6 +134,7 @@ export default function themeClassic(
): Plugin<undefined> {
const {
i18n: {currentLocale, localeConfigs},
siteStorage,
} = context;
const themeConfig = context.siteConfig.themeConfig as ThemeConfig;
const {
Expand Down Expand Up @@ -218,7 +227,7 @@ export default function themeClassic(
{
tagName: 'script',
innerHTML: `
${noFlashColorMode(colorMode)}
${noFlashColorMode({colorMode, siteStorage})}
${DataAttributeQueryStringInlineJavaScript}
${announcementBar ? AnnouncementBarInlineJavaScript : ''}
`,
Expand Down
12 changes: 8 additions & 4 deletions packages/docusaurus-theme-common/src/utils/storageUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
*/

import {useCallback, useRef, useSyncExternalStore} from 'react';
import SiteStorage from '@generated/site-storage';

const StorageTypes = ['localStorage', 'sessionStorage', 'none'] as const;
export type StorageType = (typeof SiteStorage)['type'] | 'none';

export type StorageType = (typeof StorageTypes)[number];
const DefaultStorageType: StorageType = SiteStorage.type;

const DefaultStorageType: StorageType = 'localStorage';
function applyNamespace(storageKey: string): string {
return `${storageKey}${SiteStorage.namespace}`;
}

// window.addEventListener('storage') only works for different windows...
// so for current window we have to dispatch the event manually
Expand Down Expand Up @@ -134,9 +137,10 @@ Please only call storage APIs in effects and event handlers.`);
* this API can be a no-op. See also https://github.com/facebook/docusaurus/issues/6036
*/
export function createStorageSlot(
key: string,
keyInput: string,
options?: {persistence?: StorageType},
): StorageSlot {
const key = applyNamespace(keyInput);
if (typeof window === 'undefined') {
return createServerStorageSlot(key);
}
Expand Down
15 changes: 15 additions & 0 deletions packages/docusaurus-types/src/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import type {SiteStorage} from './context';
import type {RuleSetRule} from 'webpack';
import type {Required as RequireKeys, DeepPartial} from 'utility-types';
import type {I18nConfig} from './i18n';
Expand Down Expand Up @@ -115,6 +116,15 @@ export type MarkdownConfig = {
anchors: MarkdownAnchorsConfig;
};

export type StorageConfig = {
type: SiteStorage['type'];
namespace: boolean | string;
};

export type FutureConfig = {
experimental_storage: StorageConfig;
};

/**
* Docusaurus config, after validation/normalization.
*/
Expand Down Expand Up @@ -171,6 +181,11 @@ export type DocusaurusConfig = {
* @see https://docusaurus.io/docs/api/docusaurus-config#i18n
*/
i18n: I18nConfig;
/**
* Docusaurus future flags and experimental features.
* Similar to Remix future flags, see https://remix.run/blog/future-flags
*/
future: FutureConfig;
/**
* This option adds `<meta name="robots" content="noindex, nofollow">` to
* every page to tell search engines to avoid indexing your site.
Expand Down
24 changes: 24 additions & 0 deletions packages/docusaurus-types/src/context.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,25 @@ export type SiteMetadata = {
readonly pluginVersions: {[pluginName: string]: PluginVersionInformation};
};

export type SiteStorage = {
/**
* Which browser storage do you want to use?
* Between "localStorage" and "sessionStorage".
* The default is "localStorage".
*/
type: 'localStorage' | 'sessionStorage';

/**
* Applies a namespace to the theme storage key
* For readability, the namespace is applied at the end of the key
* The final storage key will be = `${key}${namespace}`
*
* The default namespace is "" for retro-compatibility reasons
* If you want a separator, the namespace should contain it ("-myNamespace")
*/
namespace: string;
};

export type GlobalData = {[pluginName: string]: {[pluginId: string]: unknown}};

export type LoadContext = {
Expand All @@ -50,6 +69,11 @@ export type LoadContext = {
baseUrl: string;
i18n: I18n;
codeTranslations: CodeTranslations;

/**
* Defines the default browser storage behavior for a site
*/
siteStorage: SiteStorage;
};

export type Props = LoadContext & {
Expand Down
3 changes: 3 additions & 0 deletions packages/docusaurus-types/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export {
DefaultParseFrontMatter,
ParseFrontMatter,
DocusaurusConfig,
FutureConfig,
StorageConfig,
Config,
} from './config';

Expand All @@ -20,6 +22,7 @@ export {
DocusaurusContext,
GlobalData,
LoadContext,
SiteStorage,
Props,
} from './context';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = `
"baseUrlIssueBanner": true,
"clientModules": [],
"customFields": {},
"future": {
"experimental_storage": {
"namespace": false,
"type": "localStorage",
},
},
"headTags": [],
"i18n": {
"defaultLocale": "en",
Expand Down Expand Up @@ -61,6 +67,12 @@ exports[`loadSiteConfig website with ts + js config 1`] = `
"baseUrlIssueBanner": true,
"clientModules": [],
"customFields": {},
"future": {
"experimental_storage": {
"namespace": false,
"type": "localStorage",
},
},
"headTags": [],
"i18n": {
"defaultLocale": "en",
Expand Down Expand Up @@ -115,6 +127,12 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = `
"baseUrlIssueBanner": true,
"clientModules": [],
"customFields": {},
"future": {
"experimental_storage": {
"namespace": false,
"type": "localStorage",
},
},
"headTags": [],
"i18n": {
"defaultLocale": "en",
Expand Down Expand Up @@ -169,6 +187,12 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = `
"baseUrlIssueBanner": true,
"clientModules": [],
"customFields": {},
"future": {
"experimental_storage": {
"namespace": false,
"type": "localStorage",
},
},
"headTags": [],
"i18n": {
"defaultLocale": "en",
Expand Down Expand Up @@ -223,6 +247,12 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = `
"baseUrlIssueBanner": true,
"clientModules": [],
"customFields": {},
"future": {
"experimental_storage": {
"namespace": false,
"type": "localStorage",
},
},
"headTags": [],
"i18n": {
"defaultLocale": "en",
Expand Down Expand Up @@ -277,6 +307,12 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = `
"baseUrlIssueBanner": true,
"clientModules": [],
"customFields": {},
"future": {
"experimental_storage": {
"namespace": false,
"type": "localStorage",
},
},
"headTags": [],
"i18n": {
"defaultLocale": "en",
Expand Down Expand Up @@ -331,6 +367,12 @@ exports[`loadSiteConfig website with valid async config 1`] = `
"baseUrlIssueBanner": true,
"clientModules": [],
"customFields": {},
"future": {
"experimental_storage": {
"namespace": false,
"type": "localStorage",
},
},
"headTags": [],
"i18n": {
"defaultLocale": "en",
Expand Down Expand Up @@ -387,6 +429,12 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = `
"baseUrlIssueBanner": true,
"clientModules": [],
"customFields": {},
"future": {
"experimental_storage": {
"namespace": false,
"type": "localStorage",
},
},
"headTags": [],
"i18n": {
"defaultLocale": "en",
Expand Down Expand Up @@ -443,6 +491,12 @@ exports[`loadSiteConfig website with valid config creator function 1`] = `
"baseUrlIssueBanner": true,
"clientModules": [],
"customFields": {},
"future": {
"experimental_storage": {
"namespace": false,
"type": "localStorage",
},
},
"headTags": [],
"i18n": {
"defaultLocale": "en",
Expand Down Expand Up @@ -502,6 +556,12 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = `
],
"customFields": {},
"favicon": "img/docusaurus.ico",
"future": {
"experimental_storage": {
"namespace": false,
"type": "localStorage",
},
},
"headTags": [],
"i18n": {
"defaultLocale": "en",
Expand Down
Loading

0 comments on commit 620e463

Please sign in to comment.