index.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. 'use strict';
  2. const postcss = require('postcss');
  3. const ICSSUtils = require('icss-utils');
  4. const matchImports = /^(.+?|\([\s\S]+?\))\s+from\s+("[^"]*"|'[^']*'|[\w-]+)$/;
  5. const matchValueDefinition = /(?:\s+|^)([\w-]+):?\s+(.+?)\s*$/g;
  6. const matchImport = /^([\w-]+)(?:\s+as\s+([\w-]+))?/;
  7. let options = {};
  8. let importIndex = 0;
  9. let createImportedName =
  10. (options && options.createImportedName) ||
  11. ((importName /*, path*/) =>
  12. `i__const_${importName.replace(/\W/g, '_')}_${importIndex++}`);
  13. module.exports = postcss.plugin(
  14. 'postcss-modules-values',
  15. () => (css, result) => {
  16. const importAliases = [];
  17. const definitions = {};
  18. const addDefinition = atRule => {
  19. let matches;
  20. while ((matches = matchValueDefinition.exec(atRule.params))) {
  21. let [, /*match*/ key, value] = matches;
  22. // Add to the definitions, knowing that values can refer to each other
  23. definitions[key] = ICSSUtils.replaceValueSymbols(value, definitions);
  24. atRule.remove();
  25. }
  26. };
  27. const addImport = atRule => {
  28. const matches = matchImports.exec(atRule.params);
  29. if (matches) {
  30. let [, /*match*/ aliases, path] = matches;
  31. // We can use constants for path names
  32. if (definitions[path]) {
  33. path = definitions[path];
  34. }
  35. const imports = aliases
  36. .replace(/^\(\s*([\s\S]+)\s*\)$/, '$1')
  37. .split(/\s*,\s*/)
  38. .map(alias => {
  39. const tokens = matchImport.exec(alias);
  40. if (tokens) {
  41. const [, /*match*/ theirName, myName = theirName] = tokens;
  42. const importedName = createImportedName(myName);
  43. definitions[myName] = importedName;
  44. return { theirName, importedName };
  45. } else {
  46. throw new Error(`@import statement "${alias}" is invalid!`);
  47. }
  48. });
  49. importAliases.push({ path, imports });
  50. atRule.remove();
  51. }
  52. };
  53. /* Look at all the @value statements and treat them as locals or as imports */
  54. css.walkAtRules('value', atRule => {
  55. if (matchImports.exec(atRule.params)) {
  56. addImport(atRule);
  57. } else {
  58. if (atRule.params.indexOf('@value') !== -1) {
  59. result.warn('Invalid value definition: ' + atRule.params);
  60. }
  61. addDefinition(atRule);
  62. }
  63. });
  64. /* We want to export anything defined by now, but don't add it to the CSS yet or
  65. it well get picked up by the replacement stuff */
  66. const exportDeclarations = Object.keys(definitions).map(key =>
  67. postcss.decl({
  68. value: definitions[key],
  69. prop: key,
  70. raws: { before: '\n ' }
  71. })
  72. );
  73. /* If we have no definitions, don't continue */
  74. if (!Object.keys(definitions).length) {
  75. return;
  76. }
  77. /* Perform replacements */
  78. ICSSUtils.replaceSymbols(css, definitions);
  79. /* Add export rules if any */
  80. if (exportDeclarations.length > 0) {
  81. const exportRule = postcss.rule({
  82. selector: ':export',
  83. raws: { after: '\n' }
  84. });
  85. exportRule.append(exportDeclarations);
  86. css.prepend(exportRule);
  87. }
  88. /* Add import rules */
  89. importAliases.reverse().forEach(({ path, imports }) => {
  90. const importRule = postcss.rule({
  91. selector: `:import(${path})`,
  92. raws: { after: '\n' }
  93. });
  94. imports.forEach(({ theirName, importedName }) => {
  95. importRule.append({
  96. value: theirName,
  97. prop: importedName,
  98. raws: { before: '\n ' }
  99. });
  100. });
  101. css.prepend(importRule);
  102. });
  103. }
  104. );