Выполнение 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