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

4 years ago
  1. 'use strict';
  2. exports.type = 'perItem';
  3. exports.active = false;
  4. exports.params = {
  5. delim: '__',
  6. prefixIds: true,
  7. prefixClassNames: true,
  8. };
  9. exports.description = 'prefix IDs';
  10. var path = require('path'),
  11. csstree = require('css-tree'),
  12. unquote = require('unquote'),
  13. collections = require('./_collections.js'),
  14. referencesProps = collections.referencesProps,
  15. rxId = /^#(.*)$/, // regular expression for matching an ID + extracing its name
  16. addPrefix = null;
  17. // Escapes a string for being used as ID
  18. var escapeIdentifierName = function(str) {
  19. return str.replace(/[\. ]/g, '_');
  20. };
  21. // Matches an #ID value, captures the ID name
  22. var matchId = function(urlVal) {
  23. var idUrlMatches = urlVal.match(rxId);
  24. if (idUrlMatches === null) {
  25. return false;
  26. }
  27. return idUrlMatches[1];
  28. };
  29. // Matches an url(...) value, captures the URL
  30. var matchUrl = function(val) {
  31. var urlMatches = /url\((.*?)\)/gi.exec(val);
  32. if (urlMatches === null) {
  33. return false;
  34. }
  35. return urlMatches[1];
  36. };
  37. // Checks if attribute is empty
  38. var attrNotEmpty = function(attr) {
  39. return (attr && attr.value && attr.value.length > 0);
  40. };
  41. // prefixes an #ID
  42. var prefixId = function(val) {
  43. var idName = matchId(val);
  44. if (!idName) {
  45. return false;
  46. }
  47. return '#' + addPrefix(idName);
  48. };
  49. // attr.value helper methods
  50. // prefixes a class attribute value
  51. var addPrefixToClassAttr = function(attr) {
  52. if (!attrNotEmpty(attr)) {
  53. return;
  54. }
  55. attr.value = attr.value.split(/\s+/).map(addPrefix).join(' ');
  56. };
  57. // prefixes an ID attribute value
  58. var addPrefixToIdAttr = function(attr) {
  59. if (!attrNotEmpty(attr)) {
  60. return;
  61. }
  62. attr.value = addPrefix(attr.value);
  63. };
  64. // prefixes a href attribute value
  65. var addPrefixToHrefAttr = function(attr) {
  66. if (!attrNotEmpty(attr)) {
  67. return;
  68. }
  69. var idPrefixed = prefixId(attr.value);
  70. if (!idPrefixed) {
  71. return;
  72. }
  73. attr.value = idPrefixed;
  74. };
  75. // prefixes an URL attribute value
  76. var addPrefixToUrlAttr = function(attr) {
  77. if (!attrNotEmpty(attr)) {
  78. return;
  79. }
  80. // url(...) in value
  81. var urlVal = matchUrl(attr.value);
  82. if (!urlVal) {
  83. return;
  84. }
  85. var idPrefixed = prefixId(urlVal);
  86. if (!idPrefixed) {
  87. return;
  88. }
  89. attr.value = 'url(' + idPrefixed + ')';
  90. };
  91. /**
  92. * Prefixes identifiers
  93. *
  94. * @param {Object} node node
  95. * @param {Object} opts plugin params
  96. * @param {Object} extra plugin extra information
  97. *
  98. * @author strarsis <strarsis@gmail.com>
  99. */
  100. exports.fn = function(node, opts, extra) {
  101. // skip subsequent passes when multipass is used
  102. if(extra.multipassCount && extra.multipassCount > 0) {
  103. return node;
  104. }
  105. // prefix, from file name or option
  106. var prefix = 'prefix';
  107. if (opts.prefix) {
  108. if (typeof opts.prefix === 'function') {
  109. prefix = opts.prefix(node, extra);
  110. } else {
  111. prefix = opts.prefix;
  112. }
  113. } else if (opts.prefix === false) {
  114. prefix = false;
  115. } else if (extra && extra.path && extra.path.length > 0) {
  116. var filename = path.basename(extra.path);
  117. prefix = filename;
  118. }
  119. // prefixes a normal value
  120. addPrefix = function(name) {
  121. if(prefix === false){
  122. return escapeIdentifierName(name);
  123. }
  124. return escapeIdentifierName(prefix + opts.delim + name);
  125. };
  126. // <style/> property values
  127. if (node.elem === 'style') {
  128. if (node.isEmpty()) {
  129. // skip empty <style/>s
  130. return node;
  131. }
  132. var cssStr = node.content[0].text || node.content[0].cdata || [];
  133. var cssAst = {};
  134. try {
  135. cssAst = csstree.parse(cssStr, {
  136. parseValue: true,
  137. parseCustomProperty: false
  138. });
  139. } catch (parseError) {
  140. console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError);
  141. return node;
  142. }
  143. var idPrefixed = '';
  144. csstree.walk(cssAst, function(node) {
  145. // #ID, .class
  146. if (((opts.prefixIds && node.type === 'IdSelector') ||
  147. (opts.prefixClassNames && node.type === 'ClassSelector')) &&
  148. node.name) {
  149. node.name = addPrefix(node.name);
  150. return;
  151. }
  152. // url(...) in value
  153. if (node.type === 'Url' &&
  154. node.value.value && node.value.value.length > 0) {
  155. idPrefixed = prefixId(unquote(node.value.value));
  156. if (!idPrefixed) {
  157. return;
  158. }
  159. node.value.value = idPrefixed;
  160. }
  161. });
  162. // update <style>s
  163. node.content[0].text = csstree.generate(cssAst);
  164. return node;
  165. }
  166. // element attributes
  167. if (!node.attrs) {
  168. return node;
  169. }
  170. // Nodes
  171. if(opts.prefixIds) {
  172. // ID
  173. addPrefixToIdAttr(node.attrs.id);
  174. }
  175. if(opts.prefixClassNames) {
  176. // Class
  177. addPrefixToClassAttr(node.attrs.class);
  178. }
  179. // References
  180. // href
  181. addPrefixToHrefAttr(node.attrs.href);
  182. // (xlink:)href (deprecated, must be still supported)
  183. addPrefixToHrefAttr(node.attrs['xlink:href']);
  184. // (referenceable) properties
  185. for (var referencesProp of referencesProps) {
  186. addPrefixToUrlAttr(node.attrs[referencesProp]);
  187. }
  188. return node;
  189. };