diff --git a/docs/_headers b/docs/_headers new file mode 100644 index 0000000000..522acdf8ae --- /dev/null +++ b/docs/_headers @@ -0,0 +1,9 @@ +/* + X-Frame-Options: DENY + X-XSS-Protection: 1; mode=block + X-Content-Type-Options: nosniff + Strict-Transport-Security: max-age=31536000, includeSubDomains + +## Far future expires for hashed file names +/static/* + Cache-Control: max-age=31536000 diff --git a/package.json b/package.json index c563045586..84dfc7e218 100644 --- a/package.json +++ b/package.json @@ -305,7 +305,7 @@ "coveralls": "nyc report --reporter=text-lcov | coveralls", "prebuildDocs": "rm -rf docs/_dist && node scripts/docs-update-toc.js", "buildDocs": "bundle exec jekyll build --source ./docs --destination ./docs/_site --config ./docs/_config.yml --safe --drafts", - "postbuildDocs": "buildProduction docs/_site/index.html --outroot docs/_dist --canonicalroot https://mochajs.org/ --optimizeimages --inlinehtmlimage 10000 --asyncscripts ", + "postbuildDocs": "cp docs/_headers docs/_site/_headers && buildProduction docs/_site/index.html docs/_site/_headers --outroot docs/_dist --canonicalroot https://mochajs.org/ --optimizeimages --inlinehtmlimage 10000 --asyncscripts && cp docs/_headers docs/_dist/_headers && node scripts/netlify-headers.js >> docs/_dist/_headers", "prewatchDocs": "node scripts/docs-update-toc.js", "watchDocs": "bundle exec jekyll serve --source ./docs --destination ./docs/_site --config ./docs/_config.yml --safe --drafts --watch" }, diff --git a/scripts/netlify-headers.js b/scripts/netlify-headers.js new file mode 100644 index 0000000000..cc38fd3353 --- /dev/null +++ b/scripts/netlify-headers.js @@ -0,0 +1,103 @@ +var AssetGraph = require('assetgraph'); + +var headers = [ + 'Content-Security-Policy' +]; + +var resourceHintTypeMap = { + HtmlPreloadLink: 'preload', + HtmlPrefetchLink: 'prefetch', + HtmlPreconnectLink: 'preconnect', + HtmlDnsPrefetchLink: 'dns-prefetch' +}; + +function getHeaderForRelation (rel) { + let header = `Link: <${rel.href}>; rel=${resourceHintTypeMap[rel.type]}; as=${rel.as}; type=${rel.to.contentType}`; + + if (rel.crossorigin || rel.as === 'font') { + header = `${header}; crossorigin=anonymous`; + } + + return header; +} + +new AssetGraph({ root: 'docs/_dist' }) + .loadAssets('*.html') + .populate({ + followRelations: { type: 'HtmlAnchor', crossorigin: false } + }) + .queue(function (assetGraph) { + var assets = assetGraph.findAssets({ type: 'Html', isInline: false }); + + var headerMap = {}; + + assets.forEach(function (asset) { + var url = '/' + asset.url.replace(assetGraph.root, '').replace(/#.*/, '').replace('index.html', ''); + if (!headerMap[url]) { + headerMap[url] = []; + } + + headers.forEach(function (header) { + var node = asset.parseTree.querySelector('meta[http-equiv=' + header + ']'); + + if (node) { + headerMap[url].push(`${header}: ${node.getAttribute('content')}`) + + node.parentNode.removeChild(node); + asset.markDirty(); + } + }); + + var firstCssRel = asset.outgoingRelations.filter(r => { + return r.type === 'HtmlStyle' + && r.crossorigin === false + && r.href !== undefined; + })[0]; + + if (firstCssRel) { + const header = `Link: <${firstCssRel.href}>; rel=preload; as=style`; + + + headerMap[url].push(header); + } + + var resourceHintRelations = asset.outgoingRelations.filter(r => ['HtmlPreloadLink', 'HtmlPrefetchLink'].includes(r.type)); + + resourceHintRelations.forEach(rel => { + headerMap[url].push(getHeaderForRelation(rel)); + + rel.detach(); + }); + + var resourceHintRelations = asset.outgoingRelations.filter(r => ['HtmlPreconnectLink'].includes(r.type)); + + resourceHintRelations.forEach(rel => { + let header = `Link: <${rel.href}>; rel=preconnect`; + + if (rel.crossorigin) { + header = `${header}; crossorigin=anonymous`; + } + + headerMap[url].push(header); + + rel.detach(); + }); + }); + + console.log('\n## Autogenerated headers:\n') + + Object.keys(headerMap).forEach(function (url) { + console.log(url); + + var httpHeaders = headerMap[url]; + + httpHeaders.forEach(function (header) { + console.log(` ${header}`) + }); + + console.log(''); + }); + + }) + .writeAssetsToDisc({ isLoaded: true }) + .run();