UNPKG

7.91 kBJSXView Raw
1/* global __TIMESTAMP__ */
2
3import React from "react";
4import Helmet from "react-helmet";
5import {renderToString} from "react-dom/server";
6import {createMemoryHistory, match, RouterContext} from "react-router";
7import {I18nextProvider} from "react-i18next";
8import {Provider} from "react-redux";
9import createRoutes from "routes";
10import configureStore from "./storeConfig";
11import preRenderMiddleware from "./middlewares/preRenderMiddleware";
12import pretty from "pretty";
13
14import CanonProvider from "./CanonProvider";
15
16import serialize from "serialize-javascript";
17
18const tagManagerHead = process.env.CANON_GOOGLE_TAG_MANAGER === undefined ? ""
19 : `
20 <!-- Google Tag Manager -->
21 <script>
22 (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
23 new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
24 j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
25 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
26 })(window,document,'script','dataLayer','${process.env.CANON_GOOGLE_TAG_MANAGER}');
27 </script>
28 <!-- End Google Tag Manager -->
29 `;
30
31const tagManagerBody = process.env.CANON_GOOGLE_TAG_MANAGER === undefined ? ""
32 : `
33 <!-- Google Tag Manager (noscript) -->
34 <noscript>
35 <iframe src="https://www.googletagmanager.com/ns.html?id=${process.env.CANON_GOOGLE_TAG_MANAGER}" height="0" width="0" style="display:none;visibility:hidden"></iframe>
36 </noscript>
37 <!-- End Google Tag Manager (noscript) -->
38 `;
39
40const analtyicsScript = process.env.CANON_GOOGLE_ANALYTICS === undefined ? ""
41 : `
42 <!-- Google Analytics -->
43 <script>
44 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
45 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
46 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
47 })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
48 ga('create', '${process.env.CANON_GOOGLE_ANALYTICS}', 'auto');
49 ga('send', 'pageview');
50 </script>
51 <!-- End Google Analytics -->
52 `;
53
54const pixelScript = process.env.CANON_FACEBOOK_PIXEL === undefined ? ""
55 : `
56 <!-- Facebook Pixel -->
57 <script> !function(f,b,e,v,n,t,s) {if(f.fbq)return;n=f.fbq=function(){
58 n.callMethod? n.callMethod.apply(n,arguments):n.queue.push(arguments)};
59 if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
60 n.queue=[];t=b.createElement(e);t.async=!0; t.src=v;s=b.getElementsByTagName(e)[0];
61 s.parentNode.insertBefore(t,s)}(window,document,'script', 'https://connect.facebook.net/en_US/fbevents.js');
62 fbq('init', '${process.env.CANON_FACEBOOK_PIXEL}'); fbq('track', 'PageView');
63 </script>
64 <!-- End Facebook Pixel -->
65 `;
66
67const hotjarScript = process.env.CANON_HOTJAR === undefined ? ""
68 : `
69 <!-- Hotjar -->
70 <script>
71 (function(h,o,t,j,a,r){
72 h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
73 h._hjSettings={hjid:${process.env.CANON_HOTJAR},hjsv:6};
74 a=o.getElementsByTagName('head')[0];
75 r=o.createElement('script');r.async=1;
76 r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
77 a.appendChild(r);
78 })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
79 </script>
80 <!-- End Hotjar -->
81 `;
82
83const BASE_URL = process.env.CANON_BASE_URL || "/";
84const basename = BASE_URL.replace(/^[A-z]{4,5}\:\/{2}[A-z0-9\.\-]{1,}\:{0,}[0-9]{0,4}/g, "");
85const baseTag = process.env.CANON_BASE_URL === undefined ? ""
86 : `
87 <base href='${ BASE_URL }'>`;
88
89/**
90 Returns the default server logic for rendering a page.
91*/
92export default function(defaultStore = {}, headerConfig, reduxMiddleware = false) {
93
94 return function(req, res) {
95
96 const locale = req.i18n.language,
97 resources = req.i18n.getResourceBundle(req.i18n.language);
98
99 const windowLocation = {
100 basename,
101 host: req.headers.host,
102 hostname: req.headers.host.split(":")[0],
103 href: `${ req.protocol }://${ req.headers.host }${ req.url }`,
104 origin: `${ req.protocol }://${ req.headers.host }`,
105 pathname: req.url.split("?")[0],
106 port: req.headers.host.includes(":") ? req.headers.host.split(":")[1] : "80",
107 protocol: `${ req.protocol }:`,
108 query: req.query,
109 search: req.url.includes("?") ? `?${req.url.split("?")[1]}` : ""
110 };
111
112 const location = req.url.replace(BASE_URL, "");
113 const history = createMemoryHistory({basename, entries: [location]});
114 const store = configureStore({i18n: {locale, resources}, location: windowLocation, ...defaultStore}, history, reduxMiddleware);
115 const routes = createRoutes(store);
116 const rtl = ["ar", "he"].includes(locale);
117
118 match({history, routes}, (err, redirect, props) => {
119
120 if (err) res.status(500).json(err);
121 else if (redirect) res.redirect(302, `${redirect.basename}${redirect.pathname}${redirect.hash}${redirect.search}`);
122 else if (props) {
123 // This method waits for all render component
124 // promises to resolve before returning to browser
125 preRenderMiddleware(store, props)
126 .then(() => {
127 const initialState = store.getState();
128 const componentHTML = renderToString(
129 <I18nextProvider i18n={req.i18n}>
130 <Provider store={store}>
131 <CanonProvider helmet={headerConfig} locale={locale}>
132 <RouterContext {...props} />
133 </CanonProvider>
134 </Provider>
135 </I18nextProvider>
136 );
137
138 const header = Helmet.rewind();
139 const htmlAttrs = header.htmlAttributes.toString().replace(" amp", "");
140
141 const defaultAttrs = headerConfig.htmlAttributes ? Object.keys(headerConfig.htmlAttributes)
142 .map(key => {
143 const val = headerConfig.htmlAttributes[key];
144 return ` ${key}${val ? `="${val}"` : ""}`;
145 })
146 .join("") : "";
147
148 let status = 200;
149 for (const key in initialState.data) {
150 if ({}.hasOwnProperty.call(initialState.data, key)) {
151 const error = initialState.data[key] ? initialState.data[key].error : null;
152 if (error && typeof error === "number" && error > status) status = error;
153 }
154 }
155
156 res.status(status).send(`<!doctype html>
157<html dir="${ rtl ? "rtl" : "ltr" }" ${htmlAttrs}${defaultAttrs}>
158 <head>
159 ${tagManagerHead}${pixelScript}${baseTag}
160 ${ pretty(header.title.toString()).replace(/\n/g, "\n ") }
161
162 ${ pretty(header.meta.toString()).replace(/\n/g, "\n ") }
163
164 ${ pretty(header.link.toString()).replace(/\n/g, "\n ") }
165
166 <link rel='stylesheet' type='text/css' href='${ process.env.CANON_BASE_URL ? "" : "/" }assets/normalize.css'>
167 <link rel='stylesheet' type='text/css' href='${ process.env.CANON_BASE_URL ? "" : "/" }assets/styles.css?v${__TIMESTAMP__}'>
168 ${hotjarScript}
169 </head>
170 <body>
171 ${tagManagerBody}
172 <div id="React-Container">${ componentHTML }</div>
173
174 <script>
175 window.__SSR__ = true;
176 window.__APP_NAME__ = "${ req.i18n.options.defaultNS }";
177 window.__HELMET_DEFAULT__ = ${ serialize(headerConfig, {isJSON: true, space: 2}).replace(/\n/g, "\n ") };
178 window.__INITIAL_STATE__ = ${ serialize(initialState, {isJSON: true, space: 2}).replace(/\n/g, "\n ") };
179 </script>
180 ${analtyicsScript}
181 <script type="text/javascript" charset="utf-8" src="${ process.env.CANON_BASE_URL ? "" : "/" }assets/app.js?v${__TIMESTAMP__}"></script>
182
183 </body>
184</html>`);
185 })
186 .catch(err => {
187 res.status(500).send({error: err.toString(), stackTrace: err.stack.toString()});
188 });
189 }
190 else res.sendStatus(404);
191
192 });
193 };
194
195}