Skip to content

Commit

Permalink
Merge pull request #473 from oyejorge/master
Browse files Browse the repository at this point in the history
Resolve CSS variables
  • Loading branch information
jrit committed Oct 20, 2023
2 parents ed2697c + 21f4b5d commit 7827d94
Show file tree
Hide file tree
Showing 14 changed files with 133 additions and 19 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ All juice methods take an options object that can contain any of these propertie

* `removeStyleTags` - whether to remove the original `<style></style>` tags after (possibly) inlining the css from them. Defaults to `true`.

* `resolveCSSVariables` - whether to resolve CSS variables. Defaults to `true`.

* `webResources` - An options object that will be passed to [web-resource-inliner](https://www.npmjs.com/package/web-resource-inliner) for juice functions that will get remote resources (`juiceResources` and `juiceFile`). Defaults to `{}`.

* `xmlMode` - whether to output XML/XHTML with all tags closed. Note that the input *must* also be valid XML/XHTML or you will get undesirable results. Defaults to `false`.
Expand Down
1 change: 1 addition & 0 deletions juice.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ declare namespace juice {
inlinePseudoElements?: boolean;
xmlMode?: boolean;
preserveImportant?: boolean;
resolveCSSVariables?: boolean;
}

interface WebResourcesOptions {
Expand Down
4 changes: 4 additions & 0 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ cli.options = {
pMap: 'xmlMode',
def: 'generate output with tags closed? input must be valid XML',
coercion: JSON.parse },
'resolve-css-variables': {
pMap: 'resolveCSSVariables',
def: 'resolve CSS variables',
coercion: JSON.parse },
'web-resources-inline-attribute': {
pMap: 'webResourcesInlineAttribute',
map: 'inlineAttribute',
Expand Down
23 changes: 13 additions & 10 deletions lib/inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

var utils = require('./utils');
var numbers = require('./numbers');
var variables = require('./variables');

module.exports = function makeJuiceClient(juiceClient) {

Expand Down Expand Up @@ -250,13 +251,23 @@ function inlineDocument($, css, options) {
props.sort(function(a, b) {
return a.compareFunc(b);
});

var string = props
.filter(function(prop) {

// don't add css variables if we're resolving their values
if (options.resolveCSSVariables && (prop.prop.indexOf('--') === 0) ) {
return false;
}

// Content becomes the innerHTML of pseudo elements, not used as a
// style property
return prop.prop !== 'content';
return (prop.prop !== 'content');
})
.map(function(prop) {
if (options.resolveCSSVariables) {
prop.value = variables.replaceVariables(el,prop.value);
}
return prop.prop + ': ' + prop.value.replace(/["]/g, '\'') + ';';
})
.join(' ');
Expand Down Expand Up @@ -343,15 +354,7 @@ function removeImportant(value) {
return value.replace(/\s*!important$/, '')
}

function findVariableValue(el, variable) {
while (el) {
if (variable in el.styleProps) {
return el.styleProps[variable].value;
}

var el = el.pseudoElementParent || el.parent;
}
}

function applyCounterStyle(counter, style) {
switch (style) {
Expand Down Expand Up @@ -392,7 +395,7 @@ function parseContent(el) {

var varMatch = tokens[i].match(/var\s*\(\s*(.*?)\s*(,\s*(.*?)\s*)?\s*\)/i);
if (varMatch) {
var variable = findVariableValue(el, varMatch[1]) || varMatch[2];
var variable = variables.findVariableValue(el, varMatch[1]) || varMatch[2];
parsed.push(variable.replace(/^['"]|['"]$/g, ''));
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ exports.extract = function extract(selectorText) {
if (sel.length) {
sels.push(sel);
}

return sels;
};

Expand Down Expand Up @@ -158,6 +157,7 @@ exports.getDefaultOptions = function(options) {
applyWidthAttributes: true,
applyHeightAttributes: true,
applyAttributesTableElements: true,
resolveCSSVariables: true,
url: ''
}, options);

Expand Down
70 changes: 70 additions & 0 deletions lib/variables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict';

const uniqueString = (string) => {
let str = '';
do{
str = (Math.random() + 1).toString(36).substring(2);

}while(string.indexOf(str) !== -1);

return str;
}

/**
* Replace css variables with their value
*/
const replaceVariables = (el,value) => {

// find non-nested css function calls
// eg: rgb(...), drop-shadow(...)
let funcReg = /([a-z\-]+)\s*\(\s*([^\(\)]*?)\s*(?:,\s*([^\(\)]*?)\s*)?\s*\)/i;
let replacements = [];
let match;
let uniq = uniqueString(value);

while( (match = funcReg.exec(value)) !== null ){
let i = `${replacements.length}`;


// attempt to resolve variables
if( match[1].toLowerCase() == 'var' ){
const varValue = findVariableValue(el, match[2]);

// found variable value
if( varValue ){
value = value.replace(match[0],varValue);
continue;
}

// use default value
// var(--name , default-value)
if( match[3] ){
value = value.replace(match[0],match[3]);
continue;
}
}

let placeholder = `${uniq}${i.padStart(5,'-')}`;
value = value.replace(match[0],placeholder);
replacements.push({placeholder,replace:match[0]});
}

for( var i = replacements.length-1; i >=0; i--){
const replacement = replacements[i];
value = value.replace(replacement.placeholder,replacement.replace);
}

return value;
}

const findVariableValue = (el, variable) => {
while (el) {
if (el.styleProps && variable in el.styleProps) {
return el.styleProps[variable].value;
}

var el = el.pseudoElementParent || el.parent;
}
}

module.exports = { replaceVariables, findVariableValue };
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions test/cases/css-variables.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
:root{
--border-width:1px;
--border-color:red;
--border-style-default:solid;
}
p {
--border-width:2px;
--border-style:var(--border-style-default);
border: VAR(--border-width) var(--border-style) var(--border-color);
background: var(--bg);
}

td{
background-color: var(--bg,var(--border-color,rgb(255,0,0)));
}
6 changes: 6 additions & 0 deletions test/cases/css-variables.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

<html>
<body>
<p>woot</p>
<td>woot</td>
</body></html>
4 changes: 4 additions & 0 deletions test/cases/css-variables.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"resolveCSSVariables": true,
"applyAttributesTableElements": true
}
5 changes: 5 additions & 0 deletions test/cases/css-variables.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<html>
<body>
<p style="border: 2px solid red; background: var(--bg);">woot</p>
<td style="background-color: red;" bgcolor="red">woot</td>
</body></html>
3 changes: 2 additions & 1 deletion test/cases/juice-content/pseudo-elements.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"url": "./",
"removeStyleTags": true,
"inlinePseudoElements": true
"inlinePseudoElements": true,
"resolveCSSVariables": false
}
1 change: 1 addition & 0 deletions test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ it('cli parses options', function(done) {
assert.strictEqual(cli.argsToOptions({'applyHeightAttributes': 'true'}).applyHeightAttributes, true);
assert.strictEqual(cli.argsToOptions({'applyAttributesTableElements': 'true'}).applyAttributesTableElements, true);
assert.strictEqual(cli.argsToOptions({'xmlMode': 'true'}).xmlMode, true);
assert.strictEqual(cli.argsToOptions({'resolveCSSVariables': 'true'}).resolveCSSVariables, true);
assert.strictEqual(cli.argsToOptions({'webResourcesInlineAttribute': 'true'}).webResources.inlineAttribute, true);
assert.strictEqual(cli.argsToOptions({'webResourcesImages': '12'}).webResources.images, 12);
assert.strictEqual(cli.argsToOptions({'webResourcesLinks': 'true'}).webResources.links, true);
Expand Down
12 changes: 7 additions & 5 deletions test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,18 @@ optionFiles.forEach(function(file) {
});

function read(file) {
return fs.readFileSync(file, 'utf8');
try{
return fs.readFileSync(file, 'utf8');
}catch(err){}
}

function test(testName, options) {
var base = __dirname + '/cases/' + testName;
var html = read(base + '.html');
var css = read(base + '.css');
var config = options ? JSON.parse(read(base + '.json')) : null;
var config = read(base + '.json');
config = config ? JSON.parse(config) : null;

options = {};

return function(done) {
var onJuiced = function(err, actual) {
Expand All @@ -68,8 +70,8 @@ function test(testName, options) {
done();
};

if (config === null) {
onJuiced(null, juice.inlineContent(html, css, options));
if( !options ) {
onJuiced(null, juice.inlineContent(html, css, config));
} else {
juice.juiceResources(html, config, onJuiced);
}
Expand Down

0 comments on commit 7827d94

Please sign in to comment.