You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: resolve url() inside HTML style attributes via CSS parser as option (#21157)
* feat: resolve url() inside HTML style attributes via CSS parser "as" option
Add a CSS parser `as` option (default `"stylesheet"`) that selects the
parse entry point. With `as: "declaration-list"` the source is parsed as a
block's contents (CSS Syntax §5.4.5) instead of a full stylesheet, which is
the correct model for an element's `style="..."` attribute.
HTML modules now route URL-bearing `style` attributes through the CSS
pipeline (via the new `html-style-attribute` dependency category, parsed
with `as: "declaration-list"`), so `url()` / `image-set()` references in
inline styles resolve relative to the HTML file.
https://claude.ai/code/session_01RtuVwAXk4wSAPE85d8qadX
* refactor(css): inject the top-level consumer into the CSS walk
Instead of switching on an `as` string inside `grammar`, `SourceProcessor`
now accepts a `consume` driver (a `TopLevelConsumer`) and defaults to the
stylesheet consumer. `consumeADeclarationList` is exported as a reusable
driver, and `CssParser` maps its `as` option to it — so the walk no longer
hardcodes how the source is parsed.
https://claude.ai/code/session_01RtuVwAXk4wSAPE85d8qadX
* refactor(css): drive the walk through the spec parse* entry points
The injectable top-level driver now routes through the §5.3 `parse*`
functions instead of the §5.4 `consume*` internals: the default uses
`parseAStylesheetsContents` and a `style` attribute uses the new
`parseADeclarationList` ("parse a block's contents", §5.3.6). Both receive
the already-built `TokenStream`, which `normalizeIntoTokenStream` returns
as-is, so there is no re-tokenization. `parseAStylesheetsContents` gains the
same optional streaming `onRule` sink `consumeAStylesheetsContents` already
had, so the default path keeps its rule-by-rule memory profile.
https://claude.ai/code/session_01RtuVwAXk4wSAPE85d8qadX
* refactor(css): use parseABlocksContents directly for style attributes
Drop the `parseADeclarationList` wrapper — its name reflects the old spec
term "parse a list of declarations", which CSS Syntax renamed to "parse a
block's contents". CssParser now builds the `style`-attribute top-level
parser straight from `parseABlocksContents` (§5.3.6), reusing the existing
token stream as before.
https://claude.ai/code/session_01RtuVwAXk4wSAPE85d8qadX
* refactor(css): pass parseAStylesheet / parseABlocksContents as the walk driver
Drop the "declaration-list" terminology and the grammar's internal default.
`SourceProcessor#process` now requires the caller to pass the top-level parse
entry point, and CssParser passes one of the spec §5.3 functions directly:
`parseAStylesheet` for a stylesheet, `parseABlocksContents` for a block's
contents (the `as: "block"` mode, e.g. an HTML `style` attribute). The walk
reuses the existing token stream and walks the returned nodes. Revert the
now-unused streaming `onRule` extension on `parseAStylesheetsContents`.
https://claude.ai/code/session_01RtuVwAXk4wSAPE85d8qadX
* refactor(css): align `as` value with CSS spec and drop dead onRule
Rename the `style` attribute parse mode from `as: "block"` to
`as: "block-contents"` so the option values match the CSS Syntax §5.3 entry
points ("parse a block's contents" / "parse a stylesheet"), leaving room for
future spec-named modes. Also remove the now-unused `onRule` streaming param
from `consumeAStylesheetsContents`.
https://claude.ai/code/session_01RtuVwAXk4wSAPE85d8qadX
* refactor(css): keep stylesheet streaming intact, make block-contents purely additive
Restore the original "consume a stylesheet's contents" streaming driver
(`consumeAStylesheetsContents(ts, onRule)`) for the default `as: "stylesheet"`
mode — the spec parse logic is unchanged. The `as: "block-contents"` mode is
now a small additive branch in the walk's grammar that parses a block's
contents instead. Drops the `TopLevelParser` indirection.
https://claude.ai/code/session_01RtuVwAXk4wSAPE85d8qadX
* refactor(css): drive the walk through a uniform top-level consumer table
Give `consumeABlocksContents` the same streaming `onNode` callback that
`consumeAStylesheetsContents` already exposes, so both share a `(ts, onNode)`
shape. The walk's `grammar` now dispatches `as` through a `TOP_LEVEL_CONSUMERS`
map instead of a branch — a future `as` mode is one map entry, no new walk
code, and every mode streams (no full AST retained).
https://claude.ai/code/session_01RtuVwAXk4wSAPE85d8qadX
* chore: rename changeset file to match block-contents terminology
https://claude.ai/code/session_01RtuVwAXk4wSAPE85d8qadX
* feat(html): add stylesheet-style-attribute source type, rename stylesheet-inline
Let developers route a custom HTML attribute's value through the CSS pipeline
as a block's contents (a declaration list, like a `style` attribute) via the
new `stylesheet-style-attribute` source type, alongside `stylesheet-style` for
a full inline stylesheet. Renames the experimental `stylesheet-inline` type to
`stylesheet-style` so the inline pair mirrors `<style>` / `style=`.
https://claude.ai/code/session_01RtuVwAXk4wSAPE85d8qadX
* test(css): strictly type the new block-contents walk tests
Annotate the visitor arrays and cast the `VisitorMap` / visitor params, matching
the strict test-suite typing introduced on main (#21147), so the two added
`as: "block-contents"` tests pass `tsc -p tsconfig.types.test.json`.
https://claude.ai/code/session_01RtuVwAXk4wSAPE85d8qadX
* How the attribute value should be parsed and bundled. `src` extracts a single URL as a plain asset; `srcset` parses a `srcset`-style list of candidate URLs as plain assets; `script` and `script-module` emit a classic / ES-module chunk entry like `<script src>` and `<script type="module" src>`; `stylesheet` emits a CSS chunk entry like `<link rel="stylesheet">`; `stylesheet-inline` treats the attribute value as inline CSS text and bundles it through the CSS pipeline (the attribute's content is replaced with the processed CSS at render time, like an inline `<style>` body).
3593
+
* How the attribute value should be parsed and bundled. `src` extracts a single URL as a plain asset; `srcset` parses a `srcset`-style list of candidate URLs as plain assets; `script` and `script-module` emit a classic / ES-module chunk entry like `<script src>` and `<script type="module" src>`; `stylesheet` emits a CSS chunk entry like `<link rel="stylesheet">`; `stylesheet-style` treats the attribute value as a full stylesheet (like a `<style>` body) and `stylesheet-style-attribute` as a CSS block's contents (a declaration list, like a `style` attribute) — both bundle it through the CSS pipeline and replace the attribute's content with the processed CSS at render time.
* Consume a block's contents, CSS Syntax Level 3 [§5.4.5](https://drafts.csswg.org/css-syntax/#consume-block-contents). Per tabatkins/parse-css.js reference impl: returns separate `decls` and `rules` flat lists, both preserved on EOF / `}` (the spec text's "Return rules" single-list model drops trailing decls because there's no implicit flush before EOF / `}`).
2104
+
*
2105
+
* `onNode` is the same streaming extension `consumeAStylesheetsContents` exposes:
2106
+
* when given, each consumed declaration / rule is handed to it immediately (in
2107
+
* source order) instead of being collected, so the returned lists are empty.
2104
2108
* @param {TokenStream} ts token stream
2105
-
* @returns {{ decls: Declaration[], rules: Rule[] }} consumed decls + rules (stops at the enclosing `}` / EOF, left in the stream)
2109
+
* @param {((node: Declaration | Rule) => void)=} onNode optional per-node sink (streaming); nodes are not collected when given
2110
+
* @returns {{ decls: Declaration[], rules: Rule[] }} consumed decls + rules (both empty when `onNode` is given; stops at the enclosing `}` / EOF, left in the stream)
* @property {boolean=} recurseBlocks walk into block bodies' nested rules (default true)
2638
+
* @property {("stylesheet" | "block-contents")=} as which top-level production to consume the source as (see `TOP_LEVEL_CONSUMERS`): `"stylesheet"` (default) or `"block-contents"` (a block's contents, e.g. an HTML `style` attribute)
* Run the grammar over `input`, firing visitors in source order. No
2765
2796
* AST retained.
2766
2797
* @param {string} input source text
2767
-
* @param {{ locConverter?: LocConverter, comment?: (input: string, start: number, end: number) => number, recurseBlocks?: boolean}=} ctx reuse a `locConverter`, forward a `comment` callback, or set `recurseBlocks: false` to stop at top-level rules
2798
+
* @param {{ locConverter?: LocConverter, comment?: (input: string, start: number, end: number) => number, recurseBlocks?: boolean, as?: ("stylesheet" | "block-contents") }=} ctx reuse a `locConverter`, forward a `comment` callback, set `recurseBlocks: false` to stop at top-level rules, or set `as: "block-contents"` to parse a block's contents (e.g. an HTML `style` attribute) instead of a full stylesheet
0 commit comments