В этой статье рассматриваются модули ES6, показывая, как их можно использовать сегодня с помощью транспортера.
Почти у каждого языка есть понятие модулей — способ включить функциональность, объявленную в одном файле в другой. Как правило, разработчик создает инкапсулированную библиотеку кода, отвечающую за обработку связанных задач. На эту библиотеку могут ссылаться приложения или другие модули.
Выгоды:
- Код можно разбить на более мелкие файлы с автономной функциональностью.
- Одни и те же модули можно использовать в любом количестве приложений.
- В идеале, модули никогда не должны проверяться другим разработчиком, потому что они доказали свою работоспособность.
- Код, ссылающийся на модуль, понимает, что это зависимость. Если файл модуля изменяется или перемещается, проблема сразу становится очевидной.
- Код модуля (обычно) помогает устранить конфликты имен. Функция
x()
в module1 не может конфликтовать с функциейx()
в module2.module1.x()
такие параметры, как пространство имен, поэтому вызовы становятсяmodule1.x()
иmodule2.x()
.
Где находятся модули в JavaScript?
Любой, кто начал веб-разработку несколько лет назад, был бы шокирован, обнаружив, что в JavaScript не было концепции модулей. Было невозможно напрямую ссылаться или включать один файл JavaScript в другой. Поэтому разработчики прибегали к альтернативным вариантам.
Несколько тегов HTML <script>
HTML может загружать любое количество файлов JavaScript, используя несколько тегов <script>
:
<script src="lib1.js"></script> <script src="lib2.js"></script> <script src="core.js"></script> <script> console.log('inline code'); </script>
Средняя веб-страница в 2018 году использует 25 отдельных сценариев , но это не практичное решение:
- Каждый скрипт инициирует новый HTTP-запрос, который влияет на производительность страницы. HTTP / 2 в некоторой степени облегчает проблему, но не помогает сценариям, на которые ссылаются другие домены, такие как CDN.
- Каждый скрипт останавливает дальнейшую обработку во время работы.
- Управление зависимостями — это ручной процесс. В приведенном выше коде, если
lib1.js
ссылается на код вlib2.js
, код завершится ошибкой, поскольку он не был загружен. Это может нарушить дальнейшую обработку JavaScript. - Функции могут переопределять другие, если не используются соответствующие шаблоны модулей . Ранние библиотеки JavaScript были известны тем, что использовали глобальные имена функций или переопределяли нативные методы.
Объединение скриптов
Одним из решений проблем нескольких тегов <script>
является объединение всех файлов JavaScript в один большой файл. Это решает некоторые проблемы с производительностью и управлением зависимостями, но может привести к этапу ручной сборки и тестирования.
Модульные погрузчики
Такие системы, как RequireJS и SystemJS, предоставляют библиотеку для загрузки и размещения имен других библиотек JavaScript во время выполнения. Модули загружаются с использованием методов Ajax, когда это необходимо. Системы помогают, но могут усложняться для больших баз кода или сайтов, добавляя в смесь стандартные теги <script>
.
Комплекты модулей, препроцессоры и транспортеры
Поставщики вводят этап компиляции, поэтому код JavaScript генерируется во время сборки. Код обрабатывается для включения зависимостей и создает один кросс-браузерный совместимый файл ES5. Популярные опции включают Babel , Browserify , webpack и другие общие задачи, такие как Grunt и Gulp .
Процесс сборки JavaScript требует некоторых усилий, но есть и преимущества:
- Обработка автоматизирована, поэтому вероятность человеческой ошибки меньше.
- Дальнейшая обработка может привести к задержке кода, удалению команд отладки, минимизации полученного файла и т. Д.
- Прозрачность позволяет вам использовать альтернативные синтаксисы, такие как TypeScript или CoffeeScript .
ES6 Модули
Приведенные выше варианты представили множество конкурирующих форматов определения модулей. Широко принятые синтаксисы включают в себя:
- CommonJS —
module.exports
иrequire
синтаксиса, используемого в Node.js - Определение асинхронного модуля (AMD)
- Определение универсального модуля (UMD).
Поэтому в ES6 был предложен единый собственный стандарт модулей (ES2015).
Все внутри модуля ES6 является приватным по умолчанию и работает в строгом режиме (нет необходимости 'use strict'
). Публичные переменные, функции и классы предоставляются с помощью export
. Например:
// lib.js export const PI = 3.1415926; export function sum(...args) { log('sum', args); return args.reduce((num, tot) => tot + num); } export function mult(...args) { log('mult', args); return args.reduce((num, tot) => tot * num); } // private function function log(...msg) { console.log(...msg); }
В качестве альтернативы можно использовать один оператор export
. Например:
// lib.js const PI = 3.1415926; function sum(...args) { log('sum', args); return args.reduce((num, tot) => tot + num); } function mult(...args) { log('mult', args); return args.reduce((num, tot) => tot * num); } // private function function log(...msg) { console.log(...msg); } export { PI, sum, mult };
Затем import
используется для извлечения элементов из модуля в другой скрипт или модуль:
// main.js import { sum } from './lib.js'; console.log( sum(1,2,3,4) ); // 10
В этом случае lib.js
находится в той же папке, что и main.js
Можно использовать абсолютные ссылки на файлы (начиная с /
), относительные ссылки на файлы (начиная с ./
или ../
) или полные URL-адреса.
Несколько предметов могут быть импортированы одновременно:
import { sum, mult } from './lib.js'; console.log( sum(1,2,3,4) ); // 10 console.log( mult(1,2,3,4) ); // 24
а для импорта можно использовать псевдонимы для разрешения конфликтов именования:
import { sum as addAll, mult as multiplyAll } from './lib.js'; console.log( addAll(1,2,3,4) ); // 10 console.log( multiplyAll(1,2,3,4) ); // 24
Наконец, все публичные элементы можно импортировать, предоставив пространство имен:
import * as lib from './lib.js'; console.log( lib.PI ); // 3.1415926 console.log( lib.add(1,2,3,4) ); // 10 console.log( lib.mult(1,2,3,4) ); // 24
Использование модулей ES6 в браузерах
На момент написания статьи модули ES6 поддерживаются в браузерах на основе Chromium (v63 +), Safari 11+ и Edge 16+. Поддержка Firefox появится в версии 60 (она стоит за флагом about:config
в v58 +).
Скрипты, которые используют модули, должны быть загружены путем установки атрибута type="module"
в <script>
. Например:
<script type="module" src="./main.js"></script>
или встроенный:
<script type="module"> import { something } from './somewhere.js'; // ... </script>
Модули анализируются один раз, независимо от того, сколько раз на них ссылаются на странице или в других модулях.
Особенности сервера
Модули должны обслуживаться с application/javascript
MIME-типа. Большинство серверов делают это автоматически, но опасаются динамически генерируемых скриптов или файлов .mjs
( см. Раздел Node.js ниже ).
Обычные теги <script>
могут извлекать сценарии в других доменах, но модули выбираются с использованием совместного использования ресурсов между источниками (CORS). Поэтому модули в разных доменах должны устанавливать соответствующий заголовок HTTP, например Access-Control-Allow-Origin: *
.
Наконец, модули не будут отправлять файлы cookie или другие учетные данные заголовка, если в crossorigin="use-credentials"
<script>
добавлен атрибут crossorigin="use-credentials"
а ответ не содержит заголовок Access-Control-Allow-Credentials: true
.
Выполнение модуля отложено
Атрибут <script defer>
задерживает выполнение скрипта до тех пор, пока документ не будет загружен и проанализирован. Модули, включая встроенные скрипты, откладываются по умолчанию. Пример:
<!-- runs SECOND --> <script type="module"> // do something... </script> <!-- runs THIRD --> <script defer src="c.js"></script> <!-- runs FIRST --> <script src="a.js"></script> <!-- runs FOURTH --> <script type="module" src="b.js"></script>
Модуль откатов
Браузеры без поддержки модулей не будут запускать сценарии type="module"
. Резервный сценарий может быть снабжен атрибутом nomodule
который совместимы с модулями браузеров. Например:
<script type="module" src="runs-if-module-supported.js"></script> <script nomodule src="runs-if-module-not-supported.js"></script>
Вы должны использовать модули в браузере?
Поддержка браузеров растет, но, возможно, немного преждевременно переходить на модули ES6. На данный момент, вероятно, лучше использовать модуль связывания для создания сценария, который работает везде.
Использование модулей ES6 в Node.js
Когда Node.js был выпущен в 2009 году, было бы немыслимо для любой среды выполнения не предоставлять модули. Был принят CommonJS, что означало, что менеджер пакетов Node, npm, может быть разработан. Использование выросло в геометрической прогрессии с этой точки.
Модуль CommonJS может быть закодирован аналогично модулю ES2015. module.exports
используется вместо export
:
// lib.js const PI = 3.1415926; function sum(...args) { log('sum', args); return args.reduce((num, tot) => tot + num); } function mult(...args) { log('mult', args); return args.reduce((num, tot) => tot * num); } // private function function log(...msg) { console.log(...msg); } module.exports = { PI, sum, mult };
require
(вместо import
) используется для перетаскивания этого модуля в другой скрипт или модуль:
const { sum, mult } = require('./lib.js'); console.log( sum(1,2,3,4) ); // 10 console.log( mult(1,2,3,4) ); // 24
require
также импортировать все элементы:
const lib = require('./lib.js'); console.log( lib.PI ); // 3.1415926 console.log( lib.add(1,2,3,4) ); // 10 console.log( lib.mult(1,2,3,4) ); // 24
Так что модули ES6 было легко внедрить в Node.js, верно? Э, нет
Модули ES6 находятся за флагом в Node.js 9.8.0+ и не будут полностью реализованы по крайней мере до версии 10. Хотя модули CommonJS и ES6 имеют одинаковый синтаксис, они работают принципиально по-разному:
- Модули ES6 предварительно анализируются для разрешения дальнейшего импорта перед выполнением кода.
- Модули CommonJS загружают зависимости по требованию при выполнении кода.
В приведенном выше примере это не имеет значения, но рассмотрим следующий код модуля ES2015:
// ES2015 modules // --------------------------------- // one.js console.log('running one.js'); import { hello } from './two.js'; console.log(hello); // --------------------------------- // two.js console.log('running two.js'); export const hello = 'Hello from two.js';
Выход для ES2015:
running two.js running one.js hello from two.js
Аналогичный код, написанный с использованием CommonJS:
// CommonJS modules // --------------------------------- // one.js console.log('running one.js'); const hello = require('./two.js'); console.log(hello); // --------------------------------- // two.js console.log('running two.js'); module.exports = 'Hello from two.js';
Выход для CommonJS:
running one.js running two.js hello from two.js
Порядок выполнения может быть критическим в некоторых приложениях, и что произойдет, если модули ES2015 и CommonJS будут смешаны в одном файле? Чтобы решить эту проблему, Node.js разрешит только модули ES6 в файлах с расширением .mjs
. Файлы с расширением .js
умолчанию будут использовать CommonJS. Это простой вариант, который устраняет большую часть сложности и должен помочь редакторам кода и линтерам.
Стоит ли использовать модули ES6 в Node.js?
Модули ES6 практичны только с Node.js v10 (выпущен в апреле 2018 года). Преобразование существующего проекта вряд ли приведет к каким-либо преимуществам и сделает приложение несовместимым с более ранними версиями Node.js.
Для новых проектов модули ES6 предоставляют альтернативу CommonJS. Синтаксис идентичен кодированию на стороне клиента и может предложить более простой путь к изоморфному JavaScript, который может выполняться либо в браузере, либо на сервере.
Модуль ближнего боя
Стандартизованная система модулей JavaScript заняла много лет, и еще дольше внедрилась, но проблемы были устранены. Все основные браузеры и Node.js с середины 2018 года поддерживают модули ES6, хотя при каждом обновлении следует ожидать задержки переключения.
Познакомьтесь с модулями ES6 сегодня, чтобы завтрашний день помог вам в разработке JavaScript