Aral Balkan

Mastodon icon RSS feed icon

Make anything a JavaScript module using Node.js ESM Module Loaders

A message in a bottle on an empty beach with a beautiful blue sea behind it.

Disclaimer: you can’t load an actual bottle into Node.js (yet).

I have a message in a text file called bottle.txt. Here’s how I load it into Node.js and output it to the console:

import message from 'bottle.txt'
message()

Wait, what? How?

I’m glad you asked.

It’s thanks to Node.js’s experimental ESM Module Loaders feature. Here’s how you can implement this using the excellent node-esm-loader package which makes the Node ESM Loader API even easier to work with:

Message in a bottle(.txt file)

  1. Create a new project directory, switch to it, and initialise a new Node project there.

    mkdir message-in-a-bottle
    cd message-in-a-bottle
    npm init -y
    

    Once you’re done, edit the package.json file to add the following key/value pair so Node knows to interpret .js files as ESM and not CommonJS:

    "type": "module"
    
  2. Create a text file called bottle.txt and add the following text to it:

    This is a message in a bottle(.txt file)
    
  3. Next, create the index.js file you saw earlier:

    import message from 'bottle.txt'
    message()
    
  4. Now for the magic! Let’s make Node understand how to compile text files. First, install the node-esm-loader module to make it easier for us to work with the ESM Loaders feature:

    npm i node-esm-loader
    
  5. Next, let’s configure our loader to tell Node how to interpret text files. Create a file called .loaderrc.js with the following content:

    import { URL } from 'url'
    
    export default {
      loaders: [
        {
          resolve(specifier, opts) {
            if (specifier.endsWith('.txt')) {
              let { parentURL } = opts
              let url = new URL(specifier, parentURL).href
              return { url }
            }
          },
          format(url, opts) {
            if (url.endsWith('.txt')) {
              return { format: 'module' }
            }
          },
          transform(source, opts) {
            if (opts.url.endsWith('.txt')) {
              return {
                source: `export default function () {
                  console.log(\`${String(source)}\`)
                }`
              }
            }
          }
        }
      ]
    }
    
  6. Finally, run your project, telling Node to use your new loader:

    node --experimental-loader=node-esm-loader .
    

That’s it!

Alongside a warning that the ESM Loaders feature is experimental, you should see the following output in your terminal window:

This is a message in a bottle(.txt file)

How it works

In your loader, the resolve() and format() methods are where you tell Node that it should handle text files and treat them as JavaScript modules. And the transform() method is where you return an actual JavaScript module by compiling the text file (in this case, by wrapping its contents in a function that outputs them to the console).

Beyond the basics

So you’re most likely not going to use this for text files.

Instead, you can use it, as I am for example, to make a custom Express server that server-side renders Svelte files by using Vite as middleware.

I look forward to seeing all the creative uses you put this awesome feature to.

Like this? Fund us!

Small Technology Foundation is a tiny, independent not-for-profit.

We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.