Skip to content

Commit 6a3d3bf

Browse files
authored
feat: Merge pull request #297 from jaysoo/issues/291-fouc
feat: Adds support for synchronously updated tags (Closes #291)
2 parents c947ede + b22e2f4 commit 6a3d3bf

File tree

5 files changed

+125
-32
lines changed

5 files changed

+125
-32
lines changed

src/Helmet.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const Helmet = Component =>
1717
* @param {Object} base: {"target": "_blank", "href": "http://mysite.com/"}
1818
* @param {Object} bodyAttributes: {"className": "root"}
1919
* @param {String} defaultTitle: "Default Title"
20+
* @param {Boolean} defer: true
2021
* @param {Boolean} encodeSpecialCharacters: true
2122
* @param {Object} htmlAttributes: {"lang": "en", "amp": undefined}
2223
* @param {Array} link: [{"rel": "canonical", "href": "http://mysite.com/example"}]
@@ -37,6 +38,7 @@ const Helmet = Component =>
3738
PropTypes.node
3839
]),
3940
defaultTitle: PropTypes.string,
41+
defer: PropTypes.bool,
4042
encodeSpecialCharacters: PropTypes.bool,
4143
htmlAttributes: PropTypes.object,
4244
link: PropTypes.arrayOf(PropTypes.object),
@@ -51,6 +53,7 @@ const Helmet = Component =>
5153
};
5254

5355
static defaultProps = {
56+
defer: true,
5457
encodeSpecialCharacters: true
5558
};
5659

src/HelmetConstants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const REACT_TAG_MAP = {
4747

4848
export const HELMET_PROPS = {
4949
DEFAULT_TITLE: "defaultTitle",
50+
DEFER: "defer",
5051
ENCODE_SPECIAL_CHARACTERS: "encodeSpecialCharacters",
5152
ON_CHANGE_CLIENT_STATE: "onChangeClientState",
5253
TITLE_TEMPLATE: "titleTemplate"

src/HelmetUtils.js

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ const getInnermostProperty = (propsList, property) => {
203203
const reducePropsToState = propsList => ({
204204
baseTag: getBaseTagFromPropsList([TAG_PROPERTIES.HREF], propsList),
205205
bodyAttributes: getAttributesFromPropsList(ATTRIBUTE_NAMES.BODY, propsList),
206+
defer: getInnermostProperty(propsList, HELMET_PROPS.DEFER),
206207
encode: getInnermostProperty(
207208
propsList,
208209
HELMET_PROPS.ENCODE_SPECIAL_CHARACTERS
@@ -286,6 +287,23 @@ const warn = msg => {
286287
let _helmetIdleCallback = null;
287288

288289
const handleClientStateChange = newState => {
290+
if (_helmetIdleCallback) {
291+
cancelIdleCallback(_helmetIdleCallback);
292+
}
293+
294+
if (newState.defer) {
295+
_helmetIdleCallback = requestIdleCallback(() => {
296+
commitTagChanges(newState, () => {
297+
_helmetIdleCallback = null;
298+
});
299+
});
300+
} else {
301+
commitTagChanges(newState);
302+
_helmetIdleCallback = null;
303+
}
304+
};
305+
306+
const commitTagChanges = (newState, cb) => {
289307
const {
290308
baseTag,
291309
bodyAttributes,
@@ -299,43 +317,37 @@ const handleClientStateChange = newState => {
299317
title,
300318
titleAttributes
301319
} = newState;
320+
updateAttributes(TAG_NAMES.BODY, bodyAttributes);
321+
updateAttributes(TAG_NAMES.HTML, htmlAttributes);
322+
323+
updateTitle(title, titleAttributes);
324+
325+
const tagUpdates = {
326+
baseTag: updateTags(TAG_NAMES.BASE, baseTag),
327+
linkTags: updateTags(TAG_NAMES.LINK, linkTags),
328+
metaTags: updateTags(TAG_NAMES.META, metaTags),
329+
noscriptTags: updateTags(TAG_NAMES.NOSCRIPT, noscriptTags),
330+
scriptTags: updateTags(TAG_NAMES.SCRIPT, scriptTags),
331+
styleTags: updateTags(TAG_NAMES.STYLE, styleTags)
332+
};
302333

303-
if (_helmetIdleCallback) {
304-
cancelIdleCallback(_helmetIdleCallback);
305-
}
306-
307-
_helmetIdleCallback = requestIdleCallback(() => {
308-
updateAttributes(TAG_NAMES.BODY, bodyAttributes);
309-
updateAttributes(TAG_NAMES.HTML, htmlAttributes);
310-
311-
updateTitle(title, titleAttributes);
334+
const addedTags = {};
335+
const removedTags = {};
312336

313-
const tagUpdates = {
314-
baseTag: updateTags(TAG_NAMES.BASE, baseTag),
315-
linkTags: updateTags(TAG_NAMES.LINK, linkTags),
316-
metaTags: updateTags(TAG_NAMES.META, metaTags),
317-
noscriptTags: updateTags(TAG_NAMES.NOSCRIPT, noscriptTags),
318-
scriptTags: updateTags(TAG_NAMES.SCRIPT, scriptTags),
319-
styleTags: updateTags(TAG_NAMES.STYLE, styleTags)
320-
};
337+
Object.keys(tagUpdates).forEach(tagType => {
338+
const {newTags, oldTags} = tagUpdates[tagType];
321339

322-
const addedTags = {};
323-
const removedTags = {};
324-
325-
Object.keys(tagUpdates).forEach(tagType => {
326-
const {newTags, oldTags} = tagUpdates[tagType];
340+
if (newTags.length) {
341+
addedTags[tagType] = newTags;
342+
}
343+
if (oldTags.length) {
344+
removedTags[tagType] = tagUpdates[tagType].oldTags;
345+
}
346+
});
327347

328-
if (newTags.length) {
329-
addedTags[tagType] = newTags;
330-
}
331-
if (oldTags.length) {
332-
removedTags[tagType] = tagUpdates[tagType].oldTags;
333-
}
334-
});
348+
cb && cb();
335349

336-
_helmetIdleCallback = null;
337-
onChangeClientState(newState, addedTags, removedTags);
338-
});
350+
onChangeClientState(newState, addedTags, removedTags);
339351
};
340352

341353
const flattenArray = possibleArray => {

test/HelmetDeclarativeTest.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2501,6 +2501,42 @@ describe("Helmet - Declarative API", () => {
25012501
});
25022502
});
25032503

2504+
describe("deferred tags", () => {
2505+
beforeEach(() => {
2506+
window.__spy__ = sinon.spy();
2507+
});
2508+
2509+
afterEach(() => {
2510+
delete window.__spy__;
2511+
});
2512+
2513+
it("executes synchronously when defer={true} and async otherwise", done => {
2514+
ReactDOM.render(
2515+
<div>
2516+
<Helmet defer={false}>
2517+
<script>
2518+
window.__spy__(1)
2519+
</script>
2520+
</Helmet>
2521+
<Helmet>
2522+
<script>
2523+
window.__spy__(2)
2524+
</script>
2525+
</Helmet>
2526+
</div>,
2527+
container
2528+
);
2529+
2530+
expect(window.__spy__.callCount).to.equal(1);
2531+
2532+
requestIdleCallback(() => {
2533+
expect(window.__spy__.callCount).to.equal(2);
2534+
expect(window.__spy__.args).to.deep.equal([[1], [2]]);
2535+
done();
2536+
});
2537+
});
2538+
});
2539+
25042540
describe("server", () => {
25052541
const stringifiedHtmlAttributes = `lang="ga" class="myClassName"`;
25062542
const stringifiedBodyAttributes = `lang="ga" class="myClassName"`;

test/HelmetTest.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2271,6 +2271,47 @@ describe("Helmet", () => {
22712271
});
22722272
});
22732273

2274+
describe("deferred tags", () => {
2275+
beforeEach(() => {
2276+
window.__spy__ = sinon.spy();
2277+
});
2278+
2279+
afterEach(() => {
2280+
delete window.__spy__;
2281+
});
2282+
2283+
it("executes synchronously when defer={true} and async otherwise", done => {
2284+
ReactDOM.render(
2285+
<div>
2286+
<Helmet
2287+
defer={false}
2288+
script={[
2289+
{
2290+
innerHTML: `window.__spy__(1)`
2291+
}
2292+
]}
2293+
/>
2294+
<Helmet
2295+
script={[
2296+
{
2297+
innerHTML: `window.__spy__(2)`
2298+
}
2299+
]}
2300+
/>
2301+
</div>,
2302+
container
2303+
);
2304+
2305+
expect(window.__spy__.callCount).to.equal(1);
2306+
2307+
requestIdleCallback(() => {
2308+
expect(window.__spy__.callCount).to.equal(2);
2309+
expect(window.__spy__.args).to.deep.equal([[1], [2]]);
2310+
done();
2311+
});
2312+
});
2313+
});
2314+
22742315
describe("server", () => {
22752316
const stringifiedHtmlAttributes = `lang="ga" class="myClassName"`;
22762317
const stringifiedTitle = `<title ${HELMET_ATTRIBUTE}="true">Dangerous &lt;script&gt; include</title>`;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy