index.js 3.2 KB

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