ใช้ประโยชน์จากการแคชในระยะยาว

Webpack ช่วยแคชเนื้อหาได้อย่างไร

ขั้นตอนต่อไป (หลังจากเพิ่มประสิทธิภาพขนาดแอปซึ่งช่วยปรับปรุงเวลาที่ใช้ในการโหลดแอปคือการแคช ใช้เพื่อเก็บบางส่วนของแอปไว้ในไคลเอ็นต์ และหลีกเลี่ยงการดาวน์โหลดแอปซ้ำทุกครั้ง

ใช้การกำหนดเวอร์ชัน Bundle และส่วนหัวแคช

วิธีทั่วไปในการแคชคือ:

  1. ให้เบราว์เซอร์แคชไฟล์เป็นเวลานานมาก (เช่น 1 ปี)

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

    หากคุณไม่คุ้นเคยกับสิ่งที่ Cache-Control ทำ โปรดดูโพสต์ที่ยอดเยี่ยมของ Jake Archibald ในเรื่องแนวทางปฏิบัติแนะนำในการแคช

  2. แล้วเปลี่ยนชื่อไฟล์เมื่อมีการเปลี่ยนแปลงเพื่อบังคับให้ดาวน์โหลดใหม่ ดังนี้

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

วิธีนี้จะบอกเบราว์เซอร์ให้ดาวน์โหลดไฟล์ JS, แคช และใช้สำเนาที่แคชไว้ เบราว์เซอร์จะเข้าสู่เครือข่ายก็ต่อเมื่อชื่อไฟล์มีการเปลี่ยนแปลง (หรือเมื่อผ่านไป 1 ปี)

เมื่อใช้ 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"
}

อ่านเพิ่มเติม

แยกทรัพยากร Dependency และรันไทม์ไว้ในไฟล์แยกต่างหาก

การอ้างอิง

ทรัพยากร Dependency ของแอปมีแนวโน้มที่จะเปลี่ยนแปลงน้อยกว่าโค้ดของแอปจริง หากคุณย้ายไปเป็นไฟล์แยกต่างหาก เบราว์เซอร์จะแคชแยกไว้ต่างหากและจะไม่ดาวน์โหลดซ้ำทุกครั้งที่โค้ดของแอปเปลี่ยน

หากต้องการแยกทรัพยากร Dependency ออกเป็นกลุ่มที่แยกกัน ให้ทำตาม 3 ขั้นตอนดังนี้

  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'
      }
    };
    

    ในข้อมูลโค้ดนี้ "main" เป็นชื่อของชิ้นส่วน ซึ่งจะมาแทนที่ [name] จากขั้นตอนที่ 1

    ถึงตอนนี้ หากคุณสร้างแอป กลุ่มนี้จะใส่โค้ดของแอปทั้งหมดไว้ด้วย เช่นเดียวกับที่เรายังไม่ได้ทำตามขั้นตอนเหล่านี้ แต่จะเปลี่ยนไปในอีกสักครู่

  3. ใน Webpack 4 ให้เพิ่มตัวเลือก optimization.splitChunks.chunks: 'all' ลงในการกำหนดค่า Webpack ดังนี้

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

    ตัวเลือกนี้จะเปิดใช้การแยกสมาร์ทโค้ด Webpack จะแยกรหัสผู้ให้บริการออกมา หากมีขนาดใหญ่กว่า 30 kB (ก่อนการลดขนาดและ gzip) นอกจากนี้ยังจะแยกโค้ดทั่วไปด้วย ซึ่งจะเป็นประโยชน์หากบิลด์ของคุณสร้าง Bundle หลายแพ็กเกจ (เช่น หากคุณแยกแอปออกเป็นเส้นทาง)

    ใน Webpack 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

หลังจากการเปลี่ยนแปลงเหล่านี้ บิลด์แต่ละรายการจะสร้าง 2 ไฟล์แทนที่จะเป็นไฟล์เดียว: main.[chunkhash].js และ vendor.[chunkhash].js (vendors~main.[chunkhash].js สำหรับ Webpack 4) ในกรณีของ Webpack 4 ระบบอาจไม่สร้างแพ็กเกจผู้ให้บริการหากทรัพยากร Dependency มีขนาดเล็ก ซึ่งก็ไม่เป็นไร

$ 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 ดังนี้

// 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
    })
  ]
};

หลังจากการเปลี่ยนแปลงเหล่านี้ บิลด์แต่ละรายการจะสร้างไฟล์ 3 ไฟล์ ได้แก่

$ 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>

อ่านเพิ่มเติม

รันไทม์ Webpack แบบอินไลน์เพื่อบันทึกคำขอ HTTP เพิ่มเติม

เพื่อปรับปรุงประสิทธิภาพให้ดียิ่งขึ้น ให้ลองกำหนดรันไทม์ของ Webpack ไว้ในการตอบกลับของ 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 โดยใช้ตรรกะเซิร์ฟเวอร์ที่กำหนดเอง

