Комментарии коллег к моей последней статье "Почему я 'мучаюсь' с JS" навели меня на мысль, что публикации, касающиеся Tequila Framework, нужно помещать в хаб "Ненормальное программирование".
Почему-то идеи:
- создание больших web-приложений на "ванильном" JS;
- отказ от упаковщиков и транспиляторов;
- логическое пространство имён для прямого доступа к es6-модулям зависимостей, вместо импорта-экспорта на уровне npm-пакетов;
- автозагрузчик кода и инъекция зависимостей, использующие пространство имен;
- es6-модули, работающие без изменений одинаково как в браузере, так на стороне nodejs и в тестах;
- отладка в браузере точно того же кода как тот, что создаётся в редакторе;
вот это всё относится к разряду "ненормального" в современной web-разработке.
В этой публикации — пример построения "нормального" PWA-приложения с использованием "нормальных" Vue 3 и Quasar UI на базе "ненормальной" платформы Tequila Framework.
В качестве базового функционала, реализуемого приложением, выступает классический "Список задач" (ToDo List). В качестве дополнительного — интернационализация (переключение языка) и очистка кэша приложения (актуально для прогрессивных приложений).
Демо
Репозиторий демо-пакета — @flancer64/habr_teqfw_vue.
Само приложение — todo.habr.demo.teqfw.com.
Структура приложения
Основной пакет, в котором реализовано приложение: @flancer64/habr_teqfw_vue. Основные зависимости пакета:
- @teqfw/http2: тянет за собой остальные пакеты платформы (web, core, di);
- @teqfw/i18n: пакет-обёртка для пакетов i18next и i18next-browser-languagedetector;
- @teqfw/ui-quasar: пакет-обёртка для quasar v2;
- @teqfw/vue: пакет-обёртка для vue v3 и vue-router v4
Всего в приложении 32 зависимости (commander, vue, vue-router, i18n, ...).
Пакеты-обёртки
Для teq-плагинов (npm-пакетов, совместимых с платформой TeqFW) каждый es6-модуль плагина доступен для автозагрузки посредством совмещения логического пространства имён плагина с файловой структурой es6-модулей. Так для демо-плагина в дескрипторе ./teqfw.json
декларируется пространство имён Fl64_Habr_Vue
:
{
"di": {
"autoload": {
"ns": "Fl64_Habr_Vue",
"path": "./src"
}
}
}
После чего es6-модуль ./src/Front/Mod.mjs
становится доступным для автозагрузки DI-контейнером по идентификатору Fl64_Habr_Vue_Front_Mod
как для nodejs-приложения, так и в браузере.
Чтобы в браузере иметь доступ к библиотекам, написанным на JS и не совместимым с платформой, нужно:
- загрузить соответствующую библиотеку на страницу обычным способом (например, через HTML тэг
script
); - добавить объект-обёртку, который при инстанциализации находит в globals нужную библиотеку и предоставляет к ней доступ остальным объектам teq-приложения через DI-контейнер;
Подключение i18next на стартовую страницу приложения:
<script type="application/javascript" src="./src/i18n/i18next.min.js"></script>
Пакет-обёртка в своих зависимостях содержит npm-пакет с соответствующей библиотекой и прокидывает (в своём ./teqfw.json
) на её рабочий код (например, каталог ./dist/umd/
) линк для обработчика статических файлов из web-плагина:
{
"web": {
"statics": {
"/i18n/": "/i18next/dist/umd/"
}
}
}
Таким образом дистро-код пакета i18next
становится доступным для загрузки в браузер.
Из браузера дистро-код оригинального пакета извлекается объектом-обёрткой (в конструкторе или init-функции):
if (window.i18next) {
this.#i18n = window.i18next;
}
после чего он становится доступным в DI-контейнере через обёртку:
export default class TeqFw_I18n_Front_Lib {
// ...
getI18n() {
return this.#i18n;
}
}
Разумеется, объект-обёртка должен создаваться уже после того, как необходимые библиотеки были загружены на страницу.
Пакеты-обёртки позволяют использовать в teq-приложениях любые браузерные JS-библиотеки, доступные через npm.
Оболочка приложения
Web-приложение обычно начинается с HTML-файла (демо — ./web/index.html). В нём можно выделить три секции:
<head>
<!-- Bootstrap JS -->
</head>
<body>
<!-- Launchpad -->
<!-- Resource Loading -->
</body>
Launchpad — это фрагмент страницы, в который будет помещено js-приложение после его инициализации и запуска:
<div>
<app-root>
<div class="launchpad">TeqFW App is loading...</div>
</app-root>
</div>
Resource Loading — это часть, в которой на страницу загружаются ресурсы, несовместимые с TeqFW:
<script type="application/javascript" src="./src/vue/vue.global.prod.js"></script>
<link rel="stylesheet" href="./src/quasar/quasar.prod.css">
Bootstrap JS — это код, который отслеживает окончание загрузки страницы и стартует загрузку и запуск teq-приложения:
<script>
async function bootstrap() {}
if ("serviceWorker" in navigator) {
self.addEventListener(
"load",
async () => { /* check service worker then bootstrap */ }
);
}
</script>
Service Worker
В демо-приложении в задачи service worker'а (./sw.mjs) входит:
- кэширование минимального набора файлов для того, чтобы приложение могло стартовать offline;
- сохранение в кэше всех статических ресурсов, к которым обращается приложение, для ускорения работы при повторных запросах;
- очистка кэша по команде из приложения;
Код проверки наличия service worker'а и, при необходимости, его установки на странице-оболочке:
if ("serviceWorker" in navigator) {
self.addEventListener("load", async () => {
const worker = navigator.serviceWorker;
if (worker.controller === null) {
try {
const reg = await worker.register("sw.js");
if (reg.active) {
await bootstrap();
} else {
worker.addEventListener("controllerchange", async () => {
await bootstrap();
});
}
} catch (e) {/* ... */}
} else {
await bootstrap();
}
});
}
Подробнее о service worker'е, манифесте и оболочке — "Минимальное PWA"
Bootstrap
В задачи bootstrap-функции входит:
- импорт DI-контейнера "нормальным" способом;
- получение с сервера карты сопоставления пространств имён адресам на сервере для загрузки es6-модулей teq-плагинов и карты замещения модулей ("интерфейсы" и "имплементации");
- настройка DI-контейнера;
- загрузка через DI-контейнер основного модуля приложения, его инициализация и монтирование корневого vue-компонента на страницу-оболочку;
async function bootstrap() {
async function initDiContainer() {
const baseUrl = `${location.origin}${location.pathname}`;
const modContainer = await import('./src/@teqfw/di/Shared/Container.mjs');
/** @type {TeqFw_Di_Shared_Container} */
const container = new modContainer.default();
const res = await fetch('./api/@teqfw/web/load/namespaces');
const json = await res.json();
if (json?.data?.items && Array.isArray(json.data.items))
for (const item of json.data.items)
container.addSourceMapping(item.ns, (new URL(item.path, baseUrl)).toString(), true, item.ext);
if (json?.data?.replaces && Array.isArray(json.data.replaces))
for (const item of json.data.replaces)
container.addModuleReplacement(item.orig, item.alter);
return container;
}
try {
const container = await initDiContainer();
/** @type {Fl64_Habr_Vue_Front_App} */
const app = await container.get('Fl64_Habr_Vue_Front_App$');
await app.init();
app.mount('BODY > DIV');
} catch (e) {...}
}
Приложение
Так как у некоторых коллег были вопросы по моему стилю оформления кода (раз, два, три), то в этом демо-проекте я придерживался более "нормального" стиля (благо, что в Apple наконец-то подсуетились и в апреле этого года добавили поддержку private
-атрибутов в Safari):
export default class Fl64_Habr_Vue_Front_App {
/** @type {TeqFw_Web_Front_Model_Config} */
#config;
/** @type {TeqFw_I18n_Front_Lib} */
#I18nLib;
...
constructor(spec) {
this.#config = spec['TeqFw_Web_Front_Model_Config$'];
this.#I18nLib = spec['TeqFw_I18n_Front_Lib$'];
...
}
Модуль, содержащий приложение — Fl64_Habr_Vue_Front_App. Задачи приложения:
- получение из контейнера всех требуемых зависимостей;
- инициализация подсистем приложения (конфигурация, i18next, vue, quasar);
- создание корневого vue-компонента;
- монтирование корневого vue-компонента на страницу-оболочку;
Фабрики для vue-компонентов
В TeqFW es6-модуль, создающий объекты, можно оформить в виде фабричной функции или класса. Так как vue-компоненты по сути являются объектами-шаблонами, которые Vue использует в качестве базовых при превращении соответствующих тэгов в фрагменты страницы, то они используются в приложении в единственном числе (один шаблон на всё приложение). Для создания таких объектов-шаблонов достаточно фабричной функции. Вот типовая структура такой функции:
const NS = 'Fl64_Habr_Vue_Front_Layout_Base';
export default function Factory(spec) {
// EXTRACT DEPS
/** @type {TeqFw_Vue_Front_Lib} */
const VueLib = spec['TeqFw_Vue_Front_Lib$'];
// DEFINE WORKING VARS
const {ref} = VueLib.getVue();
const template = `...`;
// COMPOSE RESULT
return {
name: NS,
template,
// ...
};
}
// to get namespace on debug
Object.defineProperty(Factory, 'name', {value: `${NS}.${Factory.name}`});
Связывание vue-компонентов
Используя DI-контейнер и фабричную функцию для создания шаблона vue-компонента, можно инжектить результирующие singleton-шаблоны в другие фабричные функции для создания других vue-компонентов:
function Factory(spec) {
// EXTRACT DEPS
/** @type {Fl64_Habr_Vue_Front_Layout_Navigator.vueCompTmpl} */
const navigator = spec['Fl64_Habr_Vue_Front_Layout_Navigator$'];
return {
components: {navigator}
}
}
Символ $
в конце идентификатора зависимости означает, что DI-контейнер загружает соответствующий es6-модуль, берёт из него default-экспорт и создаёт при помощи этого экспорта (функции или класса) объект, который затем сохраняет у себя внутри и использует каждый раз, когда встречает аналогичную зависимость (шаблон singleton).
Таким образом, все фабричные функции для создания шаблонов vue-компонентов отрабатывают только по одному разу, а в дальнейшем контейнер переиспользует результат первого запуска (один и тот же объект). Если бы в конце стояло два символа — $$
, то контейнер использовал бы фабричную функцию всякий раз, когда встречал соответствующий идентификатор зависимости, и в конструкторы бы инжектились разные объекты.
Маршрутизация
Vue Router позволяет сделать ленивую загрузку vue-компонентов через динамический импорт:
const UserDetails = () => import('./views/UserDetails')
const router = createRouter({
// ...
routes: [{ path: '/users/:id', component: UserDetails }],
})
Что очень хорошо совмещается с работой DI-контейнера:
router.addRoute({
path: '/',
component: () => container.get('Fl64_Habr_Vue_Front_Route_Home$')
});
В результате соответствующие es6-модули подгружаются в браузер по мере использования (перехода на соответствующий маршрут PWA/SPA).
I18n
Для локализации текстовых вставок применяется npm-пакет i18next и teq-обёртка для него — @teqfw/i18n. JSON-ресурсы с переводами находятся в каталогах
-
./i18n/
./back/
./front/
./share/
в файлах ru.json
, en.json
, ...
На серверной стороне реализован реестр TeqFw_I18n_Back_Model_Registry, который при старте backend-приложения сканирует teq-плагины в каталоге ./node_modules/
и формирует массив с переводами, совместимый с i18next
. Имя npm-пакета при этом используется в качестве namespace'а для i18n-ресурсов соответствующего плагина:
{
"lng": {
"@vnd/plugin": {
"resource": "translation"
}
}
}
Для доставки translation-ресурсов на фронт используется объект-обёртка TeqFw_I18n_Front_Lib и сервис TeqFw_I18n_Back_Service_Load.
Функция Fl64_Habr_Vue_Front_App.init.initI18n
инициализирует обёртку, загружая соответствующие ресурсы с сервера, и добавляет пропатченную функцию-транслятор во Vue:
async function initI18n(app, I18nLib) {
await I18nLib.init(['en', 'ru'], 'en');
const appProps = app.config.globalProperties;
const i18n = I18nLib.getI18n();
// add translation function to Vue
appProps.$t = function (key, options) {
// add package name if namespace is omitted in the key
const ns = this.$options.teq?.package;
if (ns && key.indexOf(':') <= 0) key = `${ns}:${key}`;
return i18n.t(key, options);
}
}
С пропатченной функцией-траслятором, во vue-компонентах, у которых присутствует атрибут teq.package
можно не использовать i18next namespaces, как в html-шаблонах:
<div>{{$t('widget.cfg.lang.title')}}:</div>
так и в JS-коде:
computed: {
optsLang() {
return [
{label: this.$t('widget.cfg.lang.lang.en'), value: 'en'}
];
},
}
Если в функции-трансляторе i18next namespace используется в явном виде:
<div>{{$t('@vnd/plugin:myKey')}}:</div>
то можно напрямую использовать ресурсы из любого teq-плагина приложения.
Функционал
Список дел
Основной компонент — Fl64_Habr_Vue_Front_Route_Home. Виджеты:
- Fl64_Habr_Vue_Front_Widget_ToDo_New
- Fl64_Habr_Vue_Front_Widget_ToDo_List
- Fl64_Habr_Vue_Front_Widget_ToDo_Item
Конфигурация
Основной компонент — Fl64_Habr_Vue_Front_Route_Cfg:
Переключение языка
Виджеты:
С переключением языка интересно. Нужно не только изменить состояние i18next-объекта, который находится в globals, но и перерисовать весь UI с новыми переводами. Для этого во Vue предлагается использовать :key
атрибут в самом верхнем элементе иерархии vue-компонентов (у меня — в Fl64_Habr_Vue_Front_Layout_Base
):
<q-layout view="..." :key="langChange">
...
</q-layout>
И инкрементировать его каждый раз, когда необходима перерисовка UI'я. Проблема в том, что перерисовку нужно начинать с корня иерархии компонентов, а сигнал на перерисовку — подаваться из глубины иерархии (из Fl64_Habr_Vue_Front_Widget_Cfg_Lang
). Во Vue 3 для этого предлагается использовать пару provide / inject. В теории можно создать в Layout_Base
реактивный объект и предоставить его дочерним компонентам через provide
, а во вложенном Widget_Cfg_Lang
получить реактивный объект и изменить его состояние, чтобы перерисовать UI, начиная с самого верха. Вот только состояние объекта изменяется (это видно под отладчиком), а перерисовки не происходит.
Поэтому "грязный хак" — реактивный объект вешается на функцию-конструктор:
function Factory(spec) {
// ...
return {
// ...
setup() {
const langChange = ref(0);
Factory.langChangeCounter = langChange;
return {langChange};
}
};
}
а затем реактивный объект извлекается в Widget_Cfg_Lang
:
/** @type {Fl64_Habr_Vue_Front_Layout_Base.Factory} */
const BaseLayoutFactory = spec['Fl64_Habr_Vue_Front_Layout_Base#'];
// ...
watch: {
fldLang(current, old) {
// ...
BaseLayoutFactory.langChangeCounter.value++;
}
}
Вот так — работает, а через Vue 3 provide / inject — нет. Возможно, я что-то криво делаю с "родным DI" (хотя порядок создания и доступа к реактивному объекту правильный и сам объект langChangeCounter
изменяется при смене языка).
Очистка кэша
Поскольку это всё-таки PWA, то кэширование статики на уровне service worker'а добавляет некоторой специфики. Во-первых, приложение получает возможность работать offline, а во-вторых — приложению нужен какой-то способ получать с сервера изменения для файлов, сохранённых в кэше. В демо-приложении применяется радикальный способ — пользователь может полностью удалить кэш service-worker'а прямо из приложения:
В Chrome есть инструменты, облегчающие жизнь PWA-разработчику:
но в смартфонах очистку кэша лучше вынести на уровень пользователя.
Резюме
Демо-проект показывает, как можно интегрировать в "ненормальное" teq-приложение код "нормальных" браузерных npm-пакетов, написанных на JS (транспилированных в JS из других языков). Я использовал Vue, потому что мне он больше по душе, но уверен, что точно так же можно оборачивать React (насчёт Angular'а не уверен, т.к. он сам по себе — платформа):
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
В общем, всё, что может загрузиться на страницу — можно обернуть и сделать доступным через DI-контейнер (в nodejs таких проблем нет, там и так всё доступно через связки export-import пакетного уровня). Мне понравился Vue 3 и Quasar UI, поэтому мой ToDo List выглядит так (за выбор цветовой гаммы прошу сильно не пинать — я начинал с монохромных дисплеев).
Несмотря на то, что иногда мой код считают "диалектом", каждый отдельный mjs-файл — это валидный JS. Его можно импортировать как обычный es6-модуль без всяких дополнительных ухищрений и использовать в своём коде. Вот только зависимости в spec
-объект конструкторов и фабричных функций придётся добавлять вручную.
Самым ценным для себя я считаю знакомую структуру es6-модулей в браузере:
Сравните со структурой в IDE:
Разница в файлах объясняется тем, что в браузер подгрузились не все модули — "loading on demand" (хотя в кэше service-worker'а находятся все модули).
Также обратите внимание на удобство использования namespace'ов при документировании приложения:
Fl64_Habr_Vue_Front_Widget_ToDo_Item
Fl64_Habr_Vue_Front_App.init.initI18n
Некоторые считают, длинные идентификаторы для объектов кода не слишком удобными, но чем больше в приложении объектов кода (классов, функций, констант), тем длиннее становятся идентификаторы. Просто мы их прячем в имена npm-пакетов и пути к соответствующему файлу внутри пакета. Так что длинные идентификаторы — это просто следствие больших проектов.
И вообще, прямой import es6-модуля через DI-контейнер из любого пакета с использованием логического пространства имён позволяет строить "модульные монолиты" — когда монолитное приложение собирается из модулей (пакетов), но запускается как единое целое. Более того, ничего не мешает одним и тем же пакетам быть как частями большого "монолита", так и частями "микросервисов", обслуживающих этот "монолит". Код из shared-секций пакетов может, например, работать в back-секциях "микросервисов" и во front-секциях "монолита" (или "монолитов").
Да, я в курсе, что ровно то же самое можно сделать и без всякого DI, на основе "нормальных" export'ов и import'ов. Вот поэтому я и поместил публикацию в хаб "Ненормальное программирование". А что касается хаба "JavaScript" — ну так это самый, что ни на есть JS и есть.
Комментарии (6)
SerafimArts
31.07.2021 17:58Ходят слухи, что container.get (т.е. сервис локация) — это антипаттерн, за который обычно по рукам бьют))) А красивый DI с блекджеком и автовайрингом в JS не запилить по той причине что это JS. Вот и не тащат контейнер во фронтэнд особо, да и реализаций нормальных нет.
flancer Автор
31.07.2021 22:53Да, есть такие слухи. Я даже как-то эту мысль катал туда-сюда. Вы старайтесь не использовать
container.get()
вне composition root и уменьшите негативное влияние этого "антипаттерна" на ваше приложение. И останется только позитивное ;)Мне не нужен "красивый DI", мне достаточно того, который у меня есть. Он не красив, но мне нужно, чтобы он работал - и он работает. Несмотря на то, что это JS.
crispart
Читая очередную вашу статью меня посещают сильно смешанные впечатления.
Могу предположить, что коллеги рекомендовали вам перенести статьи в раздел «ненормальное программирование» во многом потому, что глядя на способы решения описанных вами проблем, в общем и целом может показаться, что не такие уж это и проблемы. Кроме того, применено достаточно практик, чуждых в мире веб-разработки, вплоть до именования ваших сущностей, что вкупе с текущим состоянием фреймворка кажется лишь созданием новых проблем, а не решением существующих, ведь решается их не так много, а взамен приносится целый фреймворк, навязывающий своё видение процесса, который кто-то должен включить в проект, сопровождать, нести за это ответственность, обучать команду и следить, чтобы очередное обновление ES/сборщиков/транспиляторов/etc не поломали шатко выстроенное взаимодействие модулей.
Безусловно, многие понимают, что веб и JS в частности имеет достаточно много кажущихся «зелёными» для многих популярных языков проблем. Конечно, изыскиваются способы их решения и действительно толковые начинания только приветствуются сообществом, и я хочу лишь искренне пожелать вам успехов в делах, как и терпения в работе с аудиторией, в частности, хабра. Здесь действительно много крутых специалистов, достаточно прошаренных в вебе и знающих не по наслышке обо всех имеющихся тут проблемах и, как это часто бывает, если какие-то начинания воспринимаются здесь в штыки, то это вовсе не потому, что начинания такие плохие, а потому, что у автора есть, конечно же, совершенно верное видение процесса и с мнением сообщества он считаться не намерен, в то же время продолжая насыпать релизов и статей. Вот и вся причина недопонимания.
flancer Автор
Спасибо за своего рода поддержку. У меня действительно есть своё видение процесса создания PWA. И я использую сообщество Хабра для проверки этих идей "на излом". Вряд ли можно назвать "популяризацией" серию статей, где в комментах изложенное называют глупостью. Тем не менее, здесь действительно полно специалистов (и крутых, и начинающих) и их замечания дают пищу для размышлений, заставляя обращать внимание на ключевые моменты.
Например, я раньше не обращал внимания на роль main в npm-пакетах. А ведь это способ сокрытия "приватного" кода пакета и документирование "публичного". С подобной проблемой столкнулись в Magento 2 (когда код в отдельном composer-модуле разросся настолько, что нужно было как-то отделить публичную часть модуля от "внутренней кухни") - вынесли "публичный" код в пространство .../Api/.... Тем не менее, подобное сокрытие (что в Magento, что в nodejs) - это такое "джентльменское соглашение". Ничто не мешает "шулеру" (например, моему module loader'у) напрямую обратиться к "приватным" скриптам внутри пакета и поиметь выгоду. Хорошо это или плохо? Зависит от решаемой задачи и применяемых критериев оценки.
Что касается ответственности за риски - ну так риски есть всегда (и с React, Vue, Angular, ...) и ответственность за них тоже. У меня тоже есть риски - я вкладываю своё время в изучение особенностей работы PWA в расчёте на то, что разработчики браузеров продолжат развивать идеи Стива Джобса об использовании web-приложений в смартфонах, от которых Apple отказалась, а Google подхватил.
В отличие от многих других IT-специалистов я очень долго вникаю в языки и платформы. Это другие могут прочитать описание платформы и уже через пару дней лепить с её помощью приложения. Мне нужно пару лет практики, чтобы я мог более-менее уверенно сказать, что я программирую на каком-то языке или использую платформу. И если с языками попроще, то платформы (framework'и) появляются по дюжине на год - я просто не успеваю (и не только я - в Magento до сих пор knockout используется). Вот я и подумал, а запилю-ка я свой framework с DI и namespace'ами. Такой, где удобно будет именно мне. Потому что, что бизнесу, что пользователям - им же всё равно, на чём это сделано. Лишь бы работало, было удобно и денег приносило. И изменялось по-быстрому, если что.
crispart
По большей части со всем согласен. Для локального применения, если оно гладко внедрилось и успешно используется, безусловно, все средства хороши.
Мы в своё время несколько раз подходили к этому вопросу и в итоге каждый раз убеждались в том, что в родных es-импортах ничего страшного нет. Особенно это известно матёрым веб-разработчикам, которые годами, десятилетиями не могли получить на фронте нечто подобное) так что это в каком-то плане уже прорыв и, на мой взгляд, используется вполне приятно.
Свои решения оказывались либо громоздкими, тащущими много бойлерплейта, либо же содержащих массу сложных абстракций, что затрудняло посвящение в проект новых участников. В конечном итоге решили отказаться от этой затеи.
Сейчас используем в новом проекте Vue 3 с TypeScript. Впечатления только положительные. К вопросам импортов не относится, но делает, на мой скромный взгляд, куда более аккуратной и надежной типизацию в вашем проекте. В любой современной IDE вышеназванный стек отлично поддерживается, типизация прекрасно работает. В новом Vue сделали большой шаг к решению проблем более ранних версий по части вертикального обмена данных между компонентами, есть нативная поддержка TS. Багов мы не замечали.
Было бы интересно, я думаю, увидеть ваше видение решения поднятых вопросов в виде чего-то более близкого к принятым практикам в JS/TS и с минимальным количеством бойлерплейта и существенного усложнения/переделки новых и уже существующих кодовых баз. Кто знает, вдруг вам удастся придумать библиотеку, которую мы включим в следующий проект)
P.S. Справедливости ради: пишу код чуть меньше 10 лет. Пробовал много разных языков и видел много разных людей. Видел людей, способных через пару дней «слепить» на чём-то новом, да и сам таким, вроде бы, являюсь, но чтобы можно было уверенно сказать, что «программирую» это разве что какие-то уникумы) на качественное освоение возможностей языка и окружения уходят месяцы, годы. Так что всё у вас с этим в порядке, вы просто обладаете здоровым уровнем самокритичности)
flancer Автор
В TS однозначно лучше организованы подсказки типов в интерфейсах функций, чем это сделано в JS (их там вообще нет). Я полагаю, что положительные моменты из TS так и будут продолжать перетекать в JS. Возможно, перетекут и указания типов входных-выходных параметров (вряд ли как гарантия, скорее, как ожидание). Если добавят ожидание типов входных-выходных аргументов на уровне языка, то вот и база для того, чтобы рефлексия в JS стала более информативной. Этого уже хватит, чтобы появился DI, основанный на свойствах самого языка (и их рефлексии), а не на придуманных конструкциях типа проксированного
spec
'а. А если добавят в язык ещё и пространства имён (JS долгое время не предполагался быть языком для написания приложений с обширной кодовой базой - до появленияnpm
в 2010-м так уж точно), то можно будет выкинуть Zend1-like наименования классов (в TS пространства имён есть).Т.е., в JS появится "стандартный DI", принимаемый всем сообществом, а не поделки типа моей. Я самоуверенно предполагаю, что не будет являться большой проблемой перевести мой код с использования одного DI-контейнера на использование другого. Ведь в чём основная фишка DI'я? Код, которым оперирует контейнер, не должен ничего знать о самом контейнере. Это просто обычные es6-модули, который могут импортиться вручную. Или через мой DI. Или через любой другой DI. Нужно будет добавить трансляцию типов из одной "системы счисления" (Zend1-like) в другую (какая будет). Ну а если ничего такого не появится, то я так и буду пользоваться сам своими инструментами. Это просто такой способ организации кода в рамках отдельного проекта. Или группы проектов. Это не "универсальная отмычка", а спец. инструмент для выполнения определённой работы - создания PWA.