Node.js allows defining scripts in
package.json
. This feature often constitutes a
major component of a developer’s workflow. For example, developers can define procedures such as
building, testing, and deployment as scripts in
package.json
. If the project is
intended to be cross-platform, the scripts should also be cross-platform.
In this post, we discuss guidelines and tips on how to create Node.js scripts that work on both Windows and POSIX systems (e.g., GNU/Linux, MacOS). The central tenet is using cross-platform Node.js features instead of POSIX/Windows-specific commands.
Use System Utility-Like npm Packages
POSIX/Windows-specific commands include cp
and sed
on a POSIX system, or copy
and md
on
Windows. We should avoid using them directly. Instead, there are Node.js packages that provide
command-line interface (CLI) utilities that perform what these system-level commands do. The table
below contains an incomplete list of common POSIX/Windows commands and npm packages that perform
similar tasks. All packages listed in the Node.js column are popularly used.
POSIX | Windows | Node.js |
---|---|---|
cp | copy | ncp |
mv | ren , rename | move-file-cli |
rm , rmdir | del , rd | rimraf |
sed , awk | N/A | replace-in-files-cli |
cd , mkdir , echo | cd , mkdir , echo | N/A |
For example, let’s say we have a project that requires copying a file to the dist/
directory in
the build step. Instead of using cp
or copy
in package.json
like:
{
"scripts": {
"build": "cp raw-js.js dist/raw-js.js"
}
}
use the ncp package (npm install --save-dev ncp
) as listed in the table above:
{
"scripts": {
"build": "ncp raw-js.js dist/raw-js.js"
}
}
The last row of the table lists two commands that are the same on POSIX systems and Windows. In this case, simply use them directly. However, there are subtle differences between them on different platforms; make sure you use them in a cross-platform way!
Use node --eval
Sometimes, we don’t have a popular npm package for some specific tasks, or we simply prefer not
to introduce an extra dependency like the previous section. In some cases, we can use node --eval
followed by cross-platform Node.js code.
For example, there is no portable way to create a directory recursively with mkdir
. The POSIX
command that recursively creates a directory, mkdir -p dir/subdir
, would create two directories
named -p
and dir/subdir
on Windows. In package.json
, we can replace the POSIX command mkdir -p dir/subdir
with
node --eval 'fs.mkdirSync("dir/subdir", {recursive: true})'
Save the above command as a script in the scripts
field of package.json
and use it instead of
mkdir -p
whenever we need to recursively create dir/subdir
:
{
"scripts": {
"mkdir": "node --eval 'fs.mkdirSync(\"dir/subdir\", {recursive: true})'",
"build": "npm run mkdir && echo done"
}
}
Write Standalone Scripts for Complex Tasks
If the tasks in a script are complex, it is usually better to write a cross-platform standalone
Node.js script and execute this script in the scripts
field specified in package.json
. The
standalone script can use any cross-platform Node.js modules. Extracting complex logic into a
standalone script also makes it easier to maintain and troubleshoot.
We can execute the script with the node
executable or a TypeScript executor, such as ts-node
and tsx. For example, if the standalone script is build.js
that builds the package, we can add
the following to package.json
:
{
"scripts": {
"build": "node build.js"
}
}
In case the script build.ts
is written in TypeScript, after running npm install --save-dev tsx
,
we add:
{
"scripts": {
"build": "tsx build.ts"
}
}
(Or use ts-node
in place of tsx
.)