UNPKG

5.72 kBJavaScriptView Raw
1import React from "react"
2import PropTypes from "prop-types"
3import loader from "./loader"
4import redirects from "./redirects.json"
5import { apiRunner } from "./api-runner-browser"
6import emitter from "./emitter"
7import { navigate as reachNavigate } from "@reach/router"
8import { parsePath } from "gatsby-link"
9
10// Convert to a map for faster lookup in maybeRedirect()
11const redirectMap = redirects.reduce((map, redirect) => {
12 map[redirect.fromPath] = redirect
13 return map
14}, {})
15
16function maybeRedirect(pathname) {
17 const redirect = redirectMap[pathname]
18
19 if (redirect != null) {
20 if (process.env.NODE_ENV !== `production`) {
21 const pageResources = loader.loadPageSync(pathname)
22
23 if (pageResources != null) {
24 console.error(
25 `The route "${pathname}" matches both a page and a redirect; this is probably not intentional.`
26 )
27 }
28 }
29
30 window.___replace(redirect.toPath)
31 return true
32 } else {
33 return false
34 }
35}
36
37const onPreRouteUpdate = (location, prevLocation) => {
38 if (!maybeRedirect(location.pathname)) {
39 apiRunner(`onPreRouteUpdate`, { location, prevLocation })
40 }
41}
42
43const onRouteUpdate = (location, prevLocation) => {
44 if (!maybeRedirect(location.pathname)) {
45 apiRunner(`onRouteUpdate`, { location, prevLocation })
46
47 // Temp hack while awaiting https://github.com/reach/router/issues/119
48 window.__navigatingToLink = false
49 }
50}
51
52const navigate = (to, options = {}) => {
53 // Temp hack while awaiting https://github.com/reach/router/issues/119
54 if (!options.replace) {
55 window.__navigatingToLink = true
56 }
57
58 let { pathname } = parsePath(to)
59 const redirect = redirectMap[pathname]
60
61 // If we're redirecting, just replace the passed in pathname
62 // to the one we want to redirect to.
63 if (redirect) {
64 to = redirect.toPath
65 pathname = parsePath(to).pathname
66 }
67
68 // If we had a service worker update, no matter the path, reload window and
69 // reset the pathname whitelist
70 if (window.___swUpdated) {
71 window.location = pathname
72 return
73 }
74
75 // Start a timer to wait for a second before transitioning and showing a
76 // loader in case resources aren't around yet.
77 const timeoutId = setTimeout(() => {
78 emitter.emit(`onDelayedLoadPageResources`, { pathname })
79 apiRunner(`onRouteUpdateDelayed`, {
80 location: window.location,
81 })
82 }, 1000)
83
84 loader.loadPage(pathname).then(pageResources => {
85 // If no page resources, then refresh the page
86 // Do this, rather than simply `window.location.reload()`, so that
87 // pressing the back/forward buttons work - otherwise when pressing
88 // back, the browser will just change the URL and expect JS to handle
89 // the change, which won't always work since it might not be a Gatsby
90 // page.
91 if (!pageResources || pageResources.status === `error`) {
92 window.history.replaceState({}, ``, location.href)
93 window.location = pathname
94 }
95 // If the loaded page has a different compilation hash to the
96 // window, then a rebuild has occurred on the server. Reload.
97 if (process.env.NODE_ENV === `production` && pageResources) {
98 if (
99 pageResources.page.webpackCompilationHash !==
100 window.___webpackCompilationHash
101 ) {
102 // Purge plugin-offline cache
103 if (
104 `serviceWorker` in navigator &&
105 navigator.serviceWorker.controller !== null &&
106 navigator.serviceWorker.controller.state === `activated`
107 ) {
108 navigator.serviceWorker.controller.postMessage({
109 gatsbyApi: `resetWhitelist`,
110 })
111 }
112
113 console.log(`Site has changed on server. Reloading browser`)
114 window.location = pathname
115 }
116 }
117 reachNavigate(to, options)
118 clearTimeout(timeoutId)
119 })
120}
121
122function shouldUpdateScroll(prevRouterProps, { location }) {
123 const { pathname, hash } = location
124 const results = apiRunner(`shouldUpdateScroll`, {
125 prevRouterProps,
126 // `pathname` for backwards compatibility
127 pathname,
128 routerProps: { location },
129 getSavedScrollPosition: args => this._stateStorage.read(args),
130 })
131 if (results.length > 0) {
132 return results[0]
133 }
134
135 if (prevRouterProps) {
136 const {
137 location: { pathname: oldPathname },
138 } = prevRouterProps
139 if (oldPathname === pathname) {
140 // Scroll to element if it exists, if it doesn't, or no hash is provided,
141 // scroll to top.
142 return hash ? hash.slice(1) : [0, 0]
143 }
144 }
145 return true
146}
147
148function init() {
149 // Temp hack while awaiting https://github.com/reach/router/issues/119
150 window.__navigatingToLink = false
151
152 window.___loader = loader
153 window.___push = to => navigate(to, { replace: false })
154 window.___replace = to => navigate(to, { replace: true })
155 window.___navigate = (to, options) => navigate(to, options)
156
157 // Check for initial page-load redirect
158 maybeRedirect(window.location.pathname)
159}
160
161// Fire on(Pre)RouteUpdate APIs
162class RouteUpdates extends React.Component {
163 constructor(props) {
164 super(props)
165 onPreRouteUpdate(props.location, null)
166 }
167
168 componentDidMount() {
169 onRouteUpdate(this.props.location, null)
170 }
171
172 componentDidUpdate(prevProps, prevState, shouldFireRouteUpdate) {
173 if (shouldFireRouteUpdate) {
174 onRouteUpdate(this.props.location, prevProps.location)
175 }
176 }
177
178 getSnapshotBeforeUpdate(prevProps) {
179 if (this.props.location.pathname !== prevProps.location.pathname) {
180 onPreRouteUpdate(this.props.location, prevProps.location)
181 return true
182 }
183
184 return false
185 }
186
187 render() {
188 return this.props.children
189 }
190}
191
192RouteUpdates.propTypes = {
193 location: PropTypes.object.isRequired,
194}
195
196export { init, shouldUpdateScroll, RouteUpdates }