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.
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?
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
.Warning: This code only works in web browsers. On Node.js, import()
does not support data URIs.
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');
});
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');
});
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 'data:text/javascript;base64,' + btoa(js);
}
For the encoding, we have switched from charset=utf-8
to base64
. Compare:
'a' < 'b'
data:text/javascript;charset=utf-8,'a'%20%3C%20'b'
data:text/javascript;base64,J2EnIDwgJ2In
Each of the two ways of encoding has different pros and cons:
charset=utf-8
(percent-encoding):base64
:btoa()
is a global utility function that encodes a string via base 64. Caveats:
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!'));
#javascript