[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