Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new Changes
Sequence Diagram(s)sequenceDiagram
participant Vite as rgba(52,152,219,0.5)
participant Plugin as rgba(46,204,113,0.5)
participant Generate as rgba(155,89,182,0.5)
participant Crawl as rgba(241,196,15,0.5)
participant Render as rgba(231,76,60,0.5)
participant FS as rgba(127,140,141,0.5)
Vite->>Plugin: closeBundle / build hook
Plugin->>Plugin: resolve config & entry
Plugin->>Generate: invoke generate(options)
Generate->>Crawl: crawlRoutes(routes)
Crawl-->>Generate: StaticRoute[]
Generate->>Generate: preloadContent (if set)
loop per static route
Generate->>Render: renderToString(component, props, {hydrate})
Render-->>Generate: { html, hasHydrationMarkers }
Generate->>Generate: inject head, minify, add _ssg content script as needed
Generate->>FS: write outputPath (HTML)
end
Generate->>FS: write sitemap.xml / robots.txt (optional)
Generate-->>Plugin: GenerateResult
Plugin-->>Vite: generation complete
sequenceDiagram
participant Browser as rgba(52,152,219,0.5)
participant StaticHTML as rgba(155,89,182,0.5)
participant HydrateScript as rgba(46,204,113,0.5)
participant Hydrator as rgba(241,196,15,0.5)
participant Component as rgba(231,76,60,0.5)
Browser->>StaticHTML: load pre-rendered HTML
StaticHTML->>Browser: display static content
Browser->>HydrateScript: load client script (hydrate)
HydrateScript->>Hydrator: scan DOM [data-gea]
loop for each marker
Hydrator->>Component: instantiate Component(props)
Component->>Browser: render interactive DOM & bind events
end
HydrateScript-->>Browser: hydration finished
Estimated Code Review Effort🎯 5 (Critical) | ⏱️ ~110 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (9)
packages/gea/src/lib/base/uid.ts-5-7 (1)
5-7:⚠️ Potential issue | 🟡 MinorValidate
seedto keep UID format stable.
resetUidCounteraccepts any number; negative, fractional, or non-finite values can lead to unexpected ID strings fromtoString(36).Defensive guard
export function resetUidCounter(seed: number = 0): void { + if (!Number.isSafeInteger(seed) || seed < 0) { + throw new RangeError('seed must be a non-negative safe integer') + } counter = seed }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea/src/lib/base/uid.ts` around lines 5 - 7, The resetUidCounter function should validate and normalize the incoming seed so counter remains a non-negative integer; update resetUidCounter to check Number.isFinite(seed) and seed >= 0, then coerce to an integer (e.g. Math.floor or equivalent) before assigning to counter, and either throw or default to 0 for invalid inputs to prevent negative, fractional, or non-finite values from producing malformed base-36 UIDs; reference the resetUidCounter function and the module-level counter variable when making this change.examples/ssg-basic/package.json-3-3 (1)
3-3:⚠️ Potential issue | 🟡 MinorRemove manual
versionfrom thispackage.json.Line 3 conflicts with repo versioning policy; let changesets own version changes.
✅ Suggested change
- "version": "1.0.0",As per coding guidelines, "Never manually edit version numbers in
package.json— let changesets own them".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/ssg-basic/package.json` at line 3, Remove the manual "version" property from the package.json so changesets can manage versions; locate the "version" key (currently "version": "1.0.0") in examples/ssg-basic/package.json and delete that line entirely, committing the file without a version field so the repository's changesets workflow can control version bumps.packages/gea/src/lib/router/link.ts-33-39 (1)
33-39:⚠️ Potential issue | 🟡 MinorNormalize trailing slashes before SSG active-route comparison.
Line 38 can miss active state when
toalready ends with/(it checksto + '/', yielding//). This breaks staticdata-activefor nested routes in trailing-slash setups.💡 Suggested fix
- const ssgPath = Link._ssgCurrentPath + const ssgPath = Link._ssgCurrentPath if (ssgPath !== null) { - const to = props.to + const normalize = (p: string) => (p !== '/' && p.endsWith('/') ? p.slice(0, -1) : p) + const to = normalize(props.to) + const current = normalize(ssgPath) const active = props.exact - ? ssgPath === to + ? current === to : to === '/' - ? ssgPath === '/' - : ssgPath === to || ssgPath.startsWith(to + '/') + ? current === '/' + : current === to || current.startsWith(to + '/') if (active) activeAttr = ' data-active' }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea/src/lib/router/link.ts` around lines 33 - 39, The active-route check in link.ts can fail when props.to ends with a trailing slash because the expression (to + '/') produces '//' — normalize both ssgPath and to before comparing: trim any trailing slash from to (except keep single '/' for root) and normalize ssgPath similarly, then evaluate the existing exact/non-exact logic using the normalizedTo and normalizedPath variables (used where to, ssgPath, active, activeAttr are referenced) so data-active is set correctly for nested routes.packages/gea/src/index.ts-6-16 (1)
6-16:⚠️ Potential issue | 🟡 MinorAdd a changeset file for these public API exports.
This PR changes
@geajs/core's public API but lacks a corresponding changeset entry. Runnpx changesetto declare the bump type (patch/minor/major) and summary.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea/src/index.ts` around lines 6 - 16, This PR adds/changes public exports (e.g., resetUidCounter, applyListChanges, ListConfig, createRouter, Router, matchRoute, Head, RouteMap, RouteEntry, etc.) but lacks a changeset; run npx changeset at the repo root and create a changeset that names the affected package (the package exposing these exports, e.g. `@geajs/core` or packages/gea), choose the appropriate bump type (patch/minor/major), and write a short summary describing the export changes so the release tooling will include this API change in the version bump.packages/gea-ssg/tests/render.test.ts-82-85 (1)
82-85:⚠️ Potential issue | 🟡 MinorThis doesn't validate
seedyet.The check around Line 82 uses
MockComponent, which always returns a constant string, so it passes even ifresetUidCounter(seed)is never applied. Render a real GeaComponentthat surfacesthis.id(and ideally compare same-seed vs different-seed output) so the test actually exercises the seeded path.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/tests/render.test.ts` around lines 82 - 85, The test currently uses MockComponent (constant output) so it never exercises seeding; replace MockComponent with a real Gea Component (e.g., a class or function component that references this.id in its render) and call renderToString with seed 42 twice to assert equality, and once with a different seed (e.g., 43) to assert the output differs; ensure you still use renderToString(...) and validate outputs so the seeded path (resetUidCounter/seed handling that affects this.id) is actually exercised.packages/gea-ssg/README.md-101-105 (1)
101-105:⚠️ Potential issue | 🟡 MinorAdd a language to this fenced block.
The docs lint will keep flagging this unlabeled snippet;
textis enough for this output-mapping example.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/README.md` around lines 101 - 105, The fenced code block showing route-to-output mappings in README.md is unlabeled; change the opening fence from ``` to ```text so the snippet is marked as text (e.g., update the block that contains "/ -> dist/index.html" "/about -> dist/about/index.html" "/contact -> dist/contact/index.html" to start with ```text).docs/tooling/ssg.md-68-79 (1)
68-79:⚠️ Potential issue | 🟡 MinorGive this fenced example a language.
The docs lint will keep flagging the unlabeled block;
textis enough for this file-tree snippet.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/tooling/ssg.md` around lines 68 - 79, The fenced code block showing the "dist/" file-tree lacks a language tag; update the fenced block (the code block that begins with "dist/") to include a language identifier (e.g., add "text" after the opening ```), so the docs lint stops flagging it.packages/gea-ssg/tests/generate.test.ts-124-127 (1)
124-127:⚠️ Potential issue | 🟡 MinorKeep the shell fixture outside
outDir.Here
shellPathistempDir/index.html, and the tests also render/tooutDir: tempDir. That means the root-page write clobbers the template file, so the suite depends ongenerate()reading the shell eagerly and can turn flaky once multiple routes render concurrently. A sibling temp dir or nesteddist/output would avoid that coupling.Also applies to: 135-140
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/tests/generate.test.ts` around lines 124 - 127, The shell fixture is being created at tempDir/index.html while tests set outDir to tempDir, causing the generated root page to overwrite the shell; fix by placing the shell outside the output dir (e.g., create a separate temp dir for fixtures or use a nested output like join(tempDir, 'dist') for outDir) so shellPath is not inside outDir; update the beforeEach setup that defines tempDir, shellPath and outDir (and the similar block referenced at lines ~135-140) to create distinct directories and ensure generate() reads the fixture without risk of being clobbered.packages/gea-ssg/package.json-3-3 (1)
3-3:⚠️ Potential issue | 🟡 MinorLet Changesets own this package version.
Hardcoding
1.0.0here bypasses the repo's release workflow for published packages. Please keep the version managed by Changesets instead of editingpackage.jsondirectly.As per coding guidelines "Never manually edit version numbers in
package.json— let changesets own them".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/package.json` at line 3, The package.json change hardcodes "version": "1.0.0" which bypasses Changesets; revert the manual edit to the "version" field in packages/gea-ssg/package.json back to the repository's Changesets-managed placeholder (remove the "1.0.0" bump), do not commit manual version changes, and if you intended to release create a proper changeset so Changesets will update the version during the release process.
🧹 Nitpick comments (2)
packages/gea/src/lib/head.ts (1)
7-16: CollectHead._currentonly during SSG to avoid browser-side accumulation.Array props are appended on every render, so repeated browser re-renders can grow
Head._currentindefinitely even though it’s primarily SSG state.Scope accumulation to SSG mode
- if (!Head._current) Head._current = {} - const props = this.props || {} - for (const [key, value] of Object.entries(props)) { - if (key === 'id') continue - if (Array.isArray(value) && Array.isArray(Head._current[key])) { - Head._current[key] = [...Head._current[key], ...value] - } else { - Head._current[key] = value - } - } + if (Component._ssgMode) { + if (!Head._current) Head._current = {} + const props = this.props || {} + for (const [key, value] of Object.entries(props)) { + if (key === 'id') continue + if (Array.isArray(value) && Array.isArray(Head._current[key])) { + Head._current[key] = [...Head._current[key], ...value] + } else { + Head._current[key] = value + } + } + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea/src/lib/head.ts` around lines 7 - 16, The Head._current accumulation should only happen during SSG to avoid browser-side growth; wrap the existing mutation block that iterates this.props and updates Head._current in a server-only guard (e.g., if (typeof window === "undefined") { ... }) so the array-append logic for Head._current[key] runs only on the server/SSG passes; keep the same behavior for skipping 'id' and merging arrays but prevent any updates to Head._current in client-side renders.packages/gea-ssg/tests/render.test.ts (1)
66-80: Add atemplate()-throwing regression case.The current error-path assertions only cover constructor failure. If
renderToStringstops trapping errors thrown duringtemplate(), this suite still passes, so please add a component whosetemplate()throws and verify both the default rethrow path and theonRenderErrorpath.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/tests/render.test.ts` around lines 66 - 80, Add a new test component whose template() (not constructor) throws and mirror the existing two cases: one asserting renderToString rethrows by default and one asserting renderToString calls onRenderError and returns an empty string; create e.g. TemplateThrowingComponent with a template() that throws a specific Error message, then add an assert.throws(() => renderToString(TemplateThrowingComponent), { message: 'Component template failed' }) case and a second case that calls renderToString(TemplateThrowingComponent, undefined, { onRenderError: (err) => capturedError = err }) asserting returned html is '' and capturedError.message matches 'Component template failed'.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/gea-ssg/src/crawl.ts`:
- Around line 37-86: The SSG route branch can emit paths with unresolved
":param" segments; before pushing any route into result (in the content slug
loop that calls resolveParams(fullPath, params), in the paths loop that calls
resolveParams(fullPath, pathEntry.params), and in the final result.push for bare
component routes) compute the resolvedPath and skip adding the entry if it still
contains unresolved params (e.g. use a small helper like
hasUnresolvedParams(path) => /(^|\/):\w+\b/.test(path)); keep the existing /404
handling but ensure all other pushes use the resolvedPath and are guarded by
this check.
In `@packages/gea-ssg/src/generate.ts`:
- Around line 130-133: Replace the brittle HTML scan with an explicit hydration
signal: update renderToString() to return a hasHydrationMarkers boolean (true
when it embeds GEA hydration markers) and update the call site in generate.ts to
branch on that flag instead of fullHtml.includes('data-gea='); remove the regex
checks for module/script/link when options.hydrate is true and use
hasHydrationMarkers to decide whether to strip module script/modulepreload tags.
Ensure any callers of renderToString() (and its return type) are updated to
accept the new tuple/object return containing fullHtml and hasHydrationMarkers
so callers use the explicit flag.
In `@packages/gea-ssg/src/head.ts`:
- Around line 64-66: replaceTitle currently only replaces an existing <title>
and does nothing if none exists; update the replaceTitle(html: string, title:
string) function to detect absence of a <title> and insert one instead of being
a no-op: if the regex /<title>[^<]*<\/title>/i does not match, create
`<title>${escHtml(title)}</title>` and inject it into the document head
(preferably right after the opening <head> or before </head> if an opening tag
position is not easily found), falling back to prepending to the document if no
<head> is present; keep using escHtml(title) and ensure the function returns the
modified HTML string.
In `@packages/gea-ssg/src/render.ts`:
- Around line 35-47: The current JSON.stringify probe in (cm as
any).setComponent only detects throwing non-serializables and misses silent
conversions (Date→string, undefined dropped, functions removed), causing
mismatched client hydration; change the logic in setComponent to perform a
deterministic, recursive validation/normalization of comp.props (reject or
explicitly convert unsupported types: serialize Dates to ISO, convert undefined
to null or explicit marker, throw on functions/symbols) and then store the
normalized object into entry.props; apply the same validation/normalization
routine to any values used for data-gea attribute injection (the code around the
data-gea attribute assignment) so attributes contain the same, lossless
representation that will be used by the client for hydration.
In `@packages/gea-ssg/src/shell.ts`:
- Around line 13-15: The regex in parseShell interpolates appElementId directly
into openTagRegex causing special regex metacharacters in IDs to be interpreted;
fix by escaping appElementId before building the RegExp (add an escapeRegExp
utility or use a sanitizedEscapedId variable) and use that escaped value when
constructing openTagRegex in parseShell; also add a unit test calling
parseShell('<div id="app.main"></div>', 'app.main') (and similar cases with '+'
or other metacharacters) to verify correct matching.
In `@packages/gea-ssg/src/vite-plugin.ts`:
- Around line 89-101: The current branch ignores a partial user override because
it only uses options.routes and options.app when both are present; update the
logic in the gea-ssg initialization so that you always load the module via
viteServer.ssrLoadModule(entry) when either option is missing, then merge
values: set ssgOpts.routes = options.routes ?? ssgEntry.routes and ssgOpts.app =
options.app ?? (ssgEntry.App || ssgEntry.default); finally, keep the existing
validation and throw the Error if either ssgOpts.routes or ssgOpts.app is still
missing after the merge (use symbols options.routes, options.app,
ssgOpts.routes, ssgOpts.app, entry, viteServer.ssrLoadModule, ssgEntry).
- Around line 103-104: The call to generate(ssgOpts) ignores per-route failures
reported in the returned result, so CI can pass with partial output; update the
usage of generate in the block around generate(ssgOpts) to capture its result
(e.g., const result = await generate(ssgOpts)), check result.errors (or
result.failedRoutes) after it completes, and if any errors exist, log them and
throw an Error (or otherwise exit non‑zero) to fail the build so route rendering
failures do not silently pass.
- Around line 42-54: In closeBundle(), change the loadConfigFromFile call to use
{ command: 'build', mode: config.mode } so build-specific branches are loaded,
and normalize resolve.alias results instead of discarding array forms: when
loaded.config.resolve?.alias exists, detect if it's an array and convert
array-form entries into an object map (merging entries and preserving
precedence) or otherwise accept the object as-is, assigning the normalized map
to userAlias; keep the existing userPlugins handling intact so SSR server
created later uses the same plugins and alias mapping as the build.
- Around line 107-113: The closeBundle hook currently forces process.exit via
setTimeout(...).unref(), which can kill other plugins' parallel closeBundle
tasks; remove that process.exit call and instead run the SSG generation that
uses the viteServer in an isolated child process (spawn/fork) so the plugin's
viteServer lifecycle is contained; update the closeBundle implementation (the
function that constructs/uses viteServer and calls setTimeout(...).unref()) to
spawn a child process to perform the SSG work and wait for its exit code before
returning, and ensure the parent does not call process.exit so other plugins'
closeBundle hooks can finish naturally.
In `@packages/gea/src/lib/base/component.tsx`:
- Around line 59-67: The created(this.props) call runs during SSG and may cause
server-side side effects; move the invocation behind the same _ssgMode guard as
createdHooks so created() only executes when Component._ssgMode is false (and
keep the existing __setupLocalStateObservers call inside that guard), or
alternatively split created into a server-safe init and a browser-only
createdBrowser method and invoke only the browser one when Component._ssgMode is
false; update references to created, createdHooks, Component._ssgMode and
__setupLocalStateObservers accordingly.
In `@packages/gea/src/lib/head.ts`:
- Around line 54-73: Injected meta/link tags from props.meta/props.link are
never removed or fully reconciled across navigations and the current link lookup
uses only rel which can collide; update the reconciliation in the head handling
so that for meta entries (handled by this._setMeta/key) you detect existing tags
by both name and property, update content when present and remove any previously
injected tags no longer in props.meta, and for link entries build a precise
selector from all identifying attributes (not just rel), or attach a tracking
data attribute when creating elements so you can reliably find, update, or
remove those exact link elements (attrs, selector, el) on subsequent updates
rather than blindly creating duplicates or overwriting unrelated links.
- Around line 116-127: The _setMeta function currently bails out on falsy
content and leaves existing meta tags stale; change behavior so when content is
null/undefined or an empty string you locate the existing meta element (using
the same attr logic: property for og/twitter, name otherwise) and remove it from
the DOM if present, then return; only create and append a new meta element when
content is non-empty, and otherwise set el.content when you have a valid element
and non-empty content. Target the _setMeta method and the
document.querySelector(`meta[${attr}="${nameOrProperty}"]`) usage to implement
this removal-first logic.
In `@packages/gea/src/lib/router/router-view.ts`:
- Around line 25-47: The SSG render path (RouterView._ssgRoute handling) can
leave Outlet._ssgHtml populated and skip component/layout.dispose if an
exception occurs; wrap the per-component and per-layout template rendering and
dispose logic in try/finally blocks so that dispose() is always called when
defined and Outlet._ssgHtml is always restored to null after each layout
iteration and after the leaf render (including the no-layout branch), ensuring
no stale _ssgHtml persists between renders (referencing RouterView._ssgRoute,
component, layouts, params, leaf, layout, dispose and Outlet._ssgHtml).
---
Minor comments:
In `@docs/tooling/ssg.md`:
- Around line 68-79: The fenced code block showing the "dist/" file-tree lacks a
language tag; update the fenced block (the code block that begins with "dist/")
to include a language identifier (e.g., add "text" after the opening ```), so
the docs lint stops flagging it.
In `@examples/ssg-basic/package.json`:
- Line 3: Remove the manual "version" property from the package.json so
changesets can manage versions; locate the "version" key (currently "version":
"1.0.0") in examples/ssg-basic/package.json and delete that line entirely,
committing the file without a version field so the repository's changesets
workflow can control version bumps.
In `@packages/gea-ssg/package.json`:
- Line 3: The package.json change hardcodes "version": "1.0.0" which bypasses
Changesets; revert the manual edit to the "version" field in
packages/gea-ssg/package.json back to the repository's Changesets-managed
placeholder (remove the "1.0.0" bump), do not commit manual version changes, and
if you intended to release create a proper changeset so Changesets will update
the version during the release process.
In `@packages/gea-ssg/README.md`:
- Around line 101-105: The fenced code block showing route-to-output mappings in
README.md is unlabeled; change the opening fence from ``` to ```text so the
snippet is marked as text (e.g., update the block that contains "/ ->
dist/index.html" "/about -> dist/about/index.html" "/contact ->
dist/contact/index.html" to start with ```text).
In `@packages/gea-ssg/tests/generate.test.ts`:
- Around line 124-127: The shell fixture is being created at tempDir/index.html
while tests set outDir to tempDir, causing the generated root page to overwrite
the shell; fix by placing the shell outside the output dir (e.g., create a
separate temp dir for fixtures or use a nested output like join(tempDir, 'dist')
for outDir) so shellPath is not inside outDir; update the beforeEach setup that
defines tempDir, shellPath and outDir (and the similar block referenced at lines
~135-140) to create distinct directories and ensure generate() reads the fixture
without risk of being clobbered.
In `@packages/gea-ssg/tests/render.test.ts`:
- Around line 82-85: The test currently uses MockComponent (constant output) so
it never exercises seeding; replace MockComponent with a real Gea Component
(e.g., a class or function component that references this.id in its render) and
call renderToString with seed 42 twice to assert equality, and once with a
different seed (e.g., 43) to assert the output differs; ensure you still use
renderToString(...) and validate outputs so the seeded path
(resetUidCounter/seed handling that affects this.id) is actually exercised.
In `@packages/gea/src/index.ts`:
- Around line 6-16: This PR adds/changes public exports (e.g., resetUidCounter,
applyListChanges, ListConfig, createRouter, Router, matchRoute, Head, RouteMap,
RouteEntry, etc.) but lacks a changeset; run npx changeset at the repo root and
create a changeset that names the affected package (the package exposing these
exports, e.g. `@geajs/core` or packages/gea), choose the appropriate bump type
(patch/minor/major), and write a short summary describing the export changes so
the release tooling will include this API change in the version bump.
In `@packages/gea/src/lib/base/uid.ts`:
- Around line 5-7: The resetUidCounter function should validate and normalize
the incoming seed so counter remains a non-negative integer; update
resetUidCounter to check Number.isFinite(seed) and seed >= 0, then coerce to an
integer (e.g. Math.floor or equivalent) before assigning to counter, and either
throw or default to 0 for invalid inputs to prevent negative, fractional, or
non-finite values from producing malformed base-36 UIDs; reference the
resetUidCounter function and the module-level counter variable when making this
change.
In `@packages/gea/src/lib/router/link.ts`:
- Around line 33-39: The active-route check in link.ts can fail when props.to
ends with a trailing slash because the expression (to + '/') produces '//' —
normalize both ssgPath and to before comparing: trim any trailing slash from to
(except keep single '/' for root) and normalize ssgPath similarly, then evaluate
the existing exact/non-exact logic using the normalizedTo and normalizedPath
variables (used where to, ssgPath, active, activeAttr are referenced) so
data-active is set correctly for nested routes.
---
Nitpick comments:
In `@packages/gea-ssg/tests/render.test.ts`:
- Around line 66-80: Add a new test component whose template() (not constructor)
throws and mirror the existing two cases: one asserting renderToString rethrows
by default and one asserting renderToString calls onRenderError and returns an
empty string; create e.g. TemplateThrowingComponent with a template() that
throws a specific Error message, then add an assert.throws(() =>
renderToString(TemplateThrowingComponent), { message: 'Component template
failed' }) case and a second case that calls
renderToString(TemplateThrowingComponent, undefined, { onRenderError: (err) =>
capturedError = err }) asserting returned html is '' and capturedError.message
matches 'Component template failed'.
In `@packages/gea/src/lib/head.ts`:
- Around line 7-16: The Head._current accumulation should only happen during SSG
to avoid browser-side growth; wrap the existing mutation block that iterates
this.props and updates Head._current in a server-only guard (e.g., if (typeof
window === "undefined") { ... }) so the array-append logic for
Head._current[key] runs only on the server/SSG passes; keep the same behavior
for skipping 'id' and merging arrays but prevent any updates to Head._current in
client-side renders.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7e73c635-0ad2-469c-8003-75bad55a6ebd
⛔ Files ignored due to path filters (2)
examples/ssg-basic/package-lock.jsonis excluded by!**/package-lock.jsonpackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (55)
.gitignoreREADME.mddocs/SUMMARY.mddocs/tooling/ssg.mdexamples/ssg-basic/README.mdexamples/ssg-basic/index.htmlexamples/ssg-basic/package.jsonexamples/ssg-basic/src/App.tsxexamples/ssg-basic/src/content/blog/getting-started-with-gea.mdexamples/ssg-basic/src/content/blog/reactive-stores-deep-dive.mdexamples/ssg-basic/src/content/blog/understanding-ssg.mdexamples/ssg-basic/src/content/changelog/v1.1.mdexamples/ssg-basic/src/content/changelog/v1.mdexamples/ssg-basic/src/main.tsexamples/ssg-basic/src/styles.cssexamples/ssg-basic/src/views/About.tsxexamples/ssg-basic/src/views/Blog.tsxexamples/ssg-basic/src/views/BlogPost.tsxexamples/ssg-basic/src/views/Contact.tsxexamples/ssg-basic/src/views/Counter.tsxexamples/ssg-basic/src/views/Home.tsxexamples/ssg-basic/src/views/LiveClock.tsxexamples/ssg-basic/src/views/NotFound.tsxexamples/ssg-basic/tsconfig.jsonexamples/ssg-basic/vite.config.tspackages/gea-ssg/README.mdpackages/gea-ssg/package.jsonpackages/gea-ssg/src/client.tspackages/gea-ssg/src/content.tspackages/gea-ssg/src/crawl.tspackages/gea-ssg/src/generate.tspackages/gea-ssg/src/head.tspackages/gea-ssg/src/index.tspackages/gea-ssg/src/render.tspackages/gea-ssg/src/shell.tspackages/gea-ssg/src/types.tspackages/gea-ssg/src/vite-plugin.tspackages/gea-ssg/tests/content.test.tspackages/gea-ssg/tests/crawl.test.tspackages/gea-ssg/tests/generate.test.tspackages/gea-ssg/tests/head.test.tspackages/gea-ssg/tests/render.test.tspackages/gea-ssg/tests/shell.test.tspackages/gea-ssg/tsconfig.jsonpackages/gea/src/index.tspackages/gea/src/lib/base/component-manager.tspackages/gea/src/lib/base/component.tsxpackages/gea/src/lib/base/uid.tspackages/gea/src/lib/head.tspackages/gea/src/lib/router/index.tspackages/gea/src/lib/router/link.tspackages/gea/src/lib/router/outlet.tspackages/gea/src/lib/router/resolve.tspackages/gea/src/lib/router/router-view.tspackages/gea/src/lib/router/types.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/gea-ssg/src/generate.ts (1)
240-250: Consider XML-escaping URL paths.Route paths and
lastmodvalues are inserted directly into XML without escaping. While developer-controlled paths rarely contain&,<, or>, a path like/foo&barwould produce malformed XML.♻️ Optional: Add XML escaping helper
+function escapeXml(str: string): string { + return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') +} + async function generateSitemap(...) { // ... .map((p) => { const head = headConfigs.get(p.path) const lastmod = head?.lastmod ? `\n <lastmod>${head.lastmod}</lastmod>` : '' - const loc = trailingSlash && p.path !== '/' ? `${hostname}${p.path}/` : `${hostname}${p.path}` + const loc = escapeXml(trailingSlash && p.path !== '/' ? `${hostname}${p.path}/` : `${hostname}${p.path}`)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/src/generate.ts` around lines 240 - 250, The sitemap generation inserts route paths and lastmod directly into XML (see map callback using headConfigs, lastmod, loc, trailingSlash, hostname, changefreq, priority) which can produce malformed XML for values with &, <, >, etc.; add a small xmlEscape helper (e.g., escape &, <, >, ", ') and apply it to the path portion when building loc and to head?.lastmod before embedding into the <lastmod> element so all inserted values are XML-escaped.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/gea-ssg/src/generate.ts`:
- Around line 240-250: The sitemap generation inserts route paths and lastmod
directly into XML (see map callback using headConfigs, lastmod, loc,
trailingSlash, hostname, changefreq, priority) which can produce malformed XML
for values with &, <, >, etc.; add a small xmlEscape helper (e.g., escape &, <,
>, ", ') and apply it to the path portion when building loc and to head?.lastmod
before embedding into the <lastmod> element so all inserted values are
XML-escaped.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 777ff576-12d6-46b9-8882-a09f27884493
📒 Files selected for processing (2)
examples/ssg-basic/vite.config.tspackages/gea-ssg/src/generate.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- examples/ssg-basic/vite.config.ts
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (4)
packages/gea-ssg/src/vite-plugin.ts (4)
103-103:⚠️ Potential issue | 🟠 MajorBuild doesn't fail when route rendering has errors.
generate()returns errors inresult.errorswithout throwing. Ignoring the result allows CI to pass with a partial static site.🛠️ Suggested fix
- await generate(ssgOpts) + const result = await generate(ssgOpts) + if (result.errors.length) { + const paths = result.errors.map(e => e.path).join(', ') + throw new Error(`[gea-ssg] SSG failed for ${result.errors.length} route(s): ${paths}`) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/src/vite-plugin.ts` at line 103, The call to await generate(ssgOpts) ignores the returned result which contains non-thrown errors in result.errors; change the call to capture the result (e.g., const result = await generate(ssgOpts)), then check result.errors (or result.errors.length) and if any errors exist throw an Error or terminate with a non-zero exit so the build fails; update the code around the generate(ssgOpts) call to perform this check and surface the first/combined error messages.
42-46:⚠️ Potential issue | 🟠 MajorUse
command: 'build'when loading config incloseBundle.This hook runs after build completes, but config is loaded with
command: 'serve'. Build-specific config branches (conditionals oncommand === 'build') are ignored, causing the SSR server to use different configuration than the actual build.🛠️ Suggested fix
if (config.configFile) { const loaded = await loadConfigFromFile( - { command: 'serve', mode: config.mode }, + { command: 'build', mode: config.mode }, config.configFile, config.root, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/src/vite-plugin.ts` around lines 42 - 46, The hook currently loads Vite config with loadConfigFromFile using { command: 'serve' } which ignores build-specific branches; update the call in the closeBundle path to use { command: 'build', mode: config.mode } so build-only conditionals are respected. Locate the loadConfigFromFile invocation in vite-plugin.ts (the call to loadConfigFromFile with command: 'serve') and change the command value to 'build'; ensure any downstream usage of the returned loaded config (e.g., the variable loaded) remains unchanged so the SSR server uses the actual build configuration.
89-101:⚠️ Potential issue | 🟠 MajorPartial overrides are silently ignored.
When only
options.routesoroptions.appis provided (not both), the code falls through to load the entry module but doesn't merge the user-provided value. This silently drops half of the override.🛠️ Suggested fix
- if (options.routes && options.app) { - ssgOpts.routes = options.routes - ssgOpts.app = options.app - } else { - const entry = options.entry || 'src/App.tsx' - const ssgEntry = await viteServer.ssrLoadModule(entry) - ssgOpts.routes = ssgEntry.routes - ssgOpts.app = ssgEntry.App || ssgEntry.default - - if (!ssgOpts.routes || !ssgOpts.app) { - throw new Error(`[gea-ssg] ${entry} must export "routes" and "App" (or default).`) - } + const entry = options.entry || 'src/App.tsx' + const ssgEntry = await viteServer.ssrLoadModule(entry) + ssgOpts.routes = options.routes ?? ssgEntry.routes + ssgOpts.app = options.app ?? ssgEntry.App ?? ssgEntry.default + + if (!ssgOpts.routes || !ssgOpts.app) { + throw new Error(`[gea-ssg] ${entry} must export "routes" and "App" (or default), or provide both in plugin options.`) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/src/vite-plugin.ts` around lines 89 - 101, The current logic ignores partial overrides: if only options.routes or options.app is provided the code still loads the entry and overwrites the provided value. Update the block around options.routes/options.app so you only call viteServer.ssrLoadModule(entry) when either ssgOpts.routes or ssgOpts.app is missing, then set ssgOpts.routes = options.routes ?? ssgEntry.routes and ssgOpts.app = options.app ?? (ssgEntry.App || ssgEntry.default); finally keep the existing validation that both ssgOpts.routes and ssgOpts.app exist and throw the same error if not.
51-54:⚠️ Potential issue | 🟠 MajorArray-form aliases are silently dropped.
The
!Array.isArray(alias)check discards array-formatresolve.aliasconfigurations, causing SSR module resolution mismatches. Normalize array entries to object form.🛠️ Suggested fix
const alias = loaded.config.resolve?.alias - if (alias && typeof alias === 'object' && !Array.isArray(alias)) { - userAlias = alias as Record<string, string> + if (alias && typeof alias === 'object') { + if (Array.isArray(alias)) { + for (const entry of alias) { + if (typeof entry.find === 'string' && typeof entry.replacement === 'string') { + userAlias[entry.find] = entry.replacement + } + } + } else { + userAlias = alias as Record<string, string> + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/src/vite-plugin.ts` around lines 51 - 54, The current check drops array-form resolve.alias entries; update the logic around loaded.config.resolve?.alias to normalize array aliases into userAlias instead of ignoring them: if alias is an array, iterate its entries (handle objects with find/replacement and pair tuples) and populate userAlias (the same Record<string,string> used for object-form aliases) by converting each entry.find to a string key and entry.replacement to the value; keep existing branch for object-form aliases and ensure later consumers use the unified userAlias mapping for SSR resolution.
🧹 Nitpick comments (1)
packages/gea-ssg/tests/head.test.ts (1)
81-84: Test name doesn't match assertion.The test is named "returns empty string when no config" but asserts that
og:typeis present. SincebuildHeadTags({})always emitsog:type(line 25 in head.ts), the function never returns an empty string. Consider renaming to reflect actual behavior.💡 Suggested fix
- it('returns empty string when no config', () => { + it('includes default og:type when no config', () => { const tags = buildHeadTags({}) assert.ok(tags.includes('og:type')) })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/tests/head.test.ts` around lines 81 - 84, The test name is misleading: update the test in head.test.ts that calls buildHeadTags({}) to reflect actual behavior (buildHeadTags always emits og:type). Rename the test from "returns empty string when no config" to something like "includes default og:type when no config" (or adjust the assertion to expect an empty string if you change buildHeadTags instead), and ensure the assertion continues to check for 'og:type' presence against buildHeadTags.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/gea-ssg/src/render.ts`:
- Line 69: The code stores the original comp.props into entry.props (entry.props
= comp.props) after detecting lossy conversions, so server HTML will differ from
the normalized props; change it to store the normalized/round-tripped value
instead (assign entry.props = roundTripped or the normalized cProps used for
JSON output). Locate the block around the lossy-detection logic (references:
comp.props, roundTripped, cProps, entry.props) and replace the assignment so
entry.props gets the round-tripped/normalized object that will be
JSON.stringify'd, ensuring server-rendered HTML and client-hydrated props match.
---
Duplicate comments:
In `@packages/gea-ssg/src/vite-plugin.ts`:
- Line 103: The call to await generate(ssgOpts) ignores the returned result
which contains non-thrown errors in result.errors; change the call to capture
the result (e.g., const result = await generate(ssgOpts)), then check
result.errors (or result.errors.length) and if any errors exist throw an Error
or terminate with a non-zero exit so the build fails; update the code around the
generate(ssgOpts) call to perform this check and surface the first/combined
error messages.
- Around line 42-46: The hook currently loads Vite config with
loadConfigFromFile using { command: 'serve' } which ignores build-specific
branches; update the call in the closeBundle path to use { command: 'build',
mode: config.mode } so build-only conditionals are respected. Locate the
loadConfigFromFile invocation in vite-plugin.ts (the call to loadConfigFromFile
with command: 'serve') and change the command value to 'build'; ensure any
downstream usage of the returned loaded config (e.g., the variable loaded)
remains unchanged so the SSR server uses the actual build configuration.
- Around line 89-101: The current logic ignores partial overrides: if only
options.routes or options.app is provided the code still loads the entry and
overwrites the provided value. Update the block around
options.routes/options.app so you only call viteServer.ssrLoadModule(entry) when
either ssgOpts.routes or ssgOpts.app is missing, then set ssgOpts.routes =
options.routes ?? ssgEntry.routes and ssgOpts.app = options.app ?? (ssgEntry.App
|| ssgEntry.default); finally keep the existing validation that both
ssgOpts.routes and ssgOpts.app exist and throw the same error if not.
- Around line 51-54: The current check drops array-form resolve.alias entries;
update the logic around loaded.config.resolve?.alias to normalize array aliases
into userAlias instead of ignoring them: if alias is an array, iterate its
entries (handle objects with find/replacement and pair tuples) and populate
userAlias (the same Record<string,string> used for object-form aliases) by
converting each entry.find to a string key and entry.replacement to the value;
keep existing branch for object-form aliases and ensure later consumers use the
unified userAlias mapping for SSR resolution.
---
Nitpick comments:
In `@packages/gea-ssg/tests/head.test.ts`:
- Around line 81-84: The test name is misleading: update the test in
head.test.ts that calls buildHeadTags({}) to reflect actual behavior
(buildHeadTags always emits og:type). Rename the test from "returns empty string
when no config" to something like "includes default og:type when no config" (or
adjust the assertion to expect an empty string if you change buildHeadTags
instead), and ensure the assertion continues to check for 'og:type' presence
against buildHeadTags.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5e41198d-6a71-448c-aca9-6a9825b84d23
📒 Files selected for processing (12)
packages/gea-ssg/src/content.tspackages/gea-ssg/src/crawl.tspackages/gea-ssg/src/generate.tspackages/gea-ssg/src/head.tspackages/gea-ssg/src/index.tspackages/gea-ssg/src/render.tspackages/gea-ssg/src/shell.tspackages/gea-ssg/src/vite-plugin.tspackages/gea-ssg/tests/generate.test.tspackages/gea-ssg/tests/head.test.tspackages/gea-ssg/tests/render.test.tspackages/gea-ssg/tests/shell.test.ts
✅ Files skipped from review due to trivial changes (3)
- packages/gea-ssg/tests/render.test.ts
- packages/gea-ssg/tests/shell.test.ts
- packages/gea-ssg/tests/generate.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/gea-ssg/src/crawl.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/gea/src/lib/head.ts`:
- Around line 64-68: The selector-building code in head.ts constructs attribute
selectors directly from attrs.rel and attrs.href which can break if those values
contain special characters; update the logic that sets selector (the branches
that assign selector = `link[rel="${attrs.rel}"][href="${attrs.href}"]` and
selector = `link[rel="${attrs.rel}"]`) to wrap attribute values with
CSS.escape() (e.g., CSS.escape(attrs.rel) and CSS.escape(attrs.href)) so the
generated selector is safe for all input.
- Line 31: The current assignment only sets document.title when props.title is
truthy, leaving the previous title on SPA navigation; update the cleanup path in
the _removeStale function to explicitly clear or reset document.title when
props.title is falsy (e.g., set document.title = '' or a configured
defaultTitle) so the title does not persist across routes—ensure you reference
and update the code path that checks props.title (the same location that
currently runs "if (props.title) document.title = props.title") and use a single
shared defaultTitle constant or site config value if available.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f9209888-cbca-4140-b269-f179b0f0cbfd
📒 Files selected for processing (4)
examples/ssg-basic/src/views/About.tsxpackages/gea/src/lib/base/component.tsxpackages/gea/src/lib/head.tspackages/gea/src/lib/router/router-view.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/gea/src/lib/base/component.tsx
- examples/ssg-basic/src/views/About.tsx
- packages/gea/src/lib/router/router-view.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/gea-ssg/src/vite-plugin.ts (1)
197-200: Add error handling for stream read.
createReadStream(notFound)can emit an error if the file is removed between theexistsSynccheck and the read (race condition). While unlikely in a preview server, unhandled stream errors could crash the server.Suggested defensive fix
if (existsSync(notFound)) { res.statusCode = 404 - createReadStream(notFound).pipe(res) + createReadStream(notFound) + .on('error', () => { + res.statusCode = 500 + res.end('Internal Server Error') + }) + .pipe(res) return }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/gea-ssg/src/vite-plugin.ts` around lines 197 - 200, The not-found response uses createReadStream(notFound) without handling stream errors; add an 'error' listener on the stream returned by createReadStream inside the block where existsSync(notFound) is checked (the code that sets res.statusCode = 404 and pipes the stream) so that if the stream emits 'error' you set an appropriate error status (e.g., 500 if headers not sent), write a short error message or call res.end(), and avoid crashing the server; ensure the error handler cleans up the stream and stops further processing after piping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/gea-ssg/src/vite-plugin.ts`:
- Around line 162-167: The invalidate function's path check can fail on Windows
due to differing separators/case, so normalize and compare canonical paths:
resolve/normalize contentDir and the incoming file path (use
path.resolve/path.normalize) and then use path.relative to determine if file is
inside contentDir (i.e., path.relative(resolvedContentDir, resolvedFile) does
not start with '..' and is not equal to '' for directories), and for Windows
also compare in lowercase or use a case-insensitive check; keep the existing
'.md' suffix check after normalization and still call server.ws.send({ type:
'full-reload' }) when the normalized file is in the directory. Ensure these
changes are made within the invalidate function and use the existing contentDir
and server variables.
- Around line 185-188: The extname check uses the raw req.url (including query)
so it can falsely detect file extensions; change the logic to strip the query
string first (compute const url = req.url.split('?')[0]) and then run
extname(url) and the root check, i.e. replace uses of extname(req.url) with
extname(url) and ensure testPath still uses the already-stripped url; update the
conditional that currently reads if (!req.url || extname(req.url) || req.url ===
'/') to use the cleaned url and preserve the call to next() when appropriate
(referencing req.url, url, extname, testPath, ts, and config.build.outDir).
---
Nitpick comments:
In `@packages/gea-ssg/src/vite-plugin.ts`:
- Around line 197-200: The not-found response uses createReadStream(notFound)
without handling stream errors; add an 'error' listener on the stream returned
by createReadStream inside the block where existsSync(notFound) is checked (the
code that sets res.statusCode = 404 and pipes the stream) so that if the stream
emits 'error' you set an appropriate error status (e.g., 500 if headers not
sent), write a short error message or call res.end(), and avoid crashing the
server; ensure the error handler cleans up the stream and stops further
processing after piping.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 956b76a6-798b-4951-ad5c-4a67e291eb13
📒 Files selected for processing (1)
packages/gea-ssg/src/vite-plugin.ts
Add @geajs/ssg package for build-time static HTML generation with: - Vite plugin integration (build, dev, preview modes) - Markdown content system via ssg.content() and ssg.file() - Dynamic routes from content files or explicit paths - Layout and outlet support for nested page structures - Active link detection for static HTML output - Sitemap generation - Path traversal protection and XSS-safe content injection Core changes: - Guard browser APIs in component-manager for Node compatibility - Add resetUidCounter for deterministic SSG rendering - Add SSG rendering hooks to RouterView, Outlet, and Link - Support SSG route configs in resolve.ts - Add Component._ssgMode to skip reactive hooks during SSG Includes ssg-basic example with blog, dynamic routes, and markdown content. 61 SSG tests, 377 core tests passing.
Client component renders an empty placeholder during SSG — child components only mount in the browser. This keeps route config clean and gives per-section control instead of per-route.
…ailing slash - Add <Head> component for per-page title, meta, og, twitter, canonical, JSON-LD - Generate 404.html from wildcard (*) routes - Generate robots.txt with configurable allow/disallow rules - Add sitemap lastmod from Head config dates - Add HTML minification (preserves pre/code/script/style) - Add trailing slash configuration for output paths - Add 29 head tests, 10 generate tests for new features - Update README and docs with new feature documentation
…meta/link/jsonld support, trailingSlash showcase
…tic sitemap, SSGRouteConfig type, build hang
…activity after load
…trip raw markdown from client payload
…e, fix onAfterRender empty string
… component hydration
Replace full SPA re-render with per-component hydration via data-gea
attributes and UID matching. Static pages get zero JavaScript. Interactive
pages load only the shared runtime + needed components.
- render.ts: inject data-gea attributes on hydrate-listed components during SSG
- client.ts: add hydrate() that attaches components to existing SSG DOM
- generate.ts: strip <script>/<link modulepreload> from static pages, remove content.js
- types.ts: add hydrate option to SSGOptions
- example: replace innerHTML='' + Router with hydrate({ Counter, LiveClock })
…ly imports App when no SSG content
…pp chunk from production build
…, nested div parsing, JSDoc, docs - render.ts: capture component props as data-gea-props, save/restore setComponent - client.ts: parse props, one-shot getUid for nested component safety, fix return value - shell.ts: depth-tracking for nested divs inside app element - index.ts: export server-side hydrate() no-op for type checking - generate.ts: document MPA content.js skip, add sitemap placeholder warning - vite-plugin.ts: expand process.exit workaround comment - content.ts, head.ts: add JSDoc on public API functions - shell.test.ts: add nested div test case - README.md: document content API behavior in MPA mode
…, and missing options
…rver compatibility
- crawl: skip routes with unresolved :param segments - render: return RenderResult with hasHydrationMarkers flag - render: warn on lossy prop serialization (Date, non-serializable) - generate: use hasHydrationMarkers instead of HTML text scan - head: insert <title> when shell has none - shell: escape regex metacharacters in appElementId - vite-plugin: add 500ms grace period before force-exit - vite-plugin: fix 404 preview fallback for trailingSlash mode - content: fix misleading JSDoc about recursive scanning
puskuruk
left a comment
There was a problem hiding this comment.
Thank you for your contribution! I'm going to review your PR tomorrow at night. Could you please make sure all the coderabbit comments are resolved and your pr is rebased with the main branch by tomorrow night? Thanks in advance!
- vite-plugin: use command 'build' for loadConfigFromFile, support array aliases - vite-plugin: merge routes/app independently so partial overrides work - vite-plugin: check generate() errors and fail build on rendering failures - vite-plugin: strip query string before extname check in preview server - vite-plugin: normalize paths for Windows compatibility in file watcher - head: track custom meta/link tags with data-gea-head, clean on route change - head: clear stale document.title when new page has no title - head: use CSS.escape() in meta selector queries for safety - render: use roundTripped (JSON-parsed) props instead of raw component props - generate: XML-escape sitemap <loc> URLs - core: remove duplicate resetUidCounter export that broke build
|
Thanks! All CodeRabbit comments are resolved and rebased on latest main. Looking forward to your review! I'm also planning to convert the gea project site to use @geajs/ssg as a follow-up PR. |
Routes are rendered to HTML at build time via a new
@geajs/ssgpackage. Pages without interactive components ship zero JS. Pages with interactive components get selective hydration throughdata-geamarkers.@geajs/ssg<Head>component — meta tags, title, JSON-LD, canonicaldata-geaattributes@geajs/corechangesHeadcomponent,resetUidCounter,Link._ssgCurrentPathRouterViewexamples/ssg-basicStatic pages, blog with markdown content, contact page with Counter and LiveClock hydration.
Tests
Summary by CodeRabbit
New Features
Documentation
Examples
Tests
Chores