From 8351c368afa430772b58c1a0e9ce4975476a6289 Mon Sep 17 00:00:00 2001 From: Sascha Tandel Date: Tue, 15 Jun 2021 09:53:32 +0200 Subject: [PATCH 1/3] feat(adapter-node): precompress (gzip & brotli) assets sirv supports using precompressed assets but they are not generated during the build. This PR precompresses html, js, json, css, svg, and xml assets using gzip and brotli during the adapt phase which allows sirv to use these. --- .changeset/clever-eagles-live.md | 5 +++ packages/adapter-node/README.md | 5 +++ packages/adapter-node/index.d.ts | 5 ++- packages/adapter-node/index.js | 62 +++++++++++++++++++++++++++-- packages/adapter-node/package.json | 3 +- packages/adapter-node/src/server.js | 13 +++--- pnpm-lock.yaml | 11 ++++- 7 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 .changeset/clever-eagles-live.md diff --git a/.changeset/clever-eagles-live.md b/.changeset/clever-eagles-live.md new file mode 100644 index 000000000000..63ba236f905a --- /dev/null +++ b/.changeset/clever-eagles-live.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-node': patch +--- + +precompress assets and prerendered pages (html,js,json,css,svg,xml) diff --git a/packages/adapter-node/README.md b/packages/adapter-node/README.md index 05ec77ce77e7..b8e12614756a 100644 --- a/packages/adapter-node/README.md +++ b/packages/adapter-node/README.md @@ -15,6 +15,7 @@ export default { adapter: adapter({ // default options are shown out: 'build' + precompress: false, }) } }; @@ -26,6 +27,10 @@ export default { The directory to build the server to. It defaults to `build` — i.e. `node build` would start the server locally after it has been created. +### precompress + +Enables precompressing using gzip and brotli for assets and prerendered pages. It defaults to `false`. + ## Environment variables By default, the server will accept connections on `0.0.0.0` using port 3000. These can be customised with the `PORT` and `HOST` environment variables: diff --git a/packages/adapter-node/index.d.ts b/packages/adapter-node/index.d.ts index fc87605e6137..2132e79d9a73 100644 --- a/packages/adapter-node/index.d.ts +++ b/packages/adapter-node/index.d.ts @@ -1,3 +1,6 @@ -declare function plugin(options?: { out?: string }): import('@sveltejs/kit').Adapter; +declare function plugin(options?: { + out?: string; + precompress?: boolean; +}): import('@sveltejs/kit').Adapter; export = plugin; diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js index 3a0a36309205..095109c10572 100644 --- a/packages/adapter-node/index.js +++ b/packages/adapter-node/index.js @@ -1,14 +1,21 @@ -import esbuild from 'esbuild'; -import { readFileSync } from 'fs'; +import { readFileSync, statSync, createReadStream, createWriteStream } from 'fs'; import { join } from 'path'; import { fileURLToPath } from 'url'; +import { pipeline } from 'stream'; +import { promisify } from 'util'; +import zlib from 'zlib'; +import esbuild from 'esbuild'; +import glob from 'tiny-glob'; + +const pipe = promisify(pipeline); /** * @param {{ * out?: string; + * precompress?: boolean * }} options */ -export default function ({ out = 'build' } = {}) { +export default function ({ out = 'build', precompress } = {}) { /** @type {import('@sveltejs/kit').Adapter} */ const adapter = { name: '@sveltejs/adapter-node', @@ -19,6 +26,11 @@ export default function ({ out = 'build' } = {}) { utils.copy_client_files(static_directory); utils.copy_static_files(static_directory); + if (precompress) { + utils.log.minor('Compressing assets'); + await compress(static_directory); + } + utils.log.minor('Building server'); const files = fileURLToPath(new URL('./files', import.meta.url)); utils.copy(files, '.svelte-kit/node'); @@ -39,8 +51,52 @@ export default function ({ out = 'build' } = {}) { await utils.prerender({ dest: `${out}/prerendered` }); + if (precompress) { + utils.log.minor('Compressing prerendered pages'); + await compress(`${out}/prerendered`); + } } }; return adapter; } + +/** + * @param {string} directory + */ +async function compress(directory) { + const files = await glob('**/*.{html,js,json,css,svg,xml}', { + cwd: directory, + dot: true, + absolute: true, + filesOnly: true + }); + + await Promise.all( + files.map((file) => Promise.all([compress_file(file, 'gz'), compress_file(file, 'br')])) + ); +} + +/** + * @param {string} file + * @param {'gz' | 'br'} format + */ +async function compress_file(file, format = 'gz') { + const compress = + format == 'br' + ? zlib.createBrotliCompress({ + params: { + [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT, + [zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY, + [zlib.constants.BROTLI_PARAM_SIZE_HINT]: statSync(file).size + } + }) + : zlib.createGzip({ + level: zlib.constants.Z_BEST_COMPRESSION + }); + + const source = createReadStream(file); + const destination = createWriteStream(`${file}.${format}`); + + await pipe(source, compress, destination); +} diff --git a/packages/adapter-node/package.json b/packages/adapter-node/package.json index 73a6c004a63c..a0af401eb335 100644 --- a/packages/adapter-node/package.json +++ b/packages/adapter-node/package.json @@ -21,7 +21,8 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "esbuild": "^0.12.5" + "esbuild": "^0.12.5", + "tiny-glob": "^0.2.9" }, "devDependencies": { "@rollup/plugin-json": "^4.1.0", diff --git a/packages/adapter-node/src/server.js b/packages/adapter-node/src/server.js index 26c2d099d7ed..ff35aa835a5f 100644 --- a/packages/adapter-node/src/server.js +++ b/packages/adapter-node/src/server.js @@ -17,12 +17,6 @@ const paths = { }; export function createServer({ render }) { - const mutable = (dir) => - sirv(dir, { - etag: true, - maxAge: 0 - }); - const immutable_path = (pathname) => { // eslint-disable-next-line no-undef let app_dir = esbuild_app_dir; @@ -43,7 +37,12 @@ export function createServer({ render }) { }; const prerendered_handler = fs.existsSync(paths.prerendered) - ? mutable(paths.prerendered) + ? sirv(paths.prerendered, { + etag: true, + maxAge: 0, + gzip: true, + brotli: true + }) : noop_handler; const assets_handler = fs.existsSync(paths.assets) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 693d088d4c12..84e87598f119 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,10 +100,12 @@ importers: polka: ^1.0.0-next.15 rollup: ^2.47.0 sirv: ^1.0.12 + tiny-glob: ^0.2.9 typescript: ^4.2.4 uvu: ^0.5.1 dependencies: esbuild: 0.12.5 + tiny-glob: 0.2.9 devDependencies: '@rollup/plugin-json': 4.1.0_rollup@2.47.0 '@sveltejs/kit': link:../kit @@ -1970,7 +1972,6 @@ packages: /globalyzer/0.1.0: resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} - dev: true /globby/11.0.3: resolution: {integrity: sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==} @@ -1986,7 +1987,6 @@ packages: /globrex/0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - dev: true /graceful-fs/4.2.6: resolution: {integrity: sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==} @@ -3566,6 +3566,13 @@ packages: globrex: 0.1.2 dev: true + /tiny-glob/0.2.9: + resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} + dependencies: + globalyzer: 0.1.0 + globrex: 0.1.2 + dev: false + /tmp/0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} From 4f90b19ebc344fa1ad96c2f0e52d7a39f8616c15 Mon Sep 17 00:00:00 2001 From: Sascha Tandel <514405+sastan@users.noreply.github.com> Date: Tue, 15 Jun 2021 22:21:45 +0200 Subject: [PATCH 2/3] Update .changeset/clever-eagles-live.md Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- .changeset/clever-eagles-live.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/clever-eagles-live.md b/.changeset/clever-eagles-live.md index 63ba236f905a..0e5c5ec92281 100644 --- a/.changeset/clever-eagles-live.md +++ b/.changeset/clever-eagles-live.md @@ -2,4 +2,4 @@ '@sveltejs/adapter-node': patch --- -precompress assets and prerendered pages (html,js,json,css,svg,xml) +compress assets and prerendered pages (html,js,json,css,svg,xml) From 37dc6d93a9d535c560822f79a75a7185e97efb8c Mon Sep 17 00:00:00 2001 From: Sascha Tandel Date: Tue, 15 Jun 2021 22:33:11 +0200 Subject: [PATCH 3/3] Revert "Update .changeset/clever-eagles-live.md" This reverts commit 58b1820148a93573ed75fdfbf8f295ea8c96ad31. --- .changeset/clever-eagles-live.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/clever-eagles-live.md b/.changeset/clever-eagles-live.md index 0e5c5ec92281..63ba236f905a 100644 --- a/.changeset/clever-eagles-live.md +++ b/.changeset/clever-eagles-live.md @@ -2,4 +2,4 @@ '@sveltejs/adapter-node': patch --- -compress assets and prerendered pages (html,js,json,css,svg,xml) +precompress assets and prerendered pages (html,js,json,css,svg,xml)