Skip to main content

ASAR Archives

애플리케이션 배포를 마친 후, 앱의 소스 코드는 일반적으로 ASAR 아카이브로 묶인다. ASAR은 Electron 앱을 위해 설계된 간단하면서도 확장 가능한 아카이브 포맷이다. 앱을 이렇게 묶으면 Windows에서 긴 경로 이름과 관련된 문제를 완화할 수 있고, require 속도를 높이며, 소스 코드를 간단한 검사로부터 숨길 수 있다.

묶인 앱은 가상 파일 시스템에서 실행되며, 대부분의 API는 정상적으로 작동한다. 하지만 몇 가지 주의사항 때문에 ASAR 아카이브를 명시적으로 다뤄야 할 경우도 있다.

ASAR 아카이브 활용

Electron에서는 두 가지 API 세트를 제공한다. 하나는 Node.js가 제공하는 Node API이고, 다른 하나는 Chromium이 제공하는 Web API이다. 두 API 모두 ASAR 아카이브에서 파일을 읽는 기능을 지원한다.

Node API

Electron은 특별한 패치를 통해 fs.readFilerequire 같은 Node API가 ASAR 아카이브를 가상 디렉터리로 처리하고, 그 안의 파일을 일반 파일 시스템의 파일처럼 다룰 수 있게 한다.

예를 들어, /path/to 경로에 example.asar 아카이브가 있다고 가정해 보자:

$ asar list /path/to/example.asar
/app.js
/file.txt
/dir/module.js
/static/index.html
/static/main.css
/static/jquery.min.js

ASAR 아카이브 안의 파일을 읽는 방법:

const fs = require('node:fs')
fs.readFileSync('/path/to/example.asar/file.txt')

아카이브 루트 아래의 모든 파일을 나열하는 방법:

const fs = require('node:fs')
fs.readdirSync('/path/to/example.asar')

아카이브에서 모듈을 사용하는 방법:

require('./path/to/example.asar/dir/module.js')

BrowserWindow를 사용해 ASAR 아카이브 안의 웹 페이지를 표시할 수도 있다:

const { BrowserWindow } = require('electron')
const win = new BrowserWindow()

win.loadURL('file:///path/to/example.asar/static/index.html')

웹 API

웹 페이지에서는 file: 프로토콜을 사용해 아카이브 내 파일을 요청할 수 있다. Node API와 마찬가지로 ASAR 아카이브가 디렉터리처럼 취급된다.

예를 들어, $.get을 사용해 파일을 가져오는 방법은 다음과 같다:

<script>
let $ = require('./jquery.min.js')
$.get('file:///path/to/example.asar/file.txt', (data) => {
console.log(data)
})
</script>

ASAR 아카이브를 일반 파일로 다루기

ASAR 아카이브의 체크섬을 검증하는 경우처럼, ASAR 아카이브의 내용을 파일로 읽어야 할 때가 있다. 이때는 asar 지원이 없는 원본 fs API를 제공하는 내장 모듈인 original-fs를 사용할 수 있다:

const originalFs = require('original-fs')
originalFs.readFileSync('/path/to/example.asar')

또는 process.noAsartrue로 설정해 fs 모듈에서 asar 지원을 비활성화할 수도 있다:

const fs = require('node:fs')
process.noAsar = true
fs.readFileSync('/path/to/example.asar')

Node API의 한계

Node API에서 ASAR 아카이브를 디렉터리처럼 동작하게 만들기 위해 많은 노력을 기울였지만, Node API의 저수준 특성 때문에 여전히 몇 가지 제약이 존재한다.

이러한 제약은 Node API가 운영체제와 직접 상호작용하는 방식에서 비롯된다. ASAR 아카이브는 기본적으로 파일 시스템의 일반 디렉터리와는 다른 구조를 가지고 있기 때문에, 모든 기능이 완벽하게 동일하게 동작하지 않을 수 있다.

예를 들어, 파일 시스템의 특정 저수준 기능을 사용할 때 ASAR 아카이브 내부의 파일에 접근하려면 추가적인 처리가 필요할 수 있다. 또한, 일부 Node.js 모듈이나 라이브러리가 ASAR 아카이브를 완전히 지원하지 않을 수도 있다.

