Skip to main content

NodeJS SEA

NodeJS "Single Executable Applications"1 are standalone CLI tools that embed bundled scripts in a special standalone copy of the NodeJS binary.

SheetJS is a JavaScript library for reading and writing data from spreadsheets.

This demo uses NodeJS SEA and SheetJS to create a standalone CLI tool for parsing spreadsheets and generating CSV rows.

It is strongly recommended to install NodeJS on systems using SheetJS libraries in command-line tools. This workaround should only be considered if a standalone binary is considered desirable.

NodeJS SEA support is considered experimental.

Great open source software grows with user tests and reports. Any issues should be reported to the NodeJS single-executable project for further diagnosis.

Integration Details

The SheetJS NodeJS module can be required from NodeJS SEA base scripts.

NodeJS SEA does not support ECMAScript Modules!

A CommonJS script is conveniently included in the SheetJS NodeJS module package.

At a high level, single-executable applications are constructed in four steps:

  1. Pre-process an existing NodeJS script, creating a SEA bundle.

  2. Copy the NodeJS binary and remove any signatures.

  3. Inject the SEA bundle into the unsigned NodeJS binary.

  4. Re-sign the binary.

macOS and Windows enforce digital signatures. Both operating systems will warn users if a signed program is modified.

Existing signatures should be removed before injecting the SEA bundle. After injecting the SEA bundle, the binary should be resigned.

Script Requirements

Scripts that exclusively use SheetJS libraries and NodeJS built-in modules can be bundled using NodeJS SEA. Due to limitations in the SEA bundler, a special require function must be created manually:

const { createRequire } = require('node:module');
require = createRequire(__filename);
const { readFile, utils } = require("xlsx");

For example, the following script accepts one command line argument, parses the specified file using the SheetJS readFile method2, generates CSV text from the first worksheet using sheet_to_csv3, and prints to terminal:

sheet2csv.js
// For NodeJS SEA, the CommonJS `require` must be used
const { createRequire } = require('node:module');
require = createRequire(__filename);
const { readFile, utils } = require("xlsx");

// argv[2] is the first argument to the script
const filename = process.argv[2];

// read file
const wb = readFile(filename);

// generate CSV of first sheet
const ws = wb.Sheets[wb.SheetNames[0]];
const csv = utils.sheet_to_csv(ws);

// print to terminal
console.log(csv);

SEA Bundles

SEA Bundles are blobs that represent the script and supporting libraries.

Configuration

SEA configuration is specified using a special JSON file. Assuming no special assets are bundled with the script, there are two relevant fields:

  • main is a relative path to the entry script.
  • output is a relative path to the output file (typically ending in .blob)

For example, the following configuration specifies sheet2csv.js as the entry script and sheet2csv.blob as the output blob:

sheet2csv.json
{
"main": "sheet2csv.js",
"output": "sheet2csv.blob"
}

Construction

The main node program, with the command-line flag --experimental-sea-config, will generate a SEA bundle:

node --experimental-sea-config sheet2csv.json

The bundle will be written to the file specified in the output field of the SEA configuration file.

Injection

A special postject utility is used to add the SEA bundle to the NodeJS binary. The specific command depends on the operating system.

On macOS, assuming the copy of the NodeJS binary is named sheet2csv and the SEA bundle is named sheet2csv.blob, the following command injects the bundle:

npx -y postject --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --macho-segment-name NODE_SEA sheet2csv NODE_SEA_BLOB sheet2csv.blob

Complete Example

Tested Deployments

This demo was tested in the following deployments:

ArchitectureNodeJSDate
darwin-x6420.11.12024-03-17
win10-x6420.12.02024-03-26
linux-x6420.11.12024-03-18
  1. Ensure NodeJS version 20 or later is installed.

To display the current version, run the following command:

node --version

The major version number starts after the v and ends before the first .

If the version number is 19 or earlier, upgrade NodeJS before proceeding.

Project Setup

  1. Create a new project folder:
mkdir sheetjs-sea
cd sheetjs-sea
npm init -y
  1. Save the contents of the sheet2csv.js code block to sheet2csv.js in the project folder.

  2. Install the SheetJS dependency:

yarn add https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz

Script Test

Before building the standalone app, the base script should be tested using the local NodeJS platform.

  1. Download the test file https://docs.sheetjs.com/pres.numbers:
curl -o pres.numbers https://docs.sheetjs.com/pres.numbers
  1. Run the script and pass pres.numbers as the first argument:
node sheet2csv.js pres.numbers

The script should display CSV contents from the first sheet:

Name,Index
Bill Clinton,42
GeorgeW Bush,43
Barack Obama,44
Donald Trump,45
Joseph Biden,46

SEA Bundle

  1. Save the contents of the sheet2csv.json code block to sheet2csv.json in the project folder.

  2. Generate the SEA bundle:

node --experimental-sea-config sheet2csv.json

SEA Injection

  1. Create a local copy of the NodeJS binary:
cp `which node` sheet2csv
  1. Remove the code signature.
codesign --remove-signature ./sheet2csv
  1. Inject the SEA bundle.
npx -y postject --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --macho-segment-name NODE_SEA sheet2csv NODE_SEA_BLOB sheet2csv.blob
  1. Resign the binary. The following command performs macOS ad-hoc signing:
codesign -s - ./sheet2csv

Standalone Test

  1. Run the command and pass pres.numbers as the first argument:
./sheet2csv pres.numbers

The program should display the same CSV contents as the script (from step 5)

  1. Validate the binary signature:
codesign -dv ./sheet2csv

Inspecting the output, the following line confirms ad-hoc signing was used:

Signature=adhoc

Footnotes

  1. See "Single Executable Applications" in the NodeJS documentation.

  2. See readFile in "Reading Files"

  3. See sheet_to_csv in "CSV and Text"