No fuzz development with EcmaScript modules

Browser support for EcmaScript modules (ESM) is good. In my opinion, widespread enough to take advantage of to reduce complexity in your development workflow.

Let me describe a bundler free approach for developing, unit testing and shipping JavaScript. We'll unit test our code in Node.js and then serve the code unchanged to the browser.

Unit testing ES modules

By using file extension .mjs with your JavaScript files, you'll get out of the box Node.js support (as of v13.2.0) for EcmaScript modules, and have import and export statements available to you.

Let's build a module, for which we'll add unit tests and then ship to the browser.

First install the test runner AVA and thereafter create test-convert-temp-units.mjs for our unit tests.

Prepare unit testing

sh
# Install ava with npm
> npm install ava --save-dev

# Create folder and unit tests script
> mkdir test
> touch test/test-convert-temp-units.mjs

As the filename hints, we'll be converting units of temperature, between Celsius to/from Fahrenheit.

Unit tests

js
// test/test-convert-temp-units.mjs
import test from "ava";
import { cToF, fToC } from "../src/convert-temp-units.mjs";

test("celsius to fahrenheit", t => {
  t.is(cToF(10), 50);
});

test("fahrenheit to celsius", t => {
  t.is(fToC(95), 35);
});

test("same °F as °C", t => {
  t.is(cToF(-40), fToC(-40));
});

If we execute the tests with npx test, we'll get an error explaining Cannot find module [...]/src/convert-temp-units.mjs. Let's create our application code module.

Code for temperature conversion

js
// src/convert-temp-units.mjs
export const cToF = celsius =>
  celsius * 9/5 + 32;

export const fToC = fahrenheit =>
  (fahrenheit - 32) * 5/9;

Another run with AVA should now show us 3 passing tests.

Run unit tests

sh
> npx ava --verbose

  ✔ celsius to fahrenheit
  ✔ fahrenheit to celsius
  ✔ same °F as °C

  3 tests passed

ES modules in the browser

To consume our code in the browser, we'll have to declare that the script is an EcmaScript module. This is achieved with <script type="module">. Let's create a web page for converting temperature units, where we import the file convert-temp-units.mjs.

Import ES module in browser

html
<!DOCTYPE html>
<html lang="en">

<head>
  <title>°C to °F</title>
</head>

<body>
  <main>
    <h1>Temperature unit conversion</h1>
    <form>
      <label for="celsius">°C</label>
      <input type="number" id="celsius" name="celsius">
      <output data-unit="fahrenheit"></output>
    </form>

    <form>
      <label for="fahrenheit">°F</label>
      <input type="number" id="fahrenheit" name="fahrenheit">
      <output data-unit="celsius"></output>
    </form>
  </main>

  <script type="module">
    // import functions from .mjs
    import { cToF, fToC } from "./convert-temp-units.mjs";

    const asFahrenheit = document.querySelector("*[data-unit='fahrenheit']");
    const asCelsius = document.querySelector("*[data-unit='celsius']");

    celsius.addEventListener("input", (e) => {
        const { value } = e.target;
        asFahrenheit.textContent = cToF(value);
    });

    fahrenheit.addEventListener("input", (e) => {
        const { value } = e.target;
        asCelsius.textContent = fToC(value);
    });
  </script>
</body>
</html>

Notice how the the conversion functions are imported and called the same way as in Node.js.

No bundler

If you know your target audience, you can make life easier for yourself by developing interactive web pages like in the good ol' days. Choose your development stack to match your user's needs. Make deliberate decisions. Ask yourself if you can serve your application without using a bundler. I'll bet, quite often you can improve your development experience by not adding unnecessary tooling.