-
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
Add <script type="module"> and module resolution/fetching/evaluation #443
Conversation
Notes from our conversation via IRC, plus some extra thoughts:
|
And, most importantly:
|
I got hung up on type=module. Is it a good idea to bind a loading behavior to the type which today is used by so many tools to specify the content type itself? This is kind of leaning overly heavy on a bunch of toolchains doing the right thing. Maybe use a new Boolean attribute? <script module ...> |
We need to use the type attribute so that downlevel browsers do not interpret the contents as scripts. type="" has never had anything to do with the content type, in reality. It has a number of values which signify "JavaScript", only one of which is the JS mimetype, and any other value signifies "do not execute this". This patch adds a third value, "module", with its own semantics. |
03bb9c3
to
cdb76aa
Compare
Mozilla and Microsoft both use this attribute though and so do tools. Some of which validate it or rewrite it. Legacy MS browsers even hit the registry to look up custom scripting languages. So adding a value here is a bit dangerous to older browsers. I understand the desire to prevent parsing but I think it may have worse side effects than it is saving from. |
I don't think we have any other choice, unless we want to disallow inline module scripts entirely. I don't think any danger posed by script type="module" could possibly be greater than the danger posed by the many existing pages with type="handlebars". If legacy browsers/Mozilla/Microsoft are already coping with the web at large, which contains many such instances, they will be able to cope with the web using type="module". |
There are many usages of type, for instance, all of the WebGL samples generally use a script block with a type set to something which identifies them as either a vertex or fragment shader. And at least in IE/Edge our mechanism for type is to parse out a text/ application/ prefix, then treat the remainder as a language. For Edge we'll look this up against known languages, which all bind to Chakra. For legacy IE we'll hit the registry and see if we can find an IActiveScript* If we are bent on using the type for this, should we not identify the module language itself? Saying module seems to imply the one and only, forever, etc... It doesn't imply room for versioning or a language change of the syntax in the future. If this is the ES6 modules proposal, then should we consider a type that at least identifies the version and parsing semantics? es6-module for instance? You made one other statement about disallowing inline module scripts. Is that a terrible idea? inline scripts are pretty bad in general. They cause stalls in the pre-parser, etc... This can be mitigated with various defer/async attributes, and we are starting to imply those by saying type=module per your prosal, but this now means that the concept of modules has to be baked into a lot of places, including the pre-parser (at least in our case, and I realize this is a technical detail that may not apply to other implementations). I haven't thought through all of the conditions of using inline scripts but since they don't have a download context, it may be non-trivial for us to implement the remainder of the algorithms. Will have to spend some time thinking through that once I'm back in the office, if Travis hasn't already jumped on it by then. |
Sure. Nothing about this proposal prevents that from continuing. As long as you don't use one of the reserved values, you can just treat that as a data-storage attribute like
Versioning on the web is a classic antipattern, so there's no reason to include that here. Every version must be backward-compatible, so there's no need to specify the version. And pretending that "es6" is a coherent conceptual entity is not good; in reality there are varying levels of support in various engines, including parts of ES6 and parts of ES7 and so on. Even some ES5 is not implemented. We could name it If we need in the future to add new ways of processing script texts, we can invent new reserved values for them (e.g.
I was at first sympathetic to this idea, but upon reflection I think it'd be surprising to developers and hurt rapid experimentation. E.g. you could not write modules in a jsbin-like environment. Or I guess you could, using
The spec largely follows the existing spec for non-module inline scripts, so it would be surprising if this causes you problems. I'd love to hear about them if so, so that we can fix them!! |
I am hoping some discussion of how this proposal fits in to how developers will use modules will be helpful. It's my understanding that The resulting root module is a bit odd: it's anonymous (correct?). What -- if any -- relationship exists between multiple As I don't see any exception for the keyword I have seen requests for named modules created with Similarly I think support for In fact, the feature unique to this proposal is blocking rendering and synchronously loaded code. To say this another way,
because the latter is async. |
In the sense that it cannot be imported, yes.
None. Each creates a separate tree of module dependencies.
That's a great point. We should be able to make that fail pretty easily. I'll work on that.
Agreed. It could be useful but it's not necessary to get things off the ground.
That's not accurate. Nothing in this proposal blocks rendering or synchronously loads code.
There is no spec for what this would do in a browser, so I don't know quite what you're referring to. It's certainly not something you can do absent this proposal. |
We could do this by relying on the source text module record returned by |
Hmm, now I am not sure. Could you find any existing Node module or similar that can be used in this dual manner? In practice I cannot see when this would be a good idea... |
Ok I see that in your proposal now. That's too bad. It makes complexity the default behavior. No reason to argue on this point, I know it's a lost cause.
I was referring to |
I have seen this before with express apps, while they create an express app and listen for incoming traffic, and exporting the app instance for test or for other more generic aggregation. Aside from that, I think about this as a reflective form of |
I guess that is convincing. OK, I'll leave it as-is. |
Would you consider restricting |
How? You mean in a validator or something?
What does that mean? Are you also proposing doing the same for
Why would an error message for |
Overall I believe your goal with the implicit
Detecting renderable elements in the HTML parser after the module tag does not seem very difficult. The approach most consistent with HTML's accept-all parsing strategy is for the error to result in the script tag emitting console error and failing to execute.
As you know,
The clearest way to express this execution order in a language like HTML is to place the tag at the bottom of the document. Using an attribute to express order was lame; making that lameness a default in some cases but not others does not create less lameness.
I am proposing that the
You and I both expect this feature to be widely adopted. Many naive developers will place |
Sorry one more quick comment: |
I don't see any benefits in making the source document order impact execution order, so I think we have a fundamental disagreement. |
Source document order affects execution order now, so we can't disagree about that. The only issue here is whether the script tag default for |
Just to play naive a bit here but since this is the first I've seen of a topic I and I'm sure many others have been waiting on, this doesn't restrict developers from doing They'll just bail out on the first import or export they find, why is this any different from any other new introduced syntax like arrow functions, get/sets, destructuring or future things like async? Developers already have to "cope" with that today and it'll always be a minor pain point.. (but we transpile and like any other web feature, watch adoption rates of capable browsers) so what in the future when the import/export spec is upgraded or we add more syntax, add another type for that? it seems like this is going down a route of becoming yet another weird web thing in a few years time. Thanks, |
Developers can do that, but the resulting file will be parsed as a legacy script, instead of a module script. Thus there will be a syntax error for the first import or export statement, and sloppy mode will be the implicit default, and top-level declarations will create global variables, a few other minor differences. I'm not sure if that's what you meant by "bail out". You can still use new ES features implemented in the browser you're programming against though.
Because it's not just new syntax: it's drastically new semantics, per the above list. It also has a greatly changed execution model, as it's impossible to execute before dependencies are loaded, so you can't execute in a blocking fashion as is done with legacy scripts.
There are no plans to add any new script types to JavaScript, although indeed other languages like wasm might want to do so. |
@johnjbarton it sounds like what you want to do is develop a validator or lint tool for your project's HTML that imposes your preferred ordering of scripts-after-elements, since you think it's important that source order reflect execution order. I don't plan to enforce that style in this spec. |
Please read what I wrote. I am not discussing style. I am objecting -- in as nice a way as I can ;-) -- to your proposal to change the default behavior of the |
I am not changing the default behavior of the script tag. I am introducing a new type of script, module scripts, in addition to the two existing ones---legacy script, which executes in a blocking fashion (potentially modified by boolean defer/async modifiers), and opaque script, which does not execute (and does not have boolean modifiers). Module scripts have their own separate third semantics, of executing after parsing is complete and all their dependencies are loaded and executed. (They also do not have boolean modifiers.) |
Thanks for the prompt reply @domenic, much appreciated, by bail out I meant older browsers would error rather than wrongly interpreting a modern script. I agree that there are big changes to how execution works when import and export are introduced, but I didn't think that would mean developers needed to tip off the browser when that was happening, from my understanding, import and export had to be top level and the first expressions within the source, as such I expect that script loading logic would change so the parser would only determine the execution model once it reaches the first expression, if it encountered an import (or export if that matters) then it would follow the newer rules of execution, otherwise the legacy method. I'm sure this has been discussed to death elsewhere and there is sound logic as to why, but just wanted to try understand. |
Yeah, developers definitely need to tip off the browser as to which type of script they are using. import and export do not need to be the first expressions in a module, and in fact do not need to appear in modules at all; you can still have a module that is used for its side effects, but gets the other benefits of modules like automatic strict mode and no global variables from top-level declarations. |
9fc8d00
to
9afdf40
Compare
This adds support for <script type="module"> for loading JavaScript modules, as well as all the infrastructure necessary for resolving, fetching, parsing, and evaluating module graphs rooted at such <script type="module">s. Some decisions encoded here include: - Always use the UTF-8 decoder, ignoring the charset="" attribute or the Content-Type header parameter. - Always use the "cors" fetch mode, and use the crossorigin="" attribute to decide between "omit"/"same-origin"/"include" for the credentials mode. - Require that the response be served with a JavaScript MIME type for its Content-Type header. - Use <script defer>-like semantics by default: do not execute until parsing is finished, and execute in order. The async="" attribute can be used to opt in to an "execute as soon as possible, in any order" semantic. Unlike for classic scripts, in no cases do we block further parsing or script execution until the module tree is finished loading. This applies to both inline and external scripts. - For module resolution, allow absolute URLs and anything that starts with "./", "../", or "/", with this latter set being interpreted as relative URLs. Everything else (e.g. bare specifiers like "jquery") fails for now. No automatic ".js" is appended. - Modules are memoized based on their request URL, whereas import specifiers inside the module are resolved based on the module's response URL. In the course of adding this functionality, large parts of script execution, fetching, and creation were refactored, including moving around some sections and reorganizing others. Conceptually, scripts are now either "classic scripts", "module scripts", or "data blocks". The result should generally be clearer and easier to follow.
9afdf40
to
cd1a9fb
Compare
Is there any way can feature detect |
This transposes some of the wisdom from #443 (comment) into the spec.
This transposes some of the wisdom from #443 (comment) into the spec.
@hax no need. Every browser that supports |
@graingert When I asked this question there was no |
Is it possible to import the module that has been defined in a <script type="module"> tag to another <script type="module"> tag in the same html file? Or to another JavaScript file that has been loaded by the html file? Also see following SO question: https://stackoverflow.com/questions/47982205/how-to-import-es6-module-that-has-been-defined-in-script-type-module-tag-ins |
The answer you got there seems correct. https://whatwg.org/faq#adding-new-features and https://whatwg.org/working-mode might be of interest if you wish to pursue the idea of making it possible somehow. |
This transposes some of the wisdom from whatwg#443 (comment) into the spec.
This adds support for
<script type="module">
for loading JavaScript modules, as well as all the infrastructure necessary for resolving, fetching, parsing, and evaluating module graphs rooted at such<script type="module">
s.This was spurred on by a request over in whatwg/loader#83 (comment). This should take care of whatwg/loader#83, whatwg/loader#84, and whatwg/loader#82. There is not much overlap with the current loader spec, which is primarily concerned with reflective modules and the author-customizable loading pipeline. This patch is much more about changing HTML's processing model for the script element, and integrating module execution the same way HTML currently integrates script execution. It deals with questions like "when do modules execute relative to HTML parsing" or "how does module fetching/parsing/evaluation integrate with the event loop".
This needs substantial review! Preferably from implementers! It is a complicated topic and I am sure I got some details wrong, in addition to the discussion points noted below.
Here are the decisions that I incorporated while speccing this, which might not be immediately obvious. Some are bolded to indicate they need discussion/help/bikeshedding.
X-Content-Type-Options: nosniff
behavior by default for module scripts.<script defer>
-like semantics: do not execute until parsing is finished, and execute in order. Theasync
attribute can be used to opt in to execute as soon as possible (but still no blocking).To make review easier, I am hosting a compiled version: singlepage, multipage. Sections of note (links go to singlepage):
<script>
section: largely contains authoring guidance updates. New diagram is also available, although the dropbox view doesn't show it inline since we are using absolute URLs.<script>
processing model section/cc @whatwg/loader @bterlson @ajklein @Constellation