Created
February 12, 2016 07:27
-
-
Save MeoMix/47e6a5db9dd8a023dafe to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as postcss from 'postcss'; | |
class MixinFrom { | |
constructor() { | |
this.plugin = this.plugin.bind(this); | |
} | |
plugin(css, result) { | |
const promises = []; | |
css.walkAtRules('mixin', (atRule) => { | |
const { needFetch, mixinName, defineMixinPath } = this._getMixinData(atRule, css.source.input.from); | |
if (needFetch) { | |
// Rewrite the mixin rule to be valid for mixins postcss plugin by dropping " from './path'" | |
// e.g. @mixin exampleMixin from './foo.css'; --> @mixin exampleMixin | |
atRule.params = mixinName; | |
// Load the file where mixin definition is believed to be. | |
// If found, add the mixin definition to the top of this file. | |
const promise = System.normalize(defineMixinPath) | |
.then(this._fetchCssFile.bind(this)) | |
.then(this._insertMixinDefinition.bind(this, css, mixinName, defineMixinPath)); | |
promises.push(promise); | |
} | |
}); | |
return Promise.all(promises); | |
} | |
// TODO: It's unclear to me if System.fetch is an acceptable means of doing this. | |
_fetchCssFile(normalizedUrl) { | |
return System.fetch({ | |
address: normalizedUrl, | |
metadata: {} | |
}); | |
} | |
_insertMixinDefinition(currentCssNode, mixinName, defineMixinPath, importedCssText) { | |
let mixinDefinition = null; | |
const parsedCss = postcss.parse(importedCssText); | |
parsedCss.walkAtRules('define-mixin', (atRule) => { | |
if (atRule.params === mixinName) { | |
mixinDefinition = atRule; | |
} | |
}); | |
if (mixinDefinition) { | |
// Add the loaded mixin to the top of the file requiring it. | |
currentCssNode.prepend(mixinDefinition); | |
} else { | |
throw new Error(`Failed to find mixin ${mixinName} at ${defineMixinPath}`); | |
} | |
} | |
_getMixinData(atRule, from) { | |
let mixinData = { | |
mixinName: '', | |
defineMixinPath: '', | |
needFetch: false | |
}; | |
// We're looking for atRules of the form: | |
// @mixin exampleMixin from './foo.css'; | |
const matchImports = /^(.+?)\s+from\s+("[^"]*"|'[^']*'|[\w-]+)$/; | |
const regexpResult = matchImports.exec(atRule.params); | |
if (regexpResult) { | |
mixinData.needFetch = true; | |
mixinData.mixinName = regexpResult[1]; | |
const rawPath = this._removeWrappingQuotes(regexpResult[2]); | |
const baseDirectory = this._getDirectory(from); | |
let absolutePath = this._getAbsolutePath(baseDirectory, rawPath); | |
// Assume CSS file if file extension is missing. | |
if (!absolutePath.includes('.css')) { | |
absolutePath = `${absolutePath}.css`; | |
} | |
mixinData.defineMixinPath = absolutePath; | |
} | |
return mixinData; | |
} | |
_removeWrappingQuotes(string) { | |
return string.replace(/^["']|["']$/g, ''); | |
} | |
_getDirectory(path) { | |
return path.substring(0, path.lastIndexOf('/') + 1); | |
} | |
_getAbsolutePath(basePath, relativePath) { | |
// If relativePath is not relative then there is no work to be done. | |
if (relativePath[0] === '/') { | |
return relativePath; | |
} | |
const stack = basePath.split('/'); | |
// Remove filename if exists (or empty string) | |
stack.pop(); | |
for (let part of relativePath.split('/')) { | |
switch(part) { | |
case '.': | |
// Stay at current level by doing nothing. | |
break; | |
case '..': | |
// Move up a level. | |
stack.pop(); | |
break; | |
default : | |
stack.push(part); | |
} | |
} | |
return stack.join('/'); | |
} | |
} | |
export default postcss.plugin('kappa', (options = {}) => { | |
return new MixinFrom().plugin; | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment