JavaScript を用いた開発プロジェクトで私がよく使う設定やコードスニペットなどを自分への覚書としてまとめました.今後も利用頻度の高い Tips が出てきたら付け加えていきます.自分で参照するために一箇所にまとめているので長くなるかもしれません.
package.json 内のバージョン番号をコードに埋め込む
Webpack の DefinPlugin はソースコード内の文字列を別の文字に置き換えるプリプロセッサとして使えます.これを利用して package.json
からバージョン番号を取り出して例えばソースコード内の __VERSION__
と置き換えるだけです.
const version = __VERSION__;
しかしこれだと Webpack でビルドしていない状態のソースをそのままユニットテストで走らせたりすると __VERSION__
が未定義でエラーになります.それを回避するのが以下のスニペットです.
const version = (typeof __VERSION__) !== 'undefined' ? __VERSION__ : 'dev';
Webpack 側の設定は以下のようになります.
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 Modules の asset/source
を使います.ここでは .css
拡張子のファイルをバンドルに含めています.
module.exports = {
// ... 省略
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'asciidoctor-chunker.js',
},
module: {
rules: [{
test: /\.css$/i,
type: 'asset/source',
}],
},
};
バンドルされた asset にコードからアクセスするには import
文でロードします.もし,常にバンドル化されたスクリプトからしかプログラムを実行しないのであれば,ソースコードのトップレベルで静的 import
を使って以下のようにロードします.
import aseet from './css/asciidoctor-chunker.css';
const css = asset.default; // moduleのdefaultでアクセスできる
しかし上のコードは Webpack でバンドル化したプログラムでしか使えません.開発中にソースの変更を watch して単体テストを動かす場合は,変更のたびにビルドすることなく直接ソーススクリプトを実行したいでしょう.そのような時は,以下のようにダイナミック import を使いロードに失敗した場合にローカルファイルからデータを読み込むという方法を使います.ただ,ここで注意すべきは,ダイナミック import はもともと遅延ロードのための機能なため,Webpack はビルド時にファイルをバンドルファイル内に埋め込まないという点です.それを回避するにはコメント行で
webpackMode: "eager"
と書き,Webpack に向けたディレクティヴを指定します.
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-all
が npm run watch
と npm run live
を並行実行します.それぞれのスクリプトの動作は以下の通りです.
-
npm run watch
:src-doc
とbuild/adoc
ディレクトリ内の変更をnpm-watch
が常時監視して変更があればnpm run html
を実行します. -
npm run live
:build/html
ディレクトリ内の変更を監視し変更があればブラウザをリロードします.
"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 watch と npm run live を並行実行する. |
プロジェクトをまたがって複数のプログラムを同時実行する
前節の例では一つの npm プロジェクト内で複数スクリプトを並列起動する方法ですが,別々のプロジェクトから npm スクリプトをそれぞれ並列起動したいことがあります.例えば SPA 用プロジェクト内で webpack の dev-server を起動して変更をライブビューで確認しながらコーディングする一方,SPA にデータをフィードする REST サーバーを同時に起動しておきたいケーづなのです.ここではシェルスクリプトから起動する方法と,parallel というプログラムを用いる方法の2つを紹介します.もちろんコンテナに全て納めてオーケストレーションツールでコマンド一発起動という方法もありますが,小さなプロジェクトでそこまでしたくない場合には,ここに記述する方法で十分でしょう.
元ネタは StackOverflow です.
some_command &
P1=$!
other_command &
P2=$!
wait $P1 $P2
parallel ::: prog1 prgo2