Webpack หรือ Browserify & อึก: ไหนดีกว่ากัน?

เผยแพร่แล้ว: 2022-03-11

เนื่องจากเว็บแอปพลิเคชันมีความซับซ้อนมากขึ้น การทำให้เว็บแอปสามารถปรับขนาดได้จึงมีความสำคัญสูงสุด ในขณะที่การเขียน ad-hoc JavaScript และ jQuery ในอดีตก็เพียงพอแล้ว ทุกวันนี้การสร้างเว็บแอปต้องการระดับวินัยที่มากขึ้นและแนวทางการพัฒนาซอฟต์แวร์ที่เป็นทางการ เช่น:

  • การทดสอบหน่วยเพื่อให้แน่ใจว่าการแก้ไขโค้ดของคุณจะไม่ทำลายฟังก์ชันที่มีอยู่
  • Linting เพื่อให้แน่ใจว่ารูปแบบการเข้ารหัสที่สอดคล้องกันไม่มีข้อผิดพลาด
  • บิลด์การผลิตที่แตกต่างจากบิลด์การพัฒนา

เว็บยังมีความท้าทายในการพัฒนาที่เป็นเอกลักษณ์ของตัวเองอีกด้วย ตัวอย่างเช่น เนื่องจากหน้าเว็บสร้างคำขอแบบอะซิงโครนัสจำนวนมาก ประสิทธิภาพของแอปพลิเคชันเว็บของคุณจะลดลงอย่างมากจากการต้องขอไฟล์ JS และ CSS หลายร้อยไฟล์ โดยแต่ละรายการมีค่าใช้จ่ายเพียงเล็กน้อย (ส่วนหัว การจับมือกัน และอื่นๆ) ปัญหานี้มักจะแก้ไขได้ด้วยการรวมไฟล์เข้าด้วยกัน ดังนั้นคุณจึงขอไฟล์ JS และ CSS ที่รวมชุดไว้ไฟล์เดียว แทนที่จะเป็นไฟล์เดียวหลายร้อยไฟล์

การรวมกลุ่มการแลกเปลี่ยนเครื่องมือ: Webpack กับ Browserify

คุณควรใช้เครื่องมือการรวมกลุ่มใด: Webpack หรือ Browserify + อึก นี่คือคำแนะนำในการเลือก
ทวีต

เป็นเรื่องธรรมดามากที่จะใช้ตัวประมวลผลล่วงหน้าของภาษา เช่น SASS และ JSX ที่คอมไพล์เป็น JS และ CSS ดั้งเดิม รวมถึงทรานสปิลเดอร์ JS เช่น Babel เพื่อให้ได้รับประโยชน์จากโค้ด ES6 ในขณะที่ยังคงความเข้ากันได้กับ ES5 ไว้

มีจำนวนงานจำนวนมากที่ไม่เกี่ยวข้องกับการเขียนตรรกะของเว็บแอปเอง นี่คือที่ที่ task runners เข้ามา จุดประสงค์ของ task runner คือทำให้งานทั้งหมดเป็นไปโดยอัตโนมัติ เพื่อให้คุณได้รับประโยชน์จากสภาพแวดล้อมการพัฒนาที่ได้รับการปรับปรุงในขณะที่เน้นที่การเขียนแอพของคุณ เมื่อกำหนดค่า task runner แล้ว สิ่งที่คุณต้องทำคือเรียกใช้คำสั่งเดียวในเทอร์มินัล

ฉันจะใช้ Gulp เป็นตัวดำเนินการเพราะมันเป็นมิตรกับนักพัฒนา เรียนรู้ง่ายและเข้าใจง่าย

บทนำอย่างรวดเร็วเกี่ยวกับอึก

Gulp's API ประกอบด้วยสี่ฟังก์ชัน:

  • gulp.src
  • gulp.dest
  • gulp.task
  • gulp.watch

อึกทำงานอย่างไร

ตัวอย่างเช่น ต่อไปนี้คืองานตัวอย่างที่ใช้ประโยชน์จากฟังก์ชันสามในสี่เหล่านี้:

 gulp.task('my-first-task', function() { gulp.src('/public/js/**/*.js') .pipe(concat()) .pipe(minify()) .pipe(gulp.dest('build')) });

เมื่อดำเนิน my-first-task ไฟล์ทั้งหมดที่ตรงกับรูปแบบ glob /public/js/**/*.js จะถูกย่อให้เล็กสุดแล้วโอนไปยังโฟลเดอร์ build ด์

ความงามของสิ่งนี้อยู่ในการผูกมัด . .pipe() คุณนำชุดของไฟล์อินพุต ไพพ์พวกมันผ่านชุดการแปลง จากนั้นส่งคืนไฟล์เอาต์พุต เพื่อให้สะดวกยิ่งขึ้น การแปลงไพพ์จริง เช่น minify() มักจะทำโดยไลบรารี NPM ด้วยเหตุนี้ จึงเป็นเรื่องยากมากในทางปฏิบัติที่คุณต้องเขียนการแปลงของคุณเองนอกเหนือจากการเปลี่ยนชื่อไฟล์ในไพพ์

ขั้นตอนต่อไปในการทำความเข้าใจอึกคือการทำความเข้าใจอาร์เรย์ของการพึ่งพางาน

 gulp.task('my-second-task', ['lint', 'bundle'], function() { ... });

ที่นี่ my-second-task จะเรียกใช้ฟังก์ชันการโทรกลับหลังจากงาน lint และ bundle เสร็จสิ้นเท่านั้น ซึ่งช่วยให้แยกข้อกังวลได้: คุณสร้างชุดของงานเล็กๆ ที่มีความรับผิดชอบเดียว เช่น การแปลง LESS เป็น CSS และสร้างงานหลักที่เรียกใช้งานอื่นๆ ทั้งหมดผ่านอาร์เรย์ของการพึ่งพางาน

สุดท้าย เรามี gulp.watch ซึ่งเฝ้าดูการเปลี่ยนแปลงรูปแบบไฟล์ glob และเมื่อตรวจพบการเปลี่ยนแปลง จะรันชุดของงาน

 gulp.task('my-third-task', function() { gulp.watch('/public/js/**/*.js', ['lint', 'reload']) })

ในตัวอย่างข้างต้น การเปลี่ยนแปลงใดๆ กับไฟล์ที่ตรงกัน /public/js/**/*.js จะทริกเกอร์งาน lint และ reload การใช้งานทั่วไปของ gulp.watch คือการทริกเกอร์การรีโหลดแบบสดในเบราว์เซอร์ ซึ่งเป็นคุณสมบัติที่มีประโยชน์อย่างมากสำหรับการพัฒนา ซึ่งคุณจะไม่สามารถอยู่ได้หากขาดมันเมื่อคุณได้สัมผัสมันแล้ว

และเช่นเดียวกัน คุณเข้าใจทุกสิ่งที่คุณจำเป็นต้องรู้เกี่ยวกับ gulp

Webpack พอดีที่ไหน?

Webpack ทำงานอย่างไร

เมื่อใช้รูปแบบ CommonJS การรวมไฟล์ JavaScript นั้นไม่ง่ายเหมือนการต่อไฟล์เข้าด้วยกัน แต่คุณมีจุดเริ่มต้น (ปกติจะเรียกว่า index.js หรือ app.js ) โดยมีชุดคำสั่ง require หรือ import ที่ด้านบนของไฟล์:

ES5

 var Component1 = require('./components/Component1'); var Component2 = require('./components/Component2');

ES6

 import Component1 from './components/Component1'; import Component2 from './components/Component2';

ต้องแก้ไขการขึ้นต่อกันก่อนโค้ดที่เหลืออยู่ใน app.js และการขึ้นต่อกันเหล่านั้นอาจมีการขึ้นต่อกันเพิ่มเติมเพื่อแก้ไข นอกจากนี้ คุณอาจ require การพึ่งพาเดียวกันในหลายที่ในแอปพลิเคชันของคุณ แต่คุณต้องการแก้ไขการพึ่งพานั้นเพียงครั้งเดียว อย่างที่คุณสามารถจินตนาการได้ เมื่อคุณมีโครงสร้างการพึ่งพาในระดับลึกสองสามระดับแล้ว กระบวนการรวมกลุ่ม JavaScript ของคุณจะค่อนข้างซับซ้อน นี่คือที่มาของบันเดิล เช่น Browserify และ Webpack

ทำไมนักพัฒนาจึงใช้ Webpack แทนอึก?

Webpack เป็นเครื่องบันเดิล ในขณะที่ Gulp เป็น task runner ดังนั้นคุณน่าจะเห็นเครื่องมือทั้งสองนี้ใช้ร่วมกันโดยทั่วไป มีแนวโน้มเพิ่มขึ้นโดยเฉพาะในกลุ่มชุมชน React ที่จะใช้ Webpack แทน อึก ทำไมถึงเป็นเช่นนี้?

พูดง่ายๆ ก็คือ Webpack เป็นเครื่องมือที่ทรงพลังที่สามารถทำงานส่วนใหญ่ที่คุณทำผ่านตัวเรียกใช้งานได้อยู่แล้ว ตัวอย่างเช่น Webpack มีตัวเลือกสำหรับการลดขนาดและซอร์สแมปสำหรับบันเดิลของคุณแล้ว นอกจากนี้ Webpack สามารถเรียกใช้เป็นมิดเดิลแวร์ผ่านเซิร์ฟเวอร์ที่กำหนดเองที่เรียกว่า webpack-dev-server ซึ่งรองรับทั้งการรีโหลดแบบสดและการโหลดซ้ำแบบด่วน (เราจะพูดถึงคุณสมบัติเหล่านี้ในภายหลัง) ด้วยการใช้ตัวโหลด คุณสามารถเพิ่ม ES6 ไปยัง ES5 transpilation และ CSS ก่อนและหลังโปรเซสเซอร์ได้ นั่นเป็นเพียงแค่การทดสอบหน่วยและผ้าสำลีเป็นงานหลักที่ Webpack ไม่สามารถจัดการได้อย่างอิสระ เนื่องจากเราได้ลดงานที่อาจเป็นไปได้อย่างน้อยครึ่งโหลลงเหลือสองงาน ผู้พัฒนาจำนวนมากจึงเลือกที่จะใช้สคริปต์ NPM โดยตรงแทน เนื่องจากวิธีนี้จะช่วยหลีกเลี่ยงค่าใช้จ่ายในการเพิ่มอึกในโปรเจ็กต์ (ซึ่งเราจะพูดถึงในภายหลัง) .

ข้อเสียเปรียบหลักของการใช้ Webpack คือการกำหนดค่าค่อนข้างยาก ซึ่งไม่น่าสนใจหากคุณพยายามทำให้โปรเจ็กต์เริ่มทำงานอย่างรวดเร็ว

การตั้งค่า Task Runner 3 รายการของเรา

ฉันจะสร้างโปรเจ็กต์ด้วยการตั้งค่า task runner ที่แตกต่างกันสามแบบ การตั้งค่าแต่ละครั้งจะทำงานต่อไปนี้:

  • ตั้งค่าเซิร์ฟเวอร์การพัฒนาด้วยการรีโหลดสดเมื่อดูการเปลี่ยนแปลงไฟล์
  • รวมไฟล์ JS & CSS ของเรา (รวมถึงการทรานสพิล ES6 ถึง ES5, การแปลง SASS เป็น CSS และซอร์สแมป) ในลักษณะที่ปรับขนาดได้สำหรับการเปลี่ยนแปลงไฟล์ที่ดู
  • เรียกใช้การทดสอบหน่วยทั้งแบบสแตนด์อโลนหรือในโหมดนาฬิกา
  • เรียกใช้ Linting เป็นงานแบบสแตนด์อโลนหรือในโหมดนาฬิกา
  • ให้ความสามารถในการดำเนินการทั้งหมดข้างต้นผ่านคำสั่งเดียวในเทอร์มินัล
  • มีคำสั่งอื่นสำหรับสร้างบันเดิลการผลิตที่มีการลดขนาดและการเพิ่มประสิทธิภาพอื่นๆ

การตั้งค่าสามแบบของเราจะเป็น:

  • อึก + เบราว์เซอร์
  • อึก + Webpack
  • Webpack + สคริปต์ NPM

แอปพลิเคชันจะใช้ React สำหรับส่วนหน้า ในขั้นต้น ฉันต้องการใช้วิธีการแบบไม่เชื่อเรื่องพระเจ้าในกรอบงาน แต่การใช้ React ช่วยลดความซับซ้อนของความรับผิดชอบของ task runner เนื่องจากต้องการไฟล์ HTML เพียงไฟล์เดียว และ React ก็ทำงานได้ดีกับรูปแบบ CommonJS

เราจะครอบคลุมข้อดีและข้อเสียของการตั้งค่าแต่ละรายการ เพื่อให้คุณสามารถตัดสินใจได้อย่างมีข้อมูลว่าการตั้งค่าประเภทใดที่เหมาะสมกับความต้องการของโครงการของคุณมากที่สุด

ฉันได้ตั้งค่าที่เก็บ Git ที่มีสามสาขา หนึ่งสาขาสำหรับแต่ละวิธี (ลิงก์) การทดสอบการตั้งค่าแต่ละรายการทำได้ง่ายๆ ดังนี้

 git checkout <branch name> npm prune (optional) npm install gulp (or npm start, depending on the setup)

มาตรวจสอบโค้ดในแต่ละสาขาอย่างละเอียดกัน…

รหัสทั่วไป

โครงสร้างโฟลเดอร์

 - app - components - fonts - styles - index.html - index.js - index.test.js - routes.js

index.html

