05. Browser Modules

Спецификация ES6 модулей поддерживается в браузерах при помощи тега <script type="module">. Выполняемый в таком скрипте код поддерживает ES6 и спецификацию модулей (импорты/экспорты).

Основные особенности:

  1. Для подключения используется <script type="module">.

  2. Нативные модули в "strict mode" по умолчанию;

  3. Все определения в модулях не являются частью глобального объекта.

  4. window по прежнему доступен, но this в модуле является undefined

  5. Модули по умолчанию асинхронны работают как deferred скрипты (такое же как у <script type="text/javascript" defer />).

Со временем нативные JS-модули будут поддерживаться браузерами без дополнительных утилит. Сейчас браузерной поддержки почти нет. Поэтому ES-модули используются в сочетании с системами сборки, такими как webpack и другими, при подключённом Babel.

<script type="module">
  import $ from "lib/jquery";
  var x = 123;

  // The current scope is not global
  console.log("$" in window); // false
  console.log("x" in window); // false

  // `this` still refers to the global object
  console.log(this === window); // true
</script>

Скрипты и модули не смешиваемы друг с другом:

  • Переменные, определенные в скриптах недоступны в модулях

  • В скриптах нельзя использовать ES6 импорты/экспорты, но можно библиотеки модулей (UMD и др.).

Module execution

Главное отличие нативных модулей от обычных скриптов заключается в том, что обычные скрипты загружаются и выполняются сразу же, блокируя парсинг html. Нативные модули по умолчанию имеют поведение deffered скриптов вне зависимости от того, импортируют ли они что-нибудь или нет. По умолчанию скрипты в модулях не блокируют, загружаются параллельно и выполняются, когда страница завершает парсинг html. Можно изменить это поведение, добавив атрибут async, тогда скрипт будет выполнен, как только он загрузится.

The order should be 2.js, 1.js, 3.js.

<!-- This script will execute after… -->
<script type="module" src="1.js"></script>

<!-- …this script… -->
<script src="2.js"></script>

<!-- …but before this script. -->
<script defer src="3.js"></script>

Пример с async:

The fast-downloading scripts should execute before the slow ones.

<!-- This executes as soon as its imports have fetched -->
<script async type="module">
  import { addTextToBody } from "./utils.js";

  addTextToBody("Inline module executed.");
</script>

<!-- This executes as soon as it & its imports have fetched -->
<script async type="module" src="1.js"></script>

Import paths

Импорт внутри модуля:

  • он может начинаться и заканчиваться пробелами;

  • он должен быть абсолютным URL-ом или:

  • он должен начинаться с “/”, “./”, или “../”.

  • .js расширение не может быть опущено в директиве import;

Еще одно отличие модулей - это возможность загружать файлы с других доменов (например, загрузка модулей с CDN).

// Supported:
import { foo } from "https://jakearchibald.com/utils/bar.js";
import { foo } from "/utils/bar.js";
import { foo } from "./bar.js";
import { foo } from "../bar.js";

// Not supported:
import { foo } from "bar.js";
import { foo } from "utils/bar.js";

sctipt attributes

Как в классических скриптах, есть много атрибутов, которые можно использовать в script type=”module”.

  • Атрибут type используется для установки типа "module".

  • src мы используем, чтобы загрузить файл с определенным URI.

  • defer не нужен для скриптов типа “module”, так как это поведение по умолчанию

  • Если вы используете async атрибут, модуль будет выполнен сразу же как только будет доступен, без defer поведения по умолчанию, когда скрипты выполняются по порядку после анализа документа, но перед событием DOMContentLoaded.

  • integrity по-прежнему может быть использован, чтобы убедиться, что выбранные файлы (например, из cdn) не были подменены на что-то другое.

  • атрибут crossorigin дает возможность контролировать обмен данными, которые отправляются с помощью CORS запросов.

  • nonce — это генерируемый случайным образом хеш, который добавляется в заголовок на сервер и добавляется в тег script.

Используйте события onload и onerror у script элемента, чтобы обнаружить, может ли модуль быть успешно выполнен или не может загрузиться.

nomodule attribute

Атрибут nomodule означает, что скрипт должен быть выполнен только если браузер не поддерживает ES модули:

<script type="module" src="app.js"></script>
<script nomodule defer src="bundle.js"></script>

В данном примере кода если браузер поддерживает модули, то будет загружен и выполнен app.js, в противном случае будет выполнен bundle.js.

CORS and credentials

Unlike regular scripts, module scripts (and their imports) are fetched with CORS. This means cross-origin module scripts must return valid CORS headers such as Access-Control-Allow-Origin: *.

<!-- This will not execute, as it fails a CORS check -->
<script type="module" src="https://….now.sh/no-cors"></script>

<!-- This will not execute, as one of its imports fails a CORS check -->
<script type="module">
  import "https://….now.sh/no-cors";

  addTextToBody("This will not execute.");
</script>

<!-- This will execute as it passes CORS checks -->
<script type="module" src="https://….now.sh/cors"></script>

Most CORS-based APIs will send credentials (cookies etc) if the request is to the same origin, but fetch() and module scripts are exceptions – they don't send credentials unless you ask for them.

You can add credentials to a same-origin module by including the crossorigin attribute (which seems a bit weird to me, and I've questioned this in the spec). If you want to send credentials to other origins too, use crossorigin="use-credentials". Note that the other origin will have to respond with the Access-Control-Allow-Credentials: true header.

<!-- Fetched with credentials (cookies etc) -->
<script src="1.js"></script>

<!-- Fetched without credentials -->
<script type="module" src="1.js"></script>

<!-- Fetched with credentials -->
<script type="module" crossorigin src="1.js?"></script>

<!-- Fetched without credentials -->
<script type="module" crossorigin src="https://other-origin/1.js"></script>

<!-- Fetched with credentials-->
<script
  type="module"
  crossorigin="use-credentials"
  src="https://other-origin/1.js?"
></script>

Also, there's a gotcha related to the "Modules only execute once" rule. Modules are keyed by their URL, so if you request a module without credentials, then request it with credentials, you'll get the same without-credentials module back. This is why I've used a ? in the URLs above, to make them unique.

References

Last updated