├── .gitignore ├── server.js ├── example.js ├── .npmignore ├── src ├── index.js ├── react_title.js ├── meta_tags_context.js ├── meta_tags_server.js ├── meta_tags.js └── utils.js ├── .babelrc ├── .prettierrc ├── server.d.ts ├── examples ├── client.js ├── shared │ ├── routes.js │ ├── page1 │ │ └── page1.js │ ├── index.js │ └── page2 │ │ └── page2.js └── app.js ├── index.d.ts ├── webpack.dev.config.js ├── lib ├── index.js ├── meta_tags_server.js ├── react_title.js ├── meta_tags_context.js ├── utils.js └── meta_tags.js ├── LICENSE ├── rollup.config.js ├── package.json ├── dist ├── react-meta-tags.min.js ├── react-meta-tags.es.js └── react-meta-tags.js ├── README.md └── .eslintrc /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | *.swp 3 | *.log 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/meta_tags_server'); 2 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | require('@babel/register'); 2 | 3 | require('./examples/app'); 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/ 3 | .cache 4 | 5 | .gitignore 6 | 7 | rollup.config.js 8 | webpack.dev.config.js 9 | .babelrc 10 | .eslintrc 11 | 12 | example.js 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import MetaTagsContext from './meta_tags_context'; 2 | import MetaTags from './meta_tags'; 3 | import ReactTitle from './react_title'; 4 | 5 | export default MetaTags; 6 | export {MetaTags, MetaTagsContext, ReactTitle}; 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react", "@babel/env"], 3 | "plugins": [ 4 | "@babel/plugin-proposal-class-properties", 5 | "@babel/plugin-proposal-object-rest-spread", 6 | "babel-plugin-add-module-exports" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "arrowParens": "always", 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "useTabs": false, 7 | "semi": true, 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "requirePragma": false, 11 | "proseWrap": "preserve", 12 | "trailingComma": "all" 13 | } -------------------------------------------------------------------------------- /src/react_title.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import MetaTags from './meta_tags'; 3 | 4 | class ReactTitle extends Component { 5 | render() { 6 | return ( 7 | 8 | {this.props.title} 9 | 10 | ); 11 | } 12 | } 13 | 14 | export default ReactTitle; 15 | -------------------------------------------------------------------------------- /server.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ExtractFunction } from './index'; 3 | 4 | export interface MetaTagsInstance { 5 | extract: ExtractFunction; 6 | renderToString: () => string; 7 | getTags: () => React.ReactElement[]; 8 | } 9 | 10 | const MetaTagsServer: () => MetaTagsInstance 11 | 12 | export default MetaTagsServer; 13 | -------------------------------------------------------------------------------- /examples/client.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import React from 'react'; 3 | import { Router, match, browserHistory } from 'react-router'; 4 | 5 | import routes from './shared/routes'; 6 | 7 | const dest = document.getElementById('app'); 8 | 9 | match({ routes:routes, history: browserHistory }, (error, redirectLocation, renderProps) => { 10 | ReactDOM.hydrate( 11 | 12 | , dest); 13 | }) 14 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface ExtractFunction { 4 | (elements: React.ReactElement | React.ReactElement[]): void; 5 | } 6 | 7 | export class MetaTagsContext extends React.Component<{ 8 | extract: ExtractFunction; 9 | }> {}; 10 | 11 | export class MetaTags extends React.Component {}; 12 | 13 | export class ReactTitle extends React.Component<{ title: string }> {}; 14 | 15 | export default MetaTags; 16 | -------------------------------------------------------------------------------- /examples/shared/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Router, Route, IndexRoute } from 'react-router'; 3 | 4 | import App from './index'; 5 | import Page1 from './page1/page1'; 6 | import Page2 from './page2/page2'; 7 | 8 | const AppRoute = ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | export default AppRoute; 19 | -------------------------------------------------------------------------------- /webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: { 3 | 'bundle' : [ 4 | './examples/client.js' 5 | ] 6 | }, 7 | devtool: "eval", 8 | output: { 9 | //publicPath: "http://localhost:9000/", 10 | // path: path.join(__dirname, "public","js"), 11 | filename: '[name].js' 12 | }, 13 | devServer: { 14 | //contentBase: path.join(__dirname, 'dist'), 15 | port: 9010, 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.js$/, 21 | use: ['babel-loader','eslint-loader'], 22 | exclude: /node_modules/, 23 | } 24 | ] 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/meta_tags_context.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children, createContext} from 'react'; 2 | 3 | const MetaContext = createContext({}); 4 | 5 | /** context class which passes extract fuunction to MetaTags Component **/ 6 | class MetaContextProviderWrapper extends Component { 7 | render() { 8 | return ( 9 | 14 | {Children.only(this.props.children)} 15 | 16 | ); 17 | } 18 | } 19 | 20 | export {MetaContext}; 21 | 22 | export default MetaContextProviderWrapper; 23 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "MetaTagsContext", { 7 | enumerable: true, 8 | get: function get() { 9 | return _meta_tags_context.default; 10 | } 11 | }); 12 | Object.defineProperty(exports, "MetaTags", { 13 | enumerable: true, 14 | get: function get() { 15 | return _meta_tags.default; 16 | } 17 | }); 18 | Object.defineProperty(exports, "ReactTitle", { 19 | enumerable: true, 20 | get: function get() { 21 | return _react_title.default; 22 | } 23 | }); 24 | exports.default = void 0; 25 | 26 | var _meta_tags_context = _interopRequireDefault(require("./meta_tags_context")); 27 | 28 | var _meta_tags = _interopRequireDefault(require("./meta_tags")); 29 | 30 | var _react_title = _interopRequireDefault(require("./react_title")); 31 | 32 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 33 | 34 | var _default = _meta_tags.default; 35 | exports.default = _default; -------------------------------------------------------------------------------- /src/meta_tags_server.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDomServer from 'react-dom/server'; 3 | import {filterAndArrangeTags} from './utils'; 4 | 5 | function MetaTagsServer(){ 6 | let headElms = []; 7 | 8 | return { 9 | extract(elms) { 10 | if (!(elms instanceof Array)) { 11 | elms = [elms]; 12 | } 13 | 14 | //filter out null nodes 15 | elms = elms.filter(elm => !!elm); 16 | 17 | headElms = headElms.concat(elms); 18 | }, 19 | renderToString() { 20 | const filteredElms = filterAndArrangeTags(headElms); 21 | const headComponent =
{filteredElms}
; 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 |
8 | 9 | React Meta Tags | Page1 10 | 11 | 15 | 16 | 17 | 18 |
19 |

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 |

27 |
28 |
29 | ); 30 | } 31 | } 32 | 33 | export default Page1; 34 | -------------------------------------------------------------------------------- /examples/shared/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Link} from 'react-router'; 3 | import MetaTags from '../../src/index'; 4 | 5 | class App extends React.Component { 6 | render() { 7 | return ( 8 |
9 | 10 | React Meta Tags | Page1 11 | 12 | 13 | 14 | 15 | 16 |
17 |

React Meta Tags

18 |
19 | 29 |
30 |
31 |
32 | {this.props.children} 33 |
34 |
35 | ) 36 | } 37 | } 38 | 39 | export default App; 40 | -------------------------------------------------------------------------------- /lib/meta_tags_server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _server = _interopRequireDefault(require("react-dom/server")); 11 | 12 | var _utils = require("./utils"); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 15 | 16 | function MetaTagsServer() { 17 | var headElms = []; 18 | return { 19 | extract: function extract(elms) { 20 | if (!(elms instanceof Array)) { 21 | elms = [elms]; 22 | } //filter out null nodes 23 | 24 | 25 | elms = elms.filter(function (elm) { 26 | return !!elm; 27 | }); 28 | headElms = headElms.concat(elms); 29 | }, 30 | renderToString: function renderToString() { 31 | var filteredElms = (0, _utils.filterAndArrangeTags)(headElms); 32 | 33 | var headComponent = _react.default.createElement("div", { 34 | className: "react-head-temp" 35 | }, filteredElms); 36 | 37 | var componentStr = _server.default.renderToStaticMarkup(headComponent); //remove wrapper div from string 38 | 39 | 40 | componentStr = componentStr.replace(/^]*class="react-head-temp"[^<>]*>(.*)<\/div>$/, function ($1, $2) { 41 | return $2; 42 | }); 43 | return componentStr; 44 | }, 45 | getTags: function getTags() { 46 | return (0, _utils.filterAndArrangeTags)(headElms); 47 | } 48 | }; 49 | } 50 | 51 | var _default = MetaTagsServer; 52 | exports.default = _default; 53 | module.exports = exports.default; -------------------------------------------------------------------------------- /examples/shared/page2/page2.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MetaTags from '../../../src/index'; 3 | 4 | class Page2 extends React.Component { 5 | render() { 6 | return ( 7 |
8 | 9 | React Meta Tags | Page2 10 | 11 | 12 | 13 | 14 | 15 |
16 |

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 |

29 |
30 |
31 | ); 32 | } 33 | } 34 | 35 | export default Page2; 36 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import babel from 'rollup-plugin-babel'; 3 | import { uglify } from 'rollup-plugin-uglify'; 4 | import fileSize from 'rollup-plugin-filesize'; 5 | import license from 'rollup-plugin-license'; 6 | import replace from "rollup-plugin-replace"; 7 | import commonjs from "rollup-plugin-commonjs"; 8 | import resolve from "rollup-plugin-node-resolve"; 9 | 10 | import PACKAGE from './package.json'; 11 | const fullYear = new Date().getFullYear(); 12 | 13 | const banner = `${PACKAGE.name} - ${PACKAGE.version} 14 | Author : ${PACKAGE.author} 15 | Copyright (c) ${(fullYear!== 2016 ? '2016,' : '')} ${fullYear} to ${PACKAGE.author}, released under the ${PACKAGE.license} license. 16 | ${PACKAGE.repository.url}`; 17 | 18 | const babelConfig = JSON.parse(fs.readFileSync('.babelrc')); 19 | 20 | const globals = { 21 | react: 'React', 22 | 'react-dom': 'ReactDOM' 23 | }; 24 | 25 | const defaultConfig = { 26 | input: 'src/index.js', 27 | output: [{ 28 | file: 'dist/react-meta-tags.es.js', 29 | format: 'esm', 30 | globals, 31 | exports: 'named', 32 | }, { 33 | file: 'dist/react-meta-tags.js', 34 | format: 'umd', 35 | name: 'MetaTags', 36 | globals, 37 | exports: 'named', 38 | }], 39 | external: ['react', 'react-dom'], 40 | plugins: [ 41 | babel({ 42 | babelrc: false, 43 | ...babelConfig, 44 | exclude: "node_modules/**", 45 | presets: ['@babel/preset-react', ['@babel/env', { modules: false }]] 46 | }), 47 | resolve(), 48 | commonjs({ 49 | include: /node_modules/ 50 | }), 51 | replace({ 52 | "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV) 53 | }), 54 | fileSize(), 55 | license({ 56 | banner 57 | }), 58 | ], 59 | }; 60 | 61 | const minConfig = { 62 | ...defaultConfig, 63 | output: { 64 | file: 'dist/react-meta-tags.min.js', 65 | format: 'umd', 66 | name: 'MetaTags', 67 | globals, 68 | exports: 'named', 69 | }, 70 | plugins: [ 71 | ...defaultConfig.plugins, 72 | uglify(), 73 | ], 74 | }; 75 | 76 | export default [defaultConfig, minConfig]; 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-meta-tags", 3 | "description": "Handle document meta/head tags in isomorphic react with ease.", 4 | "version": "1.0.1", 5 | "main": "lib/index.js", 6 | "author": "Sudhanshu Yadav", 7 | "license": "MIT", 8 | "sideEffects": false, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/s-yadav/react-meta-tags" 12 | }, 13 | "bugs": { 14 | "mail": "sudhanshuyadav2@gmail.com", 15 | "url": "https://github.com/s-yadav/react-meta-tags/issues" 16 | }, 17 | "keywords": [ 18 | "react-component", 19 | "tags", 20 | "head", 21 | "meta", 22 | "title", 23 | "react", 24 | "base", 25 | "react-meta-tags" 26 | ], 27 | "scripts": { 28 | "start": "cross-env npm run start-dev | nodemon example.js", 29 | "start-dev": "cross-env webpack-dev-server --content-base example/ --config webpack.dev.config.js --watch-poll --progress --inline --hot", 30 | "bundle": "cross-env NODE_ENV=production rollup -c rollup.config.js && npm run compile", 31 | "compile": "cross-env babel src --out-dir lib" 32 | }, 33 | "devDependencies": { 34 | "@babel/cli": "^7.0.0", 35 | "@babel/core": "^7.0.1", 36 | "@babel/plugin-proposal-class-properties": "^7.0.0", 37 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0", 38 | "@babel/preset-env": "^7.0.0", 39 | "@babel/preset-react": "^7.0.0", 40 | "@babel/register": "^7.0.0", 41 | "babel-eslint": "^9.0.0", 42 | "babel-loader": "^8.0.2", 43 | "babel-plugin-add-module-exports": "^1.0.0", 44 | "classnames": "^2.2.3", 45 | "cross-env": "^3.1.4", 46 | "eslint": "^5.6.0", 47 | "eslint-config-prettier": "^6.15.0", 48 | "eslint-loader": "^2.1.0", 49 | "eslint-plugin-import": "^2.14.0", 50 | "eslint-plugin-react": "^7.11.1", 51 | "nodemon": "^1.12.1", 52 | "prettier": "^2.2.0", 53 | "react": "^16.6.0", 54 | "react-dom": "^16.6.0", 55 | "react-router": "3", 56 | "react-transform-hmr": "^1.0.4", 57 | "rollup": "^0.65.2", 58 | "rollup-plugin-babel": "^4.0.3", 59 | "rollup-plugin-commonjs": "^9.1.6", 60 | "rollup-plugin-filesize": "^4.0.1", 61 | "rollup-plugin-license": "^0.7.0", 62 | "rollup-plugin-node-resolve": "^3.4.0", 63 | "rollup-plugin-replace": "^2.0.0", 64 | "rollup-plugin-uglify": "^5.0.2", 65 | "webpack": "^4.17.1", 66 | "webpack-cli": "^3.1.0", 67 | "webpack-dev-server": "^3.1.7" 68 | }, 69 | "peerDependencies": { 70 | "react": "^16.6.0", 71 | "react-dom": "^16.6.0" 72 | }, 73 | "dependencies": {} 74 | } 75 | -------------------------------------------------------------------------------- /examples/app.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import React from 'react'; 3 | import ReactDomServer from 'react-dom/server'; 4 | import {match, RouterContext} from 'react-router'; 5 | 6 | import MetaTagsServer from '../src/meta_tags_server'; 7 | import {MetaTagsContext} from '../src/index'; 8 | 9 | import routes from './shared/routes'; 10 | 11 | const app = express() 12 | 13 | function renderToString(metaTagsInstance, reactString) { 14 | const meta = metaTagsInstance.renderToString(); 15 | 16 | const html = ` 17 | 18 | 19 | 20 | 21 | 22 | 23 | ${meta} 24 | 25 | 26 | 27 | 28 | 29 |
${reactString}
30 | 31 | 32 | 33 | `; 34 | 35 | return html; 36 | } 37 | 38 | function renderToStringFromJSx(metaTagsInstance, reactString) { 39 | const tags = metaTagsInstance.getTags(); 40 | const wholeHtml = ( 41 | 42 | 43 | 44 | 45 | 46 | {tags} 47 | 48 | 49 | 50 |
51 | 52 | 53 | 54 | ); 55 | 56 | return ReactDomServer.renderToString(wholeHtml) 57 | } 58 | 59 | app.use((req, res) => { 60 | match({ 61 | routes, location: req.url 62 | }, (error, redirectLocation, renderProps) => { 63 | if(error) { 64 | res.status(500).send(error.message); 65 | } else if (redirectLocation) { 66 | res.redirect(302, redirectLocation.pathname + redirectLocation.search) 67 | } else if (renderProps) { 68 | let reactString; 69 | 70 | const metaTagsInstance = MetaTagsServer(); 71 | 72 | try{ 73 | reactString = ReactDomServer.renderToString( 74 | 75 | 76 | 77 | ); 78 | } 79 | catch(e){ 80 | console.log(e); 81 | res.status(500).send(e.stack); 82 | return; 83 | } 84 | 85 | //const html = renderToString(metaTagsInstance, reactString); 86 | const html = renderToStringFromJSx(metaTagsInstance, reactString); 87 | 88 | res.status(200).send(html); 89 | } else { 90 | console.log('redirected'); 91 | res.status(301).redirect('/') 92 | } 93 | }); 94 | }); 95 | 96 | app.listen(8070, () => { 97 | console.log('Listening on 8070'); 98 | }) 99 | -------------------------------------------------------------------------------- /src/meta_tags.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { 4 | getDuplicateTitle, 5 | getDuplicateCanonical, 6 | getDuplicateMeta, 7 | getDuplicateElementById, 8 | appendChild, 9 | removeChild, 10 | } from './utils'; 11 | import { MetaContext } from './meta_tags_context'; 12 | 13 | /** An wrapper component to wrap element which need to shifted to head **/ 14 | class MetaTags extends Component { 15 | static contextType = MetaContext; 16 | 17 | componentDidMount() { 18 | this.temporaryElement = document.createElement('div'); 19 | this.handleChildrens(); 20 | } 21 | componentDidUpdate(oldProps) { 22 | if (oldProps.children !== this.props.children) { 23 | this.handleChildrens(); 24 | } 25 | } 26 | componentWillUnmount() { 27 | if (this.temporaryElement) { 28 | ReactDOM.unmountComponentAtNode(this.temporaryElement); 29 | } 30 | } 31 | extractChildren() { 32 | const { extract } = this.context; 33 | const { children } = this.props; 34 | 35 | if (!children) { 36 | return; 37 | } 38 | 39 | if (extract) { 40 | extract(children); 41 | } 42 | } 43 | handleChildrens() { 44 | const { children } = this.props; 45 | if (this.context.extract || !children) { 46 | return; 47 | } 48 | 49 | const headComponent =
{children}
; 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 | --------------------------------------------------------------------------------