From e1217493b3966cabbb4f738f545723a569afaee2 Mon Sep 17 00:00:00 2001 From: Michael Stramel Date: Wed, 3 Jun 2020 15:18:01 -0500 Subject: [PATCH] Enable compression on dev server responses (#419) --- package-lock.json | 14 ++++++++++++++ package.json | 2 ++ src/commands/dev.ts | 38 ++++++++++++++++++++++++++++++++++---- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5f6bf8979..86450ad5c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2159,6 +2159,12 @@ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "@types/compressible": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/compressible/-/compressible-2.0.0.tgz", + "integrity": "sha512-ioFCyNkga3vWcvLowx1qO+/4D0jw8JYpjoIC2b6NzQ7zk7A03Sw/KEsDuRtKqtAo2/GOhbyWQYuPVsg8h/ACcA==", + "dev": true + }, "@types/es-module-lexer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@types/es-module-lexer/-/es-module-lexer-0.3.0.tgz", @@ -3884,6 +3890,14 @@ "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", "dev": true }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", diff --git a/package.json b/package.json index 706278a25f..45d1bb8cfc 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "cachedir": "^2.3.0", "chalk": "^4.0.0", "chokidar": "^3.4.0", + "compressible": "^2.0.18", "cosmiconfig": "^6.0.0", "css-modules-loader-core": "^1.1.0", "deepmerge": "^4.2.2", @@ -100,6 +101,7 @@ "@pika/plugin-ts-standard-pkg": "^0.9.1", "@types/babel__traverse": "^7.0.7", "@types/cacache": "^12.0.1", + "@types/compressible": "^2.0.0", "@types/es-module-lexer": "^0.3.0", "@types/http-proxy": "^1.17.4", "@types/mkdirp": "^1.0.0", diff --git a/src/commands/dev.ts b/src/commands/dev.ts index 29c8f19a69..60fd393a42 100644 --- a/src/commands/dev.ts +++ b/src/commands/dev.ts @@ -27,6 +27,8 @@ import cacache from 'cacache'; import chalk from 'chalk'; import chokidar from 'chokidar'; +import isCompressible from 'compressible' +import detectPort from 'detect-port'; import etag from 'etag'; import {EventEmitter} from 'events'; import execa from 'execa'; @@ -37,9 +39,10 @@ import mime from 'mime-types'; import npmRunPath from 'npm-run-path'; import os from 'os'; import path from 'path'; -import url from 'url'; import onProcessExit from 'signal-exit'; -import detectPort from 'detect-port'; +import stream from 'stream' +import url from 'url'; +import zlib from 'zlib'; import {BuildScript, SnowpackPluginBuildResult} from '../config'; import {EsmHmrEngine} from '../hmr-server-engine'; import {scanCodeImportsExports, transformEsmImports} from '../rewrite-imports'; @@ -101,19 +104,46 @@ const sendFile = ( ext = '.html', ) => { const ETag = etag(body); - const headers = { + const headers: Record = { 'Content-Type': mime.contentType(ext) || 'application/octet-stream', 'Access-Control-Allow-Origin': '*', ETag, + Vary: 'Accept-Encoding' }; if (req.headers['if-none-match'] === ETag) { res.writeHead(304, headers); + res.end() + return + } + + let acceptEncoding = (req.headers['accept-encoding'] as string) || '' + if (req.headers["cache-control"]?.includes('no-transform') || ['HEAD', 'OPTIONS'].includes(req.method!) || !isCompressible(mime.contentType(ext))) { + acceptEncoding = '' + } + + function onError(err) { + if (err) { + res.end() + console.error( + chalk.red( + ` ✘ An error occurred while compressing ${chalk.bold(req.url)}`, + ), + err + ); + } + } + + const raw = stream.Readable.from([body]) + if (/\bgzip\b/.test(acceptEncoding)) { + headers['Content-Encoding'] = 'gzip' + res.writeHead(200, headers) + stream.pipeline(raw, zlib.createGzip(), res, onError) } else { res.writeHead(200, headers); res.write(body, getEncodingType(ext)); + res.end() } - res.end(); }; const sendError = (res, status) => {