Friday, September 15, 2017

Versioning strategy for SPFx

Background


As an ISV, one of the most important aspects of our products is - you guesed it - product versioning.

Versioning is important for a lot of things:

  • It tells users if they are running the latest bits of their software
  • It helps support reproduce issues and report them more accurately
  • It helps developers track bugs and issues, when they occur and when they were fixed
  • It tells the product manager how the product is moving along its roadmap
... and more!

One thing I am not a big fan of is how SPFx handles product releases and versions.

See, as an ISV in the cloud-world, we find it important from time to time to push updates to our apps to all our customers at once, without having to ask them to download and install a new version of the application.

We do that by updating the resources in our CDN, expiring our cache and that makes sure all clients get the latest and greatest builds automatically.

Imagine you have a spelling mistake, typo, or that an update is rolling out that causes a critical failure of your product. In these situations, we usually push updates from our servers to all our customers, and they don't have to worry about updating anything themselves.

(Remember the days you had to go to the app store on your phone and hit "update" on every app? Yuck!)

So, for that reason, I find the way SPFx produces builds a bit lacking.
See, whenever you are ready to release a build, you type gulp --ship to build your scripts in release mode.
That produces a bundled JS file that contains a hash in the file name.
Meaning - every little change you do will produce a different hash code.


data-view-plus.bundle_d41aab51ecdd17f15f7f921d87534405.js

Now, the manifest points to that specific file with that specific hash, and requires you to produce a new app package, and install that new package on your app catalog.

Although the bundle JS file is most likely hosted in your CDN, each new version you release points to a new different file, so no one gets updates automatically.

Moreover, you have no way of controlling when your clients upgrade, so you can never remove older files from your CDN, making it a complete mess and nightmare to maintain in an ever-growing pile of files, most of which no one will ever use.

This is why we chose to go a different route when releasing our SPFx versions.

Instead of using the hash - we use a package version in the JS file name.
As long as we make small changes that we want to push automatically, we keep the same file name and push the changes to our CDN.
We track the build number using a JS variable inside our code to be able to keep track on what build is the current client running, since we do not change the package version on every release.


data-view-plus.1.0.0.5.js

Every time we have a bigger change, that changes our package manifest (added a web part, added or changed a dependency library) - that requires a new app package to be released. So only on these circumstances we advance the package version (config\package-solution.json) and release a new package with a new bundle file name to match it.

This significantly reduces the amount of script files we have in our CDN, makes each file very easy to understand which build it belongs to (instead of those hash codes) and allows us to push minor updates without requiring our users to install a new package in their catalog.


So, how do we do it?

I'm afraid we have yet to automate this part... but the steps are not too complicated to follow, I promise.
From this point I assume you have your SPFx project all ready and set up, configured with a CDN of your choice.

Releasing a new package version

  1. Change the version in your config\package-solution.json file
  2. Advance your "BuildNumber" variable in your code (optional)
  3. Run 'gulp --ship'
  4. Visit the new files outputted to temp\deploy
  5. Edit the bundle JS file name, replace the hash with the version you put in step 1
  6. Edit the [guid].json file, it should have the bundle JS file name in it - replace with with the new file name you came up with in step 5
  7. Save all changes
  8. Run 'gulp package-solution --ship'
  9. Now, you package will be ready for you under SharePoint/Solution and it will use the file name you set in step 5
  10. Don't forget to copy your bundle JS file to your CDN, do not remove older versions from the CDN if you have them.

Releasing patches or minor updates

  1. Advance your "BuildNumber" variable in your code (optional)
  2. Run 'gulp --ship'
  3. Copy the content of the bundle JS file from either temp\deploy or from the dist folder into the CDN, overwrite the content of the latest version file you got there
Hint: how can you tell if your change requires a new package or not? Simple. Add the web part to a SharePoint page (classic or modern) - not the workbench. It will use the old web part definition. If it runs - it means your changes did not break the signature of the bundle. If you get a nasty error message, time to release a new version.

Do you manage versions in SPFx? Do it differently? I would love to hear, please leave a comment!
Also, know how to automate my process? Please share!

Thanks for reading,
Shai.

Updating SPFx package still showing the older web part

Something I noticed while working with SPFx,
some times I would release a new package to my app catalog by dragging it over an existing older version.

SharePoint would prompt me to overwrite, I would say yes.

Now, every page refresh I would randomly get the older file loaded or the latest one loaded. It was completely unpredictable and didn't seem to depend on anything specific I did.

What happens?

It seems when you overwrite a package, you don't get the dialog to trust the package. Now, that dialog is important, since while you visit it, it plants a cookie in your browser that marks for SharePoint to drop its package cache and reload the manifest.
If that cookie is missing... you would be getting a cached version of the manifest.
Since SharePoint Online is a beast with many WFE servers in load balanced setup, you sometimes get a server that cleared its cache (maybe the one that handled the replace action) and other times a server that still has the older one cached.

This situation is not a big deal in production, since in 20 minutes or so the cache clears itself.

But for dev - here is what you have to do to avoid it:
Simply delete the package before you upload a new one, and don't use the overwrite option.

Simple, efficient but very confusing...

Hope this helps!

Thursday, September 7, 2017

Office UI Fabric JS multi select support

Following up on my earlier post, where I shared our own version of Office Fabric JS that resolved a few major issues with versioning, isolation, and custom-prefix.

 As promised we will continue to deliver enhancements and bug fixes to our hosted version of Office Fabric JS library.

 In the near future, we hope to launch this as an official fork with open source in GitHub, but for now we still host it and you are welcome to use it.

 A reminder, here is the original post explaining how to use our version.

So, to the matter at hand.

Today, I am happy to announce we have released a new build that added a much needed feature to the office UI fabric JS library: support for multi-select.

As a design decision, Microsoft decided they will not support multi select control for usability reasons, fearing it won't be compatible with touch devices.

While you may or may not agree with their decision, there are times where using a multi select control will make sense, either as a requirement from your customer, your users or just what makes more sense for you in that particular situation.

(GitHub Discussion here)

So far, select controls that were enhanced with office UI fabric JS would just render as a regular single select.

Today we released an update to the library, once you set your select control to allow multiple selection, enhancing it with Office UI Fabric JS will now render a multiple selection control.

The main differences are:
1. After selecting a value, the drop down will remain open allowing you to select another one.
2. Clicking on a selected item will remove it from the selection.
3. The text box will now show all selected values separated by ,

Here it is opened:
And closed:

Here is the working JSFiddle: