Design forces:
- ECMAScript (ES) Module Loader API is coming. The ES module ID syntax are strings that are just treated as string IDs and not paths.
- Existing JS module experience in CommonJS/Node/AMD worlds of using module IDs that are resolved to a URL/path for fetching.
- Better package managers are coming for front end web apps. These package managers install assets by IDs not by paths.
- For front end code,
baseURL + moduleID + '.js'
is likely the default ID-to-URL resolution, but other declarative config could be possible for browser-based ES Module Loaders. Examples of useful declarative config in this area are the problems solved via common AMD loader config
So it will be common in JS code to use string IDs, and not URLs to refer to dependency resources.
With the coming of web components and custom elements, it will be possible for a custom element to depend on other custom elements.
For web components HTML Imports are used to try to express the dependencies. However, it suffers from a few deficiencies when coming from the world of a JS module dependency mindset:
- HTML Imports use URLs to reference the import
- By forcing the HTML Import for dependencies to be in the custom element's definition, it makes it harder to share the same custom element dependency across custom elements: they would all have to guess the right path. This is harder to do when a package manager installs dependencies.
- custom elements name themselves. In JS module space, this is an anti-pattern: modules do not name themselves, but derive their name from how other modules refer to them, or how they are installed by the package manager. This is a very useful feature, allows swapping in implementations, even different implementations depending on what ID wanted it.
What I would like to see are two things:
- Custom element resolution to pass through either the current global ES Module Loader instance, or for the DOM to have an equivalent Loader object that allows for at least the
normalize
andresolve
steps that ES Module Loaders have. - In general, allow HTML dependencies to use module-like IDs instead of URLs. These IDs would be given to a Loader for
normalize
andresolve
steps to produce a URL that the browser could then use to find the resource.
When the browser parses a DOM fragment and finds a custom element it does not know about, it asks a Loader object to resolve that element ID to a URL. Example:
When the browser sees 'some-thing', ask a loader to resolve that to a URL, and do the HTML Import-style of work without the HTML code above having to have an explicit HTML Import tag in the source.
//JS psuedocode for what browser does:
var url = System.resolve('some-thing');
// create an HTML Import using that URL, load it,
// then create some-thing custom element.
Furthermore, I would want the ability for the URL that was created to be a JS module, instead of an HTML fragment. This would allow JS dependencies for the imported custom element.
If a JS module, then the module export is the object used for the document.register() call. This would avoid the module needing to name what tag it is for. Recall that modules (and custom tags) should not name themselves.
There is a bit of grey area on how to bind a template element for that module export that is used for the document.register call, but my first impulse is to use an AMD loader plugin-style dependency like 'template!some-thing.html'.
It may be enough though, from the HTML spec side, to just say that "the object passed to document.register has a template node attached to it".
So, anywhere a URL is used in an HTML attribute, an "id" form could be used instead. Those IDs would pass through to the Loader.resolve() to get a URL that can be used by the browser.
Some existing URL-based attributes, and their ID-based equivalents:
- href -> hrefid
- src -> srcid
IDs for these would be of the form ID + '.' + extension
. So, to reference a "boostrap" CSS file for a dialog:
<link rel="stylesheet" hrefid="boostrap/dialog.css" type="text/css">
The bootstrap/dialog
would be passed to Loader.resolve() to get an URL path, and the extension would be added to the end of that path to get the final URL.
There is a restriction that the last dot is treated as a file extension, but I think that is a fine restriction. If you want to be more formal, feel free to specify a specific separator to indicate any file extension. Maybe just .., so bootstrap/dialog..css
. For AMD require.toUrl() though we just live with the restriction of the last dot indicating file extension and strip that off to get the ID.
The above are just sketches. The main points are that there should be non-URL pathways to specify resources, and some ID-to-URL resolution. This is particularly important once dependencies are in play.
The main analogy is how ES modules have moved away from direct URL references and use IDs instead, and also, moved away from correctly ordered <script src=""> tags for dependency resolution. The same should be possible for non-JS dependencies.
@bkardell mentioned on twitter: 1) HTML imports are designed to be useful outside custom elements. 2) would there be a tag to configure hrefid mappings or just imperative in a script:
That is fine, but using them for scripts in particular make me cringe, if there is an ES module loader already available. The changes above though would still allow HTML Imports as a concept to exist, maybe used under the covers by the browser after resolving the ID in the custom element resolution case.
I would say no, at least not initially, maybe never. It should be done in script. I do not mind seeing a declarative object syntax to configure the loader, but imperative hooks should be at the very bottom of the API.