Shimming เป็นเทคนิคเพื่อให้มั่นใจถึงความเข้ากันได้ของโมดูลที่คาดหวังตัวแปรทั่วโลกหรือ API เฉพาะบางอย่าง ในระบบนิเวศของ Webpack นั้น shimming ถูกนำมาใช้ผ่าน ProvidePlugin, imports-loader และ exports-loader ซึ่งช่วยให้เชื่อมต่อไลบรารี legacy ได้โดยไม่ต้องแก้ไขซอร์สโค้ดของมัน ตาม Webpack Documentation (2026) นั้น shimming ยังคงเป็นเครื่องมือสำคัญสำหรับการรวมปลั๊กอิน jQuery และการพึ่งพาอื่นๆ ที่ไม่รองรับระบบโมดูล
สิ่งสำคัญ
Shimming เป็นเทคนิคซอฟต์แวร์ที่ฝังชั้นความเข้ากันได้ระหว่างโค้ดกับสภาพแวดล้อมโดยไม่แก้ไขซอร์สโค้ดของโมดูล ในบริบทของการบิลด์ JavaScript นั้น shimming แก้ปัญหาเมื่อโมดูลเข้าถึงตัวแปรทั่วโลก (window.$, global.process) ที่ไม่มีอยู่ในสภาพแวดล้อมแบบโมดูล
Polyfill ใช้งานฟังก์ชันที่ขาดหายไปตั้งแต่เริ่มต้น โดยเพิ่มความสามารถใหม่ให้กับสภาพแวดล้อม ตัวอย่างเช่น core-js เพิ่ม Array.prototype.flatMap สำหรับเบราว์เซอร์เก่า Shim ในทางกลับกันจะเปลี่ยนเส้นทางคำเรียกที่มีอยู่ไปยังการใช้งานที่มีอยู่ หรือแทนที่ออบเจ็กต์ทั่วโลกที่คาดหวัง ใน Webpack นั้น ProvidePlugin จะแทรก import $ from 'jquery' โดยอัตโนมัติทุกที่ที่พบการอ้างอิงถึงตัวแปรทั่วโลก $ โดยไม่ต้องแก้ไขโค้ด
ความแตกต่างหลักอยู่ที่เป้าหมาย Polyfill เพิ่มสิ่งที่ไม่เคยมี ในขณะที่ shim ทำให้โค้ดที่มีอยู่เข้ากันได้กับสภาพแวดล้อมที่มันทำงาน การเลือกระหว่างสองแบบนี้ขึ้นอยู่กับปัญหาที่กำลังแก้: การไม่มี API หรือความไม่เข้ากันของอินเทอร์เฟซ
Webpack จัดการแต่ละโมดูลเป็นหน่วยที่แยกจากกันโดยมีขอบเขตของตัวเอง หากไลบรารีอ้างอิงตัวแปรทั่วโลก jQuery ในรูปแบบ window.$ การบิลด์จะจบลงด้วยข้อผิดพลาด เนื่องจากตัวแปรนี้ไม่มีอยู่ในบริบทของโมดูล ProvidePlugin แก้ปัญหาในขั้นตอนการคอมไพล์: เมื่อตรวจพบตัวระบุ $ ในโค้ด ปลั๊กอินจะแทรก import $ from 'jquery' ลงในตอนต้นของไฟล์โดยอัตโนมัติ
// ซอร์สโค้ด (โมดูล legacy เข้าถึง jQuery ทั่วโลก)
$('.element').hide();
// หลังจากการประมวลผลของ ProvidePlugin (Webpack แทรก import)
import $ from 'jquery';
$('.element').hide();
นอกจากนี้ imports-loader ยังช่วยให้ระบุได้อย่างชัดเจนว่าโมดูลควรได้รับข้อมูลพึ่งพาใดบ้าง สิ่งนี้มีประโยชน์เมื่อไลบรารีใช้ this ในระดับบนสุด โดยคาดหวังว่า this จะอ้างถึง window แทนที่จะเป็น module.exports
ProvidePlugin เป็นปลั๊กอินในตัวของ Webpack ที่โหลดโมดูลโดยอัตโนมัติเมื่อตรวจพบการอ้างอิงถึงตัวระบุที่กำหนดไว้ คอนฟิกูเรชันเป็นออบเจ็กต์ โดยคีย์คือชื่อตัวแปร และค่าคือเส้นทางไปยังโมดูลและฟิลด์ที่ส่งออก
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
_: 'lodash',
'window.$': 'jquery',
}),
],
};
ProvidePlugin รองรับการนำเข้าเฉพาะจุดผ่านไวยากรณ์อาร์เรย์ ตัวอย่างเช่น [lodash, debounce] นำเข้าเฉพาะฟังก์ชัน debounce จาก lodash ซึ่งช่วยลดขนาดของบันเดิลสุดท้าย สิ่งนี้สำคัญเป็นพิเศษสำหรับโปรเจกต์มือถือ ที่ทุกกิโลไบต์ส่งผลต่อเวลาโหลด
imports-loader เพิ่มการนำเข้าที่จำเป็นลงในตอนต้นของโมดูล ในขณะที่ exports-loader กำหนดค่าที่ส่งออกสำหรับโมดูลที่ไม่ใช้ module.exports อย่างชัดเจน โลเดอร์เหล่านี้ทำงานในระดับไฟล์แต่ละไฟล์ ไม่ใช่ทั่วโลกเหมือน ProvidePlugin
// webpack.config.js — การตั้งค่า imports-loader
module.exports = {
module: {
rules: [
{
test: /legacy-module\.js$/,
use: [
{
loader: 'imports-loader',
options: {
imports: [
'jquery',
'$',
],
},
},
],
},
],
},
};
exports-loader ใช้เมื่อไลบรารีกำหนดค่าให้กับตัวแปรทั่วโลก แต่ไม่ส่งออกค่าผ่านระบบโมดูล โลเดอร์จะดึงค่าออกมาและเปลี่ยนให้เป็นการส่งออกแบบโมดูล ทำให้โมดูลอื่นสามารถนำเข้ามันผ่าน import ได้
Shimming ถูกตั้งค่าใน webpack.config.js ผ่านการผสมผสานของปลั๊กอินและโลเดอร์ สถานการณ์ทั่วไปประกอบด้วย ProvidePlugin สำหรับตัวแปรทั่วโลก และ imports-loader สำหรับโมดูลเฉพาะที่ต้องการการเปลี่ยนแปลงขอบเขต
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
globalObject: 'this',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules\/(?!legacy-lib)/,
use: [
{
loader: 'imports-loader',
options: {
type: 'commonjs',
imports: ['jquery', '$'],
},
},
],
},
],
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
}),
],
};
ฟิลด์ globalObject ใน output กำหนดบริบทสำหรับการอ้างอิง this ในระดับบนสุด สำหรับสภาพแวดล้อมเบราว์เซอร์ ค่า 'this' อ้างถึง window ในขณะที่สำหรับ React Native หรือ Node.js จะอ้างถึง global การเลือกค่าที่ถูกต้องจะป้องกันข้อผิดพลาดในการรันไทม์ในสภาพแวดล้อมเป้าหมาย
Shimming เป็นเครื่องมือที่ทรงพลังแต่ก็อันตราย การตั้งค่าที่ผิดนำไปสู่การทำซ้ำโค้ดในบันเดิล ความขัดแย้งของชื่อ และข้อผิดพลาดการรันไทม์ที่ไม่คาดคิด นักพัฒนามักลืมว่า ProvidePlugin ทำงานในขั้นตอนการคอมไพล์และไม่สามารถจัดการการอ้างอิงตัวแปรแบบไดนามิกได้
หากปลั๊กอินสองตัวใช้ jQuery คนละเวอร์ชัน ProvidePlugin จะแทนที่เพียงตัวเดียวเท่านั้น คือตัวที่ระบุไว้ก่อนในคอนฟิกูเรชัน ไลบรารีที่สองจะได้รับเวอร์ชันที่ไม่เข้ากัน ซึ่งทำให้เกิดข้อผิดพลาดที่แก้ไขยาก วิธีแก้ — ใช้ exports-loader สำหรับแต่ละไลบรารีพร้อมระบุเวอร์ชันอย่างชัดเจน หรือใช้ webpack.IgnorePlugin เพื่อแยกโมดูลที่ซ้ำกัน
ข้อผิดพลาดทั่วไปอีกอย่างหนึ่งคือการพยายาม shim โมดูลที่ใช้คำเรียก require แบบซิงโครนัสของ CommonJS ในบริบทแบบไดนามิก ProvidePlugin ประมวลผลเฉพาะตัวระบุแบบคงที่เท่านั้น ดังนั้นการอ้างอิงแบบไดนามิกจึงต้องถูกแทนที่ด้วยตนเองหรือใช้ NormalModuleReplacementPlugin
การตั้งค่า shimming ที่ผิดอาจทำให้ขนาดบันเดิลเพิ่มขึ้นอย่างมีนัยสำคัญ หาก ProvidePlugin ถูกตั้งค่าสำหรับตัวแปรทั่วโลกหลายสิบตัว Webpack จะแทรกการนำเข้าที่เกี่ยวข้องลงในไฟล์ทั้งหมดของโปรเจกต์ ไม่ว่าเหล่านั้นจะถูกใช้ในไฟล์เฉพาะแต่ละไฟล์หรือไม่ สิ่งนี้สร้างโค้ดที่เกินจำเป็น โดยเฉพาะในโปรเจกต์ขนาดใหญ่ที่มีโมดูลหลายพันตัว
สำหรับการวินิจฉัยปัญหากับ shimming ให้ใช้ webpack-bundle-analyzer — เครื่องมือสำหรับแสดงภาพองค์ประกอบของบันเดิล หาก jQuery หรือไลบรารีอื่นปรากฏในบันเดิลหลายครั้ง แสดงว่าเวอร์ชันที่ต่างกันกำลังขัดแย้งกัน หรือ ProvidePlugin ถูกตั้งค่าสำหรับตัวระบุหลายตัวที่นำไปสู่เวอร์ชันแพ็กเกจที่ต่างกัน วิธีแก้ — รวมเวอร์ชันข้อมูลพึ่งพาให้เป็นหนึ่งผ่าน resolve.alias และตรวจสอบว่าตัวระบุที่ shim ทั้งหมดชี้ไปยังโมดูลเดียวกัน
ก่อนที่จะใช้ shimming ให้ประเมินความเป็นไปได้ในการอัปเดตไลบรารีเป็นเวอร์ชันที่รองรับระบบโมดูล แพ็กเกจ legacy จำนวนมากมีทางเลือกสมัยใหม่ที่ไม่ต้องใช้ shimming ตัวอย่างเช่น ปลั๊กอิน jQuery สามารถแทนที่ด้วย API เนทีฟของเบราว์เซอร์: $.ajax → fetch, $.each → Array.forEach การปรับโครงสร้างให้ผลประโยชน์ระยะยาวในการบำรุงรักษา ในขณะที่ shimming เป็นวิธีแก้ไขชั่วคราวที่ทำให้คอนฟิกูเรชันซับซ้อนขึ้น
หากการอัปเดตเป็นไปไม่ได้ ให้พิจารณา NormalModuleReplacementPlugin ซึ่งช่วยให้แทนที่โมดูลหนึ่งด้วยอีกโมดูลหนึ่งในระดับการแก้ไขความสัมพันธ์ โดยไม่ต้องแก้ไขซอร์สโค้ด ปลั๊กอินนี้ทำงานในขั้นตอนการสร้างกราฟข้อมูลพึ่งพา ก่อนที่จะใช้โลเดอร์ และประมวลผลการอ้างอิงทั้งหมดไปยังโมดูลโดยไม่คำนึงถึงบริบท นี่เป็นวิธีแก้ที่สะอาดกว่าสำหรับการแทนที่ไลบรารีทั้งตัวเมื่อเทียบกับโลเดอร์เฉพาะจุด
ด้วยการพัฒนาของ ES โมดูล เนทีฟในเบราว์เซอร์และการถือกำเนิดของ import maps บางสถานการณ์ของ shimming สามารถแก้ได้โดยไม่ต้องใช้ Webpack Import maps ช่วยให้กำหนดชื่อโมดูลใหม่ได้ทันทีในระดับเบราว์เซอร์ โดยไม่มีขั้นตอนการบิลด์ อย่างไรก็ตาม วิธีนี้ไม่ได้รับการรองรับใน React Native และสภาพแวดล้อมอื่นๆ ที่ไม่มี ESM ของเบราว์เซอร์ ดังนั้น shimming ผ่าน Webpack ยังคงมีความเกี่ยวข้องสำหรับการบิลด์แบบ production ที่ต้องควบคุมข้อมูลพึ่งพาและเวอร์ชันของมันได้อย่างสมบูรณ์ การเลือกระหว่าง import maps และ Webpack shims ขึ้นอยู่กับแพลตฟอร์มเป้าหมายและข้อกำหนดความเข้ากันได้กับเบราว์เซอร์เก่า
คำถามที่พบบ่อย
Shimming เพิ่มโค้ดเพื่อให้มั่นใจถึงความเข้ากันได้ ในขณะที่ tree shaking ลบโค้ดที่ไม่ได้ใช้ เทคนิคทั้งสองนี้มีเป้าหมายตรงข้ามกัน: shimming เพิ่มขนาดบันเดิล tree shaking ลดขนาดลง ในการบิลด์แบบ production ทั้งสองถูกใช้ตามลำดับ
ได้ shimming มีอยู่เป็นเทคนิคที่อิสระจาก Webpack — ตัวอย่างเช่น ผ่านสคริปต์ทั่วโลกใน HTML หรือผ่าน ES โมดูลที่มีการส่งออกซ้ำ อย่างไรก็ตาม Webpack ให้เครื่องมืออัตโนมัติที่สะดวกที่สุด: ProvidePlugin และโลเดอร์ที่ไม่ต้องแก้ไขโค้ดด้วยตนเอง
ProvidePlugin ไม่ส่งผลต่อความเร็วในการบิลด์ เพราะมันทำงานในขั้นตอนการคอมไพล์ AST imports-loader และ exports-loader เพิ่มเวลาการประมวลผลเล็กน้อยสำหรับแต่ละไฟล์ เมื่อใช้กับไฟล์หลายร้อยไฟล์ ความแตกต่างอาจเป็น 5–15% ของเวลาการบิลด์ทั้งหมด
หากข้อมูลพึ่งพาทั้งหมดรองรับ ES โมดูล และระบบโมดูลแล้ว shimming ก็เกินความจำเป็น การเลิกใช้ shimming ทำให้คอนฟิกูเรชันง่ายขึ้น ลดขนาดบันเดิล และลดความเสี่ยงของความขัดแย้งของชื่อ แนะนำให้ตรวจสอบข้อมูลพึ่งพาบน caniuse.com
TypeScript ต้องการการประกาศชนิดเพิ่มเติมสำหรับตัวแปรที่ shim จำเป็นต้องเพิ่ม declare const $: any หรือติดตั้งชนิดผ่าน @types/jquery ProvidePlugin แทรกการนำเข้าในระดับ JavaScript หลังจากการคอมไพล์ TypeScript ดังนั้นชนิดจะถูกตรวจสอบแยกต่างหาก
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม