Атрибуты импорта
Фича 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-канале ↩