node package manager
Stop wasting time. Easily manage code sharing in your team. Create a free org »

jenkins-js-builder

Jenkins JS Builder

JIRA

Table of Contents:


Overview

NPM utility for building CommonJS module bundles (and optionally making them jenkins-js-modules compatible).

See jenkins-js-modules.

The following diagram illustrates the basic flow (and components used) in the process of building a CommonJS module bundle. It uses a number of popular JavaScript and maven tools (CommonJS/node.js, Browserify, Gulp, frontend-maven-plugin and more).

Jenkins Module Bundle Build Workflow

The responsibilities of the components in the above diagram can be summarized as follows:

  • CommonJS: JavaScript module system (i.e. the expected format of JavaScript modules). This module system works with the nice/clean synchronous require syntax synonymous with node.js (for module loading) e.g. var mathUtil = require('../util/mathUtil');. This allows us to tap into the huge NPM JavaScript ecosystem.
  • Browserify: A build time utility (NPM package - executed as a Gulp "task") for "bundling" a graph of CommonJS style modules together, producing a single JavaScript file (bundle) that can be loaded (from a single request) in a browser. Browserify ensures that the require calls (see above) resolve properly to the correct module within the bundle.
  • Gulp: A JavaScript build system (NPM package), analogous to what Maven is for Java i.e. executes "tasks" that eventually produce build artifacts. In this case, a JavaScript bundle is produced via Gulps execution of a Browserify "task".
  • frontend-maven-plugin: A Maven plugin that allows us to hook a Gulp "build" into a maven build e.g. for a Jenkins plugin. See Maven Integration below.

Features

jenkins-js-builder does a number of things:

  1. Runs Jasmine tests/specs and produce a JUnit report that can be picked up by a top level Maven build.
  2. Uses Browserify to produce a CommonJS module bundle file from a "main" CommonJS module (see the bundle task below). The bundle file is typically placed somewhere on the filesystem that allows a higher level Maven build to pick it up and include it in e.g. a Jenkins plugin HPI file (so it can be loaded by the browser at runtime).
  3. Pre-process Handlebars files (.hbs) and include them in the bundle file (see 2 above).
  4. Optionally pre-process a LESS fileset to a .css file that can be picked up by the top level Maven build and included in the e.g. a Jenkins plugin HPI file. See the bundle task below.
  5. Optionally perform module transformations (using a Browserify Transform) that "link" in Framework libs (import - see jenkins-js-modules), making the bundle a lot lighter by allowing it to use a shared instance of the Framework lib Vs it being included in the bundle. This can easily reduce the size of a bundle from e.g. 1Mb to 50Kb or less, as Framework libs are often the most weighty components. See the bundle task below.
  6. Optionally export (see jenkins-js-modules) the bundles "main" CommonJS module (see 2 above) so as to allow other bundles import it i.e. effectively making the bundle a Framework lib (see 5 above). See the bundle task below.

Install

npm install --save-dev jenkins-js-builder

This assumes you have node.js v4.0.0 (minimum) installed on your local development environment.

Note this is only required if you intend developing jenkins-js-modules compatible module bundles. Plugins using this should automatically handle all build aspects via maven (see later) i.e. simple building of a plugin should require no machine level setup.

General Usage

Add a gulpfile.js (see Gulp) in the same folder as the package.json. Then use jenkins-js-builder as follows:

var builder = require('jenkins-js-builder');
 
builder.defineTasks(['test', 'bundle', 'rebundle']);
 
builder.bundle('./index.js', 'myappbundle.js').inAdjunctPackage('com.acme');
 

Notes:

  • See the "defineTasks" section for details of the available tasks.
  • See the "bundle" section for details of the bundle command.

defineTasks

jenkins-js-builder makes it possible to easily define a number of tasks. No tasks are turned on by default, so you can also just define your own tasks. To use the tasks defined in jenkins-js-builder, simply call the defineTasks function:

builder.defineTasks(['test', 'bundle', 'rebundle']);

See next section.

Predefined Gulp Tasks

The following sections describe the available predefined Gulp tasks. The bundle and test tasks are auto-installed as the default tasks.

'test' Task

Run all Jasmine style tests. The default location for tests is the spec folder. The file names need to match the pattern "*-spec.js". The default location can be overridden by calling builder.tests(<new-path>).

