JS F/k
5 subscribers
3 photos
28 links
HTML/TS/Vue — с примерами, по делу, без воды

https://js-f-k.netlify.app

#html #vue #typescript #npm
Download Telegram
Spellcheck для любого текста

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

cspell — это CLI-инструмент, который проверяет орфографию прямо в коде: в идентификаторах, строках, комментариях и текстовых ресурсах.
Работает быстро, легко настраивается и поддерживает словари для десятков языков.

Пример базовой настройки:
npm install --save-dev cspell @cspell/dict-ru_ru

// package.json
{
"scripts": {
"lint:cspell": "cspell **"
}
}

// .cspell.json
{
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
"version": "0.2",
"language": "en,ru",
"import": ["@cspell/dict-ru_ru/cspell-ext.json"]
}


Так получаем проверку орфографии русских и английских слов за один проход по проекту.

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

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

#linters #npm
Типизированные customEvents

Наверно каждый разработчик хоть раз писал свой CustomEvent-сервис.
Вот и мы написали свой. Но хотя бы типизированный.

Мы используем window.dispatchEvent() и useEventListener() из VueUse, но оборачиваем их, чтобы:
- обеспечить строгую типизацию названий событий и их payload
- иметь автодополнение при вызове событий
- заранее знать структуру event.detail без явных проверок и any

Как использовать:
dispatchCustomDashboardEvent("user-created", user.id);

useDashboardEventListener("user-created", ({ detail }) => {
state = detail; // string, не any
});


Реализация:
import { useEventListener, type Arrayable } from "@vueuse/core";

// Доступные события и payload описываются через интерфейс
export interface CustomDashboardEvent {
"user-created": User["id"]
"user-deleted": never
}
type DashboardEventName = keyof CustomDashboardEvent;

// Создание типизированного события
function createCustomDashboardEvent<EventName extends DashboardEventName>(
eventName: EventName,
payload?: CustomDashboardEvent[EventName]
): CustomEvent<CustomDashboardEvent[EventName]> {
return new CustomEvent<CustomDashboardEvent[EventName]>(eventName, { detail: payload });
}

// Вызов события
export function dispatchCustomDashboardEvent<EventName extends DashboardEventName>(
eventName: EventName,
...[payload]: CustomDashboardEvent[EventName] extends never ? [] : [CustomDashboardEvent[EventName]]
): void {
window.dispatchEvent(createCustomDashboardEvent(eventName, payload));
}

// Прослушивание событий
export function useDashboardEventListener<EventName extends DashboardEventName>(
eventName: Arrayable<EventName>,
callback: (eventData: CustomEventInit<CustomDashboardEvent[EventName]>) => void,
options?: AddEventListenerOptions
): void {
useEventListener(window, eventName, callback, options);
}


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

#typescript #vue #vueuse
This media is not supported in your browser
VIEW IN TELEGRAM
Быстрая очистка node_modules

npkill — это CLI-утилита, которая ищет каталоги node_modules и позволяет удалять их одним нажатием клавиши.

Запуск:
npx npkill


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

⚠️ npkill удаляет каталоги без подтверждения. Убедитесь, что у вас остались lock файлы, чтобы при необходимости быстро восстановить зависимости.

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

#npm
Добавление иконки перед ссылкой

Чтобы ссылка на внешний ресурс выглядела более узнаваемо, можно добавить к ней иконку, используя только CSS:
a[href*="github.com"]::before {
display: inline-block;
width: 1em;
height: 1em;
margin-right: 0.3em;
content: "";
background-image: url('/assets/icons/github.svg');
background-repeat: no-repeat;
background-size: contain;
}
a[href*="t.me"]::before {
display: inline-block;
width: 1em;
height: 1em;
margin-right: 0.3em;
content: "";
background-image: url('/assets/icons/telegram.svg');
background-repeat: no-repeat;
background-size: contain;
}

Работает для всех ссылок, в href которых встречается нужный домен.

