در این codelab، عملکرد این برنامه ساده را که به کاربران اجازه میدهد به گربههای تصادفی امتیاز دهند بهبود دهید. با نحوه بهینهسازی بسته جاوا اسکریپت ازطریق بهحداقل رساندن مقدار کد ترجمهشده مبدأ به مبدأ آشنا شوید.

در برنامه نمونه، میتوانید کلمه یا اموجیای را انتخاب کنید تا نشان دهید چقدر هر گربه را دوست دارید. وقتی روی دکمهای کلیک میکنید، برنامه مقدار دکمه را زیر تصویر گربه فعلی نمایش میدهد.
اندازه گرفتن
همیشه ایده خوبی است که قبلاز افزودن هرگونه بهینهسازی، با بازرسی وبسایت شروع کنید:
- برای پیشدید کردن سایت، مشاهده برنامه را فشار دهید. سپس
تمامصفحه
را فشار دهید.
- کلیدهای `Control+Shift+J` (یا `Command+Option+J` در Mac) را فشار دهید تا DevTools باز شود.
- روی برگه شبکه کلیک کنید.
- چارگوش انتخاب Disable cache (غیرفعال کردن حافظه نهان) را انتخاب کنید.
- برنامه را مجدداً بار کنید.

بیشاز ۸۰ کیلوبایت برای این برنامه استفاده شده است! زمان لازم برای اینکه مشخص شود بخشهایی از بسته استفاده نمیشود:
کلیدهای
Control+Shift+P(یاCommand+Shift+Pدر Mac) را فشار دهید تا منو فرمان باز شود.
Show Coverageرا وارد کنید وEnterرا بزنید تا برگه پوشش نمایش داده شود.در برگه پوشش، روی بار کردن مجدد کلیک کنید تا برنامه درحین ضبط پوشش مجدداً بار شود.

ببینید چقدر کد استفاده شده است درمقابل چقدر کد برای بسته اصلی بار شده است:
بیشاز نیمی از بسته (۴۴ کیلوبایت) حتی استفاده نشده است. دلیل این امر این است که بخش زیادی از کد درون آن شامل شبیهسازهای مرورگر است تا اطمینان حاصل شود که برنامه در مرورگرهای قدیمیتر کار میکند.
استفاده از @babel/preset-env
نحو زبان جاوا اسکریپت با استانداردی بهنام ECMAScript یا ECMA-262 مطابقت دارد. نسخههای جدیدتر این مشخصات هر سال منتشر میشود و شامل ویژگیهای جدیدی است که فرایند پیشنهاد را گذراندهاند. هر مرورگر اصلی همیشه در مرحله متفاوتی از پشتیبانی این ویژگیها قرار دارد.
ویژگیهای ES2015 زیر در برنامه استفاده میشود:
از ویژگی ES2017 زیر نیز استفاده میشود:
میتوانید کد منبع را در src/index.js بررسی کنید تا ببینید چگونه از همه اینها
استفاده میشود.
همه این ویژگیها در جدیدترین نسخه Chrome پشتیبانی میشوند، اما مرورگرهای دیگری که از آنها پشتیبانی نمیکنند چطور؟ Babel، که در برنامه گنجانده شده است، محبوبترین کتابخانه مورداستفاده برای همگردانی کد حاوی نحو جدیدتر به کدی است که مرورگرها و محیطهای قدیمیتر بتوانند آن را درک کنند. این کار را به دو روش انجام میدهد:
- شبیهسازهای مرورگر برای شبیهسازی عملکردهای جدیدتر ES2015+ گنجانده شدهاند تا میاناهای برنامهسازی کاربردی آنها
حتی اگر توسط مرورگر پشتیبانی نشوند، قابلاستفاده باشند. در اینجا مثالی از
شبیهساز مرورگر
روش
Array.includesآورده شده است. - از افزونهها برای تبدیل کد ES2015 (یا جدیدتر) به نحو قدیمیتر ES5 استفاده میشود. ازآنجاییکه این تغییرات مربوط به نحو هستند (مانند توابع پیکان)، نمیتوان آنها را با شبیهسازهای مرورگر شبیهسازی کرد.
به 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همه شبیهسازهای مرورگر لازم را برای هر ویژگی جدیدتر ECMAScript ارائه میدهد تا بتوانند در محیطهایی که از آنها پشتیبانی نمیکنند کار کنند. قبلاً در بالایsrc/index.js.وارد شده است
import "./style.css";
import "@babel/polyfill";
@babel/preset-envمشخص میکند کدام تبدیلها و شبیهسازهای مرورگر برای مرورگرها یا محیطهای انتخابشده بهعنوان هدف ضروری هستند.
به فایل پیکربندی Babel، .babelrc، نگاه کنید تا ببینید چگونه
گنجانده شده است:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
این راهاندازی Babel و webpack است. اگر از بستهبندیکننده ماژول دیگری غیراز webpack استفاده میکنید، با نحوه گنجاندن Babel در برنامهتان آشنا شوید.
مشخصه targets
در .babelrc مشخص میکند کدام مرورگرها هدف قرار گرفتهاند. @babel/preset-env
با browserslist ادغام میشود، یعنی میتوانید فهرست کاملی از پُرسمانهای سازگار را که میتوان در این فیلد استفاده کرد در
سند browserlist پیدا کنید.
مقدار "last 2 versions" کد را در برنامه برای
دو نسخه آخر هر مرورگر ترجمه مبدأ به مبدأ میکند.
اشکالزدایی
برای مشاهده کامل همه هدفهای Babel مرورگر و همچنین همه تبدیلها و شبیهسازهای مرورگر موجود، فیلد debug را به .babelrc: اضافه کنید
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
}
]
]
}
- روی ابزارها کلیک کنید.
- روی گزارشها کلیک کنید.
برنامه را مجدداً بار کنید و به گزارشهای وضعیت Glitch در پایین ویرایشگر نگاهی بیندازید.
مرورگرهای هدفیابیشده
Babel جزئیات متعددی را درباره فرایند کامپایل کردن در کنسول ثبت میکند، ازجمله همه محیطهای هدفی که کد برای آنها کامپایل شده است.

