دليل Deno الشامل: بيئة تشغيل TypeScript الحديثة مع أمثلة برمجية عملية
Deno كبديل واعد لبيئة تشغيل Node.js الشهيرة. أطلقها ريان دال (Ryan Dahl)، المبتكر الأصلي لـ Node.js، بهدف معالجة بعض القرارات المبكرة التي ندم عليها في تصميم Node.js، وتقديم بيئة تشغيل حديثة تتوافق مع أحدث ميزات لغة JavaScript و TypeScript.
يهدف هذا الدليل الشامل إلى تعريفك بـ Deno بسرعة وفعالية. سنستكشف ميزاته الفريدة، نقارنه بـ Node.js، ونرشدك خطوة بخطوة لبناء أول واجهة برمجة تطبيقات (REST API) خاصة بك باستخدام Deno. استعد لاكتشاف بيئة تشغيل قد تغير طريقة تفكيرك في تطوير تطبيقات الخادم.
ما هو Deno؟
إذا كنت على دراية بـ Node.js، النظام البيئي الشهير لـ JavaScript من جانب الخادم، فإن Deno يشبه Node، ولكنه محسّن بشكل عميق في جوانب متعددة. دعنا نبدأ بقائمة سريعة لأبرز الميزات التي تميز Deno:
- يعتمد على الميزات الحديثة للغة
JavaScript. - يمتلك مكتبة قياسية (
Standard Library) واسعة النطاق. - تعتبر
TypeScriptجوهر عمله، مما يوفر ميزة هائلة بطرق مختلفة، بما في ذلك دعمTypeScriptمن الدرجة الأولى (لا تحتاج إلى تجميعTypeScriptبشكل منفصل، فـDenoيقوم بذلك تلقائيًا). - يتبنى الوحدات النمطية لـ
ES Modulesبشكل أصيل. - لا يعتمد على مدير حزم (
Package Manager) تقليدي. - يقدم دعمًا متكاملًا لكلمة
awaitالمفتاحية على المستوى الأعلى (top-level await). - يتضمن مرفقًا مدمجًا للاختبار (
Built-in testing facility). - يهدف إلى أن يكون متوافقًا مع المتصفحات قدر الإمكان، على سبيل المثال من خلال توفير دالة
fetchمدمجة وكائنwindowالعام.
سنستكشف جميع هذه الميزات في هذا الدليل. بعد استخدام Deno وتقدير ميزاته، قد تبدو Node.js وكأنها تقنية قديمة، خاصة وأن واجهة برمجة تطبيقات Node.js تعتمد على دوال الاستدعاء (callbacks)، حيث كُتبت قبل ظهور الوعود (promises) و async/await بوقت طويل. لا يوجد تغيير متاح لذلك في Node.js لأن مثل هذا التغيير سيكون هائلاً. لذا، نحن عالقون مع callbacks أو مع تحويل استدعاءات API إلى وعود (promisifying).
Node.js تقنية رائعة وستظل المعيار الفعلي في عالم JavaScript. لكننا نعتقد أننا سنرى Deno يُعتمد تدريجيًا أكثر فأكثر بسبب دعمه المتكامل لـ TypeScript ومكتبته القياسية الحديثة. يستطيع Deno تحمل كتابة كل شيء باستخدام التقنيات الحديثة، حيث لا توجد توافقية عكسية للحفاظ عليها. بالطبع، لا يوجد ضمان بأن نفس الشيء لن يحدث لـ Deno في عقد من الزمان وتظهر تقنية جديدة، لكن هذا هو الواقع في الوقت الحالي.
لماذا Deno؟ ولماذا الآن بالذات؟
أُعلن عن Deno قبل ما يقرب من عامين من قبل المبتكر الأصلي لـ Node.js، ريان دال، في مؤتمر JSConf EU. يمكنك مشاهدة فيديو المحاضرة على YouTube، إنه مثير للاهتمام للغاية ومشاهدته إلزامية إذا كنت منخرطًا في Node.js و JavaScript بشكل عام.
يجب على كل مدير مشروع اتخاذ قرارات. ندم ريان على بعض القرارات المبكرة في Node. بالإضافة إلى ذلك، تتطور التكنولوجيا، واليوم JavaScript لغة مختلفة تمامًا عما كانت عليه في عام 2009 عندما بدأ Node. فكر في ميزات ES6/2016/2017 الحديثة وما إلى ذلك. لذلك بدأ مشروعًا جديدًا لإنشاء نوع من الموجة الثانية من تطبيقات الخادم التي تعمل بـ JavaScript.
السبب في كتابتي لهذا الدليل الآن وليس في ذلك الوقت هو أن التقنيات تحتاج إلى الكثير من الوقت لتنضج. وقد وصلنا أخيرًا إلى Deno 1.0 (كان من المقرر إطلاق 1.0 في 13 مايو 2020)، وهو الإصدار الأول من Deno الذي أُعلن عنه رسميًا بأنه مستقر. قد يبدو هذا مجرد رقم، لكن 1.0 يعني أنه لن تكون هناك تغييرات جذرية كبيرة حتى Deno 2.0. وهذا أمر مهم عندما تتعمق في تقنية جديدة – فأنت لا تريد تعلم شيء ثم يتغير بسرعة كبيرة.
هل يجب أن تتعلم Deno؟
هذا سؤال كبير. تعلم شيء جديد مثل Deno يتطلب جهدًا كبيرًا. اقتراحي هو أنه إذا كنت تبدأ الآن في تطوير JavaScript من جانب الخادم ولم تكن تعرف Node بعد، ولم تكتب أي TypeScript مطلقًا، فابدأ بـ Node. لم يُطرد أحد أبدًا لاختياره Node.js (إعادة صياغة لمقولة شائعة). ولكن إذا كنت تحب TypeScript، ولا تعتمد على عدد لا يحصى من حزم npm في مشاريعك، وترغب في استخدام await في أي مكان، فربما يكون Deno هو ما تبحث عنه.
هل سيحل Deno محل Node.js؟
لا. Node.js عملاق، راسخ، مدعوم بشكل لا يصدق، وسيبقى موجودًا لعقود. لا توجد تقنية واحدة تحل محل أخرى تمامًا في عالم البرمجيات، بل تتطور البيئات وتتكامل.
دعم TypeScript من الدرجة الأولى
كُتب Deno بلغة Rust و TypeScript، وهما من اللغات التي تنمو بسرعة كبيرة اليوم. على وجه الخصوص، كون Deno مكتوبًا بـ TypeScript يعني أننا نحصل على الكثير من فوائد TypeScript حتى لو اخترنا كتابة شيفرتنا بـ JavaScript العادية. وتشغيل شيفرة TypeScript باستخدام Deno لا يتطلب خطوة تجميع – Deno يقوم بذلك تلقائيًا لك.
لست مجبرًا على الكتابة بـ TypeScript، لكن حقيقة أن جوهر Deno مكتوب بـ TypeScript أمر ضخم. أولاً، نسبة متزايدة من مبرمجي JavaScript يحبون TypeScript. ثانيًا، يمكن للأدوات التي تستخدمها استنتاج الكثير من المعلومات حول البرامج المكتوبة بـ TypeScript، مثل Deno. هذا يعني أنه عندما نقوم بالبرمجة في VS Code، على سبيل المثال (الذي يتمتع بالطبع بتكامل وثيق مع TypeScript نظرًا لأن كلاهما تم تطويرهما في Microsoft)، يمكننا الحصول على فوائد مثل التحقق من الأنواع (type checking) أثناء كتابة شيفرتنا، وميزات IntelliSense المتقدمة. بعبارة أخرى، يمكن للمحرر مساعدتنا بطريقة مفيدة للغاية.
أوجه التشابه والاختلاف مع Node.js
بما أن Deno هو في الأساس بديل لـ Node.js، فمن المفيد مقارنة الاثنين مباشرة.
أوجه التشابه:
- كلاهما تم تطويرهما بناءً على محرك
V8 Chromium Engine. - كلاهما رائع لتطوير تطبيقات جانب الخادم باستخدام
JavaScript.
أوجه الاختلاف:
- كُتب
NodeبلغةC++وJavaScript. بينما كُتبDenoبلغةRustوTypeScript. - يمتلك
Nodeمدير حزم رسميًا يسمىnpm. بينما لا يمتلكDenoمدير حزم، وبدلاً من ذلك يتيح لك استيراد أي وحدةES Moduleمباشرة من عناوينURL. - يستخدم
NodeصيغةCommonJSلاستيراد الحزم. بينما يستخدمDenoوحداتES Modules، وهي الطريقة الرسمية. - يستخدم
DenoميزاتECMAScriptالحديثة في جميع واجهات برمجة التطبيقات (API) والمكتبة القياسية الخاصة به، بينما يستخدمNode.jsمكتبة قياسية تعتمد على دوال الاستدعاء (callbacks) وليس لديه خطط لترقيتها. - يقدم
Denoطبقة أمان (sandbox security layer) من خلال الأذونات (permissions). يمكن للبرنامج فقط الوصول إلى الأذونات المحددة للملف التنفيذي كعلامات (flags) من قبل المستخدم. بينما يمكن لبرنامجNode.jsالوصول إلى أي شيء يمكن للمستخدم الوصول إليه. - لطالما تصور
Denoإمكانية تجميع برنامج في ملف تنفيذي يمكن تشغيله بدون تبعيات خارجية، مثلGo، لكنه لم يتحقق بعد. سيكون ذلك تغييرًا جذريًا.
غياب مدير الحزم (Package Manager)
عدم وجود مدير حزم والاضطرار إلى الاعتماد على عناوين URL لاستضافة واستيراد الحزم له إيجابيات وسلبيات. من الإيجابيات التي نفضلها هي المرونة الكبيرة، حيث يمكننا إنشاء حزم دون نشرها في مستودع مثل npm. نعتقد أن نوعًا من مديري الحزم سيظهر في المستقبل، لكن لا يوجد شيء رسمي حتى الآن.
يوفر موقع Deno استضافة للشيفرة (وبالتالي التوزيع عبر عناوين URL) لحزم الطرف الثالث على الرابط: https://deno.land/x/.
تثبيت Deno
كفى حديثًا! لنقم بتثبيت Deno. أسهل طريقة هي استخدام Homebrew (مدير الحزم الشهير لأنظمة macOS و Linux):
brew install deno