Отображение разных иконок для одного домена:
a[href^="https://github.com"] {
--icon: url('/assets/icons/github-icon.svg');
&[href*="/pulls"] {
--icon: url('/assets/icons/github-pull-icon.svg');
}
&::before {
display: inline-block;
width: 1em;
height: 1em;
margin-right: 0.3em;
content: "";
background-image: var(--icon);
background-repeat: no-repeat;
background-size: contain;
}
}


⚠️ Безопасность
Можно указать ссылку на favicon напрямую:
a[href*="github.com"]::before {
background-image: url("https://github.com/favicon.ico");
}


Но есть нюансы:
- такие запросы подчиняются CORS-политике, и браузер может заблокировать загрузку;
- запросы к favicon являются внешними — они могут обмениваться куками и оставлять след в логах целевого сайта.

Поэтому рекомендуется хранить иконки на сервере, либо вставлять их в формате base64.

#css
Forwarded from mefody.dev
llms.txt

Если готовить сайты для поисковиков мы уже давно научились (SSR, sitemap.xml, robots.txt и прочее), то для LLM пока только учимся.

Полина Гуртовая поделилась интересной ссылкой на пропозал 2024 года. Если сайт хочет быть обработанным LLM-краулером, то достаточно в корень сайта положить файл /llms.txt с текстовым представлением всего, что нужно знать о сайте. Оформлять можно в Markdown, роботы с ним очень хорошо работают.

Пока что это пропозал от отдельного комьюнити, формально в процесс стандартизации от W3C или WHATWG его даже тяжело поместить, так как сфера AI ещё слишком юная, стандарты придумываются на ходу. Но тем не менее даже без стандарта наличие такого файла не повредит. Скоро SEO превратится в LLMEO, можно начать готовиться.

https://llmstxt.org/
Поиск влитых git-веток и связанных задач

В крупном репозитории, где одновременно работают несколько разработчиков, может быть до 30 активных веток, часть из которых на паузе, часть в работе, а часть размечены в предстоящий релиз.
После каждого релиза такие ветки нужно удалять, делать это вручную — занятие сомнительное.
Чтобы не пропустить ветку или не удалить нужную, можно воспользоваться git-merged-branches.

git-merged-branches — это CLI-утилита, которая показывает ветки, влитые в основную (master или main).
Работает локально, проста в использовании, не имеет дочерних зависимостей и доступна также под коротким алиасом gmb.

Главная фича — возможность просматривать ссылки на задачи из трекера прямо в списке веток, если в названии используются идентификаторы задач, такие как JIRA-123, BUG-42 и т.п.

Использование
Установить глобально:
npm install --global git-merged-branches

Или запустить через npx:
npx git-merged-branches


Вызвав git-merged-branches, или алиас gmb, утилита определит базовую ветку (master или main) и выведет список веток, которые уже были в неё влиты:
$ gmb

Branches merged into 'master':
bugfix/fix-crash-on-start
feature/add-new-feature
hotfix/urgent-fix


Настройка идентификаторов задач
Если вы используете префиксы задач в названиях веток, например TOKEN-123_fix-layout — можно настроить автоматическое добавление ссылок на трекер задач.
// package.json
{
"git-merged-branches": {
"issueUrlFormat": "https://jira.my-company.com/browse/{{prefix}}{{id}}",
"issueUrlPrefix": ["TOKEN-", "BUG-"]
}
}

Что изменит вывод на такой:
TOKEN-123_fix-layout <https://jira.my-company.com/browse/TOKEN-123>
BUG-56_add-tests <https://jira.my-company.com/browse/BUG-56>


git-merged-branches — минималистичная, но полезная утилита, которая позволит быстро почистить ветки, заодно проверив их статус в трекере.

#npm
Скачиваем приложение с RuStore без регистрации

RuStore — российский магазин приложений, который требует обязательной регистрации для скачивания приложений. Однако у RuStore есть публичное API, с помощью которого можно получить данные о приложении и скачать APK даже без аккаунта.

Получение информации о приложении

GET https://backapi.rustore.ru/applicationData/overallInfo/{packageName}
Получаем в ответе подробные данные о приложении и нужный нам id в body.appId.

