SystemJS: A module loader for the browser

Last week i had a chance to work with single-spa (a framework to build micro-frontends), that's when i first see SystemJS as an option while i initialize single-spa with CLI, so my instinctive reaction is to google it.

What is SystemJS?

From their docs:

SystemJS is a hookable, standards-based module loader.

This is how i get it:

SystemJS is a module loader. it's easy as it sounds, it loads the module from the source, at RUN TIME.

Now the why?

After my long reading, the pain point was, when ES modules (ESM) were not widely supported on the browsers, this piece of code from the scenario below was not be able to run on the browsers.

The small scenario

Let's imagine this, you have an utils file utils.js, the main file main.js, and you want to use the utils file in the main file, then load the main file in the browser.

utils.js
1// utils.js
2export function sayHi(name) {
3 return `Hi ${name}`;
4}
main.js
1// main.js
2import { sayHi } from "./utils.js";
3
4sayHi("Loki");
index.html
1<script src="main.js"></script>

This code would not execute on the browser, since the browser can not understand the import or export syntax (yes, it is now supported on most of the browsers, but not all of them, and back to the time, it was not).

So, what's should we do?

Yes, we can not just give it up, let's take a quick look some solutions that the engineers could use back then.

Change it to CommonJs

So the widely used solution was transform this code from ESM to CommonJs.

main.js
1const { sayHi } = require("./utils.js");

Use Webpack to transpile the code

Another solution here, we can use bundler (here - Webpack, with the help of Babel) to convert the ESM to CommonJs with the following config:

webpack.config.js
1module.exports = {
2 entry: "./index.js",
3 output: {
4 filename: "bundle.js",
5 },
6 module: {
7 rules: [
8 {
9 test: /\.js$/,
10 use: "babel-loader",
11 },
12 ],
13 },
14};

then import the code in the browser with the following code:

index.html
1<script src="bundle.js"></script>

Now the main dish: SystemJS

Soooooo, now we have the solution to load the code in the browser, but what if we want to use the ESM syntax with least effort?

index.html
1<script src="https://cdn.jsdelivr.net/npm/systemjs@6/dist/system.min.js"></script>
2<script>
3 System.import("./main.js");
4</script>

and the code would be able to run on the browser, how cool is that?

Step a bit deep down to SystemJS

Not only from a file, also from a url

SystemJS not only can load the code from a file, but also from a url. Let's see an example:

index.html
1<script src="https://cdn.jsdelivr.net/npm/systemjs@6/dist/system.min.js"></script>
2<script>
3 System.config({
4 paths: {
5 "npm:": "https://cdn.jsdelivr.net/npm/",
6 },
7 });
8 System.import("npm:lodash");
9</script>

Dynamic import

As usual, if we import a module, it will be loaded immediately, but what if we want to load the module dynamically, like lazy loading?

sayHi.js
1// sayHi.js
2export default function sayHi(name) {
3 return `Hi ${name}`;
4}
index.html
1<script src="https://cdn.jsdelivr.net/npm/systemjs@6/dist/system.min.js"></script>
2<script>
3 System.config({
4 paths: {
5 "sayHi-esm": "./sayHi.js",
6 },
7 });
8 window.addEventListener("click", async () => {
9 const sayHi = await System.import("sayHi-esm").default();
10 console.log(sayHi("Loki"));
11 });
12</script>

here, we can import the module at runtime, ondemand only the click event is triggered, and the module will be loaded when the promise is resolved.

My quick take

SystemJS is a powerful module loader, will be more insight to go during my single-spa journey.

Reference

Table of Contents

What is SystemJS?
Now the why?
The small scenario
So, what's should we do?
Change it to CommonJs
Use Webpack to transpile the code
Now the main dish: SystemJS
Step a bit deep down to SystemJS
Not only from a file, also from a url
Dynamic import
My quick take
Reference