Skip to content

Commit 1074f5e

Browse files
committed
feat(config): compact gitpagedocs config without repeated values
- config.json stores the header icons as site.icons (defaults + one entry per icon with its tag and differences) instead of 279 flat Icon* keys (16 KB -> 5.4 KB); version configs keep the settings every route shares in routeDefaults (26.5 KB -> 18.5 KB). - New browser-safe @gitpagedocs/tools/config-format with inverse pairs compactSiteIcons/expandSiteIcons and factorRouteDefaults/applyRouteDefaults. The generator compacts on write; the viewer expands in withConfigDefaults and in both version-config readers (shared browser-safe expandVersionConfig); the site baseline stays flat. - Flat 0.0.x configs keep working; a flat key or a value on a route wins over the compact form. Input that is not an object is returned as is. - Round-trip tests over the generated files, viewer tests for both formats, docs templates (en/pt/es), README and CHANGELOG describe the format.
1 parent ca95c57 commit 1074f5e

26 files changed

Lines changed: 942 additions & 1002 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ All notable changes to Git Page Docs are documented here. Versions follow semver
55

66
## Unreleased
77

8+
### Compact gitpagedocs config (no repeated values)
9+
10+
- `gitpagedocs/config.json` stores the header icons as `site.icons` (`defaults` + one entry per icon
11+
with its `tag` and differences) instead of 279 flat `Icon*` keys: 16 KB -> 5.4 KB.
12+
- Version configs store the settings every route shares once in `routeDefaults`: 26.5 KB -> 18.5 KB
13+
for the shipped version.
14+
- New browser-safe `@gitpagedocs/tools/config-format` (`compactSiteIcons` / `expandSiteIcons`,
15+
`factorRouteDefaults` / `applyRouteDefaults`): the generator compacts on write, the viewer expands in
16+
`withConfigDefaults` and in both version-config readers, and the site baseline stays flat. Flat
17+
0.0.x configs keep working; a flat key or a value on a route wins over the compact form. Round-trip
18+
tests guard that expanding a compacted config gives back exactly the original.
19+
820
### Introduction guide motion
921

1022
- `/introduction-guide` gains scroll motion built with Motion for React (`motion` 13, `LazyMotion` +

‎README.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,28 @@ Behavior:
289289
- If `layoutsConfigPathOficial=true`, runtime prefers official layout/template sources.
290290
- If `layoutsConfigPathOficial=false`, runtime prefers your repository layout/template sources (`gitpagelayouts/**` or your custom paths).
291291

292+
## Compact config format
293+
294+
`gitpagedocs/config.json` and each version config store every shared value once:
295+
296+
- `site.icons` replaces the 279 flat `Icon*` keys: `defaults` holds what all header icons share
297+
(`reactIcon`, `colorDark`, `colorLight`, `size`, `imgDark`, `imgLight`, `imgWidth`, `imgHeight`),
298+
and each icon entry sets its `tag` plus any difference (`null` removes a field):
299+
300+
```json
301+
"icons": {
302+
"defaults": { "reactIcon": true, "colorDark": "White", "colorLight": "black", "size": "25px" },
303+
"NavMenuOpen": { "tag": "FaBars", "size": "22px" }
304+
}
305+
```
306+
307+
- `routeDefaults` (version config) holds the route settings every route shares (title/description
308+
CSS, positions, visibility, margins, `blockLink`, `browseAll`); routes keep only their own values.
309+
310+
The viewer expands both forms when it loads a config (`@gitpagedocs/tools/config-format`), so the
311+
flat keys still work: 0.0.x configs load unchanged, and a flat key or a value written on a route wins
312+
over the compact form.
313+
292314
## Version selector visibility
293315

294316
In the docs shell, the **version** dropdown is hidden when `VersionControl.versions` resolves to **at most one unique** `id`:

‎cli/content/docs-en.mjs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,10 @@ Version configs support multiple content types:
391391
392392
Each route can include \`title\`, \`description\` (per language), \`titleCss\`, \`titlePosition: "center"\`, \`descriptionPosition: "center"\`, \`titleIsVisible\`, \`descriptionIsVisible\`.
393393
394+
Settings every route shares are written once in \`routeDefaults\` at the top of the version config; each route keeps only its own values, and a value written on a route wins over \`routeDefaults\`.
395+
396+
Header icons live in \`site.icons\`: \`defaults\` holds what all icons share (\`reactIcon\`, \`colorDark\`, \`colorLight\`, \`size\`, \`imgDark\`, \`imgLight\`, \`imgWidth\`, \`imgHeight\`) and each icon entry sets its \`tag\` plus any difference (\`null\` removes a field). The older flat keys (\`IconNavMenuOpenReactIconesTag\`, …) still work and win when both are present.
397+
394398
## Route-level variables (blockLink, container, url, browseAll)
395399
396400
Per-route options for \`routes-md\`, \`routes-html\`, \`routes-video\`, and \`routes-audio\`:

‎cli/content/docs-es.mjs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ Claves principales:
205205
- \`docsVersion\`: version seleccionada por defecto
206206
- \`ActiveNavigation\`: habilita anterior/siguiente
207207
- \`FocusMode\`: habilita modo foco/lectura
208+
- \`icons\`: iconos de la cabecera. \`icons.defaults\` guarda lo que todos comparten (\`reactIcon\`, \`colorDark\`, \`colorLight\`, \`size\`, \`imgDark\`, \`imgLight\`, \`imgWidth\`, \`imgHeight\`) y cada icono define su \`tag\` y lo que cambia (\`null\` quita un campo). Las claves planas de abajo siguen valiendo y ganan cuando existen ambas formas.
208209
- \`IconImageMenuHeaderImgWidth\`, \`IconImageMenuHeaderImgHeight\`: tamano del icono principal
209210
- \`IconImageMenuHeaderLightImg\`, \`IconImageMenuHeaderDarkImg\`: icono principal (light/dark)
210211
- \`IconProjectLinkImgWidth\`, \`IconProjectLinkImgHeight\`: tamano del icono enlace del proyecto
@@ -256,6 +257,8 @@ Los configs de version soportan multiples tipos:
256257
257258
Cada ruta puede incluir \`title\`, \`description\` (por idioma), \`titleCss\`, \`titlePosition: "center"\`, \`descriptionPosition: "center"\`, \`titleIsVisible\`, \`descriptionIsVisible\`.
258259
260+
Los ajustes que comparten todas las rutas se escriben una sola vez en \`routeDefaults\`, al inicio del config de la version; cada ruta guarda solo sus propios valores, y un valor escrito en la ruta gana sobre \`routeDefaults\`.
261+
259262
## Variables por ruta (blockLink, container, url, browseAll)
260263
261264
Opciones por ruta en \`routes-md\`, \`routes-html\` y \`routes-video\`:

‎cli/content/docs-pt.mjs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ Principais chaves:
205205
- \`docsVersion\`: versao inicial selecionada
206206
- \`ActiveNavigation\`: habilita comportamento de anterior/proximo
207207
- \`FocusMode\`: habilita modo foco/leitura
208+
- \`icons\`: icones do cabecalho. \`icons.defaults\` guarda o que todos compartilham (\`reactIcon\`, \`colorDark\`, \`colorLight\`, \`size\`, \`imgDark\`, \`imgLight\`, \`imgWidth\`, \`imgHeight\`) e cada icone define seu \`tag\` e o que muda (\`null\` remove um campo). As chaves planas abaixo continuam valendo e vencem quando as duas formas existem.
208209
- \`IconImageMenuHeaderImgWidth\`, \`IconImageMenuHeaderImgHeight\`: tamanho do icone principal
209210
- \`IconImageMenuHeaderLightImg\`, \`IconImageMenuHeaderDarkImg\`: icone principal (light/dark)
210211
- \`IconProjectLinkImgWidth\`, \`IconProjectLinkImgHeight\`: tamanho do icone link do projeto
@@ -256,6 +257,8 @@ Configs de versao suportam multiplos tipos:
256257
257258
Cada rota pode incluir \`title\`, \`description\` (por idioma), \`titleCss\`, \`titlePosition: "center"\`, \`descriptionPosition: "center"\`, \`titleIsVisible\`, \`descriptionIsVisible\`.
258259
260+
Configuracoes que todas as rotas compartilham ficam uma unica vez em \`routeDefaults\`, no topo do config da versao; cada rota guarda so os proprios valores, e um valor escrito na rota vence o \`routeDefaults\`.
261+
259262
## Variaveis por rota (blockLink, container, url, browseAll)
260263
261264
Opcoes por rota em \`routes-md\`, \`routes-html\` e \`routes-video\`:

‎cli/runtime/output.mjs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { layoutsArtifactPaths } from "../contracts/layouts-paths.mjs";
1212
import { languageArtifactPaths } from "../contracts/langs-paths.mjs";
1313
import { SUPPORTED_LANGUAGES } from "../contracts/languages.mjs";
14+
import { compactSiteIcons, factorRouteDefaults } from "@gitpagedocs/tools/config-format";
1415

1516
const DEFAULT_ICON_SVG = `<?xml version="1.0" encoding="utf-8"?>
1617
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -65,7 +66,7 @@ async function writeLanguageArtifacts(root, outputDir, artifacts) {
6566

6667
async function writeVersionConfigs(root, outputDir, versionConfigs) {
6768
for (const [versionId, versionConfig] of Object.entries(versionConfigs)) {
68-
await writeJson(root, `${outputDir}/docs/versions/${versionId}/config.json`, versionConfig);
69+
await writeJson(root, `${outputDir}/docs/versions/${versionId}/config.json`, factorRouteDefaults(versionConfig));
6970
}
7071
}
7172

@@ -143,7 +144,8 @@ export async function writeConfigOnlyOutput(options) {
143144
createThemeTemplate,
144145
} = options;
145146

146-
await writeJson(root, `${outputDir}/config.json`, artifacts.rootConfig);
147+
// Stored compact: shared icon values and route settings are written once.
148+
await writeJson(root, `${outputDir}/config.json`, { ...artifacts.rootConfig, site: compactSiteIcons(artifacts.rootConfig.site) });
147149
await writeLanguageArtifacts(root, outputDir, artifacts);
148150
await writeText(root, `${outputDir}/icon.svg`, DEFAULT_ICON_SVG);
149151
await writeLayoutArtifacts({

‎cli/tests/core/output.test.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync
33
import os from "node:os";
44
import path from "node:path";
55
// @ts-expect-error .mjs runtime module is type-less in this package.
6+
import { applyRouteDefaults, expandSiteIcons } from "@gitpagedocs/tools/config-format";
67
import * as output from "../../runtime/output.mjs";
78
// @ts-expect-error .mjs runtime module is type-less in this package.
89
import * as orchestrator from "../../builders/config-orchestrator.mjs";
@@ -114,7 +115,10 @@ describe("writeConfigOnlyOutput with the real artifacts", { timeout: 30_000 }, (
114115

115116
const rootConfigText = readFileSync(at(root, "gitpagedocs/config.json"), "utf8");
116117
expect(rootConfigText.endsWith("\n")).toBe(true);
117-
expect(JSON.parse(rootConfigText)).toEqual(artifacts.rootConfig);
118+
// Stored compact (icons written once); expanding it gives back the generated config.
119+
const storedRoot = JSON.parse(rootConfigText);
120+
expect(storedRoot.site.icons).toBeDefined();
121+
expect({ ...storedRoot, site: expandSiteIcons(storedRoot.site) }).toEqual(artifacts.rootConfig);
118122
expect(readFileSync(at(root, "gitpagedocs/icon.svg"), "utf8").startsWith("<?xml")).toBe(true);
119123

120124
for (const language of SUPPORTED_LANGUAGES) {
@@ -128,7 +132,7 @@ describe("writeConfigOnlyOutput with the real artifacts", { timeout: 30_000 }, (
128132
}
129133

130134
for (const id of DOC_VERSIONS) {
131-
expect(readJson(root, `gitpagedocs/docs/versions/${id}/config.json`)).toEqual(artifacts.versionConfigs[id]);
135+
expect(applyRouteDefaults(readJson(root, `gitpagedocs/docs/versions/${id}/config.json`))).toEqual(artifacts.versionConfigs[id]);
132136
for (const language of SUPPORTED_LANGUAGES) {
133137
const badge = `${BADGES[language]}${id}`;
134138
const gettingStarted = readFileSync(at(root, `gitpagedocs/docs/versions/${id}/${language}/getting-started.md`), "utf8");
@@ -221,7 +225,7 @@ describe("writeConfigOnlyOutput with synthetic artifacts", () => {
221225

222226
expect(readJson(root, "out/config.json")).toEqual(artifacts.rootConfig);
223227
expect(readJson(root, "out/langs/en.json")).toEqual(artifacts.languageBundles.en);
224-
expect(readJson(root, `out/docs/versions/${versionId}/config.json`)).toEqual(artifacts.versionConfigs[versionId]);
228+
expect(applyRouteDefaults(readJson(root, `out/docs/versions/${versionId}/config.json`))).toEqual(artifacts.versionConfigs[versionId]);
225229
expect(readJson(root, "out/docs/versions/0.0.1/config.json")).toEqual({});
226230

227231
// Already tagged content is written unchanged; unknown keys and languages are skipped.

‎frontend/src/entities/docs/api/load-remote-docs-data-client.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
fetchUrlJson,
3333
} from "@/shared/api/fetch-client";
3434
import { withConfigDefaults } from "../lib/with-config-defaults";
35+
import { expandVersionConfig } from "../lib/expand-version-config";
3536
import { localizeConfig } from "./config/localize-config";
3637
import { applyLanguageToggles } from "./utils/route-utils";
3738
import { markdownToHtml } from "./utils/markdown";
@@ -221,7 +222,8 @@ async function loadVersionConfig(owner: string, repo: string, versionEntry: Vers
221222
}
222223

223224
if (/^https?:\/\//i.test(versionPath)) {
224-
return await fetchUrlJson<VersionConfig>(versionPath);
225+
const fetched = await fetchUrlJson<VersionConfig>(versionPath);
226+
return fetched ? expandVersionConfig(fetched) : null;
225227
}
226228

227229
const normalizedPathCandidates = Array.from(
@@ -230,7 +232,7 @@ async function loadVersionConfig(owner: string, repo: string, versionEntry: Vers
230232
for (const pathCandidate of normalizedPathCandidates) {
231233
const candidateConfig = await fetchRepoJson<VersionConfig>(owner, repo, pathCandidate);
232234
if (candidateConfig) {
233-
return candidateConfig;
235+
return expandVersionConfig(candidateConfig);
234236
}
235237
}
236238

‎frontend/src/entities/docs/api/version/resolve-version.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expandVersionConfig } from "../../lib/expand-version-config";
12
import type {
23
AuthConfig,
34
ContentTypeRouteConfig,
@@ -133,6 +134,7 @@ export async function loadVersionConfig(options: LoadVersionConfigOptions): Prom
133134
return undefined;
134135
}
135136

136-
const versionConfig = await readVersionConfig(versionPath, options);
137+
const stored = await readVersionConfig(versionPath, options);
138+
const versionConfig = stored ? expandVersionConfig(stored) : null;
137139
return versionConfig && hasVersionContent(versionConfig) ? versionConfig : undefined;
138140
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { applyRouteDefaults } from "@gitpagedocs/tools/config-format";
2+
3+
/**
4+
* Version configs are stored with the settings every route shares in
5+
* `routeDefaults`; readers get complete routes. Browser-safe, so both the
6+
* local (fs) and the remote (fetch) version readers use it.
7+
*/
8+
export function expandVersionConfig<T extends object>(stored: T): T {
9+
return applyRouteDefaults(stored as Record<string, unknown>) as T;
10+
}

0 commit comments

Comments
 (0)