Use relative paths for lexically static assets (#671)

Summary:
This is the first observable step toward #643. Assets whose paths are
known as literals at server-side rendering time are now referenced via
relative paths. This means that the favicon and JavaScript bundle can be
loaded from an arbitrary gateway. The actual bundle code will still only
work when loaded from `/`.

This commit stands alone so that the enclosing change to the Webpack
config can be in as small a change as possible.

Test Plan:
  - Note that `yarn start` still works.
  - Run `./scripts/build_static_site.sh` to build the site into, say,
    `/tmp/gateway`.
  - Run a static web server from `/tmp/gateway/` and note that (a) the
    paths listed in the page source are relative, and (b) everything
    works as intended, with no console messages in either Chrome or
    Firefox.
  - Run a static web server from `/tmp/` and navigate to `/gateway/` in
    the browser. Note that the favicon and JavaScript are correctly
    noted, but that the router raises an error because it is trying to
    load a non-existent route. (This behavior is unchanged.)

wchargin-branch: relative-lexically-static
This commit is contained in:
William Chargin 2018-08-15 15:30:23 -07:00 committed by GitHub
parent 094582be32
commit 91f0459753
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 17 additions and 13 deletions

View File

@ -18,17 +18,10 @@ const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
const paths = require("./paths");
const getClientEnvironment = require("./env");
// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.servedPath;
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = publicPath.slice(0, -1);
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl);
const env = getClientEnvironment(/* publicUrl: */ "");
function makeConfig(mode /*: "production" | "development" */) {
return {
@ -71,8 +64,6 @@ function makeConfig(mode /*: "production" | "development" */) {
// We don't currently advertise code splitting but Webpack supports it.
filename: "static/js/[name].[chunkhash:8].js",
chunkFilename: "static/js/[name].[chunkhash:8].chunk.js",
// We inferred the "public path" (such as / or /my-project) from homepage.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: (
info /*:

View File

@ -9,3 +9,13 @@ if (root == null) {
throw new Error("Unable to find root element!");
}
ReactDOM.hydrate(<App />, root);
// In Chrome, relative favicon URLs are recomputed at every pushState,
// although other assets (like the `src` of an `img`) are not. We don't
// want to have to keep the shortcut icon's path up to date as we
// transition; it's simpler to make it absolute at page load.
for (const el of document.querySelectorAll('link[rel="shortcut icon"]')) {
const link: HTMLLinkElement = (el: any);
// (Appearances aside, this is not a no-op.)
link.href = link.href;
}

View File

@ -6,6 +6,7 @@ import ReactDOMServer from "react-dom/server";
import {match, RouterContext} from "react-router";
import Page from "./Page";
import {Assets, rootFromPath} from "./assets";
import ExternalRedirect from "./ExternalRedirect";
import {createRoutes} from "./createRoutes";
import {resolveRouteFromPath, resolveTitleFromPath} from "./routeData";
@ -16,6 +17,8 @@ export default function render(
callback: (error: ?mixed, result?: string) => void
): void {
const path = locals.path;
const root = rootFromPath(path);
const assets = new Assets(root);
{
const route = resolveRouteFromPath(path);
if (route && route.contents.type === "EXTERNAL_REDIRECT") {
@ -42,7 +45,7 @@ export default function render(
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="shortcut icon" href="/favicon.png" />
<link rel="shortcut icon" href="${assets.resolve("/favicon.png")}" />
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed" rel="stylesheet">
<title>${resolveTitleFromPath(path)}</title>
@ -51,7 +54,7 @@ export default function render(
</head>
<body style="overflow-y:scroll">
<div id="root">${html}</div>
<script src="${bundlePath}"></script>
<script src="${assets.resolve(bundlePath)}"></script>
</body>
</html>
`;
@ -80,7 +83,7 @@ export default function render(
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta http-equiv="refresh" content="0;url=${redirectTo}" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="shortcut icon" href="${assets.resolve("favicon.png")}" />
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed" rel="stylesheet">
<title>${resolveTitleFromPath(path)}</title>