Галоўная / Артыкулы / Што насправды робіць і чаго не робіць падтрымкі Node.js для TypeScript у варыянце Native

Што насправды робіць і чаго не робіць падтрымкі Node.js для TypeScript у варыянце Native

У этай статыце пояснюецца, як Node.js запускае файлы .ts натыўна за дапамою адключэння типаў, чыму ён прыходзіць да ігноравання пераканальвання типаў, і калі вам усё ж патрэбны справжніе крокі будовы.

1595 слоў

Вы вже знаеце, як гэта працюе. Вы стварылі новы проект на TypeScript, напісалі свой першы файл .ts, прабавілі яго запрацавіць, і адразу ж згадалі, што спачатку трэба адкрыць цэлы ритуал налаштавання: запрацоўваць ts-node або tsx, налаштаваць tsconfig.json, можа, стварыць скрыпт для кампіляціі, і выясніць, чы робіце вы роботу з CommonJS чы ESM. Нічога з гэтага сама па сабе не ўскладнена. Гэта проста перакосы, якія накапліваюцца прытаманнае, пакуль вы не запісаўце жадной рэальнай логікі прыемлена, і гэта адбываецца ў кожны раз, калі вы пачынаеце ўсё новае.

У гэтам годзе для значнай часткі рэальных проектаў на Node.js весь гэты рытуал проста зник. Дастатнька толькі запісаць node file.ts — і все працуе. Ніякіх флагаў, ніяких дадатковых залежнасцей, ніякіх файлаў налаштавання. Змены былі впрынесены тыха, без якіх-лебе большых аанунсаў, але гэта тып такіх маленькіх усуненняя перакосаў, з якімі інакш павінен быў сталкацца ўсё жыце — і гэта разам дае ўсё тое, што справды варта дакладнага абстракцэю.

Што на самай працы выкананае

Адпаведны механізм называецца type stripping, і гэта назва ўсё абоцяны літэральна: Node.js парсуе вашы код на TypeScript, адключае анотаціі типаў і выкананае тое, што застаецца з сабой у виглядзе звычайнага JavaScript. Гэта і є весь сэнс.

// Before: what you write
interface User {
  name: string;
  age: number;
}
function describeUser(user: User): string {
  return `${user.name} is ${user.age} years old`;
}
// After: what Node.js actually executes, post-stripping
// (whitespace preserved, so line numbers stay accurate for debugging)
function describeUser(user) {
  return `${user.name} is ${user.age} years old`;
}

Заявленне interface прагматычна зникае. Антанаціі на кшталт : User і : string таксама стыраюцца. Што застаецца, такім чынам, — звычны, правільны JavaScript, які V8 выконвае так жа, як і ранейш; няма нічога спецыяльнага пад час выконання, ніяких поліфілів, і ў цэлым нічога новага не выканана пад час екзекуцыі.

У закуліссяй гэты процес адбываецца за дапамою бібліятэкі пад назвай Amaro, якая ўособліваеся лёгкім кэшам наваколо @swc/wasm-typescript — кампіляцыі WebAssembly парсера TypeScript, створанага SWC на мове Rust. Шырока скорасць тут не вынікае з якіх-небудзь хытрых оптымаўзацый; яна вынікае з таго, што выпрацоўваецца значна менша колькасць работы, чым у повнага кампіляра. Бн не разгледвае типы ў калькі файлаў, не пераканваліваець, чы правілі вашых анотацый, і не стварае файлаў з дэкларацыямі. Ён проста парсуе дрэво сынтаксісы, адключае тыя элементы, якія існуюць толькі ў TypeScript, і вяртае JavaScript. Самэ гэты вузкі дыяпазон і є прычыной яго шырокай скорасці.

Падрэбніцтва Node для гэтай функцыялкі праходзілі через калькі этапаў, перш чым досяглі своёй ныячайшай формы: эксперыментальнае падрэбніцтва для простага адзьёмленьня типаў з’явілася ў версіі v22.6.0, а окремы флаг для обработкі болей сложных структураў, такіх як enum’ы, — у версіі v22.7.0. Уся гэтая функцыялка стала стабільной за замовчаннем як у версіі v22.18.0, так і ў v24.3.0 — адносна чаго для коду, які падпадае пад падтрымваную сынтаксіс, зовсама не патрабуецца ніякіх флагоў. Следавае зазначыць, што пазней Node абоўсумова адмовілася ад таго флага, прызначанага спецыяльна для enum’аў, выбравшыся настойчыва прыдбіваць часткавы і прагнозаваны дыапазон падтрымкі замест таго, каб адмахнуцца ад падтрымкі всіго языка.

Чыстыя меры

Гэтае ёсць ключовая частка, якую трэба зразумець, якщо вы намераваецеся паслужыцца гэтай функцыялкай, і яе трэба чыста сказаць: адзьёмленьне типаў — гэта не тое ж самае, што і перакананне типаў.

Адзынакцыя типу не падтверджае спачатку, што яна правильная — яна проста выдаліваецца. Таму файл, які мае справжню памылку типу, напрыклад, калі строка передаецца там, дзе была неабяжна цыфра, будзе працаваць без жадных проблем пасля адзынакцыі типу, адколі калі код нарэшце будзе выкананы, інфармацыя пра тип які могла бы паказаць проблему, вже не існуе. Усі аўтарытыетныя джерагі на гэту тэму схвалююць адно і то ж парадзыванне: продовжыце выкананне tsc --noEmit як окремы крок у вашай CI-паіплайн. Адзынакцыя типу заменяе крок будовы, а не задачу компілятора па фактычнам выяўленню памылак.

Серьознейшыя обмежэння стосуюцца таго, якая саме синтаксіс TypeScript падходзіць для адчынення. Node падтрымляе толькі так званую синтаксіс, яку можна адчыніць — структуры мовы, якія можна выдаліць цэлком без змены таго, што робіць код пад час ўработы. Значны частак TypeScript не падпадае під гэтыя вакары, таму што ён стварае рэальную працэздатнае паведамленне, якое нельга проста выдаліць:

// ❌ Fails under type stripping — enums generate a real runtime object
enum Direction {
  Up,
  Down,
  Left,
  Right,
}
// ❌ Fails - parameter properties generate constructor assignment code
class Point {
  constructor(public x: number, public y: number) {}
}
// ❌ Fails - this is a CommonJS-style module alias, not an erasable type
import fs = require('fs');
// ❌ Fails - angle-bracket type assertions look like real syntax to strip,
// but the parser can't tell it apart from JSX safely
const num = <number>someValue;

Кожна з эых структураў спрычыняе явную памылку замест таго, каб будова працавала некоректна без жадных сігналаў — механізм адсёлкавання дэталей у Node ўжо з самага пачатку створаны так, каб зупініцца і паставіць запитанне, а не проста здагадвацца, што вы мелі на увазе. Декоратары у старом стылі, якія ўвядзены за дапамогою старога флага experimentalDecorators, сталкнуцца з тым жа проблемам з той самыя прычыны. Новейшыя декоратары, якія адпавядаюць стандартам TC39, — гэта іншая справа: яны заданы так, каб ператварыліся на звычную синтаксіс JavaScript, таму няма нічога спецыяльнага, што трэба было б адсёлкаваць, і яны працуюць у Node без жадных проблем.

Як адаптаваўся TypeScript

У замест на тое, каб разработчыкі пашукалі гэтыя межы адна файл за аднам, запускаючы код, команда TypeScript швытка прыняла рашэнне ясна выказаць гэтыя правілы. У версіі TypeScript 5.8 быў адкрыты новы флаг кампайляра --erasableSyntaxOnly, які змушвае сам tsc адхіліць будзь-яе з вышэўказаных немагчымых да выдалення патэранаў пад час кампайлявання. Гэта ператварае пытанне "чы рэальна Node запусці гэты код" з чагось, што трэба выявляць цёжкім спосабам пад час рэалайзу, у правіла, якое можна застосавіць з самага пачатку, як чыстаю абмежэння для всей базы коду.

// tsconfig.json
{
  "compilerOptions": {
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true  // pairs well with this —
                                   // keeps type-only imports explicit
  }
}

Значыць увімкнуць гэты варыянт, нават якшо ў вас пакуль немае планаў адменаваць этап будовы, проста таму, што гэта дае вам чыстаю, аўтаматызаваную адпаведь на тое, чы пасходзі ваш код — замест таго, каб даведацца часткова, калі ўсё ламаецца.

