To get the development and ALM process right with SPFx, some of the answers lie in use of commands like npm shrinkwrap, having a solid understanding of the package.json file and how developers specify different types of dependencies. But before we get to that in detail, let’s start by talking about the problems and some general background to understand.
I use the slide below to summarise some key pitfalls in this area, splitting them into “dev” and “shipping” considerations:
What are caret dependencies (never mind why they can be a pitfall)? What does –save do on the npm install command? If those are questions you’re asking right now, then my companion post SPFx - an overview of node_modules, package.json and other node concepts might be useful.
Otherwise, let’s get to the detail.
Not specifying --save on npm install
This is a newbie pitfall for teams/developers not very familiar with npm. When a developer adds a package with npm install, the package is downloaded from the npm repository and installed to your application – at least, in the machine and folder that the command is run on. Adding the --save flag ensures the dependency is also recorded in package.json – thereafter, when any dev runs npm install or npm update for this application, npm will again ensure the appropriate package is downloaded and installed locally (because the package.json file is shared via source control). It’s the equivalent of adding a .NET dependency via NuGet, compared to just adding a local file reference. As ever, checking the full set of packages themselves into source control is usually a bad idea. So, the pitfall is simple - if the dev forgets to use the --save flag when installing a package, no entry in package.json is made and code may fail due to a missing library on other machines. You’ll see a TypeScript compile error like “Cannot find module” when another developer tries to build or run the code (e.g. with gulp serve):
Recommendation – always track your dependencies by specifying the --save parameter (or one of the options such as –save-exact) when running npm install.
You might also consider a custom .npmrc file for your team here, which can automatically specify certain parameters (e.g. --save or --save-exact) without the dev having to remember them. I talk about how a custom .npmrc file works in the other post.
N.B. if you do fall into the trap of using a dependency without it being properly stored in package.json, simply install it again *with* the --save (or similar) flag. Then have each dev run an npm update to ensure everyone is using the same version.
Pitfall - using caret dependencies without thinking about it
When adding an npm package, the default is to use a caret dependency. However, both caret and tilde dependencies bring an element of risk to the dev process (but can have benefits too). If you’re not familiar with version numbers which use carets or tildes, read either my background post or semantic versioning (semver) in the npm docs.
Briefly, the caret character (^) denotes that any version up to (but not including) 4.0.0 is acceptable. Caret dependencies like this are the default with npm, but can lead to danger because there can be all sorts of changes within that version range, potentially including breaking changes, depending on how the package author handles things. Not everyone plays by the semver rules. So, that means that in team development, one person on the team can be using 3.1.1 and another 3.9.9 (i.e. releases of the same major version, but different minor versions), depending on how when each last ran the npm update command. So, there can be lots of variability from caret dependencies like these.
Tilde dependencies (~) are similar, except here the range is more narrow – so ~3.1.1 would allow any version up to (but not including) 3.2.0 (i.e. releases of the same major/minor version, but different patch versions).
Note also that I’m simplifying things here slightly, since npm deals with things differently when a zero is used in the version number. See the semver page for more details.
So are caret and tilde dependencies always bad?
Well no, not really. You might decide that you DO want the latest and greatest of libraries you are using during the dev process, without the hassle of managing each individually. Often you do indeed want those minor bug fixes and performance enhancements – and avoiding “exact” dependencies gives you that (so long as you npm update regularly).
Recommendation – make an explicit decision about whether you want “floating” dependencies in dev.
If NO, consider these options:
- Ensure all developers always use the –save-exact flag instead of the –save flag
- This will have the effect of writing in an exact reference into package.json, rather than a caret or tilde dependency
- Use a custom .npmrc file across the team to have the same effect (without developers having to remember –save-exact every time they install a package)
- Again, see my SPFx - an overview of node_modules, package.json and other node concepts for details on this technique
If YES, then:
- Stick with caret dependencies, but consider becoming “tighter” as you approach releasing your web part – ensure all devs are running npm update regularly, and perhaps switch to exact dependencies just before shipping.
Whether you use exact dependencies in dev or not, there’s something that I recommend you always do to freeze dependencies at ship time of each release, and that’s to run npm shrinkwrap. We’ll talk about that next..
Pitfall – not locking down dependencies when you make a release
In SPFx and node.js development, it’s crucial to remember that the node_modules folder is an entire tree of dependencies. First level dependencies that you know about have their own dependencies, which are stored in child node_modules folders – that’s why the node_modules folder can be big in size and go quite deep. The steps we’ve talked about so far, such as considering use of --save-exact to avoid caret/tilde dependencies, only get you so far – and that’s because they deal with your first level dependencies only. Consequently, you’ll find it difficult to recreate the *exact* build you shipped as v1 because you can’t restore the exact node_modules folder you had at the time of this build. Sure, you could if you always checked the entire mode_modules folder into source control for every build, but yuck!
What you need is for the entire tree of dependencies to be frozen for each release – the npm shrinkwrap command gives you this. It creates a JSON file containing details of which version was resolved of every package used:
Thereafter, if the npm install or npm update commands are used against a folder containing such a file (named npm-shrinkwrap.json), npm will look to it to restore the packages. This is different to the default behaviour which will use any caret or tilde dependencies (etc.) specified in the package.json file. Thus, with this approach you can restore the entire build as it happened at a certain time, without having the node_modules folder in its state at that time.
Recommendation – run npm shrinkwrap every time you release a version of your code. Store the npm-shrinkwrap.json in source control.
Creating SharePoint Framework solutions effectively involves more than just learning the new APIs. You need to build a good understanding of the underlying web stack, and arguably some elements are more important than others – modules, packages and npm is a combined area that I recommend spending time on. Without this, it’s easy to fall into some pitfalls when developing or shipping your SPFx solution. Overall I recommend:
- Taking care when developers add libraries – ensure they are tracked in package.json
- Deciding on whether you’re happy with floating dependencies during dev, or whether you prefer to use exact dependencies
- Running npm shrinkwrap each time you release a version of your solution, and checking the npm-shrinkwrap.json file into source control. This will allow you to rebuild the app properly later on, when never versions of your dependencies have been released