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

چگونه از وب پک برای کوچک کردن برنامه خود تا حد امکان استفاده کنید

یکی از اولین کارهایی که هنگام بهینه سازی یک برنامه باید انجام دهید این است که آن را تا حد امکان کوچک کنید. در اینجا نحوه انجام این کار با وب پک آورده شده است.

از حالت تولید استفاده کنید (فقط بسته وب 4)

Webpack 4 پرچم mode جدید را معرفی کرد. می‌توانید این پرچم را روی 'development' یا 'production' تنظیم کنید تا به پک وب اشاره کنید که برنامه را برای یک محیط خاص می‌سازید:

// webpack.config.js
module.exports = {
  mode: 'production',
};

هنگامی که برنامه خود را برای تولید می‌سازید، مطمئن شوید که حالت production را فعال کنید. این باعث می‌شود وب‌پک بهینه‌سازی‌هایی مانند کوچک‌سازی، حذف کدهای توسعه‌دهنده در کتابخانه‌ها و موارد دیگر را اعمال کند.

بیشتر خواندن

کوچک سازی را فعال کنید

کوچک‌سازی زمانی است که کد را با حذف فضاهای اضافی، کوتاه کردن نام متغیرها و غیره فشرده می‌کنید. مثل این:

// Original code
function map(array, iteratee) {
  let index = -1;
  const length = array == null ? 0 : array.length;
  const result = new Array(length);

  while (++index < length) {
    result[index] = iteratee(array[index], index, array);
  }
  return result;
}

// Minified code
function map(n,r){let t=-1;for(const a=null==n?0:n.length,l=Array(a);++t<a;)l[t]=r(n[t],t,n);return l}

Webpack از دو روش برای کوچک‌سازی کد پشتیبانی می‌کند: گزینه‌های کوچک‌سازی در سطح بسته و گزینه‌های خاص لودر . آنها باید به طور همزمان استفاده شوند.

کوچک سازی در سطح بسته

کوچک سازی سطح بسته نرم افزاری، کل بسته نرم افزاری را پس از کامپایل فشرده می کند. در اینجا نحوه کار آن آمده است:

  1. شما کدهای زیر را می نویسید:

    // comments.js
    import './comments.css';
    export function render(data, target) {
      console.log('Rendered!');
    }
    
  2. Webpack آن را تقریباً به موارد زیر کامپایل می کند:

    // bundle.js (part of)
    "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    /* harmony export (immutable) */ __webpack_exports__["render"] = render;
    /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css__ = __webpack_require__(1);
    /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css_js___default =
    __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__comments_css__);
    
    function render(data, target) {
    console.log('Rendered!');
    }
    
  3. یک مینی‌فایر آن را تقریباً به شکل زیر فشرده می‌کند:

    // minified bundle.js (part of)
    "use strict";function t(e,n){console.log("Rendered!")}
    Object.defineProperty(n,"__esModule",{value:!0}),n.render=t;var o=r(1);r.n(o)
    

در وب پک 4، کوچک‌سازی سطح بسته‌ای به طور خودکار فعال می‌شود - هم در حالت تولید و هم بدون آن. از مینی‌فایر UglifyJS در زیر کاپوت استفاده می‌کند. (اگر نیاز به غیرفعال کردن minification دارید، فقط از حالت توسعه استفاده کنید یا false به گزینه optimization.minimize ارسال کنید.)

در وب پک 3، باید مستقیماً از افزونه UglifyJS استفاده کنید. این افزونه همراه با بسته وب ارائه می شود. برای فعال کردن آن، آن را به بخش plugins پیکربندی اضافه کنید:

// webpack.config.js
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
  ],
};

گزینه های خاص لودر

راه دوم برای کوچک کردن کد، گزینه های خاص لودر است ( لودر چیست ). با گزینه‌های لودر، می‌توانید مواردی را که مینی‌فایر نمی‌تواند کوچک‌سازی کند، فشرده کنید. به عنوان مثال، هنگامی که یک فایل CSS را با css-loader وارد می کنید، فایل به یک رشته کامپایل می شود:

/* comments.css */
.comment {
  color: black;
}
// minified bundle.js (part of)
exports=module.exports=__webpack_require__(1)(),
exports.push([module.i,".comment {\r\n  color: black;\r\n}",""]);

Minifier نمی تواند این کد را فشرده کند زیرا یک رشته است. برای کوچک کردن محتوای فایل، باید لودر را برای انجام این کار پیکربندی کنیم:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { minimize: true } },
        ],
      },
    ],
  },
};

بیشتر خواندن

NODE_ENV=production را مشخص کنید

راه دیگر برای کاهش اندازه جلویی این است که متغیر محیطی NODE_ENV را در کد خود روی مقدار production تنظیم کنید.

کتابخانه ها متغیر NODE_ENV را می خوانند تا تشخیص دهند که در کدام حالت باید کار کنند - در توسعه یا تولید. برخی از کتابخانه ها بر اساس این متغیر رفتار متفاوتی دارند. به عنوان مثال، زمانی که NODE_ENV روی production تنظیم نشده باشد، Vue.js بررسی های اضافی انجام می دهد و هشدارها را چاپ می کند:

// vue/dist/vue.runtime.esm.js
// …
if (process.env.NODE_ENV !== 'production') {
  warn('props must be strings when using array syntax.');
}
// …

React به طور مشابه کار می کند - یک ساخت توسعه را بارگیری می کند که شامل هشدارها است:

// react/index.js
if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

// react/cjs/react.development.js
// …
warning$3(
    componentClass.getDefaultProps.isReactClassApproved,
    'getDefaultProps is only used on classic React.createClass ' +
    'definitions. Use a static property named `defaultProps` instead.'
);
// …

این گونه بررسی ها و هشدارها معمولاً در تولید غیر ضروری هستند، اما در کد باقی می مانند و حجم کتابخانه را افزایش می دهند. در وب پک 4، آنها را با افزودن گزینه optimization.nodeEnv: 'production' حذف کنید:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    nodeEnv: 'production',
    minimize: true,
  },
};

در وب پک 3، به جای آن از DefinePlugin استفاده کنید:

// webpack.config.js (for webpack 3)
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': '"production"'
    }),
    new webpack.optimize.UglifyJsPlugin()
  ]
};

هر دو گزینه optimization.nodeEnv و DefinePlugin به یک شکل کار می کنند - آنها همه رخدادهای process.env.NODE_ENV را با مقدار مشخص شده جایگزین می کنند. با کانفیگ بالا:

  1. Webpack همه موارد مربوط به process.env.NODE_ENV را با "production" جایگزین می کند:

    // vue/dist/vue.runtime.esm.js
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    } else if (process.env.NODE_ENV !== 'production') {
      warn('props must be strings when using array syntax.');
    }
    

    // vue/dist/vue.runtime.esm.js
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    } else if ("production" !== 'production') {
      warn('props must be strings when using array syntax.');
    }
    
  2. و سپس مینی‌فایر تمام if شاخه‌ها را حذف می‌کند - زیرا "production" !== 'production' همیشه نادرست است، و افزونه می‌داند که کد داخل این شاخه‌ها هرگز اجرا نمی‌شود:

    // vue/dist/vue.runtime.esm.js
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    } else if ("production" !== 'production') {
      warn('props must be strings when using array syntax.');
    }
    

    // vue/dist/vue.runtime.esm.js (without minification)
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    }
    

بیشتر خواندن

از ماژول های ES استفاده کنید

راه بعدی برای کاهش اندازه جلویی استفاده از ماژول‌های ES است.

وقتی از ماژول‌های ES استفاده می‌کنید، وب‌پک قادر به انجام تکان دادن درخت می‌شود. تکان دادن درخت زمانی است که یک باندلر کل درخت وابستگی را طی می کند، وابستگی هایی را که استفاده می شود بررسی می کند و وابستگی های استفاده نشده را حذف می کند. بنابراین، اگر از نحو ماژول ES استفاده می کنید، وب پک می تواند کدهای استفاده نشده را حذف کند:

  1. شما یک فایل با چندین صادرات می نویسید، اما برنامه فقط از یکی از آنها استفاده می کند:

    // comments.js
    export const render = () => { return 'Rendered!'; };
    export const commentRestEndpoint = '/rest/comments';
    
    // index.js
    import { render } from './comments.js';
    render();
    
  2. Webpack می داند که commentRestEndpoint استفاده نمی شود و یک نقطه صادرات جداگانه در بسته ایجاد نمی کند:

    // bundle.js (part that corresponds to comments.js)
    (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    const render = () => { return 'Rendered!'; };
    /* harmony export (immutable) */ __webpack_exports__["a"] = render;
    
    const commentRestEndpoint = '/rest/comments';
    /* unused harmony export commentRestEndpoint */
    })
    
  3. Minifier متغیر استفاده نشده را حذف می کند:

    // bundle.js (part that corresponds to comments.js)
    (function(n,e){"use strict";var r=function(){return"Rendered!"};e.b=r})
    

اگر کتابخانه ها با ماژول های ES نوشته شده باشند، این کار حتی با کتابخانه ها نیز کار می کند.

لازم نیست دقیقاً از مینی‌فایر داخلی پک ( UglifyJsPlugin ) استفاده کنید. هر کوچک کننده ای که از حذف کد مرده پشتیبانی می کند (به عنوان مثال افزونه Babel Minify یا افزونه Google Closure Compiler ) این کار را انجام می دهد.

بیشتر خواندن

بهینه سازی تصاویر

تصاویر بیش از نیمی از اندازه صفحه را تشکیل می دهند. در حالی که آنها به اندازه جاوا اسکریپت حیاتی نیستند (مثلاً رندر را مسدود نمی کنند)، هنوز هم بخش بزرگی از پهنای باند را می خورند. از url-loader ، svg-url-loader و image-webpack-loader برای بهینه سازی آنها در بسته وب استفاده کنید.

url-loader فایل های استاتیک کوچک را در برنامه قرار می دهد. بدون پیکربندی، یک فایل پاس شده را می گیرد، آن را در کنار بسته کامپایل شده قرار می دهد و URL آن فایل را برمی گرداند. اما اگر گزینه limit را مشخص کنیم، فایل های کوچکتر از این حد را به عنوان url داده Base64 کدگذاری می کند و این url را برمی گرداند. این تصویر را در کد جاوا اسکریپت قرار می دهد و یک درخواست HTTP را ذخیره می کند:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif)$/,
        loader: 'url-loader',
        options: {
          // Inline files smaller than 10 kB (10240 bytes)
          limit: 10 * 1024,
        },
      },
    ],
  }
};
// index.js
import imageUrl from './image.png';
// → If image.png is smaller than 10 kB, `imageUrl` will include
// the encoded image: 'data:image/png;base64,iVBORw0KGg…'
// → If image.png is larger than 10 kB, the loader will create a new file,
// and `imageUrl` will include its url: `/2fcd56a1920be.png`

svg-url-loader درست مانند url-loader کار می کند - با این تفاوت که فایل ها را به جای Base64 با کدگذاری URL کدگذاری می کند. این برای تصاویر SVG مفید است – از آنجا که فایل های SVG فقط یک متن ساده هستند، این رمزگذاری در اندازه موثرتر است.

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        loader: "svg-url-loader",
        options: {
          limit: 10 * 1024,
          noquotes: true
        }
      }
    ]
  }
};

image-webpack-loader تصاویری را که از آن عبور می کنند فشرده می کند. از تصاویر JPG، PNG، GIF و SVG پشتیبانی می کند، بنابراین ما قصد داریم از آن برای همه این انواع استفاده کنیم.

این لودر تصاویر را در برنامه جاسازی نمی کند، بنابراین باید به صورت جفت با url-loader و svg-url-loader کار کند. برای جلوگیری از کپی پیست کردن آن در هر دو قانون (یکی برای تصاویر JPG/PNG/GIF و دیگری برای تصاویر SVG)، این بارگذار را به عنوان یک قانون جداگانه با enforce: 'pre' :

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        loader: 'image-webpack-loader',
        // This will apply the loader before the other ones
        enforce: 'pre'
      }
    ]
  }
};

تنظیمات پیش‌فرض لودر در حال حاضر خوب است – اما اگر می‌خواهید آن را بیشتر پیکربندی کنید، گزینه‌های افزونه را ببینید. برای انتخاب گزینه هایی که باید مشخص کنید، راهنمای عالی Addy Osmani در مورد بهینه سازی تصویر را بررسی کنید.

بیشتر خواندن

بهینه سازی وابستگی ها

بیش از نیمی از اندازه متوسط ​​جاوا اسکریپت از وابستگی ها ناشی می شود و بخشی از آن اندازه ممکن است غیر ضروری باشد.

به عنوان مثال، Lodash (در نسخه 4.17.4) 72 کیلوبایت کد کوچک شده را به بسته اضافه می کند. اما اگر فقط از 20 روش آن استفاده کنید، تقریباً 65 کیلوبایت کد کوچک شده هیچ کاری انجام نمی دهد.

مثال دیگر Moment.js است. نسخه 2.19.1 آن 223 کیلوبایت کد کوچک می گیرد که بسیار زیاد است – متوسط ​​اندازه جاوا اسکریپت در یک صفحه در اکتبر 2017 452 کیلوبایت بود . با این حال، 170 کیلوبایت از آن اندازه فایل های محلی سازی هستند. اگر از Moment.js با چندین زبان استفاده نمی‌کنید، این فایل‌ها بدون هدف، بسته را پر می‌کنند.

همه این وابستگی ها را می توان به راحتی بهینه کرد. ما رویکردهای بهینه‌سازی را در یک مخزن GitHub جمع‌آوری کرده‌ایم – آن را بررسی کنید !

فعال کردن الحاق ماژول برای ماژول‌های ES (با نام مستعار scope hoisting)

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

// index.js
import {render} from './comments.js';
render();

// comments.js
export function render(data, target) {
  console.log('Rendered!');
}

// bundle.js (part  of)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
  var __WEBPACK_IMPORTED_MODULE_0__comments_js__ = __webpack_require__(1);
  Object(__WEBPACK_IMPORTED_MODULE_0__comments_js__["a" /* render */])();
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  __webpack_exports__["a"] = render;
  function render(data, target) {
    console.log('Rendered!');
  }
})

در گذشته، این مورد برای جداسازی ماژول های CommonJS/AMD از یکدیگر ضروری بود. با این حال، این یک سربار اندازه و عملکرد برای هر ماژول اضافه کرد.

Webpack 2 پشتیبانی از ماژول‌های ES را معرفی کرد که بر خلاف ماژول‌های CommonJS و AMD، می‌توانند بدون بسته‌بندی هر کدام با یک عملکرد همراه شوند. و webpack 3 چنین بسته‌بندی را امکان‌پذیر کرد - با الحاق ماژول . این چیزی است که الحاق ماژول انجام می دهد:

// index.js
import {render} from './comments.js';
render();

// comments.js
export function render(data, target) {
  console.log('Rendered!');
}

// Unlike the previous snippet, this bundle has only one module
// which includes the code from both files

// bundle.js (part of; compiled with ModuleConcatenationPlugin)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  Object.defineProperty(__webpack_exports__, "__esModule", { value: true });

  // CONCATENATED MODULE: ./comments.js
    function render(data, target) {
    console.log('Rendered!');
  }

  // CONCATENATED MODULE: ./index.js
  render();
})

تفاوت را ببین؟ در بسته نرم افزاری ساده، ماژول 0 به render از ماژول 1 نیاز داشت. با الحاق ماژول، require به سادگی با عملکرد مورد نیاز جایگزین می شود و ماژول 1 حذف می شود. بسته نرم افزاری ماژول های کمتری دارد - و سربار ماژول کمتری دارد!

برای روشن کردن این رفتار، در بسته وب 4 ، گزینه optimization.concatenateModules را فعال کنید:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    concatenateModules: true
  }
};

در بسته وب 3، از ModuleConcatenationPlugin استفاده کنید:

// webpack.config.js (for webpack 3)
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
};

بیشتر خواندن

اگر هم کد وب بسته و هم غیر بسته وب دارید از externals استفاده کنید

ممکن است پروژه بزرگی داشته باشید که در آن برخی از کدها با وب پک کامپایل می شوند و برخی کدها نه. مانند یک سایت میزبانی ویدیو، که ویجت پخش کننده ممکن است با بسته وب ساخته شود، و صفحه اطراف ممکن است نباشد:

اسکرین شات از یک سایت میزبان ویدیو
(یک سایت میزبانی ویدیو کاملا تصادفی)

اگر هر دو قطعه کد وابستگی های مشترکی دارند، می توانید آنها را به اشتراک بگذارید تا از دانلود چندباره کد آنها جلوگیری کنید. این کار با گزینه externals بسته وب انجام می شود - ماژول ها را با متغیرها یا سایر واردات خارجی جایگزین می کند.

اگر وابستگی ها در window موجود باشد

اگر کد غیر بسته وب شما متکی به وابستگی هایی است که به عنوان متغیر در window موجود است، نام مستعار وابستگی به نام متغیرها:

// webpack.config.js
module.exports = {
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM'
  }
};

با این پیکربندی، webpack بسته‌های react و react-dom را جمع نمی‌کند. در عوض، آنها با چیزی شبیه به این جایگزین می شوند:

// bundle.js (part of)
(function(module, exports) {
  // A module that exports `window.React`. Without `externals`,
  // this module would include the whole React bundle
  module.exports = React;
}),
(function(module, exports) {
  // A module that exports `window.ReactDOM`. Without `externals`,
  // this module would include the whole ReactDOM bundle
  module.exports = ReactDOM;
})

اگر وابستگی ها به عنوان بسته های AMD بارگذاری شوند

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

برای انجام این کار، کد وب بسته را به عنوان یک بسته AMD و ماژول های مستعار در URL های کتابخانه کامپایل کنید:

// webpack.config.js
module.exports = {
  output: {
    libraryTarget: 'amd'
  },
  externals: {
    'react': {
      amd: '/libraries/react.min.js'
    },
    'react-dom': {
      amd: '/libraries/react-dom.min.js'
    }
  }
};

Webpack بسته را در define() قرار می دهد و آن را به این URL ها وابسته می کند:

// bundle.js (beginning)
define(["/libraries/react.min.js", "/libraries/react-dom.min.js"], function () { … });

اگر کد غیر بسته وب از همان URL ها برای بارگیری وابستگی های خود استفاده کند، این فایل ها فقط یک بار بارگیری می شوند - درخواست های اضافی از حافظه پنهان بارگذار استفاده می کنند.

بیشتر خواندن

جمع بندی

  • اگر از webpack 4 استفاده می کنید، حالت تولید را فعال کنید
  • کد خود را با گزینه‌های مینی‌فایر و لودر در سطح بسته به حداقل برسانید
  • با جایگزین کردن NODE_ENV با production ، کد فقط توسعه را حذف کنید
  • از ماژول های ES برای فعال کردن تکان دادن درخت استفاده کنید
  • فشرده سازی تصاویر
  • بهینه سازی های وابستگی خاص را اعمال کنید
  • الحاق ماژول را فعال کنید
  • اگر این برای شما منطقی است از externals استفاده کنید