Skip to content
This repository has been archived by the owner on Jan 12, 2023. It is now read-only.

Commit

Permalink
feat(static): Rewrite CSS URLs to Static ESI URLs so that better cach…
Browse files Browse the repository at this point in the history
…ing can be achieved

When static is called with the `?esi=true` parameter, then all URL references in CSS files will be replaced with ESI tags that include the `.esi` version of the same resource. This `.esi` version will then resolve, with the help of adobe/helix-publish#61 to a long-cachable URL for the same resource.

Partial implementation of adobe/helix-pipeline#267
  • Loading branch information
trieloff committed Apr 20, 2019
1 parent 507e4ce commit 75b47e5
Showing 1 changed file with 86 additions and 6 deletions.
92 changes: 86 additions & 6 deletions static.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
const request = require('request-promise-native');
const crypto = require('crypto');
const mime = require('mime-types');
const postcss = require('postcss');
const postcssurl = require('postcss-url');
const parser = require('postcss-value-parser');

const { space } = postcss.list;
const uri = require('uri-js');
/* eslint-disable no-console */

// one megabyte openwhisk limit + 20% Base64 inflation + safety padding
Expand Down Expand Up @@ -74,17 +80,89 @@ function isBinary(type) {
return true;
}

function isCSS(type) {
return type === 'text/css';
}

function isJavaScript(type) {
return type.match(/(text|application)\/(x-)?(javascript|ecmascript)/);
}

function rewriteImports(tree) {
tree.walkAtRules('import', (rule) => {
if (rule.name === 'import') {
const [url, queries] = space(rule.params);
const parsedurl = parser(url);
if (parsedurl.nodes
&& parsedurl.nodes.length === 1
&& parsedurl.nodes[0].value === 'url'
&& parsedurl.nodes[0].nodes
&& parsedurl.nodes[0].nodes.length === 1
&& parsedurl.nodes[0].nodes[0].type === 'string'
&& typeof parsedurl.nodes[0].nodes[0].value === 'string'
&& typeof parsedurl.nodes[0].nodes[0].quote === 'string') {
const importuri = uri.parse(parsedurl.nodes[0].nodes[0].value);
const { quote } = parsedurl.nodes[0].nodes[0];
if (importuri.reference === 'relative' && !importuri.query) {
rule.replaceWith(postcss.atRule({
name: 'import',
params: `url(${quote}<esi:include src="${importuri.path}.esi"/><esi:remove>${importuri.path}</esi:remove>${quote}) ${queries}`,
}));
}
} else if (parsedurl.nodes
&& parsedurl.nodes[0].type === 'string'
&& typeof parsedurl.nodes[0].value === 'string'
&& typeof parsedurl.nodes[0].quote === 'string') {
const importuri = uri.parse(parsedurl.nodes[0].value);
const { quote } = parsedurl.nodes[0];
if (importuri.reference === 'relative' && !importuri.query) {
rule.replaceWith(postcss.atRule({
name: 'import',
params: `${quote}<esi:include src="${importuri.path}.esi"/><esi:remove>${importuri.path}</esi:remove>${quote} ${queries}`,
}));
}
}
}
});
return tree;
}

function rewriteCSS(css) {
const processor = postcss()
.use(rewriteImports)
.use(postcssurl({
url: (asset) => {
// TODO pass in request URL and make it absolute.
if (asset.search === '' && asset.absolutePath !== '.' && asset.relativePath !== '.') {
return `<esi:include src="${asset.relativePath}.esi"/><esi:remove>${asset.relativePath}</esi:remove>`;
}
return asset.url;
},
}));
return processor.process(css, { from: undefined }).then(result => result.css);
}

function rewriteJavaScript(javascript) {
return javascript;
}

function isJSON(type) {
return !!type.match(/json/);
}

function getBody(type, responsebody) {
function getBody(type, responsebody, esi = false) {
if (isBinary(type)) {
return Buffer.from(responsebody).toString('base64');
}
if (isJSON(type)) {
return JSON.parse(responsebody);
}
if (esi && isCSS(type)) {
return rewriteCSS(responsebody.toString());
}
if (esi && isJavaScript(type)) {
return rewriteJavaScript(responsebody.toString());
}
return responsebody.toString();
}

Expand All @@ -103,7 +181,7 @@ function staticBase(owner, repo, entry, ref, strain = 'default') {
return `__HLX/${owner}/${repo}/${strain}/${ref}/${entry}/DIST__`;
}

function deliverPlain(owner, repo, ref, entry, root) {
function deliverPlain(owner, repo, ref, entry, root, esi = false) {
const cleanentry = (`${root}/${entry}`).replace(/^\//, '').replace(/[/]+/g, '/');
console.log('deliverPlain()', owner, repo, ref, cleanentry);
const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${cleanentry}`;
Expand All @@ -116,12 +194,12 @@ function deliverPlain(owner, repo, ref, entry, root) {
encoding: null,
};

return request.get(rawopts).then((response) => {
return request.get(rawopts).then(async (response) => {
const type = mime.lookup(cleanentry) || 'application/octet-stream';
const size = parseInt(response.headers['content-length'], 10);
console.log('size', size);
if (size < REDIRECT_LIMIT) {
const body = getBody(type, response.body);
const body = await getBody(type, response.body, esi);
console.log(`delivering file ${cleanentry} type ${type} binary: ${isBinary(type)}`);
return {
statusCode: 200,
Expand Down Expand Up @@ -184,6 +262,7 @@ function blacklisted(path, allow, deny) {
* @param {string} params.allow regular expression pattern that all delivered files must follow
* @param {string} params.deny regular expression pattern that all delivered files may not follow
* @param {string} params.root document root for all static files in the repository
* @param {boolean} params.esi replace relative URL references in JS and CSS with ESI references
*/
async function main({
owner,
Expand All @@ -196,6 +275,7 @@ async function main({
allow,
deny,
root = '',
esi = false,
}) {
console.log('main()', owner, repo, ref, path, entry, strain, plain, allow, deny, root);

Expand All @@ -204,12 +284,12 @@ async function main({
}

if (plain) {
return deliverPlain(owner, repo, ref, entry, root);
return deliverPlain(owner, repo, ref, entry, root, esi);
}

return forbidden();
}

module.exports = {
main, error, addHeaders, isBinary, staticBase, blacklisted,
main, error, addHeaders, isBinary, staticBase, blacklisted, getBody,
};

0 comments on commit 75b47e5

Please sign in to comment.