@draftbox-co/gatsby-plugin-amp
Version:
A gatsby plugin for scaffolding AMP pages
344 lines (304 loc) • 13.7 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
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 = require("lodash");
var _path2 = _interopRequireDefault(require("path"));
var _fs = _interopRequireDefault(require("fs"));
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 = [],
dirName,
themePath,
pathIdentifier = "/amp/",
relAmpHtmlPattern = "{{canonicalBaseUrl}}{{pathname}}{{pathIdentifier}}"
}) => {
const headComponents = (0, _lodash.flattenDeep)(getHeadComponents());
const preBodyComponents = getPreBodyComponents();
const postBodyComponents = getPostBodyComponents();
const isAmp = pathname && pathname.indexOf(pathIdentifier) > -1;
if (isAmp) {
const file = _fs.default.readFileSync(_path2.default.resolve(dirName, themePath));
const styles = file.toString();
replaceHeadComponents([/*#__PURE__*/_react.default.createElement("script", {
async: true,
src: "https://cdn.ampproject.org/v0.js"
}), /*#__PURE__*/_react.default.createElement("style", {
"amp-boilerplate": "",
dangerouslySetInnerHTML: {
__html: ampBoilerplate
}
}), /*#__PURE__*/_react.default.createElement("noscript", null, /*#__PURE__*/_react.default.createElement("style", {
"amp-boilerplate": "",
dangerouslySetInnerHTML: {
__html: ampNoscriptBoilerplate
}
})), /*#__PURE__*/_react.default.createElement("style", {
"amp-custom": "",
dangerouslySetInnerHTML: {
__html: styles
}
}), ...components.map((component, i) => /*#__PURE__*/_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`
})), analytics !== undefined ? /*#__PURE__*/_react.default.createElement("script", {
async: true,
"custom-element": "amp-analytics",
src: "https://cdn.ampproject.org/v0/amp-analytics-0.1.js"
}) : /*#__PURE__*/_react.default.createElement(_react.Fragment, null), ...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([/*#__PURE__*/_react.default.createElement("link", {
rel: "amphtml",
key: "gatsby-plugin-amp-amphtml-link",
href: interpolate(relAmpHtmlPattern, {
canonicalBaseUrl,
pathIdentifier,
pathname
}).replace(/([^:])(\/\/+)/g, "$1/")
}), ...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([/*#__PURE__*/_react.default.createElement("link", {
rel: "canonical",
href: interpolate(relCanonicalPattern, {
canonicalBaseUrl,
pathname
}).replace(pathIdentifier, "").replace(/([^:])(\/\/+)/g, "$1/")
}), useAmpClientIdApi ? /*#__PURE__*/_react.default.createElement("meta", {
name: "amp-google-client-id-api",
content: "googleanalytics"
}) : /*#__PURE__*/_react.default.createElement(_react.Fragment, null)]);
setPreBodyComponents([analytics != undefined ? /*#__PURE__*/_react.default.createElement("amp-analytics", {
type: analytics.type,
"data-credentials": analytics.dataCredentials,
config: typeof analytics.config === "string" ? analytics.config : undefined
}, typeof analytics.config === "string" ? /*#__PURE__*/_react.default.createElement(_react.Fragment, null) : /*#__PURE__*/_react.default.createElement("script", {
type: "application/json",
dangerouslySetInnerHTML: {
__html: interpolate(JSON.stringify(analytics.config), {
pathname
})
}
})) : /*#__PURE__*/_react.default.createElement(_react.Fragment, null)]);
}
};
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.trim(), attribute.value.trim());
return attribute.name;
});
Object.keys(defaults.image).forEach(key => {
if (includedAttributes && includedAttributes.indexOf(key) === -1) {
ampImage.setAttribute(key.trim(), 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.trim(), attribute.value.trim());
return attribute.name;
});
Object.keys(defaults.twitter).forEach(key => {
if (includedAttributes && includedAttributes.indexOf(key) === -1) {
ampTwitter.setAttribute(key.trim(), 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.trim()); // 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.trim());
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.trim(), attribute.value.trim());
return attribute.name;
});
Object.keys(defaults.iframe).forEach(key => {
if (includedAttributes && includedAttributes.indexOf(key) === -1) {
ampIframe.setAttribute(key.trim(), defaults.iframe[key]);
}
});
iframe.parentNode.replaceChild(ampIframe, iframe);
}); // convert amp-instagram
const igEmbeds = [].slice.call(document.getElementsByClassName("instagram-media"));
igEmbeds.forEach(igEmbed => {
let ampIgEmbed;
let shortCode;
let igPermalink;
igPermalink = igEmbed.attributes["data-instgrm-permalink"].value;
shortCode = igPermalink.match(/\/p\/(.+)\//)[1];
headComponents.push({
name: "amp-instagram",
version: "0.1"
});
ampIgEmbed = document.createElement("amp-instagram");
ampIgEmbed.setAttribute("data-shortcode", shortCode.trim());
ampIgEmbed.setAttribute("layout", "responsive");
ampIgEmbed.setAttribute("width", 1); // The value doesn't matter, but it's required
ampIgEmbed.setAttribute("height", 1); // The value doesn't matter, but it's required
ampIgEmbed.setAttributeNode(document.createAttribute("data-captioned"));
igEmbed.parentNode.replaceChild(ampIgEmbed, igEmbed);
});
document.querySelectorAll('script[src="//www.instagram.com/embed.js"]').forEach(script => {
script.remove();
}); // convert amp-facebook
const fbPosts = [].slice.call(document.getElementsByClassName("fb-post"));
fbPosts.forEach(fbPost => {
let ampFbPost;
headComponents.push({
name: "amp-facebook",
version: "0.1"
});
ampFbPost = document.createElement("amp-facebook");
ampFbPost.setAttribute("data-href", fbPost.attributes["data-href"].value);
ampFbPost.setAttribute("width", 1); // The value doesn't matter, but it's required
ampFbPost.setAttribute("height", 1); // The value doesn't matter, but it's required
ampFbPost.setAttribute("layout", "responsive");
fbPost.parentNode.replaceChild(ampFbPost, fbPost);
});
document.querySelectorAll('script[src^="https://connect.facebook.net/en_US/sdk.js"]').forEach(script => {
script.remove();
});
setHeadComponents(Array.from(new Set(headComponents)).map((component, i) => /*#__PURE__*/_react.default.createElement(_react.Fragment, {
key: `head-components-${i}`
}, /*#__PURE__*/_react.default.createElement("script", {
async: true,
"custom-element": component.name,
src: `https://cdn.ampproject.org/v0/${component.name}-${component.version}.js`
}))));
replaceBodyHTMLString(document.body.children[0].outerHTML);
}
};
exports.replaceRenderer = replaceRenderer;