Skip to content

Instantly share code, notes, and snippets.

@jakearchibald
Last active December 17, 2015 13:39
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save jakearchibald/5618497 to your computer and use it in GitHub Desktop.
<!--
In an HTTP2 world, delivering multiple small individually cachable
and executable scripts can be more efficient than one big
concatenated script. Can we better prepare for this? Can it be
done in a way that works with existing JS files?
Imagine a page that had 4 interactive modules, all of which
depended on a couple of utility scripts. We could progressively
add behaviour to the page by executing the 'action' scripts
as soon as they download & the utilities have executed.
This means the execution of actions4.js isn't blocked on
actions1-3.
We still want scripts defined in HTML, so preloaders and scanners
pick them up and start the download as soon as possible, without
executing JavaScript.
-->
<!--
In this example, we introduce a 'future' attribute.
'future' downloads the scripts as 'async', but does not execute
them. Browsers that don't support 'future' will fall back to
synchronous in-order execution.
-->
<script src="utils1.js" class="script-utils-1" future></script>
<script src="utils2.js" class="script-utils-2" future></script>
<script src="actions1.js" class="script-actions" future></script>
<script src="actions2.js" class="script-actions" future></script>
<script src="actions3.js" class="script-actions" future></script>
<script src="actions4.js" class="script-actions" future></script>
<script>
if (document.scripts[0].execute) { // simple feature detect
// execute() returns a Future which resolves on successful
// parse & execution (no errors thrown in initial execution)
document.querySelector('.script-utils-1').execute().then(function() {
// utils2 depends on utils1, so execute it now
return document.querySelector('.script-utils-2').execute();
}).then(function() {
// the rest of the scripts depend on utils1 & utils2
// but can run execute in any order
return Future.every(
toArray(document.querySelector('.script-actions')).map(function(script) {
return script.execute();
});
);
}).then(function() {
// all scripts have executed
});
}
</script>
<!--
This example does the same as above, but entirely declaratively.
'depends' downloads the scripts as 'async', but does not execute
until all scripts matching the selector(s) have executed.
Empty 'depends' will behave exactly as 'async'.
Browsers that don't support 'depends' will fall back to
synchronous in-order execution.
However, this syntax allows for circular dependencies. Also,
what happens if I dynamically add a script.script-utils before
one of the action scripts has finished downloading?
What if I had a img[src=utils1.js], or even div[src=utils1.js]
in the document by accident?
-->
<script src="utils1.js" class="script-utils" depends></script>
<script src="utils2.js" class="script-utils" depends="[src=utils1.js]"></script>
<script src="actions1.js" class="script-actions" depends=".script-utils"></script>
<script src="actions2.js" class="script-actions" depends=".script-utils"></script>
<script src="actions3.js" class="script-actions" depends=".script-utils"></script>
<script src="actions4.js" class="script-actions" depends=".script-utils"></script>
<script depends=".script-actions">
// all scripts have executed
</script>
@sdd
Copy link

sdd commented May 21, 2013

How would the declarative syntax that you've outlined handle fallbacks, e.g. when loading from a CDN and falling back to a locally hosted resource if the CDN request fails?

@nzakas
Copy link

nzakas commented May 21, 2013

I'd vote for not worrying about dependencies. I tend to think of JavaScript dependencies as something that shouldn't be defined within HTML, rather in some external spot. I think it gives HTML far too much responsibility over how things work. The then() approach is flexible enough that you could do it in an external file...though I'm still not sure it's worth it.

@stevesouders
Copy link

One feature I like about ControlJS ( http://controljs.org/ ) is that, in addition to being declarative, it manages external & inline script execution order. It doesn't do anything for dependencies.

@dwilmer
Copy link

dwilmer commented Jun 6, 2013

Circular dependencies are really easy to detect. It is harder how to handle them.
Some options that I can think of:

  1. Fail and generate a clear error stating that there is a circular dependency. If you don't know how it works, don't use it or just google it. I think we're all grown up, and if you care enough to use this you care enough how to use this properly.
  2. Break the dependency by loading the first declared of the cycle first, regardless of dependencies. Produce a warning about the cyclic dependency.
  3. Only allow dependencies on previously declared scripts, and either fail with an error or try to work around it with a warning. This prevents cyclic dependencies, makes it easier for developers to detect them and might improve parsing speed.

I favour failures with errors over trying to do stuff with warnings, but the latter has been the web way of working since the first browsers. In any case, the possibility to screw up is - in my opinion - not a valid reason for not doing it, if it is so easy to get it right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment