jenkins-js-builder
v0.0.51
Published
Jenkins CI JavaScript Build utilities.
Downloads
1,148
Readme
Jenkins JS Builder
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).
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:
- Runs Jasmine tests/specs and produce a JUnit report that can be picked up by a top level Maven build.
- 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). - Pre-process Handlebars files (
.hbs
) and include them in the bundle file (see 2 above). - 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 thebundle
task below. - 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 thebundle
task below. - Optionally
export
(see jenkins-js-modules) the bundles "main" CommonJS module (see 2 above) so as to allow other bundlesimport
it i.e. effectively making the bundle a Framework lib (see 5 above). See thebundle
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 thebundle
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:
- Bundling CommonJS modules to produce a JavaScript bundle.
- Bundling LESS resource to produce a
.css
file. - Bundling Handlebars templates (
hbs
) into the JavaScript bundle.
It also helps with jenkins-js-modules compatibility i.e. handling import
s and export
s 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 beimport
ed at runtime. This option should be used in conjunction withbundleSpec.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'spom.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. usebundleSpec.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
import
s 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:
- Add a normal HPI dependency on "acmeplugin" (to make sure it gets loaded by Jenkins so it can serve the bundle).
- 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). - In the "app" bundle modules, simply
require
and use theacme
module e.g.var acme = require('acme');
. - In the "app" bundle's
gulpfile.js
, add awithExternalModuleMapping
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 therebundle
task, watching these folders for source changes) - test:
./src/test/js
(used by thetest
task)
Otherwise, they are:
- src:
./js
and./less
(used primarily by therebundle
task, watching these folders for source changes) - test:
./spec
(used by thetest
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 theinitialize
phase,- runs
gulp bundle
during thegenerate-sources
phase and- runs
gulp test
during thetest
phase).
You can also execute:
mvn clean -DcleanNode
: Cleans out the local node and NPM artifacts and resource (including thenode_modules
folder).