213 lines
7.1 KiB
JavaScript
213 lines
7.1 KiB
JavaScript
"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);
|
|
} |