از کش طولانی مدت استفاده کنید

چگونه وب پک به ذخیره دارایی کمک می کند

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

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

روش رایج انجام ذخیره سازی به این صورت است:

  1. به مرورگر بگویید که یک فایل را برای مدت بسیار طولانی (مثلاً یک سال) کش کند:

    # Server header
    Cache-Control: max-age=31536000
    

    اگر با کارهایی که Cache-Control انجام می دهد آشنا نیستید، به پست عالی جیک آرچیبالد در مورد بهترین شیوه های ذخیره سازی کش مراجعه کنید.

  2. و پس از تغییر نام فایل را تغییر دهید تا بارگیری مجدد اجباری شود:

    <!-- Before the change -->
    <script src="./index-v15.js"></script>
    
    <!-- After the change -->
    <script src="./index-v16.js"></script>
    

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

با webpack هم همین کار را می کنید، اما به جای شماره نسخه، هش فایل را مشخص می کنید. برای گنجاندن هش در نام فایل، از [chunkhash] استفاده کنید:

// webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
  }
};

اگر به نام فایل برای ارسال آن به مشتری نیاز دارید، از HtmlWebpackPlugin یا WebpackManifestPlugin استفاده کنید.

HtmlWebpackPlugin یک رویکرد ساده، اما کمتر انعطاف پذیر است. در طول کامپایل، این افزونه یک فایل HTML تولید می کند که شامل تمام منابع کامپایل شده است. اگر منطق سرور شما پیچیده نیست، باید برای شما کافی باشد:

<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>

WebpackManifestPlugin یک رویکرد انعطاف پذیرتر است که در صورت داشتن یک بخش سرور پیچیده مفید است. در طول ساخت، یک فایل JSON با نگاشت بین نام فایل های بدون هش و نام فایل ها با هش تولید می کند. از این JSON در سرور استفاده کنید تا بفهمید با کدام فایل کار کنید:

// manifest.json
{
  "bundle.js": "bundle.8e0d62a03.js"
}

در ادامه مطلب

وابستگی ها و زمان اجرا را در یک فایل جداگانه استخراج کنید

وابستگی ها

وابستگی های برنامه کمتر از کد واقعی برنامه تغییر می کند. اگر آنها را به یک فایل جداگانه منتقل کنید، مرورگر می‌تواند آنها را به طور جداگانه کش کند - و هر بار که کد برنامه تغییر می‌کند آنها را دوباره دانلود نمی‌کند.

برای استخراج وابستگی ها به یک تکه جداگانه، سه مرحله را انجام دهید:

  1. نام فایل خروجی را با [name].[chunkname].js :

    // webpack.config.js
    module.exports = {
      output: {
        // Before
        filename: 'bundle.[chunkhash].js',
        // After
        filename: '[name].[chunkhash].js'
      }
    };
    

    وقتی وب‌پک برنامه را می‌سازد، [name] را با نام یک تکه جایگزین می‌کند. اگر قسمت [name] را اضافه نکنیم، باید بین تکه‌ها بر اساس هش آن‌ها تفاوت قائل شویم – که بسیار سخت است!

  2. فیلد entry را به یک شی تبدیل کنید:

    // webpack.config.js
    module.exports = {
      // Before
      entry: './index.js',
      // After
      entry: {
        main: './index.js'
      }
    };
    

    در این قطعه، "main" نام یک تکه است. این نام به جای [name] از مرحله 1 جایگزین می شود.

    در حال حاضر، اگر برنامه را بسازید، این بخش شامل کل کد برنامه خواهد بود - درست مثل ما که این مراحل را انجام نداده‌ایم. اما این در یک ثانیه تغییر خواهد کرد.

  3. در بسته وب 4 ، گزینه optimization.splitChunks.chunks: 'all' به پیکربندی بسته وب خود اضافه کنید:

    // webpack.config.js (for webpack 4)
    module.exports = {
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      }
    };
    

    این گزینه تقسیم کد هوشمند را فعال می کند. با استفاده از آن، وب‌پک کد فروشنده را در صورتی که از 30 کیلوبایت بزرگتر شود (قبل از کوچک‌سازی و gzip) استخراج می‌کند. همچنین کد رایج را استخراج می‌کند - این در صورتی مفید است که بیلد شما چندین باندل تولید کند (مثلاً اگر برنامه خود را به مسیرها تقسیم کنید ).

    در بسته وب 3 ، CommonsChunkPlugin را اضافه کنید:

    // webpack.config.js (for webpack 3)
    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
        // A name of the chunk that will include the dependencies.
        // This name is substituted in place of [name] from step 1
        name: 'vendor',
    
        // A function that determines which modules to include into this chunk
        minChunks: module => module.context && module.context.includes('node_modules'),
        })
      ]
    };
    

    این افزونه همه ماژول‌هایی را که مسیرهای آن شامل node_modules هستند را می‌گیرد و آنها را به یک فایل جداگانه به نام vendor.[chunkhash].js .

پس از این تغییرات، هر بیلد به جای یک فایل، دو فایل تولید می‌کند: main.[chunkhash].js و vendor.[chunkhash].js ( vendors~main.[chunkhash].js برای وب‌پک 4). در مورد بسته وب 4، اگر وابستگی ها کوچک باشند، ممکن است بسته فروشنده ایجاد نشود - و این خوب است:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                        Asset      Size  Chunks             Chunk Names
 ./main.00bab6fd3100008a42b0.js   82 kB       0  [emitted]  main
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

مرورگر این فایل‌ها را جداگانه ذخیره می‌کند – و فقط کدهایی را که تغییر می‌کنند دوباره دانلود می‌کند.

کد زمان اجرا وب پک

متأسفانه، استخراج فقط کد فروشنده کافی نیست. اگر می‌خواهید چیزی را در کد برنامه تغییر دهید:

// index.js
…
…

// E.g. add this:
console.log('Wat');

متوجه خواهید شد که هش vendor نیز تغییر می کند:

                           Asset   Size  Chunks             Chunk Names
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

                            Asset   Size  Chunks             Chunk Names
./vendor.e6ea4504d61a1cc1c60b.js  47 kB       1  [emitted]  vendor

این به این دلیل اتفاق می‌افتد که بسته وب بسته، جدا از کد ماژول‌ها، دارای یک زمان اجرا است - یک قطعه کد کوچک که اجرای ماژول را مدیریت می‌کند. هنگامی که کد را به چندین فایل تقسیم می کنید، این قطعه کد شامل نقشه برداری بین شناسه های تکه و فایل های مربوطه شروع می شود:

// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
    "0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";

Webpack این زمان اجرا را در آخرین قطعه تولید شده، که در مورد ما vendor است، شامل می شود. و هر بار که هر تکه ای تغییر می کند، این قطعه کد نیز تغییر می کند و باعث می شود کل قطعه vendor تغییر کند.

برای حل این مشکل، اجازه دهید زمان اجرا را به یک فایل جداگانه منتقل کنیم. در وب پک 4، این با فعال کردن گزینه optimization.runtimeChunk به دست می آید:

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

در وب پک 3، این کار را با ایجاد یک تکه خالی اضافی با CommonsChunkPlugin انجام دهید:

// webpack.config.js (for webpack 3)
module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: module => module.context && module.context.includes('node_modules')
    }),
    // This plugin must come after the vendor one (because webpack
    // includes runtime into the last chunk)
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime',
      // minChunks: Infinity means that no app modules
      // will be included into this chunk
      minChunks: Infinity
    })
  ]
};

پس از این تغییرات، هر بیلد سه فایل تولید می کند:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                            Asset     Size  Chunks             Chunk Names
   ./main.00bab6fd3100008a42b0.js    82 kB       0  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       1  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

آنها را به ترتیب معکوس در index.html قرار دهید - و کارتان تمام شد:

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>

در ادامه مطلب

زمان اجرای بسته وب داخلی برای ذخیره یک درخواست HTTP اضافی

برای بهتر کردن اوضاع، سعی کنید زمان اجرا بسته وب را در پاسخ HTML قرار دهید. یعنی به جای این:

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>

این کار را انجام دهید:

<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>

زمان اجرا کم است، و درون‌سازی آن به شما کمک می‌کند درخواست HTTP را ذخیره کنید (در HTTP/1 بسیار مهم است، در HTTP/2 اهمیت کمتری دارد، اما ممکن است باز هم تأثیر بگذارد).

در اینجا نحوه انجام آن آمده است.

اگر HTML را با HtmlWebpackPlugin تولید کنید

اگر از HtmlWebpackPlugin برای تولید یک فایل HTML استفاده می کنید، InlineSourcePlugin تمام چیزی است که نیاز دارید:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      inlineSource: 'runtime~.+\\.js',
    }),
    new InlineSourcePlugin()
  ]
};

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

با وب پک 4:

  1. افزونه WebpackManifestPlugin برای دانستن نام تولید شده قسمت اجرا اضافه کنید:

    // webpack.config.js (for webpack 4)
    const ManifestPlugin = require('webpack-manifest-plugin');
    
    module.exports = {
      plugins: [
        new ManifestPlugin()
      ]
    };
    

    یک بیلد با این افزونه فایلی به شکل زیر ایجاد می کند:

    // manifest.json
    {
      "runtime~main.js": "runtime~main.8e0d62a03.js"
    }
    
  2. محتوای بخش زمان اجرا را به روشی راحت درون خطی کنید. به عنوان مثال با Node.js و Express:

    // server.js
    const fs = require('fs');
    const manifest = require('./manifest.json');
    const runtimeContent = fs.readFileSync(manifest['runtime~main.js'], 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

یا با وب پک 3:

  1. با مشخص کردن filename ، نام زمان اجرا را ثابت کنید:

    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
          name: 'runtime',
          minChunks: Infinity,
          filename: 'runtime.js'
        })
      ]
    };
    
  2. محتوای runtime.js را به روشی راحت درون خط کنید. به عنوان مثال با Node.js و Express:

    // server.js
    const fs = require('fs');
    const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

کد Lazy-load که در حال حاضر به آن نیاز ندارید

گاهی اوقات، یک صفحه دارای بخش‌های مهم‌تر و کمتری است:

  • اگر صفحه ویدیویی را در یوتیوب بارگذاری کنید، بیشتر به ویدیو اهمیت می دهید تا نظرات. در اینجا، ویدیو مهمتر از نظرات است.
  • اگر مقاله ای را در یک سایت خبری باز کنید، بیشتر به متن مقاله اهمیت می دهید تا تبلیغات. در اینجا متن مهمتر از تبلیغات است.

در چنین مواردی، با دانلود فقط مهم‌ترین موارد در ابتدا و بارگیری تنبلی قسمت‌های باقی‌مانده، عملکرد بارگذاری اولیه را بهبود بخشید. برای این کار از تابع import() و تقسیم کد استفاده کنید:

// videoPlayer.js
export function renderVideoPlayer() { … }

// comments.js
export function renderComments() { … }

// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();

// …Custom event listener
onShowCommentsClick(() => {
  import('./comments').then((comments) => {
    comments.renderComments();
  });
});

import() مشخص می کند که می خواهید یک ماژول خاص را به صورت پویا بارگذاری کنید. وقتی webpack import('./module.js') می‌بیند، این ماژول را به یک تکه جداگانه منتقل می‌کند:

$ webpack
Hash: 39b2a53cb4e73f0dc5b2
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.f7e53d8e13e9a2745d6d.js    60 kB       1  [emitted]  main
 ./vendor.4f14b6326a80f4752a98.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

و آن را فقط زمانی دانلود می کند که اجرا به تابع import() برسد.

این کار بسته main را کوچکتر می کند و زمان بارگیری اولیه را بهبود می بخشد. حتی بیشتر، حافظه نهان را بهبود می بخشد - اگر کد را در قسمت اصلی تغییر دهید، بخش نظرات تحت تأثیر قرار نمی گیرد.

در ادامه مطلب

کد را به مسیرها و صفحات تقسیم کنید

اگر برنامه شما چندین مسیر یا صفحه دارد، اما تنها یک فایل JS با کد (یک تکه main ) وجود دارد، احتمالاً در هر درخواست بایت‌های اضافی ارائه می‌کنید. به عنوان مثال، هنگامی که یک کاربر از صفحه اصلی سایت شما بازدید می کند:

یک صفحه اصلی WebFundamentals

آنها نیازی به بارگذاری کد برای ارائه مقاله ای که در صفحه دیگری است ندارند - اما آنها آن را بارگذاری می کنند. علاوه بر این، اگر کاربر همیشه فقط از صفحه اصلی بازدید کند، و شما تغییری در کد مقاله ایجاد کنید، بسته وب کل بسته نرم افزاری را باطل می کند - و کاربر باید کل برنامه را دوباره دانلود کند.

اگر برنامه را به صفحات (یا مسیرها، اگر یک برنامه تک صفحه ای است) تقسیم کنیم، کاربر فقط کد مربوطه را دانلود می کند. بعلاوه، مرورگر کد برنامه را بهتر کش می‌کند: اگر کد صفحه اصلی را تغییر دهید، بسته وب فقط تکه مربوطه را باطل می‌کند.

برای برنامه های تک صفحه ای

برای تقسیم برنامه‌های تک صفحه‌ای بر اساس مسیرها، از import() استفاده کنید (به بخش «کد تنبل بارگذاری که در حال حاضر به آن نیاز ندارید» مراجعه کنید). اگر از یک فریم ورک استفاده می کنید، ممکن است یک راه حل موجود برای این کار داشته باشد:

برای برنامه های سنتی چند صفحه ای

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

// webpack.config.js
module.exports = {
  entry: {
    home: './src/Home/index.js',
    article: './src/Article/index.js',
    profile: './src/Profile/index.js'
  }
};

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

$ webpack
Hash: 318d7b8490a7382bf23b
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./home.91b9ed27366fe7e33d6a.js    18 kB       1  [emitted]  home
./article.87a128755b16ac3294fd.js    32 kB       2  [emitted]  article
./profile.de945dc02685f6166781.js    24 kB       3  [emitted]  profile
 ./vendor.4f14b6326a80f4752a98.js    46 kB       4  [emitted]  vendor
./runtime.318d7b8490a7382bf23b.js  1.45 kB       5  [emitted]  runtime

بنابراین، اگر فقط صفحه مقاله از Lodash استفاده می‌کند، بسته‌های صفحه home و profile شامل آن نمی‌شود - و کاربر مجبور نیست این کتابخانه را هنگام بازدید از صفحه اصلی دانلود کند.

درخت‌های وابستگی مجزا دارای معایبی هستند. اگر دو نقطه ورودی از Lodash استفاده می‌کنند، و شما وابستگی‌های خود را به یک بسته فروشنده منتقل نکرده‌اید، هر دو نقطه ورودی یک کپی از Lodash را شامل می‌شوند. برای حل این مشکل، در بسته وب 4، گزینه optimization.splitChunks.chunks: 'all' را به پیکربندی بسته وب خود اضافه کنید:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

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

یا در بسته وب 3 از CommonsChunkPlugin استفاده کنید – وابستگی های رایج را به یک فایل مشخص شده جدید منتقل می کند:

module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common',
      minChunks: 2    // 2 is the default value
    })
  ]
};

برای یافتن بهترین آن، می توانید با ارزش minChunks بازی کنید. به طور کلی، شما می خواهید آن را کوچک نگه دارید، اما اگر تعداد تکه ها افزایش یابد، آن را افزایش دهید. به عنوان مثال، برای 3 تکه، minChunks ممکن است 2 باشد، اما برای 30 تکه، ممکن است 8 باشد – زیرا اگر آن را روی 2 نگه دارید، ماژول های زیادی وارد فایل رایج می شوند و آن را بیش از حد باد می کنند.

در ادامه مطلب

شناسه های ماژول را پایدارتر کنید

هنگام ساخت کد، وب پک به هر ماژول یک شناسه اختصاص می دهد. بعداً، این شناسه ها در require() در داخل بسته استفاده می شوند. شما معمولاً شناسه ها را در خروجی ساخت درست قبل از مسیرهای ماژول می بینید:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.4e50a16675574df6a9e9.js    60 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

↓ اینجا

[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
[4] ./comments.js 58 kB {0} [built]
[5] ./ads.js 74 kB {1} [built]
+ 1 hidden module

به‌طور پیش‌فرض، شناسه‌ها با استفاده از یک شمارنده محاسبه می‌شوند (یعنی ماژول اول دارای ID 0، ماژول دوم دارای ID 1 و غیره). مشکل این است که وقتی یک ماژول جدید اضافه می کنید، ممکن است در وسط لیست ماژول ظاهر شود و شناسه همه ماژول های بعدی را تغییر دهد:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.5c82c0f337fcb22672b5.js    22 kB       0  [emitted]
   ./main.0c8b617dfc40c2827ae3.js    82 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime
   [0] ./index.js 29 kB {1} [built]
   [2] (webpack)/buildin/global.js 488 bytes {2} [built]
   [3] (webpack)/buildin/module.js 495 bytes {2} [built]

↓ ما یک ماژول جدید اضافه کرده ایم…

[4] ./webPlayer.js 24 kB {1} [built]

↓ و ببینید چه کرده است! comments.js اکنون به جای 4 دارای شناسه 5 است

[5] ./comments.js 58 kB {0} [built]

ads.js اکنون به جای 5 دارای شناسه 6 است

[6] ./ads.js 74 kB {1} [built]
       + 1 hidden module

این همه تکه‌هایی را که شامل یا وابسته به ماژول‌هایی با شناسه‌های تغییر یافته هستند، باطل می‌کند - حتی اگر کد واقعی آنها تغییر نکرده باشد. در مورد ما، قطعه 0 (تکه با comments.js ) و تکه main (تکه با کد برنامه دیگر) باطل می شوند – در حالی که فقط قسمت main باید می بود.

برای حل این مشکل، نحوه محاسبه شناسه های ماژول را با استفاده از HashedModuleIdsPlugin تغییر دهید. شناسه‌های مبتنی بر ضد را با هش‌های مسیرهای ماژول جایگزین می‌کند:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.6168aaac8461862eab7a.js  22.5 kB       0  [emitted]
   ./main.a2e49a279552980e3b91.js    60 kB       1  [emitted]  main
 ./vendor.ff9f7ea865884e6a84c8.js    46 kB       2  [emitted]  vendor
./runtime.25f5d0204e4f77fa57a1.js  1.45 kB       3  [emitted]  runtime

↓ اینجا

[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
    + 1 hidden module

با این رویکرد، شناسه یک ماژول تنها در صورتی تغییر می کند که آن ماژول را تغییر نام دهید یا آن را جابجا کنید. ماژول‌های جدید روی شناسه ماژول‌های دیگر تأثیری نمی‌گذارند.

برای فعال کردن افزونه، آن را به بخش plugins پیکربندی اضافه کنید:

// webpack.config.js
module.exports = {
  plugins: [
    new webpack.HashedModuleIdsPlugin()
  ]
};

در ادامه مطلب

جمع بندی

  • باندل را کش کش کنید و با تغییر نام بسته، بین نسخه ها تفاوت قائل شوید
  • بسته نرم افزاری را به کد برنامه، کد فروشنده و زمان اجرا تقسیم کنید
  • برای ذخیره درخواست HTTP، زمان اجرا را به صورت درون خطی کنید
  • کد غیر بحرانی بارگذاری تنبل با import
  • برای جلوگیری از بارگیری موارد غیر ضروری، کد را بر اساس مسیرها/صفحات تقسیم کنید
،

چگونه وب پک به ذخیره دارایی کمک می کند

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

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

روش رایج انجام ذخیره سازی به این صورت است:

  1. به مرورگر بگویید که یک فایل را برای مدت بسیار طولانی (مثلاً یک سال) کش کند:

    # Server header
    Cache-Control: max-age=31536000
    

    اگر با کارهایی که Cache-Control انجام می دهد آشنا نیستید، به پست عالی جیک آرچیبالد در مورد بهترین شیوه های ذخیره سازی کش مراجعه کنید.

  2. و پس از تغییر نام فایل را تغییر دهید تا بارگیری مجدد اجباری شود:

    <!-- Before the change -->
    <script src="./index-v15.js"></script>
    
    <!-- After the change -->
    <script src="./index-v16.js"></script>
    

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

با webpack هم همین کار را می کنید، اما به جای شماره نسخه، هش فایل را مشخص می کنید. برای گنجاندن هش در نام فایل، از [chunkhash] استفاده کنید:

// webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
  }
};

اگر به نام فایل برای ارسال آن به مشتری نیاز دارید، از HtmlWebpackPlugin یا WebpackManifestPlugin استفاده کنید.

HtmlWebpackPlugin یک رویکرد ساده، اما کمتر انعطاف پذیر است. در طول کامپایل، این افزونه یک فایل HTML تولید می کند که شامل تمام منابع کامپایل شده است. اگر منطق سرور شما پیچیده نیست، باید برای شما کافی باشد:

<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>

WebpackManifestPlugin یک رویکرد انعطاف پذیرتر است که در صورت داشتن یک بخش سرور پیچیده مفید است. در طول ساخت، یک فایل JSON با نگاشت بین نام فایل های بدون هش و نام فایل ها با هش تولید می کند. از این JSON در سرور استفاده کنید تا بفهمید با کدام فایل کار کنید:

// manifest.json
{
  "bundle.js": "bundle.8e0d62a03.js"
}

در ادامه مطلب

وابستگی ها و زمان اجرا را در یک فایل جداگانه استخراج کنید

وابستگی ها

وابستگی های برنامه کمتر از کد واقعی برنامه تغییر می کند. اگر آنها را به یک فایل جداگانه منتقل کنید، مرورگر می‌تواند آنها را به طور جداگانه کش کند - و هر بار که کد برنامه تغییر می‌کند آنها را دوباره دانلود نمی‌کند.

برای استخراج وابستگی ها به یک تکه جداگانه، سه مرحله را انجام دهید:

  1. نام فایل خروجی را با [name].[chunkname].js :

    // webpack.config.js
    module.exports = {
      output: {
        // Before
        filename: 'bundle.[chunkhash].js',
        // After
        filename: '[name].[chunkhash].js'
      }
    };
    

    وقتی وب‌پک برنامه را می‌سازد، [name] را با نام یک تکه جایگزین می‌کند. اگر قسمت [name] را اضافه نکنیم، باید بین تکه‌ها بر اساس هش آن‌ها تفاوت قائل شویم – که بسیار سخت است!

  2. فیلد entry را به یک شی تبدیل کنید:

    // webpack.config.js
    module.exports = {
      // Before
      entry: './index.js',
      // After
      entry: {
        main: './index.js'
      }
    };
    

    در این قطعه، "main" نام یک تکه است. این نام به جای [name] از مرحله 1 جایگزین می شود.

    در حال حاضر، اگر برنامه را بسازید، این بخش شامل کل کد برنامه خواهد بود - درست مثل ما که این مراحل را انجام نداده‌ایم. اما این در یک ثانیه تغییر خواهد کرد.

  3. در بسته وب 4 ، گزینه optimization.splitChunks.chunks: 'all' به پیکربندی بسته وب خود اضافه کنید:

    // webpack.config.js (for webpack 4)
    module.exports = {
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      }
    };
    

    این گزینه تقسیم کد هوشمند را فعال می کند. با استفاده از آن، وب‌پک کد فروشنده را در صورتی که از 30 کیلوبایت بزرگتر شود (قبل از کوچک‌سازی و gzip) استخراج می‌کند. همچنین کد رایج را استخراج می‌کند - این در صورتی مفید است که بیلد شما چندین باندل تولید کند (مثلاً اگر برنامه خود را به مسیرها تقسیم کنید ).

    در بسته وب 3 ، CommonsChunkPlugin را اضافه کنید:

    // webpack.config.js (for webpack 3)
    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
        // A name of the chunk that will include the dependencies.
        // This name is substituted in place of [name] from step 1
        name: 'vendor',
    
        // A function that determines which modules to include into this chunk
        minChunks: module => module.context && module.context.includes('node_modules'),
        })
      ]
    };
    

    این افزونه همه ماژول‌هایی را که مسیرهای آن شامل node_modules هستند را می‌گیرد و آنها را به یک فایل جداگانه به نام vendor.[chunkhash].js .

پس از این تغییرات، هر بیلد به جای یک فایل، دو فایل تولید می‌کند: main.[chunkhash].js و vendor.[chunkhash].js ( vendors~main.[chunkhash].js برای وب‌پک 4). در مورد بسته وب 4، اگر وابستگی ها کوچک باشند، ممکن است بسته فروشنده ایجاد نشود - و این خوب است:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                        Asset      Size  Chunks             Chunk Names
 ./main.00bab6fd3100008a42b0.js   82 kB       0  [emitted]  main
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

مرورگر این فایل‌ها را جداگانه ذخیره می‌کند – و فقط کدهایی را که تغییر می‌کنند دوباره دانلود می‌کند.

کد زمان اجرا وب پک

متأسفانه، استخراج فقط کد فروشنده کافی نیست. اگر می‌خواهید چیزی را در کد برنامه تغییر دهید:

// index.js
…
…

// E.g. add this:
console.log('Wat');

متوجه خواهید شد که هش vendor نیز تغییر می کند:

                           Asset   Size  Chunks             Chunk Names
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

                            Asset   Size  Chunks             Chunk Names
./vendor.e6ea4504d61a1cc1c60b.js  47 kB       1  [emitted]  vendor

این به این دلیل اتفاق می‌افتد که بسته وب بسته، جدا از کد ماژول‌ها، دارای یک زمان اجرا است - یک قطعه کد کوچک که اجرای ماژول را مدیریت می‌کند. هنگامی که کد را به چندین فایل تقسیم می کنید، این قطعه کد شامل نقشه برداری بین شناسه های تکه و فایل های مربوطه شروع می شود:

// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
    "0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";

Webpack این زمان اجرا را در آخرین قطعه تولید شده، که در مورد ما vendor است، شامل می شود. و هر بار که هر تکه ای تغییر می کند، این قطعه کد نیز تغییر می کند و باعث می شود کل قطعه vendor تغییر کند.

برای حل این مشکل، اجازه دهید زمان اجرا را به یک فایل جداگانه منتقل کنیم. در وب پک 4، این با فعال کردن گزینه optimization.runtimeChunk به دست می آید:

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

در وب پک 3، این کار را با ایجاد یک تکه خالی اضافی با CommonsChunkPlugin انجام دهید:

// webpack.config.js (for webpack 3)
module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: module => module.context && module.context.includes('node_modules')
    }),
    // This plugin must come after the vendor one (because webpack
    // includes runtime into the last chunk)
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime',
      // minChunks: Infinity means that no app modules
      // will be included into this chunk
      minChunks: Infinity
    })
  ]
};

پس از این تغییرات، هر بیلد سه فایل تولید می کند:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                            Asset     Size  Chunks             Chunk Names
   ./main.00bab6fd3100008a42b0.js    82 kB       0  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       1  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

آنها را به ترتیب معکوس در index.html قرار دهید - و کارتان تمام شد:

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>

در ادامه مطلب

زمان اجرای بسته وب داخلی برای ذخیره یک درخواست HTTP اضافی

برای بهتر کردن اوضاع، سعی کنید زمان اجرا بسته وب را در پاسخ HTML قرار دهید. یعنی به جای این:

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>

این کار را انجام دهید:

<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>

زمان اجرا کم است، و درون‌سازی آن به شما کمک می‌کند درخواست HTTP را ذخیره کنید (در HTTP/1 بسیار مهم است، در HTTP/2 اهمیت کمتری دارد، اما ممکن است باز هم تأثیر بگذارد).

در اینجا نحوه انجام آن آمده است.

اگر HTML را با HtmlWebpackPlugin تولید کنید

اگر از HtmlWebpackPlugin برای تولید یک فایل HTML استفاده می کنید، InlineSourcePlugin تمام چیزی است که نیاز دارید:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      inlineSource: 'runtime~.+\\.js',
    }),
    new InlineSourcePlugin()
  ]
};

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

با وب پک 4:

  1. افزونه WebpackManifestPlugin برای دانستن نام تولید شده قسمت اجرا اضافه کنید:

    // webpack.config.js (for webpack 4)
    const ManifestPlugin = require('webpack-manifest-plugin');
    
    module.exports = {
      plugins: [
        new ManifestPlugin()
      ]
    };
    

    یک بیلد با این افزونه فایلی به شکل زیر ایجاد می کند:

    // manifest.json
    {
      "runtime~main.js": "runtime~main.8e0d62a03.js"
    }
    
  2. محتوای بخش زمان اجرا را به روشی راحت درون خطی کنید. به عنوان مثال با Node.js و Express:

    // server.js
    const fs = require('fs');
    const manifest = require('./manifest.json');
    const runtimeContent = fs.readFileSync(manifest['runtime~main.js'], 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

یا با وب پک 3:

  1. با مشخص کردن filename ، نام زمان اجرا را ثابت کنید:

    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
          name: 'runtime',
          minChunks: Infinity,
          filename: 'runtime.js'
        })
      ]
    };
    
  2. محتوای runtime.js را به روشی راحت درون خط کنید. به عنوان مثال با Node.js و Express:

    // server.js
    const fs = require('fs');
    const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

کد Lazy-load که در حال حاضر به آن نیاز ندارید

گاهی اوقات، یک صفحه دارای بخش‌های مهم‌تر و کمتری است:

  • اگر صفحه ویدیویی را در یوتیوب بارگذاری کنید، بیشتر به ویدیو اهمیت می دهید تا نظرات. در اینجا، ویدیو مهمتر از نظرات است.
  • اگر مقاله ای را در یک سایت خبری باز کنید، بیشتر به متن مقاله اهمیت می دهید تا تبلیغات. در اینجا متن مهمتر از تبلیغات است.

در چنین مواردی، با دانلود فقط مهم‌ترین موارد در ابتدا و بارگیری تنبلی قسمت‌های باقی‌مانده، عملکرد بارگذاری اولیه را بهبود بخشید. برای این کار از تابع import() و تقسیم کد استفاده کنید:

// videoPlayer.js
export function renderVideoPlayer() { … }

// comments.js
export function renderComments() { … }

// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();

// …Custom event listener
onShowCommentsClick(() => {
  import('./comments').then((comments) => {
    comments.renderComments();
  });
});

import() مشخص می کند که می خواهید یک ماژول خاص را به صورت پویا بارگذاری کنید. وقتی webpack import('./module.js') می‌بیند، این ماژول را به یک تکه جداگانه منتقل می‌کند:

$ webpack
Hash: 39b2a53cb4e73f0dc5b2
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.f7e53d8e13e9a2745d6d.js    60 kB       1  [emitted]  main
 ./vendor.4f14b6326a80f4752a98.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

و آن را فقط زمانی دانلود می کند که اجرا به تابع import() برسد.

این کار بسته main را کوچکتر می کند و زمان بارگیری اولیه را بهبود می بخشد. حتی بیشتر، حافظه نهان را بهبود می بخشد - اگر کد را در قسمت اصلی تغییر دهید، بخش نظرات تحت تأثیر قرار نمی گیرد.

در ادامه مطلب

کد را به مسیرها و صفحات تقسیم کنید

اگر برنامه شما چندین مسیر یا صفحه دارد، اما تنها یک فایل JS با کد (یک تکه main ) وجود دارد، احتمالاً در هر درخواست بایت‌های اضافی ارائه می‌کنید. به عنوان مثال، هنگامی که یک کاربر از صفحه اصلی سایت شما بازدید می کند:

یک صفحه اصلی WebFundamentals

آنها نیازی به بارگذاری کد برای ارائه مقاله ای که در صفحه دیگری است ندارند - اما آنها آن را بارگذاری می کنند. علاوه بر این، اگر کاربر همیشه فقط از صفحه اصلی بازدید کند، و شما تغییری در کد مقاله ایجاد کنید، بسته وب کل بسته نرم افزاری را باطل می کند - و کاربر باید کل برنامه را دوباره دانلود کند.

اگر برنامه را به صفحات (یا مسیرها، اگر یک برنامه تک صفحه ای است) تقسیم کنیم، کاربر فقط کد مربوطه را دانلود می کند. بعلاوه، مرورگر کد برنامه را بهتر کش می‌کند: اگر کد صفحه اصلی را تغییر دهید، بسته وب فقط تکه مربوطه را باطل می‌کند.

برای برنامه های تک صفحه ای

برای تقسیم برنامه‌های تک صفحه‌ای بر اساس مسیرها، از import() استفاده کنید (به بخش «کد تنبل بارگذاری که در حال حاضر به آن نیاز ندارید» مراجعه کنید). اگر از یک فریم ورک استفاده می کنید، ممکن است یک راه حل موجود برای این کار داشته باشد:

برای برنامه های سنتی چند صفحه ای

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

// webpack.config.js
module.exports = {
  entry: {
    home: './src/Home/index.js',
    article: './src/Article/index.js',
    profile: './src/Profile/index.js'
  }
};

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

$ webpack
Hash: 318d7b8490a7382bf23b
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./home.91b9ed27366fe7e33d6a.js    18 kB       1  [emitted]  home
./article.87a128755b16ac3294fd.js    32 kB       2  [emitted]  article
./profile.de945dc02685f6166781.js    24 kB       3  [emitted]  profile
 ./vendor.4f14b6326a80f4752a98.js    46 kB       4  [emitted]  vendor
./runtime.318d7b8490a7382bf23b.js  1.45 kB       5  [emitted]  runtime

بنابراین، اگر فقط صفحه مقاله از Lodash استفاده می‌کند، بسته‌های صفحه home و profile شامل آن نمی‌شود - و کاربر مجبور نیست این کتابخانه را هنگام بازدید از صفحه اصلی دانلود کند.

درخت‌های وابستگی مجزا دارای معایبی هستند. اگر دو نقطه ورودی از Lodash استفاده می‌کنند، و شما وابستگی‌های خود را به یک بسته فروشنده منتقل نکرده‌اید، هر دو نقطه ورودی یک کپی از Lodash را شامل می‌شوند. برای حل این مشکل، در بسته وب 4، گزینه optimization.splitChunks.chunks: 'all' را به پیکربندی بسته وب خود اضافه کنید:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

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

یا در بسته وب 3 از CommonsChunkPlugin استفاده کنید – وابستگی های رایج را به یک فایل مشخص شده جدید منتقل می کند:

module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common',
      minChunks: 2    // 2 is the default value
    })
  ]
};

برای یافتن بهترین آن، می توانید با ارزش minChunks بازی کنید. به طور کلی، شما می خواهید آن را کوچک نگه دارید، اما اگر تعداد تکه ها افزایش یابد، آن را افزایش دهید. به عنوان مثال، برای 3 تکه، minChunks ممکن است 2 باشد، اما برای 30 تکه، ممکن است 8 باشد – زیرا اگر آن را روی 2 نگه دارید، ماژول های زیادی وارد فایل رایج می شوند و آن را بیش از حد باد می کنند.

در ادامه مطلب

شناسه های ماژول را پایدارتر کنید

هنگام ساخت کد، وب پک به هر ماژول یک شناسه اختصاص می دهد. بعداً، این شناسه ها در require() در داخل بسته استفاده می شوند. شما معمولاً شناسه ها را در خروجی ساخت درست قبل از مسیرهای ماژول می بینید:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.4e50a16675574df6a9e9.js    60 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

↓ اینجا

[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
[4] ./comments.js 58 kB {0} [built]
[5] ./ads.js 74 kB {1} [built]
+ 1 hidden module

به‌طور پیش‌فرض، شناسه‌ها با استفاده از یک شمارنده محاسبه می‌شوند (یعنی ماژول اول دارای ID 0، ماژول دوم دارای ID 1 و غیره). مشکل این است که وقتی یک ماژول جدید اضافه می کنید، ممکن است در وسط لیست ماژول ظاهر شود و شناسه همه ماژول های بعدی را تغییر دهد:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.5c82c0f337fcb22672b5.js    22 kB       0  [emitted]
   ./main.0c8b617dfc40c2827ae3.js    82 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime
   [0] ./index.js 29 kB {1} [built]
   [2] (webpack)/buildin/global.js 488 bytes {2} [built]
   [3] (webpack)/buildin/module.js 495 bytes {2} [built]

↓ ما یک ماژول جدید اضافه کرده ایم…

[4] ./webPlayer.js 24 kB {1} [built]

↓ و ببینید چه کرده است! comments.js اکنون به جای 4 دارای شناسه 5 است

[5] ./comments.js 58 kB {0} [built]

ads.js اکنون به جای 5 دارای شناسه 6 است

[6] ./ads.js 74 kB {1} [built]
       + 1 hidden module

این همه تکه‌هایی را که شامل یا وابسته به ماژول‌هایی با شناسه‌های تغییر یافته هستند، باطل می‌کند - حتی اگر کد واقعی آنها تغییر نکرده باشد. در مورد ما، قطعه 0 (تکه با comments.js ) و تکه main (تکه با کد برنامه دیگر) باطل می شوند – در حالی که فقط قسمت main باید می بود.

برای حل این مشکل، نحوه محاسبه شناسه های ماژول را با استفاده از HashedModuleIdsPlugin تغییر دهید. شناسه‌های مبتنی بر ضد را با هش‌های مسیرهای ماژول جایگزین می‌کند:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.6168aaac8461862eab7a.js  22.5 kB       0  [emitted]
   ./main.a2e49a279552980e3b91.js    60 kB       1  [emitted]  main
 ./vendor.ff9f7ea865884e6a84c8.js    46 kB       2  [emitted]  vendor
./runtime.25f5d0204e4f77fa57a1.js  1.45 kB       3  [emitted]  runtime

↓ اینجا

[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
    + 1 hidden module

با این رویکرد، شناسه یک ماژول تنها در صورتی تغییر می کند که آن ماژول را تغییر نام دهید یا آن را جابجا کنید. ماژول‌های جدید روی شناسه ماژول‌های دیگر تأثیری نمی‌گذارند.

برای فعال کردن افزونه، آن را به بخش plugins پیکربندی اضافه کنید:

// webpack.config.js
module.exports = {
  plugins: [
    new webpack.HashedModuleIdsPlugin()
  ]
};

در ادامه مطلب

جمع بندی

  • باندل را کش کش کنید و با تغییر نام بسته، بین نسخه ها تفاوت قائل شوید
  • بسته نرم افزاری را به کد برنامه، کد فروشنده و زمان اجرا تقسیم کنید
  • برای ذخیره درخواست HTTP، زمان اجرا را به صورت درون خطی کنید
  • کد غیر بحرانی بارگذاری تنبل با import
  • برای جلوگیری از بارگیری موارد غیر ضروری، کد را بر اساس مسیرها/صفحات تقسیم کنید
،

چگونه وب پک به ذخیره دارایی کمک می کند

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

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

روش رایج انجام ذخیره سازی به این صورت است:

  1. به مرورگر بگویید که یک فایل را برای مدت بسیار طولانی (مثلاً یک سال) کش کند:

    # Server header
    Cache-Control: max-age=31536000
    

    اگر با کارهایی که Cache-Control انجام می دهد آشنا نیستید، به پست عالی جیک آرچیبالد در مورد بهترین شیوه های ذخیره سازی کش مراجعه کنید.

  2. و پس از تغییر نام فایل را تغییر دهید تا بارگیری مجدد اجباری شود:

    <!-- Before the change -->
    <script src="./index-v15.js"></script>
    
    <!-- After the change -->
    <script src="./index-v16.js"></script>
    

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

با webpack هم همین کار را می کنید، اما به جای شماره نسخه، هش فایل را مشخص می کنید. برای گنجاندن هش در نام فایل، از [chunkhash] استفاده کنید:

// webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
  }
};

اگر به نام فایل برای ارسال آن به مشتری نیاز دارید، از HtmlWebpackPlugin یا WebpackManifestPlugin استفاده کنید.

HtmlWebpackPlugin یک رویکرد ساده، اما کمتر انعطاف پذیر است. در طول کامپایل، این افزونه یک فایل HTML تولید می کند که شامل تمام منابع کامپایل شده است. اگر منطق سرور شما پیچیده نیست، باید برای شما کافی باشد:

<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>

WebpackManifestPlugin یک رویکرد انعطاف پذیرتر است که در صورت داشتن یک بخش سرور پیچیده مفید است. در طول ساخت، یک فایل JSON با نگاشت بین نام فایل های بدون هش و نام فایل ها با هش تولید می کند. از این JSON در سرور استفاده کنید تا بفهمید با کدام فایل کار کنید:

// manifest.json
{
  "bundle.js": "bundle.8e0d62a03.js"
}

در ادامه مطلب

وابستگی ها و زمان اجرا را در یک فایل جداگانه استخراج کنید

وابستگی ها

وابستگی های برنامه کمتر از کد واقعی برنامه تغییر می کند. اگر آنها را به یک فایل جداگانه منتقل کنید، مرورگر می‌تواند آنها را به طور جداگانه کش کند - و هر بار که کد برنامه تغییر می‌کند آنها را دوباره دانلود نمی‌کند.

برای استخراج وابستگی ها به یک تکه جداگانه، سه مرحله را انجام دهید:

  1. نام فایل خروجی را با [name].[chunkname].js :

    // webpack.config.js
    module.exports = {
      output: {
        // Before
        filename: 'bundle.[chunkhash].js',
        // After
        filename: '[name].[chunkhash].js'
      }
    };
    

    وقتی وب‌پک برنامه را می‌سازد، [name] را با نام یک تکه جایگزین می‌کند. اگر قسمت [name] را اضافه نکنیم، باید بین تکه‌ها بر اساس هش آن‌ها تفاوت قائل شویم – که بسیار سخت است!

  2. فیلد entry را به یک شی تبدیل کنید:

    // webpack.config.js
    module.exports = {
      // Before
      entry: './index.js',
      // After
      entry: {
        main: './index.js'
      }
    };
    

    در این قطعه، "main" نام یک تکه است. این نام به جای [name] از مرحله 1 جایگزین می شود.

    در حال حاضر، اگر برنامه را بسازید، این بخش شامل کل کد برنامه خواهد بود - درست مثل ما که این مراحل را انجام نداده‌ایم. اما این در یک ثانیه تغییر خواهد کرد.

  3. در بسته وب 4 ، گزینه optimization.splitChunks.chunks: 'all' به پیکربندی بسته وب خود اضافه کنید:

    // webpack.config.js (for webpack 4)
    module.exports = {
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      }
    };
    

    این گزینه تقسیم کد هوشمند را فعال می کند. با استفاده از آن، وب‌پک کد فروشنده را در صورتی که از 30 کیلوبایت بزرگتر شود (قبل از کوچک‌سازی و gzip) استخراج می‌کند. همچنین کد رایج را استخراج می‌کند - این در صورتی مفید است که بیلد شما چندین باندل تولید کند (مثلاً اگر برنامه خود را به مسیرها تقسیم کنید ).

    در بسته وب 3 ، CommonsChunkPlugin را اضافه کنید:

    // webpack.config.js (for webpack 3)
    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
        // A name of the chunk that will include the dependencies.
        // This name is substituted in place of [name] from step 1
        name: 'vendor',
    
        // A function that determines which modules to include into this chunk
        minChunks: module => module.context && module.context.includes('node_modules'),
        })
      ]
    };
    

    این افزونه همه ماژول‌هایی را که مسیرهای آن شامل node_modules هستند را می‌گیرد و آنها را به یک فایل جداگانه به نام vendor.[chunkhash].js .

پس از این تغییرات، هر بیلد به جای یک فایل، دو فایل تولید می‌کند: main.[chunkhash].js و vendor.[chunkhash].js ( vendors~main.[chunkhash].js برای وب‌پک 4). در مورد وب پک 4، اگر وابستگی ها کوچک باشند، ممکن است بسته فروشنده ایجاد نشود - و این خوب است:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                        Asset      Size  Chunks             Chunk Names
 ./main.00bab6fd3100008a42b0.js   82 kB       0  [emitted]  main
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

مرورگر این فایل‌ها را جداگانه ذخیره می‌کند – و فقط کدهایی را که تغییر می‌کنند دوباره دانلود می‌کند.

کد زمان اجرا وب پک

متأسفانه، استخراج فقط کد فروشنده کافی نیست. اگر می‌خواهید چیزی را در کد برنامه تغییر دهید:

// index.js
…
…

// E.g. add this:
console.log('Wat');

متوجه خواهید شد که هش vendor نیز تغییر می کند:

                           Asset   Size  Chunks             Chunk Names
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

                            Asset   Size  Chunks             Chunk Names
./vendor.e6ea4504d61a1cc1c60b.js  47 kB       1  [emitted]  vendor

این به این دلیل اتفاق می‌افتد که بسته وب بسته، جدا از کد ماژول‌ها، دارای یک زمان اجرا است - یک قطعه کد کوچک که اجرای ماژول را مدیریت می‌کند. هنگامی که کد را به چندین فایل تقسیم می کنید، این قطعه کد شامل نقشه برداری بین شناسه های تکه و فایل های مربوطه شروع می شود:

// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
    "0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";

Webpack این زمان اجرا را در آخرین قطعه تولید شده، که در مورد ما vendor است، شامل می شود. و هر بار که هر تکه ای تغییر می کند، این قطعه کد نیز تغییر می کند و باعث می شود کل قطعه vendor تغییر کند.

برای حل این مشکل، اجازه دهید زمان اجرا را به یک فایل جداگانه منتقل کنیم. در وب پک 4، این با فعال کردن گزینه optimization.runtimeChunk به دست می آید:

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

در وب پک 3، این کار را با ایجاد یک تکه خالی اضافی با CommonsChunkPlugin انجام دهید:

// webpack.config.js (for webpack 3)
module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: module => module.context && module.context.includes('node_modules')
    }),
    // This plugin must come after the vendor one (because webpack
    // includes runtime into the last chunk)
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime',
      // minChunks: Infinity means that no app modules
      // will be included into this chunk
      minChunks: Infinity
    })
  ]
};

پس از این تغییرات، هر بیلد سه فایل تولید می کند:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                            Asset     Size  Chunks             Chunk Names
   ./main.00bab6fd3100008a42b0.js    82 kB       0  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       1  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

آنها را به ترتیب معکوس در index.html قرار دهید - و کارتان تمام شد:

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>

در ادامه مطلب

زمان اجرای بسته وب داخلی برای ذخیره یک درخواست HTTP اضافی

برای بهتر کردن اوضاع، سعی کنید زمان اجرا بسته وب را در پاسخ HTML قرار دهید. یعنی به جای این:

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>

این کار را انجام دهید:

<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>

زمان اجرا کم است، و درون‌سازی آن به شما کمک می‌کند درخواست HTTP را ذخیره کنید (در HTTP/1 بسیار مهم است، در HTTP/2 اهمیت کمتری دارد، اما ممکن است باز هم تأثیر بگذارد).

در اینجا نحوه انجام آن آمده است.

اگر HTML را با HtmlWebpackPlugin تولید کنید

اگر از HtmlWebpackPlugin برای تولید یک فایل HTML استفاده می کنید، InlineSourcePlugin تمام چیزی است که نیاز دارید:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      inlineSource: 'runtime~.+\\.js',
    }),
    new InlineSourcePlugin()
  ]
};

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

با وب پک 4:

  1. افزونه WebpackManifestPlugin برای دانستن نام تولید شده قسمت اجرا اضافه کنید:

    // webpack.config.js (for webpack 4)
    const ManifestPlugin = require('webpack-manifest-plugin');
    
    module.exports = {
      plugins: [
        new ManifestPlugin()
      ]
    };
    

    یک بیلد با این افزونه فایلی به شکل زیر ایجاد می کند:

    // manifest.json
    {
      "runtime~main.js": "runtime~main.8e0d62a03.js"
    }
    
  2. محتوای بخش زمان اجرا را به روشی راحت درون خطی کنید. به عنوان مثال با Node.js و Express:

    // server.js
    const fs = require('fs');
    const manifest = require('./manifest.json');
    const runtimeContent = fs.readFileSync(manifest['runtime~main.js'], 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

یا با وب پک 3:

  1. با مشخص کردن filename ، نام زمان اجرا را ثابت کنید:

    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
          name: 'runtime',
          minChunks: Infinity,
          filename: 'runtime.js'
        })
      ]
    };
    
  2. محتوای runtime.js را به روشی راحت درون خط کنید. به عنوان مثال با Node.js و Express:

    // server.js
    const fs = require('fs');
    const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

