Node on Windows - it doesn't have to suck

Node on Windows - it doesn't have to suck
Photo by Pawel Czerwinski on Unsplash.

https://github.com/greenaj/NodeNomad

This article is to help Node front-end developers with development on Windows workstations. For those dealing with hosting Node services on Windows, you've learned that with enough force, you can make a pig fly; work on convincing your IT and management to let you deploy your Node backends to *nix based servers instead. Good luck with that and why I advise that is room for another post.

With Node, Python, Ruby and other frameworks, developers need to work with and switch between different versions on the same workstation. If not, innovation is restriced and teams get locked on an old version– upgrading all the more difficult with passing time. On Mac and Linux workstations, managing and switching between multiple version of these frameworks is relatively easy, not so with Windows.

If you want to work with any of the above in Windows as you would on Linux or OS X, WSL2 is a worthy option. This article however is for users doing Node development directly on Windows.

Node developers on Windows have all heard of nvm-windows which has made Node developement on Windows a lot nicer. It is patterned after nvm (more or less) used on Linux and OS X. Nvm lets the user select one version of Node for one console and different version for a different console if desired. You don't get that with nvm-windows though, changing the active Node changes the version in use system wide. At least with nvm-windows you can switch quickly using a single command.

As an alternative we present some simple PowerShell scripts and workflows that will allow a user to use different version of Node simpulateously on the same workstation.

Lets get started.

Installing Node

Do not use Visual Studio, Chocolatey, or the MSI installer to install Node on Windows. From the node site select the Other Downloads link under the LTS or Current buttons for the recommended download. Select the Windows 64 bit zip binary and download that. Save the downloaded zip into a folder of your choosing, we'll call this the Node Extract directory. This will be where you Node installs live. Extract the archive into this folder.

As an example, for Node 16.13.0 node-v16.13.0-win-x64.zip is downloaded to D:\NodeInstalls\ and extracted there. This should give the directory D:\NodeInstalls\node-v16.13.0-win-x64 with node.exe inside. We'll refer to D:\NoteInstalls as the Node Extract folder.

NodeNomad

I created a simple PowerShell based project, NodeNomad. Basically you unzip several versions of Node within the Node Extract folder and you use NodeNomad to switch between version within your development environment. Essentially it is a set of PowerShell scripts that works with a simple workflow.

To install NodeNomad, simply download the latest .zip from the releases from the project in GitHub. Extract within the Node Extract directory or within another of your liking. The extracted archive will contain a few PowerShell scripts, explained here but also in the project README.

The Secret Sauce for all of this is a basic PowerShell script nodeEnv.ps1:

param (
    [Parameter(Mandatory=$true)]
    [string]$Version
)

$nodeParentDir = $PSScriptRoot
$nodeParentDirFile = Join-Path -Path $PSScriptRoot -ChildPath "node-parent-dir.txt"
if (Test-Path $nodeParentDirFile -PathType Leaf) {
    $nodeParentDir = Resolve-Path -Path $(Join-Path -Path $nodeParentDir -ChildPath $(Get-Content $nodeParentDirFile -First 1))
}

$nodeSubDir = "node-v$($Version)-win-x64"
$thePath = Join-Path -Path $nodeParentDir -ChildPath $nodeSubDir
$env:Path += ";$thePath"
$env:NODE_HOME = $thePath

$Host.UI.RawUI.WindowTitle = "Node $Version Shell"
nodeEnv.ps1

The nodeEnv.ps1 script lives in the Node Extract directory if that is where you extracted NodeNomad. If you wish to use a different directory for download and extracting Node Nomad create the node-parent-dir.txt file and put the full or relative path to the Node Extract directory of your choosing in the top line of that file.

To use a version Node in the Node Extract directory, simply dot source the nodeEnv.ps1 script with the version as the 1 and only argument, e.g.

. ./nodeEnv.ps1 16.13.0

That's basically it. The other scripts in this article build on top of this simple script for implementing convenience utilties.

PowerShell Console Helper

If you don't want to remember the main version of Node your are using, create the node-version.txt file, put the version of Node you wish to run from the Node Extract directory in the 1st line, and run the nodePw.ps1 script.

$local:versionFile = Join-Path -Path $PSScriptRoot -ChildPath "node-version.txt"
$local:nodeVersion = Get-Content $local:versionFile -First 1
. "$PSScriptRoot\nodeEnv.ps1" $local:nodeVersion
nodePw.ps1

