Skip to content

Commit

Permalink
Add initial discussion version for netlify redirects
Browse files Browse the repository at this point in the history
This is an incomplete implementation for Add adapters for redirects middleware 11ty#16

Signed-off-by: Raphael Höser <[email protected]>
  • Loading branch information
Snapstromegon committed Mar 9, 2023
1 parent e8cb274 commit d39cb0d
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 11 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
"mime": "^3.0.0",
"minimist": "^1.2.7",
"morphdom": "^2.6.1",
"netlify-redirect-parser": "^14.1.1",
"netlify-redirector": "^0.4.0",
"please-upgrade-node": "^3.2.0",
"ssri": "^8.0.1",
"ws": "^8.12.0"
Expand Down
106 changes: 95 additions & 11 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class EleventyDevServer {
debug("Creating new Dev Server instance.")
this.name = name;
this.normalizeOptions(options);

this.fileCache = {};
// Directory to serve
if(!dir) {
Expand Down Expand Up @@ -84,10 +84,10 @@ class EleventyDevServer {
// TODO if using Eleventy and `watch` option includes output folder (_site) this will trigger two update events!
this._watcher = chokidar.watch(this.options.watch, {
// TODO allow chokidar configuration extensions (or re-use the ones in Eleventy)

ignored: ["**/node_modules/**", ".git"],
ignoreInitial: true,

// same values as Eleventy
awaitWriteFinish: {
stabilityThreshold: 150,
Expand All @@ -99,7 +99,7 @@ class EleventyDevServer {
this.logger.log( `File changed: ${path} (skips build)` );
this.reloadFiles([path]);
});

this._watcher.on("add", (path) => {
this.logger.log( `File added: ${path} (skips build)` );
this.reloadFiles([path]);
Expand Down Expand Up @@ -405,6 +405,89 @@ class EleventyDevServer {
next();
}

async netlifyRedirectMiddleware(req, res, next) {
if (req.netlifyRedirectHandled) {
return next();
}
req.netlifyRedirectHandled = true;
const matcher = await this.getNetlifyRedirectMatcher();

// We need some valid URL here. Localhost is used as a placeholder.
const reqUrl = new URL(req.url, "https://localhost");
const match = matcher.match({
scheme: reqUrl.protocol.replace(/:.*$/, ""),
host: reqUrl.hostname,
path: decodeURIComponent(reqUrl.pathname),
query: reqUrl.search.slice(1),
});

// Avoid recursive matches
if (match && req.url !== match.to) {
// This is, why we can't extract this into a separate module.
// This just rewrites the url of the request and
// treats it as a new request in the request handler.
req.url = match.to;
this.onRequestHandler(req, res);
} else {
next();
}
}

// Inspired by https://github.com/netlify/cli/blob/0f7ac190f9e1c7ed056d2feb1a1834c8305a048a/src/utils/rules-proxy.mjs
async getNetlifyRedirectMatcher() {
// Importing dynamically, because these modules are ESM based.
// This also means that they are cached between requests.
const redirectParser = await import("netlify-redirect-parser");
const redirector = (await import("netlify-redirector")).default;
// Maybe some caching can be done here?
// Currently they are loaded per request, because redirects can be generated in the output folder
const { redirects, errors } = await redirectParser.parseAllRedirects({
redirectsFiles: [path.join(this.dir, "_redirects")],
netlifyConfigPath: path.join(".", "netlify.toml"),
});

if (errors.length) {
this.logger.error(errors);
}

// `netlify-redirector` does not handle the same shape as the `netlify-redirect-parser` provides:
// - `from` is called `origin`
// - `query` is called `params`
// - `conditions.role|country|language` are capitalized
const normalizeRedirect = function ({
conditions: { country, language, role, ...conditions },
from,
query,
signed,
...redirect
}) {
return {
...redirect,
origin: from,
params: query,
conditions: {
...conditions,
...(role && { Role: role }),
...(country && { Country: country }),
...(language && { Language: language }),
},
...(signed && {
sign: {
jwt_secret: signed,
},
}),
};
};

const normRedirects = redirects.map(normalizeRedirect);
const matcher = await redirector.parseJSON(
JSON.stringify(normRedirects),
{}
);

return matcher;
}

// This runs at the end of the middleware chain
eleventyProjectMiddleware(req, res) {
// Known issue with `finalhandler` and HTTP/2:
Expand Down Expand Up @@ -476,7 +559,7 @@ class EleventyDevServer {
}

return this.augmentContentWithNotifier(content, res.statusCode !== 200, {
scriptContents,
scriptContents,
integrityHash
});
}
Expand All @@ -486,6 +569,7 @@ class EleventyDevServer {

let middlewares = this.options.middleware || [];
middlewares = middlewares.slice();
middlewares.push(this.netlifyRedirectMiddleware);

// TODO because this runs at the very end of the middleware chain,
// if we move the static stuff up in the order we could use middleware to modify
Expand Down Expand Up @@ -754,13 +838,13 @@ class EleventyDevServer {
build.templates = build.templates
.filter(entry => {
if(!this.options.domDiff) {
// Don’t include any files if the dom diffing option is disabled
return false;
}
// Don’t include any files if the dom diffing option is disabled
return false;
}

// Filter to only include watched templates that were updated
return (files || []).includes(entry.inputPath);
});
// Filter to only include watched templates that were updated
return (files || []).includes(entry.inputPath);
});
}

this.sendUpdateNotification({
Expand Down
5 changes: 5 additions & 0 deletions server/wrapResponse.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ function getContentType(headers) {

// Inspired by `resp-modifier` https://github.com/shakyShane/resp-modifier/blob/4a000203c9db630bcfc3b6bb8ea2abc090ae0139/index.js
function wrapResponse(resp, transformHtml) {
// Response is already wrapped
if (resp._wrappedOriginalWrite) {
return resp;
}

resp._wrappedOriginalWrite = resp.write;
resp._wrappedOriginalWriteHead = resp.writeHead;
resp._wrappedOriginalEnd = resp.end;
Expand Down

0 comments on commit d39cb0d

Please sign in to comment.