From 9659b4b555fc99edde717e5607c6a4ee8c46b22b Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Mon, 29 Jul 2019 14:35:09 -0400 Subject: [PATCH] Optional Server Compression (#8066) * Add `compress` option to enable gzip compression in `next start`. * Add compress option, defaulting to true * Disable compression for serverless target * Pin compression dep Co-Authored-By: Joe Haddad * Pin compression types Co-Authored-By: Joe Haddad * lockfile update for locked compression deps * simplify compression middleware application * add test for compression --- packages/next-server/server/config.ts | 1 + packages/next-server/server/next-server.ts | 21 +++++ packages/next/package.json | 2 + test/integration/compression/pages/index.js | 1 + .../compression/test/index.test.js | 30 +++++++ yarn.lock | 86 ++++++++++++++++++- 6 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 test/integration/compression/pages/index.js create mode 100644 test/integration/compression/test/index.test.js diff --git a/packages/next-server/server/config.ts b/packages/next-server/server/config.ts index a41789727f..36e4a25456 100644 --- a/packages/next-server/server/config.ts +++ b/packages/next-server/server/config.ts @@ -18,6 +18,7 @@ const defaultConfig: { [key: string]: any } = { pageExtensions: ['tsx', 'ts', 'jsx', 'js'], target: process.env.__NEXT_BUILDER_EXPERIMENTAL_TARGET || 'server', poweredByHeader: true, + compress: true, onDemandEntries: { maxInactiveAge: 60 * 1000, pagesBufferLength: 2, diff --git a/packages/next-server/server/next-server.ts b/packages/next-server/server/next-server.ts index 683bd8e0f4..84544b68ba 100644 --- a/packages/next-server/server/next-server.ts +++ b/packages/next-server/server/next-server.ts @@ -3,6 +3,7 @@ import { IncomingMessage, ServerResponse } from 'http' import { join, resolve, sep } from 'path' import { parse as parseQs, ParsedUrlQuery } from 'querystring' import { parse as parseUrl, UrlWithParsedQuery } from 'url' +import compression from 'compression' import { BUILD_ID_FILE, @@ -36,6 +37,12 @@ import { isBlockedPage, isInternalUrl } from './utils' type NextConfig = any +type Middleware = ( + req: IncomingMessage, + res: ServerResponse, + next: (err?: Error) => void +) => void + export type ServerConstructor = { /** * Where the Next project is located - @default '.' @@ -72,6 +79,7 @@ export default class Server { documentMiddlewareEnabled: boolean dev?: boolean } + private compression?: Middleware router: Router private dynamicRoutes?: Array<{ page: string; match: RouteMatch }> @@ -101,6 +109,7 @@ export default class Server { publicRuntimeConfig, assetPrefix, generateEtags, + compress, } = this.nextConfig this.buildId = this.readBuildId() @@ -122,6 +131,10 @@ export default class Server { this.renderOpts.runtimeConfig = publicRuntimeConfig } + if (compress && this.nextConfig.target !== 'serverless') { + this.compression = compression() as Middleware + } + // Initialize next/config with the environment configuration envConfig.setConfig({ serverRuntimeConfig, @@ -371,11 +384,19 @@ export default class Server { })) } + private handleCompression(req: IncomingMessage, res: ServerResponse) { + if (this.compression) { + this.compression(req, res, () => {}) + } + } + private async run( req: IncomingMessage, res: ServerResponse, parsedUrl: UrlWithParsedQuery ) { + this.handleCompression(req, res) + try { const fn = this.router.match(req, res, parsedUrl) if (fn) { diff --git a/packages/next/package.json b/packages/next/package.json index 90dd8b87d1..5c6476027a 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -76,6 +76,7 @@ "babel-plugin-transform-async-to-promises": "0.8.10", "babel-plugin-transform-react-remove-prop-types": "0.4.24", "chalk": "2.4.2", + "compression": "1.7.4", "find-up": "4.0.0", "fork-ts-checker-webpack-plugin": "1.3.4", "fresh": "0.5.2", @@ -119,6 +120,7 @@ "@types/babel__template": "7.0.2", "@types/babel__traverse": "7.0.6", "@types/cross-spawn": "6.0.0", + "@types/compression": "0.0.36", "@types/etag": "1.8.0", "@types/find-up": "2.1.1", "@types/fresh": "0.5.0", diff --git a/test/integration/compression/pages/index.js b/test/integration/compression/pages/index.js new file mode 100644 index 0000000000..e45271f6aa --- /dev/null +++ b/test/integration/compression/pages/index.js @@ -0,0 +1 @@ +export default () =>
OK
diff --git a/test/integration/compression/test/index.test.js b/test/integration/compression/test/index.test.js new file mode 100644 index 0000000000..dab6422444 --- /dev/null +++ b/test/integration/compression/test/index.test.js @@ -0,0 +1,30 @@ +/* eslint-env jest */ +/* global jasmine */ +import { join } from 'path' +import { + fetchViaHTTP, + renderViaHTTP, + findPort, + launchApp, + killApp +} from 'next-test-utils' + +const context = {} +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5 + +describe('Compression', () => { + beforeAll(async () => { + context.appPort = await findPort() + context.server = await launchApp(join(__dirname, '../'), context.appPort) + + // pre-build page at the start + await renderViaHTTP(context.appPort, '/') + }) + afterAll(() => killApp(context.server)) + + it('should compress responses by default', async () => { + const res = await fetchViaHTTP(context.appPort, '/') + + expect(res.headers.get('content-encoding')).toMatch(/gzip/) + }) +}) diff --git a/yarn.lock b/yarn.lock index 181394b578..b2d8f73031 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1800,6 +1800,28 @@ dependencies: "@babel/types" "^7.3.0" +"@types/body-parser@*": + version "1.17.0" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c" + integrity sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/compression@0.0.36": + version "0.0.36" + resolved "https://registry.yarnpkg.com/@types/compression/-/compression-0.0.36.tgz#7646602ffbfc43ea48a8bf0b2f1d5e5f9d75c0d0" + integrity sha512-B66iZCIcD2eB2F8e8YDIVtCUKgfiseOR5YOIbmMN2tM57Wu55j1xSdxdSw78aVzsPmbZ6G+hINc+1xe1tt4NBg== + dependencies: + "@types/express" "*" + +"@types/connect@*": + version "3.4.32" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28" + integrity sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg== + dependencies: + "@types/node" "*" + "@types/content-type@1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.3.tgz#3688bd77fc12f935548eef102a4e34c512b03a07" @@ -1829,6 +1851,23 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/express-serve-static-core@*": + version "4.16.7" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.7.tgz#50ba6f8a691c08a3dd9fa7fba25ef3133d298049" + integrity sha512-847KvL8Q1y3TtFLRTXcVakErLJQgdpFSaq+k043xefz9raEf0C7HalpSY7OW5PyjCnY8P7bPW5t/Co9qqp+USg== + dependencies: + "@types/node" "*" + "@types/range-parser" "*" + +"@types/express@*": + version "4.17.0" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.0.tgz#49eaedb209582a86f12ed9b725160f12d04ef287" + integrity sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "*" + "@types/serve-static" "*" + "@types/find-up@2.1.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@types/find-up/-/find-up-2.1.1.tgz#1cd2d240f1ad1f48d32346074724dc3107248a11" @@ -1960,6 +1999,11 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== +"@types/range-parser@*": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" + integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + "@types/react-dom@16.8.4": version "16.8.4" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.8.4.tgz#7fb7ba368857c7aa0f4e4511c4710ca2c5a12a88" @@ -1997,6 +2041,14 @@ "@types/mime" "*" "@types/node" "*" +"@types/serve-static@*": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48" + integrity sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q== + dependencies: + "@types/express-serve-static-core" "*" + "@types/mime" "*" + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -2315,7 +2367,7 @@ abort-controller@3.0.0: dependencies: event-target-shim "^5.0.0" -accepts@~1.3.7: +accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== @@ -3250,6 +3302,11 @@ byte-size@^4.0.3: resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-4.0.4.tgz#29d381709f41aae0d89c631f1c81aec88cd40b23" integrity sha512-82RPeneC6nqCdSwCX2hZUz3JPOvN5at/nTEw/CMf05Smu3Hrpo9Psb7LjN+k+XndNArG1EY8L4+BM3aTM4BCvw== +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -3848,6 +3905,26 @@ compress-commons@^1.2.0: normalize-path "^2.0.0" readable-stream "^2.0.0" +compressible@~2.0.16: + version "2.0.17" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.17.tgz#6e8c108a16ad58384a977f3a482ca20bff2f38c1" + integrity sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw== + dependencies: + mime-db ">= 1.40.0 < 2" + +compression@1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -8781,7 +8858,7 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.40.0: +mime-db@1.40.0, "mime-db@>= 1.40.0 < 2": version "1.40.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== @@ -9480,6 +9557,11 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"