Portable Node.js guide
source link: https://www.tuicool.com/articles/hit/YF7JVfq
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Why you should care
According to the latest Node.js user survey , 24% of Node.js developers use Windows locally, 41% use Mac locally and 39% use Linux in production.
Feedback
This document is based on my personal experience running cross-OS Node.js. Please submit an issue or PR if you find any errors or want to add more information.
If you find this document too long, you can jump to the.
Installing and updating Node
Installers for each major OS are available on the Node.js website .
To install, switch and update Node.js versions
nvm
can be used on Linux/Mac. It does not support Windows
but
nvm-windows
and
nvs
are alternatives that do.
To upgrade npm
on Windows, it is convenient to use
npm-windows-upgrade
.
Core utilities
Each OS has its own set of (from the lowest to the highest level):
-
system calls
like
fork
. -
core utilities
like
sed
. -
common user applications like
vim
orNotepad
.
Directly executing one of those binaries (e.g. calling sed
) won't usually
work on every OS.
There are several approaches to solve this:
-
Most Node.js API core modules abstract this. E.g. the
child_process
methods are executing OS-specific system calls under the hood. -
some projects abstract OS-specific core utilities like:
-
MinGW
for gcc on Windows. -
msys
for Bash on Windows. Shipped with Git for Windows . -
shelljs
-
node-windows
-
-
some projects like
opn
abstract common user applications.
Few lower-level tools attempt to bring cross-OS compatibility by emulating or translating system calls:
- Wine : to run Windows API calls on Unix.
- Cygwin : to run POSIX on Windows.
- WSL : to run Linux system calls on Windows.
Testing
Any OS can be run locally using virtual machines . Windows provides with official images .
It is recommended to run automated tests on a continuous integration provider that supports Linux, Mac and Windows, which most high-profile providers now do.
C/C++ addons
Windows users must first run
npm install -g windows-build-tools
as an admin before being to install C/C++ addons
.
Directory locations
Typical directory locations are OS-specific:
-
the main temporary directory could for example be
/tmp
on Linux,/var/folders/.../T
on Mac orC:\Users\USER\AppData\Local\Temp
on Windows.os.tmpdir()
can be used to retrieve it on any OS. -
the user's home directory could for example be
/home/USER
on Linux,/Users/USER
on Mac orC:\Users\USER
on Windows.os.homedir()
can be used to retrieve it on any OS.
System configuration
While Unix usually stores system configuration as files, Windows uses the registry , a central key-value database. Some projects like node-winreg , rage-edit or windows-registry-node can be used to access it from Node.
This should only be done when accessing OS-specific settings. Otherwise storing configuration as files or remotely is easier and more portable.
Newlines
The character representation of a newline
is OS-specific. On Unix it
is \n
(line feed) while on Windows it is \r\n
(carriage return followed by
line feed).
Newlines inside a template string translate to \n
on any OS.
const string = `this is an example`
Some Windows applications, including the cmd.exe
terminal, print \n
as
newlines, so using \n
will work just fine. However some Windows applications
don't, which is why when reading from or writing to a file the OS-specific
newline
os.EOL
should be used
instead of \n
.
File paths
While /
is used as a file path delimiter on Unix ( /file/to/path
), \
is
used on Windows instead ( \file\to\path
). The path delimiter can be retrieved
with
path.sep
. Windows
actually allows using or mixing in /
delimiters in file paths most of the
time, but not always so this should not be relied on.
Furthermore absolute paths always start with /
on Unix, but on Windows they
can take many shapes
:
-
\
: the current drive. -
C:\
: a specific drive (hereC:
). This can also be used with relative paths likeC:file\to\path
. -
\\HOST\
: UNC path, for remote hosts. -
\\?\
: allows to overcome file path length limit of 260 characters. Those can be produced in Node.js withpath.toNamespacedPath()
. -
\\.\
: device path.
When file paths are used as arguments to Node.js core methods:
-
for example as arguments to
require(path)
,fs.*(path)
methods,path.*()
methods orprocess.chdir(path)
. - only Unix paths are allowed on Unix. Both Unix and Windows paths are allowed on Windows (including mixed).
When file paths are returned by Node.js core methods:
-
for example the return values of
path.*()
methods,process.cwd()
,os.homedir()
,os.tmpdir()
or the value of__dirname
,process.argv
andprocess.execPath
. - Unix paths are returned on Unix and Windows paths on Windows.
-
exceptions:
-
using
path.win32.*()
orpath.posix.*()
instead ofpath.*()
will return Windows or Unix paths. -
methods where the path is present both as argument and as return value
depend on whether the input path is Windows-like or Unix-like. This
includes
fs.createReadStream()
andfs.mkdtemp()
.
-
using
Outside of Node.js, i.e. when the path is input from (or output to) the terminal or a file, its syntax is OS-specific.
To summarize:
-
if a path must be output outside of Node.js (e.g. terminal or file),
path.normalize()
should be used to make it OS-specific. - if a path comes from outside of Node.js or from a core method, it will be OS-specific. However all Node.js core methods will properly handle it.
- in all other cases using Unix paths will just work.
Filenames
Each OS tends to use its own file system : Windows uses NTFS , Mac uses APFS (previously HFS+ ) and Linux tends to use ext4 , Btrfs or XFS . Each file system has its own restrictions when it comes to naming files and paths.
Portable filenames need to avoid:
-
any other characters but
a-z
,0-9
and-._,=()
-
starting with
-
-
ending with a
.
- uppercase characters (Mac and Windows are case-insensitive).
- being more than 255 characters long.
-
being one of those names
:
com1
,com2
,com3
,com4
,com5
,com6
,com7
,com8
,com9
,lpt1
,lpt2
,lpt3
,lpt4
,lpt5
,lpt6
,lpt7
,lpt8
,lpt9
,con
,nul
,prn
.
Portable file paths need to avoid being more than 260 characters long .
Shell
Unix usually comes with Bash but not always. Popular alternatives include Fish , Dash , tcsh , ksh and zsh .
Writing interoperable shell code can be somewhat achieved by using either:
However this won't work on Windows which uses two other shells:
-
cmd.exe
which comes by default. - Powershell which is more recent, featureful and complex.
cmd.exe
is very different from Bash and has quite many limitations:
-
;
cannot be used to separate statements. However&&
can be used like in Bash. -
CLI flags often use slashes (
/opt
) instead of dashes (-opt
). But Node.js binaries can still use-opt
. - By default the CP866 character set is used instead of UTF-8 . This means Unicode characters won't be displayed properly. Projects like figures and log-symbols can be used to solve this.
-
Escaping
is done differently with
double quotes and
^
. This is partially solved with thechild_process.spawn()
optionwindowsVerbatimArguments
which defaults totrue
whencmd.exe
is used.
When the option shell
of
child_process.spawn()
is true
, /bin/sh
will be used on Unix and cmd.exe
(or the environment
variable ComSpec
) will be used on Windows. false
won't work on Windows
because it does not support shebangs.
As a consequence it is recommended to:
-
keep shell commands to simple
command arguments...
calls, optionally chained with&&
. -
use
execa
to fire those.
Note that
os.userInfo().shell
returns null
on Windows.
Files execution
Shebang
like #!/usr/bin/node
do not work on Windows, where only files ending with .exe
, .com
, .cmd
or .bat
can be directly executed. Portable file execution must either:
-
use an interpreter, e.g.
node file.js
instead of./file.js
. -
use
cross-spawn
(which is included inexeca
).
During file execution the extension can be omitted on Windows if it is listed
in the
PATHEXT
environment
variable, which defaults to .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
. This won't work on
Unix.
The
PATH
environment variable
uses ;
instead of :
as delimiter on Windows. This can be retrieved with
path.delimiter
.
When the option
detached: false
of
child_process.spawn()
is used, the child process will be terminated
when its parent is on Windows, but not on Unix.
When the option
detached: true
is used instead, a new terminal window will appear on Windows unless the option
windowsHide: true
is used (requires Node >= 8.8.0
).
Finally the option
argv0
does not modify process.title
on Windows.
Many of those differences can be solved by using
execa
.
Environment variables
The syntax to reference environment variables
is $VARIABLE
on Unix but %VARIABLE%
on Windows. Also if the variable is
missing, its value will be ''
on Unix but '%VARIABLE%'
on Windows.
To pass environment variables
to a command, it must be prepended with VARIABLE=value ...
on Unix. However on
Windows one must use Set VARIABLE=value
or setx VARIABLE value
as separate
statements.
cross-env
can be used
to both reference and pass environment variables on any OS.
To list the current environment variables
env
must be used on Unix and set
on Windows. However
process.env
will
work on any OS.
Environment variables are case insensitive on Windows but not on Unix.
path-key
can be used to solve this
for the PATH
environment variable.
Finally most environment variables names are OS-specific:
-
SHELL
on Unix isComSpec
on Windows. -
PS1
on Unix isPROMPT
on Windows. -
PWD
on Unix isCD
on Windows. -
HOME
on Unix isHOMEDRIVE
andHOMEPATH
on Windows -
TMPDIR
in Unix isTMP
orTEMP
on Windows. -
USER
on Unix isUSERDOMAIN
andUSERNAME
on Windows. OnlyUSERNAME
is returned byos.userInfo().username
. -
HOSTNAME
on Unix isCOMPUTERNAME
on Windows. Thehostname
CLI command can also be used on any OS.
The project
osenv
can be used to retrieve
OS-specific environment variables names.
Symlinks
Creating symlinks on Windows will most likely fail because it requires a "create symlink" permission which by default is off for non-admins.
Windows cannot create hard links on folders.
Windows (but not Unix) can use junctions
.
fs.symlink()
allows creating these.
Finally some file systems like FAT do not allow symlinks.
As a consequence it is more portable to copy files instead of symlinking them.
File metadata
The
blksize
and
blocks
values of
fs.stat()
are undefined
on Windows. On the other hand the
birthtime
and
birthtimeMs
values are undefined
on Unix.
The
O_NOATIME
flag
of
fs.open()
only works on Linux.
fs.watch()
is not very portable.
For example the option recursive
does not work on Linux.
chokidar
can be used instead.
Permissions
Unix uses POSIX permissions but Windows is based on a combination of:
-
file attributes
like
readonly
,hidden
andsystem
.winattr
andhidefile
can be used to manipulate those. - ACLs (also called NTFS permissions or just "file permissions").
- share permissions.
Node.js does not support Windows permissions.
fs.chmod()
,
fs.stat()
's
mode
,
fs.access()
,
fs.open()
's mode
,
fs.mkdir()
's options.mode
and
process.umask()
only work on
Unix with some minor exceptions:
-
fs.access()
F_OK
works. -
fs.access()
W_OK
checks thereadonly
file attribute on Windows. This is quite limited as it does not check other file attributes nor ACLs. -
The
readonly
file attribute is checked on Windows when thewrite
POSIX permission is missing for any user class (user
,group
orothers
).
On the other hand
fs.open()
works correctly on Windows where flags
are being
translated to Windows-specific file attributes and permissions.
Another difference on Windows: to execute files their extension must be listed
in the environment variable
PATHEXT
.
Finally
fs.lchmod()
is only available on Mac.
Users
Unix users are identified with a
UID
and a
GID
while Windows users
are identified with a
SID
.
Consequently all methods based on
UID
or
GID
fail on Windows:
-
os.userInfo().uid|gid
return-1
. -
fs.stat()
'suid
andgid
return0
. -
The
process
methodsgetuid()
,geteuid()
,getgid()
,getegid()
,setuid()
,seteuid()
,setgid()
,setegid()
,getgroups()
,setgroups()
andinitgroups()
throw an error. -
fs.chown()
does not do anything.
The privileged user is root
on Unix and admin
on Windows. Those are
triggered with different mechanisms. One can use
is-elevated
(and the related
is-admin
and
is-root
) to check it on any OS.
Time resolution
process.htime()
is nanoseconds-precise on Unix but is 100 times less precise on Windows.
OS identification
The main way to identify the current OS is to use
process.platform
(or the identical
os.platform()
).
The
os
core module offers some
finer-grained identification methods but those are rarely needed:
-
os.type()
is similar but slighly more precise. -
os.release()
returns the OS version number, e.g.3.11.0-14-generic
(Linux),18.0.0
(Mac) or10.0.17763
(Windows). -
os.arch()
(or the identicalprocess.arch
) returns the CPU architecture, e.g.arm
orx64
. -
os.endianness()
returns the CPU endianness, i.e.BE
orLE
.
Some projects allow retrieving:
-
getos
: the Linux distribution name. -
osname
(and the relatedwindows-release
andmacos-release
): the OS name and version in a human-friendly way. -
is-windows
: whether current OS is Windows, including through MSYS and Cygwin . -
is-wsl
: whether current OS is Windows though WSL .
Device information
Uptime, memory and CPUs can be retrieved on any OS using
os.uptime()
,
process.uptime()
,
os.freemem()
,
os.totalmem()
,
process.memoryUsage()
,
os.cpus()
and
process.cpuUsage()
.
However:
-
os.cpus()
'stimes.nice
is0
on Windows. -
os.loadavg()
is an array of0
on Windows.
systeminformation
can
be used for more device information.
Networking
os.networkInterfaces()
and
os.hostname()
work on any OS.
However on Windows:
-
sockets / named pipes must be prefixed with
\\.\pipe\
-
TCP servers cannot
listen()
on a file descriptor. -
cluster.schedulingPolicy
cannot beSCHED_RR
and defaults toSCHED_NONE
.
Processes
os.getPriority()
and
os.setPriority()
work on any OS.
Other projects can be used to manipulate processes:
-
ps-list
: list processes.tasklist
andfastlist
can also be used for Windows only. -
pid-from-port
: find processes by port. -
process-exists
: check if a process is running.
Signals
Windows do not use signals like Unix does.
However processes can be terminated using the
taskkill
command. The
taskkill
project can
be used to do it from Node.js.
fkill
builds on it to terminate processes on any OS.
Which signals can be used is OS-specific:
-
process.kill()
andprocess.on(signal)
can only use the following signals on Windows :SIGINT
,SIGTERM
,SIGKILL
and0
. -
process.on(signal)
(but notprocess.kill()
) can be used on Windows withSIGWINCH
,SIGILL
,SIGABRT
,SIGFPE
,SIGSEGV
,SIGHUP
(closingcmd.exe
) andSIGBREAK
(CTRL-BREAK
oncmd.exe
). -
SIGPOLL
,SIGPWR
andSIGUNUSED
can only be used on Linux. -
SIGINFO
can only be used on Mac.
Each signal has both an OS-agnostic name and an OS-specific integer constant.
process.kill()
can use either. It is possible to convert between both using
os.constants.signals
.
However it is more portable to use signal names instead of integer constants.
Errors
Node errors can be identified with either:
-
error.code
: an OS-agnostic string (more portable). -
error.errno
: an OS-specific integer constant.
It is possible to convert between both using
os.constants.errno
and
util.getSystemErrorName
.
Most available error.code
start with E
and
can be fired on any OS. However few
start with W
and can only be fired on Windows.
Summary
-
instead of
nvm
usenvm-windows
andnpm-windows-upgrade
on Windows. - do not rely on OS system calls or core utilities without using an abstraction layer.
- test of each OS with virtual machines and continuous integration .
-
run
npm install -g windows-build-tools
on Windows when installing C/C++ addons . -
use
os
Node.js core module when needed. -
use
os.EOL
when reading from or writing to a file,\n
otherwise. -
use
path.normalize()
when writing a file path to a terminal or file. Otherwise use Unix paths (slashes). -
only use lowercase
a-z
,0-9
and-._,=()
in filenames. - avoid paths longer than 260 characters.
-
fire shell commands with
execa
. -
keep shell commands to simple
command arguments...
calls, optionally chained with&&
. -
reference and pass environment variables to shell commands using
cross-env
. - copy files instead of symlinking them.
-
use
chokidar
to watch files. -
avoid
blksize
,blocks
,mode
,uid
,gid
,birthtime
andbirthtimeMs
returned byfs.stat()
. -
avoid
fs.chmod()
,fs.access()
(exceptF_OK
),fs.open()
'smode
,fs.mkdir()
'soptions.mode
andprocess.umask()
. -
avoid
os.userInfo().uid|gid
,fs.chown()
and theprocess
methodsgetuid()
,geteuid()
,getgid()
,getegid()
,setuid()
,seteuid()
,setgid()
,setegid()
,getgroups()
,setgroups()
andinitgroups()
. -
assume
process.hrtime()
is100ns
-precise. -
when using OS-specific logic identify the current OS with
process.platform
. -
avoid
os.cpus()
times.nice
andos.loadavg()
. -
use
systeminformation
to retrieve any device information not available through theos
core module. -
sockets / named pipes must be prefixed with
\\.\pipe\
on Windows. -
TCP servers should not
listen()
on a file descriptor. -
use
ps-list
,pid-from-port
andprocess-exists
to find and check for processes. -
use
fkill
to terminate processes. -
only use
process.kill()
with the following signals:SIGINT
,SIGTERM
,SIGKILL
and0
. -
only use
process.on(signal)
with the following signals:SIGINT
,SIGTERM
,SIGKILL
,0
,SIGWINCH
,SIGILL
,SIGABRT
,SIGFPE
,SIGSEGV
,SIGHUP
andSIGBREAK
. -
prefer
error.code
overerror.errno
.
Further reading
Recommend
-
9
Introductory Guide to Debug Node.js Apps with Built-in or VS Code DebuggerDecember 14th 2020 new story
-
4
Node.js Server Monitoring: A How to Guide Christoph Leitner April 5, 2021 Developer Tips, Tricks & ResourcesNode.js is one of the most popular Javascript frameworks in 2021. W...
-
2
Intro Guide to the GraphQL API using Node.JS and ApolloAugust 10th 2021 new story2
-
7
@noaahhhNuh Y. An computer scientist, tech enthusiast, a reader and writer for opensourceHi, in this article, I am going to create a portable BSD...
-
7
A Guide to CORS in Node.js with Express Reading Time: 4 minutes Introduction Node.js is an open-source and cro...
-
3
A Comprehensive Guide To Error Handling In Node.jsIf you've been writing anything more than "Hello world" programs, you are probably familiar with the concept of errors in programming. They are mistakes in your code, often referred to as "bug...
-
14
A Guide to Load Testing Node.js APIs with Artillery This article was originally published on the AppSignal Blog o...
-
6
<?xml encoding="utf-8" ??>Introduction What Is Node.js? Node.js is both open source and free, and is used for a variety of purposes. To name a few, Node.js is very efficient for servin...
-
11
The Ultimate Guide to Node.js Internationalization (I18n) ...
-
3
Not FoundYou just hit a route that doesn't exist... the sadness.LoginRadius empowers businesses to deliver a delightful customer experience and win customer trust. Using the LoginRadius Identity...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK