Почему лучше заранее компилировать TS в JS

Однажды встал вопрос: «Использовать ранеры, которые будут на лету компилировать TypeScript в JavaScript (например, node-ts), или компилировать самому заранее (например, через `tsc`) и запускать уже JavaScript код?» – гугление не дало четкого ответа, поэтому я сформулировал его императивным путем:
TypeScript стоит компилировать заранее, через специализированные компиляторы (например, tsc) и запускать уже JS код.

Пара злободневных причин:

Кроссплатформенность


Вы никогда не знаете, где будете разворачивать ваш код.

Сегодня это сервер контролируемый вами, где можно использовать node-ts, завтра это AWS Lambda или Google App Engine, куда нужно деплоить JS код.

При переходе с node-ts на предварительную компиляцию могут возникнуть непредвиденные ошибки и возня с изменениями в конфигурации CI/CD и подобного, поэтому надежнее сразу начинать с предварительной компиляции.

Поймай если сможешь!


Отвратительные неочевидные ошибки…

С одной из них я столкнулся совершенно недавно: написал Node.js приложение на TypeScript, скомпилировал его и задеплоил на Google App Engine.

На личном Маке все работало, а на сервере появлялась ошибка, что нет файла по пути some/folder/path/FileName.ts

Error: Cannot find module 'some/folder/path/FileName.ts' at ...

Можно было бы подумать, что код просто не компилируется, или я загружаю старую версию, или сорвался NODE_PATH и подобное, но все оказалось гораздо хуже:

Поскольку я компилировал заранее, можно было посмотреть какой js код отправляется на сервер. И тут стало ясно, что только этот ОДИН файл компилируется не с оригинальным название: FileName.ts – а с маленькой буквы: fileName.ts

image

Полез узнавать в чем дело и выяснилось:

(1) Компилятор TS называет скомпилированные файлы не в соответствии с названием их оригиналов, а в соответствии с импортами в коде.

То есть, если вы написали с маленькой буквы import * from "some/folder/path/fileName", а оригинал называется с большой буквы (FileName.ts), то TS скомпилирует его в файл с маленькой буквы. Даже если в остальном коде будут везде импорты с большой буквы: import * from "some/folder/path/FileName"

(2) На мак Node.js приложения не учитывает регистр, поэтому все спокойно работало

(3) На Google App Engine регистр учитывается…

У меня была опция компилировать ts файлы на лету и отправлять их в Google App Engine и тогда бы я потратил слишком много времени на выяснение подобной проблемы. Но поскольку я шел путем предварительной компиляции, я мог спокойно зайти в отправляемые JS файлы и подебажить их.

Итог


Если вы используете TS, то сначала компилируйте его соответствующими инструментами (tsc, webpack и т.д.), а только потом запускайте / деплойте на нужную платформу.

Вот когда появится у TS своя VM, тогда… еще пару лет будем компилировать, пока она не появится в разных экосистемах типа Google, Amazon, .etc, а вот уже потоооом заживем.

Что вы думаете на этот счет: есть ли преимущества использования node-ts? Сталкивались ли вы с неочевидными проблемами без предварительной компиляции?

Всем удачи, не дайте дебагингу убить счастье от кодинга!

P.S.


