はじめに
さきほど、JavaScript版のexpressでc言語のプログラムを実行するのを書いたばかりですがその続編で、TypeScript版をお届けします。
この記事は、c言語とかRenderとかはあまり書かず、JavaScriptでexpressを作ったのと同じことをTypeScriptでやるにはどうするかっていう、基礎に立ち戻った感じになりそうです。
c言語のコンパイルは、JavaScriptのままとします。(そこをTypeScriptにこだわる必要性を感じなかったので)
githubのリポジトリは、JavaScriptと同じで、フォルダを「app-c-lang2-ts」にしました。こちらもRenderで動作確認済み。
手順
1. 初期処理
mkdir app-c-lang2-ts
cd app-c-lang2-ts
npm init -y
npx tsc --init
npm install express
npm install --save-dev @types/express
ここから!?っていう感じですが、基礎なので。
2. サーバーをコーディング
expressサーバーです。
import express, { Request, Response } from "express";
import path from "path";
import { exec } from "child_process";
const app = express()
const port = 3000
app.get('/', (req: Request, res: Response) => {
const outputFileName = 'hello' + (process.platform === 'win32' ? '.exe' : '');
const buildOutputPath = path.join(__dirname, outputFileName);
exec(buildOutputPath, (runError, stdout, stderr) => {
if (runError) {
console.error(`実行エラー: ${runError}`);
return res.status(500).send(`実行エラー: ${runError}`);
}
if (stderr) {
console.error(`標準エラー出力: ${stderr}`);
return res.status(500).send(`標準エラー出力: ${stderr}`);
}
res.send(stdout);
});
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
JavaScriptとの違いは、app.get('/', (req: Request, res: Response) => {
と型定義しただけです。そのためのimport
と。
あ、ESMになっているので、requireじゃなくてimportに変えてます。
3. package.jsonでビルドと実行の仕方を指示
npm run build
で行うことは3つ
- 依存するモジュールをインストール
- TypeScriptをJavaScriptへ変換して、
dist
ディレクトリへ出力 - c言語ソースをコンパイルして、
dist
ディレクトリへ出力
npm run start
では、dist
ディレクトリに出力されたapp.js
(JavaScript)を実行する。
"scripts": {
"start": "node ./dist/app.js",
"build": "npm install && npx tsc && node ./scripts/build-with-c.js"
},
TypeScriptを直接実行するわけでなく、dist
に出力されたJavaScriptを実行するんだということがわかっていれば、難しい話ではないですが、たぶん初心者は躓くと思います。そのため、c言語のモジュールもdist
ディレクトリへコピーします。(次)
4. TypeScriptの出力先を設定
~~省略~~
"outDir": "./dist",
~~省略~~
outDir
という項目に、./dist
を書いておきます。
そうすると、dist
フォルダに出力されます。
5. c言語ソースのコンパイルスクリプト
ここは、JavaScript版と比べて処理は変更していないので、その話は割愛します。JavaScript版をご覧ください。
コンパイル後のコピー先であるbuildOutputFilePath
をdist
にしている点だけが変更点です。
const { exec } = require('child_process');
const { copyFileSync } = require('fs');
const { platform } = require('os');
// コンパイルするCプログラムのパスと出力ファイル名
const scriptsDirName = "scripts";
const inputFilePath = `./${scriptsDirName}/hello.c`;
const outputFileName = 'hello' + (platform() === 'win32' ? '.exe' : '');
const outputFilePath = `./${scriptsDirName}/${outputFileName}`;
const buildOutputFilePath = `./dist/${outputFileName}`;
// Cプログラムをコンパイル
exec(`gcc ${inputFilePath} -o ${outputFilePath}`, (compileError) => {
if (compileError) {
console.error(`コンパイルエラー: ${compileError}`);
process.exit(1);
}
console.log('Cプログラムのコンパイル成功');
// コンパイルしたプログラムをビルドディレクトリにコピー
copyFileSync(outputFilePath, buildOutputFilePath);
console.log('ビルドディレクトリにhelloをコピー');
});
ここもTypeScriptにする必要性ってあるのかな。c言語のソースが複雑になっても、ここが複雑になるわけではないし、型をしっかり確認したいこともあんまりないような。
そしてTypeScriptにすることで、package.json
などの設定系の変更が必要になり、そっちが複雑になることを避けました。
以上です。
おわりに
前回のJavaScript版の"おわりに"に書いた通り、やはりJavaScriptで実装した後にTypeScriptで書き直せば、そんなにハマることはありませんでした。
あえていえば私は、最初、c言語のコンパイルもTypeScriptでやろうとして、「あーもう!」ってなってました。なので今回はいろいろ言い訳をして、そこは見ないことにしたらうまくいきました。🙈
次のステップがあるとしたら、ここまではできたから、次は「c言語のコンパイルをTypeScript」ということだけに着目すればよくなって、問題もすっきりして、案外簡単に解決できるかもしれないです。でも私はいまのところ、メリットを感じないのでやりませんが。