From 0784e0b3050fb5de278e06f75c4aa3e9fdc1245f Mon Sep 17 00:00:00 2001 From: Vladimir Eremin Date: Sun, 15 Feb 2026 18:48:51 +0300 Subject: [PATCH] feat: Introduce SSG --- packages/rsbuild-config/package.json | 6 +++ .../rsbuild-config/src/lib/config/entries.ts | 27 +++++++++++ .../rsbuild-config/src/lib/config/index.ts | 1 + .../rsbuild-config/src/lib/config/rsbuild.ts | 11 +++++ packages/rsbuild-config/src/lib/config/ssg.ts | 17 +++++++ packages/ui/src/components/Switch/Switch.tsx | 42 +++++++--------- packages/ui/src/lib/theme/useTheme.ts | 13 ++++- packages/ui/src/static/styles.css | 2 + pnpm-lock.yaml | 48 +++++++++++++++++-- services/static/rsbuild.config.ts | 7 +-- services/static/src/lib/core/createPage.tsx | 10 +++- services/www/rsbuild.config.ts | 10 +--- services/www/src/lib/core/createPage.tsx | 10 +++- 13 files changed, 157 insertions(+), 47 deletions(-) create mode 100644 packages/rsbuild-config/src/lib/config/entries.ts create mode 100644 packages/rsbuild-config/src/lib/config/ssg.ts diff --git a/packages/rsbuild-config/package.json b/packages/rsbuild-config/package.json index d814c9f..91c1c66 100644 --- a/packages/rsbuild-config/package.json +++ b/packages/rsbuild-config/package.json @@ -12,10 +12,16 @@ "dependencies": { "@rsbuild/plugin-react": "^1.1.1", "@rsbuild/plugin-type-check": "^1.1.1", + "fast-glob": "^3.3.3", + "jsdom": "^28.0.0", "rsbuild-plugin-dts": "^0.19.5" }, "devDependencies": { "@rsbuild/core": "^1.7.3", + "@types/jsdom": "^27.0.0", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "react-dom": "^19.1.0", "typescript": "^5.8.2", "vite-tsconfig-paths": "^6.1.1", "vitest": "^4.0.18" diff --git a/packages/rsbuild-config/src/lib/config/entries.ts b/packages/rsbuild-config/src/lib/config/entries.ts new file mode 100644 index 0000000..e44310a --- /dev/null +++ b/packages/rsbuild-config/src/lib/config/entries.ts @@ -0,0 +1,27 @@ +import { join } from 'node:path'; +import fg from 'fast-glob'; + +export function discoverEntries(root: string, pattern: string): Record { + const files = fg.sync(pattern, { cwd: root }); + const entries: Record = {}; + + for (const file of files) { + // We expect the user to provide a pattern like './src/app/*.tsx' + // We'll strip the directory parts to get the name + const parts = file.split('/'); + // Remove extension from the last part + const filename = parts[parts.length - 1].replace(/\.tsx$/, ''); + + // For nested routes, we might want to keep the path but relative to the app root + // But since you want 'index', 'articles/index', etc. + // Let's just use the file path relative to the common root 'src/app' if it exists in the path + const appMatch = file.match(/src\/app\/(.*)\.tsx$/); + let name = appMatch ? appMatch[1] : filename; + + name = name.replace(/\/index$/, '') || 'index'; + + entries[name] = join(root, file); + } + + return entries; +} diff --git a/packages/rsbuild-config/src/lib/config/index.ts b/packages/rsbuild-config/src/lib/config/index.ts index 55c82bf..ab2ca30 100644 --- a/packages/rsbuild-config/src/lib/config/index.ts +++ b/packages/rsbuild-config/src/lib/config/index.ts @@ -1,2 +1,3 @@ export * from './rsbuild.js'; export * from './vitest.js'; +export * from './entries.js'; diff --git a/packages/rsbuild-config/src/lib/config/rsbuild.ts b/packages/rsbuild-config/src/lib/config/rsbuild.ts index 99e5dc8..78ed702 100644 --- a/packages/rsbuild-config/src/lib/config/rsbuild.ts +++ b/packages/rsbuild-config/src/lib/config/rsbuild.ts @@ -22,6 +22,17 @@ export function defineServiceConfig(config: RsbuildConfig = {}): RsbuildConfig { plugins: [pluginReact(), pluginTypeCheck()], html: { template: './src/static/index.html', + tags: [ + { + tag: 'style', + children: ` + :root { background-color: #f6f6f6; color: #262626; } + .dark { background-color: #262626; color: #f6f6f6; } + body { margin: 0; transition: background-color 0.2s ease; } + `, + head: true, + }, + ], }, ...config, }); diff --git a/packages/rsbuild-config/src/lib/config/ssg.ts b/packages/rsbuild-config/src/lib/config/ssg.ts new file mode 100644 index 0000000..ce6e320 --- /dev/null +++ b/packages/rsbuild-config/src/lib/config/ssg.ts @@ -0,0 +1,17 @@ +import type { RsbuildPlugin } from '@rsbuild/core'; + +/** + * A minimal Rsbuild plugin for build-time Static Site Generation. + * Placeholder for future extension. + */ +export function ssgPlugin(): RsbuildPlugin { + return { + name: 'ermnvldmr:ssg-plugin', + setup(api) { + api.onAfterBuild(async ({ stats }) => { + if (!stats) return; + console.log('[ssg-plugin] Build finished.'); + }); + }, + }; +} diff --git a/packages/ui/src/components/Switch/Switch.tsx b/packages/ui/src/components/Switch/Switch.tsx index 94f3ca8..d075698 100644 --- a/packages/ui/src/components/Switch/Switch.tsx +++ b/packages/ui/src/components/Switch/Switch.tsx @@ -60,7 +60,7 @@ export function Switch(props: SwitchProps): React.JSX.Element { const { inputProps } = useSwitch(props, state, ref); const { isFocusVisible, focusProps } = useFocusRing(); - const getVariantClasses = (): string => { + const getVariantStyles = (): React.CSSProperties => { const isNegative = color.endsWith('-negative'); const baseColor = color.replace('-negative', ''); @@ -76,31 +76,22 @@ export function Switch(props: SwitchProps): React.JSX.Element { const backgroundColor = isNegative ? colorValues.text : colorValues.base; if (!state.isSelected) { - switch (variant) { - case 'solid': - return 'bg-[var(--rb-muted-base)]'; - case 'outline': - return 'bg-transparent border-[var(--rb-muted-base)] border-2'; - case 'ghost': - return 'bg-transparent border-transparent'; - default: - return 'bg-[var(--rb-muted-base)]'; - } + return {}; } switch (variant) { case 'solid': - return `bg-[${backgroundColor}]`; + return { backgroundColor }; case 'outline': - return `bg-transparent border-[${backgroundColor}] border-2`; + return { borderColor: backgroundColor }; case 'ghost': - return `bg-[${backgroundColor}]/20 border-transparent`; + return { backgroundColor: `${backgroundColor}33`, borderColor: 'transparent' }; // 33 is 20% opacity in hex default: - return `bg-[${backgroundColor}]`; + return { backgroundColor }; } }; - const getThumbClasses = (): string => { + const getThumbStyles = (): React.CSSProperties => { const isNegative = color.endsWith('-negative'); const baseColor = color.replace('-negative', ''); @@ -112,20 +103,20 @@ export function Switch(props: SwitchProps): React.JSX.Element { neutral: { text: 'var(--rb-text)' }, }; - const thumbColor = isNegative ? 'var(--rb-background)' : colors[baseColor].text; + const thumbColor = isNegative ? 'var(--rb-base)' : colors[baseColor].text; if (!state.isSelected) { - return 'bg-[var(--rb-text)]'; + return {}; } switch (variant) { case 'solid': - return `bg-[${thumbColor}]`; + return { backgroundColor: thumbColor }; case 'outline': case 'ghost': - return `bg-[${isNegative ? 'var(--rb-text)' : 'var(--rb-' + baseColor + '-base)'}]`; + return { backgroundColor: isNegative ? 'var(--rb-text)' : `var(--rb-${baseColor}-base)` }; default: - return `bg-[${thumbColor}]`; + return { backgroundColor: thumbColor }; } }; @@ -142,19 +133,22 @@ export function Switch(props: SwitchProps): React.JSX.Element {