serve-handler
Advanced tools
Comparing version 3.0.0 to 3.1.0
{ | ||
"name": "serve-handler", | ||
"version": "3.0.0", | ||
"version": "3.1.0", | ||
"description": "The routing foundation of `serve` and static deployments on Now", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
@@ -59,2 +59,3 @@ # serve-handler | ||
| [`trailingSlash`](#trailingslash-boolean) | Remove or add trailing slashes to all paths | | ||
| [`renderSingle`](#rendersingle-boolean) | If a directory only contains one file, render it | | ||
@@ -235,2 +236,16 @@ ### public (String) | ||
### renderSingle (Boolean) | ||
Sometimes you might want to have a directory path actually render a file, if the directory only contains one. This is only useful for any files that are not `.html` files (for those, [`cleanUrls`](#cleanurls-booleanarray) is faster). | ||
This is disabled by default and can be enabled like this: | ||
```js | ||
{ | ||
"renderSingle": true | ||
} | ||
``` | ||
After that, if you access your directory `/test` (for example), you will see an image being rendered if the directory contains a single image file. | ||
## Middleware | ||
@@ -237,0 +252,0 @@ |
113
src/index.js
@@ -100,2 +100,4 @@ // Native | ||
const slashing = typeof trailingSlash === 'boolean'; | ||
const defaultType = 301; | ||
const matchHTML = /(\.html|\/index)$/g; | ||
@@ -106,5 +108,2 @@ if (redirects.length === 0 && !slashing && !cleanUrl) { | ||
const defaultType = 301; | ||
const matchHTML = /(\.html|\/index)$/g; | ||
let cleanedUrl = false; | ||
@@ -238,3 +237,3 @@ | ||
relativePath.endsWith('/') ? relativePath.replace(/\/$/g, extension) : (relativePath + extension) | ||
]; | ||
].filter(item => path.basename(item) !== extension); | ||
@@ -253,3 +252,3 @@ const findRelated = async (current, relativePath, rewrittenPath, originalStat) => { | ||
} catch (err) { | ||
if (err.code !== 'ENOENT') { | ||
if (err.code !== 'ENOENT' && err.code !== 'ENOTDIR') { | ||
throw err; | ||
@@ -286,4 +285,4 @@ } | ||
const renderDirectory = async (current, acceptsJSON, handlers, config, paths) => { | ||
const {directoryListing, trailingSlash, unlisted = []} = config; | ||
const renderDirectory = async (current, acceptsJSON, handlers, methods, config, paths) => { | ||
const {directoryListing, trailingSlash, unlisted = [], renderSingle} = config; | ||
const slashSuffix = typeof trailingSlash === 'boolean' ? (trailingSlash ? '/' : '') : '/'; | ||
@@ -298,4 +297,4 @@ const {relativePath, absolutePath} = paths; | ||
if (!applicable(relativePath, directoryListing)) { | ||
return null; | ||
if (!applicable(relativePath, directoryListing) && !renderSingle) { | ||
return {}; | ||
} | ||
@@ -305,2 +304,4 @@ | ||
const canRenderSingle = renderSingle && (files.length === 1); | ||
for (let index = 0; index < files.length; index++) { | ||
@@ -311,4 +312,14 @@ const file = files[index]; | ||
const details = path.parse(filePath); | ||
const stats = await handlers.stat(filePath); | ||
// It's important to indicate that the `stat` call was | ||
// spawned by the directory listing, as Now is | ||
// simulating those calls and needs to special-case this. | ||
let stats = null; | ||
if (methods.stat) { | ||
stats = await handlers.stat(filePath, true); | ||
} else { | ||
stats = await handlers.stat(filePath); | ||
} | ||
details.relative = path.join(relativePath, details.base); | ||
@@ -321,2 +332,10 @@ | ||
} else { | ||
if (canRenderSingle) { | ||
return { | ||
singleFile: true, | ||
absolutePath: filePath, | ||
stats | ||
}; | ||
} | ||
details.ext = details.ext.split('.')[1] || 'txt'; | ||
@@ -407,3 +426,5 @@ details.type = 'file'; | ||
return acceptsJSON ? JSON.stringify(spec) : template(spec); | ||
const output = acceptsJSON ? JSON.stringify(spec) : template(spec); | ||
return {directory: output}; | ||
}; | ||
@@ -442,10 +463,26 @@ | ||
try { | ||
stats = await handlers.stat(absolutePath); | ||
} catch (err) { | ||
if (err.code !== 'ENOENT') { | ||
response.statusCode = 500; | ||
response.end(err.message); | ||
// It's extremely important that we're doing multiple stat calls. This one | ||
// right here could technically be removed, but then the program | ||
// would be slower. Because for directories, we always want to see if a related file | ||
// exists and then (after that), fetch the directory itself if no | ||
// related file was found. However (for files, of which most have extensions), we should | ||
// always stat right away. | ||
// | ||
// When simulating a file system without directory indexes, calculating whether a | ||
// directory exists requires loading all the file paths and then checking if | ||
// one of them includes the path of the directory. As that's a very | ||
// performance-expensive thing to do, we need to ensure it's not happening if not really necessary. | ||
return; | ||
if (path.extname(absolutePath) !== '') { | ||
try { | ||
stats = await handlers.stat(absolutePath); | ||
} catch (err) { | ||
if (err.code !== 'ENOENT') { | ||
console.error(err); | ||
response.statusCode = 500; | ||
response.end(err.message); | ||
return; | ||
} | ||
} | ||
@@ -456,3 +493,3 @@ } | ||
if ((!stats || stats.isDirectory()) && (cleanUrl || rewrittenPath)) { | ||
if (!stats && (cleanUrl || rewrittenPath)) { | ||
try { | ||
@@ -466,2 +503,4 @@ const related = await findRelated(current, relativePath, rewrittenPath, handlers.stat); | ||
if (err.code !== 'ENOENT') { | ||
console.error(err); | ||
response.statusCode = 500; | ||
@@ -475,2 +514,17 @@ response.end(err.message); | ||
if (!stats) { | ||
try { | ||
stats = await handlers.stat(absolutePath); | ||
} catch (err) { | ||
if (err.code !== 'ENOENT') { | ||
console.error(err); | ||
response.statusCode = 500; | ||
response.end(err.message); | ||
return; | ||
} | ||
} | ||
} | ||
let acceptsJSON = null; | ||
@@ -488,9 +542,18 @@ | ||
let directory = null; | ||
let singleFile = null; | ||
try { | ||
directory = await renderDirectory(current, acceptsJSON, handlers, config, { | ||
const related = await renderDirectory(current, acceptsJSON, handlers, methods, config, { | ||
relativePath, | ||
absolutePath | ||
}); | ||
if (related.singleFile) { | ||
({stats, absolutePath, singleFile} = related); | ||
} else { | ||
({directory} = related); | ||
} | ||
} catch (err) { | ||
console.error(err); | ||
response.statusCode = 500; | ||
@@ -514,5 +577,7 @@ response.end(err.message); | ||
// The directory listing is disabled, so we want to | ||
// render a 404 error. | ||
stats = null; | ||
if (!singleFile) { | ||
// The directory listing is disabled, so we want to | ||
// render a 404 error. | ||
stats = null; | ||
} | ||
} | ||
@@ -540,2 +605,4 @@ | ||
if (err.code !== 'ENOENT') { | ||
console.error(err); | ||
response.statusCode = 500; | ||
@@ -542,0 +609,0 @@ response.end(err.message); |
33711
508
266