RE:NODE

Bun თუ Deno პატარა სერვერზე: რომელი runtime აირჩიო

ავტორი RE:NODE-ის გუნდი
19 წუთის საკითხავი


პატარა სერვერზე პატარა აპისთვის Node.js კვლავ უსაფრთხო ნაგულისხმევი არჩევანია. Bun იმისთვის ღირს, რომ სცადო, როცა გინდა TypeScript ფაილიდან მომუშავე HTTP სერვერამდე უმოკლესი გზა. Deno კი მაშინ, როცა გინდა, რომ runtime-მა უარი თქვას ყველაფერზე, რაც არ მოგითხოვია. სამივე ერთი და იმავე ტიპის პროგრამას უშვებს: ხანგრძლივად მომუშავე პროცესს, რომელიც პორტს უსმენს და HTTP-ზე პასუხობს. ჰოსტინგის გეგმაზე მნიშვნელოვანი განსხვავებები მცირე და პრაქტიკულია - როგორ ეშვება სერვერი, როგორ ინსტალირდება დამოკიდებულებები, როგორ მუშავდება TypeScript, რა ჰქვია lockfile-ს და npm-ის რომელი პაკეტები ჩუმად ფუჭდება. ეს პოსტი თითოეულს რეალური ბრძანებებით გადის და მთავრდება იმით, როგორ აირჩიო და რა შეამოწმო, როცა აპი ეშვება, მაგრამ არავინ პასუხობს.

აქ benchmark-ის რიცხვები განზრახ არ არის. runtime-ების throughput-ისა და მეხსიერების მაჩვენებლები დამოკიდებულია ვერსიაზე, დატვირთვაზე და მანქანაზე, ხოლო უმეტეს შედარებით პოსტში მოყვანილი რიცხვები hello-world handler-ს ეხება, რომელსაც რეალური აპი არ ჰგავს. იქ, სადაც მეხსიერება მნიშვნელოვანია, რჩევა ასეთია: საკუთარი აპი საკუთარ გეგმაზე გაზომე.

რა არის თითოეული runtime#

Node.js დამკვიდრებული არჩევანია. ის JavaScript-ს V8 ძრავზე უშვებს, npm-თან ერთად მოდის და აქვს ყველაზე მეტი tutorial, ჰოსტინგის მხარდაჭერა და დროში გამოცდილი ბიბლიოთეკა. TypeScript ნაგულისხმევად პირდაპირ არ სრულდება: ან აკომპილირებ, ან იყენებ loader-ს ან flag-ს, რომელიც ტიპებს აშორებს, და რა არის მხარდაჭერილი, შენი Node-ის ვერსიაზეა დამოკიდებული.

Bun ერთი შესრულებადი ფაილია, რომელიც ერთდროულად runtime-ია, package manager-ი, test runner-ი და bundler-ი. ის V8-ის ნაცვლად JavaScriptCore ძრავს იყენებს, ცდილობს Node-ის API-სა და npm პაკეტებთან თავსებადი იყოს და .ts და .tsx ფაილებს პირდაპირ უშვებს. მისი საკუთარი სერვერის API არის Bun.serve. package manager-ია bun install, ხოლო lockfile - bun.lock.

Deno runtime-ია, რომელიც უსაფრთხოების მოდელზე და ვებ-სტანდარტულ API-ებზეა აგებული. ის TypeScript-ს პირდაპირ უშვებს, მასთან ერთად მოდის formatter-ი, linter-ი, test runner-ი და ტიპების შემმოწმებელი, და კოდს ქსელზე, გარემოზე და ფაილურ სისტემაზე წვდომის გარეშე უშვებს, სანამ თავად არ მისცემ ნებართვას. მისი სერვერის API არის Deno.serve. კონფიგურაცია deno.json-შია და task-ებიც იქ განისაზღვრება.

პირველი, რაც უნდა გავარკვიოთ: ეს სამი ერთი და იმავეს სამი ვარიანტი არ არის. მათ აქვთ საერთო ენა და ვებ-სტანდარტული API-ების დიდი ნაწილი - fetch, Request, Response, URL, AbortSignal, streams - ამიტომ პატარა handler-ის ერთხელ დაწერა და მათ შორის გადატანა ხშირად შესაძლებელია. ამის მიღმა ისინი განსხვავდებიან და დროის უმეტესობაც სწორედ ამ განსხვავებას ეხარჯება.

RuntimeძრავაTypeScript ნაგულისხმევად
Node.jsV8ჯერ კომპილაცია, ან ვერსიაზე დამოკიდებული flag
BunJavaScriptCore.ts ფაილებს პირდაპირ უშვებს
DenoV8.ts ფაილებს პირდაპირ უშვებს, არასავალდებულო ტიპების შემოწმებით

ამ პოსტის რომელიმე ქცევას რომ დაეყრდნო, ჯერ შეამოწმე, რომელი ვერსია გაქვს სინამდვილეში. სამივე სწრაფად იცვლება, ხოლო node --version, bun --version და deno --version ნებისმიერი bug report-ის პირველი სტრიქონებია.

სერვერი Bun-ზე#

Bun-ის ყველაზე პატარა სასარგებლო სერვერი ერთი ფაილია:

server.ts
const port = Number(process.env.PORT) || 3000;const server = Bun.serve({  port,  hostname: "0.0.0.0",  fetch(req) {    const url = new URL(req.url);    if (url.pathname === "/healthz") {      return Response.json({ ok: true, uptime: process.uptime() });    }    return new Response("hello from bun\n");  },});console.log(`listening on ${server.port}`);

გაუშვი bun run server.ts-ით ან უბრალოდ bun server.ts-ით. handler იღებს სტანდარტულ Request-ს და აბრუნებს სტანდარტულ Response-ს, ამიტომ framework-ის დაყენება საჭირო არ არის. routing შენ უნდა დაწერო, რაც რამდენიმე endpoint-ისთვის კარგია, მეტისთვის კი მოსაბეზრებელი. ვებ-სტანდარტულ request-ებზე აგებული framework-ები, მაგალითად Hono და Elysia, Bun-ზე მუშაობს და ჩვეულებრივი შემდეგი ნაბიჯია. Express-იც მუშაობს Bun-ზე Node-თან თავსებადობის ფენის მეშვეობით, იმ დათქმებით, რომლებიც ქვემოთ, თავსებადობის თავშია.

სამი დეტალი უნდა იცოდე. პირველი, hostname: "0.0.0.0" აქ სიცხადისთვისაა გაწერილი; მთავარი ისაა, რომ სერვერმა ყველა interface-ზე უნდა მოუსმინოს, რადგან container-ში loopback-ზე მიბმული სერვერი მის წინ მდგომი proxy-სთვის მიუწვდომელია. მეორე, Bun მუშა დირექტორიაში .env ფაილს ავტომატურად კითხულობს და ცვლადებს ორივე, process.env და Bun.env, აჩვენებს. ლოკალურად ეს მოსახერხებელია, სერვერზე კი ხაფანგი: repository-ში შემთხვევით მოხვედრილი .env ჩუმად გადაფარავს შენს განზრახვას ან საიდუმლოს გაჟონავს. რეალური საიდუმლოები გარემოში შეინახე. მესამე, bun --hot server.ts ცვლილებისას თავად ახლდება, რაც დეველოპმენტისთვისაა და production-ში არ უნდა გაუშვა.

დამოკიდებულებები bun install-იდან მოდის. ის კითხულობს package.json-ს, წერს node_modules-ს და ხეს bun.lock-ში ინახავს. Bun-ის ძველი ვერსიები წერდნენ ბინარულ ფაილს სახელად bun.lockb; მიმდინარე ვერსიები წერენ ტექსტურ bun.lock-ს, რომელიც pull request-ში ნორმალურად ჩანს diff-ში. თუ შენს repository-ში ძველი bun.lockb დევს, ორიდან რომელიმეს წაშლამდე შეამოწმე, რომელს კითხულობს შენი Bun-ის ვერსია. deploy-ისთვის npm ci-ის ეკვივალენტია:

bash
$ bun install --frozen-lockfile

ის ზუსტად იმას აყენებს, რასაც lockfile ამბობს, და ვარდება, თუ package.json და lockfile ერთმანეთს არ ემთხვევა - იმავე მიზეზით, რის გამოც არსებობს npm ci. დაამატე --production, რომ devDependencies გამოტოვოს, როცა შენს გაშვების გზაზე მათგან არაფერი სჭირდება.

სერვერი Deno-ზე#

იგივე პროგრამა Deno-ზე:

main.ts
const port = Number(Deno.env.get("PORT")) || 8000;Deno.serve({ port, hostname: "0.0.0.0" }, (req: Request) => {  const url = new URL(req.url);  if (url.pathname === "/healthz") {    return Response.json({ ok: true });  }  return new Response("hello from deno\n");});

Deno.serve იღებს options ობიექტს და handler-ს, ხოლო handler ისევ ფუნქციაა Request-დან Response-მდე. თუ პორტს არ მიუთითებ, ნაგულისხმევი არის 8000, ხოლო ნაგულისხმევი hostname ყველა interface-ზე უსმენს, მაგრამ ორივეს ჩაწერა არაფერი ღირს და მოგვიანებით პრობლემის ძიებისას ერთ ვარაუდს გაგიქრობს.

გაშვებაში Deno ყველაზე მეტად განსხვავდება, permission-ების გამო:

bash
$ deno run --allow-net --allow-env=PORT main.ts

--allow-net-ის გარეშე პროგრამას უარს ეუბნება იმ მომენტში, როცა socket-ის გახსნას ცდილობს. --allow-env-ის გარეშე Deno.env.get-ის გამოძახება permission შეცდომით ვარდება (ან გკითხავს, თუ ინტერაქტიულად გაუშვი). მოდელი არის აკრძალვა ნაგულისხმევად და პატარა სერვერისთვის ის ნამდვილად სასარგებლოა: დამოკიდებულება, რომელსაც უცებ შენი გარემოს წაკითხვა ან სადმე კავშირის გახსნა მოუნდება, ჩუმად წარმატების ნაცვლად შეცდომით ჩერდება.

პროექტის კონფიგურაცია deno.json-ში იწერება, და ჰოსტზე შენთვის სასარგებლო ნაწილი tasks-ია:

deno.json
{  "tasks": {    "start": "deno run --allow-net --allow-env=PORT,DATABASE_URL main.ts",    "dev": "deno run --watch --allow-net --allow-env main.ts",    "check": "deno check main.ts"  },  "imports": {    "hono": "npm:hono@4"  }}

deno task start task-ში ჩაწერილ ბრძანებას უშვებს. imports ბლოკი import map-ია: ის პაკეტებს მოკლე სახელებს აძლევს, რომ კოდში დაწერო import { Hono } from "hono". პრეფიქსი npm: პაკეტს npm registry-დან იღებს, ხოლო jsr: - JSR-იდან, Deno-ს საკუთარი registry-დან. Deno წერს deno.lock ფაილს იმის დასაფიქსირებლად, რაც ჩამოტვირთა, და flag --frozen აიძულებს install-ს ან გაშვებას ჩავარდეს, თუ lockfile შეიცვლებოდა. თუ Deno-ს პროექტში package.json-ს ინახავ, მიმდინარე ვერსიები მას კითხულობენ და შეუძლიათ node_modules-ში დააინსტალირონ; ეს არის გზა, როცა არსებულ Node პროექტს გადააქვს და ნულიდან არ იწყებ.

Permission flag-ები და რა მისცე#

Permission-ები Deno-ს არჩევის მთავარი მიზეზია და ღირს მათი სწორად გამოყენება, ყველაფრის დამშვები flag-ის ნაცვლად.

Flagრას აძლევსვიწრო ფორმა
--allow-netქსელზე წვდომა--allow-net=0.0.0.0:8000,api.example.com
--allow-envგარემოს ცვლადების წაკითხვა--allow-env=PORT,DATABASE_URL
--allow-readფაილების წაკითხვა--allow-read=./static,./data
--allow-writeფაილებში ჩაწერა--allow-write=./data

არსებობს სხვებიც (--allow-run პროცესების გასაშვებად, --allow-ffi, --allow-sys), და -A ან --allow-all ყველაფერს რთავს. სერვერისთვის, რომელიც უსმენს, ორ გარემოს ცვლადს კითხულობს და დირექტორიას გასცემს, პატიოსანი ნაკრები ზემოთ მოცემული პირველი სამია, თითოეული დავიწროებული:

bash
$ deno run \    --allow-net=0.0.0.0:8000,db.internal:5432 \    --allow-env=PORT,DATABASE_URL \    --allow-read=./static \    main.ts

ორი გაფრთხილება. ვიწრო ფორმები იმდენად კარგია, რამდენადაც შენი სიაა: გასულ თვეს დამატებულ ბიბლიოთეკას შეიძლება კიდევ ერთი გარემოს ცვლადი დასჭირდეს და შეცდომა permission error-ის სახით პირველივე request-ზე გამოჩნდება, რომელიც მას შეეხება, და არა გაშვებისას. სანამ სიას ენდობი, აპი რეალური გზებით გაატარე. და -A პირადი ხელსაწყოსთვის კანონიერი არჩევანია, მაგრამ მაშინ უარი თქვი იმ შესაძლებლობაზე, რომელიც runtime-ის არჩევას ამართლებდა, ამიტომ საკუთარ თავთან გულწრფელი იყავი.

Bun-სა და Node-ს ასეთი გადამრთველი არ აქვთ. Node-ის ახალ ვერსიებს აქვთ ექსპერიმენტული permission მოდელი flag-ის უკან და მისი სტატუსი რელიზებს შორის იცვლება, ამიტომ რამეს მასზე რომ ააგო, ჯერ წაიკითხე დოკუმენტაცია იმ ვერსიისთვის, რომელსაც იყენებ.

TypeScript build ეტაპის გარეშე#

Bun-ისა და Deno-ს მშვიდი მიმზიდველობა ისაა, რომ main.ts როგორც არის, ისე სრულდება. არ არის tsc, არ არის dist დირექტორია, არ არის source map-ები, რომლებიც გაშვებულ პროცესთან უნდა შეათანხმო, და არ არის კომპილატორზე devDependency, რაც გვერდს უვლის npm ci პოსტში აღწერილ tsc: not found ხაფანგს. პატარა აპისთვის ეს deploy-დან მთელ ეტაპს აცილებს.

