Javascript Модули в браузере — script type=module

В этой заметке поговорим об использовании ES6 модулей в браузере. Это стандартные модули ES6. Только они используются в коде, предназначенном для браузеров.

Чтобы указать браузеру что подключаемый скрипт является модулем, нужно добавить атрибут type="module":

<script type="module" src="https://dom/script.js"></script>

Модулем может быть и инлайновый скрипт:

<script type="module">
export const doSomething = message => {
	console.log('No.');
}
</script>
[contents]

Поддержка браузерами

Главный вопрос - как сейчас поддерживаются модули в бразуере. На сегодня поддержка уже отличная - все основные браузере поддерживают модули. Поэтому уже можно использовать:

Как подключить WordPress скрипт как модуль

Для этого можно воспользоваться хуком script_loader_tag:

add_filter( 'script_loader_tag', 'scripts_as_es6_modules', 10, 3 );

function scripts_as_es6_modules( $tag, $handle, $src ) {

	if ( 'my-script' === $handle ) {
		return str_replace( '<script ', '<script type="module"', $tag );
	}

	return $tag;
}

Особенности и возможности модулей

Всегда используется «use strict»

Например, присваивание к необъявленной переменной вызовет ошибку.

<script type="module">
  a = 5; // ошибка
</script>

Отдельная область видимости переменных

Каждый модуль имеет свою собственную область видимости. Другими словами, переменные и функции, объявленные в модуле, не видны в других скриптах:

<!doctype html>
<script type="module">
	// Переменная доступна только в этом модуле
	let user = "John";
</script>

<script type="module">
	alert( user ); // Error: user is not defined
</script>

Однако сам модуль, видит переменные родительской области видимости:

<!doctype html>
<script>
	let user = "John";
</script>

<script type="module">
	alert( user ); //> John
</script>

Если нужна глобальная переменная уровня всей страницы, то лучше явно присвоить её объекту window. Но такой подход должен быть исключением, требующим веской причины.

<!doctype html>
<script>
	window.user = "John";
</script>

<script type="module">
	alert( user ); //> John
</script>

Модули должны экспортировать функциональность, предназначенную для использования извне. А другие модули могут её импортировать.

<!doctype html>
<script type="module" src="user.js"></script>
<script type="module" src="hello.js"></script>
// user.js
export let user = "John";
// hello.js
import {user} from './user.js';

Код модуля выполняется один раз (при импорте)

Если один и тот же модуль используется в нескольких местах, то его код выполнится только один раз, после чего экспортируемая функциональность передаётся всем импортёрам.

На практике, задача модуля – это, как правило, инициализация – создание внутренних структур данных. А если мы хотим, чтобы что-то можно было использовать много раз, то экспортируем это.

Подробнее тут: https://learn.javascript.ru/modules-intro#kod-v-module-vypolnyaetsya-tolko-odin-raz-pri-importe

import.meta

Объект import.meta содержит информацию о текущем модуле.

Содержимое зависит от окружения. В браузере он содержит ссылку на скрипт или ссылку на текущую веб-страницу, если модуль встроен в HTML:

<script type="module">
  alert(import.meta.url); // ссылка на html страницу для встроенного скрипта
</script>

this в модуле не определён

В модуле на верхнем уровне this не определён (undefined):

<script>
  alert(this); // window
</script>

<script type="module">
  alert(this); // undefined
</script>

Особенности в браузерах

Загрузка модуля отложена (deferred)

Модули всегда выполняются в отложенном режиме, так же, как скрипты с атрибутом defer. Это работает для внешних и встроенных скриптов.

Другими словами:

  • Загрузка внешних модулей, таких как <script type="module" src="...">, не блокирует обработку HTML.

  • Модуль, даже если загрузились быстро, ожидают полной загрузки HTML документа, и только затем выполняются.

  • Сохраняется относительный порядок скриптов: скрипты, которые идут раньше в документе, выполняются раньше.

async позволяет не откладывать выполнение модуля и работает для inline скриптов

Для обычных скриптов атрибут async будет работать только если подключается файл - не встроенный скрипт.

Для модулей атрибут async работает и для файлов и для inline скриптов. А также, он заставляет выполнятся код модуля сразу после загрузки, а не по порядку.

Например скрипт ниже выполнит импорт (загрузит ./analytics.js) и сразу запустится, когда будет готов, даже если HTML документ ещё не будет загружен, или если другие скрипты ещё загружаются.

<script async type="module">
  import {counter} from './analytics.js';

  counter.count();
</script>

Это очень полезно, когда модуль ни с чем не связан, например для счётчиков, обработчиков событий, рекламы.

Одинаковые внешние скрипты запускаются один раз

Внешние скрипты с одинаковым атрибутом src запускаются только один раз:

<!-- скрипт my.js загрузится и будет выполнен только один раз -->
<script type="module" src="my.js"></script>
<script type="module" src="my.js"></script>

Внешний скрипт с другого домена, требует CORS

Если модульный скрипт загружается с другого домена, то удалённый сервер должен установить заголовок Access-Control-Allow-Origin означающий, что загрузка скрипта разрешена.

<!-- another-site.com должен указать заголовок Access-Control-Allow-Origin -->
<!-- иначе, скрипт не выполнится -->
<script type="module" src="http://another-site.com/their.js"></script>

Это обеспечивает лучшую безопасность по умолчанию.

Не допускаются «голые» модули

В браузере import должен содержать относительный или абсолютный путь к модулю. Модули без пути называются «голыми» (bare). Они не разрешены в import.

Например, этот import неправильный:

import {sayHi} from 'sayHi'; // Ошибка, "голый" модуль
// путь должен быть, например './sayHi.js' или абсолютный

Другие окружения, например Node.js, допускают использование «голых» модулей, без путей, так как в них есть свои правила, как работать с такими модулями и где их искать. Но браузеры пока не поддерживают «голые» модули.

--

Источник: https://learn.javascript.ru/modules-intro