Skip to content

Commit

Permalink
Initial specification for resolving module specifiers
Browse files Browse the repository at this point in the history
Closes #6.
  • Loading branch information
domenic committed May 20, 2019
1 parent faf0ea0 commit 13f4ff2
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 33 deletions.
51 changes: 24 additions & 27 deletions reference-implementation/lib/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,53 +7,50 @@ const supportedBuiltInModules = new Set([`${BUILT_IN_MODULE_SCHEME}:blank`]);
exports.resolve = (specifier, parsedImportMap, scriptURL) => {
const asURL = tryURLLikeSpecifierParse(specifier, scriptURL);
const normalizedSpecifier = asURL ? asURL.href : specifier;
const scriptURLString = scriptURL.href;

for (const [normalizedScopeKey, scopeImports] of Object.entries(parsedImportMap.scopes)) {
if (scriptURL.href === normalizedScopeKey ||
(normalizedScopeKey.endsWith('/') && scriptURL.href.startsWith(normalizedScopeKey))) {
for (const [scopePrefix, scopeImports] of Object.entries(parsedImportMap.scopes)) {
if (scopePrefix === scriptURLString ||
(scopePrefix.endsWith('/') && scriptURLString.startsWith(scopePrefix))) {
const scopeImportsMatch = resolveImportsMatch(normalizedSpecifier, scopeImports);
if (scopeImportsMatch) {
return scopeImportsMatch;
}
}
}

const importsMatch = resolveImportsMatch(normalizedSpecifier, parsedImportMap.imports);
if (importsMatch) {
return importsMatch;
const topLevelImportsMatch = resolveImportsMatch(normalizedSpecifier, parsedImportMap.imports);
if (topLevelImportsMatch) {
return topLevelImportsMatch;
}

// The specifier was able to be turned into a URL, but wasn't remapped into anything.
if (asURL) {
if (asURL.protocol === BUILT_IN_MODULE_PROTOCOL) {
if (!supportedBuiltInModules.has(asURL.href)) {
throw new TypeError(`The "${asURL.href}" built-in module is not implemented.`);
}
if (asURL.protocol === BUILT_IN_MODULE_PROTOCOL && !supportedBuiltInModules.has(asURL.href)) {
throw new TypeError(`The "${asURL.href}" built-in module is not implemented.`);
}
return asURL;
}

throw new TypeError(`Unmapped bare specifier "${specifier}"`);
};

function resolveImportsMatch(normalizedSpecifier, importMap) {
for (const [specifierKey, addressArray] of Object.entries(importMap)) {
function resolveImportsMatch(normalizedSpecifier, specifierMap) {
for (const [specifierKey, addresses] of Object.entries(specifierMap)) {
// Exact-match case
if (specifierKey === normalizedSpecifier) {
if (addressArray.length === 0) {
if (addresses.length === 0) {
throw new TypeError(`Specifier "${normalizedSpecifier}" was mapped to no addresses.`);
} else if (addressArray.length === 1) {
if (addressArray[0].protocol === BUILT_IN_MODULE_PROTOCOL) {
if (supportedBuiltInModules.has(addressArray[0].href)) {
return addressArray[0];
}
throw new TypeError(`The "${addressArray[0].href}" built-in module is not implemented.`);
} else if (addresses.length === 1) {
const singleAddress = addresses[0];
if (singleAddress.protocol === BUILT_IN_MODULE_PROTOCOL && !supportedBuiltInModules.has(singleAddress.href)) {
throw new TypeError(`The "${singleAddress.href}" built-in module is not implemented.`);
}
return addressArray[0];
} else if (addressArray.length === 2 &&
addressArray[0].protocol === BUILT_IN_MODULE_PROTOCOL &&
addressArray[1].protocol !== BUILT_IN_MODULE_PROTOCOL) {
return supportedBuiltInModules.has(addressArray[0].href) ? addressArray[0] : addressArray[1];
return singleAddress;
} else if (addresses.length === 2 &&
addresses[0].protocol === BUILT_IN_MODULE_PROTOCOL &&
addresses[1].protocol !== BUILT_IN_MODULE_PROTOCOL) {
return supportedBuiltInModules.has(addresses[0].href) ? addresses[0] : addresses[1];
} else {
throw new Error('The reference implementation for multi-address fallbacks that are not ' +
'[built-in module, fetch-scheme URL] is not yet implemented.');
Expand All @@ -62,12 +59,12 @@ function resolveImportsMatch(normalizedSpecifier, importMap) {

// Package prefix-match case
if (specifierKey.endsWith('/') && normalizedSpecifier.startsWith(specifierKey)) {
if (addressArray.length === 0) {
if (addresses.length === 0) {
throw new TypeError(`Specifier "${normalizedSpecifier}" was mapped to no addresses ` +
`(via prefix specifier key "${specifierKey}").`);
} else if (addressArray.length === 1) {
} else if (addresses.length === 1) {
const afterPrefix = normalizedSpecifier.substring(specifierKey.length);
return new URL(afterPrefix, addressArray[0]);
return new URL(afterPrefix, addresses[0]);
} else {
throw new Error('The reference implementation for multi-address fallbacks that are not ' +
'[built-in module, fetch-scheme URL] is not yet implemented.');
Expand Down
56 changes: 50 additions & 6 deletions spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ spec: infra; type: dfn
text: list
spec: url; type: dfn; for: /; text: url
</pre>
<pre class="anchors">
spec: html; type: dfn; urlPrefix: https://html.spec.whatwg.org/multipage/
text: module map; for: /; url: webappapis.html#module-map
</pre>

<style>
.selected-text-file-an-issue {
Expand Down Expand Up @@ -185,13 +189,53 @@ At some point, each [=environment settings object=] will get an <dfn for="enviro

<h2 id="resolving">Resolving module specifiers</h2>

HTML already has a <a spec="html">resolve a module specifier</a> algorithm. We replace it with the following <dfn export>resolve a module specifier</dfn> algorithm, given a [=script=] |referringScript| and a [=JavaScript string=] |specifier|:

1. Let |importMap| be |referringScript|'s [=script/settings object=]'s [=environment settings object/import map=].
1. Let |baseURL| be |referringScript|'s [=script/base URL=].
1. For now, see the <a href="https://github.com/WICG/import-maps/blob/master/reference-implementation/lib/resolver.js">reference implementation</a>, carrying out the algorithm there given |specifier|, |importMap|, and |baseURL|.
1. As before, this algorithm returns a [=URL=] or failure.
<div algorithm>
HTML already has a <a spec="html">resolve a module specifier</a> algorithm. We replace it with the following <dfn export>resolve a module specifier</dfn> algorithm, given a [=script=] |referringScript| and a [=JavaScript string=] |specifier|:

1. Let |importMap| be |referringScript|'s [=script/settings object=]'s [=environment settings object/import map=].
1. Let |moduleMap| be |referringScript|'s [=script/settings object=]'s [=environment settings object/module map=].
1. Let |scriptURL| be |referringScript|'s [=script/base URL=].
1. Let |scriptURLString| be |scriptURL|, [=URL serializer|serialized=].
1. Let |asURL| be the result of [=parsing a URL-like import specifier=] given |specifier| and |scriptURL|.
1. Let |normalizedSpecifier| be the [=URL serializer|serialization=] of |asURL|, if |asURL| is non-null; otherwise, |specifier|.
1. [=map/For each=] |scopePrefix| → |scopeImports| of |importMap|'s [=import map/scopes=],
1. If |scopePrefix| is |scriptURLString|, or if |scopePrefix| ends with U+002F (/) and |scriptURLString| [=starts with=] |scopePrefix|, then:
1. Let |scopeImportsMatch| be the result of [=resolving an imports match=] given |normalizedSpecifier|, |scopeImports|, and |moduleMap|.
1. If |scopeImportsMatch| is not null, then return |scopeImportsMatch|.
1. Let |topLevelImportsMatch| be the reuslt of [=resolving an imports match=] given |normalizedSpecifier|, |importMap|'s [=import map/imports=], and |moduleMap|.
1. If |topLevelImportsMatch| is not null, then return |topLevelImportsMatch|.
1. <p class="note">At this point, the specifier was able to be turned in to a URL, but it wasn't remapped to anything by |importMap|.</p>
If |asURL| is not null, then:
1. If |asURL|'s [=url/scheme=] is "`std`", and |moduleMap|[|asURL|] does not [=map/exist=], then throw a {{TypeError}} indicating that the requested built-in module is not implemented.
1. Return |asURL|.
1. Throw a {{TypeError}} indicating that |specifier| was a bare specifier, but was not remapped to anything by |importMap|.
</div>

<p class="advisement">It seems possible that the return type could end up being a [=list=] of [=URLs=], not just a single URL, to support HTTPS → HTTPS fallback. But, we haven't gotten that far yet; for now let's assume it stays a single URL.</p>

All call sites of HTML's existing <a spec="html">resolve a module specifier</a> will need to be updated to pass the appropriate [=script=], not just its [=script/base URL=].

They will also need to be updated to account for it now throwing exceptions, instead of returning failure. (Previously they just turned failures into {{TypeError}}s manually, so this is straightforward.)

<div algorithm>
To <dfn lt="resolve an imports match|resolving an imports match">resolve an imports match</dfn>, given a [=string=] |normalizedSpecifier|, a [=specifier map=] |specifierMap|, and a [=module map=] |moduleMap|:

1. For each |specifierKey| → |addresses| of |specifierMap|,
1. If |specifierKey| is |normalizedSpecifier|, then:
1. If |addresses|'s [=list/size=] is 0, then throw a {{TypeError}} indicating that |normalizedSpecifier| was mapped to no addresses.
1. If |addresses|'s [=list/size=] is 1, then:
1. Let |singleAddress| be |addresses|[0].
1. If |singleAddress|'s [=url/scheme=] is "`std`", and |moduleMap|[|singleAddress|] does not [=map/exist=], then throw a {{TypeError}} indicating that the requested built-in module is not implemented.
1. Return |singleAddress|.
1. If |addresses|'s [=list/size=] is 2, and |addresses|[0]'s [=url/scheme=] is "`std`", and |addresses|[1]'s [=url/scheme=] is <em>not</em> "`std`", then:
1. Return |addresses|[0], if |moduleMap|[|addresses|[0]] [=map/exists=]; otherwise, return |addresses|[1].
1. Otherwise, <span class="advisement">we have no specification for more complicated fallbacks yet; throw a {{TypeError}} indicating this is not yet supported</span>.
1. If |specifierKey| ends with U+002F (/) and |normalizedSpecifier| [=starts with=] |specifierKey|, then:
1. If |addresses|'s [=list/size=] is 0, then throw a {{TypeError}} indicating that |normalizedSpecifier| was mapped to no addresses.
1. If |addresses|'s [=list/size=] is 1, then:
1. Let |afterPrefix| be the portion of |normalizedSpecifier| after the initial |specifierKey| prefix.
1. Let |url| be the result of [=URL parser|parsing=] |afterPrefix| relative to |addresses|[0].
1. If |url| is failure, throw a {{TypeError}}, implicating |normalizedSpecifier| (and in particular the |afterPrefix| portion).
1. Return |url|.
1. Otherwise, <span class="advisement">we have no specification for more complicated fallbacks yet; throw a {{TypeError}} indicating this is not yet supported</span>.
</div>

0 comments on commit 13f4ff2

Please sign in to comment.