JavaScript プロジェクトで私がよく使う定番 Tips 集

JavaScriptを使った開発プロジェクトでよく使う定番 Tips の覚書です.
目次

JavaScript を用いた開発プロジェクトで私がよく使う設定やコードスニペットなどを自分への覚書としてまとめました.今後も利用頻度の高い Tips が出てきたら付け加えていきます.自分で参照するために一箇所にまとめているので長くなるかもしれません.

package.json 内のバージョン番号をコードに埋め込む

Webpack の DefinPlugin はソースコード内の文字列を別の文字に置き換えるプリプロセッサとして使えます.これを利用して package.json からバージョン番号を取り出して例えばソースコード内の __VERSION__ と置き換えるだけです.

const version = __VERSION__;

しかしこれだと Webpack でビルドしていない状態のソースをそのままユニットテストで走らせたりすると __VERSION__ が未定義でエラーになります.それを回避するのが以下のスニペットです.

const version = (typeof __VERSION__) !== 'undefined' ? __VERSION__ : 'dev';

Webpack 側の設定は以下のようになります.

コード 1. package.json ファイル内の設定
const version = JSON.stringify(require('./package.json').version);

module.exports = {
  // ... 省略
  plugins: [
    new webpack.DefinePlugin({
      __VERSION__: version,
    })
  ],
  // ... 省略
};

プロジェクトでの使用例: webpack.config.js, コマンドオプション

Webpack でバンドル化した asset にコード内からアクセスしデータとして使う

プログラム内で用いるデータをソースコードとは別ファイルにしておくとき,開発中のユニットテスト等ではファイルからデータを読み込む一方,デプロイ時にはデータファイルをソースコードと一緒にバンドルし,そこからデータを読み込みたいことがよくあります.

例えば asciidoctor-chunker プロジェクトでは,HTML ファイルをプログラムから生成する際に,ローカル環境では別ファイルである CSS ファイルを copyIfNewer 関数内fs.copyFile() を使ってコピーしています.しかし,Webpack によって asset としてバンドル化されている場合は, import を使ってバンドル内から CSS ファイルの内容をデータとしてロードしています.

上のプロジェクトを例にまず Webpack の設定を以下に示します.Asset Modulesasset/source を使います.ここでは .css 拡張子のファイルをバンドルに含めています.

コード 2. webpack.config.js ファイル
module.exports = {
  // ... 省略
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'asciidoctor-chunker.js',
  },
  module: {
    rules: [{
      test: /\.css$/i,
      type: 'asset/source',
    }],
  },
};

バンドルされた asset にコードからアクセスするには import 文でロードします.もし,常にバンドル化されたスクリプトからしかプログラムを実行しないのであれば,ソースコードのトップレベルで静的 import を使って以下のようにロードします.

コード 3. 静的 import による asset のロード
import aseet from './css/asciidoctor-chunker.css';

const css = asset.default;  // moduleのdefaultでアクセスできる

しかし上のコードは Webpack でバンドル化したプログラムでしか使えません.開発中にソースの変更を watch して単体テストを動かす場合は,変更のたびにビルドすることなく直接ソーススクリプトを実行したいでしょう.そのような時は,以下のようにダイナミック import を使いロードに失敗した場合にローカルファイルからデータを読み込むという方法を使います.ただ,ここで注意すべきは,ダイナミック import はもともと遅延ロードのための機能なため,Webpack はビルド時にファイルをバンドルファイル内に埋め込まないという点です.それを回避するにはコメント行で webpackMode: "eager" と書き,Webpack に向けたディレクティヴを指定します.

コード 4. ダイナミック import を使った asset のロード
import( /* webpackMode: "eager" */      (1)
       './css/asciidoctor-chunker.css') // webpack bundle
  .then(module => fsp.writeFile(dest, module.default))    (2)
  .catch(e => {                                           (3)
    const __dirname = path.dirname(fileURLToPath(
      import.meta.url));
    const src = path.resolve(__dirname, 'css', 'asciidoctor-chunker.css');
    copyIfNewer(src)(dest);
  }); // no bundle, regular file
1 ファイルをダイナミック import していますが,ファイル前のコメントで eager モードを指定してWebpackにファイルをバンドルするように指定しています.
2 バンドルされている asset からロードできた場合はプロミスが resolve 時に module を返すので,module.default でロードしたデータにアクセスできます.
3 ロードに失敗した場合は catch() でローカルディスクのファイルにアクセスして CSS ファイルを処理しています.

同じプロジェクト内の複数の npm スクリプトを同時実行する

npm-run-all を使います.例えば,下の例では npm run dev を実行すると npm-run-allnpm run watchnpm run live を並行実行します.それぞれのスクリプトの動作は以下の通りです.

  • npm run watch: src-docbuild/adoc ディレクトリ内の変更を npm-watch が常時監視して変更があれば npm run html を実行します.

  • npm run live: build/html ディレクトリ内の変更を監視し変更があればブラウザをリロードします.

コード 5. package.json ファイル
  "watch": {       (1)
    "html": {
      "patterns": [
        "src-doc",
        "build/adoc"
      ],
      "extensions": "adoc,anw"
    }
  },
  "scripts": {
    "dev": "npm-run-all --parallel watch live",  (2)
    "watch": "npm-watch",
    "live": "live-server build/html",
    "html": "make html",
  },
1 npm-watch の設定.変更があれば npm run html を実行する.
2 npm run dev を実行すると npm run watchnpm run live を並行実行する.

プロジェクトをまたがって複数のプログラムを同時実行する

前節の例では一つの npm プロジェクト内で複数スクリプトを並列起動する方法ですが,別々のプロジェクトから npm スクリプトをそれぞれ並列起動したいことがあります.例えば SPA 用プロジェクト内で webpack の dev-server を起動して変更をライブビューで確認しながらコーディングする一方,SPA にデータをフィードする REST サーバーを同時に起動しておきたいケーづなのです.ここではシェルスクリプトから起動する方法と,parallel というプログラムを用いる方法の2つを紹介します.もちろんコンテナに全て納めてオーケストレーションツールでコマンド一発起動という方法もありますが,小さなプロジェクトでそこまでしたくない場合には,ここに記述する方法で十分でしょう.

元ネタは StackOverflow です.

コード 6. bash スクリプト
some_command &
P1=$!
other_command &
P2=$!
wait $P1 $P2
コード 7. GNU Parallel を使う場合
parallel ::: prog1 prgo2

wshito

Read more posts by this author.

Itoshima, Japan http://diary.wshito.com