See jenkins-js-test for more on testing.

'bundle' Task

Run the 'bundle' task. See detail on this in the dedicated section titled "Bundling" (below).

'rebundle' Task

Watch module source files (index.js, ./lib/**/*.js and ./lib/**/*.hbs) for change, auto-running the bundle task whenever changes are detected.

Note that this task will not be run by default, so you need to specify it explicitly on the gulp command in order to run it e.g.

gulp rebundle

Bundling

As stated in the "Features" section above, much of the usefulness of jenkins-js-builder lies in how it helps with the bundling of the different JavaScript and CSS components:

  1. Bundling CommonJS modules to produce a JavaScript bundle.
  2. Bundling LESS resource to produce a .css file.
  3. Bundling Handlebars templates (hbs) into the JavaScript bundle.

It also helps with jenkins-js-modules compatibility i.e. handling imports and exports so as to allow slimming down of your "app" bundle.

Step 1: Create Bundle Spec

Most of the bundling options are configured on the "Bundle Spec", which is an object returned from a call to the bundle function on the builder:

var bundleSpec = builder.bundle('<path-to-main-module>', '<bundle-name>');
  • path-to-main-module: The path to the "main" CommonJS module, from which Browserify will start the bundling process (see Browserify for more details). E.g. 'js/bootstrap3.js'.
  • bundle-name (Optional): The name of the bundle to be generated. If not specified, the "main" module name will be used.

Step 2: Specify Bundle Output Location

jenkins-js-builder lets you configure where the generated bundle is output to. There are 3 possible options for this.

Option 1: Bundle as a jenkins-js-modules "resource", which means it will be placed in the ./src/main/webapp/jsmodules folder, from where it can be imported at runtime. This option should be used in conjunction with bundleSpec.export() (see below).

bundleSpec.asJenkinsModuleResource();

Option 2: Bundle in a specified directory/folder.

bundleSpec.inDir('<path-to-dir>');

Option 3: Bundle as an "adjunct", which means the bundle is put into a package in ./target/generated-adjuncts/. If using this option, make sure the project's pom.xml has the appropriate build <resource> configuration (see below).

Of course, you can also just use the bundleSpec.inDir option (num 2 above) if you'd prefer to handle adjuncts differently i.e. use bundleSpec.inDir to generate the bundle into a dir that gets picked up by your maven build, placing the bundle in the correct place on the Java classpath.

bundleSpec.inAdjunctPackage('com.acme');

An example of how to configure the build <resource> in your pom.xml file (if using inAdjunctPackage), allowing the adjunct to be referenced from a Jelly file.

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
        <resource>
            <directory>target/generated-adjuncts</directory>
        </resource>
    </resources>
</build>

Step 3 (Optional): Specify LESS Processing

Specify a LESS file for pre-processing to CSS:

bundleSpec.less('js/bootstrap3/style.less');

The output location for the generated .css file depends on the output location chosen for the bundle. See Step 2 above.

Step 4 (Optional): Specify "external" Module Mappings (imports)

Some of the NPM packages used by your "app" bundle will be common Framework libs that, for performance reasons, you do not want bundled in every "app" bundle. Instead, you would prefer all "app" bundles to share an instance of these common Framework libs.

That said, you would generally prefer to code your application's CommonJS modules as normal, using the more simple/intuitive CommonJS style require syntax (synch), and forget about performance optimizations until later (build time). When doing it this way, your CommonJS module code should just require the NPM packages it needs and just use them as normal e.g.

var moment = require('moment');
 
moment().format('MMMM Do YYYY, h:mm:ss a');

The above code will work fine as is (without performing any mappings), but the downside is that your app bundle will be more bloated as it will include the moment NPM module. To lighten your bundle for the browser (by using a shared instance of the moment NPM module), we tell the builder (via the bundleSpec) to "map" (transform) all synchronous require calls for moment to async imports of the momentjs:momentjs2 Framework lib bundle (see the momentjs framwork lib bundle).

bundleSpec.withExternalModuleMapping('moment', 'momentjs:momentjs2');

Of course your "app" bundle may depend on a number of weighty Framework libs that you would prefer not to include in your bundle. If so, simply call withExternalModuleMapping for each.

