When deploying a JS application to production, bundling is an important practice performance-wise. By merging resources, mostly JS code, HTML templates, and CSS sheets, into a single file, we can drastically reduce the number of HTTP calls the browser has to make to serve the application.
The CLI always bundles the application it runs, even in a development environment. This makes deploying an application to a server pretty simple; it's only a matter of building it and then copying a bunch of files over.
But then comes the versioning problem. When deploying a new version of our application, if the bundles keep the same names, cached bundles may not get refreshed, causing users to run an outdated version of our application. How do we deal with this?
In this chapter, we'll see how to customize the bundling of our contact-management application. We'll also see how to leverage the CLI's revision feature to version our bundles, so we can benefit from HTTP caching as much as possible. Finally, we'll add a new build task to our project in order to facilitate deployment.
By default, a project created using the CLI contains two bundles: a first one named vendor-bundle.js
, which contains all external libraries used by the application, and a second named app-bundle.js
, which contains the application itself.
The bundles are configured in the aurelia_project/aurelia.json
file, under the build section. Here's how it looks in a typical application:
"bundles": [ { "name": "app-bundle.js", "source": [ "[**/*.js]", "**/*.{css,html}" ] }, { "name": "vendor-bundle.js", "prepend": [ "node_modules/bluebird/js/browser/bluebird.core.js", "scripts/require.js" ], "dependencies": [ "aurelia-binding", "aurelia-bootstrapper", "aurelia-dependency-injection", "aurelia-framework", //Omitted snippet... ] } ]
Each bundle has a unique name, and must define its content, which can be sourced from the application and external dependencies. Typically, the app-bundle
includes all JS, HTML, and CSS from the application's sources, while the vendor-bundle
includes external dependencies.
This is generally the best configuration for small to medium applications. The external dependencies, which commonly don't change very often, are grouped in their own bundle, so the users won't have to download those dependencies every time a new version of the application is released. In most cases, they'll only have to download the new app-bundle
.
However, if for some reason you want your application to fit in a single bundle, including both the application itself and its dependencies, it is fairly easy to do so. You simply need to define a single bundle, which contains both the application sources and the external dependencies:
aurelia_project/aurelia.json
"bundles": [ { "name": "app-bundle.js", "prepend": [ "node_modules/bluebird/js/browser/bluebird.core.js", "scripts/require.js" ], "source": [ "[**/*.js]", "**/*.{css,html}" ], "dependencies": [ "aurelia-binding", "aurelia-bootstrapper", //Omitted snippet... ] } ]
With the entry point of an Aurelia application being the aurelia-bootstrapper
library, the entry-point bundle must be the one containing the bootstrapper
. By default, this is the vendor-bundle
. If you change the entry-point bundle here it becomes the app-bundle
; you need to change a couple of things.
First, still in aurelia_project/aurelia.json
and under build
, the loader's configTarget
property must be changed for the new entry-point bundle:
aurelia_project/aurelia.json
"loader": {
"type": "require",
"configTarget": "app-bundle.js",
// Omitted snippet...
},
Additionally, the main script
tag of index.html
must also reference the new entry-point bundle:
index.html
<!-- Omitted snippet... -->
<body aurelia-app="main">
<script src="scriptsapp-bundle.js"
data-main="aurelia-bootstrapper"></script>
</body>
<!-- Omitted snippet... -->
If you run the application at this point, you will see that a single bundle is generated, and that the browser loads only this bundle when launching the application.
In some scenarios, having the whole application source in a single app-bundle
is suboptimal. We could easily imagine an application built on heavily segregated user stories. Users, depending on their role, only use specific parts of this application.
Such an application could be split into multiple smaller bundles, one for each role-related section. This way, users would not download bundles for sections of the application they never use.
The snippets in the following section are excerpts from the chapter-10/samples/ app-with-home
sample
from the book's assets.
Let's try this out by moving the contacts
feature of our application into its own bundle. To do so, we first need to exclude everything within the contacts
directory from the app-bundle
:
aurelia_project/aurelia.json
{
"name": "app-bundle.js",
"source": {
"include": [
"[**/*.js]",
"**/*.{css,html}"
],
"exclude": [
"**/contacts/**/*"
]
}
}
The source
property supports either an array of glob patterns, or an object with an include
and optional exclude
properties, both expected to contain an array of glob patterns.
Here, we simply move the previous value of source
down to the include
property, and add an exclude
property matching everything in the contacts
directory.
Next, we need to define the new bundle:
aurelia_project/aurelia.json
{
"name": "app-bundle.js",
//Omitted snippet...
},
{
"name": "contacts-bundle.js",
"source": [
"[**/contacts/**/*.js]",
"**/contacts/**/*.{css,html}"
]
},
This new bundle, named contacts-bundle.js
, will include all JS, HTML, and CSS files within the contacts
directory.
If you run the application at this point, you should first see that the scripts
directory now contains three bundles: app-bundle.js
, contacts-bundle.js
, and vendor-bundle.js
. If you open the application in a browser and check the debug console, you should see that when loading the application, the browser first loads the vendor-bundle
, then the app-bundle
, and finally, the contacts-bundle
.
The contact-bundle
is loaded when the main configure
function loads the contacts
feature during the application startup process. This is one of the limitations of Aurelia's features: it can be difficult to isolate a feature in a distinct bundle. Indeed, a feature's index
file, along with all its dependencies, should be bundled in the app-bundle
. Bundling it separately is useless, since this other bundle will be loaded upon startup anyway. However, everything else in the feature can be bundled separately.
In our application, even if you make this change, the contacts-bundle
would still get loaded when the application starts, because the app
component automatically redirects the user to the contacts default route, which is the contacts list.
If you add a home component as the default route in the application and you make sure this home component is included in the app-bundle
, you should see that the contacts-bundle
is loaded only when you navigate to it.
18.222.250.157