144 lines
5.3 KiB
Markdown
144 lines
5.3 KiB
Markdown
|
|
# Vendor Frontend Assets — Implementation Guide
|
||
|
|
|
||
|
|
**Purpose:** Establish the vendored-assets pattern in a project that serves static
|
||
|
|
frontend files through the ontoref daemon (or any Axum-based server with a static
|
||
|
|
file route). Covers the full setup: directory layout, just recipes, and template
|
||
|
|
references.
|
||
|
|
|
||
|
|
**Substitutions required before use:**
|
||
|
|
- `{project_dir}` — absolute path to project root
|
||
|
|
- `{daemon_crate}` — path to the Axum daemon crate (e.g. `crates/myproject-daemon`)
|
||
|
|
- `{assets_route}` — URL prefix the daemon serves assets from (e.g. `/assets`)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Why vendor instead of CDN
|
||
|
|
|
||
|
|
CDN references in server-rendered templates create three failure modes that vendoring
|
||
|
|
eliminates:
|
||
|
|
|
||
|
|
1. **Version drift** — a CDN package can be yanked or silently updated at the minor
|
||
|
|
level depending on the version specifier used.
|
||
|
|
2. **Offline development** — daemon templates break without network access.
|
||
|
|
3. **Registration race** — some Cytoscape extensions auto-register against
|
||
|
|
`window.cytoscape` at load time; if the CDN is slow or returns a 404 for a
|
||
|
|
sub-resource (e.g. a CSS that does not exist in that package version), the
|
||
|
|
extension silently fails to register and `cy.pluginName(...)` throws at runtime
|
||
|
|
with no network error visible in the console.
|
||
|
|
|
||
|
|
The canonical signal that a CDN asset has a sub-resource problem: `curl -sI
|
||
|
|
<url>` returns 200 but the body starts with "Couldn't find the requested file".
|
||
|
|
Always `curl -fsSL` (fail on HTTP errors) when downloading to catch this.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Directory layout
|
||
|
|
|
||
|
|
```text
|
||
|
|
assets/
|
||
|
|
vendor/ ← all vendored JS; served at {assets_route}/vendor/<file>
|
||
|
|
<lib>@<ver>.js ← optional: keep version in filename for cache-busting
|
||
|
|
justfiles/
|
||
|
|
assets.just ← recipes for downloading/updating vendor assets
|
||
|
|
justfile ← root just file; imports assets.just
|
||
|
|
```
|
||
|
|
|
||
|
|
Vendor files are committed to the repository. They are not gitignored — the goal
|
||
|
|
is reproducible builds without network access.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## assets.just structure
|
||
|
|
|
||
|
|
```just
|
||
|
|
# Frontend asset management
|
||
|
|
#
|
||
|
|
# Vendored JS libs live in assets/vendor/ and are served by the daemon
|
||
|
|
# at {assets_route}/vendor/<file>. Pin versions explicitly; bump manually.
|
||
|
|
|
||
|
|
LIB_VERSION := "x.y.z"
|
||
|
|
|
||
|
|
# Download/update all vendored frontend JS dependencies
|
||
|
|
[doc("Vendor all frontend JS dependencies")]
|
||
|
|
vendor-js: vendor-<lib>
|
||
|
|
|
||
|
|
[doc("Vendor <lib>")]
|
||
|
|
vendor-<lib>:
|
||
|
|
mkdir -p assets/vendor
|
||
|
|
curl -fsSL \
|
||
|
|
"https://cdn.jsdelivr.net/npm/<lib>@{{LIB_VERSION}}/<lib>.js" \
|
||
|
|
-o assets/vendor/<lib>.js
|
||
|
|
@echo "vendored <lib>@{{LIB_VERSION}}"
|
||
|
|
```
|
||
|
|
|
||
|
|
Rules:
|
||
|
|
- One version variable per library, at the top of the file.
|
||
|
|
- `vendor-js` is the aggregate recipe — depends on all individual `vendor-*` recipes.
|
||
|
|
- Use `curl -fsSL` (not `-sL`): `-f` makes curl exit non-zero on HTTP errors, catching
|
||
|
|
404s before they write a garbage file to disk.
|
||
|
|
- Adding a new library: add a version variable, add a `vendor-<lib>` recipe, add it
|
||
|
|
as a dependency of `vendor-js`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Importing in the root justfile
|
||
|
|
|
||
|
|
```just
|
||
|
|
import 'justfiles/ci.just'
|
||
|
|
import 'justfiles/assets.just'
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Referencing vendor assets in Tera templates
|
||
|
|
|
||
|
|
In `{% block head %}`, load vendored scripts **after** their host library:
|
||
|
|
|
||
|
|
```html
|
||
|
|
<!-- Host library (CDN is acceptable for well-established, stable libs) -->
|
||
|
|
<script src="https://cdn.jsdelivr.net/npm/cytoscape@3.30.2/dist/cytoscape.min.js"></script>
|
||
|
|
|
||
|
|
<!-- Extension: vendored locally — loads after host, auto-registers on window.cytoscape -->
|
||
|
|
<script src="{assets_route}/vendor/cytoscape-navigator.js"></script>
|
||
|
|
```
|
||
|
|
|
||
|
|
**Do not use** `<link rel="stylesheet">` for a vendor asset unless you have verified
|
||
|
|
the CSS file actually exists in the npm package (`curl -fsSI <url>` and check Content-Type).
|
||
|
|
Many Cytoscape extensions ship JS-only and use inline styles; fetching a non-existent
|
||
|
|
CSS produces a silent 404 that can confuse debugging.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Verifying a new CDN asset before vendoring
|
||
|
|
|
||
|
|
```sh
|
||
|
|
# 1. Check the package contents on npm
|
||
|
|
curl -s "https://registry.npmjs.org/<lib>/<version>" | jq '.dist.unpackedSize, .dist.fileCount'
|
||
|
|
|
||
|
|
# 2. Confirm the specific file exists (fail fast)
|
||
|
|
curl -fsSI "https://cdn.jsdelivr.net/npm/<lib>@<version>/<file>.js" | head -3
|
||
|
|
|
||
|
|
# 3. Check auto-registration pattern (for Cytoscape extensions)
|
||
|
|
curl -sL "https://cdn.jsdelivr.net/npm/<lib>@<version>/<file>.js" | tail -10
|
||
|
|
# Look for: if (typeof cytoscape !== 'undefined') { register(cytoscape); }
|
||
|
|
# If absent, the extension requires manual registration: cytoscape.use(require('<lib>'))
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Agent steps to apply this pattern
|
||
|
|
|
||
|
|
1. Read `justfiles/assets.just` if it exists; otherwise create it from the structure
|
||
|
|
above.
|
||
|
|
2. Add the library version variable and `vendor-<lib>` recipe.
|
||
|
|
3. Add the new recipe as a dependency of `vendor-js`.
|
||
|
|
4. Run `just vendor-<lib>` to download the file.
|
||
|
|
5. Verify the downloaded file is valid JS: `head -3 assets/vendor/<lib>.js` — if the
|
||
|
|
first line starts with "Couldn't" or "<!DOCTYPE", the download silently failed; re-run
|
||
|
|
with `curl -fsSL` (the `-f` flag was likely missing).
|
||
|
|
6. Update the Tera template: replace the CDN `<script src="...">` with
|
||
|
|
`<script src="{assets_route}/vendor/<lib>.js"></script>`.
|
||
|
|
7. Remove any `<link rel="stylesheet">` that referenced the same CDN package unless
|
||
|
|
you have verified the CSS exists (step 2 of the verification section above).
|
||
|
|
8. Commit `assets/vendor/<lib>.js` and `justfiles/assets.just` together.
|