وحدات ECMAScript
وحدات ECMAScript (ESM) هي مواصفة لاستخدام الوحدات على الويب. وهي مدعومة في جميع المتصفحات الحديثة، وتُعد الطريقة الموصى بها لكتابة كود وحدي للويب.
يدعم Webpack معالجة وحدات ECMAScript لتحسينها.
التصدير
تتيح الكلمة المفتاحية export كشف عناصر من ESM إلى وحدات أخرى:
export const CONSTANT = 42;
export let variable = 42;
// لا يُكشف إلا حق القراءة
// لا يمكن تعديل المتغير من الخارج
export function fun() {
console.log("fun");
}
export class C extends Super {
method() {
console.log("method");
}
}
let a, b, other;
export { a, b, other as c };
export default 1 + 2 + 3 + more();الاستيراد
تتيح الكلمة المفتاحية import الحصول داخل ESM على مراجع لعناصر من وحدات أخرى:
// استيراد "ارتباطات" إلى صادرات من وحدة أخرى
// هذه الارتباطات حية. لا يتم نسخ القيم،
// بل إن الوصول إلى "variable" سيجلب القيمة الحالية
// في الوحدة المستوردة
import { CONSTANT, variable } from "./module.js";
// اختصار لاستيراد الصادر "default"
import theDefaultValue from "./module.js";
// استيراد "كائن namespace" الذي يحتوي على كل الصادرات
import * as module from "./module.js";
module.fun();عند استيراد كائن namespace من وحدة ECMAScript، يتبع webpack عرف ESM بتعيين Symbol.toStringTag إلى "Module" على كائن namespace.
تمييز الوحدات كـ ESM
افتراضيًا، يكتشف webpack تلقائيًا ما إذا كان الملف ESM أم نظام وحدات مختلفًا.
أرست Node.js طريقة لتعيين نوع وحدة الملفات صراحةً باستخدام خاصية في package.json.
تعيين "type": "module" في package.json يفرض أن تكون جميع الملفات الموجودة أسفل ملف package.json هذا وحدات ECMAScript.
أما تعيين "type": "commonjs" فيفرض أن تكون CommonJS Modules بدلًا من ذلك.
{
"type": "module"
}بالإضافة إلى ذلك، يمكن للملفات تعيين نوع الوحدة باستخدام الامتداد .mjs أو .cjs. الامتداد .mjs يفرض أن تكون ESM، بينما يفرض .cjs أن تكون CommonJs.
في DataURIs، يؤدي استخدام نوع MIME text/javascript أو application/javascript أيضًا إلى فرض نوع الوحدة على ESM.
إلى جانب صيغة الوحدة، يؤثر تمييز الوحدات كـ ESM أيضًا في منطق الحل، ومنطق التوافق البيني، والرموز المتاحة داخل الوحدات.
import.meta في ESM
يكشف Webpack عدة خصائص من import.meta لاستخدامها في ESM:
| الخاصية | الوصف |
|---|---|
import.meta.url | عنوان URL لملف الوحدة الحالي - استخدمه مع new Worker() أو new URL() |
import.meta.webpack | رقم الإصدار الرئيسي من webpack (مثل 5) |
import.meta.webpackHot | مكافئ module.hot - استخدمه لـ HMR داخل ESM |
import.meta.webpackContext | مكافئ ESM لـ require.context |
مثال - استخدام import.meta.url للأصول:
// حل ملف شقيق نسبةً إلى الوحدة الحالية
const iconUrl = new URL("./icon.png", import.meta.url);
const img = document.createElement("img");
img.src = iconUrl.href;مثال - HMR داخل ESM:
if (import.meta.webpackHot) {
import.meta.webpackHot.accept("./module.js", () => {
// تعامل مع التحديث
});
}Await على المستوى الأعلى
في ESM، يمكنك استخدام await على المستوى الأعلى من الوحدة. يتعامل webpack مع الوحدة كوحدة غير متزامنة تلقائيًا. هذه الميزة مفعّلة افتراضيًا منذ 5.83.0؛ أما خيار experiments.topLevelAwait نفسه فقد أزيل في 5.102.0 (إذ أصبح يعمل مباشرة).
// user.js (وحدة ESM غير متزامنة)
const response = await fetch("/api/user");
export const user = await response.json();// index.js - يعمل استيراد وحدة غير متزامنة كما هو متوقع
import { user } from "./user.js";
console.log(user.name);الاستيرادات كاملة التحديد
تُحل الاستيرادات في ESM بصرامة أكبر. يجب أن تتضمن الطلبات النسبية امتداد الملف (مثل *.js أو *.mjs) وفق عرف Node.js عندما يكون الملف مميزًا كـ ESM:
// سيفشل - الامتداد مفقود
import { helper as missingExt } from "./utils";
// صحيح في ESM
import { helper } from "./utils.js";لتعطيل هذا الفحص، وهو مفيد عند ترحيل قاعدة كود CJS كبيرة، يمكنك استخدام fullySpecified=false:
// webpack.config.js
export default {
module: {
rules: [
{
test: /\.m?js/,
resolve: {
fullySpecified: false,
},
},
],
},
};التوافق البيني مع CommonJS
صيغة CommonJS غير متاحة داخل ESM: require وmodule وexports و__filename و__dirname.
عند الاستيراد من وحدة CommonJS داخل ESM، لا يتوفر إلا الصادر default (كائن module.exports بالكامل):
// esm-consumer.js (ESM)
import cjs from "./cjs-module.js";
// الاستيرادات المسماة من CJS لا تعمل
import { foo } from "./cjs-module.js"; // undefined
// cjs-module.js (CommonJS)
module.exports = { foo: 1, bar: 2 };
console.log(cjs.foo); // يعمل - cjs هو كائن الصادرات بالكاملينطبق هذا السلوك الصارم عندما يتعامل webpack مع الوحدة المستوردة على أنها CommonJS.
إذا كانت تلك الوحدة نفسها تستخدم صيغة export الخاصة بـ ESM، فسيكتشفها webpack تلقائيًا كـ ESM وستعمل الاستيرادات المسماة بشكل طبيعي. يحدث هذا كثيرًا في المشاريع التي تمزج ملفات .js
داخل مشروع عُيّن فيه "type": "module"؛ فقد يتعامل webpack مع بعض الملفات كـ ESM بينما تبقى حزم الجهات الخارجية داخل node_modules على CommonJS.
أخطاء الترحيل الشائعة
ReferenceError: require is not defined
عندما يُعامل ملف كـ ESM، لا تكون عموميات CommonJS (require وmodule وexports و__filename و__dirname) متاحة.
الإصلاح: استبدل require() بتعليمات import. وللتحميل الشرطي أو الديناميكي، استخدم import().
Must use import to load ES Module (Node.js) / SyntaxError: Cannot use import statement in a module (browser)
يحدث هذا عندما يستخدم ملف صيغة import/export الخاصة بـ ESM دون أن يكون مميزًا كـ ESM، إما لأن "type": "module" مفقودة من package.json، أو لأن الملف يستخدم امتداد .js بدلًا من .mjs.
الإصلاح: أضف "type": "module" إلى package.json، أو أعد تسمية الملف إلى .mjs.
Module not found: Error: Can't resolve './utils' (missing extension)
في ESM، يجب أن تتضمن الاستيرادات النسبية امتداد الملف. يتبع webpack هنا عرف ESM الخاص بـ Node.js.
الإصلاح: غيّر import { helper } from './utils' إلى import { helper } from './utils.js'،
أو عيّن fullySpecified: false في إعدادات webpack لتعطيل الفحص أثناء الترحيل.



