Watch Linked Style Files In Dev Server
Watch Linked Style Files in Dev Server
Written for AI agents. See Log Methodology Note below for details.
Background
When jay-html files reference external CSS files via <link rel="stylesheet" href="...">, the CSS content is extracted and inlined during compilation (see Design Log #44). However, the dev server currently only watches the jay-html file itself, not the referenced CSS files.
Problem
If a developer modifies a linked CSS file (e.g., styles/main.css), the dev server doesn't detect the change and won't recompile the jay-html file. The developer must manually touch/save the jay-html file to trigger a rebuild.
Example scenario:
<!-- page.jay-html -->
<link rel="stylesheet" href="styles/main.css" />
Editing styles/main.css → No rebuild triggered → Stale CSS served
Questions and Answers
Q1: Where does CSS extraction happen?
A1: In compiler-jay-html/lib/jay-target/jay-html-parser.ts, the extractCss function reads linked CSS files from disk at lines 722-752.
Q2: Where is the current watch registration?
A2: In rollup-plugin/lib/runtime/resolve-id.ts line 78, only the jay-html file is watched via watchChangesFor(context, resolvedBasePath).
Q3: Do we have access to the plugin context during CSS extraction?
A3: No. The extractCss function in the parser doesn't have access to Rollup/Vite's PluginContext. The context is available in the rollup-plugin layer but not passed down to the compiler.
Q4: Should we watch CSS files that fail to load? A4: Yes - if the file doesn't exist now, it might be created later. Watching it would trigger a rebuild when created.
Q5: Should external URLs be watched? A5: No - external URLs (http://, https://, //) cannot be watched and are already skipped during extraction.
Design
Option A: Return CSS file paths from parser, watch in plugin layer
Modify extractCss to return the list of resolved CSS file paths alongside the CSS content. The rollup plugin can then add these to the watch list.
// In jay-html-parser.ts
interface ExtractCssResult {
css: string | undefined;
linkedCssFiles: string[]; // Resolved absolute paths
}
async function extractCss(
root: HTMLElement,
filePath: string,
): Promise<WithValidations<ExtractCssResult>>;
// In rollup-plugin, after parsing
for (const cssFile of jayFile.linkedCssFiles) {
watchChangesFor(context, cssFile);
}
Pros:
- Clean separation of concerns
- Parser remains context-agnostic
- Easy to test
Cons:
- Requires modifying
JayHtmlSourceFileinterface - Need to pass linked files up through multiple layers
Option B: Pass watch callback to parser
Pass a callback function to the parser that gets called for each file that should be watched.
Pros:
- Minimal interface changes
Cons:
- Mixes concerns (parser shouldn't know about watching)
- Harder to test
Recommendation
Option A - cleaner architecture, follows existing patterns.
Implementation Plan
Phase 1: Extend parser to track linked CSS files
- Add
linkedCssFiles: string[]toJayHtmlSourceFileinterface injay-html-source-file.ts - Modify
extractCssinjay-html-parser.tsto collect resolved CSS file paths - Store paths in the returned source file object
- Update tests for
extractCssto verify file paths are collected
Phase 2: Add watch registration in rollup plugin
- In
loadCssFile(rollup-plugin/lib/runtime/load.ts), after parsing the jay-html file, iterate overlinkedCssFilesand callwatchChangesForfor each - Alternatively, add watching in the transform hook if that's where compilation happens
Phase 3: Test HMR behavior
- Manual test: modify a linked CSS file and verify rebuild triggers
- Verify CSS changes appear in browser without manual refresh
Files to Modify
jay/packages/compiler/compiler-jay-html/lib/jay-target/jay-html-source-file.tsjay/packages/compiler/compiler-jay-html/lib/jay-target/jay-html-parser.tsjay/packages/compiler/rollup-plugin/lib/runtime/load.tsjay/packages/compiler/compiler-jay-html/test/css-extraction.test.ts
Trade-offs
| Aspect | Current | Proposed |
|---|---|---|
| DX for CSS changes | Must save jay-html | Auto-rebuild |
| Watch count | 1 file per jay-html | 1 + N CSS files |
| Complexity | Simple | Slightly more plumbing |
Verification Criteria
- Modifying a linked CSS file triggers jay-html recompilation
- Creating a previously-missing CSS file triggers recompilation
- External URLs are not watched (no errors)
- Existing tests continue to pass
- No performance regression for pages without linked CSS
Implementation Results
Changes Made
1. jay-html-source-file.ts - Added linkedCssFiles?: string[] property to track absolute paths of referenced CSS files.
2. jay-html-parser.ts - Modified extractCss function to:
- Return
ExtractCssResultinterface with bothcssandlinkedCssFiles - Collect resolved CSS file paths before attempting to read them (so missing files are still tracked)
- Updated
parseJayFileto passlinkedCssFilesto the source file
3. rollup-plugin/lib/runtime/load.ts - Added watch registration:
- Import
watchChangesForfunction - In
loadCssFile, iterate overlinkedCssFilesand register each for watching
4. css-extraction.test.ts - Added 4 new tests:
- Verify
linkedCssFilescontains resolved paths - Verify
linkedCssFilesis undefined when no CSS linked - Verify external URLs are excluded
- Verify missing files are tracked for watch
Test Results
- compiler-jay-html: 406 passed, 4 skipped
- rollup-plugin: 47 passed
Deviations from Design
None - implementation followed the design exactly.
Log Methodology Note
Note: These design logs are written primarily for AI agents as part of the Design Log methodology and made accessible here for human readers. The language and structure are optimized for machine consumption — expect precise, specification-style prose rather than narrative documentation.