New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add inlineStyles plugin (rewrite of localStyles plugin) #592
Changes from 9 commits
87b7bb0
298c072
b4c8df9
34e6bfa
b68892b
bf29ceb
fe759cb
8ef17ee
5bfd498
3615c0e
5d1f98d
46aeebe
f16abbf
814369d
a3cec04
86cc454
65836b7
c809447
a69b35b
2fb2be9
5a7af23
a1f1e32
30f01a2
91554ef
e123e29
ebcd712
6740ab0
150868d
193d93b
92c5c48
2104ad4
e5dc5ba
1d63cc0
7897ed7
3f31b34
8bc26fe
19a19c5
f3d5de0
c7b46d5
b3853c8
941b6ca
b6dd3d1
8862918
57b84a8
297f394
3e54b1c
f37d589
01d16f1
fe49aa7
f7adbc4
af8a2ac
913c380
821c3f2
279431b
7ea1b95
b3ddf64
3c0dc05
5da1482
73fcd3c
28271b4
2ef6a97
f6e5f40
f4b3c58
f5c1003
0019773
a33efc8
123f94a
e371ef3
f222530
a336b3c
82eab75
94d36fe
f5eed30
f9eda39
5688e3b
ccff9b2
8784548
cc27c46
6709815
e79a0d5
49eb9f6
43aba2f
2b0ad6b
da20869
94651a0
c096aa0
3e2ccb4
56a229c
b3e085d
a67ed53
db20f5b
cedfda2
a794897
5dd38da
5beb962
c90ac18
3964dc4
1c7240a
5c3b65a
e9e2be9
c8241c4
b6d6c35
6167206
350bf74
c130ecc
aa7a786
ab1c8bb
ed029c5
de85b0f
30569d1
57601a9
634cb43
92b692e
cb069be
23e6b0a
2a7f45a
57827dd
58ea1bd
6117cd4
1fa6197
2d82153
c50aea5
a678f6d
d123b9d
4a19b9b
b8a7fe8
df4752a
52819cf
11ab2c5
16cc3aa
c12bc17
8af462b
25fd3ac
907bef2
3d2450c
4f47d7d
b92236d
2d91c97
13a81ec
4f40972
bb01e68
cd17121
ecad2d7
eb9de31
af0e638
0ae071e
3312973
82c277e
a3f17d9
e6dbda6
9792063
751dad0
d212cee
baa1ffe
d909bb5
ec35e22
1bc5542
935e195
64106c2
f45c1f3
ec6ac96
a8237a1
5dfc185
912dd08
165fec4
43dae25
a7f79b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,13 +48,16 @@ | |
"jshint": "jshint --show-non-errors ." | ||
}, | ||
"dependencies": { | ||
"sax": "~1.2.1", | ||
"cheerio": "^0.22.0", | ||
"coa": "~1.0.1", | ||
"js-yaml": "~3.6.1", | ||
"colors": "~1.1.2", | ||
"whet.extend": "~0.9.9", | ||
"css": "^2.2.1", | ||
"csso": "~2.2.1", | ||
"js-yaml": "~3.6.1", | ||
"juice": "^2.0.0", | ||
"mkdirp": "~0.5.1", | ||
"csso": "~2.2.1" | ||
"sax": "~1.2.1", | ||
"whet.extend": "~0.9.9" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what.extend was removed here: 4495a34 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alexjlockwood: Should I merge this change into this PR branch? |
||
}, | ||
"devDependencies": { | ||
"mocha": "~3.0.2", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
'use strict'; | ||
|
||
exports.type = 'full'; | ||
|
||
exports.active = true; | ||
|
||
exports.params = { | ||
juice: {} | ||
}; | ||
|
||
exports.description = 'moves styles from <style> to element styles'; | ||
|
||
|
||
var cssParser = require('css'), | ||
cheerio = require('cheerio'), | ||
juice = require('juice'), | ||
JSAPI = require('../lib/svgo/jsAPI.js'); | ||
|
||
|
||
function monkeysSvgo(item, callFn, arg) { | ||
item.content.forEach(function(childItem) { | ||
if(callFn(item, childItem, arg) && !childItem.isEmpty()) { // recurse | ||
monkeysSvgo(childItem, callFn, arg); | ||
} | ||
}); | ||
} | ||
|
||
function isSvgoElem(elem) { | ||
return typeof elem !== 'undefined'; | ||
} | ||
|
||
function cheerioLoadXml(xml) { | ||
return cheerio.load(xml, { xmlMode: true }); | ||
} | ||
function getXmlTag(elem) { | ||
return '<' + elem + '>'; | ||
} | ||
function makeCheerioElem(elem) { | ||
return cheerioLoadXml(getXmlTag(elem)); | ||
} | ||
function makeCheerioInst(elem) { | ||
return makeCheerioElem(elem)(elem); | ||
} | ||
|
||
function parsePrefixable(fullName) { | ||
var a = fullName.split(':'); | ||
if(a.length == 1) { | ||
return { name: a[0] }; | ||
} | ||
return { prefix: a[0], name: a[1] }; | ||
} | ||
|
||
function processSvgoElem(pae, ae, $) { | ||
// note: <xml> element skipped by svgo parser | ||
|
||
if(pae.elem == '#document') { | ||
pae.$ = $; // attach top cheerio ast node to top svgo ast node | ||
} | ||
|
||
var textToElem = pae; | ||
if(isSvgoElem(ae.elem)) { | ||
var nameInfo = parsePrefixable(ae.elem); | ||
|
||
ae.$ = makeCheerioInst(nameInfo.name); | ||
pae.$.append(ae.$); | ||
|
||
if(ae.attrs && Object.keys(ae.attrs).length > 0) { | ||
for(var attrKey in ae.attrs) { | ||
var attr = ae.attrs[attrKey]; | ||
|
||
var attrNamePrefixed = ''; | ||
if(attr.prefix) { | ||
attrNamePrefixed = attr.prefix + ':'; | ||
} | ||
attrNamePrefixed = attrNamePrefixed + attr.name; | ||
|
||
ae.$.attr(attrNamePrefixed, attr.value); | ||
} | ||
} | ||
|
||
if(nameInfo.prefix) { | ||
ae.$.prefix = nameInfo.prefix; | ||
} | ||
|
||
textToElem = ae; | ||
} | ||
|
||
if(typeof ae.text !== 'undefined') { | ||
textToElem.$.text(ae.text); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
function createEmptyCheerioDoc() { | ||
var document = makeCheerioElem('dummy'); | ||
var $document = document.root().empty(); | ||
return $document; | ||
} | ||
|
||
function svgoAst2CheerioAst(data) { | ||
var $document = createEmptyCheerioDoc(); | ||
monkeysSvgo(data, processSvgoElem, $document); | ||
return $document; | ||
} | ||
|
||
|
||
function monkeysCheerio($item, callFn, arg) { | ||
if(typeof $item.children === 'object') { | ||
for(var childItemIndex in $item.children) { | ||
var $childItem = $item.children[ childItemIndex ]; | ||
if(callFn($item, $childItem, arg) && $item.children.length > 0) { // recurse | ||
monkeysCheerio($childItem, callFn, arg); | ||
} | ||
} | ||
} else if(typeof $item.children === 'function') { | ||
$item.children().each(function(childItemIndex, $childItem) { | ||
if(callFn($item, $childItem, arg) && $item.children.length > 0) { // " | ||
monkeysCheerio($childItem, callFn, arg); | ||
} | ||
}); | ||
} else { | ||
return; | ||
} | ||
} | ||
|
||
function isCheerioElem($elem) { | ||
return typeof $elem !== 'undefined'; | ||
} | ||
function isCheerioText($elem) { | ||
return $elem.type == 'text'; | ||
} | ||
|
||
function makeSvgoElem(elem, parentElem) { | ||
return new JSAPI({ elem: elem }, parentElem); | ||
} | ||
function makeSvgoText(text, parentElem) { | ||
return new JSAPI({ text: text }, parentElem); | ||
} | ||
|
||
function processCheerioElem($pae, $ae, s) { | ||
|
||
if($pae.name == 'root') { | ||
$pae.s = s; // attach top svgo ast node to top cheerio ast node | ||
} | ||
|
||
var $textToElem = $pae; | ||
if(isCheerioElem($ae) && !isCheerioText($ae)) { | ||
$ae.s = makeSvgoElem($ae.name, $pae.s); | ||
|
||
$pae.s.content = $pae.s.content || []; | ||
$pae.s.content.push($ae.s); | ||
|
||
|
||
if($ae.attribs && Object.keys($ae.attribs).length > 0) { | ||
for(var attrName in $ae.attribs) { | ||
var attrValue = $ae.attribs[attrName]; | ||
var attrNameInfo = parsePrefixable(attrName); | ||
$ae.s.addAttr({ | ||
name: attrNameInfo.name, | ||
prefix: attrNameInfo.prefix || '', // explicit empty string otherwise expected | ||
local: attrNameInfo.name, | ||
value: attrValue | ||
}); | ||
} | ||
} | ||
|
||
$textToElem = $ae; | ||
} | ||
|
||
|
||
if(isCheerioText($ae)) { | ||
$pae.s.content = $pae.s.content || []; | ||
|
||
$textToElem.s.content.push( makeSvgoText($ae.data, $pae.s) ); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
function cheerioAst2SvgoAst($) { | ||
var data = makeSvgoElem('#document'); | ||
var $document = $.root()[0]; | ||
monkeysCheerio($document, processCheerioElem, data); | ||
return data; | ||
} | ||
|
||
|
||
|
||
|
||
/** | ||
* Moves styles from <style> to element styles | ||
* | ||
* @author strarsis <strarsis@gmail.com> | ||
*/ | ||
exports.fn = function(data, svgoOptions) { | ||
|
||
// svgo ast to cheerio ast | ||
var $o = svgoAst2CheerioAst(data); | ||
var $ = cheerioLoadXml($o.html()); | ||
|
||
|
||
// juice options required for svg and css classes cleanup | ||
svgoOptions.xmlMode = true; | ||
svgoOptions.removeStyleTags = false; | ||
|
||
var $i = juice.juiceDocument($, svgoOptions); | ||
|
||
|
||
// as last step, remove classes when they are used only by one element in document: | ||
var $styles = $('style'); | ||
$styles.each(function(si, $style) { | ||
if($style.children.length == 0) { | ||
return; | ||
} | ||
|
||
var css = $style.children[0].data; | ||
var cssAst = cssParser.parse(css); | ||
|
||
var cssRules = cssAst.stylesheet.rules; | ||
cssRules.forEach(function(cssRule, cssRuleIndex) { | ||
if(cssRule.type != 'rule') { | ||
return; | ||
} | ||
|
||
cssRule.selectors.forEach(function(selector, selectorIndex) { | ||
var $matches = $i(selector); | ||
if($matches.length <= 1) { // if matches only once or not at all | ||
cssRule.selectors.splice(selectorIndex, 1); | ||
} | ||
}); | ||
|
||
if(cssRule.selectors.length == 0) { // clean up rules without any selectors left | ||
cssRules.splice(cssRuleIndex, 1); | ||
} | ||
}); | ||
|
||
var newCss = cssParser.stringify(cssAst); | ||
$style.children[0].data = newCss; | ||
}); | ||
|
||
|
||
var dataNew = cheerioAst2SvgoAst($i); | ||
|
||
return dataNew; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CSSO also has a parser (but probably with a bit different AST), so it could be reused without adding dependency.