Получение ссылки для скачивания APK

POST https://backapi.rustore.ru/applicationData/download-link
{
"appId": "body.appId",
"firstInstall": true
}

Ответ будет содержать URL в body.apkUrl.

Скачивание APK

Перейдя по body.apkUrl будет загружен .zip файл с приложением, внутри которого будет нужный нам .apk файл.

Подробные данные об эндпоинтах можно найти на gist.github.

Итог

С помощью пары запросов к API RuStore можно обойти требование регистрации и получить APK-файл напрямую. Это пригодится для автоматической загрузки обновлений, резервного копирования или анализа приложений. Всё что вам нужно — знать packageName приложения.

#service
Альтернатива Postman и Insomnia

Postman уже давно больше, чем простой REST-клиент: он оброс авторизацией, облаками и платными фичами. Когда-то легковесной альтернативой ему была Insomnia, но и она постепенно ушла в сторону усложнения и ограничения бесплатного доступа. Теперь для базовых задач это уже не тот инструмент.

Yaak новая попытка вернуться к простоте. Он работает локально, не требует регистрации и предлагает только базовый функционал. Если нужен простой и понятный REST-клиент без отвлекающих надстроек — Yaak может подойти. Больше контекста — в посте автора.

#app
Vue, TypeScript и импортированные типы: что может пойти не так?

Когда вы используете defineProps в Vue с TypeScript, компилятор Vue запускает обратную генерацию типов: из интерфейсов, аннотаций и значений по умолчанию (default) создаются JS-конструкторыBoolean, String, Object и другие. Именно они потом используются в скомпилированном компоненте в рантайме.

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

Проблема: неработающий Boolean при импорте

На момент vue@3.5.*, если типы пропов импортируются, а не определяются прямо в компоненте — Vue может некорректно вывести тип Boolean при использовании shorthand-синтаксиса.

Пример
<!-- Comp.vue -->
<script setup lang="ts">
import { Props } from "./types";

withDefaults(defineProps<Props>(), {
disabled: false,
sampleBooleanProp: false
});
</script>

// types.ts
import { InputHTMLAttributes } from "vue";

export interface Props {
disabled?: InputHTMLAttributes["disabled"] // импортированный тип
sampleBooleanProp?: boolean
}


Результат
<!-- Пропы не указаны. Должны примениться значения по умолчанию -->
<Comp/>
<!-- Работает верно. disabled: false, sampleBooleanProp: false -->

<!-- Пропы указаны со значениями. Должны примениться значения true -->
<Comp :disabled="true" :sampleBooleanProp="true"/>
<!-- Работает верно. disabled: true, sampleBooleanProp: true -->

<!-- Пропы указаны со значениями. Должны примениться значения false -->
<Comp disabled="false" sampleBooleanProp="false"/>
<!-- Работает верно. disabled: false, sampleBooleanProp: false -->

<!-- Пропы указаны в shorthand-формате. Должны примениться значения true -->
<Comp disabled sampleBooleanProp/>
<!-- Ошибка. disabled: '' (пустая строка), sampleBooleanProp: true -->

Как видно из примера — если не передавать в проп disabled значение, то вместо ожидаемого true, проп равен пустой строке. То есть Vue не сгенерировал конструктор Boolean для disabled — он не смог вывести правильный тип из импортированного интерфейса.

Решение
Если поменять тип на локально объявленный, всё работает верно.
// types.ts
// локальный интерфейс, полностью идентичен InputHTMLAttributes
interface InputHTMLAttributes_Local { disabled: boolean | "true" | "false" }

export interface Props {
disabled?: InputHTMLAttributes_Local["disabled"] // локальный аналог
sampleBooleanProp?: boolean
}

В таком случае TypeScript передаёт в Vue AST более полную информацию о локальных типах, и Vue корректно использует конструктор Boolean.

Проверить самостоятельно
Если хотите поэкспериментировать: Открыть демо на Vue Playground

Итог
Если вы столкнулись с тем, что shorthand-пропы ведут себя странно — вероятно, проблема в неправильно выведенном типе из импортированного интерфейса. Пока единственное решение — объявить типы локально или отказаться от shorthand-синтаксиса.

Если вам известно другое решение — пишите, будет интересно разобраться!

#typescript #vue #unresolved
Как я написал свой Stylelint-плагин

На первый взгляд сделать это несложно, но на практике есть свои нюансы и подводные камни. В подробном разборе я рассказал о своём опыте, с примерами и ключевыми моментами реализации: от базового API до поддержки autofix.

Полный текст с кодом и объяснениями — по ссылке.

#linters
Почему можно (и нужно) отказаться от Prettier

Когда-то Prettier был обязательным выбором при старте нового проекта. Но времена меняются — и теперь он скорее мешает, чем помогает.

Anthony Fu (core-member Vue, Nuxt, Vite) ещё в 2022 написал убедительную статью о том, почему больше не использует Prettier. Ниже — краткое изложение ключевых аргументов:

- Opinionated. У создателей Prettier своё мнение, как должен выглядеть код — и почти никаких настроек.
- Минимум гибкости. Помимо отсутствия возможности конфигурации правил, большинство из них нельзя даже отключить.
- Конфликты с ESLint. Prettier навязывает стиль, ESLint проверяет логику. Чтобы они работали вместе приходится ставить обвязки, вроде eslint-plugin-prettier.
- ESLint — современная альтернатива. В текущих реалиях сам ESLint справляется с форматированием ничуть не хуже, но при этом даёт полный контроль в одной экосистеме.

Мнение
Я сам начал использовать Prettier году в 2018, и уже тогда он вызывал неудобства. Ранее не было достойных альтернатив, и он действительно скорее экономил время. Но сейчас его время прошло. Если вам всё ещё приходится объяснять, что не так с Prettier — просто дайте ссылку на эту статью.

#linters
Автоматические size-метки для Merge Request в GitLab CI

В больших проектах удобно сортировать Merge Request по размеру: мелкие правки можно рассмотреть быстро, а крупные требуют отдельного внимания. Чтобы не выставлять метки вручную, можно добавить скрипт в GitLab CI, который автоматически будет присваивать их ориентируясь на количество строк в diff.

Подробнее в статье по ссылке.

#ci #git
ESLint плагины: compat

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

Чтобы писать код и не задумываться об этом, можно воспользоваться плагином eslint-plugin-compat — он автоматически проверит, поддерживаются ли используемые функции в целевых браузерах.

Установка
npm install --save-dev eslint-plugin-compat

// eslint.config.js
import compat from "eslint-plugin-compat";

export default [
// ...
compat.configs["flat/recommended"]
];

// package.json / .browserslistrc
{
"browserslist": [
"last 3 Chrome versions",
"Firefox ESR"
]
}


Поддержка полифиллов
Если вы используете полифиллы, их нужно явно указать в настройках — плагин не будет реагировать на такие API:
// eslint.config.js
{
"settings": {
"polyfills": [
"Promise",
"WebAssembly.compile",
"fetch",
"Array.prototype.push"
]
}
}


Итог
eslint-plugin-compat поможет предотвратить ситуацию, когда в прод попадает код, не работающий у части ваших пользователей. Если вы знаете, какие браузеры поддерживаете — подключите этот плагин, и ESLint сам предупредит, если появятся проблемы.

⚠️ Не забывайте обновлять caniuse зависимости, чтобы browserslist всегда соответствовал актуальным данным.

#linters #npm
ESLint плагины: markdownlint

Даже если вы не пишете код, то с Markdown всё равно сталкиваетесь — документация, README или статьи. Он проще HTML и надёжнее Word: незакрытый тег не сломает вёрстку, а добавление переноса не унесёт весь абзац в другое измерение. Но если тексты объемные, то в них быстро могут накапливаться стилистические ошибки и несогласованности: лишние пробелы, неправильные стили заголовков, нумерация списков и так далее. Это мелочи, но исправив их, текст станет гораздо проще для восприятия.

eslint-plugin-markdownlint помогает подсветить такие ошибки: он интегрирует проверку Markdown-файлов прямо в ESLint, используя правила markdownlint.

Установка
npm install --save-dev eslint-plugin-markdownlint

// eslint.config.js
import markdownlintPlugin from "eslint-plugin-markdownlint";
import markdownlintParser from "eslint-plugin-markdownlint/parser.js";

const markdownlintPluginConfig = {
files: ["*.md", "*.mdx"],
plugins: { markdownlint: markdownlintPlugin },
languageOptions: { parser: markdownlintParser },
rules: markdownlintPlugin.configs.recommended.rules
};

export default [
// ...
markdownlintPluginConfig
];


Возможности
- Плагин поддерживает проверку .mdx файлов.
- Использует ESLint инфраструктуру — не нужно ставить дополнительные библиотеки.
- Большинство правил поддерживают autofix через --fix.
- Можно адаптировать любые правила под свои нужды, либо отключить ненужные.

Что именно он проверяет?
Базовые правила покрывают самые частые ошибки и несоответствия:
- Заголовки: MD001, MD003, MD018, MD025 — стили, отступы, вложенность.
- Списки и блоки: MD004, MD005, MD007, MD030, MD032.
- Отступы, пробелы и переносы строк: MD009, MD010, MD012, MD047.
- Код и ссылки: MD014, MD040, MD042, MD052.
- Общие стилистические правила: MD026 (точка в заголовке), MD036 (курсив вместо заголовка), MD041 (H1 — первой строкой).
- Вёрстка таблиц и inline-html: MD033, MD055, MD058

Полный список правил.

Итог
Если в вашем проекте с Markdown уже используется ESLint — подключите этот плагин и пишите аккуратные тексты не отвлекаясь на стилистические правки.

#linters #npm
ESLint плагины: pinia

Хранилища (далее сторы) в Pinia — это основа архитектуры любого Vue-приложения. Но чем больше проект, тем сложнее следить за тем, чтобы все defineStore были реализованы одинаково, не было дублирующихся id, а свойства явно экспортировались.

eslint-plugin-pinia поможет автоматизировать эти проверки: после подключения в ESLint плагин найдет потенциальные ошибки, несогласованный стиль и архитектурные несоответствия в ваших stores.

Установка
npm install --save-dev eslint-plugin-pinia

// eslint.config.js
import piniaPlugin from "eslint-plugin-pinia";

export default [
// ...
piniaPlugin.configs["all-flat"]
];


Что проверяет?
Правила покрывают самые частые ошибки:
- never-export-initialized-store — запрещает экспортировать результат defineStore(), только саму функцию.
- no-duplicate-store-ids — проверка уникальности id у всех defineStore.
- no-return-global-properties — запрет возврата inject, useRouter, useRoute из стора напрямую.
- no-store-to-refs-in-storestoreToRefs() не должны использоваться внутри defineStore.
- prefer-single-store-per-file — один файл должен содержать один стор. Отключено по умолчанию
- prefer-use-store-naming-convention — названия сторов должны начинаться с use: useCartStore, useUserStore, и т.д. Отключено по умолчанию
- require-setup-store-properties-export — все свойства state в setup-сторе должны быть экспортированы.

Итог
Если вы используете Pinia — этот небольшой плагин поможет избежать многих архитектурных проблем ещё на этапе написания кода. Особенно пригодится в командной работе: сторы будут оформлены единообразно, код станет предсказуемым, а ревью — быстрее и проще.

#linters #npm #pinia
Отслеживание метрик и зависимостей JS-проектов в GitHub

Если вы ведёте сразу несколько пет-проектов в GitHub, вам может быть сложно отслеживать все метрики.

GitHub Metrics собирает данные о репозиториях и показывает количество звёзд, форков, issues. Помимо этого, сервис парсит package.json и отображает используемый пакетный менеджер, бандлер, фреймворки для тестов и статус последнего CI-билда. Передав токены, дополнительно можно подключить бейджи UptimeRobot и Netlify Deploy Status.

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

GitHub Metrics — это не CI-утилита и не облачный билд-сервис, а простой и наглядный дашборд, который помогает навести порядок в зоопарке пет-проектов. Один раз настроив его, вы сможете быстро увидеть, где устарели пакеты, где забыли про лицензию или CI, а где появились новые звёзды.

#git #service
Forwarded from mefody.dev
Проблема с figcaption

Представьте вполне себе привычную, довольно семантичную вёрстку для картинок с подписями.
<figure>
<img src="image.jpg" alt="доступное описание">
<figcaption>Длинный-длинный текст, который по ширине значительно превышает ширину самой картинки</figcaption>
</figure>

Крис Койер задаётся вопросом, как сделать так, чтобы:
1. Картинка могла быть любой ширины, но не превышала размер контейнера.
2. Подпись в figcaption подстраивалась по размеру к ширине картинки.
3. Всей этой конструкции можно было добавить красивую обводку.

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

https://frontendmasters.com/blog/the-figcaption-problem/
Forwarded from mefody.dev
Креативность и цитаты

Энди Кларк задаётся вопросом, как можно стилизовать цитаты и врезки на сайте музыкального исполнителя. Начинает с базовой разметки, постепенно добавляет новые и новые стили, и в конце получает то оформление, которое его устраивает.

Мне понравилась заключительная мысль статьи: цитаты на странице тоже могут вызывать настроение через визуальные смыслы. И вполне нормально экспериментировать с их оформлением, чтобы получилось классно.

https://css-tricks.com/getting-creative-with-quotes/
Telegram Mini Apps: быстрый старт

Создавать мини-приложения для Telegram проще, чем кажется.

Всё, что нужно:
1. Зарегистрировать бота через @BotFather;
2. Указать ссылку на ваш сайт.

После этого сайт откроется прямо внутри Telegram — без сложной логики или интеграций. Фактически это тот же веб-сайт, только с запуском в мессенджере.

Хотите довести всё до идеала? Оптимизируйте страницу под размеры окна Telegram, сделайте отдельный быстрый лендинг или добавьте поддержку специфичных API. Подробный разбор с примерами таких улучшений есть в статье на Хабре: Telegram Mini App. Как создать Web App с нуля.

Главное — начать с простого, а детали можно освоить позже.

#service
Запуск TypeScript напрямую в Node.js

Хорошие новости: в новых версиях Node.js можно запускать .ts-файлы напрямую — без ts-node, tsx или ручной сборки.

v22.6.0: Появилась базовая поддержка type-stripping
v22.7.0: Добавлен флаг --experimental-transform-types
v23.6.0: Удаление аннотации типов включено по умолчанию
v24.3.0: Функция больше не считается экспериментальной

Подробнее о нативной поддержке TypeScript в Node.js — в официальной документации.

Что изменилось
- Node.js сам удаляет аннотации типов (type stripping), оставляя чистый JavaScript.
- Флаг --experimental-strip-types теперь не обязателен — он активен по умолчанию. При необходимости его можно отключить с помощью --no-experimental-strip-types.
- Если используются конструкции, требующие транспиляции (enum, namespace), потребуется флаг --experimental-transform-types.

Пример запуска
node --experimental-transform-types index.ts


Как писать совместимый код
В TypeScript 5.8 появился новый флаг erasableSyntaxOnly. Он запрещает конструкции, которые Node.js не сможет вырезать. Добавив этот параметр в tsconfig.json, редактор предупредит вас о неподдерживаемом коде.

Кому это пригодится?
Крупные проекты на TypeScript, которые активно используют namespace, enum и прочие трансформируемые конструкции, всё равно останутся на привычных инструментах вроде ts-node, tsx или полноценной сборки через tsc — и это нормально. Но для маленьких утилит, тестовых скриптов и инструментов возможность просто запустить node script.ts — отличный способ сэкономить время и не настраивать дополнительное окружение.

#build #typescript