Получение closure-compiler и Node.js для хорошей игры - PullRequest
26 голосов
/ 27 ноября 2011

Существуют ли проекты, которые использовали node.js и closure-compiler (CC для краткости) вместе?

Официальная рекомендация CC состоит в том, чтобы собрать весь код для приложения вместе, но когда я компилирую какой-то простой узел.js-код, содержащий require("./MyLib.js"), эта строка помещается непосредственно в вывод, но в этом контексте это не имеет никакого смысла.

Я вижу несколько вариантов:

  1. Кодировать все приложение в один файл.Это решает проблему, избегая ее, но плохо для обслуживания.
  2. Предположим, что все файлы будут объединены перед выполнением.Опять же, это позволяет избежать проблемы, но усложняет реализацию не скомпилированного режима отладки.
  3. Я бы хотел, чтобы CC "понял" функцию node.js require (), но это, вероятно, может 'не может быть сделано без редактирования самого компилятора, не так ли?

Ответы [ 5 ]

50 голосов
/ 02 декабря 2011

Я использовал Closure Compiler с Node для проекта, который я еще не выпустил. Это заняло немного времени, но помогло поймать много ошибок и имеет довольно короткий цикл edit-restart-test.

Во-первых, я использую plovr (это проект, который я создал и поддерживаю), чтобы совместно использовать Closure Compiler, Library и Templates. Я пишу свой код Node в стиле библиотеки Closure, поэтому каждый файл определяет свой собственный класс или набор утилит (например, goog.array).

Следующим шагом является создание группы внешних файлов для функций Node, которые вы хотите использовать. Я опубликовал некоторые из них публично по адресу:

https://github.com/bolinfest/node-google-closure-latitude-experiment/tree/master/externs/node/v0.4.8

Хотя, в конечном счете, я думаю, что это должно быть в большей степени обусловлено интересами сообщества, поскольку существует множество функций для документирования. (Это также раздражает, потому что некоторые функции Node имеют необязательные средние аргументы, а не последние аргументы, что усложняет аннотации типов.) Я сам не начал это движение, потому что вполне возможно, что мы могли бы поработать с Closure Complier, чтобы сделать это менее неудобным (см. ниже).

Допустим, вы создали файл externs для пространства имен Node http. В моей системе я решил, что когда мне понадобится http, я включу его через:

var http = require('http');

Хотя я не включаю этот require() вызов в мой код. Вместо этого я использую функцию output-wrapper в Closure Compiler для добавления всех require() s в начале файла, что при объявлении в plovr в моем текущем проекте выглядит следующим образом:

"output-wrapper": [
  // Because the server code depends on goog.net.Cookies, which references the
  // global variable "document" when instantiating goog.net.cookies, we must
  // supply a dummy global object for document.
  "var document = {};\n",

  "var bee = require('beeline');\n",
  "var crypto = require('crypto');\n",
  "var fs = require('fs');\n",
  "var http = require('http');\n",
  "var https = require('https');\n",
  "var mongodb = require('mongodb');\n",
  "var nodePath = require('path');\n",
  "var nodeUrl = require('url');\n",
  "var querystring = require('querystring');\n",
  "var SocketIo = require('socket.io');\n",
  "%output%"
],

Таким образом, мой библиотечный код никогда не вызывает Node's require(), но компилятор допускает использование таких вещей, как http в моем коде, потому что компилятор распознает их как внешние. Поскольку они не являются истинными внешними, их необходимо добавлять в начале, как я описал.

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

goog.scope(function() {

    /** @type {~NodeHttpNamesapce} */
    var http = require('http');

    // Use http throughout.

});

В этом сценарии файл externs будет определять NodeHttpNamespace таким образом, чтобы компилятор Closure смог проверить его свойства с помощью файла externs. Разница здесь в том, что вы можете назвать возвращаемое значение require() как хотите, потому что тип http будет этим специальным типом пространства имен. (Идентификация "пространства имен jQuery" для $ является аналогичной проблемой.) Этот подход устраняет необходимость согласованного именования локальных переменных для пространств имен Node и устраняет необходимость в этом гиганте output-wrapper в конфигурации plovr.

Но это было отступление ... как только у меня все настроено, как описано выше, у меня есть скрипт оболочки, который:

  1. Использует plovr для сборки всего в режиме RAW.
  2. Запускает node для файла, сгенерированного plovr.

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

6 голосов
/ 02 января 2012

Компилятор SVN HEAD закрывающего модуля, похоже, поддерживает для AMD

3 голосов
/ 14 января 2013

Я заменил свой старый подход более простым подходом:

Новый подход

  • Нет вызовов require () для собственного кода приложения, только для узлов Node
  • Мне нужно объединить код сервера в один файл, прежде чем я смогу запустить или скомпилировать его
  • Конкатенация и компиляция выполняются с использованием простого скрипта grunt

Забавно, что мне даже не пришлось добавлять экстерн для вызовов require(). Компилятор Google Closure понимает это автоматически. Мне нужно было добавить externs для модулей nodejs, которые я использую.

Старый подход

В соответствии с просьбой OP, я подробно остановлюсь на способе компиляции кода node.js с помощью Google Closure Compiler.

Я был вдохновлен тем, как bolinfest решил проблему, и мое решение использует тот же принцип. Разница в том, что я создал один скрипт node.js, который делает все, включая встроенные модули (решение bolinfest позволяет GCC позаботиться об этом). Это делает его более автоматизированным, но и более хрупким.

Я просто добавил комментарии к коду для каждого шага, который я предпринимаю для компиляции кода сервера. Смотрите этот коммит: https://github.com/blaise-io/xssnake/commit/da52219567b3941f13b8d94e36f743b0cbef44a3

Подведем итог:

  1. Я начинаю с моего основного модуля, файла JS, который я передаю Node, когда хочу его запустить.
    В моем случае это файл start.js .
  2. В этом файле, используя регулярное выражение, я обнаруживаю все require() вызовы, включая часть назначения.
    В start.js это соответствует одному требованию: var Server = require('./lib/server.js');
  3. Я извлекаю путь, по которому существует файл, на основе имени файла, извлекаю его содержимое в виде строки и удаляю присвоения module.exports из содержимого.
  4. Затем я заменяю требование require из шага 2 содержимым из шага 3. Если это не модуль core node.js, то я добавляю его в список основных модулей, которые я сохраню для дальнейшего использования.
  5. Шаг 3, вероятно, будет содержать больше вызовов require(), поэтому я повторяю шаги 3 и 4 до тех пор, пока все вызовы require() не исчезнут, и у меня останется одна огромная строка, содержащая весь код.
  6. Если вся рекурсия завершена, я компилирую код, используя REST API.
    Вы также можете использовать автономный компилятор.
    У меня есть externs для каждого основного модуля node.js. Этот инструмент полезен для генерации экстернов .
  7. Я предварительно представляю удаленный модуль core.js require для вызова скомпилированного кода.

Предварительно скомпилированный код.
Все require звонки удалены. Весь мой код сглажен.
http://pastebin.com/eC2rVMiN

Посткомпилированный код.
Ядро Node.js require было добавлено вручную.
http://pastebin.com/uB8CaejN


Почему бы вам не сделать это так:

  1. Он использует регулярные выражения (не анализатор или токенизатор) для обнаружения require вызовов, вставки и удаления module.exports. Это хрупко, поскольку не охватывает все варианты синтаксиса.
  2. При встраивании весь код модуля добавляется в глобальное пространство имен. Это противоречит принципам Node.js, где каждый файл имеет свое собственное пространство имен, и это приведет к ошибкам, если у вас два разных модуля с одинаковыми глобальными переменными.
  3. Это не сильно увеличивает скорость вашего кода, поскольку V8 также выполняет много оптимизаций кода, таких как встраивание и удаление мертвого кода.

Почему вы должны:

  1. Потому что он работает, когда у вас есть согласованный код.
  2. Он обнаружит ошибки в коде вашего сервера при включении подробных предупреждений.

2 голосов
/ 17 ноября 2013

Закрытие библиотеки на Node.js за 60 секунд.

Поддерживается, отметьте https://code.google.com/p/closure-library/wiki/NodeJS.

0 голосов
/ 27 ноября 2011

Вариант 4. Не используйте компилятор замыкания.

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

Нет ничего хорошего в минимизации.

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

И, конечно же, стоит заплатить, отладка скомпилированного JavaScript - это кошмар

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...