The `import()` operator lets us dynamically load ECMAScript modules. But they can also be used to evaluate JavaScript code (as Andrea Giammarchi recently pointed out to me), as an alternative to `eval()`. This blog post explains how that works.
`eval()` does not support `export` and `import`
A significant limitation of `eval()` is that it doesn’t support module syntax such as `export` and `import`.
If we use `import()` instead of `eval()`, we can actually evaluate module code, as we will see later in this blog post.
In the future, we may get Realms which are, roughly, a more powerful `eval()` with support for modules.
Evaluating simple code via `import()`
Let’s start by evaluating a `console.log()` via `import()`:
const js = `console.log('Hello everyone!');`; const encodedJs = encodeURIComponent(js); const dataUri = 'data:text/javascript;charset=utf-8,' + encodedJs; import(dataUri); // Output: // 'Hello everyone!'
What is going on here?
- First we create a so-called data URI. The protocol of this kind of URI is `data:`. The remainder of the URI encodes the full resource instead pointing to it. In this case, the data URI contains a complete ECMAScript module – whose content type is `text/javascript`.
- Then we dynamically import this module and therefore execute it.
Warning: This code only works in web browsers. On Node.js, `import()` does not support data URIs.
Accessing an export of an evaluated module
The fulfillment value of the Promise returned by `import()` is a module namespace object. That gives us access to the default export and the named exports of the module. In the following example, we access the default export:
const js = `export default 'Returned value'`; const dataUri = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(js); import(dataUri) .then((namespaceObject) => { assert.equal(namespaceObject.default, 'Returned value'); }) .catch((err) => { console.log(err.message); // "Importing a module script failed." // apply some logic, e.g. show a feedback for the user });
Creating data URIs via tagged templates
With an appropriate function `esm` (whose implementation we’ll see later), we can rewrite the previous example and create the data URI via a tagged template:
const dataUri = esm`export default 'Returned value'`; import(dataUri) .then((namespaceObject) => { assert.equal(namespaceObject.default, 'Returned value'); }) .catch((err) => { console.log(err.message); // "Importing a module script failed." // apply some logic, e.g. show a feedback for the user });
The implementation of `esm` looks as follows:
function esm(templateStrings, ...substitutions) { let js = templateStrings.raw[0]; for (let i=0; i<substitutions.length; i++) { js += substitutions[i] + templateStrings.raw[i+1]; } return 'da
For the encoding, we have switched from `charset=utf-8` to `base64`. Compare:
- Source code: `'a' < 'b'`
- Data URI 1: `data:text/javascript;charset=utf-8,'a'%20%3C%20'b'`
- Data URI 2: `data:text/javascript;base64,J2EnIDwgJ2In`
Each of the two ways of encoding has different pros and cons:
- Benefits of
charset=utf-8
(percent-encoding):- Much of the source code is still readable.
- Benefits of
base64
:- The URIs are usually shorter.
- Easier to nest because it doesn’t contain special characters such as apostrophes. We’ll see an example of nesting in the next section.
`btoa()` is a global utility function that encodes a string via base 64. Caveats:
- It is not available on Node.js.
- It should only be used for characters whose Unicode code points range from 0 to 255.
Evaluating a module that imports another module
With tagged templates, we can nest data URIs and encode a module `m2` that imports another module `m1`:
const m1 = esm`export function f() { return 'Hello!' }`; const m2 = esm`import {f} from '${m1}'; export default f()+f();`; import(m2) .then(ns => assert.equal(ns.default, 'Hello!Hello!')) .catch((err) => { console.log(err.message); // "Importing a module script failed." // apply some logic, e.g. show a feedback for the user });
Further reading
- Wikipedia on Data URIs
- Section on
import()
in “JavaScript for impatient programmers” - Section on tagged templates in “JavaScript for impatient programmers”