Еще, чтобы в JS вообще не было проблем с наименованием, лучше придерживаться «кябаб-кейса», как советует дядюшка Google.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 17

    +1

    Интересно, что бы было, если бы вы скомпилировали на Linux или включив на Маке case-sensitive file system.

      0

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

        0

        А если исходники на case-preserving FS, а результат компиляции на case-sensitive? Два экземпляра js-файла, отличающиеся кейсом, или тоже ошибка?

      +4

      Из-за пары нюансов, не использовать node-ts, что за вредные советы…

        –18
        Тайпскрипт не нужен. Это какая-то небольшая песочница, которая все равно в конечном итоге станет небезопасным js, и будет взаимодействовать с другим js. Примерно как построить домик без фундамента в сейсмологически активной местности и думать, что ты в безопасности. Мне кажется, что если хочется гарантий, то лучше развивать webassembly, чтобы писать на более подходящем языке в случае нужды.
          0

          Пока в web assembly нет сборки мусора туда нереально портировать популярные языки. На Rust не то чтобы хочется писать для веб.

            +3
            Для вас возможно будет открытием тот факт, что любой ЯП является «песочницей», так как в конечном счёте текст программы станет набором машинных инструкций. Значит никакой ЯП не нужен, пишите сразу всё в машинных кодах под конкретный процессор. И прибудет счастье :)
              –2
              Да неужели? Значит, раст, упомянутый чуть выше, который дает определенные гарантии на этапе компиляции, на самом деле обманывает, и код на нем будет не более безопасным, чем код на си? Все равно на один и тот же ассемблер они компилируются.

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

              Думаю, с текущей скоростью развития веба, лет через десять в самом js появятся типы и тогда уже наступит рай для фронтендеров. Примерно как было с coffeescript.
                +1

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

                  –3
                  Вы удивитесь, но я об этом и написал.

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

                  После компиляции раста вы в дальнейшем не изменяете получившийся ассемблер. Вы лишь используете получившуюся программу.

                  После компиляции ts в js, вы продолжаете работать с js. Что-то изменять, добавлять, подключать какие-то либы и тд. На этом этапе тайпскрипта уже нет, остался лишь js, который не дает гарантий. Вы же не будете спорить, что можно взять кусок кода на ts, скомпилить его в js, а потом изменить этот js так, чтобы он падал? Где тогда будут все эти гарантии? Или, к примеру, когда вам нужно будет что-то дебажить — вы будете дебажить js (либо увеличивать размер бандла через доп инфу в соурс мапах). Всё это означает двойную работу.

                  Я понимаю, когда используют scss поверх css — он в разы, если не на порядки, ускоряет разработку и поддержку большого проекта. Но ts не дает подобных ощутимых преимуществ, чтобы его использовать. В js есть много проблем, но если не писать код в стиле вопроса для собеседований «угадай, что выведет компилятор на эту дичь», то все будет нормально. А раз нет никакой разницы, то зачем платить больше лишняя абстракция?
                    0
                    После компиляции ts в js, вы продолжаете работать с js. Что-то изменять, добавлять, подключать какие-то либы и тд. На этом этапе тайпскрипта уже нет, остался лишь js, который не дает гарантий.

                    Нет, я всё это делаю на этапе работы с TS. Все библиотеки, все изменения происходят лишь в тайпскрипт-коде. Затем управление отдаётся бандлеру — webpack или rollup, — который уже компилирует, модифицирует, минифицирует конечный вариант. В общем-то, всё то же самое, что делает, например, LLVM. Разве кто-то сейчас делает по-другому? Зачем усложнять кодовую базу, влезая руками в скомпилированный JS, когда у тебя сорцы на TS? Поэтому-то TS и сравнивают с Rust — оба этих языка являются в первую очередь статическими анализаторами, которые дают гарантии именно на этапе компиляции. Да, ввиду изначально корректной архитектуры Rust даёт больше гарантий, в то время как TS создавался поверх уже существующего JS, что накладывает свой отпечаток. Но это не значит, что TS ничего не привносит — ещё как. Он делает то, что в Rust является одним из краеугольных камней дизайна языка: делает неявное явным. Explicit лучше чем implicit.


                    Или, к примеру, когда вам нужно будет что-то дебажить — вы будете дебажить js (либо увеличивать размер бандла через доп инфу в соурс мапах). Всё это означает двойную работу.

                    Я как-то дебажил Rust через GDB, и скажу вам, source maps или JavaScript — это очень удобно в сравнении :D

            +6
            Ооо, какая злободневная тема. Рискую схватить кучу минусов сейчас, но все проекты с префиксом ts- — это на текущий момент (лето 2020) страшные кривые костыли, к сожалению.
            — ts-node супермегатормоз при рестарте приложения от примерно 50K+ LOC. Плюс он поддерживает только сабсет фич из tsconfig.json.
            — ts-jest работает раза в 3 медленнее jest и имеет те же проблемы с tsconfig.
            — ts-loader для webpack никак не может догнать tsc по производительности (что неудивительно), а также у него проблемы с watch-режимом.

            Также все эти ребята просто пасуют при попытке сделать человеческое monorepo-окружение.

            При этом tsc, как ни странно, очень быстр:
            — tsc -b компилирует супербыстро, если изменений мало;
            — tsc —watch на удивление не глючит практически никогда (привет вотчерам вебпака и nodemon-у);
            — даже использование ttypescript с плагинами не особенно-то его замедляют.

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

            На костыльности утилит с префоксом ts-, кстати, пробует сейчас deno выезжать. Что-то не очень только у него выходит, но вдруг все же выедет.
              0

              А Babel весьма быстро работает, что с Jest, что в вебпаке. В принципе, логично, с учётом того, что всё, что он делает для TS — просто вырезает типы из AST. А проверку правильности типов можно и отдельно от компиляции делать.

                –1

                Babel в 2020-м году (при условии, что основной код пишется в проекте на TS) — это анахронизм и еще больший костыль. TS сам отъел у него значительный кусок пирога и вряд отъест назад.

                  +2

                  Хмм, в первый раз слышу такую точку зрения, если честно. И, пожалуй, с ней не соглашусь. Начиная с того, что у tsc невозможно выключить проверку типов при компиляции (ну, по крайней мере было невозможно на тот момент, когда я проверял в последний раз) и заканчивая тем, что tsc не поддерживает плагины, а значит огромные возможности по трансформации кода при компиляции оказываются отрезаны. Я бы сказал, что, скорее, пользоваться tsc — анахронизм с тех пор, как Babel ввёл поддержку Typescript.


                  P.S. Я не один так считаю, кстати. Самый популярный на данный момент стартер — create-react-app, — использует для трансформации Typescript именно Babel, а не tsc.

                    0

                    Про плагины — посмотрите проект ttypescript, он решает эту проблему.

              +1
              Можно подобные проблемы предотвращать с помощью TSLint. В частности, в данном случае полезным было бы правило file-name-casing.

              Only users with full accounts can post comments. Log in, please.