Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// Usage : less2stylusDir('../src/css/');
var fs = require('fs');
// this less 2 stylus conversion script make a stylus easy to read syntax
// - let the braces
// - replace the @ for var as $
// - let semicolons
function less2stylus(less)
{
return less
// remove opening brackets
//.replace(/^(\ *)(.+)\ +\{\ *\n?\ */mg, "$1$2\n$1 ")
// remove opening brackets
//.replace(/^(\ *)([^\ \n]+)\ +\{\ *\n?\ */mg, "$1$2\n$1 ")
// remove opening brackets again (some random cases I'm too lazy to think through)
//.replace(/\ *\{\ *\n*/g, "\n")
// remove closing brackets
//.replace(/\ *\}\ *\n*/g, "\n")
// remove semicolons
//.replace(/\;\ *?$/gm, "")
// replace @variable: with $variable =
.replace(/@(.+)\s*:\s*/g, function(_, $1) {
return "$" + $1 + " = ";
})
// replace all other variable call, careful with native @{keyword}
.replace(/\@(\w+)/g, function(_, $1) {
if ($1 === "import" || $1 === "media" || $1 === "font-face" || $1 === "keyframes") {
return _;
} else {
return "$" + $1;
}
})
// replace @{variable} with {$variable}
.replace(/@\{(\w+)\}/g, function(_, $1) {
return '{$' + $1 +'}';
})
// replace mixins from .border-radius(4px) to border-radius(4px)
.replace(/\.([\w-]+) ?\(/g, "$1(")
// ***** IMPORTANT *****
//
// switch this two lines if you want to disable @extend behavior
//.replace(/(\s\s*)(\.[a-zA-Z][\w-][^\(]+;$)/g, "$1@extend $2") // replace mixins without args by @extend
.replace(/\.([a-zA-Z][\w-]+);/g, "$1();") // replace mixins without args
//
// ***** IMPORTANT *****
.replace(/(\ *)(.+)>\ *([\w-]+)\(/g, "$1$2>\n$1 $3(")
// ms filter fix
.replace(/filter: ([^'"\n;]+)/g, 'filter: unquote("$1")')
// url data
// .replace(/: ?url\(([^'"\)]+)\)/g, ': url(unquote("$1"))')
.replace(/: ?url\((.+):(.+)\)/g, ': url(unquote("$1:$2"))')
// rename (useless)
.replace(/\.less/g, ".styl")
// variable definitions
.replace(/\$(.+):/g, "$$$1=")
// escaped data
.replace(/~'(.+)'/g, "unquote('$1')")
.replace(/~"(.+)"/g, "unquote('$1')")
// make all commas have 1 space after them
.replace(/,\ */g, ", ")
// replace 0.x by .x
.replace(/(:\ )0\.([0-9])+/g, ".$2 ")
// remove trailing whitespace
.replace(/\x20+$/g, '')
.replace(/\$import/g, '@import')
.replace(/\$media/g, '@media')
.replace(/\$font-face/g, '@font-face')
.replace(/\$keyframes/g, '@keyframes')
.replace(/\$retina\s*:\s*/g, '$retina = ')
.replace(/pixel-ratio\s*=\s*/g, 'pixel-ratio: ')
.replace(/max-width\s*=\s*/g, 'max-width: ')
.replace(/min-width\s*=\s*/g, 'min-width: ')
.replace(/fadeOut/gi, 'fade-out')
.replace(/fade-out\(#(.+),\s*(.+)\)/g, function(_, color, alpha){
return 'rgba(#' + color + ',' + (1 - parseFloat(alpha) * 0.01) + ')';
})
// you should be using fade-out/rgba, but okay
.replace(/fade\((.+), (.+)\)/g, 'fade-out($1, 100 - $2)')
;
}
function less2stylusDir(dir)
{
var names = fs.readdirSync(dir);
for (var _j = 0, _len = names.length; _j < _len; _j++) {
var name = names[_j];
if (name.match(/\.less$/))
{
var stylus = less2stylus(fs.readFileSync(dir + "/" + name, "utf-8"));
fs.writeFileSync(dir + "/" + (name.replace(/\.less$/, ".styl")), stylus);
}
}
}
less2stylusDir('.');
// if you have a folder tree, add each folder, or use glob()...

Post conversion

  • Some @import orders have been switched to don't broke Stylus parsing
  • Some .mixin have been rename to .mixin() for a better conversion

Conversion

First of all, I've created a base script which made most of the conversion

node less2stylus.js

This convert every .less to .styl equivalent Please note this less to stylus conversion script make a simple but easier to read syntax Stylus do not require this but this script keep the braces, the semicolons & a variable identifier ($ instead of @ a la Sass) Since less just have simple mixin, Stylus have @extend Sass feature, so I've optimized some mixin call without args by replacing them with @extend. The stylus output will probably be lighter than the less one

After conversion: stylus parse try

Fixes of "parse error": I've change just some tiny stuffs. Mainly;

  • url(data...) to url(unquote('data...'))
  • same for microsoft filters

Manual Output comparison

stylus --out _output index.styl

lessc index.less > _output/index.less.css

Since I've used @extend with less2stylus(), I can do a simple comparison. So I modified a little bit the less code to be able to try comparision without @extend (it was hard since less has same syntax for class & mixins).

Comparison results

Things that are not a problem

  • By default, stylus does not include /* */ comments so there some differences but really not important
  • The opacity() mixin is completly broken but it's not really a problem. It's just less than 10 lines. & Stylus nib extension have that https://github.com/visionmedia/nib/blob/master/lib/nib/vendor.styl#L240
  • Some url('') have been changed to url(""), it's not a problem.
  • Some color value have been optimized (e.g. : black to #000) or lowercased

Main issue

Becareful, there is a sort of major with if a less mixin, which are also class name. So for some mixin called without args, you have to add the class name after the mixin declaration.

mixin() { // previously .mixin used as a mixin
    //...
}

.mixin {
    mixin()
}

Only tiny issue

Same Color functions return differents results e.g. :

  • less lighten(#E0E3E4, 5%) => #eeeff0
  • stylus lighten(#E0E3E4, 5%) => #e2e4e5
  • sass lighten(#E0E3E4, 5%) => #eeeff0

The difference is no easily visible, but anyway, I've bumped this issue https://github.com/LearnBoost/stylus/issues/328 Compare the difference http://0to255.com/eeeff0 vs http://0to255.com/e2e4e5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.