If you're reading this post, you probably faced a problem many single page applications have, too much JavaScript! You minified and bundled all your code with a tool of your choice, but somehow still end up with too much being loaded at your first page.
Many SPAs are used by people on mobile connections, which often aren't that fast. And you probably read that users will abandon a site if it loads too long. But how to solve this, without sacrificing features?
One way is dynamic, or on-demand loading of JavaScript.
The idea is, you just load the code that is needed for the current URL the user opened. Often your login page is much smaller as the rest of your pages. Or you have a simple landing page that just shows some nicely styled text with a few small images, things that can be downloaded and rendered very fast.
Then, when the user knows what he's getting you let them download the rest, or maybe download it asynchronously while they read something that is already rendered.
Anyway, performance is UX, better UX means happier users. The fastest bit is the bit not sent, so lets dive into one solution for this problem: Webpack 2
Webpack 2 offers a way to automatically to bundle all your code into one file, like every bundler does. It also offers a way to bundle it in multiple files, that are needed at the same time, almost out of the box.
So how does this work? Really simple!
You just have to use the (dynamic) import
function in your code instead the regular ES2015 imports
that get evaluated statically.
Instead of writing:
import home from './pages/home'
import about from './pages/about'
const page = document.location.hash.substr(1)
if (page === 'home') home.render()
if (page === 'about ') about .render()
Which will put the two page files into one bundle.
You would write:
const page = document.location.hash.substr(1)
import('./pages/' + page)
.then(page => page.render())
Which will generate a bundle for all the possible files with that dynamic pattern. In this example 2, for home.js
and about.js
That's almost it!
What's missing?
The paths on the client need to be right. Webpack will generate bundles for every file that is dynamically imported and load those bundle files at runtime, instead of the ones in your source directory.
For example, your entry JS file is src/index.js
but your output (bundle) file is dist/application.js
. You would import the latter with:
<script src="dist/application.js"></script>
if your HTML file in one directory above dist/
. The other bundle files will be prefixed with numbers. For example: 0.application.js
. These will be loaded in the browser dynamically.
Webpack will throw an error in the browser, that it cannot load chunk 0
or something. This is because, it tries to load the other files relative to the your HTML file and not relative to the first JavaScript file you loaded via the script tag.
To migrate this, you have to add a publicPath
to your Webpack config:
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'application.js',
path: path.resolve(__dirname, 'dist'),
publicPath: 'dist/',
},
}
Now, when you load the built application.js
in your HTML, all the other generated bundle files will be loaded with dist/
in their path. Like dist/0.appliaction.js
instead of 0.application.js
I also created a repository with a simple complete example.
Edit: System.import() is deprecated.