ประโยชน์ของ webpack ในการแคชชิ้นงาน
สิ่งถัดไป (หลังจากการเพิ่มประสิทธิภาพขนาดแอปที่ช่วยปรับปรุงเวลาในการโหลดแอป) คือแคช ใช้เพื่อเก็บข้อมูลบางส่วนของแอปไว้ในไคลเอ็นต์และหลีกเลี่ยงการดาวน์โหลดซ้ำทุกครั้ง
ใช้การกำหนดเวอร์ชันของ App 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 และรันไทม์ออกเป็นไฟล์แยกต่างหาก
การอ้างอิง
ไลบรารีที่ใช้ร่วมกันของแอปมีแนวโน้มที่จะเปลี่ยนแปลงน้อยกว่าโค้ดแอปจริง หากคุณย้ายไฟล์เหล่านั้นไปยังไฟล์แยกต่างหาก เบราว์เซอร์จะแคชไฟล์แยกต่างหากและจะไม่ดาวน์โหลดไฟล์เหล่านั้นอีกครั้งทุกครั้งที่โค้ดแอปมีการเปลี่ยนแปลง
หากต้องการแยกไฟล์ที่ต้องพึ่งพาออกเป็นกลุ่มแยกกัน ให้ทําตาม 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) และจะดึงโค้ดทั่วไปออกมาด้วย ซึ่งจะมีประโยชน์หากบิลด์ของคุณสร้างหลายกลุ่ม (เช่น หากแยกแอปออกเป็นเส้นทาง)
ใน 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 เกี่ยวกับการแคชระยะยาว
- เอกสาร Webpack เกี่ยวกับรันไทม์และไฟล์ Manifest ของ webpack
- "การใช้ 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 ที่คุณไม่จําเป็นต้องใช้ในตอนนี้
บางครั้งหน้าเว็บอาจมีส่วนสำคัญและส่วนสำคัญน้อย ดังนี้
- หากคุณโหลดหน้าวิดีโอบน 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
Bundle เล็กลงและปรับปรุงเวลาในการโหลดครั้งแรก
นอกจากนี้ ยังช่วยปรับปรุงการแคชด้วย หากคุณเปลี่ยนโค้ดในข้อมูลโค้ดหลัก ข้อมูลโค้ดความคิดเห็นจะไม่ได้รับผลกระทบ
อ่านเพิ่มเติม
- เอกสาร Webpack สำหรับฟังก์ชัน
import()
- ข้อเสนอ JavaScript สําหรับการใช้ไวยากรณ์
import()
แยกโค้ดออกเป็นเส้นทางและหน้าเว็บ
หากแอปมีเส้นทางหรือหน้าเว็บหลายหน้า แต่มีไฟล์ JS เพียงไฟล์เดียวที่มีโค้ด (กลุ่ม main
รายการเดียว) แสดงว่าคุณอาจแสดงไบต์เพิ่มเติมในคําขอแต่ละรายการ ตัวอย่างเช่น เมื่อผู้ใช้เข้าชมหน้าแรกของเว็บไซต์
ผู้ใช้ไม่จำเป็นต้องโหลดโค้ดสำหรับแสดงผลบทความที่อยู่ในหน้าอื่น แต่ระบบจะโหลดโค้ดดังกล่าว นอกจากนี้ หากผู้ใช้เข้าชมเฉพาะหน้าแรกเสมอและคุณทำการเปลี่ยนแปลงในโค้ดบทความ webpack จะลบล้างทั้งแพ็กเกจ และผู้ใช้จะต้องดาวน์โหลดทั้งแอปอีกครั้ง
หากเราแยกแอปออกเป็นหน้าเว็บ (หรือเส้นทาง หากเป็นแอปหน้าเดียว) ผู้ใช้จะดาวน์โหลดเฉพาะโค้ดที่เกี่ยวข้อง นอกจากนี้ เบราว์เซอร์จะแคชโค้ดแอปได้ดีขึ้นด้วย หากคุณเปลี่ยนโค้ดหน้าแรก webpack จะลบเฉพาะกลุ่มที่เกี่ยวข้อง
สําหรับแอปหน้าเดียว
หากต้องการแยกแอปหน้าเว็บเดียวตามเส้นทาง ให้ใช้ import()
(ดูส่วน"โค้ดการโหลดแบบเลื่อนเวลาโหลดซึ่งคุณไม่จำเป็นต้องใช้ตอนนี้") หากคุณใช้เฟรมเวิร์ก เฟรมเวิร์กนั้นอาจมีโซลูชันสําหรับปัญหานี้อยู่แล้ว
- "การแยกโค้ด" ในเอกสารของ
react-router
(สำหรับ React) - "การโหลดแบบ Lazy Loading
เส้นทาง" ในเอกสารของ
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 แยกต่างหากและสร้างกลุ่มที่มีเฉพาะโมดูลที่ใช้โดยอินพุตนั้นๆ
$ 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 กลุ่ม minChunks
อาจเท่ากับ 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 มีรหัส 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 ด้วย
import
- แยกโค้ดตามเส้นทาง/หน้าเว็บเพื่อหลีกเลี่ยงการโหลดสิ่งที่ไม่จำเป็น