From 3855d2ca4d14ce598cfbbd48c255e069e500b299 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sun, 18 Aug 2019 22:15:18 +0200 Subject: [PATCH 1/6] WIP, carried over from assetgraph/assetgraph#989 Awaiting https://github.com/shellscape/postcss-values-parser/issues/75 --- lib/injectSubsetDefinitions.js | 37 +++++++++++++++++---------------- package.json | 2 +- test/injectSubsetDefinitions.js | 2 +- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/injectSubsetDefinitions.js b/lib/injectSubsetDefinitions.js index ee531a96..4ddd0c75 100644 --- a/lib/injectSubsetDefinitions.js +++ b/lib/injectSubsetDefinitions.js @@ -5,30 +5,28 @@ function injectSubsetDefinitions(cssValue, webfontNameMap, replaceOriginal) { const subsetFontNames = new Set( Object.values(webfontNameMap).map(name => name.toLowerCase()) ); - const tokens = postcssValuesParser(cssValue).tokens; - let resultStr = ''; + const rootNode = postcssValuesParser.parse(cssValue); let isPreceededByWords = false; - for (let i = 0; i < tokens.length; i += 1) { - const token = tokens[i]; + for (const [i, node] of rootNode.nodes.entries()) { let possibleFontFamily; let lastFontFamilyTokenIndex = i; - if (token[0] === 'string') { - possibleFontFamily = unquote(token[1]); - } else if (token[0] === 'word') { + if (node.type === 'quoted') { + possibleFontFamily = unquote(node.value); + } else if (node.type === 'word') { if (!isPreceededByWords) { const wordSequence = []; - for (let j = i; j < tokens.length; j += 1) { - if (tokens[j][0] === 'word') { - wordSequence.push(tokens[j][1]); + for (let j = i; j < rootNode.nodes.length; j += 1) { + if (rootNode.nodes[j].type === 'word') { + wordSequence.push(rootNode.nodes[j].value); lastFontFamilyTokenIndex = j; - } else if (tokens[j][0] !== 'space') { + } else { break; } } possibleFontFamily = wordSequence.join(' '); } isPreceededByWords = true; - } else if (token[0] !== 'space') { + } else { isPreceededByWords = false; } if (possibleFontFamily) { @@ -37,10 +35,14 @@ function injectSubsetDefinitions(cssValue, webfontNameMap, replaceOriginal) { // Bail out, a subset font is already listed return cssValue; } else if (webfontNameMap[possibleFontFamilyLowerCase]) { - resultStr += `'${webfontNameMap[possibleFontFamilyLowerCase].replace( - /'/g, - "\\'" - )}'`; + rootNode.insertBefore( + node, + `'${webfontNameMap[possibleFontFamilyLowerCase].replace( + /'/g, + "\\'" + )}', ` + ); + return rootNode.toString(); if (replaceOriginal) { tokens.splice(i, lastFontFamilyTokenIndex - i + 1); i -= 1; @@ -50,9 +52,8 @@ function injectSubsetDefinitions(cssValue, webfontNameMap, replaceOriginal) { } } } - resultStr += token[1]; } - return resultStr; + return cssValue; } module.exports = injectSubsetDefinitions; diff --git a/package.json b/package.json index 5f17c055..f91ed2eb 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "font-tracer": "^1.1.0", "fontkit": "^1.8.0", "lodash.groupby": "^4.6.0", - "postcss-values-parser": "^2.0.1", + "postcss-values-parser": "^3.0.5", "pretty-bytes": "^5.1.0", "urltools": "^0.4.1", "yargs": "^12.0.2" diff --git a/test/injectSubsetDefinitions.js b/test/injectSubsetDefinitions.js index 4559690d..8773f971 100644 --- a/test/injectSubsetDefinitions.js +++ b/test/injectSubsetDefinitions.js @@ -42,7 +42,7 @@ describe('injectSubsetDefinitions', function() { expect( injectSubsetDefinitions('times new roman', webfontNameMap), 'to equal', - "'times new roman__subset', times new roman" + "'times new roman__subset', times new roman" ); }); From a7ba1553d1ca51eee45994a5c45abafecb649fe6 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sun, 18 Aug 2019 23:39:03 +0200 Subject: [PATCH 2/6] Start converting lib/stripLocalTokens.js, blocked on shellscape/postcss-values-parser#91 --- lib/stripLocalTokens.js | 54 +++++++++-------------------------------- 1 file changed, 12 insertions(+), 42 deletions(-) diff --git a/lib/stripLocalTokens.js b/lib/stripLocalTokens.js index babc4af8..2a2afb67 100644 --- a/lib/stripLocalTokens.js +++ b/lib/stripLocalTokens.js @@ -1,51 +1,21 @@ const postcssValuesParser = require('postcss-values-parser'); module.exports = function stripLocalTokens(cssValue) { - const tokens = postcssValuesParser(cssValue).tokens; - for (let i = 0; i < tokens.length - 3; i += 1) { - if ( - tokens[i][0] === 'word' && - tokens[i][1].toLowerCase() === 'local' && - tokens[i + 1][0] === '(' && - (tokens[i + 2][0] === 'string' || tokens[i + 2][0] === 'word') && - tokens[i + 3][0] === ')' - ) { - let startIndex = i; - let numTokensToRemove = 4; - let commaBefore = false; - let commaAfter = false; - while ( - startIndex > 0 && - ['space', 'comma'].includes(tokens[startIndex - 1][0]) - ) { - if (tokens[startIndex - 1][0] === 'comma') { - commaBefore = true; - } - startIndex -= 1; - numTokensToRemove += 1; - } + const rootNode = postcssValuesParser.parse(cssValue); - while ( - startIndex + numTokensToRemove < tokens.length && - ['space', 'comma'].includes(tokens[startIndex + numTokensToRemove][0]) - ) { - if (tokens[startIndex + numTokensToRemove][0] === 'comma') { - commaAfter = true; + for (let i = 0; i < rootNode.nodes.length; i += 1) { + const node = rootNode.nodes[i]; + if (node.type === 'func' && node.name.toLowerCase() === 'local') { + let numTokensToRemove = 1; + if (i + 1 < rootNode.nodes.length) { + const nextToken = rootNode.nodes[i + 1]; + if (nextToken.type === 'punctuation' && nextToken.value === ',') { + numTokensToRemove += 1; } - numTokensToRemove += 1; - } - if (commaBefore && commaAfter) { - tokens.splice( - startIndex, - numTokensToRemove, - ['comma', ','], - ['space', ' '] - ); - } else { - tokens.splice(startIndex, numTokensToRemove); } - i = startIndex - 1; + rootNode.nodes.splice(i, numTokensToRemove); + i -= 1; } } - return tokens.map(token => token[1]).join(''); + return rootNode.toString(); }; From e5d2071f2f737a002187bcb55021001c171c09f8 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sun, 18 Aug 2019 23:56:09 +0200 Subject: [PATCH 3/6] Add a test for extractReferencedCustomPropertyNames --- test/extractReferencedCustomPropertyNames.js | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 test/extractReferencedCustomPropertyNames.js diff --git a/test/extractReferencedCustomPropertyNames.js b/test/extractReferencedCustomPropertyNames.js new file mode 100644 index 00000000..19b2c4c1 --- /dev/null +++ b/test/extractReferencedCustomPropertyNames.js @@ -0,0 +1,33 @@ +const expect = require('unexpected') + .clone() + .use(require('unexpected-set')); + +const extractReferencedCustomPropertyNames = require('../lib/extractReferencedCustomPropertyNames'); + +describe('extractReferencedCustomPropertyNames', function() { + it('should return the empty set when no custom properties are referenced', function() { + expect( + extractReferencedCustomPropertyNames('foo(bar), local(abc), bla-bla'), + 'to equal', + new Set() + ); + }); + + it('should return the name of a referenced custom property', function() { + expect( + extractReferencedCustomPropertyNames('foo(bar), var(--abc), bla-bla'), + 'to equal', + new Set(['--abc']) + ); + }); + + it('should return the names of multiple referenced custom properties', function() { + expect( + extractReferencedCustomPropertyNames( + 'foo(bar), var(--abc), bla-bla, var(--def)' + ), + 'to equal', + new Set(['--abc', '--def']) + ); + }); +}); From f10c43b3b779b1ab64cabcb922b8b85a43f4b664 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Mon, 19 Aug 2019 00:01:02 +0200 Subject: [PATCH 4/6] Convert extractReferencedCustomPropertyNames --- lib/extractReferencedCustomPropertyNames.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/extractReferencedCustomPropertyNames.js b/lib/extractReferencedCustomPropertyNames.js index 7a0bfdd8..c6251ff8 100644 --- a/lib/extractReferencedCustomPropertyNames.js +++ b/lib/extractReferencedCustomPropertyNames.js @@ -1,16 +1,17 @@ const postcssValuesParser = require('postcss-values-parser'); function extractReferencedCustomPropertyNames(cssValue) { - const tokens = postcssValuesParser(cssValue).tokens; + const rootNode = postcssValuesParser.parse(cssValue); const customPropertyNames = new Set(); - for (let i = 0; i < tokens.length - 3; i += 1) { + for (const node of rootNode.nodes) { if ( - tokens[i][1] === 'var' && - tokens[i + 1][0] === '(' && - tokens[i + 2][1] === '--' && - tokens[i + 3][0] === 'word' + node.type === 'func' && + node.name === 'var' && + node.nodes.length === 1 && + node.nodes[0].type === 'word' && + /^--/.test(node.nodes[0].value) ) { - customPropertyNames.add(`--${tokens[i + 3][1]}`); + customPropertyNames.add(node.nodes[0].value); } } return customPropertyNames; From e59a8a3f0ca51af82fdb3739cc7e03542f9fad71 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Mon, 19 Aug 2019 00:22:29 +0200 Subject: [PATCH 5/6] Make a little progress with stripLocalTokens --- lib/stripLocalTokens.js | 3 +++ test/stripLocalTokens.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/stripLocalTokens.js b/lib/stripLocalTokens.js index 2a2afb67..e396cb95 100644 --- a/lib/stripLocalTokens.js +++ b/lib/stripLocalTokens.js @@ -11,6 +11,9 @@ module.exports = function stripLocalTokens(cssValue) { const nextToken = rootNode.nodes[i + 1]; if (nextToken.type === 'punctuation' && nextToken.value === ',') { numTokensToRemove += 1; + if (i + 2 < rootNode.nodes.length) { + rootNode.nodes[i + 2].raws.before = node.raws.before; + } } } rootNode.nodes.splice(i, numTokensToRemove); diff --git a/test/stripLocalTokens.js b/test/stripLocalTokens.js index 5130a3f2..1ee80d7d 100644 --- a/test/stripLocalTokens.js +++ b/test/stripLocalTokens.js @@ -61,7 +61,7 @@ describe('stripLocalTokens', function() { expect( `url('foo') , local(bar) , local(quux) , url('baz')`, 'to come out as', - "url('foo'), url('baz')" + "url('foo') , url('baz')" ); }); From 5ecf96afd90936a73307acf90f266f6d79f676dd Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Mon, 26 Aug 2019 23:57:04 +0200 Subject: [PATCH 6/6] Switch to postcss-value-parser instead --- lib/extractReferencedCustomPropertyNames.js | 8 ++--- lib/injectSubsetDefinitions.js | 40 ++++++++++++--------- lib/stripLocalTokens.js | 13 ++++--- package.json | 2 +- test/injectSubsetDefinitions.js | 2 +- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/lib/extractReferencedCustomPropertyNames.js b/lib/extractReferencedCustomPropertyNames.js index c6251ff8..0a39c4c2 100644 --- a/lib/extractReferencedCustomPropertyNames.js +++ b/lib/extractReferencedCustomPropertyNames.js @@ -1,12 +1,12 @@ -const postcssValuesParser = require('postcss-values-parser'); +const postcssValueParser = require('postcss-value-parser'); function extractReferencedCustomPropertyNames(cssValue) { - const rootNode = postcssValuesParser.parse(cssValue); + const rootNode = postcssValueParser(cssValue); const customPropertyNames = new Set(); for (const node of rootNode.nodes) { if ( - node.type === 'func' && - node.name === 'var' && + node.type === 'function' && + node.value === 'var' && node.nodes.length === 1 && node.nodes[0].type === 'word' && /^--/.test(node.nodes[0].value) diff --git a/lib/injectSubsetDefinitions.js b/lib/injectSubsetDefinitions.js index 4ddd0c75..54a6ec17 100644 --- a/lib/injectSubsetDefinitions.js +++ b/lib/injectSubsetDefinitions.js @@ -1,25 +1,24 @@ -const postcssValuesParser = require('postcss-values-parser'); -const unquote = require('./unquote'); +const postcssValueParser = require('postcss-value-parser'); function injectSubsetDefinitions(cssValue, webfontNameMap, replaceOriginal) { const subsetFontNames = new Set( Object.values(webfontNameMap).map(name => name.toLowerCase()) ); - const rootNode = postcssValuesParser.parse(cssValue); + const rootNode = postcssValueParser(cssValue); let isPreceededByWords = false; for (const [i, node] of rootNode.nodes.entries()) { let possibleFontFamily; let lastFontFamilyTokenIndex = i; - if (node.type === 'quoted') { - possibleFontFamily = unquote(node.value); - } else if (node.type === 'word') { + if (node.type === 'string') { + possibleFontFamily = node.value; + } else if (node.type === 'word' || node.type === 'space') { if (!isPreceededByWords) { const wordSequence = []; for (let j = i; j < rootNode.nodes.length; j += 1) { if (rootNode.nodes[j].type === 'word') { wordSequence.push(rootNode.nodes[j].value); lastFontFamilyTokenIndex = j; - } else { + } else if (rootNode.nodes[j].type !== 'space') { break; } } @@ -35,21 +34,28 @@ function injectSubsetDefinitions(cssValue, webfontNameMap, replaceOriginal) { // Bail out, a subset font is already listed return cssValue; } else if (webfontNameMap[possibleFontFamilyLowerCase]) { - rootNode.insertBefore( - node, - `'${webfontNameMap[possibleFontFamilyLowerCase].replace( + const newToken = { + type: 'string', + value: webfontNameMap[possibleFontFamilyLowerCase].replace( /'/g, "\\'" - )}', ` - ); - return rootNode.toString(); + ), + quote: "'" + }; if (replaceOriginal) { - tokens.splice(i, lastFontFamilyTokenIndex - i + 1); - i -= 1; - continue; + rootNode.nodes.splice( + rootNode.nodes.indexOf(node), + lastFontFamilyTokenIndex - i + 1, + newToken + ); } else { - resultStr += ', '; + rootNode.nodes.splice(rootNode.nodes.indexOf(node), 0, newToken, { + type: 'div', + value: ',', + after: ' ' + }); } + return postcssValueParser.stringify(rootNode); } } } diff --git a/lib/stripLocalTokens.js b/lib/stripLocalTokens.js index e396cb95..b3ca5667 100644 --- a/lib/stripLocalTokens.js +++ b/lib/stripLocalTokens.js @@ -1,18 +1,17 @@ -const postcssValuesParser = require('postcss-values-parser'); +const postcssValueParser = require('postcss-value-parser'); module.exports = function stripLocalTokens(cssValue) { - const rootNode = postcssValuesParser.parse(cssValue); - + const rootNode = postcssValueParser(cssValue); for (let i = 0; i < rootNode.nodes.length; i += 1) { const node = rootNode.nodes[i]; - if (node.type === 'func' && node.name.toLowerCase() === 'local') { + if (node.type === 'function' && node.value.toLowerCase() === 'local') { let numTokensToRemove = 1; if (i + 1 < rootNode.nodes.length) { const nextToken = rootNode.nodes[i + 1]; - if (nextToken.type === 'punctuation' && nextToken.value === ',') { + if (nextToken.type === 'div' && nextToken.value === ',') { numTokensToRemove += 1; if (i + 2 < rootNode.nodes.length) { - rootNode.nodes[i + 2].raws.before = node.raws.before; + rootNode.nodes[i + 2].before = node.before; } } } @@ -20,5 +19,5 @@ module.exports = function stripLocalTokens(cssValue) { i -= 1; } } - return rootNode.toString(); + return postcssValueParser.stringify(rootNode); }; diff --git a/package.json b/package.json index f91ed2eb..1411820a 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "font-tracer": "^1.1.0", "fontkit": "^1.8.0", "lodash.groupby": "^4.6.0", - "postcss-values-parser": "^3.0.5", + "postcss-value-parser": "^4.0.2", "pretty-bytes": "^5.1.0", "urltools": "^0.4.1", "yargs": "^12.0.2" diff --git a/test/injectSubsetDefinitions.js b/test/injectSubsetDefinitions.js index 8773f971..4559690d 100644 --- a/test/injectSubsetDefinitions.js +++ b/test/injectSubsetDefinitions.js @@ -42,7 +42,7 @@ describe('injectSubsetDefinitions', function() { expect( injectSubsetDefinitions('times new roman', webfontNameMap), 'to equal', - "'times new roman__subset', times new roman" + "'times new roman__subset', times new roman" ); });