As the World’s Laziest Developer, I don’t like to invent anything new if I can find something that already exists (and meets my needs).
This article is a great example of that mentality. I’m really standing on the shoulder of giants and combining a few links and re-using someone else’s code (with credit, of course) to document what my approach to versioning SPFx packages is, with the hope that it helps someone else.
There are a few ways to communicate changes when working on a project: you can use your commit log diffs, GitHub Releases, use your own log, or any other standard out there.
The problem with commit log diffs is that, while comprehensive, they are an automated log of changes that include every-single-change. Log diffs are great for documenting code changes, but if you have a team of developers merging multiple commits every day between versions, they aren’t great at summarizing the noteworthy differences.
GitHub Releases solves a part of this problem by making it easy to manually (or automatically) creating release notes with git tags. (f you haven’t looked into GitHub Releases, it is awesome — take a look!.
However, GitHub Releases is still not very user-friendly (or manager-friendly).
You can always write your own change log format, but why not adopt a format and structure that you can use consistently across projects & teams?
This is where CHANGELOGs come in. According to Olivier Lacan at KeepAChangeLog.com, a changelog is…
“a file which contains a curated, chronologically ordered list of notable changes for each version of a project.”
Changelogs use the markdown syntax to make it easy to maintain. They follow a few principles (again, credit to KeepAChangeLog.com):
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- (List new added features)
### Changed
- (List changes to existing functionality)
### Deprecated
- (List soon-to-be removed features)
### Removed
- (List features removed in this version)
### Fixed
- (List bugs fixed in this version)
### Security
- (List vulnerabilities that were fixed in this version)
## [0.0.1] - 2018-04-16
Remember that not everyone is an American-born, native English speaker. Use the ISO Standard format for dates. The French-Canadian in me thanks you.
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.0.1] - 2018-04-16
### Added
- (List new added features)
### Changed
- (List changes to existing functionality)
### Removed
- (List features removed in this version)
### Fixed
- (List bugs fixed in this version)
### Security
- (List vulnerabilities that were fixed in this version)
I have worked with Microsoft technologies as long as I can remember, so it is ingrained in me that every version number should consist of 4 parts: Major, Minor, Build, Revision. For example, 1.0.0.0.
When you package an SPFx solution, the solution version always starts with version 1.0.0.0, and you can’t make it lower than that. (Well, you can, but SharePoint will ignore it and it will become version 1.0.0.0).
Imagine my horror when, one day, I was trying to change the version number of a solution and searched for 1.0.0 and found that the NodeJS package also has its own version, stored in a file called package.json. What’s worse, it didn’t even have 4 parts!
The heresy!
After my initial indignation, I decided to research this and found that the versioning schema is called Semantic Versioning (or sem-ver for short). It consists of three mandatory parts: Major, Minor, Patch, plus an optional label for pre-release and build metadata. For example, you could have a version 1.0.0-rc for a release candidate version.
Hmmm, makes it easier to keep track of versions. And it is more human-readable, which is always good.
To keep things even more confusing, each web part can have its own version number. While there are valid reasons why you would want to keep the package version, the solution version and the web part versions separate, it quickly becomes impossible to keep track of versions.
To keep things clean, it makes sense to keep version numbers in sync.
Luckily, makes it easy to update your package.json version by simply calling:
npm version <major|minor|patch>
Where you specify to increase either the major, minor, or patch version.
For example, if you start with a package.json version 0.0.3 and want to increase the major version, you’d call:
npm version major
Which would produce v1.0.0.
If only there was a way to make it this easy to synchronize the package.json version to the package-solution.json version.
If only someone way smarter than I had thought of this…
It turns out there is such a person: Stefan Bauer!
In his blog post, he shares a way to add a Gulp function that automatically syncs the package.json version with the package-solution.json.
(Thanks Stefan for being awesome!)
To add this Gulp function, do the following steps:
In your SPFx project, open gulpfile.js
Before build.initialize(gulp); add my slightly modified version of Stefan‘s code. If it works, credit goes to Stefan. If it fails, it was my changes.
let syncVersionsSubtask = build.subTask('version-sync', function (gulp, buildOptions, done) {
this.log('Synching versions');
// import gulp utilits to write error messages
const gutil = require('gulp-util');
// import file system utilities form nodeJS
const fs = require('fs');
// read package.json
var pkgConfig = require('./package.json');
// read configuration of web part solution file
var pkgSolution = require('./config/package-solution.json');
// log old version
this.log('package-solution.json version:\t' + pkgSolution.solution.version);
// Generate new MS compliant version number
var newVersionNumber = pkgConfig.version.split('-')[0] + '.0';
if (pkgSolution.solution.version !== newVersionNumber) {
// assign newly generated version number to web part version
pkgSolution.solution.version = newVersionNumber;
// log new version
this.log('New package-solution.json version:\t' + pkgSolution.solution.version);
// write changed package-solution file
fs.writeFile('./config/package-solution.json', JSON.stringify(pkgSolution, null, 4));
}
else {
this.log('package-solution.json version is up-to-date');
}
done();
});
let syncVersionTask = build.task('version-sync', syncVersionsSubtask);
build.rig.addPreBuildTask(syncVersionTask);
Save your file
The final gulpfile.js should look like this:
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
//BEGIN: Added code for version-sync
let syncVersionsSubtask = build.subTask('version-sync', function (gulp, buildOptions, done) {
this.log('Synching versions');
// import gulp utilits to write error messages
const gutil = require('gulp-util');
// import file system utilities form nodeJS
const fs = require('fs');
// read package.json
var pkgConfig = require('./package.json');
// read configuration of web part solution file
var pkgSolution = require('./config/package-solution.json');
// log old version
this.log('package-solution.json version:\t' + pkgSolution.solution.version);
// Generate new MS compliant version number
var newVersionNumber = pkgConfig.version.split('-')[0] + '.0';
if (pkgSolution.solution.version !== newVersionNumber) {
// assign newly generated version number to web part version
pkgSolution.solution.version = newVersionNumber;
// log new version
this.log('New package-solution.json version:\t' + pkgSolution.solution.version);
// write changed package-solution file
fs.writeFile('./config/package-solution.json', JSON.stringify(pkgSolution, null, 4));
}
else {
this.log('package-solution.json version is up-to-date');
}
done();
});
let syncVersionTask = build.task('version-sync', syncVersionsSubtask);
build.rig.addPreBuildTask(syncVersionTask);
//END: Added code for version-sync
build.initialize(gulp);
Next time you build your package, the Gulp task version-sync will grab the package.json version (which you updated using npm version, right?) and will update package-solution.json, adding an extra zero at the end of the version number to Microsoftify the version.
When you get the version number, go update your CHANGELOG.md file by moving the changes from [unreleased] to a new section with the new version number you just created.
So far, we have done the following:
But there is still a little annoying thing: the web part versions (stored in webpart.manifest.json, where webpart is the name of your web part) can be different than the package.json and package-solution.json.
Turns out that it is pretty easy to fix:
"version": "*",
Doing so will cause the version of the webpart.manifest.json to match the package-solution.json version.
(Turns out that the latest version of SPFx documents this by adding the following comment on the line above “version”: “*”.
// The "*" signifies that the version should be taken from the package.json
"version": "*",
How cool is that?!
By using CHANGELOG.md to keep track of changes between versions, and using semantic versioning for your versions, you can make it pretty easy to document your changes across versions.
By using npm version, you can easily maintain the semantic version of your package.json.
By using Stefan’s cool version-sync Gulp command, you can easily sync your package.json version and your package-solution.json.
By using “version”: “*”, you can synchronize your package-solution.json and your webpart.manifest.json versions.
Finally, by not reinventing the wheel and by leveraging the hard-work of other people, you can do it all with very little effort!
I hope this helps you?!