-
-
Notifications
You must be signed in to change notification settings - Fork 923
feat(ssg): add redirect plugin #4599
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4599 +/- ##
==========================================
- Coverage 91.56% 91.43% -0.14%
==========================================
Files 173 174 +1
Lines 11256 11355 +99
Branches 3267 3288 +21
==========================================
+ Hits 10307 10382 +75
- Misses 948 972 +24
Partials 1 1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| expect(content).toContain('<a href="/new">Redirecting from') | ||
| }) | ||
|
|
||
| it('should skip generating a redirect HTML when 301/302 has no Location header', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The specification mandates that redirect responses must include a Location header 1. Current implementations simply bypass generating the file if no Location header is present, though options include displaying a warning or throwing an error could be considered.
Footnotes
| disableSSG, | ||
| onlySSG, | ||
| } from './middleware' | ||
| export { redirectPlugin } from './plugins' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since built-in plugins should only handle lightweight ones, I decided to manage multiple plugins in a single plugins.ts file. This should make updating exports slightly more convenient.
| if (res.status === 301 || res.status === 302) { | ||
| const location = res.headers.get('Location') | ||
| if (!location) return false | ||
| const html = generateRedirectHtml('', location) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't specify the from parameter. Is it expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to implement it.
The Response object in afterResponseHook does not contain information about the original redirect source, so we probably need to prepare a custom header like x-hono-ssg-route to inject the request path.
I also thought of another option, but this approach might break when running in parallel. (For production use, we would need to use a FIFO queue, but in this PoC I implemented it in a straightforward way.)
export const redirectPlugin = (): SSGPlugin => {
const requestedUrls: string[] = []
return {
beforeRequestHook: (req) => {
requestedUrls.push(new URL(req.url).pathname)
return req
},
afterResponseHook: (res) => {
if (res.status === 301 || res.status === 302) {
const location = res.headers.get('Location')
if (!location) {
return false
}
const from = requestedUrls.shift()
if (!from) {
return false
}
const body = generateRedirectHtml(from, location)
return new Response(body, {
status: 200,
headers: { 'Content-Type': 'text/html; charset=utf-8' },
})
}
return res
},
}
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about removing from from this PR?
At the very least, the user knows where it comes from because they access the URL directly. And I think the information about from is not so important.
|
|
||
| const generateRedirectHtml = (from: string, to: string) => { | ||
| const html = `<!DOCTYPE html> | ||
| <title>Redirecting to: ${to}</title> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a rare case if passing the script into to, but I think you should escape it. How about html?
import { html } from '../html'And you can esapce to with ${html`${to}`}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I refactored the code to use the html helper and added test cases related to escaping. Double escaping might not be necessary.
Also, it could be useful if the html helper had an option to get a minified string. If you cannot think of many use cases for it, it may not be needed.
| app.get('/old', (c) => c.redirect('/new')) | ||
| app.get('/new', (c) => c.html('New Page')) | ||
|
|
||
| await toSSG(app, fsMock, { dir: './static', plugins: [redirectPlugin()] }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of a situation where you use it with defaultPlugin? It works well if you put the redirecPlugin above the defaultPlugin. So, I think it's good to add a description about it on our website.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think updating the documentation is essential.
Additionally, it might be helpful to add name: string and requires?: string[] to the plugin, so that we can validate the plugin order. What do you think? (I think dependency validation itself is an interesting idea, but we need to carefully discuss the interface and validation methods.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added TSDoc and tests for plugin ordering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think dependency validation itself is an interesting idea, but we need to carefully discuss the interface and validation methods.
Yes. Let's discuss another place.
4c565a9 to
00137c9
Compare
|
Please ping me if it's ready to review. |
|
Sorry for the delayed response. Other tasks besides ESLint rule enforcement are currently unfinished. Once I've completed the fixes and replied to comments, I'll send review request. |
2d4a115 to
7b4556b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Build-in plugins test should be separated to plugins.test.tsx?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ssg.test.tsx became fat. So separating it is a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default plugin is an object, but the redirect plugin is a function. Should we standardize it to functions for consistency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, I think function is better because the user can pass options like redirectPlugin({ optionA }) if needed.
close #4389
The author should do the following, if applicable
bun run format:fix && bun run lint:fixto format the code