Skip to content

Instantly share code, notes, and snippets.

@didi0613
Last active November 7, 2017 19:46
Show Gist options
  • Save didi0613/48fcbeef9b2bc0b26e6828aed1ec9f68 to your computer and use it in GitHub Desktop.
Save didi0613/48fcbeef9b2bc0b26e6828aed1ec9f68 to your computer and use it in GitHub Desktop.
Incorrect z-index Optimization With IE9

What's the Issue?

For IE9, we are splitting the CSS into two files and serving them up, whereas for other browsers we serve a single file.

The problem is that the build script is optimizing the CSS after splitting it into two parts instead of before. A step of this optimization is doing a z-index reduction, where all z-indices are reduced to their lowest possible value.

For example, if one rule has "z-index: 100" and another has "z-index: 150", it will be reduced to 1 and 2.

However, the parts are optimized individually, some information about other z-index values gets lost.

What's the possible solution?

The reduction of z-index from postcss-zindex plugin should happen before the css code split.

Where do we have the css optimization/split plugins?

Under electrode-archtype-app-dev/config/webpack/extract-style.js, we have few plugins for css:

  • OptimizeCssAssetsPlugin will search for CSS assets during the Webpack build and will optimize/minimize css assets under production mode
  • CSSSplitPlugin used for split your CSS for browsers using webpack and postcss.

And with the electrode-archetype-app-dev, we also got postcss-zindex installed from

electrode-archetype-react-app-dev@4.0.6
  └─┬ css-loader@0.26.4
    └─┬ cssnano@3.10.0
      └── postcss-zindex@2.2.0
  • postcss-zindex loading from css-loader will reduce z-index values with postcss.

The expected loading sequence should be: postcss-zindex(css-loader), OptimizeCssAssetsPlugin > CSSSplitPlugin

Let's take a look at what finalized webpack config looks like.

Finalized webpack.config.js

{
...
plugins: [{
  {
      "options": {
        "options": {
          "debug": false
        },
        "test": {}
      },
      "__name": "LoaderOptionsPlugin"
    },
    {

      "filename": "[name].style.[hash].css",
      "id": 1,
      "options": {},
      "__name": "ExtractTextPlugin"
    },
    {
      "options": {
        "assetNameRegExp": {},
        "cssProcessorOptions": {},
        "canPrint": true
      },
      "__name": "OptimizeCssAssetsPlugin"
    },
    {
      "options": {
        "size": 4000,
        "preserve": true
      },
      "__name": "CSSSplitWebpackPlugin"
    },
    {
      "options": {
        "options": {
          "context": "/Users/s0d00px/easy-reorder/src",

          "stylus": {}
        },
        "test": {}
      },
      "__name": "LoaderOptionsPlugin"
    }
  ...
  }]
...
}

Looks all the plugins are being included into the finalized webpack config.

What's the execution order of webpack plugins?

Webpack plugins are bound to the compiler and applied in the order specified. They listen to the webpack events to hook into the execution points.

So when does css-split plugin being triggered?

After taking a look at the source code of css-split-webpack-plugin here https://github.com/metalabdesign/css-split-webpack-plugin/blob/master/src/index.js#L140, it binded to optimize-chunk-assets event and wait for its being triggered.

How about optimize-css-assets-webpack-plugin then?

We currently using optimize-css-assets-webpack-plugin@1.3.2 from electrode-archetype-app-dev. It'a an older version, and the compilation phase is emit. The latest optimize-css-assets-webpack-plugin's webpack compilation phase is based on last-call-webpack-plugin which default webpack compilation phase is optimize-assets.

Webpack compilation order

optimize-chunk-assets > after-optimize-chunk-assets > optimize-assets > after-optimize-assets

So what happened here is, css-split-webpack-plugin always comes frist, then optimize-css-assets-webpack-plugin. Which leads to the root cause of this issue.

Test on easy-reorder application

Let's verify our ideas at the easy-reorder app. I've added some logs in css-split-webpack-plugin/src/dist.js and optimize-css-assets-webpack-plugin/index.js.

After you run:

$npm run build

You shall see logs in the terminal similar to:

webpack compiling: .
  ...
  basic chunk optimization: .
  chunk optimization: .
  advanced chunk optimization: .
  building modules: .
  module and chunk tree optimization: .
  chunk modules optimization: .
  advanced chunk modules optimization: .
  module reviving: .
  module order optimization: .
  module id optimization: .
  chunk reviving: .
  chunk order optimization: .
  chunk id optimization: .
  hashing: .
  module assets processing: .
  chunk assets processing: .
  additional chunk assets processing: .
  recording: .
  additional asset processing: .xxxxxxxxxxx CSSSplitWebpackPlugin xxxxxxxxxxx optimize-chunk-assets
  chunk asset optimization: .
  asset optimization: .xxxxxxxxxxx OptimizeCssAssetsPlugin xxxxxxxxxxx emit

Starting to optimize CSS...
Processing main.style.d678ccac84a1cbba52e0.css...
Processing main.style.d678ccac84a1cbba52e0-1.css...
Processing main.style.d678ccac84a1cbba52e0-2.css...
Processing main.style.d678ccac84a1cbba52e0-split.css...
Processed main.style.d678ccac84a1cbba52e0.css, before: 337400, after: 242207, ratio: 71.79%
Processed main.style.d678ccac84a1cbba52e0-1.css, before: 322335, after: 229822, ratio: 71.3%
Processed main.style.d678ccac84a1cbba52e0-2.css, before: 15169, after: 12383, ratio: 81.63%
Processed main.style.d678ccac84a1cbba52e0-split.css, before: 105, after: 104, ratio: 99.05%

From the logs above, we can tell that the optimize css happens before css got splitted.

How can we fix this?

According to the findings above, css-split-webpack-plugin is binded to optimize-chunk-assets event and wait for its being triggered, optimize-css-assets-webpack-plugin's webpack compilation phase is after-optimize-chunk-assets.

The webpack compilation order is: optimize-chunk-assets > after-optimize-chunk-assets > optimize-assets > after-optimize-assets

Change the line https://github.com/metalabdesign/css-split-webpack-plugin/blob/master/src/index.js#L140 to listen to after-optimize-chunk-assets event.

Re-run $npm run build, we can get OptimizeCssAssetsPlugin and CSSSplitWebpackPlugin triggered at the same time. After npm run build finished, go to the /dist/js folder and checked the splited css files. You shall see the unopmtimized code in the split css files.

Verify the fixes

After creating this PR: bootstarted/css-split-webpack-plugin#18 It appears that all the tests are failed. The reason after that is because after-optimize-chunk-assets is sync, doesn’t take a callback. So we can try to switch to using the optimize-assets phase, which appears to have a callback. But here is what I got after running npm run build:

error: uncaughtException: chunks.map is not a function
{ date: 'Sun Nov 05 2017 20:51:59 GMT-0800 (PST)',

  process:
   { pid: 30196,
     uid: 1168377759,
     gid: 74715970,
     cwd: '/Users/s0d00px/easy-reorder',
     execPath: '/Users/s0d00px/.nvm/versions/node/v6.10.3/bin/node',
     version: 'v6.10.3',
     argv:
      [ '/Users/s0d00px/.nvm/versions/node/v6.10.3/bin/node',
        '/Users/s0d00px/easy-reorder/node_modules/.bin/webpack',
        '--config',
        '/Users/s0d00px/easy-reorder/node_modules/electrode-archetype-react-app-dev/config/webpack/webpack.config
.js',
        '--colors' ],
     memoryUsage:
      { rss: 1049509888,
        heapTotal: 1011433472,
        heapUsed: 759755600,
        external: 3827420 } },
...

The error is because we need to pull the chunks from the assets at css-split-webpack-plugin. We are looking for more changes from css-split-webpack-plugin to pull from assets instead of chunks.

Few other possible solutions

  • Upgrade OptimizeCssAssetsPlugin to 3.2.0 and change the order of css split and optimize in the same pipeline
  • Update css split plugin to emit compilation phase
  • Figure out how internal archetype app optimize the css
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment