fix(www): resolve SSG build issues with font preloads, dark theme and RU locale

- Fix depth-dependent duplicate font preload prefixes by using locale-absolute
  fontBase path and publicPath:false on html.tags to prevent Rsbuild from
  re-applying assetPrefix (path.posix.join behavior with MPA nested entries)
- Fix .dark CSS rule stripped by critters: set reduceInlineStyles:false so
  critters only processes external stylesheets and leaves inline theme init
  styles (THEME_INIT_STYLES) intact
- Fix NormalModuleReplacementPlugin lost in mergeConfig: refactored locale MDX
  replacement from tools.rspack function into a proper Rsbuild plugin so
  mergeConfig concatenates plugin arrays correctly (enables RU locale articles)
- Extend Critters type declaration to include reduceInlineStyles option

Made-with: Cursor
This commit is contained in:
deytenit committed 2026-03-16 01:19:14 +03:00
1 parent f5e901c131
commit ce225e3f75
3 files changed
+42 -23

No files matched your search

+32 -19
View File
@@ -1,10 +1,16 @@
import { DEFAULT_LOCALE, isLocale, type Locale } from './lib/locale.js';
import type { RsbuildConfig } from '@rsbuild/core';
import type { RsbuildConfig, RsbuildPlugin } from '@rsbuild/core';
/**
* Returns a partial Rsbuild config that enables locale-aware builds.
*
* The locale MDX replacement is provided as an Rsbuild plugin (rather than a
* `tools.rspack` function) so that `mergeConfig` can safely concatenate it
* with the service's own `plugins` array. A `tools.rspack` function would be
* silently dropped by `mergeConfig` when the service config also defines
* `tools.rspack` as a plain object.
*
* Designed to be merged with defineServiceConfig() in each service's rsbuild.config.ts:
*
* @example
@@ -19,6 +25,30 @@ export function localeRsbuildConfig(): RsbuildConfig {
const rawLocale = process.env.LOCALE ?? '';
const locale: Locale = isLocale(rawLocale) ? rawLocale : DEFAULT_LOCALE;
const localePlugins: RsbuildPlugin[] = [];
if (locale !== DEFAULT_LOCALE) {
localePlugins.push({
name: 'ermnvldmr:locale-mdx-replacement',
setup(api) {
// Resolve .en.mdx imports to the locale-specific file when LOCALE != 'en'.
// The canonical import is always `.en.mdx`; other locales are resolved here.
api.modifyRspackConfig((config, { rspack }) => {
config.plugins = [
...(config.plugins ?? []),
new rspack.NormalModuleReplacementPlugin(
/\.en\.mdx$/,
(resource: { request: string }) => {
resource.request = resource.request.replace(/\.en\.mdx$/, `.${locale}.mdx`);
}
),
];
return config;
});
},
});
}
return {
source: {
define: {
@@ -36,24 +66,7 @@ export function localeRsbuildConfig(): RsbuildConfig {
},
assetPrefix: `/${locale}/`,
},
tools: {
rspack: (config, { rspack }) => {
// Resolve .en.mdx imports to the locale-specific file when LOCALE != 'en'.
// The canonical import is always `.en.mdx`; other locales are resolved here.
if (locale !== DEFAULT_LOCALE) {
config.plugins = [
...config.plugins,
new rspack.NormalModuleReplacementPlugin(
/\.en\.mdx$/,
(resource: { request: string }) => {
resource.request = resource.request.replace(/\.en\.mdx$/, `.${locale}.mdx`);
}
),
];
}
return config;
},
},
plugins: localePlugins,
};
}
@@ -111,6 +111,7 @@ export function ssgPlugin(): RsbuildPlugin {
preload?: string;
inlineFonts?: boolean;
pruneSource?: boolean;
reduceInlineStyles?: boolean;
}) => { process(html: string): Promise<string> };
// critters `path` is the filesystem root that corresponds to publicPath.
@@ -121,9 +122,10 @@ export function ssgPlugin(): RsbuildPlugin {
const critters = new Critters({
path: webDistPath,
publicPath: configuredAssetPrefix,
preload: 'swap', // <link rel="preload" as="style" onload="this.rel='stylesheet'">
inlineFonts: false, // fonts are handled via separate <link rel="preload"> tags
pruneSource: false, // keep full CSS file for post-hydration correctness
preload: 'swap', // <link rel="preload" as="style" onload="this.rel='stylesheet'">
inlineFonts: false, // fonts are handled via separate <link rel="preload"> tags
pruneSource: false, // keep full CSS file for post-hydration correctness
reduceInlineStyles: false, // preserve theme init styles (prevents .dark rule from being stripped)
});
for (const entryName of entryNames) {
+5 -1
View File
@@ -6,7 +6,8 @@ import { pluginMdx } from '@rsbuild/plugin-mdx';
import remarkFrontmatter from 'remark-frontmatter';
import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
const fontBase = 'static/font';
const locale = process.env.LOCALE ?? 'en';
const fontBase = `/${locale}/static/font`;
export default defineServiceConfig(
mergeConfig(localeRsbuildConfig(), {
@@ -44,6 +45,7 @@ export default defineServiceConfig(
href: `${fontBase}/lato-400.woff2`,
crossorigin: true,
},
publicPath: false,
},
{
tag: 'link',
@@ -54,6 +56,7 @@ export default defineServiceConfig(
href: `${fontBase}/lato-700.woff2`,
crossorigin: true,
},
publicPath: false,
},
{
tag: 'link',
@@ -64,6 +67,7 @@ export default defineServiceConfig(
href: `${fontBase}/eb-garamond-var.woff2`,
crossorigin: true,
},
publicPath: false,
},
],
},