Non-responsive Bootstrap 3 by stripping media queries

Bootstrap 3 is mobile first, and uses media queries extensively. This is problematic for people that still have to support IE8, but also want the responsive features on newer browsers. Bootstrap does have a note on disabling responsiveness but that’s designed for completely disabling for all browsers, so it’s complicated to dynamically generate their changes on the fly for both CSS3-compliant and non-compliant browsers. In addition it also requires respond.js, which is a neat script but it’s heavy on the client-side, requires weird workarounds for CDN usage that are extremely heavy client-side and cause page load delays and the flash of unstyled content. None of that is the fault of respond.js, it’s just an inevitable side effect. I was also having problems with the CDN proxy causing IE8 to just lock up trying to fetch the CSS, something to do with the way Amazon CloudFront was serving the files that IE’s iFrames didn’t like.

However, there’s an advantage that I’m dynamically generating the Bootstrap CSS from LESS server-side whenever a variable change is made. So instead I decided to generate the Bootstrap CSS from LESS, then parse that CSS using PHP to find all the media queries and if they applied to a specific width I selected (1200px), just insert them, otherwise delete them. Then serve that CSS instead of the responsive CSS to IE8, based on user agent parsing. This is a tiny bit of one-time work server-side, and removes all the client-side effort.

This uses Sabberworm’s PHP CSS Parser, which is excellent. The regular expression was created from MDN’s CSS3 Media Query BNF. The code starts by calling the compile method of the lessphp processor, then parses the CSS, iterates over it looking for all the media queries. If there’s a media query it’ll be tokenised against the regular expression. Then the tokens will be compared to see if they match against the fixed width, if so the CSS inside the query will just be included at this point in the CSS document, if not it won’t. Then the two CSS files are written to disk.

Note: I have not exhaustively tested either the regular expression, or the resulting CSS. But it seems to work…

<?php
    $file = 'bootstrap';
    $output = $less->compile($css);

    $nrs  = Sabberworm\CSS\Settings::create()->withMultibyteSupport(false);
    $nrcp = new Sabberworm\CSS\Parser($output, $nrs);
    $nrcd = @$nrcp->parse();
    $nrd  = new Sabberworm\CSS\CSSList\Document();

    $media_type    = "(?:all|aural|braille|handheld|print|projection|screen|tty|tv|embossed)";
    $media_feature = "(?:width|min-width|max-width|height|min-height|max-height|device-width|min-device-width|max-device-width|device-height|min-device-height|max-device-height|aspect-ratio|min-aspect-ratio|max-aspect-ratio|device-aspect-ratio|min-device-aspect-ratio|max-device-aspect-ratio|color|min-color|max-color|color-index|min-color-index|max-color-index|monochrome|min-monochrome|max-monochrome|resolution|min-resolution|max-resolution|scan|grid)";
    $media_expression = "\(\s*($media_feature)\s*(?:\:\s*(.*?))?\s*\)";
    $media_query = "/(?:(?:(only|not)\s+)?($media_type)(?:\s*and\s+$media_expression)*|$media_expression(?:\s+and\s+$media_expression)?)/";

    $width = 1200;

    foreach ($nrcd->getContents() as $nrrs) {
      if (!$nrrs instanceof Sabberworm\CSS\CSSList\AtRuleBlockList ||
          !$nrrs->atRuleName() == 'media' ||
          $nrrs->atRuleArgs() == 'print') {
        $nrd->append($nrrs);
        continue;
      }

      $match = true;
      $tokens = array();
      preg_match($media_query, $nrrs->atRuleArgs(), $tokens);
      $tokens = array_values(array_filter($tokens));
      for ($t=1; $t<count($tokens); $t++) {
        $token = $tokens[$t];
        switch ($token) {
          case 'only':
            if ($tokens[$t+++1] != 'screen')
              $match = false;
            break;
          case 'not':
            if ($tokens[$t+++1] == 'screen')
              $match = false;
            break;
          case 'min-width':
          case 'max-width':
            $value = array();
            if (!preg_match('/(\d+)px/', $tokens[$t+++1], $value))
              break;
            if (($token == 'min-width' && $width < $value[1]) ||
                ($token == 'max-width' && $width > $value[1]))
              $match = false;
            break;
          default:
            if ($token != 'screen')
                  $match = false;
        }
      }

      if (!$match)
        continue;

      foreach ($nrrs->getContents() as $prop)
        $nrd->append(new Sabberworm\CSS\Property\Selector($prop->__toString()));
    }

    file_put_contents($file . "nr.css", $nrd->__toString());
    file_put_contents($file . ".css", $output);

Comments

    Leave a comment