From 8aa2d52fb9c2922175621bbfe076a327326cf369 Mon Sep 17 00:00:00 2001 From: Daniel Balogh Date: Fri, 8 May 2020 23:08:59 +0300 Subject: [PATCH 1/2] Added support for public/static folder cache control --- package-lock.json | 180 ++++++++++++------ packages/s3-static-assets/package-lock.json | 5 + packages/s3-static-assets/package.json | 3 +- packages/s3-static-assets/src/index.ts | 31 ++- .../s3-static-assets/src/lib/constants.ts | 3 + .../src/lib/getPublicAssetCacheControl.ts | 43 +++++ .../.next/serverless/pages-manifest.json | 4 + .../.next/serverless/pages/todos/terms.html | 0 .../pages/todos/terms/[section].html | 0 .../.next/static/a_test_build_id/css/one.css | 0 .../.next/static/a_test_build_id/two.js | 0 .../.next/static/chunks/chunk1.js | 0 .../.next/static/runtime/runtime1.js | 0 .../fixtures/app-with-images/public/1x1.png | Bin 0 -> 88 bytes .../fixtures/app-with-images/static/1x1.png | Bin 0 -> 88 bytes .../tests/upload-assets.test.ts | 50 ++++- packages/serverless-component/README.md | 71 +++++-- .../serverless-component/package-lock.json | 8 +- packages/serverless-component/serverless.js | 7 +- 19 files changed, 312 insertions(+), 93 deletions(-) create mode 100644 packages/s3-static-assets/src/lib/getPublicAssetCacheControl.ts create mode 100644 packages/s3-static-assets/tests/fixtures/app-with-images/.next/serverless/pages-manifest.json create mode 100644 packages/s3-static-assets/tests/fixtures/app-with-images/.next/serverless/pages/todos/terms.html create mode 100644 packages/s3-static-assets/tests/fixtures/app-with-images/.next/serverless/pages/todos/terms/[section].html create mode 100644 packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/a_test_build_id/css/one.css create mode 100644 packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/a_test_build_id/two.js create mode 100644 packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/chunks/chunk1.js create mode 100644 packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/runtime/runtime1.js create mode 100644 packages/s3-static-assets/tests/fixtures/app-with-images/public/1x1.png create mode 100644 packages/s3-static-assets/tests/fixtures/app-with-images/static/1x1.png diff --git a/package-lock.json b/package-lock.json index 7f7deb0fea..7c63c15984 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3683,59 +3683,69 @@ "dependencies": { "@types/aws-lambda": { "version": "8.10.50", - "resolved": false + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.50.tgz", + "integrity": "sha512-RDzmQ5mO1f0BViKiuOudENZmoCACEa461nTRVtxhsAiEqGCgwdhCYN0aFgk42X5+ELAiqJKbv2mK0LkopYRYQg==" }, "@types/execa": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/@types/execa/-/execa-2.0.0.tgz", + "integrity": "sha512-aBnkJ0r3khaZkHzu9pDZeWXrDg1N/ZtDGRQkK+KIqNVvvTvW+URXMUHQQCQMYdb2GPrcwu9Fq6l9iiT+pirIbg==", "requires": { "execa": "*" } }, "@types/fs-extra": { "version": "8.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-UoOfVEzAUpeSPmjm7h1uk5MH6KZma2z2O7a75onTGjnNvAvMVrPzPL/vBbT65iIGHWj6rokwfmYcmxmlSf2uwg==", "requires": { "@types/node": "*" } }, "@types/node": { "version": "13.13.0", - "resolved": false + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.0.tgz", + "integrity": "sha512-WE4IOAC6r/yBZss1oQGM5zs2D7RuKR6Q+w+X2SouPofnWn+LbCqClRyhO3ZE7Ix8nmFgo/oVuuE01cJT2XB13A==" }, "@types/path-to-regexp": { "version": "1.7.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/@types/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-8a+dnJ21+K2AqnXtQk22ZcSNbNQ=", "requires": { "path-to-regexp": "*" } }, "ansi-styles": { "version": "3.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" } }, "at-least-node": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true }, "big.js": { "version": "5.2.2", - "resolved": false + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, "braces": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "requires": { "fill-range": "^7.0.1" } }, "chalk": { "version": "2.4.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -3744,33 +3754,39 @@ }, "color-convert": { "version": "1.9.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { "color-name": "1.1.3" } }, "color-name": { "version": "1.1.3", - "resolved": false + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "core-util-is": { "version": "1.0.2", - "resolved": false + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "emojis-list": { "version": "3.0.0", - "resolved": false + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" }, "end-of-stream": { "version": "1.4.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "requires": { "once": "^1.4.0" } }, "enhanced-resolve": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", + "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", "requires": { "graceful-fs": "^4.1.2", "memory-fs": "^0.5.0", @@ -3779,18 +3795,21 @@ }, "errno": { "version": "0.1.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "requires": { "prr": "~1.0.1" } }, "escape-string-regexp": { "version": "1.0.5", - "resolved": false + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "execa": { "version": "4.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", + "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "requires": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -3843,14 +3862,16 @@ }, "fill-range": { "version": "7.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "requires": { "to-regex-range": "^5.0.1" } }, "fs-extra": { "version": "9.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", + "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", "dev": true, "requires": { "at-least-node": "^1.0.0", @@ -3861,53 +3882,64 @@ }, "get-stream": { "version": "5.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "requires": { "pump": "^3.0.0" } }, "graceful-fs": { "version": "4.2.3", - "resolved": false + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, "has-flag": { "version": "3.0.0", - "resolved": false + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "human-signals": { "version": "1.1.1", - "resolved": false + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" }, "inherits": { "version": "2.0.4", - "resolved": false + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-number": { "version": "7.0.0", - "resolved": false + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-stream": { "version": "2.0.0", - "resolved": false + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" }, "isarray": { "version": "1.0.0", - "resolved": false + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", - "resolved": false + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "json5": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "requires": { "minimist": "^1.2.0" } }, "jsonfile": { "version": "6.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", "dev": true, "requires": { "graceful-fs": "^4.1.6", @@ -3916,7 +3948,8 @@ }, "loader-utils": { "version": "1.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -3925,7 +3958,8 @@ }, "memory-fs": { "version": "0.5.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", "requires": { "errno": "^0.1.3", "readable-stream": "^2.0.1" @@ -3933,11 +3967,13 @@ }, "merge-stream": { "version": "2.0.0", - "resolved": false + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "micromatch": { "version": "4.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "requires": { "braces": "^3.0.1", "picomatch": "^2.0.5" @@ -3945,15 +3981,18 @@ }, "mimic-fn": { "version": "2.1.0", - "resolved": false + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "minimist": { "version": "1.2.5", - "resolved": false + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "npm-run-path": { "version": "4.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "requires": { "path-key": "^3.0.0" }, @@ -3967,37 +4006,44 @@ }, "once": { "version": "1.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "onetime": { "version": "5.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "requires": { "mimic-fn": "^2.1.0" } }, "path-to-regexp": { "version": "6.1.0", - "resolved": false + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.1.0.tgz", + "integrity": "sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==" }, "picomatch": { "version": "2.2.2", - "resolved": false + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, "process-nextick-args": { "version": "2.0.1", - "resolved": false + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "prr": { "version": "1.0.1", - "resolved": false + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, "pump": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -4005,7 +4051,8 @@ }, "readable-stream": { "version": "2.3.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4018,48 +4065,57 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": false + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "semver": { "version": "6.3.0", - "resolved": false + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "signal-exit": { "version": "3.0.3", - "resolved": false + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } }, "strip-final-newline": { "version": "2.0.0", - "resolved": false + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" }, "supports-color": { "version": "5.5.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } }, "tapable": { "version": "1.1.3", - "resolved": false + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" }, "to-regex-range": { "version": "5.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "requires": { "is-number": "^7.0.0" } }, "ts-loader": { "version": "7.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.0.tgz", + "integrity": "sha512-qq9iPK235Xf/uPS1I72CIwB8qpbW0rys3entnXO+rIo2km50lMmeHKCNn1m047W6Sk4kceF6n5NWEnq/V8xdYQ==", "requires": { "chalk": "^2.3.0", "enhanced-resolve": "^4.0.0", @@ -4070,16 +4126,19 @@ }, "universalify": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", "dev": true }, "util-deprecate": { "version": "1.0.2", - "resolved": false + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "wrappy": { "version": "1.0.2", - "resolved": false + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } }, @@ -15469,7 +15528,8 @@ "dependencies": { "@types/aws-lambda": { "version": "8.10.50", - "resolved": false + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.50.tgz", + "integrity": "sha512-RDzmQ5mO1f0BViKiuOudENZmoCACEa461nTRVtxhsAiEqGCgwdhCYN0aFgk42X5+ELAiqJKbv2mK0LkopYRYQg==" } } }, diff --git a/packages/s3-static-assets/package-lock.json b/packages/s3-static-assets/package-lock.json index 8882b2ed2b..59cd347290 100644 --- a/packages/s3-static-assets/package-lock.json +++ b/packages/s3-static-assets/package-lock.json @@ -146,6 +146,11 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "regex-parser": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.10.tgz", + "integrity": "sha512-8t6074A68gHfU8Neftl0Le6KTDwfGAj7IyjPIMSfikI2wJUTHDMaIq42bUsfVnj8mhx0R+45rdUXHGpN164avA==" + }, "sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", diff --git a/packages/s3-static-assets/package.json b/packages/s3-static-assets/package.json index 9ed71119c7..9338786020 100644 --- a/packages/s3-static-assets/package.json +++ b/packages/s3-static-assets/package.json @@ -35,7 +35,8 @@ "aws-sdk": "^2.664.0", "fs-extra": "^9.0.0", "klaw": "^3.0.0", - "mime-types": "^2.1.27" + "mime-types": "^2.1.27", + "regex-parser": "^2.2.10" }, "devDependencies": { "@types/fs-extra": "^8.1.0", diff --git a/packages/s3-static-assets/src/index.ts b/packages/s3-static-assets/src/index.ts index ca6daeb45d..7dbe586da8 100644 --- a/packages/s3-static-assets/src/index.ts +++ b/packages/s3-static-assets/src/index.ts @@ -6,21 +6,29 @@ import filterOutDirectories from "./lib/filterOutDirectories"; import { IMMUTABLE_CACHE_CONTROL_HEADER } from "./lib/constants"; import S3ClientFactory, { Credentials } from "./lib/s3"; import pathToPosix from "./lib/pathToPosix"; +import getPublicAssetCacheControl, { + PublicAssetCacheControl +} from "./lib/getPublicAssetCacheControl"; + +type Options = { + publicAssetCache?: PublicAssetCacheControl; +}; type UploadStaticAssetsOptions = { bucketName: string; nextConfigDir: string; nextStaticDir?: string; credentials: Credentials; + options: Options; }; const uploadStaticAssets = async ( - options: UploadStaticAssetsOptions + config: UploadStaticAssetsOptions ): Promise => { - const { bucketName, nextConfigDir, nextStaticDir = nextConfigDir } = options; + const { bucketName, nextConfigDir, nextStaticDir = nextConfigDir } = config; const s3 = await S3ClientFactory({ bucketName, - credentials: options.credentials + credentials: config.credentials }); const dotNextDirectory = path.join(nextConfigDir, ".next"); @@ -66,7 +74,8 @@ const uploadStaticAssets = async ( }); const uploadPublicOrStaticDirectory = async ( - directory: "public" | "static" + directory: "public" | "static", + options: Options ): Promise[]> => { const directoryPath = path.join(nextStaticDir, directory); if (!(await fse.pathExists(directoryPath))) { @@ -80,13 +89,23 @@ const uploadStaticAssets = async ( filePath: fileItem.path, s3Key: pathToPosix( path.relative(path.resolve(nextStaticDir), fileItem.path) + ), + cacheControl: getPublicAssetCacheControl( + fileItem.path, + options.publicAssetCache ) }) ); }; - const publicDirUploads = await uploadPublicOrStaticDirectory("public"); - const staticDirUploads = await uploadPublicOrStaticDirectory("static"); + const publicDirUploads = await uploadPublicOrStaticDirectory( + "public", + config.options + ); + const staticDirUploads = await uploadPublicOrStaticDirectory( + "static", + config.options + ); const allUploads = [ ...buildStaticFileUploads, // .next/static diff --git a/packages/s3-static-assets/src/lib/constants.ts b/packages/s3-static-assets/src/lib/constants.ts index a20bdcbb35..d2af876929 100644 --- a/packages/s3-static-assets/src/lib/constants.ts +++ b/packages/s3-static-assets/src/lib/constants.ts @@ -1,2 +1,5 @@ export const IMMUTABLE_CACHE_CONTROL_HEADER = "public, max-age=31536000, immutable"; + +export const DEFAULT_ASSET_CACHE_CONTROL_HEADER = "public, max-age=31536000, must-revalidate"; +export const DEFAULT_ASSET_CACHE_REGEX = /\.(gif|jpe?g|jp2|tiff|png|webp|bmp|svg)$/i; \ No newline at end of file diff --git a/packages/s3-static-assets/src/lib/getPublicAssetCacheControl.ts b/packages/s3-static-assets/src/lib/getPublicAssetCacheControl.ts new file mode 100644 index 0000000000..2518e1b053 --- /dev/null +++ b/packages/s3-static-assets/src/lib/getPublicAssetCacheControl.ts @@ -0,0 +1,43 @@ +import path from "path"; +import regexParser from "regex-parser"; +import { + DEFAULT_ASSET_CACHE_CONTROL_HEADER, + DEFAULT_ASSET_CACHE_REGEX +} from "./constants"; + +export type PublicAssetCacheControl = + | boolean + | { + test?: string; + value?: string; + }; + +const getPublicAssetCacheControl = ( + filePath: string, + options?: PublicAssetCacheControl +): string | undefined => { + if (!options) { + return undefined; + } + + let value: string = DEFAULT_ASSET_CACHE_CONTROL_HEADER; + let test: RegExp = DEFAULT_ASSET_CACHE_REGEX; + + if (typeof options === "object") { + if (options.value) { + value = options.value; + } + + if (options.test) { + test = regexParser(options.test); + } + } + + if (test.test(path.basename(filePath))) { + return value; + } + + return undefined; +}; + +export default getPublicAssetCacheControl; diff --git a/packages/s3-static-assets/tests/fixtures/app-with-images/.next/serverless/pages-manifest.json b/packages/s3-static-assets/tests/fixtures/app-with-images/.next/serverless/pages-manifest.json new file mode 100644 index 0000000000..b5c611993a --- /dev/null +++ b/packages/s3-static-assets/tests/fixtures/app-with-images/.next/serverless/pages-manifest.json @@ -0,0 +1,4 @@ +{ + "/todos/terms": "pages/todos/terms.html", + "/todos/terms/[section]": "pages/todos/terms/[section].html" +} diff --git a/packages/s3-static-assets/tests/fixtures/app-with-images/.next/serverless/pages/todos/terms.html b/packages/s3-static-assets/tests/fixtures/app-with-images/.next/serverless/pages/todos/terms.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/s3-static-assets/tests/fixtures/app-with-images/.next/serverless/pages/todos/terms/[section].html b/packages/s3-static-assets/tests/fixtures/app-with-images/.next/serverless/pages/todos/terms/[section].html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/a_test_build_id/css/one.css b/packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/a_test_build_id/css/one.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/a_test_build_id/two.js b/packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/a_test_build_id/two.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/chunks/chunk1.js b/packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/chunks/chunk1.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/runtime/runtime1.js b/packages/s3-static-assets/tests/fixtures/app-with-images/.next/static/runtime/runtime1.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/s3-static-assets/tests/fixtures/app-with-images/public/1x1.png b/packages/s3-static-assets/tests/fixtures/app-with-images/public/1x1.png new file mode 100644 index 0000000000000000000000000000000000000000..75f873cc464e0cadee8a44610381b5e2fbfef4ff GIT binary patch literal 88 zcmeAS@N?(olHy`uVBq!ia0vp^j3CSbBp9sfW`_bP&H|6fVg?3oVGw3ym^DWND9GjM f;uyjqo16e-GchopKVkY5$YStx^>bP0l+XkKJDCq+ literal 0 HcmV?d00001 diff --git a/packages/s3-static-assets/tests/fixtures/app-with-images/static/1x1.png b/packages/s3-static-assets/tests/fixtures/app-with-images/static/1x1.png new file mode 100644 index 0000000000000000000000000000000000000000..75f873cc464e0cadee8a44610381b5e2fbfef4ff GIT binary patch literal 88 zcmeAS@N?(olHy`uVBq!ia0vp^j3CSbBp9sfW`_bP&H|6fVg?3oVGw3ym^DWND9GjM f;uyjqo16e-GchopKVkY5$YStx^>bP0l+XkKJDCq+ literal 0 HcmV?d00001 diff --git a/packages/s3-static-assets/tests/upload-assets.test.ts b/packages/s3-static-assets/tests/upload-assets.test.ts index 46a721200e..58853fd2cd 100644 --- a/packages/s3-static-assets/tests/upload-assets.test.ts +++ b/packages/s3-static-assets/tests/upload-assets.test.ts @@ -1,6 +1,9 @@ import path from "path"; import uploadStaticAssets from "../src/index"; -import { IMMUTABLE_CACHE_CONTROL_HEADER } from "../src/lib/constants"; +import { + IMMUTABLE_CACHE_CONTROL_HEADER, + DEFAULT_ASSET_CACHE_CONTROL_HEADER +} from "../src/lib/constants"; import AWS, { mockGetBucketAccelerateConfigurationPromise, mockGetBucketAccelerateConfiguration, @@ -14,7 +17,15 @@ jest.mock("aws-sdk", () => require("./aws-sdk.mock")); const upload = ( nextConfigDir: string, - nextStaticDir?: string + nextStaticDir?: string, + options?: { + publicAssetCache?: + | boolean + | { + test?: string; + value?: string; + }; + } ): Promise => { let staticDir = nextStaticDir; @@ -30,7 +41,8 @@ const upload = ( accessKeyId: "fake-access-key", secretAccessKey: "fake-secret-key", sessionToken: "fake-session-token" - } + }, + options: options || {} }); }; @@ -180,3 +192,35 @@ describe.each` }); } ); + +describe("Content upload options", () => { + describe.each([ + [undefined, undefined], + [true, DEFAULT_ASSET_CACHE_CONTROL_HEADER], + [{ value: "public, max-age=36000" }, "public, max-age=36000"] + ])("publicAssetCache", (input, expected) => { + beforeEach(async () => { + await upload("./fixtures/app-with-images", undefined, { + publicAssetCache: input + }); + }); + + it(`sets ${expected} for value of ${input}`, () => { + expect(mockUpload).toBeCalledWith( + expect.objectContaining({ + Key: "public/1x1.png", + ContentType: "image/png", + CacheControl: expected + }) + ); + + expect(mockUpload).toBeCalledWith( + expect.objectContaining({ + Key: "static/1x1.png", + ContentType: "image/png", + CacheControl: expected + }) + ); + }); + }); +}); diff --git a/packages/serverless-component/README.md b/packages/serverless-component/README.md index f3eb5ceb1d..57e37215f0 100644 --- a/packages/serverless-component/README.md +++ b/packages/serverless-component/README.md @@ -20,6 +20,7 @@ A zero configuration Nextjs 9.0 [serverless component](https://github.com/server - [Custom Cloudfront configuration](#custom-cloudfront-configuration) - [Custom domain name](#custom-domain-name) - [AWS Permissions](#aws-permissions) +- [Public asset configuration](#public-asset-configuration) - [Architecture](#architecture) - [Inputs](#inputs) - [FAQ](#faq) @@ -95,8 +96,9 @@ $ serverless ``` ### Custom Cloudfront configuration + There are four cache behaviours created in Cloudfront (see Architecture section below for more). This option allows one to set cloudfront -cache configuration options for the lambda that handles SSR and/or the api lambda. The options that can be set are listed in the +cache configuration options for the lambda that handles SSR and/or the api lambda. The options that can be set are listed in the [aws-cloudfront component](https://github.com/serverless-components/aws-cloudfront). ```yml @@ -108,7 +110,12 @@ myNextApplication: cloudfront: defaults: # options for lambda that handle SSR forward: - headers: [CloudFront-Is-Desktop-Viewer, CloudFront-Is-Mobile-Viewer, CloudFront-Is-Tablet-Viewer] + headers: + [ + CloudFront-Is-Desktop-Viewer, + CloudFront-Is-Mobile-Viewer, + CloudFront-Is-Tablet-Viewer, + ] api: # options for lambdas that handle API request ttl: 10 origins: # options for custom origins and behaviors @@ -122,7 +129,7 @@ myNextApplication: ttl: 10 ``` -The example above adds headers that can be forwarded to the SSR lambda, and sets the *ttl* for api lambdas. +The example above adds headers that can be forwarded to the SSR lambda, and sets the _ttl_ for api lambdas. ### Custom domain name @@ -219,6 +226,31 @@ myNextApplication: apiLambda: fooApiLambda ``` +### Public asset configuration + +By default, assets under `/public` or `/static` don't have cache control applied. However, you can overwrite this by setting `publicAssetCache`. +Simply setting it to `true` will use the default of setting `Cache-Control=public, max-age=31536000, must-revalidate` on the following image formats: `gif|jpe?g|jp2|tiff|png|webp|bmp|svg`: + +```yaml +myNextApplication: + component: serverless-next.js + inputs: + publicAssetCache: true +``` + +To customize either or both, you can: + +```yaml +myNextApplication: + component: serverless-next.js + inputs: + publicAssetCache: + value: public, max-age=604800 + test: /\.(gif|jpe?g|png|txt|xml)$/i +``` + +`value` must be a valid `Cache-Control` policy and `test` must be a valid regex of the types of files you wish to test. + ### Architecture ![architecture](./arch_no_grid.png) @@ -241,22 +273,23 @@ The fourth cache behaviour handles next API requests `api/*`. ### Inputs -| Name | Type | Default Value | Description | -| ------------- | ----------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| cloudfront | `object` | `{}` | Sets cloudfront cache options for both SSR and api lambdas. Options are defined in the [aws-cloudfront component](https://github.com/serverless-components/aws-cloudfront) | | -| domain | `Array` | `null` | For example `['admin', 'portal.com']` | -| bucketName | `string` | `null` | Custom bucket name where static assets are stored. By default is autogenerated. | -| nextConfigDir | `string` | `./` | Directory where your application `next.config.js` file is. This input is useful when the `serverless.yml` is not in the same directory as the next app.
**Note:** `nextConfigDir` should be set if `next.config.js` `distDir` is used | -| nextStaticDir | `string` | `./` | If your `static` or `public` directory is not a direct child of `nextConfigDir` this is needed | -| memory | `number\|object` | `512` | When assigned a number, both the default and api lambdas will assigned memory of that value. When assigned to an object, values for the default and api lambdas can be separately defined | | -| timeout | `number\|object` | `10` | Same as above | -| name | `string\|object` | / | When assigned a string, both the default and api lambdas will assigned name of that value. When assigned to an object, values for the default and api lambdas can be separately defined | -| build | `boolean\|object` | `true` | When true builds and deploys app, when false assume the app has been built and uses the `.next` `.serverless_nextjs` directories in `nextConfigDir` to deploy. If an object is passed `build` allows for overriding what script gets called and with what arguments. | -| build.cmd | `string` | `node_modules/.bin/next` | Build command | -| build.args | `Array\|string` | `['build']` | Arguments to pass to the build | -| build.cwd | `string` | `./` | Override the current working directory | -| build.enabled | `boolean` | `true` | Same as passing `build:false` but from within the config | -| build.env | `object` | `{}` | Add additional environment variables to the script | +| Name | Type | Default Value | Description | +| ---------------- | ----------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| cloudfront | `object` | `{}` | Sets cloudfront cache options for both SSR and api lambdas. Options are defined in the [aws-cloudfront component](https://github.com/serverless-components/aws-cloudfront) | | +| domain | `Array` | `null` | For example `['admin', 'portal.com']` | +| bucketName | `string` | `null` | Custom bucket name where static assets are stored. By default is autogenerated. | +| nextConfigDir | `string` | `./` | Directory where your application `next.config.js` file is. This input is useful when the `serverless.yml` is not in the same directory as the next app.
**Note:** `nextConfigDir` should be set if `next.config.js` `distDir` is used | +| nextStaticDir | `string` | `./` | If your `static` or `public` directory is not a direct child of `nextConfigDir` this is needed | +| memory | `number\|object` | `512` | When assigned a number, both the default and api lambdas will assigned memory of that value. When assigned to an object, values for the default and api lambdas can be separately defined | | +| timeout | `number\|object` | `10` | Same as above | +| name | `string\|object` | / | When assigned a string, both the default and api lambdas will assigned name of that value. When assigned to an object, values for the default and api lambdas can be separately defined | +| publicAssetCache | `boolean\|object` | `false` | Sets `Cache-Control` policy on files under the `/public`/`/static` folders. When assigned `true`, a default caching is applied to all images, this can be customized by assigning an object, with a `test` regex and/or a different `value` for the caching policy. | +| build | `boolean\|object` | `true` | When true builds and deploys app, when false assume the app has been built and uses the `.next` `.serverless_nextjs` directories in `nextConfigDir` to deploy. If an object is passed `build` allows for overriding what script gets called and with what arguments. | +| build.cmd | `string` | `node_modules/.bin/next` | Build command | +| build.args | `Array\|string` | `['build']` | Arguments to pass to the build | +| build.cwd | `string` | `./` | Override the current working directory | +| build.enabled | `boolean` | `true` | Same as passing `build:false` but from within the config | +| build.env | `object` | `{}` | Add additional environment variables to the script | Custom inputs can be configured like this: diff --git a/packages/serverless-component/package-lock.json b/packages/serverless-component/package-lock.json index 35dba731ab..89658bae47 100644 --- a/packages/serverless-component/package-lock.json +++ b/packages/serverless-component/package-lock.json @@ -1152,7 +1152,8 @@ "aws-sdk": "^2.664.0", "fs-extra": "^9.0.0", "klaw": "^3.0.0", - "mime-types": "^2.1.27" + "mime-types": "^2.1.27", + "regex-parser": "^2.2.10" }, "dependencies": { "@types/fs-extra": { @@ -1293,6 +1294,11 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "regex-parser": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.10.tgz", + "integrity": "sha512-8t6074A68gHfU8Neftl0Le6KTDwfGAj7IyjPIMSfikI2wJUTHDMaIq42bUsfVnj8mhx0R+45rdUXHGpN164avA==" + }, "sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", diff --git a/packages/serverless-component/serverless.js b/packages/serverless-component/serverless.js index b44aa34eb4..56cfba9524 100644 --- a/packages/serverless-component/serverless.js +++ b/packages/serverless-component/serverless.js @@ -101,7 +101,10 @@ class NextjsComponent extends Component { bucketName: bucketOutputs.name, nextConfigDir: nextConfigPath, nextStaticDir: nextStaticPath, - credentials: this.context.credentials.aws + credentials: this.context.credentials.aws, + options: { + publicAssetCache: inputs.publicAssetCache || false + } }); defaultBuildManifest.cloudFrontOrigins = { @@ -253,8 +256,6 @@ class NextjsComponent extends Component { allowedHttpMethods: ["HEAD", "GET"], ...defaultCloudfrontInputs, forward: { - cookies: "all", - queryString: true, cookies: "all", queryString: true, ...defaultCloudfrontInputs.forward From ff90c65a87d9bcf03248227f1a91221a1826d1ea Mon Sep 17 00:00:00 2001 From: Daniel Balogh Date: Sat, 9 May 2020 15:34:07 +0300 Subject: [PATCH 2/2] Adress feedback --- packages/s3-static-assets/src/index.ts | 22 +++---- .../s3-static-assets/src/lib/constants.ts | 5 +- .../src/lib/getPublicAssetCacheControl.ts | 19 +++--- .../tests/upload-assets.test.ts | 48 ++++++++------- packages/serverless-component/README.md | 60 ++++++++++--------- .../__tests__/custom-inputs.test.js | 37 ++++++++++++ packages/serverless-component/serverless.js | 4 +- 7 files changed, 119 insertions(+), 76 deletions(-) diff --git a/packages/s3-static-assets/src/index.ts b/packages/s3-static-assets/src/index.ts index 7dbe586da8..4203f15381 100644 --- a/packages/s3-static-assets/src/index.ts +++ b/packages/s3-static-assets/src/index.ts @@ -7,28 +7,24 @@ import { IMMUTABLE_CACHE_CONTROL_HEADER } from "./lib/constants"; import S3ClientFactory, { Credentials } from "./lib/s3"; import pathToPosix from "./lib/pathToPosix"; import getPublicAssetCacheControl, { - PublicAssetCacheControl + PublicDirectoryCache } from "./lib/getPublicAssetCacheControl"; -type Options = { - publicAssetCache?: PublicAssetCacheControl; -}; - type UploadStaticAssetsOptions = { bucketName: string; nextConfigDir: string; nextStaticDir?: string; credentials: Credentials; - options: Options; + publicDirectoryCache?: PublicDirectoryCache; }; const uploadStaticAssets = async ( - config: UploadStaticAssetsOptions + options: UploadStaticAssetsOptions ): Promise => { - const { bucketName, nextConfigDir, nextStaticDir = nextConfigDir } = config; + const { bucketName, nextConfigDir, nextStaticDir = nextConfigDir } = options; const s3 = await S3ClientFactory({ bucketName, - credentials: config.credentials + credentials: options.credentials }); const dotNextDirectory = path.join(nextConfigDir, ".next"); @@ -75,7 +71,7 @@ const uploadStaticAssets = async ( const uploadPublicOrStaticDirectory = async ( directory: "public" | "static", - options: Options + publicDirectoryCache?: PublicDirectoryCache ): Promise[]> => { const directoryPath = path.join(nextStaticDir, directory); if (!(await fse.pathExists(directoryPath))) { @@ -92,7 +88,7 @@ const uploadStaticAssets = async ( ), cacheControl: getPublicAssetCacheControl( fileItem.path, - options.publicAssetCache + publicDirectoryCache ) }) ); @@ -100,11 +96,11 @@ const uploadStaticAssets = async ( const publicDirUploads = await uploadPublicOrStaticDirectory( "public", - config.options + options.publicDirectoryCache ); const staticDirUploads = await uploadPublicOrStaticDirectory( "static", - config.options + options.publicDirectoryCache ); const allUploads = [ diff --git a/packages/s3-static-assets/src/lib/constants.ts b/packages/s3-static-assets/src/lib/constants.ts index d2af876929..b76ebe191e 100644 --- a/packages/s3-static-assets/src/lib/constants.ts +++ b/packages/s3-static-assets/src/lib/constants.ts @@ -1,5 +1,6 @@ export const IMMUTABLE_CACHE_CONTROL_HEADER = "public, max-age=31536000, immutable"; -export const DEFAULT_ASSET_CACHE_CONTROL_HEADER = "public, max-age=31536000, must-revalidate"; -export const DEFAULT_ASSET_CACHE_REGEX = /\.(gif|jpe?g|jp2|tiff|png|webp|bmp|svg)$/i; \ No newline at end of file +export const DEFAULT_PUBLIC_DIR_CACHE_CONTROL = + "public, max-age=31536000, must-revalidate"; +export const DEFAULT_PUBLIC_DIR_CACHE_REGEX = /\.(gif|jpe?g|jp2|tiff|png|webp|bmp|svg|ico)$/i; diff --git a/packages/s3-static-assets/src/lib/getPublicAssetCacheControl.ts b/packages/s3-static-assets/src/lib/getPublicAssetCacheControl.ts index 2518e1b053..33a07dc74f 100644 --- a/packages/s3-static-assets/src/lib/getPublicAssetCacheControl.ts +++ b/packages/s3-static-assets/src/lib/getPublicAssetCacheControl.ts @@ -1,27 +1,32 @@ import path from "path"; import regexParser from "regex-parser"; import { - DEFAULT_ASSET_CACHE_CONTROL_HEADER, - DEFAULT_ASSET_CACHE_REGEX + DEFAULT_PUBLIC_DIR_CACHE_CONTROL, + DEFAULT_PUBLIC_DIR_CACHE_REGEX } from "./constants"; -export type PublicAssetCacheControl = +export type PublicDirectoryCache = | boolean | { test?: string; value?: string; }; +/** + * If options is not present, or is explicitly set to true, returns a default Cache-Control configuration for image types. + * If options is explicitly set to false, it returns undefined. + * If assigned an options object, it uses whichever value is defined there, falling back to the default if one is not present. + */ const getPublicAssetCacheControl = ( filePath: string, - options?: PublicAssetCacheControl + options?: PublicDirectoryCache ): string | undefined => { - if (!options) { + if (options === false) { return undefined; } - let value: string = DEFAULT_ASSET_CACHE_CONTROL_HEADER; - let test: RegExp = DEFAULT_ASSET_CACHE_REGEX; + let value: string = DEFAULT_PUBLIC_DIR_CACHE_CONTROL; + let test: RegExp = DEFAULT_PUBLIC_DIR_CACHE_REGEX; if (typeof options === "object") { if (options.value) { diff --git a/packages/s3-static-assets/tests/upload-assets.test.ts b/packages/s3-static-assets/tests/upload-assets.test.ts index 58853fd2cd..83e7cafc09 100644 --- a/packages/s3-static-assets/tests/upload-assets.test.ts +++ b/packages/s3-static-assets/tests/upload-assets.test.ts @@ -2,7 +2,7 @@ import path from "path"; import uploadStaticAssets from "../src/index"; import { IMMUTABLE_CACHE_CONTROL_HEADER, - DEFAULT_ASSET_CACHE_CONTROL_HEADER + DEFAULT_PUBLIC_DIR_CACHE_CONTROL } from "../src/lib/constants"; import AWS, { mockGetBucketAccelerateConfigurationPromise, @@ -18,14 +18,12 @@ jest.mock("aws-sdk", () => require("./aws-sdk.mock")); const upload = ( nextConfigDir: string, nextStaticDir?: string, - options?: { - publicAssetCache?: - | boolean - | { - test?: string; - value?: string; - }; - } + publicAssetCache?: + | boolean + | { + test?: string; + value?: string; + } ): Promise => { let staticDir = nextStaticDir; @@ -42,7 +40,7 @@ const upload = ( secretAccessKey: "fake-secret-key", sessionToken: "fake-session-token" }, - options: options || {} + publicDirectoryCache: publicAssetCache }); }; @@ -193,19 +191,25 @@ describe.each` } ); -describe("Content upload options", () => { - describe.each([ - [undefined, undefined], - [true, DEFAULT_ASSET_CACHE_CONTROL_HEADER], - [{ value: "public, max-age=36000" }, "public, max-age=36000"] - ])("publicAssetCache", (input, expected) => { +describe.each` + publicDirectoryCache | expected + ${undefined} | ${DEFAULT_PUBLIC_DIR_CACHE_CONTROL} + ${false} | ${undefined} + ${true} | ${DEFAULT_PUBLIC_DIR_CACHE_CONTROL} + ${{ value: "public, max-age=36000" }} | ${"public, max-age=36000"} + ${{ value: "public, max-age=36000", test: "/.(txt|xml)$/i" }} | ${undefined} +`( + "Public directory cache settings - publicDirectoryCache=$publicDirectoryCache, expected=$expected", + ({ publicDirectoryCache, expected }) => { beforeEach(async () => { - await upload("./fixtures/app-with-images", undefined, { - publicAssetCache: input - }); + await upload( + "./fixtures/app-with-images", + undefined, + publicDirectoryCache + ); }); - it(`sets ${expected} for value of ${input}`, () => { + it(`sets ${expected} for input value of ${publicDirectoryCache}`, () => { expect(mockUpload).toBeCalledWith( expect.objectContaining({ Key: "public/1x1.png", @@ -222,5 +226,5 @@ describe("Content upload options", () => { }) ); }); - }); -}); + } +); diff --git a/packages/serverless-component/README.md b/packages/serverless-component/README.md index 57e37215f0..5eaa4799f4 100644 --- a/packages/serverless-component/README.md +++ b/packages/serverless-component/README.md @@ -20,7 +20,7 @@ A zero configuration Nextjs 9.0 [serverless component](https://github.com/server - [Custom Cloudfront configuration](#custom-cloudfront-configuration) - [Custom domain name](#custom-domain-name) - [AWS Permissions](#aws-permissions) -- [Public asset configuration](#public-asset-configuration) +- [Public directory caching](#public-directory-caching) - [Architecture](#architecture) - [Inputs](#inputs) - [FAQ](#faq) @@ -129,7 +129,7 @@ myNextApplication: ttl: 10 ``` -The example above adds headers that can be forwarded to the SSR lambda, and sets the _ttl_ for api lambdas. +The example above adds headers that can be forwarded to the SSR lambda, and sets the `ttl` for api lambdas. ### Custom domain name @@ -226,31 +226,33 @@ myNextApplication: apiLambda: fooApiLambda ``` -### Public asset configuration +### Public directory caching -By default, assets under `/public` or `/static` don't have cache control applied. However, you can overwrite this by setting `publicAssetCache`. -Simply setting it to `true` will use the default of setting `Cache-Control=public, max-age=31536000, must-revalidate` on the following image formats: `gif|jpe?g|jp2|tiff|png|webp|bmp|svg`: +By default, common image formats(`gif|jpe?g|jp2|tiff|png|webp|bmp|svg|ico`) under `/public` or `/static` folders +have a one-year `Cache-Control` policy applied(`public, max-age=31536000, must-revalidate`). + +You may customize either the `Cache-Control` header `value` and the regex of which files to `test`, with `publicDirectoryCache`: ```yaml myNextApplication: component: serverless-next.js inputs: - publicAssetCache: true + publicDirectoryCache: + value: public, max-age=604800 + test: /\.(gif|jpe?g|png|txt|xml)$/i ``` -To customize either or both, you can: +`value` must be a valid `Cache-Control` policy and `test` must be a valid `regex` of the types of files you wish to test. + +If you don't want browsers to cache assets from the public directory, you can disable this: ```yaml myNextApplication: component: serverless-next.js inputs: - publicAssetCache: - value: public, max-age=604800 - test: /\.(gif|jpe?g|png|txt|xml)$/i + publicDirectoryCache: false ``` -`value` must be a valid `Cache-Control` policy and `test` must be a valid regex of the types of files you wish to test. - ### Architecture ![architecture](./arch_no_grid.png) @@ -273,23 +275,23 @@ The fourth cache behaviour handles next API requests `api/*`. ### Inputs -| Name | Type | Default Value | Description | -| ---------------- | ----------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| cloudfront | `object` | `{}` | Sets cloudfront cache options for both SSR and api lambdas. Options are defined in the [aws-cloudfront component](https://github.com/serverless-components/aws-cloudfront) | | -| domain | `Array` | `null` | For example `['admin', 'portal.com']` | -| bucketName | `string` | `null` | Custom bucket name where static assets are stored. By default is autogenerated. | -| nextConfigDir | `string` | `./` | Directory where your application `next.config.js` file is. This input is useful when the `serverless.yml` is not in the same directory as the next app.
**Note:** `nextConfigDir` should be set if `next.config.js` `distDir` is used | -| nextStaticDir | `string` | `./` | If your `static` or `public` directory is not a direct child of `nextConfigDir` this is needed | -| memory | `number\|object` | `512` | When assigned a number, both the default and api lambdas will assigned memory of that value. When assigned to an object, values for the default and api lambdas can be separately defined | | -| timeout | `number\|object` | `10` | Same as above | -| name | `string\|object` | / | When assigned a string, both the default and api lambdas will assigned name of that value. When assigned to an object, values for the default and api lambdas can be separately defined | -| publicAssetCache | `boolean\|object` | `false` | Sets `Cache-Control` policy on files under the `/public`/`/static` folders. When assigned `true`, a default caching is applied to all images, this can be customized by assigning an object, with a `test` regex and/or a different `value` for the caching policy. | -| build | `boolean\|object` | `true` | When true builds and deploys app, when false assume the app has been built and uses the `.next` `.serverless_nextjs` directories in `nextConfigDir` to deploy. If an object is passed `build` allows for overriding what script gets called and with what arguments. | -| build.cmd | `string` | `node_modules/.bin/next` | Build command | -| build.args | `Array\|string` | `['build']` | Arguments to pass to the build | -| build.cwd | `string` | `./` | Override the current working directory | -| build.enabled | `boolean` | `true` | Same as passing `build:false` but from within the config | -| build.env | `object` | `{}` | Add additional environment variables to the script | +| Name | Type | Default Value | Description | +| -------------------- | ----------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| cloudfront | `object` | `{}` | Sets cloudfront cache options for both SSR and api lambdas. Options are defined in the [aws-cloudfront component](https://github.com/serverless-components/aws-cloudfront) | +| domain | `Array` | `null` | For example `['admin', 'portal.com']` | +| bucketName | `string` | `null` | Custom bucket name where static assets are stored. By default is autogenerated. | +| nextConfigDir | `string` | `./` | Directory where your application `next.config.js` file is. This input is useful when the `serverless.yml` is not in the same directory as the next app.
**Note:** `nextConfigDir` should be set if `next.config.js` `distDir` is used | +| nextStaticDir | `string` | `./` | If your `static` or `public` directory is not a direct child of `nextConfigDir` this is needed | +| memory | `number\|object` | `512` | When assigned a number, both the default and api lambdas will assigned memory of that value. When assigned to an object, values for the default and api lambdas can be separately defined | +| timeout | `number\|object` | `10` | Same as above | +| name | `string\|object` | / | When assigned a string, both the default and api lambdas will assigned name of that value. When assigned to an object, values for the default and api lambdas can be separately defined | +| publicDirectoryCache | `boolean\|object` | `true` | Customize the `public`/`static` folder asset caching policy. Assigning an object with `value` and/or `test` lets you customize the caching policy and the types of files being cached. Assigning false disables caching | +| build | `boolean\|object` | `true` | When true builds and deploys app, when false assume the app has been built and uses the `.next` `.serverless_nextjs` directories in `nextConfigDir` to deploy. If an object is passed `build` allows for overriding what script gets called and with what arguments. | +| build.cmd | `string` | `node_modules/.bin/next` | Build command | +| build.args | `Array\|string` | `['build']` | Arguments to pass to the build | +| build.cwd | `string` | `./` | Override the current working directory | +| build.enabled | `boolean` | `true` | Same as passing `build:false` but from within the config | +| build.env | `object` | `{}` | Add additional environment variables to the script | Custom inputs can be configured like this: diff --git a/packages/serverless-component/__tests__/custom-inputs.test.js b/packages/serverless-component/__tests__/custom-inputs.test.js index 37e5bd16ce..da36664d4e 100644 --- a/packages/serverless-component/__tests__/custom-inputs.test.js +++ b/packages/serverless-component/__tests__/custom-inputs.test.js @@ -168,6 +168,43 @@ describe("Custom inputs", () => { } ); + describe.each` + publicDirectoryCache | expected + ${undefined} | ${"public, max-age=31536000, must-revalidate"} + ${false} | ${undefined} + ${{ test: "/.(ico|png)$/i", value: "public, max-age=306000" }} | ${"public, max-age=306000"} + `( + "input=inputPublicDirectoryCache, expected=$expectedPublicDirectoryCache", + ({ publicDirectoryCache, expected }) => { + beforeEach(async () => { + process.chdir(path.join(__dirname, "./fixtures/simple-app")); + + mockS3.mockResolvedValue({ + name: "bucket-xyz" + }); + + mockCloudFront.mockResolvedValueOnce({ + url: "https://cloudfrontdistrib.amazonaws.com" + }); + + const component = createNextComponent(); + + componentOutputs = await component.default({ + publicDirectoryCache + }); + }); + + it(`sets the ${expected} Cache-Control header on ${publicDirectoryCache}`, () => { + expect(mockUpload).toBeCalledWith( + expect.objectContaining({ + Key: expect.stringMatching("public/favicon.ico"), + CacheControl: expected + }) + ); + }); + } + ); + describe.each([ [undefined, { defaultMemory: 512, apiMemory: 512 }], [{}, { defaultMemory: 512, apiMemory: 512 }], diff --git a/packages/serverless-component/serverless.js b/packages/serverless-component/serverless.js index 56cfba9524..2d41958e2e 100644 --- a/packages/serverless-component/serverless.js +++ b/packages/serverless-component/serverless.js @@ -102,9 +102,7 @@ class NextjsComponent extends Component { nextConfigDir: nextConfigPath, nextStaticDir: nextStaticPath, credentials: this.context.credentials.aws, - options: { - publicAssetCache: inputs.publicAssetCache || false - } + publicDirectoryCache: inputs.publicDirectoryCache }); defaultBuildManifest.cloudFrontOrigins = {