@hono/node-server 1.12.1 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -194,6 +194,22 @@ app.use(
194
194
  )
195
195
  ```
196
196
 
197
+ #### `onFound`
198
+
199
+ You can specify handling when the requested file is found with `onFound`.
200
+
201
+ ```ts
202
+ app.use(
203
+ '/static/*',
204
+ serveStatic({
205
+ // ...
206
+ onFound: (_path, c) => {
207
+ c.header('Cache-Control', `public, immutable, max-age=31536000`)
208
+ },
209
+ })
210
+ )
211
+ ```
212
+
197
213
  #### `onNotFound`
198
214
 
199
215
  The `onNotFound` is useful for debugging. You can write a handle for when a file is not found.
@@ -210,6 +226,19 @@ app.use(
210
226
  )
211
227
  ```
212
228
 
229
+ #### `precompressed`
230
+
231
+ The `precompressed` option checks if files with extensions like `.br` or `.gz` are available and serves them based on the `Accept-Encoding` header. It prioritizes Brotli, then Zstd, and Gzip. If none are available, it serves the original file.
232
+
233
+ ```ts
234
+ app.use(
235
+ '/static/*',
236
+ serveStatic({
237
+ precompressed: true,
238
+ })
239
+ )
240
+ ```
241
+
213
242
  ## ConnInfo Helper
214
243
 
215
244
  You can use the [ConnInfo Helper](https://hono.dev/docs/helpers/conninfo) by importing `getConnInfo` from `@hono/node-server/conninfo`.
@@ -1,14 +1,16 @@
1
- import { Context, MiddlewareHandler } from 'hono';
1
+ import { Env, Context, MiddlewareHandler } from 'hono';
2
2
 
3
- type ServeStaticOptions = {
3
+ type ServeStaticOptions<E extends Env = Env> = {
4
4
  /**
5
5
  * Root path, relative to current working directory from which the app was started. Absolute paths are not supported.
6
6
  */
7
7
  root?: string;
8
8
  path?: string;
9
9
  index?: string;
10
+ precompressed?: boolean;
10
11
  rewriteRequestPath?: (path: string) => string;
11
- onNotFound?: (path: string, c: Context) => void | Promise<void>;
12
+ onFound?: (path: string, c: Context<E>) => void | Promise<void>;
13
+ onNotFound?: (path: string, c: Context<E>) => void | Promise<void>;
12
14
  };
13
15
  declare const serveStatic: (options?: ServeStaticOptions) => MiddlewareHandler;
14
16
 
@@ -1,14 +1,16 @@
1
- import { Context, MiddlewareHandler } from 'hono';
1
+ import { Env, Context, MiddlewareHandler } from 'hono';
2
2
 
3
- type ServeStaticOptions = {
3
+ type ServeStaticOptions<E extends Env = Env> = {
4
4
  /**
5
5
  * Root path, relative to current working directory from which the app was started. Absolute paths are not supported.
6
6
  */
7
7
  root?: string;
8
8
  path?: string;
9
9
  index?: string;
10
+ precompressed?: boolean;
10
11
  rewriteRequestPath?: (path: string) => string;
11
- onNotFound?: (path: string, c: Context) => void | Promise<void>;
12
+ onFound?: (path: string, c: Context<E>) => void | Promise<void>;
13
+ onNotFound?: (path: string, c: Context<E>) => void | Promise<void>;
12
14
  };
13
15
  declare const serveStatic: (options?: ServeStaticOptions) => MiddlewareHandler;
14
16
 
@@ -23,9 +23,16 @@ __export(serve_static_exports, {
23
23
  serveStatic: () => serveStatic
24
24
  });
25
25
  module.exports = __toCommonJS(serve_static_exports);
26
- var import_fs = require("fs");
27
26
  var import_filepath = require("hono/utils/filepath");
28
27
  var import_mime = require("hono/utils/mime");
28
+ var import_fs = require("fs");
29
+ var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
30
+ var ENCODINGS = {
31
+ br: ".br",
32
+ zstd: ".zst",
33
+ gzip: ".gz"
34
+ };
35
+ var ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
29
36
  var createStreamBody = (stream) => {
30
37
  const body = new ReadableStream({
31
38
  start(controller) {
@@ -86,10 +93,29 @@ var serveStatic = (options = { root: "" }) => {
86
93
  await options.onNotFound?.(path, c);
87
94
  return next();
88
95
  }
96
+ await options.onFound?.(path, c);
89
97
  const mimeType = (0, import_mime.getMimeType)(path);
90
98
  if (mimeType) {
91
99
  c.header("Content-Type", mimeType);
92
100
  }
101
+ if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
102
+ const acceptEncodingSet = new Set(
103
+ c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim())
104
+ );
105
+ for (const encoding of ENCODINGS_ORDERED_KEYS) {
106
+ if (!acceptEncodingSet.has(encoding)) {
107
+ continue;
108
+ }
109
+ const precompressedStats = getStats(path + ENCODINGS[encoding]);
110
+ if (precompressedStats) {
111
+ c.header("Content-Encoding", encoding);
112
+ c.header("Vary", "Accept-Encoding", { append: true });
113
+ stats = precompressedStats;
114
+ path = path + ENCODINGS[encoding];
115
+ break;
116
+ }
117
+ }
118
+ }
93
119
  const size = stats.size;
94
120
  if (c.req.method == "HEAD" || c.req.method == "OPTIONS") {
95
121
  c.header("Content-Length", size.toString());
@@ -1,7 +1,14 @@
1
1
  // src/serve-static.ts
2
- import { createReadStream, lstatSync } from "fs";
3
2
  import { getFilePath, getFilePathWithoutDefaultDocument } from "hono/utils/filepath";
4
3
  import { getMimeType } from "hono/utils/mime";
4
+ import { createReadStream, lstatSync } from "fs";
5
+ var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
6
+ var ENCODINGS = {
7
+ br: ".br",
8
+ zstd: ".zst",
9
+ gzip: ".gz"
10
+ };
11
+ var ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
5
12
  var createStreamBody = (stream) => {
6
13
  const body = new ReadableStream({
7
14
  start(controller) {
@@ -62,10 +69,29 @@ var serveStatic = (options = { root: "" }) => {
62
69
  await options.onNotFound?.(path, c);
63
70
  return next();
64
71
  }
72
+ await options.onFound?.(path, c);
65
73
  const mimeType = getMimeType(path);
66
74
  if (mimeType) {
67
75
  c.header("Content-Type", mimeType);
68
76
  }
77
+ if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
78
+ const acceptEncodingSet = new Set(
79
+ c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim())
80
+ );
81
+ for (const encoding of ENCODINGS_ORDERED_KEYS) {
82
+ if (!acceptEncodingSet.has(encoding)) {
83
+ continue;
84
+ }
85
+ const precompressedStats = getStats(path + ENCODINGS[encoding]);
86
+ if (precompressedStats) {
87
+ c.header("Content-Encoding", encoding);
88
+ c.header("Vary", "Accept-Encoding", { append: true });
89
+ stats = precompressedStats;
90
+ path = path + ENCODINGS[encoding];
91
+ break;
92
+ }
93
+ }
94
+ }
69
95
  const size = stats.size;
70
96
  if (c.req.method == "HEAD" || c.req.method == "OPTIONS") {
71
97
  c.header("Content-Length", size.toString());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hono/node-server",
3
- "version": "1.12.1",
3
+ "version": "1.13.0",
4
4
  "description": "Node.js Adapter for Hono",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -60,8 +60,8 @@
60
60
  "postbuild": "publint",
61
61
  "prerelease": "yarn build && yarn test",
62
62
  "release": "np",
63
- "lint": "eslint --ext js,ts src test",
64
- "lint:fix": "eslint --ext js,ts src test --fix",
63
+ "lint": "eslint src test",
64
+ "lint:fix": "eslint src test --fix",
65
65
  "format": "prettier --check \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\"",
66
66
  "format:fix": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\""
67
67
  },
@@ -80,12 +80,12 @@
80
80
  "node": ">=18.14.1"
81
81
  },
82
82
  "devDependencies": {
83
- "@hono/eslint-config": "^0.0.4",
83
+ "@hono/eslint-config": "^1.0.1",
84
84
  "@types/jest": "^29.5.3",
85
85
  "@types/node": "^20.10.0",
86
86
  "@types/supertest": "^2.0.12",
87
87
  "@whatwg-node/fetch": "^0.9.14",
88
- "eslint": "^8.55.0",
88
+ "eslint": "^9.10.0",
89
89
  "hono": "^4.4.10",
90
90
  "jest": "^29.6.1",
91
91
  "np": "^7.7.0",
@@ -95,5 +95,8 @@
95
95
  "ts-jest": "^29.1.1",
96
96
  "tsup": "^7.2.0",
97
97
  "typescript": "^5.3.2"
98
+ },
99
+ "peerDependencies": {
100
+ "hono": "^4"
98
101
  }
99
102
  }