Статьи

Понимание модулей ES6

В этой статье рассматриваются модули ES6, показывая, как их можно использовать сегодня с помощью транспортера.

Почти у каждого языка есть понятие модулей — способ включить функциональность, объявленную в одном файле в другой. Как правило, разработчик создает инкапсулированную библиотеку кода, отвечающую за обработку связанных задач. На эту библиотеку могут ссылаться приложения или другие модули.

Выгоды:

  1. Код можно разбить на более мелкие файлы с автономной функциональностью.
  2. Одни и те же модули можно использовать в любом количестве приложений.
  3. В идеале, модули никогда не должны проверяться другим разработчиком, потому что они доказали свою работоспособность.
  4. Код, ссылающийся на модуль, понимает, что это зависимость. Если файл модуля изменяется или перемещается, проблема сразу становится очевидной.
  5. Код модуля (обычно) помогает устранить конфликты имен. Функция 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