بمجرد الانتهاء من ذلك، ستتمكن من الوصول إلى الأمر deno. إليك المساعدة التي يمكنك الحصول عليها باستخدام deno --help:
flavio@mbp~> deno --help
deno 0.42.0
A secure JavaScript and TypeScript runtime
Docs: https://deno.land/std/manual.md
Modules: https://deno.land/std/
https://deno.land/x/
Bugs: https://github.com/denoland/deno/issues
To start the REPL, supply no arguments: deno
To execute a script: deno run https://deno.land/std/examples/welcome.ts
deno https://deno.land/std/examples/welcome.ts
To evaluate code in the shell: deno eval "console.log(30933 + 404)"
Run 'deno help run' for 'run'-specific flags.
USAGE:
deno [OPTIONS] [SUBCOMMAND]
OPTIONS:
-h, --help Prints help information
-L, --log-level Set log level [possible values: debug, info]
-q, --quiet Suppress diagnostic output
By default, subcommands print human-readable diagnostic messages to stderr. If the
flag is set, restrict these messages to errors.
-V, --version Prints version information
SUBCOMMANDS:
bundle Bundle module and dependencies into single file
cache Cache the dependencies
completions Generate shell completions
doc Show documentation for a module
eval Eval script
fmt Format source files
help Prints this message or the help of the given subcommand(s)
info Show info about cache or info related to source file
install Install script as an executable
repl Read Eval Print Loop
run Run a program given a filename or url to the module
test Run tests
types Print runtime TypeScript declarations
upgrade Upgrade deno executable to newest version
ENVIRONMENT VARIABLES:
DENO_DIR Set deno's base directory (defaults to $HOME/.deno)
DENO_INSTALL_ROOT Set deno install's output directory (defaults to $HOME/.deno/bin)
NO_COLOR Set to disable color
HTTP_PROXY Proxy address for HTTP requests (module downloads, fetch)
HTTPS_PROXY Same but for HTTPS
أوامر Deno الأساسية
لاحظ قسم SUBCOMMANDS في المساعدة، والذي يسرد جميع الأوامر التي يمكننا تشغيلها. ما هي الأوامر الفرعية المتاحة؟
bundle: لتجميع الوحدة النمطية والتبعيات في ملف واحد.cache: لتخزين التبعيات مؤقتًا.completions: لتوليد إكمال الأوامر للصدفة (shell).doc: لعرض وثائق وحدة نمطية.eval: لتقييم جزء من الشيفرة، على سبيل المثال:deno eval "console.log(1 + 2)".fmt: أداة تنسيق شيفرة مدمجة (مشابهة لـgofmtفيGo).help: لعرض هذه الرسالة أو مساعدة الأوامر الفرعية المحددة.info: لعرض معلومات حول التخزين المؤقت أو معلومات متعلقة بملف المصدر.install: لتثبيت السكربت كملف تنفيذي.repl: حلقة قراءة-تقييم-طباعة (Read-Eval-Print-Loop) (الوضع الافتراضي).run: لتشغيل برنامج معين بملف أو عنوانURLللوحدة النمطية.test: لتشغيل الاختبارات.types: لطباعة تعريفاتTypeScriptالخاصة ببيئة التشغيل.upgrade: لترقية ملفdenoالتنفيذي إلى أحدث إصدار.
يمكنك تشغيل deno <subcommand> help للحصول على وثائق إضافية محددة للأمر، على سبيل المثال deno run --help. كما تشير المساعدة، يمكننا استخدام هذا الأمر لبدء REPL (حلقة قراءة-تنفيذ-طباعة) باستخدام deno بدون أي خيار آخر.

هذا هو نفس تشغيل deno repl. الطريقة الأكثر شيوعًا التي ستستخدم بها هذا الأمر هي لتنفيذ تطبيق Deno موجود في ملف TypeScript. يمكنك تشغيل ملفات TypeScript (.ts) أو ملفات JavaScript (.js). إذا لم تكن على دراية بـ TypeScript، فلا تقلق: Deno مكتوب بـ TypeScript، ولكن يمكنك كتابة تطبيقاتك بـ JavaScript.
تطبيق Deno الأول لك
لنقم بتشغيل تطبيق Deno لأول مرة. ما أجده مدهشًا حقًا هو أنك لا تحتاج حتى إلى كتابة سطر واحد من الشيفرة – يمكنك تشغيل أمر من أي عنوان URL. يقوم Deno بتنزيل البرنامج وتجميعه ثم تشغيله:

بالطبع، تشغيل شيفرة عشوائية من الإنترنت ليس ممارسة أوصي بها عمومًا. في هذه الحالة، نقوم بتشغيلها من موقع Deno الرسمي، بالإضافة إلى أن Deno يحتوي على نموذج أمان (sandbox) يمنع البرامج من القيام بأي شيء لا ترغب في السماح به. المزيد عن هذا لاحقًا.
هذا البرنامج بسيط للغاية، مجرد استدعاء لـ console.log():
console.log('Welcome to Deno ?')
إذا فتحت عنوان URL https://deno.land/std/examples/welcome.ts باستخدام المتصفح، فسترى هذه الصفحة:

غريب، أليس كذلك؟ ربما تتوقع ملف TypeScript، لكن بدلاً من ذلك لدينا صفحة ويب. السبب هو أن خادم الويب الخاص بموقع Deno يعرف أنك تستخدم متصفحًا ويقدم لك صفحة أكثر سهولة في الاستخدام. قم بتنزيل نفس URL باستخدام wget على سبيل المثال، والذي يطلب إصدار text/plain بدلاً من text/html:

إذا كنت ترغب في تشغيل البرنامج مرة أخرى، فإنه الآن مخزن مؤقتًا بواسطة Deno ولا يحتاج إلى تنزيله مرة أخرى:

يمكنك فرض إعادة تحميل المصدر الأصلي باستخدام العلامة --reload:

يحتوي deno run على الكثير من الخيارات المختلفة التي لم تُدرج في deno --help. بدلاً من ذلك، تحتاج إلى تشغيل deno run --help للكشف عنها:
flavio@mbp~> deno run --help
deno-run
Run a program given a filename or url to the module. By default all programs are run in sandbox without access to disk, network or ability to spawn subprocesses.
deno run https://deno.land/std/examples/welcome.ts
Grant all permissions:
deno run -A https://deno.land/std/http/file_server.ts
Grant permission to read from disk and listen to network:
deno run --allow-read --allow-net https://deno.land/std/http/file_server.ts
Grant permission to read whitelisted files from disk:
deno run --allow-read=/etc https://deno.land/std/http/file_server.ts
USAGE:
deno run [OPTIONS] <SCRIPT_ARG>...
OPTIONS:
-A, --allow-all Allow all permissions
--allow-env Allow environment access
--allow-hrtime Allow high resolution time measurement
--allow-net=<allow-net> Allow network access
--allow-plugin Allow loading plugins
--allow-read=<allow-read> Allow file system read access
--allow-run Allow running subprocesses
--allow-write=<allow-write> Allow file system write access
--cached-only Require that remote dependencies are already cached
--cert <FILE> Load certificate authority from PEM encoded file
-c, --config <FILE> Load tsconfig.json configuration file
-h, --help Prints help information
--importmap <FILE> UNSTABLE: Load import map file
Docs: https://deno.land/std/manual.md#import-maps
Specification: https://wicg.github.io/import-maps/
Examples: https://github.com/WICG/import-maps#the-import-map
--inspect=<HOST:PORT> activate inspector on host:port (default: 127.0.0.1:9229)
--inspect-brk=<HOST:PORT> activate inspector on host:port and break at start of user script
--lock <FILE> Check the specified lock file
--lock-write Write lock file. Use with --lock.
-L, --log-level <log-level> Set log level [possible values: debug, info]
--no-remote Do not resolve remote modules
-q, --quiet Suppress diagnostic output
By default, subcommands print human-readable diagnostic messages to stderr. If the
flag is set, restrict these messages to errors.
-r, --reload=<CACHE_BLACKLIST> Reload source code cache (recompile TypeScript)
--reload Reload everything
--reload=https://deno.land/std Reload only standard modules
--reload=https://deno.land/std/fs/utils.ts,https://deno.land/std/fmt/colors.ts
Reloads specific modules
--seed <NUMBER> Seed Math.random()
--unstable Enable unstable APIs
--v8-flags=<v8-flags> Set V8 command line options. For help: --v8-flags=--help
ARGS:
<SCRIPT_ARG>... script args
أمثلة برمجية إضافية في Deno
بالإضافة إلى المثال الذي قمنا بتشغيله أعلاه، يوفر موقع Deno بعض الأمثلة الأخرى التي يمكنك التحقق منها على الرابط: https://deno.land/std/examples/. وقت كتابة هذا الدليل، يمكننا العثور على:
cat.ts: يطبع محتوى قائمة الملفات المقدمة كوسائط.catj.ts: يطبع محتوى قائمة الملفات المقدمة كوسائط (نسخة مختلفة).chat/: تطبيق لتنفيذ محادثة.colors.ts: مثال على استخدام الألوان في الطرفية.curl.ts: تطبيق بسيط لـcurlيطبع محتوى عنوانURLالمحدد كوسيطة.echo_server.ts: خادمTCP echo.gist.ts: برنامج لنشر الملفات علىgist.github.com.test.ts: مجموعة اختبارات نموذجية.welcome.ts: جملةconsole.logبسيطة (البرنامج الأول الذي قمنا بتشغيله أعلاه).xeval.ts: يتيح لك تشغيل أي شيفرةTypeScriptلأي سطر من الإدخال القياسي المستلم. كان يُعرف سابقًا باسمdeno xevalولكنه أُزيل من الأمر الرسمي.
بناء تطبيق Deno الأول عملياً
لنكتب بعض الشيفرة. تطبيق Deno الأول الذي قمت بتشغيله باستخدام deno run https://deno.land/std/examples/welcome.ts كان تطبيقًا كتبه شخص آخر، لذلك لم ترَ أي شيء يتعلق بكيفية ظهور شيفرة Deno. سنبدأ من مثال التطبيق الافتراضي المدرج على موقع Deno الرسمي:
import { serve } from 'https://deno.land/std/http/server.ts'
const s = serve({ port: 8000 })
console.log('http://localhost:8000/')
for await (const req of s) {
req.respond({ body: 'Hello World\n' })
}
تستورد هذه الشيفرة الدالة serve من الوحدة http/server. لاحظ؟ لا نحتاج إلى تثبيتها أولاً، وهي أيضًا لا تُخزن على جهازك المحلي كما يحدث مع وحدات Node. هذا أحد الأسباب التي جعلت تثبيت Deno سريعًا جدًا.
استيراد من https://deno.land/std/http/server.ts يستورد أحدث إصدار من الوحدة. يمكنك استيراد إصدار محدد باستخدام @VERSION، مثل هذا:
import { serve } from 'https://deno.land/std@v0.42.0/http/server.ts'
تُعرّف الدالة serve في هذا الملف على النحو التالي:
/**
* Create a HTTP server
*
* import { serve } from "https://deno.land/std/http/server.ts";
* const body = "Hello World\n";
* const s = serve({ port: 8000 });
* for await (const req of s) {
* req.respond({ body });
* }
*/
export function serve(addr: string | HTTPOptions): Server {
if (typeof addr === 'string') {
const [hostname, port] = addr.split(':')
addr = { hostname, port: Number(port) }
}
const listener = listen(addr)
return new Server(listener)
}
ننتقل إلى إنشاء خادم عن طريق استدعاء الدالة serve() وتمرير كائن يحتوي على خاصية port. ثم نقوم بتشغيل هذه الحلقة للاستجابة لكل طلب قادم من الخادم.
for await (const req of s) {
req.respond({ body: 'Hello World\n' })
}
لاحظ أننا نستخدم الكلمة المفتاحية await دون الحاجة إلى تضمينها في دالة async لأن Deno يطبق top-level await (الانتظار على المستوى الأعلى).
لنقم بتشغيل هذا البرنامج محليًا. أفترض أنك تستخدم VS Code، ولكن يمكنك استخدام أي محرر تريده. أوصي بتثبيت إضافة Deno من justjavac (كانت هناك إضافة أخرى بنفس الاسم عندما جربت، ولكنها مهملة – قد تختفي في المستقبل).