რას იღებ, თითოეულში ცოტა განსხვავებულია:

  • Bun TypeScript-ს ტიპების მოშორებითა და გარდაქმნით ტრანსპილირებს და ტიპებს არ ამოწმებს. ტიპის შეცდომა პროგრამას არ აჩერებს; უბრალოდ ვერ ამჩნევს. თუ შემოწმებები გინდა, CI-ში გაუშვი tsc --noEmit.
  • Deno-ც deno run-ზე ნაგულისხმევად ფაილს შემოწმების მოთხოვნის გარეშე უშვებს და ტიპების აშკარა შემოწმებისთვის გთავაზობს deno check-ს. deno check main.ts-ის CI-ში ან task-ად ჩადება დამცავ ბადეს გაძლევს.
  • Node-ს ბოლო ვერსიებში შეუძლია TypeScript-ის გაშვება ტიპების მოშორებით, იმ შეზღუდვებით, თუ TypeScript-ის რომელი შესაძლებლობებია მხარდაჭერილი. თუ ამაზე დაეყრდნობი, Node-ის ვერსია დააფიქსირე და მისი შენიშვნები წაიკითხე.

პრაქტიკული წესი ყველგან ერთია: TypeScript-ის გაშვება მისი შემოწმება არ არის. runtime სიამოვნებით გაუშვებს პროგრამას, რომელშიც ტიპის შეცდომაა, და პირველად ამის შესახებ ლოგში გაიგებ, TypeError-ის სახით, გაშვების დროს. ტიპები CI-ში ან push-მდე შეამოწმე და სერვერს იმ ადგილად მიიჩნიე, სადაც პროგრამა მუშაობს და არა სადაც მოწმდება.

კიდევ ერთი დაბრკოლებაა. TypeScript-ის შესაძლებლობები, რომლებსაც კოდის გენერაცია სჭირდება, მაგალითად legacy decorator-ები და enum-ისა და namespace-ის ზოგი გამოყენება, სხვადასხვა ხელსაწყოში სხვადასხვანაირად მუშავდება. tsconfig.json-ის პარამეტრებს, როგორიცაა experimentalDecorators ან paths, Bun კითხულობს; Deno compiler-ის პარამეტრებს deno.json-იდან კითხულობს და მოდულების გადაწყვეტაზე საკუთარი შეხედულება აქვს, მათ შორის relative import-ებში ფაილის გაფართოების მოთხოვნით. bundler-ისთვის დაწერილი პროექტი, რომელიც ./utils-ს გაფართოების გარეშე წყვეტს, Deno-ზე ჩავარდება, სანამ import-ები ./utils.ts-ს არ იტყვიან.

npm-თან თავსებადობა და რა ფუჭდება#

npm-ის უმეტესობა ორივეზე მუშაობს. წმინდა JavaScript პაკეტები თითქმის ყოველთვის მუშაობს, ისევე როგორც პოპულარული framework-ები. შეცდომები რამდენიმე ადგილას გროვდება და არსებული პროექტის გადატანამდე მათი ცოდნა ღირს.

  • ნატიური addon-ები. პაკეტები, რომლებიც ინსტალაციისას C ან C++ კოდს აკომპილირებენ და .node ბინარულ ფაილს ტვირთავენ, მაგალითად ზოგი image, crypto და მონაცემთა ბაზის დრაივერი, Node-ის ნატიური addon ინტერფეისზეა დამოკიდებული. Bun ბევრისთვის Node-API-ს უჭერს მხარს და Deno-ც უჭერს, permission-ის უკან, მაგრამ პაკეტი, რომელიც V8-ის შიდა ნაწილებს ეწვევა, არ ჩაიტვირთება. ამჯობინე პაკეტები, რომლებიც WebAssembly-ს აწვდიან ან წმინდა JavaScript fallback აქვთ.
  • Install script-ები. bun install ნაგულისხმევად თვითნებური დამოკიდებულებების lifecycle script-ებს არ უშვებს; ის სანდო პაკეტების სიას ინახავს და მას package.json-ში trustedDependencies-ით ავსებ. პაკეტი, რომელსაც ბინარული ფაილის გადმოსაწერად postinstall სჭირდება (Prisma-ს engine-ები, Puppeteer-ის ბრაუზერი, ზოგ კონფიგურაციაში sharp-ის პლატფორმული build), შეიძლება საჭირო ფაილის გარეშე მოვიდეს. Deno-ც მოითხოვს lifecycle script-ებზე თანხმობას, install-ზე --allow-scripts flag-ით.
  • Node-ის ჩაშენებულები კიდეებზე. ორივე runtime-ს უმეტესობა node:fs-ის, node:http-ის, node:crypto-ის, node:stream-ისა და მსგავსების აქვს რეალიზებული და ორივე ცდილობს Node-ის ქცევას მიჰყვეს. ხარვეზები იშვიათად გამოყენებულ კუთხეებშია: node:cluster, node:vm-ის დეტალები, worker_threads-ის ზოგი ქცევა, node:inspector, და stream-ებისა და event-ების ზუსტი დროითობა. კოდი, რომელიც დახვეწილ ქცევაზეა დამოკიდებული, ის ადგილია, სადაც გადატანა ცუდად მიდის.
  • Framework-ები, რომლებიც Node-ის სერვერის ობიექტს ვარაუდობენ. Express Bun-ზე node:http თავსებადობის ფენით მუშაობს. Express Deno-ზე მუშაობს npm:express-ით, იმავე ფენით და სწორი permission-ებით. ორივე გზა მუშაობს, მაგრამ Node-ის API-ს არა-Node runtime-ზე უშვებ და ბაგი, რომელიც გამოჩნდება, ნაკერში იქნება. ვებ-სტანდარტულ Request-სა და Response-ზე აგებული framework ნაკერს საერთოდ გვერდს უვლის.
  • ხელსაწყოები, რომლებიც Node-ს ელიან. ყველაფერი, რაც node-ს shell-იდან იძახებს, ტოტის არჩევისთვის process.versions.node-ს კითხულობს ან დოკუმენტირებულია მხოლოდ npm run-ისა და npx-ისთვის, შემოწმებას საჭიროებს. npx-ს ცვლიან bunx და deno run npm:package.

გასარკვევი გზა მოსაწყენია: დააინსტალირე, გაუშვი აპი, გამოიძახე ყველა route, გაუშვი test suite ახალი runtime-ით და წაიკითხე ლოგი გაფრთხილებებისთვის. პაკეტი, რომელიც changelog-ში „მხარდაჭერილია“, შეიძლება მაინც ჩავარდეს იმ ერთ კოდის გზაზე, რომელსაც შენ იყენებ. თუ პროექტი ძირითადად Node-ზე მორგებული დამოკიდებულებების გროვაა, გადატანის ფასი ჩვეულებრივ იმას აჭარბებს, რასაც მოიგებ.

მეხსიერება, გაშვება და პატარა გეგმის კითხვა#

Bun-ზე ან Deno-ზე ხალხი მეხსიერების დაბალი მოხმარების ან უფრო სწრაფი გაშვების იმედით გადადის. ზოგჯერ იღებენ, ზოგჯერ - არა. აქ გამოქვეყნებული რიცხვი არაკეთილსინდისიერი იქნებოდა, ამიტომ აი, რისი თქმაც უსაფრთხოა.

რეზიდენტული მეხსიერება დამოკიდებულია ძრავის საბაზისო მოხმარებაზე, ჩატვირთული კოდის რაოდენობაზე, მეხსიერებაში შენი მონაცემების ზომაზე, garbage collector-ის ჩვევებსა და allocator-ზე. ერთი და იმავე აპის გამშვებ ორ runtime-ს განსხვავება ორივე მიმართულებით შეიძლება ჰქონდეს, ხოლო უქმე მაჩვენებელი იშვიათად გვიჩვენებს დატვირთვისას რა იქნება. JavaScriptCore და V8 garbage-ს სხვადასხვანაირად აგროვებენ და პროცესი, რომელიც უქმედ მჭლედ გამოიყურება, კონკრეტული request-ების ნაკადისას მაინც შეიძლება გაიზარდოს. არც ერთი ძრავა პატარა მოხმარების გარანტია არ არის.

რისი გაკეთებაც შეგიძლია, არის იმის გაზომვა, რასაც რეალურად გაუშვებ:

  1. გაუშვი აპი იმ გეგმაზე, რომლის ყიდვასაც აპირებ, შენი მონაცემების რეალისტური ასლით.
  2. კონსოლში მეხსიერების გრაფიკს უყურე, სანამ წარმომადგენლობით ტრაფიკს აბრუნებ, მათ შორის ყველაზე დიდ request-ს, რომელსაც აპი ამუშავებს.
  3. დატოვე ერთი დღე გაშვებული. გაჟონვა ისეთი ხაზია, რომელიც არასოდეს ეშვება ქვემოთ.
  4. შეადარე გეგმის ლიმიტს და არა ბლოგიდან აღებულ რიცხვს.

RE:NODE-ზე მეხსიერების ლიმიტები განსაკუთრებულად იქცევა: container, რომელიც ლიმიტს მიაღწევს, swap-ის ნაცვლად ჩერდება და სუფთად თავიდან ეშვება. Bun-სა და Deno-ზე შედეგი იგივეა, რაც Node-ზე - გაჟონვა რამდენიმე საათში ერთხელ restart-ად ჩანს. საათში სამი restart სერვერის გვერდზე გაფრთხილებას იწვევს და ავტომატურად ხსნის ticket-ს. Node-ის მეხსიერების ლიმიტები განმარტებით V8-ის heap flag-ებს აღწერს; ისინი Bun-ზე არ გადადის, რომელსაც საკუთარი პარამეტრები აქვს, ამიტომ Node-ის flag-ებს Bun-ის გაშვების ბრძანებაში ნუ ჩასვამ და ნუ ელი, რომ პატივს სცემს.

გაშვების დრო მეორე განცხადებაა. TypeScript ფაილი, რომელიც პირდაპირ სრულდება, კომპილაციის ეტაპს ზოგავს და Bun-ის installer სწრაფია, მაგრამ ჰოსტინგის გეგმაზე მნიშვნელოვანი რიცხვია, რამდენი დრო სჭირდება deploy-ს push-იდან პასუხამდე, და ამას დამოკიდებულებების ინსტალაცია და შენი საკუთარი boot სამუშაო განსაზღვრავს, მაგალითად მონაცემთა ბაზასთან კავშირები და migration-ები. გაზომე deploy და არა hello-world.

