Я не знаю TypeScript, поэтому и пишу эту статью. У меня есть некоторый опыт программирования на Java и PHP и этот опыт заставляет меня кодировать на JavaScript'е соответствующим образом. К последней моей статье коммент от коллеги Silverthorne был такой:


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?

В ответном комменте я попросил от него продемонстрировать TS-код, который делает то же самое. Он не ответил. Я добавил коммент с просьбой, чтобы кто-угодно продемонстрировал TS-код, который делает то же самое. Ничего. И вот я пишу уже статью с аналогичной просьбой.


Код в примере — это рабочий код на 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 и "не мучаться", как рекомендовали некоторые коллеги.


Постмортем


Ну что ж, в целом получилось неплохо. Моей кармы хватает ещё на 6 таких статей :)


Действительно, в JS нет autoloader'а, как нет его и в TS. В nodejs устоялась практика export'ов-import'ов на уровне пакетов (маппинг логической структуры export'ов на файловую структуру es-модулей). Для браузеров средства типа webpack'а собирают весь фронт приложения в единый массив (или несколько массивов — бандлов).


Межпакетные import'ы преимущественно используются в nodejs, для браузеров пакеты помещаются в бандлы (файлы). Внутри такого файла import'ы становятся излишними. Необходимость tree shaking как раз и является следствием такого подхода — когда весь пакет помещается в бандл. Затем из него приходится удалять код, который в данном бандле не используется. Причём оптимизация происходит на уровне export'ов. Т.е., если в каком-то npm-пакете нужно обеспечить последующую оптимизацию кода на уровне отдельного es-модуля, то в в его package.json нужно прописать export для каждого такого es-модуля (как правило, модули внутри пакета связаны по цепочкам и достаточно в экспорте прописать головной модуль цепочки, т.е., в пакете может быть 100 es-модулей и только 10 экспортов).


Таким образом, на данный момент, в JS-приложении, состоящем из множества npm-пакетов, "кирпичом" является не es-модуль (отдельный файл), а export'ы отдельного пакета (точки входа в цепочки связанных es-модулей). В nodejs для загрузки кода из сторонних пакетов используется (или пока ещё только предполагается использовать — в описании нет ключа exports?) autoload'инг на уровне логической структуры export'ов (@scope/package/path/to/export), для браузеров autoload'инг не нужен, но бандлы также собираются "с шагом в один export".


Такого я точно не видел ни в Java, ни в PHP. В них отдельные файлы пакета доступны для сторонних пакетов без ограничений. В чём-то это преимущество, в чём-то нет. Продолжаю вести наблюдение...

Комментарии (109)


  1. Drag13
    24.07.2021 11:22
    +13

    Мне кажется вы немного путаете.

    Возьмем ваш пример:

    import UserService from "../services/UserService";
    
    class UserController {
        constructor(private readonly userService: UserService) {}
    }

    Да, здесь выполняется две операции одновременно. Первая - импорт типа, вторая - указание куда этот тип применить. Вам кажется, что это избыточно, но тип не обязательно будет применяться в конструкторе, он может быть использован и дальше по коду.


    Хотите писать без импортов - без проблем, пишем .d.ts файл, добавляем новые глобально видимые типы, пользуемся без импортов, пример тут - https://drag13.io/posts/custom-typings/index.html

    Но я бы так не делал. Во-первых, вам придется подключать библиотеки глобально, т.е. вы сломаете себе tree-shaking. Во-вторых, возрастает риск просто забыть подключить одну из зависимостей руками. В третьих, они становятся не очевидными и про них вообще легко забыть. В четвертых, становится сложнее отслеживать зависимости своего приложения. В пятых, ой вы намучаетесь если не дай бог типы двух библиотек пересекутся.

    Так что не советую.


    1. flancer Автор
      24.07.2021 12:41

      Что-то интересное. Спасибо.


    1. flancer Автор
      24.07.2021 12:42
      -2

      Сорри, плюсик в карму не смогу поставить - уже поставил ранее :)


  1. astec
    24.07.2021 11:24
    +7

    Можете пояснить в чём именно проблема с отдельным импортом? Только тем что одной строчкой кода больше? Зато в одном месте можно посмотреть все зависимости файла - это же удобно! Плюс если несколько раз используется тип не надо указывать путь повторно.

    Все нормальные IDE делают автоимпорт сами - так что печатать даже меньше надо.

    Я например пишу на совершенно разных языках (C#, TypeScript, Python, Go) и кто там как импортирует меня меньше всего волнует. Выбираешь язык под задачу и вперёд. По мне так TypeScript на порядок лучше JavaScript - переполз после 15 лет JS на TS и безумно рад. Никогда не пойму зачем страдать с JS если есть TS.


    1. saboteur_kiev
      24.07.2021 11:49
      +1

      Я бы сказал, что импорт отдельно, использование отдельно десятки лет используется в C/C++/C#, и это нормально.

      Я даже считаю не совсем нормально ожидать, чтобы сборщик для nodejs и для браузера работал одинаково, хотя бы в силу того, что код по-разному доставляется на рантайм


      1. thatsme
        24.07.2021 12:02
        +2

        импорт отдельно, использование отдельно десятки лет используется в C/C++

        Эхм ... Расскажите про импорт в C/C++ пожалуйста. Я судя по всему вашу мысль не совсем понял. Или вы имеете в виду директиву "#include" прекомпилятора?

        При её использовании никаких танцев с бубнами и двойными декларациями не требуется.


    1. flancer Автор
      24.07.2021 12:17
      -1

      Можете пояснить в чём именно проблема с отдельным импортом?

      Каждый импорт - это противоположный экспорт. В ES6+ импорт не будет работать без экспорта. Я должен поддерживать пары export - import в соответствии. А это уже плюс две строчки кода. А если я организовываю экспорт из глубины пакета, то и поболее может быть.

      В других языках можно без этого. И в JS можно, и в TS тоже можно. Должно хватать просто типа, если мы говорим за типизированные языки. Мне, например, хватает. И экспортами я не заморачиваюсь. А окружающие говорят, что я страдаю и мучаюсь. Вот я и хочу увидеть, что ж я всё-таки пропустил.


      1. Drag13
        24.07.2021 12:21
        +3

        Вы когда в Jave объявлете поле публичным тоже страдаете?

        Импорт/экспорт это не про две строки кода. Это инкапсуляция модулей. И внезапно TS тут не причем, в ES6 все работает точно также. Так что вообще непонятны ваши вопросы к TS-у.


        1. flancer Автор
          24.07.2021 12:39
          -6

          В JS нет типов, а в TS все на них построено. Почти всё.


          1. Drag13
            24.07.2021 12:46
            +1

            Во-первых, в JS-ы типы есть. И на уровне рантайма, и на уровне самого языка (вот так внезапно)
            Во-вторых - и что? Я вам выше уже писал. Хотите типы без импорта - .d.ts вам в помощь, все будет работать - вы проигнорировали. На комментарий выше вообще ответили в стиле "синего неба".

            Это называется "даже на хабре мне ничего не смогли доказать". Лить воду на эту мельницу я не хочу, диалог прекращаю.


            1. flancer Автор
              24.07.2021 13:35

              Я вам выше "спасибо" сказал. Буду смотреть, что там по ссылке. Экий вы обидчивый.


        1. flancer Автор
          24.07.2021 13:51
          -2

          Ничего сложного в этом нет.

          Пока вы находитесь в пределах одного npm-пакета. Дальше начинаются сложности.

          В вашем случае, о котором вы говорите, вы используете public, protected, static

          это из какой-то другой статьи, не из этой (сделайте Ctrl +F по "protected")

          importJava != include PHP. Первое - это указатель на пакет (адресация), второе - оператор загрузки и выполнения кода (сравните с include_once)

          только делаете это нечитаемым

          А вот IDE нормально отрабатывает по Ctrl+Click по названию класса. И даже может подсветить его, если в двух разных файлах продекларирован один и тот же класс. Но это для Java и для PHP (современного).

          В Magento 2.3 порядка 590 тыс. классов и они свои конструкторы пишут именно так - без include'ов и require. И ведь как-то справляются.


      1. Bigata
        24.07.2021 13:59
        -1

        О да, ждём'с нормальный импорт


      1. astec
        24.07.2021 14:03
        +3

        На вкус и цвет товарища нет.

        По мне так, import/export это прекрасно. И не важно как оно реализовано. Вон в Go то что экспортируется просто должно начинаться с большой буквы - без всяких public or export. А в TypeScript нужно написать export да еще пробросить в index.ts. Это всё мелкие детали реализации и делается на автомате.

        Нет серебряной пули. За любую возможность приходится платить. И для меня чёткая декларация контракта и централизованное указание зависимостей это благо, а не боль. Так что я с удовольствием пишу export (хотя больше полюбил Go подход). И от import нисколько не коробит. И package.json при всех его проблемах для меня плюс, а не минус.

        Вспоминается "вам шашечки или ехать".

        За сим откланиваюсь.


        1. flancer Автор
          25.07.2021 06:07

          import-export в JS - это манипулирование кодом на уровне es-модулей (я сейчас за ES6 говорю). Т.е., файлов. А import в Java - это манипуляция кодом на уровне классов. Т.е. файлов. Как тут уже замечали, в Java один класс - один файл. Но в Java классы-файлы могут находиться не только в файловой системе приложения, но и в JAR-архивах, что порождает JAR hell.

          Что мне понравилось в JS, когда я столкнулся с requirejs, так это то, что я могу загрузить в приложение несколько разных версий той же jquery из разных файлов, и все они смогут работать одновременно. Это следствие именно привязки к файлам, а не маппинг имени класса на файловую систему.

          В PHP в одном файле может быть много классов, за загрузку исходников отвечает autoloader, а его можно реализовать очень по-разному. Полагаю, что даже можно устроить самому себе что-нибудь типа PHAR hell.

          Я привык думать на уровне классов, т.к. в том же PHP в больших проектах, типа Zend'а, придерживаются Java-принципа: один класс - один файл.


      1. 0xd34df00d
        24.07.2021 22:09
        +1

        Должно хватать просто типа, если мы говорим за типизированные языки.

        Только это усложнит вывод типов (нормальный, в стиле Хиндли-Милнера, а не как в C++). Выбирая между эффективным выводом типов и отсутствием списка импортов я скорее выберу вывод типов.


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


        1. nin-jin
          25.07.2021 05:38
          +1

          А в чём там проблема с выводом типов?


          1. 0xd34df00d
            25.07.2021 06:04
            +1

            А у вас вместо тупой системы атомарных уравнений (которая в каком-то смысле — просто одна большая конъюнкция атомарных термов), которые можно решать достаточно тупой унификацией, получается конъюнкция дизъюнкций (где каждый дизъюнкт — это уравнение для того случая, когда токен N имеет совпадает с определением функции с именем N в каком-то модуле, имеющей такой-то тип), и мне вот так сходу неочевидно, что это вообще в общем случае разрешимая задача.


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


            1. nin-jin
              25.07.2021 07:07
              -1

              В любом месте использования либо известно какой именно из "одноимённых" типов используется, либо используется их объединение. Так что никакой проблемы не возникает.


              1. 0xd34df00d
                25.07.2021 07:40

                Как это известно, если вы выводите типы?


                module A where
                
                foo :: String -> Int
                foo _ = 10
                
                fooArg :: String
                fooArg = "meh"
                
                module B where
                
                foo :: Double -> Int
                foo _ = 42
                
                fooArg :: Double
                fooArg = 3.14
                
                module Main where
                
                main = print $ foo fooArg

                Эта программа должна вывести 10 или 42?


                Хотя я так и не понял, что именно смущает ОПа, кроме импортов-экспортов «в общем».


                1. flancer Автор
                  25.07.2021 08:24
                  -1

                  Это не тот язык, которым я владею, поэтому в комменте выше меня смущает только ОП. Я решал проблему, каким должен быть JS-код, чтобы он мог загружаться без изменений и в браузере, и в nodejs-приложениях, и пришёл к выводу, что в нём не должно быть межпакетных import'ов. Но это проблема JS, а не проблема Haskell'я. В Haskell'е этой проблемы нет в принципе.


                1. nin-jin
                  25.07.2021 08:28

                  Эта программа должна вывести 10 или 42?

                  Ошибку компиляции.


                  1. 0xd34df00d
                    25.07.2021 08:30
                    +1

                    Правильно. Значит, ваше утверждение про «в любом месте» не выполняется.


        1. flancer Автор
          25.07.2021 06:26

          Присоединюсь к коллеге @nin-jin - я тоже не вижу проблемы в выводе типов при отсутствии импортов. Должно хватать типов при объявлении переменных и в сигнатурах функций.

          Помогает что оптимизировать код, что компилятору находить неиспользуемый

          Полагаю, этот "плюс" является следствием наличия экспортов уровня npm-пакета. В Java/PHP весь публичный код пакета (уровня maven/composer) является доступным всем остальным. Где-то это хорошо, где-то может быть плохо.

          Необходимость прописывать экспорт в явном виде даёт возможность "скрывать" какую-то часть пакета от внешнего потребителя. Правда всё равно остаётся вариант с прямым доступом к содержимому пакета по имени файла, но это уже "грязный хак" (типа моего DI'я ;)


          1. 0xd34df00d
            25.07.2021 07:43
            +1

            Должно хватать типов при объявлении переменных и в сигнатурах функций.

            Лол. А что вы тогда оставляете выводу типов?


            Полагаю, этот "плюс" является следствием наличия экспортов уровня npm-пакета. В Java/PHP весь публичный код пакета (уровня maven/composer) является доступным всем остальным.

            Я вообще на примере хаскеля говорил.


            Вы можете написать


            module Foo where
            ...

            и у вас будут экспортироваться все байндинги, либо вы можете написать


            module Foo(func1, func2, Type3) where
            ...

            и тогда у вас будут экспортироваться только эти сущности. В чём профит?


            Во-первых, если что-то не используется транзитивно этими сущностями, то компилятор даст ворнинг, и мёртвый код можно будет удалить.
            Во-вторых, неэкспортируемые сущности можно сразу инлайнить, специализировать для конкретных аргументов, и так далее.


            1. flancer Автор
              25.07.2021 08:19

              Лол. А что вы тогда оставляете выводу типов?

              Ничего? Я просто не сталкивался с такой проблемой ранее. Это в Хаскеле такие проблемы? Ну, значит это его "обратная сторона медали". Наверное, какой-то профит он всё-таки даёт, раз вы на нём пишите.


              1. 0xd34df00d
                25.07.2021 08:32
                +3

                Ничего? Я просто не сталкивался с такой проблемой ранее.

                Судя по этому треду, сама концепция вывода типов оказалась новой, ну да ладно.


                Это в Хаскеле такие проблемы? Ну, значит это его "обратная сторона медали". Наверное, какой-то профит он всё-таки даёт, раз вы на нём пишите.

                Те же проблемы есть в любом языке с, ээ, выводом типов — с окамлем, с растом, например.


                Да и не назвал бы это проблемами. Список импортов — не самая большая проблема при программировании.


  1. anonymous
    00.00.0000 00:00


  1. thatsme
    24.07.2021 12:01

    del


  1. skeevy
    24.07.2021 12:05
    +18

    Наблюдая за вашими комментариями под статьями, складывается впечатление, что вы не понимаете, зачем вам TS

    Вы жили в других парадигмах, пришли в JS со "своим уставом" и удивляетесь, почему это не работает. Вам говорят, как принято в комьюнити сейчас, но вы упорно отказываетесь. На любой аргумент в пользу TS у вас 10 против, это нормально. Не нормально то, что вас должны все убедить перейти на TS под соусом вашей, якобы, открытости к новому.

    На все вопросы даже в этой статье есть ответ. Бандлеры (роллап, вебпак, даже тот же гальп как таскраннер), плагины к ним делают те вещи, которые вы хотите, но не так, как вы хотите.

    Давайте не будем лукавить: вы не собираетесь переходить на TS, вас не нужно убеждать, потому что кроме вас никто этого сделать не может. Вы не хотите принимать правила игры, потому что они вам не по вкусу, вы заложник своих привычек. С вашей точки зрения импорты не те, то не то, это не так...

    К чему эта демагогия?


    1. flancer Автор
      24.07.2021 12:06
      -4

      Чтобы узнать что-то новое.


    1. flancer Автор
      24.07.2021 12:24
      +2

      Не нормально то, что вас должны все убедить перейти на TS

      Не должны. Я никому не угрожаю выброситься из окна или взорвать самолёт, если мне не приведут достаточно аргументов. Вы можете высказаться в пользу вашей точки зрения, большего я и не требую.

      Да, я пришёл в JS "со своим уставом". Мне так удобно, я так и программирую. Я просто проверяю "свой устав" "на излом". Пока что никто не смог опровергнуть моё предположение, что без import'а es-модули в JS/TS не связываются. Это то, ради чего статья и была написана. Если же смогут - будет повод покопаться в чём-то новом. Мне не проблема поменять "свой устав", был бы повод.


      1. kahi4
        24.07.2021 12:41
        +4

        Пока что никто не смог опровергнуть моё предположение, что без import'а es-модули в JS/TS не связываются.

        «Пока что никто не смог опровергнуть мое предположение, что без использования знака «=« переменной нельзя присвоить значение».

        Серьезно, это конструкция языка, вот так она задана и с этим ничего не сделать. Конечно, можно найти обходной путь (как в давние времена, когда все файлы просто склеивались в один большой файл, никаких импортов писать не нужно было, и этт было адски неудобно, либо можно написать свой плагин к бейбелю), но лучше смириться с тем как язык спроектирован именно сейчас и как абсолютно всё ожидает что он будет выглядеть (IDÉ, webpack, eslint, Babel, оптимизаторы кода, uglifyjs, да даже браузер с нодой). Синтаксис из php, который вы показали, просто не существует в js/ts


        1. kahi4
          24.07.2021 12:47
          +4

          Более того, все надмножества над жс (тот же flow and pure script) используют такой же синтаксис для модулей, да даже сильно другие от жс языка, компилируемые в жс, используют его (elm, dart, …). Причём не потому что не могут ничего другого, а потому что он удобнее и позволяет для каждого места проследить цепочку импортов и понять откуда что берётся.


        1. flancer Автор
          24.07.2021 13:56

          Серьезно, это конструкция языка, вот так она задана и с этим ничего не сделать. 

          Вот рабочий JS-код, в котором есть 4 зависимости и ни одного import'а. В других языках (Java/PHP) это делается давно и привычно. В JS тоже возможно (пример по ссылке).


          1. astec
            24.07.2021 14:20
            +2

            /** @type {TeqFw_Web_Front_Defaults} */
            const DEF = spec['TeqFw_Web_Front_Defaults$'];

            Какой ужас.

            • Зачем 2 раза TeqFw_Web_Front_Defaults?

            • Что за магический глобальный массив spec?

            • tree shaking с этим работать будет?

            • Где dependency injection?

            this.send = async function (data, factory) {

            Зачем внутри конструктора? Сложно же читать что именно конструктор делает.


            1. flancer Автор
              25.07.2021 09:48
              -1

              Зачем 2 раза TeqFw_Web_Front_Defaults?

              Первый раз - для IDE, второй раз - для программы. Если сделать плагин для IDE, достаточно будет и одного раза.

              Что за магический глобальный массив spec?

              Это не глобальный массив, а входной параметр конструктора. Прокси-объект, через который DI-контейнер определяет, какую зависимость запрашивает конструктор. Контейнер по идентификатору находит исходник для зависимости и импортирует модуль (если этот модуль не был импортирован ранее), затем создаёт из (default)экспорта модуля объект и возвращает его в качестве зависимости (или сразу использует ранее созданный, если это singleton). Это если коротко.

              tree shaking с этим работать будет?

              Никак. Зависимости тянутся сразу на фронт и только те, которые используются в приложении.

              Где dependency injection?

              Вот.

              this.send = async function (data, factory) {

              Мне тоже :( Я бы предпочёл приватные атрибуты (#DEF), но Safari их начал понимать только с апреля 2021-го.


          1. kahi4
            24.07.2021 14:53
            +3

            Хорошо, давайте по порядку.

            1. В моем полностью типизированном проекте есть куча файлов без импортов. Да, зависимости в них обычно глобальные или нет вообще, но это не показатель сам по себе.

            2. Собственно подразумевается что этот файл кто-то где-то будет импортировать. Хотя признаю, что в ноде легко сделать автоимпорт конкретно для подобного подхода. Но см. пункт 8

            3. Конкретно это пример фабричных методов, которые получают зависимости от аргументов. Для JS нормально в данном случае не иметь импортов, потому что они передаются как аргументы. Осталось только убедиться что они переданы и корректного типа.

            4. Для пункта 3 в TS тоже есть корретный форкфлоу. Более того, он часто даже более предпочтительный. Дело в том, что ts основан на структурном тайпинге, т.е. он проверяет что в переданном объекте есть требуемые поля, но при этом сами объекты могут быть разными. Подробнее тут https://www.typescriptlang.org/docs/handbook/type-compatibility.html Это позволяет переписать ваш пример с ТС так же без импортов.

            5. Большинство типов в вашем примере - "какой-то объект", полезность чего под вопросом, но опустим этот пункт. Местами навешать дженериков всё же не помешает.

            6. Добавьте сюда что-то вроде axios, не загружая им контекст и не вставляя его глобально. Опять же, в ноде не сложно дописать волшебный импорт, который это сделает, а в вебпаке если уж очень хочется есть import() API (раньше был require()), но и то и другое предназначено для других кейсов.

            7. Убедитесь что ваш объект spec передан в корретном виде и поля, которые вы из него читаете, соответствуют типу, который вы ожидаете. Ну или иными словами, получите какую-то пользу от типизации (кроме возможно волшебного DI).

            8. Ну и заставьте хоть какую-то IDE перейти в определение типа по клику. И отрефакторить (переименовать, например).

            9. Возможно ли так писать? Да, конечно, многие из этих пунктов можно обойти, но только никто кроме вас это поддерживать не будет и разрабатывать большой проект на этом будет невозможно. Если бы это было внесено в стандарт -- может вообще бы хорошо было (хотя по пять раз писать один и тот же путь, например, если я работаю с математикой и у меня 10 полей с типом "вектор" слегка бы утомило. И рефакторинг с переносом файла в другое место замусорил бы весь гит правками прямо в коде).

            10. Хотел написать про тестирование, но, справедливости ради, как раз для тестов мокать будет проще.

            11. Да и вообще, если так подумать, это просто другой синтаксис импорта, у которого есть преимущества и недостатки, и без специальных доп программ он тоже не будет работать. Ну вот решили в TS придерживаться синтаксиса ES Modules, и придерживаются. Такие правила игры, никто не мешает форкнуть ТС или написать свой плагин к бейбелю для поддержки именно такого синтаксиса. Хотя есть справедливый вопрос: почему сущности должны импортироваться / экспортироваться одним образом, а типы для них другим?

            Ну и бонус: ts без импортов для данного файла, если очень хочется:

            interface DEFS {
              SHARED {
                SPACE_API: string
              }
            }
            
            interface CONFIG {
              root: string;
              door: string;
              urlBase: string;
              // прочие поля, используемые конкретно этим классом
            }
            
            interface SPECS {
               TeqFw_Web_Front_Defaults$: DEFS;
               TeqWf_Web_Front_Api_Dto_Config: CONFIG;
            }
            
            export default class TeqFw_Web_Front_Service_Gate {
                constructor(spec: SPECS) {
                    const DEF = spec['TeqFw_Web_Front_Defaults$'];
                    const config = spec['TeqFw_Web_Front_Api_Dto_Config$'];
                    ...
                }
                }
              // думаю, идея понятна: указыаем что используем, тогда ТС проверит что в переданном объекте есть все необходимое

            PS. Я так понимаю, синтаксис классов вы тоже не принимаете?


            1. flancer Автор
              25.07.2021 07:04

              Конкретно это пример фабричных методов, которые получают зависимости от аргументов. Для JS нормально в данном случае не иметь импортов, потому что они передаются как аргументы. Осталось только убедиться что они переданы и корректного типа.

              Я когда-то проверял тип входных данных, а потом пришёл к выводу, что это не нужно. В JS, так уж точно. Допустим, что данные пришли не того типа - и что можно сделать? Выбросить исключение, чтобы прервать работу программы? Ну так она и так прервётся, если я начну использовать отсутствующие свойства объекта. А если не начну, то значит объект подходит по своим интерфейсам данному участку кода. Ведь это и есть суть "утиной типизации", разве нет?

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

              Убедитесь что ваш объект spec передан в корретном виде и поля, которые вы из него читаете, соответствуют типу, который вы ожидаете. Ну или иными словами, получите какую-то пользу от типизации (кроме возможно волшебного DI).

              spec - это прокси-объект, который по идентификатору зависимости (вот этому

              TeqFw_Web_Front_Api_Dto_Config$) находит соответствующий es-модуль и извлекает из него соответствующий экспорт (default в данном случае) и "готовит" из него объект ( singleton в данном случае), используя экспорт в качестве фабрики (функции или класса), после чего передаёт обратно в конструктор. spec не имеет определённой структуры, а его свойства всегда соответствуют заданному идентификатору, если этот идентификатор в принципе разрешаем в объект/класс/функцию.

              Ну и заставьте хоть какую-то IDE перейти в определение типа по клику. И отрефакторить (переименовать, например).

              Пожалуйста - это PhpStorm. Наведение курсора мыши с удержанием Ctrl. По клику будет переход на декларацию класса в файле .../@teqfw/web/src/Front/Api/Dto/Config.mjs. Не уверен, что VSCode на такое способен, он с JSDoc-аннотациями хуже работает, чем IDEA.

              И рефакторинг с переносом файла в другое место замусорил бы весь гит правками прямо в коде

              С import'ами та же проблема, если только вы не маскируете внутрипакетный перенос файла пакетным экспортом. Тогда - да, для внешнего кода экспорт останется тем же. Но внутри npm-пакета перенос файла также отразится на всех импортах использующего кода этого же пакета.

              Ну и бонус: ts без импортов для данного файла, если очень хочется:

              Спасибо за код. Это несколько не то, что хотелось бы, но всё равно спасибо.

              PS. Я так понимаю, синтаксис классов вы тоже не принимаете?

              Принимаю. Там прямо в коде и написано export default class ...


              1. kahi4
                25.07.2021 11:45

                spec - это прокси-объект, который по идентификатору зависимости (вот этому

                Т.е. это самописный автоимпорт как я и предполагал. Опять же - в ноде сработает без проблем, в вебе уже не все так просто. Вам нужно либо заведомо включить вообще все файлы в бандл, покуда иным способом вы не можете убедиться будет ли он использоваться, либо писать кастомный плагин, который будет понимать этот синтаксис и получите import() (ну или require, просто с необычным синтаксисом). К слову, сам по себе import() поддерживается тс, так что теоретически заменив ваш волшебный spec[…] на import(“root/module/blabla”) может даже сработает ваш импорт.

                хочу заметить что в данном случае вы откровенно вводите в заблуждение что у вас нет импортов других модулей. У вас другой синтаксис, но импортом он от этого не перестаёт быть.

                рефакторинг обычных импортов лучше тем что он будет править только шапку, файла, но функции как были с коммитами соответствующими правкам, так и останутся. В вашем же примере он их всех подправит с «добавил зависимость для задачи АБ-1234» на «отрефакторил абсолютно не относящийся к этому коду модуль»


                1. flancer Автор
                  25.07.2021 13:08

                  Т.е. это самописный автоимпорт как я и предполагал.

                  Вы правильно предполагали. Отсутствие import в коде не значит, что его нет вообще. Он как раз в загрузчике (autloader'е).

                  покуда иным способом вы не можете убедиться будет ли он использоваться

                  Этот "иной способ" - DI-контейнер. Как только к нему приходит запрос на создание объекта и идентификатор этого объекта (вот этот вот TeqFw_Web_Front_Api_Dto_Config$), так он сразу лезет на сервер за исходником для него. PWA предлагают кэшировать исходники на стороне клиента. Я специально разделил код своих модулей на три части (Back, Front и Shared), чтобы можно было делать preload всего кода через service worker'ы. Если этот вариант не устраивает, то есть вариант загрузки on-demand и кэширования только используемого кода. Поначалу приложение будет тормозить, а со второго запуска будет скользить, как по маслу.

                  хочу заметить что в данном случае вы откровенно вводите в заблуждение что у вас нет импортов других модулей.

                  Если у вас создалось впечатление, что я говорил, что можно создавать многомодульные приложения без импорта исходников, то прошу прощения - я совершенно не имел этого в виду. Я лишь хотел сказать, что импортить модули можно и без того, чтобы указывать в их коде оператор import на каждую зависимость.

                  Последний абзац не понял. У меня зависимости инжектятся в конструктор, а это аналог импортов в шапке файла. Добавление новой зависимости в конструктор не влияет на тот код, который использует этот конструктор для создания зависимости (конструктор используется только DI-контейнером, в этом его суть - создание иерархии зависимостей). Если же имеются в виду изменения JSDoc-аннотации, то да - везде, где использовался тип Vnd_Prj_Mod1, а стал использоваться Vnd_Prj_Mod2, произойдут изменения. В этом и смысл рефакторинга.

                  Спасибо за вдумчивые вопросы.


                  1. kahi4
                    25.07.2021 13:55

                    Да, опять же, третий раз, наверное, вы переизобрели https://webpack.js.org/api/module-methods/#import-1

                    Правда покуда он сугубо асинхронный (в отличие от вашего синхронного, что, к слову, проблематично сделать на жс и будет жутко тормозить первое время пока все не прогрузит), у него основное назначение - разбивать код по отдельным чанкам. Тогда возвращаюсь к моему пункту - хотите пользоваться ts / webpack / миллионы плагинов на все случаи жизни - играйте по их правилам. Хотите свои правила - пожалуйста, но поддерживать это никто не будет. Ну и «их» правила, при желании, можно адаптировать к вашим. Вон, первый ангулар (angularjs который) похожим образом работал, правда ожидал что вы все склеите в один файл, но не сложно доделать до дозагрузки файлов, но что-то не пошло.

                    P.S. Вообще продуманная система зависимостей позволяет построить дерево зависимостей перед исполнением кода и, таким образом, загрузить все нужные зависимости заранее, а не тормозить UI пока что-то там прогрузится (и, в вашем примере, оно будет генерировать просто тысячи запросов на сервер, что проблематично без http2), а если начнёте делать так (декларировать модули либо заранее, либо декоратором), короче переизобретете inversifyjs или ангулар. Правда оба этих играют по правилам вебпака, но суть это не меняет.


              1. kahi4
                25.07.2021 11:50

                Речь не об этом, а о том, что вы вроде бы обьявили класс, но все методы класса навесили через this в конструкторе. Так никто не делает ни старым синтаксисом, ни новым. Этих методов не будет в прототипе, они будут пересоздаваться каждый раз и вообще выглядит странно, вам вообще класс не нужен в данном случае, просто фабричная функция (ну или как оно там называлось, недоинкапсуляция в жс). В классе есть синтаксис объявления методов, который вы игнорируете без особой на то причине (да, вы замыкаете свой spec, но вообще это можно сделать изящнее и правильнее).


                1. flancer Автор
                  25.07.2021 12:50

                  Я был вынужден так делать - Safari не воспринимал #private до апреля этого года, а свой код я писал в прошлом. Я счёл это меньшим злом. Сейчас в коде конструктора можно писать так:

                  /** @type {TeqFw_Web_Front_Defaults} */
                  this.#DEF = spec['TeqFw_Web_Front_Defaults$'];

                  а методы в класс навешивать стандартным способом. Но тут уж не моя вина, я просто добивался совместимости с Safari.

                  Можно использовать фабричную функцию и даже лучше во многих случаях использовать фабричную функцию (с фронт-шлюзом - так точно), но IDEA лучше понимает код, когда используются классы и методы, а не JSDoc аннотации namespace и memberOf. Я понимаю, что проще использовать TS, чем напрягать IDEA, но иногда проще не значит лучше. Это моя точка зрения, а не универсальная :)


              1. swelf
                26.07.2021 17:49

                Ну так она и так прервётся, если я начну использовать отсутствующие свойства объекта

                Если отствующие свойства, то да. А если свойства есть, но они работаю по другому?
                Есть функция, работающая с целыми числами
                > function cmp(a,b) {return a > b}
                undefined
                > cmp(10,9)
                true
                > cmp(«10»,«9»)
                false
                или другая, тоже рассчитаная на целые числа, а передаем float. В js вроде все float, но тем не менее пример должен быть понятен.
                > function eq(a,b) {return a==b}
                undefined
                > a=0.3
                > b=0.1*3
                > eq(a,b)
                false


                1. flancer Автор
                  26.07.2021 18:05

                  Если использование неверных входных данных не приводит к сваливанию работы программы, то придётся проверять входные данные.


          1. nin-jin
            25.07.2021 06:13
            -3

            Позволил себе несколько упростить ваш код:

            namespace $ {
            
                export class $TeqFw_Web_Front_Service_Gate extends $mol_object2 {
                        
                        @ $mol_mem
                        base_url() {
                            
                            const config = this.$.$TeqFw_Web_Front_Api_Dto_Config()
                            const defaults = this.$.$TeqFw_Web_Front_Defaults()
                            
                            const schema = '//';
                            const domain = config.urlBase;
                            
                            let port = location.port; // empty string for default ports (80 & 443)
                            if (port !== '') port = `:${port}`
                            
                            const root = (config.root) ? `/${config.root}` : '';
                            const door = (config.door) ? `/${config.door}` : '';
                            const space = `/${defaults.SHARED.SPACE_API}`;
                            
                            return `${schema}${domain}${port}${root}${door}${space}`;
                        }
            
                        /** Send API service request to backend. */
                        @ $mol_fiber.method
                        send< Data extends object >(
                            /** JS-object to be sent as request */
                            data: Data,
                            factory: $TeqFw_Web_Back_Api_Service_IRoute
                        ) {
                            this.$.$TeqFw_Web_Front_Api_Gate_IAjaxLed.notify()
            
                            const URL = `${this.base_url()}${factory.getRoute()}`;
                            const res = this.$.$mol_fetch.json( URL, {
                                method: 'POST',
                                headers: {
                                    'Content-Type': 'application/json'
                                },
                                body: JSON.stringify({data})
                            });
                            return factory.createRes(res.data)
                        }
                        
                    }
                }
            
            }


            1. flancer Автор
              25.07.2021 08:51
              +1

              Что меня сразу же смущает в коде, помимо того, что это не JS - что такое @ $mol_mem и @ $mol_fiber.method? Я не смогу понять, во что превращается ваш код после транспиляции, пока не узнаю, что это.

              Во-вторых - вы просто выкинули код, который даёт приложению сигнал, что выполняется API-запрос (можно на UI'е выводить индикатор запроса, например) и код, который передаёт в приложение сообщение об ошибке выполнения запроса).

              Вы явное сделали неявным, переместив куда-то. Такое себе упрощение.


              1. nin-jin
                25.07.2021 09:19

                Это просто рантайм декораторы, берущие на себя рутину. Во что это транспилируется можно глянуть в песочнице.

                $mol_atom: теория и практика реактивности - тут про мемы.

                Quantum Mechanics of Calculations in JS - тут про файберы.

                А ещё я убрал глитчи из вашей логики, которые возникают при параллельных запросах.


                1. flancer Автор
                  25.07.2021 09:58

                  Песочница тоже не может найти mol_mem и mol_fiber :(

                  А ещё я убрал глитчи из вашей логики, которые возникают при параллельных запросах.

                  А вот это уже интереснее. Нагугли, что "глитч" - это "ошибка, которая позволяет получить преимущество, не задуманное разработчиками". И что там за ошибка и кому она даёт преимущество? Я не вижу там места для ошибки даже при параллельных запросах.


                  1. nin-jin
                    25.07.2021 13:07

                    Вы статью про MAM-то почитайте.

                    Глитчи в данном случае - это мерцание интерфейса из-за некорректной множественной смены состояния приложения.


                    1. flancer Автор
                      25.07.2021 13:11

                      Или я не там ищу?



                      1. flancer Автор
                        25.07.2021 13:28

                        Спасибо. Насколько я понял, "глитчи" - это из области "реактивности". Когда в состоянии приложения возникает инконсистентность. У меня нет своей собственной реактивности и нет своего состояния. Фронт-шлюз отвечает только за отправку запросов на сервер и получение ответов. Кто и как его использует - за кадром.

                        Я у себя использую Vue 3, если глитчи и возникают, то там. Но добавление Vue 3 к teq-приложению - это тема следующей статьи. Или через одну.


            1. 0xd34df00d
              26.07.2021 18:18

              Опять же, каждый понимает так, как хочет, плюс плохокодить (в большинстве по личным причинам), и выдавать это за best practice никто не запрещал.

              Ну, это уже какие-то ненастоящие шотландцы.


        1. flancer Автор
          25.07.2021 07:32
          +1

          1. import вначале файла говорит о том, что в текущем файле есть зависимости с другими файлами

          В любом проекте с числом файлов больше 2-х есть такая зависимость. Я выше демонстрировал, как IDE переходит на декларацию соответствующего элемента кода по ctrl+click (картинка выше по дереву комментов). Зависимость по коду гораздо более информативна, чем зависимость по файлам. Тем более, что import как раз и даёт зависимость по коду:

          import {dirname, join} from 'path';

          2. указание ссылки (полного пути) на файл внутри конструктора класса/метода/функции - проблема дальнейшего восприятия другими людьми, кто будет поддерживать ваш код

          Точно так. Особенно, когда они не сталкивались с таким ранее в том же Java или PHP.

          из второго вытекает - если у вас будет зависимостей в конструкторе более 10, вы сами потеряетесь в них же

          Во-первых, если у вас более 10 зависимостей (и не важно, через import'ы или конструктор), то это говорит о необходимости декомпозиции кода. Во-вторых, нет, не потеряюсь. Вот пример из Magento 2.4 с конструктором на 14 зависимостей. Это не мой код, а просто пример того, что возможности человека удивительны. В-третьих, в таких случаях всё-таки нужно обратиться к пункту 1.

          если ... можно указать 1 импорт,

          Ну так и с зависимостями так же можно делать - указать, что нужно в конструктор заинжектить класс, а не экземпляр этого класса. Я могу запросить в конструкторе получение модуля, отдельного экспорта, результата работы отдельного экспорта (класса или функции) как синглтон или новый экземпляр.

          Ваш устав не так важен, так как за Вас уже поработали люди

          У меня нет такой веры в людей, как у Вас. Скорее всего, люди поработали так, чтобы хорошо было именно им. Так как и я пишу "свой устав" только для того, чтобы было хорошо именно мне. Чтобы именно я через пару лет смог разобрать тот код, который я написал пару лет назад.

          5. Пытаться внести что-то, что удобно вам - не значит, что это будет удобно другим, скорее - это будет им совсем неудобно. Я видел такой код в живую, и это боль - читать такой код.

          Извините за причинённые неудобства. Но, как я уже упомянул выше, я не отношусь к альтруистам, а скорее даже наоборот.

          Только этот import выглядит иначе

          Я и не говорю, что в JS можно подтягивать зависимости без import'а - у меня он тоже есть. Только я использую его более экономно, не в каждом модуле.


      1. skeevy
        24.07.2021 15:12
        +5

        я предпочитаю не тратить время на то, что не дает профита

        Следуя ваше логике:
        - я могу пойти к питонщикам и сказать, что их_такой_синаксисис полное говно
        - могу пойти к сишникам/джавистам и сказать, что их синтаксис и типизация слишком сложна, а работу с памятью я не понимаю и нафига оно вообще надо, в JS есть свой сборщик мусора и работает все прекрасно без обращений к нему
        - пойти в любой ООП и сказать, что прототипное наследование - отстой, потому что схема не прозрачна, а композиция - лучше! Зачем классы, когда можно процедурно набахать функций, вон с jQuery так все делали!
        - пойти в любую команию и сказать, нафига вы делаете микросервисные архитектуры и т.д. и т.п., возьмите ВордПресс, у него пыха не беке и работа с бд уже есть! А еще поставьте плагинчик, он вам и сваггер сам нарисует!
        - зачем CI/CD если есть FTP
        - зачем GIT если есть архиваторы

        и т.д. и т.п.

        И когда я скажу, что "я пришел к вам из JS, там все иначе, но я хочу узнать что-то новое", но все мне не будет нравиться, просто потому, что я так не привык...

        Короче, глупо все это...


        1. ARechitsky
          24.07.2021 18:32
          +1

          - я могу пойти к питонщикам и сказать, что их_такой_синаксисис полное говно

          Можете, и кто-то даже с вами согласится - _поле для защищенных и __поле для приватных, ну что это за дичь!

          - могу пойти к сишникам/джавистам и сказать, что их синтаксис и типизация слишком сложна

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

          - пойти в любой ООП и сказать, что прототипное наследование - отстой

          Прототипное наследование - атрибут JS, в других ОО языках оно не используется.


          Но в целом вы конечно правы, это глупо.


        1. csl
          24.07.2021 22:57
          +1

          Композиция лучше наследования классов, порождающих экземпляры, так как не существует способа расширить такой класс и добавить к нему новый аспект, сохранив при этом а соглашения для метода equals (Java, например).


  1. anonymous
    00.00.0000 00:00


  1. lorus
    24.07.2021 12:11
    +2

    В пределах одного пакета вполне можно писать вот так:

    import { UserService } from '../users';
    
    class UserController {
        constructor(private readonly userService: UserService) {}
    }

    Достаточно добавить ../users/index.ts с содержанием типа:

    export * from './user';
    export * from './user.service';
    // etc

    ...и не использовать default export.

    Ну и, как выше уже писали, IDE вполне способна сама сгенерировать import для вас. Не нужно делать это руками. Вы ведь в Java вручную import не пишете? Это просто потеря времени.


    1. flancer Автор
      24.07.2021 12:37
      -6

      В пределах одного пакета вполне можно писать вот так:

      А в пределах двух?

      IDE вполне способна сама сгенерировать import для вас

      Т.е., без import'а пока ещё никак. Вы не опровергаете, а только поддерживаете моё мнение.


  1. Zoolander
    24.07.2021 12:34
    +1

    Современные IDE уменьшают необходимость ручного набора.

    Я вставлю тип в код, нажимаю Alt+Enter, у меня появляется список, откуда можно импортировать этот тип. Если есть только один источник - и списка не появляется, сразу автоматически ставится import

    Я нажимаю Alt+Shift+O - и неиспользуемые импорты в начале файла исчезают. Так работают современные IDE.

    Что касается явного указания export - это происходит по той причине, что нам нужна инкапсуляция, нам нужно открывать для доступа только самое нужное, а кишки прятать.

    В Java можно обойтись только public-private, потому что там 1 класс на 1 файл. В JS/TS файле нет таких ограничений. Можно вообще хранить только функции или константы. Поэтому возникает необходимость в export - чтобы отделить внутренние функции модуля от тех, которые мы позволяем использовать.


    1. flancer Автор
      24.07.2021 12:40

      В JS/TS файле нет таких ограничений. ... Поэтому возникает необходимость

      Согласен. Чтобы необходимость не возникала, нужны ограничения.


  1. bugy
    24.07.2021 12:48
    +3

    а в Java так не писали вообще никогда (Java'вский import ничего не подгружает, а работает как alias, можно и без import'ов, если указывать полные имена классов)

    По-моему в джаве только так и пишут. Можно указывать полные имена, да, но так мало кто делает, кроме случаев совпадающих имен

    Т.е. по факту основная претензия: в JS нельзя использовать полные имена вместо импорта, что почти никому и не нужно

    Касаемо необходимости экспорта: вам уже написали, что это просто инкапсуляция. Если не хочется иметь инкапсуляцию и хочется открыть всё, есть вебпак плагин, который сможет все поля/методы/классы экспортировать за вас.

    Пс, в джаве с версии 9 тоже есть возможность/необходимость указывать экспорты для модулей. Если бы не backward compatibilty, возможно это сделали бы настройкой по умолчанию.


  1. kuznetsovkd
    24.07.2021 13:17
    +2

    Соболезную всем кто с вами вынужден работать.


    1. flancer Автор
      24.07.2021 13:32

      Это почему?


      1. moodpulse
        24.07.2021 13:46
        +10

        Потому что как говорится — в чужой монастырь со своим уставом не ходят, кажется это на поверхности. И все JS/TS разработчики не смогут вам ничего объяснить, потому что выше в комментах ничего не вышло. У вас есть проблема, которая только ваша проблема. Ее решения нет, потому что ни для кого больше это проблемой не является.

        Вот и выходит, что в целом да, можно только соболезновать.


      1. kuznetsovkd
        24.07.2021 14:02
        +5

        Потому что добавлять импорт для вас боль, а строчить jsdoc на каждую переменную ок.

        И всё это ради spec[“import_some_shit_by_forgotten_name”]


  1. moodpulse
    24.07.2021 13:42
    +2

    По поводу кода вам всё верно написали. Есть большая проблема в типизации аргумента-объекта на TS?

    Я бы на код ревью ещё написал — зачем создавать методы внутри конструктора, когда можно определить всё в классе? Дальнейшей типизации это тоже не поможет.

    Это я к тому, что данный код выглядит неприятно в целом.

    И правильно, что за вас его никто не написал нормально, потому что нужно примерно всё написать аккуратнее и иначе, и это то что надо сделать кроме типизации.

    Вы бы ещё удивлялись, почему на PHP нельзя писать как на ассемблере. Тоже можно написать статью, как вы «мучаетесь» с PHP.


  1. RiverFlow
    24.07.2021 13:54
    -8

    Главная проблема автора в нем самом, тезис: "код ненормальный потому что я так не привык" это заведомо отказать себе в развитии.

    По поводу ТС: ТС это не язык, это лукавая ООП- бородавка на теле JS. Со всеми признаками сектантства.

    И то что вам ТС- ники будут из всех угдов говорить про "лучше" , "проще" и "вапще" и при этом ни строчки кода в пример, это как раз норма, потому, что все, что может ТС это слегка ослабить комплекс неполноценности ООПщика с джавой в анамнезе, попавшего в функциональный мир.

    Объективно сложное не бывает простым нигде и никогда, это касается и зависимостей.

    JS видит главное проблемой разработки - неоправданную сложность. И ООП и порождение типов на каждом углу - это главный источник роста сложности разрабатываемого решения.

    ТС тут не помогает а убивает, подталкивая к тому чтобы плодить типы а не к тому чтобы совершенствовать методы.

    Чтобы понять и принять import в js надо не на ТС переходить, а отказаться от примата ООП в своем методическом мышлении и смешать удельный вес решения с объектной модели на совершенствование функций


    1. Bigata
      24.07.2021 14:10
      -4

      Плюсуюсь всеми конечности. TS хайп, нужны библиотеки JS, никто не пишет на ваниле и т.д. и т.п. Вот ООП стало хайпом, лепят всюду, когда нужно и не нужно. Отсюда и желание JS сделать настоящим ООП языком, но вот никак не вижу, зачем из него лепить java...


      1. Zoolander
        25.07.2021 10:28

        ООП стало хайпом, лепят всюду, когда нужно и не нужно.

        Значит, вы все-таки признаете, что ООП нужно. А в том, что люди делают - виновато уже не ООП )


        1. Bigata
          25.07.2021 14:43

          Конечно признаю, и да, ООП классная фича. Вот только использовать бы её по назначению. А так и микроскопом гвозди забивать можно.


      1. Alexandroppolus
        25.07.2021 12:00

        ООП стало хайпом

        ООП - проверенный десятилетиями подход, с наработанными бестпрактисами. А вот шумиха последних лет вокруг ФП действительно выглядит как хайп.


        1. 0xd34df00d
          25.07.2021 22:30
          +1

          ООП — проверенный десятилетиями подход, с наработанными бестпрактисами.

          Ага, поэтому регулярно появляются статьи, обсуждающие, что же такое на самом деле SOLID/MVC/LSP/etc.


          При этом почти весь код в ООП-стиле, что я видел, даже написанный людьми, стремившимся следовать этим бестпрактисам, в итоге превращался в неподдерживаемое месиво.


          А вот шумиха последних лет вокруг ФП действительно выглядит как хайп.

          Шумиха уровня описываемого языка, где «о, мы добавили метод map, у нас теперь функциональщина!» действительно хайп.


          1. bromzh
            26.07.2021 16:46
            +2

            Ага, поэтому регулярно появляются статьи, обсуждающие, что же такое на самом деле SOLID/MVC/LSP/etc.

            Статьи про то, что же такое в действительности монада появляются примерно с такой же частотой.

            При этом почти весь код в ООП-стиле ... в итоге превращался в неподдерживаемое месиво.

            Точно так же, как и код в ФП-стиле. Вы не задумывались, может быть дело не в ФП или ООП, а код становится неподдерживаемым по другим причинам?


            1. Alexandroppolus
              26.07.2021 17:27

              Статьи про то, что же такое в действительности монада появляются примерно с такой же частотой.

              Я вообще сильно удивлюсь, если найдется какая-нибудь сфера деятельности (не обязательно связанная с программированием), относительно которой в интернетах не появляется статей, как "на самом деле" правильно то-то и то-то.


            1. 0xd34df00d
              26.07.2021 18:20

              Статьи про то, что же такое в действительности монада появляются примерно с такой же частотой.

              Только в комментах к этим статьям нет таких споров, правильно ли автор понял, что такое монада, или нет.


              А вообще монада — это просто такой интерфейс, у которого к методам есть некоторые требования. Статья «что такое монада» ничем принципиально не отличается от статьи «что такое визитор». Последняя даже абстрактнее, наверное.


              Точно так же, как и код в ФП-стиле. Вы не задумывались, может быть дело не в ФП или ООП, а код становится неподдерживаемым по другим причинам?

              У меня другой опыт. Код в ФП-стиле (только на нормальных ФП-языках, где есть типы и всё такое) более чем поддерживаем: я могу придти в относительно произвольный проект и через полчаса-час уже более-менее продуктивно в него контрибьютить. Просто потому, что типы помогают (что проектировать без сильной связности, что мне потом в результате разбираться).


              1. bromzh
                26.07.2021 18:42
                +1

                Только в комментах к этим статьям нет таких споров, правильно ли автор понял, что такое монада, или нет.

                Потому что монада более простая штука, так как это математическая концепция, и разночтений там поменьше. Но всё-равно могут начаться споры по теоркату.

                У меня другой опыт

                Ещё бы. Вы больше работаете с ФП-кодом, вам его проще читать. Я больше работаю с ООП-кодом, мне его проще читать. Мне вот, например, сложно разбираться с ФП-кодом на хаскеле, где идут вперемешку композиция кучи функций, оператор применения, flip, и всё сверху полито своими кастомными операторами, у которых ты должен помнить приоритет. Однако, есть и такой код, который легко и понятно читать.

                где есть типы и всё такое

                Можно подумать, в ООП-языках нет типов. Давайте без снобизма. Конечно, там система типов совсем не выразительная, особенно в какой-нибудь Java, но типы всё-таки есть. Вон есть Typescript с алгебраическими типами. И ничего, пишут на нём нормальный ООП-код (и плохой тоже).

                А ещё зачастую в условной java, IDE намного лучше знает о коде и помогает с ним, нежели в haskell, где вроде компилятор типы может вывести, а вот LSP там работает кое-как. Потом смотришь на функцию, и понять не можешь, откуда она взялась. То ли из where-блока, то ли из этого же файла, то ли её TH сгенерил, то ли просто импорт. А редактор подсказать не может, потому что не справляется. Это меня, кстати, больше всего удручает в ФП-языках, компиляторы там умные, но почему-то поддержка IDE никакая. Наверное, только в F# всё более-менее нормально.


                1. 0xd34df00d
                  26.07.2021 20:42

                  Потому что монада более простая штука, так как это математическая концепция, и разночтений там поменьше.

                  Про то и речь.


                  Но всё-равно могут начаться споры по теоркату.

                  А чего там спорить? Это как спорить об определении непрерывной функции.


                  Вы больше работаете с ФП-кодом, вам его проще читать. Я больше работаю с ООП-кодом, мне его проще читать.

                  Вот только до того, как я вообще начал работать с ФП-кодом, я уже довольно долго работал с ООП-кодом. Однако ж что-то это не помогло.


                  Мне вот, например, сложно разбираться с ФП-кодом на хаскеле, где идут вперемешку композиция кучи функций, оператор применения, flip, и всё сверху полито своими кастомными операторами, у которых ты должен помнить приоритет. Однако, есть и такой код, который легко и понятно читать.

                  Ну, секрет в том, чтобы не читать определения функций. Тип прочитали — и всё. А на уровне типов к сожалению нет всех этих флипов, и так далее.


                  Можно подумать, в ООП-языках нет типов. Давайте без снобизма.

                  Это не снобизм, это именно что очень помогающая штука.


                  Я когда не так давно разбирался с одной ерундой на окамле, мне было сильно сложнее, чем средний код на хаскеле — ну, просто потому, что по типу функции непонятно, куда она лезет, меняет ли она какой-то глобальный стейт (или зависит ли от него), и так далее. А в хаскеле у вас MonadReader Env m => Foo -> m Bar, и всё понятно.


                  Тут, короче, важны типы и контроль за эффектами.


                  А ещё зачастую в условной java, IDE намного лучше знает о коде и помогает с ним, нежели в haskell, где вроде компилятор типы может вывести, а вот LSP там работает кое-как. Потом смотришь на функцию, и понять не можешь, откуда она взялась. То ли из where-блока, то ли из этого же файла, то ли её TH сгенерил, то ли просто импорт. А редактор подсказать не может, потому что не справляется.

                  Фиг знает, vim + coc + hls вполне себе показывают, откуда пришла функция и её тип, и даже переходят к определению, если она из вашего проекта.


          1. Alexandroppolus
            26.07.2021 17:24

            При этом почти весь код в ООП-стиле, что я видел, даже написанный людьми, стремившимся следовать этим бестпрактисам, в итоге превращался в неподдерживаемое месиво.

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

            о, мы добавили метод map, у нас теперь функциональщина!

            Да кабы на этом остановились, было бы здорово. Мелкие ФП-шные вставки делают код понятнее, тут ничего не имею против.


  1. anonymous
    00.00.0000 00:00


    1. DarthVictor
      24.07.2021 15:21
      +5

      По поводу ТС: ТС это не язык, это лукавая ООП- бородавка на теле JS. Со всеми признаками сектантства.

      Очень жирно. При всех недостатках TS, связанных с его позиционированием, он ближе к функциональным языкам, чем любой из мейнстримовых языков, кроме Scala и может быть Kotlin.

      Да в TS не может быть нормального сопоставления с образцом (потому что это рантайм функционал) и нормальной вариантностью пожертвовали (видимо для упрощения написания тайпингов к JS).

      Но в TS например нормальный синтаксис для алгебраических типов и нормальный вывод типов при работе с ними. Что позволяет писать хорошо типизированный и поддерживаемый код не используя классов вообще. В типичном React + Typescript проекте классы вообще часто не нужны, а наследование в стиле ES6 вообще почти никогда не нужно.


    1. k12th
      24.07.2021 18:30

      TS отлично работает в ФП. ООП != явная типизация, вот вообще никак. Haskell чистой слезы ФП, есть типы. Ruby насквозь ООП, типизация неявная


      1. 0xd34df00d
        24.07.2021 22:40

        Не надо путать явную типизацию со статической.


  1. dukei
    24.07.2021 13:56
    +3

    Что вы набросились на дедушку? В СССР тайпскрипта не было!

    Его можно понять.


    1. lorus
      24.07.2021 16:39
      +1

      Зато в СССР был Квейсик. И отличался он от Бейсика... типами! И компилятором.

      Ничто не ново под луной.

      Но вот про импорты не помню.


    1. flancer Автор
      25.07.2021 07:43

      Дедушка старенький, дедушка в IT уже больше 20 лет. Дедушка уже может выбирать сам, на чём ему программировать и как. Когда был СССР, дедушка программировал на MK-61. Дедушке есть с чем сравнивать.


      1. Zoolander
        25.07.2021 10:30
        +1

        я тоже программировал на MK-61, точнее - вбивал исходники игр. До сих пор помню, каким волшебным занятием казалась игра в экономическую стратегию, в которой я видел только цифры на экране ))


        1. flancer Автор
          25.07.2021 12:12

          А я в "космос летал" по статьям из "Наука и жизнь" и удивлялся надписи ЕГГОГ на экране, только потом понял, что это ERROR :) А потом считал на нём курсовые на радиофаке, а потом ушёл с радиофака в IT. Да, согласен - это было волшебством.


  1. DarthVictor
    24.07.2021 17:25
    +2

    Я плохо уловил смысловую нить текста, но возможно автору стоит посмотреть https://angular.io/guide/architecture-modules

    Я также не понял, чем отличается

        \Magento\Framework\App\Action\Context $context,

    от

       require("Magento/Framework/App/Action").Context context,

    Вообще при помощи мата и пластыря глобального объекта и Proxy можно попробовать реализовать что-то вроде (насчет синтаксиса не уверен):

    Magento.Framework.App.Action.Context context,

    При этом стоит помнить, что

    • Импортируемые файлы в ES / TS — это всё-таки модули, и у них есть определенные правила инициализации. Например функция, которую вы импортируете в JS, может хранить в своем замыкании результат вычислений, полученный при первом обращении к модулю.

    • У вас скорее всего отлетит tree-shaking, не говоря уже о module-federation, хотя на бэке это не будет существенно.


    1. SerafimArts
      24.07.2021 17:34
      +2

      Я также не понял, чем отличается

      Первое не зависит от структуры файловой системы и вообще чего-либо, в отличии от второго. В Java это не так явно выражено (там есть привязка к classpath) в отличии от PHP, где этот класс может располагаться хоть в архиве, загружаться через eval или вообще храниться в оперативной памяти изначально при загрузке компилятора/интерпретатора.


      Помимо этого конструкция "Magento\Framework\App\Action\Context" является декларативной, означающей лишь определение сигнатуры. Если мы говорим про непосредственно пых, то самого класса может вообще не существовать в природе и он даже не будет подгружаться, если метод этот не будет вызываться. В отличии от второго кейса, где инструкция является императивной, требующей загрузки явно по указанному пути.


    1. flancer Автор
      25.07.2021 07:57

      По поводу отличий коллега @SerafimArts ответил. Первое - декларация типа, второе - я даже не уверен, что это будет работать в PHP.

      Вообще при помощи ... глобального объекта и Proxy можно попробовать реализовать

      Что-то типа этого и получилось. Контейнер - своего рода "глобальный объект". Узкоспециализированный, только для загрузки модулей, создания объектов и их хранения, но тем не менее практически каждый runtime-объект приложения им порождается.

      Импортируемые файлы в ES / TS — это всё-таки модули, и у них есть определенные правила инициализации.

      Они продолжают работать. Импорт происходит в неявном виде, просто не применяются импорты в явном.

      У вас скорее всего отлетит tree-shaking, не говоря уже о module-federation

      А вот тут получается интересно, фронт тянет только те модули, которые он использует. Загрузка модулей на фронт получается on-demand. Поэтому такая схема может использоваться в PWA (с service worker'ами и кэшированием кода), а в обычных web-приложениях будут накладные расходы на загрузку исходников по мере их использования. Но, как я уже объяснял, я во главу угла ставлю свои приоритеты, а лишь затем - интересы бизнеса и пользователя. А для меня важно иметь возможность как можно проще рефакторить свою кодовую базу.


  1. debagger
    24.07.2021 18:18
    +4

    Мне наоборот нравится наличие импортов в мире JS/TS - импортируешь в свой модуль ровно то, что в нем будет использоваться. В итоге у тебя ничего лишнего в подсказке нет - раз. Если будешь смотреть этот файл через энное количество времени - сразу с первых строчек понятно - что в нем будет использоваться - очень удобно.

    На яве и пхп писать почти не доводилось, но довольно много пишу под .net и вот там в подсказке ...смешались в кучу люди и кони... и чего в этом хорошего, не знаю, честно. С этим конечно жить вполне можно - иде решает плюс можно удобно все раскидать по неймспейсам, но подход, принятый в js по мне не хуже вот не капельки...


  1. Lodin
    24.07.2021 22:09
    +1

    Импорты в ES2015+ существуют в таком виде потому что это необходимо браузерам для статического анализа файла и предзагрузки файлов-зависимостей. Только после этого происходит выстраивание полного AST программы, и начинается компиляция в байт-код. Именно поэтому не подходят варианты in situ, как в PHP или Java, именно поэтому отказались и от нодовского require, который подгружал файл-зависимость прямо на месте. И именно поэтому динамический импорт (который import()) — асинхронный.

    Что касается использования файловой системы вместо модульной, то причина всё та же — браузер изначально работает с URL, что позволяет делать вот такие штуки:

    import confetti from 'https://cdn.skypack.dev/canvas-confetti';

    Как вы понимаете, сделать такое с модульной системой Java или PHP нереально — потребуется пакет-менеджер, в то время как JS успешно работает вообще без него. Можете взглянуть на Deno для примера.


    1. flancer Автор
      25.07.2021 08:01

      Да, я вот как раз по этим причинам и написал свой DI - пока разбирался, как работает динамический import, и что нужно, чтобы один и тот же код без изменений отрабатывал и в nodejs, и в браузере. Оказалось, что нужно отказаться от межпакетного импорта. Внутри пакета импорты работают одинаково и для nodejs, и для браузера, а межпакетные в браузере не работают.


  1. mSnus
    24.07.2021 22:12
    +2

    Сам вопрос интересный, но такой способ его не стала 2Я то э задать -- верный метод отхватить минусов и поднять вашу холивара!

    Вообще TS особая штука, там многое изобреталось заново по ходу развития, и отличия от JS именно в ходе мышления программистов -- свой мир, своим терминология и привычки. И, естественно,у них нет ни одной причины доказывать вам что-то или спорить с вами.

    Вы ведь не предлагаете им "делайте как я, и у вас всё ускорится, сэкономите кучу времени на экспортах". Вы скорее предлагаете им "признайте, что все это время заблуждались, всё делали коряво и до элементарных вещей не додумались, а я вот сумел". Ну какая тут может быть реакция?)))


    1. flancer Автор
      25.07.2021 08:06

      Ну, я бы не сказал, что автозагрузка кода - это элементарные вещи :) В Java - возможно. Она с этим родилась, а в PHP были различные варианты, пока не пришли к единому варианту. Я помню времена, когда в каждом более-менее объемном приложении был свой собственный автозагрузчик. Это когда ещё не было composer'а.

      А в целом, вы верно отметили и за минуса, и за холивар. Я, как православный буддист, терпимо отношусь к смене веры и языка программирования, но православных буддистов не так уж и много.


  1. nin-jin
    25.07.2021 05:20

    MAM: сборка фронтенда без боли - это про автолоад на js/ts.

    Инверсия контроля на голом TypeScript без боли - а тут ответ на главный вопрос вашей статьи.


    1. flancer Автор
      25.07.2021 08:12

      MAM - это здорово, а вы не могли бы кинуть ссылку на код своего es-модуля (не ts - я не пойму), который загружается, как в браузере, так и в nodejs-приложении, и у которого в зависимостях есть другие es-модули? Мой - вот. Я понимаю, что мой shared-модуль может загружаться только моим автозагрузчиком. Я просто хочу посмотреть на код вашего.


      1. nin-jin
        25.07.2021 08:46

        Что-то типа такого. Правда я не особо понял, что ваш код делает, чисто механически портировал.


        1. flancer Автор
          25.07.2021 09:22

          Это просто структура объекта. Причём именно es-модуль, а не ts, как у вас. Вы можете скопировать этот модуль в вашу девелоперскую среду (например, в mod.mjs) и импортировать его в HTML файл:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <script>
                  import ('./mod.mjs')
                      .then((mod) => {
                          const Namespace = mod.default;
                          const dto = new Namespace();
                          debugger
                      })
              </script>
          </head>
          </html>

          Разумеется, и html, и mod.mjs нужно выкладывать на web-сервер или отключать CORS в браузере, если запускаться локально с диска.

          Я ничего не могу сказать за ваш код, потому что он в таком виде (без транспиляции или транспилятора) не может быть использован в браузере.


          1. mSnus
            25.07.2021 10:33
            +1

            Простите, а зачем вообще, чтобы код мог быть использован в браузере as is? Сейчас даже CSS сначала компилируется, оптимизируется, пакуется, зачем же с JS/TS иначе?


            1. flancer Автор
              25.07.2021 12:19

              Я не пишу таких сложных приложений, где требуется LESS, мне хватает возможностей CSS 5. А с упаковкой и оптимизацией я в Magento напарился - всё свернул и оптимизировал и UI поплыл. А что стало причиной - непонятно.

              А вы можете без транспиляции сказать, что значит extends $.$mol_object? Это к вопросу о понимании человеком кода "с листа".


              1. nin-jin
                25.07.2021 13:12

                А вы можете без транспиляции сказать, что значит extends $.$mol_object?

                Ну здравствуйте, это обычный JS.


                1. flancer Автор
                  25.07.2021 13:16

                  Здравствуйте! А где код для этого объекта? Транспилятор его тоже не нашёл. А в своём коде вы тоже не указали, где искать исходник. Что он делает, этот объект? Что мы расширили? Вы пропустили import для исходников этого объекта.

                  Это обычный неработающий JS.


                  1. nin-jin
                    25.07.2021 13:41

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


              1. mSnus
                25.07.2021 14:34
                +2

                Понимаете, есть языки, а есть диалекты. Есть профессиональные диалекты. Где-то скажут "системный блок электронно-вычислительной машины", где-то "системник", где-то "ящик". Язык один, диалекты разные.

                Вы, на самом деле, тоже на своём диалекте говорите, и кому-то он понятен, кому-то нет. Мне вот пример из начала вашей статьи абсолютно не нравится, мне кажется, spec ["very_long_key"] это вообще очень неправильный подход, от которого всеми силами надо бежать.

                Но.. ваш диалект, ваше право. Хотите -- на здоровье. Я не хочу -- и пожалуйста)

                А то, что для понимания диалекта мало знать сам базовый язык -- это вообще нормально.