UNPKG

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