პროცესი, გარემო და პორტის მართვა#

იგივე წესები სამივე runtime-ზე მოქმედებს და სწორედ ისინი იწვევენ „ლოკალურად მუშაობს“ ტიპის ticket-ების უმეტესობას.

პორტი გარემოდან წაიკითხე. პორტის მკაცრად ჩაწერა ნიშნავს, რომ აპი იქ მიება, სადაც არაფერი მარშრუტდება. RE:NODE-ზე აპის გეგმას ერთი პორტის განაწილება აქვს, რომელიც Network ტაბზე ჩანს, და გარემოს ცვლადები Startup ტაბზე იწერება. გამოიყენე ცვლადის სახელი, რომელსაც შენი გეგმა აჩვენებს და არა გამოცნობილი, და ნაგულისხმევზე მხოლოდ ლოკალური გაშვებისთვის დაბრუნდი.

ყველა interface-ზე მიება. 0.0.0.0 Bun.serve-სა და Deno.serve-ში და ეკვივალენტი ნებისმიერ framework-ში. სერვერი, რომელიც 127.0.0.1-ზე წერს „listening“-ს და არავის პასუხობს, ყველაზე გავრცელებული შეცდომაა და proxy-სგან connection refused-ს იძლევა, შენს ლოგში ხაზის გარეშე.

ჩავარდი სწრაფად, თუ კონფიგურაცია აკლია. Express-ის production პოსტის პატერნი უცვლელად გადმოდის:

javascript
const required = ["DATABASE_URL", "SESSION_SECRET"];const missing = required.filter((name) => !process.env[name]);if (missing.length) {  console.error(`missing configuration: ${missing.join(", ")}`);  process.exit(1);}

Deno-ზე process.env[name] ჩაანაცვლე Deno.env.get(name)-ით და გახსოვდეს, რომ --allow-env სიაში უნდა იყოს ყველა სახელი, რომელსაც კითხულობ. Deno-ს ახალი ვერსიები process.env-საც უჭერენ მხარს Node-თან თავსებადობის ფენით, მაგრამ უფრო სუფთაა, პროექტში ერთი სტილი აირჩიო. სად ცხოვრობს საიდუმლოები, ამის შესახებ არის გარემოს ცვლადები და საიდუმლოები.

დაამუშავე SIGTERM. restart ან deploy შეწყვეტის სიგნალს აგზავნის, და სერვერი, რომელიც მას უგულებელყოფს, საპატიო ვადის შემდეგ კვდება და მიმდინარე request-ებს კარგავს. Bun-სა და Node-ზე process.on("SIGTERM", ...) მუშაობს და Bun-ში კავშირების მიღებას server.stop() აჩერებს. Deno-ზე გამოიყენე Deno.addSignalListener("SIGTERM", ...) და server.shutdown() იმ ობიექტზე, რომელსაც Deno.serve აბრუნებს. ზოგადი სქემა, health endpoint-ების ჩათვლით, არის graceful shutdown და health check-ები პოსტში.

დაწერე ლოგი stdout-ში. კონსოლი მას ფილტრის გარეშე და ცოცხლად აჩვენებს. სტრუქტურირებული JSON ხაზები დამატებით ხუთ წუთს ღირს.

გაშვება RE:NODE-ზე#

RE:NODE-ს აპების კატეგორიაში ცალკე Bun და Deno ხაზები აქვს, თითოეული იწყება $4-დან თვეში 1 GB მეხსიერებით, 5 GB დისკითა და ნახევარი vCPU-თი, ზემოთ უფრო დიდი გეგმებით. ისინი Node.js-ის, Python-ის, Go-ს, Java-ს, Laravel-სა და C#-ის გვერდითაა და პანელი ყველაზე ერთნაირია: კონსოლი, ფაილების მენეჯერი, SFTP, backup-ები და Startup ტაბი. გეგმები და მიმდინარე ფასები აპების ჰოსტინგის გვერდზეა.