ستوفر الإضافة العديد من الأدوات والميزات الرائعة لـ VS Code لمساعدتك في كتابة تطبيقاتك. الآن قم بإنشاء ملف app.ts في مجلد والصق الشيفرة أعلاه:

الآن قم بتشغيله باستخدام deno run app.ts:

يقوم Deno بتنزيل جميع التبعيات التي يحتاجها، عن طريق تنزيل الوحدة التي استوردناها أولاً. يحتوي ملف https://deno.land/std/http/server.ts على عدة تبعيات خاصة به:
import { encode } from '../encoding/utf8.ts'
import { BufReader, BufWriter } from '../io/bufio.ts'
import { assert } from '../testing/asserts.ts'
import { deferred, Deferred, MuxAsyncIterator } from '../async/mod.ts'
import {
bodyReader,
chunkedBodyReader,
emptyReader,
writeResponse,
readRequest,
} from './_io.ts'
import Listener = Deno.Listener
import Conn = Deno.Conn
import Reader = Deno.Reader
ويتم استيراد هذه التبعيات تلقائيًا. في النهاية، لدينا مشكلة:

ماذا يحدث؟ لدينا مشكلة رفض الإذن (permission denied). دعنا نتحدث عن نموذج الأمان (sandbox).
نموذج الأمان “Sandbox” في Deno
ذكرت سابقًا أن Deno يحتوي على نموذج أمان يمنع البرامج من القيام بأي شيء لا ترغب في السماح به. ماذا يعني هذا؟
أحد الأشياء التي يذكرها ريان في محاضرته التقديمية لـ Deno هو أنك في بعض الأحيان ترغب في تشغيل برنامج JavaScript خارج متصفح الويب، ومع ذلك لا ترغب في السماح له بالوصول إلى أي شيء يريده على نظامك. أو التواصل مع العالم الخارجي باستخدام الشبكة. لا يوجد ما يمنع تطبيق Node.js من الحصول على مفاتيح SSH الخاصة بك أو أي شيء آخر على نظامك وإرساله إلى خادم. لهذا السبب عادةً ما نقوم بتثبيت حزم Node فقط من مصادر موثوقة. ولكن كيف يمكننا معرفة ما إذا تم اختراق أحد المشاريع التي نستخدمها، وبالتالي اختراق كل شخص آخر؟
يحاول Deno تكرار نفس نموذج الأذونات الذي يطبقه المتصفح. لا يمكن لأي JavaScript يعمل في المتصفح القيام بأشياء مشبوهة على نظامك ما لم تسمح بذلك صراحةً.
بالعودة إلى Deno، إذا أراد برنامج الوصول إلى الشبكة كما في الحالة السابقة، فنحن بحاجة إلى منحه الإذن. يمكننا القيام بذلك عن طريق تمرير علامة (flag) عند تشغيل الأمر، في هذه الحالة --allow-net:
deno run --allow-net app.ts

