Vue + webpack + babel + IE11: queueMicrotask flush performance problem

We have a vue-cli project with a package.json like this:

  "dependencies": {
    "@babel/runtime": "^7.6.0",
    "axios": "^0.19.0",
    "bootstrap-vue": "^2.0.2",
    "core-js": "^3.4.1",
    "debug": "^4.1.1",
    "intersection-observer": "^0.7.0",
    "localforage": "^1.7.3",
    "lodash.throttle": "^4.1.1",
    "mobile-device-detect": "^0.2.3",
    "moment": "^2.24.0",
    "pdf.js-viewer": "^0.2.8",
    "pdfjs-dist": "^2.1.266",
    "regenerator-runtime": "^0.13.3",
    "register-service-worker": "^1.6.2",
    "v-click-outside": "^2.1.3",
    "vue": "^2.6.10",
    "vue-i18n": "^8.11.2",
    "vue-moment": "^4.0.0",
    "vue-offline": "^2.0.8",
    "vue-router": "^3.1.3",
    "vue-video-player": "^5.0.2",
    "vuedraggable": "^2.23.0",
    "vuelidate": "^0.7.4",
    "vuex": "^3.1.1"
  },
  "devDependencies": {
    "@babel/core": "^7.6.0",
    "@babel/parser": "<7.4.0",
    "@babel/plugin-proposal-optional-chaining": "^7.2.0",
    "@babel/plugin-transform-runtime": "^7.6.0",
    "@babel/preset-env": "^7.6.0",
    "@babel/preset-stage-2": "^7.0.0",
    "@babel/register": "^7.6.0",
    "@babel/standalone": "^7.4.5",
    "@vue/cli-plugin-babel": "^3.7.0",
    "@vue/cli-plugin-eslint": "^3.7.0",
    "@vue/cli-plugin-pwa": "^3.11.0",
    "@vue/cli-plugin-unit-jest": "^3.7.0",
    "@vue/cli-service": "^3.7.0",
    "@vue/eslint-config-prettier": "^4.0.1",
    "@vue/test-utils": "1.0.0-beta.29",
    "autoprefixer": "9.5.1",
    "babel-eslint": "^10.0.3",
    "babel-jest": "^24.9.0",
    "babel-loader": "^8.0.6",
    "commitizen": "^3.1.1",
    "concurrently": "^4.1.0",
    "cross-env": "5.2.0",
    "cz-conventional-changelog": "^2.1.0",
    "dotenv-flow": "^0.4.0",
    "eslint": "^5.16.0",
    "eslint-friendly-formatter": "^4.0.1",
    "eslint-loader": "^2.1.2",
    "eslint-plugin-babel": "^5.3.0",
    "eslint-plugin-prettier": "^3.1.0",
    "eslint-plugin-vue": "^5.2.2",
    "file-loader": "^3.0.1",
    "glob-all": "^3.1.0",
    "lint-staged": "^8.1.7",
    "moxios": "^0.4.0",
    "ncp": "^2.0.0",
    "nock": "^10.0.6",
    "node-sass": "^4.12.0",
    "prettier": "^1.17.1",
    "purgecss-webpack-plugin": "^1.6.0",
    "resize-observer-polyfill": "^1.5.1",
    "sass-loader": "^7.1.0",
    "vue-template-compiler": "^2.6.10",
    "webpack-bundle-analyzer": "^3.5.0",
    "workbox-webpack-plugin": "^4.3.1"
  },

babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          ie: '11',
          browsers: 'last 2 versions'
        },
        useBuiltIns: 'entry',
        corejs: { version: '3.4', proposals: true }
      }
    ]
  ],
  plugins: ['@babel/plugin-proposal-optional-chaining']
}

The problem is we draw page with search results, then the user changes the search params and the page freezes in IE11. On slower machines it takes about 5 minutes to update. In all other browsers it’s nearly instant.

We’ve run the IE11 performance tool which points to the shim for queueMicrotask in core-js and it’s flush() function:

We’ve been trying to figure out what is going on there, it’s flushing these over and over:

  • document.createElement()
  • appendChild()

We’ve tried

  • functional components
  • render components
  • freezing the data completely and use the vue-nonreative plugin
  • v-once, v-show

At this time we are almost certain it is not data related, the flush function is flush drawing events. We are getting into the area of code where we are leaving our project code and feel the problem may be between Vue + webpack + babel + IE11?

My project is using Webpack + Babel + TS (ts-loader) + Vue and it seems to render fine in IE11. You can try open this app create.piktochart.com in your IE11 for reference.

In my debugging way, usually I eliminate any setup one by one till I found the culprit.

For the browser target, we just use ‘defaults’ since it still support IE11. Have you tried modified the browser target to defaults?

Also we dont use the babel/plugin-proposal-optional-chaining yet, perhaps can try remove this as well.

After weeks of testing in IE we found the problem.

TL/DR: We froze the array of data, but passing items in the array from Parent to Child components using props caused unwanted observables. One of our Vue developers fixed the problem with provide and inject. :grinning:


This is in the “Edge Cases” section of the documentation, but should probably be more of a main line feature.

Our story:
We have a search page which returns an array of data called Tiles[]
We keep Tiles[] out of Vue’s observables by freezing it.
Each Tile component is passed a Tile object.

Here is a basic map of what a Tile Component looks like:

  • Tile
    • Header
    • Icons
    • Options
      • Option1
      • Option2
      • Option3
    • Meta
    • Description
    • Footer

We had thought that passing a Tile object down through the parent child structure of components would optimize the observables. Personally I imagined that they were passing down a reference to the same frozen object, but apparently each child component was creating it’s own observables.

The fix was to use

  provide: function() {
    return {
      tile: this.tile
    }
  }

In the parent Tile component and then

  inject: {
    tile: 'tile'
  },

in every child component. And remove “tile” from all props. A “singleton” tile object is now shared by all the children. And now the queueMicrotask has a lot less events to flush() :roll_eyes:

This npm package vue-offline will also cause the queueMicrotask flush problem, but only in Internet Explorer. in main.js we only include it if the Browser is not IE.