Skip to content

Commit

Permalink
feat(adapter-node): precompress (gzip & brotli) assets (#1693)
Browse files Browse the repository at this point in the history
  • Loading branch information
sastan authored Jun 16, 2021
1 parent 318cdd7 commit 926481f
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/clever-eagles-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-node': patch
---

precompress assets and prerendered pages (html,js,json,css,svg,xml)
5 changes: 5 additions & 0 deletions packages/adapter-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default {
adapter: adapter({
// default options are shown
out: 'build'
precompress: false,
})
}
};
Expand All @@ -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:
Expand Down
5 changes: 4 additions & 1 deletion packages/adapter-node/index.d.ts
Original file line number Diff line number Diff line change
@@ -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;
62 changes: 59 additions & 3 deletions packages/adapter-node/index.js
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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');
Expand All @@ -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);
}
3 changes: 2 additions & 1 deletion packages/adapter-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 6 additions & 7 deletions packages/adapter-node/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down
11 changes: 9 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 926481f

Please sign in to comment.