"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = buildHeadersProgram; var _lodash = _interopRequireDefault(require("lodash")); var _fsExtra = require("fs-extra"); var _path = require("path"); var _kebabHash = _interopRequireDefault(require("kebab-hash")); var _constants = require("./constants"); function validHeaders(headers) { if (!headers || !_lodash.default.isObject(headers)) { return false; } return _lodash.default.every(headers, (headersList, path) => _lodash.default.isArray(headersList) && _lodash.default.every(headersList, header => _lodash.default.isString(header))); } function linkTemplate(assetPath, type = `script`) { return `Link: <${assetPath}>; rel=preload; as=${type}`; } function pathChunkName(path) { const name = path === `/` ? `index` : (0, _kebabHash.default)(path); return `path---${name}`; } function createScriptHeaderGenerator(manifest, pathPrefix) { return script => { const chunk = manifest[script]; if (!chunk) { return null; } // convert to array if it's not already const chunks = _lodash.default.isArray(chunk) ? chunk : [chunk]; return chunks.filter(script => { const parsed = (0, _path.parse)(script); // handle only .js, .css content is inlined already // and doesn't need to be pushed return parsed.ext === `.js`; }).map(script => linkTemplate(`${pathPrefix}/${script}`)).join(`\n `); }; } function linkHeaders(scripts, manifest, pathPrefix) { return _lodash.default.compact(scripts.map(createScriptHeaderGenerator(manifest, pathPrefix))); } function headersPath(pathPrefix, path) { return `${pathPrefix}${path}`; } function preloadHeadersByPage(pages, manifest, pathPrefix) { let linksByPage = {}; pages.forEach(page => { const scripts = [..._constants.COMMON_BUNDLES, pathChunkName(page.path), page.componentChunkName]; const pathKey = headersPath(pathPrefix, page.path); linksByPage[pathKey] = linkHeaders(scripts, manifest, pathPrefix); }); return linksByPage; } function defaultMerge(...headers) { function unionMerge(objValue, srcValue) { if (_lodash.default.isArray(objValue)) { return _lodash.default.union(objValue, srcValue); } else { return undefined; // opt into default merge behavior } } return _lodash.default.mergeWith({}, ...headers, unionMerge); } 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-netlify 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.`); } }); } // Writes out headers file format, with two spaces for indentation // https://www.netlify.com/docs/headers-and-basic-auth/ function stringifyHeaders(headers) { return _lodash.default.reduce(headers, (text, headerList, path) => { const headersString = _lodash.default.reduce(headerList, (accum, header) => `${accum} ${header}\n`, ``); return `${text}${path}\n${headersString}`; }, ``); } // program methods const validateUserOptions = pluginOptions => headers => { if (!validHeaders(headers)) { throw new Error(`The "headers" option to gatsby-plugin-netlify is in the wrong shape. ` + `You should pass in a object with string keys (representing the paths) and an array ` + `of strings as the value (representing the headers). ` + `Check your gatsby-config.js.`); } ; [`mergeSecurityHeaders`, `mergeLinkHeaders`, `mergeCachingHeaders`].forEach(mergeOption => { if (!_lodash.default.isBoolean(pluginOptions[mergeOption])) { throw new Error(`The "${mergeOption}" option to gatsby-plugin-netlify must be a boolean. ` + `Check your gatsby-config.js.`); } }); if (!_lodash.default.isFunction(pluginOptions.transformHeaders)) { throw new Error(`The "transformHeaders" option to gatsby-plugin-netlify must be a function ` + `that returns a array of header strings.` + `Check your gatsby-config.js.`); } return headers; }; const mapUserLinkHeaders = ({ manifest, pathPrefix, publicFolder }) => headers => _lodash.default.mapValues(headers, headerList => _lodash.default.map(headerList, transformLink(manifest, publicFolder, pathPrefix))); const mapUserLinkAllPageHeaders = (pluginData, { allPageHeaders }) => headers => { if (!allPageHeaders) { return headers; } const { pages, manifest, publicFolder, pathPrefix } = pluginData; const headersList = _lodash.default.map(allPageHeaders, transformLink(manifest, publicFolder, pathPrefix)); const duplicateHeadersByPage = {}; pages.forEach(page => { const pathKey = headersPath(pathPrefix, page.path); duplicateHeadersByPage[pathKey] = headersList; }); return defaultMerge(headers, duplicateHeadersByPage); }; const applyLinkHeaders = (pluginData, { mergeLinkHeaders }) => headers => { if (!mergeLinkHeaders) { return headers; } const { pages, manifest, pathPrefix } = pluginData; const perPageHeaders = preloadHeadersByPage(pages, manifest, pathPrefix); return defaultMerge(headers, perPageHeaders); }; const applySecurityHeaders = ({ mergeSecurityHeaders }) => headers => { if (!mergeSecurityHeaders) { return headers; } return defaultMerge(headers, _constants.SECURITY_HEADERS); }; const applyCachingHeaders = (pluginData, { mergeCachingHeaders }) => headers => { if (!mergeCachingHeaders) { return headers; } const chunks = Array.from(pluginData.pages.values()).map(page => page.componentChunkName); chunks.push(`pages-manifest`, `app`); const files = [].concat(...chunks.map(chunk => pluginData.manifest[chunk])); const cachingHeaders = {}; files.forEach(file => { cachingHeaders[`/` + file] = [_constants.IMMUTABLE_CACHING_HEADER]; }); return defaultMerge(headers, cachingHeaders, _constants.CACHING_HEADERS); }; const applyTransfromHeaders = ({ transformHeaders }) => headers => _lodash.default.mapValues(headers, transformHeaders); const transformToString = headers => `${_constants.HEADER_COMMENT}\n\n${stringifyHeaders(headers)}`; const writeHeadersFile = ({ publicFolder }) => contents => (0, _fsExtra.writeFile)(publicFolder(_constants.NETLIFY_HEADERS_FILENAME), contents); function buildHeadersProgram(pluginData, pluginOptions) { return _lodash.default.flow(validateUserOptions(pluginOptions), mapUserLinkHeaders(pluginData), applySecurityHeaders(pluginOptions), applyCachingHeaders(pluginData, pluginOptions), mapUserLinkAllPageHeaders(pluginData, pluginOptions), applyLinkHeaders(pluginData, pluginOptions), applyTransfromHeaders(pluginOptions), transformToString, writeHeadersFile(pluginData))(pluginOptions.headers); }