Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): site storage config options (experimental) #10121

Merged
merged 9 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading