Compare commits

...

7 Commits

Author SHA1 Message Date
sebastienlorber b7c6d5eb53 warning wording 2021-12-30 18:36:17 +01:00
sebastienlorber c37f5b5d54 try to fix CSS insertion order with client-css-modules 2021-12-30 18:28:29 +01:00
sebastienlorber 257e0b0aae type fix 2021-12-30 17:32:37 +01:00
sebastienlorber 1434051211 Add config.styling.css option 2021-12-30 16:06:40 +01:00
sebastienlorber 1d3d9a1654 rework deprecation warning 2021-12-30 15:30:55 +01:00
sebastienlorber bb2aa5a714 deprecate themeOptions.customCss 2021-12-30 15:29:59 +01:00
sebastienlorber 6b78ffd913 site custom CSS must be inserted at the end 2021-12-30 14:34:26 +01:00
17 changed files with 175 additions and 46 deletions

View File

@ -6,11 +6,15 @@
*/
declare module '@generated/client-modules' {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const clientModules: readonly any[];
const clientModules: readonly unknown[];
export default clientModules;
}
declare module '@generated/css-client-modules' {
const cssClientModules: readonly unknown[];
export default cssClientModules;
}
declare module '@generated/docusaurus.config' {
import type {DocusaurusConfig} from '@docusaurus/types';

View File

@ -20,15 +20,6 @@ function testValidateThemeConfig(partialThemeConfig) {
});
}
function testOk(partialThemeConfig) {
expect(
testValidateThemeConfig({...DEFAULT_CONFIG, ...partialThemeConfig}),
).toEqual({
...DEFAULT_CONFIG,
...partialThemeConfig,
});
}
describe('themeConfig', () => {
test('should accept valid theme config', () => {
const userConfig = {
@ -477,31 +468,13 @@ describe('themeConfig', () => {
});
describe('customCss config', () => {
test('should accept customCss undefined', () => {
testOk({
customCss: undefined,
});
});
test('should accept customCss string', () => {
testOk({
customCss: './path/to/cssFile.css',
});
});
test('should accept customCss string array', () => {
testOk({
customCss: ['./path/to/cssFile.css', './path/to/cssFile2.css'],
});
});
test('should reject customCss number', () => {
test('should reject customCss string', () => {
expect(() =>
testValidateThemeConfig({
customCss: 42,
customCss: './path/to/cssFile.css',
}),
).toThrowErrorMatchingInlineSnapshot(
`"\\"customCss\\" must be one of [array, string]"`,
`"themeConfig.customCss is invalid. Custom css used to be provided as themeOptions.customCss, but it is deprecated now."`,
);
});
});

View File

@ -234,3 +234,4 @@ export function getSwizzleComponentList(): string[] {
}
export {validateThemeConfig} from './validateThemeConfig';
export {validateOptions} from './options';

View File

@ -0,0 +1,47 @@
/**
* 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.
*/
import {Joi} from '@docusaurus/utils-validation';
import type {
ValidationResult,
OptionValidationContext,
} from '@docusaurus/types';
import type {Options} from '@docusaurus/theme-classic';
const DEFAULT_OPTIONS: Options = {
customCss: null,
};
export const Schema = Joi.object({
customCss: Joi.alternatives()
.try(Joi.array().items(Joi.string().required()), Joi.string().required())
.optional()
.default(DEFAULT_OPTIONS.customCss)
.warning('deprecate.error', {
msg: `theme.customCss option is deprecated.
Please use siteConfig.styling.css instead.
Note that this also changes the CSS insertion order!
This enables your site CSS to override default theme CSS more easily, without using !important
Before: custom site CSS was inserted before Infima CSS and default theme CSS.
After: custom site CSS will be inserted after Infima CSS and default theme CSS.
See also https://github.com/facebook/docusaurus/pull/6227
`,
})
.messages({
'deprecate.error': '{#msg}',
}),
});
export function validateOptions({
validate,
options,
}: OptionValidationContext<Options>): ValidationResult<Options> {
return validate(Schema, options);
}

View File

@ -7,7 +7,7 @@
declare module '@docusaurus/theme-classic' {
export type Options = {
customCss?: string | string[];
customCss?: string | string[] | null;
};
}

View File

@ -244,10 +244,6 @@ const FooterLinkItemSchema = Joi.object({
// (users may need additional attributes like target, aria-role, data-customAttribute...)
.unknown();
const CustomCssSchema = Joi.alternatives()
.try(Joi.array().items(Joi.string().required()), Joi.string().required())
.optional();
const ThemeConfigSchema = Joi.object({
// TODO temporary (@alpha-58)
disableDarkMode: Joi.any().forbidden().messages({
@ -259,7 +255,13 @@ const ThemeConfigSchema = Joi.object({
'any.unknown':
'defaultDarkMode theme config is deprecated. Please use the new colorMode attribute. You likely want: config.themeConfig.colorMode.defaultMode = "dark"',
}),
customCss: CustomCssSchema,
// TODO temporary
customCss: Joi.any().forbidden().messages({
'any.unknown':
'themeConfig.customCss is invalid. Custom css used to be provided as themeOptions.customCss, but it is deprecated now.',
}),
colorMode: ColorModeSchema,
image: Joi.string(),
docs: DocsSchema,

View File

@ -22,6 +22,10 @@ export type ThemeConfig = {
[key: string]: unknown;
};
export type StylingConfig = {
css: string[];
};
// Docusaurus config, after validation/normalization
export interface DocusaurusConfig {
baseUrl: string;
@ -33,6 +37,7 @@ export interface DocusaurusConfig {
// trailingSlash undefined = legacy retrocompatible behavior => /file => /file/index.html
trailingSlash: boolean | undefined;
i18n: I18nConfig;
styling: StylingConfig;
onBrokenLinks: ReportingSeverity;
onBrokenMarkdownLinks: ReportingSeverity;
onDuplicateRoutes: ReportingSeverity;
@ -82,6 +87,9 @@ export type Config = Overwrite<
url: Required<DocusaurusConfig['url']>;
baseUrl: Required<DocusaurusConfig['baseUrl']>;
i18n?: DeepPartial<DocusaurusConfig['i18n']>;
styling?: Omit<StylingConfig, 'css'> & {
css?: string | string[];
};
}
>;
@ -419,6 +427,7 @@ interface HtmlTagObject {
innerHTML?: string;
}
// TODO weird useless type, refactor
export type ValidationResult<T> = T;
export type ValidationSchema<T> = Joi.ObjectSchema<T>;

View File

@ -5,6 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
// import order impacts CSS insertion ordering
// See https://github.com/facebook/docusaurus/pull/6227
import '@generated/client-modules';
import '@generated/registry';
import '@generated/css-client-modules';
import React from 'react';
import {hydrate, render} from 'react-dom';
import {BrowserRouter} from 'react-router-dom';

View File

@ -6,7 +6,7 @@
*/
import logger from '@docusaurus/logger';
import {DocusaurusConfig, I18nConfig} from '@docusaurus/types';
import {DocusaurusConfig, I18nConfig, StylingConfig} from '@docusaurus/types';
import {DEFAULT_CONFIG_FILE_NAME, STATIC_DIR_NAME} from '@docusaurus/utils';
import {
Joi,
@ -24,9 +24,14 @@ export const DEFAULT_I18N_CONFIG: I18nConfig = {
localeConfigs: {},
};
export const DEFAULT_STYLING_CONFIG: StylingConfig = {
css: [],
};
export const DEFAULT_CONFIG: Pick<
DocusaurusConfig,
| 'i18n'
| 'styling'
| 'onBrokenLinks'
| 'onBrokenMarkdownLinks'
| 'onDuplicateRoutes'
@ -41,6 +46,7 @@ export const DEFAULT_CONFIG: Pick<
| 'staticDirectories'
> = {
i18n: DEFAULT_I18N_CONFIG,
styling: DEFAULT_STYLING_CONFIG,
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
onDuplicateRoutes: 'warn',
@ -101,7 +107,7 @@ const LocaleConfigSchema = Joi.object({
direction: Joi.string().equal('ltr', 'rtl').default('ltr'),
});
const I18N_CONFIG_SCHEMA = Joi.object<I18nConfig>({
const I18nConfigSchema = Joi.object<I18nConfig>({
defaultLocale: Joi.string().required(),
locales: Joi.array().items().min(1).items(Joi.string().required()).required(),
localeConfigs: Joi.object()
@ -123,6 +129,15 @@ const SiteUrlSchema = URISchema.required().custom((value, helpers) => {
return value;
}, 'siteUrlCustomValidation');
const StylingSchema = Joi.object({
css: Joi.alternatives()
.try(
Joi.array().items(Joi.string().required()).required(),
Joi.string().custom((val) => [val]), // normalize: string -> string[]
)
.default(DEFAULT_STYLING_CONFIG.css),
}).default(DEFAULT_STYLING_CONFIG);
// TODO move to @docusaurus/utils-validation
export const ConfigSchema = Joi.object({
baseUrl: Joi.string()
@ -134,7 +149,8 @@ export const ConfigSchema = Joi.object({
title: Joi.string().required(),
url: SiteUrlSchema,
trailingSlash: Joi.boolean(), // No default value! undefined = retrocompatible legacy behavior!
i18n: I18N_CONFIG_SCHEMA,
i18n: I18nConfigSchema,
styling: StylingSchema,
onBrokenLinks: Joi.string()
.equal('ignore', 'log', 'warn', 'error', 'throw')
.default(DEFAULT_CONFIG.onBrokenLinks),

View File

@ -316,6 +316,15 @@ export async function load(
plugins.push(createBootstrapPlugin({siteConfig}));
plugins.push(createMDXFallbackPlugin({siteDir, siteConfig}));
// Load CSS client modules
const genCSSClientModules = generate(
generatedFilesDir,
'css-client-modules.js',
`export default [\n${siteConfig.styling.css
.map((module) => `import('${escapePath(module)}'),`)
.join('\n')}\n];\n`,
);
// Load client modules.
const clientModules = loadClientModules(plugins);
const genClientModules = generate(
@ -402,6 +411,7 @@ ${Object.keys(registry)
);
await Promise.all([
genCSSClientModules,
genClientModules,
genSiteConfig,
genRegistry,

View File

@ -76,6 +76,9 @@ const config = {
// - force trailing slashes for deploy previews
// - avoid trailing slashes in prod
trailingSlash: isDeployPreview,
styling: {
css: require.resolve('./src/css/custom.css'),
},
stylesheets: [
{
href: 'https://cdn.jsdelivr.net/npm/katex@0.13.24/dist/katex.min.css',
@ -304,7 +307,7 @@ const config = {
remarkPlugins: [npm2yarn],
},
theme: {
customCss: [require.resolve('./src/css/custom.css')],
customCss: [require.resolve('./src/css/customLegacy.css')],
},
gtag: !isDeployPreview
? {

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.
*/
/* Used to test CSS insertion order */
.test-marker-theme-custom-css-legacy {
content: "theme-custom-css-legacy";
}

View File

@ -9,6 +9,8 @@ import React from 'react';
import type {Props} from '@theme/CodeBlock';
import CodeBlock from '@theme-original/CodeBlock';
import './styles.module.css';
// This component does nothing on purpose
// Dogfood: wrapping a theme component already enhanced by another theme
// See https://github.com/facebook/docusaurus/pull/5983

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.
*/
/* Used to test CSS insertion order */
.test-marker-theme-code-block {
content: "theme-code-block";
}

View File

@ -0,0 +1,17 @@
/**
* 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.
*/
import React from 'react';
import type {Props} from '@theme/DocItem';
import DocItem from '@theme-original/DocItem';
// This component is only used to test for CSS insertion order
import './styles.module.css';
export default function DocItemWrapper(props: Props): JSX.Element {
return <DocItem {...props} />;
}

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.
*/
/* Used to test CSS insertion order */
.test-marker-theme-doc-item {
content: "theme-doc-item";
}

View File

@ -41,14 +41,17 @@ const EXPECTED_CSS_MARKERS = [
'.pills__item',
'.tabs__item',
// Test markers
'.test-marker-site-custom-css-unique-rule',
// Markers, using Webpack require()
'.test-marker-theme-custom-css-legacy', // TODO should be removed later
'.test-marker-site-client-module',
'.test-marker-theme-layout',
'.test-marker-site-index-page',
// lazy loaded lib
// Markers, using Webpack dynamic import() (routes use dynamic imports)
'.test-marker-site-index-page',
'.test-marker-site-custom-css-unique-rule',
'.DocSearch-Modal',
'.test-marker-theme-code-block',
'.test-marker-theme-doc-item',
];
const cssDirName = path.join(__dirname, 'build', 'assets', 'css');