import 'whatwg-fetch'; import React from 'react'; import ReactDOM from 'react-dom'; import deepForceUpdate from 'react-deep-force-update'; import queryString from 'query-string'; import gql from 'graphql-tag'; import { createPath, Location } from 'history'; import App from '@config@/App'; import createCache from '@config@/createCache'; import createApolloClient from '@config@/createApolloClient.client'; import history from './history'; import { updateMeta } from './DOMUtils'; import router from './router'; import { AppContextTypes } from './AppContext'; import { clientResolvers, clientTypeDefs } from './clientSchema'; const apolloClient = createApolloClient({ clientTypeDefs, clientResolvers, apolloCache: createCache(), }); // Enables critical path CSS rendering // https://github.com/kriasoft/isomorphic-style-loader const insertCss = (...styles: any[]) => { // eslint-disable-next-line no-underscore-dangle const removeCss = styles.map(x => x._insertCss()); return () => { removeCss.forEach(f => f()); }; }; // Global (context) variables that can be easily accessed from any React component // https://facebook.github.io/react/docs/context.html const context: AppContextTypes = { pathname: '' }; const container = document.getElementById('app'); let currentLocation = history.location; let appInstance: typeof App | void; const scrollPositionsHistory: { [key: string]: { scrollX: number; scrollY: number }; } = {}; // Re-render the app when window.location changes async function onLocationChange(location: Location, action?: any) { // 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 (action === 'PUSH') { delete scrollPositionsHistory[location.key || '']; } currentLocation = location; const isInitialRender = !action; try { context.pathname = location.pathname; context.query = queryString.parse(location.search); // Traverses the list of routes in the order they are defined until // it finds the first route that matches provided URL path string // and whose action method returns anything other than `undefined`. const route = await router.resolve(context); context.params = route.params; // Prevent multiple page renders during the routing process if (currentLocation.key !== location.key) { return; } if (route.redirect) { history.replace(route.redirect); return; } const renderReactApp = isInitialRender ? ReactDOM.hydrate : ReactDOM.render; appInstance = renderReactApp( {route.component} , container, () => { if (isInitialRender) { // Switch off the native scroll restoration behavior and handle it manually // https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration if (window.history && 'scrollRestoration' in window.history) { window.history.scrollRestoration = 'manual'; } const elem = document.getElementById('css'); if (elem) elem.parentNode!.removeChild(elem); return; } document.title = route.title; updateMeta('description', route.description); // Update necessary tags in at runtime here, ie: // updateMeta('keywords', route.keywords); // updateCustomMeta('og:url', route.canonicalUrl); // updateCustomMeta('og:image', route.imageUrl); // updateLink('canonical', route.canonicalUrl); // etc. 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', createPath(location)); } }, ); } catch (error) { if (__DEV__) { throw error; } console.error(error); // Do a full page reload if error occurs during client-side navigation if (!isInitialRender && currentLocation.key === location.key) { console.error('RSK will reload your page after error'); window.location.reload(); } } } // Handle client-side navigation by using HTML5 History API // For more information visit https://github.com/mjackson/history#readme history.listen(onLocationChange); onLocationChange(currentLocation); // Enable Hot Module Replacement (HMR) if (module.hot) { module.hot.accept('./router', () => { // @ts-ignore TODO if (appInstance && appInstance.updater.isMounted(appInstance)) { // Force-update the whole tree, including components that refuse to update deepForceUpdate(appInstance); } onLocationChange(currentLocation); }); } // This is a demonstration of how to mutate the client state of apollo-link-state. // If you don't need the networkStatus, please erase below lines. function onNetworkStatusChange() { apolloClient.mutate({ mutation: gql` mutation updateNetworkStatus($isConnected: Boolean) { updateNetworkStatus(isConnected: $isConnected) @client { isConnected } } `, variables: { isConnected: navigator.onLine, }, }); } window.addEventListener('online', onNetworkStatusChange); window.addEventListener('offline', onNetworkStatusChange); onNetworkStatusChange();