drufball / layered-apis

A new standards effort for collaborating on high-level features.
274 stars 12 forks source link

Separating dependency ID declaration from URL resolution via a JS API #24

Open jrburke opened 6 years ago

jrburke commented 6 years ago

For the Part 1: the infrastructure part of the solution, the core of that infrastructure seems to be:

In the fallback syntax document, I believe "(E) A general resource-specifier mapping mechanism" is most appealing, since it decouples specifying the ID from its resolution to a URL. The top level web page/app should be the ultimate decision maker on how to resolve the IDs, and leave the individual modules to just specify the ID of the module interface it needs to satisfy its goals.

I can appreciate that may be a taller hill to climb, but perhaps it would be tractable if a targeted imperative JS API is introduced for it.

A JS API would allow the most flexibility for the end user for fallbacks. The browser-supplied resolution for a particular module ID could even be skipped if the user knew the browser-supplied module had a bug, or the user needed a different implementation to meet the user's cross-browser deployment.

Here is sketch to illustrate the approach, not meant to be comprehensive, just something for visualization:

<script>
  // The browser module loader would call `import.resolve()` as the final part
  // of determining the URL to load for a module ID. If `import.resolve()`
  // returns undefined, then the module ID is treated as the URL. If an array is
  // returned, the browser treats the array items as a list of URLs to try in
  // sequence: if the first one fails, try loading the second one, etc.
  import.resolve = function(id) {
    const fallbacks = new Map([
      ['std:async-local-storage', 'https://other.cdn.com/async-local-storage.js']
    ]);

    // navigator.import.resolve would resolve IDs like std:async-local-storage
    // to a blob url or some other url protocol that the browser supports for
    // serving the module body. If it does not know about the module ID, it
    // returns `undefined`.
    return navigator.import.resolve(id) || fallbacks.get(id);
  };
</script>
<!-- rest of module declaration/loading goes here -->
domenic commented 6 years ago

Yep, using https://github.com/domenic/package-name-maps/ is one idea on the table; see especially https://github.com/domenic/package-name-maps/#supplying-fallbacks-for-host-supplied-standard-library-packages. See also https://github.com/domenic/package-name-maps/#a-programmable-resolution-hook for why a programmable resolution hook is not very feasible.

jrburke commented 6 years ago

Ah great, I missed the fallbacks in the package-name-map. It looks like the browser would always get to claim the module ID first, no way to route around it. Not the main concern though.

For the import.resolve above, I meant that more as a resolveForFetch. The cost of calling it is only once per module, when the module needs to be fetched. The cost of that JS call seems negligible compared to the cost of going through the IO layer and either networking or service worker JS hooks.

However, what would be missing is the ability to change the the resolution path based on who is asking for the module ID, which is what the scopes part of package-name-maps accomplishes.

My main concern was coupling ID resolution/fallbacks with the specifying the dependency's module ID, so if package-name-maps is what can get traction, then that satisfies the concern.