Skip to content

Instantly share code, notes, and snippets.

@domenic
Created May 25, 2012 21:03
Show Gist options
  • Save domenic/2790533 to your computer and use it in GitHub Desktop.
Save domenic/2790533 to your computer and use it in GitHub Desktop.
Tips for Writing Portable Node.js Code

Node.js core does its best to treat every platform equally. Even if most Node developers use OS X day to day, some use Windows, and most everyone deploys to Linux or Solaris. So it's important to keep your code portable between platforms, whether you're writing a library or an application.

Predictably, most cross-platform issues come from Windows. Things just work differently there! But if you're careful, and follow some simple best practices, your code can run just as well on Windows systems.

Paths and URLs

On Windows, paths are constructed with backslashes instead of forward slashes. So if you do your directory manipulation by splitting on "/" and playing with the resulting array, your code will fail dramatically on Windows.

Instead, you should be using the path module. So instead of resolving paths with string contatenation, e.g. x + "/" + y, you should instead do path.resolve(x, y). Similarly, instead of relativizing paths with string replacement, e.g. x.replace(/^parent\/dirs\//, ""), you should do path.relative("/parent/dirs", y).

Another area of concern is that, when writing portable code, you cannot count on URLs and module IDs having the same separators as paths. If you use something like path.join on a URL, Windows users will get URLs containing backslashes! Similarly for path.normalize, or in general any path methods. All this applies if you're working with module IDs, too: they are forward-slash delimited, so you shouldn't use path functions with them either.

Non-portable APIs

Windows is completely missing the process.(get|set)(gid|uid) methods, so calling them will instantly crash your program on Windows. Always guard such calls with a conditional.

The fs.watchFile API is not sufficiently cross-platform, and is recommended against in the docs because of it. You should use fs.watch instead.

The child_process module requires care cross-platform. In particular, spawn and execFile do not execute in a shell, which means that on Windows only .exe files will run. This is rather problematic, as many cross-platform binaries are included on Windows as .cmd or .bat files, among them Git, CouchDB, and many others. So if you're using these APIs, things will likely work great on OS X, Linux, etc. But when you tell your users “just install the Git build for Windows, and make sure it's in your path!” that ends up not being sufficient. There is talk of fixing this behavior in libuv, but that's still tentative. In the meantime, if you don't need to stream your output, exec works well. Otherwise you'll need branching logic to take care of Windows.

A final edge-case comes when using named sockets, e.g. with net.connect. On Unix, simple filenames suffice, but on Windows, they must conform to a bizarre syntax. There's not really a better solution for this than branching per-platform.

Being Windows-Developer Friendly

One of the most egregious problems with many projects is their unnecessary use of Unix Makefiles. Windows does not have a make command, so the tasks stored in these files are entirely inaccessible to Windows users who might try to contribute to your project. This is especially egregious if you put your test command in there!

Fortunately, we have a solution: npm comes with a scripts feature where you can include commands to be run for testing (test), installation (install), building (prepublish), and starting your app (start), among many others. You can also create custom scripts, which are then run with npm run <script-name>; I often use this for lint steps. Also of note, you can reference any commands your app depends on by their short names here: for example, "mocha" instead of "./node_modules/.bin/mocha". So, please use these! If you must have a Makefile for whatever reason, just have it delegate to an npm script.

Another crucially important step is not using Unix shell scripts as part of your development process. Windows doesn't have bash, or ls, or mv, or any of those other commands you might use. Instead, write your shell scripts in JavaScript, using a tool like Grunt if you'd like.

Bonus: Something that Breaks on Linux and Solaris!

Both Windows and, by default, OS X, use case-insensitive file systems. That means if you install a package named foo, any of require("foo") or require("FOO") or require("fOo") will work—on Windows and OS X. But then when you go to deploy your code, out of your development environment and into your Linux or Solaris production system, the latter two will not work! So it's a little thing, but make sure you always get your module and package name casing right.

Conclusion

As you can see, writing cross-platform code is sometimes painful. Usually, it's just a matter of best practices, like using the path module or remembering that URLs are different from filesystem paths. But sometimes there are APIs that just don't work cross-platform, or have annoying quirks that necessitate branching code.

Nevertheless, it's worth it. Node.js is the most exciting software development platform in recent memory, and one of its greatest strengths is its portable nature. Try your best to uphold that!

@sebmarkbage
Copy link

Another tip: If you're writing command-line tools (e.g. for use with "npm install --global") don't include a dot in the command name. NPM will create .bat/.cmd files as alias to your script file, but the command line won't find them if there's a dot in the name.

Don't call your command-line tool "anything.js".

@bevacqua
Copy link

Another one is to ensure that external commands, such as netsat or lsof exist across the platforms you're targetting with your packages:

https://github.com/bevacqua/dictatorship/blob/master/src/dictatorship.js

@JJ
Copy link

JJ commented Nov 5, 2013

Really helpful, although my best advice for those who want to use node.js on Windows is "Don't". It's extremely unlikely that the production machine will use that operating system; you'll most likely get something Unixoid.

@emilecantin
Copy link

@JJ: Please don't assume that. I currently work on a rather large Node.js app, and our deployments are all on Windows boxes, because that's what our clients use. One of our major pain points are npm modules that frequently break on Windows, and assumptions like that only make it worse.

@foxbunny
Copy link

@kcacom
Copy link

kcacom commented Mar 27, 2015

So (I know I'm coming across this very late), what would you recommend for making sure your paths turn into proper urls even if you're taking advantage of path.join and running on windows? Is the best route to simply always replace the '' with '/' when it's a url after doing a path.join?

@ChuckPierce
Copy link

not sure if this is newer to node but just found it in the docs and its helped me tremendously

https://nodejs.org/api/path.html#path_path_sep

Hopefully this answers your questions @kcacom

@ehmicky
Copy link

ehmicky commented Jan 23, 2019

Great content! I've included some information from this and added more from personal experience with this other guide: https://github.com/ehmicky/portable-node-guide

@akanshgulati
Copy link

Just to add, people add windows APIs in scripts which fails while using the same script on Node JS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment