تقليل حجم الواجهة الأمامية

كيفية استخدام حزمة الويب لتصغير حجم تطبيقك قدر الإمكان

أحد الأشياء الأولى التي يجب القيام بها عند تحسين التطبيق هو جعله صغيرًا مثل ممكن. في ما يلي كيفية إجراء ذلك باستخدام webpack.

استخدام وضع الإنتاج (Webpack 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 المصغّرة. خلف الستار. (إذا احتجت في أي وقت إلى إيقاف التصغير، ما عليك سوى استخدام وضع التطوير أو تمرير false إلى الخيار optimization.minimize).

في Webpack 3، عليك استخدام المكون الإضافي UglifyJS. مباشرةً. يأتي المكوّن الإضافي مرفقًا مع webpack؛ لتفعيلها، يجب إضافتها إلى 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}",""]);

لا يمكن للصغير ضغط هذا الرمز لأنه سلسلة. لتصغير محتوى الملف، نحتاج إلى قم بتهيئة برنامج التحميل للقيام بذلك:

// 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.'
);
// …

عادةً ما تكون عمليات التحقق والتحذيرات هذه غير ضرورية في مرحلة الإنتاج، ولكنها تظل ضمن التعليمات البرمجية وزيادة حجم المكتبة. في Webpack 4، يمكنك إزالتها عن طريق إضافة الخيار optimization.nodeEnv: 'production':

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

في Webpack 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، يصبح بإمكان webpack إجراء اهتزاز في الشجرة. يحدث اهتزاز الشجرة عندما يقوم مجمع يجتاز شجرة التبعية بأكملها، ويتحقق من التبعيات المستخدمة، ويزيل التبعيات غير المستخدمة. وبالتالي، إذا كنت تستخدم بنية وحدة 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. يزيل الرمز المصغّر المتغيّر غير المستخدَم:

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

يمكن إجراء ذلك حتى مع المكتبات إذا كانت مكتوبة باستخدام وحدات باللغة الإسبانية.

ومع ذلك، لا يتوجّب عليك استخدام المتصفّح المصغَّر المُدمَج في webpack بدقة (UglifyJsPlugin). أي مصغر يتيح إزالة الرمز المميت (على سبيل المثال، المكوّن الإضافي Babel Miniify أو المكوّن الإضافي Google Closure Compiler) سينفّذ الخدعة.

محتوى إضافي للقراءة

تحسين الصور

تمثل الصور أكثر من نصف حجم الصفحة خلال هذه الفترة لا تمثل أهمية كبيرة لـ JavaScript (على سبيل المثال، لا تحظر العرض)، ولكنها تأكل جزءًا كبيرًا من النطاق الترددي. استخدام url-loader وsvg-url-loader وimage-webpack-loader لتحسينها في webpack.

يضمِّن url-loader ملفات ثابتة صغيرة في التطبيق. بدون ضبط، يتم نقل ملف تم تمريره ووضعه بجانب الحزمة المجمّعة وإرجاعه عنوان url لهذا الملف. وإذا حدّدنا الخيار limit، سيتم ترميز الملفات الأصغر حجمًا من هذا الحد باعتباره عنوان url للبيانات Base64 ويعرض عنوان url هذا. هذا النمط يضمّن الصورة في رمز JavaScript ويحفظ طلب 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 – باستثناء أنّه يتم ترميز الملفات باستخدام عنوان URL الترميز بدلاً من Base64 واحد. يفيد ذلك في صور 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، والأخرى للصور بتنسيق 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'
      }
    ]
  }
};

إنّ الإعدادات التلقائية لأداة التحميل جاهزة للاستخدام، ولكن إذا كنت تريد ضبطها. يمكنك الاطّلاع على خيارات المكوّنات الإضافية. إلى حدِّد الخيارات التي تريد تحديدها، يمكنك الاطّلاع على دليل "آدي عثماني" الممتاز حول الصور التحسين.

محتوى إضافي للقراءة

تحسين التبعيات

يأتي أكثر من نصف متوسط حجم JavaScript من التبعيات، وقد يكون جزء من هذا الحجم غير ضرورية.

على سبيل المثال، تضيف Lodash (اعتبارًا من الإصدار 4.17.4) من الرمز البرمجي الأصغر حجمًا 72 كيلوبايت إلى الحزمة. ولكن إذا كنت تستخدم فقط، مثل 20 طريقة منها، ثم ما يقرب من 65 كيلوبايت من الرمز المصغَّر لا يفعل أي شيء.

ومثال آخر على ذلك هو Moment.js. يتطلب إصداره 2.19.1 حجم 223 كيلوبايت من الرموز المصغَّرة، وهي عبارة عن رمز ضخم للغاية متوسط حجم JavaScript على الصفحة كان 452 كيلوبايت في تشرين الأول (أكتوبر) 2017 ومع ذلك، يجب أن يكون لديك 170 كيلوبايت من هذا الحجم هو الأقلمة الملفات. في حال حذف كنت لا تستخدم Moment.js مع لغات متعددة، فإن هذه الملفات ستزيد من حجم الحزمة بدون الغرض.

يمكن تحسين كل هذه التبعيات بسهولة. لقد جمعنا أساليب التحسين في مستودع GitHub – يُرجى الاطّلاع عليه.

تفعيل تسلسل الوحدات لوحدات ES (المعروفة أيضًا برفع النطاق)

عند إنشاء حزمة، تعمل webpack على التفاف كل وحدة في دالة:

// 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 مع وحدات اللغة الإسبانية التي يمكن تجميعها على عكس وحدات 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
  }
};

في Webpack 3، استخدِم ModuleConcatenationPlugin:

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

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

محتوى إضافي للقراءة

يمكنك استخدام externals إذا كان لديك رمز حزمة ويب ورمز غير حزمة الويب.

قد يكون لديك مشروع كبير يتم فيه تجميع بعض الرموز باستخدام webpack، في حين يتم تجميع بعض الرموز البرمجية. أعجبني موقعًا لاستضافة الفيديو، حيث يمكن إنشاء أداة المشغّل باستخدام حزمة الويب، والصفحة المحيطة قد لا يكون:

لقطة شاشة لموقع إلكتروني لاستضافة الفيديوهات
(موقع إلكتروني عشوائي تمامًا لاستضافة الفيديوهات)

إذا كان لكل من جزأين من التعليمات البرمجية اعتماديات مشتركة، يمكنك مشاركتها لتجنب تنزيل الرمز الخاص بهما عدة مرات. ويتم ذلك باستخدام externals من حزمة الويب. – تستبدل الوحدات بمتغيرات أو استيرادات خارجية أخرى.

في حال توفُّر الاعتماديات في window

إذا كان الرمز الذي ليس ضِمن حزمة الويب يعتمد على عناصر تابعة متاحة كمتغيّرات في window، يتم استخدام الاسم المستعار أسماء التبعية لأسماء المتغيرات:

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

باستخدام هذه الإعدادات، لن تجمع حزمة الويب حزمة 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.

ولإجراء ذلك، عليك تجميع رمز Webpack كحزمة 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
  • استخدام وحدات اللغة الإسبانية لتفعيل اهتزاز الشجرة
  • ضغط الصور
  • تطبيق التحسينات المتعلّقة بالتبعية
  • تفعيل تسلسل الوحدات
  • يُرجى استخدام externals إذا كان ذلك يناسبك.