less-plugin-sass2less icon indicating copy to clipboard operation
less-plugin-sass2less copied to clipboard

Support for converting Bootstrap 4 to less

Open mediafreakch opened this issue 8 years ago • 13 comments

Currently the plugin cannot convert all of Bootstrap 4 (v4.0.0-alpha.6). They are using some quite advanced SASS functions that the plugin doesn't convert yet. The LESS compiler will throw errors if you try to compile the converted files.

Here's a list of what I found so far:

  • [ ] @each
  • [ ] comparable()
  • [ ] @warn - afaik there's no equivalent in LESS, maybe just strip it?
  • [ ] @else if (not) - this one might be tricky
  • [ ] map-values()
  • [ ] ( 0: ( x: 0, y: 0)) (maps) - there is no map data-type in less. maybe this can help: https://github.com/seven-phases-max/less-plugin-lists

I'll start working from the top. Ideas about how to convert / support the map data-type in LESS would be appreciated.

mediafreakch avatar Jan 07 '17 15:01 mediafreakch

Would this help you as a starting point? https://github.com/Crunch/Granola/blob/master/lib/map.js

It's a work-in-progress, so there might be some edge cases where the value doesn't evaluate IIRC (haven't worked on it in a while). But I got it to a point where it was working pretty well for most uses.

I could probably release it as a separate Less.js plugin at some point.

It allows definitions like this:

@set {
        options {
            ui: default;
            elements: default;
            theme: default;
        }
        colors {
        	dark {
        	    color: black;
        	}
        	light {
        	    color: white;
        	}
        }
    }

And then you can get a value with a get() function.

.box {
  color: get('colors.dark.color');
}

matthew-dean avatar Jan 07 '17 21:01 matthew-dean

Btw, something like maps might come to native Less. See the discussion at: https://github.com/less/less-meta/issues/12

The thread is long, but the proposal is to allow accessing variables / properties of namespaces & mixins, with a syntax like:

#namespace {
  prop1: value;
  @var1: value;
}
.box {
  value1: #namespace['prop1'];
  value2: #namespace['@var1'];
}

That would effectively turn ANY single-selector ruleset into a map, so there'd be no need for another "data type".

matthew-dean avatar Jan 07 '17 21:01 matthew-dean

And I wrote an "each" function here: https://github.com/less/less.js/pull/2786/commits/f52b0736015d69f01e9be3257b039f2f332a613f

Yes, @warn has no equivalent or current value in Less.

Elseif is a little trickier. There's an order of evaluation, but not an order of operations, since the languages are dissimilar in their approach. See: https://getcrunch.co/2015/10/08/less-the-worlds-most-misunderstood-css-pre-processor/ (Not trying to be showy, just hoping these links will help you, because I think this is a great project to have working.

Mixins match based on guards, but guards don't carry forward the result of the previous test.

So you can do:

@if($a = 1) { }
@if($a = 2) { }

But it's much trickier to do this:

@if($a = 1) { }
@elseif($a > 0) { }

...because Less is not a program like Sass is. Each block is independently evaluated by evaluation rules. However... maybe you could do it like:

.mixin() when (@a = 1) { }
.mixin() when (not (@a = 1)) and (@a > 0) { }

So, you have to exclusively negate the first condition if you want to only evaluate the second if the first doesn't match. And, of course, you'd have to negate two conditions if there are multiple elseifs. (And then you immediately call the mixin.)

So, an if / elseif / else would be like:

.mixin() when (@a = 1) { }
.mixin() when (not (@a = 1)) and (@a > 0) { }
.mixin() when (default()) { }
.mixin();

(Or you do these things in a plugin.)

matthew-dean avatar Jan 07 '17 21:01 matthew-dean

The one thing you're going to need to be careful about is variable scope and lazy-loading. Sass's variable scope is different, and in Sass, variables can't be referenced before they're defined (unlike Less).

So, in Sass, you get:

$var: 1;
.box {
  $var: 2;
  val: $var; // value is 2
  $var: 3;
}
$new_var: $var;  // value is 3

In Less, you get:

@var: 1;
.box {
  @var: 2;
  val: @var; // value is 3
  @var: 3;
}
@new_var: @var;  // value is 1

matthew-dean avatar Jan 07 '17 21:01 matthew-dean

Thanks, those inputs definitely help! Regarding maps I will also look into re-using https://github.com/seven-phases-max/less-plugin-lists .

Btw your proposal on @elseif is exactly what I already did for @if and @else, just that this one is a bit harder to write a regex for ;)

I should find time this week and will keep you posted about the progress!

mediafreakch avatar Jan 09 '17 14:01 mediafreakch

Hi @mediafreakch - 👋

I (and my company, Holidayextras) are very interested in seeing bootstrap 4 support within this module. Can I offer any of our support in seeing this issue resolved? We currently use Bootstrap 3 (Less) for a number of different brands (using subtle variable overrides) but would like to benefit from extending our brands to hand pick BS4 components (via a module like this).

elliottcrush avatar Aug 08 '18 10:08 elliottcrush

@elliottcrush Support is more than welcome 😀. You see an (outdated?) list of issues with converting BS4 with this module on top in the first post. LESS has evolved in the meantime as well, so you might find it easier to implement a conversion algorithm these days...

mediafreakch avatar Aug 08 '18 14:08 mediafreakch

Yes, there's a native if() and each() function now in Less, along with maps and lookups! And there's also this: https://github.com/seanCodes/bootstrap-less-port, but that doesn't use some of Less's newer functions / features.

matthew-dean avatar Aug 09 '18 05:08 matthew-dean

Awesome thanks @mediafreakch & @matthew-dean - I'll try and get to that as soon as.

elliottcrush avatar Aug 13 '18 06:08 elliottcrush

@elliottcrush could this package help for map-merge and other issues?

Jany-M avatar Aug 21 '18 20:08 Jany-M

Also, these conversions throw an error (from _variables.scss).

SASS

$grid-breakpoints: (
  xs: 0,
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px
) !default;

@include _assert-ascending($grid-breakpoints, "$grid-breakpoints");
@include _assert-starts-at-zero($grid-breakpoints);

$link-hover-color:          darken($link-color, 15%) !default;
$table-dark-accent-bg:        rgba($white, .05) !default;
$table-dark-hover-bg:         rgba($white, .075) !default;
$table-dark-border-color:     lighten($gray-900, 7.5%) !default;

$custom-file-text: (
  en: "Browse"
) !default;

Converted LESS

@grid-breakpoints: (
  xs: 0,
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px
);

._assert-ascending(@grid-breakpoints, "@grid-breakpoints");
._assert-starts-at-zero(@grid-breakpoints);

@input-btn-focus-color:       fade(@component-active-bg, (.25*100)); // error evaluating function `fade`: color.toHSL is not a function
@link-hover-color:          darken(@link-color, 15%); // error evaluating function `darken`: color.toHSL is not a function
@table-dark-border-color:     lighten(@gray-900, 7.5%); // same type as above
// & several others of the same type

@custom-file-text: (
  en: "Browse"
);

I'm available for testing!

Jany-M avatar Aug 21 '18 20:08 Jany-M

For those looking for Bootstrap 4 for Less that's ready to use right now, there's https://github.com/seanCodes/bootstrap-less-port.

calvinjuarez avatar Dec 07 '18 01:12 calvinjuarez

@Jany-M I think the color.toHSL is not a function error is an internal Less bug that happens when there's not a valid color value, I've seen it pop up more than once.

matthew-dean avatar Dec 07 '18 16:12 matthew-dean