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.