Let’s say you’re working on a static site generator,
and while implementing new features you decide to import text files (HTML, MD, CSS, SVG, etc) as dependencies into JavaScript files.
Sure, you may use a bundler with some loader, but what if your bushidō mandates you not to type npm install something every time you have to solve a problem?
Well, there is a solution.
You have Node.js installed, and this Node.js is not outdated.
You’re not using .mjs as a file extension for your JavaScript code, browsers do not like them, even though the syntax of the files are the same as .js. However, it’s important only if you want to write isomorphic code.
Now move to a browser. The task remains the same. We want to load modules, but not JS modules, rather we want static files with styles, templates and plain text. Browsers do not support special “hooks” for ES Modules. What’s the plan?
Now serve the page, open DevTools and check the logging:
It works. Again.
No bundlers, no additional stuff. Just pure JavaScript and browser API. Cool, isn’t it?
If you decide to use this solution in a real project, read Service Worker’s docs carefully.
There are some important limitations and nuances of compatibility with different web browsers.
For instance, currently Firefox does not run Service Workers in private browsing mode.
This is by design,
but probably will be changed in the future.
Okay, now it’s time for sick stuff. It’s just an experiment and I don’t encourage you to try it, but why not try when you can?
If you don’t know, ES Modules support top level await.
It means that before a module “returns” its export, you can do something asynchronous in it.
For instance, make a network call and get a response. Where does this lead us? Right.
Import everything else using this module as a proxy:
// module.jsimport html from'./load.js?./index.html';import doc from'./load.js?./doc.md';import css from'./load.js?./styles.css';import svg from'./load.js?./image.svg';import data from'./load.js?./data.json';
console.log(html, doc, css, svg, data);
Even though the example is simplified, it’s already isomorphic.
Which means you can use the code above in a browser and in Node.js without any changes.
Try it.
I don’t think that anyone will seriously use this method to load modules, but the approach itself is at least interesting to think about.
An important thing to note here is that each module’s exports are cached.
The cache is based on the module’s URI.
Therefore, if the imported files are changed dynamically, their URIs should be changed too.
That means that it’s possible to use dynamic imports only in this kind of situation.
But when we have a top level await support, it’s not a huge problem.