FluxMarkdown is a macOS QuickLook extension that renders Markdown files with full support for math (KaTeX), diagrams (Mermaid), and syntax highlighting. This page explains why the project uses a hybrid Swift + TypeScript architecture, how the components fit together, and how data moves from a file on disk to pixels on screen.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/xykong/flux-markdown/llms.txt
Use this file to discover all available pages before exploring further.
Why a hybrid architecture?
A pure-native Swift implementation of GitHub-Flavored Markdown with Mermaid, KaTeX, and syntax highlighting would require reimplementing a significant portion of the JavaScript ecosystem from scratch. That path both increases maintenance burden and risks visual divergence from the reference rendering experience. FluxMarkdown instead uses a native Swift host that embeds aWKWebView running a bundled TypeScript renderer. This approach lets the project reuse battle-tested npm packages directly — the same libraries used by Markdown Preview Enhanced — while keeping the app sandboxed and distributed as a standard macOS extension.
Trade-offs of the hybrid approach
Trade-offs of the hybrid approach
Benefits:
- Reuses
markdown-it,mermaid,katex, andhighlight.jswithout porting them. - Rendering output stays visually consistent with the JavaScript ecosystem.
- The TypeScript layer is independently testable with Jest.
- Slightly higher memory use than a native text view (
WKWebViewruns in its own process). - A shared
WKProcessPoolis used to cap process count when previewing multiple files simultaneously. - Features that require Node.js APIs (
fs,child_process) cannot run inside the App Sandbox.
Component overview
Swift host
The Swift side is divided across two targets inproject.yml:
Sources/Markdown/— A minimal SwiftUI container (the Host App). It provides the app bundle that embeds the extension and handles auto-update via Sparkle.Sources/MarkdownPreview/— The QuickLook Extension (AppKit).PreviewViewControllerimplementsQLPreviewingController, manages theWKWebViewlifecycle, reads the file from disk, and calls into JavaScript to trigger rendering.
TypeScript renderer
Theweb-renderer/ directory is a standalone Vite project. It is compiled to a single self-contained dist/index.html with all JavaScript, CSS, and fonts inlined (via vite-plugin-singlefile). Xcode includes this file as a bundle resource; Swift loads it with webView.loadFileURL(_:allowingReadAccessTo:).
The renderer exposes two globals that Swift calls:
| Function | Purpose |
|---|---|
window.renderMarkdown(content, options) | Parse and render Markdown to the DOM |
window.renderSource(content, theme) | Show raw Markdown source with diff marks |
window.updateTheme(theme) | Switch light/dark/system theme without a full re-render |
Directory structure
Sources/Markdown/
Host App (SwiftUI). Minimal container that embeds the QuickLook extension and bundles Sparkle for auto-updates.
Sources/MarkdownPreview/
QuickLook Extension (AppKit). Contains
PreviewViewController.swift — the core of the extension.web-renderer/
TypeScript rendering engine (Vite + markdown-it). Compiled to a single inlined
dist/index.html.scripts/
Versioning, DMG creation, Homebrew cask updates, and GitHub release automation.
| Path | Purpose |
|---|---|
project.yml | XcodeGen configuration. Edit this to add files or targets; run make generate to apply. |
Makefile | Orchestrates the full build: renderer → XcodeGen → xcodebuild. |
.version | Single source of truth for the version string (e.g. 1.13.149). |
Sources/Shared/ | Code shared between the Host App and the extension targets. |
Data flow: file preview step by step
When a user presses Space on a.md file in Finder, the following sequence runs:
QuickLook invokes the extension
macOS selects the FluxMarkdown extension based on the UTI (
net.daringfireball.markdown). QuickLook instantiates PreviewViewController.Swift reads the file
preparePreviewOfFile(at:completionHandler:) is called with the file URL. Swift reads the file contents into a String. Files larger than 500 KB are truncated at a newline boundary to keep the preview responsive.WKWebView loads the HTML bundle
viewDidLoad() loads dist/index.html from the app bundle using webView.loadFileURL(_:allowingReadAccessTo:). The renderer initialises its libraries and signals readiness back to Swift via the logger message handler.Swift calls window.renderMarkdown
Once the handshake is complete, Swift calls
webView.evaluateJavaScript("return window.renderMarkdown(content, options)"). The Markdown string and options (theme, base URL, language) are JSON-serialised before being passed across the bridge.TypeScript renders to the DOM
markdown-it parses the Markdown. KaTeX processes math blocks, Mermaid renders diagrams asynchronously, and Highlight.js applies syntax colouring to fenced code blocks. The result is written to the DOM.Local images are served via a custom scheme
The
LocalSchemeHandler in Swift handles local-md:// requests, reading image files from the directory containing the previewed Markdown file and returning them to the WebView. This is how images in Markdown are resolved while remaining within the App Sandbox.JS-to-Swift bridge
The bridge is intentionally narrow. JavaScript logs back to Swift by posting messages to a named handler:logger (diagnostics), linkClicked (external link routing), and pinchZoom / gestureZoom (zoom events).
App Sandbox considerations
The extension runs under the macOS App Sandbox. The following constraints apply:File system access
File system access
The extension receives read-only, security-scoped access to the specific file QuickLook is previewing.
url.startAccessingSecurityScopedResource() is called before reading and balanced with stopAccessingSecurityScopedResource() when the preview is dismissed.Network access
Network access
Outbound network access is disabled. The renderer bundle is fully self-contained — all fonts, CSS, and JavaScript are inlined at build time. External images in Markdown are not fetched; only local images (served via the
local-md:// scheme handler) are displayed.JavaScript execution
JavaScript execution
The original
@shd101wyy/mume rendering engine depends on Node.js APIs (fs, path, child_process) that are unavailable in a sandboxed WebView. FluxMarkdown reconstructs the rendering stack using browser-compatible client-side libraries only.Dropped features
Dropped features
The following MPE features cannot be supported in this environment due to App Sandbox and Node.js constraints: code chunk execution and local file imports (
@import). Note that FluxMarkdown implements its own PDF export using NSPrintOperation — this is distinct from MPE’s server-side PDF generation.Versioning
The project uses a three-part semantic version stored in the.version file:
make generate reads .version, exports MARKETING_VERSION and CURRENT_PROJECT_VERSION as environment variables, and passes them to xcodegen generate. This populates CFBundleShortVersionString and CFBundleVersion in both Info.plist files without manual editing.
Use make release [major|minor|patch] to increment the version, update CHANGELOG.md, build a DMG, and create a GitHub release.
Related pages
Development setup
Prerequisites, build steps, and daily development workflow.
Web renderer overview
Deep dive into the TypeScript rendering pipeline and plugin system.
Renderer plugins
How KaTeX, Mermaid, and Highlight.js are integrated.
Release process
How to cut a new release and update Homebrew casks.