|
| 1 | +# Single executable applications |
| 2 | + |
| 3 | +<!--introduced_in=REPLACEME--> |
| 4 | + |
| 5 | +> Stability: 1 - Experimental: This feature is being designed and will change. |
| 6 | +
|
| 7 | +<!-- source_link=lib/internal/main/single_executable_application.js --> |
| 8 | + |
| 9 | +This feature allows the distribution of a Node.js application conveniently to a |
| 10 | +system that does not have Node.js installed. |
| 11 | + |
| 12 | +Node.js supports the creation of [single executable applications][] by allowing |
| 13 | +the injection of a JavaScript file into the `node` binary. During start up, the |
| 14 | +program checks if anything has been injected. If the script is found, it |
| 15 | +executes its contents. Otherwise Node.js operates as it normally does. |
| 16 | + |
| 17 | +The single executable application feature only supports running a single |
| 18 | +embedded [CommonJS][] file. |
| 19 | + |
| 20 | +A bundled JavaScript file can be turned into a single executable application |
| 21 | +with any tool which can inject resources into the `node` binary. |
| 22 | + |
| 23 | +Here are the steps for creating a single executable application using one such |
| 24 | +tool, [postject][]: |
| 25 | + |
| 26 | +1. Create a JavaScript file: |
| 27 | + ```console |
| 28 | + $ echo 'console.log(`Hello, ${process.argv[2]}!`);' > hello.js |
| 29 | + ``` |
| 30 | + |
| 31 | +2. Create a copy of the `node` executable and name it according to your needs: |
| 32 | + ```console |
| 33 | + $ cp $(command -v node) hello |
| 34 | + ``` |
| 35 | + |
| 36 | +3. Inject the JavaScript file into the copied binary by running `postject` with |
| 37 | + the following options: |
| 38 | + |
| 39 | + * `hello` - The name of the copy of the `node` executable created in step 2. |
| 40 | + * `NODE_JS_CODE` - The name of the resource / note / section in the binary |
| 41 | + where the contents of the JavaScript file will be stored. |
| 42 | + * `hello.js` - The name of the JavaScript file created in step 1. |
| 43 | + * `--sentinel-fuse NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2` - The |
| 44 | + [fuse][] used by the Node.js project to detect if a file has been injected. |
| 45 | + * `--macho-segment-name NODE_JS` (only needed on macOS) - The name of the |
| 46 | + segment in the binary where the contents of the JavaScript file will be |
| 47 | + stored. |
| 48 | + |
| 49 | + To summarize, here is the required command for each platform: |
| 50 | + |
| 51 | + * On systems other than macOS: |
| 52 | + ```console |
| 53 | + $ npx postject hello NODE_JS_CODE hello.js \ |
| 54 | + --sentinel-fuse NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2 |
| 55 | + ``` |
| 56 | + |
| 57 | + * On macOS: |
| 58 | + ```console |
| 59 | + $ npx postject hello NODE_JS_CODE hello.js \ |
| 60 | + --sentinel-fuse NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \ |
| 61 | + --macho-segment-name NODE_JS |
| 62 | + ``` |
| 63 | + |
| 64 | +4. Run the binary: |
| 65 | + ```console |
| 66 | + $ ./hello world |
| 67 | + Hello, world! |
| 68 | + ``` |
| 69 | + |
| 70 | +## Notes |
| 71 | + |
| 72 | +### `require(id)` in the injected module is not file based |
| 73 | + |
| 74 | +`require()` in the injected module is not the same as the [`require()`][] |
| 75 | +available to modules that are not injected. It also does not have any of the |
| 76 | +properties that non-injected [`require()`][] has except [`require.main`][]. It |
| 77 | +can only be used to load built-in modules. Attempting to load a module that can |
| 78 | +only be found in the file system will throw an error. |
| 79 | + |
| 80 | +Instead of relying on a file based `require()`, users can bundle their |
| 81 | +application into a standalone JavaScript file to inject into the executable. |
| 82 | +This also ensures a more deterministic dependency graph. |
| 83 | + |
| 84 | +However, if a file based `require()` is still needed, that can also be achieved: |
| 85 | + |
| 86 | +```js |
| 87 | +const { createRequire } = require('node:module'); |
| 88 | +require = createRequire(__filename); |
| 89 | +``` |
| 90 | + |
| 91 | +### `__filename` and `module.filename` in the injected module |
| 92 | + |
| 93 | +The values of `__filename` and `module.filename` in the injected module are |
| 94 | +equal to [`process.execPath`][]. |
| 95 | + |
| 96 | +### `__dirname` in the injected module |
| 97 | + |
| 98 | +The value of `__dirname` in the injected module is equal to the directory name |
| 99 | +of [`process.execPath`][]. |
| 100 | + |
| 101 | +### Single executable application creation process |
| 102 | + |
| 103 | +A tool aiming to create a single executable Node.js application must |
| 104 | +inject the contents of a JavaScript file into: |
| 105 | + |
| 106 | +* a resource named `NODE_JS_CODE` if the `node` binary is a [PE][] file |
| 107 | +* a section named `NODE_JS_CODE` in the `NODE_JS` segment if the `node` binary |
| 108 | + is a [Mach-O][] file |
| 109 | +* a note named `NODE_JS_CODE` if the `node` binary is an [ELF][] file |
| 110 | + |
| 111 | +Search the binary for the |
| 112 | +`NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2:0` [fuse][] string and flip the |
| 113 | +last character to `1` to indicate that a resource has been injected. |
| 114 | + |
| 115 | +### Platform support |
| 116 | + |
| 117 | +Single-executable support is tested regularly on CI only on the following |
| 118 | +platforms: |
| 119 | + |
| 120 | +* Windows |
| 121 | +* macOS |
| 122 | +* Linux (AMD64 only) |
| 123 | + |
| 124 | +This is due to a lack of better tools to generate single-executables that can be |
| 125 | +used to test this feature on other platforms. |
| 126 | + |
| 127 | +Suggestions for other resource injection tools/workflows are welcomed. Please |
| 128 | +start a discussion at <https://github.com/nodejs/single-executable/discussions> |
| 129 | +to help us document them. |
| 130 | + |
| 131 | +[CommonJS]: modules.md#modules-commonjs-modules |
| 132 | +[ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format |
| 133 | +[Mach-O]: https://en.wikipedia.org/wiki/Mach-O |
| 134 | +[PE]: https://en.wikipedia.org/wiki/Portable_Executable |
| 135 | +[`process.execPath`]: process.md#processexecpath |
| 136 | +[`require()`]: modules.md#requireid |
| 137 | +[`require.main`]: modules.md#accessing-the-main-module |
| 138 | +[fuse]: https://www.electronjs.org/docs/latest/tutorial/fuses |
| 139 | +[postject]: https://github.com/nodejs/postject |
| 140 | +[single executable applications]: https://github.com/nodejs/single-executable |
0 commit comments