يعمل التطبيق الآن كخادم HTTP على المنفذ 8000:

تسمح علامات أخرى لـ Deno بفتح وظائف أخرى:
--allow-env: للسماح بالوصول إلى متغيرات البيئة.--allow-hrtime: للسماح بقياس الوقت بدقة عالية.--allow-net=<allow-net>: للسماح بالوصول إلى الشبكة (يمكن تحديد نطاق الوصول).--allow-plugin: للسماح بتحميل المكونات الإضافية.--allow-read=<allow-read>: للسماح بالوصول للقراءة من نظام الملفات (يمكن تحديد مسارات معينة).--allow-run: للسماح بتشغيل العمليات الفرعية (subprocesses).--allow-write=<allow-write>: للسماح بالوصول للكتابة إلى نظام الملفات (يمكن تحديد مسارات معينة).--allow-all: للسماح بجميع الأذونات (نفس-A).
يمكن أن تكون الأذونات لـ net و read و write دقيقة. على سبيل المثال، يمكنك السماح بالقراءة من مجلد معين باستخدام --allow-read=/dev.
تنسيق الشيفرة البرمجية في Deno
أحد الأشياء التي أعجبتني حقًا في Go هو الأمر gofmt الذي يأتي مع مترجم Go. تبدو جميع شيفرات Go متشابهة. الجميع يستخدم gofmt. اعتاد مبرمجو JavaScript على تشغيل Prettier، ويقوم deno fmt في الواقع بتشغيل ذلك في الخلفية.
لنفترض أن لديك ملفًا منسقًا بشكل سيء مثل هذا:

تقوم بتشغيل deno fmt app.ts ويتم تنسيقه تلقائيًا بشكل صحيح، مع إضافة الفواصل المنقوطة (semicolons) حيثما كانت مفقودة:

المكتبة القياسية في Deno
المكتبة القياسية لـ Deno واسعة النطاق على الرغم من أن المشروع لا يزال حديثًا جدًا. تتضمن:
archive: أدوات أرشيفtar.async: أدوات مساعدة غير متزامنة.bytes: أدوات مساعدة لمعالجة شرائح البايت.datetime: تحليل التاريخ/الوقت.encoding: ترميز/فك ترميز لمختلف التنسيقات.flags: تحليل علامات سطر الأوامر.fmt: التنسيق والطباعة.fs: واجهة برمجة تطبيقات نظام الملفات.hash: مكتبة التشفير.http: خادمHTTP.io: مكتبة الإدخال/الإخراج.log: أدوات التسجيل.mime: دعم البيانات متعددة الأجزاء (multipart data).node: طبقة توافق مع المكتبة القياسية لـNode.js(قيد التطوير).path: معالجة المسارات.ws: مقابس الويب (websockets).
مثال آخر على تطبيق Deno: أداة cat
دعنا نرى مثالًا آخر لتطبيق Deno، من أمثلة Deno: cat:
const filenames = Deno.args
for (const filename of filenames) {
const file = await Deno.open(filename)
await Deno.copy(file, Deno.stdout)
file.close()
}
تقوم هذه الشيفرة بتعيين محتوى Deno.args للمتغير filenames، وهو متغير يحتوي على جميع الوسائط المرسلة إلى الأمر. نكرر عبرها، ولكل ملف نستخدم Deno.open() لفتح الملف ونستخدم Deno.copy() لطباعة محتوى الملف إلى Deno.stdout. أخيرًا نغلق الملف.
إذا قمت بتشغيل هذا باستخدام:
deno run https://deno.land/std/examples/cat.ts
يتم تنزيل البرنامج وتجميعه، ولا يحدث شيء لأننا لم نحدد أي وسيطة. جرب الآن:
deno run https://deno.land/std/examples/cat.ts app.ts
بافتراض أن لديك ملف app.ts من المشروع السابق في نفس المجلد. ستحصل على خطأ في الأذونات:

لأن Deno يمنع الوصول إلى نظام الملفات افتراضيًا. امنح الوصول إلى المجلد الحالي باستخدام --allow-read=./:
deno run --allow-read=./ https://deno.land/std/examples/cat.ts app.ts

هل توجد أطر عمل مثل Express/Koa لـ Deno؟
نعم، بالتأكيد. تحقق من مشاريع مثل:
deno-drashdeno-expressoakpogoservest
مثال عملي: بناء واجهة برمجة تطبيقات (REST API) باستخدام إطار عمل Oak
أرغب في تقديم مثال بسيط لكيفية بناء واجهة برمجة تطبيقات REST API باستخدام Oak. يتميز Oak بأنه مستوحى من Koa، وهو وسيط Node.js الشهير، وبسبب هذا فهو مألوف جدًا إذا كنت قد استخدمته من قبل.
واجهة برمجة التطبيقات التي سنبنيها بسيطة جدًا. سيقوم خادمنا بتخزين قائمة من الكلاب بأسماء وأعمار في الذاكرة. نريد:
- إضافة كلاب جديدة.
- عرض قائمة الكلاب.
- الحصول على تفاصيل حول كلب معين.
- إزالة كلب من القائمة.
- تحديث عمر كلب.
سنقوم بذلك في TypeScript، ولكن لا شيء يمنعك من كتابة API في JavaScript – ما عليك سوى إزالة الأنواع (types).
أنشئ ملف app.ts. لنبدأ باستيراد الكائنين Application و Router من Oak:
import { Application, Router } from 'https://deno.land/x/oak/mod.ts'
ثم نحصل على متغيرات البيئة PORT و HOST:
const env = Deno.env.toObject()
const PORT = env.PORT || 4000
const HOST = env.HOST || '127.0.0.1'
افتراضيًا، سيعمل تطبيقنا على localhost:4000. الآن نقوم بإنشاء تطبيق Oak ونبدأ تشغيله:
const router = new Router()
const app = new Application()
app.use(router.routes())
app.use(router.allowedMethods())
console.log(`Listening on port ${PORT} ...`)
await app.listen(`${HOST}:${PORT}`)
الآن يجب أن يتم تجميع التطبيق بشكل جيد. قم بتشغيل deno run --allow-env --allow-net app.ts وسيتم تنزيل التبعيات بواسطة Deno:

ثم يستمع على المنفذ 4000. في المرات التالية التي تشغل فيها الأمر، سيتخطى Deno جزء التثبيت لأن هذه الحزم مخزنة مؤقتًا بالفعل:

في أعلى الملف، دعنا نحدد واجهة (interface) للكلب، ثم نعلن عن مصفوفة dogs أولية من كائنات Dog:
interface Dog {
name: string
age: number
}
let dogs: Array<Dog> = [
{
name: 'Roger',
age: 8,
},
{
name: 'Syd',
age: 7,
},
]
الآن دعنا ننفذ API بالفعل. لدينا كل شيء جاهز. بعد إنشاء الموجه (router)، دعنا نضيف بعض الدوال التي سيتم استدعاؤها في كل مرة يتم فيها الوصول إلى أحد هذه النقاط الطرفية (endpoints):
const router = new Router()
router
.get('/dogs', getDogs)
.get('/dogs/:name', getDog)
.post('/dogs', addDog)
.put('/dogs/:name', updateDog)
.delete('/dogs/:name', removeDog)
لاحظ كيف نحدد:
GET /dogsGET /dogs/:namePOST /dogsPUT /dogs/:nameDELETE /dogs/:name
دعنا ننفذ هذه الدوال واحدة تلو الأخرى. بدءًا من GET /dogs، التي تُرجع قائمة بجميع الكلاب:
export const getDogs = ({ response }: { response: any }) => {
response.body = dogs
}

بعد ذلك، إليك كيفية استرداد كلب واحد بالاسم:
export const getDog = (
{ params, response, }: { params: { name: string }; response: any }
) => {
const dog = dogs.filter((dog) => dog.name === params.name)
if (dog.length) {
response.status = 200
response.body = dog[0]
return
}
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
}

إليك كيفية إضافة كلب جديد:
export const addDog = async ({ request, response, }: { request: any; response: any }) => {
const body = await request.body()
const dog: Dog = body.value
dogs.push(dog)
response.body = { msg: 'OK' }
response.status = 200
}

لاحظ أنني استخدمت الآن const body = await request.body() للحصول على محتوى الجسم (body)، نظرًا لأن قيم name و age تُمرر كـ JSON. إليك كيفية تحديث عمر كلب:
export const updateDog = async (
{ params, request, response, }: { params: { name: string }; request: any; response: any }
) => {
const temp = dogs.filter((existingDog) => existingDog.name === params.name)
const body = await request.body()
const { age }: { age: number } = body.value
if (temp.length) {
temp[0].age = age
response.status = 200
response.body = { msg: 'OK' }
return
}
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
}

وإليك كيفية إزالة كلب من قائمتنا:
export const removeDog = (
{ params, response, }: { params: { name: string }; response: any }
) => {
const lengthBefore = dogs.length
dogs = dogs.filter((dog) => dog.name !== params.name)
if (dogs.length === lengthBefore) {
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
return
}
response.body = { msg: 'OK' }
response.status = 200
}

إليك الشيفرة الكاملة للمثال:
import { Application, Router } from 'https://deno.land/x/oak/mod.ts'
const env = Deno.env.toObject()
const PORT = env.PORT || 4000
const HOST = env.HOST || '127.0.0.1'
interface Dog {
name: string
age: number
}
let dogs: Array<Dog> = [
{
name: 'Roger',
age: 8,
},
{
name: 'Syd',
age: 7,
},
]
export const getDogs = ({ response }: { response: any }) => {
response.body = dogs
}
export const getDog = (
{ params, response, }: { params: { name: string }; response: any }
) => {
const dog = dogs.filter((dog) => dog.name === params.name)
if (dog.length) {
response.status = 200
response.body = dog[0]
return
}
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
}
export const addDog = async ({ request, response, }: { request: any; response: any }) => {
const body = await request.body()
const { name, age }: { name: string; age: number } = body.value
dogs.push({ name: name, age: age, })
response.body = { msg: 'OK' }
response.status = 200
}
export const updateDog = async (
{ params, request, response, }: { params: { name: string }; request: any; response: any }
) => {
const temp = dogs.filter((existingDog) => existingDog.name === params.name)
const body = await request.body()
const { age }: { age: number } = body.value
if (temp.length) {
temp[0].age = age
response.status = 200
response.body = { msg: 'OK' }
return
}
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
}
export const removeDog = (
{ params, response, }: { params: { name: string }; response: any }
) => {
const lengthBefore = dogs.length
dogs = dogs.filter((dog) => dog.name !== params.name)
if (dogs.length === lengthBefore) {
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
return
}
response.body = { msg: 'OK' }
response.status = 200
}
const router = new Router()
router
.get('/dogs', getDogs)
.get('/dogs/:name', getDog)
.post('/dogs', addDog)
.put('/dogs/:name', updateDog)
.delete('/dogs/:name', removeDog)
const app = new Application()
app.use(router.routes())
app.use(router.allowedMethods())
console.log(`Listening on port ${PORT} ...`)
await app.listen(`${HOST}:${PORT}`)
مصادر إضافية لاستكشاف Deno
- الموقع الرسمي لـ
Deno:https://deno.land - وثائق
API:https://doc.deno.landوhttps://deno.land/typedoc/index.html - مجموعة مشاريع
Denoالرائعة (awesome-deno):https://github.com/denolib/awesome-deno
ملاحظات متفرقة حول Deno
- يوفر
Denoتطبيقًا مدمجًا لـfetchيتطابق مع المتوفر في المتصفح. - يحتوي
Denoعلى طبقة توافق مع المكتبة القياسية لـNode.js(stdlib) قيد التطوير.
الخلاصة التقنية
يمثل Deno خطوة جريئة نحو مستقبل تطوير تطبيقات الخادم، حيث يقدم بيئة تشغيل حديثة تستفيد من أحدث ميزات JavaScript و TypeScript. إن تركيزه على الأمان من خلال نموذج الأذونات (sandbox)، ودعم TypeScript الأصيل، ونظام استيراد الوحدات القائم على عناوين URL، يجعله خيارًا جذابًا للمطورين الذين يبحثون عن بديل أكثر حداثة وكفاءة لـ Node.js.
على الرغم من أن Node.js سيظل لاعبًا رئيسيًا في هذا المجال لسنوات قادمة نظرًا لرسوخه ودعمه الواسع، فإن Deno يقدم حلولًا لمشكلات تصميمية أساسية في Node، مما يجعله مثاليًا للمشاريع الجديدة التي تتطلب أداءً عاليًا وأمانًا معززًا وتجربة تطوير مبسطة. إن تبني Deno قد يتطلب بعض التكيف من قبل مطوري Node.js، خاصة فيما يتعلق بإدارة التبعيات والأذونات، ولكنه يوفر في المقابل بيئة عمل أكثر اتساقًا ومرونة، مما يمهد الطريق لجيل جديد من تطبيقات الويب.