You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
5.4 KiB
243 lines
5.4 KiB
'use strict'; |
|
|
|
exports.type = 'perItem'; |
|
|
|
exports.active = false; |
|
|
|
exports.params = { |
|
delim: '__', |
|
prefixIds: true, |
|
prefixClassNames: true, |
|
}; |
|
|
|
exports.description = 'prefix IDs'; |
|
|
|
|
|
var path = require('path'), |
|
csstree = require('css-tree'), |
|
unquote = require('unquote'), |
|
collections = require('./_collections.js'), |
|
referencesProps = collections.referencesProps, |
|
rxId = /^#(.*)$/, // regular expression for matching an ID + extracing its name |
|
addPrefix = null; |
|
|
|
|
|
// Escapes a string for being used as ID |
|
var escapeIdentifierName = function(str) { |
|
return str.replace(/[\. ]/g, '_'); |
|
}; |
|
|
|
// Matches an #ID value, captures the ID name |
|
var matchId = function(urlVal) { |
|
var idUrlMatches = urlVal.match(rxId); |
|
if (idUrlMatches === null) { |
|
return false; |
|
} |
|
return idUrlMatches[1]; |
|
}; |
|
|
|
// Matches an url(...) value, captures the URL |
|
var matchUrl = function(val) { |
|
var urlMatches = /url\((.*?)\)/gi.exec(val); |
|
if (urlMatches === null) { |
|
return false; |
|
} |
|
return urlMatches[1]; |
|
}; |
|
|
|
// Checks if attribute is empty |
|
var attrNotEmpty = function(attr) { |
|
return (attr && attr.value && attr.value.length > 0); |
|
}; |
|
|
|
// prefixes an #ID |
|
var prefixId = function(val) { |
|
var idName = matchId(val); |
|
if (!idName) { |
|
return false; |
|
} |
|
return '#' + addPrefix(idName); |
|
}; |
|
|
|
|
|
// attr.value helper methods |
|
|
|
// prefixes a class attribute value |
|
var addPrefixToClassAttr = function(attr) { |
|
if (!attrNotEmpty(attr)) { |
|
return; |
|
} |
|
|
|
attr.value = attr.value.split(/\s+/).map(addPrefix).join(' '); |
|
}; |
|
|
|
// prefixes an ID attribute value |
|
var addPrefixToIdAttr = function(attr) { |
|
if (!attrNotEmpty(attr)) { |
|
return; |
|
} |
|
|
|
attr.value = addPrefix(attr.value); |
|
}; |
|
|
|
// prefixes a href attribute value |
|
var addPrefixToHrefAttr = function(attr) { |
|
if (!attrNotEmpty(attr)) { |
|
return; |
|
} |
|
|
|
var idPrefixed = prefixId(attr.value); |
|
if (!idPrefixed) { |
|
return; |
|
} |
|
attr.value = idPrefixed; |
|
}; |
|
|
|
// prefixes an URL attribute value |
|
var addPrefixToUrlAttr = function(attr) { |
|
if (!attrNotEmpty(attr)) { |
|
return; |
|
} |
|
|
|
// url(...) in value |
|
var urlVal = matchUrl(attr.value); |
|
if (!urlVal) { |
|
return; |
|
} |
|
|
|
var idPrefixed = prefixId(urlVal); |
|
if (!idPrefixed) { |
|
return; |
|
} |
|
|
|
attr.value = 'url(' + idPrefixed + ')'; |
|
}; |
|
|
|
|
|
/** |
|
* Prefixes identifiers |
|
* |
|
* @param {Object} node node |
|
* @param {Object} opts plugin params |
|
* @param {Object} extra plugin extra information |
|
* |
|
* @author strarsis <strarsis@gmail.com> |
|
*/ |
|
exports.fn = function(node, opts, extra) { |
|
|
|
// skip subsequent passes when multipass is used |
|
if(extra.multipassCount && extra.multipassCount > 0) { |
|
return node; |
|
} |
|
|
|
// prefix, from file name or option |
|
var prefix = 'prefix'; |
|
if (opts.prefix) { |
|
if (typeof opts.prefix === 'function') { |
|
prefix = opts.prefix(node, extra); |
|
} else { |
|
prefix = opts.prefix; |
|
} |
|
} else if (opts.prefix === false) { |
|
prefix = false; |
|
} else if (extra && extra.path && extra.path.length > 0) { |
|
var filename = path.basename(extra.path); |
|
prefix = filename; |
|
} |
|
|
|
|
|
// prefixes a normal value |
|
addPrefix = function(name) { |
|
if(prefix === false){ |
|
return escapeIdentifierName(name); |
|
} |
|
return escapeIdentifierName(prefix + opts.delim + name); |
|
}; |
|
|
|
|
|
// <style/> property values |
|
|
|
if (node.elem === 'style') { |
|
if (node.isEmpty()) { |
|
// skip empty <style/>s |
|
return node; |
|
} |
|
|
|
var cssStr = node.content[0].text || node.content[0].cdata || []; |
|
|
|
var cssAst = {}; |
|
try { |
|
cssAst = csstree.parse(cssStr, { |
|
parseValue: true, |
|
parseCustomProperty: false |
|
}); |
|
} catch (parseError) { |
|
console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError); |
|
return node; |
|
} |
|
|
|
var idPrefixed = ''; |
|
csstree.walk(cssAst, function(node) { |
|
|
|
// #ID, .class |
|
if (((opts.prefixIds && node.type === 'IdSelector') || |
|
(opts.prefixClassNames && node.type === 'ClassSelector')) && |
|
node.name) { |
|
node.name = addPrefix(node.name); |
|
return; |
|
} |
|
|
|
// url(...) in value |
|
if (node.type === 'Url' && |
|
node.value.value && node.value.value.length > 0) { |
|
idPrefixed = prefixId(unquote(node.value.value)); |
|
if (!idPrefixed) { |
|
return; |
|
} |
|
node.value.value = idPrefixed; |
|
} |
|
|
|
}); |
|
|
|
// update <style>s |
|
node.content[0].text = csstree.generate(cssAst); |
|
return node; |
|
} |
|
|
|
|
|
// element attributes |
|
|
|
if (!node.attrs) { |
|
return node; |
|
} |
|
|
|
|
|
// Nodes |
|
|
|
if(opts.prefixIds) { |
|
// ID |
|
addPrefixToIdAttr(node.attrs.id); |
|
} |
|
|
|
if(opts.prefixClassNames) { |
|
// Class |
|
addPrefixToClassAttr(node.attrs.class); |
|
} |
|
|
|
|
|
// References |
|
|
|
// href |
|
addPrefixToHrefAttr(node.attrs.href); |
|
|
|
// (xlink:)href (deprecated, must be still supported) |
|
addPrefixToHrefAttr(node.attrs['xlink:href']); |
|
|
|
// (referenceable) properties |
|
for (var referencesProp of referencesProps) { |
|
addPrefixToUrlAttr(node.attrs[referencesProp]); |
|
} |
|
|
|
|
|
return node; |
|
};
|
|
|