DEV Community

Ibrahim Tanyalcin
Ibrahim Tanyalcin

Posted on


ES6 Modules and Declarative Dependencies in HTML

ES6 Modules and Declarative Dependencies in HTML


This is a follow up write up of my previous work:

GitHub logo IbrahimTanyalcin / taskq

Async module loader with declerative dependency management in HTML and compatibility with ES6 import/export


Build Status Patreon donate Codacy Badge Npm Badge DOI

Async Modules Supporting ES5 & ES6 with Control Flow

⇩ First, your 1 min cheatsheet ⇩



If you want you can jump straight into the examples.


  • 0 dependencies
  • No polyfill required
  • No transpiling required.
  • No config file etc.
  • About 6kB when minimized
  • Will work on ie9+.
  • Will play nice with other technologies/patterns you use in your page
  • Non-render blocking
  • You can pause/resume the main thread
  • Aware of document state (hidden, minimized etc.)
  • Fine grained control on execution of all imported scripts.
  • Uses Promises combined with requestAnimationFrame(rAF). Falls back to rAF on older browsers (ie etc).
  • Supports nested/single dynamic imports. Your main thread will wait until a module finishes dynamically importing other modules.
  • Supports then , catch phrases for dynamic imports.
  • You can do things that you cannot do with ES6 directly

It tries to reason about what it would be like to have your dependencies declared in HTML as attributes.

Taskq.js is not new, I have released it on October 2017, since then I have been using it to upkeep a fairly large project and add new stuff to it as I see fit.

As you might know, one of the hottest debates going around in the js landscape is the comparison of module loaders/bundlers and their advantages over each other. The problem is quite clear: they are not compatible with each other, at least not without putting together another “middle man” build time tool.

It is quite understandable that consumers of modules do not wanna deal with dependency management but if you think about your own projects, there are ALWAYS parts of the app that you want to retain some control if not completely. You can bundle or do whatever you want with some parts of your app while still having control on the flow of the newly added parts. This is what Taskq was designed for, to create a future compatible(combine with ES6 import/export) module system that operates with async scripts (parallel + HTTP2 alleviates need for bundling) and gives you control over their order of execution while reducing your job of managing dependencies (point to crucial leafs in dependency tree, Taskq will work the rest out).

Today I want to go over a small exercise, we will have a minimal html with 3 async scripts that depend on each other and we will embed the dependencies right in the HTML (a version for ie11 would have to be more verbose). You can complicate this by adding several scripts with comma separated dependencies several layers deep, but I will keep it simple here. Here is the html we have:

We have 3 async scripts A, B and C which should execute in this order. From my previous write-ups, you perhaps know that we declare the _taskqId and _taskqWaitFor properties on the modules, this time instead of adding these within the module, we will directly grab it from the attribute:

X._taskqId = document.currentScript.dataset.taskqid;
X._taskqWaitFor = document.currentScript.dataset.taskqwaitfor.split(",").filter(d=>d);

So instead of hard coding the properties, we can grab them from data-taskqid and data-taskqwaitfor attributes. We do not need to know the script name as we can use the document.currentScript in evergreen browsers. So what do the scripts look like?:

They are almost identical to each other, they only export their own variables. If you open the HTML file you will get this:


You can go ahead and add more script tags and make the dependencies several layers deep or add more dependencies such as data-taskqwaitfor='X,Y,Z,...'. You can even try to add circular references, in that case Taskq will try to resolve and also display a console message.

Another interesting part here is that you can still use ES6 import/export. For instance nothing is barring you from doing this:

\<script type="module" src="./B.js" data-taskqid="B" data-taskqwaitfor="A" charset="UTF-8" async>

Enter fullscreen mode Exit fullscreen mode

And then within the module you can do:

import someExport from "./A.js";
function X(exportA){

In ES6, as far as I know, the imported modules are guaranteed to be run before the current script that imported them, but the order of execution of the imported modules cannot be guaranteed and is vendor implementation dependent. In these cases, let ES6 figure out its own dependency and import these variable as it wants, we could care less when the iief is executed!

You will still have control on the execution order of what is pushed to taskq within the outer iief. This way you take advantage of ES6 while retaining control on the execution order of the bits you care about.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)

An Animated Guide to Node.js Event Loop

Node.js doesn’t stop from running other operations because of Libuv, a C++ library responsible for the event loop and asynchronously handling tasks such as network requests, DNS resolution, file system operations, data encryption, etc.

What happens under the hood when Node.js works on tasks such as database queries? We will explore it by following this piece of code step by step.