かもメモ

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

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一体…気になりますね…