Skip to content

feat(platform): add shiki skipLangs option for analog v2 - #2282

Merged
brandonroberts merged 11 commits into
analogjs:betafrom
benpsnyder:feat/2029-mermaid-shiki-oom-beta
Apr 13, 2026
Merged

feat(platform): add shiki skipLangs option for analog v2#2282
brandonroberts merged 11 commits into
analogjs:betafrom
benpsnyder:feat/2029-mermaid-shiki-oom-beta

Conversation

@benpsnyder

Copy link
Copy Markdown
Contributor

No description provided.

@netlify

netlify Bot commented Apr 13, 2026

Copy link
Copy Markdown

Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit f68a87b
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/69dc7d8ae271b900081d239e
😎 Deploy Preview https://deploy-preview-2282--analog-blog.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Apr 13, 2026

Copy link
Copy Markdown

Deploy Preview for analog-docs ready!

Name Link
🔨 Latest commit f68a87b
🔍 Latest deploy log https://app.netlify.com/projects/analog-docs/deploys/69dc7d8a6892740008d97ebc
😎 Deploy Preview https://deploy-preview-2282--analog-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Apr 13, 2026

Copy link
Copy Markdown

Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit f68a87b
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/69dc7d8a7e7a430008a9565b
😎 Deploy Preview https://deploy-preview-2282--analog-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This multi-package pull request introduces a skipLangs feature for Shiki highlighting, refactors locale injection patterns across content and router packages, and migrates Shiki from getHighlighter to createHighlighter. The changes add HTML escaping for skipped languages, update configuration types to support language exclusion, modify locale retrieval from optional injection tokens to unconditional helper functions, and add test coverage for the new Shiki behavior including mermaid diagram handling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes


Review focus areas

Breaking changes & API compatibility:

  • Locale injection refactoring replaces inject(CONTENT_LOCALE, { optional: true }) and inject(LOCALE) with injectContentLocale() and injectLocale(). Verify these new helpers maintain semantic equivalence (especially the optional vs. unconditional behavior). The shift from optional injection to unconditional calls warrants confirmation that callers handle missing locale gracefully.

  • ShikiHighlighterOptions type signature changed from Parameters<typeof getHighlighter>[0] to Parameters<typeof createHighlighter>[0]. Confirm createHighlighter and getHighlighter accept compatible option shapes to prevent runtime configuration errors.

High-complexity changes:

  • packages/platform/src/lib/content/shiki/index.ts introduces mutable language list manipulation (additionalLangs appended, skipLangs filtered via Set). Review the order of operations and ensure hasMermaidSupport calculation correctly reflects the final filtered state. Verify that language removal via skipLangs doesn't inadvertently skip critical defaults.

  • packages/content/resources/src/content-files-resource.ts removes 37 lines of custom filterContentByLocale logic in favor of external filterByLocale(items, locale) calls. Ensure the external utility maintains the same locale-prefix and filename parsing semantics, particularly for edge cases around hyphenated locales.

Test coverage:

  • New shiki/index.spec.ts thoroughly exercises skipLangs and additionalLangs behavior, including HTML escaping for skipped languages and mermaid bypass. Confirm tests exercise mermaid detection with and without skipLangs.

Monorepo consistency:

  • Router project.json test target now depends on vite-plugin-angular:build. Validate this dependency is necessary and consistent with other test configurations in the monorepo.

  • New @analogjs/router/tokens path mapping in tsconfig.lib.json. Confirm the path and index file exist and are properly exported.

🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess relevance to the changeset. Add a description explaining the purpose, benefits, and context of the skipLangs feature for Mermaid diagram handling.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed Title follows Conventional Commit style with supported scope 'platform' and accurately summarizes the main feature addition of skipLangs option for Shiki.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added scope:content Changes in @analogjs/content scope:docs Documentation changes scope:platform Changes in @analogjs/platform scope:router Changes in @analogjs/router labels Apr 13, 2026
@github-actions

Copy link
Copy Markdown

This PR touches multiple package scopes: content, platform, router.

Please confirm the changes are closely related. Squash merge is highly preferred. If you recommend a non-squash merge, add a brief note explaining why the commit boundaries matter and why this PR should bypass focused changes per package.

@benpsnyder benpsnyder changed the title Feat/2029 mermaid shiki oom beta feat(platform): add shiki skipLangs option for analog v2 Apr 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
apps/docs-app/docs/features/routing/content.md (1)

