1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Single executable applicationsを使ってnodeアプリケーションを簡単にビルドできるようにする

Last updated at Posted at 2024-11-09

はじめに

こんにちは、Never3924です。
NodeのSingle executable applicationsを使って簡単にビルドができるようにしよう、という記事です。

環境

NodeJSのv20.1.2
Windows 10
で動作確認。

本題

早速やりましょう。ていうか以下のコードをコピペです。

build.bat
@echo off
setlocal enabledelayedexpansion
chcp 65001

mkdir dist
cd dist

set CALLPOWERSHELL_RETURN="";

echo Collecting information...
call :CALLPOWERSHELL "$text = Get-Content -Path "../package.json" -Raw; $pattern = '\s+\"main\"\s?:\s?\"(.*)\"'; if($text -match $pattern) { $filename = $matches[1] };echo $filename"
set FILENAME=%CALLPOWERSHELL_RETURN%

call :CALLPOWERSHELL "$text = Get-Content -Path "../package.json" -Raw; $pattern = '\s+\"main\"\s?:\s?\"(.*)\..+\"'; if($text -match $pattern) { $filename = $matches[1] }; echo $filename"
set FILENAMEONLY=%CALLPOWERSHELL_RETURN%

call :CALLPOWERSHELL "$signtoolPath = Get-Command signtool -ErrorAction SilentlyContinue; if ($signtoolPath) {echo True} else {echo False}"
set ISINSTALL_SIGNTOOL=%CALLPOWERSHELL_RETURN%

call :CALLPOWERSHELL "$npm = npm list --depth=0; $foundEsbuild = $false; foreach ($line in $npm.Split(\"`n\")) {if ($line -match \"esbuild\") {$foundEsbuild = $true; break}}; echo $foundEsbuild"
set ISINSTALL_ESBUILD=%CALLPOWERSHELL_RETURN%

if not "%~1"=="" (
    set FILENAME=%~1
    call :CALLPOWERSHELL "[System.IO.Path]::GetFileNameWithoutExtension(\"!FILENAME!\")"
    set FILENAMEONLY=!CALLPOWERSHELL_RETURN!

    if "%2"=="debug" (
        echo !CALLPOWERSHELL_RETURN!
    )
)

set OUTPUTNAME=!FILENAMEONLY!.exe

if "%2"=="debug" (
    echo !FILENAME!
    echo !FILENAMEONLY!
    echo %ISINSTALL_SIGNTOOL%
    echo %ISINSTALL_ESBUILD%
    echo !OUTPUTNAME!
)

echo Collection has been completed.
if %ISINSTALL_SIGNTOOL%==False (
    echo The signature tool is not installed.
    echo Skip the signature processing.
)

if %ISINSTALL_ESBUILD%==False (
    echo esbuild is not installed.
    echo installing...
    echo npm i -D esbuild > tmp.bat
    call tmp.bat
)

echo Bundle the js file.
echo @echo off > tmp.bat
echo %~dp0node_modules\.bin\esbuild !FILENAME! --bundle --outfile=./dist/out.js --platform=node --minify=true >> tmp.bat
echo exit /b >> tmp.bat

cd ../
call ./dist/tmp.bat
cd dist

echo Prepare for the build.
echo { "main": "out.js", "output": "sea-prep.blob" } > sea-config.json
node --experimental-sea-config sea-config.json
echo Copy the node.
node -e "require('fs').copyFileSync(process.execPath, '!OUTPUTNAME!')"
if %ISINSTALL_SIGNTOOL%==True (
    echo I will verify the signature.
    echo Temporarily remove the signature.
    signtool remove /s !OUTPUTNAME!
    echo The signature is complete.
)
echo Starting the build.
echo npx postject !OUTPUTNAME! NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 > tmp.bat
call tmp.bat
if %ISINSTALL_SIGNTOOL%==True (
    echo I will sign.
    signtool sign /fd SHA256 !OUTPUTNAME!
    echo The signature is complete.
)
echo Build has finished.
echo Delete the generated file.
del sea-config.json > nul
del sea-prep.blob > nul
del out.js > nul
del tmp.bat > nul
if %ISINSTALL_ESBUILD%==False (
    echo uninstall esbuild
    echo npm uninstall esbuild > tmp.bat
    call tmp.bat
)
if "%2"=="debug" pause
goto :EOF

:CALLPOWERSHELL
echo @echo off > tmp.bat
echo powershell -Command %1 >> tmp.bat
echo exit /b >> tmp.bat
for /f "usebackq" %%A in (`call tmp.bat`) do set CALLPOWERSHELL_RETURN=%%A
exit /b

:EOF

ながーい。予定の2倍ぐらいデカくなった。
なんかハイライトが変ですが気にしないでください

(2024/11/12 18:24 コード変更 npmでesbuildをインストール後、終了してしまう問題の修正)

package.jsonにbuildコマンドを追加しておくと楽ですね。

package.json
{
    ...
    "scripts": {
        "build": "build.bat",
    },
    ...
}

使い方

基本build.batをコマンドラインで実行すればメインファイルを読み出してビルドしてくれます。
事前にインストールしておくべきものはありません。自動で判断してインストールしてくれます。
インストールしたものは最後にアンインストールされます。
また、JSファイルをD&Dでもビルドしてくれます。

解説

@echo off
setlocal enabledelayedexpansion
chcp 65001

言うまでもないですね。ログを出さない、変数をローカルに、UTF-8を使うの三点セットです。
しかし遅延変数が使えるようにしてあります

mkdir dist
cd dist

出力場所を作成しています

set CALLPOWERSHELL_RETURN="";

echo Collecting information...
call :CALLPOWERSHELL "$text = Get-Content -Path "../package.json" -Raw; $pattern = '\s+\"main\"\s?:\s?\"(.*)\"'; if($text -match $pattern) { $filename = $matches[1] };echo $filename"
set FILENAME=%CALLPOWERSHELL_RETURN%

call :CALLPOWERSHELL "$text = Get-Content -Path "../package.json" -Raw; $pattern = '\s+\"main\"\s?:\s?\"(.*)\..+\"'; if($text -match $pattern) { $filename = $matches[1] }; echo $filename"
set FILENAMEONLY=%CALLPOWERSHELL_RETURN%

call :CALLPOWERSHELL "$signtoolPath = Get-Command signtool -ErrorAction SilentlyContinue; if ($signtoolPath) {echo True} else {echo False}"
set ISINSTALL_SIGNTOOL=%CALLPOWERSHELL_RETURN%

call :CALLPOWERSHELL "$npm = npm list --depth=0; $foundEsbuild = $false; foreach ($line in $npm.Split(\"`n\")) {if ($line -match \"esbuild\") {$foundEsbuild = $true; break}}; echo $foundEsbuild"
set ISINSTALL_ESBUILD=%CALLPOWERSHELL_RETURN%

powershellのコマンドを実行して情報を取得しています
取得情報は
・対象のファイルパス
・対象の拡張子抜きのファイル名
signtoolがインストールされているか
・esbuildがインストールされているか

if not "%~1"=="" (
    set FILENAME=%~1
    call :CALLPOWERSHELL "[System.IO.Path]::GetFileNameWithoutExtension(\"!FILENAME!\")"
    set FILENAMEONLY=!CALLPOWERSHELL_RETURN!

    if "%2"=="debug" (
        echo !CALLPOWERSHELL_RETURN!
    )
)

ファイルを指定する引数があったらファイルパスとファイル名を更新。

if %ISINSTALL_SIGNTOOL%==False (
    echo The signature tool is not installed.
    echo Skip the signature processing.
)

if %ISINSTALL_ESBUILD%==False (
    echo esbuild is not installed.
    echo installing...
    npm i -D esbuild
)

signtoolがなかったら署名をスキップする旨のメッセージを出す。
esbuildがインストールされてなかったらインストールする。

echo @echo off > tmp.bat
echo %~dp0node_modules\.bin\esbuild !FILENAME! --bundle --outfile=./dist/out.js --platform=node --minify=true >> tmp.bat
echo exit /b >> tmp.bat

cd ../
call ./dist/tmp.bat
cd dist

jsをバンドルする。勝手に処理を終了するので、別ファイルに書き込み。

以下ほぼチュートリアル通り。

echo { "main": "out.js", "output": "sea-prep.blob" } > sea-config.json
node --experimental-sea-config sea-config.json

sea-config.jsonを作ってsea-prep.blobを生成する。
ここらでやってることはよくわからない。

node -e "require('fs').copyFileSync(process.execPath, '!OUTPUTNAME!')"

nodeそのものをコピー。

if %ISINSTALL_SIGNTOOL%==True (
    echo I will verify the signature.
    echo Temporarily remove the signature.
    signtool remove /s !OUTPUTNAME!
    echo The signature is complete.
)

signtoolが入っていたら、署名を削除。入ってなかったら素通り。

echo npx postject !OUTPUTNAME! NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 > tmp.bat
call tmp.bat

バイナリを結合してるっぽい。ようわからん

if %ISINSTALL_SIGNTOOL%==True (
    echo I will sign.
    signtool sign /fd SHA256 !OUTPUTNAME!
    echo The signature is complete.
)

signtoolが入っていたら、署名する。

del sea-config.json > nul
del sea-prep.blob > nul
del out.js > nul
del tmp.bat > nul
if %ISINSTALL_ESBUILD%==False (
    echo uninstall esbuild
    npm uninstall esbuild
)

各種生成されたファイルの削除。また、先ほど入れたesbuildの削除。

以上で、解説終わり!

おわりに

すげぇbatの分量多くなりました。すいません。
でも汎用性のあるスクリプト作りたかったから良し
6時間近く溶かしました
ではまた。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?