Skip to content

Commit eac8775

Browse files
authored
Merge pull request #28 from nodejs/vfs-requirements
Document the complete list of requirements for the VFS
2 parents 49c5d2f + 2c7aec3 commit eac8775

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Table of Contents
1010

1111
- [Existing SEA Solutions](./docs/existing-solutions.md)
1212
- [Production Node.js CLIs](./docs/production-nodejs-clis.md)
13+
- Requirements
14+
- [Virtual File System](./docs/virtual-file-system-requirements.md)
1315

1416
Blog
1517
----
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
Virtual File System Requirements
2+
================================
3+
4+
This document aims to list all the requirements of the Virtual File System.
5+
6+
# Supported
7+
8+
## Random access reads
9+
10+
The VFS must support random access reads just like any other real file system,
11+
so that the read operations can be at least as fast as reading files from the
12+
real file system.
13+
14+
## Symbolic links
15+
16+
This is critical for applications that want to use packages like [dugite][] that
17+
attempt to download [Git executables][] that contain symlinks. Since
18+
Electron's [ASAR][] does not support symlinks, including [dugite][] as a
19+
dependency in an Electron app would expand every symlink into individual files,
20+
thus significantly increase the package size which is not nice.
21+
22+
## Preserve the executable bit of the file permissions
23+
24+
It is important to preserve the executable bit of the file permissions, so that
25+
it is possible for the single-executable to be able to execute only executable
26+
files. Other than that, all the bundled files would be readable and none will be
27+
writable.
28+
29+
## Preserve file-hierarchy information
30+
31+
A filesystem is incomplete without this because there's no way for the
32+
single-executable to be able to access nested file paths.
33+
34+
## No interference with valid paths in the file system
35+
36+
If the bundled files in the VFS correspond to certain paths that already exist
37+
in the real file system, that will break certain use-cases, so it should use
38+
such paths that cannot be used by existing files.
39+
40+
Pkg uses [`/snapshot`](https://github.com/vercel/pkg#snapshot-filesystem) as the
41+
prefix for all the embedded files. This is confusing if `/snapshot` is an
42+
existing directory on the file system. Docker workflows routinely copy files to,
43+
and run things at, the root of the filesystem, so following that approach too
44+
would run into the same problem.
45+
46+
Boxednode allows users to enter a [namespace](https://github.com/mongodb-js/boxednode/blob/6326e3277469e8cfe593616a0ed152600a5f9045/README.md?plain=1#L69-L72)
47+
and uses it like so:
48+
```js
49+
// Specify the entrypoint target name. If this is 'foo', then the resulting
50+
// binary will be able to load the source file as 'require("foo/foo")'.
51+
// This defaults to the basename of sourceFile, e.g. 'bar' for '/path/bar.js'.
52+
namespace?: string;
53+
```
54+
55+
A possible solution is to use the single executable path as the base path for
56+
the files in the VFS, i.e., if the executable has `/a/b/sea` as the path and the
57+
VFS contains a file named `file.txt`, it would be accessible by the application
58+
using `/a/b/sea/file.txt`. This approach is similar to how Electron's [ASAR][]
59+
works, i.e., if the application asar is placed in `/a/b/app.asar`, the
60+
embedded `file.txt` file would use `/a/b/app.asar/file.txt` as the path.
61+
62+
## Globbing
63+
64+
`fs.statSync(process.execPath).isDirectory()` will return `true` and
65+
`fs.statSync(process.execPath).isFile()` will return `false`. That way, if code
66+
within the single-executable does naive globbing using an off-the-shelf glob
67+
library, paths inside the VFS would also get picked up.
68+
69+
## Accept file paths in the VFS as arguments
70+
71+
If a single-executable formatter is run with an argument that is a path to a
72+
file inside the VFS, it should be able to use the `fs` APIs to read, format and
73+
print the formatted contents to `stdout`.
74+
75+
## Cross-platform tooling
76+
77+
The tooling required for archiving / extracting files into / from the VFS must
78+
be available on all the [platforms supported by Node.js][].
79+
80+
## File path contents
81+
82+
Should not limit the size or the character contents of the file paths to stay as
83+
close as possible to what a real file system provides.
84+
85+
## Case Sensitive
86+
87+
From Yarn's experience with zip, forcing case sensitivity within the archives
88+
didn't break anything, improved consistency. By contrast, making the code case
89+
insensitive would have increased the complexity, worsened the runtime
90+
performance, increased the attack surface, for a use case that virtually no-one
91+
cares about. Hence, the paths in the VFS will be case sensitive.
92+
93+
## Dynamic imports and requires
94+
95+
`require(require.resolve('./file.js'))` should work for files that are on the
96+
real file system and the VFS.
97+
98+
## VFS path manipulation as strings and URL objects
99+
100+
If someone proposes that the VFS exist at a `vfs-file://` prefix, then this
101+
might become an issue. `fs` APIs accept `URL` objects, but this means code in
102+
(transitive) dependencies which assumes all native paths are strings may fail
103+
when passed `URL` objects. Perhaps a (transitive) dependency uses
104+
`require.resolve()`.
105+
106+
Using something like `vfs-file://` might be a potential solution for placing the
107+
VFS contents somewhere that has no interference with valid paths in the file
108+
system.
109+
110+
## Interaction with Native Addons
111+
112+
TODO: Still under discussion in https://github.com/nodejs/single-executable/discussions/29.
113+
114+
# Not supported
115+
116+
## No need for supporting write operations
117+
118+
Since the VFS is going to be embedded into the single-executable and also
119+
protected by codesigning, making changes to the contents of the VFS should
120+
invalidate the signature and crash the application if run again. Hence, no write
121+
operation needs to be supported.
122+
123+
# Optionally support
124+
125+
## Increase locality of related files
126+
127+
For performance reasons.
128+
129+
## Format implementation in multiple languages
130+
131+
We want this format to already have implementation in *multiple* languages (not
132+
just JS, since not all tools used in the JS ecosystem are written in JS), all
133+
ideally production-grade and well-maintained.
134+
135+
## Consensus with third-party tools on building native integrations
136+
137+
We want this format to be consensual enough that third-party tools (VSCode,
138+
emacs, ...) won't object to build native integrations with it (for instance,
139+
Esbuild recently added zip support to integrate w/ Yarn's zip installs; it would
140+
have been a much harder sell if Yarn had used a custom-made format).
141+
142+
## Optional data compression
143+
144+
As an application grows, bundling all the source code, dependencies and static
145+
assets into a single file without compression would quickly reach the maximum
146+
segment / file (depending on the embedding approach) size limit imposed by the
147+
single executable file format / OS. A solution to this problem would be to
148+
minify the JS source files but that might not be enough for other kinds of
149+
files, so supporting data compression seems to be a better solution.
150+
151+
[ASAR]: https://github.com/electron/asar
152+
[Git executables]: https://github.com/desktop/dugite-native/releases/
153+
[dugite]: https://www.npmjs.com/package/dugite
154+
[platforms supported by Node.js]: https://github.com/nodejs/node/blob/main/BUILDING.md#supported-platforms

0 commit comments

Comments
 (0)