Overview

In part 1, we outlined a basic workflow for adding custom functionality to a WAB application. One of the weaknesses of that process is the handling of dependencies; we assumed that our logic would either be included in the widget itself, or checked into its repository, which is not ideal. A good rule of thumb is to try and keep the WAB widgets as ‘thin’ as possible, and to place the logic into more reusable components (for example, dojo dijits). This allows us to reuse the same components across multiple WAB widgets, or even outside the WAB application entirely. In addition, it is much easier to design and test our logic in isolation, outside of the Web Appbuilder. Tom Wayson and Gavin Rehkemper gave a great talk on this at the 2015 dev summit; the slides are up on github.

Once we break our logic out into dijits, the dependency management becomes more complicated. Consider the following application structure:

app
+---libs
+---widgets
+---Widget1
+---libs
+---DijitA
+---DijitB
+---Widget2
+---libs
+---DijitA
+---DijitC

We have two WAB widgets, which share some logic that is included in DijitA. Ideally, we’d like to have a structure that looks more like this:

app
+---libs
+---DijitA
+---DijitB
+---DijitC
+---widgets
+---Widget1
+---Widget2

With all of the dependencies pulled up into the application libs/ directory. If we add a bower.json file to each of our WAB widgets and declare the dependencies there, bower will navigate through the entire dependency tree, and pull them all in. The problem is, we would like to split the output into two different directories: Widgets should go in app/widgets/, and the rest of our dependencies (including dijits) should go in app/libs/. Bower only supports installing to a single directory; to separate out the WAB widgets, we need to use a custom Grunt task.

Workflow

Our task needs to do the following:

  1. Build a list of all the dependencies of our app, and install them into a directory (we’ll put them into app/libs/)
  2. Identify which of those dependencies are WAB widgets
  3. Move all of the WAB widget dependencies into app/widgets/

We can treat each step separately.

Installing Dependencies

Bower will manage all our nested dependencies automatically, as long as we configure our components correctly. Because we’ll be using a grunt task to call bower, we can leave the default bower directory alone; we don’t need to create a .bowerrc file, and bower will put everything into bower_components.

In the same way we defined our app dependencies in the bower.json file, we need specify the dependencies for our WAB widgets. The bower.json can be generated using bower init in the widget root, and the dependencies can be added. For Widget1, this will look like:

"dependencies": {
"DijitA": "https://github.com/lobsteropteryx/DijitA.git",
"DijitB": "https://github.com/lobsteropteryx/DijitB.git"
}

And for Widget2:

"dependencies": {
"DijitA": "https://github.com/lobsteropteryx/DijitA.git",
"DijitC": "https://github.com/lobsteropteryx/DijitC.git"
}

Of course, we want to make these changes in the WAB widget repositories, and not directly in our app. Once the bower configuration is set up, we no longer need to check those dependencies into source control, so they can be excluded from the individual widget repositories. Now if we go to the app level and run bower install our app/libs/ directory should look like this:

app
+---bower_components
+---DijitA
+---DijitB
+---DijitC
+---Widget1
+---Widget2
+---libs
+---widgets

So far, so good! Now we just need to move the WAB widgets into the app/widgets/ directory, and everything else into app/libs. To do this, we need to figure out which components are WAB widgets.

Identifying WAB Widgets

There are a few different ways we could distinguish a WAB widget from a normal dojo dijit; we could adopt a set of naming conventions, prefixing the names of all of our WAB widgets with a string like “WAB”. Alternately, we could look for a subset of files that identifies a component as WAB widget. We have opted for the latter option, making the assumption that any component directory that contains both Widget.js and manifest.json is a WAB widget. This logic has been rolled into a small node module, here. The module contains a single public method, isWidget, which will return true if both of the identifying files exist.

Moving WAB Widgets to the proper directory

Now that we have a way to distinguish WAB widgets from other dependencies, we just need to move each package from bower_components into either libs or widgets. As it turns out, the grunt-bower-task plugin does almost everything we want.

To use the plugin and module, we first need to install them. From the application directory, run:

npm install grunt-bower-task --save-dev
npm install wabid --save-dev

To call our process, we need to set up a grunt task; if you don’t already have Grunt installed, you can do a quick:

npm install -g grunt-cli
npm install grunt --save-dev

Then create Gruntfile.js in the root of the application, and add the following:

grunt.loadNpmTasks('grunt-bower-wab-task');

grunt.initConfig({
bower: {
install: {
options: {
targetDir: './',
cleanBowerDir: true,
layout: function (type, component, source) {
var path = '', subDirectory = '';
if (wabid.isWidget('bower_components/' + component)) {
subDirectory = ' is a widget';
path = 'widgets/' + component;
} else {
subDirectory = ' is a lib';
path = 'libs/' + component;
}
console.log(component + subDirectory);
return path;
}
}
}
},
grunt.registerTask('bower-deps', ['bower:install']);

 

Here we’re using a custom layout function to move our dependencies around. By default, bower will put everything into the bower_components directory; then we will move each component into either libs/ or widgets/. Once this process is completed, we can delete bower_components by setting cleanBowerDir to true. See the grunt-bower-task readme for more details.

Once we have the grunt task in place, just open a terminal, navigate to your app directory, and run:

grunt bower-deps

We should see some output like this:

Running "bower:install" (bower) task
>> Installed bower packages
>> Widget1 is a widget
>> widget2 is a widget
>> DijitA is a lib
>> DijitB is a lib
>> DijitC is a lib
>> Copied packages to c:developapp

If we look at our directory structure now, we should see the following:

app
+---libs
+---DijitA
+---DijitB
+---DijitC
+---widgets
+---Widget1
+---Widget2

This is just what we want!

Updates

Now that we have a Grunt task in place, it’s easy to pull in changes from different members of the development team, across any of the components. We can also use the task in our existing continuous integration processes, to make sure the app has the most up-to-date versions of it’s components. We still check in the libs/ and widgets/ directories into our application repository, but depending on the application, you can add them to your .gitignore file if need be.

Conclusion

Although the Web AppBuilder is primarily aimed at smaller teams, applications can be scaled up using common development tools as teams sizes and complexity grow. The key point is that an app created using the Web AppBuilder is still just a web application, and all of the same workflows, tools, and best practices still apply.