Embed version info

How to include version and useful meta information in the JavaScript libraries.

Large modern libraries include version and additional information as a property. Lodash for example puts its semantic information in the banner and attaches it to the exported _ object.

lodash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @license
* lodash 3.9.3 (Custom Build) <https://lodash.com/>
* Build: `lodash modern -o ./lodash.js`
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <https://lodash.com/license>
*/
;
(function() {
/** Used as the semantic version number. */
var VERSION = '3.9.3';
...
lodash.VERSION = VERSION;
}.call(this));

AngularJs has meta information object attached as angular.version

angular.version
Object {full: "1.4.0", major: 1, minor: 4, dot: 0, codeName: "jaracimrman-existence"}

If you produce a library to be used by others, make sure to attach the version and other meta information. Having version information at runtime is great help during debugging.

Version string in the banner comment

I assume that your library has an NPM package.json with at least the version string. It would be great if you followed semantic versioning.

To create a nice banner I prefer using grunt-contrib-concat plugin. It concatenates source files and can add the banner to the output file. For example freeze-prototypes project has the following Gruntfile.js

Gruntfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var pkg = grunt.file.readJSON('package.json');
grunt.initConfig({
pkg: pkg,
concat: {
options: {
banner: '/**\n' +
' <%= pkg.name %>@<%= pkg.version %>\n' +
' <%= pkg.description %>\n' +
' <%= pkg.author %>\n' +
' <%= pkg.homepage %>\n' +
'*/\n\n'
},
js: {
src: ['index.js'],
dest: 'dist/freeze-prototypes.js'
},
}
});

Running grunt concat command produces dist/freeze-prototypes.js with a nice banner, useful to anyone trying to understand what are the libraries running at runtime, especially if using single letter objects.

dist/freeze-prototypes.js
1
2
3
4
5
6
7
8
9
/**
[email protected]
Freezes common prototypes like Array.prototype to avoid any library messing with them
Gleb Bahmutov <[email protected]>
https://github.com/bahmutov/freeze-prototypes
*/
(function freezePrototypes() {
...
}());

Version string property

Let us also add version (and author, description meta) information to the exported library itself, so we can fetch it at run time from the browser console. To achieve this I attach property version to the exported library. The version property is an object with placeholders that can be replaced during the build step. For example see functional-pipeline project.

By default, the exported function fp has an object with placeholders.

input fp.js file
1
2
3
4
5
6
7
8
9
10
function fp() { ... }
fp.version = {
name: '%%name',
version: '%%version',
author: '%%author',
description: '%%description',
homepage: '%%homepage'
};
// attach to the environment / export `fp`
register(fp, 'fp');

During the concat step, we can transform the source and put the actual values read from the package.json file into the placeholders.

Gruntfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// pkg is the loaded package.json file
concat: {
options: {
process: function insertMeta(src) {
['name', 'version', 'description', 'author', 'homepage'].forEach(function (tag) {
src = src.replace('%%' + tag, pkg[tag]);
});
return src;
}
},
fp: {
src: ['fp.js'],
dest: 'dist/fp.js'
}
}

The output file will have something like this

dist/fp.js
1
2
3
4
5
6
7
8
9
...
fp.version = {
name: 'functional-pipeline',
version: '0.4.0',
author: 'Gleb Bahmutov <[email protected]>',
description: 'Quickly chain method calls, ...',
homepage: 'https://github.com/bahmutov/functional-pipeline'
};
register(fp, 'fp');

Using grunt templates

To avoid custom tags and replacement, we can use grunt's built-in Lodash template api. We can use <%= %> to put a value inside the source file.

input fp.js source
1
2
3
4
5
6
7
8
9
function fp() { ... }
fp.version = {
name: '<%= pkg.name %>',
version: '<%= pkg.version %>',
author: '<%= pkg.author %>',
description: '<%= pkg.description %>',
homepage: '<%= pkg.homepage %>'
};
register(fp, 'fp');

We can also attach the loaded package.json to the Gruntfile config object to be able to refer to the properties inside the template via pkg property.

Gruntfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var pkg = grunt.file.readJSON('package.json');
grunt.initConfig({
pkg: pkg,
concat: {
options: {
process: function insertMeta(src) {
return grunt.template.process(src);
}
},
fp: {
src: ['fp.js'],
dest: 'dist/fp.js'
}
}
});

The function insertMeta is unnecessary - it just passes the source to the grunt.template.process method; we can use point-free style call.

Gruntfile.js
1
2
3
4
5
6
7
8
9
concat: {
options: {
process: grunt.template.process
},
fp: {
src: ['fp.js'],
dest: 'dist/fp.js'
}
}

See the complete Gruntfile.js for details.

Related

  • You can also embed Git commit id in addition to the version string, see Deployed commit blog post.
  • If you want version information inside Angular modules, see Angular module info.