How to execute Cordova tasks
It's tempting to use the Cordova command-line interface directly, but there's a problem with this: there's no great way to ensure what you write works across multiple platforms. If you are certain you'll only work with a specific platform, you can go ahead and execute shell commands instead; but what we're going to do is a bit more flexible.
Note
The code in this section is inspired by https://github.com/kamrik/CordovaGulpTemplate.
The Cordova CLI is really just a thin wrapper around the cordova-lib
project. Everything the Cordova CLI can do, cordova-lib
does as well.
Because the Cordova project will be a build artifact, we need to be able to create a Cordova project in addition to building the project. We'll also need to emulate and run the app. To accomplish this, we'll need to create a new utility file named gulp/utils/cordova-tasks.js
. At the top we require cordova-lib
and other packages we'll need:
var cordovaLib = require("cordova-lib"), pkg = require("../../package.json"), config = require("../config"), path = require("path"), settings = require("../settings"), paths = require("../utils/paths"); var cordova = cordovaLib.cordova.raw;
Next, let's create the code to create a new Cordova project in the build
directory:
var cordovaTasks = { // CLI: cordova create ./build com.example.app app_name // --copy-from template_path create: function create() { return cordova.create(paths.makeFullPath(".", paths.DEST), pkg.cordova.id, pkg.cordova.name, { lib: { www: { url: path.join(process.cwd(), pkg.cordova.template), link: false } } }); } } module.exports = cordovaTasks;
Although it's a bit more complicated than cordova create
is on the command line, you should be able to see the parallels. The lib
object is passed simply to provide a template for the project (equivalent to --copy-from
on the command line). In our case, package.json
specifies that this should come from the blank/
directory in the code bundle of this book. If we don't do this, all our apps would be created with the sample Hello World app that Cordova installs by default.
Note
Our blank project template resides in ../blank
relative to the project root. Yours may reside elsewhere (since you're apt to reuse the same template), so package.json
can use whatever path you need. Or, you might want the template to be within your project's root; in which case, package.json
should use a path inside your project's root directory.
We won't create a task to use this just yet. We need to define several other methods to build and emulate the Cordova app. First, we need to add some additional settings to gulp/settings.js
:
var settings = { …, PLATFORM = gutil.env.platform ? gutil.env.platform :"ios", BUILD_MODE = gutil.env.mode ? gutil.env.mode :"debug", BUILD_PLATFORMS = (gutil.env.for ? gutil.env.for : "ios,android").split(","), TARGET_DEVICE = gutil.env.target ? "--target=" + gutil.env.target : "" }
Next, let's continue to add the additional methods we need to the cordovaTasks
object:
var cordovaTasks = { create: function create() { /* as above */ }, cdProject: function cdProject() { process.chdir(paths.makeFullPath("www", paths.DEST)); }, cdUp: function cdUp() { process.chdir(".."); }, // cordova plugin add ... addPlugins: function addPlugins() { cordovaTasks.cdProject(); return cordova.plugins("add", pkg.cordova.plugins) .then(cordovaTasks.cdUp); }, // cordova platform add ... addPlatforms: function addPlatforms() { cordovaTasks.cdProject(); function transformPlatform(platform) { return path.join(process.cwd(), "node_modules", "cordova-" + platform); } return cordova.platforms("add", pkg.cordova.platforms.map(transformPlatform)) .then(cordovaTasks.cdUp); }, // cordova build <platforms> --release|debug // --target=...|--device build: function build() { var target = settings.TARGET_DEVICE; cordovaTasks.cdProject(); if (!target || target === "" || target === "--target=device") { target = "--device"; } return cordova.build({ platforms: settings.BUILD_PLATFORMS, options: ["--" + settings.BUILD_MODE, target] }).then(cordovaTasks.cdUp); }, // cordova emulate ios|android --release|debug emulate: function emulate() { cordovaTasks.cdProject(); return cordova.emulate({ platforms: [settings.PLATFORM], options: ["--" + settings.BUILD_MODE, settings.TARGET_DEVICE] }).then(cordovaTasks.cdUp); }, // cordova run ios|android --release|debug run: function run() { cordovaTasks.cdProject(); return cordova.run({ platforms: [settings.PLATFORM], options: ["--" + settings.BUILD_MODE, "--device", settings.TARGET_DEVICE] }).then(cordovaTasks.cdUp); }, init: function() { return cordovaTasks.create() .then(cordovaTasks.copyConfig) .then(cordovaTasks.addPlugins) .then(cordovaTasks.addPlatforms); } };
Note
If you aren't familiar with promises, you might want to learn more about them. http://www.html5rocks.com/en/tutorials/es6/promises/ is a fantastic resource.
Most of the previous tasks should be fairly self-explanatory; they correspond directly to their Cordova CLI counterparts. A few, however, need a little more explanation:
cdProject
/cdUp
: These change the current working directory. All thecordova-lib
commands aftercreate
need to be executed from within the Cordova project directory, not our project's root directory. You should notice them in several of the tasks.addPlatforms
: The platforms are added directly from our project's dependencies, rather than from the Cordova CLI. This allows us to control the platform versions we are using. As such,addPlatforms
needs to do a little more work to specify the actual directory name of each platform.build
: This executes thecordova build
command. By default, CLI builds every platform. Since we will want to control the platforms that are built, hence we can useBUILD_PLATFORMS
to control this behavior. On iOS, the build for an emulator is different than the build for a physical device. So, we also need a way to specify this, which is whatTARGET_DEVICE
does. This will look for emulators with the name specified forTARGET_DEVICE
. But we might want to build for a physical device; in which case, we will look fordevice
(or no target specified at all) and switch over to the--device
flag which forces Cordova to build for a physical device.init
: This does the hard work of creating the Cordova project, copying the configuration file (and performing substitutions), adding plugins to the Cordova project, and then adding platforms.
Now is also a good time to mention that we can specify various settings with switches on the Gulp command line. In the earlier snippet, we're supporting the use of --platform
to specify the platform to emulate or run, --mode
to specify the build mode (debug
or release
), --for
to determine what platforms Cordova will build for, and --target
to specify the target device. The code will specify reasonable defaults if these switches aren't specified; but they also allow the developer extra control over the workflow, which is very useful. For example, we'll be able to use commands like the following:
$ gulp build --for ios,android --target device $ gulp emulate --platform ios --target iPhone-6s $ gulp run --platform ios --mode release
Next, let's write the code to actually perform various Cordova tasks. It isn't difficult, but we need to create a lot of files. Each file name in the code below is in comments:
// gulp/tasks/clean.js var paths = require("../utils/paths"), config = require("../config"), rimraf = require("rimraf"); function clean(cb) { var BUILD_PATH = paths.makeFullPath(".", paths.DEST); rimraf(BUILD_PATH, cb); } module.exports = { task: clean } // gulp/tasks/init.js var cordovaTasks = require("../utils/cordova-tasks"); module.exports = { deps: ["clean"], task: cordovaTasks.init }; // gulp/tasks/build.js var cordovaTasks = require("../utils/cordova-tasks"); module.exports = { deps: ["copy"], task: cordovaTasks.build }; // gulp/tasks/emulate.js var cordovaTasks = require("../utils/cordova-tasks"); module.exports = { deps: ["copy"], task: cordovaTasks.emulate }; // gulp/tasks/run.js var cordovaTasks = require("../utils/cordova-tasks"); module.exports = { deps: ["copy"], task: cordovaTasks.run };
There's a catch with the init
task: it will fail if anything is already in the build/
directory. As you can guess, this could easily happen; so we also created a clean
task. This uses rimraf
to delete a specified directory, which is equivalent to using rm -rf build
. We then ensured that init
depends on clean. So, whenever we execute gulp init
, the old Cordova project is removed and a new one is created for us.
Finally, note that all the build
(and other) tasks depend on copy
. This means that all our files in src/
will be copied (and transformed, if necessary) to build/
prior to executing the desired Cordova command. As you can see, our tasks are already becoming very complex, while remaining comprehensible when they are taken singularly.
This means that we can now use the following tasks in Gulp:
$ gulp init # create the cordova project; # cleaning first if needed $ gulp clean # remove the cordova project $ gulp build # copy src to build; apply # transformations; cordova build $ gulp build --mode release # do the above, but build in # release mode $ gulp build --for ios # only build for iOS $ gulp build --target=device # build device versions instead of # emulator versions $ gulp emulate --platform ios # copy src to build; apply # transformations; # cordova emulate ios $ gulp emulate --platform ios --target iPhone-6 # same as above, but open the # iPhone 6 emulator $ gulp run --platform ios # copy src to build; # apply transformations; # cordova run ios --device
Now, you're welcome to use the previous code as it is or you can use an NPM package that takes care of the cordovaTasks
portion for you. This has the benefit of drastically simplifying your Gulp configuration. We've already included this package in our package.json
file as well as our Gulp configuration. It's named cordova-tasks
and was created by the author. It shares a lot of similarities to the earlier code. To see how it works (and how much simpler the tasks become), see logology-v01/gulp
in the code package for this book.
This was one of the complex sections; so if you've come this far, take a coffee break. Next, we'll worry about managing app version numbers.