loader.load() special-cases one asset type by name in the middle of the shared src-resolution path:
// src/loader/loader.js:534
// strip url() wrapper for fontface assets so baseURL can be prepended to the raw path
if (asset.type === "fontface" && typeof src === "string") {
const urlMatch = src.match(/^url\(\s*['"]?(.*?)['"]?\s*\)$/);
if (urlMatch) {
src = urlMatch[1];
}
}
That is CSS knowledge sitting in the generic loader, and it is not the only piece: the baseURL guard immediately below also tests src.startsWith("local(") (loader.js:546), which is a CSS font descriptor form too. data: on the next line is genuinely type-agnostic; the other two are not.
Why it cannot simply move to the parser today
The ordering forces it. baseURL is prepended at loader.js:544-549, and the parser only runs afterwards — so by the time preloadFontFace sees data.src, the prefix is already on. Without the strip, a descriptor written as url('x.woff2') would resolve to "data/font/" + "url('x.woff2')", which the parser cannot repair after the fact.
So the result is that one value is unwrapped, prefixed, and then wrapped again in a different file:
loader.js url('x.woff2') -> x.woff2 -> data/font/x.woff2
fontface.js data/font/x.woff2 -> url('data/font/x.woff2') (parsers/fontface.js:37-40)
with url( handled in both files and local( handled in one.
Suggested shape
parsers is already a Map populated through setParser(type, parserFn) (loader.js:180, :341), so a parser can carry its own resolution rules and the loader can stop naming types:
// parsers/fontface.js
preloadFontFace.resolveSrc = (src) => {
const m = src.match(/^url\(\s*['"]?(.*?)['"]?\s*\)$/);
return m ? m[1] : src;
};
preloadFontFace.skipBaseURL = (src) => src.startsWith("local(");
load() then does src = parser.resolveSrc?.(src) ?? src before prefixing, and consults parser.skipBaseURL?.(src) in the guard, keeping only the data: test inline. Both pieces of CSS knowledge end up in the file that understands CSS font descriptors, and the shared path becomes type-agnostic.
Naming is open — a single resolveSrc(src, baseURL) returning the final value would also work, at the cost of each parser repeating the prefix logic. The two-hook split above keeps baseURL resolution in one place.
Notes
- Behaviour-preserving refactor: no asset should resolve differently afterwards.
- Existing coverage to keep green:
tests/fontface-src.spec.js, tests/loader-asset-src.spec.js, tests/asset-paths-with-spaces.spec.js (a font path containing a space is the case the quoting exists for), and tests/loader.spec.js.
- Worth checking while in there whether any other parser would benefit from the same hook, or whether fontface stays the only user — if it is the only one, that is an argument for keeping the hook minimal rather than general.
loader.load()special-cases one asset type by name in the middle of the shared src-resolution path:That is CSS knowledge sitting in the generic loader, and it is not the only piece: the
baseURLguard immediately below also testssrc.startsWith("local(")(loader.js:546), which is a CSS font descriptor form too.data:on the next line is genuinely type-agnostic; the other two are not.Why it cannot simply move to the parser today
The ordering forces it.
baseURLis prepended atloader.js:544-549, and the parser only runs afterwards — so by the timepreloadFontFaceseesdata.src, the prefix is already on. Without the strip, a descriptor written asurl('x.woff2')would resolve to"data/font/" + "url('x.woff2')", which the parser cannot repair after the fact.So the result is that one value is unwrapped, prefixed, and then wrapped again in a different file:
with
url(handled in both files andlocal(handled in one.Suggested shape
parsersis already aMappopulated throughsetParser(type, parserFn)(loader.js:180,:341), so a parser can carry its own resolution rules and the loader can stop naming types:load()then doessrc = parser.resolveSrc?.(src) ?? srcbefore prefixing, and consultsparser.skipBaseURL?.(src)in the guard, keeping only thedata:test inline. Both pieces of CSS knowledge end up in the file that understands CSS font descriptors, and the shared path becomes type-agnostic.Naming is open — a single
resolveSrc(src, baseURL)returning the final value would also work, at the cost of each parser repeating the prefix logic. The two-hook split above keepsbaseURLresolution in one place.Notes
tests/fontface-src.spec.js,tests/loader-asset-src.spec.js,tests/asset-paths-with-spaces.spec.js(a font path containing a space is the case the quoting exists for), andtests/loader.spec.js.