-
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
[ECMA-262 layering] Refactor import-related host hooks #8253
[ECMA-262 layering] Refactor import-related host hooks #8253
Conversation
I am skeptical about this change, as the goal of moving graph traversal out of HTML's control and into ES-262's seems likely to be problematic for future extensions we might want to add on the host side. However, I do not have any concrete examples of problems it will cause in mind. Similarly, I am very skeptical about the three proposals you mention, so am unsure about doing work on this side to support them; but, perhaps that's something we can wait for later to figure out. Can you address how well your changes here would work with proposals such as #6768 and #7996? Similarly, would they allow #4400 or the path-not-taken for #4419? (The latter are not current problems, but knowing whether they would be possible within this framework would help put my mind at ease about whether we are losing important abilities via this change, or not.) |
Thank you for taking a look at this! Failed dynamic import should not always be cached (#6768)
Failure caching is only implemented in HTML, and this PR does not change how we can remove it. We can support retrying by replaciny step 6 of fetch a single module script with "If moduleMap[(url, moduleType)] exists and is not null, asynchronously complete this algorithm with moduleMap[url / moduleType], and return.". Editorial cleanups to fetching scripts (#7996) (I love this, it addresses all the doubts I initially had when reading these algorithms :) )
There are a few less places where we need to change this (with this PR there are 39 occurrences of "asynchronously complete" instead of 54 in webappapis.html), but except for that it remains the same task.
Are the "perform the fetch" step passes through to fetch the descendants of and link, for example in the last step of fetch an external module script graph?
We can/should still do it; it makes it clearer when we invoke Yielding to the event loop (in some way) between module evaluations (#4400)This refactor doesn't change "how much async" It is possible to "hack a solution" to that with this refactor (or even with the current spec), by replacing step 11 of HostLoadImportedModule with something like this:
to evaluate module even if the graph is still loading, just making sure that all its dependencies have already been loaded (11.ii.2). However, it feels "wrong" to hack the expected semantics like this (even if it's compliant to the hook requirements), and I would strongly suggest proposing a first-class mechanism to do it. path-not-taken for Top-level await integration and multiple script graphs (#4419)This is at a different level from this PR, and it touches different parts of the spec. Execute the script element would have to await the promise returned by run the module script instead of just ignoring it. I also took a look at other issues related to module loading:
Ultimately one goal of this refactor is to not change host capabilities, and the issues fixed by this PR could also be fixed with the existing layering hooks. I also tried a "minimal" alternative approach. Instead of deleting the "recurse into imported modules" algorithm from HTML,
This is more similar to the existing two hooks: use cases of That leaves most of the HTML algorithms intact, to better show that this refactor shouldn't change host capabilities. If you are interested, I can complete/rebase it and open a parallel PR. |
Thank you so much for the thorough, above-and-beyond analysis! This addresses my concerns. At this point I'm somewhat eager for the refactor to land, and no longer think we need to wait for a TC39 proposal that needs it.
Yes, that is the intent. (With the "is top-level" flag being used to allow branching behavior as necessary.) I guess we can make this work by using the [[HostDefined]] field of the ModuleLoadState Record? Speaking of that, I noticed that it says loadState is undefined for dynamic import(). Is that a problem? Is it something we could change in the future, if e.g. we wanted to change the fetch destination for dynamic import(), or change the "perform the fetch" steps?
Well, with the current spec, we could just choose not to call the top-level Evaluate(). Instead we would Evaluate() portions of the module graph, as the host determines would be best. This seems possible because the host has the graph-traversal power, so it can delay the scheduling of Evaluate() on various subgraphs as necessary. It sounds like maybe we have another mechanism for exerting that control, by basically inserting host-defined top-level awaits? Maybe that's enough; back when we first raised #4400, top-level await wasn't integrated, so that option wasn't available.
This is great news!
Fixing this would be a normative change, and thus require web platform tests, and (depending on what the tests show) possibly implementer agreement. So it might be best to hold off on that for now?
Oh, this is very helpful. No need to write up a full PR with this, but knowing that we have that option in our back pocket if necessary is really helpful. |
We need that LoadStateRecord.[[HostDefined]] record to differentiate between "the current graph loading process" (static imports) and "future graph loading processes" (dynamic imports). Each loading process has comes from a separate LoadRequestedModules call, and thus can have its own loading-process-specific data. If we wanted to pass data from the referrer module to the imported module, regardless of when its imported, then we can store it on the module script struct, which HostLoadImportedModule can access using referrer.[[HostDefined]] (this was my original attempt, until I realized that the destination for dynamic import is always
Ok, I'll keep it for a future PR. It's fun that with this refactor is easier to fix it than to not, and I'll have to explicitly unset the referrer for dynamic imports :) |
8466b7f
to
e82302f
Compare
@domenic I have rebased this on top of the asynchronous completions removal, fixed the referrer handling to restore the old behavior and properly fixed the "perform the fetch" steps propagation. I plan to continue working on #7996 (which obviously has conflicts with this PR), but I would still love a review of this one in parallel. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor drive-by nits, deferring to @domenic for overall review.
source
Outdated
@@ -2891,6 +2893,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute | |||
<li>The <dfn data-x-href="https://tc39.es/ecma262/#sec-detacharraybuffer">DetachArrayBuffer</dfn> abstract operation</li> | |||
<li>The <dfn data-x-href="https://tc39.es/ecma262/#sec-enumerableownproperties">EnumerableOwnProperties</dfn> abstract operation</li> | |||
<li>The <dfn data-x-href="https://tc39.es/ecma262/#sec-finishdynamicimport">FinishDynamicImport</dfn> abstract operation</li> | |||
<li>The <dfn data-x-href="https://ci.tc39.es/preview/tc39/ecma262/pull/2905/#sec-FinishLoadImportedModule">FinishLoadImportedModule</dfn> abstract operation</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the plan to land this with these kind of links? Or will we wait for the JS PR to land and then adjust this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can land the JS PR first and then adjust the links here, but the JS PR is blocked on this one being reviewed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just at a diff level, right? Because to merge both PRs we can do
- If referrer is a Script Record or a Module Record, let referencing script be referrer.[[HostDefined]]
- Else, let referencing script be null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll mark "do not merge yet" blocking on landing the JS PR first. I didn't realize this needed review; will do that now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks good. I think we have some editorial work to do, which we'll probably make harder for ourselves due to the other concurrent module-related PRs. But I have no general objections.
source
Outdated
<li>The <dfn data-x="js-HostMakeJobCallback" data-x-href="https://tc39.es/ecma262/#sec-hostmakejobcallback">HostMakeJobCallback</dfn> abstract operation</li> | ||
<li>The <dfn data-x="js-HostPromiseRejectionTracker" data-x-href="https://tc39.es/ecma262/#sec-host-promise-rejection-tracker">HostPromiseRejectionTracker</dfn> abstract operation</li> | ||
<li>The <dfn data-x="js-HostResolveImportedModule" data-x-href="https://tc39.es/proposal-import-assertions/#sec-hostresolveimportedmodule">HostResolveImportedModule</dfn> abstract operation</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, will proposal-import-assertions be updated at the same time? We had readers confused recently in #8262 by the HTML spec not referring to a version of HostResolveImportedModule that worked well with import assertions; I hope we can avoid regressing on that.
source
Outdated
@@ -2891,6 +2893,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute | |||
<li>The <dfn data-x-href="https://tc39.es/ecma262/#sec-detacharraybuffer">DetachArrayBuffer</dfn> abstract operation</li> | |||
<li>The <dfn data-x-href="https://tc39.es/ecma262/#sec-enumerableownproperties">EnumerableOwnProperties</dfn> abstract operation</li> | |||
<li>The <dfn data-x-href="https://tc39.es/ecma262/#sec-finishdynamicimport">FinishDynamicImport</dfn> abstract operation</li> | |||
<li>The <dfn data-x-href="https://ci.tc39.es/preview/tc39/ecma262/pull/2905/#sec-FinishLoadImportedModule">FinishLoadImportedModule</dfn> abstract operation</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll mark "do not merge yet" blocking on landing the JS PR first. I didn't realize this needed review; will do that now.
source
Outdated
statements encountered throughout the graph.</p> | ||
graph</span>, or <span data-x="fetch a module worker script graph">fetching a module worker script | ||
graph</span>, but not for the fetches resulting from <code data-x="">import</code> statements | ||
encountered throughout the graph or from <code data-x="">import()</code> expressions.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this change is fine, since the two consumers of "is top-level" (modulepreload and service workers) don't care about whether dynamic import() triggers that code path or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, that flag only has effect if:
- the destination is
"worker"
or"serviceworker"
, but for dynamic imports it's always"script"
, or - there are custom perform the fetch steps, but they are never present for dynamic imports
fde337a
to
e03cd6b
Compare
Thanks for the review! I plan to prepare a PR in the import assertions proposal (edit: tc39/proposal-import-attributes#124), but I don't know how long it will take to get merged. I added a |
8848e95
to
9f95806
Compare
I rebased this on top of the import maps changes, hopefully I didn't miss anything. While doing so, I found a bug in the workaround to preserve #3744: instead of checking if the current import was directly caused by |
2f8fa0d
to
8eb8c6b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with nits. I would prefer if import assertions were merged into the JS spec as close as possible to your refactoring, to avoid the temporary confusion. But this is reasonable for now.
source
Outdated
@@ -93502,7 +93445,7 @@ document.querySelector("button").addEventListener("click", bound); | |||
<p>This diagram illustrates how these algorithms relate to the ones above, as well as to each | |||
other:</p> | |||
|
|||
<svg id="module-script-fetching-diagram" viewBox="0 0 1131 256" style="width: 100%;" role="img" aria-label="Fetch an external module script, fetch an import() module script graph, fetch a modulepreload module script graph, fetch an inline module script graph, and fetch a module worker script graph all call fetch the descendants of and link a module script. It, in turn, calls fetch the descendants of a module script, which then calls the internal module script graph fetching procedure. Finally, the internal module script graph fetching procedure loops back to call fetch the descendants of a module script."> | |||
<svg id="module-script-fetching-diagram" viewBox="0 0 941 256" style="width: 80%; max-width: 1024px" role="img" aria-label="Fetch an external module script, fetch a modulepreload module script graph, fetch an inline module script graph, and fetch a module worker script graph all call fetch the descendants of and link a module script."> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why change the style=""
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the image got smaller, width: 100%
was zooming it a lot making the text in the image way bigger than the other text in the document. width: 80%
was my first attempt to make it smaller and I forgot to put it back at 100%
; it's now width: 100%; max-width: 1024px
.
source
Outdated
<ol> | ||
<li><p>If <var>moduleScript</var> is null, then let <var>completion</var> be the | ||
<span>Completion Record</span> { [[Type]]: throw, [[Value]]: a new <code>TypeError</code>, | ||
[[Target]]: empty }.</p></li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We try to treat "let" as block-scoped, so it's not great to declare them inside ifs (even single-line ifs). So maybe make substep 1 establish the default (currently set on step 3), and then the other substeps can be changed from "let" to "set".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up initializing it as null
and keep the Completion Record creations where they are, it felt strange to initialize it to a record representing success and then later be like "oh actually, it was an error".
37134c2
to
2a4e97e
Compare
37cf6c1
to
27e6845
Compare
The ECMA-262 side of this refactor has been merged, I'll rebase this PR now! |
The the top-level module fetch is only used in two steps of "fetch a single module script": - Step 9, where it only has effect if descrination is "worker", "sharedworker" or "serviceworker" (but for dynamic imports it's always "script") - Step 11, where it only has effect if there are custom "perform the fetch" steps, but "fetch an import() module script graph" is never called with custom "perform the fetch" steps.
* Implement `HostLoadImportedModule` hook. This commit also removes the `HostImportModuleDynamically` and `HostResolveImportedModule` hooks. * Differentiate fetch referrer between static and dynamic imports * Propagate custom perform the fetch steps through LoadRequestedModules * Review by annevk * Review by domenic
27e6845
to
90672d3
Compare
Note: tc39/proposal-import-attributes#124 has not been merged yet, but the HTML implementation of |
Hi!
I plan to propose a refactor to the import-related host hooks of ECMA-262, and this PR shows how it would affect HTML.
Currently, ECMA-262 has two host hooks:
HostResolveImportedModule(referrer, specifier)
, which synchronously returns the Module Record corresponding to the(referrer, specifier)
pair;HostImportModuleDynamically(referrer, specifier)
, used for dynamicimport()
, which asynchronously loads the Module Record corresponding to(referrer, specifier)
and all its dependencies, calls.Link()
/.Evaluate()
on it and then "returns" to ECMA-262 by callingFinishDynamicImport
.We are working on three ECMA-262 proposals related to modules, and each one of them has needs that are not satisfied by the existing layering:
Instead of adding multiple new host hooks and duplicating the graph fetching algorithm, we can:
HostLoadImportedModule(referrer, specifier)
, that loads a single Module Record and can copmlete asynchronously: it's an async version ofHostResolveImportedModule
.HostLoadImportedModule
, which is called recursively for each dependency.Module Records will expose a third method, other than
.Link()
and.Evaluate()
:.LoadRequestedModules()
, which is the one responsible of callingHostLoadImportedModule
.The host algorithm to load/execute a top-level module (for example, using a
<script type="module">
tag) will now look like this.module.LoadRequestedModules()
:HostLoadImportedModule(module, specifier)
:module.Link()
module.Evaluate()
The algorithm for dynamic import will look like this.
HostLoadImportedModule(referrer, specifier)
:module.LoadRequestedModules()
:HostLoadImportedModule(module, specifier)
:module.Link()
module.Evaluate()
For the Module Blocks proposal, the algorithm to import a module block will look like the above dynamic import algorithm, except that we skip step 1.
For the Import Reflection proposal, the algorithm to load an uninstantiated JS module will just call
HostLoadImportedModule
without then calling the various Module Record methods.For the Compartments proposal, we will be able to use the new graph loading algorithm by just replacing calls to
HostLoadImportedModule
with calls to an user-provided function.Some more precise remarks about these HTML changes
The only observable change (observable from HTML, rather than users) is that when you import the same specifier twice from the same file, only the first one causes a call toHostLoadImportedModule
(unless the first one fails):In practice this doesn't change much, becauseHostImportModuleDynamically
/HostResolveImportedModule
were already required to return the same Module Record both times.We still call the host hook multiple times if the first dynamic import fails, because there was an explicit goal of allowing to retry failed dynamic imports (Normative: change idempotency for HostImportModuleDynamically tc39/ecma262#1645).EDIT: For now I reverted this change, since even if it's not observable by HTML it's observable in other places.
The first commit is a small refactor only within HTML (it doesn't affect layering) that makes it easier to move the graph traversal to ECMA-262. I believe that it should not have observable effects (I wrote why in the commit description), but if it does I'll find an alternative solution.
I had to add an
hostDefined
parameter toLoadRequestedModules
, which ECMA-262 passes as-is toHostLoadImportedModule
, so that HTML can propagate the correct requestdestination
. I couldn't just store the destination on the referrer Module Record's[[HostDefined]]
slot because it should only be propagated through static imports and not dynamic ones.On the ECMA-262 side, this refactor doesn't take into account import assertions, because they have not been merged to the main spec yet. I wrote these HTML changes assuming that
HostLoadImportedModule
will receive a{ [[Specifier]], [[Assertions]] }
record instead of just the specifier, similarly to how the import assertions proposal updatedHostResolveImportedModule
to receive{ [[Specifier]], [[Assertions]] }
.Links
/index.html ( diff )
/infrastructure.html ( diff )
/webappapis.html ( diff )