вНовости программирования

О модулях JavaScript, форматах, загрузчиках и сборщиках модулей за 10 минут

Несмотря на то, что новые языки программирования появляются каждый год, JavaScript остаётся одним из самых распространённых и любимых программистами. И как и любой современный язык, он стремительно развивается, что делает изучение его с нуля очень непростой задачей. В этом материале простым языком рассказывается о том, как в JavaScript устроена работа с модулями. Статья может не только помочь новичкам понять, что происходит, но и освежить знания основ у тех, кто уже работает с JS.

Разработка с использованием современных технологий JavaScript может ошеломлять. Работая над проектом, ты легко можешь задаться вопросом, для чего же нужны новые механизмы и инструменты.

Для чего предназначены Webpack и SystemJS? Что значит AMD, UMD или CommonJS? Какое отношение они имеют друг к другу и зачем вообще их использовать?

В этой статье речь пойдёт о различии между модулями JavaScript, а также о форматах, загрузчиках и сборщиках модулей. Это не подробное руководство по использованию каких-либо инструментов или паттернов, а краткий обзор, призванный дать общее понятие о концепции современного JavaScript.

Итак, приступим.

Что такое модуль?

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

Зачем нужны модули?

Технически код можно написать и без использования модулей. Модули — это паттерн, который в разных формах и на разных языках используется разработчиками с 60-х и 70-х годов.

В идеале, модули JavaScript позволяют нам:

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

Паттерны модулей в ES5

ECMAScript 5 и более ранние версии не были спроектированы с учётом модулей. Со временем разработчики нашли различные возможности симулировать модульную архитектуру на JavaScript.

Прим. перев. Senior-разработчики компании Noveo говорят, что помнят, как это было: как они проходили путь от работы без модулей к первым попыткам написать их самостоятельно, потом использовать чужие наработки… Ну а все системы, перечисленные ниже, они знают не понаслышке. Эх, были времена!

Чтобы дать представление о том, как выглядят такие паттерны, давайте взглянем на два из них: мгновенно вызываемая функция (Immediately Invoked Function Expressions) и выявление модуля (Revealing Module).

Мгновенно вызываемая функция (Immediately Invoked Function Expression или IIFE)

(function(){ // ... })()

Мгновенно вызываемая функция (IIFE) — анонимная функция, которая вызывается сразу после объявления. Обратите внимание: функция окружена скобками. В JavaScript строка, начинающаяся со слова function, воспринимается как объявление функции:

// Объявление функции function(){ console.log('test'); }

Мгновенный вызов объявления функции выдаёт ошибку:

// Мгновенное объявление функции function(){ console.log('test'); }() // Uncaught SyntaxError: Unexpected token )

Помещение функции в скобки делает это функциональным выражением:

// Функциональное выражение (function(){ console.log('test'); }) // returns function test(){ console.log('test') }

Функциональное выражение возвращает нам функцию, так что мы можем тут же к ней обратиться:

// Мгновенно вызываемая функция (function(){ console.log('test'); })() // пишет 'test' в консоли и возвращает undefined

Мгновенно вызываемые функции позволяют нам:

  • полностью инкапсулировать код в IIFE, так что нам не придётся разбираться, как работает код IIFE;
  • определять переменные внутри IIFE, чтобы они не засоряли глобальную область видимости (переменные, объявленные внутри IIFE, остаются в рамках замкнутого выражения).

Однако они не дают нам механизма управления зависимостями.

Паттерн выявления модуля (Revealing Module)

Паттерн выявления модуля схож с IIFE, но здесь мы назначаем возвращённое значение на переменную:

// Объявление модуля как глобальной переменной var singleton = function(){ // Внутренняя логика function sayHello(){ console.log('Hello'); } // Внешнее API return { sayHello: sayHello } }()

Заметьте, что здесь нет необходимости в скобках, так как слово function находится не в начале строки.

Теперь мы можем обратиться к API модуля через переменную:

// Access module functionality singleton.sayHello(); // Hello

Вместо синглтона модуль может выступать и как функция-конструктор:

// Объявление модуля как глобальной переменной var Module = function(){ // Внутренняя логика function sayHello(){ console.log('Hello'); } // Внешнее API return { sayHello: sayHello } }

Обратите внимание: мы не запускаем функцию при её объявлении, вместо этого мы инициализируем модуль при помощи функции-конструктора Module

var module = new Module();

для доступа к внешнему API

module.sayHello(); // Hello

Паттерн выявления модуля предоставляет те же преимущества, что и IIFE, но опять же не даёт возможности управлять зависимостями.

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

Форматы модулей

Формат модуля — это синтаксис, который используется для его определения.

До создания ECMAScript 6, или ES2015, в JavaScript не было официального синтаксиса для определения модулей. А значит, опытные разработчики предлагали разные форматы определения.

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

  • асинхронное определение модуля (Asynchronous Module Definition или AMD);
  • CommonJS;
  • универсальное определение модуля (Universal Module Definition или UMD);
  • System.register;
  • формат модуля ES6.

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

Асинхронное определение модуля (AMD)

Формат AMD используется в браузерах и применяет для определения модулей функцию define:

//Вызов функции define с массивом зависимостей и фабричной функцией define(['dep1', 'dep2'], function (dep1, dep2) { //Определение модуля с помощью возвращаемого значения return function () {}; });

Формат CommonJS

Формат CommonJS применяется в Node.js и использует для определения зависимостей и модулей require и module.exports:

var dep1 = require('./dep1');   var dep2 = require('./dep2'); module.exports = function(){    // ... }

Универсальное определение модуля (UMD)

Формат UMD может быть использован как в браузере, так и в Node.js.

(function (root, factory) {  if (typeof define === 'function' && define.amd) {    // AMD. Подключение анонимного модуля      define(['b'], factory);  } else if (typeof module === 'object' && module.exports) {    // Node. Не работает с CommonJS напрямую,    // только CommonJS-образными средами, которые поддерживают          // module.exports, как Node.    module.exports = factory(require('b'));  } else {    // Глобальные переменные браузера (root это window)    root.returnExports = factory(root.b);  } }(this, function (b) {  //как-нибудь использовать b.  // Просто возвращаем значение для определения модуля.  // Этот пример возвращает объект, но модуль  // может вернуть и функцию как экспортируемое значение.  return {}; }));

System.registerА

Формат System.register был разработан для поддержки синтаксиса модулей ES6 в ES5:

import { p as q } from './dep'; var s = 'local'; export function func() {    return q; } export class C {   }

Формат модулей ES6

В ES6 JavaScript уже поддерживает нативный формат модулей.

Он использует токен export для экспорта публичного API модуля:

// lib.js // Экспорт функции export function sayHello(){    console.log('Hello'); } // Не экспоруемая функция function somePrivateFunction(){    // ... }

и токен import для импорта частей, которые модуль экспортирует:

import { sayHello } from './lib'; sayHello();   // Hello

Мы можем даже присваивать импорту алиас, используя as:

import { sayHello as say } from './lib'; say();   // Hello

или загружать сразу весь модуль:

import * as lib from './lib'; lib.sayHello();   // Hello

Формат также поддерживает экспорт по умолчанию:

// lib.js // Экспорт дефолтной функции export default function sayHello(){    console.log('Hello'); } // Экспорт недефолтной функции export function sayGoodbye(){    console.log('Goodbye'); }

который можно импортировать, например, так:

import sayHello, { sayGoodbye } from './lib'; sayHello();   // Hello sayGoodbye();   // Goodbye

Вы можете экспортировать не только функции, но и всё, что пожелаете:

// lib.js // Экспорт дефолтной функции export default function sayHello(){    console.log('Hello'); } // Экспорт недефолтной функции export function sayGoodbye(){    console.log('Goodbye'); } //  Экспорт простого значения export const apiUrl = '...'; // Экспорт объекта export const settings = {    debug: true }

К сожалению, нативный формат модулей пока поддерживают не все браузеры.

Мы можем использовать формат модулей ES6 уже сегодня, но для этого потребуется компилятор наподобие Babel, который будет переводить наш код в формат ES5, такой, как AMD или CommonJS, перед тем, как код будет запущен в браузере.

Модули JS

 

Загрузчики модулей

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

Загрузчик модуля запускается в среде исполнения:

  • вы загружаете загрузчик модуля в браузере;
  • вы сообщаете загрузчику, какой главный файл приложения запустить;
  • модуль скачивает и интерпретирует главный файл приложения;
  • загрузчик модулей скачивает файлы по мере необходимости.

Если вы откроете вкладку «Сеть» в консоли разработчика на своём браузере, вы увидите, что многие файлы были загружены по запросу загрузчика модулей.

Вот несколько популярных загрузчиков:

  • RequireJS: загрузчик модулей в формате AMD ;
  • SystemJS: загрузчик модулей в форматах AMD, CommonJS, UMD и System.register format.

Сборщики модулей

Webpack JavaScript

 

Сборщик модулей заменяет собой загрузчик модулей. Однако в отличие от загрузчика модулей, сборщик модулей запускается при сборке:

  • вы запускаете сборщик модулей для создания файла пакета во время сборки (например, bundle.js);
  • и загружаете пакет в браузер.

Если вы откроете вкладку «Сеть» в консоли разработчика на своём браузере, вы увидите, что загружен только один файл. Таким образом, отпадает необходимость в загрузчике модулей: весь код включён в один пакет.

Пара популярных сборщиков:

  • Browserify: сборщик для модулей CommonJS;
  • Webpack: сборщик для модулей AMD, CommonJS, ES6.

Подводим итоги

Чтобы лучше разобраться в инструментах современной среды разработки на JavaScript, важно понимать разницу между модулями, форматами, загрузчиками и сборщиками модулей.

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

Формат модуля — это синтаксис, который используется для определения модулей. В прошлом возникали разные форматы: AMD, CommonJS, UMD и System.register нативный формат модулей появился в ES6.

Загрузчик модуля интерпретирует и загружает модуль, написанный в определённом формате, в время выполнения (в браузере). Распространённые — RequireJS и SystemJS.

Сборщик модуля заменяет загрузчик модулей и создаёт пакет, содержащий весь код, во время сборки.  Популярные примеры — Browserify и Webpack.

Вот и всё — теперь у вас достаточно знаний для понимания современной разработки на JavaScript. В следующий раз, когда ваш компилятор TypeScript спросит, какой формат модулей вы хотите использовать, вы уже не будете гадать, что бы это значило. А если такое произойдет — просто перечитайте эту статью.

Отличного дня и программируйте с удовольствием!


За перевод материала выражаем благодарность международной IT-компании Noveo.

Типичный программист.

Источник: Типичный программист