Получение closure-compiler и Node.js для хорошей игры
Существуют ли проекты, которые использовали node.js и closure-compiler (для краткости CC)?
Официальная рекомендация CC состоит в том, чтобы собрать весь код для приложения вместе, но когда я скомпилирую некоторый простой код node.js, который содержит require("./MyLib.js")
эта строка помещается непосредственно в вывод, но это не имеет никакого смысла в этом контексте.
Я вижу несколько вариантов:
- Кодировать все приложение в одном файле. Это решает проблему, избегая ее, но плохо для обслуживания.
- Предположим, что все файлы будут объединены перед выполнением. Опять же, это позволяет избежать проблемы, но усложняет реализацию не скомпилированного режима отладки.
- Мне бы хотелось, чтобы CC "понял" функцию node.js require(), но это, вероятно, невозможно сделать без редактирования самого компилятора, не так ли?
5 ответов
Я использовал Closure Compiler с Node для проекта, который я еще не выпустил. Это заняло немного времени, но помогло поймать много ошибок и имеет довольно короткий цикл edit-restart-test.
Во-первых, я использую plovr (проект, который я создал и поддерживаю) для совместного использования компилятора, библиотеки и шаблонов Closure. Я пишу свой код 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
в конфиге пловра.
Но это было отступление... как только у меня все настроено, как описано выше, у меня есть скрипт оболочки, который:
- Использует пловр, чтобы построить все в
RAW
Режим. - Запускается
node
на файл, сгенерированный plovr.
С помощью RAW
Режим приводит к большой конкатенации всех файлов (хотя он также заботится о переводе шаблонов сои и даже CoffeeScript в JavaScript). По общему признанию, это делает отладку болезненной, потому что номера строк бессмысленны, но до сих пор работали достаточно хорошо для меня. Все проверки, выполненные компилятором Closure, оправдывают себя.
Закрытие библиотеки на Node.js за 60 секунд.
Поддерживается, проверьте https://code.google.com/p/closure-library/wiki/NodeJS.
Я заменил свой старый подход более простым подходом:
Новый подход
- Не требует () вызовов для моего собственного кода приложения, только для узлов Node
- Мне нужно объединить код сервера в один файл, прежде чем я смогу запустить или скомпилировать его
- Конкатенация и компиляция выполняются с использованием простого скрипта
Самое смешное, что мне даже не пришлось добавлять экстерьер для require()
звонки. Компилятор Google Closure понимает это автоматически. Мне пришлось добавить externs для модулей nodejs, которые я использую.
Старый подход
В соответствии с просьбой OP, я подробно остановлюсь на способе компиляции кода node.js с помощью компилятора Google Closure.
Я был вдохновлен тем, как Bolinfest решил проблему, и мое решение использует тот же принцип. Разница в том, что я создал один скрипт node.js, который делает все, включая встроенные модули (решение bolinfest позволяет GCC позаботиться об этом). Это делает его более автоматизированным, но и более хрупким.
Я просто добавил комментарии к каждому шагу, чтобы скомпилировать код сервера. Смотрите этот коммит: https://github.com/blaise-io/xssnake/commit/da52219567b3941f13b8d94e36f743b0cbef44a3
Подвести итоги:
- Я начинаю с моего основного модуля, файла JS, который я передаю Node, когда хочу его запустить.
В моем случае это файл start.js. - В этом файле, используя регулярное выражение, я обнаруживаю все
require()
звонки, включая часть задания.
В start.js это соответствует одному требованию:var Server = require('./lib/server.js');
- Я извлекаю путь, по которому существует файл, основываясь на имени файла, извлекаю его содержимое в виде строки и удаляю присвоения module.exports внутри содержимого.
- Затем я заменяю вызов require из шага 2 содержимым из шага 3. Если это не модуль core node.js, я добавляю его в список основных модулей, которые я сохраню для дальнейшего использования.
- Шаг 3, вероятно, будет содержать больше
require()
звоните, поэтому я повторяю шаги 3 и 4, пока всеrequire()
звонки исчезли, и у меня осталась одна огромная строка, содержащая весь код. - Если вся рекурсия завершена, я компилирую код, используя REST API.
Вы также можете использовать автономный компилятор.
У меня есть externs для каждого основного модуля node.js. Этот инструмент полезен для генерации экстерьеров. - Я предварительно представляю удаленный модуль core.js
require
вызовы скомпилированного кода.
Предварительно скомпилированный код.
Все require
звонки удалены. Весь мой код сглажен.
http://pastebin.com/eC2rVMiN
Посткомпилированный код.
Ядро Node.js require
звонки были добавлены вручную.
http://pastebin.com/uB8CaejN
Почему вы не должны делать это так:
- Он использует регулярные выражения (не парсер или токенизатор) для обнаружения
require
звонки, встраивание и удалениеmodule.exports
, Это хрупко, так как не охватывает все варианты синтаксиса. - При встраивании весь код модуля добавляется в глобальное пространство имен. Это противоречит принципам Node.js, где каждый файл имеет свое собственное пространство имен, и это приведет к ошибкам, если у вас два разных модуля с одинаковыми глобальными переменными.
- Это не сильно повышает скорость вашего кода, так как V8 также выполняет много оптимизаций кода, таких как встраивание и удаление мертвого кода.
Почему вы должны:
- Потому что это работает, когда у вас есть согласованный код.
- Он обнаружит ошибки в коде вашего сервера, когда вы включите подробные предупреждения.
Вариант 4. Не используйте закрывающий компилятор.
Люди в сообществе узлов не склонны использовать его. Вам не нужно минимизировать исходный код node.js, это глупо.
Там просто нет никакой пользы для минификации.
Что касается преимуществ закрытия, я лично сомневаюсь, что это на самом деле делает ваши программы быстрее.
И, конечно же, стоит заплатить, отладка скомпилированного JavaScript - это кошмар