Skip to content

Commit f3da912

Browse files
committed
Server rendered: option to inject extra scripts into different locations in the template
1 parent 3508746 commit f3da912

File tree

5 files changed

+82
-7
lines changed

5 files changed

+82
-7
lines changed

__tests__/server/__snapshots__/renderer.jsx.snap

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ exports[`Base rendering of HTML template 1`] = `
66
<head>
77
88
9+
910
<link
1011
href=\\"/test/public/path/main-1511941200000.css\\"
1112
id=\\"tru-style\\"
@@ -20,6 +21,7 @@ exports[`Base rendering of HTML template 1`] = `
2021
/>
2122
</head>
2223
<body>
24+
2325
<div id=\\"react-view\\"></div>
2426
<script id=\\"inj\\" type=\\"application/javascript\\">
2527
window.SPLITS = {}
@@ -44,6 +46,7 @@ exports[`Config overriding for injection 1`] = `
4446
<head>
4547
4648
49+
4750
<link
4851
href=\\"/test/public/path/main-1511941200000.css\\"
4952
id=\\"tru-style\\"
@@ -58,6 +61,7 @@ exports[`Config overriding for injection 1`] = `
5861
/>
5962
</head>
6063
<body>
64+
6165
<div id=\\"react-view\\"></div>
6266
<script id=\\"inj\\" type=\\"application/javascript\\">
6367
window.SPLITS = {}
@@ -80,6 +84,7 @@ exports[`Hemlet integration works 1`] = `
8084
"<!DOCTYPE html>
8185
<html lang=\\"en\\">
8286
<head>
87+
8388
<title data-react-helmet=\\"true\\">Test Page Title</title>
8489
<meta data-react-helmet=\\"true\\" property=\\"description\\" content=\\"Test Page Description\\"/>
8590
<link
@@ -96,6 +101,7 @@ exports[`Hemlet integration works 1`] = `
96101
/>
97102
</head>
98103
<body>
104+
99105
<div id=\\"react-view\\"><div data-reactroot=\\"\\"><p>Hello World!</p><p>Goodbye World!</p></div></div>
100106
<script id=\\"inj\\" type=\\"application/javascript\\">
101107
window.SPLITS = {}
@@ -118,6 +124,7 @@ exports[`Injection of additional JS scripts 1`] = `
118124
"<!DOCTYPE html>
119125
<html lang=\\"en\\">
120126
<head>
127+
<script>1-st script after opening <head></script><script>2-nd script after opening <head></script>
121128
122129
123130
<link
@@ -134,6 +141,7 @@ exports[`Injection of additional JS scripts 1`] = `
134141
/>
135142
</head>
136143
<body>
144+
<script>1-st script after opening <body></script><script>2-nd script after opening <body></script>
137145
<div id=\\"react-view\\"></div>
138146
<script id=\\"inj\\" type=\\"application/javascript\\">
139147
window.SPLITS = {}
@@ -143,7 +151,7 @@ exports[`Injection of additional JS scripts 1`] = `
143151
src=\\"/test/public/path/polyfills-1511941200000.js\\"
144152
type=\\"application/javascript\\"
145153
></script>
146-
<script>Dummy JS Sript</script><script>Another Dummy JS Script</script>
154+
<script>Dummy JS Sript</script><script>Another Dummy JS Script</script><script>Yet another Dummy JS Script</script>
147155
<script
148156
src=\\"/test/public/path/main-1511941200000.js\\"
149157
type=\\"application/javascript\\"
@@ -156,6 +164,7 @@ exports[`Server-side rendering (SSR); injection of CSS chunks & Redux state 1`]
156164
"<!DOCTYPE html>
157165
<html lang=\\"en\\">
158166
<head>
167+
159168
<title data-react-helmet=\\"true\\"></title>
160169
161170
<link
@@ -172,6 +181,7 @@ exports[`Server-side rendering (SSR); injection of CSS chunks & Redux state 1`]
172181
/>
173182
</head>
174183
<body>
184+
175185
<div id=\\"react-view\\"><div data-reactroot=\\"\\">Hello Wold!</div></div>
176186
<script id=\\"inj\\" type=\\"application/javascript\\">
177187
window.SPLITS = {}
@@ -195,6 +205,7 @@ exports[`Setting of response HTTP status the server-side rendering 1`] = `
195205
<!DOCTYPE html>
196206
<html lang=\\"en\\">
197207
<head>
208+
198209
<title data-react-helmet=\\"true\\"></title>
199210
200211
<link
@@ -211,6 +222,7 @@ exports[`Setting of response HTTP status the server-side rendering 1`] = `
211222
/>
212223
</head>
213224
<body>
225+
214226
<div id=\\"react-view\\"><div data-reactroot=\\"\\">404 Error Test</div></div>
215227
<script id=\\"inj\\" type=\\"application/javascript\\">
216228
window.SPLITS = {}

__tests__/server/renderer.jsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import _ from 'lodash';
2-
import factory from 'server/renderer';
2+
import factory, { SCRIPT_LOCATIONS } from 'server/renderer';
33
import fs from 'fs';
44
import React from 'react';
55

@@ -142,7 +142,22 @@ test('Injection of additional JS scripts',
142142
beforeRender: async () => ({
143143
extraScripts: [
144144
'<script>Dummy JS Sript</script>',
145-
'<script>Another Dummy JS Script</script>',
145+
'<script>Another Dummy JS Script</script>', {
146+
code: '<script>Yet another Dummy JS Script</script>',
147+
location: SCRIPT_LOCATIONS.DEFAULT,
148+
}, {
149+
code: '<script>1-st script after opening <head></script>',
150+
location: SCRIPT_LOCATIONS.HEAD_OPEN,
151+
}, {
152+
code: '<script>1-st script after opening <body></script>',
153+
location: SCRIPT_LOCATIONS.BODY_OPEN,
154+
}, {
155+
code: '<script>2-nd script after opening <body></script>',
156+
location: SCRIPT_LOCATIONS.BODY_OPEN,
157+
}, {
158+
code: '<script>2-nd script after opening <head></script>',
159+
location: SCRIPT_LOCATIONS.HEAD_OPEN,
160+
},
146161
],
147162
}),
148163
}));

docs/server.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,22 @@ props:
6060
object
6161
to be injected into the page. If omitted, the one proposed by the server
6262
will be used.
63-
- **`extraScripts`** &mdash; *String[]* &mdash; Additional script tags to be
64-
injected into the page.
63+
- **`extraScripts`** &mdash; *Object[]* | *String[]* &mdash; Additional script
64+
tags to be injected into the page. Each script given as a string will be
65+
injected in the end of document's `<body>`, but immediately before the main
66+
application bundle. Each script given as object should have two fields:
67+
`code` specifies the actual code to inject, and `location` which specifies
68+
location, where the script should be injected. Possible locations are
69+
given in the `server.SCRIPT_LOCATIONS` object:
70+
- `server.SCRIPT_LOCATIONS.BODY_OPEN` - right after the openning `<body>`
71+
tag;
72+
- `server.SCRIPT_LOCATIONS.DEFAULT` - default locations described above;
73+
- `server.SCRIPT_LOCATIONS.HEAD_OPEN` - right after the openning `<head>`
74+
tag;
75+
76+
When a few scripts have the same location, they all are injected into that
77+
location in the order they are given in the `extraScripts` array.
78+
6579
- **`store`** &mdash; *Object* &mdash; Redux store which state will be
6680
injected into HTML template as the initial state of the app.
6781

src/server/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import https from 'https';
1010
import 'raf/polyfill';
1111

1212
import serverFactory from './server';
13+
import { SCRIPT_LOCATIONS } from './renderer';
1314

1415
/**
1516
* Normalizes a port into a number, string, or false.
@@ -57,7 +58,7 @@ function normalizePort(value) {
5758
* - express {Object} - ExpressJS server;
5859
* - httpServer {Object} - NodeJS HTTP(S) server.
5960
*/
60-
export default async function launch(webpackConfig, options) {
61+
async function launch(webpackConfig, options) {
6162
/* Options normalization. */
6263
const ops = options ? _.clone(options) : {};
6364
ops.port = normalizePort(ops.port || process.env.PORT || 3000);
@@ -111,3 +112,7 @@ export default async function launch(webpackConfig, options) {
111112
httpServer,
112113
};
113114
}
115+
116+
launch.SCRIPT_LOCATIONS = SCRIPT_LOCATIONS;
117+
118+
export default launch;

src/server/renderer.jsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ import { StaticRouter } from 'react-router-dom';
1818

1919
const sanitizedConfig = _.omit(config, 'SECRET');
2020

21+
export const SCRIPT_LOCATIONS = {
22+
BODY_OPEN: 'BODY_OPEN',
23+
DEFAULT: 'DEFAULT',
24+
HEAD_OPEN: 'HEAD_OPEN',
25+
};
26+
2127
/**
2228
* Reads build-time information about the app. This information is generated
2329
* by our standard Webpack config for apps, and it is written into
@@ -182,10 +188,32 @@ export default function factory(webpackConfig, options) {
182188
});
183189
});
184190

191+
let bodyOpenExtraScripts;
192+
let defaultExtraScripts;
193+
let headOpenExtraScripts;
194+
if (extraScripts) {
195+
bodyOpenExtraScripts = extraScripts
196+
.filter(script => _.isObject(script)
197+
&& script.location === SCRIPT_LOCATIONS.BODY_OPEN)
198+
.map(script => script.code)
199+
.join('');
200+
defaultExtraScripts = extraScripts
201+
.filter(script => _.isString(script)
202+
|| script.location === SCRIPT_LOCATIONS.DEFAULT)
203+
.map(script => (_.isString(script) ? script : script.code))
204+
.join('');
205+
headOpenExtraScripts = extraScripts
206+
.filter(script => _.isObject(script)
207+
&& script.location === SCRIPT_LOCATIONS.HEAD_OPEN)
208+
.map(script => script.code)
209+
.join('');
210+
}
211+
185212
res.send((
186213
`<!DOCTYPE html>
187214
<html lang="en">
188215
<head>
216+
${headOpenExtraScripts || ''}
189217
${helmet ? helmet.title.toString() : ''}
190218
${helmet ? helmet.meta.toString() : ''}
191219
<link
@@ -202,6 +230,7 @@ export default function factory(webpackConfig, options) {
202230
/>
203231
</head>
204232
<body>
233+
${bodyOpenExtraScripts || ''}
205234
<div id="react-view">${App || ''}</div>
206235
<script id="inj" type="application/javascript">
207236
window.SPLITS = ${serializeJs(context.splits, { isJSON: true })}
@@ -211,7 +240,7 @@ export default function factory(webpackConfig, options) {
211240
src="${publicPath}polyfills-${timestamp}.js"
212241
type="application/javascript"
213242
></script>
214-
${extraScripts ? extraScripts.join('') : ''}
243+
${defaultExtraScripts || ''}
215244
<script
216245
src="${publicPath}main-${timestamp}.js"
217246
type="application/javascript"

0 commit comments

Comments
 (0)