;
22 | let componentStr = ReactDomServer.renderToStaticMarkup(headComponent);
23 |
24 | //remove wrapper div from string
25 | componentStr = componentStr.replace(/^
]*class="react-head-temp"[^<>]*>(.*)<\/div>$/, ($1, $2) => {
26 | return $2;
27 | });
28 |
29 | return componentStr;
30 | },
31 | getTags() {
32 | return filterAndArrangeTags(headElms);
33 | }
34 | }
35 | }
36 |
37 | export default MetaTagsServer;
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 - present Sudhanshu Yadav
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/shared/page1/page1.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MetaTags from '../../../src/index';
3 |
4 | class Page1 extends React.Component {
5 | render() {
6 | return (
7 |
20 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
21 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
22 | exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
23 | dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
24 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
25 | mollit anim id est laborum.
26 |
17 | Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
18 | laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
19 | architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
20 | sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
21 | voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit
22 | amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut
23 | labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis
24 | nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi
25 | consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam
26 | nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
27 | pariatur?
28 |
;
50 |
51 | ReactDOM.render(headComponent, this.temporaryElement, () => {
52 | const childStr = this.temporaryElement.innerHTML;
53 |
54 | //if html is not changed return
55 | if (this.lastChildStr === childStr) {
56 | return;
57 | }
58 |
59 | this.lastChildStr = childStr;
60 |
61 | const tempHead = this.temporaryElement.querySelector('.react-head-temp');
62 |
63 | // .react-head-temp might not exist when triggered from async action
64 | if (tempHead === null) {
65 | return;
66 | }
67 |
68 | let childNodes = Array.prototype.slice.call(tempHead.children);
69 |
70 | const head = document.head;
71 | const headHtml = head.innerHTML;
72 |
73 | //filter children remove if children has not been changed
74 | childNodes = childNodes.filter((child) => {
75 | return headHtml.indexOf(child.outerHTML) === -1;
76 | });
77 |
78 | //create clone of childNodes
79 | childNodes = childNodes.map((child) => child.cloneNode(true));
80 |
81 | //remove duplicate title and meta from head
82 | childNodes.forEach((child) => {
83 | const tag = child.tagName.toLowerCase();
84 | if (tag === 'title') {
85 | const title = getDuplicateTitle();
86 | if (title) removeChild(head, title);
87 | } else if (child.id) {
88 | // if the element has id defined remove the existing element with that id
89 | const elm = getDuplicateElementById(child);
90 | if (elm) removeChild(head, elm);
91 | } else if (tag === 'meta') {
92 | const meta = getDuplicateMeta(child);
93 | if (meta) removeChild(head, meta);
94 | } else if (tag === 'link' && child.rel === 'canonical') {
95 | const link = getDuplicateCanonical(child);
96 | if (link) removeChild(head, link);
97 | }
98 | });
99 |
100 | appendChild(document.head, childNodes);
101 | });
102 | }
103 | render() {
104 | this.extractChildren();
105 | return null;
106 | }
107 | }
108 |
109 | export default MetaTags;
110 |
--------------------------------------------------------------------------------
/lib/react_title.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = void 0;
7 |
8 | var _react = _interopRequireWildcard(require("react"));
9 |
10 | var _meta_tags = _interopRequireDefault(require("./meta_tags"));
11 |
12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13 |
14 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
15 |
16 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
17 |
18 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
19 |
20 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
21 |
22 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
23 |
24 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
25 |
26 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
27 |
28 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
29 |
30 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
31 |
32 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
33 |
34 | var ReactTitle =
35 | /*#__PURE__*/
36 | function (_Component) {
37 | _inherits(ReactTitle, _Component);
38 |
39 | function ReactTitle() {
40 | _classCallCheck(this, ReactTitle);
41 |
42 | return _possibleConstructorReturn(this, _getPrototypeOf(ReactTitle).apply(this, arguments));
43 | }
44 |
45 | _createClass(ReactTitle, [{
46 | key: "render",
47 | value: function render() {
48 | return _react.default.createElement(_meta_tags.default, null, _react.default.createElement("title", null, this.props.title));
49 | }
50 | }]);
51 |
52 | return ReactTitle;
53 | }(_react.Component);
54 |
55 | var _default = ReactTitle;
56 | exports.default = _default;
57 | module.exports = exports.default;
--------------------------------------------------------------------------------
/lib/meta_tags_context.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = exports.MetaContext = void 0;
7 |
8 | var _react = _interopRequireWildcard(require("react"));
9 |
10 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
11 |
12 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
13 |
14 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
15 |
16 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
17 |
18 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
19 |
20 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
21 |
22 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
23 |
24 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
25 |
26 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
27 |
28 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
29 |
30 | var MetaContext = (0, _react.createContext)({});
31 | /** context class which passes extract fuunction to MetaTags Component **/
32 |
33 | exports.MetaContext = MetaContext;
34 |
35 | var MetaContextProviderWrapper =
36 | /*#__PURE__*/
37 | function (_Component) {
38 | _inherits(MetaContextProviderWrapper, _Component);
39 |
40 | function MetaContextProviderWrapper() {
41 | _classCallCheck(this, MetaContextProviderWrapper);
42 |
43 | return _possibleConstructorReturn(this, _getPrototypeOf(MetaContextProviderWrapper).apply(this, arguments));
44 | }
45 |
46 | _createClass(MetaContextProviderWrapper, [{
47 | key: "render",
48 | value: function render() {
49 | return _react.default.createElement(MetaContext.Provider, {
50 | value: {
51 | extract: this.props.extract
52 | }
53 | }, _react.Children.only(this.props.children));
54 | }
55 | }]);
56 |
57 | return MetaContextProviderWrapper;
58 | }(_react.Component);
59 |
60 | var _default = MetaContextProviderWrapper;
61 | exports.default = _default;
--------------------------------------------------------------------------------
/dist/react-meta-tags.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * react-meta-tags - 1.0.1
3 | * Author : Sudhanshu Yadav
4 | * Copyright (c) 2016, 2020 to Sudhanshu Yadav, released under the MIT license.
5 | * https://github.com/s-yadav/react-meta-tags
6 | */
7 |
8 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react"),require("react-dom")):"function"==typeof define&&define.amd?define(["exports","react","react-dom"],t):t(e.MetaTags={},e.React,e.ReactDOM)}(this,function(e,n,r){"use strict";var i="default"in n?n.default:n;function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){for(var n=0;n !meta.id);
16 | }
17 |
18 | export function filterAndArrangeTags(headElms) {
19 | let title = null;
20 | let canonicalLink = null;
21 | const metas = [];
22 | const rest = [];
23 |
24 | headElms.forEach((elm) => {
25 | const { type, props } = elm;
26 | if (type === 'title') {
27 | title = elm;
28 | } else if (type === 'link' && props.rel === 'canonical') {
29 | canonicalLink = elm;
30 | } else if (type === 'meta') {
31 | metas.push(elm);
32 | } else {
33 | rest.push(elm);
34 | }
35 | });
36 |
37 | return [title, ...removeDuplicateMetas(metas), canonicalLink, ...rest];
38 | }
39 |
40 | function removeDuplicateMetas(metas) {
41 | const addedMeta = {};
42 |
43 | //initialize all the identifiers with empty array
44 | uniqueIdentifiersAll.forEach((identifier) => {
45 | addedMeta[identifier] = [];
46 | });
47 |
48 | const filteredMetas = [];
49 | for (let i = metas.length - 1; i >= 0; i--) {
50 | const meta = metas[i];
51 |
52 | const { id } = meta.props;
53 | let addMeta = false;
54 |
55 | //if has id and element with id is not present than always add meta
56 | if (id) {
57 | addMeta = !addedMeta.id[id];
58 |
59 | //for any other unique identifier check if meta already available with same identifier which doesn't have id
60 | } else {
61 | addMeta =
62 | uniqueIdentifiers.filter((identifier) => {
63 | const identifierValue = meta.props[identifier];
64 | const existing = addedMeta[identifier][identifierValue];
65 | return existing && !existing.props.id;
66 | }).length === 0;
67 | }
68 |
69 | if (addMeta) {
70 | filteredMetas.unshift(meta);
71 |
72 | //add meta as added
73 | uniqueIdentifiersAll.forEach((identifier) => {
74 | const identifierValue = meta.props[identifier];
75 | if (identifierValue) addedMeta[identifier][identifierValue] = meta;
76 | });
77 | }
78 | }
79 |
80 | return filteredMetas;
81 | }
82 |
83 | export function getDuplicateTitle() {
84 | return document.head.querySelectorAll('title');
85 | }
86 |
87 | export function getDuplicateCanonical() {
88 | return document.head.querySelectorAll('link[rel="canonical"]');
89 | }
90 |
91 | export function getDuplicateElementById({ id }) {
92 | return id && document.head.querySelector(`#${id}`);
93 | }
94 |
95 | export function getDuplicateMeta(meta) {
96 | const head = document.head;
97 |
98 | //for any other unique identifier check if metas already available with same identifier which doesn't have id
99 | return uniqueIdentifiersI.reduce((duplicates, identifier) => {
100 | const identifierValue = meta.getAttribute(identifier);
101 | return identifierValue
102 | ? duplicates.concat(
103 | filterOutMetaWithId(head.querySelectorAll(`[${identifier} = "${identifierValue}"]`)),
104 | )
105 | : duplicates;
106 | }, []);
107 | }
108 |
109 | //function to append childrens on a parent
110 | export function appendChild(parent, childrens) {
111 | if (childrens.length === undefined) childrens = [childrens];
112 |
113 | const docFrag = document.createDocumentFragment();
114 |
115 | //we used for loop instead of forEach because childrens can be array like object
116 | for (let i = 0, ln = childrens.length; i < ln; i++) {
117 | docFrag.appendChild(childrens[i]);
118 | }
119 |
120 | parent.appendChild(docFrag);
121 | }
122 |
123 | export function removeChild(parent, childrens) {
124 | if (childrens.length === undefined) childrens = [childrens];
125 | for (let i = 0, ln = childrens.length; i < ln; i++) {
126 | parent.removeChild(childrens[i]);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-meta-tags
2 | Handle document meta/head tags in isomorphic react with ease.
3 |
4 | Handling title and meta/head tags in a isomporhic react is tricky. Its declarative to define those tags within the component, but they need to be moved on document head on client side as well as server side. While there are other modules which helps with the use-case like react-helmet and react-document-meta, but they require to define those tags in a object literal. react-meta-tags allow you to write those tags in a declarative way and in normal jsx format.
5 |
6 | ### Install
7 | Through npm
8 | `npm install react-meta-tags --save`
9 |
10 | Or get compiled development and production version from ./dist
11 |
12 | ### Usage
13 |
14 | #### Using MetaTag Component
15 |
16 | ```jsx
17 | import React from 'react';
18 | import MetaTags from 'react-meta-tags';
19 |
20 | class Component1 extends React.Component {
21 | render() {
22 | return (
23 |
24 |
25 | Page 1
26 |
27 |
28 |
29 |
30 |
Some Content
31 |
32 | )
33 | }
34 | }
35 | ```
36 | Note : Define id on tags so if you navigate to other page, older meta tags will be removed and replaced by new ones.
37 |
38 |
39 | #### ReactTitle Component
40 | If you just want to add title on a page you can use ReactTitle instead.
41 | ```jsx
42 | import React from 'react';
43 | import {ReactTitle} from 'react-meta-tags';
44 |
45 | class Component2 extends React.Component {
46 | render() {
47 | return (
48 |
49 |
50 |
Some Content
51 |
52 | )
53 | }
54 | }
55 | ```
56 |
57 | ### Server Usage Example
58 |
59 | ```jsx
60 | import MetaTagsServer from 'react-meta-tags/server';
61 | import {MetaTagsContext} from 'react-meta-tags';
62 | /** Import other required modules **/
63 |
64 | /*
65 | ------
66 | some serve specific code
67 | ------
68 | */
69 |
70 | app.use((req, res) => {
71 | //make sure you get a new metatags instance for each request
72 | const metaTagsInstance = MetaTagsServer();
73 |
74 | //react router match
75 | match({
76 | routes, location: req.url
77 | }, (error, redirectLocation, renderProps) => {
78 | let reactString;
79 |
80 | try{
81 | reactString = ReactDomServer.renderToString(
82 | {/*** If you are using redux ***/}
83 | {/* You have to pass extract method through MetaTagsContext so it can catch meta tags */}
84 |
85 |
86 |
87 |
88 | );
89 | }
90 | catch(e){
91 | res.status(500).send(e.stack);
92 | return;
93 | }
94 |
95 | //get all title and metatags as string
96 | const meta = metaTagsInstance.renderToString();
97 |
98 | //append metatag string to your layout
99 | const htmlStr = (`
100 |
101 |
102 |
103 |
104 | ${meta}
105 |
106 |
107 |
108 | ${reactString}
109 |
110 |
111 |
112 | `);
113 |
114 | res.status(200).send(layout);
115 | });
116 | });
117 | ```
118 |
119 | So as per above code we have to do following for server rendering
120 |
121 | 1. Import MetaTagsServer and MetaTagsContext
122 | 2. Create a new instance of MetaTagsServer
123 | 3. Wrap your component inside MetaTagsContext and pass extract method as props
124 | 4. Extract meta string using renderToString of MetaTagsServer instance
125 | 5. Append meta string to your html template.
126 |
127 | #### JSX Layout
128 | You might also be using React to define your layout, in which case you can use `getTags` method from `metaTagsInstance`. The layout part of above server side example will look like this.
129 | ```jsx
130 | //get all title and metatags as React elements
131 | const metaTags = metaTagsInstance.getTags();
132 |
133 | //append metatag string to your layout
134 | const layout = (
135 |
136 |
137 |
138 | {metaTags}
139 |
140 |
141 |
142 |
143 |
144 | );
145 |
146 | const htmlStr = ReactDomServer.renderToString(layout);
147 |
148 | res.status(200).send(htmlStr);
149 | ```
150 |
151 |
152 |
153 | ## Meta Tag Uniqueness
154 | - The module uniquely identifies meta tag by id / property / name / itemProp attribute.
155 | - Multiple meta tags with same property / name is valid in html. If you need such case. Define a different id to both so that it can be uniquely differentiate.
156 | - You should give an id if meta key is different then property/name/itemProp to uniquely identify them.
157 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.filterAndArrangeTags = filterAndArrangeTags;
7 | exports.getDuplicateTitle = getDuplicateTitle;
8 | exports.getDuplicateCanonical = getDuplicateCanonical;
9 | exports.getDuplicateElementById = getDuplicateElementById;
10 | exports.getDuplicateMeta = getDuplicateMeta;
11 | exports.appendChild = appendChild;
12 | exports.removeChild = removeChild;
13 |
14 | function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
15 |
16 | function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
17 |
18 | function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
19 |
20 | function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
21 |
22 | var camelCaseProps = ['itemProp'];
23 | var uniqueIdentifiersI = ['property', 'name', 'itemprop'];
24 | var uniqueIdentifiers = uniqueIdentifiersI.concat(camelCaseProps); //case sensitive props is defined in case anyone defined the lowercase prop
25 |
26 | var uniqueIdentifiersAll = uniqueIdentifiers.concat(['id']);
27 | /**
28 | Note:
29 | 1. In server side we will add meta tags and title at last after fitering
30 | 2. In client we will match and replace meta tagString
31 | 3. For now we will not support link and other tags properly, they can be added but we will not check for uniqueness and will not decide placement
32 | **/
33 |
34 | function filterOutMetaWithId(metas) {
35 | metas = Array.prototype.slice.call(metas || []);
36 | return metas.filter(function (meta) {
37 | return !meta.id;
38 | });
39 | }
40 |
41 | function filterAndArrangeTags(headElms) {
42 | var title = null;
43 | var canonicalLink = null;
44 | var metas = [];
45 | var rest = [];
46 | headElms.forEach(function (elm) {
47 | var type = elm.type,
48 | props = elm.props;
49 |
50 | if (type === 'title') {
51 | title = elm;
52 | } else if (type === 'link' && props.rel === 'canonical') {
53 | canonicalLink = elm;
54 | } else if (type === 'meta') {
55 | metas.push(elm);
56 | } else {
57 | rest.push(elm);
58 | }
59 | });
60 | return [title].concat(_toConsumableArray(removeDuplicateMetas(metas)), [canonicalLink], rest);
61 | }
62 |
63 | function removeDuplicateMetas(metas) {
64 | var addedMeta = {}; //initialize all the identifiers with empty array
65 |
66 | uniqueIdentifiersAll.forEach(function (identifier) {
67 | addedMeta[identifier] = [];
68 | });
69 | var filteredMetas = [];
70 |
71 | var _loop = function _loop(i) {
72 | var meta = metas[i];
73 | var id = meta.props.id;
74 | var addMeta = false; //if has id and element with id is not present than always add meta
75 |
76 | if (id) {
77 | addMeta = !addedMeta.id[id]; //for any other unique identifier check if meta already available with same identifier which doesn't have id
78 | } else {
79 | addMeta = uniqueIdentifiers.filter(function (identifier) {
80 | var identifierValue = meta.props[identifier];
81 | var existing = addedMeta[identifier][identifierValue];
82 | return existing && !existing.props.id;
83 | }).length === 0;
84 | }
85 |
86 | if (addMeta) {
87 | filteredMetas.unshift(meta); //add meta as added
88 |
89 | uniqueIdentifiersAll.forEach(function (identifier) {
90 | var identifierValue = meta.props[identifier];
91 | if (identifierValue) addedMeta[identifier][identifierValue] = meta;
92 | });
93 | }
94 | };
95 |
96 | for (var i = metas.length - 1; i >= 0; i--) {
97 | _loop(i);
98 | }
99 |
100 | return filteredMetas;
101 | }
102 |
103 | function getDuplicateTitle() {
104 | return document.head.querySelectorAll('title');
105 | }
106 |
107 | function getDuplicateCanonical() {
108 | return document.head.querySelectorAll('link[rel="canonical"]');
109 | }
110 |
111 | function getDuplicateElementById(_ref) {
112 | var id = _ref.id;
113 | return id && document.head.querySelector("#".concat(id));
114 | }
115 |
116 | function getDuplicateMeta(meta) {
117 | var head = document.head; //for any other unique identifier check if metas already available with same identifier which doesn't have id
118 |
119 | return uniqueIdentifiersI.reduce(function (duplicates, identifier) {
120 | var identifierValue = meta.getAttribute(identifier);
121 | return identifierValue ? duplicates.concat(filterOutMetaWithId(head.querySelectorAll("[".concat(identifier, " = \"").concat(identifierValue, "\"]")))) : duplicates;
122 | }, []);
123 | } //function to append childrens on a parent
124 |
125 |
126 | function appendChild(parent, childrens) {
127 | if (childrens.length === undefined) childrens = [childrens];
128 | var docFrag = document.createDocumentFragment(); //we used for loop instead of forEach because childrens can be array like object
129 |
130 | for (var i = 0, ln = childrens.length; i < ln; i++) {
131 | docFrag.appendChild(childrens[i]);
132 | }
133 |
134 | parent.appendChild(docFrag);
135 | }
136 |
137 | function removeChild(parent, childrens) {
138 | if (childrens.length === undefined) childrens = [childrens];
139 |
140 | for (var i = 0, ln = childrens.length; i < ln; i++) {
141 | parent.removeChild(childrens[i]);
142 | }
143 | }
--------------------------------------------------------------------------------
/lib/meta_tags.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = void 0;
7 |
8 | var _react = _interopRequireWildcard(require("react"));
9 |
10 | var _reactDom = _interopRequireDefault(require("react-dom"));
11 |
12 | var _utils = require("./utils");
13 |
14 | var _meta_tags_context = require("./meta_tags_context");
15 |
16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17 |
18 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
19 |
20 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
21 |
22 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
23 |
24 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
25 |
26 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
27 |
28 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
29 |
30 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
31 |
32 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
33 |
34 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
35 |
36 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
37 |
38 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
39 |
40 | /** An wrapper component to wrap element which need to shifted to head **/
41 | var MetaTags =
42 | /*#__PURE__*/
43 | function (_Component) {
44 | _inherits(MetaTags, _Component);
45 |
46 | function MetaTags() {
47 | _classCallCheck(this, MetaTags);
48 |
49 | return _possibleConstructorReturn(this, _getPrototypeOf(MetaTags).apply(this, arguments));
50 | }
51 |
52 | _createClass(MetaTags, [{
53 | key: "componentDidMount",
54 | value: function componentDidMount() {
55 | this.temporaryElement = document.createElement('div');
56 | this.handleChildrens();
57 | }
58 | }, {
59 | key: "componentDidUpdate",
60 | value: function componentDidUpdate(oldProps) {
61 | if (oldProps.children !== this.props.children) {
62 | this.handleChildrens();
63 | }
64 | }
65 | }, {
66 | key: "componentWillUnmount",
67 | value: function componentWillUnmount() {
68 | if (this.temporaryElement) {
69 | _reactDom.default.unmountComponentAtNode(this.temporaryElement);
70 | }
71 | }
72 | }, {
73 | key: "extractChildren",
74 | value: function extractChildren() {
75 | var extract = this.context.extract;
76 | var children = this.props.children;
77 |
78 | if (!children) {
79 | return;
80 | }
81 |
82 | if (extract) {
83 | extract(children);
84 | }
85 | }
86 | }, {
87 | key: "handleChildrens",
88 | value: function handleChildrens() {
89 | var _this = this;
90 |
91 | var children = this.props.children;
92 |
93 | if (this.context.extract || !children) {
94 | return;
95 | }
96 |
97 | var headComponent = _react.default.createElement("div", {
98 | className: "react-head-temp"
99 | }, children);
100 |
101 | _reactDom.default.render(headComponent, this.temporaryElement, function () {
102 | var childStr = _this.temporaryElement.innerHTML; //if html is not changed return
103 |
104 | if (_this.lastChildStr === childStr) {
105 | return;
106 | }
107 |
108 | _this.lastChildStr = childStr;
109 |
110 | var tempHead = _this.temporaryElement.querySelector('.react-head-temp'); // .react-head-temp might not exist when triggered from async action
111 |
112 |
113 | if (tempHead === null) {
114 | return;
115 | }
116 |
117 | var childNodes = Array.prototype.slice.call(tempHead.children);
118 | var head = document.head;
119 | var headHtml = head.innerHTML; //filter children remove if children has not been changed
120 |
121 | childNodes = childNodes.filter(function (child) {
122 | return headHtml.indexOf(child.outerHTML) === -1;
123 | }); //create clone of childNodes
124 |
125 | childNodes = childNodes.map(function (child) {
126 | return child.cloneNode(true);
127 | }); //remove duplicate title and meta from head
128 |
129 | childNodes.forEach(function (child) {
130 | var tag = child.tagName.toLowerCase();
131 |
132 | if (tag === 'title') {
133 | var title = (0, _utils.getDuplicateTitle)();
134 | if (title) (0, _utils.removeChild)(head, title);
135 | } else if (child.id) {
136 | // if the element has id defined remove the existing element with that id
137 | var elm = (0, _utils.getDuplicateElementById)(child);
138 | if (elm) (0, _utils.removeChild)(head, elm);
139 | } else if (tag === 'meta') {
140 | var meta = (0, _utils.getDuplicateMeta)(child);
141 | if (meta) (0, _utils.removeChild)(head, meta);
142 | } else if (tag === 'link' && child.rel === 'canonical') {
143 | var link = (0, _utils.getDuplicateCanonical)(child);
144 | if (link) (0, _utils.removeChild)(head, link);
145 | }
146 | });
147 | (0, _utils.appendChild)(document.head, childNodes);
148 | });
149 | }
150 | }, {
151 | key: "render",
152 | value: function render() {
153 | this.extractChildren();
154 | return null;
155 | }
156 | }]);
157 |
158 | return MetaTags;
159 | }(_react.Component);
160 |
161 | _defineProperty(MetaTags, "contextType", _meta_tags_context.MetaContext);
162 |
163 | var _default = MetaTags;
164 | exports.default = _default;
165 | module.exports = exports.default;
--------------------------------------------------------------------------------
/dist/react-meta-tags.es.js:
--------------------------------------------------------------------------------
1 | /**
2 | * react-meta-tags - 1.0.1
3 | * Author : Sudhanshu Yadav
4 | * Copyright (c) 2016, 2020 to Sudhanshu Yadav, released under the MIT license.
5 | * https://github.com/s-yadav/react-meta-tags
6 | */
7 |
8 | import React, { Component, Children, createContext } from 'react';
9 | import ReactDOM from 'react-dom';
10 |
11 | function _classCallCheck(instance, Constructor) {
12 | if (!(instance instanceof Constructor)) {
13 | throw new TypeError("Cannot call a class as a function");
14 | }
15 | }
16 |
17 | function _defineProperties(target, props) {
18 | for (var i = 0; i < props.length; i++) {
19 | var descriptor = props[i];
20 | descriptor.enumerable = descriptor.enumerable || false;
21 | descriptor.configurable = true;
22 | if ("value" in descriptor) descriptor.writable = true;
23 | Object.defineProperty(target, descriptor.key, descriptor);
24 | }
25 | }
26 |
27 | function _createClass(Constructor, protoProps, staticProps) {
28 | if (protoProps) _defineProperties(Constructor.prototype, protoProps);
29 | if (staticProps) _defineProperties(Constructor, staticProps);
30 | return Constructor;
31 | }
32 |
33 | function _defineProperty(obj, key, value) {
34 | if (key in obj) {
35 | Object.defineProperty(obj, key, {
36 | value: value,
37 | enumerable: true,
38 | configurable: true,
39 | writable: true
40 | });
41 | } else {
42 | obj[key] = value;
43 | }
44 |
45 | return obj;
46 | }
47 |
48 | function _inherits(subClass, superClass) {
49 | if (typeof superClass !== "function" && superClass !== null) {
50 | throw new TypeError("Super expression must either be null or a function");
51 | }
52 |
53 | subClass.prototype = Object.create(superClass && superClass.prototype, {
54 | constructor: {
55 | value: subClass,
56 | writable: true,
57 | configurable: true
58 | }
59 | });
60 | if (superClass) _setPrototypeOf(subClass, superClass);
61 | }
62 |
63 | function _getPrototypeOf(o) {
64 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
65 | return o.__proto__ || Object.getPrototypeOf(o);
66 | };
67 | return _getPrototypeOf(o);
68 | }
69 |
70 | function _setPrototypeOf(o, p) {
71 | _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
72 | o.__proto__ = p;
73 | return o;
74 | };
75 |
76 | return _setPrototypeOf(o, p);
77 | }
78 |
79 | function _assertThisInitialized(self) {
80 | if (self === void 0) {
81 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
82 | }
83 |
84 | return self;
85 | }
86 |
87 | function _possibleConstructorReturn(self, call) {
88 | if (call && (typeof call === "object" || typeof call === "function")) {
89 | return call;
90 | }
91 |
92 | return _assertThisInitialized(self);
93 | }
94 |
95 | var MetaContext = createContext({});
96 | /** context class which passes extract fuunction to MetaTags Component **/
97 |
98 | var MetaContextProviderWrapper =
99 | /*#__PURE__*/
100 | function (_Component) {
101 | _inherits(MetaContextProviderWrapper, _Component);
102 |
103 | function MetaContextProviderWrapper() {
104 | _classCallCheck(this, MetaContextProviderWrapper);
105 |
106 | return _possibleConstructorReturn(this, _getPrototypeOf(MetaContextProviderWrapper).apply(this, arguments));
107 | }
108 |
109 | _createClass(MetaContextProviderWrapper, [{
110 | key: "render",
111 | value: function render() {
112 | return React.createElement(MetaContext.Provider, {
113 | value: {
114 | extract: this.props.extract
115 | }
116 | }, Children.only(this.props.children));
117 | }
118 | }]);
119 |
120 | return MetaContextProviderWrapper;
121 | }(Component);
122 |
123 | var uniqueIdentifiersI = ['property', 'name', 'itemprop'];
124 | /**
125 | Note:
126 | 1. In server side we will add meta tags and title at last after fitering
127 | 2. In client we will match and replace meta tagString
128 | 3. For now we will not support link and other tags properly, they can be added but we will not check for uniqueness and will not decide placement
129 | **/
130 |
131 | function filterOutMetaWithId(metas) {
132 | metas = Array.prototype.slice.call(metas || []);
133 | return metas.filter(function (meta) {
134 | return !meta.id;
135 | });
136 | }
137 |
138 | function getDuplicateTitle() {
139 | return document.head.querySelectorAll('title');
140 | }
141 | function getDuplicateCanonical() {
142 | return document.head.querySelectorAll('link[rel="canonical"]');
143 | }
144 | function getDuplicateElementById(_ref) {
145 | var id = _ref.id;
146 | return id && document.head.querySelector("#".concat(id));
147 | }
148 | function getDuplicateMeta(meta) {
149 | var head = document.head; //for any other unique identifier check if metas already available with same identifier which doesn't have id
150 |
151 | return uniqueIdentifiersI.reduce(function (duplicates, identifier) {
152 | var identifierValue = meta.getAttribute(identifier);
153 | return identifierValue ? duplicates.concat(filterOutMetaWithId(head.querySelectorAll("[".concat(identifier, " = \"").concat(identifierValue, "\"]")))) : duplicates;
154 | }, []);
155 | } //function to append childrens on a parent
156 |
157 | function appendChild(parent, childrens) {
158 | if (childrens.length === undefined) childrens = [childrens];
159 | var docFrag = document.createDocumentFragment(); //we used for loop instead of forEach because childrens can be array like object
160 |
161 | for (var i = 0, ln = childrens.length; i < ln; i++) {
162 | docFrag.appendChild(childrens[i]);
163 | }
164 |
165 | parent.appendChild(docFrag);
166 | }
167 | function removeChild(parent, childrens) {
168 | if (childrens.length === undefined) childrens = [childrens];
169 |
170 | for (var i = 0, ln = childrens.length; i < ln; i++) {
171 | parent.removeChild(childrens[i]);
172 | }
173 | }
174 |
175 | /** An wrapper component to wrap element which need to shifted to head **/
176 |
177 | var MetaTags =
178 | /*#__PURE__*/
179 | function (_Component) {
180 | _inherits(MetaTags, _Component);
181 |
182 | function MetaTags() {
183 | _classCallCheck(this, MetaTags);
184 |
185 | return _possibleConstructorReturn(this, _getPrototypeOf(MetaTags).apply(this, arguments));
186 | }
187 |
188 | _createClass(MetaTags, [{
189 | key: "componentDidMount",
190 | value: function componentDidMount() {
191 | this.temporaryElement = document.createElement('div');
192 | this.handleChildrens();
193 | }
194 | }, {
195 | key: "componentDidUpdate",
196 | value: function componentDidUpdate(oldProps) {
197 | if (oldProps.children !== this.props.children) {
198 | this.handleChildrens();
199 | }
200 | }
201 | }, {
202 | key: "componentWillUnmount",
203 | value: function componentWillUnmount() {
204 | if (this.temporaryElement) {
205 | ReactDOM.unmountComponentAtNode(this.temporaryElement);
206 | }
207 | }
208 | }, {
209 | key: "extractChildren",
210 | value: function extractChildren() {
211 | var extract = this.context.extract;
212 | var children = this.props.children;
213 |
214 | if (!children) {
215 | return;
216 | }
217 |
218 | if (extract) {
219 | extract(children);
220 | }
221 | }
222 | }, {
223 | key: "handleChildrens",
224 | value: function handleChildrens() {
225 | var _this = this;
226 |
227 | var children = this.props.children;
228 |
229 | if (this.context.extract || !children) {
230 | return;
231 | }
232 |
233 | var headComponent = React.createElement("div", {
234 | className: "react-head-temp"
235 | }, children);
236 | ReactDOM.render(headComponent, this.temporaryElement, function () {
237 | var childStr = _this.temporaryElement.innerHTML; //if html is not changed return
238 |
239 | if (_this.lastChildStr === childStr) {
240 | return;
241 | }
242 |
243 | _this.lastChildStr = childStr;
244 |
245 | var tempHead = _this.temporaryElement.querySelector('.react-head-temp'); // .react-head-temp might not exist when triggered from async action
246 |
247 |
248 | if (tempHead === null) {
249 | return;
250 | }
251 |
252 | var childNodes = Array.prototype.slice.call(tempHead.children);
253 | var head = document.head;
254 | var headHtml = head.innerHTML; //filter children remove if children has not been changed
255 |
256 | childNodes = childNodes.filter(function (child) {
257 | return headHtml.indexOf(child.outerHTML) === -1;
258 | }); //create clone of childNodes
259 |
260 | childNodes = childNodes.map(function (child) {
261 | return child.cloneNode(true);
262 | }); //remove duplicate title and meta from head
263 |
264 | childNodes.forEach(function (child) {
265 | var tag = child.tagName.toLowerCase();
266 |
267 | if (tag === 'title') {
268 | var title = getDuplicateTitle();
269 | if (title) removeChild(head, title);
270 | } else if (child.id) {
271 | // if the element has id defined remove the existing element with that id
272 | var elm = getDuplicateElementById(child);
273 | if (elm) removeChild(head, elm);
274 | } else if (tag === 'meta') {
275 | var meta = getDuplicateMeta(child);
276 | if (meta) removeChild(head, meta);
277 | } else if (tag === 'link' && child.rel === 'canonical') {
278 | var link = getDuplicateCanonical(child);
279 | if (link) removeChild(head, link);
280 | }
281 | });
282 | appendChild(document.head, childNodes);
283 | });
284 | }
285 | }, {
286 | key: "render",
287 | value: function render() {
288 | this.extractChildren();
289 | return null;
290 | }
291 | }]);
292 |
293 | return MetaTags;
294 | }(Component);
295 |
296 | _defineProperty(MetaTags, "contextType", MetaContext);
297 |
298 | var ReactTitle =
299 | /*#__PURE__*/
300 | function (_Component) {
301 | _inherits(ReactTitle, _Component);
302 |
303 | function ReactTitle() {
304 | _classCallCheck(this, ReactTitle);
305 |
306 | return _possibleConstructorReturn(this, _getPrototypeOf(ReactTitle).apply(this, arguments));
307 | }
308 |
309 | _createClass(ReactTitle, [{
310 | key: "render",
311 | value: function render() {
312 | return React.createElement(MetaTags, null, React.createElement("title", null, this.props.title));
313 | }
314 | }]);
315 |
316 | return ReactTitle;
317 | }(Component);
318 |
319 | export default MetaTags;
320 | export { MetaTags, MetaContextProviderWrapper as MetaTagsContext, ReactTitle };
321 |
--------------------------------------------------------------------------------
/dist/react-meta-tags.js:
--------------------------------------------------------------------------------
1 | /**
2 | * react-meta-tags - 1.0.1
3 | * Author : Sudhanshu Yadav
4 | * Copyright (c) 2016, 2020 to Sudhanshu Yadav, released under the MIT license.
5 | * https://github.com/s-yadav/react-meta-tags
6 | */
7 |
8 | (function (global, factory) {
9 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('react-dom')) :
10 | typeof define === 'function' && define.amd ? define(['exports', 'react', 'react-dom'], factory) :
11 | (factory((global.MetaTags = {}),global.React,global.ReactDOM));
12 | }(this, (function (exports,React,ReactDOM) { 'use strict';
13 |
14 | var React__default = 'default' in React ? React['default'] : React;
15 | ReactDOM = ReactDOM && ReactDOM.hasOwnProperty('default') ? ReactDOM['default'] : ReactDOM;
16 |
17 | function _classCallCheck(instance, Constructor) {
18 | if (!(instance instanceof Constructor)) {
19 | throw new TypeError("Cannot call a class as a function");
20 | }
21 | }
22 |
23 | function _defineProperties(target, props) {
24 | for (var i = 0; i < props.length; i++) {
25 | var descriptor = props[i];
26 | descriptor.enumerable = descriptor.enumerable || false;
27 | descriptor.configurable = true;
28 | if ("value" in descriptor) descriptor.writable = true;
29 | Object.defineProperty(target, descriptor.key, descriptor);
30 | }
31 | }
32 |
33 | function _createClass(Constructor, protoProps, staticProps) {
34 | if (protoProps) _defineProperties(Constructor.prototype, protoProps);
35 | if (staticProps) _defineProperties(Constructor, staticProps);
36 | return Constructor;
37 | }
38 |
39 | function _defineProperty(obj, key, value) {
40 | if (key in obj) {
41 | Object.defineProperty(obj, key, {
42 | value: value,
43 | enumerable: true,
44 | configurable: true,
45 | writable: true
46 | });
47 | } else {
48 | obj[key] = value;
49 | }
50 |
51 | return obj;
52 | }
53 |
54 | function _inherits(subClass, superClass) {
55 | if (typeof superClass !== "function" && superClass !== null) {
56 | throw new TypeError("Super expression must either be null or a function");
57 | }
58 |
59 | subClass.prototype = Object.create(superClass && superClass.prototype, {
60 | constructor: {
61 | value: subClass,
62 | writable: true,
63 | configurable: true
64 | }
65 | });
66 | if (superClass) _setPrototypeOf(subClass, superClass);
67 | }
68 |
69 | function _getPrototypeOf(o) {
70 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
71 | return o.__proto__ || Object.getPrototypeOf(o);
72 | };
73 | return _getPrototypeOf(o);
74 | }
75 |
76 | function _setPrototypeOf(o, p) {
77 | _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
78 | o.__proto__ = p;
79 | return o;
80 | };
81 |
82 | return _setPrototypeOf(o, p);
83 | }
84 |
85 | function _assertThisInitialized(self) {
86 | if (self === void 0) {
87 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
88 | }
89 |
90 | return self;
91 | }
92 |
93 | function _possibleConstructorReturn(self, call) {
94 | if (call && (typeof call === "object" || typeof call === "function")) {
95 | return call;
96 | }
97 |
98 | return _assertThisInitialized(self);
99 | }
100 |
101 | var MetaContext = React.createContext({});
102 | /** context class which passes extract fuunction to MetaTags Component **/
103 |
104 | var MetaContextProviderWrapper =
105 | /*#__PURE__*/
106 | function (_Component) {
107 | _inherits(MetaContextProviderWrapper, _Component);
108 |
109 | function MetaContextProviderWrapper() {
110 | _classCallCheck(this, MetaContextProviderWrapper);
111 |
112 | return _possibleConstructorReturn(this, _getPrototypeOf(MetaContextProviderWrapper).apply(this, arguments));
113 | }
114 |
115 | _createClass(MetaContextProviderWrapper, [{
116 | key: "render",
117 | value: function render() {
118 | return React__default.createElement(MetaContext.Provider, {
119 | value: {
120 | extract: this.props.extract
121 | }
122 | }, React.Children.only(this.props.children));
123 | }
124 | }]);
125 |
126 | return MetaContextProviderWrapper;
127 | }(React.Component);
128 |
129 | var uniqueIdentifiersI = ['property', 'name', 'itemprop'];
130 | /**
131 | Note:
132 | 1. In server side we will add meta tags and title at last after fitering
133 | 2. In client we will match and replace meta tagString
134 | 3. For now we will not support link and other tags properly, they can be added but we will not check for uniqueness and will not decide placement
135 | **/
136 |
137 | function filterOutMetaWithId(metas) {
138 | metas = Array.prototype.slice.call(metas || []);
139 | return metas.filter(function (meta) {
140 | return !meta.id;
141 | });
142 | }
143 |
144 | function getDuplicateTitle() {
145 | return document.head.querySelectorAll('title');
146 | }
147 | function getDuplicateCanonical() {
148 | return document.head.querySelectorAll('link[rel="canonical"]');
149 | }
150 | function getDuplicateElementById(_ref) {
151 | var id = _ref.id;
152 | return id && document.head.querySelector("#".concat(id));
153 | }
154 | function getDuplicateMeta(meta) {
155 | var head = document.head; //for any other unique identifier check if metas already available with same identifier which doesn't have id
156 |
157 | return uniqueIdentifiersI.reduce(function (duplicates, identifier) {
158 | var identifierValue = meta.getAttribute(identifier);
159 | return identifierValue ? duplicates.concat(filterOutMetaWithId(head.querySelectorAll("[".concat(identifier, " = \"").concat(identifierValue, "\"]")))) : duplicates;
160 | }, []);
161 | } //function to append childrens on a parent
162 |
163 | function appendChild(parent, childrens) {
164 | if (childrens.length === undefined) childrens = [childrens];
165 | var docFrag = document.createDocumentFragment(); //we used for loop instead of forEach because childrens can be array like object
166 |
167 | for (var i = 0, ln = childrens.length; i < ln; i++) {
168 | docFrag.appendChild(childrens[i]);
169 | }
170 |
171 | parent.appendChild(docFrag);
172 | }
173 | function removeChild(parent, childrens) {
174 | if (childrens.length === undefined) childrens = [childrens];
175 |
176 | for (var i = 0, ln = childrens.length; i < ln; i++) {
177 | parent.removeChild(childrens[i]);
178 | }
179 | }
180 |
181 | /** An wrapper component to wrap element which need to shifted to head **/
182 |
183 | var MetaTags =
184 | /*#__PURE__*/
185 | function (_Component) {
186 | _inherits(MetaTags, _Component);
187 |
188 | function MetaTags() {
189 | _classCallCheck(this, MetaTags);
190 |
191 | return _possibleConstructorReturn(this, _getPrototypeOf(MetaTags).apply(this, arguments));
192 | }
193 |
194 | _createClass(MetaTags, [{
195 | key: "componentDidMount",
196 | value: function componentDidMount() {
197 | this.temporaryElement = document.createElement('div');
198 | this.handleChildrens();
199 | }
200 | }, {
201 | key: "componentDidUpdate",
202 | value: function componentDidUpdate(oldProps) {
203 | if (oldProps.children !== this.props.children) {
204 | this.handleChildrens();
205 | }
206 | }
207 | }, {
208 | key: "componentWillUnmount",
209 | value: function componentWillUnmount() {
210 | if (this.temporaryElement) {
211 | ReactDOM.unmountComponentAtNode(this.temporaryElement);
212 | }
213 | }
214 | }, {
215 | key: "extractChildren",
216 | value: function extractChildren() {
217 | var extract = this.context.extract;
218 | var children = this.props.children;
219 |
220 | if (!children) {
221 | return;
222 | }
223 |
224 | if (extract) {
225 | extract(children);
226 | }
227 | }
228 | }, {
229 | key: "handleChildrens",
230 | value: function handleChildrens() {
231 | var _this = this;
232 |
233 | var children = this.props.children;
234 |
235 | if (this.context.extract || !children) {
236 | return;
237 | }
238 |
239 | var headComponent = React__default.createElement("div", {
240 | className: "react-head-temp"
241 | }, children);
242 | ReactDOM.render(headComponent, this.temporaryElement, function () {
243 | var childStr = _this.temporaryElement.innerHTML; //if html is not changed return
244 |
245 | if (_this.lastChildStr === childStr) {
246 | return;
247 | }
248 |
249 | _this.lastChildStr = childStr;
250 |
251 | var tempHead = _this.temporaryElement.querySelector('.react-head-temp'); // .react-head-temp might not exist when triggered from async action
252 |
253 |
254 | if (tempHead === null) {
255 | return;
256 | }
257 |
258 | var childNodes = Array.prototype.slice.call(tempHead.children);
259 | var head = document.head;
260 | var headHtml = head.innerHTML; //filter children remove if children has not been changed
261 |
262 | childNodes = childNodes.filter(function (child) {
263 | return headHtml.indexOf(child.outerHTML) === -1;
264 | }); //create clone of childNodes
265 |
266 | childNodes = childNodes.map(function (child) {
267 | return child.cloneNode(true);
268 | }); //remove duplicate title and meta from head
269 |
270 | childNodes.forEach(function (child) {
271 | var tag = child.tagName.toLowerCase();
272 |
273 | if (tag === 'title') {
274 | var title = getDuplicateTitle();
275 | if (title) removeChild(head, title);
276 | } else if (child.id) {
277 | // if the element has id defined remove the existing element with that id
278 | var elm = getDuplicateElementById(child);
279 | if (elm) removeChild(head, elm);
280 | } else if (tag === 'meta') {
281 | var meta = getDuplicateMeta(child);
282 | if (meta) removeChild(head, meta);
283 | } else if (tag === 'link' && child.rel === 'canonical') {
284 | var link = getDuplicateCanonical(child);
285 | if (link) removeChild(head, link);
286 | }
287 | });
288 | appendChild(document.head, childNodes);
289 | });
290 | }
291 | }, {
292 | key: "render",
293 | value: function render() {
294 | this.extractChildren();
295 | return null;
296 | }
297 | }]);
298 |
299 | return MetaTags;
300 | }(React.Component);
301 |
302 | _defineProperty(MetaTags, "contextType", MetaContext);
303 |
304 | var ReactTitle =
305 | /*#__PURE__*/
306 | function (_Component) {
307 | _inherits(ReactTitle, _Component);
308 |
309 | function ReactTitle() {
310 | _classCallCheck(this, ReactTitle);
311 |
312 | return _possibleConstructorReturn(this, _getPrototypeOf(ReactTitle).apply(this, arguments));
313 | }
314 |
315 | _createClass(ReactTitle, [{
316 | key: "render",
317 | value: function render() {
318 | return React__default.createElement(MetaTags, null, React__default.createElement("title", null, this.props.title));
319 | }
320 | }]);
321 |
322 | return ReactTitle;
323 | }(React.Component);
324 |
325 | exports.default = MetaTags;
326 | exports.MetaTags = MetaTags;
327 | exports.MetaTagsContext = MetaContextProviderWrapper;
328 | exports.ReactTitle = ReactTitle;
329 |
330 | Object.defineProperty(exports, '__esModule', { value: true });
331 |
332 | })));
333 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true
5 | },
6 | "extends": ["prettier"],
7 | "plugins": [
8 | "react", //on react based application
9 | "import",
10 | ],
11 | "ecmaFeatures": {
12 | "arrowFunctions": true,
13 | "blockBindings": true,
14 | "classes": true,
15 | "defaultParams": true,
16 | "destructuring": true,
17 | "forOf": true,
18 | "jsx": true,
19 | "modules": true,
20 | "objectLiteralComputedProperties": true,
21 | "objectLiteralDuplicateProperties": true,
22 | "objectLiteralShorthandMethods": true,
23 | "objectLiteralShorthandProperties": true,
24 | "spread": true,
25 | "superInFunctions": true,
26 | "templateStrings": true
27 | },
28 | "parser": "babel-eslint",
29 | "rules": {
30 | /*** best practices ***/
31 |
32 | "eqeqeq": [1, "smart"],
33 | "curly": [2, "multi-line"],
34 | "no-unused-vars": 1,
35 | // enforces return statements in callbacks of array's methods
36 | "array-callback-return": 2,
37 | // require default case in switch statements
38 | "default-case": 2,
39 | // make sure for-in loops have an if statement
40 | "guard-for-in": 2,
41 | // disallow the use of alert, confirm, and prompt
42 | "no-alert": 1,
43 | // disallow use of arguments.caller or arguments.callee
44 | "no-caller": 2,
45 | // disallow lexical declarations in case/default clauses
46 | // http://eslint.org/docs/rules/no-case-declarations.html
47 | "no-case-declarations": 2,
48 | // disallow else after a return in an if
49 | "no-else-return": 2,
50 | // disallow Unnecessary Labels
51 | // http://eslint.org/docs/rules/no-extra-label
52 | "no-extra-label": 2,
53 | // disallow use of eval()
54 | "no-eval": 1,
55 | // disallow adding to native types
56 | "no-extend-native": 1,
57 | // disallow unnecessary function binding
58 | "no-extra-bind": 1,
59 | // disallow fallthrough of case statements
60 | "no-fallthrough": 1,
61 | // disallow the use of leading or trailing decimal points in numeric literals
62 | "no-floating-decimal": 1,
63 | // disallow usage of __iterator__ property
64 | "no-iterator": 2,
65 | // disallow use of labels for anything other then loops and switches
66 | "no-labels": [
67 | 2,
68 | {
69 | "allowLoop": false,
70 | "allowSwitch": false
71 | }
72 | ],
73 | // disallow unnecessary nested blocks
74 | "no-lone-blocks": 2,
75 | // disallow creation of functions within loops
76 | "no-loop-func": 2,
77 | // disallow use of multiline strings
78 | "no-multi-str": 2,
79 | // disallow reassignments of native objects
80 | "no-native-reassign": 2,
81 | // disallow use of new operator for Function object
82 | "no-new-func": 1,
83 | // disallows creating new instances of String, Number, and Boolean
84 | "no-new-wrappers": 2,
85 | // disallow use of (old style) octal literals
86 | "no-octal": 2,
87 | // disallow use of octal escape sequences in string literals, such as
88 | // var foo = 'Copyright \251';
89 | "no-octal-escape": 2,
90 | // disallow usage of __proto__ property
91 | "no-proto": 2,
92 | // disallow declaring the same variable more then once
93 | "no-redeclare": 2,
94 | // disallow use of assignment in return statement
95 | "no-return-assign": 2,
96 | // disallow use of `javascript: urls.
97 | "no-script-url": 2,
98 | // disallow comparisons where both sides are exactly the same
99 | "no-self-compare": 2,
100 | // disallow use of comma operator
101 | "no-sequences": 1,
102 | // restrict what can be thrown as an exception
103 | "no-throw-literal": 2,
104 | // disallow usage of expressions in statement position
105 | "no-unused-expressions": 2,
106 | // disallow unused labels
107 | // http://eslint.org/docs/rules/no-unused-labels
108 | "no-unused-labels": 2,
109 | // disallow unnecessary string escaping
110 | // http://eslint.org/docs/rules/no-useless-escape
111 | "no-useless-escape": 2,
112 | // disallow use of the with statement
113 | "no-with": 1,
114 | // require use of the second argument for parseInt()
115 | "radix": 2,
116 |
117 | /*** best practice rule end ***/
118 |
119 | /***********************************************************/
120 |
121 | /*** breaking error rules ***/
122 | // disallow assignment in conditional expressions
123 | "no-cond-assign": [2, "always"],
124 | // disallow use of constant expressions in conditions
125 | "no-constant-condition": 1,
126 | // disallow control characters in regular expressions
127 | "no-control-regex": 2,
128 | // disallow use of debugger
129 | //'no-debugger': 2, //for production
130 | // disallow duplicate arguments in functions
131 | "no-dupe-args": 2,
132 | // disallow duplicate keys when creating object literals
133 | "no-dupe-keys": 2,
134 | // disallow a duplicate case label.
135 | "no-duplicate-case": 2,
136 | // disallow the use of empty character classes in regular expressions
137 | "no-empty-character-class": 2,
138 | // disallow empty statements
139 | "no-empty": 2,
140 | // disallow assigning to the exception in a catch block
141 | "no-ex-assign": 2,
142 | // disallow unnecessary parentheses
143 | "no-extra-parens": [1, "functions"],
144 | // disallow unnecessary semicolons
145 | "no-extra-semi": 1,
146 | // disallow overwriting functions written as function declarations
147 | "no-func-assign": 2,
148 |
149 | // disallow function or variable declarations in nested blocks
150 | "no-inner-declarations": [2, "functions"],
151 | // disallow invalid regular expression strings in the RegExp constructor
152 | "no-invalid-regexp": 2,
153 | // disallow negation of the left operand of an in expression
154 | "no-negated-in-lhs": 2,
155 | // disallow the use of object properties of the global object (Math and JSON) as functions
156 | "no-obj-calls": 2,
157 | // disallow sparse arrays
158 | "no-sparse-arrays": 2,
159 | // disallow unreachable statements after a return, throw, continue, or break statement
160 | "no-unreachable": 2,
161 | // disallow comparisons with the value NaN
162 | "use-isnan": 2,
163 | // ensure that the results of typeof are compared against a valid string
164 | "valid-typeof": 2,
165 | /*** breaking error rules end***/
166 |
167 | /***********************************************************/
168 |
169 | /*** es6 error rules start ***/
170 | "no-var": 1,
171 | "prefer-const": 1,
172 | // disallow arrow functions where they could be confused with comparisons
173 | // http://eslint.org/docs/rules/no-confusing-arrow
174 | "no-confusing-arrow": [
175 | 2,
176 | {
177 | "allowParens": true
178 | }
179 | ],
180 | // disallow modifying variables that are declared using const
181 | "no-const-assign": 2,
182 | // disallow duplicate class members
183 | // http://eslint.org/docs/rules/no-dupe-class-members
184 | "no-dupe-class-members": 2,
185 | // disallow symbol constructor
186 | // http://eslint.org/docs/rules/no-new-symbol
187 | "no-new-symbol": 2,
188 | // disallow to use this/super before super() calling in constructors.
189 | "no-this-before-super": 2,
190 | // disallow unnecessary constructor
191 | // http://eslint.org/docs/rules/no-useless-constructor
192 | "no-useless-constructor": 2,
193 | // suggest using arrow functions as callbacks
194 | "prefer-arrow-callback": 1,
195 |
196 | // disallow invalid exports, e.g. multiple defaults
197 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/export.md
198 | "import/export": 2,
199 | // ensure imports point to files/modules that can be resolved
200 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unresolved.md
201 | "import/no-unresolved": [2, { "commonjs": true }],
202 | /*** es6 error rules end ***/
203 |
204 | /***********************************************************/
205 |
206 | /*** react error rules start ***/
207 |
208 | // Prevent use of `accessKey`
209 | // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-access-key.md
210 | //'jsx-a11y/no-access-key': 2,
211 |
212 | // Require to have a non-empty `alt` prop, or role="presentation"
213 | // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/img-uses-alt.md
214 | //* 'jsx-a11y/img-uses-alt': 2,
215 |
216 | // Prevent img alt text from containing redundant words like "image", "picture", or "photo"
217 | // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/redundant-alt.md
218 | //* 'jsx-a11y/redundant-alt': 2,
219 |
220 | // Require ARIA roles to be valid and non-abstract
221 | // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/valid-aria-role.md
222 | //* 'jsx-a11y/valid-aria-role': 2,
223 |
224 | // Prevent missing displayName in a React component definition
225 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md
226 | //* 'react/display-name': [0, { 'ignoreTranspilerName': false }],
227 |
228 | // Validate JSX has key prop when in array or iterator
229 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md
230 | "react/jsx-key": 1,
231 |
232 | // Prevent usage of .bind() and arrow functions in JSX props
233 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
234 | "react/jsx-no-bind": [
235 | 1,
236 | {
237 | "ignoreRefs": true,
238 | "allowArrowFunctions": true,
239 | "allowBind": false
240 | }
241 | ],
242 |
243 | // Prevent duplicate props in JSX
244 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-duplicate-props.md
245 | "react/jsx-no-duplicate-props": 2,
246 |
247 | // Disallow undeclared variables in JSX
248 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md
249 | "react/jsx-no-undef": 2,
250 |
251 | // Enforce PascalCase for user-defined JSX components
252 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md
253 | "react/jsx-pascal-case": 2,
254 |
255 | // Prevent React to be incorrectly marked as unused
256 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md
257 | "react/jsx-uses-react": 2,
258 |
259 | // Prevent variables used in JSX to be incorrectly marked as unused
260 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md
261 | "react/jsx-uses-vars": 2,
262 |
263 | // Prevent usage of dangerous JSX properties
264 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger.md
265 | "react/no-danger": 1,
266 |
267 | // Prevent usage of deprecated methods
268 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md
269 | "react/no-deprecated": 1,
270 |
271 | // Prevent usage of setState in componentDidMount
272 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md
273 | "react/no-did-mount-set-state": 2,
274 |
275 | // Prevent usage of setState in componentDidUpdate
276 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md
277 | "react/no-did-update-set-state": 2,
278 |
279 | // Prevent usage of isMounted
280 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md
281 | "react/no-is-mounted": 2,
282 |
283 | // Prevent multiple component definition per file
284 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md
285 | //* 'react/no-multi-comp': [1, { 'ignoreStateless': true }],
286 |
287 | // Prevent usage of unknown DOM property
288 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md
289 | "react/no-unknown-property": 2,
290 |
291 | // Require stateless functions when not using lifecycle methods, setState or ref
292 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md
293 | //* 'react/prefer-stateless-function': 2,
294 |
295 | // Prevent missing props validation in a React component definition
296 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md
297 | //* 'react/prop-types': [2, { 'ignore': [], 'customValidators': [] }],
298 |
299 | // Prevent missing React when using JSX
300 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md
301 | "react/react-in-jsx-scope": 2,
302 |
303 | // Require render() methods to return something
304 | // https://github.com/yannickcr/eslint-plugin-react/pull/502
305 | "react/require-render-return": 2
306 |
307 | // Prevent extra closing tags for components without children
308 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
309 | //* 'react/self-closing-comp': 1,
310 |
311 | // Enforce component methods order
312 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md
313 | /** 'react/sort-comp': [2, {
314 | 'order': [
315 | 'static-methods',
316 | 'lifecycle',
317 | '/^on.+$/',
318 | '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/',
319 | 'everything-else',
320 | '/^render.+$/',
321 | 'render'
322 | ]
323 | }], */
324 | // Prevent missing parentheses around multilines JSX
325 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md
326 | /** 'react/wrap-multilines': [2, {
327 | declaration: true,
328 | assignment: true,
329 | return: true
330 | }], */
331 | /*** react error rules end ***/
332 | }
333 | }
334 |
--------------------------------------------------------------------------------