Подтвердить что ты не робот

Получение закрытия-компилятора и Node.js, чтобы играть приятно

Есть ли проекты, которые использовали node.js и short-compiler (CC для краткости) вместе?

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

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

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

Ответ 1

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

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

Следующий шаг - создать кучу файлов externs для функций 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 для добавления всех require() в начале файла, который, когда объявлен в 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 require(), но компилятор допускает использование таких вещей, как http в моем коде, потому что компилятор распознает их как externs. Поскольку они не являются истинными внешними, они должны быть добавлены, как я описал.

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

goog.scope(function() {

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

    // Use http throughout.

});

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

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

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

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

Ответ 3

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

Новый подход

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

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

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

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

Я был вдохновлен тем, как bolinfest решил проблему, и мое решение использует тот же принцип. Разница заключается в том, что я сделал один node.js script, который делает все, включая встраиваемые модули (решение 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 внутри содержимого.
  • Затем я заменяю вызов вызова с шага 2 содержимым с шага 3. Если он не является ядром node.js-модуля, то я добавляю его в список основных модулей, которые я сохраняю позже.
  • Шаг 3, вероятно, будет содержать больше вызовов require(), поэтому я повторяю шаги 3 и 4 рекурсивно до тех пор, пока все вызовы require() не исчезнут, и я останусь с одной огромной строкой, содержащей весь код.
  • Если вся рекурсия завершена, я скомпилирую код с помощью REST API.
    Вы также можете использовать автономный компилятор.
    У меня есть externs для каждого основного модуля node.js. Этот инструмент полезен для создания externs.
  • Предоставляю удаленный код core.js require для скомпилированного кода.

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

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


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

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

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

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

Забастовкa >

Ответ 5

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

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

Нет простого использования для минимизации.

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

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