Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 69 additions & 24 deletions packages/melonjs/src/loader/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,51 @@ function onLoadingError(res) {
throw new Error("Failed loading resource " + res.src);
}

/** a src that is already a bare path needs no unwrapping @ignore @internal */
const keepSrc = (src) => {
return src;
};

/**
* The one rule that holds for every asset type: a data URI carries its own
* payload, so nothing can be relative to it. Checked by `load()` for all types
* rather than left to `needsBaseURL`, so that a type overriding that hook
* declares only its OWN exceptions and cannot forget this one.
* @param {string} src - the normalized src
* @returns {boolean} true for a data URI
* @ignore
* @internal
*/
const isDataURI = (src) => {
return src.startsWith("data:");
};

/** default: a src is a path relative to its type's base URL @ignore @internal */
const alwaysRelative = () => {
return true;
};

/**
* A registered parser, normalized by {@link loader.setParser}.
*
* Every type gets the same shape whether or not it needs the src hooks, so
* `load()` can treat all of them alike. The alternative — a bare function that
* some types decorate with extra properties — puts the knowledge of which
* types are special back into the caller, which is what moving it out was for.
* @typedef {object} AssetParser
* @property {Function} parse - preloads the asset and returns how many
* resources it will load (0 when already cached)
* @property {function(string): string} normalizeSrc - turns a type-specific
* descriptor into a bare path, before any base URL is applied. Identity unless
* the type overrides it
* @property {function(string): boolean} needsBaseURL - whether the normalized
* src is relative to this type's base URL. Declares only this type's own
* exceptions, such as a `local()` font naming an installed family — data URIs
* are excluded for every type before this is consulted
* @ignore
* @internal
*/

/**
* an asset definition to be used with the loader
* @typedef {object} Asset
Expand Down Expand Up @@ -390,7 +435,11 @@ export function setParser(type, parserFn) {
warning("overriding parser for " + type + " format");
}

parsers.set(type, parserFn);
parsers.set(type, {
parse: parserFn,
normalizeSrc: parserFn.normalizeSrc ?? keepSrc,
needsBaseURL: parserFn.needsBaseURL ?? alwaysRelative,
});
}

/**
Expand Down Expand Up @@ -567,6 +616,12 @@ export function load(asset, onload, onerror) {
initParsers();
}

const parser = parsers.get(asset.type);

if (typeof parser === "undefined") {
throw new Error("load : unknown or invalid resource type : " + asset.type);
}

// Resolve the effective src WITHOUT mutating the caller's asset
// descriptor: load() used to write the transformed url back into
// asset.src, so retrying the same object — loader.reload() after a
Expand All @@ -576,33 +631,23 @@ export function load(asset, onload, onerror) {
// caller's original src; only the parser sees the resolved one.
let src = asset.src;

// 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];
// Normalize, then prefix — both through the parser, so this function never
// names an asset type. `src` may legitimately be an array (an image
// fallback chain) or absent, and neither hook applies to those.
if (typeof src === "string") {
src = parser.normalizeSrc(src);
if (
typeof baseURL[asset.type] !== "undefined" &&
!isDataURI(src) &&
parser.needsBaseURL(src)
) {
src = baseURL[asset.type] + src;
}
}

// transform the url if necessary (skip for local() font sources and data URIs)
if (
typeof baseURL[asset.type] !== "undefined" &&
typeof src === "string" &&
!src.startsWith("local(") &&
!src.startsWith("data:")
) {
src = baseURL[asset.type] + src;
}

const resource =
src === asset.src ? asset : Object.assign({}, asset, { src });

const parser = parsers.get(asset.type);

if (typeof parser === "undefined") {
throw new Error("load : unknown or invalid resource type : " + asset.type);
}

const settings = {
nocache: nocache,
crossOrigin: crossOrigin,
Expand All @@ -616,7 +661,7 @@ export function load(asset, onload, onerror) {
return new Promise((resolve, reject) => {
// parser returns the amount of asset to be loaded (usually 1, more
// if it splits into several); 0 means already cached → resolve now.
const count = parser.call(
const count = parser.parse.call(
this,
resource,
() => {
Expand All @@ -632,7 +677,7 @@ export function load(asset, onload, onerror) {
}

// parser returns the amount of asset to be loaded (usually 1 unless an asset is splitted into several ones)
return parser.call(this, resource, onload, onerror, settings);
return parser.parse.call(this, resource, onload, onerror, settings);
}

/**
Expand Down
27 changes: 27 additions & 0 deletions packages/melonjs/src/loader/parsers/fontface.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,30 @@ export function preloadFontFace(data, onload, onerror) {

return 1;
}

/**
* A `fontface` src may be a CSS font descriptor rather than a bare path, so
* unwrap `url(...)` before the loader prefixes the font base URL — it cannot be
* repaired afterwards, since `"data/font/" + "url('x.woff2')"` is not a path
* either half of the pipeline can recover from.
* @param {string} src - the descriptor as written in the manifest
* @returns {string} the path inside it, or the descriptor unchanged
* @ignore
* @internal
*/
preloadFontFace.normalizeSrc = (src) => {
const urlMatch = src.match(/^url\(\s*['"]?(.*?)['"]?\s*\)$/);
return urlMatch ? urlMatch[1] : src;
};

/**
* `local('Family Name')` names a font already installed on the machine, which
* is not a location and has no base to be relative to.
* @param {string} src - the normalized src
* @returns {boolean} false for an installed family, true for a path
* @ignore
* @internal
*/
preloadFontFace.needsBaseURL = (src) => {
return !src.startsWith("local(");
};
Loading
Loading