feat: optimize head tags (#40630)

Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com>
This commit is contained in:
Ahmad Abdolsaheb
2021-01-13 16:12:32 +03:00
committed by Mrugesh Mohapatra
parent 625469c82f
commit 3ec468be38
12 changed files with 210 additions and 133 deletions

View File

@ -4,10 +4,9 @@ import { Provider } from 'react-redux';
import { I18nextProvider } from 'react-i18next'; import { I18nextProvider } from 'react-i18next';
import i18n from './i18n/config'; import i18n from './i18n/config';
import headComponents from './src/head';
import { createStore } from './src/redux/createStore'; import { createStore } from './src/redux/createStore';
import layoutSelector from './utils/gatsby/layoutSelector'; import layoutSelector from './utils/gatsby/layoutSelector';
import { getheadTagComponents, getPostBodyComponents } from './utils/tags';
const store = createStore(); const store = createStore();
@ -23,26 +22,6 @@ wrapRootElement.propTypes = {
element: PropTypes.any element: PropTypes.any
}; };
// TODO: put these in a common utils file.
const mathJaxCdn = {
address:
'https://cdnjs.cloudflare.com/ajax/libs/mathjax/' +
'2.7.4/MathJax.js?config=TeX-AMS_HTML',
id: 'mathjax',
key: 'mathjax',
type: 'text/javascript'
};
const stripeScript = {
address: 'https://js.stripe.com/v3/',
id: 'stripe-js',
key: 'stripe-js',
type: 'text/javascript'
};
const challengeRE = new RegExp('/learn/[^/]+/[^/]+/[^/]+/?$');
const donateRE = new RegExp('/donate/?$');
export const wrapPageElement = layoutSelector; export const wrapPageElement = layoutSelector;
export const onRenderBody = ({ export const onRenderBody = ({
@ -50,52 +29,6 @@ export const onRenderBody = ({
setHeadComponents, setHeadComponents,
setPostBodyComponents setPostBodyComponents
}) => { }) => {
setHeadComponents([...headComponents]); setHeadComponents(getheadTagComponents());
const scripts = [ setPostBodyComponents(getPostBodyComponents(pathname));
<script
async={true}
key='gtag-script'
src='https://www.googletagmanager.com/gtag/js?id=AW-795617839'
/>,
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'AW-795617839');
`
}}
key='gtag-dataLayer'
/>
];
if (
pathname.includes('/learn/coding-interview-prep/rosetta-code') ||
pathname.includes('/learn/coding-interview-prep/project-euler')
) {
scripts.push(
<script
async={false}
id={mathJaxCdn.id}
key={mathJaxCdn.key}
src={mathJaxCdn.address}
type={mathJaxCdn.type}
/>
);
}
if (challengeRE.test(pathname) || donateRE.test(pathname)) {
scripts.push(
<script
async={true}
id={stripeScript.id}
key={stripeScript.key}
src={stripeScript.address}
type={stripeScript.type}
/>
);
}
setPostBodyComponents(scripts.filter(Boolean));
}; };

View File

@ -2,6 +2,7 @@
"meta": { "meta": {
"title": "免费学习编程 适合忙碌人士的编程课程", "title": "免费学习编程 适合忙碌人士的编程课程",
"description": "在家学习编程,构建项目,获得认证。自 2014 年以来,已有超过 40,000 名 freeCodeCamp 学员入职谷歌、苹果、亚马逊、微软等科技公司。", "description": "在家学习编程,构建项目,获得认证。自 2014 年以来,已有超过 40,000 名 freeCodeCamp 学员入职谷歌、苹果、亚马逊、微软等科技公司。",
"social-description": "Learn to Code — For Free",
"keywords": [ "keywords": [
"javascript", "javascript",
"js", "js",

View File

@ -1,7 +1,8 @@
{ {
"meta": { "meta": {
"title": "Learn to Code — For Free — Coding Courses for Busy People", "title": "Learn to Code — For Free — Coding Courses for Busy People",
"description": "Learn to code — for free.", "description": "Learn to Code — For Free",
"social-description": "Learn to Code — For Free",
"keywords": [ "keywords": [
"javascript", "javascript",
"js", "js",

View File

@ -2,6 +2,7 @@
"meta": { "meta": {
"title": "Aprende a codificar gratis: cursos de programación para personas ocupadas", "title": "Aprende a codificar gratis: cursos de programación para personas ocupadas",
"description": "Aprende a codificar en casa. Construye proyectos. Obtén certificaciones. Desde el 2014, más de 40,000 graduados de freeCodeCamp.org han conseguido trabajos en empresas de tecnología como Google, Apple, Amazon y Microsoft.", "description": "Aprende a codificar en casa. Construye proyectos. Obtén certificaciones. Desde el 2014, más de 40,000 graduados de freeCodeCamp.org han conseguido trabajos en empresas de tecnología como Google, Apple, Amazon y Microsoft.",
"social-description": "Learn to Code — For Free",
"keywords": [ "keywords": [
"javascript", "javascript",
"js", "js",

View File

@ -4,9 +4,9 @@
*/ */
const translationsSchema = { const translationsSchema = {
meta: { meta: {
title: 'Learn to Code for Free Coding Courses for Busy People', title: 'Learn to Code — For Free Coding Courses for Busy People',
description: description: 'Learn to Code — For Free',
'Learn to code at home. Build projects. Earn certifications. Since 2014, more than 40,000 freeCodeCamp.org graduates have gotten jobs at tech companies including Google, Apple, Amazon, and Microsoft.', 'social-description': 'Learn to Code — For Free',
keywords: [ keywords: [
'javascript', 'javascript',
'js', 'js',

View File

@ -1,9 +0,0 @@
import meta from './meta';
const metaAndStyleSheets = meta.map((element, i) => ({
...element,
key: `meta-stylesheet-${i}`,
props: { ...element.props, key: `meta-stylesheet-${i}` }
}));
export default metaAndStyleSheets;

View File

@ -1,34 +0,0 @@
import React from 'react';
const meta = [
<meta content='freeCodeCamp.org' name='og:title' />,
<meta
content={
'Learn to code. Build projects. Earn certifications.' +
'Since 2015, 40,000 graduates have gotten jobs at tech ' +
'companies including Google, Apple, Amazon, and Microsoft.'
}
name='og:description'
/>,
<meta
content='https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920X1080-indigo.png'
property='og:image'
/>,
<meta content='summary_large_image' key='twitter:card' name='twitter:card' />,
<meta
content='https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920X1080-indigo.png'
name='twitter:image:src'
/>,
<meta content='freeCodeCamp.org' name='twitter:title' />,
<meta
content={
'Learn to code. Build projects. Earn certifications.' +
'Since 2015, 40,000 graduates have gotten jobs at tech ' +
'companies including Google, Apple, Amazon, and Microsoft.'
}
name='twitter:description'
/>,
<meta content='$ilp.uphold.com/LJmbPn7WD4JB' name='monetization' />
];
export default meta;

View File

@ -1,11 +0,0 @@
import React from 'react';
import styleSheets from './styleSheets';
const preloads = styleSheets.map(styleSheet => (
<React.Fragment key={`preload-${styleSheet.props.href}`}>
<link as='style' href={styleSheet.props.href} rel='preload' />
{styleSheet}
</React.Fragment>
));
export default preloads;

View File

@ -1,3 +0,0 @@
const styleSheets = [];
export default styleSheets;

View File

@ -12,8 +12,6 @@ export default class HTML extends React.Component {
content='width=device-width, initial-scale=1.0, shrink-to-fit=no' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'
name='viewport' name='viewport'
/> />
<link as='style' href='/css/bootstrap.min.css' rel='preload' />
<link href='/css/bootstrap.min.css' rel='stylesheet' />
{this.props.headComponents} {this.props.headComponents}
</head> </head>
<body {...this.props.bodyAttributes}> <body {...this.props.bodyAttributes}>

85
client/utils/tags.js Normal file
View File

@ -0,0 +1,85 @@
import React from 'react';
import { withPrefix } from 'gatsby';
import i18next from 'i18next';
export const getheadTagComponents = () => {
const socialImage =
'https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920X1080-indigo.png';
const pathToBootstrap = withPrefix('/css/bootstrap.min.css');
return [
<link
as='style'
href={pathToBootstrap}
key='boostrap-min-preload'
rel='preload'
/>,
<link href={pathToBootstrap} key='boostrap-min' rel='stylesheet' />,
<meta content='freeCodeCamp.org' key='og:title' name='og:title' />,
<meta
content={i18next.t('meta.social-description')}
key='og:description'
name='og:description'
/>,
<meta content={socialImage} key='og:image' property='og:image' />,
<meta
content='summary_large_image'
key='twitter:card'
name='twitter:card'
/>,
<meta
content={socialImage}
key='twitter:image:src'
name='twitter:image:src'
/>,
<meta
content='freeCodeCamp.org'
key='twitter:title'
name='twitter:title'
/>,
<meta
content={i18next.t('meta.social-description')}
key='twitter:description'
name='twitter:description'
/>,
<meta
content='$ilp.uphold.com/LJmbPn7WD4JB'
key='monetization'
name='monetization'
/>
];
};
export const getPostBodyComponents = pathname => {
let scripts = [];
const challengesPathRE = new RegExp('/learn/[^/]+/[^/]+/[^/]+/?$');
const donatePathRE = new RegExp('/donate/?$');
const mathJaxScriptElement = (
<script
async={false}
id='mathjax'
key='mathjax'
src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS_HTML'
type='text/javascript'
/>
);
const stripeScriptElement = (
<script
async={true}
id='stripe-js'
key='stripe-js'
src='https://js.stripe.com/v3/'
type='text/javascript'
/>
);
if (
pathname.includes('/learn/coding-interview-prep/rosetta-code') ||
pathname.includes('/learn/coding-interview-prep/project-euler')
) {
scripts.push(mathJaxScriptElement);
}
if (challengesPathRE.test(pathname) || donatePathRE.test(pathname)) {
scripts.push(stripeScriptElement);
}
return scripts.filter(Boolean);
};

115
cypress/integration/tags.js Normal file
View File

@ -0,0 +1,115 @@
/* global cy */
describe('The Document Metadata', () => {
before(() => {
cy.visit('/');
cy.document();
});
const social = {
description: 'Learn to Code — For Free'
};
const challengs = {
responsiveWebDesign:
'/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements',
rosetaCode: '/learn/coding-interview-prep/rosetta-code/100-doors',
projectEuler:
'/learn/coding-interview-prep/project-euler/problem-1-multiples-of-3-and-5'
};
const scripts = {
stripe: {
selector: 'body script[id="stripe-js"]',
src: 'https://js.stripe.com/v3/'
},
mathjax: {
selector: 'body script[id="mathjax"]',
src:
'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS_HTML'
}
};
it('landing page has correct <meta> for description', () => {
cy.get('head meta[name="description"]').should(
'have.attr',
'content',
'Learn to Code — For Free'
);
});
it('landing page has correct <meta> for og title', () => {
cy.get('head meta[name="og:title"]').should(
'have.attr',
'content',
'freeCodeCamp.org'
);
});
it('landing page has correct <meta> for og description', () => {
cy.get('head meta[name="og:description"]').should(
'have.attr',
'content',
social.description
);
});
it('landing page has correct <meta> for twitter title', () => {
cy.get('head meta[name="twitter:title"]').should(
'have.attr',
'content',
'freeCodeCamp.org'
);
});
it('landing page has correct <meta>for twitter description', () => {
cy.get('head meta[name="twitter:description"]').should(
'have.attr',
'content',
social.description
);
});
it('landing page should not have stripe body script', () => {
cy.reload();
cy.get(scripts.stripe.selector).should('not.exist');
});
it('landing page should not have mathjax body script', () => {
cy.reload();
cy.get(scripts.mathjax.selector).should('not.exist');
});
it('responsive webdesign challenges should not have mathjax body script', () => {
cy.visit(challengs.responsiveWebDesign);
cy.reload();
cy.get(scripts.mathjax.selector).should('not.exist');
});
it('donate page should have stripe body script', () => {
cy.visit('/donate');
cy.reload();
cy.get(scripts.stripe.selector).should(
'have.attr',
'src',
scripts.stripe.src
);
});
it('responsive webdesign challenges should have stripe body script', () => {
cy.visit(challengs.responsiveWebDesign);
cy.reload();
cy.get(scripts.stripe.selector).should(
'have.attr',
'src',
scripts.stripe.src
);
});
it('project euler challenges should have mathjax body script', () => {
cy.visit(challengs.projectEuler);
cy.reload();
cy.get(scripts.mathjax.selector).should(
'have.attr',
'src',
scripts.mathjax.src
);
});
it('rosetta code challenges should have mathjax body script', () => {
cy.visit(challengs.projectEuler);
cy.reload();
cy.get(scripts.mathjax.selector).should(
'have.attr',
'src',
scripts.mathjax.src
);
});
});