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.

182 lines
6.7 KiB

4 years ago
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  6. var _postcss = require('postcss');
  7. var _postcss2 = _interopRequireDefault(_postcss);
  8. var _cssSelectorTokenizer = require('css-selector-tokenizer');
  9. var _cssSelectorTokenizer2 = _interopRequireDefault(_cssSelectorTokenizer);
  10. var hasOwnProperty = Object.prototype.hasOwnProperty;
  11. function getSingleLocalNamesForComposes(selectors) {
  12. return selectors.nodes.map(function (node) {
  13. if (node.type !== 'selector' || node.nodes.length !== 1) {
  14. throw new Error('composition is only allowed when selector is single :local class name not in "' + _cssSelectorTokenizer2['default'].stringify(selectors) + '"');
  15. }
  16. node = node.nodes[0];
  17. if (node.type !== 'nested-pseudo-class' || node.name !== 'local' || node.nodes.length !== 1) {
  18. throw new Error('composition is only allowed when selector is single :local class name not in "' + _cssSelectorTokenizer2['default'].stringify(selectors) + '", "' + _cssSelectorTokenizer2['default'].stringify(node) + '" is weird');
  19. }
  20. node = node.nodes[0];
  21. if (node.type !== 'selector' || node.nodes.length !== 1) {
  22. throw new Error('composition is only allowed when selector is single :local class name not in "' + _cssSelectorTokenizer2['default'].stringify(selectors) + '", "' + _cssSelectorTokenizer2['default'].stringify(node) + '" is weird');
  23. }
  24. node = node.nodes[0];
  25. if (node.type !== 'class') {
  26. // 'id' is not possible, because you can't compose ids
  27. throw new Error('composition is only allowed when selector is single :local class name not in "' + _cssSelectorTokenizer2['default'].stringify(selectors) + '", "' + _cssSelectorTokenizer2['default'].stringify(node) + '" is weird');
  28. }
  29. return node.name;
  30. });
  31. }
  32. var processor = _postcss2['default'].plugin('postcss-modules-scope', function (options) {
  33. return function (css) {
  34. var generateScopedName = options && options.generateScopedName || processor.generateScopedName;
  35. var exports = {};
  36. function exportScopedName(name) {
  37. var scopedName = generateScopedName(name, css.source.input.from, css.source.input.css);
  38. exports[name] = exports[name] || [];
  39. if (exports[name].indexOf(scopedName) < 0) {
  40. exports[name].push(scopedName);
  41. }
  42. return scopedName;
  43. }
  44. function localizeNode(node) {
  45. var newNode = Object.create(node);
  46. switch (node.type) {
  47. case 'selector':
  48. newNode.nodes = node.nodes.map(localizeNode);
  49. return newNode;
  50. case 'class':
  51. case 'id':
  52. var scopedName = exportScopedName(node.name);
  53. newNode.name = scopedName;
  54. return newNode;
  55. }
  56. throw new Error(node.type + ' ("' + _cssSelectorTokenizer2['default'].stringify(node) + '") is not allowed in a :local block');
  57. }
  58. function traverseNode(node) {
  59. switch (node.type) {
  60. case 'nested-pseudo-class':
  61. if (node.name === 'local') {
  62. if (node.nodes.length !== 1) {
  63. throw new Error('Unexpected comma (",") in :local block');
  64. }
  65. return localizeNode(node.nodes[0]);
  66. }
  67. /* falls through */
  68. case 'selectors':
  69. case 'selector':
  70. var newNode = Object.create(node);
  71. newNode.nodes = node.nodes.map(traverseNode);
  72. return newNode;
  73. }
  74. return node;
  75. }
  76. // Find any :import and remember imported names
  77. var importedNames = {};
  78. css.walkRules(function (rule) {
  79. if (/^:import\(.+\)$/.test(rule.selector)) {
  80. rule.walkDecls(function (decl) {
  81. importedNames[decl.prop] = true;
  82. });
  83. }
  84. });
  85. // Find any :local classes
  86. css.walkRules(function (rule) {
  87. var selector = _cssSelectorTokenizer2['default'].parse(rule.selector);
  88. var newSelector = traverseNode(selector);
  89. rule.selector = _cssSelectorTokenizer2['default'].stringify(newSelector);
  90. rule.walkDecls(/composes|compose-with/, function (decl) {
  91. var localNames = getSingleLocalNamesForComposes(selector);
  92. var classes = decl.value.split(/\s+/);
  93. classes.forEach(function (className) {
  94. var global = /^global\(([^\)]+)\)$/.exec(className);
  95. if (global) {
  96. localNames.forEach(function (exportedName) {
  97. exports[exportedName].push(global[1]);
  98. });
  99. } else if (hasOwnProperty.call(importedNames, className)) {
  100. localNames.forEach(function (exportedName) {
  101. exports[exportedName].push(className);
  102. });
  103. } else if (hasOwnProperty.call(exports, className)) {
  104. localNames.forEach(function (exportedName) {
  105. exports[className].forEach(function (item) {
  106. exports[exportedName].push(item);
  107. });
  108. });
  109. } else {
  110. throw decl.error('referenced class name "' + className + '" in ' + decl.prop + ' not found');
  111. }
  112. });
  113. decl.remove();
  114. });
  115. rule.walkDecls(function (decl) {
  116. var tokens = decl.value.split(/(,|'[^']*'|"[^"]*")/);
  117. tokens = tokens.map(function (token, idx) {
  118. if (idx === 0 || tokens[idx - 1] === ',') {
  119. var localMatch = /^(\s*):local\s*\((.+?)\)/.exec(token);
  120. if (localMatch) {
  121. return localMatch[1] + exportScopedName(localMatch[2]) + token.substr(localMatch[0].length);
  122. } else {
  123. return token;
  124. }
  125. } else {
  126. return token;
  127. }
  128. });
  129. decl.value = tokens.join('');
  130. });
  131. });
  132. // Find any :local keyframes
  133. css.walkAtRules(function (atrule) {
  134. if (/keyframes$/.test(atrule.name)) {
  135. var localMatch = /^\s*:local\s*\((.+?)\)\s*$/.exec(atrule.params);
  136. if (localMatch) {
  137. atrule.params = exportScopedName(localMatch[1]);
  138. }
  139. }
  140. });
  141. // If we found any :locals, insert an :export rule
  142. var exportedNames = Object.keys(exports);
  143. if (exportedNames.length > 0) {
  144. (function () {
  145. var exportRule = _postcss2['default'].rule({ selector: ':export' });
  146. exportedNames.forEach(function (exportedName) {
  147. return exportRule.append({
  148. prop: exportedName,
  149. value: exports[exportedName].join(' '),
  150. raws: { before: '\n ' }
  151. });
  152. });
  153. css.append(exportRule);
  154. })();
  155. }
  156. };
  157. });
  158. processor.generateScopedName = function (exportedName, path) {
  159. var sanitisedPath = path.replace(/\.[^\.\/\\]+$/, '').replace(/[\W_]+/g, '_').replace(/^_|_$/g, '');
  160. return '_' + sanitisedPath + '__' + exportedName;
  161. };
  162. exports['default'] = processor;
  163. module.exports = exports['default'];