かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

npm 上位階層にある node_modules を使った npm-script を実行したい

今回も文章量に対して大変地味なエントリーです!

一つのリポジトリ内にあるディレクトリで別々に sass をコンパイルしなければならないプロジェクトがあり、ディレクトリごとにコンパイルするためのパッケージをインストールするのは管理面でも避けたかったので、root に共通でパッケージをインストールして各ディレクトリ内でそれぞれの npm-script を実行したかったのメモ。

文章にすると分かりづらい…

ディレクトリ構成
/root
  |- .git
  |- /node_modules
  |- package.json
  |- /src
     |- /projects
         |- /theme-a
         |    |- /styles 
         |- /theme-b
         |    |- /styles
         |- /theme-c
         |    |- /styles
         |- /theme-d
         |    |- /styles
  1. /src/projects 内にある各 theme ディレクトリ内に閉じて CSS をビルドしたい
  2. ビルドに必要なパッケージは共通なので root に一括インストールしたい

root にある package.json にそれぞれのディレクトリ用の npm-script を作成する

今回は sass と postCSS を使用している想定です。

/package.json

{
  "name": "MyProject",
  "scripts": {
    // build の設定
    "sass": "sass /src/projects/${THEME}/styles:/src/projects/${THEME}/css --no-source-map",
    "postcss": "postcss /src/projects/${THEME}/css/style.css -o /src/projects/${THEME}/style.min.css",
    // theme-a 用の設定
    "build:css:themeA": "THEME=theme-a npm-run-all -s sass postcss",
    "dev:css:themeA": "THEME=theme-a npm-run-all -p \"sass -- --watch\" \"postcss -- --watch\"",
    // theme-b 用の設定
    "build:css:themeB": "THEME=theme-b npm-run-all -s sass postcss",
    "dev:css:themeB": "THEME=theme-b npm-run-all -p \"sass -- --watch\" \"postcss -- --watch\"",
    // theme-c 用の設定
    // theme-d 用の設定
  }
}

テーマがたくさんあるので、sass, PostCSS のコマンドはテーマ部分を変数で指定できるようにしました。
これが一番シンプルですが、root から実行する時にコマンドが長くなるのが難点です。

それぞれのテーマディレクトリでコマンドを実行したい

npm コマンドを実行する時、今いるディレクトリより上にある階層の package.json を探しに行って実行するので、各テーマ内に package.json だけ作ってしまえばうまくいくのではないかと思いました。

package.json と node_modules が同階層にないと上手く行かない

theme-a 内に package.json を作成しました。

/root
  |- .git
  |- /node_modules
  |- package.json
  |- /src
     |- /projects
         |- /theme-a
         |    |- package.json <- 作成
         |    |- /styles 

/src/projects/theme-a/package.json

{
  "name": "ThemeA",
  "scripts": {
    "sass": "sass styles:css --no-source-map",
    "postcss": "postcss css/style.css -o css/style.min.css",
    "build:css": "npm-run-all -s sass postcss",
    "dev:css": "npm-run-all -p \"sass -- --watch\" \"postcss -- --watch\""
  }
}

これで実行しても上手く動作しません。

$ cd /src/projects/theme-a/
$ npm run build:css
> npm-run-all -s sass postcss
sh: npm-run-all: command not found
npm ERR! code ELIFECYCLE
npm ERR! syscall spawn
npm ERR! file sh
npm ERR! errno ENOENT
npm ERR! ThemeA@build:css: `npm-run-all -s sass postcss`
npm ERR! spawn ENOENT
npm ERR! 
npm ERR! Failed at the ThemeA@ build:css script.

どうやら package.json は同階層にある node_modules を探しに行くようです。

root にある package.jsonスクリプトを実行するようにすればOK

npm-script は shell のエイリアスらしいので、root の package.json がある所まで移動して、root の npm-script を実行するようにすれば、子階層にある package.json 内にある npm スクリプトから root の npm-script を実行できるようになります。 (日本語むつかしい…)

/src/projects/theme-a/package.json を下記のように変更すればOK

{
  "name": "ThemeA",
  "scripts": {
    "build:css": "cd ../../../ && npm run build:css:themeA",
    "dev:css": "cd ../../../ && npm run dev:css:themeA"
  }
}

cd で root まで移動して、/root/package.json のコマンドを実行するようにしています。
👇 実行

$ cd /src/projects/theme-a/
$ npm run build:css
> ThemeA@ build:css 
> cd ../../../ && npm run build:css:themeA
> MyProject@build:css:themeA
> THEME=theme-a npm-run-all -s sass postcss
>

開発したいテーマの階層にいる状態で root にインストールした node_modules を使った npm-script が実行できるようになりました!
₍ ᐢ. ̫ .ᐢ ₎ yattaze!!

root のパスをいい感じに取得したい

子階層から npm-script でコマンドが実行できるようになったのですが、root のディレクトリの指定方法があまりイケてません。
今後運用の関係で構造が変更になったり階層が異なる場所から実行しなければならないようなケースが出てくるとパス指定を考えるのが面倒です。

git rev-parse を使ってプロジェクトのルートディレクトリを取得する

プロジェクトが git 管理されているなら git rev-parse --show-toplevel というコマンドを使ってプロジェクトのルートが取得できるので、これを利用して root の package.json があるディレクトリを取得すると良さそうです!

/src/projects/theme-a/package.json を下記のように変更します

{
  "name": "ThemeA",
  "scripts": {
-   "build:css": "cd ../../../ && npm run build:css:themeA",
+   "build:css": "cd ${git rev-parse --show-toplevel} && npm run build:css:themeA",
-   "dev:css": "cd ../../../ && npm run dev:css:themeA"
+   "dev:css": "cd ${git rev-parse --show-toplevel} && npm run dev:css:themeA"
  }
}

₍ ᐢ. ̫ .ᐢ ₎ いい感じになった!

所管

npm 使ったプロジェクトはどの階層にいても上の階層にある package.json のコマンドが使えていたので、package.json を子階層に作っても上の階層にある node_modules のパッケージを使えるのだと思っていたのですが、package.json があると同階層の node_modules ディレクトトリからパッケージを探しに行くのだと知れてよかったです。 それと、 npm-script に変数を渡す方法も少し詳しくなれました!
git rev-parse コマンドは全然知らなかったので、また詳しく調べてみようと思います。

ちょっとした事をしたいだけだったのですが、結構盛大にハマってしまいました。
ハマった分だけ強くなれる!ゴゴゴゴゴ…


[参考]

NPMな彼女is一体…気になりますね…

チーム開発するリポジトリで node のバージョンを揃えたい

チームでフロントの開発する時に node のバージョンが異なっているとインストールされるパッケージのバージョンとか依存とかで面倒なことになる可能性があるので揃えておくのが良いと思っています。
〇〇のバージョンをインストールしてください。とお願いしても確実性がないので極力仕組みで解決したい。

node.js のバージョンを管理できる nodenv の導入

homebrew で anyenv をインストールして anyenv 経由で nodenv をインストールします

$ brew install anyenv
$ echo 'eval "$(anyenv init -)"' >> ~/.zshrc
$ anyenv init
$ exec $SHELL -l
# nodenv  のインストール
$ anyenv install nodenv
$ exec $SHELL -l
$ nodenv versions
* system
# nodenv のアップデートを楽にするライブラリを導入
$ mkdir -p $(anyenv root)/plugins
$ git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update
# anyenv 関連をまとめてアップデート
$ anyenv update

cf.

nodenv でディレクトリのバージョンを指定する

$ cd project_dir
$ nodenv local 14.17.6

プロジェクトのディレクトリに .node-version というファイルが作成されるので、git commit してプロジェクトに加える。
.node-version のファイルが有る時に、nodenv が導入された環境で指定外の node のバージョンで node コマンドを実行すると、.node-version で指定されたバージョンをインストールするようにメッセージが表示されるようになります。

node のバージョンが異なる時に npm install を制限する

package.jsonengines という項目で使用する node のバージョンを指定できる

// package.json
{
  "engines": {
    // node のバージョンを v14.17.6 以上 v15 未満に固定 
    "node": "^14.17.6"
  }
}

この指定があると yarn では指定されたバージョン外の node で yarn install するとエラーになる

npm は --engine-strict オプションが必要

npm の場合は warning だけでインストールされてしまいます。
node install --engine-strict--engine-strict オプションを付ければ yarn と同じ様にエラーになりますが、チームのメンバーに 「npm install する時は --engine-strict オプションを付けてね。」とお願いしても人間は愚か (私が一番愚かだが) なので忘れてしまうでしょう。仕組みで解決したいですね…

.npmrc にオプションを設定できる

どうやら .npmrc というファイルがあると、底に書かれているオプションが設定されるっぽいです。
ここに先のオプションの指定を書いておくと npm install = node install --engine-strict となり yarn と同じ様にバージョンが異なる際にエラーを表示できるようになります。

$ touch .npmrc
$ echo 'engine-strict=true' >> .npmrc

これで npm でも yarn でも node.js のバージョンが異なると npm install がエラーになるようにできました!

所感

npm install エラーにできる設定があるのは知らなかったので勉強になりました。
やっぱ人の努力じゃなくて仕組みで自動的に解決できる方法が良いですね。らぶ。


[参考]

ねねっち〜

TypeScrip 配列から値の Union 型を作りたい

const soleil = ['星宮いちご', '霧矢あおい', '紫吹蘭'] みたいな配列から '星宮いちご' | '霧矢あおい' | '紫吹蘭' という配列の値の Union 型が作りたい。

typeof list[number] で配列の値の Union 型が作れる!

const soleil = ['星宮いちご', '霧矢あおい', '紫吹蘭'] as const;
type MemberOfSoleil = typeof soleil[number];
// => type MemberOfSoleil = '星宮いちご' | '霧矢あおい' | '紫吹蘭'

できた ₍ ᐢ. ̫ .ᐢ ₎👌

typeof soleilreadonly ["星宮いちご", "霧矢あおい", "紫吹蘭"] という型になるので、配列の index は number なので typeof soleil[number] で配列の各要素を取り出してきた型という解釈になるっぽい。(厳密には分かってない。ゆるふわ解釈)

Object で定義する方が可読性は良いかも

ただ typeof Array[number] って表記は若干なんの型ができてるのか解りにくい気もするので、元を配列ではなくオブジェクトにしてしまってオブジェクトの値を取り出す形にした方が見やすいかも。変数を使用する箇所は Object.values() すれば配列として扱えるし。

オブジェクトで定義した版

const soleil = {
  ichigo: '星宮いちご',
  aoi: '霧矢あおい',
  ran: '紫吹蘭',
} as const;

type MemberOfSoleil = typeof soleil[keyof typeof soleil];
// => type MemberOfSoleil = '星宮いちご' | '霧矢あおい' | '紫吹蘭'

おわり。


[参考]

久々にアイカツ!を貼る!!!!!!