В Node.js есть очень хорошая модульная система, которая проста для понимания, но различает экспорт модуля и вещи, которые должны быть частными для него. В этом посте объясняется, как можно изменить код модуля Node.js, чтобы он работал как в Node.js, так и в веб-браузерах. Также объясняется, как выполнить юнит-тестирование такого кода.
1. Написание универсального модуля
1.1. Модули Node.js
Модуль Node.js выглядит следующим образом:
var privateVariable = 123; function privateFunction() { } exports.publicVariable = 345; exports.publicFunction = function () { return privateVariable * exports.publicVariable; }
Вы добавляете свои общедоступные данные в экспорт объектов, которым управляет Node.js. Кроме того, Node.js гарантирует, что все остальные данные остаются конфиденциальными для вашего модуля. Внутри модуля вы обращаетесь непосредственно к частным данным, к открытым данным через экспорт. Ниже показано, как используется такой модуль.
1.2. Делаем код Node.js универсальным
Вы можете использовать код модуля Node.js в браузерах, если обернуть его следующим образом:
"use strict"; (function(exports) { // Your Node.js code goes here }(typeof exports === "undefined" ? (this.moduleName = {}) : exports));
Давайте рассмотрим код оболочки:
- Включение строгого режима: первая строка включает строгий режим [1] в ECMAScript 5 и ничего не делает в старых версиях JavaScript.
- Немедленно вызванное определение функции (IIFE): мы определяем функцию и немедленно ее вызываем [2]. Это служит двум целям:
- Неэкспортированные данные остаются закрытыми в браузерах, где Node.js не делает этого за нас.
- Он условно создает переменную export, которая существует в Node.js, но должна быть создана в браузере. Создание новой области с новой переменной экспорта является единственным способом сделать это, вы не можете добавить переменную в существующую область, если ее еще нет. То есть вы не можете делать следующее:
if (typeof exports !== "undefined") { var exports = {}; // (*) }
Причина: объявление (но не присвоение!) В (*) переносится в начало текущей функции, что означает, что экспорт всегда будет неопределенным при выполнении проверки. Обратите внимание, что это будет работать без подъёма, но не с масштабированием блока, потому что тогда экспорт, объявленный в (*), будет существовать только внутри блока then.
- typeof exports === «undefined»: существует ли переменный экспорт?
- this.moduleName = {}: На глобальном уровне это относится к глобальному объекту. Следовательно, это назначение создает глобальную переменную moduleName (имя модуля в браузере).
Бегущий пример:
"use strict"; (function(exports) { exports.StringSet = function () { this.data = {}; } exports.StringSet.prototype.add = function(elem) { if (typeof elem !== "string") { throw new TypeError("Argument is not a string: "+elem); } this.data[elem] = true; } exports.StringSet.prototype.contains = function(elem) { // Comparison ensures boolean result return this.data[elem] === true; } exports.StringSet.prototype.copy = function() { var result = new exports.StringSet(); Object.keys(this.data).forEach(function(elem) { result.add(elem); }); return result; } }(typeof exports === "undefined" ? (this.strset = {}) : exports));
2. Использование универсального модуля
Узел:
var strset = require("./strset"); var s = new strset.StringSet();
Браузер:
<script src="strset.js"></script> <script> var s = new strset.StringSet(); </script>
3. Юнит-тестирование универсального модуля
3.1. Node.js: юнит-тестирование через модуль assert
Для модульного тестирования мы используем встроенные средства Node.js — модуль
assert .
var assert = require('assert'); var strset = require('./strset'); // contains (function() { var sset = new strset.StringSet().add("a"); assert.ok(sset.contains("a")); assert.ok(!sset.contains("b")); }()); // copy (function() { var copy = new strset.StringSet().add("a").copy(); assert.ok(copy.contains("a")); assert.ok(!copy.contains("b")); }()); // add - illegal arguments (function() { assert.throws(function() { new strset.StringSet().add(3); }); }());
Эти тесты выполняются путем сохранения их в файле strset-node-test.js и выполнения его через командную строку:
> node strset-node-test.js
Если один из тестов не пройден, будет исключение. Если все тесты пройдены успешно, ничего не происходит.
3.2. Браузер: модульное тестирование с помощью jQuery’s QUnit
В браузере у нас (слишком) много вариантов: большинство фреймворков имеют собственную поддержку модульного тестирования. Давайте использовать JQuery’s
QUnit .
<!doctype html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>strset-test</title> <script src="http://code.jquery.com/jquery-latest.js"></script> <link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" type="text/css" media="screen"> <script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script> <script type="text/javascript" src="strset.js"></script> <script> $(document).ready(function(){ test("contains", function() { var sset = new strset.StringSet().add("a"); ok(sset.contains("a")); ok(!sset.contains("b")); }); test("copy", function() { var copy = new strset.StringSet().add("a").copy(); ok(copy.contains("a")); ok(!copy.contains("b")); }); test("add - illegal arguments", function() { raises(function() { new strset.StringSet().add(3); }); }); }); </script> </head> <body> <h1 id="qunit-header">strset-test</h1> <h2 id="qunit-banner"></h2> <div id="qunit-testrunner-toolbar"></div> <h2 id="qunit-userAgent"></h2> <ol id="qunit-tests"></ol> <div id="qunit-fixture">test markup, will be hidden</div> </body> </html>
Вы выполняете эти тесты, сохраняя их в файле .html и открывая в браузере.
4. Связанное чтение
Связанное чтение
- Строгий режим JavaScript: резюме
- Область видимости переменной JavaScript и ее подводные камни
- Модули и пространства имен в JavaScript
- шаблон для модуля нацеливания на браузер и nodejs . Вдохновение для вышеупомянутого образца. Делает то же самое, но в обратном порядке — используется одна и та же переменная для экспорта общедоступных данных как в Node.js, так и в браузерах. В Node.js эта переменная указывает на объект экспорта, в браузерах — на объект в глобальной переменной.