Про модулі JavaScript, формати, завантажувачі та збирачів модулів за 10 хвилин


Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.

Попри те, що нові мови програмування з’являються щороку, 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 functionalitysingleton.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 (); // HellosayGoodbye (); // 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, до того як код буде запущений у браузері.

Завантажувачі модулів

Завантажувач модулів інтерпретує і завантажує модуль, написаний у певному форматі.

Завантажувач модуля запускається в середовищі виконання:

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

Якщо Ви відкриєте вкладку “Мережа” в консолі розробника на своєму браузері, то побачите, що багато файлів було завантажено за запитом завантажувача модулів.

Ось декілька популярних завантажувачів:

  • RequireJS: завантажувач модулів у форматі AMD ;
  • SystemJS: завантажувач модулів у форматах AMD, CommonJS, UMD і System.register format.

Збирачі модулів

Збирач модулів замінює собою завантажувач модулів. Проте на відміну від завантажувача модулів, збирач модулів запускається при складанні:

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

Якщо Ви відкриєте вкладку “Мережа” в консолі розробника на своєму браузері, то побачите, що завантажений тільки один файл. Таким чином, немає необхідності в завантажувачі модулів: увесь код включений в один пакет.

Пара популярних збирачів:

  • Browserify: збирач для модулів CommonJS;
  • Webpack: збирач для модулів AMD, CommonJS, ES6.

Підсумуємо викладене вище

Щоб краще розібратися в інструментах сучасного середовища розробки на JavaScript, важливо розуміти різницю між модулями, форматами, завантажувачами і збирачами модулів.

Модуль – це перевикористовувана частина коду, що містить деталі реалізації і надає відкрите API, яке дозволяє легко завантажити її і використати в іншому коді.

Формат модуля – це синтаксис, який використовується для визначення модулів. Раніше виникали різні формати: AMD, CommonJS, UMD і System.register, нативний формат модулів з’явився в ES6.

Завантажувач модуля інтерпретує і завантажує модуль, написаний в певному форматі, під час виконання (у браузері). Поширені – RequireJS і SystemJS.

Збирач модуля замінює завантажувач модулів і створює пакет, увесь код, що містить, під час складання. Популярні приклади – Browserify і Webpack.

От і все – тепер у Вас достатній рівень знань для розуміння сучасної розробки на JavaScript. Наступного разу, коли Ваш компілятор TypeScript запитає: “Який формат модулів Ви хочете використати?” – Ви вже не замислюватиметесь над тим, що б це означало. А якщо таке станеться – перечитайте цю статтю.

Гарного дня і програмуйте із задоволенням!


За переклад матеріалу висловлюємо вдячність міжнародній IT-компанії Noveo.

Переклад статті “A 10 minute primer to JavaScript modules, module formats, module loaders and module bundlers”

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *