- From: Ian Hickson <ian@hixie.ch>
- Date: Sat, 23 Aug 2014 00:44:23 +0000 (UTC)
- To: "whatwg@whatwg.org" <whatwg@whatwg.org>
When I last looked at preloading scripts, it was pointed out to me that the Web Perf WG had some work ongoing in this space, so I decided to let them take care of it. However, nothing much seems to be happening there, and so I'm looking at this again. It's also been pointed out that limiting this to just scripts preloading maybe taking too narrow a view, and that we should also consider preloading and deferring loading of other resources, such as images, style sheets, and so forth. I will start by looking at use cases again, then go through some of the feedback on the last proposal I'd made in this space, and then make a new proposal intended to address the use cases and feedback. On Fri, 15 Aug 2014, Ben Maurer wrote: > > [Use-case F:] A website has a page where media is the primary content. > It would like to make sure that media is downloaded before JS [e.g. > flickr, youtube, facebook photos] > > [Use-case G:] A website knows there's a piece of Javascript code that > the user might need if they click on a part of the page. The developer > would like to have the user download it, but not at the expense of other > resources. > > [Use-case H:] A website is prefetching photos in a photo album and would > like to make sure these images are lower priority than images the user > is actually viewing. On Mon, 28 Jul 2014, Ben Maurer wrote: > > [Use-case I:] Facebook uses an automated pipeline to decide what CSS/JS > files to package together. If we could pass a custom header in the > request for a CSS file (for example: what part of the page caused that > file to be loaded) we could use this information in our packaging system > to make smarter decisions. On Tue, 22 Jul 2014, Ben Maurer wrote: > > [Use-case J:] The way we render pages at Facebook [is essentially that > we] flush sections of HTML with a list of CSS and JS dependencies for > each section. The HTML is only rendered once these resources are loaded. > [Use-case K:] We have a few other use cases for this type of dependency > (for example, we have a method where you can say "call this callback > once resources X, Y and Z are requested"). On Tue, 3 Sep 2013, Ryosuke Niwa wrote: > > [Use-case L:] A web page wants to load and execute a script widget.js if > the script is already cached in the browser. However, it wants to load > other essential assets such as images first if it's not already in the > cache except as long as the user had not started interacting with the > parts of the page that require widget.js. On Tue, 18 Mar 2014, Jonas Sicking wrote: > > [Use-case M:] Being able to specify that an image/video should only be > downloaded "on demand" (aka "lazily"), i.e. when it's in view, or about > to be in view. Use case is both to lower bandwidth in cases of long > pages where the user doesn't always scroll to the bottom, as well as > make sure to fill the network pipe with the most important resources > first. > [Use-case N:] Being able to specify that a stylesheet should not block > rendering. Use case is to preload stylesheets that will be used by > content that isn't rendered in the initial view, but that might be > rendered later. > [Use-case O:] Being able to specify some form of prioritization for > resources like (non-blocking) stylesheets, (non-blocking) <script>s, > images, video, iframes etc. Use case is making sure to fill the network > pipe with the most important resources first. Possibly a simple > prioritization like "needed for initial rendering/not needed for initial > rendering" is enough, possibly there are needs for more finegrained > prioritization like "needed for initial rendering/needed for page > feature X that is commonly used, needed for other". On Fri, 30 Aug 2013, Yoav Weiss wrote: > > [Use-case P:] download dynamic page components (e.g. maps) only on > larger devices. On Wed, 10 Jul 2013, Kyle Simpson wrote: > > [Use-case Q:] I am dynamically loading one of those social widgets that, > upon load, automatically scans a page and renders social buttons. I need > to be able to preload that script so it's ready to execute, but decide > when I want it to run against the page. I don't want to wait for true > on-demand loading, like when my user clicks a button, because of the > loading delay that will be visible to the user, so I want to pre-load > that script and have it waiting, ready at a moment's notice to say "it's > ok to execute, do it now! now! now!". > [Use-case S:] One CMS plugin wants to load "A.js" and "B.js", where B > relies on A. Both need to load in parallel (for performance), but A must > execute before B executes. I don't control A and B, so changing them is > not an option. This CMS plugin [wants] to wait for some > user-interaction, such as a button click, before executing the code. We > don't want there to be any big network-loading delay visible to the user > between their click of the button and the running of that plugin's code. > > Another CMS plugin on this same page wants to load "A.js", "C.js", and > "D.js". This plugin doesn't know or care that the other plugin also > requests "A.js". It doesn't know if there is a script element in the > page requesting it or not, and it doesn't want to looking for it. It > just wants to ask for A as a pre-requisite to C and D. But C and D have > no dependency on each other, only a shared dependency on A. C and D > should be free to run ASAP (in whichever order), assuming that A has > already run [once] some user-interaction that initiates the load of A, > C, and D. This user interaction may be before the other plugin requested > A, or after it requested A. > > "A.js" can be requested relatively (via a <base> tag or just relative to > document root) or absolutely, or it might be requested with the > leading-// protocol-relative from, taking on whatever http or https > protocol the page has, whereas other references to it may specify the > prototcol. > > These plugins can't guarantee what ID's or classes they use are reliably > unique without undue processing burden. > [I've trimmed the text Kyle wrote here, but also implicit in his > description, as I understood it, was that A.js should only run once even > if both plugins tried to load it.] > [Use-case T:] I have two different calendar widgets. I want to pop one > of them up when a user clicks a button. The user may never click the > button, in which case I don't want the calendar widget to have ever > executed to render. [...] > > It'd be nice if both calendar widgets were built sensibly so that > loading the code didn't automatically render. One of them IS, the other > is unfortunately mis-behaving, and it will render itself as soon as its > code is run. [...] > > Furthermore, these two widgets are not "equal". Perhaps one is better > for smaller browser window sizes, and the other is better for larger > browser windows. [...] > > Regardless, the point is, there's run-time conditions which are going to > determine if I want to execute calendar widget A or B, or maybe I never > execute either. But I want them both preloaded and ready to go, just in > case, so that if the user DOES need one, it's free and ready to execute > with nearly no delay, instead of having to wait to request as I would > with only on-demand techniques. > [Use-case U:] I have a set of script "A.js", "B.js", and "C.js". B > relies on A, and C relies on B. So they need to execute strictly in that > order. [Now], imagine they progressively render different parts of a > widget. [...] I only want to execute A, B and C once all 3 are preloaded > and ready to go. It's [...] about minimizing delays between them, for > performance PERCEPTION. > > [For example, one of them might start playing a video, and another might > introduce the <canvas> slides for that video. You want all of the > relevant scripts to be run at once, so there's no point where the page > has a <video> element but doesn't have the <canvas>.] On Thu, 11 Jul 2013, Kyle Simpson wrote: > > [Use-case V:] you have a string of scripts ("A.js", "B.js", and "C.js") > loading which constitute a dependency chain. A must run before B, which > must run before C. However, if you detect an error in loading, you stop > the rest of the executions (and preferably loading too!), since clearly > dependencies will fail for further scripts, and the errors will just > unnecessarily clutter the developer console log making it harder to > debug. > [Use-case W:] some developers have even requested to be able to stop the > chain and prevent further executions if the script loads, but there's > some compile-time syntax error or run-time error that happens during the > execution. For them, it's not enough for B to simply finish loading > successfully, but that it must fully execute without error. On Sun, 14 Jul 2013, Kornel Lesi¨½ski wrote (trimmed): > > [Use-case X:] not all dependencies are JS files, e.g. authors use > plugins to load template files, JSON, images, etc. > > [Use-case Y:] not all dependencies are usefully satisfied immediately > after their JS file is loaded, e.g. some libraries may need asynchronous > initialization. > > [Use-case Z:] Another common kind of dependency scripts have is presence > of certain element in the DOM, e.g. `dropdown-menu.js` may require `<nav > id="menu">` to be in the document _and_ have its content fully parsed > before the script can run. On Tue, 27 Aug 2013, Ian Hickson wrote: > > Jake also mentioned these requirements: > > | - Provides an adoption path for browsers that don't support the new > | feature (happy for the fallback to be blocking document-order > | execution) > | - Is discoverable by pre-parsers (so async=false and old-IE's > | readystate methods aren't enough) > > And Kyle mentioned this scenario that we need to handle as well (not > strictly a use case, more a variant on the above use cases): > > > I want to preload a script which is hosted somewhere that I don't > > control caching headers, and to my dismay, I discover that they are > > serving the script with incorrect/busted/missing caching headers. > Incidentally, I don't think the use case is or should be "make it > possible to write a performant script loading library". I think the use > case should be more like "make it unnecessary to ever write a script > loading library". Bearing those use cases and requirements in mind, here's the feedback received since the last proposal: On Thu, 29 Aug 2013, Jake Archibald wrote: > On 27 August 2013 22:55, Ian Hickson <ian@hixie.ch> wrote: > > On Tue, 9 Jul 2013, Bruno Racineux wrote: > > > > > > I would also strongly favor restoring the previous spec portion of > > > 'defer' which allow to have defer on inline script blocks (i.e. if > > > the src attribute is not present). I don't know why this html4 > > > functionality was removed from html5? > > > > Well, primarily because basically nobody implemented it, but also, > > because it's not clear what the point is. Why would you need it? > > I like my scripts to be inert, as in have no impact on the page (like > jquery). I use a small inline script to start the work needed for that > page, meaning I know the starting point for all JS interacting with the > page is somewhere in an inline script. This is much easier to maintain > than scripts that trigger themselves ondomready depending on what they > see in the DOM. I agree entirely. But how does this impact defer=""? If the script has no effect, it really don't matter when it's run. Or are you saying you want an inline script to be deferred, so that it runs after all the external deferred scripts? Given legacy implementations and back-compat needs I doubt we could do this with defer="" at this point, but: > In your proposal, does this work?¡ > > <script src="whatever.js" whenneeded></script> > ... > <script needs="whatever.js"> > activateTheAlmightyWebsockets(); > </script> Yeah, we should support that, certainly. > > On Wed, 10 Jul 2013, Jake Archibald wrote: > > > > > > If "dependencies" took a CSS selector it could be: > > > > > > <script dependencies=".cms-core" src="cmd-plugin.js"></script> > > > > > > Now the number of scripts with class "cms-core" can change between > > > versions of the CMS but the plugin still waits for them all. No ID > > > generation needed. > > > > Using a selector is an interesting idea. > > > > It makes it harder to detect and prevent loops, but not fatally so. > > I'm not sure it's possible to get into loops with this. I imagined > dependency resolution to happen once, on element creation or adding to > document (whichever happens latest). So with: > > <script src="a.js" needs="script[src=b.js]"></script> > <script src="b.js" needs="script[src=a.js]"></script> > > ¡the first script would have zero dependencies, because the selector > matches zero elements. The second would depend on the first, so the > execution order is a.js, b.js. The thing I like about the selector thing > is you can very easily get (almost) async=false behaviour: > > <script src="a.js" needs="script"></script> > <script src="b.js" needs="script"></script> > <script src="c.js" needs="script"></script> Ah. A one-off selector matching seems less interesting. With real-time matching, you could do things like: <script src="fancytextarea.js" needs="textarea"></script> ...and have it get invoked once a textarea appeared, or some such. It would depend on exactly how the semantics were set up, I guess. > execute() should return a promise that resolves when the script > successfully downloads and executes. I guess we could do that. It's not clear to me what the benefit is. None of the use cases particularly seem to need it. > Also, should there be an event on the script element when the script has > downloaded, but not executed (like IE used to have)? Similarly here. What's the use case? > > <script> elements also get a new needs="" attribute, which takes a list > > of URLs. A <script> won't run (even if you call execute()) until all the > > <script src=""> elements referenced by its needs="" attribute are > > themselves ready to run. For example: > > > > <script src="bbbbb.js" needs="aaaaa.js"></script> > > <script src="aaaaa.js" async></script> > > > > It seems weird to refer to "needs" by url, but still require script > elements for those needs, I'd rather use IDs (or selectors yey!). IDs require more markup to do the same thing: <script src="bbbbb.js" needs="a"></script> <script src="aaaaa.js" id="a" async></script> Selectors require more markup or more complex selectors: <script src="bbbbb.js" needs="#a"></script> <script src="aaaaa.js" id="a" async></script> <script src="bbbbb.js" needs="script[src='aaaaa.js']"></script> <script src="aaaaa.js" async></script> Not clear to me that either are a win. Using URLs has the big advantage that it means you can do this logic across import documents, too, which will become more interesting when Web components are used more. Having said that, IDs and Selectors do have a few of advantages that aren't obvious in the above code: they let you reference elements other than scripts, and they avoid one confusing aspect of URLs which is that people interpret the following as actually executing a.js even if there's no <script> that mentions a.js in its src="": <script src="b.js" needs="a.js"></script> Also, IDs mean you can have dependencies on local script blocks. One _disadvantage_ of IDs, though, is that they don't let us dedupe multiple scripts with the same URL as easily. Though maybe the solution there is for there to be some attribute (e.g. whenneeded="") that applies a new rule: if this <script> refers to a resource already mentioned by another <script>, then ignore this <script>, or make references to this <script> defer to the previous one. Then again, for scripts, maybe we can just tell people to use modules? One thing worth bearing in mind is that if we use a different syntax for "import" in ES6 modules than for needs="" (or whatever we call it) in HTML, then you won't be able to simply promote "import" to the attribute, you'll have to manually figure out what it means. > Also, I'm not sure we need to deal with the case above, where the script > with the dependency appears in the DOM prior to the script it depends > on. Avoiding this avoids the circular dependency problem (see my > selector example above) Circular dependencies are relatively easy to break. Not having requirements on order means that you can import multiple documents that each invoke script, and not have to worry about getting the order exactly right. > Does a script with "needs" but not "async" block rendering? I hope not. needs="" in this proposal implies async-ish behaviour. We want to move away from anything that blocks. On Thu, 29 Aug 2013, Glenn Maynard wrote: > > > > <script whenneeded="jit"> is a special mode where instead of running > > once the script's dependencies are met, it additionally waits until > > all the scripts that depend on _it_ are ready to run. ("Just in time" > > execution.) (The default is whenneeded=asap, "as soon as possible" > > execution.) > > This mode seems to be specifically for [use case U.] > > This one seems uncommon, and less like a dependency use case than the > others. How often is this wanted? Is it too inconvenient to just mark > them all @whenneeded, and say something like: > > document.querySelector("#C").execute(function() { > A.render(); > B.render(); > C.render(); > }); > > That does require the modules render in a function, and not when the > script is first executed. I don't know how much of a burden that is for > this case. If the scripts were able to expose an API like this, much of the work we're doing here would be somewhat trivial. My impression is that we're primarily talking about scripts from multiple third-party vendors that aren't designed to interact together. There's an example in the use case description, of a script that shows a video and a separate script that shows slides synchronised with the video, where one inserts a <video> and the other a <canvas>, and you want both to happen together so that you never have one without the other. (For a similar reason, I think it's probably not sufficient to just provide JS modules as the solution to all these use cases. While modules will certainly eventually become widespread, it will be many years before all the libraries someone might want to use are in module form. The module loader API allows authors to write scripts that work around this to some extent, but that's probably above the skill level for many authors, who just want to be able to include a script that was written by someone else, and follow some copy/paste instructions for hooking the script up to the relevant parts of their page.) > Alternatively, if an event is fired when a script's dependencies have > been met, then you could mark all three scripts @whenneeded, and call > (#C).execute() once C's dependencies have been met. I don't understand how this would work. Wouldn't this execute the dependencies before C got loaded? The idea here is to download everything first, then run it all at once. > Will a @defer dependency effectively defer all scripts that depend on > it? I think, as proposed, it would. However, that's somewhat moot; if you have a needs="" attribute you're going to be loaded async regardless. On Fri, 30 Aug 2013, Ryosuke Niwa wrote: > > > > <script> gets a pair of methods, incDependencies() and > > decDependencies(), that increase and decrease the dependency count by > > one, respectively decDependencies() throws if called when the count is > > zero. If decDependencies() is called and it reduces the number to > > zero, > > I strongly oppose to adding incDependencies/decDependencies. We try not > to add those pairwise functions as much as possible our C++ code because > it's really hard to always pair function calls. On Thu, 29 Aug 2013, Glenn Maynard wrote: > > incDependencies() and decDependencies() may be hard to debug, since if > somebody messes up the counter, it's hard to tell whose fault it is. A > named interface could help with this: script.addDependency("thing"); /* > script.dependencies is now ["thing"] */ > script.removeDependency("thing"); That's better than a count, true. Jake Archibald suggested using promises -- the API could accept a promise, and should then delay until all the promises are resolved. Using promises like that is probably the easiest to not screw up. On Fri, 30 Aug 2013, Glenn Maynard wrote: > > I don't like the name "jit", because it already has a different meaning > when talking about scripting. If this was for CSS or WebVTT or > something else other than scripts, it wouldn't be as bad... Seems reasonable. On Tue, 3 Sep 2013, Ryosuke Niwa wrote: > > [...] in order for a dependency specified in "needs" to be satisfied, > "src" content attribute of the dependent script needs to match the value > when the script finished running. e.g. myscript.src = null leaves any > dependency on myscript unsatisfied. That seems reasonable. It would be a way to signal a load failure. I'm not sure how compatible it would be with the ES6 loader though. Once you're executing code, you're pretty locked-in in terms of what the dependencies are. We could maybe just consider the dependency to have failed if a script throws while being evaluated, though. That would match how it works for new ES6 module scripts. > Also make "needs" IDL property take in any HTML element; e.g. adding an > image element to "needs" makes the script wait until the corresponding > image resource is loaded. Good idea. In fact, rather than passing just an element, it seems even more powerful to be able to pass a promise, and to have elements that have a pending load to have a .loaded attributes that returns a promise. This would then integrate with Jake's idea of using promises to replace the dependency API mentioned above. The only problem is that it cuts off the UA from notifying the source of the promise that the resource is going to be needed, in the cases where we're delaying resources until needed. On Wed, 2 Oct 2013, Bruno Racineux wrote: > > I agree on the verbosity with the double markup. It feels redundant. But > perhaps so is the repeat of the script name with 'needs="A.js"' for > every script that would need a dependency. Well, presumably we need _some_ markup to indicate what a script depends on. I don't really know what else it would look like that would be less verbose than just giving the URL. We _could_ put this information in the script itself, as in modules, but then you have to fetch the file to know which file to fetch next, which wouldn't address all the use cases. > But if recompiling or looking up script+chars pairs are much slower than > deserializing a compiled representation of an entire scripts, it might > be worth considering down the road... Well, again, nothing is stopping browsers from doing this today. > >> I would also strongly favor restoring the previous spec portion of > >> 'defer' which allow to have defer on inline script blocks (i.e. if > >> the src attribute is not present). > > [...because] many inline functions have no need to run before > 'interactive' fires. A delay or 'promise' at this stage is too early and > rarely makes sense. Currently most common domReady hooks rely on > DOMContentLoaded. As such I see no very good reason to run such plugin > code prior to DOMContentLoaded. > > e.g I want to defer jQuery and I want my small inline script events > (based on the context of a page) parsed and run post jQuery, right after > DOMContentLoaded fires. That priority level is something that cannot be > done right now. Would it be sufficient to have the inline script depend on the jQuery script using needs=""? > Also something that makes me cringe, is the verbose repeat of long paths > (just like srcset does) Well we either have to repeat something, or do it from script. I toyed above with using IDs or selectors, and it's at least as bad as paths, IMHO. The main disadvantage of URLs is that it only lets us do scripts. I discuss this more after the feedback, below. >From script, there's basically two approaches that make sense, I think. We could add dependencies using promises, and we could have some elements expose an interface of sorts specifically for this purpose. The advantage of the latter is that it would let the browser support "whenneeded" semantics, loading images and style sheets only when a script needs them. > If you need dependencies, then how about something shorter like a > dependency Index, with a 'preload' semantic on top of the > lazyload+postpone proposal, like: > > <script src="jquery.js" preload branch="A1"> > <script src="plugin1.js" preload branch="A2"> > > <script src="plugin2.js" preload branch="A2"> > > <script> > E('plugin2.js'); > </script> > > i.e. The browser would takes the letter as indicator of a dependency > tree branch, and follow along it's priority number to determine the > dependencies to load when-needed. Or perhaps using the id attribute with > the letter+number syntax form. That seems rather convoluted. It's also hard to use this kind of scheme when multiple authors are sharing a page without knowing about each other (e.g. when you import two unrelated libraries). > My worry with [the concept of 'dependency' attributes and relying or > asking to the browser to handle it through markup], is a performance > caveat and potential increasing bloat in pages, vs properly isolating > what's needed on a per page basis. Platform like wordpress or drupal, or > poorly written plugins in any platform, already too often add scripts > globally to all pages, making pages an increasing piece of FUBAR script > bloat. With the 'whenneeded' concept, it can become quite tempting to > put many many scripts in the DOM without any guilt, or any sensible > notion of wether it's actually needed or not for a particular page. Fundamentally, any system that allows authors to load things on demand is going to enable this kind of behaviour. I don't think we should let that stop us from providing it. The cost of a few imports that don't get fetched isn't going to have that substantial an effect. On Thu, 29 Aug 2013, Brian Kardell wrote: > > "@slicknet: @Hixie @paul_irish this proposal conflates preloading with > dependency management. I would ignore the latter (as I did originally)." > > "@paul_irish: @slicknet @Hixie I feel similarly. Adding HTML semantics > for dep mgmt duplicates the work of AMD/CJS and ES6 modules. > #writingemailishard :)" > > "@_JamesMGreene: @paul_irish @slicknet @Hixie Agreed, adding script > dependency management in HTML would be complicated, messy, and verbose." > > Me: what they said. Well, the idea is not to add something redundant with JS modules; the idea is to very much integrate with JS modules. However, JS modules alone don't solve many of the use cases described at the top of this e-mail. It's no more complicated, messy, and verbose to have HTML support these use cases natively than it would be to require that authors write scripts to support them, IMHO (indeed I'd say it's a lot less messy). Do you think we should just not support some of the use cases in the list above? On Thu, 29 Aug 2013, Nicholas Zakas wrote: > > When Kyle and I originally started pushing for a way to preload > JavaScript many moons ago, the intent was very simple: to allow the > downloading of JavaScript and execution of JavaScript to be separate. > The idea being that you should be able to preload scripts that you'll > need later without incurring the cost of parsing and execution at that > point in time. There are many examples of people doing this, the most > famous being the Gmail mobile approach of loading JavaScript in comments > and then pulling that code out and eval()ing it. > > I still feel very strongly that this pattern is a necessary evolution of > how we should be able to load scripts into web pages. I just want a flag > that says "don't execute this now" and a method to say "okay, execute > this now". Allowing that flag to be set both in HTML and JavaScript is > ideal. This is what "whenneeded" and "execute()" provide, in the proposal from earlier in this thread. > The question of dependency management is, in my mind, a separate issue > and one that doesn't belong in this layer of the web platform. HTML > isn't the right spot for a dependency tree to be defined for scripts (or > anything else). To me, that is a problem to be solved within the > ECMAScript world much the way CSS has @import available from within CSS > code. What about dependencies on resources that aren't scripts? > I think the use cases other than the initial one (preload/execute later) > are best relegated to script loaders and are very tied to a current way > of thinking about loading JavaScript. Surely our goal should be to make script loaders unnecessary. > I'd rather provide a simple, low-level piece of functionality that make > the job of script loaders easier by providing a reliable API and then > let the dependency management use cases be addressed outside of HTML. Can you elaborate on why? It seems like having the browser be able to manage the dependencies across scripts and other resources, and manage the loading of those resources, would let the browser much better manage the network. There's no way for an author-provided script loader or other resource manager to know when other tabs are done loading critical resources so that non-critical resources can be preloaded, for example. > Other random thoughts: > > * "whenneeded" is a very strange name for that attribute. It doesn't > really tell me anything, as opposed to "preload", "noexecute", or > "future". How do I know when it will be needed? Yeah, whenneeded="" is a poor name. What we really need is a way to give the element's load policy -- should it precache or not, should it load asap or be deferred... For some elements, like scripts or style sheets, we might also want to provide the author control over whether the element should block subsequent scripts or be asynchronously loaded and applied. On Thu, 29 Aug 2013, Ryosuke Niwa wrote: > > To put it another way, I don't see why anyone wants to load a script and > not execute it other than for the purpose of avoiding the network > request at a later time. However, if that were the main purpose of > providing such a functionality, then we also need to address the issue > of this load request needing to have a lower priority than other load > requests that are vital for the page. In fact, we might want to avoid > sending the request of a script file altogether if the user isn't going > to interact the parts of the page that needs such a script. Right. That's what many of the use cases described above are about, which is how a dependency scheme ended up being part of the proposal in the first place. On Wed, 4 Sep 2013, William Chan (³ÂÖDzý) wrote: > > * <link rel=subresource> is great for resource discovery. Given the > above observation, note that it has some deficiencies. Most obviously, > it does not indicate the resource type. Browsers today can heuristically > assign a priority based on the resource type (script/image/stylesheet/ > etc). Arguably, browsers could just use the filename extension as a hint > to the resource type, and that'd get us most of the way there. In any > case, Chromium, when it encounters <link rel=subresource> is going to > assign the resource load the lowest priority level, and only when the > parser encounters the actual resource via a <script> tag or something, > will another resource load be issued with the "appropriate" priority. > Almost all modern browsers will hold back low priority resource loads > before first paint in order to get critical scripts and stylesheets in > <head> ASAP without contention. Anything marked with <link > rel=subresource> will be considered low priority and in all likelihood > not requested early. Note that HTTP/2 currently does not support > re-prioritization (and that feature is being debated), so that means > that when the resource load for <link rel=subresource> gets issued over > an HTTP/2 connection, it will have the lowest priority, which is > probably undesirable. FWIW, I think <link rel=subresource> was a good > initial start, but suffers from key weaknesses and should be thrown out > and replaced. It seems pretty clear that in general it's better to provide the resource using its proper form -- be that <img>, <link rel=stylesheet>, <link rel=icon>, <script>, whatever -- and annotate it with attributes saying how to load it -- asap, later, when needed, whatever -- than it is to just generically say "also, I might eventually perchance refer to this URL". The former would allow the browser to also preparse the contents, e.g. decoding an image, parsing a style sheet or script, preparsing an HTML file, and the like. > * Given current browser heuristics for resource prioritization based on > resource type, all <script> resources will have the same priority. > Within HTTP/1.X, that means you'll get some amount of parallelization > based on the connection per host limit and what origins the script > resources are hosted, and then get FIFO. New additions like lazyload > attributes (and perhaps leveraging the defer attribute) may affect this. > With HTTP/2, there is a very high (effectively infinite) parallelization > limit. With prioritization, there's no contention across priority > levels. But since script resources today generally all have the same > priority, they will all contend and most naive servers are going to > round robin the response bytes, which is the worst thing you could do > with script resources, since current JS VMs do not incrementally process > script resources, but process them as a whole. So round-robining all the > response bytes will just push out start time of JS processing for all > scripts, which is rather terrible. I'm not sure what to do about this exactly. > * Obviously, given what I've said above, some level of hinting of > prioritization/dependency amongst scripts/resources within the web > platform would be useful to the networking layer since the networking > layer can much more effectively prioritize resources and thus mitigate > network contention. If finer grained priority/dependency information > isn't provided in the web platform, my browser's networking stack is > likely going to have to, even with HTTP/2, do HTTP/1.X style contention > mitigation by restricting parallelization within a priority level. Which > is a shame since web developers probably think that with HTTP/2, they > can have as many fine grained resources as they want. It's hard to come up with a super fine-grained model that works well with multiple competing scripts, but we can do better than what we have now, certainly. It seems we can at least split things into the following categories, in order of highest priority to lowest: 1. resources that are needed and are causing something to block e.g. <script src="foo.js"></script> 2. resources that are needed and are neither blocking anything nor explicitly deferred e.g. <img src="foo.png" ...> 3. resources that are needed but are deferred e.g. <script src="foo.js defer></script> 4. resources that the browser wants e.g. <link rel=icon>, <html manifest> 5. resources that are not yet needed but which the author wants precached when possible, and which have not been marked deferred e.g. <link rel=subresource href=...> 6. other resources Is that fine-grained enough? We could add other levels too. Maybe a "defer" variant of #5, for instance: <script src="foo.js" whenneeded precache defer></script> ...or some such. Or maybe put these things in a "load policy" attribute, as mentioned above. On Tue, 3 Sep 2013, Ryosuke Niwa wrote: > > Use case: A web page wants to load and execute a script widget.js if the > script is already cached in the browser. However, it wants to load > other essential assets such as images first if it's not already in the > cache except as long as the user had not started interacting with the > parts of the page that require widget.js. > > i.e. it (loads and) executes the script immediately when and only when > the script had already been cached or the user had started interacting > with the parts of the page that requires the script. Otherwise, the > script is loaded with a low priority. (I've added this one to the list above as use-case L.) This seems reasonable. It's kind of opportunistic loading -- it's saying execute this ASAP, but load it when needed, albeit with precaching if that is necessary. On Thu, 23 Jan 2014, Bruno Racineux wrote: > > What bugs me the most is that a developer's expectations is superseded > by a feature (the preloader) with different implementation per browser > engine, that are neither documented (in plain English) nor specced out > by W3C or WHATWG standards. Well the spec is just "parse the document and fetch the URLs you find", basically. It's a reasonably simple model. The implementation is a bit more subtle due to off-the-main-thread parsing, document.write(), and so on, but that shouldn't affect authors all that much. > The lack of such resource control can be a huge waste of bandwidth in > collateral damage along the benefits of the pre-loader. And in the case > of responsive images, it prevents us from implementing any simple > straight forward js solutions. I don't know about "straight-forward", but it shouldn't stop you from making scripted solutions in general. What you can't do from JS is poke content into the preloader before the script runs, which is what the preloader is basically for. (This is why the proposal earlier in this thread includes a dependency model: it lets you provide that information to the browser before script actually runs, so that the browser can do useful things with the information it has earlier.) > Anyway, to come to the point of this new parallel thread, this leads me to > a suggestion: > > 'HIDDEN' as [resources control]: > > Could 'resource control' be an associated spec of the 'hidden' attribute? > The semantics seem compatible with the specs implied by 'hidden'? > > Being that: hidden "Specifies that the element represents an element that > is not yet, or is no longer, relevant" [1]. That suggest it may not be > needed at all and if so, why load it's associated resources? It would certainly make sense for a browser to defer such a load. I don't know that it would make sense to not load it at all, though. However, it would make sense to provide controls for that. On Fri, 24 Jan 2014, David Newton wrote: > > In addition to overloading `hidden`, it misses the `postpone` use case > of images that we want to be visible (i.e. not have a `hidden` > attribute), but not loaded until/unless the user scrolls enough for them > to be within the viewport. Yeah. One thing we could do to make that work would be to have images do something similar to "whenneeded", where instead of an execute() method to indicate that they are needed, they trigger that state when the browser would otherwise try to render them. On Sun, 26 Jan 2014, Qebui Nehebkau wrote: > > Basically, - and I'm trying not to over-elaborate here, since my opinion > isn't really very important - I just mean that I don't think there > should be any guarantees about how (or whether) browsers will preload, > nor any specific means of controlling this, because the way resources > get loaded is not really any of the author's business. I also think that > the purely presentational choice of a specific image file to represent > the same content (and, even in the art direction case, they're clearly > the same content if they can be represented with the same alt text; > otherwise, there should be multiple img tags) should be specified in > CSS, not HTML; the argument that preloaders can't consider CSS isn't > compelling to me, because a browser's choice to preload an image or not > isn't important enough (or, I think it shouldn't be) to justify > entrenching in a specification. I think this makes sense, insofar as the UA should have the final say (not the author) regarding how things are downloaded. But on the other hand, the UA can do a better job if the author is able to provide hints to the UA about what the best order might be. On Wed, 12 Mar 2014, Jake Archibald wrote: > > One of the problems with async scripting is doing something when/if it's > ready. A complete solution looks like this > https://gist.github.com/jakearchibald/7662e012462c7537a075, it would be > a lot easier if we could just do: > > thing.loaded().then(function() { > console.log('loaded'); > }, function() { > console.log('failed'); > }); Yeah, it would make sense for objects that have an eventual "ready" state to have an attribute that returns a promise for the current load state. That would fit in well with the model of adding dependencies by handing a promise over. On Wed, 12 Mar 2014, Boris Zbarsky wrote: > On 3/12/14 7:23 AM, Jake Archibald wrote: > > > > If the element hasn't loaded or is loading, vend a promise that > > resolves/rejects on its load/error event. If the element has fired > > load/error and isn't loading due to a source change, vend a > > resolved/rejected promise. > > This seems fundamentally racy, no? If you're changing what your image is pointing to, then yeah. We could vend an ImageBitmap object on the promise, I guess, if that's likely to be a problem. But in practice I don't see why it would be. Who's going to be using the same image to load multiple images without waiting for one to be ready? On Wed, 12 Mar 2014, Boris Zbarsky wrote: > > I realize no one would write actual code like this; the real-life use case I'm > worried about would be more like this: > > // img is already loaded sometimes > // Would like to observe a new load > var promise1 = img.loaded(); // oops! This will be pre-resolved if > // we were already loaded, but otherwise > // will resolve with the new load we're > // about to start. > img.src = bar; promise1 would be rejected as soon as you set 'src' if it hadn't loaded yet. If it had loaded, it would resolve straight away. In the latter case, you might observe either the old image or the new one, there's no way to know for sure. This is certainly bug-prone and I'm sure people will get it wrong and it'll usually work but sometimes not, due to the race. However, it's a logic error -- you're always observing the first load, not the second. It will always resolve straight away, either aborting or succeeding based on whether the original load is aborted or had completed. On Wed, 12 Mar 2014, Domenic Denicola wrote: > > With promises you should only ask for the "loaded" promise *after* > setting `src`; anything you retrieve before that represents a previous > load. Except, I suppose, for the base-case of images with no src, > transitioning to having an src? Or are they considered to have e.g. > loaded `about:blank` already? I.e. what should this do? > > var img = document.createElement("img"); > var promise1 = img.loaded(); > img.src = "foo.png"; > var promise2 = img.loaded(); I think we should spec this case to reject promise1, since the image is not .complete. I definitely don't think we should say that promise1 waits for the src to be set. > // (4) or it could be rejected with an "InvalidStateError" saying you > // can't wait for the loading of a non-src'ed image. This would be a perfect case for a sync exception, rather than a rejected promise, IMHO. Throwing an exception synchronously would clearly indicate to the programmer that the code is Just Wrong. On Fri, 14 Mar 2014, Jake Archibald wrote: > > Problem: Initialising an app after a non-module script > Solution: https://gist.github.com/jakearchibald/000ab94ad9fa5cfe62a8 This seems simpler: <script src="/whatever.js" id=whatever async></script> <script type=module needs="whatever"> import app from "myapp"; app.init() </script> We would have to make sure, though, that needs="" blocked the execution of the script _after_ the script was parsed to pull out 'import' statements. > [Use-case Q:] Want to avoid executing a social-media script until the user > give some intent, although the script should be preloaded. > Solution: https://gist.github.com/jakearchibald/dd25f0f2cf47bf2ab93e This seems simpler: <script id="socializor" src="http://myface.com/socializor.js" whenneeded></script> <script> document.ready.then(function () { document.querySelector('.social-button').addEventListener('click', function(event) { document.scripts.socializor.execute(); event.preventDefault(); }); }); </script> > [Use-case S:] One plugin wants to execute "A.js" and "B.js" in order > following an interaction. Another wants to load "A.js" then "C.js" & "D.js" > in either order. "A.js" should only execute once. Scripts aren't written as > modules and out of developer's control. > Solution: https://gist.github.com/jakearchibald/120309d88a8bf025e92e That's a lot longer than the original proposal's solution. Also it requires the plugins to know about each other. Also it uses markup. If we can use markup, then the solution becomes even simpler than the original proposal's solution, IMHO: <script src="A.js" whenneeded></script> <script id=B src="B.js" whenneeded needs="A.js"></script> <script id=C src="C.js" whenneeded needs="A.js"></script> <script id=D src="D.js" whenneeded needs="A.js"></script> // CMS plugin 1 <script> document.querySelector('.button').onclick = function(event) { document.scripts.B.execute(); } </script> // CMS plugin 2 <script> document.scripts.C.execute(); document.scripts.D.execute(); </script> This is dramatically simpler than the solution presented above. > [Use-case T:] Preload 2 scripts, execute one or the other on particular > interactions > Solution: Same as Q. Similar to Q, I guess, but presumably not identical. Q only has one script. Again, though, I don't really see why we'd want something as complicated as suggested in that gist, when we can just have two script elements and call "execute()" on the one we want. (I note here that actually my solutions to Q and T here and in my e-mail from last year actually make the mistake of not distinguishing scripts that should be preloaded from those that should not. We probably need an explicit way to control the load policy here, as mentioned earlier in this e-mail.) > [Use-case U:] "A.js", "B.js", "C.js" - load them in parallel, execute in > order, only execute when all have preloaded. > Solution: https://gist.github.com/jakearchibald/5898e3a4fce62579d75b At a glance I've no idea what this script is doing. I don't think we want to be requiring that authors follow this kind of convoluted code to do something as simple as use case U. Recall that with the whenneeded=jit proposal and with needs="" taking URLs, this was as simple as: <script src="/a.js" whenneeded=jit></script> <script src="/b.js" whenneeded=jit needs="/a.js"></script> <script id=c src="/c.js" whenneeded=jit needs="/b.js"></script> <script> // when we need it... document.scripts.c.execute(); </script> > [Use-case V:] As U, but don't need to wait for all before executing. > Stop executing if any script fails to load. > Solution: https://gist.github.com/jakearchibald/ea7583d50bf3b46395e0 It's not at all clear to me how a random author is supposed to figure out the difference between your solution to U and to V. It seems like any solution we provide for this should be way more straight-forward. The solution using the "whenneeded" proposal is the same as for U but with the "=jit" bit removed. I think we should probably make this more clear, e.g. making it policy="precache late-execute" vs policy="precache early-execute" or some such, but that's a far cry from going all the way to requiring that authors reduce arrays of promises. (Though to be fair, the solution I proposed to V doesn't actually go as far as the use case asks for, since it doesn't abort later loads. I think I intended tho onerror function in that solution to manually go in and stop the loads, but I'm not sure what exactly one could do to do that, since they're trying to preload, at least. Maybe we should just require that UAs be clever enough to notice that if they have aborted execution of a particular dependency chain, they should abort the loads too if they have no reason to think the loads will be useful later.) > [Use-case W:] As W, but break on execution errors > Solution: https://gist.github.com/jakearchibald/1b12a0e5414a69d9350f That's WAY more complicated than the whenneeded="" proposed solution. > [Use-case X:] Loading non-js dependencies > Solution: Use XHR + preload for prescanner XHR makes it hard to hook into the prioritisation system, but yeah. > [Use-case Y:] Some libraries may need async initialization. > Solution: These libs should provide a ready promise. Agreed, though I would just have them hand that promise to the <script> element so that it hooks into the rest of the dependency system. > [Use-case Z:] Wait on existence of particular element before executing > script > Solution: Either put the <script> that handles script loading after the > element in question, or use mutation observers Yeah. On Sat, 15 Mar 2014, Jake Archibald wrote: > > It's everything we need but perhaps not everything we desire. Last time > we went round with script loading the proposal for high-level dependency > declaration got weighed down by use-cases that should have been left to > lower level primitives. These are the lower level bits, something higher > level is still possible. It's not clear to me that promises really are the right "low-level" bits here. I think we need something lower than those that lets the JS module system, the HTML Imports system, the <script needs> system, and any promise-based dependency system all interact in one coherent way. > For legacy-free project, modules + promises for non-script assets are > the answer. How do modules solve the problems discussed here of defining when things should preload, wait til they're needed, etc? On Fri, 14 Mar 2014, Kyle Simpson wrote: > > I'd also like to make the observation that putting <link > rel=preload>.loaded() together with <script>.loaded(), and indeed > needing a promise mechanism to wire it altogether, is a fair bit more > complicated than the initial proposals for script preloading use-cases > from the earlier threads over the last few years of this list. > > For one, we're talking about twice as many DOM elements. For another, > there's a seemingly implicit requirement that we have to get both ES6 > promises and DOM promises to land for these suggested approaches to > work. I don't know if that's already a guarantee or if there are some > browsers which are possibly going to land DOM promises before ES6 > promises? If so, ES6 promises become the long pole, which isn't ideal. > > Lastly, I'd observe that many of the arguments against the > original/previous script preloading proposals were heavily weighted > towards "we don't like script loaders, we want to make them obsolete, we > need simple enough (declarative markup) mechanisms for that stuff so as > to make script loaders pointless¡" > > At one point the conversation shifted towards ServiceWorker as being > "the answer". When we explored the use cases, it was my impression there > was still a fair amount of non-trivial code logic to perform these > different loading cases with SW, which means for the general user to > take advantage of such use-cases, they're almost certainly going to need > some library to do it. > > I can't imagine most end-users writing the previously-suggested > ServiceWorker code, and I'm having a hard time believing that they'd > alternatively be writing this newly suggested promises-based loading > logic all over their pages. In either case, I think for many of the > use-cases to be handled, most general users will need to use some > script-loader lib. > > So, if this .loaded() DOM promises thing isn't the silver bullet that > gets us to "no script loader utopia", then I don't see why it's > demonstrably better than the simpler earlier proposals. > > Moreover, the earlier proposals relied on the browser having logic > built-in to handle stuff like "download in parallel, execute serially", > which made their interface (the surface area users needed to work with) > much less than either the Promises here or the ServiceWorker before. > > What you're implicitly suggesting with both sets of suggestions is, > let's make the user do the more complicated logic to wire things > together, instead of the browser doing it. Why? > > Why isn't putting preloading into existing <script> elements (whether > exposed by events or by promises) better than splitting it out into a > separate element (<link rel=preload>) and requiring a third mechanism > (promises) to wire them up? I have to agree with Kyle here. On Sat, 15 Mar 2014, Jake Archibald wrote: > > Yep, markup is important for preparsers. <module>/<script type="module"> > & ES6 modules give us a solution for the future. I don't really understand how. Modules offer one small part of what we're talking about here, but not much more. They don't offer a way to declare the dependency tree before the files are fetched, there's not really a way to concatenate modules without putting them in some other file like a ZIP file or an HTML Import, there's no way to delay the load or execution of a module until it's needed as far as I can tell, there's no way for modules to be declared as dependent on other resources, etc. I think modules will be an important part of this solution, but I don't think they are, on their own, a complete solution to the use cases presented. Certainly without a lot of rather complicated code. On Tue, 22 Jul 2014, Ben Maurer wrote: > On Tue, Jul 22, 2014 at 10:26 AM, Ian Hickson <ian@hixie.ch> wrote: > > On Mon, 21 Jul 2014, Ben Maurer wrote: > > > > > > (1) Allowing the user to specify parameters to Fetch. For example, a > > > user could say: > > > > > > <script src="/my.js" params="{'headers':{'myheader':'value'}}" > > > id="myscript" /> > > > > > > This would allow the user to make any type of request customization > > > that they could for a direct call to Fetch. > > > > I think this would make a lot of sense. It's similar to earlier > > suggestions for controlling "Referer" on a per-link basis, but more > > generic. The problem I had with the proposals for controlling Referer > > was that while there's an obvious way to do it for <script> and > > <link>, it quickly starts breaking down don't have a 1:1 mapping of > > URL to element. For example, how do you get the fetches of URLs in a > > style sheet? Or of the poster frame of a <video> element? Or > > EventSource objects? Should we just have different ways to set the > > options for each one? > > I guess the upshot of of doing this through fetch is that once we added > the ability to specify fetch metadata it would hopefully reduce the need > for future modification of all the different ways one could load an > object. > http://lists.w3.org/Archives/Public/public-webappsec/2014Jan/0129.html > suggests the idea of using fetch for stylesheet subresources in the > context of subresource integrity. Yeah. I think it makes sense to expose a Request object once one is underway, and a RequestInit object (probably in the form of a JSON-encoded content attribute?) to configure it, at least for the main resources. I'm not sure how to handle elements with multiple resources, e.g. <video poster> or the new <picture> stuff. > > > (3) Allowing the user to construct a script or stylesheet from a > > > fetch. Complex sites often want precise control over when scripts > > > and stylesheets are loaded and inserted into the DOM. For example, > > > today inserting a stylesheet blocks the execution of inline script > > > tags. Some sites may know that this dependency is not needed. > > > > This specific use case is something I'm already working on. My > > long-term plan is to integrate something like this with the HTML > > Imports dependency model and the ES Module dependency model so that we > > have a single model that can handle everything. > > This sounds interesting. The way we render pages at Facebook seems very > similar to what you are suggesting. We essentially flush sections of > HTML with a list of CSS and JS dependencies for each section. The HTML > is only rendered once these resources are loaded. In essence, it seems > like we're doing inline HTML imports. We have a few other use cases for > this type of dependency (for example, we have a method where you can say > "call this callback once resources X, Y and Z are requested". Presumably the callback part of this could be done using promises if we expose promises for everything that can load. (I've added the above, as well as some other use cases you listed, into the use case list at the top of this e-mail, as use cases I through K.) > > > var myfetch = window.fetch(...); > > > myfetch.then(function(resp) { > > > document.body.appendChild(resp.body.asStyleSheet()); > > > }); > > > > > > By calling asStyleSheet, the user could preserve the devtools > > > experience that exists today while still having complete control > > > over the fetching of their content. > > > > We could do this too. It's mostly just a convenience method, right? > > One advantage of doing this is that if there is some use case a site has > that isn't met by the dependency model they can still manually separate > the fetch of an object from its insertion into the DOM. Giving the > author the freedom to manually separate the fetch of a resource from > it's use in the document gives them a powerful ability to experiment > with different approaches to resource loading. asStyleSheet/asScript > wouldn't merely be a helper method to construct a stylesheet from a > given string of text. For example, line numbers in developer tools (or > JS stack traces) as if they were loaded directly from the given URL. A > browser might decide to parse the JS/CSS in a background thread as soon > as the document was fetched (or cache a pre-parsed version of the file). If we provide a way to not apply the resource until it's "needed", doesn't that just take care of the above automatically? I'm not sure I understand precisely the use case here. > This model also might be beneficial for sites that were written prior to > the dependency model you are describing. For example, at Facebook we > already schedule JS/CSS loading ourselves. This API would allow us to do > things like fetch a CSS stylesheet without blocking inline script > execution that follows while making a minimal number of changes to the > site. Can't you do that already? I'm not really following what you need to do here that you can't already do. On Tue, 22 Jul 2014, Boris Zbarsky wrote: > > One issue worth considering here: there are various situations (CSP, > extension) in which a browser would like to know what sort of resource > is being loaded, or more precisely how it will be consumed, before > loading it. This argues strongly for either reusing the existing loading mechanisms for preloading, or at least for providing that context information in the preload request. On Tue, 22 Jul 2014, Ben Maurer wrote: > > To follow this up with a concrete suggestion: > > var myfetch = window.fetch('my.css', {'fetch-as': 'stylesheet'}); > myfetch.then(function(resp) { > document.body.appendChild(resp.body.asStyleSheet()); > }); > > You can only call asStyleShet if fetch-as=stylesheet. Passing this > parameter would cause the browser to do all the things it would do if it > were fetching a stylesheet. For example, it would specify an accept > header of text/css unless otherwise specified. It would request at the > same priority as the browser requests other stylesheets (again, unless > overridden with a yet-to-be-defined syntax). Rules around CORS and > tainting would be identical to a normal stylesheet fetch (namely that > you could call.asStyleSheet on a request to a different origin but it > would have whatever restrictions the browser has on stylesheets from > different origins). Links in the stylesheet would be interpreted > relative to the URL used to load it, etc. > > Essentially, fetch-as=stylesheet would be the canonical definition of > the browser's behavior around loading stylesheets. It would define the > behavior if the proposals I suggested in the original email were > implemented -- namely if the user chose to pass parameters to the fetch > algorithm or if the user accessed the result of a stylesheet's fetch. > > Boris, Will -- would this setup address the concerns you have about the > problems websites that use XHR to load resources encounter? How would this interact with th ES6 module loader? Would fetch() be above or below it? On Wed, 30 Jul 2014, Ben Maurer wrote: > On Wed, Jul 30, 2014 at 11:35 AM, Ian Hickson <ian@hixie.ch> wrote: > > On Wed, 30 Jul 2014, Anne van Kesteren wrote: > > > > > > it would be desirable to have Accept / Accept-Language be set by > > > APIs, such as <img>. XMLHttpRequest already does this (unless a > > > developer added those headers), see http://xhr.spec.whatwg.org/ > > > > > > If we are eventually going to expose something like a "Fetch" object > > > for each API that can issue a fetch it would seem best if these > > > details were defined at the API-level. > > > > > > I guess for now I'll add some notes to the network-level bits of > > > Fetch to indicate Accept / Accept-Language cannot be set at that > > > point by the user agent. > > > > There's three ways that I see: > > > > 1. Expose it on a "fetch" object available from all the places that can > > do fetches. (HTMLImageElement, XMLHttpRequest, StyleSheet, etc) > > > > var img = new Image(); > > img.src = 'foo.png'; > > img.fetch.doWhateverWithTheAcceptHeader('foo'); > > At what point is the fetch actually being initiated? It's possible that > fetch will offer some things which can be done post-request (eg, > dynamically changing the spdy priority), but the accept header may need > to be specified pre-request. The fetch wouldn't be initiated until the event loop spun, but other than that, it's hard to predict exactly when it'll be done in the general sense. Depends on things like the load policy. On Thu, 31 Jul 2014, Anne van Kesteren wrote: > > Probably not before the end of the current task. The only exception to > that is EventSource and maybe WebSocket, but they can have a constructor > argument of sorts to make this work I guess. WebSocket probably won't use fetch. Good point about EventSource though. If we want to add requestInit settings for that, please file a bug. On Thu, 7 Aug 2014, Ilya Grigorik wrote: > > Latest draft: https://igrigorik.github.io/resource-hints/ The preconnect, preload, and prerender link types seem important to define in more detail, I'm glad you are doing this work. I suggest that for things like scheduling and prioritisation, we reuse the proposals that this thread end up at (whatever that is, be in the proposal below or something evolved from that). Are there requiirements and use cases that you need to address that aren't covered by the proposals in this e-mail? I think I covered them all, but I'm not 100% sure. On Fri, 8 Aug 2014, bizzbyster@gmail.com wrote: > > +1 to breaking the dependency between fetching the resource and how it > is later used in the document. This type of ¡°late binding¡± enables many > page optimizations. Does anyone have a concrete depiction of this use case so we can evaluate the proposal in this thread againt the use case? On Mon, 11 Aug 2014, Ilya Grigorik wrote: > > Will this result in API that extends beyond import and ES6 module use > cases? For example, I want to have the building blocks that will allow > me to declare these dependencies on my own: "these two images before > that script, but after this stylesheet, oh and that stylesheet should > have higher priority than"... We can express all of this in HTTP/2, and > I'd like that to be surfaced to the developers as a first class API. That's my goal. I don't know if the ES modules spec will be extended sufficiently to support that, but I hope so. The alternative is that we have two mostly redundant dependency systems, which I assume wouldn't be popular with browser vendors. > I'm looking at the gliffy, but having a hard time understanding how this > will work... It's murky to me how I'd declare a resource, specify its > priority relative to others, and/or add a dependency. Any other (more > complete) examples, by any chance? :) Check out the examples at the bottom of this e-mail, let me know if they answer your question, let me know how they look! That concludes the feedback that was sent on the previous proposals. There's also some other developments that are relevant. There's JavaScript's modules, as mentioned earlier. Modules introduce their own dependency system. It would probably behoove us to integrate them with whatever we have for making scripts depend on images and so on. HTML Imports are also coming along, and they also have a dependency system. Again, we should probably make sure that this integrates with the JavaScript modules dependenc system and whatever we come up with to address the use cases listed earlier. There's a bug tracking the merging of the dependency systems: https://www.w3.org/Bugs/Public/show_bug.cgi?id=25715 I also brought this up in es-discuss recently, in a series of e-mails on the subject. Before we can come up with a coherent overarching model, though, we need to know what precisely it is we need to address the use cases in this e-mail. Let's first look at the dependency tree aspect. There's a variety of objects that one might want to depend on: - JS modules - <script> elements - <img> elements - <video> elements - promises - inline style sheets - external style sheets - HTML imports - etc Some can be identified by ID, some by URL, some by element or object, others by some sort of name. They don't all need to be identifiable from an attribute. For example, I don't think we need to make it possible for a <script> to depend on a promise declaratively. It's ok if depending on a promise can only be done via an API call. Some of these are somewhat special. For example, if we want multiple <scripts> pointing to the same src="" to be deduped, then the dependency mechanism needs to be based on URLs in some way (and presumably pre-redirect URLs otherwise we make the dependency mechanism depend on network latency). HTML imports also probably have to be URL-based, because the actual Document object that an HTML import imports doesn't exist until the import has been started. Modules make this a little more complicated, in that there's a desire that modules be named so that you can e.g. import "jquery" and be safe in the knowledge that jQuery will be imported regardless of its local filename, assuming that it has been previously declared. In HTML imports, the mechanism to identify another HTML import to depend on is <link rel=import href="...">. This only gets invoked once the resource has already been fetched. HTML imports are identified by URL. That URL has to be deduped -- each master document only imports each URL once. In JS modules, there's syntax in the script itself, which works similarly. The syntax identifies modules using a string. The script is first parsed, the dependencies are identified, they are fetched, this happens recursively, and then the scripts are evaluated. CSS similarly has @imports, though those are currently _not_ deduped. We should probably change that, so that at least CSSRuleLists are deduped. Alternatively, we can have a feature that forces deduping (or vice versa). To avoid the latency of a round-trip per dependency level, it would be good if the dependencies could be predeclared, even for e.g. CSS or JS modules, where you would normally only mention the dependency when you import it. Some dependencies have a presence in the same document, or can have a presence without any particular difficulty. For example, <script src=""> elements (not modules) all get listed in the document, even if they depend on each other. However, not all dependencies are this way. For example, ES6 modules can reference modules that aren't otherwise mentioned. Similarly, style sheets can @import other sheets, but if you <link rel=stylesheet> those sheets in the parent document, you're going to change the cascade in probably noticeable (page-breaking) ways. So if we have a way to declare dependencies declaratively in the markup, we have an interesting mixture of requirements, even if we assume that we're not going to be able to reference things like promises declaratively and can safely leave those to an API: - being able to reference specific elements (e.g. depending on an inline script or style block) - being able to reference specific URLs in the context of specific handling (e.g. "this URL is an ES6 module that someone will import", or "this URL is a CSS file that someone will try to @import") This suggests either that the dependency mechanism needs to be able to take different types of identifiers (e.g. element IDs or selectors on the one hand, and URLs+contexts on the other), or that we need to have two ways to declarare dependencies, e.g. two attributes (one for IDs, one for URLs+contexts), or that we should rely on rel=preload/rel=subresource to handle the second type. Here's how these three options could look, assuming that bar.css is a CSS file that foo.css imports, and "baz" is the ID of an element that this style sheet depends on (maybe a <script> element). * different ways we could jam all this into one attribute: <link rel=stylesheet href="foo.css" dependson="bar.css as css, baz"> <link rel=stylesheet href="foo.css" dependson="css:bar.css baz"> <link rel=stylesheet href="foo.css" dependson="bar.css css; #baz"> * different ways we could have multiple attributes to split this information out into: <link rel=stylesheet href="foo.css" dependson-id="baz" dependson-css="bar.css"> <link rel=stylesheet href="foo.css" dependson="baz" dependson-href="bar.css as css"> * using just IDs and rel=preload or rel=subresource: <link rel=subresource type="text/css" href="bar.css" id="bar"> <link rel=stylesheet href="foo.css" dependson="bar baz"> <link rel=preload type="text/css" href="bar.css" id="bar"> <link rel=stylesheet href="foo.css" dependson="bar baz"> <link rel="preload stylesheet" href="bar.css" id="bar"> <link rel=stylesheet href="foo.css" dependson="bar baz"> * reusing the existing mechanisms for each type, but having a way to force a particular case not to be instantiated ever, because it will instead be instantiated using the other mechanisms (e.g. @import): <link declare rel=stylesheet href="bar.css" id="bar"> <link rel=stylesheet href="foo.css" dependson="bar baz"> These all suffer from pretty notable disadvantages: complicated syntaxes in attributes or complicated multiple-attribute processing models for the first five cases, having to introduce a whole new way to declare that something is "CSS" for all but the last two, and lack of a decent backwards- compatibility story for the last two. The three before the last one also suffer from not having an obvious way to be extended to predeclare JS modules. The last one could be extended easily enough: <script declare type=module id=jquery href="p/jq/main.js"></script> <script type=module dependson=jquery> import $ from "jquery"; </script> We could also introduce a new void element specifically for preloading: <preload kind=stylesheet href="bar.css" id="bar"> <link rel=stylesheet href="foo.css" dependson="bar baz"> ...where the "kind" is from a list of predefined types that browsers can preload, like "stylesheet", "module", "script", "image", etc. However, introducing a new void element is a non-trivial cost. Two other problems with the <link> examples above are the duplication of information because of using id="", and the use of type="", a MIME type, to identify the resource type, when in practice how to handle a feature depends on both more than the MIME type (e.g. script program vs ES6 module) and less than the MIME type (e.g. most image/* types are handled by sniffing, not based on the type). The declare="" idea avoid this by reusing the existing mechanisms. Merging the <preload> and rel=preload/rel=subresource ideas, one could imagine a variant that used a different attribute rather than type=, so that we didn't have to give a MIME type. <link rel=preload as=stylesheet href="bar.css" id="bar"> <link rel=stylesheet href="foo.css" dependson="bar baz"> ...or: <link rel=subresource kind=stylesheet href="bar.css" id="bar"> <link rel=stylesheet href="foo.css" dependson="bar baz"> rel=subresource has the minor advantage of being already implemented in Chrome, at least. On the other hand, rel=preload is shorter and would let us use a shorter attribute name (as="" vs kind="" -- as="" doesn't read well when the link type is "subresource"). Also, "subresource" is much harder to type, at least for me. If we're left with deciding between rel=subresource, rel=preload, and declare="", then basically the choice becomes one of how important backwards-compatibility is: do we want something we can deploy today, something we can deploy next year, or something we can deploy next decade? The longer we can wait, the neater the solution. The sooner we want it, the more we have to compromise. The attribute dependson="" could probably also be refined. People have suggested various names that could work: uses="" needs="" dependson="" dependencies="" subresources="" import="" uses="", needs="", and dependson="" seem more or less equivalent. I'm a bit dubious about adding an attribute that ends with "on" to avoid the similarity with event handler attributes. dependencies="" and subresources="" seem a bit long without giving an obvious benefit. import="" seems reasonable, but I'm worried that it is too close to the ES6 "import", CSS "@import", or HTML rel="import", which all have similar but stronger semantics. Here we're not trying to actually effect an import, we're just trying to let the browser know that a particular element is going to come in useful when this element is invoked. There have also been some proposals with names that don't really fit grammatically, like depends="". I haven't considered those further. Let's take the above example and add more nodes to the dependency tree, and see how it looks with the various proposals. rel=preload, as=..., uses=... -- the shortest names mentioned above: <!-- core files --> <script src="core.js" type=module id="core"></script> <link rel=preload as=stylesheet href="core.css" id="core-css"> <!-- calendar files --> <link rel=prelaod as=stylesheet href="calendar.css" id="cal-css" uses="core-css"> <link rel=preload as=image href="imgs/cal.png" id="cal-sprites"> <script src="calendar.js" type=module uses="core cal-css cal-sprites"></script> rel=subresource, kind=... subresources=... -- has a nice consistent symmetry, but man is it annoying to type: <!-- core files --> <script src="core.js" type=module id="core"></script> <link rel=subresource kind=stylesheet href="core.css" id="core-css"> <!-- calendar files --> <link rel=subresource kind=stylesheet href="calendar.css" id="cal-css" subresources="core-css"> <link rel=subresource kind=image href="imgs/cal.png" id="cal-sprites"> <script src="calendar.js" type=module subresources="core cal-css cal-sprites"></script> rel=subresource, kind=..., needs=... -- less annoying, still annoying: <!-- core files --> <script src="core.js" type=module id="core"></script> <link rel=subresource kind=stylesheet href="core.css" id="core-css"> <!-- calendar files --> <link rel=subresource kind=stylesheet href="calendar.css" id="cal-css" needs="core-css"> <link rel=subresource kind=image href="imgs/cal.png" id="cal-sprites"> <script src="calendar.js" type=module needs="core cal-css cal-sprites"></script> rel=preload, kind=..., needs=... -- similar to as/uses: <!-- core files --> <script src="core.js" type=module id="core"></script> <link rel=preload kind=stylesheet href="core.css" id="core-css"> <!-- calendar files --> <link rel=preload kind=stylesheet href="calendar.css" id="cal-css" needs="core-css"> <link rel=preload kind=image href="imgs/cal.png" id="cal-sprites"> <script src="calendar.js" type=module needs="core cal-css cal-sprites"></script> declare="" and needs="", reusing the existing load mechanisms: <!-- core files --> <script src="core.js" type=module id="core"></script> <link declare rel=stylesheet href="core.css" id="core-css"> <!-- calendar files --> <link declare rel=stylesheet href="calendar.css" id="cal-css" needs="core-css"> <img declare src="imgs/cal.png" id="cal-sprites"> <script src="calendar.js" type=module needs="core cal-css cal-sprites"></script> We do use kind="" on <track>, for something similar, which would be an argument for kind="" over as="". Also, as="", while cute and, in context, pretty nice looking, does suffer from lack of clarity when used out of context (plus, consider the confusing with is="" from Web components). Searching [link element as attribute] is not likely to get you as helpful a set of results as [link element kind attribute]. It's really tempting to just use the declare="" attribute, though, since that reuses so much machinery and avoids having to intoduce kind="" at all. It's harder to decide between needs="" and uses="". I think "needs" probably has a slight edge only because it, to me at least, more strongly implies that if the load of a dependency fails, the element cannot be satisfied. uses="" sounds more opportunistic. For now, let's suppose we go with needs="". (As an aside, I should add that I welcome bike-shedding on all these names. If you have an idea I haven't considered, or a way of looking at the names that I haven't mentioned here, please don't hesitate to bring it up. Now's the time, before anything gets implemented.) The discussion above suggests that needs="", the attribute that defines these dependencies, should take a space-separate list of IDs, rather than URLs. This, though, brings in a new set of subtle questions. When are these IDs resolved? Should they update dynamically? For consistency with the rest of the platform, the answer should probably be yes, but this does introduce "spooky action-at-a-distance" effects, which is unfortunate. For example: <link id=a rel=preload kind=image href="a.png"> <link id=b rel=preload kind=image href="b.png"> <script needs="a" ... id="demo"> ... </script> <!-- later... --> <script> document.getElementById('a').id = 'c'; document.getElementById('b').id = 'a'; </script> <!-- later... --> <script> document.scripts.demo.execute(); </script> Which image(s) should be downloaded? If the browser is aggressively precaching everything that something depends on, probably all of them; if it's only fetching what is actually needed when it's needed, presumably only b.png. ES6 modules make this a bit more complicated too, in that we don't fully control when the normalize hook is run (which is where we'd presumably evaluate these mappings). In the example above, the <script> element has a "..." in its attribute list, which brings us to the next and more thorny part of the whole issue: how to tell the browser that certain resources are only to be loaded when they are needed. Normally, browsers aggressively download everything. <link rel=stylesheet href="a.css"> <img src="a.png" alt=""> <script src="a.js"></script> All three files will be immediately fetched and applied. In fact, it's worse than that; the style sheet will block the script from executing, and the script will block the parser from continuing to the rest of the page (e.g. blocking a subsequent video from starting to play). We have ways to prevent the blocking for <script>, namely defer="" and async="". Their exact behaviour is a little subtle, but, broadly speaking, the defer="" attribute delays the script until after the page has finished loading, while the async="" attribute tries to run the script as fast as possible without blocking. Style sheets don't currently have such a mechanism, though it has previously been requested. Everything else, more or less, loads like the async="" behaviour. (All of the above, including 'defer', also delay the 'load' event.) So, to address most of the use cases described at the top of this e-mail, we need a way to delay the application (and in some cases loading) of resources until they are "needed", for some definition of needed. Some of the use cases also need a way for us to de-dupe references to the same resource, even across otherwise independent packages. For example, two separate packages that both want to load jQuery, or two separate style sheets for different parts of the app that both want to load a "reset" sheet first. (The CSS case is not spoken about as much as the scripting case, but it's still real, especially for big applications; see, for instance, this talk: https://www.youtube.com/watch?v=_MD1WQclOJM ...about Google+'s rather elaborate system to solve this.) In the proposal from last year, I had suggested a whenneeded="" attribute with three states: today's status quo, a value to delay the load and execution until the script was needed (indicated by calling "execute()" on the script element), and a value to delay the load until then and delay the execution until everything depending on this script itself was also ready to execute. As discussed earlier in this e-mail, however, this is probably insufficient. We probably need to just expose a way to control the load policy for an element. There's actually two somewhat orthogonal aspects to this. When to download the resource, and when to execute or apply it. For some resources, when to download it is irrelevant (e.g. inline scripts), and for some, when to apply it is irrelevant (e.g. images). For downloading, it again breaks down into several somewhat orthogonal controls - when, and what priority: - When to consider downloading it: - don't wait, just get it immediately - wait until either it's needed, or nothing that _is_ needed is being downloaded (precaching) - wait until it's needed, don't precache - What priority to give the download: - download it as soon as possible - allow other downloads to take priority That sort of maps to three booleans: - fetch-asap vs fetch-when-needed - precache vs no-precache - high-priority vs low-priority ...except that 'precache' requires 'fetch-when-needed'. You can alternatively view this as six states: - download now at high priority (default) - download when needed at high priority (fetch-when-needed) - download when needed at high priority, but allow precaching (fetch-when-needed, precache) - download now at low priority (low-priority) - download when needed at low priority (fetch-when-needed, low-priority) - download when needed at low priority, but allow precaching (fetch-when-needed, precache, low-priority) For execution/application, there's basically one control, with five levels (one of which is the equivalent of declare="", from earlier): - execute this now, blocking subsequent scripts from executing until it is done (this describes the legacy behaviour of <script> and style sheets, and probably shouldn't be exposed. It also overrides the download behaviour, since when you're blocked you need the resource as soon as possible and so you won't wait, nor use a low-priority channel) - execute as soon as possible after it is downloaded - execute as soon as it is needed - execute just before anything depending on this is executed - don't ever execute this, this is a placeholder for something that will be referenced from elsewhere that is just being listed here so that we can preload it and reference it The "execute as soon as possible after it is downloaded" and "execute as soon as it is needed" options would obviously be equivalent when the download option is "when-needed", but it would _not_ be the same for other cases (e.g. it controls whether to execute after precaching or not, and decides when to apply inline resources, which don't get downloaded in the first place). Let's give those five states straw-man names: block, use-asap, use-when-needed, use-late, and declare. The "low-priority" modifier applies to most of these values, and "precache" applies to all those with fetch-when-needed. Expanding out all the combinations, using square brackets to show different possible combinations for the "low-priority" and "precache" values, we get: block use-asap [low-priority] use-when-needed [low-priority] use-late [low-priority] declare [low-priority] fetch-when-needed use-asap [low-priority] [precache] fetch-when-needed use-when-needed [low-priority] [precache] fetch-when-needed use-late [low-priority] [precache] fetch-when-needed declare [low-priority] [precache] These names are quite confusing. In particular, "use-when-needed" alone makes it sound like it'll be fetched when needed too, not immediately. And "fetch-when-needed use-when-needed" is pretty horrible for what might be a pretty common case. (So common the earlier proposal just had a single attribute for it, whenneeded="".) Here's an alternative set of names for the same states. I used "async" for "use-asap" because that's basically what <script async> today does. The others are new names. block async [low-priority] when-needed preload [low-priority] late-run preload [low-priority] declare preload [low-priority] optimistic [low-priority] [precache] when-needed [low-priority] [precache] late-run [low-priority] [precache] declare [low-priority] [precache] Here, "preload" means "fetch it ASAP", and "precache" means "fetch it whenever you want (but not later than when it's needed)". If you had one of each of these, what order would they load in, assuming all the ones that are marked as loading only when needed were requested after everything else had loaded, and that only one resource could be loaded at a time? It would be something like this, I think: block async async low-priority when-needed preload, declare preload when-needed preload low-priority late-run preload late-run preload low-priority optimistic precache when-needed precache, declare precache late-run precache optimistic low-priority precache when-needed low-priority precache late-run low-priority precache // (pause until they are marked as needed) optimistic when-needed, declare late-run optimistic low-priority when-needed low-priority, declare low-priority late-run low-priority (I'm assuming "when-needed" and "declare" have the same importance in terms of fetching priority.) Even when you hide the low-priority lines, it's still a lot: block async when-needed preload, declare preload late-run preload optimistic precache when-needed precache, declare precache late-run precache // (pause until they are marked as needed) optimistic when-needed, declare late-run We clearly don't need all of these. In particular, we could merge "preload" and "precache" so that it's more than a hint, but less than an immediate request. This gets us down to basically four keywords plus "async" and "block", which represent the default behaviours for most interesting elements: block async optimistic [precache] [low-priority] when-needed [precache] [low-priority] late-run [precache] [low-priority] declare [precache] [low-priority] We could also add a flag that says "always force a new download, rather than deduping", to match current default behaviour of certain features. Say, "force". Here's how these would map to some existing features: <link rel=stylesheet>: block force <style>: block @import: block force <script src>: block force <script src defer>: when-needed precache force; plus, the page load itself triggers the script as being needed <script src async>: async force <link rel=icon>: when-needed precache <img ...>: async force <img ... hidden>: async low-priority force So for example, if you had a style sheet that you only wanted to apply when it was needed by some script, but which could be downloaded earlier if the browser wanted to, and which should be deduped if there are multiple instances of it loaded, you could declare it as: <link rel=stylesheet href="foo.css" load-policy="when-needed low-priority precache"> For resources that don't have an "execute" step, like images, there's no concept of late execution. For these, optimistic, when-needed, and late-run are all essentially the same, and "block" doesn't apply; the choice is really between "fetch now", "fetch soon", and "fetch when needed", aka async, when-needed precache, and when-needed. Again, I'm not a great fan of these names. Bikeshed away. To complement the dependency model and load policy, we need a few additional features. There needs to be a way to indicate that something that is waiting to be needed is in fact needed (separate from something depending on it, that is). So probably a method like .execute(), though it would be nice to have a name that could apply to all elements. The browser also needs a way to say "I need this" for some elements, such as images -- there, it would kick in when the element is being rendered. It would also be nice to be able to have an API that lets you add elements to another element's dependency list without having to add an ID and add the element that way. (That would also allow cross-document dependencies, which might be useful.) Maybe foo.addDependency(). To make this all work well with promises, it would also be nice to have an API to say "add this promise as a dependency". This could be an overload of the element-receiving one mentioned in the previous paragraph. Unfortunately, as far as I can tell, a promise breaks the back-channel logic. For example, consider this case: <link rel=stylesheet href="foo.css" load-policy="when-needed" id=a> <img src="bar.png" alt="..." load-policy="when-needed" id=b> <script> links.a.addDependency(images.b); </script> At this point, ideally, nothing would happen. Now suppose that we told the style sheet link that it was needed. Ideally, the image would then download itself also. But in practice, I don't see how we can actually do that. There's no back-channel along the promise to inform the image that it should now start downloading itself. So I guess for promises as dependencies, we'd only have a way to wait on the promise, not a way to also trigger the load that the promise represents. That's probably sufficient for most cases if we also have a way to add a dependency by ID. To help hook into things that use promises, we should presumably have .loaded and .ready attributes that return promises for when something has executed/applied (.loaded, similar to onload) and when things are prefetched and ready to go (.ready). We could pair that last one with a new event, onready, for those who prefer the event model. We also need a way to actually say that things should be executed. For consistency with onload, I guess a .load() method. Earlier in this e-mail I mentioned ES6 modules and HTML imports, and pointed out that they both had dependency systems. If we add in the system described above for HTML resources, we now have three. I think it's pretty critical that we not require that browsers implement three redundant dependency systems. Since the more mature of the three is ES6, I've been trying to work on fitting HTML imports and the system described above into the ES6 module world. Mostly this just means defining the default behaviour of the ES6 loader hooks, though there's probably a few things that would need changing at the ES6 module level to make it fully work (I've been sending this feedback to the es-discuss list directly for those who want to follow along over there). The main thing that ES6 doesn't currently support is the ability to set the dependencies ahead of time. The discussion below assumes that that will be resolved. There's also a minor issue with being able to associate metadata with a load when it's discovered as a dependency and when its name is normalised, so that @import rules can work right. Modulo those issues, the idea is that any fetch within the context of a Web page would be added to the ES6 module loader mechanism. The "normalize" hook would need a way to reference elements by ID. There's two obvious ways to do that: support any string that matches an ID as being an ID, and support any string starting with a "#" as being an ID. The former is neater-looking: <link rel=stylesheet id=foo ... load-policy="when-needed"> <script type=module> import styles from "foo"; </script> ...but means that we're now overloading the string passed to import as multiple things (package locators, IDs, and URLs) in a rather ambiguous way. So the hash idea is probably better: <link rel=stylesheet id=foo ... load-policy="when-needed"> <script type=module> import styles from "#foo"; </script> It's important to distinguish this from a URL, though; I don't expect this to work: <!-- in foo.html --> <link rel=stylesheet id=foo ... load-policy="when-needed"> <!-- in bar.html --> <link rel=import href="foo.html"> <script type=module> import styles from "foo.html#foo"; // the #foo here presumably has no effect? </script> But what should the "#foo" do? Probably it should throw, but we could imagine maybe having the "normalize" hook wait until the import is done? ("normalize", by default, would probably also need to support two other things: 1. absolute and scheme-relative URLs 2. some sort of relative naming for ES6 modules themselves, so that you can do things like: import "jquery"; import "../base/utils"; import "login/login"; ...and have it get "jquery.js" from some predeclared registry, "../base/utils.js" relative to the current script (not the doc!), and "login/login.js" in the same way. I'm not really sure how to do the package part of this.) The other hooks do pretty much the obvious things, except maybe "instantiate" which has to figure out how to import something once it has its MIME type. Pulling all of the above together, here's the tentative proposal: These "loadable" elements: <script>, <link>, <style>, <video>, <img>, <object>, <iframe>, <audio> ...get the following new attributes: needs="" Gives a list of IDs of other elements that this one needs, known as The Dependencies. Each dependency is added to this element's [[Dependencies]] in the ES6 loader. load-policy="" The load policy. Consists of a space-separated set of keywords, of which one may be from the following list: block, async, optimistic, when-needed, late-run, declare. The other allowed keywords are precache, low-priority, and force. (Maybe we disallow "block" and "force" since they're for legacy only.) Different elements have different defaults. "precache" isn't allowed if the keywords "block" or "async" are specified, since those always load immediately. load-settings="" A JSON-encoded dictionary to pass to the Request constructor. ...and API: .addDependency() Passed a promise, makes this element depend on that promise. Passed a "loadable" element, does the same as if that element's ID was mentioned in needs="". .load() Mark the element as needed, and apply or execute it as soon as possible. Returns the new .loaded promise (any earlier one is rejected). .ready Promise indicating calling load() will immediately apply or execute when load() is called. .loaded Promise indicating that the element has applied or executed. .request The current Request object, if a fetch has been started. .needs reflects needs, maybe as a custom object, or otherwise as a DOMTokenList .loadPolicy reflects load-policy, maybe as a custom object, or otherwise as a DOMTokenList .loadSettings reflects load-settings, maybe as a custom object These elements can be in six states. The first five are sequential; elements try to go through them in turn: - idle (the initial state at creation time) - prefetching... - ready (matches the .ready promise) - loading... - loaded (matches the .loaded promise) ...and the sixth is "error", meaning something failed permanently. Setting src="", or whatever causes the element's state to be reset, immediately rejects the preexisting .loaded promise and creates a new one, moving the element back to "idle". When an element is created, it's added to the ES6 module registry. (When one of these elements has its ID or URL changed, its entries in the registry are updated.) The ES6 LoadModule() operation is called for this module (that's how it is added to the registry). Except if the load policy has the "force" flag, when the element is added to the registry it's done in such a way as to rely on ES6 deduping. An element can be needed. By default it's not, unless it has a load-policy of "block" or "async". Upon creation, and when its needs="" is changed while the element is still not ready, or when another element's ID is changed and that matches an ID in an element's needs="", the element's [[Dependencies]] list is updated accordingly. When an element is marked as needed, all the things in its [[Dependencies]] get marked as needed also. An element in "idle" moves to "prefetching" if the load-policy is optimistic and the browser has nothing better to do, or the load-policy has "precache" declared and the browser has nothing to do, or the element is marked as "needed" somehow. An element's "fetch" hook blocks until the element reaches "prefetching". Once it does, if this is something to download, it creates a Request object from the load-settings="" attribute and the appropriate URL. For inline scripts and styles, the body comes from the element. Once the fetch hook finishes, an event is to be fired at the element. Once all its dependencies are ready, another event is to be fired at the element. When a script is needed, once it's ready, it is EnsureEvaluated() (see the ES6 spec for details). When scripts run, if they throw an uncaught exception then they go to the "error" state and that prevents any dependencies from resolving. If something is being loaded and it depends on something that's reached "error", it aborts loading. Something that depends on a resource in the "error" state won't load, it'll just transition to "error" straight away. (Or should it just wait, so you can remove the dependency and unblock it?) Changing the "load-policy" doesn't reset the element's state, it just causes it to resume from its current state with the new policy. If the new policy is irrelevant (because it applies to a state earlier than the current one), then nothing interesting happens. For instance, moving from "block" to "declare" after the file has already been executed does nothing. For style elements (and most elements, in fact), the "execute" callback does nothing. The StyleSheet object is created earlier. The script is applied once it is both needed and ready (in place of EnsureEvaluated()). While it is marked with a load-policy of "declare", subsequent fetches of the same URL in a style sheet context in the same Loader will use the same file, not refetch it. Certain loadable elements can also be told to execute by the browser even when they are awaiting being needed. Notably, <video>, <audio>, <img>, <object>, and <iframe> will self-need themselves if the come into view. The <link rel=preload> feature is given an attribute, kind="", which it can use to determine how to parse the file (image, style sheet, HTML import, JS script, JS module, ...). Documents add things like synchronous scripts, style sheets, deferred scripts, images, etc, to their dependency list while they are loading. When a Document loads, all the <script defer> elements are told they are needed. Elements that aren't one of these "loadable" elements that end up being imported as ES6 modules act as follows: - fetch: blocks until the ID is visible - instantiate: returns the element By default, the priority of loads is based on the load policy and whether the element is needed or not. Here's how this would handle the use cases listed above. > [Use-case F:] A website has a page where media is the primary content. > It would like to make sure that media is downloaded before JS [e.g. > flickr, youtube, facebook photos] <!DOCTYPE HTML> <html> <title>Photo 23 in My Gallery</title> <body> <h1>Photo 23</h1> <p><img src="photo23.jpeg" alt="..."></p> <script src="more-features.js" load-policy="low-priority"></script> </body> > [Use-case G:] A website knows there's a piece of Javascript code that > the user might need if they click on a part of the page. The developer > would like to have the user download it, but not at the expense of other > resources. <script src="button-reaction.js" id="reaction" load-policy="when-needed precache low-priority"> // button-reaction.js defines react() </script> <button type=button onclick="document.scripts.reaction.load().then( function() { react(); })"> Part of the Page </button> > [Use-case H:] A website is prefetching photos in a photo album and would > like to make sure these images are lower priority than images the user > is actually viewing. <img src="photo1.jpg" alt="..." load-policy="when-needed precache"> <img src="photo2.jpg" alt="..." load-policy="when-needed precache"> <img src="photo3.jpg" alt="..." load-policy="when-needed precache"> <img src="photo4.jpg" alt="..." load-policy="when-needed precache"> <img src="photo5.jpg" alt="..." load-policy="when-needed precache"> As they come into view, they'll become needed automatically. When they are not needed, they get precached if that wouldn't get in the way of other things getting loaded. On Mon, 28 Jul 2014, Ben Maurer wrote: > > [Use-case I:] Facebook uses an automated pipeline to decide what CSS/JS > files to package together. If we could pass a custom header in the > request for a CSS file (for example: what part of the page caused that > file to be loaded) we could use this information in our packaging system > to make smarter decisions. <link rel=stylesheet href="..." load-settings='{ "headers": { "X-Facebook-Blame" : "<head>" } }'> ...or some such. The exact syntax of load-settings="" isn't something I've studied closely. It might make sense to have a more dedicated syntax, since JSON doesn't work well in HTML attributes due to quoting rules. >From script: var style = document.createElement('link'); style.rel = 'stylesheet'; style.href = '...'; style.loadSettings = { "headers": { "X-Facebook-Blame" : "<head>" } }.toJSON(); ...or some such. Again, it might make sense to have a dedicated API for loadSettings which would make this better. Also, medium term, Tab is working on a StyleSheet object and constructor. > [Use-case J:] The way we render pages at Facebook [is essentially that > we] flush sections of HTML with a list of CSS and JS dependencies for > each section. The HTML is only rendered once these resources are loaded. We could have dedicated features for this (like making any element depend on any other, making <div>s whose dependencies aren't met be display:none, or even having :loaded/:ready pseudo-classes to match elements based on their dependencies' statuses) but for now, even with the stuff above, the simplest solution is probably more or less what you'd do now: <section hidden class="foobar"> <!-- this is the section of HTML mentioned in the use case --> <!-- each dependency is listed explicitly: --> <script> // Prior to this we must have loaded a script that defines two // functions. The first adds style sheets, the second adds scripts. // They return promises. All the things added by these functions are // fetched right away. var promises = []; // To add a style sheet, you need to know the URL to the sheet, // any sheets it depends on, and the relative order amongst // all the sheets in the project promises.push(addStyleSheet('common.css', [], 1)); promises.push(addStyleSheet('foobar.css', ['common.css'], 4)); // To add a script, you just need the URL and its dependencies. promises.push(addScript('common.js', [])); promises.push(addScript('foobar.js', ['common.js'])); var section = document.currentScript.parentNode; Promise.all(promises).then(function () { section.hidden = false; }) </script> </section> Depending on the size of your total dependency tree, you could predeclare it in the <head> and just refer to that from import statements, something like: <link rel=stylesheet load-policy="declare when-needed" href="common.css"> <link rel=stylesheet load-policy="declare when-needed" href="foobar.css" needs="common.css"> <script src="common.js" load-policy="declare when-needed"></script> <script src="foobar.js" load-policy="declare when-needed" needs="common.js"></script> ...and then later when you send the markup: <section hidden class="foobar"> <!-- this is the section of HTML mentioned in the use case --> <!-- each dependency is listed explicitly: --> <script type="module"> import "./foobar.css"; import "./foobar.js"; document.currentScript.parentNode.hidden = false; </script> </section> If you gave the <link> and <script> blocks IDs, you could refer to them that way instead, to be more explicit: <link rel=stylesheet load-policy="declare when-needed" href="common.css" id=common-css> <link rel=stylesheet load-policy="declare when-needed" href="foobar.css" id=foobar-css needs="common.css"> <script src="common.js" load-policy="declare when-needed" id=common-script></script> <script src="foobar.js" load-policy="declare when-needed" needs="common.js" id=foobar-script></script> ... <section hidden class="foobar"> <!-- this is the section of HTML mentioned in the use case --> <!-- each dependency is listed explicitly: --> <script type="module"> import "#foobar-css"; import "#foobar-script"; document.currentScript.parentNode.hidden = false; </script> </section> However, giving all the dependencies ahead of time doesn't really scale for even medium-sized sites, unfortunately. > [Use-case K:] We have a few other use cases for this type of dependency > (for example, we have a method where you can say "call this callback > once resources X, Y and Z are requested"). Promise.all([X.loaded, Y.loaded, Z.loaded]).then(thisCallback) > [Use-case L:] A web page wants to load and execute a script widget.js if > the script is already cached in the browser. However, it wants to load > other essential assets such as images first if it's not already in the > cache except as long as the user had not started interacting with the > parts of the page that require widget.js. <script id=w src=widget.js load-policy="optimistic low-priority"> </script> <div> <!-- part of the page that needs the widget --> <script> var div = document.currentScript.parentNode; function startWidget () { document.scripts.w.load(); div.removeEventListener('mouseover', startWidget, true); div.removeEventListener('focus', startWidget, true); }; div.addEventListener('mouseover', startWidget, true); div.addEventListener('focus', startWidget, true); </script> </div> > [Use-case M:] Being able to specify that an image/video should only be > downloaded "on demand" (aka "lazily"), i.e. when it's in view, or about > to be in view. Use case is both to lower bandwidth in cases of long > pages where the user doesn't always scroll to the bottom, as well as > make sure to fill the network pipe with the most important resources > first. <img src="..." alt="..." load=policy="when-needed"> > [Use-case N:] Being able to specify that a stylesheet should not block > rendering. Use case is to preload stylesheets that will be used by > content that isn't rendered in the initial view, but that might be > rendered later. There's various options here. If we're going to explicitly invoke the style sheet in due course: <link rel=stylesheet href="..." id="mystyle" load-policy="when-needed precache"> // when needed: document.getElementById('mystyle').load(); If we're just hoping it'll be loaded slower, but will be applied automatically: <link rel=stylesheet href="..." id="mystyle" load-policy="async low-priority"> Medium term we might want to consider letting random elements depend on the style sheet, not just <img>, so that when the elements that do use this come into view, the style sheet can be forced on (and maybe the aforementioned pseudo-classes can be used to display:none the element while it's loading the style sheet or something.) > [Use-case O:] Being able to specify some form of prioritization for > resources like (non-blocking) stylesheets, (non-blocking) <script>s, > images, video, iframes etc. Use case is making sure to fill the network > pipe with the most important resources first. Possibly a simple > prioritization like "needed for initial rendering/not needed for initial > rendering" is enough, possibly there are needs for more finegrained > prioritization like "needed for initial rendering/needed for page > feature X that is commonly used, needed for other". The low-priority flag should help with this. To give more control, if RequestInit (part of Fetch) exposes HTTP2's priorities, authors can directly manipulate that. > [Use-case P:] download dynamic page components (e.g. maps) only on > larger devices. Long term, we could add a media="" attribute to <script> to make this easier. Short term, you can do it with scripts by checking the width of the device and calling load() on the script if you want it. If you're a browser vendor who wants to implement <script media>, please comment on this bug: https://www.w3.org/Bugs/Public/show_bug.cgi?id=23509 > [Use-case Q:] I am dynamically loading one of those social widgets that, > upon load, automatically scans a page and renders social buttons. I need > to be able to preload that script so it's ready to execute, but decide > when I want it to run against the page. I don't want to wait for true > on-demand loading, like when my user clicks a button, because of the > loading delay that will be visible to the user, so I want to pre-load > that script and have it waiting, ready at a moment's notice to say "it's > ok to execute, do it now! now! now!". You'd declare it like this: <script ... load-policy="when-needed precache" id=w> ...and run it like this: document.scripts.w.load(); > [Use-case S:] One CMS plugin wants to load "A.js" and "B.js", where B > relies on A. Both need to load in parallel (for performance), but A must > execute before B executes. I don't control A and B, so changing them is > not an option. This CMS plugin [wants] to wait for some > user-interaction, such as a button click, before executing the code. We > don't want there to be any big network-loading delay visible to the user > between their click of the button and the running of that plugin's code. <script src="a.js" load-policy="when-needed precache" id=a> <script src="b.js" load-policy="when-needed precache" needs=a id=b> <input type=button onclick="document.scripts.b.load()"> > Another CMS plugin on this same page wants to load "A.js", "C.js", and > "D.js". This plugin doesn't know or care that the other plugin also > requests "A.js". It doesn't know if there is a script element in the > page requesting it or not, and it doesn't want to looking for it. It > just wants to ask for A as a pre-requisite to C and D. But C and D have > no dependency on each other, only a shared dependency on A. C and D > should be free to run ASAP (in whichever order), assuming that A has > already run [once] some user-interaction that initiates the load of A, > C, and D. This user interaction may be before the other plugin requested > A, or after it requested A. First the previous part: <script src="a.js" load-policy="when-needed precache" id=a1> <script src="b.js" load-policy="when-needed precache" needs=a1 id=b1> <input type=button onclick="document.scripts.b1.load()"> Now this plugin: <script src="a.js" load-policy="when-needed precache" id=a2> <script src="c.js" load-policy="opportunistic precache" needs=a2 id=c2> <script src="d.js" load-policy="opportunistic precache" needs=a2 id=d2> <input type=button onclick="document.scripts.c2.load(); document.scripts.d2.load();"> Because the load-policy doesn't contain "force" (which would be in the default for scripts), the second <script src="a.js"> defers to the first one. > "A.js" can be requested relatively (via a <base> tag or just relative to > document root) or absolutely, or it might be requested with the > leading-// protocol-relative from, taking on whatever http or https > protocol the page has, whereas other references to it may specify the > prototcol. That just works with the above, since the src="" attribute is resolved before being compared. > These plugins can't guarantee what ID's or classes they use are reliably > unique without undue processing burden. Ah. This is more problematic. If the plugins are put into HTML imports, this works because imports have their own ID scope. Is that sufficient? > [Use-case T:] I have two different calendar widgets. I want to pop one > of them up when a user clicks a button. The user may never click the > button, in which case I don't want the calendar widget to have ever > executed to render. [...] > > It'd be nice if both calendar widgets were built sensibly so that > loading the code didn't automatically render. One of them IS, the other > is unfortunately mis-behaving, and it will render itself as soon as its > code is run. [...] > > Furthermore, these two widgets are not "equal". Perhaps one is better > for smaller browser window sizes, and the other is better for larger > browser windows. [...] > > Regardless, the point is, there's run-time conditions which are going to > determine if I want to execute calendar widget A or B, or maybe I never > execute either. But I want them both preloaded and ready to go, just in > case, so that if the user DOES need one, it's free and ready to execute > with nearly no delay, instead of having to wait to request as I would > with only on-demand techniques. <script src="calendar1.js" load-policy="when-needed precache" id=cal1> </script> <script src="calendar2.js" load-policy="when-needed precache" id=cal2> </script> <script> function showCalendar() { if (useCalendar1) document.scripts.cal1.load(); else document.scripts.cal2.load(); } </script> <input type=button onclick="showCalendar()"> > [Use-case U:] I have a set of script "A.js", "B.js", and "C.js". B > relies on A, and C relies on B. So they need to execute strictly in that > order. [Now], imagine they progressively render different parts of a > widget. [...] I only want to execute A, B and C once all 3 are preloaded > and ready to go. It's [...] about minimizing delays between them, for > performance PERCEPTION. > > [For example, one of them might start playing a video, and another might > introduce the <canvas> slides for that video. You want all of the > relevant scripts to be run at once, so there's no point where the page > has a <video> element but doesn't have the <canvas>.] <script src="A.js" id=A load-policy="late-run"></script> <script src="B.js" id=B load-policy="late-run" needs=A></script> <script src="C.js" id=C load-policy="async" needs=B></script> > [Use-case V:] you have a string of scripts ("A.js", "B.js", and "C.js") > loading which constitute a dependency chain. A must run before B, which > must run before C. However, if you detect an error in loading, you stop > the rest of the executions (and preferably loading too!), since clearly > dependencies will fail for further scripts, and the errors will just > unnecessarily clutter the developer console log making it harder to > debug. That's the built-in behaviour. > [Use-case W:] some developers have even requested to be able to stop the > chain and prevent further executions if the script loads, but there's > some compile-time syntax error or run-time error that happens during the > execution. For them, it's not enough for B to simply finish loading > successfully, but that it must fully execute without error. If something throws, it'll trigger the same failure behaviour as failing to load at all. > [Use-case X:] not all dependencies are JS files, e.g. authors use > plugins to load template files, JSON, images, etc. Images are handled since you can use an <img> element as the target of a needs="" attribute. You can add dependencies on manually-started loads using promises; these won't automatically trigger the loads, but they will cause the loads to wait. > [Use-case Y:] not all dependencies are usefully satisfied immediately > after their JS file is loaded, e.g. some libraries may need asynchronous > initialization. You can add a promise as a dependency. > [Use-case Z:] Another common kind of dependency scripts have is presence > of certain element in the DOM, e.g. `dropdown-menu.js` may require `<nav > id="menu">` to be in the document _and_ have its content fully parsed > before the script can run. needs="" can point to any element by ID; it'll only be resolved once the ID is present in the DOM. On Tue, 27 Aug 2013, Ian Hickson wrote: > > Jake also mentioned these requirements: > > | - Provides an adoption path for browsers that don't support the new > | feature (happy for the fallback to be blocking document-order > | execution) That's the fallback for the declarative features. (This might actually be a problem for load-policy=declare.) > | - Is discoverable by pre-parsers (so async=false and old-IE's > | readystate methods aren't enough) This is true for the declarative cases, at least. Note though that if we make everything async (as this essentially would), preparsing becomes much less important. > And Kyle mentioned this scenario that we need to handle as well (not > strictly a use case, more a variant on the above use cases): > > > I want to preload a script which is hosted somewhere that I don't > > control caching headers, and to my dismay, I discover that they are > > serving the script with incorrect/busted/missing caching headers. We can handle this by defining the behaviour of "precache" more explicitly, or putting options or load-settings="", but I'm not sure what behaviour we want exactly. What headers should we ignore? When? On Wed, 23 Jul 2014, Ben Maurer wrote: > On Tue, Jul 22, 2014 at 5:33 PM, Ian Hickson <ian@hixie.ch> wrote: > > > > Why not: > > > > var mystyle = E('link', { rel: 'stylesheet', href: 'my.css', > > whenneeded: true }); > > document.body.appendChild(mystyle); > > var myfetch = mystyle.fetch; > > ... > > Yeah, I think that API would be perfect (assuming that whenneeded=true > initiates the fetch, but not the load into the document). In combination > with allowing the user to specify an attribute with a set of parameters > to the fetch algorithm (eg, a custom header) I think that would cover > the use cases I was thinking of. So this would be: var mystyle = E('link', { rel: 'stylesheet', href: 'my.css', 'load-policy': 'declare prefetch', // declare prevents it from ever being applied 'load-settings': '...' // this is your custom headers, etc }); document.body.appendChild(mystyle); mystyle.load(); // triggers the fetch // since it's marked "declare", it doesn't get automatically applied mystyle.loaded.then(function () { // we can remove the 'declare' part of the policy to make it apply: mystyle.loadPolicy.remove('declare'); // alternatively we could create a new <link rel=stylesheet> element // and insert that; since the URL is the same it would reuse what it // got for mystyle instead of starting anew. }); var myfetch = mystyle.request; // you can manipulate the priority or whatever with myfetch -- Ian Hickson U+1047E )\._.,--....,'``. fL http://ln.hixie.ch/ U+263A /, _.. \ _\ ;`._ ,. Things that are impossible just take longer. `._.-(,_..'--(,_..'`-.;.'
Received on Saturday, 23 August 2014 00:46:35 UTC