แนวคิดของการแยกส่วนเป็นส่วนหนึ่งของภาษาโปรแกรมสมัยใหม่ส่วนใหญ่ แม้ว่า 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';
ทุกครั้งที่เราต้องการนำเข้าสคริปต์หรือโมดูลเราจำเป็นต้องทราบตำแหน่งของไดเร็กทอรีปัจจุบันและค้นหาเส้นทางสัมพัทธ์กับสิ่งที่เราต้องการนำเข้า เราสามารถจินตนาการได้ว่าปัญหานี้จะเพิ่มความซับซ้อนได้อย่างไรหากเรามีโครงการขนาดใหญ่ที่มีโครงสร้างไฟล์ซ้อนกันหรือเราต้องการปรับโครงสร้างบางส่วนของโครงสร้างโครงการที่ซับซ้อน
เราสามารถจัดการปัญหานี้ได้อย่างง่ายดายด้วย resolve.alias
ของ Webpack ตัวเลือก เราสามารถประกาศสิ่งที่เรียกว่านามแฝง - ชื่อไดเร็กทอรีหรือโมดูลพร้อมตำแหน่งที่ตั้งและเราไม่อาศัยเส้นทางสัมพัทธ์ในซอร์สโค้ดของโปรเจ็กต์
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 ว่าจุดเริ่มต้นสำหรับบันเดิลสุดท้าย จุดเริ่มต้นสามารถมีข้อมูลได้สามประเภท: String, Array หรือ Object
หากเรามีจุดเริ่มต้นเพียงจุดเดียวเราสามารถใช้รูปแบบเหล่านี้และได้ผลลัพธ์เดียวกัน
หากเราต้องการต่อท้ายไฟล์หลาย ๆ ไฟล์และไม่ได้ขึ้นอยู่กันเราสามารถใช้รูปแบบ 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
admin.html
CommonsChunkPlugin
webpack.config.js
บันเดิล JavaScript ทั้งสองสามารถแชร์ไลบรารีและคอมโพเนนต์ทั่วไปได้ ด้วยเหตุนี้เราจึงสามารถใช้ 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] };
ซึ่งจะค้นหาโมดูลที่เกิดขึ้นในหลาย ๆ รายการและสร้างบันเดิลที่ใช้ร่วมกันซึ่งสามารถแคชระหว่างหลายเพจได้
require.ensure
System.import
ตอนนี้เราต้องไม่ลืมที่จะเพิ่มก่อนที่จะรวมสคริปต์
Webpack สามารถแบ่งเนื้อหาแบบคงที่ออกเป็นชิ้นเล็ก ๆ และวิธีนี้มีความยืดหยุ่นมากกว่าการต่อแบบมาตรฐาน หากเรามีแอปพลิเคชันหน้าเดียวขนาดใหญ่ (SPA) การเชื่อมต่ออย่างง่ายเป็นกลุ่มเดียวไม่ใช่แนวทางที่ดีเนื่องจากการโหลดชุดข้อมูลขนาดใหญ่หนึ่งชุดอาจทำได้ช้าและโดยปกติแล้วผู้ใช้ไม่จำเป็นต้องพึ่งพาทั้งหมดในแต่ละมุมมอง
บัตรเครดิตที่ถูกแฮ็กด้วย cvv
เราได้อธิบายไว้ก่อนหน้านี้ว่าจะแบ่งแอปพลิเคชันออกเป็นกลุ่มหลายชุดเชื่อมต่อการอ้างอิงทั่วไปและประโยชน์จากพฤติกรรมการแคชของเบราว์เซอร์ วิธีนี้ใช้ได้ดีมากสำหรับแอปพลิเคชันหลายหน้า แต่ไม่ใช่สำหรับแอปพลิเคชันหน้าเดียว
สำหรับ SPA เราควรจัดเตรียมเนื้อหาคงที่ที่จำเป็นในการแสดงผลมุมมองปัจจุบันเท่านั้น เราเตอร์ฝั่งไคลเอ็นต์ในสถาปัตยกรรม SPA เป็นสถานที่ที่สมบูรณ์แบบในการจัดการการแยกโค้ด เมื่อผู้ใช้เข้าสู่เส้นทางเราสามารถโหลดเฉพาะการอ้างอิงที่จำเป็นสำหรับมุมมองผลลัพธ์ หรือเราสามารถโหลดการอ้างอิงเมื่อผู้ใช้เลื่อนหน้าลงมา
เพื่อจุดประสงค์นี้เราสามารถใช้ admin.jsx
หรือ import React, {Component} from 'react'; export default class Admin extends Component { render() { return Admin ; } }
ซึ่ง Webpack สามารถตรวจจับแบบคงที่ Webpack สามารถสร้างบันเดิลแยกตามจุดแยกนี้และเรียกมันตามความต้องการ
ในตัวอย่างนี้เรามีคอนเทนเนอร์ React สองคอนเทนเนอร์ มุมมองผู้ดูแลระบบและมุมมองแดชบอร์ด
dashboard.jsx
import React, {Component} from 'react'; export default class Dashboard extends Component { render() { return Dashboard ; } }
/dashboard
/admin
หากผู้ใช้ป้อน 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; }); }
URL จะโหลดเฉพาะกลุ่ม JavaScript ที่จำเป็นเท่านั้น ด้านล่างนี้เราสามารถดูตัวอย่างที่มีและไม่มีเราเตอร์ฝั่งไคลเอ็นต์
index.jsx
ReactDOM.render( {props.children} }> { require.ensure([], function (require) { cb(null, require('./containers/dashboard').default) }, 'dashboard')}} /> { require.ensure([], function (require) { cb(null, require('./containers/admin').default) }, 'admin')}} /> , document.getElementById('content') );
style-loader
css-loader
ใน Webpack รถตัก , ชอบ ExtractTextWebpackPlugin
และ webpack.config.js
ประมวลผลสไตล์ชีตล่วงหน้าและฝังลงในบันเดิล JavaScript ของเอาต์พุต แต่ในบางกรณีอาจทำให้เกิด แฟลชของเนื้อหาที่ไม่มีการเปลี่ยนแปลง (FOUC) .
เราสามารถหลีกเลี่ยง FOUC ได้ด้วย 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') ] }
ที่อนุญาตให้สร้างสไตล์ทั้งหมดลงในบันเดิล CSS แยกกันแทนที่จะฝังไว้ในบันเดิล JavaScript สุดท้าย
$
jQuery
หลายครั้งเราจำเป็นต้องใช้ไลบรารีของบุคคลที่สามปลั๊กอินต่างๆหรือสคริปต์เพิ่มเติมเนื่องจากเราไม่ต้องการใช้เวลาในการพัฒนาส่วนประกอบเดิมตั้งแต่ต้น มีไลบรารีและปลั๊กอินแบบดั้งเดิมจำนวนมากที่ไม่ได้รับการดูแลอย่างแข็งขันไม่เข้าใจโมดูล JavaScript และถือว่ามีการอ้างอิงทั่วโลกภายใต้ชื่อที่กำหนดไว้ล่วงหน้า
ด้านล่างนี้เป็นตัวอย่างบางส่วนของปลั๊กอิน jQuery พร้อมคำอธิบายวิธีกำหนดค่า Webpack อย่างถูกต้องเพื่อให้สามารถสร้างบันเดิลสุดท้ายได้
ปลั๊กอินของบุคคลที่สามส่วนใหญ่อาศัยการมีอยู่ของการอ้างอิงส่วนกลางที่เฉพาะเจาะจง ในกรณีของ jQuery ปลั๊กอินจะขึ้นอยู่กับ $(‘div.content’).pluginFunc()
หรือ ProvidePlugin
มีการกำหนดตัวแปรและเราสามารถใช้ปลั๊กอิน jQuery ได้โดยเรียก var $ = require('jquery')
ในรหัสของเรา
เราสามารถใช้ปลั๊กอิน Webpack $
เพื่อนำหน้า webpack.config.js
ทุกครั้งที่พบทั่วโลก webpack.ProvidePlugin({ ‘$’: ‘jquery’, })
ตัวระบุ
$
require
เมื่อ Webpack ประมวลผลโค้ดจะค้นหาสถานะ $
และให้การอ้างอิงไปยังการอ้างอิงส่วนกลางโดยไม่ต้องอิมพอร์ตโมดูลที่ระบุโดย this
ฟังก์ชัน
ปลั๊กอิน jQuery บางตัวถือว่า 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' }] }
.
ในกรณีการใช้งานที่สอง:
=>
this
โดยใช้ปุ่ม window
สัญลักษณ์ (เพื่อไม่ให้สับสนกับ ฟังก์ชัน ES6 Arrow ) เราสามารถตั้งค่าตัวแปรตามอำเภอใจ ค่าสุดท้ายกำหนดตัวแปรส่วนกลางใหม่ (function () { ... }).call(window);
ชี้ไปที่ this
วัตถุ. มันเหมือนกับการห่อเนื้อหาทั้งหมดของไฟล์ด้วย window
และโทร // CommonJS var $ = require('jquery'); // jquery is available // AMD define([‘jquery’], function($) { // jquery is available });
ฟังก์ชันด้วย jquery-plugin.js
เป็นข้อโต้แย้ง
เรายังสามารถต้องการไลบรารีโดยใช้รูปแบบโมดูล CommonJS หรือ AMD:
(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. } });
ไลบรารีและโมดูลบางตัวสามารถรองรับรูปแบบโมดูลต่างๆได้
ในตัวอย่างถัดไปเรามีปลั๊กอิน jQuery ซึ่งใช้รูปแบบโมดูล AMD และ CommonJS และมีการพึ่งพา jQuery:
webpack.config.js
module: { loaders: [{ test: /jquery-plugin/, loader: 'imports?define=>false,exports=>false' }] }
define
false
เราสามารถเลือกรูปแบบโมดูลที่เราต้องการใช้สำหรับไลบรารีเฉพาะ ถ้าเราประกาศ exports
เท่ากับ false
Webpack จะไม่แยกวิเคราะห์โมดูลในรูปแบบโมดูล AMD และหากเราประกาศตัวแปร expose-loader
เท่ากับ webpack.config.js
, Webpack จะไม่แยกวิเคราะห์โมดูลในรูปแบบโมดูล CommonJS
หากเราต้องการให้โมดูลแสดงบริบททั่วโลกเราสามารถใช้ module: { loaders: [ test: require.resolve('jquery'), loader: 'expose-loader?jQuery!expose-loader?$' ] }
สิ่งนี้จะมีประโยชน์เช่นหากเรามีสคริปต์ภายนอกที่ไม่ได้เป็นส่วนหนึ่งของการกำหนดค่า Webpack และอาศัยสัญลักษณ์ในเนมสเปซส่วนกลางหรือเราใช้ปลั๊กอินของเบราว์เซอร์ที่จำเป็นต้องเข้าถึงสัญลักษณ์ในคอนโซลของเบราว์เซอร์
window.$ window.jQuery
externals
ขณะนี้ไลบรารี jQuery พร้อมใช้งานในเนมสเปซส่วนกลางสำหรับสคริปต์อื่น ๆ บนหน้าเว็บ
webpack.config.js
หากเราต้องการรวมโมดูลจากสคริปต์ที่โฮสต์ภายนอกเราจำเป็นต้องกำหนดโมดูลเหล่านี้ในการกำหนดค่า มิฉะนั้น Webpack จะไม่สามารถสร้างบันเดิลสุดท้ายได้
เราสามารถกำหนดค่าสคริปต์ภายนอกได้โดยใช้ externals: { react: 'React', 'react-dom': 'ReactDOM' }
ตัวเลือกในการกำหนดค่า Webpack ตัวอย่างเช่นเราสามารถใช้ไลบรารีจาก CDN ผ่านแท็กคั่นในขณะที่ยังคงประกาศอย่างชัดเจนว่าเป็นการพึ่งพาโมดูลในโครงการของเรา
project | |-- node_modules | |-- react |-- react-plugin | |--node_modules | |--react
react-plugin
การใช้ตัวจัดการแพ็กเกจ NPM ในการพัฒนาส่วนหน้าเป็นสิ่งที่ดีมากในการจัดการไลบรารีและการอ้างอิงของบุคคลที่สาม อย่างไรก็ตามบางครั้งเราอาจมีหลาย ๆ อินสแตนซ์ของไลบรารีเดียวกันกับเวอร์ชันที่แตกต่างกันและไม่สามารถเล่นร่วมกันได้ดีในสภาพแวดล้อมเดียว
สิ่งนี้อาจเกิดขึ้นได้เช่นกับไลบรารี React ซึ่งเราสามารถติดตั้ง React จาก NPM และต่อมา React เวอร์ชันอื่นสามารถใช้ได้กับแพ็คเกจหรือปลั๊กอินเพิ่มเติมบางอย่าง โครงสร้างโครงการของเรามีลักษณะดังต่อไปนี้:
webpack.config.js
ส่วนประกอบที่มาจาก module.exports = { resolve: { alias: { 'react': path.join(__dirname, './node_modules/react'), 'react/addons': path.join(__dirname, '/node_modules/react/addons'), } } }
มีอินสแตนซ์ React ที่แตกต่างจากส่วนประกอบอื่น ๆ ในโครงการ ตอนนี้เรามี React สองชุดแยกกันและอาจเป็นเวอร์ชันที่แตกต่างกัน ในแอปพลิเคชันของเราสถานการณ์นี้อาจทำให้ DOM ที่เปลี่ยนแปลงไม่ได้ทั่วโลกและเราสามารถเห็นข้อความแสดงข้อผิดพลาดในบันทึกของเว็บคอนโซล วิธีแก้ปัญหานี้คือต้องมี React เวอร์ชันเดียวกันตลอดทั้งโครงการ เราสามารถแก้ได้ด้วยนามแฝง Webpack
s corp vs c corp vs ห้างหุ้นส่วน
react-plugin
node_modules
เมื่อ console.log(React.version)
พยายามที่จะต้องใช้ React โดยจะใช้เวอร์ชันใน
|_+_|ของโปรเจ็กต์ หากเราต้องการทราบว่าเราใช้ React เวอร์ชันใดเราสามารถเพิ่ม
|_+_|ในซอร์สโค้ด
โพสต์นี้เป็นเพียงแค่รอยขีดข่วนพื้นผิวของพลังงานและยูทิลิตี้ของ Webpack
ยังมี Webpack อื่น ๆ อีกมากมาย รถตัก และ ปลั๊กอิน ที่จะช่วยคุณเพิ่มประสิทธิภาพและปรับปรุงการรวมกลุ่ม JavaScript
แม้ว่าคุณจะเป็นมือใหม่ แต่คู่มือนี้จะช่วยให้คุณมีพื้นฐานที่มั่นคงในการเริ่มต้นใช้งาน Webpack ซึ่งจะช่วยให้คุณสามารถมุ่งเน้นไปที่การพัฒนาได้มากขึ้นในการกำหนดค่าการรวมกลุ่ม
ที่เกี่ยวข้อง: รักษาการควบคุม: คำแนะนำสำหรับ Webpack และ React, Pt. 1