Jak Deno 2.x cicho rozwiązał problemy z kompatybilnością z Node oraz zmęczenie narzędziami
Ten artykuł omawia wersje Deno od 2.0 do 2.9, pokazując, w jaki sposób kompatybilność z npm, zestawy uprawnień oraz wbudowane narzędzia usunęły przeszkody, które kiedyś skłaniały programistów do porzucenia tego narzędzia.
To pierwsze testy odbyły się pięć lat temu.
W zeszłym miesiącu szybki projekt poboczny wymagał małego serwera HTTP. Nowy katalog, polecenie deno init, i dziesięć minut później miałem już działający serwer z TypeScript, testami, formatowaniem oraz narzędziami do sprawdzania kodu już w pełni skonfigurowane. Nie trzeba było pisać pliku tsconfig. Brak konfiguracji prettier, brak ustawień eslint, brak pliku jest.config.ts. Nie było też żadnego pliku package.json.
Po sprawdzeniu wersji okazało się, że jest to 2.9.3.
Oto krótki przegląd tego, co faktycznie się zmieniło, jeden stary problem po drugim.
Skarga 1: pakiety npm były traktowane jako gorszej jakości
To był główny problem dla wielu programistów, w tym i mnie. W Deno 1.x wszystko, co nie zostało opublikowane na deno.land/x lub nie miało prawidłowych adresów URL dla modułów ES, po prostu nie działało. Rozdział między światem Deno a światem npm wydawał się trwały.
Deno 2.0, wypuszczony w październiku 2024 roku, całkowicie zamknął tę luki. Można teraz bezpośrednio ściągać dowolny pakiet z katalogu npm zawierającego ponad dwa miliony pakietów, używając specyfikatora npm::
import express from "npm:express@5";
import { PrismaClient } from "npm:@prisma/client";
Jeśli wolisz korzystać z pliku package.json, to również jest to obsługiwane. Deno go analizuje, pobiera zależności z rejestru npm i może nawet utworzyć lokalną folder node_modules, jeśli włączysz tę opcję. Prywatne rejestratory również działają za pomocą pliku .npmrc, dokładnie tak jak w Node.
Statystyka, która naprawdę przekonała wszystkich: ponad 75 procent własnego zestawu testów Node teraz działa pomyślnie w Deno. To nie jest tylko powierzchowne rozwiązanie – wskazuje na rzeczywistą, sprawdzoną kompatybilność.
Kluczowym momentem była próba uruchomienia Deno w istniejącym projekcie Express. Nie trzeba było zmieniać ani jednego instrukcji importu. Dodano plik deno.json z wartością "nodeModulesDir": "auto", uruchomiono polecenie deno install, a czas instalacji wyniósł około 900 ms, w porównaniu z ponad 3 sekundami u npm w tych samych warunkach (testowano na projekcie używającym React, Vite, Babel i ESLint przy czystej pamięci cache). Serwer działał poprawnie już przy pierwszej próbie.
Skarga 2: system uprawnień był uciążliwy w praktyce
Koncepcja modelu uprawnień wydawała się sensowna. W praktyce oznaczało to konieczność ponownego wpisywania takich informacji przy każdym uruchomieniu:
deno run --allow-read=./data --allow-write=./data --allow-net=api.example.com --allow-env main.ts
Jeśli zapomnieć o jednej flagi, proces przestaje działać. Jeśli dodać nową zależność, która ma za zadanie odczytywać zmienną środowiskową, proces znów przestaje działać. Wydawało się to bardziej podobne do konfiguracji reguł firewall niż tworzenia projektu pobocznego.
Deno 2.5, wydany we wrześniu 2025 roku, wprowadził zestawy uprawnień zdefiniowane bezpośrednio w pliku konfiguracyjnym. Deklaruje się je pod kluczem "permissions" w pliku deno.json, a następnie używa się ich za pomocą flagi -P (skrót od --permission-set):
{
"permissions": {
"default": {
"read": ["./src", "./data"],
"write": ["./data"],
"net": ["api.example.com", "0.0.0.0:3000"],
"env": true
},
"test": {
"read": true,
"write": ["./tmp"],
"net": false
}
}
}
W tym momencie uruchomienie polecenia deno run -P main.ts automatycznie pobiera „domyślny” zestaw uprawnień z pliku deno.json znajdującego się w katalogu roboczym, albo można użyć polecenia deno test -P=test, aby zastosować oddzielny zestaw uprawnień przeznaczony wyłącznie do testów. Kod testowy i kod produkcyjny otrzymują różne zakresy uprawnień bez konieczności edytowania żadnego flagi w linii poleceń. Podstawowe gwarancje bezpieczeństwa nie uległy zmianie — po prostu zniknęła cała skomplikowana procedura związana z nimi.
Istnieje również zmienna środowiskowa DENO_AUDIT_PERMISSIONS, która tworzy plik logu w formacie JSONL, zapisujący każdą sprawdzoną uprawnienie podczas działania programu. Dzięki temu można dokładnie zobaczyć, do czego próbują uzyskać dostęp Twoje zależności, bez konieczności analizy ich kodu źródłowego.
Skarga 3: Nadal potrzebowałem mnóstwa dodatkowych narzędzi
Błogosławieństwem i jednocześnie ciężarem Node’a jest to, że niemal każda responsybilność jest przekazywana oddzielnemu pakietowi. Potrzebna jest formatyzacja? Użyj prettiera. Sprawdzanie poprawności kodu? ESLint wraz z kilkoma pluginami. Testowanie? Jest lub Vitest, plus plik konfiguracyjny do obsługi transformacji TypeScript. Sprawdzanie typów? tsc, skonfigurowany niezależnie od tego, co uruchamia twój kod. Pakietowanie? Wybierz spośród pół tuzina narzędzi do pakietowania, z których każde ma swój własny dialekt konfiguracji.
Deno łączy to wszystko w wbudowane podkomendy. Istniały one już przed wydaniem 2.0:
deno fmt # formats TS, JS, JSON, HTML, CSS, YAML, SQL
deno lint # built-in linter with quick fixes
deno test # test runner with coverage, snapshots, sharding
deno check # type checking
deno compile # single binary, cross-platform, code signing
deno bench # benchmarking
deno doc # documentation generation
Począwszy od wersji 2.8 pojawiło się sześć dodatkowych podkomend:
deno audit # security audit of dependencies
deno audit fix # auto-upgrade vulnerable packages to nearest patched version
deno why # explain why a package is installed (traces dependency paths)
deno transpile # strip types, emit .d.ts declarations
deno pack # build an npm-publishable tarball from JSR/Deno code
deno ci # reproducible install for CI (errors without lockfile)
A wraz z 2.9 pojawiło się jeszcze więcej:
deno desktop # build native desktop apps via webview (experimental)
deno list # show dependency tree (like npm ls)
deno link/unlink # local package linking for development
Najczęściej używanym narzędziem jest deno compile. Tworząc małe narzędzie w interfejsie linii poleceń i uruchamiając deno compile --target x86_64-unknown-linux-gnu main.ts, uzyskuje się samodzielny plik binarny. Na maszynie docelowej nie trzeba instalować niczego więcej — wystarczy go skopiować na serwer i uruchomić.
Uwaga: powstały plik binarny zawiera V8 oraz całe środowisko działania Deno, więc typowe aplikacje zajmują około 60–100 MB. Gdy rozmiar jest istotny, opcja
deno compile --bundle(nadal niestabilna w wersji 2.8) stosuje intensywne usuwanie niepotrzebnego kodu, co może znacznie zmniejszyć rozmiar pliku w przypadku prostych skryptów. W własnym blogu Deno pokazano, że „hello world” z lodash zajmuje 1,5 MB przy użyciu opcji--bundle --minify.
Skarga 4: migracja rzeczywistego projektu wydawała się niemożliwa
Największą przeszkodą w zmianie środowiska wykonywania zazwyczaj nie jest samo środowisko to. Chodzi o plik lockfile, strukturę node_modules, od której zależą istniejące narzędzia, oraz rozproszone wywołania require() w całym kodzie.
Deno 2.9 wprowadziło funkcję tworzenia pliku lockfile na podstawie istniejących danych. Załóżmy, że projekt już odnotowuje swoje zależności za pomocą jednego z powszechnych plików lockfile zarządzaczy pakietami — na przykład package-lock.json od npm, pnpm-lock.yaml, yarn.lock lub bun.lock — ale nigdy nie utworzył pliku deno.lock. Uruchomienie polecenia deno install w takim projekcie tworzy brakujący plik deno.lock bezpośrednio na podstawie tego z tych plików, który zostanie znaleziony. Wersje pakietów są zgodne, a hashe integralności również. Nie ma ryzyka różnic w rozwiązaniach do obawiania się.
W sytuacjach, w których rzeczywiście potrzebny jest fizyczny katalog node_modules — np. dla dodatków natywnych lub narzędzi, które skanują system plików bezpośrednio — ustawienie "nodeModulesDir": "auto" poleca Deno na stworzenie takiego katalogu. Dla starszych narzędzi opracowanych w oparciu o płaską strukturę katalogów npm zamiast symlifikacji w stylu pnpm dostępna jest również opcja hoisted-layout ("nodeModulesLinker": "hoisted" w pliku deno.json, dostępna od wersji 2.8).
Shim dla węzła to świetne rozwiązanie: Deno automatycznie instaluje zamiennik binarny node na Twojej ścieżce PATH bez konieczności ręcznej konfiguracji, zaraz po zainstalowaniu samego Deno. Dopóki nic innego nie dostarcza wykonywalnego pliku node, ten shim przerywa wywołania wiersza poleceń Node, konwertuje argumenty i przekazuje je do Deno. Dzięki temu skrypty CI, które nadal używają node dist/server.js, funkcjonują bez żadnych zmian. Możesz wyłączyć to zachowanie za pomocą DENO_DISABLE_NODE_SHIM=1, jeśli wolisz, aby nie działo się to automatycznie.
Importy z wyraźnikiem „bare” — pisanie import fs from „fs”, co skutkuje rozwiązaniem do node:fs — zostały dodane w wersji 2.0 i stały się w pełni stabilne, działając bez żadnych flag ani konfiguracji od wersji 2.9. Nie ma potrzeby cofania się i przepisywania zdań importowych w celu migracji.
Próba migracji
Rozważmy skromny projekt API Hono (Hono to lekki framework HTTP porównywalny z Express), który zawiera około 15 tras, bazę danych Postgres dostępną przez Drizzle ORM oraz procesor zadań w tle. Projekt ten działał na Node 22 z użyciem pnpm. Całe doświadczenie przeprowadzono przy użyciu Deno 2.9.3.
Oto jak przebiegała konwersja:
- Z uruchomionego w katalogu głównym projektu wiersza poleceń wywołaj
deno install. W ciągu niecałych dwóch sekund generuje to plikdeno.lockna podstawie istniejącego plikupnpm-lock.yaml. - Dodaj do świeżo utworzonego pliku
deno.jsonwartość"nodeModulesDir": "auto". - Zapuść aplikację za pomocą polecenia
deno run -A src/server.ts.
Wszystko działa bez problemów. Każda z 15 tras reaguje prawidłowo, zapytania oparte na Drizzle wykonywają się zgodnie z oczekiwaniami, a procesor kolejki nadal przetwarza zadania w tle.
Jednak trzy rzeczy się zepsuły:
- Jeden plik testowy polegał na użyciu
jest.mock(), którego nie ma w wbudowanym narzędziu testowym Deno. Zastąpienie go ręcznym mockiem zajmuje mniej niż pięć minut. - Jedna z zależności odwoływała się do
__dirnamew pliku CommonJS, ale Deno załadowało go jako moduł ES. Rozwiązaniem było dodanie"type": "commonjs"do lokalnych ustawień tego konkretnego pakietu.
process.env.NODE_ENV bez wyraźnego importowania modułu process. Ta część faktycznie działała poprawnie, ponieważ od wersji 2.0 Deno udostępnia process jako zmienną globalną — ale flagi uprawnień nie zawierały opcji env: true dla tego konkretnego skryptu, więc trzeba to było dodać.Celowa konwersja trwała w przybliżeniu 25 minut od początku do końca. Czas rozruchu spadł z około 620 ms do 320 ms, według pomiarów przeprowadzonych za pomocą hyperfine w 50 próbach. Zużycie pamięci w stanie bezczynności (RSS) zmniejszyło się z 142 MB do 64 MB. Ponadto można było usunąć cztery oddzielne pliki konfiguracyjne: prettier, eslint, jest oraz tsconfig.
Pozostałe niedoskonałości
Tytuł tego artykułu mówi o „wszystkim, czego nienawidziłem”, a wcześniej wymienione skargi były autentyczne i stanowią powszechne problemy wśród deweloperów Node. Niemniej jednak istnieje kilka nowszych uwag, które warto podkreślić:
- Obejmowanie API Node nie jest kompletne. 75-procentowy wskaźnik pomyślności w zestawie testów Node oznacza, że jedna czwarta z nich nadal zawodzi. Ta luka może nie wpływać na typową pracę, ale wszyscy, którzy polegają na nietypowych aspektach
node:vm, programowym wykorzystaniunode:inspectorlub zaawansowanej funkcjonalnościnode:cluster, powinni najpierw sprawdzić panel kompatybilności na stronie node-test-viewer.deno.dev.
node_modules. Wszystko, co wykorzystuje bindingi C++ — takie jak sharp, bcrypt, sqlite3 oraz podobne pakiety — wymaga zarówno ustawienia nodeModulesDir, jak i flagi --allow-ffi. To działa, ale jest to dodatkowy krok konfiguracyjny, który łatwo przypadkowo pominąć.postinstall — np. kroki kompilacji node-gyp lub prisma generate — wymagają wyraźnej zgody poprzez użycie flagi --allow-scripts=npm:package-name. Jest to świadoma decyzja bezpieczeństwa, ale może zaskoczyć zespół w trakcie migracji.process.versions.node i dostosowują swoje zachowanie, albo używają sztywno zdefiniowanych ścieżek plików, takich jak /node_modules/.cache. Układ modułów w Deno oparty na symlinkach w stylu pnpm sprawia problemy niektórym z tych narzędzi. Istnieje opcja trybu hoisted-mode, która pozwala obejść ten problem, ale jest to jedynie tymczasowe rozwiązanie, a nie prawdziwa odpowiedź.Żaden z tych problemów nie był na tyle poważny, by utrudnić powyższą migrację. W innych projektach mogą być jednak istotne, dlatego warto je sprawdzić z góry, zamiast odkrywać je w trakcie migracji.
Dlaczego te poprawki pozostały niezauważone
Ta mała wersja była wypuszczana co 21 miesięcy, a każda z nich cicho rozwiązywała od trzech do pięciu problemów. Nie doszło do żadnej spektakularnej przepracowywania kodu ani specjalnego wydarzenia premiery „Deno 3.0”. Zespół wprowadził szczegółowe zestawy uprawnień w wersji 2.5, prędkość instalacji na poziomie npm w wersji 2.8 oraz funkcję tworzenia plików lockfile w wersji 2.9, traktując każdą z tych zmian jako rutynową konserwację, a nie jako ważną wiadomość.
Porównaj to z tym, jak zwykle promuje się przejścia na nowe frameworki: post na blogu, wykład na konferencji, przewodnik migracyjny, fala komentarzy w mediach społecznościowych, kontrowersyjne opinie oraz odpowiedzi na nie. Deno ominęło cały ten cykl publicznych debat i po prostu wypuściło poprawki bezpośrednio.
Rozwijający, którzy wcześnie odrzucili ten silnik, często robili to dlatego, że ukształtowali sobie jedną opinię i nigdy jej nie zmieniali w miarę dojrzewania narzędzia. To brak uwagi, a nie wada samego projektu.
Dla tych, którzy mieli wrażenie o Deno przed wersją 2.0, narzędzie testowane wtedy praktycznie zniknęło. To, co istnieje obecnie, mniej przypomina „interesujący silnik wykonywania TypeScriptu, który nie może korzystać z npm”, a bardziej „Node bez całego jego zbędnego bagażu”. Rozczarowania z tamtego czasu były uzasadnione. Zostały one później rozwiązane. Warto spojrzeć na to ponownie.
Literatura pokrewna
- Zastąpienie Jest narodowym wykonywaczem testów Node w Node 24 — Przykład z praktyki pokazuje, jak wbudowany wykonywacz testów w Node 24 oraz natywna obsługa TypeScriptu skracają czas CI, jednocześnie eliminując cztery zależności.