New Jun 9, 2025

How to import() a JavaScript String

Top Front-end Bloggers All from Zach Leatherman View How to import() a JavaScript String on zachleat.com

You can use arbitrary JavaScript in front matter in Eleventy project files (via the js type).

Historically Eleventy has made use of the node-retrieve-globals package to accomplish this, which was a nightmarish conglomeration of a few different Node.js approaches (each with different advantages and drawbacks).

Related research: Dynamic Script Evaluation in JavaScript

The biggest drawbacks to node-retrieve-globals include:

Regardless, this abomination was a necessary evil due to the experimental status of Node.js’ vm.Module (since Node v12, ~2019), the ESM-friendly partner to CommonJS-compatible vm.Script. I’d still love to see vm.Module achieve a level of stability, but I digress.

New Best Friend is import()

Moving forward, I’ve been having success from a much lighter approach using import(), described in Evaluating JavaScript code via import() by Dr. Axel Rauschmayer. It looks something like this:

let code = `export default function() {}`;
let u = `data:text/javascript;charset=utf-8,${encodeURIComponent(code)}`;
let mod = await import(u);

Newer runtimes with Blob support might look like this (example from David Bushell):

let code = `export default function() {}`;
let blob = new Blob([code], {type: "text/javascript"});
let u = URL.createObjectURL(blob);
let mod = await import(u);
URL.revokeObjectURL(u);

Limitations

  1. Importing a Blob of code does not work in Node.js (as of v24), despite Node having support for Blob in v18 and newer.

    Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. Received protocol 'blob:'

  2. import.meta.url just points to the parent data: or blob:, which isn’t super helpful in script.
  3. No import of relative references, even if you’ve mapped it to a full path via an Import Map.
    • e.g. import './relative.js'

      TypeError: Failed to resolve module specifier ./relative.js: Invalid relative url or base scheme isn't hierarchical.

  4. No import of bare references. These can be remapped via Import Maps.
    • e.g. import 'barename'

      TypeError: Failed to resolve module specifier "barename". Relative references must start with either "/", "./", or "../".

Though interestingly, Node.js will let you import builtins e.g. import 'node:fs'.

Enter import-module-string

I’ve worked around the above limitations and packaged this code up into import-module-string, a package that could be described as a super lightweight runtime-independent (server or client) JavaScript bundler.

I was able to repurpose a package I created in June 2022 to help here: esm-import-transformer recursively preprocesses and transform imports to remap them to Blob URLs (falling back to data: when a feature test determines Blob doesn’t work).

import { importFromString } from "import-module-string";

await importFromString(</span><span class="token string">import num from "./relative.js"; export const c = num;</span><span class="token template-punctuation string">);

Where relative.js contains export default 3;, the above code becomes (example from Node.js):

await importFromString(`import num from "data:text/javascript;charset=utf-8,export%20default%203%3B";
export const c = num;`);

Which returns:

{ c: 3 }

This transformation happens recursively for all imports (even imports in imports) with very little ceremony.

When you’ve added a <script type="importmap"> Import Map to your HTML, the script will use import.meta.resolve to use the Import Map when resolving module targets.

Even more features

A few more features for this new package:

As always with dynamic script execution, do not use this mechanism to run code that is untrusted (especially when running in-browser on a domain with privileged access to secure information like authentication tokens). Make sure you sandbox appropriately!

Scroll to top