في هذا الدرس التطبيقي حول الترميز، يمكنك تحسين أداء هذا التطبيق البسيط الذي يتيح للمستخدمين تقييم قطط عشوائية. تعرَّف على كيفية تحسين حِزمة JavaScript من خلال تقليل عدد الرموز البرمجية التي يتم تحويلها.
في نموذج التطبيق، يمكنك اختيار كلمة أو رمز تعبيري للتعبير عن مدى إعجابك بكل قطة. عند النقر على زر، يعرض التطبيق قيمة الزر أسفل صورة القطة الحالية.
القياس
من الأفضل دائمًا البدء بفحص الموقع الإلكتروني قبل إجراء أي تحسينات:
- لمعاينة الموقع الإلكتروني، اضغط على عرض التطبيق. ثم اضغط على ملء الشاشة .
- اضغط على Ctrl + Shift + J (أو Command + Option + J على نظام التشغيل Mac) لفتح DevTools.
- انقر على علامة التبويب الشبكة.
- ضَع علامة في مربّع الاختيار إيقاف ذاكرة التخزين المؤقت.
- أعِد تحميل التطبيق.
يتم استخدام أكثر من 80 كيلوبايت لهذا التطبيق. حان الوقت لمعرفة ما إذا كانت أجزاء من الحزمة لا يتم استخدامها:
اضغط على
Control+Shift+P
(أوCommand+Shift+P
على أجهزة Mac) ل فتح قائمة الأمر.أدخِل
Show Coverage
واضغط علىEnter
لعرض علامة التبويب التغطية.في علامة التبويب التغطية، انقر على إعادة تحميل لإعادة تحميل التطبيق أثناء تسجيل التغطية.
اطّلِع على مقدار الرمز البرمجي الذي تم استخدامه مقارنةً بالكمية التي تم تحميلها للحزمة الرئيسية:
ولا يتم استخدام أكثر من نصف الحزمة (44 كيلوبايت). ويرجع ذلك إلى أنّ الكثير من الرمز البرمجي يتكوّن من polyfills لضمان عمل التطبيق في browsers القديمة.
استخدام @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-loader
على Babel في عملية إنشاء 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، ما يعني أنّه يمكنك العثور على قائمة كاملة بالطلبات المتوافقة
التي يمكن استخدامها في هذا الحقل في
مستندات browserslist.
تُحوِّل قيمة "last 2 versions"
الرمز البرمجي في التطبيق إلى
الإصدارَين الأخيرَين من كل متصفّح.
تصحيح الأخطاء
للحصول على نظرة شاملة على جميع استهدافات Babel للمتصفّح بالإضافة إلى جميع
عمليات التحويل وpolyfills المضمّنة، أضِف حقل debug
إلى .babelrc:
.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
}
]
]
}
- انقر على الأدوات.
- انقر على السجّلات.
أعِد تحميل التطبيق واطّلِع على سجلّات حالة Glitch في أسفل المحرِّر.
المتصفّحات المستهدَفة
يسجِّل Babel عددًا من التفاصيل في وحدة التحكّم حول عملية الترجمة، بما في ذلك جميع البيئات المستهدَفة التي تم ترجمة الرمز البرمجي لها.
يُرجى ملاحظة أنّ المتصفّحات التي تم إيقافها نهائيًا، مثل Internet Explorer، مضمّنة في هذه القائمة. ويشكّل ذلك مشكلة لأنّ المتصفّحات غير المتوافقة لن تحصل على ميزات أحدث، وسيواصل Babel تحويل بنية معيّنة لها. يؤدي ذلك إلى زيادة حجم الحِزمة بدون داعٍ إذا لم يكن المستخدمون يستخدمون هذا browser للوصول إلى موقعك الإلكتروني.
يسجِّل Babel أيضًا قائمة بالمكوّنات الإضافية للتحويل المستخدَمة:
هذه قائمة طويلة جدًا. هذه هي جميع المكوّنات الإضافية التي يحتاج Babel إلى استخدامها لتحويل أي بنية جملة ES2015 والإصدارات الأحدث إلى بنية جملة قديمة لجميع المتصفّحات المستهدَفة.
ومع ذلك، لا يعرض Babel أيّ polyfills محدّدة يتم استخدامها:
ويعود السبب في ذلك إلى أنّه يتم استيراد @babel/polyfill
بالكامل مباشرةً.
تحميل polyfills بشكل فردي
يتضمّن Babel تلقائيًا كلّ polyfill مطلوب لبيئة ES2015 أو الإصدارات الأحدث عند استيراد
@babel/polyfill
إلى ملف. لاستيراد مكونات polyfill معيّنة مطلوبة
للمتصفّحات المستهدَفة، أضِف useBuiltIns: 'entry'
إلى الإعداد.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
أعِد تحميل التطبيق. يمكنك الآن الاطّلاع على جميع مجموعات polyfills المحدّدة المضمّنة:
على الرغم من أنّه تمّ الآن تضمين مجموعات polyfills المطلوبة فقط لنظام التشغيل "last 2 versions"
، لا تزال هذه القائمة طويلة جدًا. ويعود السبب في ذلك إلى أنّه لا يزال يتم تضمين
العناصر القابلة للاستبدال التي تحتاجها المتصفّحات المستهدَفة لكل ميزة جديدة. غيِّر قيمة السمة إلى usage
لتضمين السمات المطلوبة فقط للميزات المستخدَمة في الرمز.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true,
"useBuiltIns": "entry"
"useBuiltIns": "usage"
}
]
]
}
وبهذا الإجراء، يتم تضمين وحدات الملء اللاحق للوظائف تلقائيًا عند الحاجة.
وهذا يعني أنّه يمكنك إزالة عملية استيراد @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%"
) مع استبعاد متصفّحات معيّنة تثق بأنّ
المستخدِمين لا يستخدمونها هو النهج المُقترَح. اطّلِع على مقالة "آخر إصدارَين" من التطبيق يُعتبَران ضارَّين penned by James Kyle للتعرّف على مزيد من المعلومات حول هذا الموضوع.
استخدِم علامة <script type="module">.
لا يزال هناك مجال للتحسين. على الرغم من إزالة عدد من polyfills غير المستخدَمة، هناك العديد من polyfills التي يتم إرسالها غير مطلوبة لبعض المتصفّحات. باستخدام الوحدات، يمكن كتابة بنية نحوية أحدث وشحنها إلى browsers مباشرةً بدون استخدام أيّ 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 Modules مع 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
يتضمّن جميع عمليات التحويل ووحدات الملء اللازمة لاستهداف كل متصفّح
لا يتيح استخدام وحدات 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
على true هنا، ما يعني أنّ الرمز المبرمَج الذي يتم عرضه في هذه الوحدة هو نص برمجي أصغر حجمًا وأقل معالجة، ولا يخضع لأي تحويل في هذا المثال لأنّ جميع الميزات المستخدَمة متوافقة مع المتصفّحات التي تتيح استخدام الوحدات.
في نهاية الملف، تصدِّر كلا الإعدادَين في صفيف واحد.
module.exports = [
legacyConfig, moduleConfig
];
يؤدي ذلك الآن إلى إنشاء وحدة أصغر للمتصفحات التي تتيح ذلك ونص برمجي أكبر مُحوَّل للغة أخرى للمتصفحات القديمة.
تتجاهل المتصفحات التي تتيح استخدام الوحدات النصوص البرمجية التي تحتوي على سمة nomodule
.
في المقابل، تتجاهل المتصفّحات التي لا تتيح استخدام الوحدات عناصر النصوص البرمجية التي تحتوي على
type="module"
. وهذا يعني أنّه يمكنك تضمين وحدة بالإضافة إلى ملف compiled
احتياطي. من الناحية المثالية، يجب أن يكون كلا الإصدارَين من التطبيق بتنسيق 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
script
بالإضافة إلى سمة nomodule
لجميع وحدات .js
script.
عرض الوحدات في ملف HTML
الخطوة الأخيرة التي يجب اتّخاذها هي إخراج عناصر النصوص البرمجية القديمة والحديثة إلى ملف HTML. إنّ المكوّن الإضافي الذي ينشئ ملف HTML النهائي، وهو HTMLWebpackPlugin
، لا يتيح حاليًا عرض ناتج النص البرمجي لكل من module وnomodule. على الرغم من توفّر حلول بديلة ومكونات إضافية منفصلة تم إنشاؤها لحلّ هذه المشكلة، مثل BabelMultiTargetPlugin وHTMLWebpackMultiBuildPlugin، يتم استخدام نهج أبسط لإضافة عنصر نص البرنامج للوحدة يدويًا بغرض هذا الدليل التعليمي.
أضِف ما يلي إلى src/index.js
في نهاية الملف:
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
الآن، حمِّل التطبيق في متصفّح متوافق مع الوحدات، مثل أحدث إصدار من Chrome.
يتم جلب الوحدة فقط، بحجم حِزمة أصغر بكثير بسبب عدم تحويلها إلى حد كبير. ويتجاهل المتصفّح تمامًا عنصر النص البرمجي الآخر.
في حال تحميل التطبيق على متصفّح قديم، لن يتم جلب سوى الرمز البرمجي المُحوَّل والمُعدَّل الأكبر حجمًا مع جميع عمليات التحويل وعمليات إضافة العناصر اللازمة. في ما يلي لقطة شاشة لجميع الطلبات التي تم إجراؤها على إصدار قديم من Chrome (الإصدار 38).
الخاتمة
تعرّفت الآن على كيفية استخدام @babel/preset-env
لتقديم ملفَّي معالجة مسبقة ضروريَّين فقط للمتصفّحات المستهدَفة. وتعرف أيضًا كيف يمكن أن تحسِّن وحدات JavaScript
الأداء بشكلٍ أكبر من خلال شحن نسختَين مختلفتَين من تطبيق
تم تحويلهما. بعد أن تعرّفت على كيفية خفض حجم الحِزمة بشكلٍ كبير باستخدام هاتين الطريقتَين، يمكنك بدء عملية التحسين.