Понимание (всех) «модульных» форматов и инструментов JavaScript
Доброго времени суток, друзья!
Представляю вашему вниманию перевод статьи «Understanding (all) JavaScript module formats and tools» автора Dixin.
При создании приложения часто возникает желание разделить код на части, логические или функциональные блоки (модули). Однако JavaScript изначально не имел поддержки модулей. Это привело к появлению различных модульных технологий. В настоящей статье обсуждаются все основные понятия, шаблоны, библиотеки, синтаксис и инструменты для работы с модулями в JavaScript.
IIFE модуль: шаблон JS модуля
Определяя переменную в JS, мы определяем ее как глобальную переменную. Это означает, что такая переменная будет доступна во всех файлах JS, загружаемых на текущей странице:
// определяем глобальные переменные
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
// используем глобальные переменные
increase()
reset()
Для того, чтобы избежать загрязнения глобального пространства имен, можно использовать анонимную функцию:
(() => {
let count = 0
// ...
})
Вуаля, глобальных переменных больше нет. Однако код внутри функции не выполняется.
IIFE: немедленно вызываемое функциональное выражение
Для того, чтобы выполнить код внутри функции f
, ее необходимо вызвать с помощью ()
как f()
. Для выполнения кода внутри анонимной функции (() => {})
следует также использовать ()
. Это выглядит так (() => {})()
:
(() => {
let count = 0
// ...
})()
Это называется IIFE (немедленно вызываемым функциональным выражением). Модуль может быть определен следующим образом:
// определяем IIFE модуль
const iifeCounterModule = (() => {
let count = 0
return {
increase: () => ++count,
reset: () => {
count = 0
console.log('Счетчик сброшен.')
}
}
})()
// используем IIFE модуль
iifeCounterModule.increase()
iifeCounterModule.reset()
Мы оборачиваем код модуля в IIFE. Анонимная функция возвращает объект. Это заменяет интерфейс экспорта. Присутствует только одна глобальная переменная — название модуля (или его пространство имен). Впоследствии название модуля может использоваться для его вызова (экспорта). Это называется шаблоном JS модуля.
Примеси импорта
При определении модуля могут потребоваться некоторые зависимости. При использовании модульного шаблона каждый зависимый модуль — глобальная переменная. Зависимые модули могут определяться внутри анонимной функции или передаваться ей в качестве аргументов:
// определяем IIFE модуль с зависимостями
const iifeCounterModule = ((dependencyModule1, dependencyModule2) => {
let count = 0
return {
increase: () => ++count,
reset: () => {
count = 0
console.log('Счетчик сброшен.')
}
}
})(dependencyModule1, dependencyModule2)
Ранние версии популярных библиотек, таких как jQuery, использовали этот шаблон (в последней версии jQuery используется UMD модуль).
Открытый модуль: шаблон открытого JS модуля
Шаблон открытого модуля был придуман Christian Heilmann. Этот шаблон также является IIFE, но акцент в нем делается на определении всех интерфейсов как локальных переменных внутри анонимной функции:
// определяем открытый модуль
const revealingCounterModule = (() => {
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
return {
increase,
reset
}
})()
// используем открытый модуль
revealingCounterModule.increase()
revealingCounterModule.reset()
Такой синтаксис облегчает понимание того, за что отвечает (или что делает) каждый интерфейс.
CJS модуль: CommonJS модуль или Node.js модуль
CommonJS, первоначально названный ServerJS, это шаблон для определения и использования модулей. Он встроен в Node.js. По умолчанию каждый JS файл — это CJS. Переменные module
и exports
обеспечивают экспорт модуля (файла). Функция require
обеспечивает загрузку и использование модуля. Следующий код демонстрирует определение модуля счетчика на синтаксисе CommonJS:
// определяем CommonJS модуль: commonJSCounterModule.js
const dependencyModule1 = require('./dependencyModule1')
const dependencyModule2 = require('./dependencyModule2')
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
exports.increase = increase
exports.reset = reset
// или (эквивалентно)
module.exports = {
increase,
reset
}
Вот как этот модуль используется:
// используем CommonJS модуль
const {
increase,
reset
} = require('./commonJSCounterModule')
increase()
reset()
// или
const commonJSCounterModule = require('./commonJSCounterModule')
commonJSCounterModule.increase()
commonJSCounterModule.reset()
В среде выполнения (движке) Node.js этот шаблон используется путем оборачивания кода внутри файла в функцию, которой в качестве параметров передаются переменные exports, module
и функция require
:
// определяем CommonJS модуль
(function(exports, require, module, __filename, __dirname) {
const dependencyModule1 = require('./dependencyModule1')
const dependencyModule2 = require('./dependencyModule2')
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
module.exports = {
increase,
reset
}
return module.exports
}).call(thisValue, exports, require, module, filename, dirname)
// используем CommonJS модуль
(function(exports, require, module, __filename, __dirname) {
const commonJSCounterModule = require('./commonJSCounterModule')
commonJSCounterModule.increase()
commonJSCounterModule.reset()
}).call(thisValue, exports, require, module, filename, dirname)
AMD модуль или RequireJS модуль
AMD (асинхронное определение модуля) — это шаблон для определения и использования модулей. Он используется в библиотеке RequireJS. AMD содержит функцию define
для определения модуля, которая принимает название модуля, названия зависимостей и фабричную функцию:
// определяем AMD модуль
define('amdCounterModule', ['dependencyModule1', 'dependencyModule2'], (dependencyModule1, dependencyModule2) => {
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
return {
increase,
reset
}
})
Он также содержит функцию require
для использования модуля:
// используем AMD модуль
require(['amdCounterModule'], amdCounterModule => {
amdCounterModule.increase()
amdCounterModule.reset()
})
require
AMD отличается от require
CommonJS тем, что в качестве аргументов функции принимает названия модулей и сами модули.
Динамическая загрузка
Функция define
также имеет другое назначение. Она принимает функцию обратного вызова и передает похожую на CommonJS require
этой функции. Внутри функции обратного вызова require вызывается для динамической загрузки модуля:
// используем динамический AMD модуль
define(require => {
const dynamicDependencyModule1 = require('dependencyModule1')
const dynamicDependencyModule2 = require('dependencyModule2')
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
return {
increase,
reset
}
})
AMD модуль из CommonJS модуля
Приведенная выше функция define
, кроме require
, может принимать в качестве аргументов переменные exports
и module
. Поэтому внутри define
может выполняться код из CommonJS:
// определяем AMD модуль с CommonJS кодом
define((require, exports, module) => {
// CommonJS код
const dependencyModule1 = require('dependencyModule1')
const dependencyModule2 = require('dependencyModule2')
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
exports.increase = increase
exports.reset = reset
})
// используем AMD модуль с CommonJS кодом
define(require => {
// CommonJS код
const counterModule = require('amdCounterModule')
counterModule.increase()
counterModule.reset()
})
UMD модуль: универсальное определение модуля или UmdJS модуль
UMD (универсальное определение модуля) — набор шаблонов для обеспечения работы модуля в разных средах выполнения.
UMD для AMD (RequireJS) и браузера
Следующий код обеспечивает работу модуля как в AMD (RequireJS), так и в браузере:
// определяем UMD модуль для AMD (RequireJS) и браузера
((root, factory) => {
// обнаруживаем функцию define AMD/RequireJS
if (typeof define === 'function' && define.amd) {
// если присутствует, вызываем фабричную функцию с ее помощью
define('umdCounterModule', ['dependencyModule1', 'dependencyModule2'], factory)
} else {
// если отсутствует, вызываем фабричную функцию напрямую
// импортируемые зависимости являются глобальными переменными (свойствами объекта Window)
// как и экспортируемый модуль
root.umdCounterModule = factory(root.dependencyModule1, root.dependencyModule2)
}
})(typeof self !== undefined ? self : this, (dependencyModule1, dependencyModule2) => {
// код модуля
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log('Счетчик сброшен')
}
return {
increase,
reset
}
})
Выглядит сложно, но это всего лишь IIFE. Анонимная функция определяет наличие функции define
из AMD/RequireJS.
- Если
define
обнаружена, фабричная функция вызывается через нее. - Если
define
не обнаружена, фабричная функция вызывается напрямую. В этот момент аргументroot
— это объект Window браузера. Он получает зависимые модули из глобальных переменных (свойств объекта Window). Когдаfactory
возвращает модуль, он также становится глобальной переменной (свойством объекта Window).
UMD для AMD (RequireJS) и CommonJS (Node.js)
Следующий код обеспечивает работу модуля как в AMD (RequireJS), так и в CommonJS (Node.js):
(define => define((require, exports, module) => {
// код модуля
const dependencyModule1 = require("dependencyModule1")
const dependencyModule2 = require("dependencyModule2")
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log("Count is reset.")
}
module.export = {
increase,
reset
}
}))( // обнаруживаем переменные module и exports из CommonJS/Node.js
// также обнаруживаем функцию define из AMD/RequireJS
typeof module === 'object' && module.exports && typeof define !== 'function'
? // CommonJS/Node.js. Вручную создаем функцию define
factory => module.exports = factory(require, exports, module)
: // AMD/RequireJS. Используем функцию define
define)
Не пугайтесь, это снова всего лишь IIFE. При вызове анонимной функции, происходит «оценка» ее аргумента. Оценивание аргумента позволяет определить среду выполнения (определяется наличие переменных module
и exports
из CommonJS/Node.js, а также функции define
из AMD/RequireJS).
- Если средой выполнения является CommonJS/Node.js, аргумент анонимной функции вручную создает функцию
define
. - Если средой выполнения является AMD/RequireJS, аргументом анонимной функции является функция
define
из этой среды. Выполнение анонимной функции гарантирует работу функцииdefine
. Внутри анонимной функции для создания модуля вызывается функцияdefine
.
ES модуль: ECMAScript2015 или ES6 модуль
В 2015 году в 6 версии спецификации JS был представлен новый модульный синтаксис. Данная сецификаци получила название ECMAScript 2015 (ES2015) или ECMAScript 6 (ES6). Основа нового синтаксиса — ключевые слова import
и export
. Следующий код демонстирует использование ES модуля для именованного и «дефолтного» (по умолчанию) импорта/экспорта:
// определяем ES модуль: esCounterModule.js или esCounterModule.mjs
import dependencyModule1 from './dependencyModule1.mjs'
import dependencyModule2 from './dependencyModule2.mjs'
let count = 0
// именованный экспорт
export const increase = () => ++count
export const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
// или экспорт по умолчанию
export default {
increase,
reset
}
Для использования модульного файла в браузере необходимо добавить тег и определить его как модуль:
. Для использования этого модуля в Node.js меняем его расширение на
.mjs
:
// использование ES модуля
// импорт из именованного экспорта
import {
increase,
reset
} from './esCounterModule.mjs'
increase()
reset()
// или импорт из экпорта по умолчанию
import esCounterModule from './esCounterModule.mjs'
esCounterModule.increase()
esCounterModule.reset()
Для обратной совместимости в браузере можно добавить тег с атрибутом
nomodule
:
ES динамический модуль: ECMAScript2020 или ES11 динамический модуль
В последней 11 версии спецификации JS 2020 года представлена встроенная функция import
для динамического использования ES модулей. Данная функция возвращает промис, поэтому использовать модуль можно с помощью then
:
// используем динамический ES модуль с интерфейсом промисов, импорт из именованного экспорта
import('./esCounterModule.js').then(({
increase,
reset
}) => {
increase()
reset()
})
// или импорт из экспорта по умолчанию
import('./esCounterModule.js').then(dynamicESCounterModule => {
dynamicESCounterModule.increase()
dynamicESCounterModule.reset()
})
Благодаря тому, что функция import
возвращает промис, в ней может использоваться ключевое слово await
:
// используем динамический ES модуль с async/await
(async () => {
// импорт из именованного экспорта
const {
increase,
reset
} = await import('./esCounterModule.js')
increase()
reset()
// или импорт из экспорта по умолчанию
const dynamicESCounterModule = await import('./esCounterModule.js')
dynamicESCounterModule.increase()
dynamicESCounterModule.reset()
})
Системный модуль: SystemJS модуль
SystemJS — это библиотека для обеспечения работы ES модулей в старых браузерах. Например, следующий модуль написан с использованием синтаксиса ES6:
// определяем ES модуль
import dependencyModule1 from "./dependencyModule1.js"
import dependencyModule2 from "./dependencyModule2.js"
dependencyModule1.api1()
dependencyModule2.api2()
let count = 0
// именованный экспорт
export const increase = function() {
return ++count
}
export const reset = function() {
count = 0
console.log("Count is reset.")
}
// или экспорт по умолчанию
export default {
increase,
reset
}
Этот код не будет работать в браузерах, не поддерживающих синтаксис ES6. Одним из решений данной проблемы является транспиляция кода с помощью интерфейса System.register
библиотеки SystemJS:
// определяем SystemJS модуль
System.register(['./dependencyModule1', './dependencyModule2'], function(exports_1, context_1) {
'use strict'
var dependencyModule1_js_1, dependencyModule2_js_1, count, increase, reset
var __moduleName = context_1 && context_1.id
return {
setters: [
function(dependencyModule1_js_1_1) {
dependencyModule1_js_1 = dependencyModule1_js_1_1
},
function(dependencyModule2_js_1_1) {
dependencyModule2_js_1 = dependencyModule2_js_1_1
}
],
execute: function() {
dependencyModule1_js_1.default.api1()
dependencyModule2_js_1.default.api2()
count = 0
// именованный экспорт
exports_1('increase', increase = function() {
return ++count
})
exports_1('reset', reset = function() {
count = 0
console.log('Счетчик сброшен.')
})
// или экспорт по умолчанию
exports_1('default', {
increase,
reset
})
}
}
})
Нового модульного синтаксиса ES6 больше нет. Зато код будет прекрасно работать в старых браузерах. Эта транспиляция может быть выполнена автоматически с помощью Webpack, TypeScript и т.д.
Динамическая загрузка модуля
SystemJS также содержит функцию import
для динамического импорта:
// используем SystemJS модуль с промисами
System.import('./esCounterModule.js').then(dynamicESCounterModule => {
dynamicESCounterModule.increase()
dynamicESCounterModule.reset()
})
Webpack модуль: транспиляция и сборка CJS, AMD и ES модулей
Webpack — это сборщик модулей. Его транспилятор объединяет CommonJS, AMD и ES модули в единый сбалансированный модульный шаблон и собирает весь код в один файл. Например, в следующих 3 файлах определяются 3 модуля с помощью различного синтаксиса:
// определяем AMD модуль: amdDependencyModule1.js
define('amdDependencyModule1', () => {
const api1 = () => {}
return {
api1
}
})
// определяем CommonJS модуль: commonJSDependencyModule2.js
const dependencyModule2 = require('./commonJSDependencyModule2')
const api2 = () => dependencyModule1.api1()
exports.api2 = api2
// определяем ES модуль: esCounterModule.js
import dependencyModule1 from './amdDependencyModule1'
import dependencyModule2 from './commonJSDependencyModule2'
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
export default {
increase,
reset
}
Следующий код демонстрирует использование этого модуля:
// использование ES модуля: index.js
import counterModule from './esCounterModule'
counterModule.increase()
counterModule.reset()
Webpack способен объединить эти файлы, несмотря на то, что они представляют собой разные модульные системы, в один файл main.js
:
root dist main.js (сборка файлов, находящихся в папке src) src amdDependencyModule1.js commonJSDependencyModule2.js esCounterModule.js index.js webpack.config.js
Поскольку Webpack основан на Node.js, он использует модульный синтаксис CommonJS. В webpack.config.js
:
const path = require('path')
module.exports = {
entry: './src/index.js',
mode: 'none', // не оптимизировать и не минифицировать код в целях сохранения читаемости
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
}
Для транспиляции и сборки необходимо выполнить следующие команды:
npm install webpack webpack-cli --save-dev npx webpack --config webpack.config.js
В результате Webpack создаст файл main.js
. Следующий код из main.js
отформатирован для улучшения читаемости:
(function(modules) { // инициализация Webpack
// кэш для сохранения модулей
var installedModules = {}
// функция require
function require(moduleId) {
// проверяем, есть ли модуль в кэше
if (installedModules[moduleId]) {
return installedModules[moduleId].exports
}
// создаем новый модуль и помещаем его в кэш
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
}
// выполняем функцию module
modules[moduleId].call(module.exports, module, module.exports, require)
// помечаем модуль как загруженный
module.l = true
// возвращаем свойство exports модуля
return module.exports
}
// привязываем объект modules (__webpack_modules__)
require.m = modules
// привязываем кэш с модулями
require.c = installedModules
// определяем геттер для сбалансированного экспорта
require.d = function(exports, name, getter) {
if (!require.o(exports, name)) {
Object.defineProperty(exports, name, {
enumerable: true,
get: getter
})
}
}
// определяем __esModule в exports
require.r = function(exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module'
})
}
Object.defineProperty(exports, '__esModule', {
value: true
})
}
// создаем временный объект для хранения пространства имен
// mode & 1: значение - идентификатор модуля, запрашиваем его
// mode & 2: объединяем все свойства значения в объект ns (namespace)
// mode & 4: возвращаем значение, когда объект ns уже существует
// mode & 8|1: поведение аналогично require
require.t = function(value, mode) {
if (mode & 1) value = require(value)
if (mode & 8) return value
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value
var ns = Object.create(null)
require.r(ns)
Object.defineProperty(ns, 'default', {
enumerable: true,
value: value
})
if (mode & 2 && typeof value !== 'string')
for (var key in value) require.d(ns, key, function(key) {
return value[key]
}.bind(null, key))
return ns
}
// фукнция getDefaultExport для обеспечения совместимости с несбалансированными модулями
require.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() {
return module['default']
} :
function getModuleExports() {
return module
}
require.d(getter, 'a', getter)
return getter
}
// Object.prototype.hasOwnProperty.call
require.o = function(object, property) {
return Object.prototype.hasOwnProperty.call(object, property)
}
// __webpack_public_path__
require.p = ''
// загружаем входящий модуль и возвращаем exports
return require(require.s = 0)
})([
function(module, exports, require) {
'use strict'
require.r(exports)
// используем ES модуль: index.js
var esCounterModule = require(1)
esCounterModule['default'].increase()
esCounterModule['default'].reset()
},
function(module, exports, require) {
'use strict'
require.r(exports)
// определяем ES модуль: esCounterModule.js
var amdDependencyModule1 = require.n(require(2))
var commonJSDependencyModule2 = require.n(require(3))
amdDependencyModule1.a.api1()
commonJSDependencyModule2.a.api2()
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
exports['default'] = {
increase,
reset
}
},
function(module, exports, require) {
var result!(result = (() => {
// определяем AMD модуль: amdDependencyModule1.js
const api1 = () => {}
return {
api1
}
}).call(exports, require, exports, module),
result !== undefined && (module.exports = result))
},
function(module, exports, require) {
// определяем CommonJS модуль: commonJSDependencyModule2.js
const dependencyModule1 = require(2)
const api2 = () => dependencyModule1.api1()
exports.api2 = api2
}
])
И снова это всего лишь IIFE. Код из 4 файлов преобразован в массив из 4 функций. И этот массив передается анонимной функции в качестве параметра.
Babel модуль: транспиляция ES модуля
Babel — это еще один транспилятор для обеспечения работы ES6+ кода в старых браузерах. Приведенный выше ES6+ модуль может быть преобразован в Babel модуль следуюшим образом:
// Babel
Object.defineProperty(exports, '__esModule', {
value: true
})
exports['default'] = void 0
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
'default': obj
}
}
// определяем ES модуль: esCounterModule.js
var dependencyModule1 = _interopRequireDefault(require('./amdDependencyModule1'))
var dependencyModule2 = _interopRequireDefault(require('./commonJSDependencyModule2'))
dependencyModule1['default'].api1()
dependencyModule2['default'].api2()
var count = 0
var increase = function() {
return ++count
}
var reset = function() {
count = 0
console.log('Счетчик сброшен.')
}
exports['default'] = {
increase: increase,
reset: reset
}
А вот код в index.js
, демонстрирующий использование этого модуля:
// Babel
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
'default': obj
}
}
// используем ES модуль: index.js
var esCounterModule = _interopRequireDefault(require('./esCounterModule.js'))
esCounterModule['default'].increase()
esCounterModule['default'].reset()
Это транспиляция по умолчанию. Babel также умеет работать с другими инструментами.
Babel и SystemJS
SystemJS может использоваться как плагин для Babel:
npm install --save-dev @babel/plugin-transform-modules-systemjs
Данный плагин должен быть добавлен в babel.config.json
:
{ 'plugins': ['@babel/plugin-transform-modules-systemjs'], 'presets': [ [ '@babel/env', { 'targets': { 'ie': '11' } } ] ] }
Теперь Babel может работать с SystemJS для транспиляции CommonJS/Node.js, AMD/RequireJS и ES модулей:
npx babel src --out-dir lib
Результат:
root lib amdDependencyModule1.js (транспилирован с помощью SystemJS) commonJSDependencyModule2.js (транспилирован с помощью SystemJS) esCounterModule.js (транспилирован с помощью SystemJS) index.js (транспилирован с помощью SystemJS) src amdDependencyModule1.js commonJSDependencyModule2.js esCounterModule.js index.js babel.config.json
Весь синтаксис AMD, CommonJS и ES модулей транспилирован в синтаксис SystemJS:
// транспилируем AMD/RequireJS модуль в SystemJS модуль: lib/amdDependencyModule1.js
System.register([], function(_export, _context) {
'use strict'
return {
setters: [],
execute: function() {
// определяем AMD модуль: src/amdDependencyModule1.js
define('amdDependencyModule1', () => {
const api1 = () => {}
return {
api1
}
})
}
}
})
// транспилируем CommonJS/Node.js модуль в SystemJS модуль: lib/commonJSDependencyModule2.js.
System.register([], function(_export, _context) {
'use strict'
var dependencyModule1, api2
return {
setters: [],
execute: function() {
// определяем CommonJS модуль: src/commonJSDependencyModule2.js
dependencyModule1 = require('./amdDependencyModule1')
api2 = () => dependencyModule1.api1()
exports.api2 = api2
}
}
})
// транспилируем ES модуль в SystemJS модуль
System.register(['./amdDependencyModule1', './commonJSDependencyModule2'], function(_export, _context) {
var dependencyModule1, dependencyModule2, count, increase, reset
return {
setters: [function(_amdDependencyModule) {
dependencyModule1 = _amdDependencyModule.default
}, function(_commonJSDependencyModule) {
dependencyModule2 = _commonJSDependencyModule.default
}],
execute: function() {
// определяем ES модуль: src/esCounterModule.js
dependencyModule1.api1()
dependencyModule1.api2()
count = 0
increase = () => ++count
reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
_export('default', {
increase,
reset
})
}
}
})
// транспилируем ES модуль в SystemJS модуль: lib/index.js
System.register(['./esCounterModule'], function(_export, _context) {
var esCounterModule
return {
setters: [function(_esCounterModule) {
esCounterModule = _esCounterModule.default
}],
execute: function() {
// используем ES модуль: src/index.js
esCounterModule.increase()
esCounterModule.reset()
}
}
})
TypeScript модуль: транспиляция CJS, AMD, ES и SystemJS модулей
TypeScript поддерживает все разновидности синтаксиса JS, включая ES6. При транспиляции синтаксис ES6 модуля может быть сохранен или преобразован в другой формат, в том числе CommonJS/NOde.js, AMD/RequireJS, UMD/UmdJS или SystemJS согласно настройкам транспиляции в tsconfig.json
:
{ 'compilerOptions': { 'module': 'ES2020' // None, CommonJS, AMD, System, UMD, ES6, ES2015, ESNext } }
Например:
// TypeScript и ES модуль
// с compilerOptions: { module: 'ES6' }. Транспилируем с сохранением синтаксиса
import dependencyModule from './dependencyModule'
dependencyModule.api()
let count = 0
export const increase = function() {
return ++count
}
// с compilerOptions: { module: 'CommonJS' }. Транспилируем в CommonJS/Node.js модуль
var __importDefault = (this && this.__importDefault) || function(mod) {
return (mod && mod.__esModule) ? mod : {
'default': mod
}
}
exports.__esModule = true
var dependencyModule_1 = __importDefault(require('./dependencyModule'))
dependencyModule_1['default'].api()
var count = 0
exports.increase = function() {
return ++count
}
// с compilerOptions: { module: 'AMD' }. Транспилируем в AMD/RequireJS модуль
var __importDefault = (this && this.__importDefault) || function(mod) {
return (mod && mod.__esModule) ? mod : {
'default': mod
}
}
define(['require', 'exports', './dependencyModule'], function(require, exports, dependencyModule_1) {
'use strict'
exports.__esModule = true
dependencyModule_1 = __importDefault(dependencyModule_1)
dependencyModule_1['default'].api()
var count = 0
exports.increase = function() {
return ++count
}
})
// с compilerOptions: { module: 'UMD' }. Транспилируем в UMD/UmdJS модуль
var __importDefault = (this & this.__importDefault) || function(mod) {
return (mod && mod.__esModule) ? mod : {
'default': mod
}
}
(function(factory) {
if (typeof module === 'object' && typeof module.exports === 'object') {
var v = factory(require, exports)
if (v !== undefined) module.exports = v
} else if (typeof define === 'function' && define.amd) {
define(['require', 'exports', './dependencyModule'], factory)
}
})(function(require, exports) {
'use strict'
exports.__esModule = true
var dependencyModule_1 = __importDefault(require('./dependencyModule'))
dependencyModule_1['default'].api()
var count = 0
exports.increase = function() {
return ++count
}
})
// с compilerOptions: { module: 'System' }. Транспилируем в System/SystemJS модуль
System.register(['./dependencyModule'], function(exports_1, context_1) {
'use strict'
var dependencyModule_1, count, increase
car __moduleName = context_1 && context_1.id
return {
setters: [
function(dependencyModule_1_1) {
dependencyModule_1 = dependencyModule_1_1
}
],
execute: function() {
dependencyModule_1['default'].api()
count = 0
exports_1('increase', increase = function() {
return ++count
})
}
}
})
Модульный синтаксис ES, поддерживаемый TypeScript, получил название внешних модулей.
Внутренние модули и пространство имен
TypeScript также имеет ключевые слова module
и namespace
. Они называются внутренними модулями:
module Counter {
let count = 0
export const increase = () => ++count
export const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
}
namespace Counter {
let count = 0
export const increase = () => ++count
export const reset = () => {
count = 0
console.log('Счетчик сброшен.')
}
}
Оба транспилируются в JS объекты:
var Counter(function(Counter) {
var count = 0
Counter.increase = function() {
return ++count
}
Counter.reset = function() => {
count = 0
console.log('Счетчик сброшен.')
}
})(Counter || (Counter = {}))
TypeScript module и namespace могут иметь несколько уровней вложенности через разделитель .
:
module Counter.Sub {
let count = 0
export const increase = () => ++count
}
namespace Counter.Sub {
let count = 0
export const increase = () => ++count
}
Sub module и sub namespace транспилируются в свойства объекта:
var Counter(function(Counter) {
var Sub(function(Sub) {
var count = 0
Sub.increase = function() {
return ++count
}
})(Sub = Counter.Sub || (Counter.Sub = {}))
})(Counter || (Counter = {}))
TypeScript module и namespace также могут использоваться в операторе export
:
module Counter {
let count = 0
export module Sub {
export const increase = () => ++count
}
}
module Counter {
let count = 0
export namespace Sub {
export const increase = () => ++count
}
}
Приведенный код также транспилируется в sub module и sub namespace:
var Counter
(function(Counter) {
var count = 0
var Sub
(function(Sub) {
Sub.increase = function() {
return ++count
}
})(Sub = Counter.Sub || (Counter.Sub = {}))
})(Counter || (Counter = {}))
Заключение
Добро пожаловать в JS, который имеет 10+ систем/форматов модуляции/пространства имен:
- IIFE модуль: шаблон JS модуля
- Открытый модуль: шаблон открытого JS модуля
- CJS модуль: CommonJS модуль или Node.js модуль
- AMD модуль: асинхронное определение модуля или RequireJS модуль
- UMD модуль: универсальное определение модуля или UmdJS модуль
- ES модуль: ECMAScript2015 или ES6 модуль
- ES динамический модуль: ECMAScript2020 или ES11 динамический модуль
- Системный модуль: SystemJS модуль
- Webpack модуль: транспиляция и сборка CJS, AMD и ES модулей
- Babel модуль: транспиляция ES модуля
- TypeScript модуль и пространство имен
К счастью, в настоящее время JS имеет стандартные встроенные инструменты для работы с модулями, поддерживаемые Node.js и всеми современными браузерами. Для старых браузеров можно использовать новый модульный синтаксис ES, транспилируя его в совместимый синтаксис с помощью Webpack/Babel/SystemJS/TypeScript.
Спасибо за потраченное время. Надеюсь, оно было потрачено не зря.