[whatwg] Script preloading

Ryosuke Niwa rniwa at apple.com
Tue Sep 3 14:49:29 PDT 2013


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