따라서 ASAR 아카이브를 사용할 때는 이러한 제약을 고려해야 하며, 필요한 경우 추가적인 조치를 취해야 한다. 이는 ASAR 아카이브의 장점을 최대한 활용하면서도 Node API의 한계를 극복하는 데 도움이 될 것이다.

아카이브는 읽기 전용이다

아카이브는 수정할 수 없으므로 파일을 변경할 수 있는 모든 Node API는 ASAR 아카이브에서 작동하지 않는다.

작업 디렉토리를 아카이브 내의 디렉토리로 설정할 수 없음

ASAR 아카이브는 디렉토리처럼 취급되지만, 실제 파일 시스템에는 디렉토리가 존재하지 않는다. 따라서 ASAR 아카이브 내의 디렉토리를 작업 디렉토리로 설정할 수 없다. 일부 API의 cwd 옵션으로 이를 전달하면 오류가 발생할 수 있다.

일부 API에서의 추가적인 파일 압축 해제

대부분의 fs API는 ASAR 아카이브에서 파일을 읽거나 파일 정보를 가져올 때 압축을 해제할 필요가 없다. 하지만 실제 파일 경로를 시스템 호출에 전달해야 하는 일부 API의 경우, Electron은 필요한 파일을 임시 파일로 추출하고 그 임시 파일의 경로를 API에 전달해 동작하게 한다. 이로 인해 해당 API들은 약간의 오버헤드가 발생한다.

추가적인 파일 압축 해제가 필요한 API는 다음과 같다:

  • child_process.execFile
  • child_process.execFileSync
  • fs.open
  • fs.openSync
  • process.dlopen - 네이티브 모듈에서 require에 사용됨

fs.stat의 가짜 상태 정보

asar 아카이브 내 파일에 대해 fs.stat 및 관련 함수가 반환하는 Stats 객체는 추측을 통해 생성된다. 해당 파일들은 실제 파일 시스템에 존재하지 않기 때문이다. 따라서 파일 크기를 확인하거나 파일 타입을 검사하는 경우를 제외하고는 Stats 객체를 신뢰해서는 안 된다.

ASAR 아카이브 내부에서 바이너리 실행하기

Node.js에는 child_process.exec, child_process.spawn, child_process.execFile과 같이 바이너리를 실행할 수 있는 API가 있다. 하지만 ASAR 아카이브 내부에서 바이너리를 실행할 때는 execFile만 지원된다.

이유는 execspawnfile 대신 command를 입력으로 받기 때문이다. 그리고 command는 셸에서 실행된다. ASAR 아카이브 내부의 파일을 사용하는지 여부를 신뢰할 수 있는 방법이 없으며, 설령 확인할 수 있다 하더라도 부작용 없이 명령어 내의 경로를 대체할 수 있는지 확신할 수 없다.

따라서 ASAR 아카이브 내부에서 바이너리를 실행하려면 execFile을 사용해야 한다. 이 방법은 파일 경로를 직접 지정할 수 있어 ASAR 아카이브 내부의 파일을 안정적으로 실행할 수 있다.

ASAR 아카이브에 압축 해제된 파일 추가하기

앞서 언급했듯이, 일부 Node API는 호출될 때 파일을 파일 시스템에 압축 해제한다. 이 동작은 성능 문제를 일으킬 뿐만 아니라 다양한 안티바이러스 스캐너를 트리거할 수도 있다.

이 문제를 해결하기 위해 --unpack 옵션을 사용해 특정 파일을 압축 해제된 상태로 남겨둘 수 있다. 다음 예제에서는 네이티브 Node.js 모듈의 공유 라이브러리가 압축되지 않도록 설정한다:

$ asar pack app app.asar --unpack *.node

이 명령어를 실행하면 app.asar 파일과 함께 app.asar.unpacked라는 폴더가 생성된다. 이 폴더에는 압축 해제된 파일이 포함되어 있으며, app.asar 아카이브와 함께 배포해야 한다.