Всем привет, уважаемые хабражители.
Многие из вас так или иначе имели дело с Node.js. Наверное, не имеет смысла рассказывать о том, какие преимущества есть у JavaScript и у его серверной реализации в частности. В настоящий момент я много всего делаю на JS, начиная от простых консольных скриптов и заканчивая API, сервисами и сайтами. Современный стандарт EcmaScript принес значительные изменения в язык: он не только исправил некоторые древние «косяки» JS, но и добавил новые возможности, позволив, в частности, красиво избавиться от Callback Hell.
Когда передо мной в очередной раз встала задача развернуть простой сайт, состоящий из нескольких десятков страничек, мне захотелось сделать это при помощи какой-нибудь легковесной, но современной CMS, основанной на Node. Оценив обстановку, я понял, что ничего подходящего до сих пор нет. Мой старый и добрый Taracot оказался для этой задачи слишком тяжелым, к тому же, он не работает с современными версиями Node и перегружен функционалом.
Что я хотел получить в итоге?
Автоматическую систему обновлений
Простой модуль для создания и редактирования контента с удобной загрузкой изображений на сервер
Zoia.js
Работал я в свободное время и исключительно ради удовольствия, поэтому проект немного затянулся, но с первого коммита в конце мая до середины октября 2017 года получилось сделать многое по перечисленным выше пунктам:
![image](https://habrastorage.org/getpro/habr/post_images/1b2/fce/924/1b2fce92489c14bcce04dfa5bdef685d.png)
Лицензия — MIT.
В качестве базового фреймворка используется Express.js, база данных — MongoDB, шаблонизатор — Nunjucks от Mozilla, а для UI используется UIkit.
Почему Web Framework, а не просто CMS? Прежде всего потому, что на базе системы можно сделать API, используя, например, только возможности, связанные с авторизацией. То есть из Zoia можно спокойно «выкинуть» модули, связанные с отображением контента для пользователя, и возвращать только JSON/XML.
Динамические таблицы и формы
Для динамического отображения данных в табличной форме я написал jQuery плагин zoiaTable. Он позволяет превратить любую HTML-таблицу в «динамическую», с возможностью фильтрации данных, разбивкой на страницы и сортировкой. Как это выглядит «вживую», можно посмотреть здесь.
Удобное построение форм — ещё одна задача, для которой я написал другой плагин: zoiaFormBuilder. У него две основные задачи: динамическое постороение форм с возможностью сериализации/десериализации данных и валидация данных по заданным правилам.
Оба плагина доступны по лицензии MIT, и их можно использовать отдельно от Zoia, хотя в данный момент они мне нужны только там. Отображение не привязано к конкретному фреймворку (можно в параметрах вызова задавать нужный HTML и стили), по умолчанию используется UIkit.
Что ещё реализовано на данный момент?
Прежде всего, доступная система управления пользователями и группами. В перспективе это позволит сделать разграничение прав для модулей и их отдельных компонентов (например, определенная группа может редактировать странички только в определенной папке). На данный момент существует одна системная группа — admin, которая позволяет пользователям заходить в backend.
Система регистрации пользователей сделана достаточно стандартно — с валидацией по e-mail. Существует возможность восстановить забытый пароль (также через e-mail). В перспективе здесь нужно будет прикрутить авторизацию через различные социальные сервисы (по Oauth), двухфакторную авторизацию (например, через Google Authenticator или SMS), а также сделать простой личный кабинет. Что из этого необходимо в системе, позиционирующей себя как «лековесная» — отдельный вопрос.
Самописная Captcha не использует сторонних библиотек вроде GraphicMagick, вместо используется JIMP — бибилотека, не использующая внешних зависимостей.
Редактор контента (Pages) использует CKEditor как WISIWYG редактор. Есть возможность вставлять в страницы «хлебные крошки» (breadcrumbs). Также написан простой бразуер с возможностью загрузки файлов и автоматическим созданием thumbnail'ов:
![](https://habrastorage.org/webt/59/df/23/59df23d04b4b4983158432.png)
Он умеет создавать папки, копировать-перемещать файлы, в общем, имеет в наличии весь базовый функционал.
Модуль навигации (Navigation) позволяет создавать многоуровневые навигационные меню для сайта. Работа с навигацией осуществляется в виде дерева, в котором можно создавать, редактировать и перетаскивать элементы.
Система автоматических обновлений позволяет загружать новую версию системы и смотреть список изменений.
Как установить Zoia на своём сервере?
Потребуется установленный Node 7+ и MongoDB. Разработку я веду под Windows, поэтому там это тоже работает, но в продакшене, конечно, лучше использовать Linux-based системы. Если у Вас Debian-совместимая система, то есть простой способ установить всё одной командой:
Также можно установить всё через Docker:
Подробнее об установке можно почитать в документации.
Что будет реализовано
В настоящий момент фреймворк находится в состоянии beta (думаю, в таком состоянии он будет ещё достаточно долго). Как я уже написал выше, хочется доработать до определенной стадии модуль авторизации (Auth), чтобы сделать двухфакторную авторизацию и Oauth — но это ещё не точно.
Также, отдельно от базовой системы, я хочу сделать модуль для ведения блога (личного или коллективного), а также простой модуль интернет-магазина.
Документация — над ней я ещё работаю. Вообще, я работаю над всем, включая архитектуру, дизайн, разработку и тестирование, а времени очень мало. Именно поэтому перевода на русский язык (и другие языки) пока нет, но я буду рад, если кто-то сможет мне с этим помочь. Также буду рад всем разработчикам, кто захочет совместно работать над проектом — код открыт.
Давно ничего не публиковал на Хабре.
Если что-то сделал не так, пожалуйста, пишите в личку.
Многие из вас так или иначе имели дело с Node.js. Наверное, не имеет смысла рассказывать о том, какие преимущества есть у JavaScript и у его серверной реализации в частности. В настоящий момент я много всего делаю на JS, начиная от простых консольных скриптов и заканчивая API, сервисами и сайтами. Современный стандарт EcmaScript принес значительные изменения в язык: он не только исправил некоторые древние «косяки» JS, но и добавил новые возможности, позволив, в частности, красиво избавиться от Callback Hell.
Когда передо мной в очередной раз встала задача развернуть простой сайт, состоящий из нескольких десятков страничек, мне захотелось сделать это при помощи какой-нибудь легковесной, но современной CMS, основанной на Node. Оценив обстановку, я понял, что ничего подходящего до сих пор нет. Мой старый и добрый Taracot оказался для этой задачи слишком тяжелым, к тому же, он не работает с современными версиями Node и перегружен функционалом.
Что я хотел получить в итоге?
- Систему регистрации, авторизации, управления пользователями и группами, чтобы об этом не нужно было каждый раз думать
- Удобный шаблонизатор с возможностью использования асинхронных функций
- Модуль для быстрой отправки e-mail пользователям
- Captcha, желательно без сторонних библиотек
- Валидацию форм и полей
- Быструю и удобную AJAX-driven таблицу для отображения данных
- Код с использованием возможностей ES6
- Многоязычность из коробки
- Модульную структуру с возможностью быстро и комфортно написать новый модуль
Автоматическую систему обновлений
Простой модуль для создания и редактирования контента с удобной загрузкой изображений на сервер
Zoia.js
Работал я в свободное время и исключительно ради удовольствия, поэтому проект немного затянулся, но с первого коммита в конце мая до середины октября 2017 года получилось сделать многое по перечисленным выше пунктам:
- Офсайт (ещё не доделал его до конца)
- Страница на Github
- Wiki с документацией
- Демо установка (перезагружается каждый час)
![image](https://habrastorage.org/getpro/habr/post_images/1b2/fce/924/1b2fce92489c14bcce04dfa5bdef685d.png)
Лицензия — MIT.
В качестве базового фреймворка используется Express.js, база данных — MongoDB, шаблонизатор — Nunjucks от Mozilla, а для UI используется UIkit.
Почему Web Framework, а не просто CMS? Прежде всего потому, что на базе системы можно сделать API, используя, например, только возможности, связанные с авторизацией. То есть из Zoia можно спокойно «выкинуть» модули, связанные с отображением контента для пользователя, и возвращать только JSON/XML.
Динамические таблицы и формы
Для динамического отображения данных в табличной форме я написал jQuery плагин zoiaTable. Он позволяет превратить любую HTML-таблицу в «динамическую», с возможностью фильтрации данных, разбивкой на страницы и сортировкой. Как это выглядит «вживую», можно посмотреть здесь.
Удобное построение форм — ещё одна задача, для которой я написал другой плагин: zoiaFormBuilder. У него две основные задачи: динамическое постороение форм с возможностью сериализации/десериализации данных и валидация данных по заданным правилам.
Оба плагина доступны по лицензии MIT, и их можно использовать отдельно от Zoia, хотя в данный момент они мне нужны только там. Отображение не привязано к конкретному фреймворку (можно в параметрах вызова задавать нужный HTML и стили), по умолчанию используется UIkit.
Что ещё реализовано на данный момент?
Прежде всего, доступная система управления пользователями и группами. В перспективе это позволит сделать разграничение прав для модулей и их отдельных компонентов (например, определенная группа может редактировать странички только в определенной папке). На данный момент существует одна системная группа — admin, которая позволяет пользователям заходить в backend.
Система регистрации пользователей сделана достаточно стандартно — с валидацией по e-mail. Существует возможность восстановить забытый пароль (также через e-mail). В перспективе здесь нужно будет прикрутить авторизацию через различные социальные сервисы (по Oauth), двухфакторную авторизацию (например, через Google Authenticator или SMS), а также сделать простой личный кабинет. Что из этого необходимо в системе, позиционирующей себя как «лековесная» — отдельный вопрос.
Самописная Captcha не использует сторонних библиотек вроде GraphicMagick, вместо используется JIMP — бибилотека, не использующая внешних зависимостей.
Редактор контента (Pages) использует CKEditor как WISIWYG редактор. Есть возможность вставлять в страницы «хлебные крошки» (breadcrumbs). Также написан простой бразуер с возможностью загрузки файлов и автоматическим созданием thumbnail'ов:
![](https://habrastorage.org/webt/59/df/23/59df23d04b4b4983158432.png)
Он умеет создавать папки, копировать-перемещать файлы, в общем, имеет в наличии весь базовый функционал.
Модуль навигации (Navigation) позволяет создавать многоуровневые навигационные меню для сайта. Работа с навигацией осуществляется в виде дерева, в котором можно создавать, редактировать и перетаскивать элементы.
Система автоматических обновлений позволяет загружать новую версию системы и смотреть список изменений.
Как установить Zoia на своём сервере?
Потребуется установленный Node 7+ и MongoDB. Разработку я веду под Windows, поэтому там это тоже работает, но в продакшене, конечно, лучше использовать Linux-based системы. Если у Вас Debian-совместимая система, то есть простой способ установить всё одной командой:
wget -q https://xtremespb.github.io/zoia/zoia_install && sudo bash zoia_install
Также можно установить всё через Docker:
docker pull mongo:latest
docker pull xtremespb/zoia:latest
docker run -d --name mongo mongo
docker run -p 3000:3000 -d --name zoia --link=mongo:mongo xtremespb/zoia
docker exec -it zoia node /usr/local/zoia/bin/install.js
Подробнее об установке можно почитать в документации.
Что будет реализовано
В настоящий момент фреймворк находится в состоянии beta (думаю, в таком состоянии он будет ещё достаточно долго). Как я уже написал выше, хочется доработать до определенной стадии модуль авторизации (Auth), чтобы сделать двухфакторную авторизацию и Oauth — но это ещё не точно.
Также, отдельно от базовой системы, я хочу сделать модуль для ведения блога (личного или коллективного), а также простой модуль интернет-магазина.
Документация — над ней я ещё работаю. Вообще, я работаю над всем, включая архитектуру, дизайн, разработку и тестирование, а времени очень мало. Именно поэтому перевода на русский язык (и другие языки) пока нет, но я буду рад, если кто-то сможет мне с этим помочь. Также буду рад всем разработчикам, кто захочет совместно работать над проектом — код открыт.
Давно ничего не публиковал на Хабре.
Если что-то сделал не так, пожалуйста, пишите в личку.
asdf404
Есть пара вопросов:
Dockerfile
?require(path.join(__dirname, '..', 'etc', 'config.js'))
, когда можно писать простоrequire('../etc/config.js')
?let that = this
, как, например, тут?version.js
, а не вpackage.json
?И ради всего святого, не делайте так никогда (тем более с sudo):
xtremespb Автор
1. Dockerfile здесь: github.com/xtremespb/xtremespb.github.io/blob/master/zoia/Dockerfile
2. Я использую path для нормализации пути с учетом различных нотаций в различных ОС, насколько я понимаю, это best practice
3. Стрелочные функции используются не везде, поскольку местами есть копипаст с моего старого ES5 кода. Я стараюсь использовать их везде, где это возможно.
4. С package.json хорошая идея, брать версию оттуда. Сделал отдельный файл, т.к. не хотел при обновлении трогать package.json, но, похоже, это всё-таки хорошая идея. Спасибо!
5. Это дефотлный конфиг. Он находится в системе контроля версий, т.к. с ним можно сразу стартануть приложение, и в бета-версии его структура ещё может меняться.
6. Я привык работать с Express и неплохо его знаю. После одного маленького напильника он позволяет использовать асинхронные функции в routes, поэтому не вижу причин, почему не использовать его дальше ;-)
По поводу кода для установки: почему нет? Это стандартная практика, например, вот здесь такой мануал по установке официального Node:
asdf404
Спасибо за быстрый ответ.
делать apt-get upgrade
, лучше использовать новую версию родительского образа (в данном случае Debian);RUN
кешируется и сохраняется как отдельный слой, обычно этого стараются избегать;--restart always
, тогда упавший процесс будет перезапускаться самим докером (см.docker run --help
);let that = this
. Стрелочные функции не имеют своего контекста, они наследуют родительскийй, поэтому в этом трюке нет смысла.npm version ...
, который сам изменит версию вpackage.json
и поставит тег в git; важно, чтобы все текущие изменения были закоммичены, иначе он упадёт с ошибкой. Рекомендую в вопросе версионирования следовать semver.asdf404
8. ну и по поводу установки через
curl ... | sudo bash ...
— это ужасная практика, которая учит пользователей запускать непроверенные скрипты, полученные через интернет (не говоря уже про sudo). Не каждый полезет внутрь читать и разбираться в bash'евских закорючках, чтобы понять что же делает этот скрипт.xtremespb Автор
А как бы Вы рекомендовали делать инсталляцию?
asdf404
Общие принципы изложил в другом комментарии. Здесь напишу подробнее, хотя сдаётся мне, что лучше оформить это уже отдельной статьёй, возможно позже.
Итак, как я делаю это у себя:
Есть 2 Dockerfile (в др. комментарии), в production версию копируется весь код приложения, и для него устанавливаются зависимости (
npm install
и т.п.), полученный образ тегается и пушится в реестр, он готов к использованию. Стоит учесть, что вся конфигурация задаётся через переменные окружения, согласно 12factor, это позволяет не влазить каждый раз в код для изменения настроек, а просто перезапустить контейнер с новым окружением.Для разработки используется другой Dockerfile (также в др. комментарии). В нём устанавливаюстя лишь те компоненты, которые я не могу пробросить извне (
imagemagick
в моём случае), остальное: код и node_modules, через volume монтируется внутрь контейнера и оттуда запускается. При этом (у меня Linux, не знаю как будет на Windows), благодаря nodemon, при изменении файлов автоматически перезапускается приложение.Вот как-то так. Возможно, не совсем понятно объяснил, не стесняйтесь спрашивать и уточнять.
xtremespb Автор
Спасибо, про Docker идея ясна.
А как быть, если Docker не используется? Мой скрипт загружает необходимые зависимости и производит установку последнего релиза с Github. В принципе, sudo здесь необходим только для установки через apt-get, всё остальное загружается в локальную директорию.
asdf404
Для
apt-get
лучше указать зависимости вREADME.md
, и дать пользователю самому это сделать. Да, это несколько сложнее для пользователя (целое лишнее действие), но зато более понятно, что именно произойдёт в итоге.По поводу если Docker не используется, то можно оформить вашу CMS в виде NPM пакета, который можно глобально (
npm install -g ...
) установить. Либо можно подсмотреть как делают другие, например упомянутый ниже KeystoneJS.Вообще, глядя на всякие CMS на NodeJS, видно, что они используют подход с генератором шаблона, для которого сама CMS устанавливается как зависимость после.
xtremespb Автор
Да, спасибо, нужно подумать в этом направлении.
Идею со скриптом я, конечно, «украл» у установщика Node.js. Но Ваши аргументы выглядят более разумно.
justboris
А если у меня не apt-get а, например, yum?
Будет удобнее написать, какие зависимости вам нужны, а пользователи пусть сами ставят.
xtremespb Автор
Поэтому я написал Debian-based systems. Для разных дистрибутивов не только менеджеры пакетов разные, но и сами названия пакетов тоже?
justboris
То есть если у меня не Debian-based, то я в пролете? Печально :(
Почитал скрипт установки, а там из debian-специфичного только apt-get. Убрать его, и можно будет заменить Debian-based на "Linux и Mac".
xtremespb Автор
Конечно не в пролете ;-) Просто зависимости придётся ставить руками.
Я постараюсь сделать инструкцию под разные дистрибутивы.
xtremespb Автор
Спасибо за дельные комментарии!
1. С Docker'ом я имею дело ровно два дня, так что всё, что Вы написали — очень ценная для меня информация. 1) по поводу образа Node учту, спасибо 2) Я использовал :latest версию, и всё равно там была не обновлена часть пакетов, отсюда и apt-get upgrade 3) есть другие альтернативы, когда нужно выполнить команду в контейнере? 4) будет работать как monit, т.е. перезапускать при недоступности порта (или процесса)?
2. Да, под Windows вряд ли запуститься, надо будет попробовать.
3. Стрелочные функции, например, вот здесь. Но я в любом случае буду делать рефакторинг кода с that = this.
4. Отдельное спасибо за npm version.
5. Да, с конфигом Вы правы. Учту это.
7. Mongoose не использую, т.к. API, предоставляемое драйвером MongoDB-Node, нравится мне больше.
MikailBag
Из мест, где не работает — указание пути к бинарнику в cmd.exe (в PowerShell работает)
asdf404
рад, что оказался полезным;
2. вы не должны обновлять их сами, в документации написано:
3. если я правильно понял вопрос: когда вы собираете докер-образ, то нет другого способа выполнять сборочные команды, кроме как через
RUN
. Но рекомендуется объединять вызов множества команд в одинRUN
, т.е.:вместо
А вот уже после того как контейнер запущен, вы можете выполнить в нём команду с помощью
exec
. Но нужно учитывать, что любые изменения в работающем контейнере потеряются, после его удаления (или остановки, если запускали с флагом--rm
).4. если приложение падает внутри контейнера, то контейнер останавливается вместе с ним с кодом ошибки, докер это видит и перезапускает (если указано
--restart always
). При разработке вам всё ещё может быть удобно использовать forever или nodemon, для перезапуска процесса при изменении исходных файлов, например. Для такого случая я у себя использую 2 Dockerfile:prod
иdev
, для продакшена и разработки, соответственно:xtremespb Автор
Спасибо, вопросов по Docker'у пока больше нет.
Займусь более детальным изучением и пересоберу образ в соответствии с Вашими рекомендациями.
antirek
плюсую за использование docker-compose — быстро развернуть и попробовать
Akuma
Позвольте дополнить :)
1. Указывайте версию в FROM. Думаю не нужно объяснять зачем.
2. Пути через / под Windows прекрасно работают. Вообще ни разу не встречал ОС, где они бы не работали.
xtremespb Автор
Спасибо! Мне показалось, что решение с path более универсально, но возможно, Вы и правы, поскольку фреймворк в любом случае ориентируется на *NIX системы.
serf
кстати в промис заворочивать коллбэк вызовы вручную не обязательно, в 8й ноде есть util.promisify (а ранее то же самое делалось с Bluebird)
MooooM
А можно подробнее чем не устроил, например, KeystoneJS?
xtremespb Автор
Лично мне он не понравился некоторой нелогичностью интерфейса админки и отсутствием «из коробки» некоторых вещей, которые я перечислил в списке «Что я хотел получить в итоге?». Ну и «леговесным» при всём желании Keystone назвать нельзя, ИМХО.
cane
В меню навигация реализован функционал интернализации. Для одних и тех же записей на разных языках можно указать разные страницы. При большом количестве страниц, такие ошибки возможны. Лучше реализовать навигацию в виде дерева, при клике на элемент которого открывать форму с полями для доступных языков.
xtremespb Автор
Идея как раз в том, что для каждого языка можно делать абсолютно разное дерево навигации. Возможно, имеет смысл сделать функцию «скопировать структуру из языка n»?
cane
А для чего разные деревья для разных языков? Переключения между языками так и не нашел.
xtremespb Автор
Допустим, версия для одного языка готова, а для другого есть только пара переведенных страниц. Нужно будет либо показывать пользователю каждый раз что-то вроде «Страница ещё не переведена на Ваш язык», либо просто показывать ему другое меню навигации.
Переключение между языками сейчас работает через куки и через поддомены. Т.е. можно открыть что-то вроде ru.example.com и попасть на русскоязычную версию. Ещё я хочу сделать постоянное переключение на другой язык через GET, как-то так: example.com?lang=de
cane
Идея понятна. Но с пользовательской точки зрения, в меню навигация я предпочел бы видеть оба дерева. Так более наглядно. А то страница навигации не несет должной нагрузки.
xtremespb Автор
ОК, спасибо за идею!
ingumsky
Любопытный проект. Как раз что-то подобное ищу, поэтому будет интересно попробовать.
Если вам нужна помощь в переводе, я могу поучаствовать (я как раз переводчик и редактор).
xtremespb Автор
Спасибо, буду очень рад! В репозитории на GitHub есть директории lang, в которых содержатся языковые файлы в формате JSON. Можно их переводить и делать Pull Request'ы.
ingumsky
Отлично, сделал первый PR :)
Reon
Вы пишете, что сделали упор на легковесность и минимум зависимостей, а в итоге подтянули
почти весь набор зависимостей, который используют современные проекты.
Ожидал что-то простое, получил монстра на 746 пакетов.
xtremespb Автор
Говоря об отсутствии зависимостей, я подразумеваю, прежде всего, системные пакеты (вроде GraphicsMagick). Всего система использует 24 библиотеки из NPM, среди которых express и mongodb, они, разумеется, тоже подтягивают свои зависимости. Если говорить о легковесности, то речь идёт прежде всего об общей простоте архитектуры всей системы.
yarommax
Выглядит привлекательно
sutarmin
Самый главный вопрос про любой фреймворк: А почему Зоя? :) Просто броское название или есть вложенный смысл?
Вообще начинание хорошее. Несколько месяцев назад потребовался такого рода фреймворк. Удивился тому, что единственный живой — KeystoneJS. Учитывая, как стремительно сейчас развивается Node.js-сообщество, такое наблюдать было странно. Так что начинание отличное, не останавливайтесь! Сам постараюсь присоединиться по мере возникновения свободного времени.
xtremespb Автор
Люблю называть проекты женскими именами. У меня нет ни одной знакомой Зои, а звучит красиво :-)