Performing substitutions
Many times, we need to convert certain keywords in a Gulp stream into some other values. A simple example is to transform {{{VERSION}}}
your app's version number—for example, into 1.23.4456
. Doing this is pretty simple, but it opens up a large number of possibilities.
To do this, we'll use the gulp-replace-task
plugin. This plugin will replace all the instances of a particular regular expression with a replacement value. These expressions can become very complex; but in our case, we'll keep them simple.
We'll only need to support substitutions in our code files, so let's create a new task that is designed to copy our code files and apply any necessary substitutions along the way. We'll call it gulp/tasks/copy-code.js
.The file should start as follows:
var gulp = require("gulp"), replace = require("gulp-replace-task"), concat = require("gulp-concat"), pkg = require("../../package.json"), config = require("../config"), paths = require("../utils/paths");
Next, we need to define a method that will perform substitutions on the input streams. Remember, these will be the files matched by the pattern provided to gulp.src()
:
function performSubstitutions() { return replace({ patterns: [ { match: /{{{VERSION}}}/g, replacement: pkg.version } ] }); }
Next, let's define another configuration setting that specifies the code files that do need substitutions and where they should be stored. In gulp/config.js
, add a code
section to the config.assets
object, like this:
assets: { copy: [ … ], code: {src: "www/js/app/**/*.js", dest: "www/js/app"} }, …
Next, we need to define the code that will copy the files specified by config.assets.code
to the appropriate destination. This will be added to gulp/tasks/copy-code.js
, and it should look like this:
function copyCode() { return gulp.src([paths.makeFullPath(config.assets.code.src, paths.SRC)]) .pipe(performSubstitutions()) .pipe(concat("app.js")) .pipe(gulp.dest(paths.makeFullPath( config.assets.code.dest, paths.DEST))); } module.exports = { task: copyCode }
The copyCode
method is pretty simple to follow. First, all the JavaScript files are located using the configuration we've specified. These are all passed through performSubstitutions()
. The results of the substitutions are then packaged together in a neat little bundle with concat
. So, even if we have multiple JavaScript files, they will all be packaged into a single file (app.js
).
Note
You don't have to concatenate your files if you don't want to. When you have multiple JavaScript files, however, it means that you have to include each one in your index.html
file. Whereas if you bundle them into a single file, you reduce the number of script
tags you have in your index.html
file.
To test these tasks, we can create two simple files. The first should be placed in src/www/
and named index.html
:
<!DOCTYPE html> <html> <head> <script src="cordova.js" type="text/javascript"></script> <script src="js/app/app.js" type="text/javascript"></script> </head> <body> <p>Hello!</p> <p id="demo"></p> </body> </html>
The second file should be in src/www/js/app/
and named index.js
:
document.getElementById("demo").textContent = "{{{VERSION}}}";
The JavaScript file itself is very simple, obviously. The idea is simply to prove that our Gulp tasks work. If you execute gulp copy-assets
, you'll find that index.html
has been copied from src/www/
to build/www/
. Likewise, if you execute gulp copy-code
, you'll find that index.js
has been copied from src/www/js/app/
to build/www/js/app/
and renamed to app.js
. If you open the latter file in an editor, you'll also see that {{{VERSION}}}
has been replaced with 1.0.0
(which came from package.json
).
As you may recall, we indicated earlier in this chapter that we still need a config.xml
file. This is true, but we've specified everything we need in package.json
. Wouldn't it be great to generate a valid config.xml
file from a template? This means that we need more substitutions and a proper template.
Let's define our template first. This should be in src/config.xml
(see the code package for the entire file):
<?xml version='1.0' encoding='utf-8'?> <widget id="{{{ID}}}" version="{{{VERSION}}}" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:gap="http://phonegap.com/ns/1.0"> <name>{{{NAME}}}</name> <description> {{{DESCRIPTION}}} </description> <author email="{{{AUTHOR.EMAIL}}}"href="{{{AUTHOR.SITE}}}"> {{{AUTHOR.NAME}}} </author> <content src="index.html" /> {{{PREFS}}} <access origin="*" /> … </widget>
Notice that there are a lot of substitution variables in the preceding code. Most of them are pretty simple: {{{ID}}}
, {{{NAME}}}
, and so on. One of them is a little more complex: {{{PREFS}}}
. This will need to render our simpler list of preferences in package.json
into the XML format required by Cordova.
Let's create a new utility file named gulp/utils/performSubstitutions.js
with a new version of the performSubstitutions
method. We'll need this new version in two tasks, hence the need to split it out into its own file. The new file should look like this:
var pkg = require("../../package.json"), replace = require("gulp-replace-task"); function performSubstitutions() { function transformCordovaPrefs() { var template = '<preference name="{{{NAME}}}" ' + 'value="{{{VALUE}}}" />'; if (pkg.cordova && pkg.cordova.preferences instanceof Object) { return Object.keys(pkg.cordova.preferences).map( function(prefName) { var str = template.replace(/{{{NAME}}}/g, prefName) .replace(/{{{VALUE}}}/g, pkg.cordova.preferences[prefName]); return str; }).join("\n "); } } return replace({ patterns: [ { match: /{{{VERSION}}}/g, replacement: pkg.version }, { match: /{{{ID}}}/g, replacement: pkg.cordova.id }, { match: /{{{NAME}}}/g, replacement: pkg.cordova.name }, { match: /{{{DESCRIPTION}}}/g, replacement: pkg.cordova.description }, { match: /{{{AUTHOR.NAME}}}/g, replacement: pkg.cordova.author.name }, { match: /{{{AUTHOR.EMAIL}}}/g, replacement: pkg.cordova.author.email }, { match: /{{{AUTHOR.SITE}}}/g, replacement: pkg.cordova.author.site }, { match: /{{{PREFS}}}/g, replacement: transformCordovaPrefs } ] }); } module.exports = performSubstitutions;
Next, we'll need to edit gulp/copy-code.js
to include this new version. Remove the performSubstitutions
method from this file first, and then add the following require
to the top of the file:
var …, performSubstitutions = require("../utils/performSubstitutions");
Finally, let's add another task that can copy the configuration file. We'll call it gulp/tasks/copy-config.js
, and it should look like this:
var gulp = require("gulp"), performSubstitutions = require("../utils/performSubstitutions"), config = require("../config"), paths = require("../utils/paths"); function copyConfig() { return gulp.src([paths.makeFullPath("config.xml", paths.SRC)]) .pipe(performSubstitutions()) .pipe(gulp.dest(paths.makeFullPath(".", paths.DEST))); } module.exports = { task: copyConfig }
Of course, we don't want to have to run lots of inpidual tasks just to copy files. So let's create a simple task that depends upon these three tasks. By doing so, Gulp will run all of these tasks with a single command.
Let's create the new task with the name gulp/tasks/copy.js
. The file should contain the following:
module.exports = { deps: ["copy-assets", "copy-config", "copy-code"], }
This is the shortest task so far. All it does is list the other three tasks as dependencies. This means that they will be executed prior to copy
. Since copy
doesn't contain any additional code, it's just a simple way to execute several tasks at once. If you execute gulp copy
, you'll find that you have a new config.xml
file under build
. It should look a lot like the following:
<?xml version='1.0' encoding='utf-8'?> <widget id="com.packtpub.logologyv1" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:gap="http://phonegap.com/ns/1.0"> <name>Logology</name> <description> Dictionary application for Mastering PhoneGap book </description> <author email="kerrishotts@gmail.com" href="http://www.photokandy.com"> Kerri Shotts </author> <content src="index.html" /> <preference name="permissions" value="none" /> <preference name="fullscreen" value="false" /> <preference name="orientation" value="default" /> … <access origin="*" /> </widget>
Now that you've mastered the method of performing substitutions, you will learn how to interact with Cordova programmatically in the next section.