Статьи

Написание многоразового многоплатформенного JavaScript с компонентом

В настоящее время я занимаюсь портированием среды NoFlo Flow-Based Programming для работы в браузере. Хотя существуют некоторые очевидные различия в таких вещах, как взаимодействие с файловой системой и загрузка компонентов, цель здесь состоит в том, чтобы повторно использовать как можно больше одного и того же кода между этими двумя платформами.

Многие из строительных блоков уже на месте, и поэтому порт должен быть завершен еще на этой неделе. Вы можете отслеживать работу в выпуске 63 .

Фрагментированная экосистема

Текущая клиентская JavaScript-экосистема довольно фрагментирована. Хотя на общем уровне любой код может использоваться где угодно, существует много разных подходов к упаковке, загрузке кода и шаблонам. Во многом это похоже на ландшафт PHP до того, как Composer решил проблему там .

На Node.js у нас не было этой проблемы некоторое время, так как NPM предоставляет отличный способ поделиться и установить код. Имея более 27000 доступных модулей, это действительно решение по умолчанию для серверного управления пакетами JavaScript. Некоторые фреймворки, такие как Meteor, попытались отойти от этого, представив своих собственных менеджеров пакетов, но в итоге вернулись к своей сути .

Определения модулей

Существует много разных способов обработки загрузки и инкапсуляции кода JavaScript. Шаблон модуля является довольно популярным строительным блоком. Многие также представляют свой код в виде плагинов jQuery, хотя это действительно имеет смысл только при обработке DOM.

Вот как бы вы определили плагин jQuery:

(function ($) {
  $.fn.somePlugin = function () {
    // some code here
  };
})(jQuery);

Были предприняты некоторые усилия по стандартизации, включая спецификацию определения асинхронного модуля и спецификацию синхронного модуля CommonJS, которую также использует Node.js.

Вот как выглядит оболочка AMD для кода:

define('somePlugin', ['jquery'], function ($) {
  return function () {
    // some code here
  };
});

Определение CommonJS для чего-то подобного будет следующим. Это будет выглядеть знакомо разработчикам Node.js:

var $ = require('jquery');
exports.someFunction = function () {
  // some code here
});

Harmony — это следующее поколение языка JavaScript, которое включает новый синтаксис модуля :

module 'myModule' {
  import 'jquery' as $;
  export function someFunction () {
    // some code here
  }
}

Неудивительно, что многие разработчики чувствуют себя немного растерянными из-за того, как они должны представлять свой код или виджеты!

Спецификация модуля Harmony может в конечном итоге привести это в соответствие, но потребуется некоторое время, прежде чем вы действительно сможете отправлять подобный код даже для большинства разумных браузеров.

Установка и загрузка модуля

После того, как вы выбрали шаблон модуля, чтобы следовать, следующий вопрос заключается в том, как на самом деле установить ваши зависимости.

Традиционным способом является выборка «известных хороших» версий библиотек, добавление их в репозиторий вашего проекта, а затем просто добавление каждой библиотеки со своим собственным scriptтегом. Но это означает необходимость поддерживать все библиотеки в вашем собственном проекте, загромождать ваш репозиторий и вносить в них журнал изменений.

Вариант этого — загрузка общих библиотек из сети доставки контента, такой как Google . Преимущество этого заключается в том, что вашим пользователям не нужно загружать что-то вроде jQuery отдельно от каждого веб-сервера, и вам не нужно дублировать библиотечные файлы в вашем собственном хранилище кода. Но в то же время это зависит от того, что провайдер CDN не ломает вещи, и усложняет автономное использование.

И вам еще нужно написать scriptтег для каждого из них.

Беседка

Менеджер пакетов Twitter Bower призван помочь в управлении зависимостями. Вы объявляете библиотеки, в которых нуждается ваш код, в component.jsonфайле, запускаете Bower, и вы получаете правильные версии, загруженные в вашу систему.

Bower обрабатывает только разрешение и загрузку зависимостей, поэтому вы все равно будете писать scriptтеги для всех установленных модулей. Но по крайней мере это позволяет вам хранить файлы библиотеки вне вашего хранилища.

Require.js и volo

Проект Require.js пытается решить эту проблему путем автоматической загрузки модулей. Они даже предоставляют менеджер пакетов volo для установки всех необходимых вам библиотек.

Require.js — довольно популярный способ решения этой проблемы, но это означает, что вам нужно будет следовать спецификации асинхронного определения модуля с вашим кодом.

Составная часть

Компонент — более новое решение для этого, начатого TJ Holowaychuk Экспресса и славы Мокко .

Система объясняется намного лучше во вступительном посте в блоге , но в двух словах: Компонент — это комбинация менеджера пакетов и системы загрузки модулей на основе CommonJS.

С помощью Компонента вы можете легко писать и распространять повторно используемые модули JavaScript, включая компоненты пользовательского интерфейса, которые могут включать шаблоны HTML и CSS. Доступно руководство по написанию компонентов .

Установщик компонентов извлечет все зависимости для вас и создаст из них один, простой для включения файл JavaScript и ваш собственный код. Если вы хотите думать об этом таким образом, это похоже на то, как Composer генерирует файл автозагрузки для кода PHP .

Какой выбрать?

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

  • Компонент для клиентских библиотек и виджетов
  • NPM для библиотек Node.js

Если бы я просто писал код на стороне клиента, то Require.js и volo могли бы быть таким же хорошим вариантом, по крайней мере, если бы я хотел иметь дело с AMD.

Однако большое преимущество Component состоит в том, что он основан на модулях CommonJS, которые также использует Node.js. С его помощью я могу гораздо легче делиться библиотечным кодом между браузером и сервером, двумя основными платформами Universal Runtime .

Модули CommonJS прекрасно работают в браузере, на Node.js и в других средах выполнения JavaScript на стороне сервера .

Написание мультиплатформенной библиотеки

Написание виджетов с помощью Компонента хорошо описано в руководстве по созданию компонента выбора даты , и поэтому я сосредоточился на том, как создавать более универсальные библиотеки здесь.

Получение компонента

Первый шаг с Компонентом — установка инструмента на место. Компонент — как и большинство менеджеров JavaScript зависимостей — написано на вершине Node.js . Можно было бы реализовать спецификацию Компонента на других языках для более легкой интеграции в их собственный набор инструментов, но сейчас Node.js — это то, что вы должны установить.

После запуска Node.js получить инструменты Component очень просто:

$ sudo npm install -g component

Это даст вам componentкоманду. Вы можете запустить его, чтобы увидеть его функциональность.

Нахождение зависимостей

Следующий шаг — найти нужные вам библиотеки. Довольно много библиотек и виджетов уже доступны, и их можно найти в Component Wiki .

Вы также можете использовать команду Component для поиска модулей:

$ component search underscore

component/underscore
url: https://github.com/component/underscore
desc: JavaScript's functional programming helper library.

nathan7/memoize
url: https://github.com/nathan7/memoize
desc: underscore's memoize

Как вы можете видеть, компоненты используют «GitHub-like» схему именования <vendor>/<module>. Это снова похоже на имена поставщиков в Composer:

Имя пакета состоит из имени поставщика и имени проекта. Часто они будут идентичны — имя поставщика просто существует, чтобы предотвратить конфликт имен. Это позволяет двум разным людям создавать библиотеку с именем json, которая затем будет называться igorw / json и seldaek / json.

Поскольку NoFlo сильно зависит от API событий Node , нам нужно найти эквивалентную библиотеку для Component. После быстрого просмотра component search eventsвыясняется, что компонент / эмиттер выполняет свою работу.

Файл component.json

Каждый компонентный модуль должен предоставить component.jsonфайл, в котором вы объявляете такие вещи, как имя пакета, номер версии, лицензия на программное обеспечение, предоставленные файлы и возможные зависимости. Это очень похоже на файл package.json в NPM .

В качестве примера я использую очень упрощенную версию класса Graph NoFlo, чтобы я мог вызвать библиотеку bergie/graph. Как и большинство библиотек JavaScript, это будет под лицензией MIT .

{
  "name": "graph",
  "repo": "bergie/graph",
  "description": "Simple graph class",
  "license": "MIT",
  "version": "1.0.0",
  "scripts": [
    "graph.js"
  ],
  "dependencies": {
    "component/emitter": "*"
  }
}

Для поддержки Node.js вам также понадобится соответствующий package.jsonфайл:

{
  "name": "graph",
  "description": "Simple graph class",
  "main": "./graph.js",
  "version": "1.0.0"
}

Как только зависимости объявлены, запустите установку:

$ component install

В этом примере используются только встроенные библиотеки Node.js, поэтому установка NPM пока не требуется. Если вы добавляете сторонние библиотеки, вам также необходимо установить их:

$ npm install

Код модуля

Написание модуля Component очень похоже на написание модулей Node.js. Создайте файл, который мы только что объявили в файле JSON, и откройте его в своем любимом редакторе.

Так как мы стремимся к многоплатформенному коду, основное отличие заключается в том, что касается различий между платформами Например, вызывается библиотека генератора событий в Node.js events, а компонентный эквивалент вызывается. К emitterсчастью, их API-интерфейсы абсолютно одинаковы, поэтому мы должны выполнять загрузку только условно:

var EventEmitter;
if (typeof process === 'object' && process.title === 'node') {
  // Node.js
  EventEmitter = require('events').EventEmitter;
} else {
  // Browser
  EventEmitter = require('emitter');
}

Таким образом, у нас есть правильная реализация генератора событий, доступная для нашего кода. Теперь мы просто создаем функцию-конструктор для наследования от нее:

// The constructor, just call "super"
function Graph () {
  this.nodes = [];
  EventEmitter.call(this);
}

// Set up inheritance
Graph.prototype = Object.create(EventEmitter.prototype);

// Define methods
Graph.prototype.addNode = function (name) {
  this.nodes.push(name);
  this.emit('node', name);
};

Как только код появится, нам нужно представить его как модуль CommonJS:

module.exports = Graph;

Запуск модуля в Node.js

Для Node это все, что нам нужно сделать, чтобы использовать наш График в качестве модуля:

// Include the module
var Graph = require('./graph');

// Instantiate
var g = new Graph();

// Hook into events
g.on('node', function (name) {
  console.log("Node added " + name);
});

// Call a method
g.addNode('Foo');

Выполнение этого должно закончиться сообщением, Node added Fooпоказанным на консоли.

Запуск модуля в браузере

Чтобы иметь возможность запустить модуль в браузере, нам нужно запустить процесс сборки компонента.

$ component build

Это создаст файл JavaScript, build/build.jsкоторый обеспечивает поддержку загрузки модуля CommonJS, и весь код JavaScript, который мы объявили в файле JSON.

Теперь вы можете включить этот файл в ваш HTML и начать использовать модуль Graph:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script src="./build/build.js"></script>
    <script>
      var Graph = require('graph/graph.js');
      var g = new Graph();
      g.on('node', function (name) {
        alert("Node added " + name);
      });
      g.addNode('foo');
    </script>
  </head>
  <body>
  </body>
</html>

Вывод

Компонент может быть использован для решения проблемы совместного использования кода JavaScript. Они позволяют создавать полноценные приложения из небольших, многократно используемых модулей. А благодаря спецификации модуля CommonJS эти модули довольно легко записать, что позволяет использовать их также в Node.js и других средах выполнения JavaScript.

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

Даже при наличии более 800 доступных компонентов слишком рано объявлять Component победителем во внешнем пространстве управления зависимостями, но это хорошо продуманная система, которая работает достаточно хорошо.

Я буду использовать Компонент для некоторых из моих проектов JavaScript в будущем.