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

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

#html #vue #typescript #npm
Download Telegram
Channel created
Автообновление npm зависимостей

Многие до сих пор проверяют зависимости package.json вручную или через npm outdated.
Но есть инструмент, который сделает это удобнее и проще: npm-check-updates (ncu).

Установка:
npm install --global npm-check-updates

ncu          # показать обновления
ncu -u # обновить package.json
npm install # установить зависимости


Показать обновления до определенного уровня:
ncu --target patch
ncu --target minor
ncu --target latest


Показать обновления определенных пакетов:
ncu -f webpack  # только webpack
ncu -x webpack # всё, кроме webpack


Проверка обновлений глобальных зависимостей:
ncu -g


Игнорирование пакетов:
// .ncurc
{
"reject": ["webpack", "eslint", "@types/*"]
}


⚠️ Пока нет возможности ограничить пакеты только определенным интервалом версий.

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

#npm
Улучшение дефолтного поведения TypeScript

ts-reset — утилита, которая расширяет стандартную типизацию в TypeScript, устраняя устаревшие и нестрогие участки в базовых API.
Подключается на уровне проекта и повышает строгость типизации в ряде API.

Установка:
npm install --save-dev ts-reset

// reset.d.ts
import "@total-typescript/ts-reset";


Также возможно установить отдельные правила:
// reset.d.ts
import "@total-typescript/ts-reset/json-parse";
import "@total-typescript/ts-reset/fetch";


Основные изменения:

JSON.parse, .json(), localStorage, sessionStorage теперь возвращают unknown, а не any
.filter(Boolean) корректно удаляет falsy-значения
.includes(), .indexOf(), Set.has(), Map.has() не требуют точного сравнения (чего и пытаемся добиться, вызывая их)
Array.isArray() больше не считает any[] безопасным

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

Внутри существующих проектов подключение ts-reset, как правило, не вызывает проблем: типы становятся строже, но остаются совместимыми с корректным кодом.
Если после подключения появляются ошибки — скорее всего, это участки, где типизация и так была небезопасной.

#npm #typescript
Типизация querySelector

typed-query-selector — улучшение типизации методов querySelector и querySelectorAll с выводом типов на основе CSS-селекторов.

⚠️ Требуется TypeScript версии 4.1 или выше.

Установка:
npm install --save-dev typed-query-selector

// typed-query-selector.d.ts
import "typed-query-selector";


Результат:
document.querySelector("div#app"); // HTMLDivElement
document.querySelector("div#app > form#login"); // HTMLFormElement
document.querySelectorAll("span.badge"); // NodeListOf<HTMLSpanElement>
document.querySelector("button#submit"); // HTMLButtonElement

#npm #typescript
Аннотации над текстом с тегом ruby

ruby (MDN) — HTML-элемент для аннотирования слов: переводы, транскрипции, пояснения.
Часто используется для японского и китайского, но работает и для любых других языков.

Элементы <ruby>, <rt> и <rp> входят в Baseline: поддерживаются всеми современными браузерами.

Пример:
we usually say <ruby>привет<rp>(</rp><rt>hi</rt><rp>)</rp></ruby> as a greeting


Зачем `<rp>`?

<rp> (ruby parentheses) отображается только в браузерах без поддержки <ruby>.
Обычно содержит скобки вокруг <rt>, чтобы сохранить читаемость: привет (hi)

#html
Channel photo updated
Переход с CommonJS на ESM

Модули на основе require() и module.exports (CommonJS) — устаревающий формат.
Современный стек (включая Node.js) постепенно переходит на ESM (ECMAScript Modules),
который стал официальным стандартом.

Зачем рассматривать переход:

- ESM поддерживается во всех современных сборщиках
- даёт доступ к import/export, top-level await, tree-shaking
- новые API Node.js, например import.meta, ориентированы на ESM
- всё больше библиотек публикуются только в ESM-формате

Подробнее: antfu.me/posts/move-on-to-esm-only

Подробная инструкция по миграции от sindresorhus

Для постепенного перехода можно включить правило prefer-module из eslint-plugin-unicorn.

⚠️ Переход на ESM может быть затруднён, особенно в CLI-приложениях и проектах где используются зависимости, которые давно не обновлялись.
В таких случаях лучше придерживаться CommonJS до появления стабильной поддержки.

ESM — направление развития платформы. Если проект позволяет — стоит начинать миграцию. Но для некоторых типов приложений переход пока может быть преждевременным.

#build
Pinia: стоит ли использовать setup store

Документация Pinia описывает два способа создания стора: setup store и option-store.

Option-синтаксис:
export const useCounter = defineStore("counter", {
state: () => ({ count: 0 }),
actions: {
increment() { this.count++; }
}
});


Setup-синтаксис:
export const useCounter = defineStore("counter", () => {
const count = ref(0);
function increment() { count.value++; }
return { count, increment }
});


Setup store позволяет использовать Composition API внутри стора, но имеет ряд ограничений:
Необходимо вручную вернуть все переменные и методы из setup().
Пропущенные значения приведут к проблемам с SSR, Vue Devtools и сторонними плагинами.
Типизация и линтинг работают ограниченно, проверки на уровне TS/ESLint отсутствуют.


