commit a32e7b6dd72386986ab5117b562485dd8771176d
parent 448121f2f275e8842e0d40874098b94ac928b79e
Author: Vladimir Kutepov <frenzzy.man@gmail.com>
Date: Fri, 23 Sep 2016 18:26:56 +0400
Simplify client.js, remove DOMUtils (#873)
- IE9+ support
- Remove redundant pagehide logic
- Remove Layout usage from ErrorPage
- Remove unnecessary DOMUtils
Diffstat:
5 files changed, 70 insertions(+), 131 deletions(-)
diff --git a/src/client.js b/src/client.js
@@ -15,12 +15,6 @@ import UniversalRouter from 'universal-router';
import queryString from 'query-string';
import createBrowserHistory from 'history/createBrowserHistory';
import App from './components/App';
-import {
- addEventListener,
- removeEventListener,
- windowScrollX,
- windowScrollY,
-} from './core/DOMUtils';
// Global (context) variables that can be easily accessed from any React component
// https://facebook.github.io/react/docs/context.html
@@ -37,51 +31,36 @@ const context = {
},
};
-function updateTag(tag, nameKey, valueKey, name, value) {
- // Remove and create a new <meta /> tag in order to make it work with bookmarks in Safari
- let meta = document.head.querySelector(`${tag}[${nameKey}=${name}]`);
- if (!meta || meta.getAttribute(valueKey) !== value) {
- if (meta) {
- meta.parentNode.removeChild(meta);
- }
- if (typeof value === 'string') {
- meta = document.createElement(tag);
- meta.setAttribute(nameKey, name);
- meta.setAttribute(valueKey, value);
- document.head.appendChild(meta);
- }
+function updateTag(tagName, keyName, keyValue, attrName, attrValue) {
+ const node = document.head.querySelector(`${tagName}[${keyName}=${keyValue}]`);
+ if (node && node.getAttribute(attrName) === attrValue) return;
+
+ // Remove and create a new tag in order to make it work with bookmarks in Safari
+ if (node) {
+ node.parentNode.removeChild(node);
+ }
+ if (typeof attrValue === 'string') {
+ const nextNode = document.createElement(tagName);
+ nextNode.setAttribute(keyName, keyValue);
+ nextNode.setAttribute(attrName, attrValue);
+ document.head.appendChild(nextNode);
}
}
-function updateMeta(name, value) {
- updateTag('meta', 'name', 'content', name, value);
+function updateMeta(name, content) {
+ updateTag('meta', 'name', name, 'content', content);
}
-function updateLink(name, value) { // eslint-disable-line no-unused-vars
- updateTag('link', 'rel', 'href', name, value);
+function updateCustomMeta(property, content) { // eslint-disable-line no-unused-vars
+ updateTag('meta', 'property', property, 'content', content);
}
-function updateCustomMeta(name, value) { // eslint-disable-line no-unused-vars
- updateTag('meta', 'property', 'content', name, value);
+function updateLink(rel, href) { // eslint-disable-line no-unused-vars
+ updateTag('link', 'rel', rel, 'href', href);
}
-// Restore the scroll position if it was saved into the state
-let locationStates = {};
-function restoreScrollPosition({ key, hash }) {
- let scrollX = 0;
- let scrollY = 0;
- const state = locationStates[key];
- if (state) {
- scrollX = state.scrollX;
- scrollY = state.scrollY;
- } else {
- const targetHash = hash && hash.substr(1);
- if (targetHash) {
- const target = document.getElementById(targetHash);
- if (target) {
- scrollY = windowScrollY() + target.getBoundingClientRect().top;
- }
- }
- }
-
- window.scrollTo(scrollX, scrollY);
+// Switch off the native scroll restoration behavior and handle it manually
+// https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration
+const scrollPositionsHistory = {};
+if (window.history && 'scrollRestoration' in window.history) {
+ window.history.scrollRestoration = 'manual';
}
let onRenderComplete = function initialRenderComplete() {
@@ -91,19 +70,38 @@ let onRenderComplete = function initialRenderComplete() {
document.title = route.title;
updateMeta('description', route.description);
- // Update necessary custom tags at runtime here, ie:
+ // Update necessary tags in <head> at runtime here, ie:
// updateMeta('keywords', route.keywords);
- // updateLink('canonical', route.canonicalUrl);
// updateCustomMeta('og:url', route.canonicalUrl);
// updateCustomMeta('og:image', route.imageUrl);
+ // updateLink('canonical', route.canonicalUrl);
// etc.
- restoreScrollPosition(location);
+ let scrollX = 0;
+ let scrollY = 0;
+ const pos = scrollPositionsHistory[location.key];
+ if (pos) {
+ scrollX = pos.scrollX;
+ scrollY = pos.scrollY;
+ } else {
+ const targetHash = location.hash.substr(1);
+ if (targetHash) {
+ const target = document.getElementById(targetHash);
+ if (target) {
+ scrollY = window.pageYOffset + target.getBoundingClientRect().top;
+ }
+ }
+ }
+
+ // Restore the scroll position if it was saved into the state
+ // or scroll to the given #hash anchor
+ // or scroll to top of the page
+ window.scrollTo(scrollX, scrollY);
// Google Analytics tracking. Don't send 'pageview' event after
// the initial rendering, as it was already sent
if (window.ga) {
- window.ga('send', 'pageview', location.pathname + location.search);
+ window.ga('send', 'pageview', `${location.pathname}${location.search}`);
}
};
};
@@ -131,13 +129,14 @@ let routes = require('./routes').default;
// Re-render the app when window.location changes
async function onLocationChange(location) {
- // Save the page scroll position into the current location's state
- locationStates[currentLocation.key] = {
- scrollX: windowScrollX(),
- scrollY: windowScrollY(),
+ // Remember the latest scroll position for the previous location
+ scrollPositionsHistory[currentLocation.key] = {
+ scrollX: window.pageXOffset,
+ scrollY: window.pageYOffset,
};
+ // Delete stored scroll position for next page if any
if (history.action === 'PUSH') {
- delete locationStates[location.key];
+ delete scrollPositionsHistory[location.key];
}
currentLocation = location;
@@ -145,44 +144,27 @@ async function onLocationChange(location) {
const route = await UniversalRouter.resolve(routes, {
path: location.pathname,
query: queryString.parse(location.search),
- state: location.state,
});
await render(route, location);
} catch (err) {
- // TODO: Inform the user about the failed page transition.
+ if (process.env.NODE_ENV !== 'production') {
+ throw err;
+ }
console.error(err); // eslint-disable-line no-console
}
}
-// Add History API listener and trigger initial change
-const removeHistoryListener = context.history.listen(onLocationChange);
+// Handle client-side navigation by using HTML5 History API
+// For more information visit https://github.com/mjackson/history#readme
+context.history.listen(onLocationChange);
onLocationChange(currentLocation);
-// Switch off the native scroll restoration behavior and handle it manually
-// https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration
-let originalScrollRestoration;
-if (window.history && 'scrollRestoration' in window.history) {
- originalScrollRestoration = window.history.scrollRestoration;
- window.history.scrollRestoration = 'manual';
-}
-
-// Prevent listeners collisions during history navigation
-addEventListener(window, 'pagehide', function onPageHide() {
- removeEventListener(window, 'pagehide', onPageHide);
- removeHistoryListener();
- locationStates = {};
- if (originalScrollRestoration) {
- window.history.scrollRestoration = originalScrollRestoration;
- originalScrollRestoration = undefined;
- }
-});
-
// Enable Hot Module Replacement (HMR)
if (module.hot) {
module.hot.accept('./routes', () => {
routes = require('./routes').default; // eslint-disable-line global-require
- onLocationChange(context.history.location);
+ onLocationChange(currentLocation);
});
}
diff --git a/src/components/Html.js b/src/components/Html.js
@@ -41,16 +41,11 @@ function Html({ title, description, style, script, children }) {
}
Html.propTypes = {
- title: PropTypes.string,
- description: PropTypes.string,
+ title: PropTypes.string.isRequired,
+ description: PropTypes.string.isRequired,
style: PropTypes.string,
script: PropTypes.string,
children: PropTypes.string,
};
-Html.defaultProps = {
- title: '',
- description: '',
-};
-
export default Html;
diff --git a/src/core/DOMUtils.js b/src/core/DOMUtils.js
@@ -1,34 +0,0 @@
-/**
- * React Starter Kit (https://www.reactstarterkit.com/)
- *
- * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE.txt file in the root directory of this source tree.
- */
-
-export function addEventListener(node, event, listener) {
- if (node.addEventListener) {
- node.addEventListener(event, listener, false);
- } else {
- node.attachEvent(`on${event}`, listener);
- }
-}
-
-export function removeEventListener(node, event, listener) {
- if (node.removeEventListener) {
- node.removeEventListener(event, listener, false);
- } else {
- node.detachEvent(`on${event}`, listener);
- }
-}
-
-export function windowScrollX() {
- return (window.pageXOffset !== undefined) ? window.pageXOffset :
- (document.documentElement || document.body.parentNode || document.body).scrollLeft;
-}
-
-export function windowScrollY() {
- return (window.pageYOffset !== undefined) ? window.pageYOffset :
- (document.documentElement || document.body.parentNode || document.body).scrollTop;
-}
diff --git a/src/routes/error/ErrorPage.js b/src/routes/error/ErrorPage.js
@@ -9,27 +9,24 @@
import React, { PropTypes } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
-import Layout from '../../components/Layout';
import s from './ErrorPage.css';
function ErrorPage({ error }) {
- if (process.env.NODE_ENV === 'production') {
+ if (process.env.NODE_ENV !== 'production') {
return (
<div>
- <h1>Error</h1>
- <p>Sorry, a critical error occurred on this page.</p>
+ <h1>{error.name}</h1>
+ <p>{error.message}</p>
+ <pre>{error.stack}</pre>
</div>
);
}
return (
- <Layout>
- <div>
- <h1>{error.name}</h1>
- <p>{error.message}</p>
- <pre>{error.stack}</pre>
- </div>
- </Layout>
+ <div>
+ <h1>Error</h1>
+ <p>Sorry, a critical error occurred on this page.</p>
+ </div>
);
}
diff --git a/src/server.js b/src/server.js
@@ -107,7 +107,6 @@ app.get('*', async (req, res, next) => {
const route = await UniversalRouter.resolve(routes, {
path: req.path,
query: req.query,
- context,
});
const data = { ...route };