وحدات ‎ECMAScript في Node.js

من موسوعة حسوب
مراجعة 11:17، 23 أكتوبر 2018 بواسطة عبد اللطيف ايمش (نقاش | مساهمات) (استبدال النص - '\[\[تصنيف:(.*)\]\]' ب'{{SUBPAGENAME}}')
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)

الاستقرار: 1-قيد التجريب

تحوي Node.js دعمًا لوحدات ES اعتمادًا على Node.js EP من أجل وحدات ES.

ليست جميع مزايا EP كاملةً بعد، وستُحضَر كدعمٍ وتنفيذٍ من أجل VM عندما يكون جاهزًا. لا تزال رسائل الخطأ في طور التحسين والتطوير.

عملية التفعيل

يمكن استعمال الراية ‎--experimental-modules‎ لتفعيل المزايا التي تمكن من تحميل وحدات ESM. متى ما ضُبِط ذلك، يمكن تحميل الملفات التي تنتهي باللاحقة ‎.mjs‎ كوحدات ES.

node --experimental-modules my-app.mjs

المزايا

المزايا المدعومة

يمكن أن يكون الوسيط CLI لنقطة الإدخال الرئيسية للبرنامج نقطةَ إدخالٍ لمخطط ESM فقط. يمكن استعمال الاستيراد الديناميكي (dynamic import) أيضًا لإنشاء نُقط إدخال إلى مخططات ESM في وقت التشغيل (runtime).

import.meta‎

الخاصية import.meta‎ الوصفية (metaproperty) هي كائن Object‎ يحتوي على الخاصية التالية:

  • url‎: ‏<string> العنوان ‎ file:‎ URLللوحدة.

المزايا غير المدعومة

المزيَّة السبب
require('./foo.mjs')‎ إن لوحدات ES دقة وتوقيت مختلف، إذ تستعمل الاستيراد الديناميكي (dynamic import).

أبرز الاختلافات بين «الاستيراد» (import) و «الطلب» (require)

لا يوجد NODE_PATH‎

ليس NODE_PATH‎ جزءًا من استبيان المحددات import‎. استعمل رجاء الوصلات الرمزية إن كان هذا السلوك مرغوبًا ولا بأس به.

لا يوجد require.extensions‎

لا يُستعمَل require.extensions‎ من قبل import‎. يتوقع أن خطافات المحمِّل (loader hooks) يمكن أن توفر تدفق العمل (workflow) هذا في المستقبل.

لا يوجد require.cache‎

لا يُستعمَل require.cache‎ من قِبَل import‎، إذ لديه ذاكرة مخبئية منفصلة.

العنوان URL المعتمد على المسارات

تُستبيَن ESM وتخزَّن اعتمادًا على دلالات العنوان URL. هذا يعني أنَّ الملفات تحتوي على محارف خاصية مثل #‎ و ?‎ وتحتاج إلى تهريب.

ستُحمَّل الوحدات عدة مرات إن كان المحدد import‎ المستعمل لاستبيانها يملك استعلامًا (query) أو قطعةً (fragment) مختلفةً.

import './foo?query=1'; // loads ./foo with query of "?query=1"
import './foo?query=2'; // loads ./foo with query of "?query=2"

في الوقت الحالي، الوحدات التي تستعمل البروتوكول file:‎ يمكنها أن تتحمَّل فقط.

التشغيل المتداخل مع الوحدات الموجودة (Interop with existing modules)

يمكن استعمال الوحدات CommonJS، و JSON، و C++‎ جميعها مع import‎.

الوحدات التي تحمَّل بهذه الطريقة ستُحمَّل مرةً واحدةً فقط حتى إن اختلفت سَلسَلتها النصية التي تمثِّل الاستعلام أو القطعة بين العبارات improt‎.

عند التحميل عبر import‎، ستوفر هذه الوحدات تصديرًا وحيدًا من أجل default‎ يمثل قيمة module.exports‎ في الوقت الذي تُنهِي فيه التقييم.

// foo.js
module.exports = { one: 1 };

// bar.js
import foo from './foo.js';
foo.one === 1; // true

ستوفِّر الوحدات المضمَّنة تصديرات مسماة (named exports) لواجهاتها البرمجية العامة بالإضافة إلى تصديرٍ افتراضيٍّ يمكن استعماله من أجل عدة أشياء أهمها تعديل التصديرات المسماة. تُحدَّث التصديرات المسماة للوحدات المضمَّنة عندما يتم الوصول إلى خاصية التصديرات المقابلة أو إعادة تعريفها أو حذفها.