کد Lazy-load که در حال حاضر به آن نیاز ندارید

گاهی اوقات، یک صفحه دارای بخش‌های مهم‌تر و کمتری است:

  • اگر صفحه ویدیویی را در یوتیوب بارگذاری کنید، بیشتر به ویدیو اهمیت می دهید تا نظرات. در اینجا، ویدیو مهمتر از نظرات است.
  • اگر مقاله ای را در یک سایت خبری باز کنید، بیشتر به متن مقاله اهمیت می دهید تا تبلیغات. در اینجا متن مهمتر از تبلیغات است.

در چنین مواردی، با دانلود فقط مهم‌ترین موارد در ابتدا و بارگیری تنبلی قسمت‌های باقی‌مانده، عملکرد بارگذاری اولیه را بهبود بخشید. برای این کار از تابع import() و تقسیم کد استفاده کنید:

// videoPlayer.js
export function renderVideoPlayer() { … }

// comments.js
export function renderComments() { … }

// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();

// …Custom event listener
onShowCommentsClick(() => {
  import('./comments').then((comments) => {
    comments.renderComments();
  });
});

import() مشخص می کند که می خواهید یک ماژول خاص را به صورت پویا بارگذاری کنید. وقتی webpack import('./module.js') می‌بیند، این ماژول را به یک تکه جداگانه منتقل می‌کند:

$ webpack
Hash: 39b2a53cb4e73f0dc5b2
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.f7e53d8e13e9a2745d6d.js    60 kB       1  [emitted]  main
 ./vendor.4f14b6326a80f4752a98.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

و آن را فقط زمانی دانلود می کند که اجرا به تابع import() برسد.

این کار بسته main را کوچکتر می کند و زمان بارگیری اولیه را بهبود می بخشد. حتی بیشتر، حافظه نهان را بهبود می بخشد - اگر کد را در قسمت اصلی تغییر دهید، بخش نظرات تحت تأثیر قرار نمی گیرد.

در ادامه مطلب

کد را به مسیرها و صفحات تقسیم کنید

اگر برنامه شما چندین مسیر یا صفحه دارد، اما تنها یک فایل JS با کد (یک تکه main ) وجود دارد، احتمالاً در هر درخواست بایت‌های اضافی ارائه می‌کنید. به عنوان مثال، هنگامی که یک کاربر از صفحه اصلی سایت شما بازدید می کند:

یک صفحه اصلی WebFundamentals

آنها نیازی به بارگذاری کد برای ارائه مقاله ای که در صفحه دیگری است ندارند - اما آنها آن را بارگذاری می کنند. علاوه بر این، اگر کاربر همیشه فقط از صفحه اصلی بازدید کند، و شما تغییری در کد مقاله ایجاد کنید، بسته وب کل بسته نرم افزاری را باطل می کند - و کاربر باید کل برنامه را دوباره دانلود کند.

اگر برنامه را به صفحات (یا مسیرها، اگر یک برنامه تک صفحه ای است) تقسیم کنیم، کاربر فقط کد مربوطه را دانلود می کند. بعلاوه، مرورگر کد برنامه را بهتر کش می‌کند: اگر کد صفحه اصلی را تغییر دهید، بسته وب فقط تکه مربوطه را باطل می‌کند.

برای برنامه های تک صفحه ای

برای تقسیم برنامه های تک صفحه ای بر اساس مسیرها ، از import() استفاده کنید (به بخش "کد بار تنبل که در حال حاضر نیازی به آن ندارید" مراجعه کنید). اگر از یک چارچوب استفاده می کنید ، ممکن است یک راه حل موجود برای این کار داشته باشد:

برای برنامه های سنتی چند صفحه ای

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

// webpack.config.js
module.exports = {
  entry: {
    home: './src/Home/index.js',
    article: './src/Article/index.js',
    profile: './src/Profile/index.js'
  }
};

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

$ webpack
Hash: 318d7b8490a7382bf23b
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./home.91b9ed27366fe7e33d6a.js    18 kB       1  [emitted]  home
./article.87a128755b16ac3294fd.js    32 kB       2  [emitted]  article
./profile.de945dc02685f6166781.js    24 kB       3  [emitted]  profile
 ./vendor.4f14b6326a80f4752a98.js    46 kB       4  [emitted]  vendor
./runtime.318d7b8490a7382bf23b.js  1.45 kB       5  [emitted]  runtime

بنابراین ، اگر فقط در صفحه مقاله از Lodash استفاده می کند ، بسته های home و profile آن را شامل نمی شوند - و کاربر هنگام مراجعه به صفحه اصلی مجبور به بارگیری این کتابخانه نخواهد بود.

درختان وابستگی جداگانه اشکال خود را دارند. اگر دو نقطه ورود از Lodash استفاده کنید ، و وابستگی های خود را به بسته نرم افزاری فروشنده منتقل نکرده اید ، هر دو نقطه ورود شامل یک نسخه از Lodash است. برای حل این مسئله ، در Webpack 4 ، optimization.splitChunks.chunks: 'all' به پیکربندی وب خود اضافه کنید:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

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

یا در وب 3 ، از CommonsChunkPlugin استفاده کنید - وابستگی های مشترک را به یک پرونده مشخص شده جدید منتقل می کند:

module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common',
      minChunks: 2    // 2 is the default value
    })
  ]
};

برای یافتن بهترین مورد ، با ارزش minChunks بازی کنید. به طور کلی ، شما می خواهید آن را کوچک نگه دارید ، اما در صورت افزایش تعداد تکه ها افزایش می یابد. به عنوان مثال ، برای 3 قطعه ، minChunks ممکن است 2 باشند ، اما برای 30 قطعه ، ممکن است 8 باشد - زیرا اگر آن را در 2 نگه دارید ، ماژول های زیادی وارد پرونده مشترک می شوند و بیش از حد آن را باد می کنند.

در ادامه مطلب

شناسه ماژول را با ثبات تر کنید

هنگام ساخت کد ، Webpack به هر ماژول شناسه اختصاص می دهد. بعداً ، این شناسه ها در داخل بسته نرم افزاری require() استفاده می شوند. شما معمولاً قبل از مسیرهای ماژول ، شناسه ها را در خروجی ساخت مشاهده می کنید:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.4e50a16675574df6a9e9.js    60 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

↓ اینجا

[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
[4] ./comments.js 58 kB {0} [built]
[5] ./ads.js 74 kB {1} [built]
+ 1 hidden module

به طور پیش فرض ، شناسه ها با استفاده از پیشخوان محاسبه می شوند (یعنی ماژول اول دارای شناسه 0 ، مورد دوم دارای شناسه 1 و غیره). مشکل این مسئله این است که وقتی یک ماژول جدید اضافه می کنید ، ممکن است در وسط لیست ماژول ظاهر شود و همه شناسه های ماژول های بعدی را تغییر دهد:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.5c82c0f337fcb22672b5.js    22 kB       0  [emitted]
   ./main.0c8b617dfc40c2827ae3.js    82 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime
   [0] ./index.js 29 kB {1} [built]
   [2] (webpack)/buildin/global.js 488 bytes {2} [built]
   [3] (webpack)/buildin/module.js 495 bytes {2} [built]

↓ ما یک ماژول جدید اضافه کرده ایم ...

[4] ./webPlayer.js 24 kB {1} [built]

↓ و نگاه کنید که چه کاری انجام داده است! comments.js اکنون به جای 4 شناسه 5 دارد

[5] ./comments.js 58 kB {0} [built]

ads.js اکنون به جای 5 شناسه 6 دارد

[6] ./ads.js 74 kB {1} [built]
       + 1 hidden module

این همه تکه هایی را که شامل یا به ماژول هایی با شناسه های تغییر یافته وابسته یا وابسته هستند ، بی اعتبار می کند - حتی اگر کد واقعی آنها تغییر نکرده باشد. در مورد ما ، 0 قطعه (تکه با comments.js ) و قطعه main (قطعه با کد برنامه دیگر) باطل می شوند - در حالی که فقط قسمت main باید بوده است.

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

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.6168aaac8461862eab7a.js  22.5 kB       0  [emitted]
   ./main.a2e49a279552980e3b91.js    60 kB       1  [emitted]  main
 ./vendor.ff9f7ea865884e6a84c8.js    46 kB       2  [emitted]  vendor
./runtime.25f5d0204e4f77fa57a1.js  1.45 kB       3  [emitted]  runtime

↓ اینجا

[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
    + 1 hidden module

با این روش ، شناسه یک ماژول فقط در صورت تغییر نام یا حرکت آن ماژول تغییر می کند. ماژول های جدید بر شناسه های ماژول های دیگر تأثیر نمی گذارد.

برای فعال کردن افزونه ، آن را به بخش plugins پیکربندی اضافه کنید:

// webpack.config.js
module.exports = {
  plugins: [
    new webpack.HashedModuleIdsPlugin()
  ]
};

در ادامه مطلب

جمع بندی

  • بسته نرم افزاری را ذخیره کرده و با تغییر نام بسته نرم افزاری بین نسخه ها تمایز قائل شوید
  • بسته نرم افزاری را به کد برنامه ، کد فروشنده و زمان اجرا تقسیم کنید
  • زمان اجرا را برای ذخیره درخواست HTTP وارد کنید
  • کد غیر بحرانی بار تنبل با import
  • برای جلوگیری از بارگیری چیزهای غیر ضروری ، کد را توسط مسیرها/صفحات تقسیم کنید
،

چگونه Webpack به حافظه پنهان دارایی کمک می کند

نکته بعدی (پس از بهینه سازی اندازه برنامه که باعث بهبود زمان بارگیری برنامه می شود ، ذخیره سازی است. از آن استفاده کنید تا قسمت هایی از برنامه را روی مشتری نگه دارید و هر بار از بارگیری مجدد آنها جلوگیری کنید.

از نسخه های نسخه بسته و حافظه پنهان استفاده کنید

رویکرد مشترک انجام حافظه پنهان این است:

  1. به مرورگر بگویید تا یک پرونده را برای مدت زمان طولانی (به عنوان مثال ، یک سال) ذخیره کنید:

    # Server header
    Cache-Control: max-age=31536000
    

    اگر آشنا نیستید که چه چیزی Cache-Control انجام می دهد ، به پست عالی جیک Archibald در ذخیره بهترین روشها مراجعه کنید.

  2. و هنگامی که تغییر کرده است برای مجبور کردن بارگذاری مجدد ، پرونده را تغییر نام دهید:

    <!-- Before the change -->
    <script src="./index-v15.js"></script>
    
    <!-- After the change -->
    <script src="./index-v16.js"></script>
    

این رویکرد به مرورگر می گوید که پرونده JS را بارگیری کنید ، آن را ذخیره کرده و از نسخه ذخیره شده استفاده کنید. مرورگر فقط در صورت تغییر نام پرونده (یا اگر یک سال می گذرد) فقط به شبکه ضربه می زند.

با استفاده از وب ، همین کار را می کنید ، اما به جای شماره نسخه ، هش پرونده را مشخص می کنید. برای وارد کردن هش در نام پرونده ، از [chunkhash] استفاده کنید:

// webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
  }
};

اگر برای ارسال آن به مشتری نیاز به نام پرونده دارید ، از HtmlWebpackPlugin یا WebpackManifestPlugin استفاده کنید.

HtmlWebpackPlugin یک رویکرد ساده اما انعطاف پذیر است. در حین تدوین ، این افزونه یک فایل HTML تولید می کند که شامل کلیه منابع کامپایل شده است. اگر منطق سرور شما پیچیده نیست ، باید برای شما کافی باشد:

<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>

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

// manifest.json
{
  "bundle.js": "bundle.8e0d62a03.js"
}

در ادامه مطلب

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

وابستگی ها

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

برای استخراج وابستگی ها به یک تکه جداگانه ، سه مرحله را انجام دهید:

  1. نام پرونده خروجی را با [name].[chunkname].js :

    // webpack.config.js
    module.exports = {
      output: {
        // Before
        filename: 'bundle.[chunkhash].js',
        // After
        filename: '[name].[chunkhash].js'
      }
    };
    

    هنگامی که Webpack برنامه را می سازد ، [name] را با نام یک تکه جایگزین می کند. اگر قسمت [name] را اضافه نکنیم ، باید بین تکه های هش آنها تفاوت قائل شویم - که بسیار سخت است!

  2. قسمت entry را به یک شی تبدیل کنید:

    // webpack.config.js
    module.exports = {
      // Before
      entry: './index.js',
      // After
      entry: {
        main: './index.js'
      }
    };
    

    در این قطعه ، "اصلی" نام یک تکه است. این نام از مرحله 1 به جای [name] جایگزین می شود.

    در حال حاضر ، اگر برنامه را بسازید ، این بخش کل کد برنامه را شامل می شود - دقیقاً مثل این که ما این مراحل را انجام نداده ایم. اما این در یک ثانیه تغییر خواهد کرد.

  3. در Webpack 4 ، به optimization.splitChunks.chunks: 'all' به پیکربندی Webpack خود اضافه کنید:

    // webpack.config.js (for webpack 4)
    module.exports = {
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      }
    };
    

    این گزینه تقسیم کد هوشمند را امکان پذیر می کند. با استفاده از آن ، Webpack در صورت بزرگتر شدن از 30 کیلوبایت (قبل از Minification و GZIP) کد فروشنده را استخراج می کند. همچنین کد مشترک را استخراج می کند - اگر ساخت شما چندین بسته تولید کند (به عنوان مثال اگر برنامه خود را به مسیرها تقسیم کنید ) مفید است.

    در وب 3 ، CommonsChunkPlugin اضافه کنید:

    // webpack.config.js (for webpack 3)
    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
        // A name of the chunk that will include the dependencies.
        // This name is substituted in place of [name] from step 1
        name: 'vendor',
    
        // A function that determines which modules to include into this chunk
        minChunks: module => module.context && module.context.includes('node_modules'),
        })
      ]
    };
    

    این افزونه تمام ماژول ها را شامل می شود که مسیرها شامل node_modules هستند و آنها را به یک پرونده جداگانه به نام vendor.[chunkhash].js .

پس از این تغییرات ، هر ساخت به جای یکی دو پرونده ایجاد می کند: main.[chunkhash].js و vendors~main.[chunkhash].js vendor.[chunkhash].js در صورت استفاده از Webpack 4 ، اگر وابستگی های کوچک باشد ، بسته نرم افزاری فروشنده تولید نمی شود - و این خوب است:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                        Asset      Size  Chunks             Chunk Names
 ./main.00bab6fd3100008a42b0.js   82 kB       0  [emitted]  main
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

مرورگر این پرونده ها را به طور جداگانه ذخیره می کند - و فقط کد را تغییر می دهد که تغییر می کند.

کد Webpack کد زمان اجرا

متأسفانه ، استخراج فقط کد فروشنده کافی نیست. اگر سعی می کنید چیزی را در کد برنامه تغییر دهید:

// index.js
…
…

// E.g. add this:
console.log('Wat');

متوجه خواهید شد که هش vendor نیز تغییر می کند:

                           Asset   Size  Chunks             Chunk Names
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

                            Asset   Size  Chunks             Chunk Names
./vendor.e6ea4504d61a1cc1c60b.js  47 kB       1  [emitted]  vendor

این اتفاق می افتد زیرا بسته نرم افزاری Webpack ، جدا از کد ماژول ها ، دارای زمان اجرا است - یک قطعه کوچک از کد که مدیریت ماژول را مدیریت می کند. هنگامی که کد را به چندین پرونده تقسیم می کنید ، این قطعه کد شامل نقشه برداری بین شناسه های قطعه و پرونده های مربوطه است:

// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
    "0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";

Webpack شامل این زمان اجرا در آخرین تکه تولید شده است که در مورد ما vendor است. و هر بار که هر قطعه تغییر می کند ، این قطعه کد نیز تغییر می کند و باعث می شود کل تکه vendor تغییر کند.

برای حل این مسئله ، بیایید زمان اجرا را به یک پرونده جداگانه منتقل کنیم. در Webpack 4 ، این با فعال کردن گزینه optimization.runtimeChunk حاصل می شود. گزینه runtimechunk:

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

در Webpack 3 ، این کار را با ایجاد یک تکه خالی اضافی با CommonsChunkPlugin انجام دهید:

// webpack.config.js (for webpack 3)
module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: module => module.context && module.context.includes('node_modules')
    }),
    // This plugin must come after the vendor one (because webpack
    // includes runtime into the last chunk)
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime',
      // minChunks: Infinity means that no app modules
      // will be included into this chunk
      minChunks: Infinity
    })
  ]
};

پس از این تغییرات ، هر ساخت و ساز سه پرونده تولید می کند:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                            Asset     Size  Chunks             Chunk Names
   ./main.00bab6fd3100008a42b0.js    82 kB       0  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       1  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

آنها را در index.html به ترتیب معکوس وارد کنید - و شما تمام شد:

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>

در ادامه مطلب

بسته بندی وب درون خطی برای ذخیره یک درخواست HTTP اضافی

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

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>

این کار را انجام دهید:

<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>

زمان اجرا اندک است و از بین بردن آن به شما کمک می کند تا یک درخواست HTTP را ذخیره کنید (با HTTP/1 بسیار مهم است ؛ با HTTP/2 از اهمیت کمتری برخوردار است اما هنوز هم ممکن است تأثیر بگذارد).

در اینجا نحوه انجام آن آمده است.

اگر HTML را با htmlwebpackplugin تولید کنید

اگر از HTMLWebPackPlugin برای تولید یک فایل HTML استفاده می کنید ، inlinesourceplugin تمام آنچه شما نیاز دارید است:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      inlineSource: 'runtime~.+\\.js',
    }),
    new InlineSourcePlugin()
  ]
};

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

با صفحه وب 4:

  1. WebpackManifestPlugin را اضافه کنید تا نام تولید شده از زمان اجرا را بدانید:

    // webpack.config.js (for webpack 4)
    const ManifestPlugin = require('webpack-manifest-plugin');
    
    module.exports = {
      plugins: [
        new ManifestPlugin()
      ]
    };
    

    ساخت با این افزونه فایلی را ایجاد می کند که به نظر می رسد:

    // manifest.json
    {
      "runtime~main.js": "runtime~main.8e0d62a03.js"
    }
    
  2. محتوای قطعه زمان اجرا را به روشی مناسب وارد کنید. به عنوان مثال با Node.js و Express:

    // server.js
    const fs = require('fs');
    const manifest = require('./manifest.json');
    const runtimeContent = fs.readFileSync(manifest['runtime~main.js'], 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

یا با صفحه وب 3:

  1. با مشخص کردن filename ، نام زمان اجرا را استاتیک کنید:

    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
          name: 'runtime',
          minChunks: Infinity,
          filename: 'runtime.js'
        })
      ]
    };
    
  2. محتوای runtime.js را به روشی مناسب وارد کنید. به عنوان مثال با Node.js و Express:

    // server.js
    const fs = require('fs');
    const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

کد بار تنبل که الان به آن احتیاج ندارید

بعضی اوقات ، یک صفحه دارای بخش های بیشتر و کمتری است:

  • اگر یک صفحه ویدیویی را در YouTube بارگذاری می کنید ، بیشتر به این فیلم اهمیت می دهید تا درباره نظرات. در اینجا ، این ویدئو مهمتر از نظرات است.
  • اگر مقاله ای را در یک سایت خبری باز کنید ، بیشتر به متن مقاله اهمیت می دهید تا تبلیغات. در اینجا ، متن از تبلیغات مهمتر است.

در چنین مواردی ، عملکرد بارگذاری اولیه را با بارگیری فقط مهمترین مطالب ابتدا بهبود بخشید و بار دیگر قطعات باقی مانده را بارگیری کنید. برای این کار از عملکرد import() و کدگذاری کد استفاده کنید:

// videoPlayer.js
export function renderVideoPlayer() { … }

// comments.js
export function renderComments() { … }

// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();

// …Custom event listener
onShowCommentsClick(() => {
  import('./comments').then((comments) => {
    comments.renderComments();
  });
});

import() مشخص می کند که می خواهید یک ماژول خاص را به صورت پویا بارگیری کنید. هنگامی که Webpack import('./module.js') ، این ماژول را به یک تکه جداگانه منتقل می کند:

$ webpack
Hash: 39b2a53cb4e73f0dc5b2
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.f7e53d8e13e9a2745d6d.js    60 kB       1  [emitted]  main
 ./vendor.4f14b6326a80f4752a98.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

و آن را فقط در هنگام اجرای اجرا به عملکرد import() بارگیری می کند.

این باعث می شود بسته main کوچکتر شود و زمان بارگذاری اولیه را بهبود بخشد. حتی بیشتر ، این باعث می شود که حافظه پنهان شود - اگر کد را در قسمت اصلی تغییر دهید ، Comports Chunk تحت تأثیر قرار نمی گیرد.

در ادامه مطلب

کد را به مسیرها و صفحات تقسیم کنید

اگر برنامه شما دارای چندین مسیر یا صفحات است ، اما فقط یک پرونده JS با کد (یک قطعه main ) وجود دارد ، به احتمال زیاد شما در هر درخواست بایت اضافی سرو می کنید. به عنوان مثال ، هنگامی که کاربر از صفحه اصلی سایت شما بازدید می کند:

یک صفحه اصلی WebFundamentals

آنها نیازی به بارگذاری کد برای ارائه مقاله ای که در صفحه دیگری وجود دارد - اما آنها آن را بارگیری می کنند. علاوه بر این ، اگر کاربر همیشه فقط از صفحه اصلی بازدید کند ، و شما در کد مقاله تغییری ایجاد می کنید ، Webpack کل بسته نرم افزاری را باطل می کند-و کاربر مجبور است مجدداً کل برنامه را بارگیری کند.

اگر برنامه را به صفحات تقسیم کنیم (یا مسیرها ، اگر یک برنامه تک صفحه ای باشد) ، کاربر فقط کد مربوطه را بارگیری می کند. به علاوه ، مرورگر کد برنامه را بهتر ذخیره می کند: اگر کد صفحه اصلی را تغییر دهید ، وب وب فقط تکه مربوطه را باطل می کند.

برای برنامه های تک صفحه ای

برای تقسیم برنامه های تک صفحه ای بر اساس مسیرها ، از import() استفاده کنید (به بخش "کد بار تنبل که در حال حاضر نیازی به آن ندارید" مراجعه کنید). اگر از یک چارچوب استفاده می کنید ، ممکن است یک راه حل موجود برای این کار داشته باشد:

برای برنامه های سنتی چند صفحه ای

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

// webpack.config.js
module.exports = {
  entry: {
    home: './src/Home/index.js',
    article: './src/Article/index.js',
    profile: './src/Profile/index.js'
  }
};

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

$ webpack
Hash: 318d7b8490a7382bf23b
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./home.91b9ed27366fe7e33d6a.js    18 kB       1  [emitted]  home
./article.87a128755b16ac3294fd.js    32 kB       2  [emitted]  article
./profile.de945dc02685f6166781.js    24 kB       3  [emitted]  profile
 ./vendor.4f14b6326a80f4752a98.js    46 kB       4  [emitted]  vendor
./runtime.318d7b8490a7382bf23b.js  1.45 kB       5  [emitted]  runtime

بنابراین ، اگر فقط در صفحه مقاله از Lodash استفاده می کند ، بسته های home و profile آن را شامل نمی شوند - و کاربر هنگام مراجعه به صفحه اصلی مجبور به بارگیری این کتابخانه نخواهد بود.

درختان وابستگی جداگانه اشکال خود را دارند. اگر دو نقطه ورود از Lodash استفاده کنید ، و وابستگی های خود را به بسته نرم افزاری فروشنده منتقل نکرده اید ، هر دو نقطه ورود شامل یک نسخه از Lodash است. برای حل این مسئله ، در Webpack 4 ، optimization.splitChunks.chunks: 'all' به پیکربندی وب خود اضافه کنید:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

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

یا در وب 3 ، از CommonsChunkPlugin استفاده کنید - وابستگی های مشترک را به یک پرونده مشخص شده جدید منتقل می کند:

module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common',
      minChunks: 2    // 2 is the default value
    })
  ]
};

برای یافتن بهترین مورد ، با ارزش minChunks بازی کنید. به طور کلی ، شما می خواهید آن را کوچک نگه دارید ، اما در صورت افزایش تعداد تکه ها افزایش می یابد. به عنوان مثال ، برای 3 قطعه ، minChunks ممکن است 2 باشند ، اما برای 30 قطعه ، ممکن است 8 باشد - زیرا اگر آن را در 2 نگه دارید ، ماژول های زیادی وارد پرونده مشترک می شوند و بیش از حد آن را باد می کنند.

در ادامه مطلب

شناسه ماژول را با ثبات تر کنید

هنگام ساخت کد ، Webpack به هر ماژول شناسه اختصاص می دهد. بعداً ، این شناسه ها در داخل بسته نرم افزاری require() استفاده می شوند. شما معمولاً قبل از مسیرهای ماژول ، شناسه ها را در خروجی ساخت مشاهده می کنید:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.4e50a16675574df6a9e9.js    60 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

↓ اینجا

[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
[4] ./comments.js 58 kB {0} [built]
[5] ./ads.js 74 kB {1} [built]
+ 1 hidden module

به طور پیش فرض ، شناسه ها با استفاده از پیشخوان محاسبه می شوند (یعنی ماژول اول دارای شناسه 0 ، مورد دوم دارای شناسه 1 و غیره). مشکل این مسئله این است که وقتی یک ماژول جدید اضافه می کنید ، ممکن است در وسط لیست ماژول ظاهر شود و همه شناسه های ماژول های بعدی را تغییر دهد:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.5c82c0f337fcb22672b5.js    22 kB       0  [emitted]
   ./main.0c8b617dfc40c2827ae3.js    82 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime
   [0] ./index.js 29 kB {1} [built]
   [2] (webpack)/buildin/global.js 488 bytes {2} [built]
   [3] (webpack)/buildin/module.js 495 bytes {2} [built]

↓ ما یک ماژول جدید اضافه کرده ایم ...

[4] ./webPlayer.js 24 kB {1} [built]

↓ و نگاه کنید که چه کاری انجام داده است! comments.js اکنون به جای 4 شناسه 5 دارد

[5] ./comments.js 58 kB {0} [built]

ads.js اکنون به جای 5 شناسه 6 دارد

[6] ./ads.js 74 kB {1} [built]
       + 1 hidden module

این همه تکه هایی را که شامل یا به ماژول هایی با شناسه های تغییر یافته وابسته یا وابسته هستند ، بی اعتبار می کند - حتی اگر کد واقعی آنها تغییر نکرده باشد. در مورد ما ، 0 قطعه (تکه با comments.js ) و قطعه main (قطعه با کد برنامه دیگر) باطل می شوند - در حالی که فقط قسمت main باید بوده است.

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

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.6168aaac8461862eab7a.js  22.5 kB       0  [emitted]
   ./main.a2e49a279552980e3b91.js    60 kB       1  [emitted]  main
 ./vendor.ff9f7ea865884e6a84c8.js    46 kB       2  [emitted]  vendor
./runtime.25f5d0204e4f77fa57a1.js  1.45 kB       3  [emitted]  runtime

↓ اینجا

[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
    + 1 hidden module

با این روش ، شناسه یک ماژول فقط در صورت تغییر نام یا حرکت آن ماژول تغییر می کند. ماژول های جدید بر شناسه های ماژول های دیگر تأثیر نمی گذارد.

برای فعال کردن افزونه ، آن را به بخش plugins پیکربندی اضافه کنید:

// webpack.config.js
module.exports = {
  plugins: [
    new webpack.HashedModuleIdsPlugin()
  ]
};

در ادامه مطلب

جمع بندی

  • بسته نرم افزاری را ذخیره کرده و با تغییر نام بسته نرم افزاری بین نسخه ها تمایز قائل شوید
  • بسته نرم افزاری را به کد برنامه ، کد فروشنده و زمان اجرا تقسیم کنید
  • زمان اجرا را برای ذخیره درخواست HTTP وارد کنید
  • کد غیر بحرانی بار تنبل با import
  • برای جلوگیری از بارگیری چیزهای غیر ضروری ، کد را توسط مسیرها/صفحات تقسیم کنید