-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow React front-end to generate static HTML pages (#1194)
This adds functionality for the React front-end to generate its own "static pages", which consist of completely self-contained HTML that isn't progressively enhanced in any way. The idea here is for us to be able to create HTML content in our server-side renderer that isn't intended for delivery to a browser, but rather for alternate media such as a PDF (via WeasyPrint) or richly-formatted email. There's a few reasons we might want to do this: * It's much easier to write valid HTML in React than it is in Django templates. * It's easier for our developers to only learn one way of creating HTML content, rather than multiple ways. * It's easier to unit test our content; currently all our Django templates aren't very well-tested in part because it's not easy to test them, whereas it _is_ straightforward to test React code. * It's easier to ensure that our HTML-generating code won't raise exceptions, because it's TypeScript whereas Django templates have no type safety. * We might want to reuse similar components in both alternate media and our website (for example, a function that "prettifies" a U.S. phone number). * We sometimes duplicate view logic in Python and TypeScript, such as in #892, which this could help us avoid. * Django templates tend to be tightly coupled to Django models, which makes them harder to reuse and test. Rendering them React-side might mean a bit more work because it doesn't have direct access to our models, but it could also mean a better separation of concerns and more reusable code. * We're eventually going to internationalize the site (see #12) and it's very likely that our internationalization solution on the React side will be based on ICU MessageFormat, which results in more user-friendly localization than Django's gettext-based solution. Also, it's much easier for developers to learn only one internationalization solution. As a proof-of-concept, this adds a page at `/dev/examples/static-page` that renders a simple static page consisting of the following HTML: ``` <!DOCTYPE html> <html> <meta charSet="utf-8" /> <title>This is an example static page.</title> <p>Hello, this is an example static page…</p> </html> ``` Visiting this page in the browser will yield only that HTML and nothing else.
- Loading branch information
Showing
10 changed files
with
154 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import React from "react"; | ||
import { StaticPage } from "../static-page"; | ||
|
||
export const ExampleStaticPage: React.FC<{}> = () => ( | ||
<StaticPage> | ||
<html> | ||
{/* Yes, this is valid HTML5. */} | ||
<meta charSet="utf-8" /> | ||
<title>This is an example static page.</title> | ||
<p>Hello, this is an example static page…</p> | ||
</html> | ||
</StaticPage> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { useEffect } from "react"; | ||
import { withRouter, RouteComponentProps } from "react-router-dom"; | ||
import { getAppStaticContext } from "./app-static-context"; | ||
|
||
export type StaticPageProps = { children: JSX.Element }; | ||
|
||
/** | ||
* A <StaticPage> represents a web page of completely self-contained HTML | ||
* that isn't progressively enhanced in any way. Almost all components that | ||
* use this should pass an <html> element as a child. | ||
* | ||
* The primary use case for this component is for content that isn't | ||
* intended for use in a browser, but rather for alternate media | ||
* such as a PDF (via WeasyPrint) or richly-formatted HTML email. | ||
* | ||
* Using this element will actually *not* render anything above it in | ||
* the component heirarchy. If it's visited via a <Link> or any other | ||
* pushState-based mechanism, it will cause a hard refresh on the user's | ||
* browser. | ||
*/ | ||
export const StaticPage = withRouter( | ||
(props: RouteComponentProps & StaticPageProps) => { | ||
// If the user got here through a <Link> or other type of | ||
// dynamic redirect, we want to trigger a page reload so | ||
// that our static content is rendered server-side. However, | ||
// we also want to ensure that if the user presses the | ||
// back button on their browser, it triggers a hard redirect/reload | ||
// instead of a popstate event (which won't do anything because | ||
// we're a static page without any JS). So we'll add a | ||
// querystring to trigger this. | ||
// | ||
// Note that the following querystring should never actually | ||
// be included in a <Link> on the site, or we'll break the user's | ||
// browsing history. | ||
useEffect(() => window.location.replace("?staticView")); | ||
|
||
const staticCtx = getAppStaticContext(props); | ||
if (staticCtx) { | ||
staticCtx.staticContent = props.children; | ||
} | ||
return null; | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters