С добавлением в JavaScript ES-модулей появилось не менее 24 способов подгрузить скрипты: с атрибутом src и без него; с async или без; defer или нет; type=module и nomodule. Все они немножко отличаются друг от друга.
В этой статье сравним, как встроенные в HTML тэги <script> обрабатываются в зависимости от набора атрибутов.
Картинка вместо тысячи слов
Мы видим, что async используется в legacy-скриптах, когда нужно выполнить их пораньше, а module — наоборот, чтобы задержать выполнение до подходящего момента (модульные скрипты по умолчанию обладают атрибутом defer).
Шпаргалку сохранили, а теперь рассмотрим каждый из вариантов подробней.
Сравнение обычного <script> с async, defer и async defer
Async и defer полностью поддерживаются и, как уже говорилось, интуитивно понятная разница между ними заключается в том, что скрипты с async выполняются сразу. Они не ждут окончания парсинга HTML, полного формирования DOM, а также подгрузки остальных скриптов.
Обычные немодульные <script>
- Приостанавливают парсинг HTML.
- Сразу подгружаются, парсятся и выполняются.
- Гарантируют порядок выполнения относительно других обычных немодульных скриптов.
- Блокируют событие DOMContentLoaded.
- Учитывая всё вышесказанное, такие скрипты не подходят для некритичного кода, поскольку замедляют рендеринг и, как следствие, загрузку динамических веб-приложений.
<script defer>
- Для встроенных (inline) немодульных скриптов defer игнорируется, и код выполняются сразу. Если defer прям сильно нужен, можно воспользоваться обходным путём с участием base64.
- Для встроенных скриптов с указанием type=”module” defer применяется по умолчанию.
- Подгружаются без остановки HTML-парсера.
- Гарантируют порядок выполнения относительно других defer-скриптов (если они внешние — с атрибутом src). Криво работают в IE9.
- Выполняются после окончания парсинга DOM (но перед срабатыванием DOMContentLoaded).
- Блокируют событие DOMContentLoaded (только если скрипт не async defer).
<script async>
- Для встроенных (inline) немодульных скриптов async игнорируется.
- Для встроенных модульных скриптов async поддерживается.
- Подгружаются без остановки HTML-парсера.
- Выполняются без очереди.
- Не гарантируют порядок выполнения относительно других скриптов с async (также касается модульных скриптов с async).
- Не ждут окончания парсинга HTML. Могут прервать построение DOM (в частности, когда он достаётся из кэша браузера).
- Блокируют событие load (но не DOMContentLoad).
- Не поддерживаются в IE9.
<script async defer>
Воспринимаются как async. В древних брузерных движках, которые не поддерживают async (IE9), работает так же, как defer.
Сравнение type=module, type=text/javascript и nomodule
Скрипты с type=module (также касается type=text/javascript)
- Предполагают defer (также для встроенных скриптов, в отличие от немодульных скриптов).
- Исходя из этого, гарантируют порядок выполнения относительно всех модульных скриптов, не использующих async (как встроенных, так и внешних).
- Выполняются только раз, даже если скрипты с одинаковым src подгружаются несколько раз.
- Могут использовать import для объявления зависимости с другими модульными скриптами (одна из причин, почему модули предполагают использование defer).
- Подвергаются проверке CORS (в модулях из разных источников потребуется указать Access-Control-Allow-Origin: [источники]).
- Не выполняются браузерами, которые не поддерживают модульные скрипты. Однако они всё ещё, по видимому, подгружаются в IE11, Firefox 52 ESR и т.д.
<script nomodule>
Не выполняются браузерами, которые не поддерживают <script type=”module”>. Однако, даже некоторые современные браузеры по ошибке подтягивают их (например Safari 10.3, но существует способ это исправить).
Сравнение встроенных (inline) и внешних скриптов
Встроенные скрипты (без атрибута src)
- Для немодульных скриптов async и defer игнорируются.
- Блокируют HTML-парсеры и построение DOM, так как выполняются сразу после загрузки.
- Встроенные модульные скрипты предполагают defer. Также поддерживают async.
- Не кэшируются браузерами.
Внешние скрипты
Кэшируются браузерами (при условии подходящих заголовков в ответе от сервера), поэтому могут использоваться в будущем без повторной подгрузки из сети.
Примеры использования скриптов