Дополнительно: приватное состояние возможно только через отдельные store-инстансы — например, так:
const usePrivateState = defineStore("store-private", () => {
const secret = ref('Never seen outside');
return { secret }
});

export const useStore = defineStore("store", () => {
const privateState = usePrivateState();
const censoredSecret = computed(() => '*'.repeat(privateState.secret.length));
return { censoredSecret }
});


Подробнее:
Pinia docs: Setup Stores
Mastering Pinia: Private State

Таким образом, можно использовать setup store, однако мы не рекомендуем это делать, пока его архитектура остаётся неочевидной, а ошибки не выявляются инструментами.
В большинстве проектов предпочтительнее продолжать использовать option store — он безопаснее, прозрачнее и лучше поддерживается.

#pinia
Агрегатор информации по доменным зонам

Если вы когда-либо покупали домен, то знаете, как это выглядит: нужно открыть десяток сайтов регистраторов, вручную вбивать доменные имена, разбираться в условиях, ждать загрузки, обходить рекламу…
А потом выясняется, что продление стоит в 3 раза дороже, а приватный WHOIS — платная опция.

tld-list решает все вопросы одной страницей.

Это агрегатор всей информации по доменным зонам:

- показывает цену регистрации и продления у популярных регистраторов (включая скрытые комиссии)
- отображает, какие зоны поддерживают WHOIS privacy
- позволяет отфильтровать домены по длине, символам, доступности
- сортирует по цене и особенностям (например, можно исключить зоны с премиум-ценами)

Никаких устаревших подборок, перебора регистраторов вручную и вопросов "а сколько .dev стоит у Namecheap?".
tld-list делает это за вас.

#service
Pinia: запрет неявной мутации

Одна из главных причин использовать TypeScript — это предсказуемость.
Любое изменение данных должно быть намеренным: мы либо вызываем функцию, либо обновляем ref.
Но в случае с Pinia возможен такой код:
const store = useDummyStore()
store.stateValue = null // прямая мутация state, но нет ошибки


Явная vs неявная мутация

Что здесь не так?

- store.stateValue = null — выглядит как поле объекта, но на самом деле мутирует глобальное хранилище напрямую.
- storeToRefs(store).stateValue.value = null — требует предварительного шага, вызов storeToRefs, и работы с .value, что делает намерение очевидным.

Такое поведение ближе к commit() из Vuex: мы не просто что-то присваиваем, мы просим хранилище редактировать свой state.
Именно эта намеренность и отделяет хорошую архитектуру от хрупкой.

Pinia и неявные мутации
В отличие от Vuex, Pinia по умолчанию позволяет напрямую менять state.
Нет ни mutations, ни встроенного запрета.
strict режим обсуждался ещё в 2020, но так и остался не реализован.

Как запретить неявные мутации
Мы можем вернуть контроль сами через типизацию. Делаем state доступным только для чтения:
// env.d.ts
declare module 'pinia' {
export interface StoreDefinition<
Id extends string = string,
S extends StateTree = StateTree,
G = _GettersTree<S>,
A = _ActionsTree
> {
(pinia?: Pinia | null | undefined, hot?: StoreGeneric): Store<Id, Readonly<S>, G, A>
}
}


Теперь:
// Прямое присваивание
const store = useDummyStore()
store.stateValue = null // Error: Cannot assign to 'stateValue' because it is a read-only property
// Посредством storeToRefs
const { stateValue } = storeToRefs(useDummyStore())
stateValue.value = null //
// Посредством action
store.setValue(null) //


Pinia предлагает гибкость, но вместе с ней — уязвимость.
Если вы хотите, чтобы изменения state всегда были преднамеренными и контролируемыми, настройка Readonly<StateTree> — хорошая отправная точка.
Мы не ограничиваем возможности Pinia, мы лишь возвращаем понятную дисциплину, которая особенно нужна в команде или на долгосрочном проекте.

#pinia #typescript
Очистка node_modules от устаревших полифилов

Даже если вы используете Current или LTS версию Node.js, многие популярные пакеты по-прежнему тянут за собой устаревшие полифилы — вплоть до Node.js 4.
Они часто попадают в проект как зависимости — например, eslint-plugin-import, eslint-plugin-jsx-a11y, eslint-plugin-react.
Это увеличивает размер node_modules, замедляет установку и даже выполнение кода.
Всё потому, что некоторые из этих полифилов используются вместо нативных API, даже если они уже доступны в среде выполнения, что снижает производительность, хотя в этом нет нужды.

nolyfill — это CLI-инструмент, который автоматически находит и заменяет устаревшие полифилы на безопасные заглушки.

⚠️ Он вам не подойдет, если ваш проект запускается на версии Node.js ниже 12.4.0 или вы разрабатываете под среду, которая не поддерживает ECMAScript2019.

Использование:
npx nolyfill // Найдёт полифилы в текущем проекте
npx nolyfill install // Заменит их на заглушки


Если вы часто меняете зависимости, чтобы не забывать запускать nolyfill — добавьте вызов в postinstall:
// package.json
{
"scripts": {
"postinstall": "npx nolyfill"
}
}


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

#npm
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/