คู่มือการจัดการการพึ่งพา Webpack
เผยแพร่แล้ว: 2022-03-11แนวคิดของการทำให้เป็นโมดูลเป็นส่วนหนึ่งของภาษาโปรแกรมที่ทันสมัยที่สุด อย่างไรก็ตาม JavaScript ยังขาดแนวทางที่เป็นทางการในการทำให้เป็นโมดูล จนกระทั่ง ECMAScript ES6 เวอร์ชันล่าสุดมาถึง
ใน Node.js หนึ่งในเฟรมเวิร์ก JavaScript ที่ได้รับความนิยมมากที่สุดในปัจจุบัน ตัวรวมโมดูลอนุญาตให้โหลดโมดูล NPM ในเว็บเบราว์เซอร์ และไลบรารีที่เน้นองค์ประกอบ (เช่น React) สนับสนุนและอำนวยความสะดวกในการปรับโค้ด JavaScript ให้เป็นโมดูล
Webpack เป็นหนึ่งในชุดรวมโมดูลที่พร้อมใช้งานซึ่งประมวลผลโค้ด JavaScript รวมถึงเนื้อหาสแตติกทั้งหมด เช่น สไตล์ชีต รูปภาพ และฟอนต์ ลงในไฟล์ที่รวมกลุ่ม การประมวลผลสามารถรวมงานที่จำเป็นทั้งหมดสำหรับการจัดการและเพิ่มประสิทธิภาพการพึ่งพาโค้ด เช่น การคอมไพล์ การต่อข้อมูล การลดขนาด และการบีบอัด
อย่างไรก็ตาม การกำหนดค่า Webpack และการขึ้นต่อกันอาจทำให้เครียดและไม่ใช่กระบวนการที่ตรงไปตรงมาเสมอไป โดยเฉพาะสำหรับผู้เริ่มต้น
โพสต์บล็อกนี้ให้แนวทาง พร้อมตัวอย่าง วิธีกำหนดค่า Webpack สำหรับสถานการณ์ต่างๆ และชี้ให้เห็นข้อผิดพลาดที่พบบ่อยที่สุดที่เกี่ยวข้องกับการรวมกลุ่มของการพึ่งพาโครงการโดยใช้ Webpack
ส่วนแรกของโพสต์ในบล็อกนี้จะอธิบายวิธีทำให้คำจำกัดความของการพึ่งพาในโครงการง่ายขึ้น ต่อไป เราจะหารือและสาธิตการกำหนดค่าสำหรับการแยกโค้ดของแอปพลิเคชันแบบหลายหน้าและแบบหน้าเดียว สุดท้ายนี้ เราจะพูดถึงวิธีกำหนดค่า Webpack หากเราต้องการรวมไลบรารีของบุคคลที่สามในโครงการของเรา
การกำหนดค่านามแฝงและเส้นทางสัมพัทธ์
เส้นทางสัมพัทธ์ไม่เกี่ยวข้องโดยตรงกับการขึ้นต่อกัน แต่เราใช้มันเมื่อเรากำหนดการอ้างอิง หากโครงสร้างไฟล์ของโปรเจ็กต์มีความซับซ้อน อาจเป็นเรื่องยากที่จะแก้ไขพาธของโมดูลที่เกี่ยวข้อง ประโยชน์พื้นฐานที่สุดประการหนึ่งของการกำหนดค่า Webpack คือช่วยให้คำจำกัดความของพาธสัมพัทธ์ในโปรเจ็กต์ง่ายขึ้น
สมมติว่าเรามีโครงสร้างโครงการดังต่อไปนี้:
- Project - node_modules - bower_modules - src - script - components - Modal.js - Navigation.js - containers - Home.js - Admin.js
เราสามารถอ้างอิงการพึ่งพาโดยพาธสัมพัทธ์ไปยังไฟล์ที่เราต้องการ และหากเราต้องการนำเข้าส่วนประกอบไปยังคอนเทนเนอร์ในซอร์สโค้ดของเรา จะมีลักษณะดังนี้:
Home.js
Import Modal from '../components/Modal'; Import Navigation from '../components/Navigation';
Modal.js
import {datepicker} from '../../../../bower_modules/datepicker/dist/js/datepicker';
ทุกครั้งที่เราต้องการนำเข้าสคริปต์หรือโมดูล เราจำเป็นต้องทราบตำแหน่งของไดเร็กทอรีปัจจุบันและค้นหาเส้นทางที่สัมพันธ์กับสิ่งที่เราต้องการนำเข้า เราสามารถจินตนาการได้ว่าปัญหานี้จะบานปลายในความซับซ้อนได้อย่างไรถ้าเรามีโครงการขนาดใหญ่ที่มีโครงสร้างไฟล์ที่ซ้อนกัน หรือเราต้องการปรับโครงสร้างบางส่วนของโครงสร้างโครงการที่ซับซ้อน
เราสามารถจัดการกับปัญหานี้ได้อย่างง่ายดายด้วยตัวเลือก fix.alias ของ resolve.alias
เราสามารถประกาศชื่อแทน - ชื่อของไดเร็กทอรีหรือโมดูลพร้อมตำแหน่ง และเราไม่พึ่งพาพาธสัมพัทธ์ในซอร์สโค้ดของโปรเจ็กต์
webpack.config.js
resolve: { alias: { 'node_modules': path.join(__dirname, 'node_modules'), 'bower_modules': path.join(__dirname, 'bower_modules'), } }
ในไฟล์ Modal.js
เราสามารถนำเข้า datepicker ได้ง่ายกว่ามาก:
import {datepicker} from 'bower_modules/datepicker/dist/js/datepicker';
การแยกรหัส
เราสามารถมีสถานการณ์ที่เราจำเป็นต้องต่อท้ายสคริปต์ลงในบันเดิลสุดท้าย หรือแยกบันเดิลสุดท้าย หรือเราต้องการโหลดบันเดิลแยกกันตามความต้องการ การตั้งค่าโปรเจ็กต์และการกำหนดค่า Webpack สำหรับสถานการณ์เหล่านี้อาจไม่ตรงไปตรงมา
ในการกำหนดค่า Webpack ตัวเลือก Entry
จะบอก Webpack ว่าจุดเริ่มต้นอยู่ที่ใดสำหรับบันเดิลสุดท้าย จุดเริ่มต้นสามารถมีประเภทข้อมูลที่แตกต่างกันสามประเภท: สตริง อาร์เรย์ หรืออ็อบเจ็กต์
หากเรามีจุดเริ่มต้นเพียงจุดเดียว เราสามารถใช้รูปแบบใดก็ได้เหล่านี้และได้ผลลัพธ์แบบเดียวกัน
หากเราต้องการผนวกไฟล์หลาย ๆ ไฟล์ และไม่ขึ้นอยู่กับแต่ละไฟล์ เราสามารถใช้รูปแบบ Array ได้ ตัวอย่างเช่น เราสามารถผนวก analytics.js
ต่อท้าย bundle.js
:
webpack.config.js
module.exports = { // creates a bundle out of index.js and then append analytics.js entry: ['./src/script/index.jsx', './src/script/analytics.js'], output: { path: './build', filename: bundle.js ' } };
การจัดการจุดเข้าใช้งานหลายจุด
สมมติว่าเรามีแอปพลิเคชันแบบหลายหน้าที่มีไฟล์ HTML หลายไฟล์ เช่น index.html
และ admin.html
เราสามารถสร้างบันเดิลได้หลายชุดโดยใช้จุดเริ่มต้นเป็นประเภทอ็อบเจ็กต์ การกำหนดค่าด้านล่างสร้างบันเดิล JavaScript สองชุด:
webpack.config.js
module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) } };
index.html
<script src=”build/index.js”></script>
admin.html
<script src=”build/admin.js”></script>
บันเดิล JavaScript ทั้งสองสามารถแชร์ไลบรารีและส่วนประกอบทั่วไปได้ เพื่อสิ่งนี้ เราสามารถใช้ CommonsChunkPlugin
ซึ่งค้นหาโมดูลที่เกิดขึ้นในหลาย ๆ รายการและสร้างบันเดิลที่ใช้ร่วมกันที่สามารถแคชระหว่างหลาย ๆ เพจได้
webpack.config.js
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) }, plugins: [commonsPlugin] };
ตอนนี้ เราต้องไม่ลืมที่จะเพิ่ม <script src="build/common.js"></script>
ก่อนการรวมสคริปต์
เปิดใช้งาน Lazy Loading
Webpack สามารถแบ่งทรัพย์สินแบบคงที่ออกเป็นชิ้นเล็กๆ ได้ และวิธีนี้มีความยืดหยุ่นมากกว่าการต่อข้อมูลแบบมาตรฐาน หากเรามีแอปพลิเคชันหน้าเดียวขนาดใหญ่ (SPA) การต่อกันอย่างง่ายเป็นชุดเดียวไม่ใช่วิธีที่ดีเพราะการโหลดชุดรวมขนาดใหญ่หนึ่งชุดอาจทำได้ช้า และผู้ใช้มักจะไม่ต้องการการพึ่งพาทั้งหมดในแต่ละมุมมอง
เราได้อธิบายก่อนหน้านี้ถึงวิธีแบ่งแอปพลิเคชันออกเป็นหลายบันเดิล เชื่อมโยงการพึ่งพาทั่วไป และได้รับประโยชน์จากพฤติกรรมการแคชของเบราว์เซอร์ วิธีนี้ใช้ได้ผลดีกับแอปพลิเคชันแบบหลายหน้า แต่ไม่ใช่สำหรับแอปพลิเคชันแบบหน้าเดียว
สำหรับ SPA เราควรจัดเตรียมเฉพาะเนื้อหาคงที่ที่จำเป็นสำหรับการแสดงผลมุมมองปัจจุบัน เราเตอร์ฝั่งไคลเอ็นต์ในสถาปัตยกรรม SPA เป็นสถานที่ที่สมบูรณ์แบบในการจัดการการแยกโค้ด เมื่อผู้ใช้เข้าสู่เส้นทาง เราสามารถโหลดเฉพาะการขึ้นต่อกันที่จำเป็นสำหรับมุมมองผลลัพธ์ อีกทางหนึ่ง เราสามารถโหลดการขึ้นต่อกันเมื่อผู้ใช้เลื่อนหน้าลง
เพื่อจุดประสงค์นี้ เราสามารถใช้ฟังก์ชัน require.ensure
หรือ System.import
ซึ่ง Webpack สามารถตรวจจับแบบสแตติกได้ Webpack สามารถสร้างบันเดิลแยกต่างหากตามจุดแยกนี้และเรียกได้ตามต้องการ
ในตัวอย่างนี้ เรามีคอนเทนเนอร์ React สองคอนเทนเนอร์ มุมมองผู้ดูแลระบบและมุมมองแดชบอร์ด
admin.jsx
import React, {Component} from 'react'; export default class Admin extends Component { render() { return <div > Admin < /div>; } }
dashboard.jsx
import React, {Component} from 'react'; export default class Dashboard extends Component { render() { return <div > Dashboard < /div>; } }
หากผู้ใช้ป้อน URL /dashboard
หรือ /admin
จะโหลดเฉพาะบันเดิล JavaScript ที่จำเป็นที่เกี่ยวข้องเท่านั้น ด้านล่างนี้ เราสามารถดูตัวอย่างที่มีและไม่มีเราเตอร์ฝั่งไคลเอ็นต์
index.jsx
if (window.location.pathname === '/dashboard') { require.ensure([], function() { require('./containers/dashboard').default; }); } else if (window.location.pathname === '/admin') { require.ensure([], function() { require('./containers/admin').default; }); }
index.jsx
ReactDOM.render( <Router> <Route path="/" component={props => <div>{props.children}</div>}> <IndexRoute component={Home} /> <Route path="dashboard" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/dashboard').default) }, "dashboard")}} /> <Route path="admin" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/admin').default) }, "admin")}} /> </Route> </Router> , document.getElementById('content') );
การแยกสไตล์ออกเป็นบันเดิลที่แยกจากกัน
ใน Webpack ตัวโหลด เช่น style-loader
และ css-loader
ประมวลผลสไตล์ชีตล่วงหน้าและฝังลงในบันเดิล JavaScript เอาต์พุต แต่ในบางกรณี สิ่งเหล่านี้อาจทำให้ Flash ของเนื้อหาที่ไม่มี สไตล์ (FOUC)

เราสามารถหลีกเลี่ยง FOUC ด้วย ExtractTextWebpackPlugin
ที่อนุญาตให้สร้างสไตล์ทั้งหมดเป็นบันเดิล CSS ที่แยกจากกัน แทนที่จะฝังไว้ในบันเดิล JavaScript สุดท้าย
webpack.config.js
var ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { loaders: [{ test: /\.css/, loader: ExtractTextPlugin.extract('style', 'css')' }], }, plugins: [ // output extracted CSS to a file new ExtractTextPlugin('[name].[chunkhash].css') ] }
การจัดการไลบรารีและปลั๊กอินของบุคคลที่สาม
หลายครั้งที่เราจำเป็นต้องใช้ไลบรารีของบุคคลที่สาม ปลั๊กอินต่างๆ หรือสคริปต์เพิ่มเติม เนื่องจากเราไม่ต้องการใช้เวลาในการพัฒนาส่วนประกอบเดียวกันตั้งแต่เริ่มต้น มีไลบรารีและปลั๊กอินรุ่นเก่าจำนวนมากที่ไม่ได้รับการดูแล ไม่เข้าใจโมดูล JavaScript และถือว่ามีการอ้างอิงทั่วโลกภายใต้ชื่อที่กำหนดไว้ล่วงหน้า
ด้านล่างนี้คือตัวอย่างบางส่วนของปลั๊กอิน jQuery พร้อมคำอธิบายเกี่ยวกับวิธีกำหนดค่า Webpack อย่างถูกต้องเพื่อให้สามารถสร้างบันเดิลสุดท้ายได้
จัดเตรียมปลั๊กอิน
ปลั๊กอินบุคคลที่สามส่วนใหญ่อาศัยการมีอยู่ของการพึ่งพาทั่วโลกที่เฉพาะเจาะจง ในกรณีของ jQuery ปลั๊กอินจะขึ้นอยู่กับตัวแปร $
หรือ jQuery
ที่กำหนดไว้ และเราสามารถใช้ปลั๊กอิน jQuery ได้โดยการเรียก $('div.content').pluginFunc()
ในโค้ดของเรา
เราสามารถใช้ปลั๊กอิน ProvidePlugin
เพื่อเติม var $ = require("jquery")
ทุกครั้งที่พบ global $
identifier
webpack.config.js
webpack.ProvidePlugin({ '$': 'jquery', })
เมื่อ Webpack ประมวลผลโค้ด โปรแกรมจะค้นหาสถานะ $
และให้การอ้างอิงถึงการพึ่งพาส่วนกลางโดยไม่ต้องนำเข้าโมดูลที่ระบุโดยฟังก์ชัน require
นำเข้า-loader
ปลั๊กอิน jQuery บางตัวถือว่า $
ในเนมสเปซส่วนกลางหรืออาศัย this
เป็นวัตถุ window
เพื่อจุดประสงค์นี้ เราสามารถใช้ imports-loader
ซึ่งแทรกตัวแปรส่วนกลางลงในโมดูล
example.js
$('div.content').pluginFunc();
จากนั้น เราสามารถแทรกตัวแปร $
ลงในโมดูลโดยกำหนดค่า imports-loader
:
require("imports?$=jquery!./example.js");
นี่เป็นเพียงการ var $ = require("jquery");
ไปที่ example.js
ในกรณีการใช้งานที่สอง:
webpack.config.js
module: { loaders: [{ test: /jquery-plugin/, loader: 'imports?jQuery=jquery,$=jquery,this=>window' }] }
โดยใช้สัญลักษณ์ =>
(เพื่อไม่ให้สับสนกับฟังก์ชัน ES6 Arrow) เราสามารถตั้งค่าตัวแปรตามอำเภอใจได้ ค่าสุดท้ายกำหนดตัวแปรส่วนกลางใหม่เพื่อชี้ไป this
วัตถุ window
เหมือนกับการตัดเนื้อหาทั้งหมดของไฟล์ด้วย (function () { ... }).call(window);
และเรียกใช้ฟังก์ชัน this
ด้วย window
เป็นอาร์กิวเมนต์
เรายังต้องการไลบรารี่โดยใช้รูปแบบโมดูล CommonJS หรือ AMD:
// CommonJS var $ = require("jquery"); // jquery is available // AMD define(['jquery'], function($) { // jquery is available });
ไลบรารีและโมดูลบางตัวสามารถรองรับรูปแบบโมดูลต่างๆ ได้
ในตัวอย่างต่อไป เรามีปลั๊กอิน jQuery ซึ่งใช้รูปแบบโมดูล AMD และ CommonJS และมีการพึ่งพา jQuery:
jquery-plugin.js
(function(factory) { if (typeof define === 'function' && define.amd) { // AMD format is used define(['jquery'], factory); } else if (typeof exports === 'object') { // CommonJS format is used module.exports = factory(require('jquery')); } else { // Neither AMD nor CommonJS used. Use global variables. } });
webpack.config.js
module: { loaders: [{ test: /jquery-plugin/, loader: "imports?define=>false,exports=>false" }] }
เราสามารถเลือกรูปแบบโมดูลที่เราต้องการใช้สำหรับไลบรารีเฉพาะ หากเราประกาศ define
false
เท่ากัน Webpack จะไม่แยกวิเคราะห์โมดูลในรูปแบบโมดูล AMD และหากเราประกาศการ exports
ตัวแปร false
เท่ากับ Webpack จะไม่แยกวิเคราะห์โมดูลในรูปแบบโมดูล CommonJS
เปิดเผยโหลด
หากเราจำเป็นต้องเปิดเผยโมดูลต่อบริบทส่วนกลาง เราก็สามารถใช้ expose-loader
ได้ สิ่งนี้มีประโยชน์ ตัวอย่างเช่น หากเรามีสคริปต์ภายนอกที่ไม่ได้เป็นส่วนหนึ่งของการกำหนดค่า Webpack และอาศัยสัญลักษณ์ในเนมสเปซส่วนกลาง หรือเราใช้ปลั๊กอินของเบราว์เซอร์ที่จำเป็นต้องเข้าถึงสัญลักษณ์ในคอนโซลของเบราว์เซอร์
webpack.config.js
module: { loaders: [ test: require.resolve('jquery'), loader: 'expose-loader?jQuery!expose-loader?$' ] }
ขณะนี้ไลบรารี jQuery พร้อมใช้งานในเนมสเปซส่วนกลางสำหรับสคริปต์อื่นๆ บนหน้าเว็บ
window.$ window.jQuery
การกำหนดค่าการพึ่งพาภายนอก
หากเราต้องการรวมโมดูลจากสคริปต์ที่โฮสต์ภายนอก เราจำเป็นต้องกำหนดโมดูลเหล่านี้ในการกำหนดค่า มิฉะนั้น Webpack จะไม่สามารถสร้างบันเดิลสุดท้ายได้
เราสามารถกำหนดค่าสคริปต์ภายนอกได้โดยใช้ตัวเลือก externals
ในการกำหนดค่า Webpack ตัวอย่างเช่น เราสามารถใช้ไลบรารี่จาก CDN ผ่านแท็ก <script>
แยกต่างหาก ในขณะที่ยังคงประกาศอย่างชัดเจนว่าเป็นการพึ่งพาโมดูลในโครงการของเรา
webpack.config.js
externals: { react: 'React', 'react-dom': 'ReactDOM' }
รองรับหลายอินสแตนซ์ของไลบรารี
เป็นการดีที่จะใช้ตัวจัดการแพ็คเกจ NPM ในการพัฒนาส่วนหน้าสำหรับจัดการไลบรารีและการพึ่งพาของบุคคลที่สาม อย่างไรก็ตาม บางครั้งเราสามารถมีไลบรารีเดียวกันได้หลายอินสแตนซ์โดยใช้เวอร์ชันต่างๆ กัน และไม่สามารถทำงานร่วมกันได้ดีในสภาพแวดล้อมเดียว
สิ่งนี้อาจเกิดขึ้นได้ ตัวอย่างเช่น กับไลบรารี React ซึ่งเราสามารถติดตั้ง React จาก NPM และหลังจากนั้น React เวอร์ชันอื่นจะสามารถใช้งานได้กับแพ็คเกจหรือปลั๊กอินเพิ่มเติม โครงสร้างโครงการของเราสามารถมีลักษณะดังนี้:
project | |-- node_modules | |-- react |-- react-plugin | |--node_modules | |--react
ส่วนประกอบที่มาจาก react-plugin
มีอินสแตนซ์ React ที่แตกต่างจากส่วนประกอบที่เหลือในโปรเจ็กต์ ตอนนี้เรามี React สองชุดแยกกัน และพวกมันสามารถเป็นเวอร์ชันที่แตกต่างกันได้ ในแอปพลิเคชันของเรา สถานการณ์นี้อาจทำให้ DOM ที่ไม่แน่นอนทั่วโลกของเรายุ่งเหยิง และเราสามารถเห็นข้อความแสดงข้อผิดพลาดในบันทึกของเว็บคอนโซล วิธีแก้ปัญหานี้คือต้องมี React เวอร์ชันเดียวกันตลอดทั้งโปรเจ็กต์ เราสามารถแก้ไขได้ด้วยนามแฝง Webpack
webpack.config.js
module.exports = { resolve: { alias: { 'react': path.join(__dirname, './node_modules/react'), 'react/addons': path.join(__dirname, '/node_modules/react/addons'), } } }
เมื่อ react-plugin
พยายามต้องการ React มันจะใช้เวอร์ชันใน node_modules
ของโปรเจ็กต์ หากเราต้องการทราบว่าเราใช้ React เวอร์ชันใด เราสามารถเพิ่ม console.log(React.version)
ลงในซอร์สโค้ดได้
เน้นที่การพัฒนา ไม่ใช่การกำหนดค่า Webpack
โพสต์นี้เป็นเพียงรอยขีดข่วนพื้นผิวของพลังและประโยชน์ของ Webpack
มีตัวโหลดและปลั๊กอิน Webpack อื่นๆ อีกมากมายที่จะช่วยคุณเพิ่มประสิทธิภาพและปรับปรุงการรวมกลุ่ม JavaScript
แม้ว่าคุณจะเป็นมือใหม่ คู่มือนี้จะช่วยให้คุณมีพื้นฐานที่ดีในการเริ่มใช้ Webpack ซึ่งจะช่วยให้คุณมุ่งเน้นที่การพัฒนามากขึ้นน้อยลงในการกำหนดค่าการรวมกลุ่ม