الوحدات: واجهة برمجة التطبيقات 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
:
// module.mjs
// في وحدة ECMAScript
import { builtinModules as builtin } from 'node:module'
// module.cjs
// في وحدة CommonJS
const builtin = require('node:module').builtinModules
module.createRequire(filename)
تم الإضافة في: v12.2.0
filename
<string> | <URL> اسم الملف الذي سيتم استخدامه لإنشاء دالة require. يجب أن يكون كائن عنوان URL للملف، أو سلسلة عنوان URL للملف، أو سلسلة مسار مطلق.- الإرجاع: <require> دالة Require
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
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
.
/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'
// /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'
// /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
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 |
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 - تطوير نشط
options
<Object>load
<Function> | <undefined> انظر خطاف التحميل. افتراضي:undefined
.resolve
<Function> | <undefined> انظر خطاف الحل. افتراضي:undefined
.
تسجيل الخطافات التي تخصّص دقة وحدة نمطية 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.
import { stripTypeScriptTypes } from 'node:module'
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code)
console.log(strippedCode)
// يطبع: const a = 1;
const { stripTypeScriptTypes } = require('node:module')
const code = 'const a: number = 1;'
const strippedCode = stripTypeScriptTypes(code)
console.log(strippedCode)
// يطبع: const a = 1;
إذا تم توفير sourceUrl
, فسيتم إضافته كتعليق في نهاية الإخراج:
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;
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:
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, ...
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.
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
:
node --import ./register-hooks.js ./my-app.js
node --require ./register-hooks.js ./my-app.js
// register-hooks.js
// لا يمكن طلب هذا الملف إلا إذا لم يحتوِ على انتظار أعلى مستوى.
// استخدم module.register() لتسجيل الخطافات غير المتزامنة في مؤشر ترابط مخصص.
import { register } from 'node:module'
register('./hooks.mjs', import.meta.url)
// register-hooks.js
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
// استخدم module.register() لتسجيل الخطافات غير المتزامنة في مؤشر ترابط مخصص.
register('./hooks.mjs', pathToFileURL(__filename))
// استخدم module.registerHooks() لتسجيل الخطافات المتزامنة في الخيط الرئيسي.
import { registerHooks } from 'node:module'
registerHooks({
resolve(specifier, context, nextResolve) {
/* implementation */
},
load(url, context, nextLoad) {
/* implementation */
},
})
// استخدم module.registerHooks() لتسجيل الخطافات المتزامنة في الخيط الرئيسي.
const { registerHooks } = require('node:module')
registerHooks({
resolve(specifier, context, nextResolve) {
/* implementation */
},
load(url, context, nextLoad) {
/* implementation */
},
})
يمكن أن يكون الملف الممرر إلى --import
أو --require
أيضًا تصديرًا من تبعية:
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 يجب تشغيله بعد تسجيل الخطافات.
import { register } from 'node:module'
register('http-to-https', import.meta.url)
// نظرًا لأن هذا استيراد ديناميكي `import()`، فستعمل خطافات `http-to-https`
// على التعامل مع `./my-app.js` وأي ملفات أخرى تستوردها أو تتطلبها.
await import('./my-app.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()
.
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')
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
:
node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("http-to-https", pathToFileURL("./"));' ./my-app.js
الربط التسلسلي
من الممكن استدعاء register
أكثر من مرة:
// entrypoint.mjs
import { register } from 'node:module'
register('./foo.mjs', import.meta.url)
register('./bar.mjs', import.meta.url)
await import('./my-app.mjs')
// 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
بشكل مشابه. إذا تم خلط الخطافات المتزامنة وغير المتزامنة، يتم دائمًا تشغيل الخطافات المتزامنة أولاً قبل بدء تشغيل الخطافات غير المتزامنة، أي في آخر خطاف متزامن يتم تشغيله، تتضمن الخطافات التالية استدعاء الخطافات غير المتزامنة.
// 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)
// 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
. قد تتضمن البيانات المرسلة إلى الخطاف كائنات قابلة للتحويل مثل المنافذ.
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],
})
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 لتخصيص عملية حل الوحدات وتحميلها. يجب أن يكون للدوال المصدرة أسماء وتواقيع محددة، ويجب تصديرها كصادرات مسماة.
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()
.
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
<وعد>، وفي هذه الحالة سيتم انتظارها قبل استئناف تنفيذ سلسلة التطبيق الرئيسية.
رمز تخصيص الوحدة:
// path-to-my-hooks.js
export async function initialize({ number, port }) {
port.postMessage(`increment: ${number + 1}`)
}
رمز المُستدعي:
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],
})
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
.
// النسخة غير المتزامنة التي يقبلها 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)
}
// النسخة المتزامنة التي يقبلها 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
سيتم حلها إلى كائن من هذا القبيل. تقبل النسخة المتزامنة كائنًا مُرجعًا بشكل متزامن فقط.format
<string>shortCircuit
<undefined> | <boolean> إشارة إلى أن هذا الخطاف يعتزم إنهاء سلسلة خطافاتload
. الافتراضي:false
source
<string> | <ArrayBuffer> | <TypedArray> المصدر لـ Node.js لتقييمه
يوفر خطاف 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'
من أجل التوافق مع الإصدارات السابقة. فيما يلي مثال لخطاف سيختار استخدام السلوك غير الافتراضي:
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
.
// الإصدار غير المتزامن الذي تقبله 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)
}
// الإصدار المتزامن الذي تقبله module.registerHooks().
function load(url, context, nextLoad) {
// مشابه لـ load() غير المتزامن أعلاه، نظرًا لأنّه لا يحتوي على
// أي منطق غير متزامن.
}
في سيناريو أكثر تقدمًا، يمكن استخدام هذا أيضًا لتحويل مصدر غير مدعوم إلى مصدر مدعوم (انظر أمثلة أدناه).
أمثلة
يمكن استخدام خطافات تخصيص الوحدات المختلفة معًا لإنجاز عمليات تخصيص واسعة النطاق لسلوكيات تحميل وتقييم التعليمات البرمجية في Node.js.
الاستيراد من HTTPS
يسجل الخطاف أدناه خطافات لتمكين دعم بدائي لمثل هذه المواصفات. على الرغم من أن هذا قد يبدو بمثابة تحسين كبير لوظائف Node.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)
}
// 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؛ يجب استخدام خطافات المُترجم فقط لأغراض التطوير والاختبار.
النسخة غير المتزامنة
// 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, '..'))
}
النسخة المتزامنة
// 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 })
تشغيل الخطافات
# 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}"
# 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 أخرى (هذا تنفيذ مبسط جدًا لمجموعة فرعية صغيرة من مواصفات "خرائط الاستيراد").
النسخة غير المتزامنة
// 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)
}
النسخة المتزامنة
// 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 })
استخدام الخطافات
مع هذه الملفات:
// main.js
import 'a-module'
// import-map.json
{
"imports": {
"a-module": "./some-module.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
مساعدات للتفاعل مع ذاكرة التخزين المؤقتة لخريطة المصدر. يتم ملء ذاكرة التخزين المؤقتة هذه عند تمكين تحليل خريطة المصدر ويتم العثور على تعليمات تضمين خريطة المصدر في أسفل الوحدات.
لتمكين تحليل خريطة المصدر، يجب تشغيل Node.js باستخدام العلم --enable-source-maps
، أو مع تمكين تغطية المصدر عن طريق تعيين NODE_V8_COVERAGE=dir
.
// module.mjs
// في وحدة ECMAScript
import { findSourceMap, SourceMap } from 'node:module'
// module.cjs
// في وحدة CommonJS
const { findSourceMap, SourceMap } = require('node:module')
module.findSourceMap(path)
مضاف في: v13.7.0، v12.17.0
path
<string>- القيمة المُرجعَة: <module.SourceMap> | <undefined> يرجع
module.SourceMap
إذا وُجدت خريطة مصدر،undefined
خلاف ذلك.
path
هو المسار المُحلل للملف الذي يجب جلب خريطة المصدر المُناظرة له.
Class: module.SourceMap
مضاف في: v13.7.0، v12.17.0
new SourceMap(payload[, { lineLengths }])
{#new-sourcemappayload-{-linelengths-}}
payload
<Object>lineLengths
<number[]>
يُنشئ مثيلًا جديدًا لـsourceMap
.
payload
هو كائن بمفاتيح تُطابق تنسيق خريطة المصدر v3:
file
: <string>version
: <number>sources
: <string[]>sourcesContent
: <string[]>names
: <string[]>mappings
: <string>sourceRoot
: <string>
lineLengths
هي مصفوفة اختيارية لطول كل سطر في الكود المُولّد.
sourceMap.payload
- القيمة المُرجعَة: <Object>
معرّف الوصول (Getter) للدفع المُستخدم لبناء مثيل SourceMap
.
sourceMap.findEntry(lineOffset, columnOffset)
lineOffset
<number> إزاحة رقم السطر ذو الفهرس الصفري في المصدر المُولّدcolumnOffset
<number> إزاحة رقم العمود ذو الفهرس الصفري في المصدر المُولّد- قيمة مُرجعَة: <Object>
بإعطاء إزاحة رقم سطر وإزاحة رقم عمود في ملف المصدر المُولّد، تُرجِع دالة sourceMap.findEntry
كائنًا يمثل نطاق SourceMap في الملف الأصلي إذا وُجِد، أو كائنًا فارغًا إذا لم يُوجَد.
يحتوي الكائن المُرجع على المفاتيح التالية:
generatedLine
: <number> إزاحة سطر بداية النطاق في المصدر المُولّدgeneratedColumn
: <number> إزاحة عمود بداية النطاق في المصدر المُولّدoriginalSource
: <string> اسم ملف المصدر الأصلي، كما هو مُبلّغ عنه في SourceMaporiginalLine
: <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 لموقع الاتصال المُقابِل في المصدر الأصلي