في هذا الدرس التطبيقي حول الترميز، حسِّن أداء هذا التطبيق البسيط الذي يتيح للمستخدمين تقييم القطط العشوائية. تعرف على كيفية تحسين حزمة JavaScript عن طريق تقليل مقدار التعليمات البرمجية التي يتم نقلها.
في نموذج التطبيق، يمكنك تحديد كلمة أو رمز تعبيري للتعبير عن مدى إعجابك بكل قطة. وعند النقر على زر، يعرض التطبيق قيمة الزر أسفل صورة القطة الحالية.
قياس
من الأفضل دائمًا أن تبدأ بفحص موقع الويب قبل إضافة أي تحسينات:
- لمعاينة الموقع الإلكتروني، اضغط على عرض التطبيق، ثم اضغط على ملء الشاشة .
- اضغط على "Control+Shift+J" (أو "Command+Option+J" على أجهزة Mac) لفتح "أدوات مطوري البرامج".
- انقر على علامة التبويب الشبكة.
- ضع علامة في مربّع الاختيار إيقاف ذاكرة التخزين المؤقت.
- أعِد تحميل التطبيق.
يتم استخدام أكثر من 80 كيلوبايت لهذا التطبيق. حان الوقت لمعرفة ما إذا لم يتم استخدام أجزاء من الحزمة:
اضغط على
Control+Shift+P
(أوCommand+Shift+P
على جهاز Mac) لفتح قائمة Command.أدخِل
Show Coverage
واضغط علىEnter
لعرض علامة التبويب التغطية.في علامة التبويب التغطية، انقر على إعادة تحميل لإعادة تحميل التطبيق أثناء تسجيل التغطية.
ألق نظرة على مقدار التعليمات البرمجية التي تم استخدامها ومقدار البيانات التي تم تحميلها للحزمة الرئيسية:
لم يتم استخدام أكثر من نصف الحزمة (44 كيلوبايت). وذلك لأن الكثير من الرموز داخلها تتكون من رموز polyfill لضمان عمل التطبيق في المتصفحات القديمة.
استخدام @babel/preset-env
تتوافق بنية لغة JavaScript مع معيار يُعرف باسم ECMAScript أو ECMA-262. يتم إصدار إصدارات أحدث من المواصفات كل عام وتشمل ميزات جديدة اجتازت عملية العرض. يكون كل متصفح رئيسي دائمًا في مرحلة مختلفة من دعم هذه الميزات.
يتم استخدام ميزات ES2015 التالية في التطبيق:
يتم أيضًا استخدام ميزة ES2017 التالية:
لا تترددوا في التعمّق في تفاصيل رمز المصدر في src/index.js
لمعرفة كيفية استخدام كل ذلك.
تتوافق جميع هذه الميزات مع أحدث إصدار من Chrome، ولكن ماذا عن المتصفحات الأخرى التي لا تتوافق معها؟ تعتبر Babel، المُضمَّنة في التطبيق، المكتبة الأكثر شيوعًا المستخدمة لتجميع رموز برمجية تحتوي على بنية أحدث في التعليمات البرمجية التي يمكن للمتصفحات والبيئات القديمة فهمها. ويحدث ذلك بطريقتَين:
- يتم تضمين Polyfills لمحاكاة وظائف ES2015 الأحدث الأحدث بحيث يمكن استخدام واجهات برمجة التطبيقات الخاصة بها
حتى إذا لم يكن متوافقًا مع المتصفح. في ما يلي مثال على polyfill لطريقة
Array.includes
. - تُستخدَم المكوّنات الإضافية لتحويل رمز ES2015 (أو الإصدارات الأحدث) إلى بنية ES5 الأقدم. نظرًا لأن هذه تغييرات متعلقة ببناء الجملة (مثل الدوال السهمية)، لا يمكن محاكاتها باستخدام الرموز polyfill.
ألقِ نظرة على 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.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
عمليات التحويل ورموز polyfill اللازمة لأي متصفِّحات أو بيئات يتم اختيارها كأهداف.
ألقِ نظرة على ملف إعدادات Babel، .babelrc
، للاطّلاع على
كيفية تضمينه:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
هذا إعداد Babel وWebpack. تعرَّف على طريقة تضمين Babel في تطبيقك إذا كنت تستخدم أداة حزم وحدات مختلفة عن حزمة الويب.
وتحدّد السمة targets
في .babelrc
المتصفّحات التي يتم استهدافها. يمكن استخدام @babel/preset-env
مع قائمة المتصفّحات، ما يعني أنّه يمكنك العثور على قائمة كاملة بطلبات البحث المتوافقة التي يمكن استخدامها في هذا الحقل ضمن مستندات قائمة المتصفِّح.
تعرض القيمة "last 2 versions"
الرمز الموجود في التطبيق لآخر إصدارين من كل متصفح.
تصحيح الأخطاء
للحصول على نظرة شاملة على جميع أهداف Babel للمتصفّح بالإضافة إلى جميع
عمليات التحويل ورموز polyfill المضمَّنة، يمكنك إضافة حقل debug
إلى .babelrc:
.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
}
]
]
}
- انقر على الأدوات.
- انقر على السجلات.
أعِد تحميل التطبيق واطّلِع على سجلات حالة Glitch في أسفل المحرّر.
المتصفّحات المستهدَفة
يسجل Babel عددًا من التفاصيل في وحدة التحكم حول عملية التجميع، بما في ذلك جميع البيئات المستهدفة التي تم تجميع الرمز البرمجي لها.
لاحظ كيف يتم تضمين المتصفحات المتوقفة في هذه القائمة، مثل Internet Explorer. والسبب في ذلك هو أنّه لن تتم إضافة ميزات جديدة إلى المتصفّحات غير المتوافقة، وسيواصل Babel نقل بنية معيّنة لها. يؤدي ذلك إلى زيادة حجم الحزمة بدون داعٍ إذا كان المستخدمون لا يستخدمون هذا المتصفّح للوصول إلى موقعك الإلكتروني.
يسجل Babel أيضًا قائمة بمكونات التحويل الإضافية المستخدمة:
هذه قائمة طويلة جدًا! في ما يلي جميع المكوّنات الإضافية التي يحتاج Babel إلى استخدامها لتحويل أي بنية ES2015+ إلى بنية قديمة لجميع المتصفحات المستهدفة.
مع ذلك، لا يعرض Babel أي رموز polyfill محددة تم استخدامها:
ويرجع ذلك إلى أنّه يتم استيراد @babel/polyfill
بالكامل مباشرةً.
تحميل رموز polyfill بشكل فردي
يتضمّن تطبيق Babel تلقائيًا كل رمز polyfill مطلوب لبيئة ES2015+ كاملة عند
استيراد @babel/polyfill
إلى ملف. لاستيراد رموز polyfill محددة لازمة للمتصفّحات المستهدَفة، أضِف useBuiltIns: 'entry'
إلى الإعدادات.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
أعِد تحميل التطبيق. يمكنك الآن الاطّلاع على جميع رموز polyfill المحددة المدرَجة:
على الرغم من أنّنا لم نضمّن سوى رموز 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">
لا يزال هناك مجال أكبر للتحسين. وعلى الرغم من إزالة عدد من رموز polyfill غير المستخدمة، ولكن يتم شحن العديد منها ولن تكون هناك حاجة إليها في بعض المتصفحات. باستخدام الوحدات، يمكن كتابة بنية جديدة وشحنها إلى المتصفحات مباشرةً بدون استخدام أي رموز polyfill غير ضرورية.
وحدات 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 يتضمّن جميع عمليات التحويل ورموز polyfill اللازمة لاستهداف كل متصفّح لا يتيح استخدام وحدات 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
، لا يتيح حاليًا إخراج كل من النصوص البرمجية للوحدة النمطية وnomodule. على الرغم من أن هناك حلولاً بديلة ومكوّنات إضافية منفصلة تم إنشاؤها لحل هذه المشكلة، مثل BabelMultiTargetPlugin وHTMLWebpackMultiBuildPlugin، يتم استخدام طريقة أبسط لإضافة عنصر البرنامج النصي للوحدة يدويًا لغرض هذا البرنامج التعليمي.
أضِف ما يلي إلى src/index.js
في نهاية الملف:
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
الآن يمكنك تحميل التطبيق في متصفح متوافق مع الوحدات، مثل أحدث إصدار من Chrome.
يتم جلب الوحدة فقط، مع حجم حزمة أصغر بكثير بسبب عدم نقلها بشكل كبير! يتجاهل المتصفح تمامًا عنصر النص البرمجي الآخر.
في حال تحميل التطبيق على متصفّح أقدم، لن يتم استرجاع سوى النص البرمجي الأكبر حجمًا والمحول والذي يحتوي على جميع رموز polyfill والتحويل المطلوبة. إليك لقطة شاشة لجميع الطلبات التي تم إجراؤها على إصدار قديم من Chrome (الإصدار 38).
الخلاصة
لقد تعرفت الآن على كيفية استخدام @babel/preset-env
لتقديم رموز polyfill
اللازمة اللازمة فقط للمتصفّحات المستهدَفة. كما تعرف كيف يمكن لوحدات JavaScript تحسين الأداء بشكل أكبر من خلال شحن إصدارين مختلفين تم ترجمة مضمونهما من التطبيق. ومن خلال فهم جيدًا للطريقة التي يمكن بهاتين هاتين الوسيلتين خفض حجم حزمتك، يمكنك المضي قدمًا وتحسين الأداء.