Колькісна величына роботы з міграціі, яка выявляецца пад час такога падходу, сильна змінюецца залежна ад таго, з чаго вы запускаеце процес. Нова створаная служба на бакэндзе або інструмент у командной лініи зазвычай можа відразу увялічыць параметр erasableSyntaxOnly, практычна не выконваючы жадных змян. Адно часо, калі кодавая база сильна залежыць ад декларацый enum або створена на фреймворку, які выкарыстоўвае старыя декоратары — прыкладамі часта ёсць старэйшыя конфігурацыі NestJS або TypeORM — патрабуеяцца сэрьёзныя зусілля па перапісве коду, або ж свядомы выбор застацца з традыцыйным процесам будовы, а не мігруваць усё заодно. Найнадзеяный спосаб разрахаваць обсяг роботы перш чым прыняць які-небудзь рашэнне — це увялічыць параметр erasableSyntaxOnly, запрацаваць команду tsc --noEmit аднойчы і проста паглядзець, сколькіх памылак будзе выявлена. Цей адзін запуск пакажае вам рэальны масштаб проблемы перш чым вы будзеце зміняць які-небудзь параметры рантайму.

Практычныя рэкамендацыі

У командах, якія вже прыйшлі да такога пераходу, выклікаўся досыць аднаковы працэс прыняття рашэнняў.

Не трэба выканання крока будовы для сервісаў задньага фону, інструментаў у командной лініи, внутрашняях утиліт і самастоятельных скрыптаў — гэта все тое, што запускаецца безпосередна пад Node і не выклікаецца як пакет для вжывання іншымі. Гэта самэ тое разлічэнне, для якаго была створана функцыя адсечкі элементаў, і сервісы, створаныя на Express або Fastify, часта працуюць з яю без жадных змян у кодзе.

Зберагаеце крок будовы для всьго, што запускаецца ў браузеры, адколькі браузеры вообща не можаць выкананыя файлы .ts — вам усё рава патрэбны засобы для агрупавання коду, незалежна ад таго, што падтрымлівае Node на серверы. Таксама зберагаеце крок будовы для любых пакетаў npm, якія вы публікуеце, адтолькі людзі, якія іх інсталююць, патрэбуюць скомпіляванага JavaScript асо бяга файлаў декларацый .d.ts, і вы не можете гарантаваць, што версія Node у ўпынку падтрымлівае адключэнне типаў. Таксама зберагаеце крок будовы для любых баз коду, якія ўсё яшчэ выкарыстоўваюць старыя декоратары або множыства enum, якія ўсё яшчэ не былі пераканвертаваныя.

Які бы паход вы не выбраў, продовжайце запускаць tsc --noEmit у CI. Адменшэнне крока будовы выключае толькі крок компілявання — ён ніколі не быў прызначаны для выключэння перакантролі типаў, і спрыяванне яго як замены tsc — гэта ўсё, што можа таямна паспрабаваць зашкодзіць вашай безпецы.

Асалодны вывад

Тое, што робіць гэтыя змены цікавым, — это не столькі прырост скорасці, хоць швэдзейшы цикл зворотнага зв’язку ўпроцьвітае справжню, негайную перавагу. Это сігнал пра тое, куды концэптуальна праеходзіць TypeScript. Большую частку свайго існавання TypeScript апісвалі як мову, якая кампілюецца у JavaScript — аднойчынную мову, якая перакладаецца прытым, перш чым можа быць выкананая. Тое, што тайныя процесы адсірвавання типаў у Node натычна падказваюць, — это тое, што TypeScript праеходзіць да таго, каб яго спачатку вважалі б больш варыянтам JavaScript, які рантым часам можа проста чытаць такім, які ён є, прынеймна для шырокага, повсякдзеннага сабсэту мовы, які насправде викорыстоўваюць большасць разработчыкаў. Гэта не вся мова, і ніколі яе не будзе — enum-ы і старыя декоратары все ўсё маюць рэальныя прыклады викорыстоўвання і не зникаюць. Але для всіх кодаў, якім яны не патрэбны, крок, які раней стаяў межы між напісаннем TypeScript і яго выкананнем, больш не ўтварае обавязку.

Спадзяючыяся статты