Skip to content

الوحدات: واجهة برمجة التطبيقات node:module

تم الإضافة في: v0.3.7

كائن Module

يوفر طرقًا مفيدة عامة عند التفاعل مع مثيلات Module، متغير module الذي يُرى غالبًا في وحدات CommonJS. يتم الوصول إليه عبر import 'node:module' أو require('node:module').

module.builtinModules

[السجل]

الإصدارالتغييرات
v23.5.0تحتوي القائمة الآن أيضًا على وحدات بادئة فقط.
v9.3.0، v8.10.0، v6.13.0تم الإضافة في: v9.3.0، v8.10.0، v6.13.0

قائمة بأسماء جميع الوحدات التي توفرها Node.js. يمكن استخدامها للتحقق مما إذا كانت الوحدة تُدعم من قِبل طرف ثالث أم لا.

module في هذا السياق ليس هو نفس الكائن الذي توفره مغلف الوحدة. للوصول إليه، قم بتضمين وحدة Module:

js
// module.mjs
// في وحدة ECMAScript
import { builtinModules as builtin } from 'node:module'
js
// module.cjs
// في وحدة CommonJS
const builtin = require('node:module').builtinModules

module.createRequire(filename)

تم الإضافة في: v12.2.0

  • filename <string> | <URL> اسم الملف الذي سيتم استخدامه لإنشاء دالة require. يجب أن يكون كائن عنوان URL للملف، أو سلسلة عنوان URL للملف، أو سلسلة مسار مطلق.
  • الإرجاع: <require> دالة Require
js
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)

// sibling-module.js هي وحدة CommonJS.
const siblingModule = require('./sibling-module')

module.findPackageJSON(specifier[, base])

تم الإضافة في: v23.2.0

[مستقر: 1 - تجريبي]

مستقر: 1 الثبات: 1.1 - تطوير نشط

  • specifier <string> | <URL> المُحدد للوحدة التي سيتم استرداد ملف package.json الخاص بها. عند تمرير مُحدد عاري، يتم إرجاع ملف package.json في جذر الحزمة. عند تمرير مُحدد نسبي أو مُحدد مطلق، يتم إرجاع أقرب ملف package.json أبوي.
  • base <string> | <URL> الموقع المطلق (file: سلسلة عنوان URL أو مسار FS) للوحدة المحتوية. بالنسبة إلى CJS، استخدم __filename (ليس __dirname!)؛ بالنسبة إلى ESM، استخدم import.meta.url. لا تحتاج إلى تمريره إذا كان specifier مُحددًا مطلقًا.
  • الإرجاع: <string> | <undefined> مسار إذا تم العثور على ملف package.json. عندما يكون startLocation حزمة، فإن حزمة الجذر package.json; عندما يكون نسبيًا أو غير محلول، فإن أقرب package.json إلى startLocation.
text
/path/to/project
  ├ packages/
    ├ bar/
      ├ bar.js
      └ package.json // name = '@foo/bar'
    └ qux/
      ├ node_modules/
        └ some-package/
          └ package.json // name = 'some-package'
      ├ qux.js
      └ package.json // name = '@foo/qux'
  ├ main.js
  └ package.json // name = '@foo'
js
// /path/to/project/packages/bar/bar.js
import { findPackageJSON } from 'node:module'

findPackageJSON('..', import.meta.url)
// '/path/to/project/package.json'
// نفس النتيجة عند تمرير مُحدد مطلق بدلاً من ذلك:
findPackageJSON(new URL('../', import.meta.url))
findPackageJSON(import.meta.resolve('../'))

findPackageJSON('some-package', import.meta.url)
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
// عند تمرير مُحدد مطلق، قد تحصل على نتيجة مختلفة إذا كانت الوحدة المحلولة داخل مجلد فرعي يحتوي على `package.json` متداخلة.
findPackageJSON(import.meta.resolve('some-package'))
// '/path/to/project/packages/bar/node_modules/some-package/some-subfolder/package.json'

findPackageJSON('@foo/qux', import.meta.url)
// '/path/to/project/packages/qux/package.json'
js
// /path/to/project/packages/bar/bar.js
const { findPackageJSON } = require('node:module')
const { pathToFileURL } = require('node:url')
const path = require('node:path')

findPackageJSON('..', __filename)
// '/path/to/project/package.json'
// نفس النتيجة عند تمرير مُحدد مطلق بدلاً من ذلك:
findPackageJSON(pathToFileURL(path.join(__dirname, '..')))

findPackageJSON('some-package', __filename)
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
// عند تمرير مُحدد مطلق، قد تحصل على نتيجة مختلفة إذا كانت الوحدة المحلولة داخل مجلد فرعي يحتوي على `package.json` متداخلة.
findPackageJSON(pathToFileURL(require.resolve('some-package')))
// '/path/to/project/packages/bar/node_modules/some-package/some-subfolder/package.json'

findPackageJSON('@foo/qux', __filename)
// '/path/to/project/packages/qux/package.json'

module.isBuiltin(moduleName)

مضاف في: v18.6.0، v16.17.0

  • moduleName <string> اسم الوحدة النمطية
  • الإرجاع: <boolean> تُرجع true إذا كانت الوحدة النمطية مضمنة، وإلا تُرجع false
js
import { isBuiltin } from 'node:module'
isBuiltin('node:fs') // true
isBuiltin('fs') // true
isBuiltin('wss') // false

module.register(specifier[, parentURL][, options])

[السجل]

الإصدارالتغييرات
v20.8.0، v18.19.0إضافة دعم لمعاهدات URL WHATWG.
v20.6.0، v18.19.0مضاف في: v20.6.0، v18.19.0

[مستقر: 1 - تجريبي]

مستقر: 1 ثبات: 1.2 - إصدار مرشح

  • specifier <string> | <URL> خطاطيف التخصيص التي سيتم تسجيلها؛ يجب أن يكون هذا هو نفس السلسلة التي سيتم تمريرها إلى import()، باستثناء أنه إذا كان نسبيًا، فسيتم حله بالنسبة إلى parentURL.
  • parentURL <string> | <URL> إذا كنت ترغب في حل specifier بالنسبة لعنوان URL أساسي، مثل import.meta.url، يمكنك تمرير عنوان URL هذا هنا. الافتراضي: 'data:'
  • options <Object>
    • parentURL <string> | <URL> إذا كنت ترغب في حل specifier بالنسبة لعنوان URL أساسي، مثل import.meta.url، يمكنك تمرير عنوان URL هذا هنا. سيتم تجاهل هذه الخاصية إذا تم توفير parentURL كحجة ثانية. الافتراضي: 'data:'
    • data <any> أي قيمة JavaScript قابلة للاستنساخ تعسفية لتمريرها إلى خطاف initialize.
    • transferList <Object[]> كائنات قابلة للتحويل لتمريرها إلى خطاف initialize.

سجّل وحدة نمطية تقوم بتصدير خطاطيف تقوم بتخصيص سلوك حل وتحميل وحدة نمطية Node.js. انظر خطاطيف التخصيص.

module.registerHooks(options)

مضاف في: v23.5.0

[مستقر: 1 - تجريبي]

مستقر: 1 استقرار: 1.1 - تطوير نشط

تسجيل الخطافات التي تخصّص دقة وحدة نمطية Node.js وسلوك التحميل. انظر خطافات التخصيص.

module.stripTypeScriptTypes(code[, options])

مضاف في: v23.2.0

[مستقر: 1 - تجريبي]

مستقر: 1 استقرار: 1.1 - تطوير نشط

  • code <string> التعليمات البرمجية لإزالة تعليقات الأنواع منها.

  • options <Object>

    • mode <string> افتراضي: 'strip'. القيم الممكنة هي:

    • 'strip' قم فقط بإزالة تعليقات الأنواع دون إجراء تحويل لميزات TypeScript.

    • 'transform' قم بإزالة تعليقات الأنواع وتحويل ميزات TypeScript إلى JavaScript.

    • sourceMap <boolean> افتراضي: false. فقط عندما يكون mode هو 'transform', إذا كان true, سيتم إنشاء خريطة مصدر للكود المحول.

    • sourceUrl <string> يحدد عنوان url المصدر المستخدم في خريطة المصدر.

  • الإرجاع: <string> الكود مع تعليقات الأنواع المُزالَة. تقوم module.stripTypeScriptTypes() بإزالة تعليقات الأنواع من كود TypeScript. يمكن استخدامها لإزالة تعليقات الأنواع من كود TypeScript قبل تشغيله باستخدام vm.runInContext() أو vm.compileFunction(). بشكل افتراضي، ستطرح خطأ إذا كان الكود يحتوي على ميزات TypeScript تتطلب تحويلًا مثل Enums, انظر إزالة الأنواع لمزيد من المعلومات. عندما يكون الوضع 'transform', فإنه يحول أيضًا ميزات TypeScript إلى JavaScript، انظر تحويل ميزات TypeScript لمزيد من المعلومات. عندما يكون الوضع 'strip', لا يتم إنشاء خرائط المصدر، لأن المواقع محفوظة. إذا تم توفير sourceMap, عندما يكون الوضع 'strip', سيتم طرح خطأ.

تحذير: لا ينبغي اعتبار مخرجات هذه الدالة مستقرة عبر إصدارات Node.js، نظرًا لتغيرات في مُحلل TypeScript.

js
import { stripTypeScriptTypes } from 'node:module'
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code)
console.log(strippedCode)
// يطبع: const a         = 1;
js
const { stripTypeScriptTypes } = require('node:module')
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code)
console.log(strippedCode)
// يطبع: const a         = 1;

إذا تم توفير sourceUrl, فسيتم إضافته كتعليق في نهاية الإخراج:

js
import { stripTypeScriptTypes } from 'node:module'
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' })
console.log(strippedCode)
// يطبع: const a         = 1\n\n//# sourceURL=source.ts;
js
const { stripTypeScriptTypes } = require('node:module')
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' })
console.log(strippedCode)
// يطبع: const a         = 1\n\n//# sourceURL=source.ts;

عندما يكون mode هو 'transform', يتم تحويل الكود إلى JavaScript:

js
import { stripTypeScriptTypes } from 'node:module'
const code = `
  namespace MathUtil {
    export const add = (a: number, b: number) => a + b;
  }`
const strippedCode = stripTypeScriptTypes(code, { mode: 'transform', sourceMap: true })
console.log(strippedCode)
// يطبع:
// var MathUtil;
// (function(MathUtil) {
//     MathUtil.add = (a, b)=>a + b;
// })(MathUtil || (MathUtil = {}));
// # sourceMappingURL=data:application/json;base64, ...
js
const { stripTypeScriptTypes } = require('node:module')
const code = `
  namespace MathUtil {
    export const add = (a: number, b: number) => a + b;
  }`
const strippedCode = stripTypeScriptTypes(code, { mode: 'transform', sourceMap: true })
console.log(strippedCode)
// يطبع:
// var MathUtil;
// (function(MathUtil) {
//     MathUtil.add = (a, b)=>a + b;
// })(MathUtil || (MathUtil = {}));
// # sourceMappingURL=data:application/json;base64, ...

module.syncBuiltinESMExports()

مُضاف في: v12.12.0

تقوم طريقة module.syncBuiltinESMExports() بتحديث جميع الروابط الحية لـ وحدات ES المُدمجة لتتطابق مع خصائص تصدير CommonJS. لا تقوم بإضافة أو إزالة أسماء مُصدّرة من وحدات ES.

js
const fs = require('node:fs')
const assert = require('node:assert')
const { syncBuiltinESMExports } = require('node:module')

fs.readFile = newAPI

delete fs.readFileSync

function newAPI() {
  // ...
}

fs.newAPI = newAPI

syncBuiltinESMExports()

import('node:fs').then(esmFS => {
  // تقوم بمزامنة خاصية readFile الموجودة مع القيمة الجديدة
  assert.strictEqual(esmFS.readFile, newAPI)
  // تم حذف readFileSync من fs المطلوب
  assert.strictEqual('readFileSync' in fs, false)
  // لا تقوم syncBuiltinESMExports() بإزالة readFileSync من esmFS
  assert.strictEqual('readFileSync' in esmFS, true)
  // لا تقوم syncBuiltinESMExports() بإضافة أسماء
  assert.strictEqual(esmFS.newAPI, undefined)
})

ذاكرة التخزين المؤقت لترجمة الوحدات

[السجل]

الإصدارالتغييرات
v22.8.0إضافة واجهات برمجة تطبيقات JavaScript الأولية للوصول في وقت التشغيل.
v22.1.0مُضاف في: v22.1.0

يمكن تمكين ذاكرة التخزين المؤقت لترجمة الوحدات إما باستخدام طريقة module.enableCompileCache() أو متغير البيئة NODE_COMPILE_CACHE=dir. بعد تمكينه، عندما يقوم Node.js بترجمة وحدة CommonJS أو وحدة ECMAScript، فإنه سيستخدم ذاكرة تخزين مؤقت لرمز V8 المُستمرة على القرص في الدليل المُحدد لتسريع عملية الترجمة. قد يؤدي هذا إلى إبطاء التحميل الأول لشبكة الوحدات، ولكن قد يحصل التحميل اللاحق لنفس شبكة الوحدات على تسريع كبير إذا لم تتغير محتويات الوحدات.

لتنظيف ذاكرة التخزين المؤقت المُولدة على القرص، ما عليك سوى إزالة دليل ذاكرة التخزين المؤقت. سيتم إعادة إنشاء دليل ذاكرة التخزين المؤقت في المرة التالية التي يتم فيها استخدام نفس الدليل لتخزين ذاكرة التخزين المؤقت للترجمة. لتجنب ملء القرص بذاكرة تخزين مؤقت قديمة، يُوصى باستخدام دليل ضمن os.tmpdir(). إذا تم تمكين ذاكرة التخزين المؤقت للترجمة عن طريق الاتصال بـ module.enableCompileCache() دون تحديد الدليل، فسوف يستخدم Node.js متغير البيئة NODE_COMPILE_CACHE=dir إذا تم تعيينه، أو الافتراضي إلى path.join(os.tmpdir(), 'node-compile-cache') خلاف ذلك. لتحديد موقع دليل ذاكرة التخزين المؤقت للترجمة الذي تستخدمه مثيل Node.js قيد التشغيل، استخدم module.getCompileCacheDir().

حاليًا، عند استخدام ذاكرة التخزين المؤقت للترجمة مع تغطية رمز JavaScript لـ V8، قد تكون التغطية التي يتم جمعها بواسطة V8 أقل دقة في الدوال التي يتم إلغاء تسلسلها من ذاكرة التخزين المؤقت للرمز. يُوصى بإيقاف تشغيل هذا عند تشغيل الاختبارات لإنشاء تغطية دقيقة.

يمكن تعطيل ذاكرة التخزين المؤقت لترجمة الوحدات المُمكّنة بواسطة متغير البيئة NODE_DISABLE_COMPILE_CACHE=1. قد يكون هذا مفيدًا عندما تؤدي ذاكرة التخزين المؤقت للترجمة إلى سلوكيات غير متوقعة أو غير مرغوب فيها (مثل تغطية اختبار أقل دقة).

لا يمكن إعادة استخدام ذاكرة التخزين المؤقت للترجمة المُولدة بواسطة إصدار واحد من Node.js بواسطة إصدار مختلف من Node.js. سيتم تخزين ذاكرة التخزين المؤقت المُولدة بواسطة إصدارات مختلفة من Node.js بشكل منفصل إذا تم استخدام نفس الدليل الأساسي لإبقاء ذاكرة التخزين المؤقت، بحيث يمكنها التعايش.

في الوقت الحالي، عندما يتم تمكين ذاكرة التخزين المؤقت للترجمة ويتم تحميل وحدة جديدة، يتم إنشاء ذاكرة التخزين المؤقت للرمز من الرمز المُترجم على الفور، ولكن سيتم كتابتها على القرص فقط عندما يكون مثيل Node.js على وشك الخروج. هذا قابل للتغيير. يمكن استخدام طريقة module.flushCompileCache() للتأكد من أن ذاكرة التخزين المؤقت المُجمعة للرمز يتم تفريغها على القرص في حالة رغبة التطبيق في إنشاء مثيلات أخرى من Node.js والسماح لها بمشاركة ذاكرة التخزين المؤقت قبل وقت طويل من خروج الأصل.

module.constants.compileCacheStatus

مضاف في: v22.8.0

[مستقر: 1 - تجريبي]

