index.js 4.2 KB

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