ไฟล์ HTML ที่ตรงไปตรงมา แอปพลิเคชัน React ถูกโหลดลงใน <div></div> และเราใช้ไฟล์ JS และ CSS ที่รวมกันเพียงไฟล์เดียว อันที่จริง ในการตั้งค่าการพัฒนา Webpack ของเรา เราไม่จำเป็นต้องมี bundle.css ด้วยซ้ำ

index.js

ซึ่งทำหน้าที่เป็นจุดเริ่มต้น JS ของแอปของเรา โดยพื้นฐานแล้วเราเพิ่งโหลด React Router ลงใน div ด้วย app id ที่เรากล่าวถึงก่อนหน้านี้

เส้นทาง.js

ไฟล์นี้กำหนดเส้นทางของเรา URL / , /about และ /contact ถูกแมปกับองค์ประกอบ HomePage , AboutPage และ ContactPage ตามลำดับ

index.test.js

นี่คือชุดการทดสอบหน่วยที่ทดสอบพฤติกรรม JavaScript ดั้งเดิม ในแอปคุณภาพการผลิตจริง คุณจะต้องเขียนการทดสอบหน่วยต่อส่วนประกอบ React (อย่างน้อยหนึ่งรายการที่จัดการสถานะ) ทดสอบพฤติกรรมเฉพาะของ React อย่างไรก็ตาม สำหรับจุดประสงค์ของโพสต์นี้ เพียงแค่มีการทดสอบหน่วยการทำงานที่สามารถทำงานในโหมดนาฬิกาก็เพียงพอแล้ว

ส่วนประกอบ/App.js

ซึ่งถือได้ว่าเป็นคอนเทนเนอร์สำหรับการดูหน้าเว็บทั้งหมดของเรา แต่ละหน้ามีองค์ประกอบ <Header/> และ this.props.children ซึ่งประเมินเป็นการดูหน้าเว็บเอง (เช่น/ ContactPage หากอยู่ที่ /contact ในเบราว์เซอร์)

ส่วนประกอบ/home/HomePage.js

นี่คือมุมมองบ้านของเรา ฉันเลือกใช้ react-bootstrap เนื่องจากระบบกริดของ bootstrap นั้นยอดเยี่ยมสำหรับการสร้างเพจที่ตอบสนอง ด้วยการใช้บูตสแตรปอย่างเหมาะสม จำนวนการสืบค้นสื่อที่คุณต้องเขียนสำหรับวิวพอร์ตที่มีขนาดเล็กลงจะลดลงอย่างมาก

ส่วนประกอบที่เหลือ ( Header , AboutPage , ContactPage ) มีโครงสร้างคล้ายกัน ( react-bootstrap markup ไม่มีการจัดการสถานะ)

ทีนี้มาพูดถึงการจัดแต่งทรงผมกันดีกว่า

แนวทางการจัดรูปแบบ CSS

แนวทางที่ฉันชอบในการจัดสไตล์ส่วนประกอบ React คือการมีหนึ่งสไตล์ชีตต่อองค์ประกอบ ซึ่งสไตล์จะถูกกำหนดขอบเขตให้ใช้กับองค์ประกอบเฉพาะนั้นเท่านั้น คุณจะสังเกตเห็นว่าในแต่ละองค์ประกอบ React ของฉัน div ระดับบนสุดมีชื่อคลาสที่ตรงกับชื่อของส่วนประกอบ ตัวอย่างเช่น HomePage.js มีมาร์กอัปที่ล้อมรอบโดย:

 <div className="HomePage"> ... </div>

นอกจากนี้ยังมีไฟล์ HomePage.scss ที่เกี่ยวข้องซึ่งมีโครงสร้างดังนี้:

 @import '../../styles/variables'; .HomePage { // Content here }

เหตุใดวิธีการนี้จึงมีประโยชน์มาก ส่งผลให้ CSS เป็นแบบโมดูลาร์สูง ซึ่งช่วยขจัดปัญหาพฤติกรรมการเรียงซ้อนที่ไม่ต้องการได้เป็นส่วนใหญ่

สมมติว่าเรามีส่วนประกอบ React สองส่วนประกอบ Component1 และ Component2 ในทั้งสองกรณี เราต้องการแทนที่ขนาดฟอนต์ h2

 /* Component1.scss */ .Component1 { h2 { font-size: 30px; } } /* Component2.scss */ .Component2 { h2 { font-size: 60px; } }

ขนาดฟอนต์ h2 ของ Component1 และ Component2 เป็นอิสระจากกัน ไม่ว่าส่วนประกอบจะอยู่ติดกัน หรือส่วนประกอบหนึ่งซ้อนอยู่ภายในอีกส่วนประกอบหนึ่ง ตามหลักการแล้ว หมายความว่าการจัดสไตล์ของส่วนประกอบมีความสมบูรณ์ในตัวเอง หมายความว่าส่วนประกอบจะมีลักษณะเหมือนกันทุกประการไม่ว่าจะวางไว้ที่ใดในมาร์กอัปของคุณ ในความเป็นจริง ไม่ใช่เรื่องง่ายเสมอไป แต่เป็นก้าวที่ยิ่งใหญ่ในทิศทางที่ถูกต้องอย่างแน่นอน

นอกจากสไตล์ตามองค์ประกอบแล้ว ฉันชอบที่จะมีโฟลเดอร์ styles ที่มีสไตล์ชีต global.scss พร้อมกับบางส่วนของ SASS ที่จัดการความรับผิดชอบเฉพาะ (ในกรณีนี้คือ _fonts.scss และ _variables.scss สำหรับแบบอักษรและตัวแปรตามลำดับ ). สไตล์ชีตส่วนกลางช่วยให้เรากำหนดลักษณะทั่วไปของแอปทั้งหมดได้ ในขณะที่สไตล์ชีตต่อองค์ประกอบสามารถนำเข้าบางส่วนของตัวช่วยได้ตามต้องการ

ตอนนี้เราได้สำรวจโค้ดทั่วไปในแต่ละสาขาอย่างละเอียดแล้ว เรามาเปลี่ยนโฟกัสไปที่แนวทาง task runner / bundler แบบแรกกัน

อึก + ตั้งค่าเบราว์เซอร์

gulpfile.js

สิ่งนี้ออกมาเป็นไฟล์ gulpfile ขนาดใหญ่อย่างน่าประหลาดใจ โดยมีการนำเข้า 22 รายการและโค้ด 150 บรรทัด ดังนั้น เพื่อความกระชับ ฉันจะตรวจสอบเฉพาะงาน js , css , server , watch และงาน default โดยละเอียดเท่านั้น

JS บันเดิล

 // Browserify specific configuration const b = browserify({ entries: [config.paths.entry], debug: true, plugin: PROD ? [] : [hmr, watchify], cache: {}, packageCache: {} }) .transform('babelify'); b.on('update', bundle); b.on('log', gutil.log); (...) gulp.task('js', bundle); (...) // Bundles our JS using Browserify. Sourcemaps are used in development, while minification is used in production. function bundle() { return b.bundle() .on('error', gutil.log.bind(gutil, 'Browserify Error')) .pipe(source('bundle.js')) .pipe(buffer()) .pipe(cond(PROD, minifyJS())) .pipe(cond(!PROD, sourcemaps.init({loadMaps: true}))) .pipe(cond(!PROD, sourcemaps.write())) .pipe(gulp.dest(config.paths.baseDir)); }

วิธีการนี้ค่อนข้างน่าเกลียดด้วยเหตุผลหลายประการ ประการหนึ่ง งานแบ่งออกเป็นสามส่วนแยกกัน ขั้นแรก คุณสร้างวัตถุบันเดิล Browserify b ส่งผ่านตัวเลือกบางตัวและกำหนดตัวจัดการเหตุการณ์บางตัว จากนั้น คุณมีงานอึก ซึ่งต้องส่งฟังก์ชันที่มีชื่อเป็นการเรียกกลับแทนการอินไลน์ (เนื่องจาก b.on('update') ใช้การเรียกกลับแบบเดียวกันนั้น สิ่งนี้แทบจะไม่มีความสง่างามของงานอึกที่คุณเพียงแค่ผ่านใน gulp.src และทำการเปลี่ยนแปลงบางอย่าง

อีกปัญหาหนึ่งคือสิ่งนี้บังคับให้เรามีวิธีการต่าง ๆ ในการโหลด html , css และ js ในเบราว์เซอร์ใหม่ ดูงาน watch อึกของเรา:

 gulp.task('watch', () => { livereload.listen({basePath: 'dist'}); gulp.watch(config.paths.html, ['html']); gulp.watch(config.paths.css, ['css']); gulp.watch(config.paths.js, () => { runSequence('lint', 'test'); }); });

เมื่อไฟล์ HTML ถูกเปลี่ยน งาน html จะถูกรันใหม่

 gulp.task('html', () => { return gulp.src(config.paths.html) .pipe(gulp.dest(config.paths.baseDir)) .pipe(cond(!PROD, livereload())); });

ไปป์สุดท้ายเรียก livereload() หาก NODE_ENV ไม่ได้ใช้ production จริง ซึ่งทริกเกอร์การรีเฟรชในเบราว์เซอร์

ตรรกะเดียวกันนี้ใช้สำหรับนาฬิกา CSS เมื่อไฟล์ CSS มีการเปลี่ยนแปลง งาน css จะถูกรันใหม่ และไปป์สุดท้ายในงาน css จะทริกเกอร์ livereload() และรีเฟรชเบราว์เซอร์

อย่างไรก็ตาม นาฬิกา js จะไม่เรียกใช้งาน js เลย ตัวจัดการเหตุการณ์ของ b.on('update', bundle) จัดการการโหลดซ้ำโดยใช้วิธีการที่แตกต่างไปจากเดิมอย่างสิ้นเชิง (กล่าวคือ การแทนที่โมดูลยอดนิยม) ความไม่สอดคล้องกันในแนวทางนี้ทำให้เกิดความรำคาญ แต่น่าเสียดายที่จำเป็นต้องสร้างส่วน เพิ่ม หากเราเรียก livereload() อย่างไร้เดียงสาที่ส่วนท้ายของฟังก์ชัน bundle ล สิ่งนี้จะสร้างบันเดิล JS ทั้งหมด ขึ้นใหม่ตามการเปลี่ยนแปลงไฟล์ JS แต่ละรายการ เห็นได้ชัดว่าวิธีการดังกล่าวไม่สามารถปรับขนาดได้ ยิ่งคุณมีไฟล์ JS มาก การรีบันเดิลแต่ละครั้งจะใช้เวลานานขึ้น ทันใดนั้น การรวมกลุ่มอีกครั้ง 500 ms ของคุณเริ่มใช้เวลา 30 วินาที ซึ่งขัดขวางการพัฒนาที่ว่องไวจริงๆ

บันเดิล CSS

 gulp.task('css', () => { return gulp.src( [ 'node_modules/bootstrap/dist/css/bootstrap.css', 'node_modules/font-awesome/css/font-awesome.css', config.paths.css ] ) .pipe(cond(!PROD, sourcemaps.init())) .pipe(sass().on('error', sass.logError)) .pipe(concat('bundle.css')) .pipe(cond(PROD, minifyCSS())) .pipe(cond(!PROD, sourcemaps.write())) .pipe(gulp.dest(config.paths.baseDir)) .pipe(cond(!PROD, livereload())); });

ปัญหาแรกที่นี่คือการรวม CSS ของผู้ขายที่ยุ่งยาก เมื่อใดก็ตามที่มีการเพิ่มไฟล์ CSS ของผู้ขายรายใหม่ลงในโครงการ เราต้องไม่ลืมที่จะเปลี่ยน gulpfile ของเราเพื่อเพิ่มองค์ประกอบในอาร์เรย์ gulp.src แทนที่จะเพิ่มการนำเข้าลงในตำแหน่งที่เกี่ยวข้องในซอร์สโค้ดจริงของเรา

ปัญหาหลักอื่น ๆ คือตรรกะที่ซับซ้อนในแต่ละไพพ์ ฉันต้องเพิ่มไลบรารี NPM ที่เรียกว่า gulp-cond เพื่อตั้งค่าลอจิกแบบมีเงื่อนไขในไพพ์ของฉัน และผลลัพธ์สุดท้ายก็ไม่สามารถอ่านได้ (วงเล็บสามอันทุกที่!)

งานเซิร์ฟเวอร์

 gulp.task('server', () => { nodemon({ script: 'server.js' }); });

งานนี้ตรงไปตรงมามาก โดยพื้นฐานแล้วมันคือตัวห่อหุ้มรอบการเรียกใช้บรรทัดคำสั่ง nodemon server.js ซึ่งรัน server.js ในสภาพแวดล้อมของโหนด nodemon ถูกใช้แทน node ดังนั้นการเปลี่ยนแปลงใดๆ ในไฟล์ทำให้ไฟล์รีสตาร์ท โดยค่าเริ่มต้น nodemon จะรีสตาร์ทกระบวนการที่ทำงานอยู่เมื่อ มี การเปลี่ยนแปลงไฟล์ JS ซึ่งเป็นสาเหตุสำคัญที่ต้องรวมไฟล์ nodemon.json เพื่อจำกัดขอบเขต:

 { "watch": "server.js" }

มาตรวจทานรหัสเซิร์ฟเวอร์ของเรากัน

server.js

 const baseDir = process.env.NODE_ENV === 'production' ? 'build' : 'dist'; const port = process.env.NODE_ENV === 'production' ? 8080: 3000; const app = express();

สิ่งนี้ตั้งค่าไดเร็กทอรีฐานของเซิร์ฟเวอร์และพอร์ตตามสภาพแวดล้อมของโหนด และสร้างอินสแตนซ์ของด่วน

 app.use(require('connect-livereload')({port: 35729})); app.use(express.static(path.join(__dirname, baseDir)));

สิ่งนี้จะเพิ่มมิดเดิลแวร์ connect-livereload (จำเป็นสำหรับการตั้งค่าการรีโหลดแบบสดของเรา) และมิดเดิลแวร์แบบสแตติก (จำเป็นสำหรับการจัดการสินทรัพย์สแตติกของเรา)

 app.get('/api/sample-route', (req, res) => { res.send({ website: 'Toptal', blogPost: true }); });

นี่เป็นเพียงเส้นทาง API ง่ายๆ หากคุณไปที่ localhost:3000/api/sample-route ในเบราว์เซอร์ คุณจะเห็น:

 { website: "Toptal", blogPost: true }

ในแบ็กเอนด์จริง คุณจะมีทั้งโฟลเดอร์เฉพาะสำหรับเส้นทาง API แยกไฟล์สำหรับสร้างการเชื่อมต่อ DB และอื่นๆ เส้นทางตัวอย่างนี้ถูกรวมไว้เพียงเพื่อแสดงว่าเราสามารถสร้างแบ็กเอนด์บนฟรอนท์เอนด์ที่เราตั้งค่าไว้ได้อย่างง่ายดาย

 app.get('*', (req, res) => { res.sendFile(path.join(__dirname, './', baseDir ,'/index.html')); });

นี่คือเส้นทางที่รับได้ทั้งหมด ซึ่งหมายความว่าไม่ว่าคุณจะพิมพ์ URL ใดในเบราว์เซอร์ เซิร์ฟเวอร์จะส่งคืนหน้า index.html เดียวของเรา จึงเป็นความรับผิดชอบของ React Router ในการแก้ไขเส้นทางของเราในฝั่งไคลเอ็นต์

 app.listen(port, () => { open(`http://localhost:${port}`); });

สิ่งนี้บอกให้อินสแตนซ์ด่วนของเราฟังพอร์ตที่เราระบุ และเปิดเบราว์เซอร์ในแท็บใหม่ที่ URL ที่ระบุ

จนถึงตอนนี้ สิ่งเดียวที่ฉันไม่ชอบเกี่ยวกับการตั้งค่าเซิร์ฟเวอร์คือ:

 app.use(require('connect-livereload')({port: 35729}));

เนื่องจากเราใช้ gulp-livereload ใน gulpfile ของเราอยู่แล้ว จึงทำให้ต้องใช้ livereload แยกกันสองแห่ง

ตอนนี้ สิ่งสุดท้ายแต่ไม่ท้ายสุด:

งานเริ่มต้น

 gulp.task('default', (cb) => { runSequence('clean', 'lint', 'test', 'html', 'css', 'js', 'fonts', 'server', 'watch', cb); });

นี่เป็นงานที่ดำเนินการเมื่อเพียงแค่พิมพ์ gulp ลงในเครื่องเทอร์มินัล ความแปลกประหลาดอย่างหนึ่งคือต้องใช้ runSequence เพื่อให้งานทำงานตามลำดับ โดยปกติ อาร์เรย์ของงานจะดำเนินการควบคู่กันไป แต่นี่ไม่ใช่พฤติกรรมที่ต้องการเสมอไป ตัวอย่างเช่น เราจำเป็นต้อง clean ใช้งานใหม่ทั้งหมดก่อน html เพื่อให้แน่ใจว่าโฟลเดอร์ปลายทางของเราว่างเปล่าก่อนที่จะย้ายไฟล์ไปไว้ในนั้น เมื่อเปิดตัว gulp 4 มันจะสนับสนุนวิธี gulp.series และ gulp.parallel แบบเนทีฟ แต่สำหรับตอนนี้ เราต้องปล่อยให้มีมุมแหลมเล็กน้อยนี้ในการตั้งค่าของเรา

ยิ่งไปกว่านั้น มันค่อนข้างหรูหราจริงๆ การสร้างและโฮสต์แอปของเราทั้งหมดดำเนินการในคำสั่งเดียว และการทำความเข้าใจส่วนใดส่วนหนึ่งของเวิร์กโฟลว์นั้นง่ายพอๆ กับการตรวจสอบงานแต่ละงานในลำดับการทำงาน นอกจากนี้ เราสามารถแบ่งลำดับทั้งหมดออกเป็นส่วนย่อยๆ เพื่อให้ได้แนวทางที่ละเอียดยิ่งขึ้นในการสร้างและโฮสต์แอป ตัวอย่างเช่น เราสามารถตั้งค่างานแยกต่างหากที่เรียกว่า validate ซึ่งรันงาน lint และ test หรือเราอาจมีงาน host ที่รัน server และ watch ความสามารถในการจัดการงานนี้มีประสิทธิภาพมาก โดยเฉพาะอย่างยิ่งเมื่อแอปพลิเคชันของคุณปรับขนาดและต้องการงานอัตโนมัติมากขึ้น

การพัฒนาเทียบกับบิลด์การผลิต

 if (argv.prod) { process.env.NODE_ENV = 'production'; } let PROD = process.env.NODE_ENV === 'production';

การใช้ yargs NPM เราสามารถจัดหาแฟล็กบรรทัดคำสั่งให้กับ Gulp ที่นี่ฉันสั่งให้ gulpfile ตั้งค่าสภาพแวดล้อมของโหนดเป็นการผลิตหาก --prod ถูกส่งไปยัง gulp ในเทอร์มินัล จากนั้นตัวแปร PROD ของเราจะถูกใช้เป็นเงื่อนไขเพื่อแยกความแตกต่างระหว่างการพัฒนาและพฤติกรรมการผลิตใน gulpfile ของเรา ตัวอย่างเช่น หนึ่งในตัวเลือกที่เราส่งไปยังการ browserify ของเราคือ:

 plugin: PROD ? [] : [hmr, watchify]

สิ่งนี้บอกให้ browserify ไม่ใช้ปลั๊กอินใดๆ ในโหมดการใช้งานจริง และใช้ hmr และ watchify ปลั๊กอินในสภาพแวดล้อมอื่นๆ

เงื่อนไข PROD นี้มีประโยชน์มากเพราะช่วยเราไม่ต้องเขียน gulpfile แยกต่างหากสำหรับการผลิตและการพัฒนา ซึ่งท้ายที่สุดแล้วจะมีการซ้ำซ้อนของโค้ดเป็นจำนวนมาก แต่เราสามารถทำสิ่งต่างๆ เช่น gulp --prod เพื่อเรียกใช้งานเริ่มต้นในการผลิต หรือ gulp html --prod เพื่อเรียกใช้งาน html ในการผลิตเท่านั้น ในอีกทางหนึ่ง เราเห็นก่อนหน้านี้ว่าการทิ้งไปป์ไลน์อึกของเราด้วยข้อความเช่น .pipe(cond(!PROD, livereload())) นั้นอ่านยากที่สุด ในท้ายที่สุด มันเป็นเรื่องของการตั้งค่าว่าคุณต้องการใช้วิธีตัวแปรบูลีนหรือตั้งค่า gulpfiles แยกกันสองไฟล์

ตอนนี้เรามาดูกันว่าจะเกิดอะไรขึ้นเมื่อเรายังคงใช้ Gulp เป็น task runner ของเรา แต่แทนที่ Browserify ด้วย Webpack

อึก + ตั้งค่า Webpack

ทันใดนั้น gulpfile ของเรามีความยาวเพียง 99 บรรทัด โดยมีการนำเข้า 12 รายการ ซึ่งค่อนข้างลดลงจากการตั้งค่าก่อนหน้าของเรา! หากเราตรวจสอบงานเริ่มต้น:

 gulp.task('default', (cb) => { runSequence('lint', 'test', 'build', 'server', 'watch', cb); });

ตอนนี้การตั้งค่าเว็บแอปแบบสมบูรณ์ของเราต้องการเพียงห้างานแทนที่จะเป็นเก้างาน ซึ่งเป็นการปรับปรุงอย่างมาก

นอกจากนี้ เรายังได้ขจัดความจำเป็นใน livereload งาน watch ของเราตอนนี้ง่าย ๆ :

 gulp.task('watch', () => { gulp.watch(config.paths.js, () => { runSequence('lint', 'test'); }); });

ซึ่งหมายความว่าผู้สังเกตการณ์อึกของเราไม่ได้เรียกพฤติกรรมการรวมกลุ่มใหม่ใดๆ เพื่อเป็นโบนัสเพิ่มเติม เราไม่จำเป็นต้องถ่ายโอน index.html จาก app พไปยัง dist หรือ build อีกต่อไป

งาน html , css , js และ fonts ของเราถูกแทนที่ด้วยงาน build ด์เดียว:

 gulp.task('build', () => { runSequence('clean', 'html'); return gulp.src(config.paths.entry) .pipe(webpack(require('./webpack.config'))) .pipe(gulp.dest(config.paths.baseDir)); });

ง่ายพอ รันงาน clean และ html ตามลำดับ เมื่อทำเสร็จแล้ว ให้คว้าจุดเข้าใช้งาน ไพพ์ผ่าน Webpack ส่งไฟล์ webpack.config.js เพื่อกำหนดค่า และส่งบันเดิลที่เป็นผลลัพธ์ไปยัง baseDir ของเรา (ทั้ง dist หรือ build ขึ้นอยู่กับโหนด env)

มาดูไฟล์กำหนดค่า Webpack กัน:

webpack.config.js

นี่เป็นไฟล์กำหนดค่าที่ค่อนข้างใหญ่และน่ากลัว ดังนั้นเรามาอธิบายคุณสมบัติที่สำคัญบางอย่างที่ตั้งค่าไว้ในอ็อบเจ็กต์ module.exports ของเรา

 devtool: PROD ? 'source-map' : 'eval-source-map',

ซึ่งจะกำหนดประเภทของ sourcemaps ที่ Webpack จะใช้ Webpack ไม่เพียงแค่สนับสนุน Sourcemaps เท่านั้น แต่ยังสนับสนุนตัวเลือก Sourcemap ที่หลากหลายอีกด้วย แต่ละตัวเลือกให้ความสมดุลที่แตกต่างกันของรายละเอียดแผนที่ต้นทางกับความเร็วในการสร้างใหม่ (เวลาที่ใช้ในการรวมกลุ่มใหม่ตามการเปลี่ยนแปลง) ซึ่งหมายความว่าเราสามารถใช้ตัวเลือก sourcemap ที่ "ถูก" สำหรับการพัฒนาเพื่อให้โหลดซ้ำได้เร็ว และตัวเลือก sourcemap ที่มีราคาแพงกว่าในการผลิต

 entry: PROD ? './app/index' : [ 'webpack-hot-middleware/client?reload=true', // reloads the page if hot module reloading fails. './app/index' ]

นี่คือจุดเริ่มต้นบันเดิลของเรา ขอให้สังเกตว่าอาร์เรย์ถูกส่งผ่าน หมายความว่าเป็นไปได้ที่จะมีจุดเข้าใช้งานหลายจุด ในกรณีนี้ เรามีจุดเข้าใช้งานที่คาดหวัง app/index.js รวมทั้งจุดเข้าใช้งาน webpack-hot-middleware ที่ใช้เป็นส่วนหนึ่งของการตั้งค่าการโหลดโมดูลใหม่ของเรา

 output: { path: PROD ? __dirname + '/build' : __dirname + '/dist', publicPath: '/', filename: 'bundle.js' },

นี่คือที่ที่บันเดิลที่คอมไพล์แล้วจะถูกส่งออก ตัวเลือกที่สับสนที่สุดคือ publicPath มันตั้งค่า URL พื้นฐานสำหรับที่บันเดิลของคุณจะโฮสต์บนเซิร์ฟเวอร์ ตัวอย่างเช่น หาก publicPath ของคุณคือ /public/assets บันเดิลจะปรากฏภายใต้ /public/assets/bundle.js บนเซิร์ฟเวอร์

 devServer: { contentBase: PROD ? './build' : './app' }

สิ่งนี้จะบอกเซิร์ฟเวอร์ว่าจะใช้โฟลเดอร์ใดในโปรเจ็กต์ของคุณเป็นไดเร็กทอรีรากของเซิร์ฟเวอร์

หากคุณเคยสับสนว่า Webpack จับคู่บันเดิลที่สร้างขึ้นในโปรเจ็กต์ของคุณกับบันเดิลบนเซิร์ฟเวอร์อย่างไร ให้จำสิ่งต่อไปนี้:

  • path + filename : ตำแหน่งที่แน่นอนของบันเดิลในซอร์สโค้ดของโปรเจ็กต์
  • contentBase (ในฐานะ root, / ) + publicPath : ตำแหน่งของบันเดิลบนเซิร์ฟเวอร์
 plugins: PROD ? [ new webpack.optimize.OccurenceOrderPlugin(), new webpack.DefinePlugin(GLOBALS), new ExtractTextPlugin('bundle.css'), new webpack.optimize.DedupePlugin(), new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}) ] : [ new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin() ],

เหล่านี้เป็นปลั๊กอินที่ปรับปรุงการทำงานของ Webpack ในทางใดทางหนึ่ง ตัวอย่างเช่น webpack.optimize.UglifyJsPlugin มีหน้าที่ในการลดขนาด

 loaders: [ {test: /\.js$/, include: path.join(__dirname, 'app'), loaders: ['babel']}, { test: /\.css$/, loader: PROD ? ExtractTextPlugin.extract('style', 'css?sourceMap'): 'style!css?sourceMap' }, { test: /\.scss$/, loader: PROD ? ExtractTextPlugin.extract('style', 'css?sourceMap!resolve-url!sass?sourceMap') : 'style!css?sourceMap!resolve-url!sass?sourceMap' }, {test: /\.(svg|png|jpe?g|gif)(\?\S*)?$/, loader: 'url?limit=100000&name=img/[name].[ext]'}, {test: /\.(eot|woff|woff2|ttf)(\?\S*)?$/, loader: 'url?limit=100000&name=fonts/[name].[ext]'} ]

นี่คือรถตัก โดยพื้นฐานแล้ว พวกมันจะประมวลผลไฟล์ล่วงหน้าที่โหลดผ่านคำสั่ง require() พวกมันค่อนข้างคล้ายกับท่ออึกเพราะคุณสามารถใส่โซ่ตรวนเข้าด้วยกันได้

มาตรวจสอบหนึ่งในออบเจ็กต์ตัวโหลดของเรา:

 {test: /\.scss$/, loader: 'style!css?sourceMap!resolve-url!sass?sourceMap'}

คุณสมบัติ test บอก Webpack ว่าตัวโหลดที่กำหนดจะใช้หากไฟล์ตรงกับรูปแบบ regex ที่ให้มา ในกรณีนี้ /\.scss$/ คุณสมบัติ loader สอดคล้องกับการกระทำที่ตัวโหลดดำเนินการ ที่นี่เรากำลังเชื่อมโยง style , css , dissolve resolve-url และ sass loaders เข้าด้วยกันซึ่งจะดำเนินการในลำดับที่กลับกัน

ฉันต้องยอมรับว่าฉันไม่พบ loader3!loader2!loader1 ที่หรูหรามาก ท้ายที่สุดคุณต้องอ่านอะไรในโปรแกรมจากขวาไปซ้ายเมื่อใด อย่างไรก็ตาม เรื่องนี้ ตัวโหลดเป็นคุณสมบัติที่ทรงพลังมากของ webpack อันที่จริง ตัวโหลดที่ฉันเพิ่งพูดถึงช่วยให้เรานำเข้าไฟล์ SASS ลงใน JavaScript ของเราได้โดยตรง! ตัวอย่างเช่น เราสามารถนำเข้าผู้ขายและสไตล์ชีตส่วนกลางในไฟล์จุดเข้าใช้งานของเรา:

index.js

 import React from 'react'; import {render} from 'react-dom'; import {Router, browserHistory} from 'react-router'; import routes from './routes'; // CSS imports import '../node_modules/bootstrap/dist/css/bootstrap.css'; import '../node_modules/font-awesome/css/font-awesome.css'; import './styles/global.scss'; render(<Router history={browserHistory} routes={routes} />, document.getElementById('app'));

ในทำนองเดียวกัน ในองค์ประกอบส่วนหัวของเรา เราสามารถเพิ่ม import './Header.scss' เพื่อนำเข้าสไตล์ชีตที่เกี่ยวข้องของส่วนประกอบ สิ่งนี้ใช้กับส่วนประกอบอื่นๆ ทั้งหมดของเราด้วย

ในความคิดของฉัน สิ่งนี้ถือได้ว่าเป็นการเปลี่ยนแปลงที่ปฏิวัติวงการในโลกของการพัฒนา JavaScript ไม่จำเป็นต้องกังวลเกี่ยวกับการรวมกลุ่ม CSS การลดขนาด หรือซอร์สแมป เนื่องจากตัวโหลดของเราจัดการทั้งหมดนี้ให้เรา แม้แต่การโหลดโมดูลใหม่ก็ยังใช้ได้กับไฟล์ CSS ของเรา จากนั้น ความสามารถในการจัดการการนำเข้า JS และ CSS ในไฟล์เดียวกันทำให้การพัฒนาแนวคิดง่ายขึ้น: มีความสอดคล้องมากขึ้น การสลับบริบทน้อยลง และให้เหตุผลได้ง่ายขึ้น

เพื่อให้ข้อมูลสรุปโดยย่อเกี่ยวกับวิธีการทำงานของคุณลักษณะนี้: Webpack จะแทรก CSS ลงในบันเดิล JS ของเรา อันที่จริง Webpack สามารถทำได้สำหรับรูปภาพและฟอนต์เช่นกัน:

 {test: /\.(svg|png|jpe?g|gif)(\?\S*)?$/, loader: 'url?limit=100000&name=img/[name].[ext]'}, {test: /\.(eot|woff|woff2|ttf)(\?\S*)?$/, loader: 'url?limit=100000&name=fonts/[name].[ext]'}

ตัวโหลด URL สั่งให้ Webpack แทรกรูปภาพและแบบอักษรของเราเป็น URL ข้อมูล หากมีขนาดต่ำกว่า 100 KB มิฉะนั้นจะทำหน้าที่เป็นไฟล์แยกต่างหาก แน่นอน เรายังสามารถกำหนดค่าขนาดจุดตัดเป็นค่าอื่น เช่น 10 KB

และนั่นคือการกำหนดค่า Webpack โดยสรุป ฉันจะยอมรับว่ามีการติดตั้งในปริมาณที่พอเหมาะ แต่ประโยชน์ของการใช้มันเป็นปรากฎการณ์ง่ายๆ แม้ว่า Browserify จะมีปลั๊กอินและการแปลง แต่ก็ไม่สามารถเปรียบเทียบกับตัวโหลด Webpack ในแง่ของฟังก์ชันเพิ่มเติม

การตั้งค่าสคริปต์ Webpack + NPM

ในการตั้งค่านี้ เราใช้สคริปต์ npm โดยตรงแทนที่จะใช้ gulpfile เพื่อทำให้งานของเราเป็นแบบอัตโนมัติ

package.json

 "scripts": { "start": "npm-run-all --parallel lint:watch test:watch build", "start:prod": "npm-run-all --parallel lint test build:prod", "clean-dist": "rimraf ./dist && mkdir dist", "clean-build": "rimraf ./build && mkdir build", "clean": "npm-run-all clean-dist clean-build", "test": "mocha ./app/**/*.test.js --compilers js:babel-core/register", "test:watch": "npm run test -- --watch", "lint": "esw ./app/**/*.js", "lint:watch": "npm run lint -- --watch", "server": "nodemon server.js", "server:prod": "cross-env NODE_ENV=production nodemon server.js", "build-html": "node tools/buildHtml.js", "build-html:prod": "cross-env NODE_ENV=production node tools/buildHtml.js", "prebuild": "npm-run-all clean-dist build-html", "build": "webpack", "postbuild": "npm run server", "prebuild:prod": "npm-run-all clean-build build-html:prod", "build:prod": "cross-env NODE_ENV=production webpack", "postbuild:prod": "npm run server:prod" }

ในการรันบิวด์การพัฒนาและการผลิต ให้ป้อน npm start และ npm run start:prod ตามลำดับ

มีขนาดกะทัดรัดกว่า gulpfile ของเราอย่างแน่นอน เนื่องจากเราได้ตัดโค้ด 99 ถึง 150 บรรทัดเหลือ 19 สคริปต์ NPM หรือ 12 หากเราไม่รวมสคริปต์ที่ใช้งานจริง (ซึ่งส่วนใหญ่เป็นเพียงการจำลองสคริปต์การพัฒนาด้วยสภาพแวดล้อมโหนดที่ตั้งค่าเป็นการใช้งานจริง ). ข้อเสียคือ คำสั่งเหล่านี้ค่อนข้างคลุมเครือเมื่อเทียบกับคำสั่งอื่นๆ ของ Gulp และไม่ได้แสดงออกมากนัก ตัวอย่างเช่น ไม่มีทาง (อย่างน้อยที่ฉันรู้) ให้สคริปต์ npm เดียวรันคำสั่งบางคำสั่งในซีรีส์และคำสั่งอื่นๆ แบบคู่ขนานกัน เป็นอย่างใดอย่างหนึ่ง

อย่างไรก็ตาม มีข้อได้เปรียบอย่างมากสำหรับแนวทางนี้ ด้วยการใช้ไลบรารี NPM เช่น mocha โดยตรงจากบรรทัดคำสั่ง คุณไม่จำเป็นต้องติดตั้ง Gulp wrapper ที่เทียบเท่ากันสำหรับแต่ละรายการ (ในกรณีนี้คือ gulp-mocha )

แทนที่จะติดตั้ง NPM

  • อึก-eslint
  • อึก-มอคค่า
  • อึก-nodemon
  • ฯลฯ

เราติดตั้งแพ็คเกจต่อไปนี้:

  • eslint
  • มอคค่า
  • โนเดมอน
  • ฯลฯ

การอ้างอิงโพสต์ของ Cory House เหตุใดฉันจึงทิ้งอึกและฮึกเหิมสำหรับสคริปต์ NPM :

ฉันเป็นแฟนตัวยงของอึก แต่ในโปรเจ็กต์สุดท้ายของฉัน ฉันลงเอยด้วย gulpfile หลายร้อยบรรทัดและปลั๊กอิน Gulp ประมาณโหล ฉันกำลังดิ้นรนเพื่อรวม Webpack, Browsersync, hot reloading, Mocha และอีกมากมายโดยใช้ Gulp ทำไม? ปลั๊กอินบางตัวมีเอกสารไม่เพียงพอสำหรับกรณีการใช้งานของฉัน ปลั๊กอินบางตัวเปิดเผยเฉพาะส่วนหนึ่งของ API ที่ฉันต้องการ ตัวหนึ่งมีข้อบกพร่องแปลก ๆ ที่จะดูไฟล์จำนวนเล็กน้อยเท่านั้น สีลอกอีกเมื่อส่งออกไปยังบรรทัดคำสั่ง

เขาระบุประเด็นหลักสามประการเกี่ยวกับอึก:

  1. ขึ้นอยู่กับผู้เขียนปลั๊กอิน
  2. ผิดหวังกับการดีบัก
  3. เอกสารที่ไม่ปะติดปะต่อ

ฉันมักจะเห็นด้วยกับสิ่งเหล่านี้ทั้งหมด

1. การพึ่งพาผู้เขียนปลั๊กอิน

เมื่อใดก็ตามที่ไลบรารีเช่น eslint ได้รับการอัปเดต ไลบรารี gulp-eslint ที่เกี่ยวข้องจะต้องมีการอัปเดตที่สอดคล้องกัน หากผู้ดูแลห้องสมุดหมดความสนใจ เวอร์ชันอึกของไลบรารีจะไม่ซิงค์กับไลบรารีดั้งเดิม เช่นเดียวกับเมื่อมีการสร้างห้องสมุดใหม่ หากมีคนสร้างไลบรารี่ xyz และมันใช้งานได้ ทันใดนั้นคุณต้องมีไลบรารี gulp-xyz ที่สอดคล้องกันเพื่อใช้ในงานอึกของคุณ

ในแง่หนึ่ง วิธีการนี้ไม่สามารถปรับขนาดได้ เป็นการดีที่เราต้องการแนวทางเช่นอึกที่สามารถใช้ไลบรารีดั้งเดิมได้

2. ผิดหวังกับการดีบัก

แม้ว่าไลบรารี่อย่าง gulp-plumber จะช่วยบรรเทาปัญหานี้ได้มาก แต่ก็เป็นความจริงที่การรายงานข้อผิดพลาดใน gulp นั้นไม่ได้มีประโยชน์มากนัก หากไพพ์เดียวส่งข้อยกเว้นที่ไม่สามารถจัดการได้ คุณจะได้รับการติดตามสแต็กสำหรับปัญหาที่ดูเหมือนไม่เกี่ยวข้องกับสาเหตุที่ทำให้เกิดปัญหาในซอร์สโค้ดของคุณ ซึ่งอาจทำให้การดีบักกลายเป็นฝันร้ายในบางกรณี ไม่มีการค้นหาใน Google หรือ Stack Overflow มากมายที่สามารถช่วยคุณได้หากข้อผิดพลาดนั้นคลุมเครือหรือทำให้เข้าใจผิดเพียงพอ

3. เอกสารที่ไม่ปะติดปะต่อ

บ่อยครั้งที่ฉันพบว่าห้องสมุด gulp ขนาดเล็กมักจะมีเอกสารที่จำกัดมาก ฉันสงสัยว่าเป็นเพราะผู้เขียนมักจะสร้างห้องสมุดเพื่อการใช้งานของตนเองเป็นหลัก นอกจากนี้ เป็นเรื่องปกติที่จะต้องดูเอกสารประกอบสำหรับทั้งปลั๊กอิน Gulp และไลบรารีเนทีฟเอง ซึ่งหมายถึงการสลับบริบทจำนวนมากและต้องอ่านมากเป็นสองเท่า

บทสรุป

ดูเหมือนว่าค่อนข้างชัดเจนสำหรับฉันว่า Webpack นั้นดีกว่า Browserify และสคริปต์ NPM นั้นดีกว่า Gulp แม้ว่าแต่ละตัวเลือกจะมีข้อดีและข้อเสีย อึกสามารถแสดงออกและใช้งานได้สะดวกกว่าสคริปต์ NPM อย่างแน่นอน แต่คุณต้องจ่ายราคาในส่วนที่เป็นนามธรรมที่เพิ่มเข้ามาทั้งหมด

ไม่ใช่ทุกชุดค่าผสมจะสมบูรณ์แบบสำหรับแอปของคุณ แต่ถ้าคุณต้องการหลีกเลี่ยงการพึ่งพาการพัฒนาจำนวนมากและประสบการณ์การดีบักที่น่าผิดหวัง Webpack ที่มีสคริปต์ NPM เป็นวิธีที่จะไป ฉันหวังว่าคุณจะพบว่าบทความนี้มีประโยชน์ในการเลือกเครื่องมือที่เหมาะสมสำหรับโครงการต่อไปของคุณ

ที่เกี่ยวข้อง:
  • รักษาการควบคุม: A Guide to Webpack and React, Pt. 1
  • Gulp Under the Hood: การสร้างเครื่องมือทำงานอัตโนมัติบนสตรีม