netcdf4-wasm
v0.2.0
Published
NetCDF4 library compiled to WebAssembly with TypeScript bindings
Maintainers
Readme
netcdf4-wasm
NetCDF4 library compiled to WebAssembly with JavaScript/TypeScript bindings.
Overview
This project provides a complete WebAssembly port of the NetCDF4 C library, enabling NetCDF file operations in browser and Node.js environments. It includes:
- Complete NetCDF4 C library compiled to WASM using Emscripten
- High-level TypeScript/JavaScript API
- Support for reading and writing NetCDF4 files
- Comprehensive test suite
Installation
NPM/Yarn (Node.js and bundlers)
npm install netcdf4-wasmCDN (Browser)
<!-- Load WASM module first -->
<script src="https://unpkg.com/netcdf4-wasm@latest/dist/netcdf4-module.js"></script>
<!-- Then UMD build (recommended for browser) -->
<script src="https://unpkg.com/netcdf4-wasm@latest/dist/netcdf4-wasm.umd.min.js"></script>
<!-- ES Module build -->
<script type="module">
import { Dataset } from "https://unpkg.com/netcdf4-wasm@latest/dist/netcdf4-wasm.esm.js";
</script>Prerequisites
For building from source, you'll need:
- Emscripten SDK
- CMake
- Make
- wget or curl
Check dependencies:
npm run check-depsInstall Emscripten locally:
npm run install-emscriptenUsage
The JavaScript API is modeled closely on the netcdf4-python API.
Basic Example
ES Modules (Node.js, bundlers)
import { Dataset } from "netcdf4-wasm";
// or: import { NetCDF4 } from 'netcdf4-wasm';
async function example() {
// Create a new NetCDF file (similar to Python netCDF4.Dataset)
const nc = await Dataset("example.nc", "w", { format: "NETCDF4" });
// or: const nc = await NetCDF4.Dataset('example.nc', 'w', { format: 'NETCDF4' });
// Create dimensions
const lat = await nc.createDimension("lat", 73);
const lon = await nc.createDimension("lon", 144);
const time = await nc.createDimension("time", null); // unlimited dimension
// Create variables
const temp = await nc.createVariable("temperature", "f4", [
"time",
"lat",
"lon",
]);
const times = await nc.createVariable("time", "f8", ["time"]);
// Set variable attributes
temp.units = "Kelvin";
temp.long_name = "surface temperature";
times.units = "hours since 0001-01-01 00:00:00.0";
times.calendar = "gregorian";
// Set global attributes
nc.setAttr("description", "bogus example script");
nc.setAttr("history", "Created " + new Date().toISOString());
nc.setAttr("source", "netCDF4-wasm example");
// Write data
const tempData = new Float64Array(73 * 144);
tempData.fill(288.0); // Fill with 288K
await temp.setValue(tempData);
// Close the file
await nc.close();
}Browser UMD (Global variable)
<!-- Load WASM module first -->
<script src="https://unpkg.com/netcdf4-wasm@latest/dist/netcdf4-module.js"></script>
<!-- Then UMD build -->
<script src="https://unpkg.com/netcdf4-wasm@latest/dist/netcdf4-wasm.umd.min.js"></script>
<script>
async function example() {
// Access via global NetCDF4WASM object
const { Dataset } = NetCDF4WASM;
// Create a new NetCDF file
const nc = await Dataset(new ArrayBuffer(0), "w", { format: "NETCDF4" });
// Create dimensions
const lat = await nc.createDimension("lat", 73);
const lon = await nc.createDimension("lon", 144);
const time = await nc.createDimension("time", null); // unlimited dimension
// Create variables
const temp = await nc.createVariable("temperature", "f4", [
"time",
"lat",
"lon",
]);
// Set variable attributes
temp.units = "Kelvin";
temp.long_name = "surface temperature";
// Set global attributes
nc.setAttr("description", "Created with UMD build");
// Write data
const tempData = new Float64Array(73 * 144);
tempData.fill(288.0); // Fill with 288K
await temp.setValue(tempData);
// Export as blob for download
const blob = await nc.toBlob();
await nc.close();
// Create download link
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'example.nc';
a.click();
}
</script>Reading Files
import { Dataset } from "netcdf4-wasm";
async function readExample() {
// Open existing file for reading
const nc = await Dataset("data.nc", "r");
// Access dimensions
console.log("Dimensions:", Object.keys(nc.dimensions));
console.log("Time dimension size:", nc.dimensions.time.size);
// Access variables
console.log("Variables:", Object.keys(nc.variables));
const temp = nc.variables.temperature;
// Read variable attributes
console.log("Temperature units:", temp.units);
console.log("Temperature long name:", temp.long_name);
// Read data
const data = await temp.getValue();
console.log("Temperature data shape:", data.length);
console.log("First few values:", data.slice(0, 5));
// Access global attributes
console.log("Global attributes:", nc.attrs());
console.log("Description:", nc.getAttr("description"));
await nc.close();
}Alternative Constructor (Direct Instantiation)
import { NetCDF4 } from "netcdf4-wasm";
async function directExample() {
// Direct instantiation (requires manual initialization)
const nc = new NetCDF4("example.nc", "w", { format: "NETCDF4" });
await nc.initialize();
// Use same API as above...
const lat = await nc.createDimension("lat", 10);
const temp = await nc.createVariable("temperature", "f8", ["lat"]);
await nc.close();
}Working with Groups
async function groupExample() {
const nc = await Dataset("grouped.nc", "w", { format: "NETCDF4" });
// Create a group
const forecasts = nc.createGroup("forecasts");
// Create dimensions and variables in the group
const time = await forecasts.createDimension("time", 24);
const temp = await forecasts.createVariable("temperature", "f4", ["time"]);
// Set group attributes
forecasts.setAttr("description", "Forecast data");
await nc.close();
}API Reference
The API closely follows netcdf4-python conventions for ease of use by scientists familiar with Python.
Classes
NetCDF4
Main class for NetCDF file operations, similar to netCDF4.Dataset in Python.
Constructor
new NetCDF4(filename?: string, mode?: string, options?: NetCDF4WasmOptions)Static Methods
NetCDF4.Dataset(filename: string, mode?: string, options?: object): Promise<NetCDF4>- Factory method (Python-like)
Module Functions
Dataset(filename: string, mode?: string, options?: object): Promise<NetCDF4>- Convenience function (import directly)
Properties
dimensions: {[name: string]: Dimension}- Dictionary of dimensionsvariables: {[name: string]: Variable}- Dictionary of variablesgroups: {[name: string]: Group}- Dictionary of groupsfile_format: string- File format (e.g., 'NETCDF4')filepath: string- Path to the fileisopen: boolean- Whether file is currently open
Methods
File Operations
initialize(): Promise<void>- Initialize the WASM moduleclose(): Promise<void>- Close the filesync(): Promise<void>- Flush data to disk
Structure Definition
createDimension(name: string, size: number): Promise<Dimension>- Create dimensioncreateVariable(name: string, datatype: string, dimensions: string[], options?: object): Promise<Variable>- Create variablecreateGroup(name: string): Group- Create hierarchical group
Attribute Access
setAttr(name: string, value: any): void- Set global attributegetAttr(name: string): any- Get global attributeattrs(): string[]- List all global attributes
Variable
Represents a NetCDF variable, similar to Python's Variable class.
Properties
name: string- Variable namedatatype: string- Data type ('f4', 'f8', 'i4', etc.)dimensions: string[]- Dimension namesunits: string- Units attribute (convenience property)long_name: string- Long name attribute (convenience property)standard_name: string- Standard name attribute (convenience property)
Methods
getValue(): Promise<Float64Array>- Read variable datasetValue(data: Float64Array): Promise<void>- Write variable datasetAttr(name: string, value: any): void- Set variable attributegetAttr(name: string): any- Get variable attributeattrs(): string[]- List variable attributes
Dimension
Represents a NetCDF dimension.
Properties
name: string- Dimension namesize: number- Dimension sizeisUnlimited: boolean- Whether dimension is unlimited
Methods
__len__(): number- Get dimension size (Python-like)
Constants
The NC_CONSTANTS object provides NetCDF constants:
NC_CONSTANTS.NC_NOERR; // No error
NC_CONSTANTS.NC_NOWRITE; // Read-only access
NC_CONSTANTS.NC_WRITE; // Write access
NC_CONSTANTS.NC_CLOBBER; // Overwrite existing file
NC_CONSTANTS.NC_NETCDF4; // NetCDF4 format
NC_CONSTANTS.NC_DOUBLE; // Double data type
NC_CONSTANTS.NC_UNLIMITED; // Unlimited dimensionBuilding
Install dependencies
npm installCheck build dependencies
npm run check-depsBuild the project
npm run buildThis will:
- Download and compile zlib, HDF5, and NetCDF4 C libraries
- Create the WASM module with Emscripten
- Compile TypeScript bindings
- Build multiple output formats:
- UMD build for browsers (
dist/netcdf4-wasm.umd.js) - UMD minified for production (
dist/netcdf4-wasm.umd.min.js) - ES modules for bundlers (
dist/netcdf4-wasm.esm.js) - CommonJS for Node.js (
dist/index.js)
- UMD build for browsers (
Clean build artifacts
npm run cleanTesting
Run tests:
npm testRun tests with coverage:
npm run test:coverageWatch mode:
npm run test:watchDevelopment
Project Structure
netcdf4-wasm/
├── src/ # TypeScript source code
│ ├── index.ts # Main API exports
│ ├── types.ts # Type definitions
│ ├── constants.ts # NetCDF constants
│ ├── netcdf4.ts # Main NetCDF4 class
│ ├── group.ts # Group class
│ ├── variable.ts # Variable class
│ ├── dimension.ts # Dimension class
│ ├── wasm-module.ts # WASM module loader
│ └── __tests__/ # Test files
├── scripts/ # Build scripts
│ ├── build-wasm.sh # Main WASM build script
│ ├── check-dependencies.sh
│ └── install-emscripten.sh
├── bindings/ # WASM bindings
│ ├── pre.js # Pre-run JavaScript
│ └── post.js # Post-run JavaScript
├── build/ # Build artifacts (generated)
├── dist/ # Distribution files (generated)
│ ├── index.js # CommonJS build
│ ├── netcdf4-wasm.umd.js # UMD build for browsers
│ ├── netcdf4-wasm.umd.min.js # UMD minified build
│ └── netcdf4-wasm.esm.js # ES modules build
├── rollup.config.js # Rollup bundler configuration
├── tsconfig.rollup.json # TypeScript config for Rollup
└── package.jsonContributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Run the test suite
- Submit a pull request
Continuous Integration & Releasing
GitHub Actions workflows (.github/workflows/):
| Workflow | Trigger | What it does |
|----------|---------|--------------|
| ci.yml | every push to main / PR to main | Runs the test suite (Node 18/20/22), typecheck, and the JS/UMD bundle build. No WASM needed (tests run against a mock module). |
| wasm-build.yml | PRs/pushes that touch the build scripts, bindings, or C wrapper; manual | Builds the full emscripten WASM module (zlib + HDF5 + netcdf-c) and uploads it as an artifact. Caches the SDK and compiled libs so it only does the heavy work when build inputs change. |
| publish.yml | pushing a v* tag | Verifies the tag matches package.json, runs tests, builds WASM + JS + bundles, and publishes to npm with provenance. |
Cutting a release
Bump the version in
package.jsonand commit.Tag and push:
git tag v0.1.2 git push origin v0.1.2
The publish.yml workflow builds everything and publishes. It fails fast if the
tag (v0.1.2) does not match the package.json version.
Publishing uses npm Trusted Publishing (OIDC) — no NPM_TOKEN secret, and it
is not affected by account 2FA.
Setup required (once): on npmjs.com, configure a Trusted Publisher for the
netcdf4-wasmpackage linked to this repository and thepublish.ymlworkflow (Package → Settings → Trusted Publishers). The workflow already grants the requiredid-token: writepermission.
License
MIT License - see LICENSE file for details.
NetCDF4 Documentation
For more information about NetCDF4, visit: https://docs.unidata.ucar.edu/netcdf-c/current/
Troubleshooting
WASM Module Not Found
Make sure the WASM files are properly built and accessible:
npm run build:wasmEmscripten Not Found
Install Emscripten:
npm run install-emscripten
source build/emsdk/emsdk_env.shMemory Issues
If you encounter memory-related errors, try increasing the initial memory:
const netcdf = new NetCDF4({ memoryInitialPages: 512 });