Атрибуты импорта


Фича ECMAScript "Атрибуты импорта" (import attributes) позволяет импортировать артефакты, отличающиеся от модулей JavaScript. В этом разделе мы рассмотрим, как это выглядит и почему может быть полезным.


Атрибуты импорта достигли 4 стадии в октябре 2024 года и, вероятно, станут частью ECMAScript 2025.


❯ 1. Импорт артефактов, отличающихся от JS-модулей (ESM)


Импорт артефактов, которые не являются кодом JS в виде модулей, имеет давнюю традицию в экосистеме JS.


Например, загрузчик JS-модулей RequireJS поддерживает так называемые "плагины". Чтобы вы понимали, насколько RequireJS старый: версия 1.0.0 была представлена в 2009. Спецификаторы модулей (module specifiers), импортируемых с помощью плагина, выглядят так:


'спецификатор-плагина!спецификатор-артефакта'

Например, следующий спецификатор модуля импортирует файл как JSON:


'json!./data/config.json'

Вдохновленный RequireJS, webpack поддерживает аналогичный синтаксис спецификаторов модулей для своих загрузчиков (loaders).


1.1. Случаи импорта прочих (не JS) артефактов


  • импорт настроек в формате JSON
  • импорт кода WebAssembly как модуля JS
  • импорт CSS для UI

Можете взглянуть на список загрузчиков webpack для понимания того, для чего еще может пригодиться такой импорт.


❯ 2. Атрибуты импорта


Мотивацией для атрибутов импорта является потребность импорта данных JSON в качестве модуля. Это выглядит следующим образом (и более подробно определяется в отдельном предложении):


import configData from './config-data.json' with { type: 'json' };

Логичный вопрос: почему движок JS не может использовать расширение .json в названии файла для определения того, что это данные JSON? Но один из ключевых архитектурных принципов веба заключается в том, чтобы никогда не полагаться на название файла для определения его содержимого. Для этого используются типы содержимого (content types).


Если сервер настроен правильно, почему бы не использовать обычный импорт и опустить атрибуты импорта?


  • Сервер может быть намеренно настроен неправильно — например, внешний сервер, который не контролируется людьми, писавшими код. Он может заменять импортируемый файл JSON кодом, неожиданным для импортера
  • сервер может быть ненамеренно настроен неправильно. Атрибуты импорта позволяют быстрее получить обратную связь
  • атрибуты также описывают требования программиста к контенту файла

❯ 3. Синтаксис атрибутов импорта


Рассмотрим подробнее, как выглядят атрибуты импорта.


3.1. Инструкции статического импорта


Мы уже видели инструкцию обычного (статического) импорта:


import configData from './config-data.json' with { type: 'json' };

Атрибуты импорта начинаются с ключевого слова with. За ним следует объектный литерал. В настоящее время поддерживаются следующие возможности объектов:


  • ключи без кавычек и ключи в кавычках
  • значения должны быть строками

Других ограничений синтаксиса ключей и значений нет, но движки должны выбрасывать исключение в случае, если они не поддерживают ключ и/или значение:


  • атрибуты меняют импорт, поэтому игнорировать их рискованно, поскольку они меняют поведение кода во время выполнения
  • косвенным преимуществом является то, что это облегчает добавление новых возможностей в будущем, поскольку никто не будет использовать ключи и значения неожиданными способами

3.2. Динамический импорт


Для поддержки импорта атрибутов динамический импорт будет принимать второй параметр — объект с настройками:


import('./data/config.json', { with: { type: 'json' } })

Атрибуты импорта не существуют на верхнем уровне, они определяются через свойство with. Это делает возможным добавление других настроек в будущем.


3.3. Инструкции повторного экспорта


Повторный экспорт файла с атрибутами импорта выглядит так:


export { default as config } from './data/config.json' with { type: 'json' };

❯ 4. Ожидаемые возможности, основанные на атрибутах импорта


Атрибуты импорта — это всего лишь синтаксический сахар. Они закладывают основы для реальных возможностей, которые будут использовать этот синтаксис. В следующих разделах мы рассмотрим несколько кандидатов на эту роль.


4.1. Модули JSON


Первой возможностью, основанной на атрибутах импорта, вероятно, будут модули JSON. Мы уже видели их в действии:


import configData from './config-data.json' with { type: 'json' };

4.2. Модули CSS


Предложение возможности WHATWG для импорта CSS выглядит так:


import styles from './styles.css' with { type: 'css' };

document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];

4.3. Импорт WebAssembly


Будут ли атрибуты импорта использоваться для поддержки прямого импорта WebAssembly из JS, в настоящее время обсуждается. Если будут, то, вероятно, у нас появится возможность создавать веб-воркеры (web workers) следующим образом:


new Worker('my-app.wasm', { type: 'module', with: { type: 'webassembly' } })

И нам также потребуются атрибуты импорта для элемента HTML script:


<script src="my-app.wasm" type="module" withtype="webassembly"></script>

❯ 5. Потенциальные будущие возможности


5.1. Игнорируемые атрибуты


В настоящее время хост выбрасывает исключение при обнаружении незнакомого атрибута. Одним из возможных решений является указание "игнорируемости" атрибута при отсутствии его поддержки (источник):


import logo from './logo.png' with { type: 'image', 'as?': 'canvas' };

Если хост поддерживает as, указанная инструкция будет эквивалентна следующей:


import logo from './logo.png' with { type: 'image', as: 'canvas' };

В противном случае, такой:


import logo from './logo.png' with { type: 'image' };

