motion
Version:
motion - moving development forward
94 lines (83 loc) • 3 kB
JavaScript
import postcss from 'postcss'
import replaceSymbols, { replaceAll } from 'icss-replace-symbols'
const matchImports = /^(.+?)\s+from\s+("[^"]*"|'[^']*'|[\w-]+)$/
const matchValueDefinition = /(?:,\s+|^)([\w-]+):?\s+("[^"]*"|'[^']*'|\w+\([^\)]+\)|[^,]+)\s?/g
const matchImport = /^([\w-]+)(?:\s+as\s+([\w-]+))?/
let options = {}
let importIndex = 0
let createImportedName = options && options.createImportedName || ((importName/*, path*/) => `i__const_${importName.replace(/\W/g, '_')}_${importIndex++}`)
export default css => {
let importAliases = []
let definitions = {}
const addDefinition = atRule => {
let matches
while (matches = matchValueDefinition.exec(atRule.params)) {
let [/*match*/, key, value] = matches
// Add to the definitions, knowing that values can refer to each other
definitions[key] = replaceAll(definitions, value)
atRule.remove()
}
}
const addImport = atRule => {
let matches = matchImports.exec(atRule.params)
if (matches) {
let [/*match*/, aliases, path] = matches
// We can use constants for path names
if (definitions[path]) path = definitions[path]
let imports = aliases.split(/\s*,\s*/).map(alias => {
let tokens = matchImport.exec(alias)
if (tokens) {
let [/*match*/, theirName, myName = theirName] = tokens
let importedName = createImportedName(myName)
definitions[myName] = importedName
return {theirName, importedName}
} else {
throw new Error(`@import statement "${alias}" is invalid!`)
}
})
importAliases.push({path, imports})
atRule.remove()
}
}
/* Look at all the @value statements and treat them as locals or as imports */
css.walkAtRules('value', atRule => {
if (matchImports.exec(atRule.params)) {
addImport(atRule)
} else {
addDefinition(atRule)
}
})
/* 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 */
let exportDeclarations = Object.keys(definitions).map(key => postcss.decl({
value: definitions[key],
prop: key,
raws: { before: "\n " },
_autoprefixerDisabled: true
}))
/* If we have no definitions, don't continue */
if (!Object.keys(definitions).length) return
/* Perform replacements */
replaceSymbols(css, definitions)
/* Add export rules if any */
if (exportDeclarations.length > 0) {
css.prepend(postcss.rule({
selector: `:export`,
raws: { after: "\n" },
nodes: exportDeclarations
}))
}
/* Add import rules */
importAliases.reverse().forEach(({path, imports}) => {
css.prepend(postcss.rule({
selector: `:import(${path})`,
raws: { after: "\n" },
nodes: imports.map(({theirName, importedName}) => postcss.decl({
value: theirName,
prop: importedName,
raws: { before: "\n " },
_autoprefixerDisabled: true
}))
}))
})
}