본문 바로가기
Random

node.js 어플리케이션 exe 파일로 컴파일하기

by SeanK 2023. 5. 23.

안녕하세요:) 오늘은 문득 nodejs어플리케이션을 어떻게 exe 파일로 컴파일하는지 궁금해 관련된 포스트를 읽어봤습니다.

그중 잘 설명된 글이 있어 아래와 같이 변역해 옮겨 적어봅니다.

Compiling a Node.js Application into an .exe File

개발단계에서 개발자는 어플리케이션을 만들고 실행시키기 위해 설치와 코딩과 같은 여러 프로세스를 거치게 됩니다. 하지만 실제 사용자는 그 안의 코드와 프로세스에는 관심이 없고 오로지 실행시키는 데에만 관심이 있죠.

 

이러한 이유로 운영체제 시스템에서 별다른 조작없이 단순히 실행시키기만 하는 파일이 필요합니다.

 

이번 튜토리얼에서는 node.js 어플리케이션을 실행파일로 만드는 법에 대해 알아볼 것입니다. 그리고 실제로 자바스크립트 파일을 실행파일로 변환해 보겠습니다.

What are Executable (.exe) files

실행파일 (.exe files): 인코딩 된 지시사항 묶음을 가지고 있는 파일. 파일을 클릭하거나 실행시키면 순차적으로 실행됩니다.

.exe는 윈도우의 실행파일 확장자입니다. 다른 운영체제도 별도의 실행파일 확장자를 가지고 있습니다. 예를 들어 리눅스나 유닉스 기반의 운영체제는 .bin, .elf 혹은 [none] 확장자를 가집니다.

 

여기서 우리는 윈도우 실행파일을 먼저 살펴볼 것입니다.

Pros of executable files

유저들이 어떠한 경우에 실행파일을 선호하는지 한번 살펴보겠습니다.

 

실행파일의 장점:

  • 코드없이 빠른 실행 - 파일을 실행시키기 위해서는 한 번의 클릭만이 필요합니다. 그러면 끝입니다. 프로그램을 실행시키기 위해 터미널에서 별도의 코드를 입력할 필요가 없죠.
  • 원치 않는 코드 변경 방지 - 예상치 않았거나 우발적인 소스코드 변경을 방지합니다.
  • 배포 지원: 하드웨어에 대한 지식이 없는 유저가 다른 패키지나 의존성 패키지를 설치할 필요가 없어집니다. 이는 다양한 기계에서 프로그램을 실행시킬 수 있도록 해줍니다.

Packages used

자바스크립트 파일을 실행파일로 컴파일할 때 자주 사용되는 패키지로는 아래 두 개가 있습니다.

 

  • nexe: 이 패키지는 node.js 어플리케이션을 하나의 실행파일로 컴파일해 주는 커맨드 라인 유틸리티입니다. 기본값으로, 윈도우 실행파일로 변환합니다.
  • pkg: 이 패키지는 node.js 어플리케이션을 여러 운영체제 혹은 하나의 운영체제를 위한 실행파일로 변환하는 패키지입니다.

nexe

필요한 패키지와 리소스를 이용해 프로젝트파일을 실행파일로 컴파일 해보도록 하겠습니다.

 

nexe installation

nexe를 글로벌 환경에 설치하기 위해 아래 커맨드를 터미널에서 실행합니다.

npm i nexe -g

만약 우분투를 사용한다면 아래 코드를 이용하시면 됩니다.

sudo npm i nexe -g
Note: pkg 모듈고는 달리 nexe는 로컬이 아닌 글로벌환경에 설치합니다. 만약 로컬에 저장한다면 에러가 발생하게 됩니다. 'nexe' not found와 같은 에러가 발생할 겁니다.

설치가 완료되면 아래 코드로 설치가 잘 되었는지 확인해 줍니다.

nexe -h

 

Compile one javaScript file into an executable using nexe

이제 어플리케이션의 엔트리 포인트를 실행파일로 변환하기 위해 아래 명령문을 입력합니다.

nexe index.js

위 코드는 nexe에게 빌드 프로세스동안에 오로지 indes.js 파일만을 바라보고 실행파일로 컴파일해야 한다고 알려주는 셈입니다. 위 과정이 끝나면 아래 커맨드를 터미널에 입력해 빌드를 시작합니다.

nexe --build

자동적으로 nexe 패키지는 어플리케이션의 타깃으로 시스템의 운영체제와 아키텍처 타입을 이용합니다.

따라서 사용자의 시스템에서 빠르게 실행이 가능합니다.

 

Compile the entire project into an executable using nexe

이 경우 두가지 방법이 있습니다.

  • 우선 --resource 혹은 -r 플래그를 아래와 같이 이용해  리소스를 추가할 수 있습니다.
nexe -r "views/**/*"

아웃풋 실행 파일 경로를 설정하는 --output 혹은 -o 혹은--target -t --name -n --build -b와 같이 추가적인 커맨드도 추가할 수 있습니다.

nexe에서 'target'은 플랫폼(window, linux, macos), 아키텍처 x86, x64 타입, 그리고 node.js 버전을 지칭하는 말입니다.
  • 리소스를 package.json파일 안에서 정의할 수 있습니다. 필자는 이 방법을 추천하는데 이유는 이후에 재실행을 위한 설정을 저장할 수 있기 때문입니다. 이미 어플의 main이 index.js임을 선언했으니, 추가적으로 build 스크립트를 추가하겠습니다.

package.json 파일로가서 scripts 태그 아래에 build 스크립트를 아래와 같이 추가해 줍니다.

(express node.js 어플의 경우)

{
    "scripts": {
        "start": "node index.js",
        "build": "nexe -r views/**/*",
        "test": "echo \"Error: no test specified\" && exit 1"
    }
}

이러면 빌드 도중 찾아야하는 리소스의 로컬의 위치를 알려주게 됩니다.

 

이제 아래 커맨드를 실행해 nexe에게 빌드 태그를 이용하도록 해줍니다.

npm run build

빌드 프로세스를 실행시키려면 아래 커맨드를 이용합니다.

npm --build

이러면 생성된 파일을 실행시킬 수 있습니다. 이러면 실행파일을 카피해 리소스 없이 다른 디렉터리에 넣고 실행시켜 리소스들이 실행파일에 잘 컴파일되었는지 확인할 수 있습니다.

 

pgk

 

pkg installation

로컬에 아래 커맨드로 패키지를 설치합니다

npm i pkg

글로벌 환경에 설치하고 싶다면 아래 커맨드를 이용합니다.

npm i pkg -g

설치를 기다린 다음에 백그라운드에 다른 코드가 실행되는건 없는지 확인해 줍니다.

 

Compile one javaScript file into an executable using pkg

아래 코드를 실행시켜 pkg 모듈에 어플리케이션의 엔트리 포인트를 알려줍니다.

 

index.js의 경우:

pkg index.js

 

별도의 타겟을 설정하지 않았으니 어플의 엔트리 포인트를 세 개의 다른 실행파일로 컴파일할 것입니다. 컴파일이 끝나면 메인 디렉터리로 가보겠습니다. 그러면 아래 세 파일이 만들어진 것을 확인할 수 있습니다.

  1. Window 운영체제 (index-win.exe)
  2. Linux 운영체제 (index-linux)
  3. mac 운영체제 (index-macos)

이는 별도의 운영체제를 명시하지 않았기 때문입니다. 사용하는 운영체제에 맞는 파일을 실행시켜 보세요. 만약 윈도우라면 index-win.exe 파일을 실행시키면 됩니다.

이러면 어플을 실행시키고 사용자와 어플의 상호작용을 하는 인터페이스 콘솔이 나타납니다.

Note: 실행파일이 작동하는 동안 어플리케이션도 작동합니다.

 

Deeper dive

그러면 조금더 깊게 들어가 node.js 버전과 운영체제를 명시하는 방법에 대해 알아보도록 하겠습니다. 이 경우 --targets 플래그를 이용해 설정합니다.

이전에 생성된 실행파일을 지우고 아래 커맨드를 실행합니다.

pkg index.js --targets node12-win-x64

위 명령문은 Node.js 12 버전과 윈도우 운영체제 64비트로 실행하는 파일로 컴파일하겠다는 뜻입니다.  

지원되는 Node.js 버전, 플랫폼, 아키텍쳐 범위는 아래 테이블과 같습니다.


Node.js Versions Platforms Archs
node8 alpine x64
node10 linux arm64
node12 linuxstatic (armv6)
node14 win (armv7)
node16 macos  
latest (freebsd)  

 

Compile your project into an executable using pkg

pkg에게 리소스 폴더를 알려주어야 합니다. 위와 마찬가지로 package.json 파일에 스크립트와 에셋 설정을 저장할 수 있습니다.

 

package.json에 가서 pkg에 아래와 같이 추가해 줍니다:

{
    "name": "executable",
    "bin": "index.js",
    "version": "1.0.0",
    "description": "Simple express app",
    "main": "index.js",
    "scripts": {
        "start": "node index.js",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "pkg": {
        "assets": [
            "views/**/*"
        ],
        "output": "dist"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
        "express": "^4.17.1",
        "pkg": "^5.2.1"
    }
}

 pkg에게 엔트리 포인트를 알려주기 위해 bin키를 추가해 주었습니다. pkg 스크립트 안에는 assets를 추가해 어떤 파일이 리소스를 가지고 있는지 알려주었습니다.

콤마로 구분해 더 추가할 수도 있습니다. "outputPath"를 추가해 output이 저장될 위치도 추가해 주었습니다.

 

package.json에 저장된 설정을 실행시키려면 아래 커맨드를 입력하시면 됩니다.

pkg .

혹은

pkg package.json

만약 특정 타겟으로 컴파일해야 한다면 아래와 같이 설정할 수 있습니다.

{
    "pkg": {
              "scripts": "build/**/*.js",
              "assets": "views/**/*",
              "targets": [ "node14-linux-arm64" ],
              "outputPath": "dist"
            }
}

 

File or directory not included error

만약 "was not included" 에러를 만나게 된다면, 아래의 방법을 한번 사용해 보시길 바랍니다. pkg 모듈에서는 상대적 경로의 direct concatenation 사용이 불가능합니다. 예를 들어 res.sendFile(__dirname + '/views/index.html');과 같이 말이죠.

 

이는 안 좋은 프로그래밍 습관일 뿐만 아니라 아래와 같은 에러를 반환합니다.

john@john:~/Tofa/Projects/Convert node project into .exe/Secondtest/express$ ./express
Error: File or directory '/**/express/views/index.html' was not included into executable at compilation stage. Please recompile adding it as asset or script.
at error_ENOENT (pkg/prelude/bootstrap.js:539:17)
at findNativeAddonForStat (pkg/prelude/bootstrap.js:1201:32)
at statFromSnapshot (pkg/prelude/bootstrap.js:1224:25)
at Object.stat (pkg/prelude/bootstrap.js:1250:5)
at SendStream.sendFile (/snapshot/express/node_modules/send/index.js:721:6)
at SendStream.pipe (/snapshot/express/node_modules/send/index.js:595:8)
at sendfile (/snapshot/express/node_modules/express/lib/response.js:1103:8)
at ServerResponse.sendFile (/snapshot/express/node_modules/express/lib/response.js:433:3)
at /snapshot/express/index.js:21:9
at Layer.handle [as handle_request] (/snapshot/express/node_modules/express/lib/router/layer.js:95:5)

 이는 pkg가 패턴을 인식하지 못하기 때문에 프로젝트 리소스와 경로를 올바르게 컴파일 하지 못하기 때문입니다.

 

__dirname을 사용하는 대신에, path.join 혹은 getDir()을 이용하길 바랍니다.

 

 

 

 

https://www.section.io/engineering-education/compile-your-nodejs-application-into-a-exe-file/