⚡ Alias @tinacms/mdx to keep its 2 MB markdown parser out of the client bundle#4912
⚡ Alias @tinacms/mdx to keep its 2 MB markdown parser out of the client bundle#4912joshbermanssw wants to merge 2 commits into
Conversation
tinacms/dist/rich-text — the module behind <TinaMarkdown>, imported by 65
components here — opens with `import { sanitizeUrl } from "@tinacms/mdx"`.
That package publishes a single entry point built with bundleDeps, so the
import drags the whole remark / mdast / micromark / prettier toolchain in
to reach a 22-line URL sanitiser: 1,973,803 bytes on disk, ~1.14 MB minified
in the deployed bundle.
Alias the package to a shim exporting the real sanitizeUrl (copied verbatim
from @tinacms/mdx@2.1.9, byte-identical across its node and browser builds)
plus loudly-throwing stubs for parseMDX and serializeMDX, neither of which
runs in a browser on a public page.
Aliased for webpack and Turbopack both, so `next dev` and `next build` agree.
No application code changes — the 65 TinaMarkdown imports are untouched.
Upstream tinacms#7233 adds a `@tinacms/mdx/sanitize-url` subpath that makes
this unnecessary; delete the shim and both aliases once it ships.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Coverage report
Test suite run success13 tests passing in 1 suite. Report generated by 🧪jest coverage report action from 014674c |
Before/after measuredTwo clean production builds on the same machine, identical apart from the alias (baseline =
One thing this does not fixA second chunk still matches the markdown-parser fingerprint after the alias:
The alias barely touches Two consequences:
|
isaaclombardssw
left a comment
There was a problem hiding this comment.
This is really ugly but since the fix is just waiting on Tina to come through... ✅
|
Tested this branch locally — works great for Cause: So the webpack alias (prod, CI build, your bundle measurements) is fine — only Repro: git checkout perf/tina-mdx-sanitize-url
pnpm dev
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/events/ai-for-business-leaders
# -> 500 (dev log: Can't resolve './Users/.../lib/tinacms-mdx-shim.js')Fix — point just the Turbopack alias at a project-relative path; webpack keeps the absolute one: turbopack: {
resolveAlias: { "@tinacms/mdx": "./lib/tinacms-mdx-shim.js" },
}Confirmed: with that one change the same page returns 200 and rich text renders. Nothing else needed touching — the shim's |
Turbopack's resolveAlias resolves values against the project root and prepends "./" to whatever it receives, so the absolute path became "./Users/.../lib/tinacms-mdx-shim.js" and failed to resolve. Every page rendering <TinaMarkdown> 500d under `next dev`; `next build` was unaffected because webpack's resolve.alias does want an absolute path. Keep the absolute form for webpack and give Turbopack a relative one. The two are deliberately different — a comment says so, since unifying them re-breaks one bundler or the other. CI only runs `next build`, so it could not have caught this. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Reproduced and fixed in Confirmed the diagnosis before changing anything — Turbopack does prepend And after pointing Turbopack at a relative path, on the same page: Also verified rich text genuinely renders rather than the route merely returning 200 — the block-2 description paragraph is present in the dev-rendered HTML, and no markdown-parser strings appear, so the shim is being used rather than silently falling back. Kept the two forms deliberately different — absolute for webpack, relative for Turbopack — with a comment saying why, because unifying them re-breaks one bundler or the other. My original comment claimed absolute would "resolve identically" in both; that was the wrong assumption, and it introduced exactly the dev/prod divergence the change was supposed to prevent. Your point about CI is the more important one: One correction for the record: the installed version here is |
Affected routes: all routes rendering rich text (every page importing
TinaMarkdown— 65 components), verified on/events/ai-for-business-leadersFixed 💸 Perf - Keep the 1.1 MB TinaCMS MDX parser out of the client bundle #4896
TL;DR
tinacms/dist/rich-text— the module behind<TinaMarkdown>— opens withimport { sanitizeUrl } from "@tinacms/mdx", and that package ships a singlebundleDepsentry point of 1,973,803 bytes to supply a 22-line URL sanitiser. This aliases the package to a shim that keepssanitizeUrland drops the parser; no application code changes.Why
On the deployed site,
/events/ai-for-business-leadersreferences 5,966,858 bytes of JS across 60 chunks.dd3583a7-*.js— the@tinacms/mdxbundle — is 1,142,097 of them, making it the largest single attributable slice of the page's JavaScript and the bulk of Lighthouse's "Reduce unused JavaScript — Est savings of 1,064 KiB".Reproduce the page total:
Local before/after build figures to follow in a comment.
Reviewer notes
sanitizeUrlis copied, not reimplemented. Verbatim from@tinacms/mdx@2.1.9and byte-identical across that package's node and browser builds. If you bump the Tina packages, re-diff it.parseMDX/serializeMDXthrow. Neither runs in a browser on a public page —serializeMDXis reached only from the editor's save path,parseMDXonly from the Tina GraphQL layer, and both/adminand@tinacms/cliare built outside this Next config, so both still get the real package. They throw rather than returning a plausible value so a wrong assumption fails loudly instead of silently mangling content.next devuses Turbopack andnext builduses webpack; wiring only one would let dev and prod diverge silently.@tinacms/mdx/sanitize-urlsubpath that makes the shim unnecessary — delete the file and the two aliases once it ships in a release we consume.Tests
sanitizeUrlagainst the real export across 31 inputs — allowed and blocked schemes (javascript:,data:,vbscript:,file:,ftp:), relative paths, anchors, userinfo, explicit ports, and query/hash/trailing-slash combinations: 0 mismatches, and an identical export surface.Links