#JavaScript
7 нотаток
- ActiveRecord
- AmazonRedshift
- API
- AppleScript
- AWS
- AWSLambda
- Cadencia
- CGO
- Chezmoi
- CI
- Clojure
- Cloudflare
- CloudflarePages
- CloudWatch
- CssParser
- CтохастичнийТаймтрекер
- DNS
- Docker
- Dotfiles
- Fly.io
- GCP
- Git
- GitHub
- GitHubActions
- Go
- Golang
- GTD
- HomeAssistant
- Hugo
- I18next
- JavaScript
- Jira
- JSON
- Kafka
- MacMiniВДорозі
- MacOS
- Markdown
- Mastodon
- Obsidian
- ObsidianCanvas
- OmniWOPE
- OpenSearch
- Oura
- PagerDuty
- Ping
- Plausible
- PostgreSQL
- ReactNative
- Redis
- RSS
- Ruby
- RubyOnRails
- Sentry
- Sintra
- Slack
- SMTP
- SQL
- SQLite
- Svelte
- Swift
- SwiftData
- SwiftUI
- Telegram
- Terraform
- TLS
- TypeScript
- Vercel
- VPN
- WeightPlot
- WordPress
- XCode
- Адвент2024
- Бази Даних
- БДС
- Безпека
- Блокнот
- Вебтехнології
- ВолюІнформації
- Гаджети
- ДизайнМовПрограмування
- Ігри
- Інструменти
- ІнтеграційніТести
- Кава
- КеруваннняЗадачами
- Кодогенерація
- Криптографія
- Локалізація
- Маркетинг
- МетаПост
- МоїПроєкти
- Навігація
- Оптимізація
- ОсновиІнтернетБезпеки
- Помічник ШІ
- ПомічникШІ
- ПостПроПохід
- Програмування
- Продуктивність
- Проєкти
- Проза
- РДУГ
- Рівночасність
- РобочийКомп
- Розробка
- РозумнийБудинок
- СинПопросивПриготувати
- Сон
- СтохастичнийТаймтрекер
- Танці
- ХмарніТехнології
-
Транспайлери JavaScript та цільова версія мови
JavaScript - це, наскільки я знаю, єдина мова програмування, яка компілюється сама в себе. Це створює унікальні обставини. Ніхто вже багато років не хоче писати на тому JavaScript, який працюватиме в будь-якому браузері (хоча за останні десять років ця планка просто злетіла завдяки зникненню Internet Explorer.) Ні, ми пишемо сучасною мовою з усіма можливостями - async/await, null coalesce і так далі. А які середовища будемо підтримувати — залежить від нашого компілятора, точніше, транспайлера.
Зазвичай налаштування транспайлера за замовчуванням всіх влаштовують, але деколи доводиться робити зміни; так в сьогоднішній версії пакета mailtrap ми за недоглядом втратили підтримку старих версій Node.js.
В TypeScript є опція target, яка вказує, який саме стандарт JavaScript буде підтримувати скомпільований код. Який стандарт обрати — вгадайте сами. (Довідка радить ES6.) До того ж є окрема інструкція по вибору версії для Node.js - Node Target Mapping. От на неї ми й отримали проблеми, бо поміняли ціль з
es5наes2020та втратили підтримку Node.js старіше за 14.До речі, на сайті Node.js є сторінка з переліком версій, що ще підтримуються; підтримка версії 14 припиняється наприкінці цього місяця. Але, як бачите, це не заважає деяким командам продовжувати користуватись і старішими версіями.
А в Babel це налаштовується через @babel/preset-env. Це дуже зручно, бо дає вказати не тільки конкретні версії браузерів, які ми хочемо підтримувати, а й просто сказати: “99.9% ринку” чи щось схоже. Далі, чим старіше обрані браузери, тим більше нашого коду буде замінено на спрощений.
На останок, компіляція в іншу мову програмування — дуже поширений підхід. Так роблять майже всі компільовані мови програмування. А саме, компілюють вони в мову C, для якої вже є надійний та перевірений компілятор та пакет інструментів GCC. Генерація машинного коду — складна задача, та простіше перекласти її на GCC, ніж робити це наново.
-
Express.js
Один з моїх улюблених засобів для створення простого вебсервера - Express. Для рубістів - Express дуже схожий за настроєм з Sinatra.
const app = express(); app.get("/", function (req, res) { res.send(`Hello, ${req.query.name}!`); });-
JavaScript гарна мова з бібліотеками на всі випадки життя. TypeScript та Prettier роблять розробку зручною без обтяжування. Порівняно з тією ж Сінатрою, я можу впевнено досягати результатів. А порівняно з Go швидко. Для прототипів це золота середина.
-
Синтаксис та семантика Express прості та зрозумілі. Оголошувати маршрути, читати запити та генерувати відповіді можна з мінімумом рухів. Абстракція над HTTP прозора, завжди видно, що куди йде. Чомусь робота з тілом запита POST винесена в окремий пакет body-parser, але то дрібниці.
-
Nodemon та ts-node роблять локальний запуск та перезапуск простим та зрозумілим. Запускаєш
yarn run nodemon entry.tsта все, можна працювати. -
Express легко (відносно…) розгорнути — або навіть перетворити застосунок на serverless. Відносно, бо не люблю технології, які залежать від вихідного коду бібліотек в продакшні — хай то node_modules, геми Ruby тощо. Втім, принаймні всі залежності для Node.js розташовуються в одній директорії з проєктом.
Раджу мати Express на озброєнні для маленьких вебсервісів — прототипів, однозадачних серверів, обгорток всіляких.
-
-
Підписки з YouTube - до RSS
📺 Я якщо що великий прихильник не тільки RSS, а ще й читати їх раз на тиждень, щоб не витрачати час на вхідні потоки. От як раз нещодавно я повернувся на цей графік, бо він має схильність зʼїжджати: спочатку наздоганяєш щось що не встиг, а далі не встигнеш озирнутись, як заглядаєш в читач кожні 15 хвилин.
В цій схемі з RSS була одна діра: YouTube. Ніколи не міг знайти зручного способу передивлятися підписки. (Зручний — це коли я знаю, які нові відео зʼявилися, та можу їх відмітити як прочитані, щоб більше не бачити.)
В YouTube є RSS для кожного каналу, на щастя. Але немає RSS для всіх моїх підписок. Щоб не переносити вручну, знайшов оце скрипт, який генерує зі сторінки каналів файл OPML. Все б гарно, але в тому OPML є тільки адреси RSS, а назв не було.
Почав доробляти… та роздивився, що скрипт генерує з даних JSON, а потім парсить його регулярними виразами. Такого я ще не бачив! При тому, що дані для сторінки сидять в глобальній змінній (теж велика вдача.) Єдине, що там в змінній наче дані вже підготовані для компонент, тому знайти в структурі потрібні дані дійсно непросто, а скрипт з жорстко заданими шляхами буде надто крихким.
Отже, переробив скрипт. Мій варіант ходить по структурі даних та шукає обʼєкти, схожі на канали. Це працює, а головне — з обʼєкта каналу вже нескладно дістати його імʼя та адресу. А генерацію OPML залишив так саме як і було — через додавання рядків.
Ще мені в цьому скрипті сподобалося автоматичне направлення файлу на завантаження. В моїх попередніх рішеннях я просто виводив зміст в консоль для копіювання вручну, а тут значно зручніше.
Осьо мій скрипт. До речі, раніше я викладав схожі скрипти на GitHub Gist, але цього разу думаю — а чому не можу просто до себе на сайт? Тільки голий
.jsна сайті не дуже зручно, а от як стаття разом зі стислою документацією — чудово. -
Повторювані залежності Yarn зі станом — обережно!
Припустимо, у вас в
package.jsonпідключена інтеграція… ну скажімо@some/coreта@some/angular. Раптом приходить Mend, та каже — в@some/coreвразливість, оновлюємо її! А з@some/angularвсе добре, її залишаємо. Дякуємо Mend, оновлюємо, живемо далі.Проходить час та виявляється, що пакет неініціалізований та інтеграція тихо не працює. Ми потрапили в одну з відомих пасток Yarn. Принаймні, відомих після першого такого випадку — в мене, на жаль, навіть вже не перший.
Річ у тім, що в кожного пакета (та в нашого застосунку) свої вимоги до версій залежностей. Та якщо вони не збігаються, Yarn надає кожному пакету власні екземпляри залежності потрібної версії. Тобто застосунок отримує оновлену версію
@some/core@1.2та@some/angular@1.1, а останній, якщо в нього, як часто буває, жорстко задані версії внутрішніх залежностей, в собі може приховувати@some/core@1.1.В простому випадку це проблема тільки для розміру збірки. (А хто на нього дивиться?) Але, якщо залежність має внутрішній стан, то ми тепер маємо два екземпляри цього стану. Бо кожна версія — це абсолютно окремий пакет! Також нас очікує халепа, якщо пакети містять ООП та перевіряють сумісність прототипів — бо прототипи теж будуть у двох екземплярах. В таке я колись вляпався із
pouchdb.А тепер, що з цим робити? Знайшов плагін для Webpack - DuplicatePackageCheckerPlugin. Він принаймні повідомить, або навіть видасть помилку. Попереджаю, що у вас гарантовано є дублікати, та майже гарантовано від всіх не позбутися — але проблемні (чи навпаки — безпроблемні?) можна ігнорувати.
Також корисною буде команда
yarn dedupe- вона намагається оптимізувати залежності, якщо вони роз`їхалися з часом. Але суттєво вона ситуацію не змінить без корекції версій.Інша корисна команда -
yarn why -R packagename- покаже, чому транзитивна залежність є у вашому застосунку. (-Rце як раз для транзитивних, щоб дивитися глибше одного рівня.) Я нею користуюся ще й коли хочу зрозуміти, чи можна швидко позбавитися якогось негарного пакета.Але головне, що ви тепер знаєте, що таке буває, та роздвоєння особистості в пакета не стане сюрпризом.
-
Чому в JavaScript такий хаотичний парсер дат?
Поділилися на роботі тестом “на знання парсера дат JavaScript” jsdate.wtf. Так, там багато дичини, так, я навіть на 50% не вгадав, але це всі й так знають. Краще з’ясуймо, чому?
Спочатку можна зайти в специфікацію ECMAScript. Тут все цілком притомно: є один-єдиний формат
YYYY-MM-DDTHH:mm:ss.sssZз невеличкими варіаціями. Ну й хіба те, що аргумент обовʼязково перетворюється в рядок. Все решта… на розсуд реалізації.Тепер до реалізації. Тест написаний для Node.js. Втім, варто знати, що Node.js побудований на рушії V8, який також використовується в Chrome (та інших місцях.) Тож шукати подробиці будемо в коді V8, який, до речі, написаний на C++. Знаходимо там метод ParseDateTimeString, а з нього - DateParser::Parse. Тут з першого ж погляду бачимо коментар, який пояснює наявність “legacy dates”. В ньому перелічені чи не всі приклади з jsdate.wtf, хоча, на жаль, нема пояснень, звідки саме взялася спадщина.
Можливо, ще цікавіше подивитися на юніт-тест парсера, бо в ньому перелічено багато прикладів. З прикладів можна побачити, чому дивно інтерпретуються короткі значення років та чому має сенс пропускати рядки на початку (як-от день тижня.) Єдине, що мені досі незрозуміле, це пропуск змісту
(дужок).Оцю всю кухню доводиться підтримувати досі, бо браузери хочуть мати найбільшу сумісність, а ніяких версій в JavaScript немає. Отак і живемо, і далі жити будемо, аж поки JavaScript не переродиться чи не помре. (Як гадаєте, побачимо таке?)
У власних проєктах я б радив уникати таких вбудованих методів та використовувати бібліотеку, як-от dayjs, де ви самі контролюєте версію та формат. Та точно не використовувати
Date.parse()для валідації! А від хиб на кшталтDate.parse(-[])захистить, як завжди, TypeScript.Так що, як бачите, річ не у тім, що парсер погано написаний, та не в тому, що специфікація погана. Спадщина — ось найбільший тягар JavaScript.
-
Проблеми з i18next
Вчора в підтримку Сінтри надійшов лист про те, що у нас поламані листи підтвердження підписки. По-перше, дякую автору, бо небагато хто б потрудився це зробити. По-друге, чи це ще один випадок недостачі моніторингу та насамперед тестування в проєкті двох людей? Авжеж! Бо помилка сиділа роки з два. (І ні, це далеко не перша підписка за цей час.)
Помилка крилася в неправильному шаблоні для i18next. В нас там цікаве рішення для шаблонів листів. Технічно вони використовують “Handlebars”, але на практиці шаблони стають просто рядками перекладу для i18next.
Причин було відразу дві. Одна прямо зовсім проста — неправильний префікс ключа. Знаєте, в i18next можна написати
підписка на $t(subscription_interval.{{interval}})та підставити туди, наприклад,subscription_interval.monthly. Дуже зручно. Але на це немає перевірок — якщо такого ключа немає в словнику, i18next підставить сам ключ. Що й відбулося, бо я банально переплутавsubscription_intervalтаsubscription_period.Зате рівно тільки що дізнався, що є налаштування відсутніх ключів — в тому числі обробник
missingKeyHandler, який міг би кинути помилку.Друга причина — в моєму форматувальнику дат формат читається з того ж словнику. Дуже зручно (та може варто окремої розповіді.) Але. Словник же ж потребує обраної мови! На фронтенді це працює зрозуміло: у нас є мова користувача, вона встановлена глобально. А на бекенді немає “глобально обраної мови”, бо бекенд надсилає пошту до всіх користувачів. Тому виходило так, що в пошті цей форматувальник не знав мови, використовував хибний формат (знову без помилки…) та видавав сміття.
Розвʼязалося все передачею мови параметром
lng:format = i18next.t(format_name, {lng: userLanguage}).Отак, наче нічого складного, але години дві витратив. До речі, дуже допомогло те, що в мене принаймні були скрипти для генерації тих листів.
-
Динамічність — абстракція, яка тече
Хотів прокоментувати пост з Федиверсу. Якщо коротко: чому в JavaScript звернення
pixels[i*2+1]буде швидше заpixels[i].x? Ну тобто, чому масив значень швидше за масив обʼєктів зі значеннями?Якби це був C чи навіть Go, то відповідь зрозуміла. Там масив — це блок даних в памʼяті, а обʼєкти — окремі блоки. Це зрозуміло, наприклад, тому, що для зміни довжини масиву його потрібно виділяти наново. Операції з безперервним блоком памʼяті - швидше. Але ж JavaScript дозволяє нам взагалі додавати елементи в будь-який індекс масиву, не зважаючи на його довжину. Тобто масиви в JavaScript - це очевидно не просто блок памʼяті. Чому ж все одно масив швидше?
Про представлення даних в JS я вже писав. Навіть в JS масив буде ну в гіршому випадку набором блоків памʼяті. А ще в тому пості є посилання на розгорнуту відповідь про те, що V8 та інші рушії здатні оптимізувати доступ до масиву десь до рівня мови зі статичною типізацією. А от кожний обʼєкт в JS зберігається в памʼяті окремо. Наскільки я знаю.
(Хто працює з C чи Go підтвердить — що там як раз легко зробити масив структур, який буде такий саме швидкий, як “голий” масив. Якщо це були структури, а не вказівники. Де-де, а в C оголошення структур для споживання блоків памʼяті — класична техніка.)
В JavaScript можна піти далі та зробити масив типу Float64Array. От він вже точно буде єдиним блоком памʼяті, як в статичній мові — з такими саме обмеженнями. Якби я робив щось з графікою, то, мабуть, його б і використовував… якщо без нього було б повільно, не треба оптимізувати наперед!
До чого я це все… Навіть в мові, яка не змушує тебе думати про розмір масиву чи типи даних, все одно різні мовні конструкції будуть швидше чи повільніше — незалежно від алгоритмічної складності.