English | 简体中文
ASAR is a simple extensive archive format. It concatenates all files together without compression
(like tar) while having random access support.
- Support random access
- Use JSON to store file information
- Very easy to write a parser
- Store the contents of duplicated files only once
This module requires Node 22.12.0 or later.
npm i -g asarproThe package installs the asarpro command.
$ asarpro --help
Usage: asarpro [options] [command]
Commands:
pack|p <dir> <output>
create asar archive
list|l <archive>
list files of asar archive
extract-file|ef <archive> <filename>
extract one file from archive
extract|e <archive> <dest>
extract archive
replace|r <archive> <filename> <source>
replace a file inside archive with contents of source file
Options:
-h, --help output usage information
-V, --version output the version number
asarpro pack app app.asar
# leave files matching a glob unpacked (written next to the archive, in app.asar.unpacked/)
asarpro pack app app.asar --unpack "*.node"
# exclude hidden files
asarpro pack app app.asar --exclude-hidden
# control the order files are stored in (can improve app startup)
asarpro pack app app.asar --ordering order.txtGiven:
app
(a) ├── x1
(b) ├── x2
(c) ├── y3
(d) │ ├── x1
(e) │ └── z1
(f) │ └── x2
(g) └── z4
(h) └── w1
Exclude: a, b
asarpro pack app app.asar --unpack-dir "{x1,x2}"Exclude: a, b, d, f
asarpro pack app app.asar --unpack-dir "**/{x1,x2}"Exclude: a, b, d, f, h
asarpro pack app app.asar --unpack-dir "{**/x1,**/x2,z4/w1}"asarpro list app.asar
# also show whether each entry is packed or unpacked
asarpro list --is-pack app.asarpack : /a.txt
pack : /sub
unpack : /sub/b.bin
# extract the whole archive into ./dest
asarpro extract app.asar dest
# extract a single file, written to the current directory using its basename
asarpro extract-file app.asar path/inside/b.bin # -> ./b.binasarpro replace app.asar path/inside/archive.js new-file.jsThe new contents are appended to the archive and the entry is re-pointed at
them, so the rest of the archive is left untouched (including entries that
share contents through deduplication). Old bytes stay as dead space until the
archive is repacked with asarpro pack.
For full API usage, see the API documentation.
| Function | Description |
|---|---|
createPackage(src, dest) |
Pack directory src into archive dest |
createPackageWithOptions(src, dest, options) |
Pack with unpack, unpackDir, ordering, pattern, transform, dot / globOptions options |
createPackageFromFiles(src, dest, filenames, metadata?) |
Pack an explicit list of files |
createPackageFromStreams(dest, streams) |
Pack from stream descriptors (AsarStreamType) |
listPackage(archive, options) |
List entries; { isPack: true } also marks each as packed/unpacked |
statFile(archive, filename, followLinks?) |
Stat one entry |
extractFile(archive, filename, followLinks?) |
Read one entry as a Buffer |
extractAll(archive, dest) |
Extract all entries into dest |
replaceFile(archive, filename, source) |
Replace one entry in place |
getRawHeader(archive) |
Read the raw header |
uncache(archive) / uncacheAll() |
Drop cached archive state |
createPackage* are async, the rest are synchronous.
import { createPackage } from 'asarpro';
const src = 'some/path/';
const dest = 'name.asar';
await createPackage(src, dest);
console.log('done.');Please note that there is currently no error handling provided!
import { replaceFile } from 'asarpro';
replaceFile('app.asar', 'path/inside/archive.js', 'new-file.js');The new contents are appended to the end of the data section and the entry is
re-pointed at them, so every other entry keeps its offset — including entries
that share contents through deduplication. Old bytes stay as dead space until
the archive is repacked with createPackage.
Entries that were unpacked to disk are overwritten in place under
app.asar.unpacked instead.
Throws if filename does not exist, refers to a directory, or source cannot
be read. The archive is rewritten through a temporary file and renamed, so it
never leaves a half-written archive behind.
Files with identical contents are stored once and shared: the first copy is
written into the archive and every other copy's header entry points at that same
offset. Nothing changes for readers — each file still has its own entry, size,
integrity hash, and executable bit — but archives with duplicated contents (a
common shape for bundled node_modules) get smaller and pack faster, since the
redundant bytes are never written.
Unpacked files (unpack / unpackDir) are always written out in full, because
they live on disk outside the archive.
You can pass in a transform option, that is a function, which either returns
nothing, or a stream.Transform. The latter will be used on files that will be
in the .asar file to transform them (e.g. compress).
import { createPackageWithOptions } from 'asarpro';
const src = 'some/path/';
const dest = 'name.asar';
function transform (filename) {
return new CustomTransformStream()
}
await createPackageWithOptions(src, dest, { transform: transform });
console.log('done.');Asar uses Pickle to safely serialize binary value to file.
The format of asar is very flat:
| UInt32: header_size | String: header | Bytes: file1 | ... | Bytes: file42 |The header_size and header are serialized with Pickle class, and
header_size's Pickle object is 8 bytes.
The header is a JSON string, and the header_size is the size of header's
Pickle object.
Structure of header is something like this:
{
"files": {
"tmp": {
"files": {}
},
"usr" : {
"files": {
"bin": {
"files": {
"ls": {
"offset": "0",
"size": 100,
"executable": true,
"integrity": {
"algorithm": "SHA256",
"hash": "...",
"blockSize": 1024,
"blocks": ["...", "..."]
}
},
"cd": {
"offset": "100",
"size": 100,
"executable": true,
"integrity": {
"algorithm": "SHA256",
"hash": "...",
"blockSize": 1024,
"blocks": ["...", "..."]
}
}
}
}
}
},
"etc": {
"files": {
"hosts": {
"offset": "200",
"size": 32,
"integrity": {
"algorithm": "SHA256",
"hash": "...",
"blockSize": 1024,
"blocks": ["...", "..."]
}
}
}
}
}
}offset and size records the information to read the file from archive, the
offset starts from 0 so you have to manually add the size of header_size and
header to the offset to get the real offset of the file.
Files with identical contents share a single copy in the archive, so more than
one entry can point at the same offset.
offset is a UINT64 number represented in string, because there is no way to
precisely represent UINT64 in JavaScript Number. size is a JavaScript
Number that is no larger than Number.MAX_SAFE_INTEGER, which has a value of
9007199254740991 and is about 8PB in size. We didn't store size in UINT64
because file size in Node.js is represented as Number and it is not safe to
convert Number to UINT64.
integrity is an object consisting of a few keys:
- A hashing
algorithm, currently onlySHA256is supported. - A hex encoded
hashvalue representing the hash of the entire file. - An array of hex encoded hashes for the
blocksof the file (i.e. for a blockSize of 4KB, this array contains the hash of every block if you split the file into N 4KB blocks). - A integer value
blockSizerepresenting the size in bytes of each block in theblockshashes above.