@11ty/eleventy-dev-server
Advanced tools
Comparing version 1.0.4 to 2.0.0
@@ -179,5 +179,7 @@ class Util { | ||
for (let link of document.querySelectorAll(`link[rel="stylesheet"]`)) { | ||
let url = new URL(link.href); | ||
url.searchParams.set("_11ty", Date.now()); | ||
link.href = url.toString(); | ||
if (link.href) { | ||
let url = new URL(link.href); | ||
url.searchParams.set("_11ty", Date.now()); | ||
link.href = url.toString(); | ||
} | ||
} | ||
@@ -184,0 +186,0 @@ Util.log(`CSS updated without page reload.`); |
@@ -18,3 +18,3 @@ #!/usr/bin/env node | ||
const debug = require("debug")("EleventyDevServer:cmd"); | ||
const debug = require("debug")("Eleventy:DevServer"); | ||
@@ -21,0 +21,0 @@ try { |
{ | ||
"name": "@11ty/eleventy-dev-server", | ||
"version": "1.0.4", | ||
"version": "2.0.0", | ||
"description": "A minimal, modern, generic, hot-reloading local web server to help web developers.", | ||
@@ -12,3 +12,3 @@ "main": "server.js", | ||
"engines": { | ||
"node": ">=14" | ||
"node": ">=18" | ||
}, | ||
@@ -42,4 +42,4 @@ "funding": { | ||
"dependencies": { | ||
"@11ty/eleventy-utils": "^1.0.1", | ||
"chokidar": "^3.5.3", | ||
"@11ty/eleventy-utils": "^1.0.2", | ||
"chokidar": "^3.6.0", | ||
"debug": "^4.3.4", | ||
@@ -50,10 +50,12 @@ "dev-ip": "^1.0.1", | ||
"minimist": "^1.2.8", | ||
"morphdom": "^2.7.0", | ||
"morphdom": "^2.7.2", | ||
"please-upgrade-node": "^3.2.0", | ||
"ssri": "^8.0.1", | ||
"ws": "^8.13.0" | ||
"send": "^0.18.0", | ||
"ssri": "^10.0.5", | ||
"urlpattern-polyfill": "^10.0.0", | ||
"ws": "^8.16.0" | ||
}, | ||
"devDependencies": { | ||
"ava": "^5.2.0" | ||
"ava": "^6.1.2" | ||
} | ||
} |
@@ -32,3 +32,3 @@ <p align="center"><img src="https://www.11ty.dev/img/logo-github.svg" width="200" height="200" alt="11ty Logo"></p> | ||
This package requires Node 14 or newer. | ||
This package requires Node 18 or newer. | ||
@@ -58,1 +58,5 @@ ### CLI Usage | ||
- We use the [ava JavaScript test runner](https://github.com/avajs/ava) ([Assertions documentation](https://github.com/avajs/ava/blob/master/docs/03-assertions.md)) | ||
## Changelog | ||
* `v2.0.0` bumps Node.js minimum to 18. |
124
server.js
@@ -9,10 +9,15 @@ const pkg = require("./package.json"); | ||
const ssri = require("ssri"); | ||
const send = require("send"); | ||
const devip = require("dev-ip"); | ||
const chokidar = require("chokidar"); | ||
const { TemplatePath } = require("@11ty/eleventy-utils"); | ||
const { TemplatePath, isPlainObject } = require("@11ty/eleventy-utils"); | ||
const debug = require("debug")("EleventyDevServer"); | ||
const debug = require("debug")("Eleventy:DevServer"); | ||
const wrapResponse = require("./server/wrapResponse.js"); | ||
if (!globalThis.URLPattern) { | ||
require("urlpattern-polyfill"); | ||
} | ||
const DEFAULT_OPTIONS = { | ||
@@ -31,3 +36,16 @@ port: 8080, | ||
aliases: {}, // Aliasing feature | ||
indexFileName: "index.html", // Allow custom index file name | ||
onRequest: {}, // Maps URLPatterns to dynamic callback functions that run on a request from a client. | ||
// Example: | ||
// "/foo/:name": function({ url, pattern, patternGroups }) { | ||
// return { | ||
// headers: { | ||
// "Content-Type": "text/html", | ||
// }, | ||
// body: `${url} ${JSON.stringify(patternGroups)}` | ||
// } | ||
// } | ||
// Logger (fancier one is injected by Eleventy) | ||
@@ -50,3 +68,3 @@ logger: { | ||
this.normalizeOptions(options); | ||
this.fileCache = {}; | ||
@@ -91,6 +109,6 @@ // Directory to serve | ||
// TODO allow chokidar configuration extensions (or re-use the ones in Eleventy) | ||
ignored: ["**/node_modules/**", ".git"], | ||
ignoreInitial: true, | ||
// same values as Eleventy | ||
@@ -107,3 +125,3 @@ awaitWriteFinish: { | ||
}); | ||
this._watcher.on("add", (path) => { | ||
@@ -263,3 +281,3 @@ this.logger.log( `File added: ${path} (skips build)` ); | ||
let indexHtmlPath = this.getOutputDirFilePath(url, "index.html"); | ||
let indexHtmlPath = this.getOutputDirFilePath(url, this.options.indexFileName); | ||
let indexHtmlExists = fs.existsSync(indexHtmlPath); | ||
@@ -289,3 +307,3 @@ | ||
statusCode: 301, | ||
url: url + "/", | ||
url: u.pathname + "/", | ||
}; | ||
@@ -298,3 +316,3 @@ } | ||
statusCode: 301, | ||
url: url.substring(0, url.length - 1), | ||
url: u.pathname.substring(0, u.pathname.length - 1), | ||
}; | ||
@@ -425,3 +443,47 @@ } | ||
eleventyDevServerMiddleware(req, res, next) { | ||
async eleventyDevServerMiddleware(req, res, next) { | ||
for(let urlPatternString in this.options.onRequest) { | ||
let fn = this.options.onRequest[urlPatternString]; | ||
let fullPath = this.getServerPath(urlPatternString); | ||
let p = new URLPattern({ pathname: fullPath }); | ||
// request url should already include pathprefix. | ||
let fullUrl = this.getServerUrlRaw("localhost", req.url); | ||
let match = p.exec(fullUrl); | ||
let u = new URL(fullUrl); | ||
if(match) { | ||
let result = await fn({ | ||
url: u, | ||
pattern: p, | ||
patternGroups: match?.pathname?.groups || {}, | ||
}); | ||
if(!result && result !== "") { | ||
continue; | ||
} | ||
if(typeof result === "string") { | ||
return res.end(result); | ||
} | ||
if(isPlainObject(result)) { | ||
if(typeof result.status === "number") { | ||
res.statusCode = result.status; | ||
} | ||
if(isPlainObject(result.headers)) { | ||
for(let name in result.headers) { | ||
res.setHeader(name, result.headers[name]); | ||
} | ||
} | ||
return res.end(result.body || ""); | ||
} | ||
throw new Error(`Invalid return type from \`onRequest\` pattern for ${urlPatternString}: expected string or object.`); | ||
} | ||
} | ||
if(req.url === `/${this.options.injectedScriptsFolder}/reload-client.js`) { | ||
@@ -468,8 +530,13 @@ if(this.options.liveReload) { | ||
debug( req.url, match ); | ||
if (match) { | ||
// Content-Range request, probably Safari trying to stream video | ||
if (req.headers.range) { | ||
return send(req, match.filepath).pipe(res); | ||
} | ||
if (match.statusCode === 200 && match.filepath) { | ||
return this.renderFile(match.filepath, res); | ||
} | ||
// Redirects, usually for trailing slash to .html stuff | ||
@@ -481,3 +548,3 @@ if (match.url) { | ||
} | ||
let raw404Path = this.getOutputDirFilePath("404.html"); | ||
@@ -616,8 +683,6 @@ if(match.statusCode === 404 && this.isOutputFilePathExists(raw404Path)) { | ||
let { port } = this._server.address(); | ||
let hostsStr = ""; | ||
if(this.options.showAllHosts) { | ||
// TODO what happens when the cert doesn’t cover non-localhost hosts? | ||
let hosts = devip().map(host => `${this._serverProtocol}//${host}:${port}${this.options.pathPrefix} or`); | ||
let hosts = devip().map(host => `${this.getServerUrl(host)} or`); | ||
hostsStr = hosts.join(" ") + " "; | ||
@@ -627,3 +692,3 @@ } | ||
let startBenchmark = ""; // this.start ? ` ready in ${Date.now() - this.start}ms` : ""; | ||
this.logger.info(`Server at ${hostsStr}${this._serverProtocol}//localhost:${port}${this.options.pathPrefix}${this.options.showVersion ? ` (v${pkg.version})` : ""}${startBenchmark}`); | ||
this.logger.info(`Server at ${hostsStr}${this.getServerUrl("localhost")}${this.options.showVersion ? ` (v${pkg.version})` : ""}${startBenchmark}`); | ||
}); | ||
@@ -640,2 +705,23 @@ | ||
getServerPath(pathname) { | ||
// duplicate slashes | ||
if(this.options.pathPrefix.endsWith("/") && pathname.startsWith("/")) { | ||
pathname = pathname.slice(1); | ||
} | ||
return `${this.options.pathPrefix}${pathname}`; | ||
} | ||
getServerUrlRaw(host, pathname = "", isRaw = true) { | ||
if(!this._server || !this._serverProtocol) { | ||
throw new Error("Access to `serverUrl` property not yet available."); | ||
} | ||
let { port } = this._server.address(); | ||
return `${this._serverProtocol}//${host}:${port}${isRaw ? pathname : this.getServerPath(pathname)}`; | ||
} | ||
getServerUrl(host, pathname = "") { | ||
return this.getServerUrlRaw(host, pathname, false); | ||
} | ||
async getPort() { | ||
@@ -738,4 +824,4 @@ return new Promise(resolve => { | ||
if(path.endsWith("/index.html")) { | ||
urls.push(path.slice(0, -1 * "index.html".length)); | ||
if(path.endsWith(`/${this.options.indexFileName}`)) { | ||
urls.push(path.slice(0, -1 * this.options.indexFileName.length)); | ||
} else if(path.endsWith(".html")) { | ||
@@ -742,0 +828,0 @@ urls.push(path.slice(0, -1 * ".html".length)); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
44870
1201
60
13
+ Addedsend@^0.18.0
+ Addedurlpattern-polyfill@^10.0.0
+ Addeddepd@2.0.0(transitive)
+ Addeddestroy@1.2.0(transitive)
+ Addedencodeurl@1.0.2(transitive)
+ Addedetag@1.8.1(transitive)
+ Addedfresh@0.5.2(transitive)
+ Addedhttp-errors@2.0.0(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedmime@1.6.0(transitive)
+ Addedminipass@7.1.2(transitive)
+ Addedrange-parser@1.2.1(transitive)
+ Addedsend@0.18.0(transitive)
+ Addedsetprototypeof@1.2.0(transitive)
+ Addedssri@10.0.6(transitive)
+ Addedtoidentifier@1.0.1(transitive)
+ Addedurlpattern-polyfill@10.0.0(transitive)
- Removedminipass@3.3.6(transitive)
- Removedssri@8.0.1(transitive)
- Removedyallist@4.0.0(transitive)
Updated@11ty/eleventy-utils@^1.0.2
Updatedchokidar@^3.6.0
Updatedmorphdom@^2.7.2
Updatedssri@^10.0.5
Updatedws@^8.16.0