-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding scriptElement.exports for configuration use cases #7367
Comments
Personally this seems a decent feature to add, but I would think having it vary between module and classic Scripts as a bit odd. If the getter resolves a Promise to a module namespace object it is somewhat handy since it ~= the return type of I don't have a strong preference but think whatever the value is seems fine due to |
Not my area of expertise, but for me one of the pain points of configuration objects as they work currently is the global's pollution and the possible conflicts between them. So I was wondering if taking the problem the other way around and using an <script type="module" async src="https://cdn.example.net/script.mjs" import="./configuration.js"> or even, by using same-document URL reference <!-- here page authors can use whatever id they want -->
<script type="module" id="my-config">
export const deviceType = "tier1";
// ...
</script>
<script type="module" async src="https://cdn.example.net/script.mjs" import="#my-config"> I'm sure there are a lot of things involved here that I am missing, but as an author I think I'd prefer such a design, even if it means having to place my inline configuration script before the library's script. |
I think allowing any ordering is the key value of this proposal, so alternate solutions are best considered with that constraint in mind. See the "Alternative considered" section for another approach that doesn't meet that constraint, and why the authors who originated this proposal are not fond of it. |
I think the proposal can address the global namespace problem and remove the need to author the
|
I'm a bit curious if there even is a global namespace problem given Shadow DOM. My main thought is if these can be injected as slots it seems to take away a bunch of the concerns, for example: <x-editor>
<script slot="config" type="module">/*inline works, but so would src=*/</script>
</x-editor> This likely would still need some glue if using something like a generic CDN since I'm not familiar with how this could let |
I have trouble to see why it's so hard to maintain the ordering of two script elements that it would dismiss any other issues with the presented use-case. Could you share an example scenario? To be clear, the |
I like this proposal a lot, especially since it can easily match the return value of import(). I think personally the script case being wrapped up in the promise makes sense to me, since you can then handle that logic the same way. if you want to check "synchronously" you can read the element's type attribute right? |
Some random thoughts... Another alternative in my mind is
This is to give names to inline scripts on the module maps, and is somewhat similar to the second one of "Alternative considered". This alleviates some of the concerns (e.g. module fetching is more regularly working and isn't blocked by to-be-added inline scripts), while sharing the other concerns. For example, the following scripts result in
(Also this looks like fetching |
Some other (minor) thoughts on exposing
|
The reason this is being brought up is based on sites maintained by multiple teams, where the final HTML document is assembled via many layers in the stack (e.g. framework, application developers, server-injected HTML, and even CDN-injected changes). The value proposition of this proposal is allowing dependencies to cross these layers without the different teams having to cooridnate on an ordering. |
Yeah, this seems unfortunate to me.
I don't quite understand this point. There are tons of DOM mutations after prepare-a-script that impact the rest of the page, if the rest of the page is inspecting the
Hmm. I'd be happy to null out the value in such cases, just because generally I find moving scripts between documents to be problematic and am happy to cripple those cases as much as possible (like we've already done for fetching). But I'm not sure I see it as being very important. That is, I don't think anything bad would happen if we continued to expose the same value for |
Hmm. Upon reconsidering maybe it's not terrible. We can already get a similar effect today, where an <script type="module" src="a.mjs"></script>
<script type="module" src="b.mjs"></script> // a.mjs
import "./b.mjs";
console.log(1); // b.mjs
console.log(2); i.e. this will output 2 1 in a similar fashion. The remaining problem is that it's much easier to indefinitely hang your application this way. The failure case where I'd be curious to hear others' thoughts. It certainly is a nice bit simpler to do |
@domenic I think either option is good. Some Babel plugin work would be required in either case. I do think that |
Are there any thoughts on how to make progress here? Accessing a module is pretty important for use cases like declarative components: #2235 (comment) |
It's very difficult to polyfill declarative custom elements that allow customization via script, or to implement Vue-like single-file components, without this proposal. Many of the userland implementation have a roughly similar shape: <define-element name="my-element">
<template>...</template>
<script type="module">
export default class extends HTMLElement {
// customizations here...
}
</script>
</define-element> but the only way to get to the "export" of the <define-element name="my-element" custom-class="abc123">
<template>...</template>
<script type="module">
import {register} from './register.js';
register('abc123', class extends HTMLElement {
// customizations here...
});
</script>
</define-element> which is obviously cumbersome and error-prone. The proposal to add an |
I’d be happy to prototype this in a browser, with blessing from a vendor representative. |
(Credit to @dvoytenko for this proposal.)
Problem statement
It's a common pattern to serve most of your JavaScript from CDNs, but use an inline script for configuration settings. E.g.
where
https://cdn.example.net/script.js
could end up usingself.configuration
.This setup is fragile, as it relies on the web page author to ensure the inline script appears before any CDN-included scripts, and and doesn't play well with
async=""
which might cause scripts to execute early.Additionally, some authors would prefer to use modules for this, so that the dependency between
script.js
and the global configuration is explicit.Proposal
We could add an
exports
property toHTMLScriptElement
. This was mentioned previously in #2235, but without use cases. Then we could have this setup:Where
script.mjs
containsand
waitForElement
is a developer-written helper utility that uses aMutationObserver
to wait for the specified element to appear.Note that
exports
itself would be a promise, because in the general case the inline module might itself use top-level await. That's not what's going on in our configuration-based example, so in our case the promise will immediately fulfill, but it seems like the right primitive at the spec level.There could also be a level of indirection, so that
script.mjs
doesn't need its ownwaitForElement
function. For example ifscript.mjs
didwhere
https://cdn.example.net/constants.mjs
contains some default configuration values, then the page itself could use an import map to remaphttps://cdn.example.com/configuration.mjs
to its own script that looks like the following:Alternative considered
Another way of accomplishing this, which is less powerful but potentially easier to use, would be to introduce the ability to directly import an inline script. Something like the following:
The problem with this idea is that the semantics of resolving
document:configuration
specifiers is tricky:The most natural thing would be that resolving does a synchronous lookup in the current state of the document, and fails if there is no element with the specified ID. However, then you basically go back to the current state of things, just with modules: you'll still have problems if your inline module isn't before any CDN-provided modules, or if you use
async=""
. You could combine it withwaitForElement("#configuration")
+ dynamicimport()
like so:but this is not much of a win.
Alternately, we could try to specify a semantic where if the element with such an ID doesn't exist yet, we wait until it does before finishing module resolution. This would be nice to use, but it breaks some existing properties of modules, such as how they execute in order. I.e., we would have to delay the resolution and fetching of
script.mjs
's dependencies until an element with the appropriate ID appears in the tree, and then we would have to go skip the usual ordering to execute that element's inline script (and any dependencies) immediately. Or, we could end up waiting indefinitely, if no such element appears. Also, these strange semantics an be caused deep in the tree, by anyimport
statement. So this seems bad.Combined with the idea that there might be speculative future HTML modules-related use cases for an
exports
property, per #2235, probably this alternative is not a good direction and we should doexports
instead.Details
We've said
exports
should be a promise. What about in the non-module script case? It could be a forever-pending promise, or a promise already resolved with null. Or maybe it could be null, instead of a promise? Web IDL might make the latter impossible currently...This feature makes inline JSON and CSS modules useful. Should we consider allowing them at the same time?
/cc @whatwg/modules
The text was updated successfully, but these errors were encountered: