# 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.

```markup
<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`, тогда скрипт будет выполнен, как только он загрузится.

![alt text](https://1356653101-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M5bu9Zoywb01ca-XJjd%2F-M5buJXbyydR9RxqeVRI%2F-M5buRq_eUoyut_kpcDr%2Fmodule-scripts.png?generation=1587660055162849\&alt=media)

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

```markup
<!-- 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.

```markup
<!-- 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).

```javascript
// 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 модули:

```markup
<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: *`.

```markup
<!-- 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.

```markup
<!-- 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

* [Jake Archibald Blog: ES6 modules in browsers](https://jakearchibald.com/2017/es-modules-in-browsers/)
* [Native ES6 modules: the new features and differences from Webpack modules](https://habrahabr.ru/company/tuturu/blog/329918/)
* [Native ES6 modules: `nomodule` attribute for the migration](https://hospodarets.com/native-ecmascript-modules-nomodule)
