Using Bower for Custom Builds

Using Bower (A package manager for the front-end web development) is another option for developers of the JavaScript API to generate custom builds of the ArcGIS API for JavaScript. You would use the Bower download of the ArcGIS API for JavaScript if you wanted to create local builds of your application. This is similar to what you can do with the JavaScript Optimizer but locally.

Note: See the prerequisites section on configuring your system before proceeding. If you system is already configured with Git, Node.js, Grunt.js, Bower and Java you may proceed to the Getting Started section.

Prerequisites

  1. Node.js (includes npm, the node package manager)
  2. git
  3. Grunt - The JavaScript Task Runner
  4. Bower - A package manager for the front-end web development
  5. Java 7 or greater - required by Closure Compiler (Dojo build)

To check if these are installed:

node --version
git --version
grunt --version
bower --version
java -version

Tip: if you have issues with ssh on your machine, which is common on Windows or due to firewall settings, you can tell git to use https over ssh.

git config --global url."https://github.com".insteadOf git://github.com

Getting Started

Now that your system is configured, it is time to make a decision on whether you are going to use the Dojo Toolkit or RequireJS.

Note: There are some limitations in choosing the RequireJS AMD loader over the Dojo AMD loader. The RequireJS loader is missing some methods that might be required at runtime, one of which is a cross-domain loader. You can view the provided Gruntfile for information on some build limitations. These mainly have to do with limitations in how the r.js optimizer performs builds.

Clone the jsapi-resources repository (code below) to your local machine or download the repository to your local machine.

git clone https://github.com/Esri/jsapi-resources.git

Understanding Bower and npm

Understanding Bower

Bower requires Node.js so they naturally work well together. In the Generate a custom build (Dojo) and Generate a custom build (RequireJS) sections, you will be asked to issue a command npm install. You can read more about npm-scripts, but essentially this tells node to download any dependencies and/or devDependencies listed in the package.json file. See the package.json file in the dojo or RequireJS samples for more information.

The npm-scripts field in the package.json file is special in the fact that some of the named scripts such as install have the option to run special pre and post hooks. This is important because it allows you to perform operations before or after a given command. In our instance, we want to run the bower install command on our preinstall hook.

When using Bower, you can predefine what Bower packages you want to install via a file called bower.json. Take a look at the example below:

{
  "name": "arcgis-js-api-sample-app",
  "version": "1.0.0",
  "dependencies": {
    "esri": "arcgis-js-api#4.4.0"
  },
  "resolutions": {
    "dojo": "v1.10.4/esri-3.14.0"
  }
}

What this file is saying is that when you do bower install it will download version 4.4 of the ArcGIS API for JavaScript.

The default folder all Bower dependencies are downloaded to is called bower_components. Notice how we have a dependency called esri that is pointing the arcgis-js-api Bower package. This means it will name the folder esri that the Bower package is downloaded, so it will look like bower_components/esri.

The ArcGIS JS API package has some of its own predefined dependencies that will also be downloaded. This is why there is a resolution for the dojo dependency to use our version. You can see a list of the dependencies in the README for the Bower package. When everything is downloaded, you should have a folder structure that looks like this.

bower_components
├── dgrid
├── dijit
├── dojo
├── dojox
├── dstore
├── esri
├── moment
└── util

When you develop your application, you can set up your dojoConfig to point to these folders.

var dojoConfig = {
  baseUrl: '.',
  packages: [
    {
      name: 'dgrid',
      location: 'bower_components/dgrid',
    },
    {
      name: 'dijit',
      location: 'bower_components/dijit',
    },
    {
      name: 'dojo',
      location: 'bower_components/dojo',
    },
    {
      name: 'dojox',
      location: 'bower_components/dojox',
    },
    {
      name: 'dstore',
      location: 'bower_components/dstore',
    },
    {
      name: 'esri',
      location: 'bower_components/esri',
    }
  ]
};

Now you have full access to the ArcGIS API for JavaScript and its dependencies.

You could take this one step further and have the Bower packages installed into a different folder. Let's assume you are writing your application in a src/app folder. You can have the Bower packages downloaded into the src folder by default by creating a .bowerrc file in the root of your application that looks like this.

{
  "directory":"src/"
}

When you do bower install it will download all the same packages into the src directory. This allows you to simplify your dojoConfig inside your src folder.

var dojoConfig = {
  baseUrl: '.',
  packages: [
    'app',
    'dijit',
    'dojo',
    'dojox',
    'dstore',
    'dgrid',
    'esri'
  ]
};

This will make it easier to do local custom builds of your application.

Understanding npm

Since Bower is run under the preinstall hook of the npm-scripts it precedes the install script. So when you issue the command npm install, preinstall runs first which is actually the command bower install. Then npm install runs looking for dependencies listed in the package.json file. Npm will then download and install any dependencies listed in the dependencies and/or devDependencies section in the package.json file. Finally, the npm install sequence is where Grunt and the supporting plugins grunt-contrib-clean and grunt-contrib-copy are installed.

See the package.json file in the Dojo or RequireJS samples for more information.

Generate a custom build (Dojo)

For both options (clone or download), you need to copy the source files from https://github.com/Esri/jsapi-resources/tree/master/4.x/bower/dojo to a new directory. In the following steps, we'll name the new directory esrijs-bower-dojo.

The contents of your directory should appear as follows:

esrijs-bower-dojo
├── .bowerrc
├── Gruntfile.js
├── README.md
├── bower.json
├── build.profile.js
├── package.json
└── src
    ├── app
    │   ├── main.js
    │   ├── package.js
    │   └── package.json
    ├── built.html
    └── index.html
  1. Open a terminal window or shell command window
  2. Change directory to esrijs-bower-dojo
    • In the terminal window run: npm install
    • The first time this is run it may take a while
    • The arcgis-js-api will be downloaded by bower
    • Subsequent times when bower runs, the arcgis-js-api repository will be cached by bower and take less time to download
    • dependencies and/or devDependencies will download via npm (Grunt plugins including grunt-dojo)
  3. In the terminal window type npm run build and press return
  4. The command will run the dojo build system against the ArcGIS API for JavaScript library and your application
  5. Note: (This will delete the built application!). In the terminal window type npm run clean and press return to delete the built application

Dojo technical details

The Dojo build system accepts a build configuration profile. A sample build profile is available in the Bower dojo sample in the jsapi-resources repository on GitHub. The key is to define your build packages, which you can learn more about in this AMD loader tutorial. In addition to packages, you should understand what a layer file is, which is essentially a single file that is composed of many modules and their dependencies.

layers: {
  'dojo/dojo': {
    include: [
      'app/main', // Your application entry point
      'esri/widgets/Attribution' // a dynamically loaded module
    ]
  }
}

This tells the Dojo build system to build the dojo/dojo.js file and to include other files into it. This will get you an optimized single-file that you can use to deploy your application. Please see the sample build profile for more details.

Generate a custom build (RequireJS)

For both options (clone or download), you need to copy the source files from https://github.com/Esri/jsapi-resources/tree/master/4.x/bower/requirejs to your esrijs-bower-requirejs directory.

The contents of your directory should appear as follows:

esrijs-bower-requirejs
├── .bowerrc
├── Gruntfile.js
├── README.md
├── bower.json
├── package.json
└── src
    ├── app
    │   └── main.js
    ├── built.html
    └── index.html
  1. Open a terminal window or shell command window
  2. Change directory to esrijs-bower-requirejs
  3. In the terminal window run: npm install
    • The first time this is run it may take a while
    • The arcgis-js-api will be downloaded by bower
    • Subsequent times when bower runs, the arcgis-js-api repository will be cached by bower and take less time to download
    • dependencies and/or devDependencies will download via npm (Grunt plugins including grunt-contrib-requirejs and uglify-js
  4. In the terminal window type npm run build and press return
  5. The command will run the requirejs build system against the ArcGIS API for JavaScript library and your application
  6. Note: (This will delete the built application!). In the terminal window type npm run clean and press return to delete the built application

RequireJS technical details: The RequireJS Optimizer has some limitations when trying to build some of the Esri and Dojo modules. What this means is that you can get pretty close to a single-file release; however, some modules will still need to be dynamically loaded.

Many of the issues related to using the RequireJS Optimizer have to do with how it handles loader plugins during the build process.

An example of a loader plugin is the dojo/has plugin, that can do conditional loading of modules. The RequireJS Optimizer will attempt to execute these plugins as it sees them in a module.

However, the RequireJS Optimizer by default runs in a Node environment and does not have JavaScript host objects such as document that may be used by a loader plugin. The Dojo build tools work around this issue. You can setup the build configuration to work around most of these use cases. You would need to tell the RequireJS Optimizer to ignore modules that will cause an error with loader plugins.

paths: {
  'dojox/gfx': 'empty'
}

This is the syntax you would use to tell the RequireJS Optimizer to not load the module during the build process. Some Dojo plugins have RequireJS counterparts that can be swapped out during the build process, so you can do something like this.

paths: {
  'dojo/text': 'text/text'
}

This syntax tells the RequireJS Optimizer to load the text/text module in place of the dojo/text module allowing your build to complete.

Attempting to build many modules into a single file may require a little trial and error. Some modules will attempt to dynamically load modules when they are needed, which means they are not picked up by the build process. If you notice some files dynamically loaded after you do a build, you can try and force them into your built file.

include: [
  'esri/widgets/Attribution'
]

This syntax will tell the RequireJS Optimizer to add this module and its dependencies to your output file. You can find more details in the RequireJS build configuration file.