Выполнение JavaScript кода с использованием оператора import()
Оператор import()
позволяет динамически загружать ECMAScript модули. Также, данный оператор позволяет выполнять JavaScript код и может использоваться как альтернатива eval()
(об этом мне недавно рассказал Andrea Giammarchi). Данная статья объясняет как это работает.
1. eval()
не поддерживает export
и import
Существенным ограничением eval()
является то, что данный оператор не поддерживает работу с модулями (import
,export
).
Если использовать import()
вместо eval()
, мы можем выполнить код из модуля. Пример будет рассмотрен далее.
В будущем, мы можем получить в своё распоряжение Realms API, которое, по сути, являет более мощной версией eval()
с поддержкой модулей.
2. Выполнение простого фрагмента кода с помощью оператора import()
Начнём с выполнения console.log()
с использованием 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!'
Как это работает?
- Сначала мы создаём data URI. Протокол этого вида URI -
data:
. Оставшаяся часть URI кодирует весь ресурс, а не просто указывает на него. Т.е. data URI содержит целый ECMAScript модуль. Content type данного модуля имеет значениеtext/javascript
. - Далее мы динамически импортируем модуль и выполняем его.
WARNING: Данный код работает только в браузерах. В Node.js,
import()
не поддерживает работу с data URI.
2.1 Доступ к экспортированному содержимому модуля
Значение Promise, возвращаемого import()
, при переходе в состояние fulfilled представляет собоё объект содержащий пространство имён модуля. Далее, мы получим доступ к 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");
});
3. Создание data URI с использованием тегированных шаблонных литералов
С помощью функции “esm`(реализацию данной функции мы рассмотрим позже), мы можем переписать предыдущий пример и создать data URI с использованием тегированных шаблонных литералов:
const dataUri = esm`export default 'Returned value'`;
import(dataUri).then(namespaceObject => {
assert.equal(namespaceObject.default, "Returned value");
});
Имплементация функции esm
выглядит следующим образом:
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);
}
Для кодирования сменим кодировку с charset=utf-8
на base64
. Сравним:
- Исходный код:
'a'<'b'
- Data URI 1:
data:text/javascript;charset=utf-8,'a'%20%3C%20'b'
- Data URI 2:
data:text/javascript;base64,J2EnIDwgJ2In
Оба варианта имеют свои преимущества и недостатки:
- Преимущества
charset=utf-8
:
- Большая часть исходного кода остаётся читаемой.
- Преимущества
base64
- URI,обычно, короче
- Легче встраивать т.к не содержит специальных символов. Мы далее рассмотрим пример встраивания в следующей секции.
btoa()
глобальная функция утилита, которая кодирует строку в base64.
WARNING: Недоступна в Node.js. WARNING: Может быть использована только для символов, Unicode код которых расположен в диапазоне от 0 до 255.
4. Выполнение модуля, импортирующего другой модуль
Используя тегированные шаблонные литералы мы можем встраивать data URI и получить модуль m2, который импортирует модуль 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!"));
5. Дополнительные источники
- Wikipedia on Data URIs
- Раздел
import()
в книге “JavaScript for impatient programmers” - Раздел, посвященный тегированным шаблонным литералам в книге “JavaScript for impatient programmers”
Источник: Статья:“Evaluating JavaScript code via import()” Dr. Axel Rauschmayer