Web applications can be deployed to many environments, including desktops, tablets, and mobile devices. We can even deploy web applications natively using a wrapper such as Apache Cordova to gain access to device features such as GPS, battery, and accelerometer data. However, it is not always optimal to package our application into a universal layer file, as we may be sending Cordova-specific code to our web users and packaging web-specific code with our native application. Luckily, Dojo provides us with tools to limit or exclude platform-specific code from our deployments by using dojo/has
feature detection and staticHasFeatures
within our build profiles.
Feature Detection
Dojo’s feature testing and detection module is dojo/has
. It includes a number of built-in tests and provides the ability to register custom tests. When creating a new Dojo 1.x application, it is recommended that you create a file within your package named has.js
, which gives us a place to define custom feature detection tests using has.add()
. These tests are application-specific and can be used to test the parameters by which we define our application. For example, if we need tests to distinguish between web and native environments, we add has
tests for those environments:
// app/has.js
define([
'dojo/has'
], function (has) {
// test if we are running in Apache Cordova
has.add('native', window.device ? window.device.cordova : false);
// add a web feature test
has.add('web', !has('native'));
return has;
});
Now, we can conditionally execute code based on these tests.
// app/main.js
define([
'app/has'
], function (has) {
if (has('native')) {
// execute native-specific code
} else if (has('web')) {
// execute web-specfic code
}
});
Targeted builds
Using the Dojo build system and multiple profiles, we can create builds for each target platform that contain only the code for that platform. This is done using feature detection, the staticHasFeatures
profile setting, and dead code removal, provided by the Google Closure Compiler.
A Dojo build profile is a JavaScript file that defines how the application should be built and optimized. Creating a separate build profile for each target platform allows us to customize how the application is built and what is included in the deployable file. When creating a custom profile, the main configuration options we need to set are releaseDir
, layers
, and staticHasFeatures
.
// profiles/app-native.profile.js
var profile = {
basePath: '../src/',
action: 'release',
releaseDir: 'dist/native',
layers: {
'dojo/dojo': {
boot: true,
customBase: true,
include: [
'app',
// additional dependencies required by the native application
]
}
},
staticHasFeatures: {
'native': 1,
'web': 0,
// additional features
}
// ...
};
We specify the releaseDir
as a directory within dist/
. Each platform-specific build will be in its own directory: dist/native
for the native deployment, and dist/web
for the web deployment.
A layer is a JavaScript file that is made up of a number of combined modules. We are creating a layer called dojo/dojo
, which will replace the code in dojo/dojo.js
with our optimized layer code. Setting the customBase
and boot
properties to true
ensures the layer contains only the Dojo loader and its base set of utilities. Naming the layer file dojo/dojo
ensures that the layer file will be loaded in place of Dojo on the page. This is extremely handy as we do not need to change how our application is loaded between development and production.
<-- DEVELOPMENT: dojo is loaded -->
<-- PRODUCTION: custom layer file is loaded -->
<script data-dojo-config="deps:['app']" src="dojo/dojo.js"></script>
From there, we can set the include
property of the layer to include our application and any target-specific modules necessary, thus creating a single file containing the loader and all of our application code, making the deployment as small as possible.
It is possible to force the result of specific feature detection tests at build time by defining them in staticHasFeatures
. Here, we can force native
to be true
and desktop
to be false
during the build. The code within the application that uses this test will be changed to the static values we have set.
// app/main.js
define([
'app/has'
], function (has) {
if ( 1 ) {
// execute native-specific code
} else if ( 0 ) {
// execute web-specfic code
}
});
From here, the Closure Compiler is intelligent enough to recognize that the web-specific code branch in the above example will never be reached, so it is removed from the build. This process is called dead path removal, and it removes unreachable code from our native deployment. The same process can be used to create a web-specific version of our application. As a result of the targeted builds, we are left with two deployable versions of our application that target the native and web environments and are each as small as possible. The web version will be located at dist/web
and the native version at dist/native
. We can deploy each version to their respective environments and be sure that the deployed code base is as small as possible.
Dead code removal in other projects
The Dojo build system was several years ahead of its time when first released. Today, other tools such as webpack can provide similar and even greater flexibility for optimizing your production-ready code. By default, Dojo 2 will leverage webpack for its code optimization strategies.