Новые возможности ECMAScript — атрибуты импорта и модификаторы шаблона регулярного выражения
- вторник, 28 января 2025 г. в 00:00:04
Фича ECMAScript "Атрибуты импорта" (import attributes) позволяет импортировать артефакты, отличающиеся от модулей JavaScript. В этом разделе мы рассмотрим, как это выглядит и почему может быть полезным.
Атрибуты импорта достигли 4 стадии в октябре 2024 года и, вероятно, станут частью ECMAScript 2025.
Импорт артефактов, которые не являются кодом JS в виде модулей, имеет давнюю традицию в экосистеме JS.
Например, загрузчик JS-модулей RequireJS поддерживает так называемые "плагины". Чтобы вы понимали, насколько RequireJS старый: версия 1.0.0 была представлена в 2009. Спецификаторы модулей (module specifiers), импортируемых с помощью плагина, выглядят так:
'спецификатор-плагина!спецификатор-артефакта'
Например, следующий спецификатор модуля импортирует файл как JSON:
'json!./data/config.json'
Вдохновленный RequireJS, webpack поддерживает аналогичный синтаксис спецификаторов модулей для своих загрузчиков (loaders).
Можете взглянуть на список загрузчиков webpack для понимания того, для чего еще может пригодиться такой импорт.
Мотивацией для атрибутов импорта является потребность импорта данных JSON в качестве модуля. Это выглядит следующим образом (и более подробно определяется в отдельном предложении):
import configData from './config-data.json' with { type: 'json' };
Логичный вопрос: почему движок JS не может использовать расширение .json
в названии файла для определения того, что это данные JSON? Но один из ключевых архитектурных принципов веба заключается в том, чтобы никогда не полагаться на название файла для определения его содержимого. Для этого используются типы содержимого (content types).
Если сервер настроен правильно, почему бы не использовать обычный импорт и опустить атрибуты импорта?
Рассмотрим подробнее, как выглядят атрибуты импорта.
Мы уже видели инструкцию обычного (статического) импорта:
import configData from './config-data.json' with { type: 'json' };
Атрибуты импорта начинаются с ключевого слова with
. За ним следует объектный литерал. В настоящее время поддерживаются следующие возможности объектов:
Других ограничений синтаксиса ключей и значений нет, но движки должны выбрасывать исключение в случае, если они не поддерживают ключ и/или значение:
Для поддержки импорта атрибутов динамический импорт будет принимать второй параметр — объект с настройками:
import('./data/config.json', { with: { type: 'json' } })
Атрибуты импорта не существуют на верхнем уровне, они определяются через свойство with
. Это делает возможным добавление других настроек в будущем.
Повторный экспорт файла с атрибутами импорта выглядит так:
export { default as config } from './data/config.json' with { type: 'json' };
Атрибуты импорта — это всего лишь синтаксический сахар. Они закладывают основы для реальных возможностей, которые будут использовать этот синтаксис. В следующих разделах мы рассмотрим несколько кандидатов на эту роль.
Первой возможностью, основанной на атрибутах импорта, вероятно, будут модули JSON. Мы уже видели их в действии:
import configData from './config-data.json' with { type: 'json' };
Предложение возможности WHATWG для импорта CSS выглядит так:
import styles from './styles.css' with { type: 'css' };
document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];
Будут ли атрибуты импорта использоваться для поддержки прямого импорта 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>
В настоящее время хост выбрасывает исключение при обнаружении незнакомого атрибута. Одним из возможных решений является указание "игнорируемости" атрибута при отсутствии его поддержки (источник):
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' };
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.
То, как работает регулярка, определяется так называемыми флагами, например, флагом 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) флагами.
Синтаксис модификаторов шаблона выглядит так:
(?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
В качестве модификаторов шаблона могут использоваться следующие флаги:
Флаг | Название | ES | Описание |
---|---|---|---|
i | ignoreCase | ES3 | Делает сопоставление нечувствительным к регистру |
m | multiline | ES3 | ^ и $ сопоставляются построчно |
s | dotAll | ES2018 | Точка сопоставляется с прерывателями строки (line terminators) |
Для получения более подробной информации загляните сюда.
Другие флаги не поддерживаются, поскольку они могут сделать семантику регулярок слишком сложной (например, флаг v
) или применять их имеет смысл только ко всей регулярке (например, флаг g
).
Иногда может потребоваться изменить флаги только для определенной части регулярки. Например, 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
В некоторых случаях размещение флагов за пределами регулярки может быть неудобным. В этих случаях на помощь приходят модификаторы шаблона. Примеры:
regex('i')`world`
regex`(?i:world)`
Что если мы хотим составлять сложные регулярки из простых? Упомянутая библиотека поддерживает это. Модификаторы шаблона пригодятся в случае, если простая регулярка нуждается в других флагах.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