Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add onLoad attribute to <link> tag #299

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ function HTML () {
}
`}</script>

{/* link elements */}
<link onLoad="doSomethingInteresting()" rel="stylesheet" media="all" type="text/css" href="critical-style.css" />
<link onLoad="if(media!='all')media='all'" rel="stylesheet" media="none" type="text/css" href="non-critical-style.css" />

{/* noscript elements */}
<noscript>{`
<link rel="stylesheet" type="text/css" href="foo.css" />
Expand Down
2 changes: 1 addition & 1 deletion src/Helmet.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const Helmet = Component =>
* @param {Boolean} defer: true
* @param {Boolean} encodeSpecialCharacters: true
* @param {Object} htmlAttributes: {"lang": "en", "amp": undefined}
* @param {Array} link: [{"rel": "canonical", "href": "http://mysite.com/example"}]
* @param {Array} link: [{"rel": "canonical", "href": "http://mysite.com/example", "onLoad": "functionCall()", "onError": "functionCall()"}]
* @param {Array} meta: [{"name": "description", "content": "Test description"}]
* @param {Array} noscript: [{"innerHTML": "<img src='http://mysite.com/js/test.js'"}]
* @param {Function} onChangeClientState: "(newState) => console.log(newState)"
Expand Down
4 changes: 3 additions & 1 deletion src/HelmetConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export const TAG_PROPERTIES = {
NAME: "name",
PROPERTY: "property",
REL: "rel",
SRC: "src"
SRC: "src",
ONLOAD: "onload",
ONERROR: "onerror"
};

export const REACT_TAG_MAP = {
Expand Down
48 changes: 29 additions & 19 deletions src/HelmetUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,12 @@ const reducePropsToState = propsList => ({
htmlAttributes: getAttributesFromPropsList(ATTRIBUTE_NAMES.HTML, propsList),
linkTags: getTagsFromPropsList(
TAG_NAMES.LINK,
[TAG_PROPERTIES.REL, TAG_PROPERTIES.HREF],
[
TAG_PROPERTIES.REL,
TAG_PROPERTIES.HREF,
TAG_PROPERTIES.ONLOAD,
TAG_PROPERTIES.ONERROR
],
propsList
),
metaTags: getTagsFromPropsList(
Expand Down Expand Up @@ -273,19 +278,21 @@ const rafPolyfill = (() => {

const cafPolyfill = (id: string | number) => clearTimeout(id);

const requestAnimationFrame = typeof window !== "undefined"
? window.requestAnimationFrame ||
const requestAnimationFrame =
typeof window !== "undefined"
? window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
rafPolyfill
: global.requestAnimationFrame || rafPolyfill;
: global.requestAnimationFrame || rafPolyfill;

const cancelAnimationFrame = typeof window !== "undefined"
? window.cancelAnimationFrame ||
const cancelAnimationFrame =
typeof window !== "undefined"
? window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
cafPolyfill
: global.cancelAnimationFrame || cafPolyfill;
: global.cancelAnimationFrame || cafPolyfill;

const warn = msg => {
return console && typeof console.warn === "function" && console.warn(msg);
Expand Down Expand Up @@ -442,9 +449,10 @@ const updateTags = (type, tags) => {
);
}
} else {
const value = typeof tag[attribute] === "undefined"
? ""
: tag[attribute];
const value =
typeof tag[attribute] === "undefined"
? ""
: tag[attribute];
newElement.setAttribute(attribute, value);
}
}
Expand Down Expand Up @@ -477,9 +485,10 @@ const updateTags = (type, tags) => {

const generateElementAttributesAsString = attributes =>
Object.keys(attributes).reduce((str, key) => {
const attr = typeof attributes[key] !== "undefined"
? `${key}="${attributes[key]}"`
: `${key}`;
const attr =
typeof attributes[key] !== "undefined"
? `${key}="${attributes[key]}"`
: `${key}`;
return str ? `${str} ${attr}` : attr;
}, "");

Expand Down Expand Up @@ -508,12 +517,13 @@ const generateTagsAsString = (type, tags, encode) =>
)
)
.reduce((string, attribute) => {
const attr = typeof tag[attribute] === "undefined"
? attribute
: `${attribute}="${encodeSpecialCharacters(
tag[attribute],
encode
)}"`;
const attr =
typeof tag[attribute] === "undefined"
? attribute
: `${attribute}="${encodeSpecialCharacters(
tag[attribute],
encode
)}"`;
return string ? `${string} ${attr}` : attr;
}, "");

Expand Down
143 changes: 122 additions & 21 deletions test/HelmetDeclarativeTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ describe("Helmet - Declarative API", () => {

ReactDOM.render(
<Helmet>
<title>Title: {someValue}</title>
<title>
Title: {someValue}
</title>
</Helmet>,
container
);
Expand Down Expand Up @@ -247,7 +249,9 @@ describe("Helmet - Declarative API", () => {

ReactDOM.render(
<Helmet titleTemplate={"This is a %s"}>
<title>{dollarTitle}</title>
<title>
{dollarTitle}
</title>
</Helmet>,
container
);
Expand All @@ -264,7 +268,9 @@ describe("Helmet - Declarative API", () => {
it("properly handles title with children and titleTemplate", done => {
ReactDOM.render(
<Helmet titleTemplate={"This is an %s"}>
<title>{"extra"} + {"test"}</title>
<title>
{"extra"} + {"test"}
</title>
</Helmet>,
container
);
Expand All @@ -281,7 +287,9 @@ describe("Helmet - Declarative API", () => {

ReactDOM.render(
<Helmet>
<title>{chineseTitle}</title>
<title>
{chineseTitle}
</title>
</Helmet>,
container
);
Expand Down Expand Up @@ -341,7 +349,7 @@ describe("Helmet - Declarative API", () => {

ReactDOM.render(
<Helmet>
<title>{" "}</title>
<title />
<meta name="keywords" content="stuff" />
</Helmet>,
container
Expand Down Expand Up @@ -2073,6 +2081,83 @@ describe("Helmet - Declarative API", () => {
done();
});
});

it("renders tag when onload attribute is present", done => {
ReactDOM.render(
<Helmet>
<link
onLoad="functionCall()"
onError="errorFunctionCall()"
rel="stylesheet"
media="all"
type="text/css"
href="http://localhost/critical-style.css"
/>
<link
onLoad="if(media!='all')media='all'"
rel="stylesheet"
media="none"
type="text/css"
href="http://localhost/non-critical-style.css"
/>
</Helmet>,
container
);

requestAnimationFrame(() => {
const tagNodes = headElement.querySelectorAll(
`link[${HELMET_ATTRIBUTE}]`
);
const existingTags = Array.prototype.slice.call(tagNodes);
const firstTag = existingTags[0];

expect(existingTags).to.not.equal(undefined);
expect(existingTags.length).to.be.equal(2);

expect(existingTags).to.have.deep
.property("[0]")
.that.is.an.instanceof(Element);
expect(firstTag).to.have.property("getAttribute");
expect(firstTag.getAttribute("rel")).to.equal("stylesheet");
expect(firstTag.getAttribute("media")).to.equal("all");
expect(firstTag.getAttribute("type")).to.equal("text/css");
expect(firstTag.getAttribute("onload")).to.equal(
"functionCall()"
);
expect(firstTag.getAttribute("onerror")).to.equal(
"errorFunctionCall()"
);
expect(firstTag.getAttribute("href")).to.equal(
"http://localhost/critical-style.css"
);
expect(firstTag.outerHTML).to.equal(
`<link onload="functionCall()" onerror="errorFunctionCall()" rel="stylesheet" media="all" type="text/css" href="http://localhost/critical-style.css" ${HELMET_ATTRIBUTE}="true">`
);

const secondTag = existingTags[1];

expect(existingTags).to.have.deep
.property("[1]")
.that.is.an.instanceof(Element);
expect(secondTag).to.have.property("getAttribute");
expect(secondTag.getAttribute("rel")).to.equal(
"stylesheet"
);
expect(secondTag.getAttribute("media")).to.equal("none");
expect(secondTag.getAttribute("type")).to.equal("text/css");
expect(secondTag.getAttribute("onload")).to.equal(
"if(media!='all')media='all'"
);
expect(secondTag.getAttribute("href")).to.equal(
"http://localhost/non-critical-style.css"
);
expect(secondTag.outerHTML).to.equal(
`<link onload="if(media!='all')media='all'" rel="stylesheet" media="none" type="text/css" href="http://localhost/non-critical-style.css" ${HELMET_ATTRIBUTE}="true">`
);

done();
});
});
});

describe("script tags", () => {
Expand Down Expand Up @@ -2308,7 +2393,9 @@ describe("Helmet - Declarative API", () => {
const noscriptInnerHTML = `<link rel="stylesheet" type="text/css" href="foo.css" />`;
ReactDOM.render(
<Helmet>
<noscript id="bar">{noscriptInnerHTML}</noscript>
<noscript id="bar">
{noscriptInnerHTML}
</noscript>
</Helmet>,
container
);
Expand Down Expand Up @@ -2376,7 +2463,9 @@ describe("Helmet - Declarative API", () => {
it("does not render tag when primary attribute is null", done => {
ReactDOM.render(
<Helmet>
<noscript>{undefined}</noscript>
<noscript>
{undefined}
</noscript>
</Helmet>,
container
);
Expand Down Expand Up @@ -2408,8 +2497,12 @@ describe("Helmet - Declarative API", () => {

ReactDOM.render(
<Helmet>
<style type="text/css">{cssText1}</style>
<style>{cssText2}</style>
<style type="text/css">
{cssText1}
</style>
<style>
{cssText2}
</style>
</Helmet>,
container
);
Expand Down Expand Up @@ -2454,7 +2547,9 @@ describe("Helmet - Declarative API", () => {
`;
ReactDOM.render(
<Helmet>
<style type="text/css">{cssText}</style>
<style type="text/css">
{cssText}
</style>
</Helmet>,
container
);
Expand Down Expand Up @@ -2498,7 +2593,9 @@ describe("Helmet - Declarative API", () => {
it("does not render tag when primary attribute is null", done => {
ReactDOM.render(
<Helmet>
<style>{undefined}</style>
<style>
{undefined}
</style>
</Helmet>,
container
);
Expand Down Expand Up @@ -2529,14 +2626,10 @@ describe("Helmet - Declarative API", () => {
ReactDOM.render(
<div>
<Helmet defer={false}>
<script>
window.__spy__(1)
</script>
<script>window.__spy__(1)</script>
</Helmet>
<Helmet>
<script>
window.__spy__(2)
</script>
<script>window.__spy__(2)</script>
</Helmet>
</div>,
container
Expand Down Expand Up @@ -2622,7 +2715,9 @@ describe("Helmet - Declarative API", () => {
it("opts out of string encoding", () => {
ReactDOM.render(
<Helmet encodeSpecialCharacters={false}>
<title>{"This is text and & and '."}</title>
<title>
{"This is text and & and '."}
</title>
</Helmet>,
container
);
Expand Down Expand Up @@ -2926,7 +3021,9 @@ describe("Helmet - Declarative API", () => {
it("renders title tag as string", () => {
ReactDOM.render(
<Helmet>
<title>{"Dangerous <script> include"}</title>
<title>
{"Dangerous <script> include"}
</title>
</Helmet>,
container
);
Expand All @@ -2946,7 +3043,9 @@ describe("Helmet - Declarative API", () => {

ReactDOM.render(
<Helmet>
<title>Title: {someValue}</title>
<title>
Title: {someValue}
</title>
</Helmet>,
container
);
Expand Down Expand Up @@ -3181,7 +3280,9 @@ describe("Helmet - Declarative API", () => {
ReactDOM.render(
<div>
<Helmet>
<title>{chineseTitle}</title>
<title>
{chineseTitle}
</title>
</Helmet>
</div>,
container
Expand Down