В этом посте рассматриваются четыре решения для написания модуля, который работает как в браузерах, так и в Node.js.
Синхронные и асинхронные модули
Node.js — синхронные модули. Node.js загружает свои модули синхронно. Следующий код представляет собой модуль, который использует функцию require () для импорта двух модулей module1 и module2. Экспортирует функциональную панель.
var module1 = require("./module1"); module1.foo(); var module2 = require("./module2"); export.bar = function() { module2.baz(); }
Когда module1 импортируется, выполнение JavaScript останавливается, файл загружается с диска и оценивается, а выполнение продолжается.
Браузеры — асинхронные модули. В браузерах все работает иначе. Загрузка может занять много времени, поэтому модули (скрипты) всегда загружаются асинхронно: вы даете команду на загрузку файла плюс обратный вызов, который используется, чтобы сообщить вам, когда файл был загружен. Следовательно, вы не можете загружать импорт где-либо в модуле, как в коде Node.js выше; вам нужно сначала загрузить их все, а затем запустить модуль за один раз. Для этой цели был разработан стандарт определения асинхронных модулей (AMD). Приведенный выше модуль Node.js выглядит как AMD:
define([ "./module1", "./module2" ], function (module1, module2) { module1.foo(); return { // export bar: function () { module2.baz(); } }; } );
Да, есть немного больше синтаксического шума, но необходимо заставить работать асинхронный подход. Для разработки хорошо иметь много маленьких модулей, потому что они обеспечивают структуру. Для развертывания необходимо иметь как можно меньше файлов из соображений производительности. Следовательно, инструмент AMD RequireJS поставляется с
оптимизатором, который позволяет вам скомпилировать несколько AMD в один минимизированный файл. Такой файл может быть загружен с помощью упрощенных драм модуля загрузчика
миндаля , который составляет всего 750 байт , когда уменьшенные и сжатые.
Обратите внимание, что приведенный выше пример демонстрирует основной синтаксис AMD. Полный стандарт AMD имеет больше возможностей.
Пересечение платформ. По своей сути, два формата модулей не сильно отличаются: укажите путь к файлу, присвойте результат оценки его переменной. В следующих разделах рассматриваются два подхода к использованию любого стандарта модулей на обеих платформах:
- Используйте шаблон для обеспечения совместимости.
- Преобразуйте «чистые» модули на лету или посредством компиляции.
Совместимость через шаблон
Вставив стандартный шаблон, вы можете определить, на какой платформе работает модуль, и адаптировать его при необходимости. Учитывая вызов функции, такой как define (…) выше, существует три основных шаблона для адаптации вызова так, чтобы он работал на обеих платформах. Во всех случаях предполагается, что в браузерах уже загружен скрипт, который настраивает глобальную функцию define (). Шаблоны можно различить по тому, как они предоставляют значение для define ():
- Через переменную.
var define; // does nothing if `define` has already been declared. if (typeof define === "undefined") { define = function (...) { ... } } define(...);
Эта модель основана на особенностях var хрупким образом. Например, он перестанет работать, если код обернут в функцию. Таким образом, этого следует избегать.
- Через параметр функции.
(function (define) { define(...); }(typeof define === "function" ? define : function (...) { ... }));
Подход. Оберните весь код в выражение для немедленного вызова функции, проверьте, существует ли define (), и, если нет, укажите для него значение.
- Через определение метода.
({ define: typeof define === "function" ? define : function (...) { ... } }). define(...);
Подход: превратите define () в вызов метода, добавив объект внутри подходящего метода. Преимущество: короче и только префикс (легче удалить, легче добавить с помощью копирования / вставки).
Все нижеприведенные примеры используют метод определения метода. Помимо размещения избыточного кода в каждом модуле, существенным недостатком стандартного кода является то, что он может помешать работе инструментов, которые объединяют модули в отдельные файлы для браузеров.
Boilerplate для браузера-включить модуль Node.js.
({ define: typeof define === "function" ? define : function(F) { F(require,exports,module) } }). define(function(require, exports, module) { // Node.js code goes here });
Подход: оберните код Node.js. На Node.js шаблон может довольно легко использовать требования, экспорт и модуль Node. В браузерах вы используете расширенную функцию AMD, где define () получает аргументы, подходящие для кода Node.js. Он выходит за рамки основного синтаксиса, представленного выше, но каждая полная реализация AMD (например, RequireJS [1]) поддерживает его. Такая реализация AMD будет анализировать код модуля для извлечения имен всех модулей, которые требуются внутри него, она будет предварительно загружать эти модули и затем передавать функцию require (), которая просто возвращает модули, которые уже находятся в оперативной памяти. Разбор возможен благодаря нестандартной функции большинства механизмов JavaScript: если вы вызываете метод toString () функции, вы получаете ее исходный код.
- Недостатки: шаблон, исходный код должен быть проанализирован в браузерах.
- Преимущество: работает с тегами скрипта.
Boilerplate для Node.js-включить AMD.
({ define: typeof define === "function" ? define : function(A,F) { module.exports = F.apply(null, A.map(require)) } }). define([ "./module1", "./module2" ], function (module1, module2) { return ... } );
Подход: Второй аргумент define () называется
телом модуля . В Node.js используйте require (), чтобы вычислить аргументы для тела, вызвать его и назначить результат для module.exports. В браузерах используйте совместимый с AMD загрузчик скриптов, такой как RequireJS [1].
- Недостаток: шаблон.
- Преимущество: нет разбора в браузерах, работает с тегами скрипта.
Чистые модули Node.js или AMD
Модули Node.js в браузерах. На Node.js вы можете использовать модули напрямую. В браузерах необходимо различать разработку и развертывание. Во время разработки вы можете использовать более простой, но медленный подход и загружать модули с помощью инструмента lobrow [2]. Для развертывания вы можете скомпилировать несколько модулей в один файл с помощью такого инструмента, как browserify [3] или webmake [4].
- Недостаток: lobrow необходимо проанализировать код и ограничен использованием XMLHttpRequest (по сравнению с тегами скрипта).
- Преимущество: нет шаблона.
AMD на Node.js. Инструмент node-amd-loader [5] изначально поддерживает AMD под Node.js. Тем не менее, вы должны импортировать его, чтобы активировать его. Очевидно, это хлопотно, если вам приходится выполнять импорт каждый раз, когда вы используете REPL Node в качестве интерактивной командной строки JavaScript. К счастью, есть хитрость [6], которая позволяет вам избежать этого — автоматически выполняя код при запуске REPL. В браузерах вы используете AMD-совместимый загрузчик скриптов.
- Недостаток: нужен адаптер на Node.js.
- Преимущество: можно использовать теги сценария, без шаблонов.
Структурирующие модули
Я в основном использую два способа структурирования своих модулей. Существует много других способов, некоторые из которых представляют собой смесь того, что показано ниже.
Отдельный экспорт.
- Node.js
function foo() { } // public function bar() { // private foo(); // call public function } // Exports are separate: exports.foo = foo;
- AMDs
function foo() { } // public function bar() { // private foo(); // call public function } // Exports are separate: return { foo: foo };
Недостаток: вы должны указать идентификатор в общей сложности три раза.
Действующий экспорт.
- Node.js
var e = exports; e.foo = function () { }; // public function bar() { // private e.foo(); // call public function }
- AMDs
var e = {}; e.foo = function () { }; // public function bar() { // private e.foo(); // call public function } return e;
Недостатки: Вы всегда должны ставить перед экспортированными идентификаторами префикс «e», как минимум менее эффективный, чем прямой доступ к экспортированным значениям.
В качестве альтернативы можно поместить экспортированные значения в литерал объекта, который изначально назначен для e:
var e = { foo: function () { } // public }; function bar() { // private e.foo(); // call public function }
Это позволяет избежать избыточного «е». при определении экспортируемого значения, но тогда вы не можете свободно смешивать частные и публичные идентификаторы.
Рекомендации
- RequireJS, загрузчик файлов и модулей JavaScript
- Загрузка модулей Node.js в браузеры через lobrow
- Browserify — заставьте в браузере работать в стиле узла require ()
- modules-webmake — Комплект модулей CommonJS для веб-браузера
- node-amd-loader, загрузчик AMD для Node.js
- Выполнять код каждый раз при запуске REPL Node.js