Add bundler compatibility to styles plugin#92
Conversation
✅ Deploy Preview for nude-element ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
dfc136b to
6b60c65
Compare
src/plugins/styles/base.js
Outdated
|
|
||
| for (let options of def) { | ||
| if (typeof options === "string") { | ||
| if (options instanceof URL) { |
There was a problem hiding this comment.
Not all URL instances are due to bundlers, these may come up naturally for a number of other reasons.
This seems to conflate two things:
- Styles could be either a URL reference or an options dictionary
- Style URL could be either a string or a
URLobject
There are 4 combinations of conditions here, and this only handles 3 of them. A natural way to handle this without any increase in complexity is:
- Check if
optionsis a URL → if so, rewrite to{ url: options } - Check if
options.urlis aURLobject → if so, dourl = url.href(if that's even needed, most APIs that handle string URLs also handleURLinstances since they naturally get coerced tourl.href).
There was a problem hiding this comment.
Fixed. getStyle() handles all 4 combinations:
- Bare
URL→ coerce to string, fall through to string handling - Bare string →
{ url: value } - Options dict with
URL→options.url = options.url.href - Options dict with string url → used as-is
defineStyles is now a single getStyle() call per entry.
src/plugins/styles/base.js
Outdated
| else if (options instanceof Promise) { | ||
| // Dynamic import: import("./foo.css", { with: { type: "css" } }) | ||
| // Resolves to { default: CSSStyleSheet } — unwrap .default, fall back to raw value | ||
| options = { css: options.then(m => m.default ?? m), ...defaultOptions }; |
There was a problem hiding this comment.
This is quite an assumption. Not all Promises would resolve to a CSSStylesheet. A promise could resolve to any other value we support. We should first check if it's a promise and then proceed with the resolved value as normal.
There was a problem hiding this comment.
Fixed. The Promise branch resolves first, then recurses through getStyle() — the resolved value goes through the same normalization as any other input. No assumption about what the Promise contains.
ESM default unwrapping ({ default: ... }) is scoped to the Promise .then() only, since namespace objects only appear from import() resolution — never from direct caller input.
LeaVerou
left a comment
There was a problem hiding this comment.
I left some comments, but also this is getting to the point where it needs a utility function (e.g. getStyle()), rather than doing everything in base.js.
1a8e61f to
2a55bc1
Compare
- Handle URL, Promise, and CSSStyleSheet in styles definition (base.js) Partially addresses #89
43f9ff1 to
b8ba3a3
Compare
b8ba3a3 to
dc55a24
Compare
| value = value.default; | ||
| } | ||
|
|
||
| if (value instanceof Promise) { |
There was a problem hiding this comment.
Using .then() is a little awkward. I wonder if there may be value in exporting a getStyleSync() that this calls, then this can be async (which effectively it already is)
There was a problem hiding this comment.
We tried splitting into getStyleSync() + async getStyle() — the naming was confusing (it's not "the sync version", it's a different function). Merged back to one function.
getStyle can't be async — the caller reads options.fullUrl, options.url synchronously for deduplication in defineStyles. Making it async would propagate to the hook system.
The .then() remains but the callback is now minimal: unwrap ESM .default if present, then recurse via getStyle(resolved, baseUrl).css. Recursion eliminates all duplication between sync and async paths.
src/plugins/styles/util/get-style.js
Outdated
| } | ||
|
|
||
| if (value instanceof CSSStyleSheet) { | ||
| return { css: value, ...defaults }; |
There was a problem hiding this comment.
This is not quite normalized yet, css could now be either a CSSStyleSheet object or a string, so we need to handle it further at call sites.
There was a problem hiding this comment.
Fixed. css is now always CSSStyleSheet or Promise<CSSStyleSheet>:
- CSS strings → converted via
cssToSheet()insidegetStyle - URL-based inputs →
cssToSheet(cachedFetch(fullUrl)) - Explicit
cssin options dict is preserved (css ??=— only fetches when absent)
No further handling needed at call sites.
- Extract cssToSheet() from adopt-style.js into its own module - Split getStyle() into getStyleSync() (sync paths) + getStyle() (adds Promise handling) - Promise branch now pipes through cssToSheet() so css always resolves to CSSStyleSheet - Export getStyleSync and cssToSheet from util.js - Update tests: split into getStyleSync/getStyle groups, add defaults coverage Addresses review feedback on #92
- Merge getStyleSync into getStyle (single function, no naming confusion)
- Always populate css: CSSStyleSheet or Promise<CSSStyleSheet> for URL inputs
- Compute fullUrl inside getStyle, simplify base.js deduplication
- Fix spread order: { ...defaults, css } so explicit values win
- URL inputs coerce to string and fall through (no duplicated handling)
- Promise branch uses recursion instead of manual fetch/convert pipeline
- Remove getStyleSync from public barrel export
Addresses review feedback on #92
The { default: ... } namespace only appears from dynamic import()
resolution, never from direct caller input. Moving the check inside
the Promise .then() callback eliminates false positives on options
dicts that happen to have a "default" property.
- Shared run() is a simple pass-through to getStyle() - Non-promise group: tests for CSSStyleSheet, string, URL, options dict variants - Promise group: explicit Promise.resolve() args, inherited check verifies css resolves to CSSStyleSheet - ESM default test uses real dynamic import() via data URI - subset: true check — only assert properties we care about - Mock CSSStyleSheet and fetch to avoid Node/network dependencies
Summary
getStyle()normalizes any style input into an options object with a ready-to-adoptcssproperty (CSSStyleSheetorPromise<CSSStyleSheet>). This enables bundler-compatible patterns likenew URL("./foo.css", import.meta.url)and CSS Import Attributes.Changes
get-style.js: SinglegetStyle(value, baseUrl, defaults)function handling all input types:CSSStyleSheet— returned as-isstring/URL— resolved viacssToSheet(cachedFetch(fullUrl))css ??=(explicit css wins over fetch), CSS string →CSSStyleSheetPromise— resolves, unwraps ESM.defaultif present, recurses throughgetStyle{ ...defaults, ... }ensures explicit values winbase.js:defineStylessimplified to onegetStyle()call per entry; deduplication usesoptions.fullUrldirectlyutil.js: Barrel exportsgetStylerun()is a one-liner pass-through; Promise tests verifycssis a Promise resolving toCSSStyleSheetPartially addresses #89.
🤖 Generated with Claude Code