Visual Studio Code Helper

The startVsCode.ps1 script starts Visual Studio Code with the version of Node specified in the node-version.txt conveniently in the %PATH% for use by its terminal windows and other utilities.

$vsCodeHome = Join-Path -Path $env:LocalAppData -ChildPath "Programs\Microsoft VS Code"

$versionFile = Join-Path -Path $PSScriptRoot -ChildPath "node-version.txt"

if (Test-Path $versionFile -PathType Leaf) {
	$nodeVersion = Get-Content $versionFile -First 1
	. "$PSScriptRoot\nodeEnv.ps1" $nodeVersion
}

$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = "cmd.exe"
$startInfo.Arguments = "/c", "start", "`"`"", "`"$(Join-Path -Path $vsCodeHome -ChildPath 'code.exe')`""

$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
[void]$process.Start()
startVsCode.ps1

More than anything, the startVsCode.ps1 script provides an example of leveraging the nodeEnv.ps1 script for starting development tools with your desired version of Node in the path.

Clearly it would be nice to just run nodePw.ps1 and startVSCode.ps1 using shortcuts, not having to invoke from a PowerShell console. With the myshortcuts.ps1 script we've got you covered.

$WshShell = New-Object -comObject WScript.Shell
$PathToPws = Join-Path -Path $PsHome -ChildPath "powershell.exe"
$PathToRunCon = Join-Path -Path $PSScriptRoot -ChildPath "nodePw.ps1"
$PathToRunVS = Join-Path -Path $PSScriptRoot -ChildPath "startVsCode.ps1"

$Shortcut = $WshShell.CreateShortcut($(Join-Path -Path $PSScriptRoot -ChildPath "node_console.lnk"))
$Shortcut.TargetPath = $($PathToPws)
$Shortcut.Arguments = "-NoExit ""$PathToRunCon"""
$Shortcut.WorkingDirectory = $PSScriptRoot
$Shortcut.Save()

$Shortcut = $WshShell.CreateShortcut($(Join-Path -Path $PSScriptRoot -ChildPath "node_vscode.lnk"))
$Shortcut.TargetPath = $($PathToPws)
$Shortcut.Arguments = """$PathToRunVS"""
$Shortcut.WorkingDirectory = $PSScriptRoot
$Shortcut.Save()
myshortcuts.ps1

The code in myshortcuts.ps1 is straightforward enough to modify for your own tools if you don't use Visual Studio Code or would like to use other tools.

Updating NPM

Google the search term update npm on windows. Here is a my short list of finds:

On OS X or Linux with nvm, its rainbows and kittens, Windows? Just fugly. Along with NodeNomad there is a PowerShell script for that:

$shell = New-Object -ComObject "Shell.Application"
$shellFolder = $shell.Namespace($env:NODE_HOME)

foreach ($f in @("npm", "npm.cmd", "npx", "npx.cmd")) {
    $shellItem = $shellFolder.ParseName($f)
    $shellItem.InvokeVerb("delete")	
}

$nodeModules = Join-Path -Path $env:NODE_HOME -ChildPath "node_modules"
$npmFolder = Join-Path -Path $nodeModules -ChildPath "npm"
$npmFolder2 = Join-Path -Path $nodeModules -ChildPath "npm2"

Rename-Item $npmFolder $npmFolder2
$npmCli = Join-Path -Path $npmFolder2 -ChildPath "bin\npm-cli.js"
node $npmCli i -g npm@latest

$shellNodeModules = $shell.Namespace($nodeModules)
$shellItem = $shellNodeModules.ParseName("npm2")
$shellItem.InvokeVerb("delete")

This updates npm, it does not preserve the built-in npmrc file however. If you want, create a pull request. Make sure the version of Node from within the Node Extract folder is activated using nodeEnv.ps1 or nodePw.ps1 first.

Wrapup

  • With NodeNomad you have a way of using different versions of Node side by side on Windows.
  • Unlike nvm-windows or nvm on Linux this approach is non centralized.
  • You can change node versions with a command line in nvm-windows and nvm, but with NodeNomad you have to edit a text file or setup in a different folder pointing to a different version.
  • Basically what NodeNomad does well compared to nvm on Linux or nvm-windows is that it lets you run any node version version installed anywhere you want, and NodeNamad can be setup in multiple places simultaneosly depending on your needs.