From e1d53c598e1abf90af1186aa09dcc8ba019f1691 Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Fri, 17 Apr 2020 16:59:10 -0700 Subject: [PATCH] Refactor fusion-plugin-react-router to make depending on browser history easier --- fusion-plugin-react-router/src/plugin.js | 230 +++++++++++++---------- 1 file changed, 132 insertions(+), 98 deletions(-) diff --git a/fusion-plugin-react-router/src/plugin.js b/fusion-plugin-react-router/src/plugin.js index c4a0d9acf3..783613db82 100644 --- a/fusion-plugin-react-router/src/plugin.js +++ b/fusion-plugin-react-router/src/plugin.js @@ -18,6 +18,7 @@ import { unescape, memoize, RouteTagsToken, + RoutePrefixToken, } from 'fusion-core'; import type {Token, Context, FusionPlugin} from 'fusion-core'; @@ -60,18 +61,37 @@ type PluginDepsType = { RouteTags: typeof RouteTagsToken, }; -// Preserve browser history instance across HMR +// Preserving browser history across hmr fixes warning "Warning: You cannot change " +// we don't want to preserve the `browserHistory` instance across jsdom tests however, as it will cause +// routes to match based on the previous location information. +// Note that for a given client, the route prefix is static. +// That makes it safe to do this caching. let browserHistory; +function getBrowserHistory(basename) { + if (!browserHistory || + (__DEV__ && typeof window.jsdom !== 'undefined')) { + browserHistory = createBrowserHistory({basename}); + } + return browserHistory; +} -const plugin: FusionPlugin = createPlugin({ +export const BrowserHistoryToken = createToken('BrowserHistory'); + +export const browserHistoryPlugin = createPlugin({ + deps: {prefix: RoutePrefixToken}, + provides: ({prefix}) => getBrowserHistory(prefix) +}); + +const browserPlugin: FusionPlugin = createPlugin({ deps: { emitter: UniversalEventsToken.optional, Provider: RouterProviderToken.optional, getStaticContext: GetStaticContextToken.optional, RouteTags: RouteTagsToken, + browserHistory: BrowserHistoryToken }, middleware: ( - {RouteTags, emitter, Provider = DefaultProvider, getStaticContext}, + {RouteTags, emitter, browserHistory, Provider = DefaultProvider, getStaticContext}, self ) => { return async (ctx, next) => { @@ -81,101 +101,23 @@ const plugin: FusionPlugin = createPlugin({ return next(); } const myAPI = self.from(ctx); - if (__NODE__) { - let pageData = { - title: ctx.path, - page: ctx.path, - }; - const context = getStaticContext - ? getStaticContext(ctx) - : { - action: null, - location: null, - set status(code: number) { - ctx.status = code; - }, - set url(url: string) { - const toUrl = addRoutePrefix(url, prefix); - if (typeof toUrl === 'string') { - ctx.redirect(toUrl); - } - }, - }; - // Expose the history object - const history = createServerHistory(prefix, context, prefix + ctx.url); - myAPI.history = history; - ctx.element = ( - { - pageData = d; - tags.name = pageData.title; - tags.page = pageData.page; - }} - basename={prefix} - context={context} - > - {ctx.element} - - ); - return next().then(() => { - ctx.template.body.push( - html` - - ` - ); - - if (emitter) { - const scopedEmitter = emitter.from(ctx); - const emitTiming = type => timing => { - scopedEmitter.emit(type, { - title: pageData.title, - page: pageData.page, - status: ctx.status, - timing, - }); - }; - scopedEmitter.map(payload => { - if (payload && typeof payload === 'object') { - payload.__url__ = pageData.title; - } - return payload; - }); - ctx.timing.end.then(timing => { - emitTiming('pageview:server')(timing); - ctx.timing.render.then(emitTiming('render:server')); - }); + // TODO(#3): We should consider adding render/downstream/upstream timings for the browser + let pageData = {}; + const element = document.getElementById('__ROUTER_DATA__'); + if (element) { + pageData = JSON.parse(unescape(element.textContent)); + tags.name = pageData.title; + tags.page = pageData.page; + } + emitter && + emitter.map(payload => { + if (payload && typeof payload === 'object') { + payload.__url__ = pageData.title; + payload.__urlParams__ = pageData.params; } + return payload; }); - } else if (__BROWSER__) { - // TODO(#3): We should consider adding render/downstream/upstream timings for the browser - let pageData = {}; - const element = document.getElementById('__ROUTER_DATA__'); - if (element) { - pageData = JSON.parse(unescape(element.textContent)); - tags.name = pageData.title; - tags.page = pageData.page; - } - emitter && - emitter.map(payload => { - if (payload && typeof payload === 'object') { - payload.__url__ = pageData.title; - payload.__urlParams__ = pageData.params; - } - return payload; - }); - // preserving browser history across hmr fixes warning "Warning: You cannot change " - // we don't want to preserve the `browserHistory` instance across jsdom tests however, as it will cause - // routes to match based on the previous location information. - if ( - !browserHistory || - (__DEV__ && typeof window.jsdom !== 'undefined') - ) { - browserHistory = createBrowserHistory({basename: ctx.prefix}); - } + // Expose the history object myAPI.history = browserHistory; ctx.element = ( @@ -194,19 +136,111 @@ const plugin: FusionPlugin = createPlugin({ ); return next(); - } }; }, provides() { return { from: memoize(() => { + // Note that `history` is set by the middleware layer above const api: {history: RouterHistoryType} = ({ history: null, }: any); return api; - }), + }) + } + } +}); + +const serverPlugin: FusionPlugin = createPlugin({ + deps: { + emitter: UniversalEventsToken.optional, + Provider: RouterProviderToken.optional, + getStaticContext: GetStaticContextToken.optional, + RouteTags: RouteTagsToken, + }, + middleware: ( + {RouteTags, emitter, Provider = DefaultProvider, getStaticContext}, + self + ) => { + return async (ctx, next) => { + const tags = RouteTags.from(ctx); + const prefix = ctx.prefix || ''; + if (!ctx.element) { + return next(); + } + const myAPI = self.from(ctx); + let pageData = { + title: ctx.path, + page: ctx.path, + }; + const context = getStaticContext + ? getStaticContext(ctx) + : { + action: null, + location: null, + set status(code: number) { + ctx.status = code; + }, + set url(url: string) { + const toUrl = addRoutePrefix(url, prefix); + if (typeof toUrl === 'string') { + ctx.redirect(toUrl); + } + }, + }; + // Expose the history object + const history = createServerHistory(prefix, context, prefix + ctx.url); + myAPI.history = history; + ctx.element = ( + { + pageData = d; + tags.name = pageData.title; + tags.page = pageData.page; + }} + basename={prefix} + context={context} + > + {ctx.element} + + ); + return next().then(() => { + ctx.template.body.push( + html` + + ` + ); + + if (emitter) { + const scopedEmitter = emitter.from(ctx); + const emitTiming = type => timing => { + scopedEmitter.emit(type, { + title: pageData.title, + page: pageData.page, + status: ctx.status, + timing, + }); + }; + scopedEmitter.map(payload => { + if (payload && typeof payload === 'object') { + payload.__url__ = pageData.title; + } + return payload; + }); + ctx.timing.end.then(timing => { + emitTiming('pageview:server')(timing); + ctx.timing.render.then(emitTiming('render:server')); + }); + } + }); }; }, }); +const plugin = __NODE__ ? serverPlugin : browserPlugin; + export default plugin;