UNPKG

gatsby-plugin-gatsby-cloud

Version:
169 lines (163 loc) 6.05 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = buildHeadersProgram; var _flow2 = _interopRequireDefault(require("lodash/flow")); var _map2 = _interopRequireDefault(require("lodash/map")); var _mapValues2 = _interopRequireDefault(require("lodash/mapValues")); var _mergeWith2 = _interopRequireDefault(require("lodash/mergeWith")); var _union2 = _interopRequireDefault(require("lodash/union")); var _isArray2 = _interopRequireDefault(require("lodash/isArray")); var _fsExtra = require("fs-extra"); var _constants = require("./constants"); var _ipc = require("./ipc"); function getHeaderName(header) { const matches = header.match(/^([^:]+):/); return matches && matches[1]; } function headersPath(pathPrefix, path) { return `${pathPrefix}${path}`; } function defaultMerge(...headers) { function unionMerge(objValue, srcValue) { if ((0, _isArray2.default)(objValue)) { return (0, _union2.default)(objValue, srcValue); } else { return undefined; // opt into default merge behavior } } return (0, _mergeWith2.default)({}, ...headers, unionMerge); } function headersMerge(userHeaders, defaultHeaders) { const merged = {}; Object.keys(defaultHeaders).forEach(path => { if (!userHeaders[path]) { merged[path] = defaultHeaders[path]; return; } const headersMap = {}; defaultHeaders[path].forEach(header => { headersMap[getHeaderName(header)] = header; }); userHeaders[path].forEach(header => { headersMap[getHeaderName(header)] = header; // override if exists }); merged[path] = Object.values(headersMap); }); Object.keys(userHeaders).forEach(path => { if (!merged[path]) { merged[path] = userHeaders[path]; } }); return merged; } function transformLink(manifest, publicFolder, pathPrefix) { return header => header.replace(_constants.LINK_REGEX, (__, prefix, file, suffix) => { const hashed = manifest[file]; if (hashed) { return `${prefix}${pathPrefix}${hashed}${suffix}`; } else if ((0, _fsExtra.existsSync)(publicFolder(file))) { return `${prefix}${pathPrefix}${file}${suffix}`; } else { throw new Error(`Could not find the file specified in the Link header \`${header}\`.` + `The gatsby-plugin-gatsby-cloud is looking for a matching file (with or without a ` + `webpack hash). Check the public folder and your gatsby-config.js to ensure you are ` + `pointing to a public file.`); } }); } // program methods const mapUserLinkHeaders = ({ manifest, pathPrefix, publicFolder }) => headers => (0, _mapValues2.default)(headers, headerList => (0, _map2.default)(headerList, transformLink(manifest, publicFolder, pathPrefix))); const mapUserLinkAllPageHeaders = (pluginData, { allPageHeaders }) => headers => { if (!allPageHeaders) { return headers; } const { pages, manifest, publicFolder, pathPrefix } = pluginData; const headersList = (0, _map2.default)(allPageHeaders, transformLink(manifest, publicFolder, pathPrefix)); const duplicateHeadersByPage = {}; pages.forEach(page => { const pathKey = headersPath(pathPrefix, page.path); duplicateHeadersByPage[pathKey] = headersList; }); return defaultMerge(headers, duplicateHeadersByPage); }; const applySecurityHeaders = ({ mergeSecurityHeaders }) => headers => { if (!mergeSecurityHeaders) { return headers; } // It is a common use case to want to iframe preview if (process.env.GATSBY_IS_PREVIEW === `true`) { _constants.SECURITY_HEADERS[`/*`] = _constants.SECURITY_HEADERS[`/*`].filter(headers => headers !== `X-Frame-Options: DENY`); } return headersMerge(headers, _constants.SECURITY_HEADERS); }; const applyCachingHeaders = (pluginData, { mergeCachingHeaders }) => headers => { if (!mergeCachingHeaders) { return headers; } const files = new Set(); for (const [_assetOrChunkName, fileNameOrArrayOfFileNames] of Object.entries(pluginData.manifest)) { if (Array.isArray(fileNameOrArrayOfFileNames)) { for (const filename of fileNameOrArrayOfFileNames) { files.add(filename); } } else if (typeof fileNameOrArrayOfFileNames === `string`) { files.add(fileNameOrArrayOfFileNames); } } const cachingHeaders = {}; files.forEach(file => { if (typeof file === `string`) { cachingHeaders[`/` + file] = [_constants.IMMUTABLE_CACHING_HEADER]; } }); return defaultMerge(headers, cachingHeaders, _constants.CACHING_HEADERS); }; const applyTransfromHeaders = ({ transformHeaders }) => headers => (0, _mapValues2.default)(headers, transformHeaders); const sendHeadersViaIPC = async headers => { /** * Emit Headers via IPC */ let lastMessage; Object.entries(headers).forEach(([k, val]) => { lastMessage = (0, _ipc.emitHeaders)({ url: k, headers: val }); }); await lastMessage; }; const writeHeadersFile = async (publicFolder, contents) => new Promise((resolve, reject) => { const contentsStr = JSON.stringify(contents); const writeStream = (0, _fsExtra.createWriteStream)(publicFolder(_constants.HEADERS_FILENAME)); const chunkSize = 10000; const numChunks = Math.ceil(contentsStr.length / chunkSize); for (let i = 0; i < numChunks; i++) { writeStream.write(contentsStr.slice(i * chunkSize, Math.min((i + 1) * chunkSize, contentsStr.length))); } writeStream.end(); writeStream.on(`finish`, () => { resolve(); }); writeStream.on(`error`, reject); }); const saveHeaders = ({ publicFolder }) => contents => Promise.all([sendHeadersViaIPC(contents), writeHeadersFile(publicFolder, contents)]); function buildHeadersProgram(pluginData, pluginOptions) { return (0, _flow2.default)(mapUserLinkHeaders(pluginData), applySecurityHeaders(pluginOptions), applyCachingHeaders(pluginData, pluginOptions), mapUserLinkAllPageHeaders(pluginData, pluginOptions), applyTransfromHeaders(pluginOptions), saveHeaders(pluginData))(pluginOptions.headers); }