diff --git a/src/HelmetUtils.js b/src/HelmetUtils.js
index 3abf32fe..fe854ba9 100644
--- a/src/HelmetUtils.js
+++ b/src/HelmetUtils.js
@@ -304,6 +304,27 @@ const handleClientStateChange = newState => {
cancelIdleCallback(_helmetIdleCallback);
}
+ const splitUpdates = {
+ baseTag: splitSyncAndDeferred(baseTag),
+ linkTags: splitSyncAndDeferred(linkTags),
+ metaTags: splitSyncAndDeferred(metaTags),
+ noscriptTags: splitSyncAndDeferred(noscriptTags),
+ scriptTags: splitSyncAndDeferred(scriptTags),
+ styleTags: splitSyncAndDeferred(styleTags)
+ };
+
+ const syncUpdates = {
+ baseTag: updateTags(TAG_NAMES.BASE, splitUpdates.baseTag.sync),
+ linkTags: updateTags(TAG_NAMES.LINK, splitUpdates.linkTags.sync),
+ metaTags: updateTags(TAG_NAMES.META, splitUpdates.metaTags.sync),
+ noscriptTags: updateTags(
+ TAG_NAMES.NOSCRIPT,
+ splitUpdates.noscriptTags.sync
+ ),
+ scriptTags: updateTags(TAG_NAMES.SCRIPT, splitUpdates.scriptTags.sync),
+ styleTags: updateTags(TAG_NAMES.STYLE, splitUpdates.styleTags.sync)
+ };
+
_helmetIdleCallback = requestIdleCallback(() => {
updateAttributes(TAG_NAMES.BODY, bodyAttributes);
updateAttributes(TAG_NAMES.HTML, htmlAttributes);
@@ -311,12 +332,27 @@ const handleClientStateChange = newState => {
updateTitle(title, titleAttributes);
const tagUpdates = {
- baseTag: updateTags(TAG_NAMES.BASE, baseTag),
- linkTags: updateTags(TAG_NAMES.LINK, linkTags),
- metaTags: updateTags(TAG_NAMES.META, metaTags),
- noscriptTags: updateTags(TAG_NAMES.NOSCRIPT, noscriptTags),
- scriptTags: updateTags(TAG_NAMES.SCRIPT, scriptTags),
- styleTags: updateTags(TAG_NAMES.STYLE, styleTags)
+ baseTag: syncUpdates.baseTag.concat(
+ updateTags(TAG_NAMES.BASE, splitUpdates.baseTag.deferred)
+ ),
+ linkTags: syncUpdates.linkTags.concat(
+ updateTags(TAG_NAMES.LINK, splitUpdates.linkTags.deferred)
+ ),
+ metaTags: syncUpdates.metaTags.concat(
+ updateTags(TAG_NAMES.META, splitUpdates.metaTags.deferred)
+ ),
+ noscriptTags: syncUpdates.noscriptTags.concat(
+ updateTags(
+ TAG_NAMES.NOSCRIPT,
+ splitUpdates.noscriptTags.deferred
+ )
+ ),
+ scriptTags: syncUpdates.scriptTags.concat(
+ updateTags(TAG_NAMES.SCRIPT, splitUpdates.scriptTags.deferred)
+ ),
+ styleTags: syncUpdates.styleTags.concat(
+ updateTags(TAG_NAMES.STYLE, splitUpdates.styleTags.deferred)
+ )
};
const addedTags = {};
@@ -450,10 +486,68 @@ const updateTags = (type, tags) => {
oldTags.forEach(tag => tag.parentNode.removeChild(tag));
newTags.forEach(tag => headElement.appendChild(tag));
- return {
+ return Update({
oldTags,
newTags
- };
+ });
+};
+
+// Helper object that implements concat method that takes care of handling
+// intersection between old and new tags. (implements Semigroup)
+const Update = ({oldTags, newTags}) => {
+ return Object.create(Object.prototype, {
+ oldTags: {
+ enumerable: true,
+ value: oldTags
+ },
+ newTags: {
+ enumerable: true,
+ value: newTags
+ },
+ concat: {
+ value: other => {
+ const mergedNewTags = newTags.concat(other.newTags);
+ const mergedOldTags = oldTags.concat(other.oldTags);
+
+ // Remove intersection between old and new tags since they cancel
+ // each other out.
+ for (let i = 0; i < mergedNewTags.length; i++) {
+ for (let j = 0; j < mergedOldTags.length; j++) {
+ if (mergedNewTags[i].isEqualNode(mergedOldTags[j])) {
+ mergedNewTags.splice(i, 1);
+ mergedOldTags.splice(j, 1);
+ }
+ }
+ }
+
+ return Update({
+ newTags: mergedNewTags,
+ oldTags: mergedOldTags
+ });
+ }
+ }
+ });
+};
+
+const splitSyncAndDeferred = tags => {
+ return (
+ tags &&
+ tags.reduce(
+ (acc, tag) => {
+ // undefined counts as sync
+ if (tag.defer === false) {
+ acc.sync.push(tag);
+ } else {
+ acc.deferred.push(tag);
+ }
+ return acc;
+ },
+ {
+ deferred: [],
+ sync: []
+ }
+ )
+ );
};
const generateElementAttributesAsString = attributes =>
diff --git a/test/HelmetDeclarativeTest.js b/test/HelmetDeclarativeTest.js
index d7c3bd0b..ff06e2b6 100644
--- a/test/HelmetDeclarativeTest.js
+++ b/test/HelmetDeclarativeTest.js
@@ -2501,6 +2501,41 @@ describe("Helmet - Declarative API", () => {
});
});
+ describe("deferred tags", () => {
+ beforeEach(() => {
+ window.__spy__ = sinon.spy();
+ });
+
+ afterEach(() => {
+ delete window.__spy__;
+ });
+
+ it("executes synchronously when defer={true} and async otherwise", done => {
+ ReactDOM.render(
+