@remix-run/static-middleware 0.3.0 → 0.4.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/dist/lib/directory-listing.js +2 -2
- package/dist/lib/static.d.ts +45 -22
- package/dist/lib/static.d.ts.map +1 -1
- package/dist/lib/static.js +16 -21
- package/package.json +8 -8
- package/src/lib/directory-listing.ts +2 -2
- package/src/lib/static.ts +63 -24
|
@@ -2,7 +2,7 @@ import * as path from 'node:path';
|
|
|
2
2
|
import * as fsp from 'node:fs/promises';
|
|
3
3
|
import { html } from '@remix-run/html-template';
|
|
4
4
|
import { createHtmlResponse } from '@remix-run/response/html';
|
|
5
|
-
import {
|
|
5
|
+
import { detectMimeType } from '@remix-run/mime';
|
|
6
6
|
export async function generateDirectoryListing(dirPath, pathname) {
|
|
7
7
|
let entries = [];
|
|
8
8
|
try {
|
|
@@ -19,7 +19,7 @@ export async function generateDirectoryListing(dirPath, pathname) {
|
|
|
19
19
|
try {
|
|
20
20
|
let stats = await fsp.stat(fullPath);
|
|
21
21
|
size = stats.size;
|
|
22
|
-
let mimeType =
|
|
22
|
+
let mimeType = detectMimeType(dirent.name);
|
|
23
23
|
type = mimeType || 'application/octet-stream';
|
|
24
24
|
}
|
|
25
25
|
catch {
|
package/dist/lib/static.d.ts
CHANGED
|
@@ -1,51 +1,74 @@
|
|
|
1
1
|
import type { Middleware } from '@remix-run/fetch-router';
|
|
2
2
|
import { type FileResponseOptions } from '@remix-run/response/file';
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Function that determines if HTTP Range requests should be supported for a given file.
|
|
5
|
+
*
|
|
6
|
+
* @param file - The File object being served
|
|
7
|
+
* @returns true if range requests should be supported
|
|
8
|
+
*/
|
|
9
|
+
export type AcceptRangesFunction = (file: File) => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Options for the `staticFiles` middleware.
|
|
12
|
+
*/
|
|
13
|
+
export interface StaticFilesOptions extends Omit<FileResponseOptions, 'acceptRanges'> {
|
|
4
14
|
/**
|
|
5
15
|
* Filter function to determine which files should be served.
|
|
6
16
|
*
|
|
7
17
|
* @param path The relative path being requested
|
|
8
|
-
* @
|
|
18
|
+
* @return Whether to serve the file
|
|
9
19
|
*/
|
|
10
20
|
filter?: (path: string) => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether to support HTTP Range requests for partial content.
|
|
23
|
+
*
|
|
24
|
+
* Can be a boolean or a function that receives the file.
|
|
25
|
+
* When enabled, includes Accept-Ranges header and handles Range requests
|
|
26
|
+
* with 206 Partial Content responses.
|
|
27
|
+
*
|
|
28
|
+
* Defaults to enabling ranges only for non-compressible MIME types,
|
|
29
|
+
* as defined by `isCompressibleMimeType()` from `@remix-run/mime`.
|
|
30
|
+
*
|
|
31
|
+
* Note: Range requests and compression are mutually exclusive. When
|
|
32
|
+
* `Accept-Ranges: bytes` is present in the response headers, the compression
|
|
33
|
+
* middleware will not compress the response. This is why the default behavior
|
|
34
|
+
* enables ranges only for non-compressible types.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // Force range request support for all files
|
|
38
|
+
* acceptRanges: true
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // Enable ranges for videos only
|
|
42
|
+
* acceptRanges: (file) => file.type.startsWith('video/')
|
|
43
|
+
*/
|
|
44
|
+
acceptRanges?: boolean | AcceptRangesFunction;
|
|
11
45
|
/**
|
|
12
46
|
* Files to try and serve as the index file when the request path targets a directory.
|
|
13
47
|
*
|
|
14
|
-
* - `true
|
|
48
|
+
* - `true`: Use default index files `['index.html', 'index.htm']`
|
|
15
49
|
* - `false`: Disable index file serving
|
|
16
50
|
* - `string[]`: Custom list of index files to try in order
|
|
51
|
+
*
|
|
52
|
+
* @default true
|
|
17
53
|
*/
|
|
18
54
|
index?: boolean | string[];
|
|
19
55
|
/**
|
|
20
56
|
* Whether to return an HTML page listing the files in a directory when the request path
|
|
21
57
|
* targets a directory. If both this and `index` are set, `index` takes precedence.
|
|
58
|
+
*
|
|
59
|
+
* @default false
|
|
22
60
|
*/
|
|
23
61
|
listFiles?: boolean;
|
|
24
62
|
}
|
|
25
63
|
/**
|
|
26
64
|
* Creates a middleware that serves static files from the filesystem.
|
|
27
65
|
*
|
|
28
|
-
* Uses the URL pathname to resolve files, removing the leading slash to make it
|
|
29
|
-
*
|
|
30
|
-
* is not found or an error occurs.
|
|
66
|
+
* Uses the URL pathname to resolve files, removing the leading slash to make it a relative path.
|
|
67
|
+
* The middleware always falls through to the handler if the file is not found or an error occurs.
|
|
31
68
|
*
|
|
32
69
|
* @param root The root directory to serve files from (absolute or relative to cwd)
|
|
33
|
-
* @param options
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* let router = createRouter({
|
|
37
|
-
* middleware: [staticFiles('./public')],
|
|
38
|
-
* })
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* // With cache control
|
|
42
|
-
* let router = createRouter({
|
|
43
|
-
* middleware: [
|
|
44
|
-
* staticFiles('./public', {
|
|
45
|
-
* cacheControl: 'public, max-age=3600',
|
|
46
|
-
* }),
|
|
47
|
-
* ],
|
|
48
|
-
* })
|
|
70
|
+
* @param options Configuration for file responses
|
|
71
|
+
* @return The static files middleware
|
|
49
72
|
*/
|
|
50
73
|
export declare function staticFiles(root: string, options?: StaticFilesOptions): Middleware;
|
|
51
74
|
//# sourceMappingURL=static.d.ts.map
|
package/dist/lib/static.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../src/lib/static.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAkC,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAInG,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;
|
|
1
|
+
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../src/lib/static.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAkC,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAInG;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAA;AAE1D;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,cAAc,CAAC;IACnF;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IAElC;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAA;IAE7C;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,EAAE,CAAA;IAC1B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,UAAU,CA8EtF"}
|
package/dist/lib/static.js
CHANGED
|
@@ -6,32 +6,17 @@ import { generateDirectoryListing } from "./directory-listing.js";
|
|
|
6
6
|
/**
|
|
7
7
|
* Creates a middleware that serves static files from the filesystem.
|
|
8
8
|
*
|
|
9
|
-
* Uses the URL pathname to resolve files, removing the leading slash to make it
|
|
10
|
-
*
|
|
11
|
-
* is not found or an error occurs.
|
|
9
|
+
* Uses the URL pathname to resolve files, removing the leading slash to make it a relative path.
|
|
10
|
+
* The middleware always falls through to the handler if the file is not found or an error occurs.
|
|
12
11
|
*
|
|
13
12
|
* @param root The root directory to serve files from (absolute or relative to cwd)
|
|
14
|
-
* @param options
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* let router = createRouter({
|
|
18
|
-
* middleware: [staticFiles('./public')],
|
|
19
|
-
* })
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* // With cache control
|
|
23
|
-
* let router = createRouter({
|
|
24
|
-
* middleware: [
|
|
25
|
-
* staticFiles('./public', {
|
|
26
|
-
* cacheControl: 'public, max-age=3600',
|
|
27
|
-
* }),
|
|
28
|
-
* ],
|
|
29
|
-
* })
|
|
13
|
+
* @param options Configuration for file responses
|
|
14
|
+
* @return The static files middleware
|
|
30
15
|
*/
|
|
31
16
|
export function staticFiles(root, options = {}) {
|
|
32
17
|
// Ensure root is an absolute path
|
|
33
18
|
root = path.resolve(root);
|
|
34
|
-
let { filter, index: indexOption, listFiles, ...fileOptions } = options;
|
|
19
|
+
let { acceptRanges, filter, index: indexOption, listFiles, ...fileOptions } = options;
|
|
35
20
|
// Normalize index option
|
|
36
21
|
let index;
|
|
37
22
|
if (indexOption === false) {
|
|
@@ -85,7 +70,17 @@ export function staticFiles(root, options = {}) {
|
|
|
85
70
|
if (filePath) {
|
|
86
71
|
let fileName = path.relative(root, filePath);
|
|
87
72
|
let file = openFile(filePath, { name: fileName });
|
|
88
|
-
|
|
73
|
+
let finalFileOptions = { ...fileOptions };
|
|
74
|
+
// If acceptRanges is a function, evaluate it with the file
|
|
75
|
+
// Otherwise, pass it directly to sendFile
|
|
76
|
+
if (typeof acceptRanges === 'function') {
|
|
77
|
+
finalFileOptions.acceptRanges = acceptRanges(file);
|
|
78
|
+
}
|
|
79
|
+
else if (acceptRanges !== undefined) {
|
|
80
|
+
finalFileOptions.acceptRanges = acceptRanges;
|
|
81
|
+
}
|
|
82
|
+
return sendFile(file, context.request, finalFileOptions);
|
|
89
83
|
}
|
|
84
|
+
return next();
|
|
90
85
|
};
|
|
91
86
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remix-run/static-middleware",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Middleware for serving static files from the filesystem",
|
|
5
5
|
"author": "Michael Jackson <mjijackson@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,13 +29,16 @@
|
|
|
29
29
|
"@types/node": "^24.6.0",
|
|
30
30
|
"typescript": "^5.9.3",
|
|
31
31
|
"@remix-run/fetch-router": "0.12.0",
|
|
32
|
-
"@remix-run/
|
|
33
|
-
"@remix-run/
|
|
32
|
+
"@remix-run/form-data-middleware": "0.1.0",
|
|
33
|
+
"@remix-run/method-override-middleware": "0.1.1",
|
|
34
|
+
"@remix-run/response": "^0.2.0",
|
|
35
|
+
"@remix-run/mime": "^0.1.0"
|
|
34
36
|
},
|
|
35
37
|
"peerDependencies": {
|
|
36
38
|
"@remix-run/fetch-router": "^0.12.0",
|
|
37
|
-
"@remix-run/fs": "^0.
|
|
38
|
-
"@remix-run/
|
|
39
|
+
"@remix-run/fs": "^0.2.0",
|
|
40
|
+
"@remix-run/mime": "^0.1.0",
|
|
41
|
+
"@remix-run/response": "^0.2.0",
|
|
39
42
|
"@remix-run/html-template": "^0.3.0"
|
|
40
43
|
},
|
|
41
44
|
"keywords": [
|
|
@@ -45,9 +48,6 @@
|
|
|
45
48
|
"static-files",
|
|
46
49
|
"file-server"
|
|
47
50
|
],
|
|
48
|
-
"dependencies": {
|
|
49
|
-
"mrmime": "^2.0.0"
|
|
50
|
-
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "tsc -p tsconfig.build.json",
|
|
53
53
|
"clean": "git clean -fdX",
|
|
@@ -2,7 +2,7 @@ import * as path from 'node:path'
|
|
|
2
2
|
import * as fsp from 'node:fs/promises'
|
|
3
3
|
import { html } from '@remix-run/html-template'
|
|
4
4
|
import { createHtmlResponse } from '@remix-run/response/html'
|
|
5
|
-
import {
|
|
5
|
+
import { detectMimeType } from '@remix-run/mime'
|
|
6
6
|
|
|
7
7
|
interface DirectoryEntry {
|
|
8
8
|
name: string
|
|
@@ -32,7 +32,7 @@ export async function generateDirectoryListing(
|
|
|
32
32
|
try {
|
|
33
33
|
let stats = await fsp.stat(fullPath)
|
|
34
34
|
size = stats.size
|
|
35
|
-
let mimeType =
|
|
35
|
+
let mimeType = detectMimeType(dirent.name)
|
|
36
36
|
type = mimeType || 'application/octet-stream'
|
|
37
37
|
} catch {
|
|
38
38
|
// Unable to stat file, use defaults
|
package/src/lib/static.ts
CHANGED
|
@@ -6,25 +6,66 @@ import { createFileResponse as sendFile, type FileResponseOptions } from '@remix
|
|
|
6
6
|
|
|
7
7
|
import { generateDirectoryListing } from './directory-listing.ts'
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Function that determines if HTTP Range requests should be supported for a given file.
|
|
11
|
+
*
|
|
12
|
+
* @param file - The File object being served
|
|
13
|
+
* @returns true if range requests should be supported
|
|
14
|
+
*/
|
|
15
|
+
export type AcceptRangesFunction = (file: File) => boolean
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for the `staticFiles` middleware.
|
|
19
|
+
*/
|
|
20
|
+
export interface StaticFilesOptions extends Omit<FileResponseOptions, 'acceptRanges'> {
|
|
10
21
|
/**
|
|
11
22
|
* Filter function to determine which files should be served.
|
|
12
23
|
*
|
|
13
24
|
* @param path The relative path being requested
|
|
14
|
-
* @
|
|
25
|
+
* @return Whether to serve the file
|
|
15
26
|
*/
|
|
16
27
|
filter?: (path: string) => boolean
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether to support HTTP Range requests for partial content.
|
|
31
|
+
*
|
|
32
|
+
* Can be a boolean or a function that receives the file.
|
|
33
|
+
* When enabled, includes Accept-Ranges header and handles Range requests
|
|
34
|
+
* with 206 Partial Content responses.
|
|
35
|
+
*
|
|
36
|
+
* Defaults to enabling ranges only for non-compressible MIME types,
|
|
37
|
+
* as defined by `isCompressibleMimeType()` from `@remix-run/mime`.
|
|
38
|
+
*
|
|
39
|
+
* Note: Range requests and compression are mutually exclusive. When
|
|
40
|
+
* `Accept-Ranges: bytes` is present in the response headers, the compression
|
|
41
|
+
* middleware will not compress the response. This is why the default behavior
|
|
42
|
+
* enables ranges only for non-compressible types.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // Force range request support for all files
|
|
46
|
+
* acceptRanges: true
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* // Enable ranges for videos only
|
|
50
|
+
* acceptRanges: (file) => file.type.startsWith('video/')
|
|
51
|
+
*/
|
|
52
|
+
acceptRanges?: boolean | AcceptRangesFunction
|
|
53
|
+
|
|
17
54
|
/**
|
|
18
55
|
* Files to try and serve as the index file when the request path targets a directory.
|
|
19
56
|
*
|
|
20
|
-
* - `true
|
|
57
|
+
* - `true`: Use default index files `['index.html', 'index.htm']`
|
|
21
58
|
* - `false`: Disable index file serving
|
|
22
59
|
* - `string[]`: Custom list of index files to try in order
|
|
60
|
+
*
|
|
61
|
+
* @default true
|
|
23
62
|
*/
|
|
24
63
|
index?: boolean | string[]
|
|
25
64
|
/**
|
|
26
65
|
* Whether to return an HTML page listing the files in a directory when the request path
|
|
27
66
|
* targets a directory. If both this and `index` are set, `index` takes precedence.
|
|
67
|
+
*
|
|
68
|
+
* @default false
|
|
28
69
|
*/
|
|
29
70
|
listFiles?: boolean
|
|
30
71
|
}
|
|
@@ -32,33 +73,18 @@ export interface StaticFilesOptions extends FileResponseOptions {
|
|
|
32
73
|
/**
|
|
33
74
|
* Creates a middleware that serves static files from the filesystem.
|
|
34
75
|
*
|
|
35
|
-
* Uses the URL pathname to resolve files, removing the leading slash to make it
|
|
36
|
-
*
|
|
37
|
-
* is not found or an error occurs.
|
|
76
|
+
* Uses the URL pathname to resolve files, removing the leading slash to make it a relative path.
|
|
77
|
+
* The middleware always falls through to the handler if the file is not found or an error occurs.
|
|
38
78
|
*
|
|
39
79
|
* @param root The root directory to serve files from (absolute or relative to cwd)
|
|
40
|
-
* @param options
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* let router = createRouter({
|
|
44
|
-
* middleware: [staticFiles('./public')],
|
|
45
|
-
* })
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* // With cache control
|
|
49
|
-
* let router = createRouter({
|
|
50
|
-
* middleware: [
|
|
51
|
-
* staticFiles('./public', {
|
|
52
|
-
* cacheControl: 'public, max-age=3600',
|
|
53
|
-
* }),
|
|
54
|
-
* ],
|
|
55
|
-
* })
|
|
80
|
+
* @param options Configuration for file responses
|
|
81
|
+
* @return The static files middleware
|
|
56
82
|
*/
|
|
57
83
|
export function staticFiles(root: string, options: StaticFilesOptions = {}): Middleware {
|
|
58
84
|
// Ensure root is an absolute path
|
|
59
85
|
root = path.resolve(root)
|
|
60
86
|
|
|
61
|
-
let { filter, index: indexOption, listFiles, ...fileOptions } = options
|
|
87
|
+
let { acceptRanges, filter, index: indexOption, listFiles, ...fileOptions } = options
|
|
62
88
|
|
|
63
89
|
// Normalize index option
|
|
64
90
|
let index: string[]
|
|
@@ -116,7 +142,20 @@ export function staticFiles(root: string, options: StaticFilesOptions = {}): Mid
|
|
|
116
142
|
if (filePath) {
|
|
117
143
|
let fileName = path.relative(root, filePath)
|
|
118
144
|
let file = openFile(filePath, { name: fileName })
|
|
119
|
-
|
|
145
|
+
|
|
146
|
+
let finalFileOptions: FileResponseOptions = { ...fileOptions }
|
|
147
|
+
|
|
148
|
+
// If acceptRanges is a function, evaluate it with the file
|
|
149
|
+
// Otherwise, pass it directly to sendFile
|
|
150
|
+
if (typeof acceptRanges === 'function') {
|
|
151
|
+
finalFileOptions.acceptRanges = acceptRanges(file)
|
|
152
|
+
} else if (acceptRanges !== undefined) {
|
|
153
|
+
finalFileOptions.acceptRanges = acceptRanges
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return sendFile(file, context.request, finalFileOptions)
|
|
120
157
|
}
|
|
158
|
+
|
|
159
|
+
return next()
|
|
121
160
|
}
|
|
122
161
|
}
|