توجه کنید که مرورگرهای متوقفشده، مانند Internet Explorer، در این فهرست گنجانده شدهاند. این مشکلساز است زیرا ویژگیهای جدیدتر به مرورگرهای پشتیبانینشده اضافه نمیشود و Babel همچنان نحو خاصی را برای آنها ترجمه مبدأ به مبدأ میکند. اگر کاربران از این مرورگر برای دسترسی به سایت شما استفاده نکنند، این کار اندازه بسته شما را بیدلیل افزایش میدهد.
Babel همچنین فهرستی از افزونههای تبدیل استفادهشده را ثبت میکند:

فهرست خیلی طولانیای است! اینها همه افزایههایی هستند که Babel باید برای تبدیل هر نحو ES2015+ به نحو قدیمیتر برای همه مرورگرهای هدف استفاده کند.
بااینحال، Babel هیچ شبیهساز مرورگر خاصی را که استفاده میشود نشان نمیدهد:
![]()
دلیلش این است که کل @babel/polyfill مستقیماً وارد میشود.
بار کردن شبیهسازهای مرورگر بهصورت جداگانه
بهطور پیشفرض، Babel شامل هر شبیهساز مرورگر موردنیاز برای محیط کامل ES2015+ است وقتی
@babel/polyfill به فایل وارد میشود. برای وارد کردن شبیهسازهای مرورگر خاص موردنیاز برای
مرورگرهای هدف، useBuiltIns: 'entry' را به پیکربندی اضافه کنید.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
برنامه را مجدداً بار کنید. اکنون میتوانید همه شبیهسازهای مرورگر خاص گنجاندهشده را ببینید:

اگرچه اکنون فقط شبیهسازهای مرورگر موردنیاز برای "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";
اکنون فقط شبیهسازهای مرورگر موردنیاز برای برنامه گنجانده شده است.

اندازه دسته برنامه بهطور قابلتوجهی کاهش مییابد.

محدود کردن فهرست مرورگرهای پشتیبانیشده
تعداد مرورگرهای هدف گنجاندهشده هنوز بسیار زیاد است و کاربران زیادی از مرورگرهای متوقفشده مانند 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"> استفاده کنید
هنوز جای بیشتری برای بهبود وجود دارد. اگرچه تعدادی از شبیهسازهای مرورگر استفادهنشده برداشته شدهاند، اما بسیاری از آنها که ارسال میشوند برای برخیاز مرورگرها لازم نیستند. بااستفاده از ماژولها، نحو جدیدتر را میتوان نوشت و مستقیماً بدون استفاده از هیچ شبیهساز مرورگر غیرضروری به مرورگرها ارسال کرد.
مدولهای جاوا اسکریپت ویژگی نسبتاً جدیدی هستند که در همه مرورگرهای اصلی پشتیبانی میشوند.
میتوان بااستفاده از مشخصه 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 درحالحاضر در محیطهایی که از مدولهای جاوا اسکریپت پشتیبانی میکنند (بهجای نیاز به 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
شامل همه تبدیلها و شبیهسازهای مرورگر لازم برای هدف قرار دادن هر مرورگری است که
هنوز از ماژولهای 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" را نادیده میگیرند. یعنی میتوانید یک ماژول و همچنین یک نسخه پشتیبان کامپایلشده را اضافه کنید. در حالت ایدهآل، دو نسخه برنامه باید در 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 نهایی را ایجاد میکند، HTMLWebpackPlugin، درحالحاضر از خروجی هر دو اسکریپت module و nomodule پشتیبانی نمیکند. اگرچه راهحلهای موقت و افزونههای جداگانهای برای حل این مشکل ایجاد شدهاند، مانند BabelMultiTargetPlugin و HTMLWebpackMultiBuildPlugin، اما برای این آموزش از رویکرد سادهتر افزودن دستی عنصر اسکریپت ماژول استفاده میشود.
موارد زیر را به src/index.js در انتهای فایل اضافه کنید:
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
اکنون برنامه را در مرورگری که از ماژولها پشتیبانی میکند، مانند جدیدترین نسخه Chrome، بار کنید.

فقط ماژول واکشی میشود، با اندازه بسته بسیار کوچکتر بهدلیل اینکه تا حد زیادی ترجمه نشده است! عنصر اسکریپت دیگر کاملاً توسط مرورگر نادیده گرفته میشود.
اگر برنامه را در مرورگر قدیمیتری بار کنید، فقط اسکریپت بزرگتر و ترجمه مبدأ به مبدأ شده با همه شبیهسازهای مرورگر و تبدیلهای موردنیاز واکشی میشود. در اینجا نماگرفتی از همه درخواستهای انجامشده در نسخه قدیمیتر Chrome (نسخه ۳۸) آورده شده است.

نتیجهگیری
اکنون میدانید چگونه از @babel/preset-env استفاده کنید تا فقط شبیهسازهای مرورگر لازم را که برای مرورگرهای هدف موردنیاز است ارائه دهید. همچنین میدانید که چگونه ماژولهای جاوا اسکریپت
میتوانند با ارسال دو نسخه ترجمه مبدأ به مبدأ متفاوت از یک
برنامه، عملکرد را بیشتر بهبود بخشند. با درک مناسبی از اینکه چگونه هر دو این تکنیکها میتوانند اندازه بسته شما را بهطور قابلتوجهی کاهش دهند، پیش بروید و بهینهسازی کنید!