Skip to content

Выполнение JavaScript кода с использованием оператора import()

Опубликовано: at 15:20

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

Как это работает?

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. Сравним:

Оба варианта имеют свои преимущества и недостатки:

  1. Большая часть исходного кода остаётся читаемой.
  1. URI,обычно, короче
  2. Легче встраивать т.к не содержит специальных символов. Мы далее рассмотрим пример встраивания в следующей секции.

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. Дополнительные источники

Источник: Статья:“Evaluating JavaScript code via import()” Dr. Axel Rauschmayer