Как запустить код: Node, tsc и первая программа
- Зачем это нужно
- Node — среда выполнения, и она не блокчейн
- Три вещи, которые программа отдаёт наружу
- Способ первый: компилятор собирает .js, потом Node его запускает
- Способ второй: node запускает .ts напрямую
- Чего прямой запуск не умеет
- Что остаётся после компиляции
- Три вида ошибок и кто их ловит
- Паспорт проекта: package.json
- Настройки компилятора и строгий режим
- Типичные ошибки
- Слова главы
- Тренажёр: набрать вслепую
- На чемпионате
- Что дальше
- Углубиться
- Куда дальше: проверенные ресурсы
Зачем это нужно
В Solidity вопрос «как запустить» почти не стоял: контракт компилировался и уезжал в сеть, а вызывал его кто угодно — кошелёк, другой контракт, скрипт.
Обычная программа так не работает. У неё нет сети, которая её подхватит: её запускают руками, командой в терминале. Эта глава — про то, что происходит между строкой в файле и текстом на экране, и почему у одной программы на TypeScript есть три разных способа запуска, отличающихся по времени в тридцать раз.
Node — среда выполнения, и она не блокчейн
Среда выполнения (runtime) — программа, которая читает ваш код и исполняет его. В Solidity такой средой была виртуальная машина сети: ни файлов, ни исходящих запросов, ни ввода с клавиатуры; время видно только как метка блока, зато есть газ и вечное хранилище.
Node — противоположность:
| виртуальная машина сети | Node | |
|---|---|---|
| файлы, сеть, часы | нет | есть |
| хранилище между запусками | вечное | нет: всё живёт до конца процесса |
| плата за работу | газ | нет |
| кто запускает | транзакция | человек командой |
Внутри Node лежит тот же движок, что в браузере, но без страницы и окна: браузер и серверный процесс — две разные обвязки вокруг одного механизма.
Три вещи, которые программа отдаёт наружу
Обычный вывод. console.log печатает аргументы через пробел и переводит строку. Несколько аргументов писать удобнее, чем склеивать строку плюсом: числа и объекты Node форматирует сам.
Поток ошибок. Туда уходят console.error, предупреждения и все необработанные исключения. Поэтому падение видно на экране, даже когда обычный вывод перенаправлен в файл.
Код возврата. Ноль — «отработала», любое другое число — «упала». На него смотрят все инструменты сборки, и в этой главе он встретится ещё не раз.
Способ первый: компилятор собирает .js, потом Node его запускает
.ts — то, что пишет человек: там есть аннотации типов и интерфейсы. .js — то, что исполняется: там ничего этого нет. Между ними всегда есть перевод; вопрос только в том, кто его делает и остаётся ли результат на диске.
npx tsc # проверяет типы и пишет app.js рядом
node app.js # запускает то, что получилось
Первая модель проходит все три способа и печатает, что происходит:
до всего: app.js рядом с app.ts есть? false
=== способ 1: tsc собирает .js, потом node запускает .js ===
npx tsc : код возврата 0 , вывод пустой: true
появился app.js : true - 601 байт
node : v24.14.1
tsc : 6.0.3
komissiya: 25
=== способ 2: node app.ts напрямую ===
появился app.js : false
node : v24.14.1
komissiya: 25
=== что дороже: замер этого прогона ===
сборка дольше запуска .js : true
node app.ts дольше node app.js : true
(сами миллисекунды пляшут от прогона к прогону, порядок — нет)
=== способ 3: тот же файл, но срезание типов выключено ===
код возврата 1 - TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"
Компилятор даёт две вещи, которых нет у остальных способов. Первая — проверка типов: код возврата 0 означает «типы сошлись». Вторая — файл на диске, который можно положить в образ и запускать там, где компилятора нет вовсе.
Способ второй: node запускает .ts напрямую
Современный Node умеет читать .ts сам: он вырезает из текста всё типовое и исполняет остаток. Никаких настроек не нужно — просто node app.ts.
Ключевая оговорка, ради которой этот раздел и написан: Node ничего не проверяет. Он не знает про компилятор, не читает его настройки и с удовольствием запустит файл, который компилятор отверг. Это мы увидим в третьей модели.
Прямой запуск удобен для скриптов: развернуть контракт, посчитать балансы, дёрнуть узел. Для кода, который сдаётся, проверку типов всё равно запускают отдельно — иначе смысл языка теряется.
Чего прямой запуск не умеет
Срезание типов работает там, где удаление символов оставляет годный код. Четыре конструкции этому не подчиняются, потому что требуют генерации кода. Модель запускает каждую:
enum-demo.ts -> код 1 | SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]:
TypeScript enum is not supported in strip-only mode
ns-demo.ts -> код 1 | TypeScript namespace declaration is not supported…
pp-demo.ts -> код 1 | TypeScript parameter property is not supported…
ia-demo.ts -> код 1 | TypeScript import equals declaration is not supported…
=== те же файлы, но через компилятор ===
node enum-demo.js : код 0 , напечатано: 0 Lender
Падение происходит до первой строки выполнения: это отказ прочитать файл, а не ошибка в программе.
Лечится двумя способами: собрать компилятором — или не пользоваться этими четырьмя конструкциями в скриптах. Второе проще: перечисление заменяется набором строковых значений, поле в конструкторе пишется длинной записью.
Что остаётся после компиляции
Вторая модель компилирует файл и печатает результат целиком:
sample.ts: 21 строк, 768 символов
sample.js: 19 строк, 579 символов
=== сгенерированный sample.js целиком ===
var Role;
(function (Role) {
Role["Lender"] = "lender";
Role["Borrower"] = "borrower";
})(Role || (Role = {}));
class Vault {
total;
constructor(total) {
this.total = total;
}
}
const age = 17;
const wallet = { address: '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B' };
const vault = new Vault(1000);
…
=== что печатает запуск собранного .js ===
typeof age : number
typeof wallet : object
typeof Role : object -> lender
typeof Vault : function -> 1000
=== поиск имён в готовом .js ===
Age вычеркнут компилятором
Wallet вычеркнут компилятором
Role остался в .js
Vault остался в .js
Два вывода.
Типы существуют только до запуска. Псевдоним типа и интерфейс вычеркнуты — в готовом файле их нет вообще. Проверка типа во время работы даёт number, а не имя вашего типа: имени уже не существует.
Для человека, пришедшего из Solidity, это переворот. Там struct — это ещё и раскладка в хранилище, реальная вещь. Здесь тип — заметка для компилятора, которая исчезает.
Не всё исчезает. Перечисление превратилось в объект, класс — в функцию. Ровно то, что остаётся кодом, прямой запуск и не умеет: удалить это, ничего не сгенерировав, нельзя.
Три вида ошибок и кто их ловит
Третья модель кладёт в одну папку три сломанные программы:
=== проверка типов до запуска: npx tsc --noEmit по всей папке ===
код возврата 2
bad-type.ts(2,7): error TS2322: Type 'string' is not assignable to type 'number'.
=== ошибка 1: несовместимый тип. Что скажет node? ===
node bad-type.ts: код возврата 0
zalog plus sto = 1500100
=== ошибка 2: во время выполнения ===
node bad-runtime.ts: код возврата 1
TypeError: Cannot read properties of undefined (reading 'lltv')
at lltvOf (file://bad-runtime.ts:13:25)
=== ошибка 3: логическая ===
node bad-logic.ts: код возврата 0
LTV : 0.9
порог LLTV : 75
ликвидировать? false
а на самом деле 90 >= 75 -> true
=== итог ===
tsc поймал ошибок: 1
упало при запуске : 1 из 3
Самое поучительное — первая строка результата. Компилятор отверг файл с кодом возврата 2, а Node запустил его и напечатал 1500100: строка «1500» склеилась с числом 100 вместо сложения. Программа отработала успешно и выдала бессмыслицу.
Вторая ошибка видна только в запуске: обращение к несуществующему элементу. Знакомое Cannot read properties of undefined — самая частая ошибка в этом языке вообще, и следующая глава целиком про то, откуда она берётся.
Третья не ловится ничем. Условие ликвидации перепутано, типы сошлись, программа не упала — и напечатала неправду. Такие ошибки ловят только тестами и сверкой чисел.
Паспорт проекта: package.json
Файл создаётся одной командой и описывает проект: имя, версия, зависимости и короткие команды. На запуск влияют два поля.
Тип модулей. Строка "type": "module" переключает проект на современный способ подключения файлов. Без неё Node сначала пробует старый формат и печатает предупреждение, которое прямо подсказывает починку.
Зависимости для разработчика. Компилятор и прочие инструменты ставятся отдельно от того, что нужно самой программе, — командой с ключом -D.
Рядом появляются папка с установленными пакетами и файл с точными версиями всего дерева. Правило: описание проекта и файл версий кладут в репозиторий, папку с пакетами — никогда; её восстанавливают одной командой.
Установка компилятора в живом прогоне создала папку на 26 МБ из 281 файла — при двух строчках в списке зависимостей. Это нормально: пакеты тянут другие пакеты. Именно поэтому папку не кладут в репозиторий и не носят с собой.
Настройки компилятора и строгий режим
Компилятор читает файл настроек — там указывают, какую версию языка выпускать, куда класть результат и насколько строго проверять.
Одна настройка важнее всех: строгий режим. Он включает набор проверок, главная из которых — запрет молча обращаться к тому, чего может не быть. Без него компилятор пропустит половину ошибок, ради которых язык и берут.
Правило простое: строгий режим включён всегда. Следующая глава показывает, что именно он ловит.
Типичные ошибки
node app.ts на файле с перечислением. Отказ прочитать файл: конструкция требует генерации кода.
Ожидание, что прямой запуск проверит типы. Не проверит. Проверка — отдельная команда.
Запуск .ts без поддержки срезания типов. ERR_UNKNOWN_FILE_EXTENSION — среда не понимает расширение.
Отсутствие строки про тип модулей. Предупреждение при каждом запуске; текст сам подсказывает починку.
Папка с пакетами в репозитории. Десятки мегабайт и сотни файлов вместо двух строк в описании проекта.
Глобальная установка компилятора вместо локальной. На другой машине версия окажется другой, и сборка поведёт себя иначе.
Компилятор вызывают по полному пути. Есть команда, которая находит локальный инструмент сама.
Игнорирование кода возврата. Сборка «прошла», хотя компилятор вернул 2 — в автоматической проверке это выяснится слишком поздно.
Слова главы
- Среда выполнения (runtime) — программа, исполняющая ваш код; здесь это Node.
console.log/console.error— обычный вывод и поток ошибок.- Код возврата — 0 «отработала», иначе «упала».
.tsи.js— исходник человека и то, что исполняется.- Компилятор — проверяет типы, стирает их и пишет результат на диск.
- Срезание типов — прямой запуск
.ts: типы вырезаются, ничего не проверяется. - Строгий режим — набор проверок компилятора; включён всегда.
- Описание проекта — имя, версии, команды; тип модулей и зависимости разработчика.
- Файл версий — фиксирует точные версии всего дерева пакетов.
Тренажёр: набрать вслепую
Цель: скорость ≥ 100 зн/мин, точность ≥ 88%
npm init -y
На чемпионате
Модуль А. TypeScript здесь — язык скриптов: развернуть контракты, раздать стартовые балансы, провести шаги демонстрации. Практические выводы:
- скрипты запускают напрямую — так быстрее и не остаётся мусора рядом с исходниками;
- проверку типов при этом всё равно запускают отдельно, иначе опечатка выяснится в середине демонстрации;
- в скриптах не используют перечисления и поля в конструкторе — прямой запуск их не примет;
- исполняемый файл запуска решения, который требует задание, — это те же команды, собранные в одну.
Проверка площадки в первые минуты. Посмотреть версию среды выполнения и наличие компилятора — одна команда. Если компилятора нет и интернета нет, остаётся прямой запуск: значит, ограничение на четыре конструкции становится обязательным.
Что дальше
Программа запускается, ошибки видно. Следующая глава — про самое частое падение в этом языке: обращение к тому, чего нет. В Solidity такого не было вовсе — словарь всегда возвращал нули. Здесь появляется идея «значения нет», и у неё два разных обличья.
Углубиться
- Флаг полного преобразования — включает генерацию кода вместо срезания и снимает ограничение на четыре конструкции.
- Инструменты запуска без сборки — что делали до того, как среда научилась читать
.ts, и почему их всё ещё встретишь в чужих проектах. - Карты соответствия — как показать номер строки исходника, когда исполняется собранный файл.
- Команда точной установки — чем отличается от обычной и почему на автоматической проверке используют именно её.
- Аргументы командной строки — как передать скрипту адрес контракта, не правя исходник.
Сводная проверка по всей главе: 8 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.
Куда дальше: проверенные ресурсы
- Node.js: запуск TypeScript — срезание типов, ограничения, флаг полного преобразования.
- TypeScript: tsconfig — все настройки компилятора с пояснениями.
- TypeScript Handbook: TS for JS Programmers — краткий обзор языка.
- npm: package.json — все поля описания проекта.
- Node.js: process — код возврата, аргументы командной строки, потоки вывода.
Комментарии
Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.