Webpack atau Browserify & Gulp: Mana yang Lebih Baik?

Diterbitkan: 2022-03-11

Saat aplikasi web tumbuh semakin kompleks, membuat aplikasi web Anda dapat diskalakan menjadi hal yang paling penting. Jika dulu menulis JavaScript ad-hoc dan jQuery sudah cukup, saat ini membangun aplikasi web membutuhkan tingkat disiplin dan praktik pengembangan perangkat lunak formal yang jauh lebih tinggi, seperti:

  • Tes unit untuk memastikan modifikasi pada kode Anda tidak merusak fungsionalitas yang ada
  • Linting untuk memastikan gaya pengkodean yang konsisten bebas dari kesalahan
  • Build produksi yang berbeda dari build pengembangan

Web juga menyediakan beberapa tantangan pengembangan uniknya sendiri. Misalnya, karena halaman web membuat banyak permintaan asinkron, kinerja aplikasi web Anda dapat diturunkan secara signifikan karena harus meminta ratusan file JS dan CSS, masing-masing dengan overhead kecilnya sendiri (header, handshake, dan sebagainya). Masalah khusus ini sering kali dapat diatasi dengan menggabungkan file-file tersebut, jadi Anda hanya meminta satu paket file JS dan CSS daripada ratusan file individual.

Pengorbanan alat bundel: Webpack vs Browserify

Alat bundling mana yang harus Anda gunakan: Webpack atau Browserify + Gulp? Berikut adalah panduan untuk memilih.
Menciak

Juga cukup umum untuk menggunakan praprosesor bahasa seperti SASS dan JSX yang dikompilasi ke JS dan CSS asli, serta transpiler JS seperti Babel, untuk mendapatkan manfaat dari kode ES6 sambil mempertahankan kompatibilitas ES5.

Ini berarti sejumlah besar tugas yang tidak ada hubungannya dengan penulisan logika aplikasi web itu sendiri. Di sinilah tugas pelari masuk. Tujuan pelari tugas adalah untuk mengotomatiskan semua tugas ini sehingga Anda bisa mendapatkan keuntungan dari lingkungan pengembangan yang disempurnakan sambil berfokus pada penulisan aplikasi Anda. Setelah pelari tugas dikonfigurasi, yang perlu Anda lakukan hanyalah menjalankan satu perintah di terminal.

Saya akan menggunakan Gulp sebagai pelari tugas karena sangat ramah pengembang, mudah dipelajari, dan mudah dimengerti.

Pengantar Singkat tentang Gulp

API Gulp terdiri dari empat fungsi:

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

Cara Kerja Gulp

Di sini, misalnya, adalah contoh tugas yang menggunakan tiga dari empat fungsi ini:

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

Saat my-first-task dilakukan, semua file yang cocok dengan pola glob /public/js/**/*.js diperkecil dan kemudian ditransfer ke folder build .

Keindahan ini ada di .pipe() chaining. Anda mengambil satu set file input, menyalurkannya melalui serangkaian transformasi, lalu mengembalikan file output. Untuk membuat segalanya lebih nyaman, transformasi perpipaan yang sebenarnya, seperti minify() , sering dilakukan oleh perpustakaan NPM. Akibatnya, dalam praktiknya sangat jarang Anda perlu menulis transformasi Anda sendiri selain mengganti nama file di dalam pipa.

Langkah selanjutnya untuk memahami Gulp adalah memahami susunan dependensi tugas.

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

Di sini, my-second-task hanya menjalankan fungsi panggilan balik setelah tugas lint dan bundle selesai. Ini memungkinkan pemisahan masalah: Anda membuat serangkaian tugas kecil dengan satu tanggung jawab, seperti mengonversi LESS ke CSS , dan membuat semacam tugas utama yang hanya memanggil semua tugas lain melalui larik dependensi tugas.

Akhirnya, kami memiliki gulp.watch , yang mengawasi pola file glob untuk perubahan, dan ketika perubahan terdeteksi, menjalankan serangkaian tugas.

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

Dalam contoh di atas, setiap perubahan pada file yang cocok dengan /public/js/**/*.js akan memicu tugas lint dan reload . Penggunaan umum dari gulp.watch adalah untuk memicu pemuatan ulang langsung di browser, sebuah fitur yang sangat berguna untuk pengembangan sehingga Anda tidak akan dapat hidup tanpanya setelah Anda mengalaminya.

Dan begitu saja, Anda memahami semua yang benar-benar perlu Anda ketahui tentang gulp .

Di Mana Webpack Cocok?

Bagaimana Webpack Bekerja

Saat menggunakan pola CommonJS, menggabungkan file JavaScript tidak sesederhana menggabungkannya. Sebaliknya, Anda memiliki titik masuk (biasanya disebut index.js atau app.js ) dengan serangkaian pernyataan require atau import di bagian atas file:

ES5

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

ES6

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

Dependensi harus diselesaikan sebelum kode yang tersisa di app.js , dan dependensi itu sendiri mungkin memiliki dependensi lebih lanjut untuk diselesaikan. Selain itu, Anda mungkin require ketergantungan yang sama di beberapa tempat di aplikasi Anda, tetapi Anda hanya ingin menyelesaikan ketergantungan itu sekali. Seperti yang dapat Anda bayangkan, setelah Anda memiliki pohon ketergantungan beberapa tingkat, proses bundling JavaScript Anda menjadi agak rumit. Di sinilah bundler seperti Browserify dan Webpack masuk.

Mengapa Pengembang Menggunakan Webpack Alih-alih Gulp?

Webpack adalah bundler sedangkan Gulp adalah task runner, jadi Anda akan berharap untuk melihat kedua alat ini biasa digunakan bersama. Sebaliknya, ada tren yang berkembang, terutama di antara komunitas React, untuk menggunakan Webpack daripada Gulp. Kenapa ini?

Sederhananya, Webpack adalah alat yang sangat kuat sehingga sudah dapat melakukan sebagian besar tugas yang seharusnya Anda lakukan melalui pelari tugas. Misalnya, Webpack sudah menyediakan opsi untuk minifikasi dan peta sumber untuk bundel Anda. Selain itu, Webpack dapat dijalankan sebagai middleware melalui server khusus yang disebut webpack-dev-server , yang mendukung pemuatan ulang langsung dan pemuatan ulang panas (kita akan membicarakan fitur ini nanti). Dengan menggunakan loader, Anda juga dapat menambahkan transpilasi ES6 ke ES5, dan pra-dan pasca-prosesor CSS. Itu benar-benar hanya meninggalkan tes unit dan linting sebagai tugas utama yang tidak dapat ditangani oleh Webpack secara mandiri. Mengingat bahwa kami telah mengurangi setidaknya setengah lusin tugas teguk potensial menjadi dua, banyak pengembang memilih untuk menggunakan skrip NPM secara langsung, karena ini menghindari biaya tambahan untuk menambahkan Gulp ke proyek (yang juga akan kita bicarakan nanti) .

Kelemahan utama menggunakan Webpack adalah agak sulit untuk dikonfigurasi, yang tidak menarik jika Anda mencoba untuk menjalankan dan menjalankan proyek dengan cepat.

3 Pengaturan Pelari Tugas kami

Saya akan menyiapkan proyek dengan tiga pengaturan pelari tugas yang berbeda. Setiap penyiapan akan melakukan tugas-tugas berikut:

  • Siapkan server pengembangan dengan pemuatan ulang langsung pada perubahan file yang ditonton
  • Bundel file JS & CSS kami (termasuk transpilasi ES6 ke ES5, konversi SASS ke CSS, dan peta sumber) dengan cara yang dapat diskalakan pada perubahan file yang ditonton
  • Jalankan pengujian unit baik sebagai tugas mandiri atau dalam mode menonton
  • Jalankan linting baik sebagai tugas mandiri atau dalam mode menonton
  • Berikan kemampuan untuk menjalankan semua hal di atas melalui satu perintah di terminal
  • Miliki perintah lain untuk membuat bundel produksi dengan minifikasi dan pengoptimalan lainnya

Tiga pengaturan kami adalah:

  • Gulp + Browserify
  • Gulp + Webpack
  • Webpack + Skrip NPM

Aplikasi akan menggunakan React untuk front-end. Awalnya, saya ingin menggunakan pendekatan kerangka-agnostik, tetapi menggunakan React sebenarnya menyederhanakan tanggung jawab pelari tugas, karena hanya satu file HTML yang diperlukan, dan React bekerja sangat baik dengan pola CommonJS.

Kami akan membahas keuntungan dan kerugian dari setiap pengaturan sehingga Anda dapat membuat keputusan berdasarkan informasi tentang jenis pengaturan yang paling sesuai dengan kebutuhan proyek Anda.

Saya telah menyiapkan repositori Git dengan tiga cabang, satu untuk setiap pendekatan (tautan). Menguji setiap penyiapan semudah:

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

Mari kita periksa kode di setiap cabang secara detail…

Kode Umum

Struktur Folder

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

index.html

File HTML langsung. Aplikasi React dimuat ke <div></div> dan kami hanya menggunakan satu paket file JS dan CSS. Bahkan, dalam penyiapan pengembangan Webpack kami, kami bahkan tidak memerlukan bundle.css .

index.js

Ini bertindak sebagai titik masuk JS dari aplikasi kita. Pada dasarnya, kami hanya memuat React Router ke div dengan app id yang kami sebutkan sebelumnya.

route.js

File ini mendefinisikan rute kami. Url / , /about dan /contact dipetakan ke komponen HomePage , AboutPage , dan ContactPage , masing-masing.

index.test.js

Ini adalah serangkaian pengujian unit yang menguji perilaku JavaScript asli. Dalam aplikasi kualitas produksi nyata, Anda akan menulis pengujian unit per komponen React (setidaknya yang memanipulasi status), menguji perilaku spesifik React. Namun, untuk keperluan posting ini, cukup hanya memiliki tes unit fungsional yang dapat berjalan dalam mode tontonan.

komponen/App.js

Ini dapat dianggap sebagai wadah untuk semua tampilan halaman kami. Setiap halaman berisi komponen <Header/> serta this.props.children , yang mengevaluasi tampilan halaman itu sendiri (misalnya/ ContactPage jika di /contact di browser).

komponen/home/HomePage.js

Ini adalah tampilan rumah kami. Saya telah memilih untuk menggunakan react-bootstrap karena sistem grid bootstrap sangat baik untuk membuat halaman responsif. Dengan penggunaan bootstrap yang tepat, jumlah kueri media yang harus Anda tulis untuk area pandang yang lebih kecil berkurang secara dramatis.

Komponen yang tersisa ( Header , AboutPage , ContactPage ) terstruktur dengan cara yang sama ( markup react-bootstrap , tidak ada manipulasi status).

Sekarang mari kita bicara lebih banyak tentang gaya.

Pendekatan Gaya CSS

Pendekatan pilihan saya untuk menata komponen React adalah memiliki satu lembar gaya per komponen, yang gayanya hanya berlaku untuk komponen tertentu itu. Anda akan melihat bahwa di setiap komponen React saya, div tingkat atas memiliki nama kelas yang cocok dengan nama komponen. Jadi, misalnya, HomePage.js memiliki markup yang dibungkus oleh:

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

Ada juga file HomePage.scss terkait yang terstruktur sebagai berikut:

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

Mengapa pendekatan ini sangat berguna? Ini menghasilkan CSS yang sangat modular, sebagian besar menghilangkan masalah perilaku cascading yang tidak diinginkan.

Misalkan kita memiliki dua komponen React, Component1 dan Component2 . Dalam kedua kasus, kami ingin mengganti ukuran font h2 .

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

Ukuran font h2 dari Component1 dan Component2 independen apakah komponen berdekatan, atau satu komponen bersarang di dalam yang lain. Idealnya, ini berarti gaya komponen sepenuhnya mandiri, artinya komponen akan terlihat persis sama di mana pun ditempatkan di markup Anda. Pada kenyataannya, itu tidak selalu sesederhana itu, tetapi ini tentu saja merupakan langkah besar ke arah yang benar.

Selain gaya per komponen, saya ingin memiliki folder styles yang berisi global stylesheet global.scss , bersama dengan parsial SASS yang menangani tanggung jawab tertentu (dalam hal ini, _fonts.scss dan _variables.scss untuk font dan variabel, masing-masing ). Stylesheet global memungkinkan kita untuk menentukan tampilan dan nuansa umum dari seluruh aplikasi, sementara sebagian pembantu dapat diimpor oleh stylesheet per komponen sesuai kebutuhan.

Sekarang setelah kode umum di setiap cabang telah dieksplorasi secara mendalam, mari alihkan fokus kita ke pendekatan runner/bundel tugas pertama.

Pengaturan Gulp + Browserify

gulpfile.js

Ini menghasilkan gulpfile yang sangat besar, dengan 22 impor dan 150 baris kode. Jadi, untuk singkatnya, saya hanya akan meninjau tugas js , css , server , watch , dan default secara mendetail.

bundel 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)); }

Pendekatan ini agak jelek karena beberapa alasan. Untuk satu hal, tugas dibagi menjadi tiga bagian terpisah. Pertama, Anda membuat objek bundel Browserify b , meneruskan beberapa opsi dan mendefinisikan beberapa event handler. Kemudian Anda memiliki tugas Gulp itu sendiri, yang harus melewati fungsi bernama sebagai panggilan baliknya alih-alih menyejajarkannya (karena b.on('update') menggunakan panggilan balik yang sama). Ini hampir tidak memiliki keanggunan tugas Gulp di mana Anda hanya memasukkan gulp.src dan menyalurkan beberapa perubahan.

Masalah lainnya adalah ini memaksa kami untuk memiliki pendekatan berbeda untuk memuat ulang html , css dan js di browser. Melihat tugas watch Gulp kami:

 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'); }); });

Ketika file HTML diubah, tugas html dijalankan kembali.

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

Pipa terakhir memanggil livereload() jika NODE_ENV bukan production , yang memicu penyegaran di browser.

Logika yang sama digunakan untuk jam tangan CSS. Ketika file CSS diubah, tugas css dijalankan kembali, dan pipa terakhir dalam tugas css memicu livereload() dan menyegarkan browser.

Namun, jam tangan js tidak memanggil tugas js sama sekali. Sebagai gantinya, pengendali acara b.on('update', bundle) menangani pemuatan ulang menggunakan pendekatan yang sama sekali berbeda (yaitu, penggantian modul panas). Inkonsistensi dalam pendekatan ini menjengkelkan, tetapi sayangnya diperlukan untuk memiliki build tambahan . Jika kita secara naif memanggil livereload() di akhir fungsi bundle , ini akan membangun kembali seluruh bundel JS pada setiap perubahan file JS individu. Pendekatan seperti itu jelas tidak berskala. Semakin banyak file JS yang Anda miliki, semakin lama setiap bundel ulang. Tiba-tiba, bundel ulang 500 ms Anda mulai membutuhkan waktu 30 detik, yang benar-benar menghambat pengembangan tangkas.

paket 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())); });

Masalah pertama di sini adalah penyertaan CSS vendor yang rumit. Setiap kali file CSS vendor baru ditambahkan ke proyek, kita harus ingat untuk mengubah gulpfile kita untuk menambahkan elemen ke array gulp.src , daripada menambahkan impor ke tempat yang relevan dalam kode sumber kita yang sebenarnya.

Masalah utama lainnya adalah logika yang berbelit-belit di setiap pipa. Saya harus menambahkan perpustakaan NPM yang disebut gulp-cond hanya untuk mengatur logika kondisional di pipa saya, dan hasil akhirnya tidak terlalu mudah dibaca (kurung tiga di mana-mana!).

Tugas Server

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

Tugas ini sangat mudah. Ini pada dasarnya adalah pembungkus di sekitar permintaan baris perintah nodemon server.js , yang menjalankan server.js di lingkungan node. nodemon digunakan sebagai ganti node sehingga setiap perubahan pada file menyebabkannya restart. Secara default, nodemon akan memulai kembali proses yang berjalan pada setiap perubahan file JS, itulah mengapa penting untuk menyertakan file nodemon.json untuk membatasi cakupannya:

 { "watch": "server.js" }

Mari kita tinjau kode server kita.

server.js

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

Ini menetapkan direktori dasar server dan port berdasarkan lingkungan node, dan membuat instance express.

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

Ini menambahkan middleware connect-livereload (diperlukan untuk pengaturan reload langsung kami) dan middleware statis (diperlukan untuk menangani aset statis kami).

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

Ini hanya rute API sederhana. Jika Anda menavigasi ke localhost:3000/api/sample-route di browser, Anda akan melihat:

 { website: "Toptal", blogPost: true }

Di backend nyata, Anda akan memiliki seluruh folder yang didedikasikan untuk rute API, file terpisah untuk membuat koneksi DB, dan sebagainya. Rute sampel ini hanya disertakan untuk menunjukkan bahwa kita dapat dengan mudah membangun backend di atas frontend yang telah kita siapkan.

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

Ini adalah rute penampung-semua, artinya apa pun url yang Anda ketikkan ke browser, server akan mengembalikan halaman index.html satu-satunya kami. Kemudian React Router bertanggung jawab untuk menyelesaikan rute kita di sisi klien.

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

Ini memberi tahu instance ekspres kami untuk mendengarkan port yang kami tentukan, dan membuka browser di tab baru di URL yang ditentukan.

Sejauh ini satu-satunya hal yang saya tidak suka tentang pengaturan server adalah:

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

Mengingat kita sudah menggunakan gulp-livereload di gulpfile kita, ini membuat dua tempat terpisah di mana livereload harus digunakan.

Sekarang, yang terakhir namun tidak kalah pentingnya:

Tugas Default

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

Ini adalah tugas yang berjalan saat mengetik gulp ke terminal. Satu keanehan adalah kebutuhan untuk menggunakan runSequence untuk menjalankan tugas secara berurutan. Biasanya, serangkaian tugas dijalankan secara paralel, tetapi ini tidak selalu merupakan perilaku yang diinginkan. Misalnya, kita perlu menjalankan tugas clean sebelum html untuk memastikan bahwa folder tujuan kita kosong sebelum memindahkan file ke dalamnya. Ketika gulp 4 dirilis, itu akan mendukung metode gulp.series dan gulp.parallel secara asli, tetapi untuk saat ini kita harus meninggalkan sedikit quirk ini dalam pengaturan kita.

Di luar itu, ini sebenarnya cukup elegan. Seluruh pembuatan dan hosting aplikasi kami dilakukan dalam satu perintah, dan memahami bagian mana pun dari alur kerja semudah memeriksa tugas individu dalam urutan proses. Selain itu, kami dapat memecah seluruh urutan menjadi potongan yang lebih kecil untuk pendekatan yang lebih terperinci dalam membuat dan menghosting aplikasi. Misalnya, kita dapat menyiapkan tugas terpisah yang disebut validate yang menjalankan tugas lint dan test . Atau kita dapat memiliki tugas host yang menjalankan server dan watch . Kemampuan untuk mengatur tugas ini sangat kuat, terutama karena aplikasi Anda berskala dan membutuhkan lebih banyak tugas otomatis.

Pengembangan vs Pembuatan Produksi

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

Menggunakan perpustakaan yargs NPM, kami dapat menyediakan flag baris perintah ke Gulp. Di sini saya menginstruksikan gulpfile untuk mengatur lingkungan node ke produksi jika --prod diteruskan ke gulp di terminal. Variabel PROD kami kemudian digunakan sebagai kondisi untuk membedakan pengembangan dan perilaku produksi di gulpfile kami. Misalnya, salah satu opsi yang kami berikan ke konfigurasi browserify kami adalah:

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

Ini memberitahu browserify untuk tidak menggunakan plugin apa pun dalam mode produksi, dan menggunakan plugin hmr dan watchify di lingkungan lain.

Kondisional PROD ini sangat berguna karena menyelamatkan kita dari keharusan menulis gulpfile terpisah untuk produksi dan pengembangan, yang pada akhirnya akan berisi banyak pengulangan kode. Sebagai gantinya, kita dapat melakukan hal-hal seperti gulp --prod untuk menjalankan tugas default dalam produksi, atau gulp html --prod untuk hanya menjalankan tugas html dalam produksi. Di sisi lain, kita melihat sebelumnya bahwa mengotori saluran pipa Gulp kita dengan pernyataan seperti .pipe(cond(!PROD, livereload())) bukanlah yang paling mudah dibaca. Pada akhirnya, ini masalah preferensi apakah Anda ingin menggunakan pendekatan variabel boolean atau menyiapkan dua file gulp terpisah.

Sekarang mari kita lihat apa yang terjadi ketika kita tetap menggunakan Gulp sebagai task runner kita tetapi mengganti Browserify dengan Webpack.

Pengaturan Gulp + Webpack

Tiba-tiba gulpfile kami hanya sepanjang 99 baris dengan 12 impor, cukup pengurangan dari pengaturan kami sebelumnya! Jika kami memeriksa tugas default:

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

Sekarang penyiapan aplikasi web lengkap kami hanya memerlukan lima tugas, bukan sembilan, peningkatan yang dramatis.

Selain itu, kami telah menghilangkan kebutuhan akan livereload . Tugas watch kami sekarang sederhana:

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

Ini berarti pengamat tegukan kami tidak memicu semua jenis perilaku rebundling. Sebagai bonus tambahan, kita tidak perlu lagi mentransfer index.html dari app ke dist atau build .

Mengembalikan fokus kami ke pengurangan tugas, tugas html , css , js dan fonts kami semuanya telah digantikan oleh satu tugas build :

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

Cukup sederhana. Jalankan tugas clean dan html secara berurutan. Setelah selesai, ambil titik masuk kami, pipa melalui Webpack, meneruskan file webpack.config.js untuk mengonfigurasinya, dan mengirim bundel yang dihasilkan ke baseDir kami (baik dist atau build , tergantung pada node env).

Mari kita lihat file konfigurasi Webpack:

webpack.config.js

Ini adalah file konfigurasi yang cukup besar dan menakutkan, jadi mari kita jelaskan beberapa properti penting yang disetel pada objek module.exports kita.

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

Ini menetapkan jenis peta sumber yang akan digunakan Webpack. Webpack tidak hanya mendukung peta sumber di luar kotak, tetapi juga mendukung beragam opsi peta sumber. Setiap opsi memberikan keseimbangan yang berbeda dari detail peta sumber vs. kecepatan pembangunan kembali (waktu yang dibutuhkan untuk rebundel pada perubahan). Ini berarti kita dapat menggunakan opsi peta sumber “murah” untuk pengembangan guna mencapai pemuatan ulang yang cepat, dan opsi peta sumber yang lebih mahal dalam produksi.

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

Ini adalah titik masuk bundel kami. Perhatikan bahwa sebuah array dilewatkan, artinya dimungkinkan untuk memiliki beberapa titik masuk. Dalam hal ini, kami memiliki titik masuk yang diharapkan app/index.js serta titik masuk webpack-hot-middleware yang digunakan sebagai bagian dari penyiapan pemuatan ulang modul panas kami.

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

Di sinilah bundel yang dikompilasi akan menjadi output. Opsi yang paling membingungkan adalah publicPath . Ini menetapkan url dasar untuk tempat bundel Anda akan di-host di server. Jadi, misalnya, jika publicPath Anda adalah /public/assets , maka bundel akan muncul di bawah /public/assets/bundle.js di server.

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

Ini memberi tahu server folder mana dalam proyek Anda yang akan digunakan sebagai direktori root server.

Jika Anda bingung tentang bagaimana Webpack memetakan bundel yang dibuat dalam proyek Anda ke bundel di server, cukup ingat yang berikut ini:

  • path + filename : Lokasi pasti bundel dalam kode sumber proyek Anda
  • contentBase (sebagai root, / ) + publicPath : Lokasi bundel di server
 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() ],

Ini adalah plugin yang meningkatkan fungsionalitas Webpack dalam beberapa cara. Misalnya, webpack.optimize.UglifyJsPlugin bertanggung jawab untuk minifikasi.

 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]'} ]

Ini adalah loader. Pada dasarnya, mereka melakukan pra-proses file yang dimuat melalui pernyataan require() . Mereka agak mirip dengan pipa Gulp di mana Anda dapat menghubungkan loader bersama-sama.

Mari kita periksa salah satu objek loader kita:

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

Properti test memberi tahu Webpack bahwa pemuat yang diberikan berlaku jika file cocok dengan pola regex yang disediakan, dalam hal ini /\.scss$/ . Properti loader sesuai dengan tindakan yang dilakukan loader. Di sini kita menyatukan style , css , resolve-url dan sass loader, yang dieksekusi dalam urutan terbalik.

Saya harus mengakui bahwa saya tidak menemukan loader3!loader2!loader1 sangat elegan. Lagi pula, kapan Anda harus membaca sesuatu dalam program dari kanan ke kiri? Meskipun demikian, loader adalah fitur webpack yang sangat kuat. Faktanya, loader yang baru saja saya sebutkan memungkinkan kita untuk mengimpor file SASS langsung ke JavaScript kita! Misalnya, kami dapat mengimpor vendor dan stylesheet global kami di file titik masuk kami:

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'));

Demikian pula, dalam komponen Header kami, kami dapat menambahkan import './Header.scss' untuk mengimpor stylesheet terkait komponen. Ini juga berlaku untuk semua komponen kami yang lain.

Menurut pendapat saya, ini hampir dapat dianggap sebagai perubahan revolusioner dalam dunia pengembangan JavaScript. Tidak perlu khawatir tentang bundling CSS, minifikasi, atau peta sumber karena pemuat kami menangani semua ini untuk kami. Bahkan memuat ulang modul panas berfungsi untuk file CSS kami. Kemudian mampu menangani impor JS dan CSS dalam file yang sama membuat pengembangan secara konseptual lebih sederhana: Lebih banyak konsistensi, lebih sedikit pengalihan konteks, dan lebih mudah untuk dipikirkan.

Untuk memberikan ringkasan singkat tentang cara kerja fitur ini: Webpack memasukkan CSS ke dalam bundel JS kami. Bahkan, Webpack dapat melakukan ini untuk gambar dan font juga:

 {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]'}

Pemuat URL menginstruksikan Webpack untuk menyejajarkan gambar dan font kami sebagai url data jika ukurannya di bawah 100 KB, jika tidak, sajikan sebagai file terpisah. Tentu saja, kami juga dapat mengonfigurasi ukuran cutoff ke nilai yang berbeda seperti 10 KB.

Dan itulah konfigurasi Webpack secara singkat. Saya akui ada cukup banyak pengaturan, tetapi manfaat menggunakannya sangat fenomenal. Meskipun Browserify memang memiliki plugin dan transformasi, mereka tidak dapat dibandingkan dengan pemuat Webpack dalam hal fungsionalitas tambahan.

Pengaturan Skrip Webpack + NPM

Dalam pengaturan ini, kami menggunakan skrip npm secara langsung alih-alih mengandalkan gulpfile untuk mengotomatiskan tugas kami.

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" }

Untuk menjalankan pengembangan dan pembuatan produksi, masukkan npm start dan npm run start:prod .

Ini tentu lebih kompak daripada gulpfile kami, mengingat kami telah memotong 99 hingga 150 baris kode menjadi 19 skrip NPM, atau 12 jika kami mengecualikan skrip produksi (sebagian besar hanya mencerminkan skrip pengembangan dengan lingkungan simpul yang disetel ke produksi ). Kekurangannya adalah bahwa perintah ini agak samar dibandingkan dengan rekan tugas Gulp kami, dan tidak cukup ekspresif. Misalnya, tidak ada cara (setidaknya yang saya tahu) untuk memiliki satu skrip npm menjalankan perintah tertentu secara seri dan lainnya secara paralel. Itu salah satu atau yang lain.

Namun, ada keuntungan besar dari pendekatan ini. Dengan menggunakan pustaka NPM seperti mocha langsung dari baris perintah, Anda tidak perlu menginstal pembungkus Gulp yang setara untuk masing-masing (dalam hal ini, gulp-mocha ).

Alih-alih menginstal NPM

  • gulp-eslint
  • gulp-mocha
  • gulp-nodemon
  • dll

Kami menginstal paket-paket berikut:

  • eslint
  • moka
  • nodemon
  • dll

Mengutip postingan Cory House, Why I Left Gulp and Grunt for NPM Scripts :

Saya adalah penggemar berat Gulp. Tetapi pada proyek terakhir saya, saya berakhir dengan ratusan baris di gulpfile saya dan sekitar selusin plugin Gulp. Saya berjuang untuk mengintegrasikan Webpack, Browsersync, hot reload, Mocha, dan banyak lagi menggunakan Gulp. Mengapa? Nah, beberapa plugin memiliki dokumentasi yang tidak memadai untuk kasus penggunaan saya. Beberapa plugin hanya mengekspos bagian dari API yang saya butuhkan. Satu memiliki bug aneh di mana ia hanya akan menonton sejumlah kecil file. Warna lain yang dilucuti saat mengeluarkan ke baris perintah.

Dia menentukan tiga masalah inti dengan Gulp:

  1. Ketergantungan pada pembuat plugin
  2. Frustrasi untuk men-debug
  3. Dokumentasi terputus-putus

Saya cenderung setuju dengan semua ini.

1. Ketergantungan pada Pengarang Plugin

Setiap kali perpustakaan seperti eslint diperbarui, perpustakaan gulp-eslint yang terkait membutuhkan pembaruan yang sesuai. Jika pengelola perpustakaan kehilangan minat, versi tegukan perpustakaan tidak sinkron dengan yang asli. Hal yang sama berlaku ketika perpustakaan baru dibuat. Jika seseorang membuat xyz perpustakaan dan menangkapnya, maka tiba-tiba Anda memerlukan perpustakaan gulp-xyz yang sesuai untuk menggunakannya dalam tugas tegukan Anda.

Dalam arti tertentu, pendekatan ini tidak berskala. Idealnya, kami menginginkan pendekatan seperti Gulp yang dapat menggunakan pustaka asli.

2. Frustrasi untuk Debug

Meskipun perpustakaan seperti gulp-plumber sangat membantu meringankan masalah ini, tetap saja benar bahwa pelaporan kesalahan dalam gulp tidak terlalu membantu. Jika bahkan satu pipa mengeluarkan pengecualian yang tidak tertangani, Anda mendapatkan jejak tumpukan untuk masalah yang tampaknya sama sekali tidak terkait dengan apa yang menyebabkan masalah dalam kode sumber Anda. Ini dapat membuat debugging menjadi mimpi buruk dalam beberapa kasus. Tidak ada jumlah pencarian di Google atau Stack Overflow yang benar-benar dapat membantu Anda jika kesalahannya samar atau cukup menyesatkan.

3. Dokumentasi Terputus

Seringkali saya menemukan bahwa perpustakaan gulp kecil cenderung memiliki dokumentasi yang sangat terbatas. Saya menduga ini karena penulis biasanya membuat perpustakaan terutama untuk penggunaan sendiri. Selain itu, biasanya harus melihat dokumentasi untuk plugin Gulp dan pustaka asli itu sendiri, yang berarti banyak pengalihan konteks dan dua kali lebih banyak membaca yang harus dilakukan.

Kesimpulan

Tampaknya cukup jelas bagi saya bahwa Webpack lebih disukai daripada Browserify dan skrip NPM lebih disukai daripada Gulp, meskipun setiap opsi memiliki kelebihan dan kekurangannya. Gulp tentu saja lebih ekspresif dan nyaman digunakan daripada skrip NPM, tetapi Anda membayar harga untuk semua abstraksi tambahan.

Tidak setiap kombinasi mungkin sempurna untuk aplikasi Anda, tetapi jika Anda ingin menghindari ketergantungan pengembangan yang berlebihan dan pengalaman debug yang membuat frustrasi, Webpack dengan skrip NPM adalah cara yang tepat. Saya harap Anda akan menemukan artikel ini berguna dalam memilih alat yang tepat untuk proyek Anda berikutnya.

Terkait:
  • Pertahankan Kontrol: Panduan untuk Webpack dan React, Pt. 1
  • Gulp Under the Hood: Membangun Alat Otomatisasi Tugas Berbasis Aliran