في هذا الدرس التطبيقي حول الترميز، يمكنك تحسين أداء هذا التطبيق البسيط الذي يتيح للمستخدمين تقييم قطط عشوائية. تعرَّف على كيفية تحسين حزمة JavaScript من خلال تقليل مقدار الرموز التي تتم ترجمتها من مصدر إلى مصدر.

في نموذج التطبيق، يمكنك اختيار كلمة أو رمز تعبيري للتعبير عن مدى إعجابك بكل قطة. عند النقر فوق زر، يعرض التطبيق قيمة الزر أسفل صورة القطة الحالية.
القياس
من الأفضل دائمًا البدء بفحص الموقع الإلكتروني قبل إضافة أي تحسينات:
- لمعاينة الموقع الإلكتروني، انقر على عرض التطبيق، ثم انقر على
ملء الشاشة
.
- اضغط على `Control+Shift+J` (أو `Command+Option+J` على أجهزة Mac) لفتح "أدوات مطوّري البرامج".
- انقر على علامة التبويب الشبكة.
- ضَع علامة في مربّع الاختيار إيقاف ذاكرة التخزين المؤقت.
- أعِد تحميل التطبيق.

يتم استخدام أكثر من 80 كيلوبايت لهذا التطبيق. حان الوقت لمعرفة ما إذا كانت هناك أجزاء من الحزمة لا يتم استخدامها:
اضغط على
Control+Shift+P(أوCommand+Shift+Pعلى أجهزة Mac) لفتح قائمة الأوامر.
أدخِل
Show Coverageواضغط علىEnterلعرض علامة التبويب التغطية.في علامة التبويب التغطية، انقر على إعادة التحميل لإعادة تحميل التطبيق أثناء التقاط التغطية.

لنلقِ نظرة على مقدار الرموز البرمجية التي تم استخدامها مقارنةً بمقدار الرموز التي تم تحميلها للحزمة الرئيسية:
لا يتم استخدام أكثر من نصف الحزمة (44 كيلوبايت). ويرجع ذلك إلى أنّ الكثير من الرموز البرمجية في هذه الحزمة تتألف من polyfills لضمان عمل التطبيق في المتصفحات القديمة.
استخدام @babel/preset-env
تتوافق بنية لغة JavaScript مع معيار يُعرف باسم ECMAScript أو ECMA-262. يتم إصدار إصدارات أحدث من المواصفات كل عام وتتضمن ميزات جديدة اجتازت عملية الاقتراح. يكون كل متصفّح رئيسي دائمًا في مرحلة مختلفة من دعم هذه الميزات.
يتم استخدام ميزات ES2015 التالية في التطبيق:
يتم أيضًا استخدام ميزة ES2017 التالية:
يمكنك الاطّلاع على الرمز المصدر في src/index.js لمعرفة كيفية استخدام كل هذه العناصر.
تتوفّر كل هذه الميزات في أحدث إصدار من Chrome، ولكن ماذا عن المتصفّحات الأخرى التي لا تتوافق معها؟ Babel، المضمّن في التطبيق، هو المكتبة الأكثر شيوعًا المستخدمة لتجميع التعليمات البرمجية التي تحتوي على بناء جملة أحدث في التعليمات البرمجية التي يمكن للمتصفحات والبيئات القديمة فهمها. ويتم ذلك بطريقتَين:
- يتم تضمين Polyfills لمحاكاة وظائف ES2015+ الأحدث حتى يمكن استخدام واجهات برمجة التطبيقات الخاصة بها حتى إذا لم تكن متوافقة مع المتصفّح. في ما يلي مثال على polyfill للطريقة
Array.includes. - تُستخدَم المكوّنات الإضافية لتحويل رمز ES2015 (أو الإصدارات الأحدث) إلى بنية ES5 الأقدم. بما أنّ هذه التغييرات مرتبطة بالبنية (مثل دوال الأسهم)، لا يمكن محاكاتها باستخدام polyfills.
اطّلِع على package.json لمعرفة مكتبات Babel المضمّنة:
"dependencies": {
"@babel/polyfill": "^7.0.0"
},
"devDependencies": {
//...
"babel-loader": "^8.0.2",
"@babel/core": "^7.1.0",
"@babel/preset-env": "^7.1.0",
//...
}
-
@babel/coreهو برنامج التجميع الأساسي في Babel. وبذلك، يتم تحديد جميع إعدادات Babel في ملف.babelrcفي جذر المشروع. - يتضمّن
babel-loaderBabel في عملية إنشاء حزمة الويب.
والآن، لنلقِ نظرة على webpack.config.js لمعرفة كيفية تضمين babel-loader كقاعدة:
module: {
rules: [
//...
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
- يوفّر
@babel/polyfillجميع الرموز البديلة اللازمة لأي ميزات ECMAScript جديدة لكي تعمل في البيئات التي لا تتيحها. تم استيراده في أعلىsrc/index.js.
import "./style.css";
import "@babel/polyfill";
- يحدّد
@babel/preset-envعمليات التحويل وpolyfills اللازمة لأي متصفحات أو بيئات يتم اختيارها كأهداف.
ألقِ نظرة على ملف إعدادات Babel، .babelrc، لمعرفة كيفية
تضمينه:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
هذا هو إعداد Babel وwebpack. تعرَّف على كيفية تضمين Babel في تطبيقك إذا كنت تستخدم حزمة وحدات مختلفة عن webpack.
تحدّد السمة targets في .babelrc المتصفّحات المستهدَفة. تتكامل @babel/preset-env مع browserslist، ما يعني أنّه يمكنك العثور على قائمة كاملة بطلبات البحث المتوافقة التي يمكن استخدامها في هذا الحقل في مستندات browserlist.
تترجم القيمة "last 2 versions" الرمز البرمجي في التطبيق لآخر إصدارَين من كل متصفّح.
تصحيح الأخطاء
لإلقاء نظرة كاملة على جميع أهداف Babel في المتصفّح وجميع عمليات التحويل وpolyfills المضمّنة، أضِف حقل debug إلى .babelrc:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
}
]
]
}
- انقر على الأدوات.
- انقر على السجلّات.
أعِد تحميل التطبيق وألقِ نظرة على سجلات حالة Glitch في أسفل المحرّر.
المتصفّحات المستهدَفة
يسجّل Babel عددًا من التفاصيل في وحدة التحكّم حول عملية التجميع، بما في ذلك جميع البيئات المستهدَفة التي تم تجميع الرمز البرمجي لها.

