-
-
Notifications
You must be signed in to change notification settings - Fork 32k
sea: support embedding assets #50960
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3cd0c4b
b4bc3be
e58bdbf
dd27a1d
7030989
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -178,14 +178,54 @@ The configuration currently reads the following top-level fields: | |
"output": "/path/to/write/the/generated/blob.blob", | ||
"disableExperimentalSEAWarning": true, // Default: false | ||
"useSnapshot": false, // Default: false | ||
"useCodeCache": true // Default: false | ||
"useCodeCache": true, // Default: false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nodejs/single-executable I recommend creating a JSON schema and distributing it through nodejs.org, to have better intellisense when this json includes "$schema" parameter. This could help with maintenance and usability. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you want to open an issue in https://github.com/nodejs/single-executable/issues instead? |
||
"assets": { // Optional | ||
"a.dat": "/path/to/a.dat", | ||
"b.txt": "/path/to/b.txt" | ||
} | ||
} | ||
``` | ||
|
||
If the paths are not absolute, Node.js will use the path relative to the | ||
current working directory. The version of the Node.js binary used to produce | ||
the blob must be the same as the one to which the blob will be injected. | ||
|
||
### Assets | ||
|
||
Users can include assets by adding a key-path dictionary to the configuration | ||
as the `assets` field. At build time, Node.js would read the assets from the | ||
specified paths and bundle them into the preparation blob. In the generated | ||
executable, users can retrieve the assets using the [`sea.getAsset()`][] and | ||
[`sea.getAssetAsBlob()`][] APIs. | ||
|
||
```json | ||
{ | ||
"main": "/path/to/bundled/script.js", | ||
"output": "/path/to/write/the/generated/blob.blob", | ||
"assets": { | ||
"a.jpg": "/path/to/a.jpg", | ||
"b.txt": "/path/to/b.txt" | ||
} | ||
} | ||
``` | ||
|
||
The single-executable application can access the assets as follows: | ||
|
||
```cjs | ||
const { getAsset } = require('node:sea'); | ||
// Returns a copy of the data in an ArrayBuffer. | ||
const image = getAsset('a.jpg'); | ||
// Returns a string decoded from the asset as UTF8. | ||
const text = getAsset('b.txt', 'utf8'); | ||
// Returns a Blob containing the asset. | ||
const blob = getAssetAsBlob('a.jpg'); | ||
// Returns an ArrayBuffer containing the raw asset without copying. | ||
const raw = getRawAsset('a.jpg'); | ||
``` | ||
|
||
See documentation of the [`sea.getAsset()`][] and [`sea.getAssetAsBlob()`][] | ||
APIs for more information. | ||
|
||
### Startup snapshot support | ||
|
||
The `useSnapshot` field can be used to enable startup snapshot support. In this | ||
|
@@ -229,11 +269,79 @@ execute the script, which would improve the startup performance. | |
|
||
**Note:** `import()` does not work when `useCodeCache` is `true`. | ||
|
||
## Notes | ||
## In the injected main script | ||
|
||
### Single-executable application API | ||
|
||
The `node:sea` builtin allows interaction with the single-executable application | ||
from the JavaScript main script embedded into the executable. | ||
|
||
#### `sea.isSea()` | ||
|
||
<!-- YAML | ||
added: REPLACEME | ||
--> | ||
|
||
* Returns: {boolean} Whether this script is running inside a single-executable | ||
application. | ||
|
||
### `sea.getAsset(key[, encoding])` | ||
|
||
### `require(id)` in the injected module is not file based | ||
<!-- YAML | ||
added: REPLACEME | ||
--> | ||
|
||
This method can be used to retrieve the assets configured to be bundled into the | ||
single-executable application at build time. | ||
joyeecheung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
An error is thrown when no matching asset can be found. | ||
|
||
* `key` {string} the key for the asset in the dictionary specified by the | ||
`assets` field in the single-executable application configuration. | ||
* `encoding` {string} If specified, the asset will be decoded as | ||
a string. Any encoding supported by the `TextDecoder` is accepted. | ||
If unspecified, an `ArrayBuffer` containing a copy of the asset would be | ||
returned instead. | ||
* Returns: {string|ArrayBuffer} | ||
|
||
### `sea.getAssetAsBlob(key[, options])` | ||
|
||
<!-- YAML | ||
added: REPLACEME | ||
--> | ||
|
||
Similar to [`sea.getAsset()`][], but returns the result in a [`Blob`][]. | ||
An error is thrown when no matching asset can be found. | ||
|
||
* `key` {string} the key for the asset in the dictionary specified by the | ||
`assets` field in the single-executable application configuration. | ||
* `options` {Object} | ||
* `type` {string} An optional mime type for the blob. | ||
* Returns: {Blob} | ||
joyeecheung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### `sea.getRawAsset(key)` | ||
|
||
`require()` in the injected module is not the same as the [`require()`][] | ||
<!-- YAML | ||
added: REPLACEME | ||
--> | ||
|
||
This method can be used to retrieve the assets configured to be bundled into the | ||
single-executable application at build time. | ||
An error is thrown when no matching asset can be found. | ||
|
||
Unlike `sea.getRawAsset()` or `sea.getAssetAsBlob()`, this method does not | ||
return a copy. Instead, it returns the raw asset bundled inside the executable. | ||
|
||
For now, users should avoid writing to the returned array buffer. If the | ||
injected section is not marked as writable or not aligned properly, | ||
writes to the returned array buffer is likely to result in a crash. | ||
|
||
* `key` {string} the key for the asset in the dictionary specified by the | ||
`assets` field in the single-executable application configuration. | ||
* Returns: {string|ArrayBuffer} | ||
|
||
### `require(id)` in the injected main script is not file based | ||
|
||
`require()` in the injected main script is not the same as the [`require()`][] | ||
available to modules that are not injected. It also does not have any of the | ||
properties that non-injected [`require()`][] has except [`require.main`][]. It | ||
can only be used to load built-in modules. Attempting to load a module that can | ||
|
@@ -250,15 +358,17 @@ const { createRequire } = require('node:module'); | |
require = createRequire(__filename); | ||
``` | ||
|
||
### `__filename` and `module.filename` in the injected module | ||
### `__filename` and `module.filename` in the injected main script | ||
|
||
The values of `__filename` and `module.filename` in the injected module are | ||
equal to [`process.execPath`][]. | ||
The values of `__filename` and `module.filename` in the injected main script | ||
are equal to [`process.execPath`][]. | ||
|
||
### `__dirname` in the injected module | ||
### `__dirname` in the injected main script | ||
|
||
The value of `__dirname` in the injected module is equal to the directory name | ||
of [`process.execPath`][]. | ||
The value of `__dirname` in the injected main script is equal to the directory | ||
name of [`process.execPath`][]. | ||
|
||
## Notes | ||
|
||
### Single executable application creation process | ||
|
||
|
@@ -298,9 +408,12 @@ to help us document them. | |
[Mach-O]: https://en.wikipedia.org/wiki/Mach-O | ||
[PE]: https://en.wikipedia.org/wiki/Portable_Executable | ||
[Windows SDK]: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/ | ||
[`Blob`]: https://developer.mozilla.org/en-US/docs/Web/API/Blob | ||
[`process.execPath`]: process.md#processexecpath | ||
[`require()`]: modules.md#requireid | ||
[`require.main`]: modules.md#accessing-the-main-module | ||
[`sea.getAsset()`]: #seagetassetkey-encoding | ||
[`sea.getAssetAsBlob()`]: #seagetassetasblobkey-options | ||
[`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data | ||
[`v8.startupSnapshot` API]: v8.md#startup-snapshot-api | ||
[documentation about startup snapshot support in Node.js]: cli.md#--build-snapshot | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
'use strict'; | ||
const { | ||
ArrayBufferPrototypeSlice, | ||
} = primordials; | ||
|
||
const { isSea, getAsset: getAssetInternal } = internalBinding('sea'); | ||
const { TextDecoder } = require('internal/encoding'); | ||
const { validateString } = require('internal/validators'); | ||
const { | ||
ERR_NOT_IN_SINGLE_EXECUTABLE_APPLICATION, | ||
ERR_SINGLE_EXECUTABLE_APPLICATION_ASSET_NOT_FOUND, | ||
} = require('internal/errors').codes; | ||
const { Blob } = require('internal/blob'); | ||
|
||
/** | ||
* Look for the asset in the injected SEA blob using the key. If | ||
* no matching asset is found an error is thrown. The returned | ||
* ArrayBuffer should not be mutated or otherwise the process | ||
* can crash due to access violation. | ||
* @param {string} key | ||
* @returns {ArrayBuffer} | ||
*/ | ||
function getRawAsset(key) { | ||
validateString(key, 'key'); | ||
|
||
if (!isSea()) { | ||
throw new ERR_NOT_IN_SINGLE_EXECUTABLE_APPLICATION(); | ||
anonrig marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
const asset = getAssetInternal(key); | ||
if (asset === undefined) { | ||
throw new ERR_SINGLE_EXECUTABLE_APPLICATION_ASSET_NOT_FOUND(key); | ||
joyeecheung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
return asset; | ||
} | ||
|
||
/** | ||
* Look for the asset in the injected SEA blob using the key. If the | ||
* encoding is specified, return a string decoded from it by TextDecoder, | ||
* otherwise return *a copy* of the original data in an ArrayBuffer. If | ||
* no matching asset is found an error is thrown. | ||
* @param {string} key | ||
* @param {string|undefined} encoding | ||
* @returns {string|ArrayBuffer} | ||
*/ | ||
function getAsset(key, encoding) { | ||
if (encoding !== undefined) { | ||
validateString(encoding, 'encoding'); | ||
} | ||
const asset = getRawAsset(key); | ||
if (encoding === undefined) { | ||
return ArrayBufferPrototypeSlice(asset); | ||
} | ||
const decoder = new TextDecoder(encoding); | ||
return decoder.decode(asset); | ||
} | ||
|
||
/** | ||
* Look for the asset in the injected SEA blob using the key. If | ||
* no matching asset is found an error is thrown. The data is returned | ||
* in a Blob. If no matching asset is found an error is thrown. | ||
* @param {string} key | ||
* @param {ConstructorParameters<Blob>[1]} [options] | ||
* @returns {Blob} | ||
*/ | ||
function getAssetAsBlob(key, options) { | ||
anonrig marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const asset = getRawAsset(key); | ||
return new Blob([asset], options); | ||
} | ||
|
||
module.exports = { | ||
isSea, | ||
getAsset, | ||
getRawAsset, | ||
getAssetAsBlob, | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.