Using Browserify
Very few applications require completely custom code. Most applications can benefit from modularization and code reuse, and it's important to use modularization to your benefit so that you can build apps more efficiently.
There's a syndrome common to many developers (and even some companies): the not-invented-here syndrome. The afflicted developer (or company) is extremely hesitant to use code, algorithms, or any other assets that they themselves didn't create. It's easy to see where this syndrome comes from—it ensures that one can't be held liable for third-party code, one can't be sued for improperly licensing assets, and so on; but it also means that one must continually reinvent the wheel. This invariably results in buggier, less secure algorithms, as everyone is always attempting to build their apps completely from scratch.
If you've worked through other books on mobile application development, chances are you've already been introduced to modularizing your own code. Rather than having one large monolithic JavaScript file that contains everything, you can separate your code so that it is easier to understand (and so that each concern is clearly separated).
It's extremely useful, however, to extend this to the JavaScript world at large; there are a large number of open source modules with permissive licenses that you can base your application upon. This means that one has to worry about properly abiding with licensing requirements, but it also enables you to assemble well-tested building blocks upon which your application is built.
Browserify (http://browserify.org) is a tool that lets you use the huge repository of Node.js packages. Many of these packages work fine in the browser as-is, but Node.js does have a different global environment. Browserify shims this Node.js environment into the browser, which means that just about any Node.js package can work within the browser environment. Most packages will indicate if the browser isn't supported in the documentation.
Note
Browserify is also used to package all your app's dependencies into one or more large files. This is useful in production, when you might want to supply one minified file containing all your code.
Furthermore, since we can use Node.js packages, we may as well use the Node.js package manager, npm
. We've been using this already to install development dependencies, such as Cordova, Gulp, and more. But we can use npm
to manage our application's dependencies as well.
You should take a moment and browse https://www.npmjs.com. It should be apparent that there's a lot there. The ability to leverage npm
as a resource can greatly speed your own development.
Modifying our Gulp configuration
Unfortunately, in order to use Browserify and ES2015 together, we need to modify our Gulp configuration. Whereas, before we call babel
directly to convert our code, we have to get a little fancy; browserify
has to be called and browserify
will call babel
on its own. It's a subtle distinction, perhaps, but it is an important one.
First, let's install our new development dependencies:
$ npm --save-dev install vinyl-source-stream@1.1.0 vinyl-buffer@1.1.0 browserify@11.2.0 babelify@6.3.0 $ npm --save install babel@5.8.23 $ npm --save-dev uninstall gulp-babel
Note
The first two modules that we are installing will allow us to convert the output from browserify
into something that Gulp can handle.
Then, we need to add some configuration at the top of our configuration file (gulp/config.js
):
assets: { ..., code: {src: "www/js/app/index.js", dest: "www/js/app"} }
Next, we need to modify the gulp/tasks/copy-code.js
file. First, we need to add additional require statements to the top:
var ..., babelify = require("babelify"), browserify = require("browserify"), buffer = require("vinyl-buffer"), source = require("vinyl-source-stream");
Then, we modify copyCode to look like this:
function copyCode() { var isRelease = (settings.BUILD_MODE === "release"); return browserify(paths.makeFullPath(config.assets.code.src, paths.SRC), { // [1] debug: !isRelease, // [2] standalone: "app" }).transform(babelify.configure({ // [3] stage: 0, // allow experimental features // [4] })) .bundle() .on("error", function(error) { this.emit("end"); // [5] }).on("error", notify.onError(function(error) { return "BABEL: " + error.message; // [6] })) .pipe(source("app.js")) // [7] .pipe(buffer()) // [8] .pipe(cordovaTasks.performSubstitutions()) .pipe(isRelease ? gutil.noop() : sourcemaps.init({ loadMaps: true })) // loads map from browserify file .pipe(isRelease ? uglify({ preserveComments: "some" }) : gutil.noop()) .pipe(isRelease ? gutil.noop() : sourcemaps.write()) .pipe(gulp.dest(paths.makeFullPath(config.assets.code.dest, paths.DEST))) .pipe(browserSync.stream()); }
The second half of the earlier code should look pretty familiar—it's what we had in the previous chapter, but the top half is completely new. Let's go over it a little more in detail.
First, instead of using gulp.src
, we called browserify
directly ([1]
). In the previous chapter, we asked Gulp to concatenate all our JavaScript files into one file. Browserify, on the other hand, will discover all the files it needs to package together by following the dependency tree from our main file. Files that aren't dependencies will be automatically left out of the final output. This alone is a pretty nice feature.
While calling browserify
, we ask it to create sourcemaps by setting the debug
property ([2]
). We would only want to do this if we are developing, so we make this based upon the --mode
setting we pass on to Gulp.
We also want to be able to access our application's state globally while testing. By default, Browserify would package everything up into a neat bundle, but we wouldn't have direct access to the objects our application might create. Passing standalone:"app"
to Browserify in allows us to specify a single global variable (app
)that will give us access to our entire app. This isn't required for production code; but during development, it definitely helps when it comes to debugging.
Next, we transform the code using babelify
([3]
). Browserify will then call babel
for us automatically. We also pass stage: 0
([4]
)so that we can use ES2016 features such as await
and async
.
A couple of error handlers are also added so that we can be notified if Browserify encounters issues while transforming our code. The first one ([5]
) ends the stream (which is important if you ever want to have Gulp watch your code), and the second ([6]
) generates an error message.
Finally, we enter the Gulp territory—the first two pipe
methods are where we bring Gulp into the picture ([7],[8]
). Because we aren't actually asking Gulp to look for specific files, we used the two vinyl
modules we installed earlier to pass the output from Browserify into a form that Gulp can work with. From this point onward, our code is identical to the code in Chapter 1, Task Automation.
Including Node.js packages
Including a Node.js package is very simple. First, you need to find a package you like from npm
. In our example, we'll add a module called yasmf-h
. It's not a complex package and it's essentially a fancier version of our little templating method in index.js
from Chapter 1, Task Automation.
To install the package, enter the following:
$ npm --save install yasmf-h
Next, we need to modify src/www/js/app/index.js
so that we can use the package:
import 'babel/polyfill'; // [1] import h from 'yasmf-h'; // [2] export var app = { start() { var n = h.el("p", h.el("ul", h.el("li", "Some information about this app..."), h.el("li", "App name: {{{NAME}}}"), h.el("li", "App version: {{{VERSION}}}") ) ); h.renderTo(n, document.querySelector("#demo")); } }; document.addEventListener("deviceready", app.start, false);
This isn't entirely different from what we had in Chapter 1, Task Automation. Notice the two import
statements. The first line of code ([1]
) imports the polyfill
that is necessary to support Babel, the ES2015 transpiler we have been using. The second ([2]
) imports the code from the npm
module we installed and assigns it to the h
variable.
We need to modify our src/www/index.html
file slightly as well, namely, to remove a line. Take a look at the following:
<!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>This is static content..., but below is dynamic content.</p> <p id="demo"></p> </body> </html>
Everything is the same, except that we've removed the following line:
<script src="./js/lib/browser-polyfill.js" type="text/javascript"></script>
We don't need to include the ES2015 polyfill in our index.html
file anymore simply because we've imported it in our index.js
file. The fewer scripts we need to include in index.html
, the better.
At this point, you can use gulp build
to rebuild the project with the new changes. It will run exactly as before, but you're now using a module from a third party that you didn't write!
Of course, it's incredibly important to remember a few things while using modules you didn't write:
- Check the license, always.
npm
packages are usually pretty permissive, but it is wise to double-check. The package we included is MIT-licensed, and the requirements only ask for the original copyright to be included somewhere in the output. Because the license is included in the package's code, it is transferred into our output code. But it is up to you to ensure that you properly license any code you include in your project. You should also include a list of packages that you use in a visible portion of your app as well, perhaps in your app's About or Settings section. - Review the package prior to installing it. Look at the code or any documentation, and see whether there are any big issues you can't work around. If a package has tests, even better—you can verify that the package actually works.