في هذا الدرس التطبيقي حول الترميز، يمكنك تحسين أداء هذا التطبيق البسيط الذي يتيح للمستخدمين تقييم القطط بشكل عشوائي. تعرَّف على كيفية تحسين حزمة 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.
والآن، لنلقِ نظرة على webpack.config.js لمعرفة كيفية تضمين babel-loader كقاعدة:
module: {
rules: [
//...
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
- يوفّر
@babel/polyfillجميع رموز 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"
}
]
]
}
أعِد تحميل التطبيق. يمكنك الآن الاطّلاع على جميع الرموز المضمّنة:

على الرغم من تضمين الرموز البرمجية polyfill اللازمة فقط لـ "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 لتوفير الرموز البرمجية
الضرورية فقط للمتصفّحات المستهدَفة. وتعرف أيضًا كيف يمكن لوحدات JavaScript
تحسين الأداء بشكل أكبر من خلال شحن نسختين مختلفتين من
التطبيق. بعد أن فهمت جيدًا كيف يمكن لهاتين الطريقتين تقليل حجم الحزمة بشكل كبير، يمكنك الآن المضي قدمًا وتحسين الأداء!