web: MDX without React - #21949
Conversation
✅ Deploy Preview for authentik-storybook ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for authentik-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #21949 +/- ##
==========================================
+ Coverage 93.25% 93.27% +0.02%
==========================================
Files 1032 1032
Lines 60164 60164
Branches 400 400
==========================================
+ Hits 56104 56119 +15
+ Misses 4060 4045 -15
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
|
authentik PR Installation instructions Instructions for docker-composeAdd the following block to your AUTHENTIK_IMAGE=ghcr.io/goauthentik/dev-server
AUTHENTIK_TAG=gh-9a9c87208a5e6a488dbcc5cbaf99eafb46e75b60
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)sAfterwards, run the upgrade commands from the latest release notes. Instructions for KubernetesAdd the following block to your authentik:
outposts:
container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
global:
image:
repository: ghcr.io/goauthentik/dev-server
tag: gh-9a9c87208a5e6a488dbcc5cbaf99eafb46e75b60Afterwards, run the upgrade commands from the latest release notes. |
f24e54a to
ea1130e
Compare
4ce183f to
7f7802a
Compare
7f7802a to
e40c1bf
Compare
05fa351 to
dc1a879
Compare
|
Suggestion 1: Make the first change (Extract Mermaid) into its own PR. |
|
|
||
| protected refresh = (): void => { | ||
| new FlowsApi(DEFAULT_CONFIG) | ||
| .flowsInstancesDiagramRetrieve({ |
There was a problem hiding this comment.
aki(FlowsApi) ? Now that it's merged?
|
Split out into #22980, with both of the FlowDiagram suggestions applied over there. |
kensternberg-authentik
left a comment
There was a problem hiding this comment.
What I see here is:
-
the
<ak-mdx>and<ak-md-a>elements are consumers of markdown to produce HTML. -
The bundler is also a consumer of markdown to produce HTML.
-
The transform presets by remark are duplicated in two places, and could be unified and published as a separate package in the same way you've got packages for eslint or prettier.
-
The bundler plugin is probably also publishable as a separate package. It takes the content as arguments and produces the corresponding output.
| // plain text. | ||
| const normalized = normalizeAdmonitionLabels(source); | ||
|
|
||
| const file = await unified() |
There was a problem hiding this comment.
Why is this different from the process done in the Bundler, and is there a risk that we'll make changes to one without also changing the other?
The left hoof wants to say "Centralize this process into a common function," and another that says," and on the other hoof I wonder if there's enough difference between the two to just put a comment here reminding people that if you change this, change the Bundler as well.
66c83df to
9a9c872
Compare
Replace `@mdx-js/mdx`'s `evaluate`/`run` (which depends on `'unsafe-eval'` in the page CSP) with a pure `unified`/remark/rehype pipeline. URL-mode content now arrives from the build-time `mdx-plugin` as pre-rendered HTML and is stamped through a Trusted Types passthrough policy (`CompiledMarkdownTrustPolicy`) so admin-side custom elements like `<ak-alert>` and `<ak-md-a>` survive. Content-mode (admin-supplied markdown) is compiled in-browser via `compileRuntimeMarkdown` and routed through the existing `BrandedHTMLPolicy` (DOMPurify). `<ak-md-a>` replaces the React `MDXAnchor`/`MDXWrapper` pair: a tiny Lit custom element with `display: contents` that intercepts in-doc fragment clicks for shadow-root scrolling. The remark plugins gain `caution` and `tip` admonition types and promote `:::name[Title]` directive labels to `<strong>`. `one-dark.css` learns `:host` so syntax-highlighted code blocks inherit the palette inside `<ak-mdx>`'s shadow tree. Removes `@mdx-js/mdx`, `react`, `react-dom`, `rehype-mermaid`, and `remark-mdx-frontmatter`. Adds `unified`, `remark-parse`, `remark-rehype`, and `mdast-util-to-string`. Co-Authored-By: Agent <279763771+playpen-agent@users.noreply.github.com>
Compile each `.md` / `.mdx` file through a build-time `unified` pipeline
(remark-parse, GFM, frontmatter, directives, custom admonition/headings/
lists transforms; rehype-rehype, anchor wrapping, syntax highlighting,
mermaid extraction) and emit a JSON envelope of
`{ content: HTML, frontmatter, publicPath, publicDirectory }`. The
on-load result still ships via the `file` loader so the runtime side
keeps its existing fetch path. The compiled HTML uses `<ak-md-a>` and
`<ak-alert>` custom elements so `<ak-mdx>` can stamp it directly into
shadow DOM with no client-side JavaScript evaluation.
Plugin is split across four sibling files (`node.js`, `compile.js`,
`remark.js`, `rehype.js`) so each concern lives in its own module. The
title/wrapper HTML is built as a hast tree and serialized through
`hast-util-to-html` rather than string-concatenated, dropping the
hand-rolled `escapeHTML` helper and unifying escape semantics with the
rest of the document.
Co-Authored-By: Agent <279763771+playpen-agent@users.noreply.github.com>
The OAuth2 provider docs page exercises the full pipeline in one place: frontmatter title, multiple H2 slugs, `:::caution`/`:::info` admonitions (with and without titles), relative-doc links, external links, and an embedded mermaid sequence diagram. Tests boot the admin UI, provision a fresh OAuth2 provider, navigate to its view page, and assert against the compiled DOM inside `<ak-mdx>`'s shadow root. Co-Authored-By: Agent <279763771+playpen-agent@users.noreply.github.com>
9a9c872 to
ab7626e
Compare
Address PR review feedback on the URL-mode trust boundary. `<ak-mdx>`'s `replacers` hook runs over pre-rendered build-time HTML before it is stamped into the DOM, and consumers (e.g. `ProxyProviderViewPage`) splice admin-controlled values such as `provider.externalHost` into it. The old React pipeline ran replacers on raw markdown that was then compiled, so those values were HTML-escaped on serialization; the new URL mode passed the post-replacer HTML straight through, dropping that guarantee. Replace the passthrough `CompiledMarkdownTrustPolicy` with `CompiledMarkdownSanitizePolicy`: a DOMPurify policy that whitelists the custom elements (`<ak-alert>`, `<ak-md-a>`, `<ak-diagram>`) and the `part`/`level` attributes our pipeline emits, and strips anything else a replacer could inject. Also add a reciprocal drift note to the runtime `markdown.ts` pointing at `bundler/mdx-plugin/`, mirroring the existing note on the bundler side. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BeryJu
left a comment
There was a problem hiding this comment.
From a visual standpoint in the UI everything looks great, I'll leave the code review to @kensternberg-authentik
Replaces the React-based
<ak-mdx>(and the@mdx-js/mdxruntime evaluator behind it) with a build-time Markdown→HTML pipeline plus a pair of light-DOM custom-element wrappers. The rewrite removes the last runtime use ofnew AsyncFunction(...)from the bundle, so the page CSP no longer needs'unsafe-eval'for<ak-mdx>to function.Build pipeline (
web/bundler/mdx-plugin/node.js):.md/.mdxto HTML viaunified+ remark + rehype../foo.mdhrefs against the docs site at build time and addstarget="_blank"+rel="noopener noreferrer".<a>in<ak-md-a>and every<pre><code class="language-mermaid">in<ak-md-mermaid>(wrap-don't-replace, so the original semantic HTML survives for graceful degradation).:::name Titleadmonitions into the spec form:::name[Title]before parsing, then promotes the title paragraph to<strong>inside<ak-alert>.cautionandtipto the admonition vocabulary, mapping them to the closest PFAlert level.title:into a<h1 part="title">and wraps the whole document in<div class="pf-c-content" part="content">.Runtime (
web/src/elements/ak-mdx/):<ak-mdx>is a thin Lit element. URL mode fetches the JSON envelope and stamps the pre-rendered HTML throughCompiledMarkdownTrustPolicy(a passthrough Trusted Types policy for our own build output). Content mode runs the sameunifiedpipeline in-browser for admin-supplied prose, sanitised throughBrandedHTMLPolicy.<ak-md-a>(light-DOM Lit element via:host { display: contents }) attaches a click interceptor on the slotted<a>so fragment links scroll within the host shadow tree instead of overwritinglocation.hashand yanking the hash-routed SPA off its current page.<ak-md-mermaid>delegates to the existing<ak-diagram>element, using the slotted<pre><code>as the source of mermaid markup.Mermaid theming (
web/src/elements/Interface.ts,web/src/common/mermaid.ts):applyMermaidThemeis now invoked once at startup with the resolved current theme so the first SVG render of a page loaded in dark mode comes out in the right colours, instead of waiting for the user's first explicit theme toggle to fix it.<ak-diagram>listens forAKMermaidRefreshEventonwindowand callsrequestUpdate()on each fire, picking up the latest global mermaid config without per-instance theme tracking.Dependencies:
@mdx-js/mdx,react,react-dom,rehype-mermaid,remark-mdx-frontmatter.unified,remark-parse,remark-rehype,mdast-util-to-stringfrom transitive to direct.reactimports (HTMLAttributes,ImgHTMLAttributes,LabelHTMLAttributes) toimport typeso the bundler erases them entirely.Tests:
web/test/browser/ak-mdx.test.ts— Playwright e2e, exercises frontmatter, heading slugs, admonitions, link wrapping (external, relative, fragment), mermaid render, and theme-toggle re-rendering against the OAuth2 provider docs.