Native Shell
Native Shell은 모든 플랫폼의 앱 안에서 만들어지는 AI UI입니다
Node.js용 미니멀 바이트코드 컴파일러
A minimalist bytecode compiler for Node.js
개발 도구JavaScriptGitHub @bytenode직접 설치운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
설치형 프로젝트는 배포 도메인 없이도 확인할 수 있습니다. 개인 저장소는 GitHub 로그인으로, 조직 저장소는 위의 저장소 확인 파일로 직접 확인해 주세요.
메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A minimalist bytecode compiler for Node.js.
This tool truly compiles your JavaScript code into V8 bytecode, so that you can protect your source code. It runs on Node.js and Electron. See examples/ for current runnable projects.
npm install --save bytenode
Or globally:
sudo npm install -g bytenode
Bytenode compiles JavaScript into V8 bytecode, which removes original source formatting, comments, and variable names, drastically increasing the barrier to entry for reverse engineering. However, be aware that V8 bytecode is not encryption. Decompilation and analysis tools exist, such as View8 and Ghidra plugins. These tools are valuable for the cybersecurity community to audit applications and analyze malicious code. Some of them extract only abstract pseudo-code from .jsc files, while others can decompiles them back to readable JavaScript.
If your goal is maximum intellectual property protection, Bytenode should not be your only line of defense. We recommend adding an obfuscation step (using tools like javascript-obfuscator) into your build pipeline before compiling the code with Bytenode. This ensures that even if the bytecode is decompiled, the resulting JavaScript remains unreadable and hard to reverse-engineer.
In Node 10.x, Bytenode does not work in debug mode. See #29.
Any code depends on Function.prototype.toString function will break, because Bytenode removes the source code from .jsc files and puts a dummy code instead. See #34. For a workaround, see #163
Async Arrow Functions (and Arrow Functions in general) cause crashes in Puppeteer and in Electron apps. See #106, #47. They also cause issues with the ndb debugger. See #135. It seems that whenever there is a context change (or even when called from another file or module), arrow functions break because V8 inspects them internally using Function.prototype.toString in these cases. See #157.
Electron ≥ 42 (V8 ≥ 14.8): bytecode compiled via ELECTRON_RUN_AS_NODE crashes the main process with SIGTRAP. V8's serialized bytecode header carries a read-only snapshot checksum (header offset 16). The Electron main/browser process boots V8 from Chromium's v8_context_snapshot, whose read-only heap differs from the Node default snapshot used by ELECTRON_RUN_AS_NODE. So a .jsc produced with compileElectronCode() (run-as-node) carries a checksum that does not match the main process. Starting with V8 14.8 this no longer fails gracefully (cachedDataRejected) but hard-aborts (EXC_BREAKPOINT / SIGTRAP) while deserializing. The fix is to compile in the same process type that will load the .jsc — use compileElectronMainCode() or compileFile({ electronMain: true }), which compile inside an actual Electron main process. (On Electron ≤ 41 / V8 ≤ 14.6 the two checksums happened to be compatible, so electron: true worked there.)
Electron renderer bytecode is context-specific. On Electron 43.5.1 and later, the renderer snapshot differs from the Node and main-process snapshots. The project’s reported checks found that bytecode compiled with electron: true or electronMain: true was rejected by renderer preloads/pages, while renderer-compiled bytecode loaded in both. Use compileElectronRendererCode() or compileFile({ electronRenderer: true }) for renderer code. This mode compiles in a hidden window’s preload, so the result is suitable for renderer/preload contexts and should not be used in the main process or worker threads. The reported compatibility checks covered Electron 43.4.1, 43.5.1, 43.7.3, and 44.4.3; verify the mode against the exact Electron versions you ship. See #265.
| Loaded in | Electron 43.4.1 | Electron 43.5.1, 43.7.3, 44.4.3 |
|---|---|---|
main, worker_threads | electronMain | electronMain or electron |
utilityProcess | electron | electron or electronMain |
| preload, renderer page | electron or electronRenderer | electronRenderer only |
Usage: bytenode [option] [ FILE... | - ] [arguments]
Options:
-h, --help show help information.
-v, --version show bytenode version.
-c, --compile [ FILE... | - ] compile stdin, a file, or a list of files
--compress compress bytecode
--source-map write an external source map beside each .jsc file
-n, --no-module compile without producing commonjs module
-e, --electron compile for Electron
-ep, --electron-path path to Electron executable
-l, --loader [ FILE | PATTERN ] create a loader file and optionally define
loader filename or pattern using % as
filename replacer
defaults to %.loader.js
--no-loader do not create a loader file, conflicts
with -l
-t, --loader-type type create a loader file of type commonjs or
module. Defaults to CommonJS
Examples:
$ bytenode -c script.js compile `script.js` to `script.jsc`.
$ bytenode -c server.js app.js
$ bytenode -c src/*.js compile all `.js` files in `src/` directory.
$ bytenode -c *.js -l %.load.js create `filename.load.js` loader files along side `.jsc` files
$ bytenode script.jsc [arguments] run `script.jsc` with arguments.
$ bytenode open Node REPL with bytenode pre-loaded.
Examples:
express-server.js to express-server.jsc.user@machine:~$ bytenode --compile express-server.js
express-server.jsc.user@machine:~$ bytenode express-server.jsc
Server listening on port 3000
.js files in ./app directory.user@machine:~$ bytenode --compile ./app/*.js
.js files in your project.user@machine:~$ bytenode --compile ./**/*.js
Note: you may need to enable globstar option in bash (you should add it to ~/.bashrc):
shopt -s globstar
stdin.echo 'console.log("Hello");' | bytenode --compile - > hello.jsc
const bytenode = require("bytenode");
Generates v8 bytecode buffer.
| Name | Type | Description |
|---|---|---|
| javascriptCode | string | JavaScript source that will be compiled to bytecode. |
{Buffer} The generated bytecode.
let helloWorldBytecode = bytenode.compileCode(
`console.log('Hello World!');
43; // this will be returned`,
);
This helloWorldBytecode bytecode can be saved to a file. However, if you want to use your code as a module (i.e. if your file has some exports), you have to compile it using bytenode.compileFile({compileAsModule: true}), or wrap your code manually, using Module.wrap() function.
Asynchronous function which generates v8 bytecode buffer for Electron.
Same as bytenode.compileCode(), but generates bytecode for the version of Electron currently installed in node_modules.
| Name | Type | Description |
|---|---|---|
| javascriptCode | string | JavaScript source that will be compiled to bytecode. |
| options | object | Options object. |
| options.electronPath | string | Path to Electron executable. |
{Promise<Buffer>} A Promise which resolves with the generated bytecode.
let helloWorldBytecode = await bytenode.compileElectronCode(
`console.log('Hello World!');
43; // this will be returned`,
);
This helloWorldBytecode bytecode can be saved to a file. However, if you want to use your code as a module (i.e. if your file has some exports), you have to compile it using bytenode.compileFile({compileAsModule: true}), or wrap your code manually, using Module.wrap() function.
Asynchronous function which generates v8 bytecode buffer by compiling inside an actual Electron main process (NOT ELECTRON_RUN_AS_NODE).
Use this instead of compileElectronCode() when the .jsc will be loaded from the Electron main process on Electron ≥ 42 (V8 ≥ 14.8). Because the main process boots V8 from Chromium's v8_context_snapshot, the bytecode's read-only snapshot checksum only matches the runtime when it is produced in the same process type. Compiling via run-as-node there leads to a SIGTRAP crash at load time (see Known Issues).
It briefly launches Electron (no window is created) to compile, then exits.
CI / build environment notes
Because this spawns the Electron browser binary (not Node), the build machine must be able to run Electron:
- macOS / Windows runners (e.g. GitHub Actions
macos-*,windows-*): work out of the box.- Linux runners: Electron needs a display, so run the build under a virtual framebuffer, e.g.
xvfb-run -a <build command>(installxvfband the usual Electron shared libs:libgtk-3-0,libnss3,libasound2, …). Headless containers withoutxvfbwill fail.- Running as root in a container: handled automatically — the compile launches Electron with
--no-sandbox(no renderer is created, so the sandbox is unnecessary).- The spawned Electron must be the same version that will run the app (it is, when resolved from your project's
node_modules), so the read-only snapshot checksum matches.- Cross-compiling bytecode for another OS/arch is not possible here — the
.jscmust be produced by the Electron build for the target platform. Compile each platform on its own runner.
| Name | Type | Description |
|---|---|---|
| javascriptCode | string | JavaScript source that will be compiled to bytecode. |
| options | object | Options object. |
| options.electronPath | string | Path to Electron executable. Defaults to the electron binary resolved from node_modules. |
| options.compress | boolean | If true, compress the output bytecode with Brotli. |
{Promise<Buffer>} A Promise which resolves with the generated bytecode.
let helloWorldBytecode = await bytenode.compileElectronMainCode(
`console.log('Hello World!');
43; // this will be returned`,
);
Asynchronous function which generates v8 bytecode buffer by compiling inside an Electron renderer process — the preload script of a hidden BrowserWindow.
Use this for code that will be loaded from a preload script or a renderer page on Electron versions where renderer bytecode needs its own snapshot (reported from Electron 43.5.1; see Known Issues). compileElectronCode() and compileElectronMainCode() output is not interchangeable with renderer output on those versions. The output is renderer-specific and should not be loaded in the main process or worker threads.
It briefly launches Electron with a hidden window, using a private temporary --user-data-dir, then exits. The CI notes for compileElectronMainCode() apply (on Linux, run it under xvfb-run).
| Name | Type | Description |
|---|---|---|
| javascriptCode | string | JavaScript source that will be compiled to bytecode. |
| options | object | Options object. |
| options.electronPath | string | Path to Electron executable. Defaults to the electron binary resolved from node_modules. |
| options.compress | boolean | If true, compress the output bytecode with Brotli. |
{Promise<Buffer>} A Promise which resolves with the generated bytecode.
Runs v8 bytecode buffer and returns the result.
| Name | Type | Description |
|---|---|---|
| bytecodeBuffer | Buffer | The buffer object that was created using compileCode function. |
{any} The result of the very last statement executed in the script.
const result = bytenode.runBytecode(helloWorldBytecode);
// prints: Hello World!
console.log(result);
// prints: 43
Asynchronous function which compiles JavaScript file to .jsc file.
| Name | Type | Description |
|---|---|---|
| args | object | string | |
| args.filename | string | The JavaScript source file that will be compiled. |
| args.compileAsModule | boolean | If true, the output will be a commonjs module. Default: true. |
| args.electron | boolean | If true, the output will be compiled through Electron via ELECTRON_RUN_AS_NODE. Default: false. |
| args.electronMain | boolean | If true, compile inside an actual Electron main process instead of ELECTRON_RUN_AS_NODE. Required for Electron ≥ 42 (V8 ≥ 14.8) when the .jsc is loaded from the main process. See Known Issues. Default: false. |
| args.electronRenderer | boolean | If true, compile inside an Electron renderer (a hidden window's preload). Use for renderer/preload bytecode on Electron versions that require the renderer snapshot (reported from 43.5.1). Cannot be combined with electronMain. Default: false. |
| args.electronPath | string | Path to Electron executable. Default: Electron binary from node_modules. |
| args.output | string | The output filename. Defaults to the same path and name of the original file, but with .jsc extension. |
| args.sourceMap | boolean | If true, write <output>.map and map runtime error stack traces back to the source file, or to the original sources when the input has its own source map. Default: false. |
| args.createLoader | boolean | string | If true, create a CommonJS loader file. As a string, select between module or commonjs loader. Default: false |
| args.loaderFilename | string | Filename or pattern for generated loader files. Defaults to originalFilename.loader.js. Use % as a substitute for originalFilename. |
| output | string | The output filename. (Deprecated: use args.output instead) |
{Promise<string>}: A Promise that resolves as the compiled filename.
let compiledFilename = bytenode.compileFile({
filename: "/path/to/your/file.js",
sourceMap: true,
output: "/path/to/compiled/file.jsc", // if omitted, it defaults to '/path/to/your/file.jsc'
});
When sourceMap is enabled, Bytenode writes a separate standard source map at
<output>.map (for example, file.jsc.map). It does not include source text in
the map. Keep this file next to the .jsc while debugging so Bytenode can map
runtime error stacks; delete it before shipping if you do not want to distribute
debugging metadata. The CLI equivalent is bytenode --compile --source-map file.js.
If the input already has a source map, for example because a bundler such as
Vite, esbuild, webpack or Rollup produced it, Bytenode rebases that map instead,
so stack traces point at the original sources. It looks for the input's
//# sourceMappingURL= comment (a file or an inline base64 data URL), then for
<input>.map. The input map's sources, sourcesContent, names and
debugId are kept, with sources rewritten relative to the output map.
To symbolicate errors in a service such as Sentry, upload <output>.map and do
not ship it. A frame from a .jsc is reported on line 1, at the character
offset into the compiled code, which is the coordinate space of the map. A
preserved debugId lets debug-ID based symbolication work without a release
name. Some services also require the generated source beside the map; the
.jsc contains no source, so upload a copy of the input file with every line
break replaced by a space, wrapped in Module.wrap() when compileAsModule is
true.
Previous code will produce a commonjs module that can be required using require function.
let compiledFilename = await bytenode.compileFile({
filename: "/path/to/your/file.js",
output: "/path/to/compiled/file.jsc",
compileAsModule: false,
});
Previous code will produce a direct .jsc file, that can be run using bytenode.runBytecodeFile() function. It can NOT be required as a module. Please note that compileAsModule MUST be false in order to turn it off. Any other values (including: null, "", etc) will be treated as true. (It had to be done this way in order to keep the old code valid.)
For Electron apps, compile through Electron so the bytecode matches Electron's V8. On Electron ≥ 42 (V8 ≥ 14.8) you must compile in the main process:
let compiledFilename = await bytenode.compileFile({
filename: "/path/to/your/main.js",
output: "/path/to/compiled/main.jsc",
electronMain: true, // Electron main-process compile (Electron >= 42 / V8 >= 14.8)
// electron: true // legacy ELECTRON_RUN_AS_NODE compile (Electron <= 41)
});
// Preload scripts and renderer pages when renderer-specific bytecode is needed:
await bytenode.compileFile({
filename: "/path/to/your/preload.js",
output: "/path/to/compiled/preload.jsc",
electronRenderer: true,
});
Runs .jsc file and returns the result.
| Name | Type |
|---|---|
| filename | string |
{any} The result of the very last statement executed in the script.
// test.js
console.log("Hello World!");
43; // this will be returned
const result = bytenode.runBytecodeFile("/path/to/test.jsc");
// prints: Hello World!
console.log(result);
// prints: 43
| Name | Type |
|---|---|
| filename | string |
{any} exported module content
let myModule = require("/path/to/your/file.jsc");
Just like regular .js modules. You can also omit the extension .jsc.
.jsc file must have been compiled using bytenode.compileFile(), or have been wrapped inside Module.wrap() function. Otherwise it won't work as a module and it can NOT be required.
Please note .jsc files must run with the same Node.js version that was used to compile it (using same architecture of course). Also, .jsc files are CPU-agnostic. However, you should run your tests before and after deployment, because V8 sanity checks include some checks related to CPU supported features, so this may cause errors in some rare cases.
Set the BYTENODE_DEBUG=1 environment variable to print the V8 cached-data header when a .jsc is loaded. It dumps, field by field, the on-disk bytecode header alongside a dummy compiled in the current runtime process (i.e. what V8 expects), so you can see exactly which field is mismatched:
BYTENODE_DEBUG=1 ./your-electron-app
[bytenode] runtime: node v24.16.0, electron 42.4.1, v8 14.8.178.31-electron.0
[bytenode] on-disk .jsc header (before fixBytecode) ...
versionHash @4 = 0x995ac8a1 <- must match runtime
flagHash @12 = 0x24360e99 <- must match runtime (V8 flags)
roChecksum @16 = 0x2c3e475a <- must match runtime (read-only snapshot)
[bytenode] runtime dummy header (what V8 expects) ...
[bytenode] vm.Script created, cachedData accepted.
If versionHash, flagHash or roChecksum differ from the runtime dummy, the cached data is incompatible. A mismatched roChecksum specifically indicates the .jsc was compiled in a different process type than the one loading it (see Known Issues).
I had the idea of this tool many years ago. However, I finally decided to implement it after seeing this issue by @hashseed. Also, some parts were inspired by v8-compile-cache by @zertosh.