From 91f045975369f85a4758c3bc2fc90c08080c6452 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Wed, 15 Aug 2018 15:30:23 -0700 Subject: [PATCH] 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 --- config/makeWebpackConfig.js | 11 +---------- src/app/index.js | 10 ++++++++++ src/app/server.js | 9 ++++++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/config/makeWebpackConfig.js b/config/makeWebpackConfig.js index c8e8495..65d9eae 100644 --- a/config/makeWebpackConfig.js +++ b/config/makeWebpackConfig.js @@ -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 /*: diff --git a/src/app/index.js b/src/app/index.js index a6065b3..74237cf 100644 --- a/src/app/index.js +++ b/src/app/index.js @@ -9,3 +9,13 @@ if (root == null) { throw new Error("Unable to find root element!"); } ReactDOM.hydrate(, 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; +} diff --git a/src/app/server.js b/src/app/server.js index 2b4f88e..32e4e55 100644 --- a/src/app/server.js +++ b/src/app/server.js @@ -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( - + ${resolveTitleFromPath(path)} @@ -51,7 +54,7 @@ export default function render(
${html}
- + `; @@ -80,7 +83,7 @@ export default function render( - + ${resolveTitleFromPath(path)}