UNPKG

gatsby-plugin-amp

Version:
397 lines (365 loc) 13.6 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.replaceRenderer = exports.onRenderBody = exports.onPreRenderHTML = void 0; var _react = _interopRequireWildcard(require("react")); var _server = require("react-dom/server"); var _minimatch = require("minimatch"); var _lodash = _interopRequireDefault(require("lodash.flattendeep")); var _jsxFileName = "/Users/jfaircloth/GitHub/gatsby-plugin-amp/src/gatsby-ssr.js"; const JSDOM = eval('require("jsdom")').JSDOM; const minimatch = require("minimatch"); const ampBoilerplate = `body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}`; const ampNoscriptBoilerplate = `body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}`; const interpolate = (str, map) => str.replace(/{{\s*[\w\.]+\s*}}/g, match => map[match.replace(/[{}]/g, "")]); const onPreRenderHTML = ({ getHeadComponents, replaceHeadComponents, getPreBodyComponents, replacePreBodyComponents, getPostBodyComponents, replacePostBodyComponents, pathname }, { analytics, canonicalBaseUrl, components = [], includedPaths = [], excludedPaths = [], pathIdentifier = "/amp/", relAmpHtmlPattern = "{{canonicalBaseUrl}}{{pathname}}{{pathIdentifier}}" }) => { const headComponents = (0, _lodash.default)(getHeadComponents()); const preBodyComponents = getPreBodyComponents(); const postBodyComponents = getPostBodyComponents(); const isAmp = pathname && pathname.indexOf(pathIdentifier) > -1; if (isAmp) { const styles = headComponents.reduce((str, x) => { if (x.type === "style") { if (x.props.dangerouslySetInnerHTML) { str += x.props.dangerouslySetInnerHTML.__html; } } else if (x.key && x.key === "TypographyStyle") { str = `${x.props.typography.toString()}${str}`; } return str; }, ""); replaceHeadComponents([_react.default.createElement("script", { async: true, src: "https://cdn.ampproject.org/v0.js", __source: { fileName: _jsxFileName, lineNumber: 50 }, __self: void 0 }), _react.default.createElement("style", { "amp-boilerplate": "", dangerouslySetInnerHTML: { __html: ampBoilerplate }, __source: { fileName: _jsxFileName, lineNumber: 51 }, __self: void 0 }), _react.default.createElement("noscript", { __source: { fileName: _jsxFileName, lineNumber: 55 }, __self: void 0 }, _react.default.createElement("style", { "amp-boilerplate": "", dangerouslySetInnerHTML: { __html: ampNoscriptBoilerplate }, __source: { fileName: _jsxFileName, lineNumber: 56 }, __self: void 0 })), _react.default.createElement("style", { "amp-custom": "", dangerouslySetInnerHTML: { __html: styles }, __source: { fileName: _jsxFileName, lineNumber: 61 }, __self: void 0 }), ...components.map((component, i) => _react.default.createElement("script", { key: `custom-element-${i}`, async: true, "custom-element": `${typeof component === "string" ? component : component.name}`, src: `https://cdn.ampproject.org/v0/${typeof component === "string" ? component : component.name}-${typeof component === "string" ? "0.1" : component.version}.js`, __source: { fileName: _jsxFileName, lineNumber: 63 }, __self: void 0 })), analytics !== undefined ? _react.default.createElement("script", { async: true, "custom-element": "amp-analytics", src: "https://cdn.ampproject.org/v0/amp-analytics-0.1.js", __source: { fileName: _jsxFileName, lineNumber: 75 }, __self: void 0 }) : _react.default.createElement(_react.Fragment, { __source: { fileName: _jsxFileName, lineNumber: 81 }, __self: void 0 }), ...headComponents.filter(x => x.type !== "style" && (x.type !== "script" || x.props.type === "application/ld+json") && x.key !== "TypographyStyle")]); replacePreBodyComponents([...preBodyComponents.filter(x => x.key !== "plugin-google-tagmanager")]); replacePostBodyComponents(postBodyComponents.filter(x => x.type !== "script")); } else if (excludedPaths.length > 0 && pathname && excludedPaths.findIndex(_path => new _minimatch.Minimatch(pathname).match(_path)) < 0 || includedPaths.length > 0 && pathname && includedPaths.findIndex(_path => minimatch(pathname, _path)) > -1 || excludedPaths.length === 0 && includedPaths.length === 0) { replaceHeadComponents([_react.default.createElement("link", { rel: "amphtml", key: "gatsby-plugin-amp-amphtml-link", href: interpolate(relAmpHtmlPattern, { canonicalBaseUrl, pathIdentifier, pathname }).replace(/([^:])(\/\/+)/g, "$1/"), __source: { fileName: _jsxFileName, lineNumber: 107 }, __self: void 0 }), ...headComponents]); } }; exports.onPreRenderHTML = onPreRenderHTML; const onRenderBody = ({ setHeadComponents, setHtmlAttributes, setPreBodyComponents, pathname }, { analytics, canonicalBaseUrl, pathIdentifier = "/amp/", relCanonicalPattern = "{{canonicalBaseUrl}}{{pathname}}", useAmpClientIdApi = false }) => { const isAmp = pathname && pathname.indexOf(pathIdentifier) > -1; if (isAmp) { setHtmlAttributes({ amp: "" }); setHeadComponents([_react.default.createElement("link", { rel: "canonical", href: interpolate(relCanonicalPattern, { canonicalBaseUrl, pathname }).replace(pathIdentifier, "").replace(/([^:])(\/\/+)/g, "$1/"), __source: { fileName: _jsxFileName, lineNumber: 135 }, __self: void 0 }), useAmpClientIdApi ? _react.default.createElement("meta", { name: "amp-google-client-id-api", content: "googleanalytics", __source: { fileName: _jsxFileName, lineNumber: 145 }, __self: void 0 }) : _react.default.createElement(_react.Fragment, { __source: { fileName: _jsxFileName, lineNumber: 147 }, __self: void 0 })]); setPreBodyComponents([analytics != undefined ? _react.default.createElement("amp-analytics", { type: analytics.type, "data-credentials": analytics.dataCredentials, config: typeof analytics.config === "string" ? analytics.config : undefined, __source: { fileName: _jsxFileName, lineNumber: 152 }, __self: void 0 }, typeof analytics.config === "string" ? _react.default.createElement(_react.Fragment, { __source: { fileName: _jsxFileName, lineNumber: 160 }, __self: void 0 }) : _react.default.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: interpolate(JSON.stringify(analytics.config), { pathname }) }, __source: { fileName: _jsxFileName, lineNumber: 162 }, __self: void 0 })) : _react.default.createElement(_react.Fragment, { __source: { fileName: _jsxFileName, lineNumber: 173 }, __self: void 0 })]); } }; exports.onRenderBody = onRenderBody; const replaceRenderer = ({ bodyComponent, replaceBodyHTMLString, setHeadComponents, pathname }, { pathIdentifier = "/amp/" }) => { const defaults = { image: { width: 640, height: 475, layout: "responsive" }, twitter: { width: "390", height: "330", layout: "responsive" }, iframe: { width: 640, height: 475, layout: "responsive" } }; const headComponents = []; const isAmp = pathname && pathname.indexOf(pathIdentifier) > -1; if (isAmp) { const bodyHTML = (0, _server.renderToString)(bodyComponent); const dom = new JSDOM(bodyHTML); const document = dom.window.document; // convert images to amp-img or amp-anim const images = [].slice.call(document.getElementsByTagName("img")); images.forEach(image => { let ampImage; if (image.src && image.src.indexOf(".gif") > -1) { ampImage = document.createElement("amp-anim"); headComponents.push({ name: "amp-anim", version: "0.1" }); } else { ampImage = document.createElement("amp-img"); } const attributes = Object.keys(image.attributes); const includedAttributes = attributes.map(key => { const attribute = image.attributes[key]; ampImage.setAttribute(attribute.name, attribute.value); return attribute.name; }); Object.keys(defaults.image).forEach(key => { if (includedAttributes && includedAttributes.indexOf(key) === -1) { ampImage.setAttribute(key, defaults.image[key]); } }); image.parentNode.replaceChild(ampImage, image); }); // convert twitter posts to amp-twitter const twitterPosts = [].slice.call(document.getElementsByClassName("twitter-tweet")); twitterPosts.forEach(post => { headComponents.push({ name: "amp-twitter", version: "0.1" }); const ampTwitter = document.createElement("amp-twitter"); const attributes = Object.keys(post.attributes); const includedAttributes = attributes.map(key => { const attribute = post.attributes[key]; ampTwitter.setAttribute(attribute.name, attribute.value); return attribute.name; }); Object.keys(defaults.twitter).forEach(key => { if (includedAttributes && includedAttributes.indexOf(key) === -1) { ampTwitter.setAttribute(key, defaults.twitter[key]); } }); // grab the last link in the tweet for the twee id const links = [].slice.call(post.getElementsByTagName("a")); const link = links[links.length - 1]; const hrefArr = link.href.split("/"); const id = hrefArr[hrefArr.length - 1].split("?")[0]; ampTwitter.setAttribute("data-tweetid", id); // clone the original blockquote for a placeholder const _post = post.cloneNode(true); _post.setAttribute("placeholder", ""); ampTwitter.appendChild(_post); post.parentNode.replaceChild(ampTwitter, post); }); // convert iframes to amp-iframe or amp-youtube const iframes = [].slice.call(document.getElementsByTagName("iframe")); iframes.forEach(iframe => { let ampIframe; let attributes; if (iframe.src && iframe.src.indexOf("youtube.com/embed/") > -1) { headComponents.push({ name: "amp-youtube", version: "0.1" }); ampIframe = document.createElement("amp-youtube"); const src = iframe.src.split("/"); const id = src[src.length - 1].split("?")[0]; ampIframe.setAttribute("data-videoid", id); const placeholder = document.createElement("amp-img"); placeholder.setAttribute("src", `https://i.ytimg.com/vi/${id}/mqdefault.jpg`); placeholder.setAttribute("placeholder", ""); placeholder.setAttribute("layout", "fill"); ampIframe.appendChild(placeholder); const forbidden = ["allow", "allowfullscreen", "frameborder", "src"]; attributes = Object.keys(iframe.attributes).filter(key => { const attribute = iframe.attributes[key]; return !forbidden.includes(attribute.name); }); } else { headComponents.push({ name: "amp-iframe", version: "0.1" }); ampIframe = document.createElement("amp-iframe"); attributes = Object.keys(iframe.attributes); } const includedAttributes = attributes.map(key => { const attribute = iframe.attributes[key]; ampIframe.setAttribute(attribute.name, attribute.value); return attribute.name; }); Object.keys(defaults.iframe).forEach(key => { if (includedAttributes && includedAttributes.indexOf(key) === -1) { ampIframe.setAttribute(key, defaults.iframe[key]); } }); iframe.parentNode.replaceChild(ampIframe, iframe); }); setHeadComponents(Array.from(new Set(headComponents)).map((component, i) => _react.default.createElement(_react.Fragment, { key: `head-components-${i}`, __source: { fileName: _jsxFileName, lineNumber: 307 }, __self: void 0 }, _react.default.createElement("script", { async: true, "custom-element": component.name, src: `https://cdn.ampproject.org/v0/${component.name}-${component.version}.js`, __source: { fileName: _jsxFileName, lineNumber: 308 }, __self: void 0 })))); replaceBodyHTMLString(document.body.children[0].outerHTML); } }; exports.replaceRenderer = replaceRenderer;