لاحظ كيف يتم تضمين المتصفحات التي تم إيقافها نهائيًا، مثل Internet Explorer، في هذه القائمة. هذه مشكلة لأنّ المتصفحات غير المتوافقة لن تتم إضافة ميزات جديدة إليها، وسيواصل Babel الترجمة من مصدر إلى مصدر بنية محدّدة لها. يؤدي ذلك إلى زيادة حجم الحزمة بدون داعٍ إذا لم يكن المستخدمون يستخدمون هذا المتصفّح للوصول إلى موقعك الإلكتروني.
يسجّل Babel أيضًا قائمة بالمكوّنات الإضافية للتحويل المستخدَمة:

هذه قائمة طويلة جدًا. هذه هي جميع المكوّنات الإضافية التي يحتاج Babel إلى استخدامها لتحويل أي بنية ES2015+ إلى بنية قديمة لجميع المتصفحات المستهدفة.
ومع ذلك، لا يعرض Babel أي polyfills محدّدة يتم استخدامها:
![]()
ويعود السبب في ذلك إلى أنّه يتم استيراد @babel/polyfill بالكامل مباشرةً.
تحميل الرموز البرمجية polyfill بشكل فردي
بشكل تلقائي، يتضمّن Babel كل polyfill مطلوب لبيئة ES2015+ كاملة عند استيراد @babel/polyfill إلى ملف. لاستيراد polyfills معيّنة مطلوبة للمتصفّحات المستهدَفة، أضِف useBuiltIns: 'entry' إلى الإعدادات.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
أعِد تحميل التطبيق. يمكنك الآن الاطّلاع على جميع الرموز المضمّنة:

على الرغم من تضمين polyfills المطلوبة فقط لـ "last 2 versions" الآن، إلا أنّ القائمة لا تزال طويلة جدًا. ويرجع ذلك إلى أنّ
الرموز البرمجية polyfill المطلوبة للمتصفّحات المستهدَفة لكل ميزة جديدة لا تزال مضمّنة. غيِّر قيمة السمة إلى usage
لتضمين القيم المطلوبة فقط للميزات المستخدَمة في الرمز البرمجي.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true,
"useBuiltIns": "entry"
"useBuiltIns": "usage"
}
]
]
}
وبذلك، يتم تضمين الرموز البرمجية polyfill تلقائيًا عند الحاجة.
هذا يعني أنّه يمكنك إزالة عملية استيراد @babel/polyfill في src/index.js.
import "./style.css";
import "@babel/polyfill";
والآن، يتم تضمين الرموز البرمجية polyfill المطلوبة للتطبيق فقط.

يتم تقليل حجم حزمة التطبيق بشكل كبير.

