Single Executable Application (SEA), bir Node.js uygulamasını Node.js kurulu olmayan bir makinede çalışacak tek bir çalıştırılabilir dosyaya dönüştürme özelliğidir. Node.js, paketlenmiş betiğinizi ve isteğe bağlı varlık dosyalarını bir "hazırlık blob'u" içine koyar, bu blob'u node ikili dosyasının bir kopyasına yerleştirir; program açılırken içine bir şey yerleştirilmiş mi diye bakar ve bulursa o betiği çalıştırır. v25.5.0 ile gelen node --build-sea sea-config.json komutu bu işi harici araç gerektirmeden tek adımda yapar.

Özellik belgelerde 1.1 - Active development düzeyindedir: deneyseldir ve asgari kullanılabilirliğe yaklaşmış sayılır. Bu yazıda Windows üzerinde gerçek bir SEA ürettik, varlık ekledik, modül yükleme sınırına takıldık ve çözümünü gösterdik. Bütün komutlar Node.js v26.13.0 ile çalıştırıldı. Uygulamayı sunucuda konteyner içinde dağıtmayı düşünüyorsanız Dockerfile rehberi farklı bir yaklaşım sunar.

SEA nasıl çalışır?

Üretim iki aşamadan oluşur. Önce Node.js, yapılandırma dosyasındaki ana betiği ve varlıkları okuyup çalıştırmak için gereken her şeyi içeren hazırlık blob'unu üretir. Ardından bu blob, node ikili dosyasının bir kopyasına yerleştirilir: Windows'taki PE dosyalarında NODE_SEA_BLOB adlı bir kaynak (resource), macOS'taki Mach-O dosyalarında NODE_SEA segmentindeki NODE_SEA_BLOB bölümü, Linux'taki ELF dosyalarında ise aynı adlı bir not olarak. Son olarak ikili dosyadaki NODE_SEA_FUSE_... dizgisinin son karakteri 0'dan 1'e çevrilir; Node.js açılışta bu "sigortaya" bakarak içine blob yerleştirildiğini anlar. --build-sea bu adımların hepsini kendi içinde yapar.

Bu yapının önemli bir sonucu var: çıkan dosya, Node.js çalışma ortamının tamamını içerir. Denememizde kullandığımız node.exe 100.097.864 bayt, ürettiğimiz merhaba.exe 100.241.736 bayt geldi. Yani "küçük bir betik" bile yaklaşık Node.js ikili dosyası kadar yer kaplar. Blob'u üretmek için kullanılan Node.js sürümü, içine yerleştirileceği ikili dosyanın sürümüyle aynı olmalıdır.

Adım adım ilk SEA

Komut satırından bir ad alan, SEA içinde çalışıp çalışmadığını söyleyen ve gömülü bir JSON dosyasından sürüm okuyan küçük bir CommonJS betiği yazalım:

javascript
// merhaba.js
const { isSea, getAsset, getAssetKeys } = require('node:sea');

const ad = process.argv[2] ?? 'dünya';
console.log(`Merhaba, ${ad}!`);
console.log('SEA içinde mi:', isSea());
if (isSea()) {
  console.log('varlıklar:', getAssetKeys());
  const ayar = JSON.parse(getAsset('ayar.json', 'utf8'));
  console.log('sürüm:', ayar.surum);
  console.log('execArgv:', process.execArgv);
}

Yanına { "surum": "1.4.0" } içeren bir ayar.json koyuyoruz. Yapılandırma dosyası şöyle; Windows'ta çıktı adının .exe ile bitmesi zorunludur:

json
{
  "main": "merhaba.js",
  "output": "merhaba.exe",
  "disableExperimentalSEAWarning": true,
  "useCodeCache": true,
  "execArgv": ["--no-warnings"],
  "assets": {
    "ayar.json": "ayar.json"
  }
}
bash
$ node merhaba.js Ayşe
Merhaba, Ayşe!
SEA içinde mi: false

$ node --build-sea sea-config.json
Generated single executable C:\kullanici\node-v26.13.0-win-x64\node.exe + sea-config.json -> merhaba.exe

$ .\merhaba.exe Ayşe
Merhaba, Ayşe!
SEA içinde mi: true
varlıklar: [ 'ayar.json' ]
sürüm: 1.4.0
execArgv: [ '--no-warnings' ]

Node.js yolunu kısalttık. Derleme bir saniyeden kısa sürdü. Aynı betik normal node ile çalıştığında isSea() false döndü ve varlık bloğu atlandı; bu kontrol, betiği hem geliştirme sırasında hem paketlenmiş hâlde çalıştırabilmenizi sağlar. Kullanıcı argümanları her zamanki gibi process.argv dizisinin 2. indeksinden başlar; process.argv yazısındaki kurallar burada da geçerlidir. Yapılandırmadaki göreli yollar çalışma dizinine göre çözülür.

Son adım imzalamadır. macOS'ta ikili dosya codesign --sign - sea ile imzalanmalıdır. Windows'ta imzalama isteğe bağlıdır: signtool sign /fd SHA256 sea.exe bir sertifika gerektirir, imzasız dosya da çalışır; nitekim bizim örneğimiz imzasız çalıştı. Linux için belgelerde imza adımı yoktur.

Yapılandırma alanları

Yapılandırma alanları: main, output, mainFormat, executable
AlanVarsayılanAçıklama
mainzorunluGömülecek, tek dosyaya paketlenmiş betik
outputzorunluÜretilecek çalıştırılabilir dosyanın yolu
mainFormatcommonjscommonjs veya module (ESM)
executableçalışan nodeBlob'un yerleştirileceği Node.js ikili dosyası
disableExperimentalSEAWarningfalseDeneysel özellik uyarısını kapatır
useCodeCachefalseDerleme sırasında V8 kod önbelleği üretir
useSnapshotfalseBaşlangıç anlık görüntüsü kullanır
execArgvyokAçılışta uygulanacak Node.js bayrakları
execArgvExtensionenvnone, env veya cli: ek bayrakların nereden gelebileceği
assetsyokAnahtar ve dosya yolu eşlemesiyle gömülü varlıklar

useCodeCache açıkken Node.js ana betiği derleme makinesinde derleyip V8 kod önbelleğini blob'a ekler; açılışta betik sıfırdan derlenmek yerine bu önbellekle derlenir ve belgelere göre başlangıç performansı iyileşir. useSnapshot açıkken ana betik son kullanıcının makinesinde değil, blob üretilirken çalışır; betiğin v8.startupSnapshot.setDeserializeMainFunction() ile açılışta çalışacak fonksiyonu tanımlaması gerekir. Başka bir platform için SEA üretiyorsanız (örneğin macOS'ta Linux için) her ikisini de false yapın, çünkü kod önbelleği ve anlık görüntü yalnızca derlendikleri platformda yüklenebilir.

Başka bir platform için üretmek: executable alanı

executable alanını boş bırakırsanız blob, komutu çalıştıran Node.js ikili dosyasının kopyasına yerleştirilir; yani Windows'ta çalıştırdığınızda Windows dosyası elde edersiniz. Başka bir işletim sistemi ya da mimari için dosya üretmek istediğinizde bu alana o platformun node ikili dosyasının yolunu verirsiniz. Belgelerdeki iki kural burada belirleyicidir: blob'u üreten Node.js ile içine yerleştirilen ikili dosya aynı sürümde olmalı, ve çapraz platform üretimde useCodeCache ile useSnapshot kapalı olmalıdır. İkinci kuralı çiğnerseniz dosya üretilir ama hedef makinede açılışta çökebilir, çünkü kod önbelleği ve anlık görüntü yalnızca derlendikleri platformda yüklenebilir. Pratikte her hedef platform için o platformun resmî Node.js arşivinden aynı sürümü indirip ayrı bir yapılandırma dosyasıyla derlemek düzenli bir yoldur. Ürettiğiniz dosyayı mutlaka hedef platformda bir kez çalıştırarak doğrulayın.

Gömülü varlıklar: node:sea API'si

Gömülü varlıklar: node:sea API'si: isSea(), getAsset(anahtar), getAsset(anahtar, 'utf8'), getAssetAsBlob(anahtar, { type })

assets alanındaki dosyalar derleme anında okunup blob'a eklenir. Çalışma anında bunlara node:sea modülüyle erişirsiniz:

FonksiyonDöndürdüğüNot
isSea()booleanBetik SEA içinde mi çalışıyor
getAsset(anahtar)ArrayBuffer kopyasıKodlama verilirse metin döner
getAsset(anahtar, 'utf8')stringTextDecoder'ın desteklediği kodlamalar
getAssetAsBlob(anahtar, { type })Blobİsteğe bağlı MIME tipiyle
getRawAsset(anahtar)ArrayBuffer (kopyasız)Belgelere göre şimdilik üzerine yazmayın
getAssetKeys()string dizisiv24.8.0 ile geldi; SEA dışında hata fırlatır

Bulunamayan bir anahtar istendiğinde bu fonksiyonlar hata fırlatır. getRawAsset() veriyi kopyalamadan verdiği için hızlıdır, ancak belgeler dönen tampona yazmanın çökmeye yol açabileceği konusunda uyarır. Yerel eklentiler (.node dosyaları) de varlık olarak gömülebilir; belgelerdeki yöntem, varlığı geçici bir dosyaya yazıp process.dlopen() ile yüklemektir. Gelen ArrayBuffer ile çalışmak için Buffer rehberine bakabilirsiniz.

Mini örnek: hem node ile hem SEA olarak çalışan araç

Geliştirirken her değişiklikte yeniden paket üretmek istemezsiniz. Varlığa erişimi küçük bir yardımcı fonksiyonun arkasına saklarsanız aynı dosya node arac.js ile diskteki dosyayı, SEA içinde ise gömülü varlığı okur:

javascript
// arac.js
const sea = require('node:sea');
const fs = require('node:fs');
const path = require('node:path');

function varlikOku(ad) {
  if (sea.isSea()) return sea.getAsset(ad, 'utf8');
  return fs.readFileSync(path.join(__dirname, ad), 'utf8');
}

const { surum } = JSON.parse(varlikOku('ayar.json'));
const komut = process.argv[2];
if (komut === '--version') {
  console.log(`arac ${surum}`);
} else {
  console.error('kullanım: arac --version');
  process.exitCode = 2;
}
json
{ "main": "arac.js", "output": "arac.exe", "disableExperimentalSEAWarning": true, "execArgvExtension": "none", "assets": { "ayar.json": "ayar.json" } }
bash
$ node arac.js --version
arac 1.4.0
$ node --build-sea sea4.json
$ .\arac.exe --version
arac 1.4.0
$ .\arac.exe
kullanım: arac --version
$ echo $?
2

İki çalıştırma da aynı sonucu verdi. fs ve path yerleşik modüller olduğu için gömülü betikte sorunsuz yüklendi. Hatalı kullanımda çıkış kodunu 2 yaptık; böylece betiği çağıran bir kabuk betiği, yanlış kullanımı başka hatalardan ayırabilir. "execArgvExtension": "none" seçtiğimiz için kullanıcının ortamındaki NODE_OPTIONS değeri aracın davranışını değiştiremez. Bu kalıbı büyüttükçe argüman ayrıştırma için util.parseArgs işinizi kolaylaştırır; onu da process.argv ve util.parseArgs yazısında anlattık.

SEA ne zaman mantıklı?

SEA en çok, Node.js kurulu olmayan kullanıcılara dağıtacağınız komut satırı araçlarında ve iç araçlarda işe yarar: kullanıcı tek bir dosya indirir ve çalıştırır, sürüm uyumsuzluğu ve bağımlılık kurulumu derdi ortadan kalkar. Buna karşılık her platform için ayrı dosya üretmeniz, her sürümde Node.js çalışma ortamının tamamını yeniden dağıtmanız ve yaklaşık yüz megabaytlık dosyalarla uğraşmanız gerekir. Kendi kontrolünüzdeki bir sunucuda çalışan bir API için çoğu zaman Node.js kurup uygulamayı bir servis olarak çalıştırmak daha sade bir yoldur; çok çekirdekli dağıtım ve sıfır kesintili güncelleme gibi konular cluster ve PM2 yazısında ele alınıyor. Özelliğin hâlâ etkin geliştirme aşamasında olduğunu ve davranışın sürümler arasında değişebileceğini de karara katın.

Modül yükleme sınırı ve çözümü

SEA'nın en sık şaşırtan yanı modül yüklemedir. Gömülü ana betikte require() ve import varsayılan olarak yalnızca yerleşik modülleri yükleyebilir; dosya sisteminden modül okumaz. İki dosyalı bir uygulamayı doğrudan paketlersek:

javascript
// ana.js
console.log('başladı');
const yardimci = require('./yardimci.js');
console.log(yardimci());
bash
$ node --build-sea sea2.json
$ .\ana.exe
başladı
(node:21272) Warning: Currently the require() provided to the main script embedded into single-executable applications only supports loading built-in modules.
To load a module from disk after the single executable application is launched, use require("module").createRequire().
Support for bundled module loading or virtual file systems are under discussions in https://github.com/nodejs/single-executable
(Use `ana --trace-warnings ...` to show where the warning was created)
node:internal/modules/helpers:292
  throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
  ^

Error [ERR_UNKNOWN_BUILTIN_MODULE]: No such built-in module: ./yardimci.js
  code: 'ERR_UNKNOWN_BUILTIN_MODULE'
}

Node.js v26.13.0

sea2.json yalnızca { "main": "ana.js", "output": "ana.exe" } içeriyor. Hata, göreli yolun yerleşik bir modül adı gibi arandığını gösteriyor. Önerilen çözüm, uygulamanızı tek bir JavaScript dosyasına paketleyip (bundle) onu gömmektir; belgeler bunun bağımlılık grafiğini de daha öngörülebilir yaptığını söyler. Dosyaların SEA'nın yanında diskte durması kabul edilebilirse uyarıdaki yolu izleyip module.createRequire() ile diskten okuyan bir require üretebilirsiniz:

javascript
// ana.js (düzeltilmiş)
const { createRequire } = require('node:module');
const diskRequire = createRequire(__filename);
console.log(__filename === process.execPath); // çıktı: true
console.log(diskRequire('./yardimci.js')()); // çıktı: yardımcı

Gömülü betikte __filename ve module.filename değeri process.execPath ile, __dirname ise onun klasörüyle aynıdır; bu yüzden ./yardimci.js çalıştırılabilir dosyanın yanında aranır. Bu sürümde düzeltilmiş dosyayı çalıştırdığımızda, betik uyarı alanını kapatmadığımız için "Single executable application is an experimental feature" uyarısı da göründü. Gömülü require(), normal require()'ın require.main dışındaki özelliklerine sahip değildir. CommonJS çözümleme kurallarını require ve module.exports yazısında anlattık.

ESM ana betik

Ana betiği ES modülü olarak yazmak için yapılandırmaya "mainFormat": "module" eklersiniz:

javascript
// esm.mjs
import { isSea } from 'node:sea';
import os from 'node:os';
console.log('ESM SEA:', isSea(), import.meta.main, typeof os.platform());
// çıktı: ESM SEA: true true string

Bu modda import.meta.url, import.meta.filename ve import.meta.dirname çalıştırılabilir dosyayı gösterir, import.meta.main true olur; import.meta.resolve ise henüz desteklenmez. Dinamik import() yalnızca yerleşik modülleri yükleyebilir. "mainFormat": "module" şu an useSnapshot ile birlikte kullanılamaz. import.meta alanlarının normal davranışı için import.meta yazısına bakabilirsiniz.

Çalıştırma bayrakları: execArgv ve execArgvExtension

Son kullanıcının Node.js bayraklarını bilmesini beklemeden execArgv ile açılış bayrakları verebilirsiniz; örneğimizde --no-warnings verdik ve process.execArgv bunu gösterdi. execArgvExtension ek bayrakların nereden gelebileceğini belirler: "none" yalnızca execArgv listesini kullanır ve NODE_OPTIONS değişkenini yok sayar; varsayılan "env" NODE_OPTIONS ile genişletmeye izin verir; "cli" ise dosyayı --node-options="--bayrak" ile başlatmayı sağlar. Dağıttığınız bir aracın kullanıcı ortamındaki NODE_OPTIONS değerinden etkilenmesini istemiyorsanız "none" seçin. NODE_OPTIONS hakkında ayrıntılar CLI bayrakları yazısında.

Eski yol: blob üretip postject ile yerleştirmek

--build-sea öncesindeki iş akışı hâlâ belgelerde duruyor ve doğrulama için kullanılabiliyor. --experimental-sea-config (1 - Experimental) aynı yapılandırmayla yalnızca blob dosyasını üretir; bu kez output alanı çalıştırılabilir dosyanın değil blob'un yoludur. Ardından node ikili dosyasını kopyalar, macOS'ta imzasını kaldırır ve postject aracıyla blob'u yerleştirirsiniz:

bash
node --experimental-sea-config sea-config.json
node -e "require('fs').copyFileSync(process.execPath, 'hello.exe')"
npx postject hello.exe NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2

postject bir npm paketidir ve bu yazıda çalıştırmadık; komutlar belgelerdeki Windows örneğinden alındı. Yeni projelerde tek komutluk --build-sea yolu daha basittir.

Platform desteği

Belgelere göre SEA, CI üzerinde düzenli olarak yalnızca Windows, macOS (yalnızca arm64; x64 şu an desteklenmiyor ve testlerde atlanıyor) ve Alpine dışındaki Linux dağıtımlarında (s390x hariç desteklenen mimarilerde) test edilir. Bilinen bir uyarı da var: SEA, Linux arm64 Docker konteyneri içinde postject ile üretilirse ELF dosyasının hash tablosu hatalı olur ve process.dlopen() çağrısında çöker. Hedef platformlarınızı bu listeyle karşılaştırın.

Sık yapılan hatalar

Sık yapılan hatalar: Windows'ta .exe uzantısını unutmak, Göreli require ile birden fazla dosya kullanmak, Farklı sürümle üretmek, Çapraz platformda kod önbelleği açık bırakmak
  • Windows'ta .exe uzantısını unutmak: belgeler output adının .exe ile bitmesini zorunlu tutar.
  • Göreli require ile birden fazla dosya kullanmak: gömülü betik yalnızca yerleşik modülleri yükler; uygulamayı tek dosyaya paketleyin ya da createRequire() kullanın.
  • Farklı sürümle üretmek: blob'u üreten Node.js ile içine yerleştirilen ikili dosya aynı sürümde olmalıdır.
  • Çapraz platformda kod önbelleği açık bırakmak: başka platform için üretirken useCodeCache ve useSnapshot değerlerini false yapın.
  • getRawAsset() tamponuna yazmak: kopyasız dönen veri üzerinde yazma çökmeye neden olabilir; değiştirecekseniz getAsset() kopyasını kullanın.
  • getAssetKeys() fonksiyonunu SEA dışında çağırmak: SEA içinde değilseniz hata fırlatır; önce isSea() ile kontrol edin.
  • macOS'ta imzayı atlamak: belgelerdeki adım codesign --sign - ile imzalamaktır.

Sık Sorulan Sorular

SEA kaynak kodumu gizler mi?

Belgeler SEA'yı dağıtım kolaylığı için tanımlar; kaynak kodu gizleme ya da koruma amacından söz etmez. Betik blob içinde taşınır, bu yüzden SEA'yı bir gizleme yöntemi olarak düşünmeyin.

Hedef makinede Node.js kurulu olmalı mı?

Hayır, özelliğin amacı tam olarak budur. Çıkan dosya Node.js çalışma ortamını içerir; bu yüzden boyutu da Node.js ikili dosyasına yakındır.

npm bağımlılıklarını nasıl dahil ederim?

Gömülü betik node_modules klasöründen modül yükleyemez. Belgelerin önerisi, uygulamayı bağımlılıklarıyla birlikte tek bir JavaScript dosyasına paketleyip onu main olarak vermektir. Hangi paketleyiciyi kullanacağınız Node.js belgelerinin kapsamı dışındadır.

Uygulamayı güncellemek için ne yapmalıyım?

Gömülü betik ikili dosyanın içinde olduğu için her değişiklikte SEA'yı yeniden üretip dağıtmanız gerekir. Node.js sürümünü yükselttiğinizde de aynı şey geçerlidir: blob'u yeni sürümle üretip yeni sürümün ikili dosyasına yerleştirmelisiniz.

Hangi Node.js sürümü gerekir?

--build-sea bayrağı v25.5.0 ile geldi. Daha eski sürümlerde blob'u --experimental-sea-config ile üretip postject gibi bir araçla yerleştirmeniz gerekir. Sürümünüzü öğrenmek ve yönetmek için Node.js kurulumu ve sürüm yöneticileri yazısına bakabilirsiniz.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Node.js öğrenme rehberi · require ve module.exports · CLI bayrakları · import.meta · Node.js kurulumu · Dockerfile rehberi