208-231: Mention that loadMermaid is still required.

This snippet only changes build-time highlighting. If readers copy it without the withMarkdownRenderer({ loadMermaid: ... }) setup from the Mermaid section below, they can end up with raw <pre class="mermaid"> output at runtime. A short note or cross-reference here would make the setup self-contained. As per coding guidelines, apps/docs-app/**/*.{md,mdx}: Add concise documentation with descriptive sections to the appropriate guides in the docs-app.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs-app/docs/features/routing/content.md` around lines 208 - 231, Add a
brief note after the config snippet clarifying that this only affects build-time
highlighting and that the runtime still requires the loadMermaid setup (e.g.,
the withMarkdownRenderer({ loadMermaid: ... }) call) to render <pre
class="mermaid"> blocks; reference the Mermaid section and mention the need to
keep loadMermaid so readers don't end up with raw Mermaid blocks, and refer to
the shikiOptions keys (skipLangs/additionalLangs) and
loadMermaid/withMarkdownRenderer symbols to guide where to wire it up.
packages/platform/src/lib/content/shiki/index.spec.ts (1)

53-95: Add one skipLangs case for a default language.

Current coverage only exercises skipped languages introduced through additionalLangs. A small skipLangs: ['ts'] or ['js'] case would protect the default-lang initialization/filter path in packages/platform/src/lib/content/shiki/index.ts too. As per coding guidelines, **/*.spec.{ts,tsx}: Keep tests lightweight and targeted to critical functionality testing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/platform/src/lib/content/shiki/index.spec.ts` around lines 53 - 95,
Add a new unit test using getShikiHighlighter that sets highlighter.skipLangs to
include a default language like 'ts' (or 'js') and assert that the returned
extension.highlight for a TS/JS code string returns a plain escaped fenced block
(same shape as the existing YAML tests); target the same
highlighter.getHighlightExtension() and its highlight(code, lang, props) promise
to ensure the default-lang initialization/filter path in
packages/platform/src/lib/content/shiki/index.ts is exercised without adding
heavy fixtures.
packages/platform/src/lib/content/shiki/index.ts (1)

21-53: Normalize into a local copy instead of mutating highlighter.

delete highlighter.additionalLangs and delete highlighter.skipLangs strip fields from the caller's config object after the first call. Cloning before normalization keeps this helper side-effect free and avoids surprising reuse bugs.

♻️ Suggested normalization pattern
-  const additionalLangs = highlighter.additionalLangs ?? [];
-  const skipLangs = highlighter.skipLangs ?? [];
+  const {
+    additionalLangs = [],
+    skipLangs = [],
+    ...normalizedHighlighter
+  } = highlighter;

   const hasMermaidSupport =
-    highlighter.langs?.includes('mermaid') ||
+    normalizedHighlighter.langs?.includes('mermaid') ||
     additionalLangs.includes('mermaid');

-  if (!highlighter.themes) {
+  if (!normalizedHighlighter.themes) {
     if (highlight.theme) {
-      highlighter.themes = [highlight.theme];
+      normalizedHighlighter.themes = [highlight.theme];
     } else if (highlight.themes && typeof highlight.themes === 'object') {
-      highlighter.themes = Object.values(highlight.themes) as string[];
+      normalizedHighlighter.themes = Object.values(highlight.themes) as string[];
     } else {
-      highlighter.themes = defaultHighlighterOptions.themes;
+      normalizedHighlighter.themes = defaultHighlighterOptions.themes;
     }
   }

-  if (!highlighter.langs) {
-    highlighter.langs = [...defaultHighlighterOptions.langs];
+  if (!normalizedHighlighter.langs) {
+    normalizedHighlighter.langs = [...defaultHighlighterOptions.langs];
   }

   if (additionalLangs.length > 0) {
-    highlighter.langs.push(...additionalLangs);
+    normalizedHighlighter.langs.push(...additionalLangs);
   }

   if (skipLangs.length > 0) {
     const skipSet = new Set<string>(skipLangs);
-    highlighter.langs = highlighter.langs.filter(
+    normalizedHighlighter.langs = normalizedHighlighter.langs.filter(
       (lang: unknown) => typeof lang !== 'string' || !skipSet.has(lang),
     );
   }

   highlighterInstance = new ShikiHighlighter(
-    highlighter as ShikiHighlighterOptions,
+    normalizedHighlighter as ShikiHighlighterOptions,
     highlight,
     container,
     hasMermaidSupport,
     skipLangs,
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/platform/src/lib/content/shiki/index.ts` around lines 21 - 53, Make
a shallow clone of the incoming highlighter config at the top (e.g., const
normalizedHighlighter = { ...highlighter }) and perform all normalization steps
against normalizedHighlighter (compute additionalLangs/skipLangs from
normalizedHighlighter, set normalizedHighlighter.themes/langs, push
additionalLangs, filter skipLangs, compute hasMermaidSupport using
normalizedHighlighter) instead of mutating the original highlighter; remove the
final delete highlighter.additionalLangs / delete highlighter.skipLangs lines so
the caller object isn’t modified, and ensure downstream code uses
normalizedHighlighter (or return it) where the normalized config is expected.
🤖 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/router/src/lib/i18n/provide-i18n.ts`:
- Around line 117-118: Write a test that verifies ENVIRONMENT_INITIALIZER's
re-read of LOCALE respects a server-provided token: set up a test application
that calls provideServerContext({ locale: '<server-locale>' }) before
bootstrapping, ensure the ENVIRONMENT_INITIALIZER runs, and assert that
injectLocale() within the initializer returns the server locale and that
initI18n(resolved, locale) is invoked with that value; focus the test on the
path exercised by provide-i18n's initializer (the function that returns () =>
initI18n(resolved, locale ?? undefined)) so it proves server-context LOCALE
precedence over client-detected locale.

---

Nitpick comments:
In `@apps/docs-app/docs/features/routing/content.md`:
- Around line 208-231: Add a brief note after the config snippet clarifying that
this only affects build-time highlighting and that the runtime still requires
the loadMermaid setup (e.g., the withMarkdownRenderer({ loadMermaid: ... })
call) to render <pre class="mermaid"> blocks; reference the Mermaid section and
mention the need to keep loadMermaid so readers don't end up with raw Mermaid
blocks, and refer to the shikiOptions keys (skipLangs/additionalLangs) and
loadMermaid/withMarkdownRenderer symbols to guide where to wire it up.

In `@packages/platform/src/lib/content/shiki/index.spec.ts`:
- Around line 53-95: Add a new unit test using getShikiHighlighter that sets
highlighter.skipLangs to include a default language like 'ts' (or 'js') and
assert that the returned extension.highlight for a TS/JS code string returns a
plain escaped fenced block (same shape as the existing YAML tests); target the
same highlighter.getHighlightExtension() and its highlight(code, lang, props)
promise to ensure the default-lang initialization/filter path in
packages/platform/src/lib/content/shiki/index.ts is exercised without adding
heavy fixtures.

In `@packages/platform/src/lib/content/shiki/index.ts`:
- Around line 21-53: Make a shallow clone of the incoming highlighter config at
the top (e.g., const normalizedHighlighter = { ...highlighter }) and perform all
normalization steps against normalizedHighlighter (compute
additionalLangs/skipLangs from normalizedHighlighter, set
normalizedHighlighter.themes/langs, push additionalLangs, filter skipLangs,
compute hasMermaidSupport using normalizedHighlighter) instead of mutating the
original highlighter; remove the final delete highlighter.additionalLangs /
delete highlighter.skipLangs lines so the caller object isn’t modified, and
ensure downstream code uses normalizedHighlighter (or return it) where the
normalized config is expected.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a7e0ef1e-5234-44d9-bd84-7da486424b62

📥 Commits

Reviewing files that changed from the base of the PR and between 860f65e and f68a87b.

📒 Files selected for processing (11)
  • apps/docs-app/docs/features/routing/content.md
  • packages/content/prism-highlighter/src/lib/prism-highlighter.ts
  • packages/content/resources/src/content-file-resource.ts
  • packages/content/resources/src/content-files-resource.ts
  • packages/platform/src/lib/content/shiki/index.spec.ts
  • packages/platform/src/lib/content/shiki/index.ts
  • packages/platform/src/lib/content/shiki/options.ts
  • packages/platform/src/lib/content/shiki/shiki-highlighter.ts
  • packages/router/project.json
  • packages/router/src/lib/i18n/provide-i18n.ts
  • packages/router/tsconfig.lib.json

Comment on lines +117 to +118
const locale = injectLocale();
return () => initI18n(resolved, locale ?? undefined);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether router i18n tests cover server-overridden locale precedence.
# Expected: at least one spec asserting init path uses server-provided LOCALE/injectLocale outcome.
rg -nP --type=ts -C3 '\b(provideI18n|initI18n|injectLocale|LOCALE|provideServerContext)\b'
fd -i 'i18n'

Repository: analogjs/analog

Length of output: 19909


🏁 Script executed:

cat packages/router/src/lib/i18n/provide-i18n.spec.ts

Repository: analogjs/analog

Length of output: 8147


🏁 Script executed:

fd -i 'provide-server-context\|\.spec\|\.test' packages/router/server/src
cat packages/router/server/src/provide-server-context.ts
rg -A 10 'provideI18n.*provideServerContext|provideServerContext.*provideI18n' --type ts

Repository: analogjs/analog

Length of output: 3181


🏁 Script executed:

rg -l 'provideServerContext.*provideI18n|ENVIRONMENT_INITIALIZER' --type ts
find apps -name '*.spec.ts' -o -name '*.e2e.ts' | xargs grep -l 'provideServerContext\|server.*i18n' 2>/dev/null | head -5

Repository: analogjs/analog

Length of output: 192


🏁 Script executed:

git log --oneline -n 20 packages/router/src/lib/i18n/provide-i18n.ts
git show HEAD:packages/router/src/lib/i18n/provide-i18n.ts 2>/dev/null | sed -n '110,125p' || echo "HEAD revision unavailable, checking current state only"

Repository: analogjs/analog

Length of output: 588


🏁 Script executed:

# Check previous commits to see if this ENVIRONMENT_INITIALIZER pattern is new
git log --all --oneline packages/router/src/lib/i18n/provide-i18n.ts | head -10
git show f68a87bb:packages/router/src/lib/i18n/provide-i18n.ts 2>/dev/null | sed -n '110,125p' || echo "Previous version not available"
git diff f68a87bb~1 f68a87bb packages/router/src/lib/i18n/provide-i18n.ts 2>/dev/null | head -60 || echo "No diff available"

Repository: analogjs/analog

Length of output: 647


🏁 Script executed:

# Check the actual diff of the feature PR to see what tests were added
git show 7dbc7dfa packages/router/src/lib/i18n/provide-i18n.spec.ts 2>/dev/null | head -100 || echo "Cannot retrieve feature PR diff"
# Also check if there are any integration tests in the analog-app-e2e or similar
find apps -name '*e2e*' -type d | head -5

Repository: analogjs/analog

Length of output: 2915


Add test for server-locale precedence in ENVIRONMENT_INITIALIZER.

Lines 117–118 introduce logic that re-reads the LOCALE token during app initialization to allow server context to override client-detected locale. The code comment explicitly documents this behavior ("Re-read LOCALE in case the server context overrode it"), but the test suite does not verify that injectLocale() correctly picks up a server-provided LOCALE token during the initializer phase. Add a unit or integration test confirming this path works when provideServerContext({ locale }) sets a LOCALE token before the ENVIRONMENT_INITIALIZER runs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router/src/lib/i18n/provide-i18n.ts` around lines 117 - 118, Write a
test that verifies ENVIRONMENT_INITIALIZER's re-read of LOCALE respects a
server-provided token: set up a test application that calls
provideServerContext({ locale: '<server-locale>' }) before bootstrapping, ensure
the ENVIRONMENT_INITIALIZER runs, and assert that injectLocale() within the
initializer returns the server locale and that initI18n(resolved, locale) is
invoked with that value; focus the test on the path exercised by provide-i18n's
initializer (the function that returns () => initI18n(resolved, locale ??
undefined)) so it proves server-context LOCALE precedence over client-detected
locale.

@brandonroberts brandonroberts left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@brandonroberts
brandonroberts merged commit d6e932c into analogjs:beta Apr 13, 2026
43 of 44 checks passed
@brandonroberts
brandonroberts deleted the feat/2029-mermaid-shiki-oom-beta branch April 13, 2026 13:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope:content Changes in @analogjs/content scope:docs Documentation changes scope:platform Changes in @analogjs/platform scope:router Changes in @analogjs/router

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants