0
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?

Cloud Run functions × TypeScript 開発をしよう!【ビルド:Parcel を使ったTypeScriptのビルド】

Last updated at Posted at 2024-12-08

はじめに

この記事では以下のことについて解説しています。

  • TSのビルドツールの選定
  • Cloud Run functions関数を Parcel を使ってビルドする手法
  • Parcelでのモジュール解決方法
  • Parcel の注意点 / 問題点

TypsScriptのビルドツールを選ぶ上で考えること

本番環境ではTypeScriptをそのまま動かすことはしません。JavaScriptに変換(トランスパイル)して使います。
この変換するプロセスを「ビルド」と言いますが、TS → JSのビルドプロセスは以下のように進みます。

  1. 型チェック
  2. TypeScript → JavaScript変換
  3. ECMAScript構文変換
  4. Polyfill(古いブラウザでサポートされていない機能を使えるようにするためのコード変換)
  5. バンドル(一つのファイルにまとめる, minifyする)

mermaid-diagram-2024-12-08-211821.png

巷には大量のTSビルドツールが溢れていますが、上記のプロセスを踏んでいるかつxxx.config.json的なものを一生懸命書かなくてもサクッと使える Parcel を選択したパターンを紹介したいと思います。

Parcelは上記で述べたビルドプロセスに加えて、

  • Tree shaking(実行されないコードの削除)
  • minify

もやってくれる上に、「Zero Config」つまりほとんど自前で設定する必要がないという特徴があります。規模の大きなプロジェクトとかだと物足りないかもしれませんが、今回の開発要件(小規模かつ一人で開発している)だと十分です。

Parcel で Node.js関数(TS)をビルドする

まず Parcel を devDependencies にインストールしましょう。

npm install --save-dev parcel

以下のコマンドを package.json に追加しましょう。

package.json
  "scripts": {
    "build": "npx parcel build ./src/index.ts"
  }

npm run build を実行してみましょう。

$ npm run build                                            

> advent-calendar-2024@1.0.0 build
> npx parcel build ./src/index.ts

✨ Built in 228ms

dist/index.js    211 B    39ms

tsconfig.json のファイルを読み込んでよしなにファイル出力してくれました。

ビルド後のディレクトリ構成
├── .parcel-cache
├── dist
│   └── index.js
│   └── index.js.map
...

.parcel-cache は適宜 .gitignore などに入れておきましょう。

しかし Parcel は何も設定していないとTSの型チェックを行ってくれません。
試しに以下のように書き換えてあえて型エラーを起こしてみましょう。

stringの型に数値を入れてみる
import * as ff from '@google-cloud/functions-framework';
import type { HttpFunction } from "@google-cloud/functions-framework";

export const helloGET: HttpFunction =  (req: ff.Request, res: ff.Response) => {
    let a: string = "aaa";
    a = 1;
    res.send(`Hello World!`);
};

npm run build を入力してみます。

$ npm run build                                            

> advent-calendar-2024@1.0.0 build
> npx parcel build ./src/index.ts

✨ Built in 228ms

dist/index.js    211 B    39ms

通ってしまいました。出力後のファイルを見ても何にも改善されていません。

const $c3f6c693698dc7cd$export$900f632196ed0f96 = (req, res)=>{
    let a = "aaa";
    a = 1;
    res.send(`Hello World!`);
};


export {$c3f6c693698dc7cd$export$900f632196ed0f96 as helloGET};

公式のページではこのことについて「Parcelはデフォでは型チェック行わへんで〜〜別途コマンド叩いて確認してや〜〜」と述べています。

一応Experimentalではありますが、型チェック用のプラグイン @parcel/validator-typescript が用意されています。このプラグインを使うという情報を設定ファイルを作って伝えましょう。Zero Configとは。

.parcelrcを追加する
├── .parcelrc
├── src
...
.parcelrc
{
  "extends": "@parcel/config-default",
  "validators": {
    "*.{ts,tsx}": ["@parcel/validator-typescript"]
  }
}

もう一度 npm run build を実行してみましょう。

$ npm run build                             

> advent-calendar-2024@1.0.0 build
> npx parcel build ./src/index.ts

🚨 Build failed.

@parcel/validator-typescript: Type 'number' is not assignable to type 'string'.

  /Users/usr0302216/local/advent-calendar-2024/src/index.ts:5:3
    4 |   let a: string = "aaa";
  > 5 |   a = 1;
  >   |   ^^ Type 'number' is not assignable to type 'string'.
    6 |   res.send(`Hello World!`);
    7 | });

しっかり型チェックが走るようになりましたね。
「Experimental」と書いてあるように、まだ実験段階の機能のため不安定な挙動を起こすことがあるようです。ビルド前に tsc --noEmit を叩いて型チェックを挟むのが安全かもしれませんね。

Parcelのモジュール解決方法

「Zero Config」ではありますが、何も設定しなくていい反面何かカスタマイズしようとすると超めんどくさかったりします。
例えば tsconfig.json の章で紹介した以下の設定。

tsconfig.json(最初)
{
  "compilerOptions": {
    "target": "es2022",
    "module": "node16",
    "moduleResolution": "node16",
    "baseUrl": "./src",
    "paths": {
      "@/*": ["./*"]
    },
    "rootDirs": ["./src"],
    "outDir": "./dist",
    "sourceMap": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
  },
  "include": ["src/**/*"],
  "exclude": ["dist", "node_modules"]
}

Parcel を使うと強制定期にpackage.jsonの存在するディレクトリがbaseUrlかつrootUrlになります。ついでにpathsのエイリアスも~(チルダ)になります。そして Parcel の設定にtsconfig.jsonを合わせなければいけません。

Parcel 用にカスタマイズ
{
  "compilerOptions": {
    "target": "es2022",
    "module": "node16",
    "moduleResolution": "node16",
    "baseUrl": ".",
    "paths": {
      "~/*": ["./*"]
    },
    "rootDirs": ["."],
    "outDir": "./dist",
    "sourceMap": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
  },
  "include": ["src/**/*"],
  "exclude": ["dist", "node_modules"]
}

例えば以下のディレクトリ構成でファイルを作成したとします。

サンプル
├── src
│   ├── hoge
│   │   └── hoge.ts
│   └── index.ts
├── package.json
└── tsconfig.json
src/hoge/hoge.ts
export const hoge = () => "hogehoge"

このsrc/hoge/hoge.tssrc/index.tsで呼び出すとなると、以下のようになります。

src/index.ts
import * as ff from '@google-cloud/functions-framework';
import type { HttpFunction } from "@google-cloud/functions-framework";

import { hoge } from '~/src/hoge/hoge.js';

export const helloGET: HttpFunction =  (req: ff.Request, res: ff.Response) => {
  const hogehoge = hoge();
  console.log(hogehoge)
  res.send(`Hello World!`);
};
build
$ npm run build

> advent-calendar-2024@1.0.0 build
> npx parcel build ./src/index.ts --no-cache

✨ Built in 466ms

dist/index.js    347 B    32ms

出力されたファイルを見ると、パス解決がされたバンドルファイルが出力されました。

dist/index.js
const $a15d677254c32d38$export$bdaf53e7a5e88bfe = ()=>"hogehoge";


const $c3f6c693698dc7cd$export$900f632196ed0f96 = (req, res)=>{
    const hogehoge = (0, $a15d677254c32d38$export$bdaf53e7a5e88bfe)();
    console.log(hogehoge);
    res.send(`Hello World!`);
};


export {$c3f6c693698dc7cd$export$900f632196ed0f96 as helloGET};
//# sourceMappingURL=index.js.map

Parcel のイケていないところ

僕的に Parcel を使っていて思った痒いところは、

  • せっかく書いた tsconfig.json の設定をあんまり使ってくれない
    • paths, baseUrl が Parcel 側で決まっている
    • なんなら Parcel 側に合わせて書き換えろと言われる始末
    • 結果 ~/src って書かないといけないが、なんかダサい
  • ビルド後のファイルの minifyが 甘い
  • ビルド時の型チェックが実験的な機能でしかできない

とその他、言ったところでしょうか。微妙にやりたいことができない割に実現しようと思うと結構めんどくさいなといった印象です。こだわらなければ楽でそれなりにいいツールだと思います。色々と細かい設定をしたいはずのビルドが「Zero Config」でできるはずがないんですよね

補足

moduleResolutionnode16 を使用しているためモジュールインポートの際に拡張子を記載しています。今ではこの拡張子も記載する記法がスタンダードになっているみたいですね。

おわりに

今回は Parcel を利用したTSのビルド方法を紹介しました。

Parcelを使ったビルド環境を構築したことがあったので紹介してみたのですが、記事書いていて「あれ?Parcel微妙じゃね?」ってなったのでもう一つビルド系の記事書きます。

おまけ:トラブルシューティング

@parcel/core: Failed to resolve '@google-cloud/functions-framework' from
@parcel/resolver-default: External dependency "@google-cloud/functions-framework" is not declared in package.json.

原因

ビルド時に@google-cloud/functions-frameworkが見つからねーよって言っています。
devDependencies にfunctions-frameworkをインスコしている可能性が高いです。本番用にビルドするため、本番用のライブラリを読みこむ dependencies に置かないといけません。

解決法

functions-frameworkを以下のコマンドで devDependencies → dependencies に移しましょう。

npm install --save-prod @google-cloud/functions-framework
0
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
0
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?