مستقر: 1 استقرار: 1.1 - تطوير نشط

يتم إرجاع الثوابت التالية كحقل status في الكائن الذي تُرجعه الدالة module.enableCompileCache() للإشارة إلى نتيجة محاولة تمكين ذاكرة التخزين المؤقتة لترميز الوحدات.

الثابتالوصف
ENABLEDقام Node.js بتمكين ذاكرة التخزين المؤقتة للترميز بنجاح. سيتم إرجاع المسار المُستخدم لتخزين ذاكرة التخزين المؤقتة للترميز في حقل directory في الكائن المُرجع.
ALREADY_ENABLEDتم تمكين ذاكرة التخزين المؤقتة للترميز بالفعل من قبل، إما عن طريق اتصال سابق بالدالة module.enableCompileCache()، أو عن طريق متغير البيئة NODE_COMPILE_CACHE=dir. سيتم إرجاع المسار المُستخدم لتخزين ذاكرة التخزين المؤقتة للترميز في حقل directory في الكائن المُرجع.
FAILEDفشل Node.js في تمكين ذاكرة التخزين المؤقتة للترميز. قد يكون هذا بسبب عدم وجود إذن لاستخدام المسار المحدد، أو أنواع مختلفة من أخطاء نظام الملفات. سيتم إرجاع تفاصيل الفشل في حقل message في الكائن المُرجع.
DISABLEDلا يمكن لـ Node.js تمكين ذاكرة التخزين المؤقتة للترميز نظرًا لتمكين متغير البيئة NODE_DISABLE_COMPILE_CACHE=1.

module.enableCompileCache([cacheDir])

مضاف في: v22.8.0

[مستقر: 1 - تجريبي]

مستقر: 1 استقرار: 1.1 - تطوير نشط

  • cacheDir <string> | <undefined> مسار اختياري لتحديد المسار حيث سيتم تخزين/استرداد ذاكرة التخزين المؤقتة للترميز.
  • القيمة المُرجعه: <Object>
    • status <integer> أحد قيم module.constants.compileCacheStatus
    • message <string> | <undefined> إذا لم يتمكن Node.js من تمكين ذاكرة التخزين المؤقتة للترميز، فهذا يحتوي على رسالة الخطأ. يتم تعيينه فقط إذا كانت قيمة status هي module.constants.compileCacheStatus.FAILED.
    • directory <string> | <undefined> إذا تم تمكين ذاكرة التخزين المؤقتة للترميز، فهذا يحتوي على المسار حيث يتم تخزين ذاكرة التخزين المؤقتة للترميز. يتم تعيينه فقط إذا كانت قيمة status هي module.constants.compileCacheStatus.ENABLED أو module.constants.compileCacheStatus.ALREADY_ENABLED.

تمكين ذاكرة التخزين المؤقتة لترميز الوحدات في مثيل Node.js الحالي.

إذا لم يتم تحديد cacheDir، فسوف يستخدم Node.js إما المسار المحدد بواسطة متغير البيئة NODE_COMPILE_CACHE=dir إذا تم تعيينه، أو يستخدم path.join(os.tmpdir(), 'node-compile-cache') بخلاف ذلك. في حالات الاستخدام العامة، يُوصى بالاتصال بـ module.enableCompileCache() دون تحديد cacheDir، بحيث يمكن تجاوز المسار بواسطة متغير بيئة NODE_COMPILE_CACHE عند الضرورة.

بما أن ذاكرة التخزين المؤقتة للترميز من المفترض أن تكون تحسينًا هادئًا غير مطلوب لوظيفة التطبيق، فقد تم تصميم هذه الطريقة لعدم إرسال أي استثناء عندما لا يمكن تمكين ذاكرة التخزين المؤقتة للترميز. بدلاً من ذلك، سترجع كائنًا يحتوي على رسالة خطأ في حقل message للمساعدة في تصحيح الأخطاء. إذا تم تمكين ذاكرة التخزين المؤقتة للترميز بنجاح، فسوف يحتوي حقل directory في الكائن المُرجع على المسار إلى المسار حيث يتم تخزين ذاكرة التخزين المؤقتة للترميز. ستكون قيمة حقل status في الكائن المُرجع إحدى قيم module.constants.compileCacheStatus للإشارة إلى نتيجة محاولة تمكين ذاكرة التخزين المؤقتة لترميز الوحدات.

لا تؤثر هذه الطريقة إلا على مثيل Node.js الحالي. لتمكينه في مؤشرات العمل الفرعية، اتصل بهذه الطريقة في مؤشرات العمل الفرعية أيضًا، أو قم بتعيين قيمة process.env.NODE_COMPILE_CACHE إلى مسار ذاكرة التخزين المؤقتة للترميز حتى يمكن توريث السلوك في مؤشرات العمل الفرعية. يمكن الحصول على المسار إما من حقل directory الذي تُرجعه هذه الطريقة، أو باستخدام module.getCompileCacheDir().

module.flushCompileCache()

أضيف في: v23.0.0

[مستقر: 1 - تجريبي]

مستقر: 1 استقرار: 1.1 - تطوير نشط

قم بمسح ذاكرة التخزين المؤقت لترجمة الوحدة النمطية /api/module#module-compile-cache المتراكمة من الوحدات النمطية المحملة بالفعل في مثيل Node.js الحالي إلى القرص. يعود هذا الأمر بعد انتهاء جميع عمليات نظام الملفات، بغض النظر عن نجاحها أم لا. في حالة وجود أي أخطاء، فسيفشل هذا الأمر بصمت، نظرًا لأن فقدان ذاكرة التخزين المؤقت للترجمة لا يجب أن يتعارض مع التشغيل الفعلي للتطبيق.

module.getCompileCacheDir()

أضيف في: v22.8.0

[مستقر: 1 - تجريبي]

مستقر: 1 استقرار: 1.1 - تطوير نشط

  • الرجوع: <string> | <undefined> المسار إلى دليل ذاكرة التخزين المؤقت لترجمة الوحدة النمطية /api/module#module-compile-cache إذا كان ممكناً، أو undefined خلاف ذلك.

خطافات التخصيص

[السجل]

الإصدارالتغييرات
v23.5.0إضافة دعم للخطافات المتزامنة وفي الخيط.
v20.6.0, v18.19.0تمت إضافة خطاف initialize ليحل محل globalPreload.
v18.6.0, v16.17.0إضافة دعم لسلاسل المحملات.
v16.12.0تمت إزالة getFormat, getSource, transformSource, و globalPreload؛ تمت إضافة خطاف load وخطاف getGlobalPreload.
v8.8.0أضيف في: v8.8.0

[مستقر: 1 - تجريبي]

مستقر: 1 استقرار: 1.2 - مرشح للإصدار (نسخة غير متزامنة) استقرار: 1.1 - تطوير نشط (نسخة متزامنة)

هناك نوعان من خطافات تخصيص الوحدات النمطية المدعومة حاليًا:

التفعيل

يمكن تخصيص حل الوحدات النمطية وتحميلها بواسطة:

يمكن تسجيل الخطافات قبل تشغيل رمز التطبيق باستخدام علامة --import أو --require:

bash
node --import ./register-hooks.js ./my-app.js
node --require ./register-hooks.js ./my-app.js
js
// register-hooks.js
// لا يمكن طلب هذا الملف إلا إذا لم يحتوِ على انتظار أعلى مستوى.
// استخدم module.register() لتسجيل الخطافات غير المتزامنة في مؤشر ترابط مخصص.
import { register } from 'node:module'
register('./hooks.mjs', import.meta.url)
js
// register-hooks.js
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
// استخدم module.register() لتسجيل الخطافات غير المتزامنة في مؤشر ترابط مخصص.
register('./hooks.mjs', pathToFileURL(__filename))
js
// استخدم module.registerHooks() لتسجيل الخطافات المتزامنة في الخيط الرئيسي.
import { registerHooks } from 'node:module'
registerHooks({
  resolve(specifier, context, nextResolve) {
    /* implementation */
  },
  load(url, context, nextLoad) {
    /* implementation */
  },
})
js
// استخدم module.registerHooks() لتسجيل الخطافات المتزامنة في الخيط الرئيسي.
const { registerHooks } = require('node:module')
registerHooks({
  resolve(specifier, context, nextResolve) {
    /* implementation */
  },
  load(url, context, nextLoad) {
    /* implementation */
  },
})

يمكن أن يكون الملف الممرر إلى --import أو --require أيضًا تصديرًا من تبعية:

bash
node --import some-package/register ./my-app.js
node --require some-package/register ./my-app.js

حيث يحتوي some-package على حقل "exports" يُعرّف تصدير /register ليتم تعيينه إلى ملف يستدعي register()، مثل مثال register-hooks.js التالي.

يضمن استخدام --import أو --require تسجيل الخطافات قبل استيراد أي ملفات تطبيق، بما في ذلك نقطة دخول التطبيق ولأي خيوط عاملة بشكل افتراضي أيضًا.

بدلاً من ذلك، يمكن استدعاء register() و registerHooks() من نقطة الدخول، على الرغم من أنه يجب استخدام import() الديناميكي لأي رمز ESM يجب تشغيله بعد تسجيل الخطافات.

js
import { register } from 'node:module'

register('http-to-https', import.meta.url)

// نظرًا لأن هذا استيراد ديناميكي `import()`، فستعمل خطافات `http-to-https`
// على التعامل مع `./my-app.js` وأي ملفات أخرى تستوردها أو تتطلبها.
await import('./my-app.js')
js
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')

register('http-to-https', pathToFileURL(__filename))

// نظرًا لأن هذا استيراد ديناميكي `import()`، فستعمل خطافات `http-to-https`
// على التعامل مع `./my-app.js` وأي ملفات أخرى تستوردها أو تتطلبها.
import('./my-app.js')

ستعمل خطافات التخصيص لأي وحدات نمطية يتم تحميلها بعد التسجيل والوحدات التي تشير إليها عبر import وrequire المدمج. لا يمكن تخصيص دالة require التي أنشأها المستخدمون باستخدام module.createRequire() إلا بواسطة الخطافات المتزامنة.

في هذا المثال، نقوم بتسجيل خطافات http-to-https، لكنها ستكون متاحة فقط للوحدات النمطية المستوردة لاحقًا — في هذه الحالة، my-app.js وأي شيء تشير إليه عبر import أو require المدمج في تبعيات CommonJS.

لو كان import('./my-app.js') بدلاً من ذلك استيرادًا ثابتًا import './my-app.js'، لكانت الوحدة النمطية قد تم تحميلها بالفعل قبل تسجيل خطافات http-to-https. هذا بسبب مواصفات وحدات ES، حيث يتم تقييم الاستيرادات الثابتة من أوراق الشجرة أولاً، ثم إلى الجذع. يمكن أن تكون هناك استيرادات ثابتة داخل my-app.js، والتي لن يتم تقييمها حتى يتم استيراد my-app.js ديناميكيًا.

إذا تم استخدام خطافات متزامنة، فسيتم دعم كل من import، وrequire، وrequire الخاص بالمستخدم الذي تم إنشاؤه باستخدام createRequire().

js
import { registerHooks, createRequire } from 'node:module'

registerHooks({
  /* implementation of synchronous hooks */
})

const require = createRequire(import.meta.url)

// تؤثر الخطافات المتزامنة على دالة import، و require()، ودالة require الخاصة بالمستخدم
// التي تم إنشاؤها من خلال createRequire().
await import('./my-app.js')
require('./my-app-2.js')
js
const { register, registerHooks } = require('node:module')
const { pathToFileURL } = require('node:url')

registerHooks({
  /* implementation of synchronous hooks */
})

const userRequire = createRequire(__filename)

// تؤثر الخطافات المتزامنة على دالة import، و require()، ودالة require الخاصة بالمستخدم
// التي تم إنشاؤها من خلال createRequire().
import('./my-app.js')
require('./my-app-2.js')
userRequire('./my-app-3.js')

أخيرًا، إذا كان كل ما تريد القيام به هو تسجيل الخطافات قبل تشغيل تطبيقك ولا ترغب في إنشاء ملف منفصل لهذا الغرض، فيمكنك تمرير عنوان URL data: إلى --import:

bash
node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("http-to-https", pathToFileURL("./"));' ./my-app.js

الربط التسلسلي

من الممكن استدعاء register أكثر من مرة:

js
// entrypoint.mjs
import { register } from 'node:module'

register('./foo.mjs', import.meta.url)
register('./bar.mjs', import.meta.url)
await import('./my-app.mjs')
js
// entrypoint.cjs
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')

const parentURL = pathToFileURL(__filename)
register('./foo.mjs', parentURL)
register('./bar.mjs', parentURL)
import('./my-app.mjs')

في هذا المثال، ستشكل خطافات التسجيل المسجلة سلاسل. تعمل هذه السلاسل وفقًا لمبدأ "آخر من دخل أول من خرج" (LIFO). إذا عّرف كل من foo.mjs و bar.mjs خطاف resolve، فسيتم استدعاؤهما على النحو التالي (لاحظ من اليمين إلى اليسار): افتراضي node ← ./foo.mjs./bar.mjs (بدءًا من ./bar.mjs، ثم ./foo.mjs، ثم الافتراضي في Node.js). ينطبق الشيء نفسه على جميع الخطافات الأخرى.

كما تؤثر الخطافات المسجلة على register نفسها. في هذا المثال، سيتم حل bar.mjs وتحميلها عبر الخطافات المسجلة بواسطة foo.mjs (لأن خطافات foo ستكون قد أضيفت بالفعل إلى السلسلة). هذا يسمح بأشياء مثل كتابة الخطافات بلغات غير جافا سكريبت، طالما أن الخطافات المسجلة سابقًا تُترجم إلى جافا سكريبت.

لا يمكن استدعاء طريقة register من داخل الوحدة النمطية التي تُعرّف الخطافات.

يعمل ربط registerHooks بشكل مشابه. إذا تم خلط الخطافات المتزامنة وغير المتزامنة، يتم دائمًا تشغيل الخطافات المتزامنة أولاً قبل بدء تشغيل الخطافات غير المتزامنة، أي في آخر خطاف متزامن يتم تشغيله، تتضمن الخطافات التالية استدعاء الخطافات غير المتزامنة.

js
// entrypoint.mjs
import { registerHooks } from 'node:module'

const hook1 = {
  /* implementation of hooks */
}
const hook2 = {
  /* implementation of hooks */
}
// hook2 run before hook1.
registerHooks(hook1)
registerHooks(hook2)
js
// entrypoint.cjs
const { registerHooks } = require('node:module')

const hook1 = {
  /* implementation of hooks */
}
const hook2 = {
  /* implementation of hooks */
}
// hook2 run before hook1.
registerHooks(hook1)
registerHooks(hook2)

التواصل مع خطافات تخصيص الوحدات

تُشغَّل الخطافات غير المتزامنة على مؤشر ترابط مخصص، منفصل عن المؤشر الترابط الرئيسي الذي يُشغِّل شفرة التطبيق. وهذا يعني أن تغيير المتغيرات العالمية لن يؤثر على مؤشر(ات) الترابط الأخرى، ويجب استخدام قنوات الرسائل للتواصل بين مؤشرات الترابط.

يمكن استخدام طريقة register لإرسال البيانات إلى خطاف initialize. قد تتضمن البيانات المرسلة إلى الخطاف كائنات قابلة للتحويل مثل المنافذ.

js
import { register } from 'node:module'
import { MessageChannel } from 'node:worker_threads'

// يوضح هذا المثال كيفية استخدام قناة الرسائل للتواصل مع الخطافات، من خلال إرسال `port2` إلى الخطافات.
const { port1, port2 } = new MessageChannel()

port1.on('message', msg => {
  console.log(msg)
})
port1.unref()

register('./my-hooks.mjs', {
  parentURL: import.meta.url,
  data: { number: 1, port: port2 },
  transferList: [port2],
})
js
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
const { MessageChannel } = require('node:worker_threads')

// يوضح هذا المثال كيفية استخدام قناة الرسائل للتواصل مع الخطافات، من خلال إرسال `port2` إلى الخطافات.
const { port1, port2 } = new MessageChannel()

port1.on('message', msg => {
  console.log(msg)
})
port1.unref()

register('./my-hooks.mjs', {
  parentURL: pathToFileURL(__filename),
  data: { number: 1, port: port2 },
  transferList: [port2],
})

