Skip to content

Make compiled stylesheets reference their assets relatively - #23

Merged
EiffL merged 3 commits into
mainfrom
fix/relative-css-asset-urls
Sep 17, 2026
Merged

EiffL merged 3 commits into
mainfrom
fix/relative-css-asset-urls

Conversation

@EiffL

@EiffL EiffL commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

Problem

On https://www.mystviewer.org/eiffl/myst_proto the paper renders in Georgia: all five bundled brand font files 404. Remix writes every url() in the compiled CSS against the /myst_assets_folder/ public path, and myst build --html rewrites that placeholder in html, js and json but not in CSS (packages/myst-cli/src/build/html/index.ts, still the case on mystmd main). Under a base URL the font urls therefore point at the host root.

This has been latent since the brand fonts were first bundled. It went unnoticed until #17 dropped the Google Fonts link that used to supply Newsreader, Quattrocento and Alegreya from absolute URLs. The KaTeX font faces in the same stylesheet have always 404ed on static hosts, masked by the CDN KaTeX stylesheet.

The same rewrite already exists twice downstream: the theme's express server patches CSS when serving under a prefix, and astra-ui's preview builder patches the static export. Neither helps a static host such as mystviewer.

Fix

A post-build step, relativize-css-assets ./public, runs after remix build in both themes and rewrites every url(/myst_assets_folder/...) in the stylesheets under public/build/ to a path relative to that stylesheet. Fonts sit beside the stylesheets, so the result is the form Vite and webpack emit natively and resolves in every serving mode without anyone substituting the placeholder:

  • static export under a base URL (mystviewer)
  • the express server, with or without MYSTRA_BASE_URL (JupyterLab)
  • remix dev

The compiled stylesheet keeps its hashed name, so manifests are unaffected. The flip side: the same fingerprint now names different bytes than the previous release, so a static host that cached the old copy under the immutable policy needs a purge or a fresh deployment path once. mystviewer deploys under a per-build directory, so it is not affected.

The script fails the build if any /myst_assets_folder/ reference survives the rewrite, so a bundler change that emits a form it does not recognise cannot ship silently.

Verification

  • npm test: 16 files, 142 tests pass, including a new test for the script (quoted/unquoted urls, nested stylesheets, data and remote urls, scripts and files outside build/ untouched).
  • npm run build:article and build:book: 5 stylesheets rewritten each, no placeholder left in any compiled CSS.
  • BASE_URL=/eiffl/myst_proto myst build --html on the DESI prototype, served from a plain file server under that path: all five brand fonts load, document.fonts reports no failures, and the inline reference span measures identically to the local render.
  • The express server still serves the fonts at /myst_assets_folder/_assets/….

Also in this PR

The express asset handler no longer rewrites stylesheets, only JavaScript, since the compiled CSS carries relative urls. Its path safety check now runs before the extension gate so a null-byte probe gets a 404 whatever its extension. The server test's CSS fixture uses the relative form the build produces and expects it served verbatim.

Follow-ups (not in this PR)

  • Upstream one-line fix to mystmd's exporter so .css is rewritten like the other extensions.
  • Once a theme with this change is released, astra-ui's preview rewrite becomes redundant and can be removed.

🤖 Generated with Claude Code

EiffL and others added 3 commits September 17, 2026 14:24
Remix writes every url() in the compiled CSS against the /myst_assets_folder/
public path. `myst build --html` rewrites that placeholder in html, js and
json but not in CSS, so on a static host under a base URL the bundled brand
fonts 404 and the paper falls back to Georgia. This went unnoticed until the
Google Fonts link was dropped in favour of the bundled brand fonts.

Add a post-build step, `relativize-css-assets`, that rewrites those urls
relative to each stylesheet under public/build. Fonts sit beside the
stylesheets, so the result resolves in every serving mode: static export,
the express server with or without a prefix, and remix dev.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The build now makes every url() in the compiled CSS relative, so the
prefixed server only needs to rewrite module imports and asset URLs in
JavaScript. The path safety check moves ahead of the extension gate so a
null-byte probe still gets a 404 whatever its extension.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Derive the change from the rewrite itself instead of a duplicated
public-path literal and pre-check, pass the stylesheet directory once,
match only the minified url() form Remix emits, and stop describing remix
dev, which never runs the script. The test follows the existing bin-script
idiom, cleans up per test, and keeps one negative check per gate. The
README note moves beside the static-export instructions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Lightcone previews: Paper · Playground — inventory, viewers, dialogs and glyphs

d591643 · @astra-spec/ui 0.0.6 from 6248ae0 · Lightcone brand 0.0.3 · astra-theme d591643 · content 3735fcc · MySTRA https://github.com/LightconeResearch/MySTRA/releases/download/v0.0.8/mystra.mjs

@EiffL EiffL left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

LGTM

@EiffL
EiffL merged commit a6b6ca2 into main Sep 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant