Cara menggunakan webpack untuk membuat aplikasi Anda sekecil mungkin
Salah satu hal pertama yang harus dilakukan saat Anda mengoptimalkan aplikasi adalah membuatnya sekecil mungkin. Berikut cara melakukannya dengan webpack.
Menggunakan mode produksi (khusus webpack 4)
Webpack 4 memperkenalkan flag mode
baru. Anda dapat menetapkan
tanda ini ke 'development'
atau 'production'
untuk memberi tahu webpack bahwa Anda mem-build
aplikasi untuk lingkungan tertentu:
// webpack.config.js
module.exports = {
mode: 'production',
};
Pastikan untuk mengaktifkan mode production
saat mem-build aplikasi untuk produksi.
Tindakan ini akan membuat webpack menerapkan pengoptimalan seperti minifikasi, penghapusan kode khusus pengembangan
di library, dan lainnya.
Bacaan lebih lanjut
Mengaktifkan minifikasi
Minifikasi adalah saat Anda mengompresi kode dengan menghapus spasi tambahan, mempersingkat nama variabel, dan sebagainya. Seperti ini:
// Original code
function map(array, iteratee) {
let index = -1;
const length = array == null ? 0 : array.length;
const result = new Array(length);
while (++index < length) {
result[index] = iteratee(array[index], index, array);
}
return result;
}
↓
// Minified code
function map(n,r){let t=-1;for(const a=null==n?0:n.length,l=Array(a);++t<a;)l[t]=r(n[t],t,n);return l}
Webpack mendukung dua cara untuk melakukan minifikasi kode: minifikasi tingkat paket dan opsi khusus loader. Keduanya harus digunakan secara bersamaan.
Minifikasi tingkat paket
Pengompresian tingkat paket mengompresi seluruh paket setelah kompilasi. Berikut cara kerjanya:
Anda menulis kode seperti ini:
// comments.js import './comments.css'; export function render(data, target) { console.log('Rendered!'); }
Webpack mengompilasi kode tersebut menjadi kira-kira seperti berikut:
// bundle.js (part of) "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony export (immutable) */ __webpack_exports__["render"] = render; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css__ = __webpack_require__(1); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__comments_css__); function render(data, target) { console.log('Rendered!'); }
Pengompresi akan mengompresi kode tersebut menjadi kira-kira seperti berikut:
// minified bundle.js (part of) "use strict";function t(e,n){console.log("Rendered!")} Object.defineProperty(n,"__esModule",{value:!0}),n.render=t;var o=r(1);r.n(o)
Di webpack 4, minifikasi tingkat paket diaktifkan secara otomatis, baik dalam mode produksi maupun tanpa mode produksi. Library ini menggunakan minifier UglifyJS
di balik layar. (Jika Anda perlu menonaktifkan minifikasi, cukup gunakan mode pengembangan
atau teruskan false
ke opsi optimization.minimize
.)
Di webpack 3, Anda perlu menggunakan plugin UglifyJS
secara langsung. Plugin ini dipaketkan dengan webpack; untuk mengaktifkannya, tambahkan ke bagian plugins
konfigurasi:
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin(),
],
};
Opsi khusus loader
Cara kedua untuk melakukan minifikasi kode adalah opsi khusus loader (pengertian loader). Dengan opsi loader, Anda dapat mengompresi hal-hal yang
tidak dapat diminifikasi oleh minifier. Misalnya, saat Anda mengimpor file CSS dengan
css-loader
, file akan dikompilasi menjadi string:
/* comments.css */
.comment {
color: black;
}
// minified bundle.js (part of)
exports=module.exports=__webpack_require__(1)(),
exports.push([module.i,".comment {\r\n color: black;\r\n}",""]);
Pengompresi tidak dapat mengompresi kode ini karena merupakan string. Untuk melakukan minifikasi konten file, kita perlu mengonfigurasi loader untuk melakukannya:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { minimize: true } },
],
},
],
},
};
Bacaan lebih lanjut
- Dokumen UglifyJsPlugin
- Pengecil populer lainnya: Babel Minify, Google Closure Compiler
Tentukan NODE_ENV=production
Cara lain untuk mengurangi ukuran frontend adalah dengan menetapkan NODE_ENV
variabel lingkungan
dalam kode Anda ke nilai production
.
Library membaca variabel NODE_ENV
untuk mendeteksi mode yang harus digunakannya – dalam
mode pengembangan atau produksi. Beberapa library berperilaku berbeda berdasarkan variabel ini. Misalnya, jika NODE_ENV
tidak ditetapkan ke production
, Vue.js akan melakukan pemeriksaan tambahan dan mencetak
peringatan:
// vue/dist/vue.runtime.esm.js
// …
if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
// …
React bekerja dengan cara yang sama – memuat build pengembangan yang menyertakan peringatan:
// react/index.js
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}
// react/cjs/react.development.js
// …
warning$3(
componentClass.getDefaultProps.isReactClassApproved,
'getDefaultProps is only used on classic React.createClass ' +
'definitions. Use a static property named `defaultProps` instead.'
);
// …
Pemeriksaan dan peringatan tersebut biasanya tidak diperlukan dalam produksi, tetapi tetap ada dalam kode dan
meningkatkan ukuran library. Di webpack 4, hapus dengan menambahkan
opsi optimization.nodeEnv: 'production'
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
nodeEnv: 'production',
minimize: true,
},
};
Di webpack 3, gunakan DefinePlugin
sebagai gantinya:
// webpack.config.js (for webpack 3)
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.optimize.UglifyJsPlugin()
]
};
Opsi optimization.nodeEnv
dan DefinePlugin
berfungsi dengan cara yang sama –
keduanya mengganti semua kemunculan process.env.NODE_ENV
dengan nilai yang ditentukan. Dengan konfigurasi dari atas:
Webpack akan mengganti semua kemunculan
process.env.NODE_ENV
dengan"production"
:// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if (process.env.NODE_ENV !== 'production') { warn('props must be strings when using array syntax.'); }
↓
// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if ("production" !== 'production') { warn('props must be strings when using array syntax.'); }
Kemudian, minifier akan menghapus semua cabang
if
tersebut – karena"production" !== 'production'
selalu salah, dan plugin memahami bahwa kode di dalam cabang ini tidak akan pernah dieksekusi:// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if ("production" !== 'production') { warn('props must be strings when using array syntax.'); }
↓
// vue/dist/vue.runtime.esm.js (without minification) if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; }
Bacaan lebih lanjut
- Pengertian “variabel lingkungan”
- Dokumen Webpack tentang:
DefinePlugin
,EnvironmentPlugin
Menggunakan modul ES
Cara berikutnya untuk mengurangi ukuran frontend adalah menggunakan modul ES.
Saat Anda menggunakan modul ES, webpack dapat melakukan tree-shaking. Tree-shaking adalah saat bundler melintasi seluruh hierarki dependensi, memeriksa dependensi yang digunakan, dan menghapus dependensi yang tidak digunakan. Jadi, jika Anda menggunakan sintaksis modul ES, webpack dapat menghapus kode yang tidak digunakan:
Anda menulis file dengan beberapa ekspor, tetapi aplikasi hanya menggunakan salah satunya:
// comments.js export const render = () => { return 'Rendered!'; }; export const commentRestEndpoint = '/rest/comments'; // index.js import { render } from './comments.js'; render();
Webpack memahami bahwa
commentRestEndpoint
tidak digunakan dan tidak menghasilkan titik ekspor terpisah dalam paket:// bundle.js (part that corresponds to comments.js) (function(module, __webpack_exports__, __webpack_require__) { "use strict"; const render = () => { return 'Rendered!'; }; /* harmony export (immutable) */ __webpack_exports__["a"] = render; const commentRestEndpoint = '/rest/comments'; /* unused harmony export commentRestEndpoint */ })
Pengoptimal akan menghapus variabel yang tidak digunakan:
// bundle.js (part that corresponds to comments.js) (function(n,e){"use strict";var r=function(){return"Rendered!"};e.b=r})
Hal ini bahkan berfungsi dengan library jika ditulis dengan modul ES.
Namun, Anda tidak perlu menggunakan minifier bawaan webpack (UglifyJsPlugin
) secara akurat.
Setiap minifier yang mendukung penghapusan kode mati
(misalnya plugin Babel Minify
atau plugin Google Closure Compiler)
akan berhasil.
Bacaan lebih lanjut
Dokumen Webpack tentang tree shaking
Mengoptimalkan gambar
Gambar mencakup lebih dari
setengah ukuran halaman. Meskipun tidak
sesentral JavaScript (misalnya, tidak memblokir rendering), keduanya tetap menghabiskan sebagian besar
bandwidth. Gunakan url-loader
, svg-url-loader
, dan image-webpack-loader
untuk mengoptimalkannya di
webpack.
url-loader
menyisipkan file statis kecil ke dalam
aplikasi. Tanpa konfigurasi, file ini akan mengambil file yang diteruskan, menempatkannya di samping paket yang dikompilasi, dan menampilkan
URL file tersebut. Namun, jika kita menentukan opsi limit
, opsi ini akan mengenkode file yang lebih kecil dari
batas ini sebagai URL data Base64 dan menampilkan URL ini. Tindakan ini akan
menyisipkan gambar ke dalam kode JavaScript dan menyimpan permintaan HTTP:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif)$/,
loader: 'url-loader',
options: {
// Inline files smaller than 10 kB (10240 bytes)
limit: 10 * 1024,
},
},
],
}
};
// index.js
import imageUrl from './image.png';
// → If image.png is smaller than 10 kB, `imageUrl` will include
// the encoded image: 'data:image/png;base64,iVBORw0KGg…'
// → If image.png is larger than 10 kB, the loader will create a new file,
// and `imageUrl` will include its url: `/2fcd56a1920be.png`
svg-url-loader
berfungsi seperti url-loader
,
tetapi file dienkode dengan encoding URL, bukan Base64. Hal ini berguna untuk gambar SVG – karena file SVG hanyalah teks biasa, encoding ini
lebih efektif ukurannya.
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
loader: "svg-url-loader",
options: {
limit: 10 * 1024,
noquotes: true
}
}
]
}
};
image-webpack-loader
mengompresi gambar yang
diprosesnya. Library ini mendukung gambar JPG, PNG, GIF, dan SVG, jadi kita akan menggunakannya untuk semua jenis ini.
Loader ini tidak menyematkan gambar ke dalam aplikasi, sehingga harus berfungsi bersama dengan url-loader
dan
svg-url-loader
. Untuk menghindari penyalinan dan penempelan ke dalam kedua aturan (satu untuk gambar JPG/PNG/GIF, dan satu lagi untuk gambar SVG), kita akan menyertakan loader ini sebagai aturan terpisah dengan enforce: 'pre'
:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/,
loader: 'image-webpack-loader',
// This will apply the loader before the other ones
enforce: 'pre'
}
]
}
};
Setelan default loader sudah siap digunakan, tetapi jika Anda ingin mengonfigurasinya lebih lanjut, lihat opsi plugin. Untuk memilih opsi yang akan ditentukan, lihat panduan tentang pengoptimalan gambar yang bagus dari Addy Osmani.
Bacaan lebih lanjut
- "Apa kegunaan encoding base64?"
- Panduan pengoptimalan gambar dari Addy Osmani
Mengoptimalkan dependensi
Lebih dari setengah ukuran JavaScript rata-rata berasal dari dependensi, dan sebagian dari ukuran tersebut mungkin tidak diperlukan.
Misalnya, Lodash (mulai v4.17.4) menambahkan kode yang diminifikasi sebesar 72 KB ke paket. Namun, jika Anda hanya menggunakan, seperti, 20 metodenya, maka sekitar 65 KB kode yang diminifikasi tidak akan melakukan apa pun.
Contoh lainnya adalah Moment.js. Versi 2.19.1-nya menggunakan kode yang diminifikasi sebesar 223 KB, yang sangat besar – ukuran rata-rata JavaScript di halaman adalah 452 KB pada Oktober 2017. Namun, 170 KB dari ukuran tersebut adalah file lokalisasi. Jika Anda tidak menggunakan Moment.js dengan beberapa bahasa, file ini akan membengkak paket tanpa tujuan.
Semua dependensi ini dapat dioptimalkan dengan mudah. Kami telah mengumpulkan pendekatan pengoptimalan di repo GitHub – lihat.
Mengaktifkan penyambungan modul untuk modul ES (alias pengangkatan cakupan)
Saat Anda mem-build paket, webpack menggabungkan setiap modul ke dalam fungsi:
// index.js
import {render} from './comments.js';
render();
// comments.js
export function render(data, target) {
console.log('Rendered!');
}
↓
// bundle.js (part of)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
var __WEBPACK_IMPORTED_MODULE_0__comments_js__ = __webpack_require__(1);
Object(__WEBPACK_IMPORTED_MODULE_0__comments_js__["a" /* render */])();
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_exports__["a"] = render;
function render(data, target) {
console.log('Rendered!');
}
})
Sebelumnya, hal ini diperlukan untuk mengisolasi modul CommonJS/AMD satu sama lain. Namun, hal ini menambahkan overhead ukuran dan performa untuk setiap modul.
Webpack 2 memperkenalkan dukungan untuk modul ES yang, tidak seperti modul CommonJS dan AMD, dapat dipaketkan tanpa menggabungkan setiap modul dengan fungsi. Dan webpack 3 memungkinkan penggabungan tersebut – dengan penggabungan modul. Berikut adalah fungsi penggabungan modul:
// index.js
import {render} from './comments.js';
render();
// comments.js
export function render(data, target) {
console.log('Rendered!');
}
↓
// Unlike the previous snippet, this bundle has only one module
// which includes the code from both files
// bundle.js (part of; compiled with ModuleConcatenationPlugin)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
// CONCATENATED MODULE: ./comments.js
function render(data, target) {
console.log('Rendered!');
}
// CONCATENATED MODULE: ./index.js
render();
})
Lihat perbedaannya? Dalam paket biasa, modul 0 memerlukan render
dari modul 1. Dengan
koncatenasi modul, require
hanya diganti dengan fungsi yang diperlukan, dan modul 1
akan dihapus. Paket memiliki lebih sedikit modul – dan lebih sedikit overhead modul.
Untuk mengaktifkan perilaku ini, di webpack 4, aktifkan opsi optimization.concatenateModules
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
concatenateModules: true
}
};
Di webpack 3, gunakan ModuleConcatenationPlugin
:
// webpack.config.js (for webpack 3)
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
};
Bacaan lebih lanjut
- Dokumen Webpack untuk ModuleConcatenationPlugin
- “Pengantar singkat tentang pengangkatan cakupan”
- Deskripsi mendetail tentang fungsi plugin ini
Gunakan externals
jika Anda memiliki kode webpack dan non-webpack
Anda mungkin memiliki project besar dengan beberapa kode yang dikompilasi dengan webpack, dan beberapa kode tidak dikompilasi. Seperti situs hosting video, tempat widget pemutar mungkin dibuat dengan webpack, dan halaman di sekitarnya mungkin tidak:
Jika kedua bagian kode memiliki dependensi yang sama, Anda dapat membagikannya untuk menghindari download kodenya
beberapa kali. Hal ini dilakukan dengan opsi externals
webpack – opsi ini mengganti modul dengan variabel atau impor eksternal lainnya.
Jika dependensi tersedia di window
Jika kode non-webpack Anda mengandalkan dependensi yang tersedia sebagai variabel di window
, aliaskan
nama dependensi ke nama variabel:
// webpack.config.js
module.exports = {
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
};
Dengan konfigurasi ini, webpack tidak akan memaketkan paket react
dan react-dom
. Sebagai gantinya, kode tersebut akan diganti dengan kode seperti ini:
// bundle.js (part of)
(function(module, exports) {
// A module that exports `window.React`. Without `externals`,
// this module would include the whole React bundle
module.exports = React;
}),
(function(module, exports) {
// A module that exports `window.ReactDOM`. Without `externals`,
// this module would include the whole ReactDOM bundle
module.exports = ReactDOM;
})
Jika dependensi dimuat sebagai paket AMD
Jika kode non-webpack Anda tidak mengekspos dependensi ke window
, hal-hal akan menjadi lebih rumit.
Namun, Anda masih dapat menghindari pemuatan kode yang sama dua kali jika kode non-webpack menggunakan dependensi ini sebagai paket AMD.
Untuk melakukannya, kompilasi kode webpack sebagai paket AMD dan alias modul ke URL library:
// webpack.config.js
module.exports = {
output: {
libraryTarget: 'amd'
},
externals: {
'react': {
amd: '/libraries/react.min.js'
},
'react-dom': {
amd: '/libraries/react-dom.min.js'
}
}
};
Webpack akan menggabungkan paket ke dalam define()
dan membuatnya bergantung pada URL berikut:
// bundle.js (beginning)
define(["/libraries/react.min.js", "/libraries/react-dom.min.js"], function () { … });
Jika kode non-webpack menggunakan URL yang sama untuk memuat dependensinya, file ini hanya akan dimuat satu kali – permintaan tambahan akan menggunakan cache loader.
Bacaan lebih lanjut
- Dokumen Webpack di
externals
Merangkum
- Mengaktifkan mode produksi jika Anda menggunakan webpack 4
- Minimalkan kode Anda dengan opsi loader dan minifier tingkat paket
- Hapus kode khusus pengembangan dengan mengganti
NODE_ENV
denganproduction
- Menggunakan modul ES untuk mengaktifkan tree shaking
- Mengompresi gambar
- Menerapkan pengoptimalan khusus dependensi
- Mengaktifkan penyambungan modul
- Gunakan
externals
jika sesuai untuk Anda