Update: December 12th, 2016
A lot has changed since this article was published. The bundling situation is a lot better and things have changed. While some of this article might still be applicable, please do your research and assume that parts of this article are no longer relevant.
When it comes to Aurelia, one aspect that seems to trip up and confuse first timers and even seasoned developers is bundling.
There was a point during the development of Aurelia (around mid 2015) where bundling was changing, difficult and plagued with issues.
The good news is the bundler for the most part now works without issue. Yet, people seem to be getting hung on this crucial step in deploying a single page application.
Let’s put the bundling issue to rest in this definitive guide that should answer all questions regarding bundling.
I am going to show you how to bundle your Aurelia application, how to create multiple bundles, how to use versioning numbers, wildcard globbing, using the export functionality and more.
How do I bundle my Aurelia application?
Refer to the Skeleton Navigation repository which has a functioning bundling solution using Gulp. You can see the crucial files for bundling here.
Let’s go further and explain each file and how it relates to bundling. Pay close attention to the version numbers of Jspm and System.js in the Skeleton package.json
file.
Later versions of Jspm are currently unsupported and will result in bundling not working, amongst other things. This is potential pitfall number one you need to be aware of.
At the time of writing this the supported versions are: jspm@0.16.15
and systemjs@0.19.6
– check the package.json
file in the skeleton to make sure though.
This article assumes your bundling approach is the same as the one used in the Skeleton. There are numerous ways you could do this with and without JSON configuration files. I believe the Skeleton method promotes clean separation and configuration.
First a crash course in bundling configuration
Behind the scenes the bundler uses JSPM to bundle assets, HTML templates and other aspects. The Aurelia Bundler is merely an abstraction of this and makes it easier to work with using a configuration approach using JSON files.
Before we get onto how we create our bundles, let’s delve into specifics of the bundler.
This is a JSON file that tells the bundler all of the files we want to bundle. It also allows us to specify multiple named bundles and provided configuration options.
The thing with this file is in your application, it will slightly differ. let’s break each bundle section down in the bundles file to show how everything works.
"dist/app-build": {
"includes": [
"[*.js]",
"*.html!text",
"*.css!text"
],
"options": {
"inject": true,
"minify": true,
"depCache": true,
"rev": true
}
}
“dist/app-build” — This specifies the location and filename of our bundle. Keep in mind the filename will not be exact if you set the rev
option to true. You will get a random value added to the end to be able to revision number your bundle files.
“includes” — Here is where we specify file patterns, names and locations of where we want to find files to bundle. This values accepts an array of strings. The Skeleton provides a great example of including all JS files as well as all HTML and CSS files.
Note the use of !text
this tells the bundler these files are to be loaded using the System.js text loader plugin.
In the includes section we can include actual names and locations of files or we can specify wildcard file globs that will just include everything dictated by your specified file extension.
In my Aurelia applications, I actually use a slight variation of the above to ensure all files are included:
"includes": [
"[**/*.js]",
"**/*.html!text",
"**/*.css!text"
],
As always, don’t just copy and paste. Determine what works best for you. But the above supplied includes paths should work for you if you do copy it.
A note on double brackets []: It is highly encouraged you wrap your JS glob in brackets. This prevents the bundler from bundling up any referenced dependencies in your included files. We handle bundling and importing of dependencies separately.
“options” — This is an object of options for bundling.
- inject – If set to true, your bundle is added to
config.js
and in most situations this is what you want. - minify – Use minification on your bundled file. You want minification turned on in a production environment.
- depCache – Enables dependency caching to reduce round trip latency issues from loading bundled files. Keep this on.
- rev – Add a revision number to the bundle filename. This allows you to cache bundles and break caching if the bundle is changed.
Show me how to bundle
We have explained how the file globbing works and how to configure the bundler, now let’s create a bundle configuration for a standard application.
What is considered best practice is still a point of contention. I personally create two bundles in my application, using the approach the Skeleton does. I have an application bundle and an Aurelia bundle.
Let me show you my actual export.json
configuration for a project I am working on:
{
"bundles": {
"dist/app-build": {
"includes": [
"[**/*.js]",
"**/*.html!text",
"**/*.css!text",
"cloneya",
"dexie",
"jquery",
"jquery-ui",
"medium-editor-webpack",
"moment",
"polymer/mutationobservers",
"safe-json-stringify"
],
excludes: [
"config.js" // So our wildcard globbing doesn't include this config file
],
"options": {
"inject": true,
"minify": true,
"depCache": true,
"rev": true
}
},
"dist/aurelia": {
"includes": [
"aurelia-bootstrapper",
"aurelia-event-aggregator",
"aurelia-fetch-client",
"aurelia-framework",
"aurelia-history-browser",
"aurelia-loader-default",
"aurelia-logging-console",
"aurelia-router",
"aurelia-templating-binding",
"aurelia-templating-resources",
"aurelia-templating-router",
"fetch"
],
"options": {
"inject": true,
"minify": true,
"depCache": true,
"rev": true
}
}
}
}
Now as I mentioned earlier, don’t just copy and paste this, because this is tailored to my application dependencies. Let me explain the above the best way that I can.
My app-build
bundle includes all transpiled Javascript files, all HTML templates (ViewModel views) and all CSS files. But then I also include all dependencies listed under the JSPM section of my package.json
file.
You might also notice I am using under includes
a section called excludes
. This is because I am wildcard globbing every Javascript file and I don’t want my config.js
accidentally being bundled.
I could probably be a little smarter how I go about creating this bundle, but it comes in at just a few hundred kilobytes which is pretty good.
What to bundle?
In my aurelia
bundle I am including every single Aurelia dependency being used in my application. Once again, taken from the JSPM section of my package.json
file which I will share with you below.
The last item in my Aurelia bundle is interesting, it is the fetch polyfill you need to install for IE support of Fetch. I bundle it with my Aurelia dependencies simply because it exists to make an Aurelia dependency work, so it makes sense.
You can actually choose to only bundle some dependencies and use the export functionality to export other dependencies that might be lazily loaded later on in other parts of your application.
My advice is to bundle everything and get it working, familiarise yourself and then work your way back exploring other and potentially more intelligent ways of bundling. We will touch upon this later on when we discuss exporting.
Now as promised, here is my package.json
file, well the section listing the above dependencies. See if you can spot the packages being included.
"jspm": {
"dependencies": {
"aurelia-bootstrapper": "npm:aurelia-bootstrapper@^1.0.0-beta.1.1.2",
"aurelia-event-aggregator": "npm:aurelia-event-aggregator@^1.0.0-beta.1.1.1",
"aurelia-fetch-client": "npm:aurelia-fetch-client@^1.0.0-beta.1.1.0",
"aurelia-framework": "npm:aurelia-framework@^1.0.0-beta.1.1.3",
"aurelia-history-browser": "npm:aurelia-history-browser@^1.0.0-beta.1.1.2",
"aurelia-loader-default": "npm:aurelia-loader-default@^1.0.0-beta.1.1.2",
"aurelia-logging-console": "npm:aurelia-logging-console@^1.0.0-beta.1.1.4",
"aurelia-router": "npm:aurelia-router@^1.0.0-beta.1.1.1",
"aurelia-templating-binding": "npm:aurelia-templating-binding@^1.0.0-beta.1.1.1",
"aurelia-templating-resources": "npm:aurelia-templating-resources@^1.0.0-beta.1.1.1",
"aurelia-templating-router": "npm:aurelia-templating-router@^1.0.0-beta.1.1.1",
"cloneya": "npm:cloneya@^1.0.0",
"dexie": "npm:dexie@^1.2.0",
"fetch": "github:github/fetch@^0.10.1",
"jquery": "npm:jquery@^2.2.0",
"jquery-ui": "github:components/jqueryui@^1.11.4",
"json": "github:systemjs/plugin-json@^0.1.0",
"medium-editor-webpack": "npm:medium-editor-webpack@^0.2.0",
"moment": "npm:moment@^2.11.1",
"polymer/mutationobservers": "github:polymer/mutationobservers@^0.4.2",
"safe-json-stringify": "npm:safe-json-stringify@^1.0.3",
"text": "github:systemjs/plugin-text@^0.0.3"
},
"devDependencies": {
"core-js": "npm:core-js@^2.0.3"
},
"overrides": {
"npm:core-js@2.0.3": {
"main": "client/shim.min"
}
}
}
You probably notice there are a few dependencies not being referenced above; json, text and core-js. We will do something with these later during the export step of our bundling and deployment.
In the above configuration of our bundles, we end up with two bundle files with revision numbers stamped onto them. You will also notice after running gulp bundle
that your config.js
now has a heap of lines in it. This is a bundle map which tells System.js we have bundled files and how to get them out of the bundles.
Using a variation of my above supplied bundle.json
file you should be able to create an awesome bundling configuration file. Make sure you include the Aurelia dependencies you are using in your application as some of the above listed ones you might not even use like the Router or Event Aggregator.
After you have done all of that and assuming you are using the Skeleton and its provided configuration workflow and tasks, you should be able to run: gulp bundle
to get an awesome bundled application.
Exporting Your Aurelia Application
Aka getting your Aurelia applications ready for production. This is the step where we export the required assets and bundles to deployed to our web server.
During the export step, other tasks are run in your application; transpiling, your assets are copied/built, directories cleaned and bundling is run.
At the end of the export task you will have a folder in your application directory called export
which will contain a dist
folder with your bundles, a jspm_packages
folder with various dependencies and assets (if separate from the above CSS bundling).
Once again, we have a configuration file called export.json
which lives alongside the bundle.json
file we edited earlier.
The Skeleton provides basic configuration out of the box and it looks like this:
{
"list": [
"index.html",
"config.js",
"favicon.ico",
"LICENSE",
"jspm_packages/system.js",
"jspm_packages/system-polyfills.js",
"jspm_packages/system-csp-production.js",
"styles/styles.css",
"jspm_packages/npm/font-awesome@4.5.0/css/font-awesome.min.css",
"jspm_packages/npm/font-awesome@4.5.0/fonts/*",
"jspm_packages/github/github/fetch@0.10.1.js",
"jspm_packages/github/github/fetch@0.10.1/fetch.js"
]
}
The "list"
array of strings defines files to be exported to your export
folder. The Skeleton uses Font Awesome, so it ensures that the Font Awesome stylesheet and fonts are copied to your export
folder.
The Fetch polyfill is also copied over to your export folder as well. Other basics include copying the root directory index.html
file, the System.js loader and polyfills.
Like before, mine looks a little different. I am including a couple of additional JSPM dependencies in my output.
{
"list": [
"config.js",
"favicon.ico",
"jspm_packages/github/systemjs/**",
"jspm_packages/system.js",
"jspm_packages/system.js.map",
"jspm_packages/system-polyfills.js",
"jspm_packages/system-csp-production.js",
"jspm_packages/github/github/fetch@0.10.1.js",
"jspm_packages/github/github/fetch@0.10.1/fetch.js",
"jspm_packages/npm/core-js@2.0.3/**",
"dist/app-build-*.js",
"dist/aurelia-*.js"
]
}
- “jspm_packages/github/systemjs/“** – If you use any of the System.js loaders like the Text and JSON loaders, you want this line. This copies over the loader files to your exported directory.
- “jspm_packages/npm/core-js@2.0.3/“** – I am using Core JS in my app to account for browser support issues. You might want this, you might not.
- “dist/app-build-*.js” and “dist/aurelia-*.js” – This is is an important one. It copies our bundled files over to the
export\dist
folder. It accounts for revision numbers on the end using a wildcard.
The export task is nothing more than a task that copies files and folders to your export
directory. This is the step you run last in your build process after npm install
and jspm install -y
before deploying into production.
Smart bundling and export
As promised earlier, let’s have a discussion in how we can use the export task with bundling to bring down our resulting bundle sizes by only bundling global and crucial dependencies first, letting Jspm and System.js lazily load the rest when needed.
What do I mean? In my above supplied bundle configuration, I have a heap of dependencies, but how many of them need to be included in the bundle upfront?
The Moment dependency is a great example. In my situation, I am only using it for one small part of the app. A part that the user does not hit first, they have to navigate some screens to hit the page that uses it.
So what can I do about it? Well I can remove the dependency from the bundle and then include it in my export.json
file instead. This means the Moment files get deployed in my jspm_packages
folder and when I ask for them they are loaded.
Looking in my jspm_packages\npm
folder I see moment@2.11.2.js
and a folder called moment@2.11.2
– so we reference those in our export configuration to copy them.
You would just add these two lines to the list array and they will be copied when you export for use by lazy loading.
"jspm_packages/npm/moment@2.11.2.js",
"jspm_packages/npm/moment@2.11.2/**"
The double asterisks above instruct the export task to just copy the entire folder contents to our export
folder along with the parent Javascript file as well.
FAQ & Conclusion
Hopefully the above helps you get bundling and exporting working in your Aurelia applications. If you still have questions, see below for a conclusive FAQ.
Why are my bundles really big?
If you find yourself with some seriously large bundles, exploring the use of export functionality and removing dependencies that are not immediately used upon application load will help alleviate the load. Also ensure you have Gzip compression enabled to help reduce the file size of the transferred bundle files.
What do I bundle?
This was addressed early on in this article. Go back and read through it again. My recommendation is to bundle everything, get it working and then work your way back, exploring the use of the export task to reduce bundle size (as mentioned in previous point).
Do I commit my jspm_packages
folder into source control?
No. The jspm_packages
folder is populated and maintained by Jspm. Just like you know not to commit your node_modules
folder into source control, do not do this with your jspm_packages
folder either.
Do I commit my bundled files into source control?
Absolutely no. If you do this, every time the bundle changes, you will force a heap of merge conflicts upon others who are working on the project. Working solo? Still don’t do it. Source control is for development, not for deployment. Your bundled files should be deployed to your production/staging server environment.
Do I commit my export
folder into source control?
No. This is an automatically generated folder that contains your bundles and other copied files. You would just be duplicating files you already have in source control. This is a folder to deploy from only.
Why are my bundle files not loading or being ignored?
First check that your bundles are being added to your config.js
file if the inject option is enabled. Then also make sure you bundles exist in your application dist
or export/dist
folders. If they are still not loading, it could be a bad file issues causing the bundles to break. Also ensure you are using the recommended version of Jspm and have the latest System.js loader files pulled down using: “`jspm dl-loader –latest“
Very helpful post! Thanks!
Thanks very nice to have all information at one place.
Can you extent the article to include bundling for existing MVC 4-5 applications, where you use a mix of razor and Aurelia?
This needs to be merged into the “bundling” section of the docs. Thanks!
Another great post. Might I add: shouldn’t the export.json in this sentence: ” Let me show you my actual export.json configuration for a project I am working on:” be bundles.json?
Any recommendations regarding baseURL?
Great bundling article, this really helped me get up to speed on the whole process.
What’s the best practice for serving this up statically? Some camps are using Gulp, others are using Node. Anyone have any thoughts, or maybe could point me in the right direction?
Martin,
Yes, for sure I can add a section on bundling .NET MVC applications.
Great writeup Dwayne!
I would like to know the difference between build and export.
Also, I don’t really understand the bundling and minification of .css and .html. Is the minification of css is done by aurelia-bundler or do I have to write my own gulp task for that? And what happen to html when bundling?
Thank you!
When I do ‘gulp bundle’ from the typescript asp.net 5 skeleton I get the following error, can you help?
Error on fetch for app.js at file:///C:/Users/jerem/Downloads/skeleton-navigation-1.0.0-beta.1.2.5/skeleton-navigation-1.0.0-beta.1.2.5/ts-aspnet5/src/skeleton-navigation-typescript-vs/wwwroot/dist/app.js
Error: ENOENT: no such file or directory, open ‘C:\Users\jerem\Downloads\skeleton-navigation-1.0.0-beta.1.2.5\skeleton-navigation-1.0.0-beta.1.2.5\ts-aspnet5\src\skeleton-navigation-typescript-vs\wwwroot\dist\app.js.map’
at Error (native)
I’m getting a similar error to Jay on my own app, even though the path referenced does in fact exist. Any ideas?
About the app.js.map issue, this has been fixed in the skeleton framework by replacing a gulp plugin that had problems with source maps. The new one is gulp-typescript.
You talk about what to put in an export.json file but I don’t see any such file in the skeleton applications. I see plenty of export.js files. Can you please explain where we would put that file, why it doesn’t exist now in the current projects, anything we need to do in order for it to be used, etc. ?
Hi David, probably the name of the file has changed, as it should be export.js in the build folder. https://github.com/aurelia/skeleton-navigation/blob/master/skeleton-typescript/build/export.js
I also don’t understand the idea of export. In the past I’ve ‘bundled’ my app and that’s the dist. Now I have a bundle dist and an export dist. What for? Is this for extended modularity purposes?