CSS Code Splitting

TAN LI HAU

Frontend Developer at Shopee

lihautan.com

@import 'typography';

@import 'buttons';

 

.button {

    @include button($radius: 5px);

    @include typo-light-1;

}

.button {

   border-radius: 5px;

   display: block;

   background-color: '#ee4d2d';

   font-size: 12px;

   font-weight: lighter;

   color: #ddd;

}

 

:local {

   .button {

      color: green;

   }

}

 

 

import classNames from './style.css';

 

console.log(classNames.button);

 

style.css
component.js

 

.a8eqrd {

   color: green;

}

 

 

 

 

import classNames from './style.css';

 

console.log(classNames.button);

// 'a8eqrd'

style.css
component.js

+

HOW BIG IS SHOPEE?

> 1K CSS files

and growing...

> 67K lines of CSS

~500KB

~ 4s to download and parse

with SLOW 3G Network

6x CPU slowdown

Downloaded: ~518KB

Unused:          ~477KB

(~92%)

Too long to load

Too much to parse

Unused CSS

SO HOW?

switch(url) {

      case '/':

            return import('./PageHome').then(PageHome => {

            // do something

            })

      case '/campaigns':

            return import('./PageCampaign').then(PageCampaign => {

            // do something

            })

}

Code Splitting

npm install --save-dev mini-css-extract-plugin

  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
  ],
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "sass-loader",
        ]
      }
    ]
  }

THE RESULT

~ 200ms to download and parse

with SLOW 3G Network

6x CPU slowdown

Downloaded: ~53KB

Unused:          ~43KB

(~81%)

PROBLEM #1

Styles broken when user goes from Page XXX to Page YYY 

Styles working when user goes directly to Page YYY, only broken when user goes from Page XXX to Page YYY 

<html>

      <head>

            <link rel="stylesheet" type="text/css" href="page-1-style.css" />

            <link rel="stylesheet" type="text/css" href="page-2-style.css" />

      </head>

      <body>

            <div class="foo">HELLO WORLD</div>

      </body>

</html>

/* page-1-styles.css */

.foo {

      color: green;

}

 

/* page-2-styles.css */

.foo {

      color: blue;

}

page1 → page2

      <head>

            <link rel="stylesheet" type="text/css" href="page-1-style.css" />

            <link rel="stylesheet" type="text/css" href="page-2-style.css" />

      </head>

page2 → page1

      <head>

            <link rel="stylesheet" type="text/css" href="page-2-style.css" />

            <link rel="stylesheet" type="text/css" href="page-1-style.css" />

      </head>

function ensureCss(href) {

      const existingLinkTags = document.getElementsByTagName("link");

      for(let i = 0; i < existingLinkTags.length; i++) {

            if (tag.rel === 'stylesheet' && tag.getAttribute("href") === href) {

                  return;

            }

      }

 

      const linkTag = document.createElement('link');

      linkTag.rel = "stylesheet";

      linkTag.type = "text/css";

      linkTag.href = href;

 

      const head = document.getElementsByTagName("head")[0];

      head.appendChild(linkTag);

}

 

// somewhere in your application code

// when you write

import './styles.scss';

 

// webpack's mini-css-extract-plugin transform it into

ensureCss('https://shopee.sg/page-1-style.css');

/* page-1-styles.css */

.page-1 .foo {

      color: green;

}

 

/* page-2-styles.css */

.page-2 .foo {

      color: blue;

}

<div class="class-a class-b">

      HELLO WORLD

</div>

/* styles.css */

.class-b {

      color: green;

}

 

.class-a {

      color: blue;

}

import styles from './styles.scss';

 

function MyComponent({ className }) {

      return <div className={styles.classA + ' ' + className}>Hello World</div>;

}

/* styles.scss */

:local {

       .classA {

              color: blue;

       }

}

ensureCss('https://shopee.sg/76ab609c.css');

 

const styles = { classA: 'c8e4436e' };

function MyComponent({ className }) {

      return <div className={styles.classA + ' ' + className}>Hello World</div>;

}

/* 76ab609c.css */

.c8e4436e {

       color: blue;

}

ensureCss('https://shopee.sg/76ab609c.css');

 

const styles = { classA: 'c8e4436e' };

function MyComponent({ className }) {

      return <div className={styles.classA + ' ' + className}>Hello World</div>;

}

<MyComponent className="classB" />

 

// ↓↓↓↓↓↓↓↓↓↓↓↓↓

 

<div class="c8e4436e classB">Hello World</div>

<div class="c8e4436e classB">Hello World</div>

/* 76ab609c.css */

.c8e4436e {

       color: blue;

}

/* 41c877ed.css */

.classB {

       color: green;

}

<div class="c8e4436e classB">Hello World</div>

/* 76ab609c.css */

.c8e4436e {

       color: blue;

}

/* 41c877ed.css */

.classB.classB {

       color: green;

}

PROBLEM #2

Server-Side Rendering Not Working

function ensureCss(href) {

      const existingLinkTags = document.getElementsByTagName("link");

      for(let i = 0; i < existingLinkTags.length; i++) {

            if (tag.rel === 'stylesheet' && tag.getAttribute("href") === href) {

                  return;

            }

      }

 

      const linkTag = document.createElement('link');

      linkTag.rel = "stylesheet";

      linkTag.type = "text/css";

      linkTag.href = href;

 

      const head = document.getElementsByTagName("head")[0];

      head.appendChild(linkTag);

}

CSS Code Splitting

By Li Hau Tan

CSS Code Splitting

  • 2,926