10 привычек довольного node.js разработчика

Original author: Hunter Loftis
  • Translation


К концу 2015 года в распоряжении JavaScript разработчиков образовалось огромное количество инструментов. В этой экосистеме легко потеряться, поэтому успешные команды следуют выработанным правилам, которые позволяют не терять время и сохранять здоровье проектов. Под катом перевод статьи 2016 года от команды Heroku, в которой они рассказывают о десяти привычках веб разработчиков, у которых все работает и ничего не болит. Скорее всего 80% написанного вы уже знаете – тем интереснее вам будет прочитать об оставшихся двух приемах!


1. Начинайте новый проект командой npm init


Эта команда сразу создаст правильный package.json, основываясь на имени директории проекта и ваших ответах на вопросы визарда:

$ mkdir my-awesome-app
$ cd my-awesome-app
$ npm init --yes

Использование флага --yes позволяет не отвечать на вопросы визарда, а в поле engines получившегося package.json можно сразу установить нужную версию ноды (node -v):

"engines": {
  "node": "4.2.1"
}

2. Используйте «умный» .npmrc


По умолчанию npm не записывает установленные зависимости в package.json (а за зависимостями надо следить всегда!).

Если вы воспользуетесь флагом --save для автоматического обновления package.json, то npm сохранит имя зависимости с префиксом ^. Семантическое версионирование ставит ваши зависимости под угрозу, так как при чистой инсталляции в проект могут установиться другие версии зависимостей, и кто знает, какие в них могут быть баги и проблемы. Такой способ хорош во время разработки, но не в продакшне. Одно из решений – сохранять точную версию зависимости:

$ npm install foobar --save --save-exact

А еще лучше – поменять глобальные настройки npm в ~/.npmrc, чтобы все происходило автоматически:

$ npm config set save=true
$ npm config set save-exact=true
$ cat ~/.npmrc

При такой конфигурации установка зависимостей сохранит точную версию и ваш проект никогда не настигнет жуткий “version drift”!

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

3. Не упустите уходящий поезд ES6


Начиная с 4-й версии Node.js вы можете использовать многие функции ES6. Начните использовать уже сейчас простые и удобные улучшения синтаксиса, которые сделают ваш код проще и понятнее:

let user = users.find(u => u.id === ID);
console.log(`Hello, ${ user.name }!`);

4. Придерживайтесь нижнего регистра


Некоторые языки рекомендуют именовать файлы так же, как классы. Например, файл, имплементирующий MyClass, предлагается называть MyClass.js. Не делайте так в node. Используйте нижний регистр:

let MyClass = require('my-class');

Node.js это редкий пример линуксовой программы с великолепной поддержкой кроссплатформенности. Несмотря на то, что OSX и Windows будут считать файлы myclass.js и MyClass.js одним и тем же файлом, Linux так считать не будет. Если вы хотите писать кросс-платформенный код, вам необходимо именовать файлы в том же регистре, который вы используете для require.

Самый простой пусть сделать все правильно – это использовать нижний регистр, то есть my-class.js (примечание переводчика: или my_class.js, что все-таки популярнее среди программистов, нежели css нотация).

5. Кластеризируйте ваше приложение


Так как процесс node ограничен одним ядром и полутора гигабайтами оперативной памяти, деплой некластеризованного приложения на мощный сервер является тратой полезных ресурсов впустую. Чтобы использовать несколько ядер и много памяти, добавьте в свое приложение поддержку кластеризации. Даже если сейчас вы запускаете приложение на одноядерной vps с гигом памяти, добавление кластеризации будет хорошим заделом на будущее.

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

const CONCURRENCY = process.env.WEB_CONCURRENCY || 1;

Использование кластеризации позволяет не изобретать велосипед для управления потоками. Если вам нравится разделение по файлам на “master” и “worker”, то попробуйте forky. Если же вы предпочитаете единую точку входа, то throng.

6. Следите за переменными окружения


Используйте переменные окружения вместо конфиг файлов. Для этого установите node-foreman:

$ npm install --save --save-exact foreman

Затем создайте Procfile, чтобы указать тип приложения:

web: bin/web
worker: bin/worker

И установите альяс для запуска:

"scripts": {
  "start": "nf start"
}

Теперь, чтобы изменить настойки окружения, достаточно создать (и добавить в .gitignore) файл с именем .env, который будет автоматически загружен node-foreman:

DATABASE_URL='postgres://localhost/foobar'
HTTP_TIMEOUT=10000

С такими настройками достаточно одной команды npm start, чтобы запустить web и worker процессы, а затем прменить к ним настройки в соответствии с переменными окружения. Это намного проще, чем 'config/abby-dev.js', 'config/brian-dev.js', 'config/qa1.js', 'config/qa2.js', 'config/prod.js', итд.

7. Избегайте мусора


Node.js (в лице движка V8) использует «ленивый» и «жадный» сборщик мусора. С лимитом по умолчанию в полтора гигабайта он часто ждет до последнего, прежде чем освободить не используемую память. Если у вас постепенно растет использованная память, это может быть не ее утечка, а вполне штатное поведение сборщика мусора node.

Для лучшего контроля за сборщиком мусора, в Procfile можно поместить команды для движка V8:

web: node --optimize_for_size --max_old_space_size=920 --gc_interval=100 server.js

Это особенно важно, если ваше приложение запущено на машине с менее чем полутора гигами доступной памяти. Например, для контейнера с 512 мегабайтами памяти строка аргументов может выглядеть вот так:

web: node --optimize_for_size --max_old_space_size=460 --gc_interval=100 server.js

8. Используйте хуки


“Lifecycle scripts” в node.js предоставляют широкие возможности для автоматизации. К примеру, если вам нужно что-нибудь запустить перед сборкой приложения, вы можете воспользоваться скриптом preinstall. Нужно собрать ассеты с помощью grunt, gulp, browserify или webpack? Используйте скрипт postinstall. Все эти скрипты можно задать прямо в package.json:

"scripts": {
  "postinstall": "bower install && grunt build",
  "start": "nf start"
}

И конечно вы можете использовать переменные окружения для управления скриптами:

"postinstall": "if $BUILD_ASSETS; then npm run build-assets; fi",
"build-assets": "bower install && grunt build"

Если скрипты стали слишком сложные – перенесите их в файлы:

"postinstall": "scripts/postinstall.sh"

Важно: скрипты в package.json выполняются с ./node_modules/.bin автоматически добавленным в PATH, так что вы можете напрямую вызывать локально установленные npm пакеты, такие как webpack. Это позволяет вообще не устанавливать пакеты глобально.

9. Only git the important bits


Мне настолько понравился этот заголовок, что я оставил его без перевода. Красивая игра слов – «гитуйте только важные биты» = «добавляйте в git только то, что важно». Большинство приложений состоят из файлов двух типов: которые можно или нельзя сгенерировать автоматически. Когда вы используете систему контроля версий, избегайте добавлять в нее файлы, которые можно сгенерировать автоматически.

Например, у вашего приложения есть директория node_modules с установленными зависимостями. Да, есть люди, которые добавляют ее в систему контроля версий! Не надо так делать. Указание нужных версий зависимостей в package.json намного надежнее, так как npm install не очищает node_modules, и если там уже будут файлы из системы контроля версий, может случиться беда.

Также добавление генерируемых файлов производит много лишнего шума в логах и нотификациях вашего репозитория. Более того, некоторые зависимости требуют компиляции при установки, так что добавление установленной версии в репозиторий делает ваше приложение непортабельным и может привести к странным ошибкам.
По тем же причинам не стоит добавлять bower_components или скомпилированные grunt ассеты. Если в проекте директория node_modules уже добавлена в систему контроля версий, то ее всегда можно убрать, например, вот так:

$ echo 'node_modules' >> .gitignore
$ git rm -r --cached node_modules
$ git commit -am 'ignore node_modules'

Также я обычно добавляю в игнор логи npm, чтобы не засорять код:

$ echo 'npm-debug.log' >> .gitignore
$ git commit -am 'ignore npm-debug'

Игнорируя эти менее важные файлы, вы уменьшаете размер репозитория, упрощаете коммиты и не получаете конфликтов слияния.

10. Упрощайте


Предсказания в айти – дело неблагодарное. Но я все-таки сделаю одно. Предсказываю, что для JavaScript 2016 год станет годом упрощения.
Все больше программистов упрощают архитектуру своих решений. На смену монолитных MVC решений приходит статический фронтенд, который так удобно раздавать через CDN, и Node.js бэкенд для динамических данных.

Мы также начали замечать, насколько сложные системы сборки усложняют наши проекты. На острие прогресса разработчики упрощают эти решения, в частности, используя npm скрипты вместо bower, gulp и grunt.

И, наконец, в 2016 году мы будем упрощать написанный нами код. В ряде случаев это будет отказ от лишних фичей, как показал Douglas Crockford в своих The Better Parts. Или, наоборот, упрощение придет от добавления новой функциональности. Такой, как мои любимые async и await. В ноде их пока нет, но всегда можно воспользоваться транспайлером, например BabelJS.

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

Comments 50

    +1
    bower, grunt, gulp, brunch, webpack

    Они все одно и то же делают?
      0
      Не совсем
      bower — менеджер пакетов для клиентских библиотек
      grunt и gulp — примерно одно и тоже, просто таск раннеры
      brunch — не знаю что такое
      webpack — бандлер и загрузчик модулей
        0
        brunch — не знаю что такое

        Brunch
        Очень удобный сборщик.
          +2
          Вот интересно, как человек со стороны должен понять разницу между "менеджером пакетов" и "загрузчиком модулей". Звучит как синонимы :)
            0
            угу, для меня это одно и то же: 8 копий одной тулзы, которые просто сделаны разными людьми, NIH
          0
          • grunt и gulp по сути одно и то же, но с разным синтаксисом и подходом к написанию сценариев сборки.
          • webpack в ряде случаев (но не во всех) может заменить grunt и gulp
          • bower дает более широкие возможности по сравнению с установкой из npm, но можно обойтись и без него
            0
            bower особенно важен для фронта, т.к. объединяет зависимости одинаковых имён.
              +1
              Bower — архаизм, но зачастую в npm отсутствует нужный модуль, а ручками копировать и отслеживать не хочется.
              Webpack — гибкая и мощная система сборки, однако, в отличие от grunt и gulp, для бэка практически бесполезна, основное — сборка фронта. И нет задач, которые не смог бы решить вебпак (еще бы, у него достаточно мощная система расширений), но, к сожалению, зачастую нужные вещи написаны под грант или галп, а на вебпак переводить слишком накладно. Правда именно так и плодится зоопарк (у меня сейчас галп создает иконичный шрифт из набора svg и запускает регрессионное тестирование, все остальное силами webpack. И если первое — нужно просто посидеть немного, то второе — даже идей нет как впилить, а беглый поиск в гугле ничего внятного не предложил).
          +1
          (примечание переводчика: или my_class.js, что все-таки популярнее среди программистов, нежели css нотация).

          Все же в node.js для именования файлов, по моему субъективному ощущению, чаще используется дефис.
            –3
            У дефиса есть бо-о-о-о-ольшой минус — большинство редакторов и утилит командной строки воспринимает его как разделитель слов. А имя файла обычно используют как одно слово.
              +2
              Ну не такой уж и бо-о-о-о-ольшой, я вот вообще не вижу проблемы. В большинстве случаев, имя файла используется в import директивах, а там есть нормальный автокомплит от IDE.
            +1
            > Only git the important bits

            bits — это кусочки. (Но на русский всё равно плохо мантруется, да.)
            А по оставлению важных кусочков — мысль более глубокая, если считать, что важные != необходимые.
            К примеру, для проекта может быть важно наличие в нём последней рабочей сбоки без необходимости ставить Ноду и запускать сборку. Не нужно держать /node-modules, но полезно — конечную сборку, хотя бы в тестовой папке, или в /dist, и часто — полезно держать /bower-components — сразу есть возможность запустить проект без сборки.

            > Попробуйте не соревноваться друг с другом в том, сколько новых технологий можно запихнуть в один проект
            +1
            • UFO just landed and posted this here
                +3
                Видимо автор хотел сделать отсылку что в некоторых диалектах США get произносится как git
                Это здесь не причем. «To git smth» образовалось так же, как и «to google smth».
                • UFO just landed and posted this here
                    +1
                    Да, обычно говорят "запушь"/"запуши" :)
                      +2
                      А как же известное "We will rock you"? Ведь в этой фразе явно не в значении "качать" глагол rock употреблён.

                      Мне всегда казалось, что в разговорном английском языке норма взять существительное и сделать из него глагол. И даже сомнений не вызвало значение фразы "Only git the important bits", особенно после приведённой расшифровки.
                      • UFO just landed and posted this here
                          0
                          Шикарно, вы даже не поленились спросить на StackExchange. :-)

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

                          На самом деле, правду знает только автор. Вариант с get тоже не исключён, но я так глубоко не знаю язык, чтобы хотя бы предположить такое.
                          • UFO just landed and posted this here
                –4
                my-class.js
                Если честно, хочется убивать за такое, и особенно за такие советы.

                Как я должен догадаться, что экспортируется из этого файла, MyClass, myClass, my_class или вообще myclass? Ну если есть слово class, то еще понятно, а если там (M|m)y(-_)?(T|t)hing? Неужели так трудно сразу назвать файл в нормальном кейсе?
                  0
                  Если мы говорим про NodeJS, то это вообще-то зависит только от вашего личного желания и кодстайла. Вы можете написать:

                  const myClass = require('my-class');
                  
                  const myclass = require('my-class');
                  
                  const my_class = require('my-class');

                  И здесь как раз наоборот — я сам хочу решать вопрос именования переменных в своем приложении, а не использовать кашу-малашу от авторов разных модулей, которыми я пользуюсь.
                    +2
                    Категорически нет. Должен быть единый кодстайл как минимум в наименовании сущностей, и очень плохо, что не все его соблюдают до сих пор.
                      –1
                      В идеальном мире да. Но есть одно но — вкусы у всех свои. Кому-то кажется, что классы созданы, чтобы их называли с большой буквы, а кто-то уверен, что это выглядит отвратительно, а третьему вообще нравится перед именем класса ставить $. И все они правы, потому что нет никакого правила, которое говорило бы, как правильно. И не мне это решать и не вам. Вы не заставите всех писать так, как вам нравится. Вы можете соблюдать свой кодстайл в пределах вашего проекта. И именно для этого вам никто не навязывает, как называть классы.
                        +1
                        Именно поэтому умные люди придумали PEP8, решив тем самым проблему глобально на уровне языка.
                          0
                          Вот чего JS не хватает, так это чего-нибудь вроде PEP8 или PSR. Зато есть 11 пресетов для JSCS и полдесятка форматов для написания документации.
                          +2
                          Какбэ нет. Класс для работы с датами называется Date, а не $date или Dat_e или еще что-нибудь. Аналогично с прочими встроенными вещами. PascalCase для классов, camelCase для методов, свойства и функций, UPPER_CASE для констант. Никакого кебаб-кейса или снейк_кейса. Вот чтобы не было разнобоя, надо ориентироваться на это. Ради общего блага. Чтобы не тратить время на идиотские споры, как именовать файлы.
                            –1
                            А что вы скажите по поводу объекта Math, который ни разу не класс? Или про константу Math.pi? EcmaScript плохой пример стандарта единым кодстайлом.
                                0
                                Да, согласен, с PI ошибся, но Math все также не кошерно пишется с заглавной буквы.
                                  +2
                                  Пардон, а чем Math не "static class"?
                                    0
                                    По такой логике любой объект можно назвать статическим классом и писать с большой буквы.
                                      +1
                                      Отчего же? Math содержит ряд математических методов и констант. Этакий namespace по смыслу, helper. Вот, к примеру, document.location это скорее набор полей, отличающихся от страницы к странице.

                                      Правда мне не ясно, почему же тогда crypto, вместо Crypto. При том что Crypto тоже наличиствует, но на мои попытки им как-то воспользоваться ругается illegal constructor. Судя по документации к нему нужно обращаться через window.crypto. Ребята явно чего-то перемудрили.
                              • UFO just landed and posted this here
                                  0
                                  Присоединяюсь к совету. :)
                              0
                              И не мне это решать и не вам

                              Простите, а кому это решать?
                                0
                                Если вы спрашивает о том, как должно быть, то это организация, которая занимается разработкой стандарта. Если о том, как есть, то каждый определяет кодстайл в пределах своего проекта. NodeJS-модули спроектированы таким образом, чтобы вы как можно реже заглядывали в сторонний код.
                            +2
                            Речь не о том, как надо называть ваши ячейки памяти, а о том, что название файлов под разными ОС ведут себя по разному. Регистро, или не регистрозависимые
                            –1
                            Как я должен догадаться, что экспортируется из этого файла

                            Вы как будто ни одной строчки в node.js не написали.
                            +1
                            По умолчанию npm не записывает установленные зависимости в package.json (а за зависимостями надо следить всегда!).
                            Это не проблема, если после установки зависимости открывать package.json и вручную вписывать зависимость в него.

                            Понятно, что это вроде как лишний труд, но его не так много (подумаешь, одну строчку в текстовый файл воткнуть!), а преимуществом такого подхода является контроль над тем, какой диапазон версий зависимости считать приемлемым.

                            Почему это полезно?

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

                            Для одних зависимостей годится предложенное в этой блогозаписи решение (сохранять в package.json точную версию, а обновления запретить, потому что кто знает, какие в них могут быть баги и проблемы), но для других не годится (а в package.json про них вписываешь «~x.y.z» для разрешения установки новой пропатченной версии, потому что кто знает, какие в нынешней версии могут быть баги и проблемы).

                            Выбор между этими двумя стратегиями лучше делать индивидуально (ориентируясь на возможность разработчиков зависимостей находить и исправлять ошибки, не создавая новых багов и проблем) и делать его именно в момент вписывания зависимости в package.json.
                              0
                              Кластеризируйте ваше приложение
                              У нас в supervisord висит просто nodejs приложений по числу ядер. Использование `process.env.WEB_CONCURRENCY` лучше?
                                0
                                Используйте переменные окружения вместо конфиг файлов.
                                Почему? Из того, что написано в этом разделе, так и не понял в чём профит, и почему в сообществе nodejs так любят переменные окружения.
                                  0
                                  мне кажется конфиги в файлах не любят те кто не догадался до require('./config');
                                  Других причин не любить конфиги — не вижу.
                                  Но есть ситуации когда переменные окружения удобнее — например при сборке webpack-ом, которая запускается через npm скриптинг — скажем npm run target, а каждый target выставляет TARGET=blabla webpack… а там уже в конфиге сборки подхватывается.
                                  В общем тут от повара зависит — у каждого свои рецепты.
                                    +1
                                    Самое печальное — видеть, как поклонники линуксовой и маковой среды начинают портировать проект на Windows, а у них в package.json ужé полным-полно команд в формате «переменная1=значение1 переменная2=значение2 команда параметры», который в Windows командною строкою не поддерживается в качестве средства пополнения переменных окружения, в котором выполняется команда.

                                    Мрачно подозреваю также, что require('./config') не нравится тем разработчикам, которых отталкивает чрезмерная строгость языка JSON, а простота формата «переменная=значение» (по сравнению с форматом «"переменная": "значение"», где символов больше, а ошибка в любом из них приводит к неработоспособности) представляется привлекательною.

                                    Немного поразмыслив над этим, я стал сознавать ещё бóльшую привлекательность простого текстового формата конфигураций, в котором и знак равенства не нужен, а простой пробел (или несколько пробелов) отделяет имя переменной от значения:

                                    # Описание пользователя:
                                    User      Mithgol
                                    Karma     60½
                                    ReadOnly  No
                                    
                                    # Социальные сети:
                                    Fidonet   2:50/88
                                    Twitter   FidonetRunes

                                    После этого я пошёл и сочинил модуль для чтения такого формата, потому что готовые модули мне как-то не попадалися для этой цели.
                                      0
                                      который в Windows командною строкою не поддерживается

                                      Соглашусь полностью, этот момент не учел в своем высказывании.

                                      После этого я пошёл и сочинил модуль

                                      Интересный модуль, правильная лицензия — утянул в копилку.
                                  +4
                                  Используйте переменные окружения вместо конфиг файлов
                                  Теперь, чтобы изменить настойки окружения, достаточно создать (и добавить в .gitignore) файл с именем .env

                                  Одного меня что-то в этом смущает? Вместо конфиг-файла у нас теперь есть конфиг-файл и тулза, которая его читает. Нужно больше абстракций, ага.
                                    0
                                    echo 'node_modules' >> .gitignore

                                    А потом, когда у вас в офисе начал лагать инет, или сам нпм слёг (да, несколько раз бывало и такое), вы сидите и тупо курите бабмук...
                                      +1
                                      Есть кэш, есть sinopia. И есть раздувшийся репозиторий.
                                        0
                                        спасибо за sinopia — как то она на глаза не попадалась.
                                        0
                                        сам не делал, но знаю что народ вообще-то свой gitlab разворачивает в локалке и с него тянет модули. И на него же обновляет с github-a. И при форсмажорах чувствует себя в сухе и тепле. Совать модули в проект я вижу только одну причину — пропатченный модуль до момента принятия владельцем пулл-реквеста.

                                      Only users with full accounts can post comments. Log in, please.