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

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

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

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

  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 และรันไทม์ออกเป็นไฟล์แยกต่างหาก

การอ้างอิง

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

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

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

// 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 ที่คุณไม่จําเป็นต้องใช้ในตอนนี้

บางครั้งหน้าเว็บอาจมีส่วนสำคัญและส่วนสำคัญน้อย ดังนี้

  • หากคุณโหลดหน้าวิดีโอบน YouTube แสดงว่าคุณให้ความสำคัญกับวิดีโอมากกว่าความคิดเห็น ในวิดีโอนี้ วิดีโอมีความสำคัญมากกว่าความคิดเห็น
  • หากคุณเปิดบทความในเว็บไซต์ข่าว แสดงว่าคุณสนใจข้อความในบทความมากกว่าโฆษณา ข้อความมีความสำคัญมากกว่าโฆษณา

ในกรณีดังกล่าว ให้ปรับปรุงประสิทธิภาพการโหลดเริ่มต้นโดยการดาวน์โหลดเฉพาะรายการที่สำคัญที่สุดก่อน แล้วโหลดส่วนที่เหลือในภายหลัง ใช้ฟังก์ชัน import() และcode-splittingสำหรับรายการต่อไปนี้

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

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

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

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

หน้าแรกของ WebFundamentals

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

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

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

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

สําหรับแอปแบบหลายหน้าแบบดั้งเดิม

หากต้องการแยกแอปแบบดั้งเดิมตามหน้าเว็บ ให้ใช้จุดแรกเข้าของ 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 แยกต่างหากและสร้างกลุ่มที่มีเฉพาะโมดูลที่ใช้โดยอินพุตนั้นๆ

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

อย่างไรก็ตาม แผนภูมิความเกี่ยวข้องแยกต่างหากก็มีข้อเสียอยู่ หากมี Entry Point 2 รายการที่ใช้ Lodash และคุณยังไม่ได้ย้าย Dependency ไปยัง Bundle ของ Vendor ทั้ง 2 Entry Point จะมีสำเนาของ 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 มีรหัส 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()
  ]
};

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

สรุป

  • แคชชุดและแยกความแตกต่างระหว่างเวอร์ชันด้วยการเปลี่ยนชื่อ Bundle
  • แยกกลุ่มเป็นโค้ดแอป โค้ดผู้ให้บริการ และรันไทม์
  • ใส่รันไทม์ในบรรทัดเพื่อบันทึกคําขอ HTTP
  • โหลดโค้ดที่ไม่สําคัญแบบ Lazy ด้วย import
  • แยกโค้ดตามเส้นทาง/หน้าเว็บเพื่อหลีกเลี่ยงการโหลดสิ่งที่ไม่จำเป็น