5.2. Больше типов


Kris Kowal предлагает 3 значения type:


// `text` - строка
import text from 'text.txt' with { type: 'text' };

// `bytes` - экземпляр Uint8Array
import bytes from 'bytes.oct' with { type: 'bytes' };

// `imageUrl` - строка
import imageUrl from 'image.jpg' with { type: 'url' };

Модификаторы шаблона регулярного выражения


В настоящее время мы можем применять флаги, такие как i (для игнорирования регистра), только ко всему регулярному выражению. Фича ECMAScript "Модификаторы шаблона регулярного выражения" (regular expression pattern modifiers) позволит применять их к части регулярки. В этом разделе мы рассмотрим, как они работают и для чего могут использоваться.


Модификаторы шаблона достигли 4 стадии в октябре 2024 года и, вероятно, станут частью ECMAScript 2025.


❯ 1. Модификаторы шаблона


То, как работает регулярка, определяется так называемыми флагами, например, флагом i для игнорирования регистра в процессе сопоставления:


> /yes/i.test('yes')
true
> /yes/i.test('YES')
true

Модификаторы шаблона позволяют применять флаги только к определенным частям регулярок, например, в следующей регулярке флаг i применяется только к "HELLO":


> /^x(?i:HELLO)x$/.test('xHELLOx')
true
> /^x(?i:HELLO)x$/.test('xhellox')
true
> /^x(?i:HELLO)x$/.test('XhelloX')
false

Таким образом, модификаторы шаблона является встроенными (inline) флагами.


❯ 2. Синтаксис


Синтаксис модификаторов шаблона выглядит так:


(?ims-ims:pattern)
(?ims:pattern)
(?-ims:pattern)

  • флаг, следующий за ?, включается
  • флаг, следующий за -, отключается
  • флаг не может быть одновременно включенным и выключенным
  • без флагов данный синтаксис представляет собой обычную незахватывающую группу (?:шаблон)

Изменим предыдущий пример: теперь регулярка нечувствительна к регистру, за исключением "HELLO":


> /^x(?-i:HELLO)x$/i.test('xHELLOx')
true
> /^x(?-i:HELLO)x$/i.test('XHELLOX')
true
> /^x(?-i:HELLO)x$/i.test('XhelloX')
false

❯ 3. Поддерживаемые флаги


В качестве модификаторов шаблона могут использоваться следующие флаги:


Флаг Название ES Описание
i ignoreCase ES3 Делает сопоставление нечувствительным к регистру
m multiline ES3 ^ и $ сопоставляются построчно
s dotAll ES2018 Точка сопоставляется с прерывателями строки (line terminators)

Для получения более подробной информации загляните сюда.


Другие флаги не поддерживаются, поскольку они могут сделать семантику регулярок слишком сложной (например, флаг v) или применять их имеет смысл только ко всей регулярке (например, флаг g).


❯ 4. Случаи использования


4.1. Модификация флагов для части регулярки


Иногда может потребоваться изменить флаги только для определенной части регулярки. Например, Ron Buckton объясняет, что изменение флага m помогает с сопоставлением блока Markdown frontmatter в начале строки (я немного модифицировал его версию):


const re = /(?-m:^)---\r?\n((?:^(?!---$).*\r?\n)*)^---$/m;
assert.equal(re.test('---a'), false);
assert.equal(re.test('---\n---'), true);
assert.equal(
  re.exec('---\n---')[1],
  ''
);
assert.equal(
  re.exec('---\na: b\n---')[1],
  'a: b\n'
);

Данная регулярка работает следующим образом:


  • по умолчанию, флаг m включен и якорь ^ сопоставляется с началом, а якорь $ — с концом line
  • первый ^ отличается: он должен сопоставляться с началом string. Для этого используется модификатор шаблона, выключающий флаг m

Вот та же регулярка с комментариями:


(?-m:^)---\r?\n  # first line of string
(  # группа захвата для frontmatter
  (?:  # шаблон для одной line (незахватывающая группа)
    ^(?!---$)  # line не должна начинаться с "---" + EOL (lookahead - проспективное сопоставление)
    .*\r?\n
  )*
)
^---$  # закрывающий разделитель frontmatter

4.2. Встроенные флаги


В некоторых случаях размещение флагов за пределами регулярки может быть неудобным. В этих случаях на помощь приходят модификаторы шаблона. Примеры:


  • хранение регулярок в файлах с настройками, например, в формате JSON
  • библиотека Regex+ предоставляет литералы шаблонов (template literals) для более удобного создания регулярок. Синтаксис для определения флагов добавляет некоторый шум, который можно устранить с помощью модификаторов шаблона:

regex('i')`world`
regex`(?i:world)`

4.3. Фрагменты регулярок могут менять флаги


Что если мы хотим составлять сложные регулярки из простых? Упомянутая библиотека поддерживает это. Модификаторы шаблона пригодятся в случае, если простая регулярка нуждается в других флагах.




Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале

Комментарии (1)


  1. SergeiZababurin
    27.01.2025 13:56

    // `text` - строкаimport text from 'text.txt' with { type: 'text' };// `bytes` - экземпляр Uint8Array import bytes from 'bytes.oct' with { type: 'bytes' };// `imageUrl` - строкаimport imageUrl from 'image.jpg' with { type: 'url' };

    Вот этого для полного счастья не хватает сейчас. Побыстрее бы появилось.

    Я сейчас ковыряю проект на backbone, там весь код синхронный и импорт html файлов стал очень неприятным.