ΠΠ°ΠΊ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΡΠΆΠ°ΡΠΈΠ΅ ΠΎΡΠ²Π΅ΡΠΎΠ² (gzip, Brotli)?
ΠΠΎΠ»Π½ΡΠΉ ΠΎΡΠ²Π΅Ρ
ΠΡΠΎΡΠ΅ΡΡ ΡΠΎΠ³Π»Π°ΡΠΎΠ²Π°Π½ΠΈΡ ΡΠΆΠ°ΡΠΈΡ:
ΠΡΠ°ΡΠ·Π΅Ρ Π‘Π΅ΡΠ²Π΅Ρ
| |
| -- GET /app.js |
| Accept-Encoding: gzip, deflate, br --> |
| |
| [Π‘Π΅ΡΠ²Π΅Ρ Π²ΡΠ±ΠΈΡΠ°Π΅Ρ Π»ΡΡΡΠΈΠΉ Π°Π»Π³ΠΎΡΠΈΡΠΌ] |
| |
| <-- 200 OK |
| Content-Encoding: br |
| Content-Type: application/javascript |
| [ΡΠΆΠ°ΡΠΎΠ΅ ΡΠ΅Π»ΠΎ ΠΎΡΠ²Π΅ΡΠ°] |
| |
| [ΠΡΠ°ΡΠ·Π΅Ρ ΡΠ°ΡΠΏΠ°ΠΊΠΎΠ²ΡΠ²Π°Π΅Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ] |
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° Π½Π° nginx:
# nginx.conf
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6; # 1-9, Π±Π°Π»Π°Π½Ρ ΡΠΊΠΎΡΠΎΡΡΠΈ ΠΈ ΡΡΠ΅ΠΏΠ΅Π½ΠΈ ΡΠΆΠ°ΡΠΈΡ
gzip_min_length 256; # Π½Π΅ ΡΠΆΠΈΠΌΠ°ΡΡ ΠΌΠ΅Π»ΠΊΠΈΠ΅ ΠΎΡΠ²Π΅ΡΡ
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml
font/woff2;
# Brotli (Π½ΡΠΆΠ΅Π½ ΠΌΠΎΠ΄ΡΠ»Ρ ngx_brotli)
brotli on;
brotli_comp_level 6; # 1-11, Π΄Π»Ρ on-the-fly ΠΎΠ±ΡΡΠ½ΠΎ 4-6
brotli_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml;
ΠΡΠ΅Π΄Π²Π°ΡΠΈΡΠ΅Π»ΡΠ½ΠΎΠ΅ ΡΠΆΠ°ΡΠΈΠ΅ (ΡΡΠ°ΡΠΈΡΠ΅ΡΠΊΠΈΠ΅ ΡΠ°ΠΉΠ»Ρ):
# Π‘ΠΆΠ°ΡΠΈΠ΅ ΠΏΡΠΈ ΡΠ±ΠΎΡΠΊΠ΅ -- ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΡΠΉ ΡΡΠΎΠ²Π΅Π½Ρ, Π½Π΅ ΡΡΠ°ΡΠΈΡ CPU Π½Π° Π»Π΅ΡΡ
# gzip
gzip -k -9 dist/app.js # ΡΠΎΠ·Π΄Π°ΡΡ app.js.gz ΡΡΠ΄ΠΎΠΌ Ρ ΠΎΡΠΈΠ³ΠΈΠ½Π°Π»ΠΎΠΌ
# Brotli (ΡΡΠΎΠ²Π΅Π½Ρ 11 -- ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΡΠΉ, ΠΌΠ΅Π΄Π»Π΅Π½Π½ΡΠΉ, Π½ΠΎ Π»ΡΡΡΠ΅Π΅ ΡΠΆΠ°ΡΠΈΠ΅)
brotli -k -q 11 dist/app.js # ΡΠΎΠ·Π΄Π°ΡΡ app.js.br
# nginx ΡΠ°Π·Π΄Π°ΡΡ ΠΏΡΠ΅Π΄ΡΠΆΠ°ΡΡΠ΅ ΡΠ°ΠΉΠ»Ρ
gzip_static on; # ΠΈΡΠ΅Ρ .gz ΡΡΠ΄ΠΎΠΌ Ρ ΡΠ°ΠΉΠ»ΠΎΠΌ
brotli_static on; # ΠΈΡΠ΅Ρ .br ΡΡΠ΄ΠΎΠΌ Ρ ΡΠ°ΠΉΠ»ΠΎΠΌ
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° Π² Express:
import express from 'express';
import compression from 'compression';
const app = express();
// ΠΠ°Π·ΠΎΠ²ΠΎΠ΅ ΡΠΆΠ°ΡΠΈΠ΅ (gzip ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ)
app.use(compression({
level: 6,
threshold: 1024, // Π½Π΅ ΡΠΆΠΈΠΌΠ°ΡΡ ΠΎΡΠ²Π΅ΡΡ ΠΌΠ΅Π½ΡΡΠ΅ 1 KB
filter: (req, res) => {
// ΠΠ΅ ΡΠΆΠΈΠΌΠ°ΡΡ ΡΠΆΠ΅ ΡΠΆΠ°ΡΡΠ΅ ΡΠΎΡΠΌΠ°ΡΡ
const contentType = res.getHeader('Content-Type') || '';
if (/image| video |audio/.test(contentType)) return false;
return compression.filter(req, res);
},
}));
// ΠΠ»Ρ Brotli Π² Express Π½ΡΠΆΠ΅Π½ shrink-ray-current ΠΈΠ»ΠΈ iltorb
import shrinkRay from 'shrink-ray-current';
app.use(shrinkRay({
brotli: { quality: 4 }, // Π½Π° Π»Π΅ΡΡ -- Π½ΠΈΠΆΠ΅ ΡΡΠΎΠ²Π΅Π½Ρ Π΄Π»Ρ ΡΠΊΠΎΡΠΎΡΡΠΈ
}));
Π‘ΡΠ°Π²Π½Π΅Π½ΠΈΠ΅ Π°Π»Π³ΠΎΡΠΈΡΠΌΠΎΠ²:
Π€Π°ΠΉΠ»: React bundle (500 KB)
-----------------------------------------
ΠΠ΅Π· ΡΠΆΠ°ΡΠΈΡ: 500 KB (100%)
gzip -6: 145 KB (29%) Π±ΡΡΡΡΠΎ
gzip -9: 140 KB (28%) ΡΡΡΡ ΠΌΠ΅Π΄Π»Π΅Π½Π½Π΅Π΅
Brotli -4: 130 KB (26%) ΡΡΠ°Π²Π½ΠΈΠΌΠΎ Ρ gzip ΠΏΠΎ ΡΠΊΠΎΡΠΎΡΡΠΈ
Brotli -11: 115 KB (23%) ΠΌΠ΅Π΄Π»Π΅Π½Π½ΠΎ, ΡΠΎΠ»ΡΠΊΠΎ Π΄Π»Ρ ΡΡΠ°ΡΠΈΠΊΠΈ
π Π Π΅Π°Π»ΡΠ½ΡΠ΅ ΠΊΠ΅ΠΉΡΡ
ΠΡΠΎΠ²Π΅ΡΠΊΠ° ΡΠΆΠ°ΡΠΈΡ Π² ΠΊΠΎΠ΄Π΅:
// ΠΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ, ΡΠΆΠΈΠΌΠ°Π΅ΡΡΡ Π»ΠΈ ΡΠ΅ΡΡΡΡ
const resources = performance.getEntriesByType('resource');
for (const res of resources) {
if (res.decodedBodySize > 0 && res.transferSize > 0) {
const ratio = 1 - res.encodedBodySize / res.decodedBodySize;
const isCompressed = ratio > 0.1; // ΡΠΆΠ°ΡΠΈΠ΅ Π±ΠΎΠ»ΡΡΠ΅ 10%
if (!isCompressed && res.decodedBodySize > 10_000) {
console.warn(
`ΠΠ΅ΡΠΆΠ°ΡΡΠΉ ΡΠ΅ΡΡΡΡ (${(res.decodedBodySize / 1024).toFixed(0)} KB): ${res.name}`
);
}
}
}
Webpack-ΠΏΠ»Π°Π³ΠΈΠ½ Π΄Π»Ρ ΠΏΡΠ΅Π΄ΡΠΆΠ°ΡΠΈΡ:
// webpack.config.js
import CompressionPlugin from 'compression-webpack-plugin';
import zlib from 'zlib';
export default {
plugins: [
// gzip
new CompressionPlugin({
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\.(js| css |html| svg |json)$/,
threshold: 1024,
minRatio: 0.8,
}),
// Brotli
new CompressionPlugin({
filename: '[path][base].br',
algorithm: 'brotliCompress',
test: /\.(js| css |html| svg |json)$/,
compressionOptions: { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 } },
threshold: 1024,
minRatio: 0.8,
}),
],
};
Π Π΅Π·ΡΠΌΠ΅
Π‘ΠΆΠ°ΡΠΈΠ΅ -- ΠΎΠ΄Π½Π° ΠΈΠ· ΡΠ°ΠΌΡΡ ΠΏΡΠΎΡΡΡΡ ΠΈ ΡΡΡΠ΅ΠΊΡΠΈΠ²Π½ΡΡ ΠΎΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΠΉ. Brotli ΡΠΆΠΈΠΌΠ°Π΅Ρ Π»ΡΡΡΠ΅ gzip Π½Π° 15-25% Π΄Π»Ρ ΡΠ΅ΠΊΡΡΠ°. ΠΠ»Ρ ΡΡΠ°ΡΠΈΡΠ΅ΡΠΊΠΈΡ ΡΠ°ΠΉΠ»ΠΎΠ² ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠΉ ΠΏΡΠ΅Π΄Π²Π°ΡΠΈΡΠ΅Π»ΡΠ½ΠΎΠ΅ ΡΠΆΠ°ΡΠΈΠ΅ Π½Π° ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΠΎΠΌ ΡΡΠΎΠ²Π½Π΅ (Brotli 11), Π΄Π»Ρ Π΄ΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΠΈΡ -- ΡΠΆΠ°ΡΠΈΠ΅ Π½Π° Π»Π΅ΡΡ Ρ ΡΠΌΠ΅ΡΠ΅Π½Π½ΡΠΌ ΡΡΠΎΠ²Π½Π΅ΠΌ (Brotli 4-6). ΠΠ΅ ΡΠΆΠΈΠΌΠ°ΠΉ ΠΈΠ·ΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΡ, Π²ΠΈΠ΄Π΅ΠΎ ΠΈ Π΄ΡΡΠ³ΠΈΠ΅ Π±ΠΈΠ½Π°ΡΠ½ΡΠ΅ ΡΠΎΡΠΌΠ°ΡΡ -- ΠΎΠ½ΠΈ ΡΠΆΠ΅ ΡΠΆΠ°ΡΡ.