Webpack ช่วยแคชเนื้อหาได้อย่างไร
ขั้นตอนต่อไป (หลังจากเพิ่มประสิทธิภาพขนาดแอปซึ่งช่วยปรับปรุงเวลาที่ใช้ในการโหลดแอปคือการแคช ใช้เพื่อเก็บบางส่วนของแอปไว้ในไคลเอ็นต์ และหลีกเลี่ยงการดาวน์โหลดแอปซ้ำทุกครั้ง
ใช้การกำหนดเวอร์ชัน Bundle และส่วนหัวแคช
วิธีทั่วไปในการแคชคือ:
ให้เบราว์เซอร์แคชไฟล์เป็นเวลานานมาก (เช่น 1 ปี)
# Server header Cache-Control: max-age=31536000
หากคุณไม่คุ้นเคยกับสิ่งที่
Cache-Control
ทำ โปรดดูโพสต์ที่ยอดเยี่ยมของ Jake Archibald ในเรื่องแนวทางปฏิบัติแนะนำในการแคชแล้วเปลี่ยนชื่อไฟล์เมื่อมีการเปลี่ยนแปลงเพื่อบังคับให้ดาวน์โหลดใหม่ ดังนี้
<!-- 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"
}
อ่านเพิ่มเติม
- Jake Archibald เกี่ยวกับแนวทางปฏิบัติแนะนำในการแคช
แยกทรัพยากร Dependency และรันไทม์ไว้ในไฟล์แยกต่างหาก
การอ้างอิง
ทรัพยากร Dependency ของแอปมีแนวโน้มที่จะเปลี่ยนแปลงน้อยกว่าโค้ดของแอปจริง หากคุณย้ายไปเป็นไฟล์แยกต่างหาก เบราว์เซอร์จะแคชแยกไว้ต่างหากและจะไม่ดาวน์โหลดซ้ำทุกครั้งที่โค้ดของแอปเปลี่ยน
หากต้องการแยกทรัพยากร Dependency ออกเป็นกลุ่มที่แยกกัน ให้ทำตาม 3 ขั้นตอนดังนี้
แทนที่ชื่อไฟล์เอาต์พุตด้วย
[name].[chunkname].js
ดังนี้// webpack.config.js module.exports = { output: { // Before filename: 'bundle.[chunkhash].js', // After filename: '[name].[chunkhash].js' } };
เมื่อ Webpack สร้างแอป ระบบจะแทนที่
[name]
ด้วยชื่อกลุ่ม ถ้าไม่เพิ่มส่วน[name]
เราจะต้องแยก ส่วนต่างๆ ตามแฮช ซึ่งค่อนข้างยาก!แปลงช่อง
entry
เป็นออบเจ็กต์// webpack.config.js module.exports = { // Before entry: './index.js', // After entry: { main: './index.js' } };
ในข้อมูลโค้ดนี้ "main" เป็นชื่อของชิ้นส่วน ซึ่งจะมาแทนที่
[name]
จากขั้นตอนที่ 1ถึงตอนนี้ หากคุณสร้างแอป กลุ่มนี้จะใส่โค้ดของแอปทั้งหมดไว้ด้วย เช่นเดียวกับที่เรายังไม่ได้ทำตามขั้นตอนเหล่านี้ แต่จะเปลี่ยนไปในอีกสักครู่
ใน 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 เกี่ยวกับการแคชระยะยาว
- เอกสาร Webpack เกี่ยวกับรันไทม์ของ Webpack และไฟล์ Manifest
- "ใช้ CommonsChunkPlugin ให้เกิดประโยชน์สูงสุด"
- วิธีการทำงานของ
optimization.splitChunks
และoptimization.runtimeChunk
รันไทม์ 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
เพิ่ม
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" }
แทรกเนื้อหาไว้ในส่วนรันไทม์ด้วยวิธีการที่สะดวก เช่น เมื่อใช้ 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 ให้ทำดังนี้
ทำให้ชื่อรันไทม์เป็นแบบคงที่โดยการระบุ
filename
module.exports = { plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', minChunks: Infinity, filename: 'runtime.js' }) ] };
แทรกเนื้อหา
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
มีขนาดเล็กลง ซึ่งช่วยปรับปรุงเวลาที่ใช้ในการโหลดในช่วงแรก
ยิ่งไปกว่านั้น วิธีนี้ช่วยปรับปรุงการแคชให้ดียิ่งขึ้นด้วย หากคุณเปลี่ยนโค้ดในส่วนหลัก
กลุ่มความคิดเห็นจะไม่ได้รับผลกระทบใดๆ
อ่านเพิ่มเติม
- เอกสาร Webpack สำหรับฟังก์ชัน
import()
- ข้อเสนอ JavaScript สำหรับการใช้ไวยากรณ์
import()
แยกโค้ดออกเป็นเส้นทางและหน้าเว็บ
หากแอปมีหลายเส้นทางหรือหน้าเว็บ แต่มีเพียงไฟล์ JS ที่มีโค้ดเพียงไฟล์เดียว (กลุ่ม main
กลุ่มเดียว) อาจเป็นไปได้ว่าคุณกำลังใช้ไบต์มากเป็นพิเศษในคำขอแต่ละรายการ ตัวอย่างเช่น เมื่อผู้ใช้เข้าชมหน้าแรกของเว็บไซต์
ไม่จำเป็นต้องโหลดโค้ดเพื่อแสดงผลบทความที่อยู่ในหน้าอื่น แต่จะโหลดโค้ดแทน นอกจากนี้ หากผู้ใช้เข้าชมเฉพาะหน้าแรกเสมอ และคุณเปลี่ยนแปลงโค้ดบทความ WebP จะทำให้กลุ่มบทความทั้งหมดไม่ถูกต้อง และผู้ใช้จะต้องดาวน์โหลดแอปทั้งหมดใหม่
หากเราแยกแอปออกเป็นหลายหน้า (หรือเส้นทางหากเป็นแอปแบบหน้าเดียว) ผู้ใช้จะดาวน์โหลดเฉพาะโค้ดที่เกี่ยวข้องเท่านั้น นอกจากนี้ เบราว์เซอร์จะแคชโค้ดของแอปให้ดีขึ้นด้วย ถ้าคุณเปลี่ยนโค้ดหน้าแรก เว็บแพ็คจะทำให้เฉพาะส่วนที่ตรงกันไม่ถูกต้อง
สำหรับแอปแบบหน้าเดียว
หากต้องการแยกแอปแบบหน้าเดียวตามเส้นทาง ให้ใช้ import()
(ดูส่วน "โค้ดการโหลดแบบ Lazy Loading ที่คุณยังไม่ต้องใช้ในขณะนี้") หากคุณใช้เฟรมเวิร์ก
อาจมีโซลูชันสำหรับสิ่งนี้อยู่แล้ว
- "การแยกโค้ด"
ในเอกสารของ
react-router
(สำหรับ React) - "Lazy Loading
Routes" ในเอกสารของ
vue-router
(สำหรับ Vue.js)
สำหรับแอปที่มีหลายหน้าแบบดั้งเดิม
หากต้องการแยกแอปแบบดั้งเดิมตามหน้าเว็บ ให้ใช้จุดแรกเข้าของ 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 เกี่ยวกับแนวคิดของจุดแรกเข้า
- เอกสาร Webpack เกี่ยวกับ CommonsChunkPlugin
- "ใช้ CommonsChunkPlugin ให้เกิดประโยชน์สูงสุด"
- วิธีการทำงานของ
optimization.splitChunks
และoptimization.runtimeChunk
ทำให้รหัสโมดูลเสถียรขึ้น
เมื่อสร้างโค้ด 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()
]
};
อ่านเพิ่มเติม
- เอกสาร Webpack เกี่ยวกับ HashedModuleIdsPlugin
สรุป
- แคชแพ็กเกจและแยกความแตกต่างระหว่างเวอร์ชันต่างๆ ด้วยการเปลี่ยนชื่อแพ็กเกจ
- แยกแพ็กเกจเป็นโค้ดของแอป รหัสผู้ให้บริการ และรันไทม์
- แทรกรันไทม์เพื่อบันทึกคำขอ HTTP
- โค้ดที่ไม่สำคัญแบบ Lazy Loading ด้วย
import
- แยกโค้ดตามเส้นทาง/หน้าเพื่อหลีกเลี่ยงการโหลดเนื้อหาที่ไม่จำเป็น