Another method to reduce the volume of SPA applications (webpack)

    It just so happened that recently I have to learn new tools. Another such tool was webpack . The tool is interesting, but after moving from Google Closure * it became a mystery to me why webpack does not squeeze class names like Google Closure Stylesheets does. For a day, on my knee, I wrote a plugin that implemented this functionality quite well. More detailed description below.

    And so let's start with TK. This is done, firstly, for myself, and secondly, for those who have not yet understood that it is happening, but somehow got on this page. Personally, I like to write large and beautiful long class names, which immediately understand what is happening.

    For instance:

    .header {
      position: fixed;
      top: 0;
      ...
    }
    .header a {
      display: block;
      float: right;
      ...
    }
    .sidebar {
      float:right;
      max-width: 30%;
      ...
    }
    .sidebar a {
      font-size: 16px;
      ....
    }
    

    But you can reduce header to h , a sidebar to s , thus saving a lot of bytes not only in CSS, but also in JS files, because most likely your JS will contain selectors by class names.

    However, code readability will suffer from such a reduction, and as a result, development speed. Therefore, you need to create a tool to carry out this replacement automatically.

    A brief explanation of how this works in Closure


    Google Closure consists of several tools, one of which is Google Closure Stylesheets, which is both a preprocessor and a postprocessor for style sheets.

    As a preprocessor, it is similar to all its brothers, but most of all it is similar to SCSS / SASS.
    As a postprocessor, it parses class names by creating a replacement dictionary and replaces all class names with their short notation.

    For example, the code above will become:

    .a {
      position: fixed;
      top: 0;
      ...
    }
    .a a {
      display: block;
      float: right;
      ...
    }
    .b {
      float:right;
      max-width: 30%;
      ...
    }
    .b a {
      font-size: 16px;
      ....
    }
    

    A dictionary of substitutions will be:

    {
      "header": "a",
      "sidebar": "b"
    }
    

    In fact, there is much more functionality, but the article is not about that. There is also Closure Templates, a good template engine, in which it is necessary to mark all class names with a special directive, which would then apply the replacement dictionary to the templates.

    For instance:

    {namespace test}
    /**
    * Test template
    *
    */
    {template .test}
    
    HeaderHomeAbout
    ...
    Sidebar

    Also, do not forget that we have JS in which we also need to "refine" the names of all CSS classes:

    var header = goog.dom.getElementByClass(goog.getCssName('header'));
    var sidebar = goog.dom.getElementByClass(goog.getCssName('sidebar'));
    

    And only when we fix all the sources and send them for compilation, together with the replacement dictionary, everything will work.

    The main drawback of this method is that the dictionary is compiled in CSS, i.e. if you have a class that is used only to select the DOM element from JS, then it may not get into the dictionary (or it may, but I will make a reservation that this article is not a review of Closure Tools).

    Back to the plugin


    It wasn’t very convenient to scatter functions everywhere, so I decided to set the class names by the mask ___ <% className%> __ .

    Thus, the styles come to the form:

    .___header__ {
      position: fixed;
      top: 0;
      ...
    }
    .___header__ a {
      display: block;
      float: right;
      ...
    }
    .___sidebar__ {
      float:right;
      max-width: 30%;
      ...
    }
    .___sidebar__ a {
      font-size: 16px;
      ....
    }
    

    And working with the DOM in JS, using jQuery as an example:

      var header = $('.___header__');
      var sidebar = $('.___sidebar__');
    

    For example, React:

    function Header(props) {
      return (
        
    {props.children}
    ); }

    For example, Backbone:

    module.exports = Backbone.View.extend({
      tagName: 'div',
      className: '___header__'
    });
    


    UPD:
    For Angular, the example is thick.

    I will make a reservation right away that constructions like:

    var genClassName = function(v) {
      return '___' + v + '__';
    }
    module.exports = Backbone.View.extend({
      tagName: 'div',
      className: genClassName('header')
    });
    

    will not work. As well as styles:

    [class*="bold"] {
      font-weight:bolder;
    }
    

    First steps


    By installing the package:

    npm install --save cssrename-webpack-plugin
    

    And with a little tweak to webpack.config.js :

    const CssRenameWebpackPlugin = require('cssrename-webpack-plugin');
    ...
    module.exports = {
      ...
      plugins: [
        CssRenameWebpackPluginConfig,
         ...
      ]
    };
    

    During the assembly process, a line appears:

    “Profit: 355”,

    which will indicate how many bytes have been saved.

    Discomforts and solutions


    But if in the diverse animal world of JS there are a huge number of libraries that cannot be covered with one parser, then the CSS in this matter is much more humane, and it is much easier to parse it (CSS).

    In its simplest form, it will be one regular expression. Therefore, why not add a similar loader that saves us from adding underscores at least to CSS.

    npm install --save cssrename-loader
    

    Next mutations webpack.config.js :

    module.exports = {
      module: {
        loaders: [
          {
            test: /\.css$/,
            loader: "style-loader!css-loader!cssrename-loader"
          }
        ]
      }
    };
    

    What happened with the test project

    Update:

    I didn’t want to write about that, but apparently I need to add. Further, only styles are considered, the winnings in JS and templates are so easy not to count. For Google Closure Stylesheets:
    Site / FileOriginal Volume / zipReceived Volume / zipSavings Percentage (zip)
    acss.io bundle.488facb7.css13.8KB / 4.4KB13.3KB / 4.0KB≈9%
    getbem.com/introduction getbem.com.1.0.0.css13.3KB / 3.3KB12KB / 3.1KB≈6%
    Bootstrap121.2KB / 18.7KB96.9KB / 16.7KB≈10%
    habrahabr.ru global_main.css212.3KB / 30.6KB155.2KB / 26.9KB≈13%

    Proof

    What “didn't fit” in Google Closure Stylesheets:

    For Atomic, I had to make two replacements for regular expressions:

    \\\(((?:(?!\\\)).)*?)\\\) => --$1--
    \\. => --
    

    For BEM "did not crawl":

    @supports (display: -moz-box) {
        [class*=LineClamp] {
            display: block
        }
    }
        @-webkit-keyframes bounce {
            0% {
                -webkit-transform: translateY(-100%);
                transform: translateY(-100%);
                -webkit-filter: blur(5px);
                filter: blur(5px)
            }
            100%, 40% {
                -webkit-transform: translateY(0);
                transform: translateY(0)
            }
            60% {
                -webkit-transform: translateY(-10%);
                transform: translateY(-10%)
            }
        }
    @keyframes bounce {
             0% {
                 -webkit-transform: translateY(-100%);
                 transform: translateY(-100%);
                 -webkit-filter: blur(5px);
                 filter: blur(5px)
             }
             100%, 40% {
                 -webkit-transform: translateY(0);
                 transform: translateY(0)
             }
             60% {
                 -webkit-transform: translateY(-10%);
                 transform: translateY(-10%)
             }
         }
    

    From Bootstrap "did not crawl":

    border-top: 4px solid \9;
    @media all and (transform-3d),(-webkit-transform-3d)
    @-ms-viewport {
        width: device-width
    }
    

    From Habr "did not crawl":

    @charset "UTF-8";
    @-moz-document url-prefix() {
        .search-field__select {
            text-indent: .01px;
            text-overflow: ''
        }
    }
    

    Also popular now: