Read more

Webpack: How to split your bundles

Tobias Kraze
May 21, 2019Software engineer at makandra GmbH

To keep JavaScript sources small, it can sometimes make sense to split your webpack bundles. For example, if your website uses some large JavaScript library – say TinyMCE – which is only required on some selected pages, it makes sense to only load that library when necessary.

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

In modern webpack this is easily doable by using the asynchronous import function.

Say we have an unpoly compiler that sets up TinyMCE like this (code is somewhat simplified):

// TinyMCE as part of the main bundle!

import tinymce from 'tinymce/tinymce'

// UI
import 'tinymce/themes/silver'
import 'tinymce/skins/ui/oxide/skin.min.css'

// Plugin
import 'tinymce/plugins/autoresize'

up.compiler('[tinymce]', (element) => {
  let editor = undefined

  tinymce.init({
    target: element,
    plugins: ['autoresize'],
  }).then((editors) => {
    editor = editors[0]
  })

  return () => {
    if (editor) {
      editor.destroy()
    }
  }
})

Note that import is used as a keyword. This causes webpack to bundle TinyMCE and its dependencies into the main bundle. However, we can instead use the import function to make webpack create a separate bundle:

import('tinymce/tinymce').then(({ default: tinymce }) => {
  // this gets called after tinymce has been loaded via AJAX
})

The import function allows webpack to automatically put the imported JavaScript into a separate bundle (it uses some heuristic to decide whether to do it), automatically load that bundle via AJAX, and resolve a promise as soon as the JavaScript has been loaded.

The compiler above can thus be rewritten like this:

// TinyMCE is a separate bundle!

import 'tinymce/skins/ui/oxide/skin.min.css' // CSS is still put into the main bundle

up.compiler('[tinymce]', (element) => {
  let editor = undefined

  loadTinyMce()
  .then(tinyMce => {
    return tinymce.init({
      target: element,
      plugins: ['autoresize'],
    })
  })
  .then((editors) => {
    editor = editors[0]
  })

  function loadTinyMce() {
    return Promise.all([
      import(/* webpackChunkName: "tinymce" */ 'tinymce/tinymce'),
      // UI styles
      import(/* webpackChunkName: "tinymce" */ 'tinymce/themes/silver'),
      // Plugins
      import(/* webpackChunkName: "tinymce" */ 'tinymce/plugins/autoresize'),
    ])
    .then((imports) => {
      return imports[0].default // tinymce, i.e. the default export of the tinymce/tinymce package
    })
  }

  return () => {
    if (editor) {
      editor.destroy()
    }
  }
})

The magic webpackChunkName comments force webpack to put all those files into a bundle of the given name, overriding webpacks own heuristic.

Tobias Kraze
May 21, 2019Software engineer at makandra GmbH
Posted by Tobias Kraze to makandra dev (2019-05-21 12:09)