Cjs что это
Перейти к содержимому

Cjs что это

  • автор:

.CJS — Расширение файла

Файл CJS содержит CommonJS -код, который предназначен для использования JavaScript в средах за пределами веб -браузера Frontend. Он хранит код в синтаксисе CommonJS, который может быть открыт и изменен с помощью любого текстового редактора.

CommonJS — это проект, который был первоначально запущен в 2009 году Кевином Дангором, инженером Mozilla, для расширения использования JavaScript. Тем не менее, среда выполнения node.js JavaScript сделала обыкновенными устаревшими, поскольку она стала предпочтительным методом запуска JavaScript на бэкэнд-сервере веб-сайта.

CommonJS Code File — Открыватели файлов

Windows
  1. Microsoft Visual Studio Code
  2. Adobe Dreamweaver 2020
  3. Microsoft Notepad
  4. text editor

Модули — amd, cjs, esm и umd

И еще немного про модули. Какие они были, к чему все пришло.

И так, начнём мы с AMD (Asynchronous module definition), это первые трушные модули на фронте с которыми мне пришлось поработать. Наверняка среди вас есть такие же динозавры как и я, которые помнят всю боль и кучу костылей RequireJS . RequireJS (https://requirejs.org/docs/whyamd.html) — это такая древняя JS либа, реализующая AMD спеку. Она появилась за долго до бандлеров(webpack, rollup, etc) и позвала менеджить модули и их зависимости, а также загружать все это дело в рантайме по требованию, да-да код сплитинг в 2012 годах. Но как я и сказал ранее с RequireJS было много боли, в основном с зависимостями. Т.к. способ их определения — тупо массив строк с названиями модулей:

define('myModuleName', ['dep1', 'dep2'], function (dep1, dep2) return function () // my module code is here 
>;
>);

На небольших проектах это вполне работало и не доставляло больших проблем. Но с ростом проекта массивы зависимостей становились ОГРОМНЫЕ, и это еще хорошо если в компании были прописаны код-стайл гайды и их соблюдали. Т.к. в 2014 году про линтеры никто ничего особо не знал, ну либо меня это обошло стороной �� . И как вы уже поняли, у нас стандартов не было, была только большая боль, и если над файлом работало параллельно несколько человек, появлялись конфликты в определении зависимостей, которые было достаточно сложно резолвать(поиск в 2ух массивах по 30+ зависимостей дифов, и это только для одного файла, доходило до того, что мержили по несколько дней, когда сливали два больших фича-бранча). Код выглядел как-то так:

define('myModuleName', [ "require", "jquery", "blade/object", "blade/fn", "rdapi", "oauth", "blade/jig", "blade/url", "dispatch", "accounts", "storage", "services", "widgets/AccountPanel", "widgets/TabButton", "widgets/AddAccount", "less", "osTheme", "jquery-ui-1.8.7.min", "jquery.textOverflow"],
function (require, $, object, fn, rdapi, oauth, jig, url, dispatch, accounts, storage, services, AccountPanel, TabButton, AddAccount, less osTheme) // code is here
>);

Да, у RequireJS был сахар-синтаксис который позволял подрубать зависимости через require метод, но он медленнее работал, был забагованный и мы его не юзали. В общем на больших проектах мы, скорее всего и не только мы, очень страдали с RequireJS . Страдали до тех пор пока не появились бандлеры, которые могли нормально работать с CJS модулями.

CJS (CommonJS) — стандартные модули в NodeJS из спокон веков. Про них написано кучу статей, да и в доке ноды все можно найти. Единственное про что хочу рассказать, это динамичность этих модулей, с помощью которой можно делать разного рода хаки:

// reexportModule.js
var MyModule = require('./myModule.js');
// default export
module.exports = function doSomething(n) const myModuleInstance = new MyModule(n);
module.exports = myModuleInstance
>
// named export
exports.doSomething = function doSomething(n) const myModuleInstance = new MyModule(n);
exports.doSomething = myModuleInstance
>

В примере выше, в теле функций мы переопределяем экспортируемое значение. Да, оба примера — рабочий код, экспорты перепишутся после вызова функций. Разница лишь в том, что дефолтный экспорт переопределится только для тех модулей, которые подключаются ПОСЛЕ вызова reexportModule функции, а именованный экспорт переопределится для всех модулей не важно в каком месте они подключены. Так работает потому, что импорт дефолтного экспорта, это как одной переменной присвоить значение другой var reexportModule = reexportModuleJS.exports . После переопределения module.exports в reexportModule , значение reexportModuleJS.exports меняется, но в модулях которые используют/подключили reexportModule.js до вызова вызова функции ссылка в var reexportModule останется старой, т.е. там будет функция которая переопределяет модуль еще раз. В свою очередь именованные модули работают по ссылке. Т.е. в reexportModuleJS.exports уже лежит не наша функция, а объект с функцией. И при изменение значения в этом объекте ссылка на reexportModuleJS.exports не меняется что поваляет другим модулям получить последнее значение экспорта(только если в этих модулях не используется диструкторизация или иной способ отвязки от ссылки объекта). Это ОЧЕНЬ грязные хаки и не рекомендую юзать в проде, ибо потом хрен разгребете че оно перестало работать ��.

Пару слов о UMD (Universal Module Definition) — по сути своей это не модули, это паттерн для использования AMD и CJS в зависимости от энва. Стал популярен с приходом бандлеров, т.к. разные пакеты использовали разные системы модулей, а бандлерам нужно было все это дело как-то упаковывать.

(function (root, factory) // если AMD, использовать define что бы определить модуль и подрубить зависимости 
if (typeof define === "function" && define.amd) define(["jquery", "underscore"], factory);
// использовать require для exports если CJS
> else if (typeof exports === "object") module.exports = factory(require("jquery"), require("underscore"));
// иначе записать в переменную root, в браузерах сюда обыно передавалось window
> else root.Requester = factory(root.$, root._);
>
>(this, function ($, _) // this is where I defined my module implementation
var Requester = < // . >; return Requester;
>));

Ну и на подлесок ESM (EcmaScript Module) — самый свежий и единственный (CJS — был стандартом в ноде, но не в самом JS) стандарт модулей в JS. Кардинально отличается от CJS тем — что модули статичны. Т.е. зависимости и модули определяются до того как ранается код, поэтому в ESM на уровне синтаксиса нельзя переопределять значение экспорта (я имею ввиду нельзя поменять ссылку на экспорт, но если экспортится объект, его менять конечно же можно), import _, <> from » и export const | export default могут быть только на верхнем уровне файла . Благодаря статичным модулям, бандлеры могут без проблем делать Tree Shaking(удалять неиспользованные экспорты). Например в нашем модуле экспортится две функции a и b , функция a используется в других модулях, а вот функция b нет, как и не используется в самом модуле, это значит что функция b нигде не используется и может быть удалена при билде. С CJS это сделать достаточно сложно, т.к. экспорт модуля может поменяться в рантайме. Из за этих различий в ноде нельзя использовать одновременно 2 системы модулей, т.к. для них используются разные загрузчики, бандлерам типа вебпака пофигу, т.к. они умеют хавать все .

А и еще одна забавная штука про ESM — это стандарт только JS модулей, он не определяет как эти модули должны грузится в ваш браузер, т.к. загрузка кода это уже не JS, насколько я помню за это отвечает какой-то HTML стандарт, из-за этого долгое время после релиза ESM их не могли завести в браузер т.к. нужен был еще один стандарт. Да, современный веб без стандартов — и не веб в общем-то ��

Понимание (всех) «модульных» форматов и инструментов JavaScript

Представляю вашему вниманию перевод статьи «Understanding (all) JavaScript module formats and tools» автора Dixin.

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

  • 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 модуль: транспиляция CJS, AMD, ES и SystemJS модулей

IIFE модуль: шаблон JS модуля

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

 // определяем глобальные переменные let count = 0 const increase = () => ++count const reset = () => < count = 0 console.log('Счетчик сброшен.') >// используем глобальные переменные increase() reset() 

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

Вуаля, глобальных переменных больше нет. Однако код внутри функции не выполняется.

IIFE: немедленно вызываемое функциональное выражение

Для того, чтобы выполнить код внутри функции f , ее необходимо вызвать с помощью () как f() . Для выполнения кода внутри анонимной функции (() => <>) следует также использовать () . Это выглядит так (() => <>)() :

Это называется 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 =

Вот как этот модуль используется:

 // используем 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

Для использования модульного файла в браузере необходимо добавить тег и определить его как модуль: . Для использования этого модуля в 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

Этот код не будет работать в браузерах, не поддерживающих синтаксис 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

Следующий код демонстрирует использование этого модуля:

 // использование 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'] =

А вот код в 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 :

Теперь 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 :

 // 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 модуль и пространство имен

Спасибо за потраченное время. Надеюсь, оно было потрачено не зря.

  • javascript
  • программирование
  • разработка
  • Веб-разработка
  • JavaScript
  • Программирование

Модули в ECMAScript 6

Несмотря на то, что JavaScript не поддерживает модули на уровне языка, сообществом были созданы впечатляющие решения для их реализации. Два наиболее популярных (но, к сожалению, несовместимых) стандарта:

  • CommonJS (CJS): главное воплощение этого стандарта — модульная система Node.js (в Node.js есть несколько фич, выходящих за рамки CJS). Характеристики:

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

  • Asynchronous Module Definition (AMD): наиболее популярной реализацией этого стандарта стал RequireJS. Характеристики:

— синтаксис немного сложнее, что позволяет AMD работать без eval() или этапа компиляции; — предназначен для асинхронной загрузки; — преимущественно используется на стороне клиента. Все это упрощенное объяснение текущего положения вещей. Если вы хотите углубится в эту тему, прочтите «Написание модульного JavaScript с помощью AMD, CommonJS и ES Harmony» Эдди Османи (Addy Osmani).

Модули в ECMAScript 6

  • на этапе компиляции вы получите ошибки, если попытаетесь импортировать что-то, что не было предварительно экспортировано;
  • вы можете легко осуществить асинхронную загрузку модулей ES6.
  • декларативный синтаксис (для импорта и экспорта);
  • программной загрузки (чтобы задать конфигурацию загрузки модулей и для условной загрузки модулей).

Синтаксис модуля ECMAScript 6

Модули ECMAScript 6 очень похожи на модули Node.js. Модуль — это просто файл с JavaScript кодом внутри. Для примера рассмотрим проект, файлы которого находятся в папке calculator/.

calculator/ lib/ calc.js main.js

Экспорт

Ключевое слово export, стоящее перед объявлением переменной (посредством var, let, const), функции или класса экспортирует их значение в остальные части программы. В нашем примере calculator/lib/calc.js содержит следующий код:

// calculator/lib/calc.js let notExported = 'abc'; export function square(x) < return x * x; > export const MY_CONSTANT = 123;

Этот модуль экспортирует функцию square и значение MY_CONSTANT.

Импорт

main.js, другой модуль, импортирует square с calc.js:

// calculator/main.js import < square >from 'lib/calc'; console.log(square(3));

main.js ссылается на calc.js посредством идентификатора модуля — строки «lib/calc». По умолчанию интерпретацией идентификатора модуля является относительный путь к импортируемому модулю. Обратите внимание, что, при необходимости, вы можете импортировать несколько значений:

// calculator/main.js import < square, MY_CONSTANT >from 'lib/calc';

Вы также можете импортировать модуль как объект, значениями свойств которого будут экспортированные значения:

// calculator/main.js import 'lib/calc' as c; console.log(c.square(3));

Если вам неудобно использовать имена, определенные в экспортируемом модуле, вы можете переименовать их при импорте:

// calculator/main.js import < square as squ > from 'lib/calc'; console.log(squ(3));

Экспорт по умолчанию

Иногда модуль экспортирует только одно значение (большой класс, например). В таком случае удобно определить это значение как экспортируемое по умолчанию:

// myapp/models/Customer.js export default class < // анонимный класс constructor(id, name) < this.id = id; this.name = name; > >;

Синтаксис импорта таких значений аналогичный обычному импорту без фигурных скобок (для простоты запоминания: вы не импортируете что-либо с модуля, а импортируете сам модуль):

// myapp/myapp.js import Customer from 'models/Customer'; let c = new Customer(0, 'Jane');

Встроенные модули

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

  

В ECMAScript 6 вы можете использовать анонимный внутренний модуль:

  

Кроме того, что такая конструкция проще с точки зрения синтаксиса, ее содержание автоматически отображается в strict mode 3. Обратите внимание, что не обязательно осуществлять импорт внутри модуля. Инструкция import может использоваться в контексте обычного скрипта.

Альтернатива встроенному экспорту

Если не хотите вставлять export-ы в код, то можно все экспортировать позже, например, в конце:

let notExported = 'abc'; function square(x) < return x * x; > const MY_CONSTANT = 123; export < square, MY_CONSTANT >;

Также можно переименовывать значения во время экспорта:

export < square as squ, MY_CONSTANT as SOME_CONSTANT >;

Осуществление реэкспорта

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

export < encrypt as en > from 'lib/crypto';

Также вы можете реэкспортировать все сразу:

export * from 'lib/crypto';

API загрузки модулей ECMAScript 6

В дополнение к декларативному синтаксису для работы с модулями, в стандарте также присутствует программный API. Он позволяет делать две вещи: программно работать с модулями и скриптами и настраивать загрузку модулей.

Импорт модулей и загрузка скриптов

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

System.import( ['module1', 'module2'], function (module1, module2) < // успешное выполнение … >, function (err) < // ошибка … >);

Среди прочего, это делает возможной условную загрузку модулей. System.load() работает аналогично с System.import(), но загружает файлы скриптов вместо импорта модулей.

Настройка загрузки модулей

  • настройка отображения идентификатора модуля;
  • проверка валидности модуля при импорте (к примеру, посредством KSLint или JSHint);
  • автоматическая трансляция модулей при импорте (они могут содержать код CoffeeScript или TypeScript);
  • использовать существующие модули (AMD, Node.js).

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

Еще больше полезной информации смотрите у нас в сообществе.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *