کدهای مدرن را به مرورگرهای مدرن برای بارگذاری سریعتر صفحه ارائه دهید

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

اسکرین شات برنامه

در برنامه نمونه، می‌توانید یک کلمه یا شکلک انتخاب کنید تا میزان علاقه‌تان به هر گربه را نشان دهد. وقتی روی دکمه ای کلیک می کنید، برنامه مقدار دکمه را در زیر تصویر گربه فعلی نمایش می دهد.

اندازه گرفتن

همیشه ایده خوبی است که قبل از افزودن هر گونه بهینه سازی، یک وب سایت را بررسی کنید:

  1. برای پیش نمایش سایت، View App را فشار دهید. سپس تمام صفحه را فشار دهید تمام صفحه .
  2. «Control+Shift+J» (یا «Command+Option+J» در Mac) را فشار دهید تا DevTools باز شود.
  3. روی تب Network کلیک کنید.
  4. چک باکس Disable cache را انتخاب کنید.
  5. برنامه را دوباره بارگیری کنید.

درخواست اندازه باندل اصلی

بیش از 80 کیلوبایت برای این برنامه استفاده شده است! زمان آن است که بفهمیم آیا از قطعات بسته استفاده نمی شود:

  1. Control+Shift+P (یا Command+Shift+P در مک) را فشار دهید تا منوی Command باز شود. منوی فرمان

  2. وارد Show Coverage و Enter بزنید تا تب Coverage نمایش داده شود.

  3. در تب Coverage ، روی Reload کلیک کنید تا برنامه در حین گرفتن پوشش مجدد بارگیری شود.

    بارگیری مجدد برنامه با پوشش کد

  4. به مقدار کد استفاده شده در مقابل مقدار بارگذاری شده برای بسته اصلی نگاهی بیندازید:

    پوشش کد بسته نرم افزاری

بیش از نیمی از بسته نرم افزاری (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";

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

لیست پلی فیل ها به طور خودکار گنجانده شده است

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

حجم بسته به 30.1 کیلوبایت کاهش یافت

محدود کردن لیست مرورگرهای پشتیبانی شده

تعداد اهداف مرورگر گنجانده شده هنوز بسیار زیاد است و کاربران زیادی از مرورگرهای متوقف شده مانند اینترنت اکسپلورر استفاده نمی کنند. تنظیمات را به موارد زیر به روز کنید:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "targets": [">0.25%", "not ie 11"],
        "debug": true,
        "useBuiltIns": "usage",
      }
    ]
  ]
}

به جزئیات بسته واکشی شده نگاهی بیندازید.

حجم بسته 30.0 کیلوبایت

از آنجایی که برنامه بسیار کوچک است، واقعاً تفاوت زیادی با این تغییرات وجود ندارد. با این حال، استفاده از درصد سهم بازار مرورگر (مانند ">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 ، در حال حاضر خروجی اسکریپت‌های ماژول و nomodule را پشتیبانی نمی‌کند . اگرچه راه‌حل‌ها و پلاگین‌های جداگانه‌ای برای حل این مشکل ایجاد شده‌اند، مانند BabelMultiTargetPlugin و HTMLWebpackMultiBuildPlugin ، یک رویکرد ساده‌تر برای افزودن عنصر اسکریپت ماژول به صورت دستی برای هدف این آموزش استفاده می‌شود.

موارد زیر را در انتهای فایل به src/index.js اضافه کنید:

    ...
    </form>
    <script type="module" src="main.mjs"></script>
  </body>
</html>

اکنون برنامه را در مرورگری بارگذاری کنید که از ماژول ها پشتیبانی می کند، مانند آخرین نسخه کروم.

ماژول 5.2 کیلوبایتی از طریق شبکه برای مرورگرهای جدیدتر واکشی شد

فقط ماژول واکشی شده است، با اندازه بسته نرم افزاری بسیار کوچکتر به دلیل اینکه تا حد زیادی ترانسفورم نشده است! عنصر دیگر اسکریپت به طور کامل توسط مرورگر نادیده گرفته می شود.

اگر برنامه را بر روی یک مرورگر قدیمی بارگیری کنید، فقط اسکریپت بزرگتر و ترجمه شده با تمام پلی پرها و تبدیل های مورد نیاز واکشی می شود. در اینجا یک اسکرین شات برای همه درخواست‌های انجام شده در نسخه قدیمی کروم (نسخه 38) آمده است.

اسکریپت 30 کیلوبایتی برای مرورگرهای قدیمی‌تر واکشی شد

نتیجه

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