Step 4.1 (Optional): Generating a "no_imports" bundle

since: 0.0.34

Externalizing commons Framework libs (see Step 4) is important in terms of producing a JavaScript bundle that can be used in production (is lighter etc), but can make things a bit trickier when it comes to Integration Testing your bundle because your test (and test environment) will now need to accommodate the fact that your bundle no longer contains all the Framework libs it depends on.

For that reason, jenkins-js-builder supports the generateNoImportsBundle option, which tells the builder to also generate a bundle that includes all of it's dependency Framework libs i.e. a bundle which does not apply imports (hence "no_imports").

bundleSpec.generateNoImportsBundle();

Note that this is an additional bundle i.e. not instead of the "main" bundle (in which "imports" are applied).

With this option set, the "no_imports" bundle is generated into a sub-folder named "no_imports", inside the same folder in which the "main" bundle is generated.

For an example of how to use the generateNoImportsBundle option, see the "step-08-zombie-tests" Integration Test sample plugin.

Step 5 (Optional): Export

Exporting the "main" module (allowing other bundle modules to import it) from the bundle is easy:

bundleSpec.export();

The builder will use the plugin's artifactId from the pom.xml (which becomes the plugin ID), as well as the bundle name (normalised from the bundle name specified during Step 1) to determine the export bundle ID for the module.

For example, if the plugin's artifactId is "acmeplugin" and the bundle name specified is "acme.js", then the module would be exported as acmeplugin:acme. The package associated with the "acme.js" module should also be "published" to NPM so as to allow "app" bundles that might use it to add a dev dependency on it (so tests etc can run).

So how would an "app" bundle in another plugin use this later?

It would need to:

  1. Add a normal HPI dependency on "acmeplugin" (to make sure it gets loaded by Jenkins so it can serve the bundle).
  2. Add a dev dependency on the package associated with the "acme.js" module i.e. npm install --save-dev acme. This allows the next step will work (and tests to run etc).
  3. In the "app" bundle modules, simply require and use the acme module e.g. var acme = require('acme');.
  4. In the "app" bundle's gulpfile.js, add a withExternalModuleMapping e.g. bundleSpec.withExternalModuleMapping('acme', 'acmeplugin:acme');.

See Step 4 above.

Step 6 (Optional): Minify bundle JavaScript

since: 0.0.35

This can be done by calling minify on jenkins-js-builder:

bundleSpec.minify();

Or, by passing --minify on the command line. This will result in the minification of all generated bundles.

$ gulp --minify

Setting 'src' and 'test' (spec) paths

The default paths depend on whether or not running in a maven project.

For a maven project, the default source and test/spec paths are:

  • src: ./src/main/js and ./src/main/less (used primarily by the rebundle task, watching these folders for source changes)
  • test: ./src/test/js (used by the test task)

Otherwise, they are:

  • src: ./js and ./less (used primarily by the rebundle task, watching these folders for source changes)
  • test: ./spec (used by the test task)

Changing these defaults is done through the builder instance e.g.:

var builder = require('jenkins-js-builder');
 
builder.src('src/main/js');
builder.tests('src/test/js');

You can also specify an array of src folders e.g.

builder.src(['src/main/js', 'src/main/less']);

Command line options

A number of jenkins-js-builder options can be specified on the command line.

--minify

since: 0.0.35

Passing --minify on the command line will result in the minification of all generated bundles.

$ gulp --minify

--test

since: 0.0.35

Run a single test.

$ gulp --test configeditor

The above example would run test specs matching the **/configeditor*-spec.js pattern (in the test source directory).

Maven Integration

Hooking a Gulp based build into a Maven build involves adding a few Maven <profile>s to the Maven project's pom.xml.

We have extracted these into a sample_extract_pom.xml from which they can be copied.

NOTE: We hope to put these <profile> definitions into one of the top level Jenkins parent POMs. Once that's done and your project has that parent POM as a parent, then none of this will be required.

With these <profiles>s installed, Maven will run Gulp as part of the build.

  • runs npm install during the initialize phase,
  • runs gulp bundle during the generate-sources phase and
  • runs gulp test during the test phase).

You can also execute:

  • mvn clean -DcleanNode: Cleans out the local node and NPM artifacts and resource (including the node_modules folder).