Я не знаю TypeScript, поэтому и пишу эту статью. У меня есть некоторый опыт программирования на Java и PHP и этот опыт заставляет меня кодировать на JavaScript'е соответствующим образом. К последней моей статье коммент от коллеги Silverthorne был такой:
Код в примере — это рабочий код на JS. И он написан в стиле, который сформировался у меня за время работы с Java и PHP. Я привык, что я могу разбивать код своего проекта на пакеты. Что я могу размещать пакеты в отдельных git-репозиториях. Что maven и composer помогают мне собирать проект из пакетов. Что я могу использовать одни и те же пакеты в разных проектах. Что в рамках одного проекта код из одного пакета может свободно использовать код из другого пакета.
Что-то подобного я ожидал и от JavaScript'а после того, как в нём появились nodejs (2009) и npm (2010). Так как JavaScript преимущественно использовался в браузерах, то пакеты начали добавлять в проекты (сначала вручную, затем через системы сборки — grunt и webpack c 2012-го, gulp с 2013-го), а в браузер загружали код при помощи requirejs (2009) и browserify (2011). Появились различные форматы загружаемых модулей (скриптов) — AMD, CommonJS, UMD. В 2012-м даже появились типы, пусть и с транспиляцией — TypeScript. Типы без транспиляции появились в 2015-м (EcmaScript 6). В общем, всё указывало на то, что JS стал "взрослым" языком.
Для меня, после почти десятка лет программирования на PHP, привычен такой код конструктора:
public function __construct(
\Magento\Framework\App\Action\Context $context,
\Flancer32\BotSess\Service\Clean\Files $servClean
) {...}
Это нормально, когда я указываю в конструкторе просто тип зависимости, а среда выполнения сама определяет местоположение файла с исходником, подгружает соответствующий код и создаёт требуемую зависимость. Без всяких include, import, require — я ведь уже указал тип зависимости.
А вот это, с моей точки зрения, ненормально:
import UserController from './controllers/UserController';
...
const userController = Container.get(UserController);
и это тоже:
import UserService from "../services/UserService";
class UserController {
constructor(private readonly userService: UserService) {}
}
Я в коде дважды определяю, какая зависимость мне нужна — при импорте класса и в контейнере (конструкторе). В PHP так писали до 2004-го года (__autoload), а в Java так не писали вообще никогда (Java'вский import ничего не подгружает, а работает как alias, можно и без import'ов, если указывать полные имена классов).
Более того, в пределах одного npm-пакета я завязан на файловую структуру (import ... from './.../...', а при межпакетном взаимодействии я должен в package.json пакета-донора прописывать экспорты до соответствующего файла.
"main": "index.js",
"exports": {
".": "./index.js",
"./promise": "./promise.js",
"./promise.js": "./promise.js"
}
Согласен, что в java-package & php-namespace мы привязаны к структуре логического разбиения кода:
Тем не менее, у нас остаётся некоторая свобода внутри этой структуры — при росте пакета com.teqfw.http2 мы можем выделить из него пакет com.teqfw.http2.back в отдельный пакет без изменения зависимого кода. Мне не нужно будет совмещать export пакета-донора с import пакетов-реципиентов. В Java мне вообще ничего не надо совмещать, а в PHP нужно будет прописать соответствующий маппинг в composer.json нового пакета, а всё остальное сделают composer и autoloader.
Когда я в очередной раз решил "прокачать" свои навыки в JS пару лет назад, я сразу же стал искать подходящий DI-контейнер. И я очень удивился, когда столкнулся с необходимостью импортировать исходники самостоятельно. Даже TypeScript'овые библиотеки не давали мне желаемого функционала. А мои желания поначалу были совсем простые — DI-контейнер, одинаково работающий в браузере и в nodejs. Лучшее, что я нашёл — awilix, который работал только для nodejs.
Мне пришлось самому разбираться, в чём отличия работы функции import в браузере и в nodejs. И, разумеется, я делал это на ванильном JS — зачем мне "прокладка" в виде TS, тем более, что любой код на JS также является и валидным кодом на TS?
Я не знаю TS, и не сильно удивлюсь, если кто-то без import'ов напишет пример конструктора, использующего в качестве зависимости скрипт из другого npm-пакета. Но мне будет крайне любопытно изучить библиотеку, которая это делает, и разобраться, как она это делает. Особенно, если эта библиотека работает и в браузере, и в nodejs. Плюсик в карму обещаю (если моей будет достаточно для этого к тому моменту). А возможно это даже станет поводом перейти на TypeScript и "не мучаться", как рекомендовали некоторые коллеги.
Источник статьи: https://habr.com/ru/post/569384/
В ответном комменте я попросил от него продемонстрировать TS-код, который делает то же самое. Он не ответил. Я добавил коммент с просьбой, чтобы кто-угодно продемонстрировал TS-код, который делает то же самое. Ничего. И вот я пишу уже статью с аналогичной просьбой.export default class TeqFw_Http2_Back_Server {
constructor(spec) {
// EXTRACT DEPS
/** @type {Function|TeqFw_Http2_Back_Server_Stream.action} */
const process = spec['TeqFw_Http2_Back_Server_Stream$'];
/** @type {TeqFw_Web_Back_Handler_Registry} */
const registryHndl = spec['TeqFw_Web_Back_Handler_Registry$'];
зачем все это, когда есть TypeScript?
Код в примере — это рабочий код на JS. И он написан в стиле, который сформировался у меня за время работы с Java и PHP. Я привык, что я могу разбивать код своего проекта на пакеты. Что я могу размещать пакеты в отдельных git-репозиториях. Что maven и composer помогают мне собирать проект из пакетов. Что я могу использовать одни и те же пакеты в разных проектах. Что в рамках одного проекта код из одного пакета может свободно использовать код из другого пакета.
Что-то подобного я ожидал и от JavaScript'а после того, как в нём появились nodejs (2009) и npm (2010). Так как JavaScript преимущественно использовался в браузерах, то пакеты начали добавлять в проекты (сначала вручную, затем через системы сборки — grunt и webpack c 2012-го, gulp с 2013-го), а в браузер загружали код при помощи requirejs (2009) и browserify (2011). Появились различные форматы загружаемых модулей (скриптов) — AMD, CommonJS, UMD. В 2012-м даже появились типы, пусть и с транспиляцией — TypeScript. Типы без транспиляции появились в 2015-м (EcmaScript 6). В общем, всё указывало на то, что JS стал "взрослым" языком.
Для меня, после почти десятка лет программирования на PHP, привычен такой код конструктора:
public function __construct(
\Magento\Framework\App\Action\Context $context,
\Flancer32\BotSess\Service\Clean\Files $servClean
) {...}
Это нормально, когда я указываю в конструкторе просто тип зависимости, а среда выполнения сама определяет местоположение файла с исходником, подгружает соответствующий код и создаёт требуемую зависимость. Без всяких include, import, require — я ведь уже указал тип зависимости.
А вот это, с моей точки зрения, ненормально:
import UserController from './controllers/UserController';
...
const userController = Container.get(UserController);
и это тоже:
import UserService from "../services/UserService";
class UserController {
constructor(private readonly userService: UserService) {}
}
Я в коде дважды определяю, какая зависимость мне нужна — при импорте класса и в контейнере (конструкторе). В PHP так писали до 2004-го года (__autoload), а в Java так не писали вообще никогда (Java'вский import ничего не подгружает, а работает как alias, можно и без import'ов, если указывать полные имена классов).
Более того, в пределах одного npm-пакета я завязан на файловую структуру (import ... from './.../...', а при межпакетном взаимодействии я должен в package.json пакета-донора прописывать экспорты до соответствующего файла.
"main": "index.js",
"exports": {
".": "./index.js",
"./promise": "./promise.js",
"./promise.js": "./promise.js"
}
Согласен, что в java-package & php-namespace мы привязаны к структуре логического разбиения кода:
- com.teqfw.http2.back.server.Stream
- \TeqFw\Http2\Back\Server\Stream
Тем не менее, у нас остаётся некоторая свобода внутри этой структуры — при росте пакета com.teqfw.http2 мы можем выделить из него пакет com.teqfw.http2.back в отдельный пакет без изменения зависимого кода. Мне не нужно будет совмещать export пакета-донора с import пакетов-реципиентов. В Java мне вообще ничего не надо совмещать, а в PHP нужно будет прописать соответствующий маппинг в composer.json нового пакета, а всё остальное сделают composer и autoloader.
Когда я в очередной раз решил "прокачать" свои навыки в JS пару лет назад, я сразу же стал искать подходящий DI-контейнер. И я очень удивился, когда столкнулся с необходимостью импортировать исходники самостоятельно. Даже TypeScript'овые библиотеки не давали мне желаемого функционала. А мои желания поначалу были совсем простые — DI-контейнер, одинаково работающий в браузере и в nodejs. Лучшее, что я нашёл — awilix, который работал только для nodejs.
Мне пришлось самому разбираться, в чём отличия работы функции import в браузере и в nodejs. И, разумеется, я делал это на ванильном JS — зачем мне "прокладка" в виде TS, тем более, что любой код на JS также является и валидным кодом на TS?
Я не знаю TS, и не сильно удивлюсь, если кто-то без import'ов напишет пример конструктора, использующего в качестве зависимости скрипт из другого npm-пакета. Но мне будет крайне любопытно изучить библиотеку, которая это делает, и разобраться, как она это делает. Особенно, если эта библиотека работает и в браузере, и в nodejs. Плюсик в карму обещаю (если моей будет достаточно для этого к тому моменту). А возможно это даже станет поводом перейти на TypeScript и "не мучаться", как рекомендовали некоторые коллеги.
Источник статьи: https://habr.com/ru/post/569384/