تُشغَّل خطافات الوحدات المتزامنة على نفس مؤشر الترابط حيث تُشغَّل شفرة التطبيق. ويمكنها تغيير المتغيرات العالمية لسياق الوصول إليه بواسطة المؤشر الترابط الرئيسي مباشرة.

الخطافات

الخطافات غير المتزامنة التي تقبلها module.register()

يمكن استخدام طريقة register لتسجيل وحدة تُصدِّر مجموعة من الخطافات. والخطافات هي دالات يتم استدعاؤها بواسطة Node.js لتخصيص عملية حل الوحدات وتحميلها. يجب أن يكون للدوال المصدرة أسماء وتواقيع محددة، ويجب تصديرها كصادرات مسماة.

js
export async function initialize({ number, port }) {
  // يستقبل البيانات من `register`.
}

export async function resolve(specifier, context, nextResolve) {
  // أخذ مُحدِّد `import` أو `require` وحله إلى عنوان URL.
}

export async function load(url, context, nextLoad) {
  // أخذ عنوان URL محلول وإرجاع شفرة المصدر التي سيتم تقييمها.
}

تُشغَّل الخطافات غير المتزامنة في مؤشر ترابط منفصل، معزولة عن المؤشر الترابط الرئيسي حيث تعمل شفرة التطبيق. وهذا يعني أنها نطاق مختلف realm. قد يُنهي مؤشر الترابط الرئيسي مؤشر ترابط الخطافات في أي وقت، لذا لا تعتمد على العمليات غير المتزامنة (مثل console.log) حتى تكتمل. يتم توريثها في عمال الطفل بشكل افتراضي.

خطافات متزامنة مقبولة بواسطة module.registerHooks()

مضاف في: v23.5.0

[مستقر: 1 - تجريبي]

مستقر: 1 استقرار: 1.1 - تطوير نشط

تقبل طريقة module.registerHooks() دوال خطاف متزامنة. لا يتم دعم initialize() ولا ضرورة لها، حيث يمكن لمنفذ الخطاف ببساطة تشغيل رمز التهيئة مباشرةً قبل الاتصال بـ module.registerHooks().

js
function resolve(specifier, context, nextResolve) {
  // خذ مُحدد `import` أو `require` وقم بحله إلى عنوان URL.
}

function load(url, context, nextLoad) {
  // خذ عنوان URL محلولًا وأعد رمز المصدر المراد تقييمه.
}

يتم تشغيل الخطافات المتزامنة في نفس الخيط ونفس المجال حيث يتم تحميل الوحدات. على عكس الخطافات غير المتزامنة، لا تُورث في خيوط عامل فرعي تابعة افتراضيًا، على الرغم من أنه إذا تم تسجيل الخطافات باستخدام ملف تم تحميله مسبقًا بواسطة --import أو --require، فيمكن لخيوط عامل فرعي تابعة أن ترث البرامج النصية المحملة مسبقًا عبر وراثة process.execArgv. راجع وثائق Worker للحصول على التفاصيل.

في الخطافات المتزامنة، يمكن للمستخدمين توقع اكتمال console.log() بنفس الطريقة التي يتوقعون بها اكتمال console.log() في رمز الوحدة النمطية.

اتفاقيات الخطافات

تُعد الخطافات جزءًا من سلسلة، حتى لو كانت هذه السلسلة تتكون من خطاف مخصص واحد فقط (مقدم من المستخدم) وخطاف افتراضي، وهو موجود دائمًا. تتداخل دوال الخطاف: يجب على كل منها إرجاع كائن عادي دائمًا، ويحدث الترابط نتيجةً لكل دالة تستدعي next\<hookName\>()، وهو مرجع لخطاف المُحمِّل التالي (بترتيب LIFO).

يؤدي الخطاف الذي يُرجع قيمة تفتقر إلى خاصية مطلوبة إلى إثارة استثناء. كما يؤدي الخطاف الذي يُرجع دون استدعاء next\<hookName\>() ودون إرجاع shortCircuit: true إلى إثارة استثناء أيضًا. تهدف هذه الأخطاء إلى المساعدة في منع حالات تعطيل غير مقصودة في السلسلة. أرجع shortCircuit: true من خطاف للإشارة إلى أن السلسلة تنتهي عمدًا عند خطافك.

initialize()

مضاف في: v20.6.0، v18.19.0

[مستقر: 1 - تجريبي]

مستقر: 1 استقرار: 1.2 - إصدار مرشح

  • data <أي> البيانات من register(loader, import.meta.url, { data }).

لا يقبل مُعلق initialize إلا بواسطة register. لا يدعم registerHooks() هذا ولا يحتاج إليه نظرًا لأنه يمكن تشغيل تهيئة المُعلق المزامن مباشرةً قبل الاتصال بـ registerHooks().

يُوفر مُعلق initialize طريقة لتعريف دالة مخصصة تعمل في سلسلة مُعلق عندما يتم تهيئة وحدة المُعلق. يحدث التهيئة عندما يتم تسجيل وحدة المُعلق عبر register.

يمكن لهذا المُعلق استقبال البيانات من استدعاء register، بما في ذلك المنافذ والكائنات القابلة للنقل الأخرى. يمكن أن تكون قيمة الإرجاع لـ initialize <وعد>، وفي هذه الحالة سيتم انتظارها قبل استئناف تنفيذ سلسلة التطبيق الرئيسية.

رمز تخصيص الوحدة:

js
// path-to-my-hooks.js

export async function initialize({ number, port }) {
  port.postMessage(`increment: ${number + 1}`)
}

رمز المُستدعي:

js
import assert from 'node:assert'
import { register } from 'node:module'
import { MessageChannel } from 'node:worker_threads'

// يوضح هذا المثال كيفية استخدام قناة الرسائل للتواصل
// بين سلسلة التطبيق الرئيسية وسلاسل المُعلق التي تعمل على سلسلة المُعلق،
// عن طريق إرسال `port2` إلى مُعلق `initialize`.
const { port1, port2 } = new MessageChannel()

port1.on('message', msg => {
  assert.strictEqual(msg, 'increment: 2')
})
port1.unref()

register('./path-to-my-hooks.js', {
  parentURL: import.meta.url,
  data: { number: 1, port: port2 },
  transferList: [port2],
})
js
const assert = require('node:assert')
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
const { MessageChannel } = require('node:worker_threads')

// يوضح هذا المثال كيفية استخدام قناة الرسائل للتواصل
// بين سلسلة التطبيق الرئيسية وسلاسل المُعلق التي تعمل على سلسلة المُعلق،
// عن طريق إرسال `port2` إلى مُعلق `initialize`.
const { port1, port2 } = new MessageChannel()

port1.on('message', msg => {
  assert.strictEqual(msg, 'increment: 2')
})
port1.unref()

register('./path-to-my-hooks.js', {
  parentURL: pathToFileURL(__filename),
  data: { number: 1, port: port2 },
  transferList: [port2],
})

resolve(specifier, context, nextResolve)

[السجل]

الإصدارالتغييرات
v23.5.0إضافة دعم للخطافات المتزامنة وفي الخيط.
v21.0.0، v20.10.0، v18.19.0تم استبدال الخاصية context.importAssertions بـ context.importAttributes. لا يزال استخدام الاسم القديم مدعومًا وسوف يصدر تحذيرًا تجريبيًا.
v18.6.0، v16.17.0إضافة دعم لسلاسل خطافات resolve. يجب على كل خطاف إما استدعاء nextResolve() أو تضمين خاصية shortCircuit مُعيّنة إلى true في قيمة الإرجاع الخاصة به.
v17.1.0، v16.14.0إضافة دعم لتأكيدات الاستيراد.

[مستقر: 1 - تجريبي]

مستقر: 1 الثبات: 1.2 - إصدار مرشح (نسخة غير متزامنة) الثبات: 1.1 - تطوير نشط (نسخة متزامنة)

  • specifier <سلسلة>

  • context <كائن>

    • conditions <مصفوفة السلاسل> شروط التصدير لملف package.json ذي الصلة
    • importAttributes <كائن> كائن أزواج مفتاح-قيمة تمثل سمات الوحدة النمطية المراد استيرادها
    • parentURL <سلسلة> | <غير مُعرّف> الوحدة النمطية التي تستورد هذه الوحدة، أو غير مُعرّف إذا كانت هذه هي نقطة دخول Node.js
  • nextResolve <دالة> خطاف resolve التالي في السلسلة، أو خطاف resolve الافتراضي لـ Node.js بعد آخر خطاف resolve مُزوّد من قبل المستخدم

  • قيمة مُرجعّة: <كائن> | <وعد> تأخذ النسخة غير المتزامنة إما كائنًا يحتوي على الخصائص التالية، أو وعدًا سيُحل إلى كائن من هذا القبيل. النسخة المتزامنة تقبل فقط كائنًا مُرجعًا بشكل متزامن.

    • format <سلسلة> | <لا شيء> | <غير مُعرّف> تلميح لخطاف التحميل (قد يتم تجاهله) 'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
    • importAttributes <كائن> | <غير مُعرّف> سمات الاستيراد لاستخدامها عند تخزين الوحدة النمطية مؤقتًا (اختياري؛ إذا تم استبعادها، فسيتم استخدام الإدخال)
    • shortCircuit <غير مُعرّف> | <قيمة منطقية> إشارة إلى أن هذا الخطاف يعتزم إنهاء سلسلة خطافات resolve. افتراضيًا: false
    • url <سلسلة> عنوان URL المطلق الذي يُحل هذا الإدخال إليه

سلسلة خطافات resolve مسؤولة عن إخبار Node.js بمكان العثور على بيان أو تعبير import معين، أو استدعاء require، وكيفية تخزينه مؤقتًا. يمكنه أن يُرجع اختياريًا تنسيقًا (مثل 'module') كتلميح لخطاف load. إذا تم تحديد تنسيق، فإن خطاف load هو المسؤول في النهاية عن توفير قيمة format النهائية (ويحق له تجاهل التلميح الذي يوفره resolve)؛ إذا كان resolve يوفر format، فيُطلب خطاف load مخصص حتى لو كان فقط لإرسال القيمة إلى خطاف load الافتراضي لـ Node.js.

سمات نوع الاستيراد جزء من مفتاح التخزين المؤقت لحفظ الوحدات النمطية المحملة في ذاكرة التخزين المؤقت للوحدة الداخلية. خطاف resolve مسؤول عن إرجاع كائن importAttributes إذا كان يجب تخزين الوحدة النمطية مؤقتًا بسمات مختلفة عن تلك الموجودة في التعليمات البرمجية المصدر.

خاصية conditions في context هي مصفوفة من الشروط التي سيتم استخدامها لمطابقة شروط تصدير الحزمة لطلب الحل هذا. يمكن استخدامها للبحث عن تعيينات مشروطة في أماكن أخرى أو لتعديل القائمة عند استدعاء منطق الحل الافتراضي.

شروط تصدير الحزمة الحالية شروط تصدير الحزمة موجودة دائمًا في مصفوفة context.conditions المُمرّرة إلى الخطاف. لضمان سلوك حل مُحدد الوحدة النمطية الافتراضي لـ Node.js عند استدعاء defaultResolve، يجب أن تتضمن مصفوفة context.conditions المُمرّرة إليها جميع عناصر مصفوفة context.conditions المُمرّرة في الأصل إلى خطاف resolve.

js
// النسخة غير المتزامنة التي يقبلها module.register().
export async function resolve(specifier, context, nextResolve) {
  const { parentURL = null } = context

  if (Math.random() > 0.5) {
    // بعض الشروط.
    // بالنسبة لبعض مُحددات الوحدات النمطية أو جميعها، قم ببعض المنطق المخصص للحل.
    // قم دائمًا بإرجاع كائن بالشكل {url: <سلسلة>}.
    return {
      shortCircuit: true,
      url: parentURL ? new URL(specifier, parentURL).href : new URL(specifier).href,
    }
  }

  if (Math.random() < 0.5) {
    // شرط آخر.
    // عند استدعاء `defaultResolve`، يمكن تعديل الوسائط. في هذه
    // الحالة، يتم إضافة قيمة أخرى لمطابقة التصدير المشروط.
    return nextResolve(specifier, {
      ...context,
      conditions: [...context.conditions, 'another-condition'],
    })
  }

  // تأجيل إلى الخطاف التالي في السلسلة، والذي سيكون
  // حل Node.js الافتراضي إذا كان هذا هو مُحمّل المستخدم الأخير.
  return nextResolve(specifier)
}
js
// النسخة المتزامنة التي يقبلها module.registerHooks().
function resolve(specifier, context, nextResolve) {
  // مشابه لـ resolve() غير المتزامن أعلاه، نظرًا لأن هذا الأخير لا يحتوي على
  // أي منطق غير متزامن.
}

load(url, context, nextLoad)

[السجل]

الإصدارالتغييرات
v23.5.0إضافة دعم للإصدار المتزامن وفي الخيط.
v20.6.0إضافة دعم لـ source مع التنسيق commonjs.
v18.6.0, v16.17.0إضافة دعم لسلاسل خطافات التحميل. يجب على كل خطاف إما استدعاء nextLoad() أو تضمين خاصية shortCircuit مُعيّنة إلى true في قيمة الإرجاع الخاصة به.

[مستقر: 1 - تجريبي]

مستقر: 1 الثبات: 1.2 - إصدار مرشح (نسخة غير متزامنة) ثبات: 1.1 - تطوير نشط (نسخة متزامنة)

  • url <string> عنوان URL الذي تم إرجاعه بواسطة سلسلة resolve

  • context <Object>

    • conditions <string[]> شروط التصدير لملف package.json ذي الصلة
    • format <string> | <null> | <undefined> التنسيق الذي تم تزويده اختيارياً بواسطة سلسلة خطافات resolve
    • importAttributes <Object>
  • nextLoad <Function> خطاف load التالي في السلسلة، أو خطاف load الافتراضي لـ Node.js بعد آخر خطاف load مُزوّد من قبل المستخدم

  • القيمة المُرجعة: <Object> | <Promise> تأخذ النسخة غير المتزامنة إما كائنًا يحتوي على الخصائص التالية، أو Promise سيتم حلها إلى كائن من هذا القبيل. تقبل النسخة المتزامنة كائنًا مُرجعًا بشكل متزامن فقط.

يوفر خطاف load طريقة لتعريف طريقة مخصصة لتحديد كيفية تفسير عنوان URL واسترداده وتحليله. كما أنه مسؤول عن التحقق من صحة سمات الاستيراد.

يجب أن تكون القيمة النهائية لـ format واحدة من التالي:

formatالوصفالأنواع المقبولة لـ source المُرجعة بواسطة load
'builtin'تحميل وحدة Node.js مُدمجةغير قابل للتطبيق
'commonjs'تحميل وحدة Node.js CommonJS{ string , ArrayBuffer , TypedArray , null , undefined }
'json'تحميل ملف JSON{ string , ArrayBuffer , TypedArray }
'module'تحميل وحدة ES{ string , ArrayBuffer , TypedArray }
'wasm'تحميل وحدة WebAssembly{ ArrayBuffer , TypedArray }

يتم تجاهل قيمة source لنوع 'builtin' لأنه حاليًا لا يمكن استبدال قيمة وحدة Node.js مُدمجة (أساسية).

تحذير في خطاف load غير المتزامن

عند استخدام خطاف load غير المتزامن، فإنّ حذف source مقابل توفيره لـ 'commonjs' له تأثيرات مختلفة جدًا:

  • عند توفير source، سيتم معالجة جميع مكالمات require من هذا المُعامل بواسطة مُحمّل ESM مع خطافات resolve و load المُسجّلة؛ سيتم معالجة جميع مكالمات require.resolve من هذا المُعامل بواسطة مُحمّل ESM مع خطافات resolve المُسجّلة؛ ستكون مجموعة فرعية فقط من واجهة برمجة تطبيقات CommonJS متاحة (مثلًا، لا يوجد require.extensions، ولا require.cache، ولا require.resolve.paths) ولن يُطبّق التصحيح المؤقت على مُحمّل وحدة CommonJS.
  • إذا كان source غير مُعرّف أو null، فسيتم معالجته بواسطة مُحمّل وحدة CommonJS ولن تمرّ مكالمات require/require.resolve عبر الخطافات المُسجّلة. هذا السلوك لـ source الذي قيمته منعدمة مؤقت — في المستقبل، لن يتم دعم source الذي قيمته منعدمة.

لا تنطبق هذه التحذيرات على خطاف load المتزامن، حيث تكون مجموعة كاملة من واجهات برمجة تطبيقات CommonJS متاحة لوحدات CommonJS المُخصّصة، ويمرّ require/require.resolve دائمًا عبر الخطافات المُسجّلة.