import EventEmitter from 'events';
const e = new EventEmitter();
import { readFile } from 'fs';
readFile('./foo.txt', (err, source) => {
  if (err) {
    console.error(err);
  } else {
    console.log(source);
  }
});
import fs, { readFileSync } from 'fs';

fs.readFileSync = () => Buffer.from('Hello, ESM');

fs.readFileSync === readFileSync;

خطافات المحمِّل (Loader hooks)

إن أردت تخصيص الدقة الافتراضية للوحدة، فيمكن تمرير خطافات المحمِّل اختياريًّا عبر الوسيط ‎--loader ./loader-name.mjs‎ إلى Node.js.

عندما تُستعمَل الخطافات، فإنّها تُطبَّق على محمِّل الوحدة ES فقط وليس على أي محمِّل لوحدات CommonJS

الخطاف resolve‎

يعيد الخطاف resolve‎ ملف عنوان URL المستبين وصيغة الوحدة لمحدد الوحدة المعطاة وملف عنوان URL الأب:

const baseURL = new URL('file://');
baseURL.pathname = `${process.cwd()}/`;

export async function resolve(specifier,
                              parentModuleURL = baseURL,
                              defaultResolver) {
  return {
    url: new URL(specifier, parentModuleURL).href,
    format: 'esm'
  };
}

يُعطَى parentModuleURL‎ على أنَّه undefined‎ عند إجراء عملية تحميل Node.js الرئيسية نفسها.

تمرَّر دالة دقة الوحدة Node.js ES الافتراضية كوسيط ثالث إلى المستبين من أجل سهولة توافق تدفقات العمل.

بالإضافة إلى إعادة قيمة ملف عنوان URL المستبين، يعيد الخطاف resolve‎ الخاصية format‎ أيضًا التي تحدد صيغة الوحدة للوحدة المستبينة. يمكن أن تأخذ هذه الخاصية إحدى القيم التالية:

format الوصف
'esm' تحميل الوحدة JavaScript القياسية.
'cjs' تحميل الوحدة CommonJS بنمط node-style.
'builtin' تحميل الوحدة CommonJS المضمَّنة في node.
'json' تحميل الملف JSON.
'addon' تحميل الإضافة C++‎.
'dynamic' استعمال الخطاف instantiate‎ الديناميكي.

على سبيل المثال، يتقيد المُحمِّل الزائف (dummy loader) الذي يحمِّل JavaScritp بقواعد دقة المتصفح مع إمكانية كتابة الملف JS الملحق ووحدات Node.js المضمَّنة:

import path from 'path';
import process from 'process';
import Module from 'module';

const builtins = Module.builtinModules;
const JS_EXTENSIONS = new Set(['.js', '.mjs']);

const baseURL = new URL('file://');
baseURL.pathname = `${process.cwd()}/`;

export function resolve(specifier, parentModuleURL = baseURL, defaultResolve) {
  if (builtins.includes(specifier)) {
    return {
      url: specifier,
      format: 'builtin'
    };
  }
  if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) {
    // For node_modules support:
    // return defaultResolve(specifier, parentModuleURL);
    throw new Error(
      `imports must begin with '/', './', or '../'; '${specifier}' does not`);
  }
  const resolved = new URL(specifier, parentModuleURL);
  const ext = path.extname(resolved.pathname);
  if (!JS_EXTENSIONS.has(ext)) {
    throw new Error(
      `Cannot load file with non-JavaScript file extension ${ext}.`);
  }
  return {
    url: resolved.href,
    format: 'esm'
  };
}

مع هذا المحمِّل، يؤدي تشغيل:

NODE_OPTIONS='--experimental-modules --loader ./custom-loader.mjs' node x.js

إلى تحميل الوحدة x.js‎ كوحدة ES مع دعم الدقة النسبية (مع تخطي تحميل node_modules‎ في هذا المثال).

الخطاف dynamicInstantiate الديناميكي

إن أردت إنشاء وحدة ديناميكية مخصَّصة لا تتطابق مع أيٍّ من تفسيرات format‎ الموجودة، فاستعمل الخطاف dynamicInstantiate‎. يُستدعَى هذا الخطاف مع الوحدات التي تعيد format: 'dynamic'‎ من الخطاف resolve‎ فقط.

export async function dynamicInstantiate(url) {
  return {
    exports: ['customExportName'],
    execute: (exports) => {
      // اجلب واضبط الدوال المتوافرة للحجز المسبق لأسماء التصدير
      exports.customExportName.set('value');
    }
  };
}

ستُستدعَى الدالة execute‎ مع قائمة تصديرات الوحدة التي أعطيت في المقدمة حينئذٍ عند نقطة محددة بدقة لترتيب تقييم الوحدة لتلك الوحدة في شجرة الاستيرادات (import tree).

مصادر