add generic content rewriting
mwcz opened this issue · 1 comments
spandx has two settings that perform content rewriting in HTML responses, portalChrome.resolveSPAComments
and primer.preview
. @zhawkins requested configurable content rewriting based on a CSS selector.
Here are some ideas of what the config API could look like.
Replace is an array to allow any number of replacements
{
replace: []
}
Each replacement object has a content
field
The content
field can be either a string or a function that returns a string. Strings will be injected directly into each response, whereas functions will be executed for each response.
{
replace: [
{
content: "MY CONTENT"
},
{
content: () => `MY DYNAMIC CONTENT ${Math.random()}`
}
]
}
Replace header tag with the contents of local file header.html
const fs = require("fs");
{
replace: [
{
selector: "header",
content: fs.readFileSync("./header.html").toString(),
},
// But this would read header.html only once when spandx is launched, so
// you'd probably really want to provide `content` as a function so it
// would read header.html fresh for each request, like this:
{
selector: "header",
content: () => fs.readFileSync("./header.html").toString(),
},
]
}
Replace header tag with the body of an HTTP response
const got = require("got"); // or axios, etc, take your pick of HTTP libs.
{
replace: [
{
selector: "header",
content: async () => (await got("https://foo.com/header.html")).body
}
]
}
Replace using start/end tokens
For replacing things that CSS selectors can't match, like text or comments.
{
replace: [
{
start: "<!-- START -->",
end: "<!-- END -->",
content: "MY CONTENT"
}
]
}
Powered by token-slice.
A replace object with both selector
and (start
or end
) is an error.
Replacements should happen in order, and should be able to operate on the previous replacement's result.
Possibly use https://github.com/fb55/css-select for executing CSS selectors in node.
A content-type filter is probably necessary as well, to avoid trying to do replacements on unwanted file types (like images). Perhaps a generic filter
function that's given the response object, returns a boolean, and can perform whatever filtering logic is desired.
{
replace: [
{
start: "<!-- START -->",
end: "<!-- END -->",
content: "MY CONTENT",
// only run the replacement if Content-Type starts with "text/html"
filter: (res) => /text\/html.*/.test(res.headers["content-type"])
}
]
}