[whatwg] Script preloading

Ryosuke Niwa rniwa at apple.com
Tue Sep 3 16:36:44 PDT 2013


Per IRC discussion, I misunderstood the timing at which these at which dependencies are executed.  Now I agree it's desirable to have two values for when needed as proposed by Ian in the original e-mail.

For other people following this thread's sake, a.js will execute immediately as soon as it's loaded in this example:
<script src="a.js" whenneeded></script>
<script id=b src="b.js" needs="a.js" whendeeded></script>
// later
document.scripts.b.execute();

whereas a.js doesn't get executed until b.js is loaded in this example:
<script src="a.js" whenneeded=jit></script>
<script id=b src="b.js" needs="a.js" whendeeded></script>
// later
document.scripts.b.execute();

- R. Niwa

On Sep 3, 2013, at 2:49 PM, Ryosuke Niwa <rniwa at apple.com> wrote:

> Original proposal:
> http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-August/040664.html
> http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-August/040666.html
> 
> In order to address use cases incDependencies and decDependencies satisfied, I'm going to add the following proposals:
> 
> I make one more change that 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.
> 
> 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.
> 
> On Aug 27, 2013, at 2:55 PM, Ian Hickson <ian at hixie.ch> wrote:
> 
>> 
>> First, let's get down to use cases. Kyle did a great job of describing 
>> some key use cases:
>> 
>> 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!".
> 
> <script id="social" src="social-button.js" prefetch></script>
> <button onmouseover="document.scripts.social.execute()"> ... </button>
> 
>>> [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.
> 
> // CMS plugin 1
> var A = E('script', { src: 'A.js', prefetch: true });
> var B = E('script', { src: 'B.js', needs: 'A.js', prefetch: true });
> document.body.append(A, B);
> function sawUserInteraction() {
>  B.execute();
> };
> 
> 
>>> 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.
> 
> // CMS plugin 2
> var A = E('script', { src: 'A.js', prefetch: true });
> var C = E('script', { src: 'C.js', needs: 'A.js', prefetch: true });
> var D = E('script', { src: 'D.js', needs: 'A.js', prefetch: true });
> document.body.append(A, C, D);
> function sawUserInteraction() {
>  C.execute();
>  D.execute();
> };
> 
>>> "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. [...]
> 
> <script id=calA src="a.js" prefetch></script>
> <script id=calB src="b.js" prefetch></script>
> <script>
> function showCalendar(which) {
> if (which == 'a')
>   document.scripts.calA.execute();
> else
>   document.scripts.calB.execute();
> }
> </script>
> 
> Neither Ian's nor my proposal fallbacks gracefully here :(
> 
>>> [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" prefetch></script>
> <script src="B.js" prefetch needs="A.js"></script>
> <script id=c src="C.js" prefetch needs="B.js"></script>
> <script>
> // when we need it...
> document.scripts.c.execute();
> </script>
> 
>> 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.
> 
> <script src="A.js" prefetch></script>
> <script src="B.js" prefetch needs="A.js"></script>
> <script id=c src="C.js" prefetch needs="B.js"></script>
> <script>
> onerror = function (message, source, lineno, colno, error) {
>   // report error
> }
> // when we need it...
> document.scripts.c.execute();
> </script>
> 
>>> [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.
> 
> <script id=a src="A.js" whenneeded></script>
> <script src="B.js" whenneeded needs="A.js"></script>
> <script id=c src="C.js" whenneeded needs="B.js"></script>
> <script>
> function onAfailedToInitialize() {
>  document.scripts.a.src = null;
> }
> // when we need it...
> document.scripts.c.execute();
> </script>
> 
>> 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.
> 
> <script src="deps.js" async></script>
> <script src="build.js" needs="deps.js"></script>
> 
> // in deps.js:
> var me = document.currentScript;
> var image = new Image();
> image.src = 'image.png';
> currentScript.needs.add(image);
> 
>>> [Use-case Y:] not all dependencies are usefully satisfied immediately 
>>> after their JS file is loaded, e.g. some libraries may need asynchronous 
>>> initialization.
> 
> I don't intend to address use case but we could make it so that script element without src is an indefinite dependency as in:
> 
> <script src="slowLoad.js" async></script>
> <script src="next.js" needs="slowLoad.js"></script>
> 
> // in slowLoad.js:
> 
> var me = document.currentScript;
> var dummyScript = document.createElement('script');
> me.needs.add(dummyScript);
> me.incDependencies();
> setTimeout(init, 1000);
> function init() {
>  // ok, we're ready
>  me.needs.remove(dummyScript);
> }
> 
>>> [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.
> 
> I guess we can somehow make needs.add take an id as well as a script name and a node?
> Or add a method like requireElementWithId as in:
> 
> <script src="navi.js" async></script>
> // in nav.js:
> document.currentScript.requireElementWithId('menu');
> 
> - R. Niwa
> 




More information about the whatwg mailing list