Skip to content

Instantly share code, notes, and snippets.

@MeoMix
Created February 12, 2016 07:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MeoMix/47e6a5db9dd8a023dafe to your computer and use it in GitHub Desktop.
Save MeoMix/47e6a5db9dd8a023dafe to your computer and use it in GitHub Desktop.
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