[JavaScript] @teqfw/core
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Эта статья не о том, как нужно писать приложения на JavaScript'е. Эта статья о том, как можно писать приложения на JavaScript'е. В прошлой публикации я описал свой "велосипед" — DI-контейнер @teqfw/di. В этой я покажу, каким образом его можно применять для создания консольных приложений.
Сразу отмечу, что речь идет о "чистом" JavaScript (ECMAScript 2015+ aka ES6+). Я признателен авторам TypeScript за то влияние, которое он оказал на развитие JS, но считаю, что в 2021-м году отличия TS от JS не столь драматические, как это было в году 2012-м, и не вижу для себя смысла использовать TS там, где достаточно JS. Если вы считаете по-другому и имеете острое желание высказать своё мнение, то можете сразу переходить к комментам, пропустив саму публикацию.
Те же, кому интересно, как же всё-таки в JS-приложении может использоваться "логическая адресация" элементов кода (пространства имён) вместо "физической" (файловая система) — добро пожаловать под кат.
Определения
Для начала зафиксирую некоторые термины:
- приложение: комплекс программ, исполняемых на различном физическом оборудовании (сервера, смартфоны, планшеты, персональные компьютеры), использующий общую кодовую базу, доступную через web.
- пакет: (npm-пакет) компонент приложения, управляемый Node Package Manager, из которых и состоит общая кодовая база.
- модуль: отдельный файл с исходным JS-кодом, соответствующий требованиям, предъявляемым к es-модулям.
- элемент кода: объект или примитив, который может экспортировать модуль.
- пространство имён: строка, соответствующая отдельному модулю, уникально идентифицирующая данный модуль среди всех остальных модулей приложения. Идентификация элементов кода модуля производится относительно идентификатора самого модуля.
- плагин: пакет, содержащий файл ./teqfw.json с конфигурационной информацией, позволяющей DI-контейнеру @teqfw/di в пределах данного пакета сопоставлять пространства имён путям к модулям, этим пространствам соответствующим.
- teq-приложение: приложение, созданное на базе разрабатываемой мной платформы Tequila Framework.
Области кода в приложении
В общем случае модули приложения можно разбить на две большие группы:
- используемые на сервере (в nodejs);
- используемые на фронте (в браузерах);
Отсюда логично вытекает третья группа — смешанная. Модули из этой группы могут использоваться как в nodejs на сервере, так и в браузерах. Это могут быть различные утилиты/хэлперы, а также DTO, описывающие данные передаваемые между браузером и сервером.
В плагинах файловая структура исходников делится на три области:
- ./src/
- ./Back/
- ./Front/
- ./Shared/
В коде модулей из области ./Back/ допускается использование инструкций import для обращения к API nodejs и к npm-пакетам, не являющимся плагинами (без дескриптора ./teqfw.json).
import {dirname, join} from 'path';
В коде модулей ./Front/ и ./Shared/ зависимости подтягиваются через DI-контейнер, без использования иструкций import, касающихся API nodejs и npm-пакетов, не являющихся плагинами. Возможно использование import с относительной адресацией внутри одного пакета или с абсолютной адресацией статического ресурса с исходным кодом, но лучше обходиться без этого.
В контексте данной публикации будет рассматриваться только код из back-области.
Bootstrap
Консольное приложение — это nodejs-приложение. Чтобы это nodejs-приложение могло использовать DI-контейнер, его нужно загрузить обычным способом (через import) из соответствующего npm-пакета (@teqfw/di):
import Container from '@teqfw/di';
После чего создать DI-контейнер и настроить его на использование пространств имён в пакетах @teqfw/di и @teqfw/core:
/** @type {TeqFw_Di_Shared_Container} */
const container = new Container();
const srcCore = join(root, 'node_modules/@teqfw/core/src');
const srcDi = join(root, 'node_modules/@teqfw/di/src');
container.addSourceMapping('TeqFw_Core', srcCore, true, 'mjs');
container.addSourceMapping('TeqFw_Di', srcDi, true, 'mjs');
Теперь DI-контейнер сможет находить модули с исходным кодом по их логическим идентификаторам (namespace'ам) в пакете @teqfw/core. Создаём экземпляр teq-приложения, инициализируем его, передавая через входной параметр путь к корню всего проекта и текущую версию приложения, и запускаем:
const app = await container.get('TeqFw_Core_Back_App$');
await app.init({path: root, version: '0.0.1'});
await app.run();
Полный код bootstrap-скрипта './bin/tequila.mjs'
SPL
#!/usr/bin/env node
'use strict';
import {dirname, join} from 'path';
import Container from '@teqfw/di';
const url = new URL(import.meta.url);
const script = url.pathname;
const bin = dirname(script);
const root = join(bin, '..');
try {
/** @type {TeqFw_Di_Shared_Container} */
const container = new Container();
const pathDi = join(root, 'node_modules/@teqfw/di/src');
const pathCore = join(root, 'node_modules/@teqfw/core/src');
container.addSourceMapping('TeqFw_Di', pathDi, true, 'mjs');
container.addSourceMapping('TeqFw_Core', pathCore, true, 'mjs');
/** @type {TeqFw_Core_Back_App} */
const app = await container.get('TeqFw_Core_Back_App$');
await app.init({path: root, version: '0.1.0'});
await app.run();
} catch (e) {
console.error('Cannot create or run TeqFW application.');
console.dir(e);
}
Полный код может использоваться в качестве стартового для всех teq-приложений практически без изменения (нужно менять только номер текущей версии и путь к корню приложения).
Плагины
Функциональность к приложению добавляется за счёт плагинов — npm-пакетов, у которых в корне пакета находится "дескриптор плагина" (файл ./teqfw.json). Структура файла зависит от того, какие именно плагины используются в приложении, но минимальное содержимое, соответствующее плагину @teqfw/di, такое:
{
"di": {
"autoload": {
"ns": "Vnd_Plugin",
"path": "./src"
}
}
}
Эти инструкции позволяют конфигурировать DI-контейнер в приложении и маппить используемые пространства имен на файловую систему (см. "Загрузка исходников").
При старте приложения запускается сканер плагинов TeqFw_Core_Back_Scan_Plugin, который пробегает по всем пакетам приложения и ищет те, которые являются плагинами (для которых есть дескриптор ./teqfw.json). Сканер добавляет найденные плагины в реестр плагинов TeqFw_Core_Back_Scan_Plugin_Registry, который доступен любому элементу кода в приложении через DI-контейнер. Структура дескриптора не определена — каждый плагин может искать в дескрипторах других плагинов понятную ему информацию. Выше я привёл часть дескриптора, которая используется DI-плагином (полная структура дескриптора для DI-плагина — в модуле TeqFw_Di_Back_Api_Dto_Plugin_Desc).
Команды
Core-плагин использует внутри пакет commander и предоставляет сторонним плагинам интерфейс для добавления своих консольных команд к приложению. Для этого в стороннем плагине должен быть модуль, default-экспорт которого возвращает фабрику по созданию соответствующей команды (структура команды в TeqFw_Core_Back_Api_Dto_Command), а идентификатор этого модуля должен быть зарегистрирован в дескрипторе этого стороннего плагина.
Вот пример инструкций, подключающих в ./teqfw.json демо-проекта CLI-команду для вывода списка плагинов, используемых в приложении:
{
"core": {
"commands": [
"Fl64_Habr_Back_Cli_PluginsList"
]
}
}
Содержимое модуля Fl64_Habr_Back_Cli_PluginsList выглядит примерно так (оставлен только значимый для создания команды код):
export function Factory(spec) {
// EXTRACT DEPS
/** @type {TeqFw_Core_Back_Api_Dto_Command.Factory} */
const fCommand = spec['TeqFw_Core_Back_Api_Dto_Command#Factory$'];
// COMPOSE RESULT
const res = fCommand.create();
res.realm = 'demo';
res.name = 'plugins-list';
res.desc = 'Get list of teq-plugins.';
res.action = function() {/* ... */};
return res;
}
Так как core-плагин собирает CLI-команды из других плагинов приложения, то каждый плагин определяет свой риэлм (область), в котором он размещает свои команды. Это позволяет снизить вероятность возникновения конфликтов между одноимёнными командами из разных плагинов.
В результате, при запросе справки по консольным командам приложения, новая команда появляется в списке:
$ node ./bin/tequila.mjs help
Usage: tequila [options] [command]
Options:
-h, --help display help for command
Commands:
demo-plugins-list [options] Get list of teq-plugins.
core-startup-logs print out startup logs from the application core.
core-version get version of the application.
help [command] display help for command
Добавление опций в коде выше отсутствует, можно посмотреть в исходниках:
$ node ./bin/tequila.mjs help demo-plugins-list
Usage: tequila demo-plugins-list [options]
Get list of teq-plugins.
Options:
-s, --short get plugins names and namespaces
-f, --full get plugins names, namespaces and path to the sources directory
-h, --help display help for command
Резюме
- Логическая адресация элементов кода при помощи namespace'ов несколько упрощает документирование и конфигурирование приложения по сравнению с адресацией с привязкой к файловой системе и npm-пакетам.
- Dependency Injection контейнер, основанный на логических идентификаторах элементов кода (namespace'ах) и динамической загрузке кода, позволяет выделить группу es-модулей (./Shared/), которые могут использоваться как в nodejs-приложениях, так и в браузерах.
- Использование npm позволяет группировать код, связанный по функционалу, по пакетам (плагинам). При этом в одном пакете может находиться код как для фронта web-приложения, так и для серверной части ("микросервис" и "микрофронтенд" в одном npm-пакете).
- Подобная архитектура позволяет собирать из пакетов "монолитные" web-приложения и переиспользовать пакеты в разных приложениях с похожим функционалом (точно так же, как в Wordpress, Drupal, Magento, с той разницей, что языком программирования для фронта и бэка является один и тот же — ES2015+).
- Вполне возможно, что всё то же самое можно сделать и на TypeScript'е.
Для чего я написал эту статью?
Во-первых, чтобы получить критические замечания. Мне нужен инструмент для создания прогрессивных web-приложений, и чем больше будет конструктивной критики, тем выше вероятность, что получится хороший инструмент. Ну и, как говорится, одна голова — хорошо, а с мозгами лучше.
Во-вторых, без объяснения, что такое плагины, области кода в приложении, как добавляются CLI-команды в приложение, будет очень сложно объяснить, как добавляется web-сервер и как консольное приложение превращается в web-приложение.
Спасибо всем, кто дочитал. Можно пинать.
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, WebGL] Знакомство фронтендера с WebGL: рефакторинг, анимация (часть 4)
- [JavaScript, WebGL] Знакомство фронтендера с WebGL: четкие линии (часть 3)
- [JavaScript, WebGL] Знакомство фронтендера с WebGL: первые наброски (часть 2)
- Выпуск jsii 1.31, генератора кода C#, Go, Java и Python из TypeScript
- [JavaScript, WebGL] Знакомство фронтендера с WegGL (часть 1)
- [JavaScript] DTO в JS
- [JavaScript, Разработка мобильных приложений] Разработка под iOS без Xcode
- [JavaScript] Как я писал тестовое задание на Angular и почему некоторым разработчикам не стоит давать тестовое задание
- [JavaScript, Node.JS] Создаем свой сайт или блог на Ghost в образе Docker
- [JavaScript, Алгоритмы] Быстрая математика для графиков, на примере вычисления среднего
Теги для поиска: #_javascript, #_teqfw, #_dependency_injection, #_es6, #_plugin, #_commander, #_cli, #_javascript
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 14:13
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Эта статья не о том, как нужно писать приложения на JavaScript'е. Эта статья о том, как можно писать приложения на JavaScript'е. В прошлой публикации я описал свой "велосипед" — DI-контейнер @teqfw/di. В этой я покажу, каким образом его можно применять для создания консольных приложений. Сразу отмечу, что речь идет о "чистом" JavaScript (ECMAScript 2015+ aka ES6+). Я признателен авторам TypeScript за то влияние, которое он оказал на развитие JS, но считаю, что в 2021-м году отличия TS от JS не столь драматические, как это было в году 2012-м, и не вижу для себя смысла использовать TS там, где достаточно JS. Если вы считаете по-другому и имеете острое желание высказать своё мнение, то можете сразу переходить к комментам, пропустив саму публикацию. Те же, кому интересно, как же всё-таки в JS-приложении может использоваться "логическая адресация" элементов кода (пространства имён) вместо "физической" (файловая система) — добро пожаловать под кат. Определения Для начала зафиксирую некоторые термины:
Области кода в приложении В общем случае модули приложения можно разбить на две большие группы:
Отсюда логично вытекает третья группа — смешанная. Модули из этой группы могут использоваться как в nodejs на сервере, так и в браузерах. Это могут быть различные утилиты/хэлперы, а также DTO, описывающие данные передаваемые между браузером и сервером. В плагинах файловая структура исходников делится на три области:
В коде модулей из области ./Back/ допускается использование инструкций import для обращения к API nodejs и к npm-пакетам, не являющимся плагинами (без дескриптора ./teqfw.json). import {dirname, join} from 'path';
В коде модулей ./Front/ и ./Shared/ зависимости подтягиваются через DI-контейнер, без использования иструкций import, касающихся API nodejs и npm-пакетов, не являющихся плагинами. Возможно использование import с относительной адресацией внутри одного пакета или с абсолютной адресацией статического ресурса с исходным кодом, но лучше обходиться без этого. В контексте данной публикации будет рассматриваться только код из back-области. Bootstrap Консольное приложение — это nodejs-приложение. Чтобы это nodejs-приложение могло использовать DI-контейнер, его нужно загрузить обычным способом (через import) из соответствующего npm-пакета (@teqfw/di): import Container from '@teqfw/di';
После чего создать DI-контейнер и настроить его на использование пространств имён в пакетах @teqfw/di и @teqfw/core: /** @type {TeqFw_Di_Shared_Container} */
const container = new Container(); const srcCore = join(root, 'node_modules/@teqfw/core/src'); const srcDi = join(root, 'node_modules/@teqfw/di/src'); container.addSourceMapping('TeqFw_Core', srcCore, true, 'mjs'); container.addSourceMapping('TeqFw_Di', srcDi, true, 'mjs'); Теперь DI-контейнер сможет находить модули с исходным кодом по их логическим идентификаторам (namespace'ам) в пакете @teqfw/core. Создаём экземпляр teq-приложения, инициализируем его, передавая через входной параметр путь к корню всего проекта и текущую версию приложения, и запускаем: const app = await container.get('TeqFw_Core_Back_App$');
await app.init({path: root, version: '0.0.1'}); await app.run(); Полный код bootstrap-скрипта './bin/tequila.mjs'SPL#!/usr/bin/env node
'use strict'; import {dirname, join} from 'path'; import Container from '@teqfw/di'; const url = new URL(import.meta.url); const script = url.pathname; const bin = dirname(script); const root = join(bin, '..'); try { /** @type {TeqFw_Di_Shared_Container} */ const container = new Container(); const pathDi = join(root, 'node_modules/@teqfw/di/src'); const pathCore = join(root, 'node_modules/@teqfw/core/src'); container.addSourceMapping('TeqFw_Di', pathDi, true, 'mjs'); container.addSourceMapping('TeqFw_Core', pathCore, true, 'mjs'); /** @type {TeqFw_Core_Back_App} */ const app = await container.get('TeqFw_Core_Back_App$'); await app.init({path: root, version: '0.1.0'}); await app.run(); } catch (e) { console.error('Cannot create or run TeqFW application.'); console.dir(e); } Полный код может использоваться в качестве стартового для всех teq-приложений практически без изменения (нужно менять только номер текущей версии и путь к корню приложения). Плагины Функциональность к приложению добавляется за счёт плагинов — npm-пакетов, у которых в корне пакета находится "дескриптор плагина" (файл ./teqfw.json). Структура файла зависит от того, какие именно плагины используются в приложении, но минимальное содержимое, соответствующее плагину @teqfw/di, такое: {
"di": { "autoload": { "ns": "Vnd_Plugin", "path": "./src" } } } Эти инструкции позволяют конфигурировать DI-контейнер в приложении и маппить используемые пространства имен на файловую систему (см. "Загрузка исходников"). При старте приложения запускается сканер плагинов TeqFw_Core_Back_Scan_Plugin, который пробегает по всем пакетам приложения и ищет те, которые являются плагинами (для которых есть дескриптор ./teqfw.json). Сканер добавляет найденные плагины в реестр плагинов TeqFw_Core_Back_Scan_Plugin_Registry, который доступен любому элементу кода в приложении через DI-контейнер. Структура дескриптора не определена — каждый плагин может искать в дескрипторах других плагинов понятную ему информацию. Выше я привёл часть дескриптора, которая используется DI-плагином (полная структура дескриптора для DI-плагина — в модуле TeqFw_Di_Back_Api_Dto_Plugin_Desc). Команды Core-плагин использует внутри пакет commander и предоставляет сторонним плагинам интерфейс для добавления своих консольных команд к приложению. Для этого в стороннем плагине должен быть модуль, default-экспорт которого возвращает фабрику по созданию соответствующей команды (структура команды в TeqFw_Core_Back_Api_Dto_Command), а идентификатор этого модуля должен быть зарегистрирован в дескрипторе этого стороннего плагина. Вот пример инструкций, подключающих в ./teqfw.json демо-проекта CLI-команду для вывода списка плагинов, используемых в приложении: {
"core": { "commands": [ "Fl64_Habr_Back_Cli_PluginsList" ] } } Содержимое модуля Fl64_Habr_Back_Cli_PluginsList выглядит примерно так (оставлен только значимый для создания команды код): export function Factory(spec) {
// EXTRACT DEPS /** @type {TeqFw_Core_Back_Api_Dto_Command.Factory} */ const fCommand = spec['TeqFw_Core_Back_Api_Dto_Command#Factory$']; // COMPOSE RESULT const res = fCommand.create(); res.realm = 'demo'; res.name = 'plugins-list'; res.desc = 'Get list of teq-plugins.'; res.action = function() {/* ... */}; return res; } Так как core-плагин собирает CLI-команды из других плагинов приложения, то каждый плагин определяет свой риэлм (область), в котором он размещает свои команды. Это позволяет снизить вероятность возникновения конфликтов между одноимёнными командами из разных плагинов. В результате, при запросе справки по консольным командам приложения, новая команда появляется в списке: $ node ./bin/tequila.mjs help
Usage: tequila [options] [command] Options: -h, --help display help for command Commands: demo-plugins-list [options] Get list of teq-plugins. core-startup-logs print out startup logs from the application core. core-version get version of the application. help [command] display help for command Добавление опций в коде выше отсутствует, можно посмотреть в исходниках: $ node ./bin/tequila.mjs help demo-plugins-list
Usage: tequila demo-plugins-list [options] Get list of teq-plugins. Options: -s, --short get plugins names and namespaces -f, --full get plugins names, namespaces and path to the sources directory -h, --help display help for command Резюме
Для чего я написал эту статью? Во-первых, чтобы получить критические замечания. Мне нужен инструмент для создания прогрессивных web-приложений, и чем больше будет конструктивной критики, тем выше вероятность, что получится хороший инструмент. Ну и, как говорится, одна голова — хорошо, а с мозгами лучше. Во-вторых, без объяснения, что такое плагины, области кода в приложении, как добавляются CLI-команды в приложение, будет очень сложно объяснить, как добавляется web-сервер и как консольное приложение превращается в web-приложение. Спасибо всем, кто дочитал. Можно пинать. =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 14:13
Часовой пояс: UTC + 5