რამდენიმე რამ ამ ორ ხაზს განსაკუთრებით ეხება:

  • საწყისი გვერდი. Node.js-ის გარდა ყველა runtime ხაზი ახალ სერვერს მისამართზე პატარა მომუშავე გვერდით იწყებს, სანამ საკუთარ კოდს არ გაუშვებ, და ის შენს ფაილებს არასოდეს გადაწერს. ეს სწრაფი გზაა დასადასტურებლად, რომ მისამართი და პორტის განაწილება მუშაობს, სანამ შენი კოდი ჩართულია. Node.js სერვერებს ეს გვერდი არ აქვთ.
  • Deploy GitHub-იდან. Git deploy ყველა აპის ხაზზეა ხელმისაწვდომი: მხოლოდ GitHub, GitHub App-ის მეშვეობით ხანმოკლე token-ებით, ამიტომ private repository-ებიც მუშაობს. მას ორი გადამრთველი მართავს - pull ყოველ გაშვებაზე და deploy push-ზე, რომელიც გაშვებულ სერვერს restart-ს უკეთებს და გაჩერებულს უშვებს - და ყოველი deploy ჩანაწერს ტოვებს. Node-ის გასავლელი Node.js აპის deploy GitHub-იდან სხვა ხაზებზეც დაახლოებით ერთნაირად გადაიტანება.
  • Proxy slot-ი. აპის გეგმები proxy slot-ს შეიცავს. მიუთითე A record ნაჩვენებ მისამართზე და სერტიფიკატი ავტომატურად გაიცემა და ახლდება 21-დღიან ფანჯარაში; რეალური კლიენტის მისამართი X-Forwarded-For-ში მოდის. Bun.serve-სა და Deno.serve-ში trust proxy პარამეტრი არ არის, ამიტომ header თავად წაიკითხე და, თუ ერთ hop-ს ენდობი, ბოლო ჩანაწერი აიღე. დეტალები არის რას აკეთებს reverse proxy და დომენის მიმართვა შენს სერვერზე პოსტებში.
  • პროცესების მენეჯერი საჭირო არ არის. პანელი თავიდან უშვებს პროცესს, რომელიც გამოდის, ამიტომ PM2 ან supervisor არ გჭირდება. PM2 თუ ჰოსტინგის პანელი პატიოსანი შედარებაა.
  • მონაცემთა ბაზის slot-ები. აპის გეგმები ორ მონაცემთა ბაზის slot-ს შეიცავს, რომლებიც პანელში გენერირებული credentials-ით იქმნება და აპს გარემოს ცვლადებით გადაეცემა.

ის, რაც არ უნდა იგულისხმო, ისაა, რასაც პანელი არ ამბობს. ეს პოსტი არ გეუბნება, რომელი ზუსტი გაშვების ბრძანება აქვს ახალ Bun ან Deno სერვერს ან რომელი ცვლადის სახელი ატარებს პორტს, რადგან ეს შენი Startup ტაბიდან წასაკითხია. hello-world გვერდი მისამართის დასტურად მიიჩნიე და შემდეგ ჩაანაცვლე.

ტიპური deploy-ის install და გაშვების ბრძანებები მოკლეა:

bash
# Bunbun install --frozen-lockfile && bun run server.ts# Denodeno task start

არჩევანი Node-ს, Bun-სა და Deno-ს შორის#

უნივერსალური პასუხი არ არსებობს და ქვემოთ დაწერილი გაზომვა კი არა, შეფასებაა.

აირჩიე Node.js, როცა გაქვს არსებული აპი npm დამოკიდებულებების გროვით, როცა ნატიურ addon-ებს ეყრდნობი, როცა გუნდმა უკვე იცის ან როცა ყველაზე ნაკლებად მოულოდნელი ქცევა გინდა. თითქმის ყველა deploy პრობლემას, რომელსაც შეხვდები, Node-ისთვის უკვე დაწერილი პასუხი აქვს. ფასია TypeScript-ის კომპილაციის ეტაპი, რომელიც მცირეა.

აირჩიე Bun, როცა ახალ TypeScript სერვისს იწყებ, გინდა ერთი ხელსაწყო install-ის, გაშვებისა და ტესტირებისთვის და შენი დამოკიდებულებები წმინდა JavaScript-ია ან ცნობილია, რომ Bun-ზე მუშაობს. ის Node პროექტის ყველაზე ახლო drop-in ჩანაცვლებაა, რადგან package.json-სა და node_modules-ს კითხულობს, ამიტომ გასინჯვა ერთი შუადღე ჯდება და არა გადაწერა. ტესტები მასზე გაუშვი, სანამ commit-ს გააკეთებ.

აირჩიე Deno, როცა permission მოდელი მოგწონს, როცა გინდა ჩაშენებული deno check, deno fmt და deno lint ან როცა ვებ-სტანდარტულ API-ებზე წერ და JSR-იდან იმპორტირებ. ის კარგად ერგება პატარა API-ს ცოტა დამოკიდებულებით. ნაკლებად კომფორტულად ერგება დიდ Express კოდის ბაზას, რომელიც permission-ების გათვალისწინებით არასოდეს დაწერილა.

თუ დარწმუნებული არ ხარ, ააგე აპი ვებ-სტანდარტულ Request-სა და Response-ზე პატარა framework-ით, როგორიცაა Hono, დამოკიდებულებები წმინდა JavaScript-ად დატოვე და სამს შორის გადასვლა შეგეძლება შეცვლილი entry ფაილით. ეს გადატანადობა ნებისმიერ დეკლარირებულ სიჩქარის სხვაობაზე მეტად ღირს.

პრობლემების მოგვარება#

სერვერი ეშვება და არავინ პასუხობს. bind მისამართი ან პორტი. შეამოწმე, რომ host არის 0.0.0.0 და პორტი ის არის, რომელსაც გეგმა გიყოფს. გახსენი Network ტაბი, შეადარე და ნახე ლოგის ხაზი, რომელსაც აპი ბეჭდავს.

`error: Cannot find module` Bun-ზე deploy-ის შემდეგ. install არ გაეშვა ან არასწორი flag-ებით გაეშვა. გაშვების ბრძანებაში გაუშვი bun install --frozen-lockfile და გამოსავალი წაიკითხე კონსოლში. თუ პაკეტს postinstall ნაბიჯი სჭირდება, დაამატე trustedDependencies-ში.

`error: Requires net access` ან `Requires env access` Deno-ზე. permission აკლია. შეცდომა permission-ს და ხშირად ზუსტ host-ს ან ცვლადს ასახელებს. დაამატე ვიწრო flag და არა -A.

`error: Relative import path "x" not prefixed with / or ./ or ../`. Deno-ს სჭირდება ან relative გზა გაფართოებით, ან მიბმა deno.json-ის imports-ში. დაამატე მიბმა.

Lockfile-ის შეუსაბამობა install-ზე. bun install --frozen-lockfile და Deno-ს --frozen ვარდება, როცა manifest lockfile-ის გარეშე შეიცვალა. თავიდან დააგენერირე ლოკალურად, commit-ი გააკეთე, deploy-ი. სერვერზე flag-ს ნუ გააქრობ.

პაკეტი ლოკალურად მუშაობს და სერვერზე ვარდება. პლატფორმაზე დამოკიდებული არასავალდებულო დამოკიდებულებები, ნატიური ბინარები ან აკლია postinstall. lockfile დააგენერირე Linux მანქანაზე ან CI-ში და შეამოწმე პაკეტის შენიშვნები შენი runtime-ისთვის.

მეხსიერების გრაფიკი იზრდება და შემდეგ სერვერი თავიდან ეშვება. გაჟონვა, შეუზღუდავი cache ან მეხსიერებაში დარჩენილი დიდი payload. წაიკითხე ზემოთ მეხსიერების თავი, შეზღუდე cache-ები და კონსოლში restart-ების სურათი შეამოწმე. რას უნდა დააკვირდე, კონსოლის კითხვა გვიჩვენებს.

WebSocket ლოკალურად უკავშირდება და proxy-ს გავლით წყდება. უქმობის timeout-ები და upgrade-ის დამუშავება proxy-ს გზაზე. იხილე WebSockets reverse proxy-ს უკან.

FAQ#

Bun production-ისთვის მზადაა?

ბევრი გუნდი მას production-ში იყენებს და ამავე დროს ის Node-ზე ახალგაზრდაა და თავსებადობის ნაკლებად სრული ისტორია აქვს. შენი აპისთვის მზადაა თუ არა, შენს დამოკიდებულებებზეა. გაუშვი test suite მასზე, გატესტე დატვირთვით route-ები, რომლებიც შენთვის მნიშვნელოვანია, და შეინარჩუნე შესაძლებლობა, ერთი-ორი რელიზით Node-ზე დაბრუნდე.

შემიძლია არსებული Express აპი Bun-ზე ან Deno-ზე გავუშვა?

ჩვეულებრივ, დიახ, Node-თან თავსებადობის ფენებით, მაგრამ ეს განიხილე როგორც მიგრაცია ტესტების გავლით და არა flag-ის გადამრთველი. Deno-ზე უმატებ npm: specifier-ებს ან package.json-ს, plus permission-ებს. ფრთხილად იყავი ნატიურ მოდულებთან და ყველაფერთან, რაც Node-ის შიდა ნაწილებზეა დამოკიდებული.

მჭირდება build ეტაპი TypeScript-ისთვის?

Bun-სა და Deno-ზე არა, რომლებიც .ts ფაილებს პირდაპირ უშვებენ. ტიპების შემოწმება მაინც სადმე გჭირდება, რადგან არც ერთი გაშვების ბრძანება ტიპის შეცდომას არ უარყოფს. CI-ში ჩადე tsc --noEmit ან deno check.

რომელი იყენებს ნაკლებ მეხსიერებას, Bun თუ Deno?

დამოკიდებულია აპზე, ვერსიებზე და დატვირთვაზე, და ნებისმიერი ერთი რიცხვი ვარაუდი იქნებოდა. გაზომე საკუთარი აპი იმ გეგმაზე, რომლის გამოყენებასაც აპირებ, და შეადარე გრაფიკი ლიმიტს.

რა ჰქვია Bun-ის lockfile-ს?

bun.lock, ტექსტური ფაილი, მიმდინარე ვერსიებში. ძველი ვერსიები წერდნენ ბინარულ bun.lockb-ს. lockfile commit-ში შეიტანე და სერვერზე დააინსტალირე bun install --frozen-lockfile-ით.

მუშაობს Deno-ს permission flag-ები ჰოსტინგის გეგმაზე?

დიახ. ისინი deno run-ის flag-ებია, ამიტომ გაშვების ბრძანებაში ან deno.json-ის task-ში იწერება. ისინი შენს პროცესს ზღუდავენ; ჰოსტინგის container იზოლაციის ცალკე ფენაა.