เมื่อใช้ Webpack 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>
        …
      `);
    });
    

หรือเมื่อใช้ Webpack 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 Loading ที่คุณไม่ใช้ในขณะนี้

ในบางครั้ง หน้าเว็บอาจมีส่วนที่สำคัญมากหรือน้อยต่างกันไป ดังนี้

  • หากโหลดหน้าวิดีโอบน 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 มีขนาดเล็กลง ซึ่งช่วยปรับปรุงเวลาที่ใช้ในการโหลดในช่วงแรก ยิ่งไปกว่านั้น วิธีนี้ช่วยปรับปรุงการแคชให้ดียิ่งขึ้นด้วย หากคุณเปลี่ยนโค้ดในส่วนหลัก กลุ่มความคิดเห็นจะไม่ได้รับผลกระทบใดๆ

อ่านเพิ่มเติม

แยกโค้ดออกเป็นเส้นทางและหน้าเว็บ

หากแอปมีหลายเส้นทางหรือหน้าเว็บ แต่มีเพียงไฟล์ JS ที่มีโค้ดเพียงไฟล์เดียว (กลุ่ม main กลุ่มเดียว) อาจเป็นไปได้ว่าคุณกำลังใช้ไบต์มากเป็นพิเศษในคำขอแต่ละรายการ ตัวอย่างเช่น เมื่อผู้ใช้เข้าชมหน้าแรกของเว็บไซต์

หน้าแรกของ WebFundamentals

ไม่จำเป็นต้องโหลดโค้ดเพื่อแสดงผลบทความที่อยู่ในหน้าอื่น แต่จะโหลดโค้ดแทน นอกจากนี้ หากผู้ใช้เข้าชมเฉพาะหน้าแรกเสมอ และคุณเปลี่ยนแปลงโค้ดบทความ WebP จะทำให้กลุ่มบทความทั้งหมดไม่ถูกต้อง และผู้ใช้จะต้องดาวน์โหลดแอปทั้งหมดใหม่

หากเราแยกแอปออกเป็นหลายหน้า (หรือเส้นทางหากเป็นแอปแบบหน้าเดียว) ผู้ใช้จะดาวน์โหลดเฉพาะโค้ดที่เกี่ยวข้องเท่านั้น นอกจากนี้ เบราว์เซอร์จะแคชโค้ดของแอปให้ดีขึ้นด้วย ถ้าคุณเปลี่ยนโค้ดหน้าแรก เว็บแพ็คจะทำให้เฉพาะส่วนที่ตรงกันไม่ถูกต้อง

สำหรับแอปแบบหน้าเดียว

หากต้องการแยกแอปแบบหน้าเดียวตามเส้นทาง ให้ใช้ import() (ดูส่วน "โค้ดการโหลดแบบ Lazy Loading ที่คุณยังไม่ต้องใช้ในขณะนี้") หากคุณใช้เฟรมเวิร์ก อาจมีโซลูชันสำหรับสิ่งนี้อยู่แล้ว

สำหรับแอปที่มีหลายหน้าแบบดั้งเดิม

หากต้องการแยกแอปแบบดั้งเดิมตามหน้าเว็บ ให้ใช้จุดแรกเข้าของ Webpack หากแอปมีหน้าเว็บ 3 ประเภท ได้แก่ หน้าแรก หน้าบทความ และหน้าบัญชีผู้ใช้ แอปควรมี 3 รายการดังต่อไปนี้

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

Webpack จะสร้างโครงสร้างทรัพยากร Dependency แยกกัน สำหรับไฟล์แต่ละรายการแต่ละไฟล์ รวมถึงสร้าง Bundle ที่มีเฉพาะโมดูลที่ใช้โดยรายการนั้น

$ 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 ไว้ด้วย และผู้ใช้ไม่ต้องดาวน์โหลดคลังนี้เมื่อไปที่หน้าแรก

แต่ต้นไม้ Dependency ที่แยกกันจะมีข้อเสีย หากจุดแรกเข้า 2 จุดใช้ Lodash และคุณไม่ได้ย้ายทรัพยากร Dependency ไว้ในแพ็กเกจผู้ให้บริการ จุดแรกเข้าทั้ง 2 จุดจะมีสำเนาของ Lodash อยู่ด้วย หากต้องการแก้ไขปัญหานี้ ให้ใน Webpack 4 ให้เพิ่มตัวเลือก optimization.splitChunks.chunks: 'all' ลงในการกำหนดค่า Webpack ดังนี้

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

ตัวเลือกนี้จะเปิดใช้การแยกสมาร์ทโค้ด เมื่อใช้ตัวเลือกนี้ Webpack จะค้นหาโค้ดทั่วไป และแยกลงในไฟล์แยกต่างหากโดยอัตโนมัติ

หรือใน Webpack 3 ให้ใช้ CommonsChunkPlugin ซึ่งจะย้ายทรัพยากร Dependency ทั่วไปไปยังไฟล์ที่ระบุใหม่ดังนี้

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 รหัสที่ 2 มี 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 มีรหัส 5 จากเดิม 4 รหัส

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

↓ ตอนนี้ ads.js มีรหัส 6 แทนที่จะเป็น 5

[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
  • โค้ดที่ไม่สำคัญแบบ Lazy Loading ด้วย import
  • แยกโค้ดตามเส้นทาง/หน้าเพื่อหลีกเลี่ยงการโหลดเนื้อหาที่ไม่จำเป็น