يُعيد التنفيذ الداخلي لـ Node.js لـ load غير المتزامن، وهو قيمة next للخطاف الأخير في سلسلة load، قيمة null لـ source عندما يكون format هو 'commonjs' من أجل التوافق مع الإصدارات السابقة. فيما يلي مثال لخطاف سيختار استخدام السلوك غير الافتراضي:

js
import { readFile } from 'node:fs/promises'

// الإصدار غير المتزامن الذي تقبله module.register(). هذا الإصلاح ليس ضروريًا
// للإصدار المتزامن الذي تقبله module.registerSync().
export async function load(url, context, nextLoad) {
  const result = await nextLoad(url, context)
  if (result.format === 'commonjs') {
    result.source ??= await readFile(new URL(result.responseURL ?? url))
  }
  return result
}

هذا لا ينطبق على خطاف load المتزامن أيضًا، حيث تحتوي قيمة source المُرتجعة على شفرة المصدر المُحمّلة بواسطة الخطاف التالي، بغض النظر عن تنسيق الوحدة.

  • كائن ArrayBuffer المحدد هو كائن SharedArrayBuffer.
  • كائن TypedArray المحدد هو كائن Uint8Array.

إذا لم تكن قيمة source لتنسيق نصي (أي، 'json', 'module') سلسلة، فسيتم تحويلها إلى سلسلة باستخدام util.TextDecoder.

يُوفر خطاف load طريقة لتعريف طريقة مخصصة لاسترجاع شفرة المصدر لعنوان URL المُحلّل. هذا سيسمح للمُحمّل بتجنب قراءة الملفات من القرص. ويمكن استخدامه أيضًا لربط تنسيق غير معروف بتنسيق مدعوم، مثلًا yaml إلى module.

js
// الإصدار غير المتزامن الذي تقبله module.register().
export async function load(url, context, nextLoad) {
  const { format } = context

  if (Math.random() > 0.5) {
    // شرط ما
    /*
      بالنسبة لبعض عناوين URL أو جميعها، قم ببعض المنطق المخصص لاسترجاع المصدر.
      أرجع دائمًا كائنًا بالشكل التالي: {
        format: <سلسلة>,
        source: <سلسلة|عازلة>,
      }.
    */
    return {
      format,
      shortCircuit: true,
      source: '...',
    }
  }

  // تأجيل إلى الخطاف التالي في السلسلة.
  return nextLoad(url)
}
js
// الإصدار المتزامن الذي تقبله module.registerHooks().
function load(url, context, nextLoad) {
  // مشابه لـ load() غير المتزامن أعلاه، نظرًا لأنّه لا يحتوي على
  // أي منطق غير متزامن.
}

في سيناريو أكثر تقدمًا، يمكن استخدام هذا أيضًا لتحويل مصدر غير مدعوم إلى مصدر مدعوم (انظر أمثلة أدناه).

أمثلة

يمكن استخدام خطافات تخصيص الوحدات المختلفة معًا لإنجاز عمليات تخصيص واسعة النطاق لسلوكيات تحميل وتقييم التعليمات البرمجية في Node.js.

الاستيراد من HTTPS

يسجل الخطاف أدناه خطافات لتمكين دعم بدائي لمثل هذه المواصفات. على الرغم من أن هذا قد يبدو بمثابة تحسين كبير لوظائف Node.js الأساسية، إلا أن هناك عيوبًا كبيرة لاستخدام هذه الخطافات بالفعل: الأداء أبطأ بكثير من تحميل الملفات من القرص، ولا يوجد ذاكرة تخزين مؤقت، ولا يوجد أمان.

js
// https-hooks.mjs
import { get } from 'node:https'

export function load(url, context, nextLoad) {
  // لكي يتم تحميل جافا سكريبت عبر الشبكة، نحتاج إلى جلبها وإعادتها.
  if (url.startsWith('https://')) {
    return new Promise((resolve, reject) => {
      get(url, res => {
        let data = ''
        res.setEncoding('utf8')
        res.on('data', chunk => (data += chunk))
        res.on('end', () =>
          resolve({
            // يفترض هذا المثال أن جميع جافا سكريبت التي يتم توفيرها عبر الشبكة هي عبارة عن تعليمات برمجية وحدة ES.
            format: 'module',
            shortCircuit: true,
            source: data,
          })
        )
      }).on('error', err => reject(err))
    })
  }

  // دع Node.js يتعامل مع جميع عناوين URL الأخرى.
  return nextLoad(url)
}
js
// main.mjs
import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js'

console.log(VERSION)

باستخدام وحدة الخطافات السابقة، فإن تشغيل node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./https-hooks.mjs"));' ./main.mjs يُطبع الإصدار الحالي من CoffeeScript لكل وحدة في عنوان URL في main.mjs.

الترجمة

يمكن تحويل المصادر التي تكون بصيغ لا يفهمها Node.js إلى JavaScript باستخدام خطاف load.

هذا أقل أداءً من ترجمة ملفات المصدر قبل تشغيل Node.js؛ يجب استخدام خطافات المُترجم فقط لأغراض التطوير والاختبار.

النسخة غير المتزامنة
js
// coffeescript-hooks.mjs
import { readFile } from 'node:fs/promises'
import { dirname, extname, resolve as resolvePath } from 'node:path'
import { cwd } from 'node:process'
import { fileURLToPath, pathToFileURL } from 'node:url'
import coffeescript from 'coffeescript'

const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/

export async function load(url, context, nextLoad) {
  if (extensionsRegex.test(url)) {
    // يمكن أن تكون ملفات CoffeeScript إما وحدات CommonJS أو وحدات ES، لذلك نريد أن تعالج Node.js أي ملف CoffeeScript بنفس طريقة ملف .js في نفس الموقع. لتحديد كيفية تفسير Node.js لملف .js تعسفي، ابحث في نظام الملفات عن أقرب ملف package.json الأبوي واقرأ حقل "type" الخاص به.
    const format = await getPackageType(url)

    const { source: rawSource } = await nextLoad(url, { ...context, format })
    // يقوم هذا الخطاف بتحويل شفرة المصدر CoffeeScript إلى شفرة مصدر JavaScript لجميع ملفات CoffeeScript المستوردة.
    const transformedSource = coffeescript.compile(rawSource.toString(), url)

    return {
      format,
      shortCircuit: true,
      source: transformedSource,
    }
  }

  // دع Node.js تعالج جميع عناوين URL الأخرى.
  return nextLoad(url)
}

async function getPackageType(url) {
  // `url` هو مجرد مسار ملف أثناء التكرار الأول عند تمرير عنوان url المحلّل من خطاف load ()
  // مسار ملف فعلي من load () سيحتوي على امتداد ملف لأنه مطلوب من قبل المواصفات
  // سيعمل هذا الاختبار البسيط للحقيقة على ما إذا كان `url` يحتوي على امتداد ملف لمعظم المشاريع ولكنه لا يغطي بعض الحالات الحدية (مثل الملفات بدون امتداد أو عنوان url ينتهي بمسافة زائدة)
  const isFilePath = !!extname(url)
  // إذا كان مسار ملف، فاحصل على الدليل الذي يوجد فيه
  const dir = isFilePath ? dirname(fileURLToPath(url)) : url
  // قم بتكوين مسار ملف إلى package.json في نفس الدليل، والذي قد يكون موجودًا أو لا يكون
  const packagePath = resolvePath(dir, 'package.json')
  // حاول قراءة package.json غير الموجود ربما
  const type = await readFile(packagePath, { encoding: 'utf8' })
    .then(filestring => JSON.parse(filestring).type)
    .catch(err => {
      if (err?.code !== 'ENOENT') console.error(err)
    })
  // إذا كان package.json موجودًا واحتوى على حقل `type` بقيمة، فـ voilà
  if (type) return type
  // خلاف ذلك، (إذا لم يكن في الجذر) استمر في التحقق من الدليل التالي لأعلى
  // إذا كان في الجذر، توقف وأعد خطأ
  return dir.length > 1 && getPackageType(resolvePath(dir, '..'))
}
النسخة المتزامنة
javascript
// coffeescript-sync-hooks.mjs
import { readFileSync } from 'node:fs/promises'
import { registerHooks } from 'node:module'
import { dirname, extname, resolve as resolvePath } from 'node:path'
import { cwd } from 'node:process'
import { fileURLToPath, pathToFileURL } from 'node:url'
import coffeescript from 'coffeescript'

const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/

function load(url, context, nextLoad) {
  if (extensionsRegex.test(url)) {
    const format = getPackageType(url)

    const { source: rawSource } = nextLoad(url, { ...context, format })
    const transformedSource = coffeescript.compile(rawSource.toString(), url)

    return {
      format,
      shortCircuit: true,
      source: transformedSource,
    }
  }

  return nextLoad(url)
}

function getPackageType(url) {
  const isFilePath = !!extname(url)
  const dir = isFilePath ? dirname(fileURLToPath(url)) : url
  const packagePath = resolvePath(dir, 'package.json')

  let type
  try {
    const filestring = readFileSync(packagePath, { encoding: 'utf8' })
    type = JSON.parse(filestring).type
  } catch (err) {
    if (err?.code !== 'ENOENT') console.error(err)
  }
  if (type) return type
  return dir.length > 1 && getPackageType(resolvePath(dir, '..'))
}

registerHooks({ load })

تشغيل الخطافات

coffeescript
# main.coffee {#maincoffee}
import { scream } from './scream.coffee'
console.log scream 'hello, world'

import { version } from 'node:process'
console.log "Brought to you by Node.js version #{version}"
coffeescript
# scream.coffee {#screamcoffee}
export scream = (str) -> str.toUpperCase()

مع وحدات خطافات ما سبق، فإن تشغيل node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee أو node --import ./coffeescript-sync-hooks.mjs ./main.coffee يتسبب في تحويل main.coffee إلى جافا سكريبت بعد تحميل رمزها المصدر من القرص ولكن قبل أن يقوم Node.js بتنفيذه؛ وهكذا بالنسبة لأي ملفات .coffee أو .litcoffee أو .coffee.md يتم الرجوع إليها عبر عبارات import لأي ملف تم تحميله.

خرائط الاستيراد

حدد المثالين السابقين خطافات load. هذا مثال على خطاف resolve. يقوم هذا الخطاف بقراءة ملف import-map.json الذي يحدد مواصفات لتجاوزها إلى عناوين URL أخرى (هذا تنفيذ مبسط جدًا لمجموعة فرعية صغيرة من مواصفات "خرائط الاستيراد").

النسخة غير المتزامنة
js
// import-map-hooks.js
import fs from 'node:fs/promises'

const { imports } = JSON.parse(await fs.readFile('import-map.json'))

export async function resolve(specifier, context, nextResolve) {
  if (Object.hasOwn(imports, specifier)) {
    return nextResolve(imports[specifier], context)
  }

  return nextResolve(specifier, context)
}
النسخة المتزامنة
js
// import-map-sync-hooks.js
import fs from 'node:fs/promises'
import module from 'node:module'

const { imports } = JSON.parse(fs.readFileSync('import-map.json', 'utf-8'))

function resolve(specifier, context, nextResolve) {
  if (Object.hasOwn(imports, specifier)) {
    return nextResolve(imports[specifier], context)
  }

  return nextResolve(specifier, context)
}

module.registerHooks({ resolve })
استخدام الخطافات

مع هذه الملفات:

js
// main.js
import 'a-module'
json
// import-map.json
{
  "imports": {
    "a-module": "./some-module.js"
  }
}
js
// some-module.js
console.log('some module!')

يجب أن يُطبع تشغيل node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./import-map-hooks.js"));' main.js أو node --import ./import-map-sync-hooks.js main.js some module!.

دعم خريطة المصدر الإصدار 3

مضاف في: v13.7.0، v12.17.0

[مستقر: 1 - تجريبي]

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

مساعدات للتفاعل مع ذاكرة التخزين المؤقتة لخريطة المصدر. يتم ملء ذاكرة التخزين المؤقتة هذه عند تمكين تحليل خريطة المصدر ويتم العثور على تعليمات تضمين خريطة المصدر في أسفل الوحدات.

لتمكين تحليل خريطة المصدر، يجب تشغيل Node.js باستخدام العلم --enable-source-maps، أو مع تمكين تغطية المصدر عن طريق تعيين NODE_V8_COVERAGE=dir.

js
// module.mjs
// في وحدة ECMAScript
import { findSourceMap, SourceMap } from 'node:module'
js
// module.cjs
// في وحدة CommonJS
const { findSourceMap, SourceMap } = require('node:module')

module.findSourceMap(path)

مضاف في: v13.7.0، v12.17.0

path هو المسار المُحلل للملف الذي يجب جلب خريطة المصدر المُناظرة له.

Class: module.SourceMap

مضاف في: v13.7.0، v12.17.0

new SourceMap(payload[, { lineLengths }]) {#new-sourcemappayload-{-linelengths-}}

يُنشئ مثيلًا جديدًا لـsourceMap.

payload هو كائن بمفاتيح تُطابق تنسيق خريطة المصدر v3:

lineLengths هي مصفوفة اختيارية لطول كل سطر في الكود المُولّد.

sourceMap.payload

  • القيمة المُرجعَة: <Object>

معرّف الوصول (Getter) للدفع المُستخدم لبناء مثيل SourceMap.

sourceMap.findEntry(lineOffset, columnOffset)

  • lineOffset <number> إزاحة رقم السطر ذو الفهرس الصفري في المصدر المُولّد
  • columnOffset <number> إزاحة رقم العمود ذو الفهرس الصفري في المصدر المُولّد
  • قيمة مُرجعَة: <Object>

بإعطاء إزاحة رقم سطر وإزاحة رقم عمود في ملف المصدر المُولّد، تُرجِع دالة sourceMap.findEntry كائنًا يمثل نطاق SourceMap في الملف الأصلي إذا وُجِد، أو كائنًا فارغًا إذا لم يُوجَد.

يحتوي الكائن المُرجع على المفاتيح التالية:

  • generatedLine: <number> إزاحة سطر بداية النطاق في المصدر المُولّد
  • generatedColumn: <number> إزاحة عمود بداية النطاق في المصدر المُولّد
  • originalSource: <string> اسم ملف المصدر الأصلي، كما هو مُبلّغ عنه في SourceMap
  • originalLine: <number> إزاحة سطر بداية النطاق في المصدر الأصلي
  • originalColumn: <number> إزاحة عمود بداية النطاق في المصدر الأصلي
  • name: <string>

تمثّل القيمة المُرجعَة النطاق الخام كما يظهر في SourceMap، بناءً على الإزاحات ذات الفهرس الصفري، ليس أرقام الأسطر والأعمدة ذات الفهرس 1 كما تظهر في رسائل الخطأ وكائنات CallSite.

للحصول على أرقام الأسطر والأعمدة ذات الفهرس 1 من رقم سطر ورقم عمود كما هو مُبلّغ عنه بواسطة تراكمات الأخطاء وكائنات CallSite، استخدم sourceMap.findOrigin(lineNumber, columnNumber)

sourceMap.findOrigin(lineNumber, columnNumber)

  • lineNumber <number> رقم السطر ذو الفهرس 1 لموقع الاتصال في المصدر المُولّد
  • columnNumber <number> رقم العمود ذو الفهرس 1 لموقع الاتصال في المصدر المُولّد
  • القيمة المُرجعة: <Object>

بافتراض رقم سطر lineNumber ورقم عمود columnNumber ذوي الفهرس 1 من موقع اتصال في المصدر المُولّد، ابحث عن موقع اتصال مُقابِل في المصدر الأصلي.

إذا لم يتم العثور على رقم السطر lineNumber ورقم العمود columnNumber المُعطى في أيّ خريطة مصدر، فسيتم إرجاع كائن فارغ. خلاف ذلك، يحتوي الكائن المُرجَع على المفاتيح التالية:

  • name: <string> | <undefined> اسم النطاق في خريطة المصدر، إذا تم توفيره
  • fileName: <string> اسم ملف المصدر الأصلي، كما هو مُبلّغ عنه في SourceMap
  • lineNumber: <number> رقم السطر ذو الفهرس 1 لموقع الاتصال المُقابِل في المصدر الأصلي
  • columnNumber: <number> رقم العمود ذو الفهرس 1 لموقع الاتصال المُقابِل في المصدر الأصلي