Modify Bootstrap LESS files to use an ID selector

2019-05-15 00:58发布

问题:

Briefly, what I'm trying to do is apply a custom ID selector on top of Bootstrap.CSS so that I don't break other CSS on a website. So this :

.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12
{
    position: relative;
    min-height: 1px;
    padding-left: 15px;
    padding-right: 15px;    
}

becomes this:

#my-id-selector .col-xs-1, #my-id-selector .col-sm-1, #my-id-selector .col-md-1, #my-id-selector .col-lg-1, #my-id-selector .col-xs-2, #my-id-selector .col-sm-2, #my-id-selector .col-md-2, #my-id-selector .col-lg-2, #my-id-selector .col-xs-3, #my-id-selector .col-sm-3, #my-id-selector .col-md-3, #my-id-selector .col-lg-3, #my-id-selector .col-xs-4, #my-id-selector .col-sm-4, #my-id-selector .col-md-4, #my-id-selector .col-lg-4, #my-id-selector .col-xs-5, #my-id-selector .col-sm-5, #my-id-selector .col-md-5, #my-id-selector .col-lg-5, #my-id-selector .col-xs-6, #my-id-selector .col-sm-6, #my-id-selector .col-md-6, #my-id-selector .col-lg-6, #my-id-selector .col-xs-7, #my-id-selector .col-sm-7, #my-id-selector .col-md-7, #my-id-selector .col-lg-7, #my-id-selector .col-xs-8, #my-id-selector .col-sm-8, #my-id-selector .col-md-8, #my-id-selector .col-lg-8, #my-id-selector .col-xs-9, #my-id-selector .col-sm-9, #my-id-selector .col-md-9, #my-id-selector .col-lg-9, #my-id-selector .col-xs-10, #my-id-selector .col-sm-10, #my-id-selector .col-md-10, #my-id-selector .col-lg-10, #my-id-selector .col-xs-11, #my-id-selector .col-sm-11, #my-id-selector .col-md-11, #my-id-selector .col-lg-11, #my-id-selector .col-xs-12, #my-id-selector .col-sm-12, #my-id-selector .col-md-12, #my-id-selector .col-lg-12
{
    position:relative;
    min-height:1px;
    padding-left:15px;
    padding-right:15px
}

So, imagine this scenario, I think it's pretty common:

You use Bootstrap (in our case, 3.1.1) to build a web/mobile app widget that needs to run inside another application. The widget will have its own CSS that must not break the host application's CSS.

The widget will live in a nice little DIV with a specific ID.

So what we're trying to do is modify the LESS files that compile Bootstrap to reference that specific ID. I'm getting close to doing this, but I keep running into issues. The current issue I have is that the Bootstrap LESS code uses mixins to create GRID-specific CSS (in the GRID.LESS file). When I try to add my selector to the GRID.LESS file, it doesn't behave like I'd expect/hope: Instead of applying my ID selector to all the class selectors in the GRID.LESS file, it only applies to the first selector in a list of selectors.

(As a little background on Bootstrap as I understand it (hopefully so those of you with LESS experience can answer my question even if you've never used Bootstrap): the source LESS files are all referenced via @import statements in the BOOTSTRAP.LESS file. I use Grunt to compile the Bootstrap.CSS and JS files per Bootstrap's website documentation (so a Grunt.JSON file exists that orchestrates the LESS compiling and a bunch of other stuff). BOOTSTRAP.LESS uses @import to access VARIABLES.LESS, MIXINS.LESS, and GRID.LESS, which are all players in this drama).

Hopefully I can explain what I mean:

Original Code (Bootstrap 3.1.1)'s GRID.LESS file contains:

// Columns
//
// Common styles for small and large grid columns
.make-grid-columns();

That code calls a mixin in the MIXIN.LESS file that dynamically creates some column class selectors:

.make-grid-columns() {
  // Common styles for all sizes of grid columns, widths 1-12
  .col(@index) when (@index = 1) { // initial
    @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
    .col((@index + 1), @item);
  }
  .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo
    @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
    .col((@index + 1), ~"@{list}, @{item}");
  }
  .col(@index, @list) when (@index > @grid-columns) { // terminal
    @{list} {
      position: relative;
      // Prevent columns from collapsing when empty
      min-height: 1px;
      // Inner gutter via padding
      padding-left:  (@grid-gutter-width / 2);
      padding-right: (@grid-gutter-width / 2);
    }
  }
  .col(1); // kickstart it
}

I don't fully understand the code above, but I think it loops through the parameters it receives from the VARIABLES.LESS file that comes with Bootstrap, and creates the list of class selectors into CSS that looks like this:

.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12
{
   position: relative;
   min-height: 1px;
   padding-left: 15px;
   padding-right: 15px;
}

The modification I made was to add my custom ID selector scope around the call, like below:

// Columns
//
// Common styles for small and large grid columns
#my-id-selector .make-grid-columns();

The problem is that this doesn't apply my selector across all the dynamically generated columns that Bootstrap creates in the MIXIN.LESS code, and instead only adds my ID selector to the first selector in the list, like this:

#my-id-selector .col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12
{
    position: relative;
    min-height: 1px;
    padding-left: 15px;
    padding-right: 15px;
}

What I find odd is that if I just write a LESS file without the mixin to apply my ID selector's scope to a list of selectors, I get the output that I'm expecting. For example, this...

#my-id-selector
{
    .col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12
    {
        position: relative;
        min-height: 1px;
        padding-left: 15px;
        padding-right: 15px;    
    }
}

...produces the expected and much desired output:

#my-id-selector .col-xs-1,#my-id-selector .col-sm-1,#my-id-selector .col-md-1,#my-id-selector .col-lg-1,#my-id-selector .col-xs-2,#my-id-selector .col-sm-2,#my-id-selector .col-md-2,#my-id-selector .col-lg-2,#my-id-selector .col-xs-3,#my-id-selector .col-sm-3,#my-id-selector .col-md-3,#my-id-selector .col-lg-3,#my-id-selector .col-xs-4,#my-id-selector .col-sm-4,#my-id-selector .col-md-4,#my-id-selector .col-lg-4,#my-id-selector .col-xs-5,#my-id-selector .col-sm-5,#my-id-selector .col-md-5,#my-id-selector .col-lg-5,#my-id-selector .col-xs-6,#my-id-selector .col-sm-6,#my-id-selector .col-md-6,#my-id-selector .col-lg-6,#my-id-selector .col-xs-7,#my-id-selector .col-sm-7,#my-id-selector .col-md-7,#my-id-selector .col-lg-7,#my-id-selector .col-xs-8,#my-id-selector .col-sm-8,#my-id-selector .col-md-8,#my-id-selector .col-lg-8,#my-id-selector .col-xs-9,#my-id-selector .col-sm-9,#my-id-selector .col-md-9,#my-id-selector .col-lg-9,#my-id-selector .col-xs-10,#my-id-selector .col-sm-10,#my-id-selector .col-md-10,#my-id-selector .col-lg-10,#my-id-selector .col-xs-11,#my-id-selector .col-sm-11,#my-id-selector .col-md-11,#my-id-selector .col-lg-11,#my-id-selector .col-xs-12,#my-id-selector .col-sm-12,#my-id-selector .col-md-12,#my-id-selector .col-lg-12
{
    position:relative;
    min-height:1px;
    padding-left:15px;
    padding-right:15px
}

You can see that my #my-id-selector was applied to all selectors in the list, and the only difference I can detect is a mixin call. So it would seem that the mixin evaluates after my selector ID is applied.

If that's the case, is there some way to ensure that the mixin is evaluated first, then apply my scope?

Has anyone tried to customize Bootstrap so that it is ID selector specific?

Update: A work around was to modify the MIXINS.LESS' .make-grid-columns() mixin to pass in my ID selector. This is the code:

.make-grid-columns(@id_prefix: "") {
  // Common styles for all sizes of grid columns, widths 1-12
  .col(@index) when (@index = 1) { // initial
    @item: ~"@{id_prefix} .col-xs-@{index}, @{id_prefix} .col-sm-@{index}, @{id_prefix} .col-md-@{index}, @{id_prefix} .col-lg-@{index}";
    .col((@index + 1), @item);
  }
  .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo
    @item: ~"@{id_prefix} .col-xs-@{index}, @{id_prefix} .col-sm-@{index}, @{id_prefix} .col-md-@{index}, @{id_prefix} .col-lg-@{index}";
    .col((@index + 1), ~"@{list}, @{item}");
  }
  .col(@index, @list) when (@index > @grid-columns) { // terminal
    @{list} {
      position: relative;
      // Prevent columns from collapsing when empty
      min-height: 1px;
      // Inner gutter via padding
      padding-left:  (@grid-gutter-width / 2);
      padding-right: (@grid-gutter-width / 2);
    }
  }
  .col(1); // kickstart it
}

What's really gross about that is the amount of customization that I'll have to do in order to "fix" all of the Bootstrap CSS so it uses my ID selector. So I'm still looking for a better solution...

回答1:

For the moment the only possible solution is to import compiled Bootstrap CSS into a Less namespace i.e.:

#my-id-selector {
    @import (less) "bootstrap.css";
}

Importing Bootstrap less sources into a namespace won't work as expected not only because of column classes but also for a hundred and one reason (see #2052 for more details).