در این نرم افزار کد، عملکرد این برنامه ساده را بهبود بخشید که به کاربران اجازه می دهد به گربه های تصادفی امتیاز دهند. نحوه بهینه سازی بسته جاوا اسکریپت را با کوچک کردن مقدار کد ترانویسی شده بیاموزید.
در برنامه نمونه، میتوانید یک کلمه یا شکلک انتخاب کنید تا میزان علاقهتان به هر گربه را نشان دهد. وقتی روی دکمه ای کلیک می کنید، برنامه مقدار دکمه را در زیر تصویر گربه فعلی نمایش می دهد.
اندازه گیری کنید
همیشه ایده خوبی است که قبل از افزودن هر گونه بهینه سازی، یک وب سایت را بررسی کنید:
- برای پیش نمایش سایت، View App را فشار دهید. سپس تمام صفحه را فشار دهید .
- «Control+Shift+J» (یا «Command+Option+J» در Mac) را فشار دهید تا DevTools باز شود.
- روی تب Network کلیک کنید.
- چک باکس Disable cache را انتخاب کنید.
- برنامه را دوباره بارگیری کنید.
بیش از 80 کیلوبایت برای این برنامه استفاده شده است! زمان آن است که بفهمیم آیا از قطعات بسته استفاده نمی شود:
Control+Shift+P
(یاCommand+Shift+P
در مک) را فشار دهید تا منوی Command باز شود.وارد
Show Coverage
وEnter
بزنید تا تب Coverage نمایش داده شود.در تب Coverage ، روی Reload کلیک کنید تا برنامه در حین گرفتن پوشش مجدد بارگیری شود.
به مقدار کد استفاده شده در مقابل مقدار بارگذاری شده برای بسته اصلی نگاهی بیندازید:
بیش از نیمی از بسته نرم افزاری (44 کیلوبایت) حتی استفاده نمی شود. این به این دلیل است که بسیاری از کدهای داخل شامل polyfills هستند تا اطمینان حاصل شود که برنامه در مرورگرهای قدیمی کار می کند.
از @babel/preset-env استفاده کنید
نحو زبان جاوا اسکریپت با استانداردی به نام ECMAScript یا ECMA-262 مطابقت دارد. نسخه های جدیدتر مشخصات هر سال منتشر می شود و شامل ویژگی های جدیدی است که پروسه پیشنهاد را پشت سر گذاشته است. هر مرورگر اصلی همیشه در مرحله متفاوتی از پشتیبانی از این ویژگی ها است.
ویژگی های ES2015 زیر در برنامه استفاده می شود:
از ویژگی ES2017 زیر نیز استفاده می شود:
با خیال راحت وارد کد منبع در src/index.js
شوید تا ببینید چگونه از همه اینها استفاده می شود.
همه این ویژگیها در آخرین نسخه کروم پشتیبانی میشوند، اما مرورگرهای دیگری که از آنها پشتیبانی نمیکنند چطور؟ Babel ، که در برنامه گنجانده شده است، محبوب ترین کتابخانه ای است که برای کامپایل کدهایی استفاده می شود که حاوی نحو جدیدتر به کدهایی است که مرورگرها و محیط های قدیمی می توانند آن را درک کنند. این کار را به دو صورت انجام می دهد:
- Polyfills برای تقلید توابع جدیدتر ES2015+ گنجانده شده است تا APIهای آنها حتی اگر توسط مرورگر پشتیبانی نشود قابل استفاده باشند. در اینجا یک مثال از یک 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.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
مشخص میکند که کدام تبدیلها و polyfillها برای هر مرورگر یا محیطی که بهعنوان هدف انتخاب شدهاند، ضروری هستند.
به فایل تنظیمات Babel، .babelrc
. نگاهی بیندازید تا ببینید چگونه شامل می شود:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
این یک راه اندازی بابل و بسته وب است. در صورت استفاده از بستهبندی ماژول متفاوت از وبپک، یاد بگیرید که چگونه Babel را در برنامه خود قرار دهید .
ویژگی targets
در .babelrc
مشخص می کند که کدام مرورگرها هدف قرار می گیرند. @babel/preset-env
با فهرست مرورگرها ادغام میشود، به این معنی که میتوانید فهرست کاملی از جستارهای سازگار را که میتوان در این زمینه در مستندات فهرست مرورگر استفاده کرد، بیابید.
مقدار "last 2 versions"
کد موجود در برنامه را برای دو نسخه آخر هر مرورگر انتقال می دهد.
اشکال زدایی
برای مشاهده کامل تمام اهداف Babel مرورگر و همچنین همه تبدیلها و polyfillهای موجود، یک فیلد debug
را به .babelrc:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
}
]
]
}
- روی Tools کلیک کنید.
- روی Logs کلیک کنید.
برنامه را دوباره بارگیری کنید و به گزارش های وضعیت اشکال در پایین ویرایشگر نگاهی بیندازید.
مرورگرهای هدفمند
Babel تعدادی از جزئیات را در مورد فرآیند کامپایل در کنسول ثبت می کند، از جمله تمام محیط های هدف که کد برای آنها کامپایل شده است.
توجه داشته باشید که چگونه مرورگرهای متوقف شده، مانند اینترنت اکسپلورر، در این لیست گنجانده شده است. این یک مشکل است زیرا مرورگرهای پشتیبانینشده ویژگیهای جدیدتری اضافه نمیکنند، و Babel به ترجمه دستور خاصی برای آنها ادامه میدهد. در صورتی که کاربران از این مرورگر برای دسترسی به سایت شما استفاده نمی کنند، این به طور غیر ضروری اندازه بسته شما را افزایش می دهد.
Babel همچنین لیستی از افزونه های تبدیل استفاده شده را ثبت می کند:
این یک لیست نسبتا طولانی است! اینها همه افزونههایی هستند که Babel برای تبدیل هر نحو ES2015+ به نحو قدیمیتر برای همه مرورگرهای مورد نظر باید استفاده کند.
با این حال، Babel هیچ پلی پر خاصی را که استفاده می شود نشان نمی دهد:
این به این دلیل است که کل @babel/polyfill
مستقیماً وارد میشود.
پلی فیل ها را به صورت جداگانه بارگیری کنید
بهطور پیشفرض، Babel شامل تمام پلیفیلهای مورد نیاز برای یک محیط کامل ES2015+ میشود که @babel/polyfill
به یک فایل وارد میشود. برای وارد کردن polyfill های خاص مورد نیاز برای مرورگرهای هدف، یک useBuiltIns: 'entry'
را به پیکربندی اضافه کنید.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
برنامه را دوباره بارگیری کنید. اکنون می توانید تمام پلی فیل های خاص را مشاهده کنید:
اگرچه اکنون فقط پلیفیلهای مورد نیاز برای "last 2 versions"
گنجانده شده است، اما هنوز یک لیست فوق العاده طولانی است! این به این دلیل است که polyfill های مورد نیاز برای مرورگرهای هدف برای هر ویژگی جدیدتر هنوز هم گنجانده شده است. مقدار مشخصه را به usage
تغییر دهید تا فقط موارد مورد نیاز برای ویژگی هایی را که در کد استفاده می شود شامل شود.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true,
"useBuiltIns": "entry"
"useBuiltIns": "usage"
}
]
]
}
با این کار، پلیفیلها بهطور خودکار در صورت نیاز گنجانده میشوند. این به این معنی است که می توانید @babel/polyfill
import را در src/index.js.
import "./style.css";
import "@babel/polyfill";
اکنون فقط پلی پرهای مورد نیاز برای برنامه گنجانده شده است.
اندازه بسته نرم افزاری به طور قابل توجهی کاهش می یابد.
محدود کردن لیست مرورگرهای پشتیبانی شده
تعداد اهداف مرورگر گنجانده شده هنوز بسیار زیاد است و کاربران زیادی از مرورگرهای متوقف شده مانند اینترنت اکسپلورر استفاده نمی کنند. تنظیمات را به موارد زیر به روز کنید:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"targets": [">0.25%", "not ie 11"],
"debug": true,
"useBuiltIns": "usage",
}
]
]
}
به جزئیات بسته واکشی شده نگاهی بیندازید.
از آنجایی که برنامه بسیار کوچک است، واقعاً تفاوت زیادی با این تغییرات وجود ندارد. با این حال، استفاده از درصد سهم بازار مرورگر (مانند ">0.25%"
) همراه با حذف مرورگرهای خاصی که مطمئن هستید کاربران شما از آنها استفاده نمی کنند، رویکرد توصیه شده است. برای کسب اطلاعات بیشتر در مورد این، به مقاله "2 نسخه آخر" که توسط جیمز کایل مضر در نظر گرفته شده است، نگاهی بیندازید.
از <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).
- نسخهای که در مرورگرهای جدیدتر که از ماژولها پشتیبانی میکنند کار میکند و شامل ماژولی است که تا حد زیادی ترجمه نشده است اما اندازه فایل کوچکتری دارد.
- نسخه ای که شامل یک اسکریپت بزرگتر و ترجمه شده است که در هر مرورگر قدیمی کار می کند
استفاده از ماژول های ES با Babel
برای داشتن تنظیمات جداگانه @babel/preset-env
برای دو نسخه برنامه، فایل .babelrc
را حذف کنید. تنظیمات Babel را می توان با تعیین دو فرمت کامپایل متفاوت برای هر نسخه از برنامه به پیکربندی بسته وب اضافه کرد.
با افزودن یک پیکربندی برای اسکریپت قدیمی به 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
در اینجا روی 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 است. متأسفانه، افزونه ای که فایل HTML نهایی را ایجاد می کند، HTMLWebpackPlugin
، در حال حاضر خروجی اسکریپت های ماژول و نومول را پشتیبانی نمی کند . اگرچه راهحلها و پلاگینهای جداگانهای برای حل این مشکل ایجاد شدهاند، مانند BabelMultiTargetPlugin و HTMLWebpackMultiBuildPlugin ، یک رویکرد سادهتر برای افزودن عنصر اسکریپت ماژول به صورت دستی برای هدف این آموزش استفاده میشود.
موارد زیر را در انتهای فایل به src/index.js
اضافه کنید:
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
اکنون برنامه را در مرورگری بارگذاری کنید که از ماژول ها پشتیبانی می کند، مانند آخرین نسخه کروم.
فقط ماژول واکشی شده است، با اندازه بسته نرم افزاری بسیار کوچکتر به دلیل اینکه تا حد زیادی ترانسفورم نشده است! عنصر دیگر اسکریپت به طور کامل توسط مرورگر نادیده گرفته می شود.
اگر برنامه را بر روی یک مرورگر قدیمی بارگیری کنید، فقط اسکریپت بزرگتر و ترجمه شده با تمام پلی پرها و تبدیل های مورد نیاز واکشی می شود. در اینجا یک اسکرین شات برای همه درخواستهای انجام شده در نسخه قدیمی کروم (نسخه 38) آمده است.
نتیجه گیری
اکنون میدانید که چگونه از @babel/preset-env
استفاده کنید تا فقط پلیفیلهای لازم برای مرورگرهای هدف را فراهم کنید. همچنین میدانید که چگونه ماژولهای جاوا اسکریپت میتوانند با ارسال دو نسخه مختلف از یک برنامه کاربردی، عملکرد را بیشتر بهبود بخشند. با درک درستی از اینکه چگونه هر دو این تکنیک ها می توانند اندازه بسته شما را به میزان قابل توجهی کاهش دهند، پیش بروید و بهینه سازی کنید!