gatsby-plugin-gatsby-cloud
Version:
A Gatsby plugin which optimizes working with Gatsby Cloud
169 lines (163 loc) • 6.05 kB
JavaScript
;
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);
}