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()
:
1 2 3 4 5 6 7 8 |
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 istext/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:
1 2 3 4 5 6 7 8 9 10 11 |
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:
1 2 3 4 5 6 7 8 9 |
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:
1 2 3 4 5 6 |
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
:
1 2 3 4 5 6 7 8 |
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”