تضييق قائمة المتصفّحات المتوافقة
لا يزال عدد المتصفحات المستهدفة المشمولة كبيرًا جدًا، ولا يستخدم العديد من المستخدمين المتصفحات التي تم إيقافها نهائيًا مثل Internet Explorer. عدِّل الإعدادات إلى ما يلي:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"targets": [">0.25%", "not ie 11"],
"debug": true,
"useBuiltIns": "usage",
}
]
]
}
اطّلِع على تفاصيل الحزمة التي تم جلبها.

بما أنّ التطبيق صغير جدًا، لن يكون هناك فرق كبير مع هذه التغييرات. ومع ذلك، فإنّ النهج المقترَح هو استخدام نسبة مئوية لحصة السوق من المتصفّحات (مثل ">0.25%") مع استبعاد متصفّحات معيّنة تثق بأنّ المستخدمين لا يستخدمونها. يمكنك الاطّلاع على مقالة
"آخر إصدارَين" يُعتبران ضارَّين
بقلم "جيمس كايل" لمعرفة المزيد حول هذا الموضوع.
استخدام <script type="module">
لا يزال هناك مجال كبير للتحسين. على الرغم من إزالة عدد من الرموز البرمجية غير المستخدَمة، هناك العديد من الرموز البرمجية التي يتم شحنها والتي لا تحتاج إليها بعض المتصفّحات. باستخدام الوحدات، يمكن كتابة البنية الأحدث وإرسالها إلى المتصفحات مباشرةً بدون استخدام أي polyfills غير ضرورية.
وحدات JavaScript هي ميزة جديدة نسبيًا تتوافق مع جميع المتصفّحات الرئيسية.
يمكن إنشاء الوحدات باستخدام سمة type="module" لتحديد النصوص البرمجية التي يتم استيرادها وتصديرها من وحدات أخرى. على سبيل المثال:
// math.mjs
export const add = (x, y) => x + y;
<!-- index.html -->
<script type="module">
import { add } from './math.mjs';
add(5, 2); // 7
</script>
تتوفّر العديد من ميزات ECMAScript الأحدث في البيئات التي تتيح استخدام وحدات JavaScript (بدلاً من الحاجة إلى Babel). هذا يعني أنه يمكن تعديل إعدادات Babel لإرسال نسختين مختلفتين من تطبيقك إلى المتصفح:
- إصدار يعمل في المتصفحات الأحدث التي تتيح استخدام الوحدات ويتضمّن وحدة غير محوَّلة إلى حد كبير ولكن حجم ملفها أصغر
- نسخة تتضمّن نصًا برمجيًا أكبر مترجمًا من مصدر إلى مصدر يعمل في أي متصفّح قديم
استخدام وحدات ES مع Babel
للحصول على إعدادات @babel/preset-env منفصلة لإصدارَي التطبيق، عليك إزالة ملف .babelrc. يمكن إضافة إعدادات Babel إلى إعدادات webpack من خلال تحديد تنسيقَي تجميع مختلفَين لكل إصدار من التطبيق.
ابدأ بإضافة إعدادات للنص البرمجي القديم إلى webpack.config.js:
const legacyConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].bundle.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: false
}
}]
]
}
},
cssRule
]
},
plugins
}
لاحظ أنّه بدلاً من استخدام القيمة targets للسمة "@babel/preset-env"،
يتم استخدام السمة esmodules مع القيمة false بدلاً من ذلك. وهذا يعني أنّ Babel
يتضمّن جميع عمليات التحويل وpolyfills اللازمة لاستهداف كل متصفّح لا يتيح
استخدام وحدات ES حتى الآن.
أضِف عناصر entry وcssRule وcorePlugins إلى بداية ملف webpack.config.js. تتم مشاركة كل هذه العناصر بين كل من الوحدة والنصوص البرمجية القديمة التي يتم عرضها على المتصفح.
const entry = {
main: "./src"
};
const cssRule = {
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
};
const plugins = [
new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
new HtmlWebpackPlugin({template: "./src/index.html"})
];
الآن، أنشِئ كائن ضبط لملف النص البرمجي للوحدة أدناه حيث يتم تحديد legacyConfig:
const moduleConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].mjs"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: true
}
}]
]
}
},
cssRule
]
},
plugins
}
الاختلاف الرئيسي هنا هو استخدام امتداد الملف .mjs لاسم ملف الإخراج. تم ضبط قيمة esmodules على "صحيح" هنا، ما يعني أنّ الرمز
الذي يتم إخراجه في هذه الوحدة هو نص برمجي أصغر وأقل تجميعًا لا
يخضع لأي تحويل في هذا المثال لأنّ جميع الميزات المستخدمة
متوافقة حاليًا مع المتصفحات التي تتيح استخدام الوحدات.
في نهاية الملف، يمكنك تصدير كلا الإعدادَين في مصفوفة واحدة.
module.exports = [
legacyConfig, moduleConfig
];
يؤدي ذلك إلى إنشاء وحدة أصغر للمتصفّحات المتوافقة معها، بالإضافة إلى نص برمجي أكبر مترجَم من مصدر إلى مصدر للمتصفّحات القديمة.
تتجاهل المتصفحات التي تتيح الوحدات النصوص البرمجية التي تتضمّن السمة nomodule.
في المقابل، تتجاهل المتصفّحات التي لا تتيح الوحدات عناصر النصوص البرمجية التي تتضمّن type="module". هذا يعني أنّه يمكنك تضمين وحدة بالإضافة إلى عنصر احتياطي
مجمَّع. من الناحية المثالية، يجب أن يكون الإصداران من التطبيق في index.html
على النحو التالي:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>
تسترجع المتصفّحات التي تتوافق مع الوحدات main.mjs وتنفّذها وتتجاهل main.bundle.js.، بينما تفعل المتصفّحات التي لا تتوافق مع الوحدات العكس.
من المهم ملاحظة أنّه على عكس النصوص البرمجية العادية، يتم دائمًا تأجيل النصوص البرمجية للوحدات بشكل تلقائي.
إذا أردت تأجيل تنفيذ نص nomodule البرمجي المكافئ أيضًا وتنفيذه فقط بعد
التحليل، عليك إضافة السمة defer:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>
الخطوة الأخيرة التي يجب تنفيذها هنا هي إضافة السمتَين module وnomodule إلى الوحدة والنص البرمجي القديم على التوالي، واستيراد ScriptExtHtmlWebpackPlugin في أعلى webpack.config.js:
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
عدِّل الآن مصفوفة plugins في الإعدادات لتضمين هذا المكوّن الإضافي:
const plugins = [
new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
new HtmlWebpackPlugin({template: "./src/index.html"}),
new ScriptExtHtmlWebpackPlugin({
module: /\.mjs$/,
custom: [
{
test: /\.js$/,
attribute: 'nomodule',
value: ''
},
]
})
];
تضيف إعدادات المكوّن الإضافي هذه سمة type="module" لجميع عناصر النصوص البرمجية .mjs بالإضافة إلى سمة nomodule لجميع وحدات النصوص البرمجية .js.
عرض الوحدات في مستند HTML
الخطوة الأخيرة التي يجب تنفيذها هي إخراج عناصر النصوص البرمجية القديمة والحديثة إلى ملف HTML. للأسف، المكوّن الإضافي الذي ينشئ ملف HTML النهائي، HTMLWebpackPlugin، لا يتيح حاليًا إخراج كل من النصوص البرمجية للوحدة وغير الوحدة. على الرغم من توفّر حلول بديلة ومكوّنات إضافية منفصلة تم إنشاؤها لحلّ هذه المشكلة، مثل BabelMultiTargetPlugin وHTMLWebpackMultiBuildPlugin، يتم استخدام طريقة أبسط لإضافة عنصر النص البرمجي للوحدة يدويًا لغرض هذا الدليل التعليمي.
أضِف ما يلي إلى src/index.js في نهاية الملف:
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
الآن، حمِّل التطبيق في متصفّح يتيح استخدام الوحدات، مثل أحدث إصدار من Chrome.

يتم جلب الوحدة فقط، مع حجم حزمة أصغر بكثير لأنّها غير مترجمة إلى حد كبير. يتم تجاهل عنصر النص البرمجي الآخر تمامًا بواسطة المتصفح.
إذا حمّلت التطبيق على متصفّح قديم، سيتم جلب النص البرمجي الأكبر حجمًا الذي تم تحويله من مصدر إلى مصدر فقط مع جميع الرموز البرمجية polyfill وtransform المطلوبة. في ما يلي لقطة شاشة لجميع الطلبات التي تم إجراؤها على إصدار قديم من Chrome (الإصدار 38).

الخاتمة
أنت الآن تفهم كيفية استخدام @babel/preset-env لتوفير الرموز البرمجية polyfill الضرورية فقط للمتصفحات المستهدفة. تعرف أيضًا كيف يمكن لوحدات JavaScript
تحسين الأداء بشكل أكبر من خلال شحن نسختين مختلفتين منقولتين من
التطبيق. بعد أن فهمت جيدًا كيف يمكن لهاتين الطريقتين تقليل حجم الحزمة بشكل كبير، يمكنك الآن المضي قدمًا وتحسين الأداء!