- 投稿日:2020-10-18T23:55:16+09:00
M5StickCの書き換えが面倒だったので、Node.jsでArduinoっぽくしてみた
M5StickCのように、ESP32にGroveが付いているのが多いで、いろんな周辺デバイスをいじりたいのですが、そのたびに、リコンパイル&書き込みをするのはすごく面倒で時間がかかるので、MQTTを使ってリモートから操作できるようにして、クライアント側はNode.jsでたたけるようにしました。
M5StickC側では、I2C、Serial、Lcd、Gpioを操作できるようにしましたので、一度バイナリを書き込んでしまえばあとは、クライアント側のNode.jsで周辺デバイスのドライバを書くことができます。
ですが、動作はそこまで速くない(特にLcd描画系)ので、お遊び程度に思ってください。で、Node.jsになって開発しやすくなったので、手元にあったI2Cデバイスを一気に実装しました。
・M5Stack ENVユニット
https://www.switch-science.com/catalog/5690/
・M5Stack TVOC/eCO2ユニット
https://www.switch-science.com/catalog/6619/
・Grove OLED Display 0.96”
https://www.switch-science.com/catalog/829/
・Grove Digital Light Sensor
https://www.switch-science.com/catalog/1174/以下のGitHubに上げておきました。
poruruba/RemoteArduino
https://github.com/poruruba/RemoteArduinoMQTTトピック名
2つのMQTTトピックを使います。
1つは、Node.js側からM5StickCに要求するコマンド送信のためのトピック
もう一つは、M5StickCから処理結果をNode.jsに返すためのトピックとりあえず、
前者は、m5lite/cmd【M5StickCのMacアドレスの16進数表記】、
後者は、m5lite/rsp【M5StickCのMacアドレスの16進数表記】
にしています。Arduino側
以下のライブラリを使わせていただいています。
tanakamasayuki/ESP32LitePack
https://github.com/tanakamasayuki/ESP32LitePackもちろん、それに含まれる以下も利用させていただきました。
lovyan03/LovyanGFX
https://github.com/lovyan03/LovyanGFXこれらのおかげで、描画系が整備され、マルチデバイスで動くようになりましたっ!!
また、以下も利用させていただいています。
knolleary/pubsubclient
https://github.com/knolleary/pubsubclientArduinoJson
https://arduinojson.org/MQTTで受信されるデータフォーマット
こんなJSONがM5StickCに届きます。
{ “client_id”: “【クライアントID】”, “tx_id”: 【トランザクションID】, “device_type”: “【デバイスタイプ】”, “cmd”: “【コマンド名】”, “params”: { “param1”: 【1番目のパラメータ】, “param2”: 【2番目のパラメータ】, “param3”: 【3番目のパラメータ】, “param4”: 【4番目のパラメータ】, “param5”: 【5番目のパラメータ】, “param6”: 【6番目のパラメータ】, “param7”: 【7番目のパラメータ】 } }クライアントIDとトランザクションIDは、送信側(Node.js側)が自由に付けて送ります。M5StickC側はそのまま同じ値をレスポンスに返します。トランザクションIDを毎回インクリメントすれば、送ったレスポンスかどうかがわかることになります。
デバイスタイプは今のところ以下の4種類です。
- Serial
- Gpio
- Lcd
- Wire、Wire1
コマンド名は、デバイスタイプごとにサポートする名前が異なります。
整理する意味でざっと上げてみました。
デバイス名 コマンド名 Serial begin ^ end ^ available ^ read ^ peek ^ flush ^ ^ println ^ write ^ write_str ^ write_buf Gpio pinMode ^ digitalWrite ^ digitalRead ^ analogRead ^ analogReadResolution Wire begin ^ requestFrom ^ beginTransmission ^ endTransmission ^ write ^ write_str ^ write_buf ^ available ^ read ^ read_buf Lcd setRotation ^ setTextColor ^ setBrightness ^ drawPixel ^ drawLine ^ drawRect ^ fillRect ^ fillScreen ^ drawTriangle ^ fillTriangle ^ drawCircle ^ fillCircle ^ drawEllipse ^ fillEllipse ^ drawBmpData ^ getRange およそArduinoに合わせてあるので、イメージしやすいかと思います。
M5StickCでは、受け取ったMQTTパケットを解析し、ArduinoのAPI呼び出しに渡す処理をしているだけです。
処理結果は、MQTTトピックにPublishして戻しています。レスポンスのフォーマットは以下の通りです。
{ “client_id”: “【クライアントID】”, “tx_id”: 【トランザクションID】, “device_type”: “【デバイスタイプ】”, “rsp”: “【コマンド名】”, “status”: “【処理結果】”, “params”: { “param1”: 【1番目のパラメータ】, “param2”: 【2番目のパラメータ】, “param3”: 【3番目のパラメータ】, “param4”: 【4番目のパラメータ】, “param5”: 【5番目のパラメータ】, “param6”: 【6番目のパラメータ】, “param7”: 【7番目のパラメータ】 } }cmdがrspに代わっただけです。
クライアントIDとトランザクションIDは、コマンドにあったものをそのまま返しています。
処理結果には、”OK”か”NG”が入ります。NGの場合には、reasonも一緒に理由が入って帰ります。
paramsは処理結果です。デバイスタイプとコマンド名で決まる処理内容によって異なります。複雑な処理はしておらず、ただただ、各コマンドをArduinoのAPIに変換する処理をえんえんと記述しています。
Node.js側
以下のファイル構成になっています。
arduino.js
これがメインとなるクラスです。これをrequireすると、Serial、Gpio、Lcd、WireおよびWire1が一緒にインスタンス化されます。arduino_device/Gpio.js
Gpioのコマンド送信のためのクラスです。arduino_device/Serial.js
Serialのコマンド送信のためのクラスです。arduino_device/Lcd.js
Lcdのコマンド送信のためのクラスです。arduino_device/Wire.js
Wireのコマンド送信のためのクラスです。
周辺デバイスはI2Cデバイスが多いので、こちらを多用しました。使い方(Node.js側)
まずは、メインとなるArduinoクラスをインスタンス化します。
index.jsconst Arduino = require('./arduino'); const arduino = new Arduino(MQTT_CLIENT_ID, MQTT_HOST_URL, MQTT_TOPIC_CMD, MQTT_TOPIC_RSP);その際に、接続するMQTTのクライアントID、MQTTブローカのURL、接続するMQTTトピック名の送信用と受信用を指定します。トピック名は、接続するM5StickCの設定に合わせます。
MQTTブローカのURLは以下のような感じに指定します。
tcp://【MQTTブローカのホスト名】:1883そして、以下のようにしてMQTTに接続し、内部でLcdの画面サイズを取得しています。
index.jsawait arduino.connect();あとは、Wireを使いたい場合は、arduino.Wireにあります。
index.jsvar wire = arduino.Wire; await wire.begin();周辺デバイスの制御
手持ちにある、I2Cデバイスの周辺デバイスを一通り実装しました。
I2Cアドレスが違うのであれば、I2Cハブを介して同時に接続できるかと思います。以下では4つ同時につないでいます。SSD 1308のOLED Displayは少々遅いですが、それ以外はあまり遅さは気になりませんかね。・M5Stack ENVユニット
device/DHT12.js
device/BME280.js
・M5Stack TVOC/eCO2ユニット
device/SGC30.js
・Grove OLED Display 0.96”
device/SSD1308.js
・Grove Digital Light Sensor
device/TSL2561.jsM5StackやAdafruitやseeedが提供するサンプルを参考にさせていただきました。っていうより、ほぼそのままNode.jsにポーティングです。
以下のようにして使います。index.jsconst SGC30 = require('./device/SGC30'); const DHT12 = require('./device/DHT12'); const TSL2561 = require('./device/TSL2561'); const BME280 = require('./device/BME280'); const SSD1308 = require('./device/SSD1308'); var bme280 = new BME280(wire); await bme280.begin(); var ret = await bme280.readTemperature(); console.log(ret); var ret = await bme280.readPressure(); console.log(ret); var dht12 = new DHT12(wire); var ret = await dht12.readTemperature(); console.log(ret); var ret = await dht12.readHumidity(); console.log(ret); var tsl2561 = new TSL2561(wire); await tsl2561.init(); var ret = await tsl2561.readVisibleLux(); console.log(ret); var sgc30 = new SGC30(wire); var ret = await sgc30.begin(); console.log(ret); await sgc30.IAQmeasure(); console.log(sgc30.TVOC); console.log(sgc30.eCO2); var ssd1308 = new SSD1308(wire); await ssd1308.init(); await ssd1308.clear(); await ssd1308.put_pixel(1, 1, true); await ssd1308.update();終わりに
自分はNode.jsに慣れているので、周辺デバイスのドライバ作成のデバッグが非常にはかどりました。修正・再実行が一瞬(数秒)なので。
ただ、動いたっぽく見えただけなので、いろいろバグがあるかもしれません。
さらに、I2C以外は動作確認していません。ほんと機械的な実装なので。。。以上
- 投稿日:2020-10-18T22:35:31+09:00
Obnizと圧電スピーカーで日が暮れたら「ゆうやけこやけ」を流して切なくなってみた
まずは完成形
CdSセル(照度センサ)で暗くなると、ゆうやけこやけが流れます。
この曲聞くと切なくなるな。。
#protoout #obniz
— @Jis (@PmanRabbit) October 18, 2020
夕暮れ時に「ゆうやけこやけ」を圧電スピーカーから流してみたよ。 pic.twitter.com/km2YmlAKAi使った部品
・Obniz Board 1Y
・圧電スピーカー(PKM13EPYH4000-A0)
・CdSセル(MI527/MI5527)
・カーボン抵抗 1/2W330Ω(CFS50J330RB)
・ミニブレッドボード(BB-601)
・ブレッドボード・ジャンパーワイヤ(オス-オス)(BBJ-20)接続図
コード
.jsconst Obniz = require('obniz'); const obniz = new Obniz('0000-0000'); // Obniz_ID // 任意の秒数待つことができる関数 // 参考: https://qiita.com/suin/items/99aa8641d06b5f819656 const sleep = (msec) => new Promise(res => setTimeout(res, msec)); // 音階 const Key = { "ド" : 261.626, "レ" : 293.665, "ミ" : 329.628, "ファ" : 349.228, "ソ" : 391.995, "ラ" : 440.000, "シ" : 493.883, "ド2" : 523.251, "レ2" : 587.330 } obniz.onconnect = async function () { // ディスプレイ表示 obniz.display.clear(); obniz.display.print('TEST'); // スピーカー const speaker = obniz.wired('Speaker', { signal: 0, gnd: 1 }); // 照度センサ obniz.io9.output(true); // io9電圧を5Vに(電源+) obniz.io11.output(false); // io11電圧を0Vに(電源−) // setIntervalで一定間隔で処理 setInterval(async function () { // io10をアナログピンに(照度センサーの値を取得) var voltage = await obniz.ad10.getWait(); console.log(`changed to ${voltage} v`); if (voltage < 0.3) { // 暗くなったら「ゆうやけこやけ」を流す speaker.play(Key["ソ"]); await sleep(1000); speaker.stop(); await sleep(100); speaker.play(Key["ソ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ラ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ソ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ソ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ソ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ミ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ド"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ド"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["レ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ミ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["レ"]); await sleep(1500); speaker.stop(); await sleep(500); speaker.play(Key["ミ"]); await sleep(1000); speaker.stop(); await sleep(100); speaker.play(Key["ミ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ソ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ラ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ド2"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ド2"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ラ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ソ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ソ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ラ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ソ"]); await sleep(500); speaker.stop(); await sleep(100); speaker.play(Key["ド2"]); await sleep(1500); speaker.stop(); await sleep(500); } }, 20000); // 20秒(約1曲分は待つ) }やってみた感想
圧電スピーカーは周波数で音階を決められるので、曲を流すのは割と簡単でした。
参考:音階周波数今回、圧電スピーカーで曲を流してみたかっただけですが、なかなか面白かった。ハマりそうです。。スピーカーを複数繋げれば、和音も表現できそうで曲の幅が広がりそうで、時間があったらやってみたい。
- 投稿日:2020-10-18T21:28:09+09:00
GitHub Actions で WebGL2 を動かすまで
概要
WebGL2 を含むプログラムを GitHub Actions の Node.js 環境で実行する現実的な選択肢は二つ
- Headless Chrome を使って localhost 越しに呼びたい式を渡し、評価された結果を受け取る
- node-gles を使って WebGL の関数呼び出しを OpenGL ES に内部で変換して呼び出す
どちらも面倒な点はありますが、それぞれ動かすところまで試したのでやり方を書いておきます。
対象読者
- Node.js を使ったことがある
- WebGL を使ったことがある
環境
- Node.js
- TypeScript
- Jest
リポジトリ
https://github.com/agehama/webgl2-test
方法1. Headless Chrome を使う
Chrome には画面を立ち上げずにバックグラウンドで動作するヘッドレスモードがあり、プログラムからこれを操作することでテストやキャプチャの撮影などが自動で行えます。
Node.js から Chrome を操作するには次のいずれかのライブラリを使用するのが一般的なようです。
- Chrome を立ち上げるのに chrome-launcher を、操作を行うのに chrome-remote-interface を使う
- Headless Chrome 用の高機能 API を提供する Puppeteer を使う
今回はページを開いて式を実行するだけなのでどちらでも大して変わりませんが、より簡単な Puppeteer を使うことにします。
npm install -D puppeteer実験1.1. WebGL の情報を取得して表示する(Chrome)
まずは WebGL のバージョンや使えるリソースのサイズを取得するプログラムで実験をしてみます。
ソースコード
次の
webglSimple()
は引数にWebGL2RenderingContext
を取るのでこれをどうやって与えるかというのが今回の問題になります。src/index.tsexport function webglSimple(gl: WebGL2RenderingContext): string { return [ `------------------------------------------------------------`, `gl.RENDERER | ${gl.getParameter(gl.RENDERER)}`, `gl.VERSION | ${gl.getParameter(gl.VERSION)}`, `------------------------------------------------------------`, `gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS | ${gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS)}`, `gl.MAX_CUBE_MAP_TEXTURE_SIZE | ${gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE)}`, `gl.MAX_FRAGMENT_UNIFORM_VECTORS | ${gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS)}`, `gl.MAX_TEXTURE_IMAGE_UNITS | ${gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)}`, `gl.MAX_TEXTURE_SIZE | ${gl.getParameter(gl.MAX_TEXTURE_SIZE)}`, `gl.MAX_VARYING_VECTORS | ${gl.getParameter(gl.MAX_VARYING_VECTORS)}`, `gl.MAX_VERTEX_ATTRIBS | ${gl.getParameter(gl.MAX_VERTEX_ATTRIBS)}`, `gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS | ${gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS)}`, `gl.MAX_VERTEX_UNIFORM_VECTORS | ${gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS)}`, `------------------------------------------------------------`, ].join('\n'); }この関数を GitHub Actions から呼び出して結果を表示するのがとりあえずの目標です。
ブラウザから使うので HTML も仮で用意しておきます。
public/index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>webgl2-test</title> </head> <body> <canvas id="gl"></canvas> <script src="./index.js"></script> </body> </html>テストコード
テストでは Puppeteer を使ってブラウザ上で JavaScript を実行します。
次の
remoteEval()
関数はpuppeteer.launch()
で Chrome を立ち上げた後page.goto()
で localhost に接続します。そしてページが読み込まれたらpage.evaluate()
を呼ぶことでページ上で式を実行してその結果を返します。test/chrome_helper.tsconst puppeteer = require('puppeteer'); export async function remoteEval(expr: () => any, port: number, headless: boolean, chromeFlags: string[] = []) { return new Promise(async (resolve: (x:any) => void) => { const browser = await puppeteer.launch({ headless: headless, args: chromeFlags }); const page = await browser.newPage(); await page.goto(`http://localhost:${port}`, { waitUntil: 'domcontentloaded' }); const result = await page.evaluate(expr); await browser.close(); resolve(result); }); }これを使って
src/index.ts
で定義したwebglSimple()
を呼び出すだけのテストを書きます。次の
call_webglSimple()
は先ほどの HTML を開いたページ上で実行するためdocument.querySelector()
で canvas を取得することができます。これを通常モードとヘッドレスモードでそれぞれ実行するテストを書きます。test/chrome_simple.test.tsimport {remoteEval} from "./chrome_helper"; const call_webglSimple = () => eval(`(() => { const canvas = document.querySelector("canvas"); const gl = canvas.getContext("webgl2"); return webglSimple(gl); })()`); test("simple (chrome headless)", (async function() { return remoteEval(call_webglSimple, 8080, true).then( (result:any) => { console.log(result); expect(`${result}`).not.toBe(""); }); }), 60000); test("simple (chrome browser)", (async function() { return remoteEval(call_webglSimple, 8080, false).then( (result:any) => { console.log(result); expect(`${result}`).not.toBe(""); }); }), 60000);GitHub Actions
最後にこのプログラムを実際に実行するワークフローを書きます。run 以外はテンプレをそのまま使いました。ここでは tsc で TypeScript をコンパイルした後 http-server を立ち上げた状態で test を実行しています。
.github/workflows/node.js.yml#... 略 ... jobs: chrome_simple: runs-on: macos-latest strategy: matrix: node-version: [12.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: | npm ci npm install -D puppeteer npm run tsc npm run server ./public -p 8080 & npm test -- chrome_simple実行結果
https://github.com/agehama/webgl2-test/runs/1268400898simple (chrome headless) ------------------------------------------------------------ gl.RENDERER | WebKit WebGL gl.VERSION | WebGL 2.0 (OpenGL ES 3.0 Chromium) ------------------------------------------------------------ gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS | 32 gl.MAX_CUBE_MAP_TEXTURE_SIZE | 8192 gl.MAX_FRAGMENT_UNIFORM_VECTORS | 261 gl.MAX_TEXTURE_IMAGE_UNITS | 16 gl.MAX_TEXTURE_SIZE | 8192 gl.MAX_VARYING_VECTORS | 32 gl.MAX_VERTEX_ATTRIBS | 32 gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS | 16 gl.MAX_VERTEX_UNIFORM_VECTORS | 256 ------------------------------------------------------------ simple (chrome browser) ------------------------------------------------------------ gl.RENDERER | WebKit WebGL gl.VERSION | WebGL 2.0 (OpenGL ES 3.0 Chromium) ------------------------------------------------------------ gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS | 80 gl.MAX_CUBE_MAP_TEXTURE_SIZE | 16384 gl.MAX_FRAGMENT_UNIFORM_VECTORS | 1024 gl.MAX_TEXTURE_IMAGE_UNITS | 16 gl.MAX_TEXTURE_SIZE | 16384 gl.MAX_VARYING_VECTORS | 32 gl.MAX_VERTEX_ATTRIBS | 16 gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS | 16 gl.MAX_VERTEX_UNIFORM_VECTORS | 1024 ------------------------------------------------------------GitHub Actions で動かした出力結果がこちらです。これで WebGL2 が動作することを無事確認できました!
ただし、ここで気になるのはヘッドレスモードのリソースサイズが通常モードより大きく制限されているという点です。このように同じ実行環境でもヘッドレスモードだとグラフィックスの挙動が変わるということは意識しておく必要があります。
実験1.2. Texture3D に MRT を使って描き込む(Chrome)
動作が確認できたので早速 WebGL2 でないと動かないプログラムを試してみましょう。
ソースコード
次の
webglTexture3d()
は Texture3D に Multiple Render Targets を使って値を描き込み、その結果をgl.readPixels()
で読んで配列に詰めて返す関数です。描き込む値はフラグメントシェーダの中で定義しています。src/index.tsexport function webglTexture3d(gl: WebGL2RenderingContext): Uint8Array[] { const vs = gl.createShader(gl.VERTEX_SHADER) as WebGLShader; { gl.shaderSource(vs, `#version 300 es void main() { vec3[6] vertices = vec3[]( vec3(-1.0, -1.0, 0), vec3(+1.0, -1.0, 0), vec3(-1.0, +1.0, 0), vec3(-1.0, +1.0, 0), vec3(+1.0, -1.0, 0), vec3(+1.0, +1.0, 0) ); gl_Position = vec4(vertices[gl_VertexID], 1); }` ); gl.compileShader(vs); } const fs = gl.createShader(gl.FRAGMENT_SHADER) as WebGLShader; { gl.shaderSource(fs, `#version 300 es precision mediump float; out uvec4 outColor[4]; void main() { uvec2 xy = uvec2(gl_FragCoord.xy); outColor[0] = uvec4(xy + uvec2( 0, 0), 0u, 255u); outColor[1] = uvec4(xy + uvec2(10, 10), 0u, 255u); outColor[2] = uvec4(xy + uvec2(20, 20), 0u, 255u); outColor[3] = uvec4(xy + uvec2(30, 30), 0u, 255u); }` ); gl.compileShader(fs); } const program = gl.createProgram() as WebGLProgram; gl.attachShader(program, vs); gl.attachShader(program, fs); gl.linkProgram(program); gl.useProgram(program); const width = 2; const height = 2; const depth = 4; const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_3D, texture); gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA8UI, width, height, depth, 0, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, null); const fb = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fb); for(let z = 0; z < 4; z++) { gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + z, texture, 0, z); } gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3]); gl.viewport(0, 0, width, height); gl.drawArrays(gl.TRIANGLES, 0, 6); let results = []; const pixels = new Uint8Array(4*width*height); for(let z = 0; z < 4; z++) { gl.readBuffer(gl.COLOR_ATTACHMENT0 + z); gl.readPixels(0, 0, width, height, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, pixels); results.push(pixels.slice()); } return results; }テストコード
こちらも通常モードとヘッドレスモードでそれぞれ
webglTexture3d()
を呼び出すテストを書きます。test/chrome_texture3d.test.tsimport {remoteEval} from "./chrome_helper"; const call_webglTexture3d = () => eval(`(() => { const canvas = document.querySelector("canvas"); const gl = canvas.getContext("webgl2"); return webglTexture3d(gl); })()`); test("texture3d (chrome headless)", (async function() { return remoteEval(call_webglTexture3d, 8080, true).then( (result:any) => { console.log(result); expect(`${result}`).not.toBe([]); }); }), 60000); test("texture3d (chrome browser)", (async function() { return remoteEval(call_webglTexture3d, 8080, false).then( (result:any) => { console.log(result); expect(`${result}`).not.toBe([]); }); }), 60000);GitHub Actions
ワークフローも 実験1.1 と同じで、呼び出す test だけ変えます。
.github/workflows/node.js.ymljobs: #... 略 ... chrome_texture3d: runs-on: macos-latest strategy: matrix: node-version: [12.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: | npm ci npm install -D puppeteer npm run tsc npm run server ./public -p 8080 & npm test -- chrome_texture3d実行結果
https://github.com/agehama/webgl2-test/runs/1268400887// texture3d (chrome headless) [ { '0': 1, '1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0, '7': 0, '8': 0, '9': 0, '10': 0, '11': 0, '12': 112, '13': 114, '14': 101, '15': 115 }, { '0': 35, '1': 118, '2': 101, '3': 114, '4': 115, '5': 105, '6': 111, '7': 110, '8': 32, '9': 51, '10': 48, '11': 48, '12': 32, '13': 101, '14': 115, '15': 10 }, { '0': 112, '1': 114, '2': 101, '3': 99, '4': 105, '5': 115, '6': 105, '7': 111, '8': 110, '9': 32, '10': 109, '11': 101, '12': 100, '13': 105, '14': 117, '15': 109 }, { '0': 112, '1': 32, '2': 102, '3': 108, '4': 111, '5': 97, '6': 116, '7': 59, '8': 10, '9': 111, '10': 117, '11': 116, '12': 32, '13': 117, '14': 118, '15': 101 } ] // texture3d (chrome browser) [ { '0': 0, '1': 0, '2': 0, '3': 255, '4': 1, '5': 0, '6': 0, '7': 255, '8': 0, '9': 1, '10': 0, '11': 255, '12': 1, '13': 1, '14': 0, '15': 255 }, { '0': 10, '1': 10, '2': 0, '3': 255, '4': 11, '5': 10, '6': 0, '7': 255, '8': 10, '9': 11, '10': 0, '11': 255, '12': 11, '13': 11, '14': 0, '15': 255 }, { '0': 20, '1': 20, '2': 0, '3': 255, '4': 21, '5': 20, '6': 0, '7': 255, '8': 20, '9': 21, '10': 0, '11': 255, '12': 21, '13': 21, '14': 0, '15': 255 }, { '0': 30, '1': 30, '2': 0, '3': 255, '4': 31, '5': 30, '6': 0, '7': 255, '8': 30, '9': 31, '10': 0, '11': 255, '12': 31, '13': 31, '14': 0, '15': 255 } ]実行するとなんと二つの出力が全然異なる結果になってしまいました。これはヘッドレスモードの方が間違っており通常モードでは正しい結果を出力できています。ヘッドレスモードの実行結果はランダムっぽい値が入っているときと全て0埋めされているときがあるので、もはや何も描画できていないのかもしれません。
そしてもう一つ気になるのが、関数からは
Uint8Array[]
型で結果を返しているのに受け取った結果は JSON に変換されているという点です。この理由はシンプルで、テストを行う Node.js と式を実行する Chrome はそれぞれ別々に JavaScript 実行環境を動かしており、両者の間でデータを受け渡す手段が文字列しかないためです。この変換は Puppeteer が内部で自動的に行っているようで、chrome-remote-interface だと容赦なく undefined が返って来るため事前にJSON.stringify()
などに通して文字列にした状態で返す必要があります。自分の想定では複雑なシェーダについて CPU で同じ計算を行う関数を書いておき、シェーダの計算結果と照らし合わせるテストをしたいと思っていたので途中でこういった変換を挟まざるを得ないというのはちょっと微妙だなあという感想でした。まあテストまで全部 Chrome 上で行ってしまえばこれは問題になりませんが…
いずれにせよ WebGL の単体テストを行うという用途に対しては、この方法だと少し大がかり過ぎてあまり向いていない気がしました。しかしその分 Chrome での動作が保証できるという強みはあるので、厳密なデータ比較まで行わなくてもとりあえず動かすだけ動かしておくというのが良いのかもしれません。
方法2. node-gles を使う
https://github.com/google/node-gles
node-gles は WebGL から OpenGL ES へのバインディングを提供するライブラリで、これ自身は内部で ANGLE を呼び出します。同じコンセプトで広く使われている headless-gl から WebGL2 に対応する気配がないため TensorFlow.js の中の人によって新たに開発されています。
しかしながら、2020年10月現在ではまだ WebGL2 の関数バインディングはほとんど入っていません。バインディングが少ない理由については一応説明があり、将来的に Khronos の IDL ファイルから自動生成する予定でとりあえず使うものしか入れていない(https://github.com/google/node-gles/issues/1#issuecomment-435165176) とのことです。ということで今はかなり作り途中のものを無理やり使っていくことになります。
ここで大事なのはバインディングさえ追加すれば WebGL 2.0 ( ES 3.0 ) の関数を呼び出せることで、追加するだけならそれほど難しくはないので現時点でも実用できなくはないということです。また、GitHub Actions の windows-latest と ubuntu-latest では残念ながら動きませんでしたが、macos-latest では動くことが確認できたのでひとまずこれで使ってみることにします。
npm install -D node-gles実験2.1. WebGL の情報を取得して表示する(node-gles)
使うソースコードは 実験1.1 と同じ
webglSimple()
関数なので省略します。ちなみにwebglSimple()
は現在の node-gles が対応している範囲に収まっている(収めた)ためそのまま実行することができます。テストコード
nodeGles.createWebGLRenderingContext()
で WebGL と同じ API にアクセスできるオブジェクトが返ってくるので、これをそのままwebglSimple()
に渡して結果を取得します。test/node_gles_simple.test.tsconst nodeGles = require('node-gles'); import {webglSimple} from "../src/index"; test("simple (node-gles)", () => { const gl = nodeGles.createWebGLRenderingContext(); const result = webglSimple(gl); console.log(result); expect(result).not.toBe([]); });GitHub Actions
ワークフローは 実験1.1 とほぼ同じです。
.github/workflows/node.js.ymljobs: #... 略 ... node_gles_simple: runs-on: macos-latest strategy: matrix: node-version: [12.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: | npm ci npm install -D node-gles npm test -- node_gles_simple実行結果
https://github.com/agehama/webgl2-test/runs/1268400898simple (node-gles) ------------------------------------------------------------ gl.RENDERER | ANGLE (Apple Inc., Apple Software Renderer, OpenGL 4.1 core) gl.VERSION | OpenGL ES 3.0 (ANGLE 2.1.0.9512a0ef062a) ------------------------------------------------------------ gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS | 32 gl.MAX_CUBE_MAP_TEXTURE_SIZE | 16384 gl.MAX_FRAGMENT_UNIFORM_VECTORS | 1024 gl.MAX_TEXTURE_IMAGE_UNITS | 16 gl.MAX_TEXTURE_SIZE | 16384 gl.MAX_VARYING_VECTORS | 32 gl.MAX_VERTEX_ATTRIBS | 16 gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS | 16 gl.MAX_VERTEX_UNIFORM_VECTORS | 1024 ------------------------------------------------------------node-gles でも無事に動作が確認できました。
実験1.1 で
gl.RENDERER
の情報を取得したときは WebKit としか返してくれませんでしたが、今回は ANGLE が中で実際に呼んでいるグラフィックス API の情報を取得できていますね。Software Renderer と書いてあるのが若干気になりますが、まあこれは仮想環境なのでしょうがないのかもしれません。よくわかりませんが。とりあえず動くことが確認できたので先に進むことにします。
実験2.2. Texture3D に MRT を使って描き込む(node-gles)
次に 実験1.2 で書いた
webglTexture3d()
をこちらでも動かしてみます。ここで問題になるのが
webglTexture3d()
の中でバインディングが足りない関数がいくつかあることです。具体的にはtexImage3D()
,framebufferTextureLayer()
,drawBuffers()
,readBuffer()
とあと他にいくつかの定数が足りていないのでこれらを以下に追加していきます。https://github.com/agehama/node-gles
バインディングの追加
必要になるのは基本的に次の三つです
egl_context_wrapper.h(.cc)
に呼び出したい GLES の関数ポインタを取得してメンバに持っておくwebgl_rendering_context.h(.cc)
に WebGL から受け取った引数を取り出して取得した関数ポインタに渡して呼び出す関数を定義するwebgl_rendering_context.cc
に 2. で定義した関数にNAPI_DEFINE_METHOD()
で名前を付けて Node.js に公開するあとは
npm install
を呼ぶと自動でコンパイルが走るようなので、これでbuild/Release/nodejs_gl_binding.node
が生成されて勝手に使えるようになります。今回追加した差分はこちらから確認できます。
https://github.com/agehama/node-gles/compare/b4cb488...47d2d00テストコード
実験2.1 と大体同じですがこちらは
nodeGles
のパスだけ fork 版を呼ぶために変更しています。node_gles_texture3d.test.tsconst nodeGles = require('../temp/node-gles/src/index'); import {webglTexture3d} from "../src/index"; test("texture3d (node-gles)", () => { const gl = nodeGles.createWebGLRenderingContext(); const result = webglTexture3d(gl); console.log(result); expect(result).not.toBe([]); });GitHub Actions
ワークフローにも fork 版の node-gles を使用するために、npm からではなく git から clone してインストールするという手順を加えています。
.github/workflows/node.js.ymljobs: #... 略 ... node_gles_texture3d: runs-on: macos-latest strategy: matrix: node-version: [12.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: | cd temp git clone https://github.com/agehama/node-gles.git cd node-gles npm install cd ../../ npm ci npm test -- node_gles_texture3d実行結果
https://github.com/agehama/webgl2-test/runs/1268400898texture3d (node-gles) [ Uint8Array(16) [ 0, 0, 0, 255, 1, 0, 0, 255, 0, 1, 0, 255, 1, 1, 0, 255 ], Uint8Array(16) [ 10, 10, 0, 255, 11, 10, 0, 255, 10, 11, 0, 255, 11, 11, 0, 255 ], Uint8Array(16) [ 20, 20, 0, 255, 21, 20, 0, 255, 20, 21, 0, 255, 21, 21, 0, 255 ], Uint8Array(16) [ 30, 30, 0, 255, 31, 30, 0, 255, 30, 31, 0, 255, 31, 31, 0, 255 ] ]こちらが実行結果です。今度は完全に期待通りの出力が得られました!!嬉しい!
ということでバインディングを追加するという手間さえ無ければ文句なくお勧めできるのですが、現状だと実験的なプロジェクトとかでない限りまだ気軽には使いにくそうだなと感じました。
また、ブラウザの WebGL との挙動の違いについてですが、node-gles だと例えばシェーダが使っていない uniform 変数へ値をセットしようとするとセグフォで落ちてしまいます。ブラウザだと仮に不正な入力があってもそのまま投げて落ちないよう入念にチェックしていると思うのでそこは大きく異なる点だと思います。グラフィックス部分に限れば ANGLE に投げているだけなのでブラウザと描画結果が変わるみたいなことはあまり起きにくいんじゃないかなあと思います。
結論
しばらくは node-gles の成長を待ちつつ使いたい関数を自分で追加していくのが良いかなと思いました。それはそれとして Puppeteer でページを表示するだけなら簡単だったので
headless: false
でとりあえず動かしてキャプチャでも撮っておくのが良さそうでした。
- 投稿日:2020-10-18T17:25:50+09:00
EC2で古いバージョンのnode.jsを最新バージョンにする
ポートフォリオの作成がひと段落ついたのでAWSにデプロイしようとしたところ、node.jsの設定でどハマりしたので皆さんに情報共有していこうと思います。
事象
以下の記事を参考にAWSで通常デプロイに挑戦しました。
【画像付きで丁寧に解説】AWS(EC2)にRailsアプリをイチから上げる方法↓
https://qiita.com/Yuki_Nagaoka/items/975b7598806d6ae0c0b2こちらの記事を参考にRailsアプリの公開編まではスムーズにいきアプリの公開をしてみたのですがurlを入力してみたところ「このサイトにアクセスできません」と表示され・・・
ログを調べてみると
EC2インスタンスに繋がった状態で以下のコマンドを実行しログファイルを参照
EC2.[daiki@ip-10-0-0-56 log]$ tail -n 30 production.log F, [2020-10-12T13:28:37.845307 #22722] FATAL -- : [66518e09-a517-412e-8f3c-97c588a4b2f1] ActionView::Template::Error (The asset "homes.css" is not present in the asset pipeline. ): F, [2020-10-12T13:28:37.845424 #22722] FATAL -- : [66518e09-a517-412e-8f3c-97c588a4b2f1] 1: <% content_for :css do %> [66518e09-a517-412e-8f3c-97c588a4b2f1] 2: <%= stylesheet_link_tag 'homes' %> [66518e09-a517-412e-8f3c-97c588a4b2f1] 3: <% end %> [66518e09-a517-412e-8f3c-97c588a4b2f1] 4: <%= render 'shared/flash_messages'%> [66518e09-a517-412e-8f3c-97c588a4b2f1] 5: F, [2020-10-12T13:28:37.845450 #22722] FATAL -- : [66518e09-a517-412e-8f3c-97c588a4b2f1] F, [2020-10-12T13:28:37.845472 #22722] FATAL -- : [66518e09-a517-412e-8f3c-97c588a4b2f1] app/views/homes/index.html.erb:2:in `block in _app_views_homes_index_html_erb__1348361279455874858_75120' [66518e09-a517-412e-8f3c-97c588a4b2f1] app/views/homes/index.html.erb:1:in `_app_views_homes_index_html_erb__1348361279455874858_75120' I, [2020-10-12T13:38:07.919149 #22721] INFO -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] Started GET "/" for 126.11.108.222 at 2020-10-12 13:38:07 +0000 I, [2020-10-12T13:38:07.919848 #22721] INFO -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] Processing by HomesController#index as HTML D, [2020-10-12T13:38:07.956165 #22721] DEBUG -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] (2.8ms) SET NAMES utf8mb4 COLLATE utf8mb4_general_ci, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483 D, [2020-10-12T13:38:07.962259 #22721] DEBUG -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] Pcrpositive Load (3.0ms) SELECT `pcrpositives`.* FROM `pcrpositives` ORDER BY `pcrpositives`.`id` DESC LIMIT 1 D, [2020-10-12T13:38:07.965472 #22721] DEBUG -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] Pcrtested Load (2.8ms) SELECT `pcrtesteds`.* FROM `pcrtesteds` ORDER BY `pcrtesteds`.`id` DESC LIMIT 1 D, [2020-10-12T13:38:07.968633 #22721] DEBUG -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] Recovery Load (2.8ms) SELECT `recoveries`.* FROM `recoveries` ORDER BY `recoveries`.`id` DESC LIMIT 1 I, [2020-10-12T13:38:07.969160 #22721] INFO -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] Rendering homes/index.html.erb within layouts/application I, [2020-10-12T13:38:07.969980 #22721] INFO -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] Rendered homes/index.html.erb within layouts/application (0.8ms) I, [2020-10-12T13:38:07.970113 #22721] INFO -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] Completed 500 Internal Server Error in 50ms (ActiveRecord: 11.4ms) F, [2020-10-12T13:38:07.970845 #22721] FATAL -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] F, [2020-10-12T13:38:07.970881 #22721] FATAL -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] ActionView::Template::Error (The asset "homes.css" is not present in the asset pipeline. ): F, [2020-10-12T13:38:07.971160 #22721] FATAL -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] 1: <% content_for :css do %> [385da9dc-780c-4a4b-93ea-77683c0f5809] 2: <%= stylesheet_link_tag 'homes' %> [385da9dc-780c-4a4b-93ea-77683c0f5809] 3: <% end %> [385da9dc-780c-4a4b-93ea-77683c0f5809] 4: <%= render 'shared/flash_messages'%> [385da9dc-780c-4a4b-93ea-77683c0f5809] 5: F, [2020-10-12T13:38:07.971197 #22721] FATAL -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] F, [2020-10-12T13:38:07.971221 #22721] FATAL -- : [385da9dc-780c-4a4b-93ea-77683c0f5809] app/views/homes/index.html.erb:2:in `block in _app_views_homes_index_html_erb__1348361279455874858_75120' [385da9dc-780c-4a4b-93ea-77683c0f5809] app/views/homes/index.html.erb:1:in `_app_views_homes_index_html_erb__1348361279455874858_75120' [daiki@ip-10-0-0-56 log]$なんかasset piipelineで落ちている??
と思いもう一度、Railsアプリをプリコンパイルしてみる。EC2.[daiki@ip-10-0-0-56 SONAERU_APP]$ bundle exec rake assets:precompile RAILS_ENV=production Error: ENOTDIR: not a directory, open '/home/daiki/.config/yarn' at Error (native) at Object.fs.openSync (fs.js:642:18) at fs.readFileSync (fs.js:510:33) at /usr/lib/node_modules/yarn/lib/cli.js:100892:58 at Array.map (native) at parseRcPaths (/usr/lib/node_modules/yarn/lib/cli.js:100890:78) at Object.findRc (/usr/lib/node_modules/yarn/lib/cli.js:100904:10) at getRcConfigForCwd (/usr/lib/node_modules/yarn/lib/cli.js:56916:74) at /usr/lib/node_modules/yarn/lib/cli.js:92255:56 at next (native) rake aborted! Autoprefixer doesn't support Node v6.17.1. Update it. /home/daiki/.rbenv/versions/2.7.0/bin/bundle:23:in `load' /home/daiki/.rbenv/versions/2.7.0/bin/bundle:23:in `<main>' Tasks: TOP => assets:precompile (See full trace by running task with --trace)ん〜下から5行目あたりでnode.jsが古いと怒られていますね。
ということでnode.jsを最新版にしてみましょう!以下の記事を参考にしました!
↓yumでのnodejsのバージョンアップにはまった話と解決方法
https://qiita.com/robitan/items/a684a81214767c21a560手順
まずは下記のコードを実行してroot状態にしておく
EC2.$ sudo su -現在のnode.jsのバージョンを確認
EC2.$ node -v v6.12.3node.jsのrpmを確認。2レコード出てきたら古いほうが優先されるのが原因みたいです。
EC2.$ ll /etc/yum.repos.d/ | grep node -rw-r--r--. 1 root root 472 Oct 21 2016 nodesource-el6.repo -rw-r--r--. 1 root root 472 Apr 26 2016 nodesource-el.repoよって、古いほうのrpmを削除してします。
EC2.$ rm /etc/yum.repos.d/nodesource-el.repo一旦、yumをクリーンするコマンドを実行
EC2.$ yum clean all再びインストールしなおします。
EC2.$ yum -y install nodejsバージョンを確認
EC2.$ $ node -v v12.19.0以上
- 投稿日:2020-10-18T13:38:50+09:00
Node.jsでTop-Level Awaitを試す
Node.jsでTop-Level Awaitがサポートされ(て)たので、非同期通信と言えばなaxiosで試してみます。
Top-Level Await
今まではawaitを利用する際に、async関数内じゃないと使えませんでしたが、async関数を宣言せずにawaitを使えるようになります。
v14.3.0でサポート、v14.8.0でフラグなし
Top-Level AwaitはNode.js v14.3.0でサポートされましたが、この時点だと
--experimental-top-level-await
のフラグを付けて実行する必要がありました。v14.8.0以降でフラグ無しで利用できます。
axiosで利用してみる
(一応)今回試した環境はNode.js v14.14.0です。
package.jsonに
"type": "module"
を追記して利用できます。
また、拡張子をmjsにするだけでも利用できます。↓ではmjsで試してみます。$ npm init -y $ npm i axios
app.mjs
を作成ES Modules形式でimportします。
app.mjsimport axios from 'axios'; const res = await axios.get(`https://protoout.studio`); console.log(res.data);めちゃシンプルに書けますね。
- 実行
実行も(.jsではなく)
.mjs
のファイルを実行します。$ node app.mjs
補足: 今までの書き方
今までだと、CommonJS 形式でモジュールを読み込み、async関数の中でawait呼び出しをするというのが通常だったと思います。
app.js'use strict;' const axios = require('axios'); (async () => { const res = await axios.get('https://protoout.studio'); console.log(res.data); })();あと
'use strict;'
の表記もありますね。ESM形式だとStrictモードがデフォルトで有効なので省略できてます。こちらは実行は通常通り。
$ node app.js所感
ちょっとしたことを試す時にasync関数を書くのは結構めんどくさかったので、Top-Level Awaitはありがたいですね。
Common JS(require)からES Modules(impot/from)への移行の流れもあるのでちょっとしたところから慣れていきたい。.jsを使わずに.mjsを基本とする流れでも良いのかな...? この辺気になります。
- 投稿日:2020-10-18T00:50:27+09:00
【初心者】そろそろ適当に npm install するのを卒業したい人へ (><)
はじめに
ネットに転がっている記事などで
npm install
コマンドを実行するとき、-g
、--save
、--save-dev
のオプションを指定したり、指定しなかったり、そのときによくわかんないけど作成されるnode_modules
、package.json
、package-lock.json
とか毎回調べていたので、備忘録を兼ねてまとめたいと思います。動作環境
# Node.js のバージョン確認 $ node -v v14.13.1 # npm のバージョン確認 $ npm -v 6.14.8Node.js のバージョン管理
Node.js 自体のバージョン管理は
nodebrew
やnodenv
などがありますが、お好みでいいと思います。
nodenv
はディレクトリごとにバージョンを指定することが可能ですが、nodebrew
でも必要に応じてバージョンを切り替えることができるので、そこまで困ることはありませんでした。初心者であれば
nodebrew
をお勧めします。
nodebrew
で困ることが発生してからnodenv
に乗り換えるということでいいと思います。(参考)
MacにNode.jsをnodebrewでインストールして環境構築【決定版】
MacにNode.jsをインストール
MacにNode.jsをインストール(anyenv + nodenv編)npm のアップデート
npm 自体のアップデートは以下のコマンドになります。
基本的にバージョンを指定する必要はなく最新でいいと思います。# npm のアップデート $ npm update -g npm※ npm はメジャーバージョンが異なるとかなり挙動が異なるので参照する記事の npm のバージョンにも注意が必要です。
基本的な用語の説明(わかる人は飛ばしてください)
Node.js とは
Node.js とはサーバサイドで動く JavaScript のことです。
npm とは
npm(Node Package Manager)とは Node.js のパッケージ(Package)を管理する(Manager)ツールです。
Node.jsのパッケージ(Package)とは予め用意された便利な機能(各種フレームワークやライブラリ)をまとめたものです。
package.json とは
package.json とは Node.js ベースの JavaScript アプリ開発において、自身のパッケージ(= プロジェクトそのもの)を管理するために使われるファイルのことです。
package.json は touch コマンドなどでも作成できますが、基本的には
npm init
とコマンド実行して作成します。# 何もない空のディレクトリ $ ls (標準出力なし) # npm init による package.json の作成(質問をされるが一旦すべて Enter で通過する = npm init -y コマンド実行時と同じ挙動) $ npm init # package.json が作成されている $ ls package.jsonpackage.json{ "name": "<current directory name>", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }package.json にはいろいろ項目がありますが
name
,version
,description
,keywords
,author
,license
などのデータは単なる自身のパッケージ(= プロジェクトそのもの)のメタデータであり、自身のパッケージ(= プロジェクトそのもの)を公開するつもりがないならばあまり気にする必要はありません。調べるといろいろ出てくるのですが、
dependencies
,devDependencies
,scripts
,config
の4つぐらいを抑えれば、あとは必要に応じて設定すれば問題ないかと思います。(参考)
package.jsonの構造【脱線】
npm init
しないとどうなる?
npm init
も package.json も作成せずにいきなりnpm install
したらどうなるのか気になったので、以下の2通りで試してみました。
npm init
せずにnpm install
した場合npm init
せずにnpm install <package>
した場合結論から先にいうと
npm init
しなくてもnpm install
に失敗するわけでもないし、パッケージを指定すれば node_modules が作成されてパッケージのインストールにも成功しました。しかし、package.json がないとどのパッケージをインストールしたかの記述がどこにも記されないので、node_modules の内容を管理するのは難しくなります。
(package-lock.json は作成されますが、package-lock.json にはインストールしたパッケージの結果のみが記されて、パッケージの依存関係まではわからないので package.json はソースを管理する上で必要になります。)
npm init
せずにnpm install
した場合# 何もない空のディレクトリ $ ls (標準出力なし) # npm install の実行 $ npm install # package-lock.json のみが作成される(package.json は作成されない) $ ls package-lock.json # 中身は lockfileVersion のみの記述でパッケージ情報はありませんでした(当たり前) $ cat package-lock.json { "lockfileVersion": 1 }
npm init
せずにnpm install <package>
した場合# 何もない空のディレクトリ $ ls (標準出力なし) # cowsay パッケージをインストール $ npm install cowsay + cowsay@1.4.0 # package-lock.json と node_modules が作成されるが package.json がない $ ls node_modules package-lock.jsonnpm と package.json の関係
公開されているパッケージ(ライブラリやフレームワークなど)は
npm
コマンドによってインストールすることができます。package.json が存在するディレクトリで
npm
コマンドによってパッケージをインストールすると、自動的に package.json が更新されます。npm コマンド実施によって更新される項目は
dependencies
とdevDependencies
という項目なります。また、パッケージの管理の情報(
dependencies
とdevDependencies
の項目)について、人間が package.json を直接編集することはありません。すべて
npm
コマンド経由で更新します。(大事)node_modules とは
node_modules とは package.json や package-lock.json を元にしてインストールされる各種パッケージのがインストールされるディレクトリのことです。
実質的には package-lock.json に記載されているバージョンのパッケージがインストールされています。
また、package.json さえあれば、
npm install
コマンドの実行によって node_modules が生成することができるため、通常.gitignore
に指定されるディレクトリです。node_modules はいろんなパッケージがインストールされているため、とても容量の大きなディレクトリになるので、その管理を package.json や package-lock.json にまかせて node_modules 自体の管理は git で管理対象外にするケースがほとんどです。
npm install
とnpm install <package>
の違い
npm install
(パッケージの引数なし)
- package.json の
dependencies
とdevDependencies
に記述されているパッケージ情報を元に node_modules にパッケージをインストールするnpm install <package>
(パッケージの引数あり)
- package.json の
dependencies
とdevDependencies
の項目に引数に指定されたパッケージを記述する- package.json の
dependencies
とdevDependencies
に記述されているパッケージ情報を元に node_modules にパッケージをインストールするポイントは引数にパッケージを指定すると package.json に引数のパッケージが追記されることと、どちらも node_modules にパッケージをインストールすることです。
グローバルインストール と ローカルインストールの違い
npm インストールは「グローバルインストール」と「ローカルインストール」の2種類あります。
ちなみに「グローバルインストール」と「ローカルインストール」の両方とも自身のPCの環境へのインストールのことです。イメージとしては以下のとおりです。
- ローカルインストール:node_modules と同じディレクトリにある場合にパッケージ(コマンド)が実行できる
- グローバルインストール:自身の PC の環境ならどこでもインストールしたパッケージ(コマンド)が実行できる
ただし、グローバルインストールしたからといっても
nodebrew
などで複数の Node.js のバージョンを管理している場合は、それぞれのバージョンでnpm install -g <package>
したときのパッケージがそれぞれ別のものとして管理されるので注意が必要です。package.json{ "name": "", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }# 適当なパッケージ(cowsay)をインストール $ npm install cowsay + cowsay@1.4.0 # node_modules と package-lock.json が追加される $ ls node_modules package-lock.json package.json # dependencies の項目に cowsay が追加されていることが確認できる $ cat package.json { (省略) + "dependencies": { + "cowsay": "^1.4.0" + }, (省略) }# 適当なパッケージ(typescript)を -D のオプションをつけてインストール $ npm install -D typescript + typescript@4.0.3 # devDependencies の項目の typescript が追加されていることが確認できる $ cat package.json { (省略) "dependencies": { "cowsay": "^1.4.0" }, + "devDependencies": { + "typescript": "^4.0.3" + }, (省略) } # さらにパッケージを追加してみる $ npm install webpack + webpack@5.1.0 # dependencies の項目に webpack が追加されていることが確認できる $ cat package.json { (省略) "dependencies": { "cowsay": "^1.4.0", + "webpack": "^5.1.0" }, "devDependencies": { "typescript": "^4.0.3" }, (省略) }グローバルインストール と ローカルインストールの実行コマンド(npm install)
グローバルインストールされたパッケージとローカルインストールされたパッケージの確認は以下のようになります。
# (前提その1)nodebrew で複数のバージョンの Node.js のバージョンを管理している状態(v14.13.1 を使用) $ nodebrew ls v12.6.0 v14.13.1 current: v14.13.1 # グローバルインストール先の確認(npm list -g | head -1) $ npm list -g | head -1 /Users/sugurutakahashi/.nodebrew/node/v14.13.1/lib # グローバルインストールされたパッケージの確認(npm list -g --depth=0) $ npm list -g --depth=0 /Users/sugurutakahashi/.nodebrew/node/v14.13.1/lib ├── npm@6.14.8 ├── typescript@4.0.3 └── yarn@1.22.10 # v12.6.0 に切り替え $ nodebrew use v12.6.0 $ nodebrew ls v12.6.0 v14.13.1 current: v12.6.0 # グローバルインストール先の確認 $ npm list -g | head -1 /Users/sugurutakahashi/.nodebrew/node/v12.6.0/lib # グローバルインストールされたパッケージの確認 $ npm list -g --depth=0 /Users/sugurutakahashi/.nodebrew/node/v12.6.0/lib ├── @vue/cli@4.4.6 ├── @vue/cli-service-global@4.4.6 ├── express@4.17.1 ├── firebase-tools@8.6.0 ├── gatsby-cli@2.12.66 ├── gitbook-cli@2.3.2 ├── multi-file-swagger@2.3.0 ├── npm@6.14.8 └── yarn@1.22.10このように Node.js のバージョンを切り替えるとグローバルインストールされたパッケージの内容が異なることがわかります。
# typescript のインストール $ npm install typescript + typescript@4.0.3 # node_modules に typescript のパッケージが存在していることの確認 $ ls node_modules typescript # ローカルインストールされたパッケージの確認 $ npm list --depth=0 typescript-node-base@1.0.0 /Users/sugurutakahashi/git/typescript-node-base └── typescript@4.0.3 # node_modules のない適当なディレクトリに移動 $ cd ../ $ ls node_modules 標準出力なし(= node_modules が空) # ローカルインストールされたパッケージの確認 $ npm list --depth=0 /Users/sugurutakahashi/git └── (empty)このようにローカルインストールされたパッケージはカレントディレクトリの node_modules の内容を確認していることがわかります。
グローバルインストール VS ローカルインストール
では、パッケージをインストールする場合、グローバルインストール と ローカルインストール のどちらにインストールすべきでしょうか?
基本的には ローカルインストール を選択すべきです。理由としては、ローカルインストール時に package.json というファイルが作られ、package.json が存在するディレクトリで
npm install
コマンドを実行するとそのディレクトリにパッケージがインストールされて、インストールされたパッケージが環境に依存しないようにするためです。グローバルインストールされたパッケージは環境が異なると使用することができません。
例えば、PC を買い替えたらそのパッケージを使用することができないですし、他人の PC でもそのパッケージを使用することができません。そいういうときにローカルインストールで作成される package.json だけを共有すれば、どの環境でも
npm install
を実行するだけで、同じパッケージを使用することができます。ローカルインストールしたパッケージをコマンド実行する
例えば、cowsay というパッケージを
npm install cowsay
でローカルインストールをした場合、いきなり$ cowsay
とコマンドを実行することができません。
$ cowsay
と実行すると、以下のようにcommand not found: cowsay
と怒られます。# cowsay パッケージのローカルインストール $ npm install cowsay + cowsay@1.4.0 # ローカルインストールされたパッケージの確認 $ npm list --depth=0 typescript-node-base@1.0.0 /Users/sugurutakahashi/git/typescript-node-base └── cowsay@1.4.0 # cowsay コマンドの実行(パスが通っていないのでエラーになる) $ cowsay "hoge" zsh: command not found: cowsayローカルインストールしたパッケージのコマンドを実行するには以下の3つ(4つ目はグローバルインストールなのでカウント外)が挙げられます。
- 方法1:パスを通す
$ ./node_modules/.bin/<package>
のようにパスを通しながら実行する($(npm bin)/<package>
でも同じ挙動)- 方法2:
npm-srcipts
- package.json の
scripts
の項目に{ "scripts" : { "key" : "value" }}
と"value"
にコマンドを指定したのちに$ npm run <key>
と実行する- ※2
$ npm run <key>
と実行するとローカルインストール先(./node_modules/.bin/
)にパスを通しながらコマンドを実行してくれる- 方法3:
npx
npx <package>
とコマンドを実行すると、ローカルインストール先にパッケージがあればそれを使い、なければ一時的にローカル環境にパッケージをインストールして実行する(実行後は一時的にインストールしたパッケージは削除される)- (方法4:グローバルインストール)
$ npm install -g <package>
後に$ <package>
とコマンド実行する基本的に
npm-srcipts
またはnpx
での実行をお勧めします。ローカルインストールしたパッケージのコマンドを頻繁に実行するコマンドであったりプロジェクトで共有すべきコマンドであれば
npm-srcipts
に登録したほうがいいと思います。一方で、たまにしか実行しないコマンドであったり、動作の検証であれば、わざわざ
npm-srcipts
に登録せずにnpx
での実行でいいと思います。
むしろ、むやみにnpm-srcipts
に登録すると管理する対象が増えるのでお勧めしません。さらには
npx
はローカルインストールされていないパッケージは、一時的にパッケージをインストールして実行してくれるので、そもそも、たまにしか実行しないコマンドであったり、動作の検証でであれば、ローカルインストールすらしないで、npx
経由で毎回インストールしながらコマンド実行してもいいと思います。
時間はかかりますが、検証の結果不要になった場合にパッケージのアンインストールのし忘れなどがないです。さらにさらに、よくネットに転がっている記事はやたらとグローバルインストールさせてきますが、そのとき安易にグローバルインストールしないほうがいいです。
大抵の場合npx
を使って一時的にインストールするだけで済むケースがほとんどです。(npx
最強!!)(参考)
(npxでnodeモジュールを実行する)[https://qiita.com/tatakahashiap/items/1c4ab221c4993e7c4ebf]使い分けをまとめると以下になります。
ケース インストール先 CLIでの実行方法 ・よく実行する
・プロジェクトで共有して管理したいローカルインストール npm-srcipts ・まあまあ実行する
・今のところ自分だけ知っていればいい
・バージョンを管理したいローカルインストール npx ・たまにしか実行しない
・検証中である
・ちょっと時間がかかってもよい
・環境に影響をかけたくない
・とりあえず実行させたいnpx による一時的インストール npx ・それ以外
・どうしてもグローバルインストールしたいグローバルインストール 普通に実行 方法1:パスを通す
# ローカルインストール先のパスを指定した cowsay コマンドの実行 $ ./node_modules/.bin/cowsay "hoge" ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || # $(npm bin) を使用しても同様にローカルインストール先のパスを指定したことになる(./node_modules/.bin/<package> = $(npm bin)/<package>) $ $(npm bin)/cowsay "hoge" ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||方法2:
npm-srcipts
# package.json の scripts に cowsay を追加(npm run scripts<key> とコマンドを実行するとローカルインストール先のパスを通しながら scripts<value> コマンドを実行する) $ vi package.json { (省略) "dependencies": { "cowsay": "^1.4.0" }, "scripts": { + "cowsay": "cowsay" }, (省略) } # npm-scripts での cowsay コマンドの実行 $ npm run cowsay -- "hoge" # npm-scripts の引数のオプションは -- のあとに指定する(-- なくても大丈夫な場合もあるがつけた方が無難) > typescript-node-base@1.0.0 cowsay /Users/sugurutakahashi/git/typescript-node-base > cowsay "hoge" ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || # npm run 実行時に -s (= --silent) の オプションをつけると出力がすっきりします $ npm run -s cowsay -- "hoge" ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||【脱線】 npm-scripts の pre、 post プレフィックス機能
npm-scriptsには、
pre
またはpost
プレフィックスをつけることで、その npm-scripts の前後に実行される処理を記述することができます。
- pre
- その npm-scripts の前に実行される
- ex)
prestart
はstart
の前に実行される- post
- その npm-scripts の後に実行される
- ex)
prestart
はstart
の後に実行される# cowsay 実行前に実行する precowsay、cowsay 実行後に実行する postcowsay を追加(内容はechoされる簡単なもの) $ vi package.json { (省略) "dependencies": { "cowsay": "^1.4.0" }, "scripts": { "cowsay": "cowsay", + "precowsay": "echo 'pre cowsay'", + "postcowsay": "echo 'post cowsay'" }, (省略) } # npm-scripts の cowsay コマンド実行によって package.json の npm-scripts の precowsay と postcowsay が実行されることの確認 $ npm run -s cowsay -- "hoge" pre cowsay ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || post cowsay【脱線】 npm-scripts の 順次・並列実行(npm-run-all)
npm-run-all は複数の npm-scripts を実行できるコマンドラインツールです。
オプションをつけることで引数の npm-scripts の順次実行 または 並列実行することができます。
- 順次実行
npm-run-all --serial <task>
=npm-run-all -s <task>
=run-s <task>
- 並列実行
npm-run-all --parallel <task>
=npm-run-all -p <task>
=run-p <task>
# 以下のような hello:foo と hello:bar という npm-scripts を追加 $ vi package.json { (省略) "scripts": { + "hello:foo": "sleep 1; echo FOO", + "hello:bar": "sleep 1; echo BAR" }, (省略) } # 1秒後に "FOO" を echo する $ npm run -s hello:foo FOO # 1秒後に "BAR" を echo する $ npm run -s hello:bar BAR # npm-run-all のパッケージをインストール $ npm install --save-dev npm-run-all # hello:foo と hello:bar を順次実行する hello-s と hello:foo と hello:bar を並列実行する hello-p を登録 $ vi package.json { (省略) "devDependencies": { + "npm-run-all": "^4.1.5" }, "scripts": { + "hello-s": "npm-run-all -s hello:*", + "hello-p": "npm-run-all -p hello:*", "hello:foo": "sleep 1; echo FOO", "hello:bar": "sleep 1; echo BAR" }, (省略) } # FOO が echo されて 1秒後に BAR が echo される(順次実行) $ npm run -s hello-s FOO BAR # FOO と BAR がほぼ同時に echo される(並列実行) $ npm run -s hello-p BAR FOO方法3:
npx
# npx <package> コマンドでもローカルインストール先のパスを通しながら <package> のコマンドを実行できる(ローカルインストールしている場合は ./node_modules/.bin/<package> = npx <package>) $ npx cowsay "hoge" ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || # node_modules のない適当なディレクトリに移動 $ cd ../ $ npm list --depth=0 /Users/sugurutakahashi/git └── (empty) # npx <package> コマンド実行時にローカルインストール先(node_modules)にパッケージが存在しない場合は、一時的にローカルにパッケージをインストールして実行する(実行後は一時的にインストールしたパッケージは削除される) $ npx cowsay "hoge" npx: 10個のパッケージを1.636秒でインストールしました。 ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||npx でパッケージ名とコマンドが異なる場合(
npx -p <package> -c "コマンド"
)(脱線)例えば
express-generator
のパッケージでは使用するコマンドはexpress
というように、パッケージ名とコマンドが異なる場合はnpx -p <package> -c "コマンド"
とします。挙動をみると
npx -p <package> -c "コマンド"
と実行する場合は、ローカルインスルされたパッケージの有無に関わらず、リモートのパッケージをインストールするみたいです。# express というコマンドは存在しないため失敗する $ npx express --version npx: 50個のパッケージを1.814秒でインストールしました。 コマンドが見つかりません: express # -p で express-generator パッケージ、-c で express コマンドを指定すると実行できる $ npx -p express-generator -c "express --version" npx: 10個のパッケージを1.277秒でインストールしました。 4.16.1(方法4:グローバルインストール)
# もちろんグローバルインストールすればローカルインストールする必要もないし npm-scripts に登録する必要もないし、npx とつける必要もないが、その環境でしか実行できなくなる(非推奨) $ cowsay "hoge" zsh: command not found: cowsay $ npm install -g cowsay $ cowsay "hoge" ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||基本的な npm install コマンド
グローバルインストール
# グローバルインストール npm install -g <package> npm install --global <package>ローカルインストール
# ローカルインストール # package.json の dependencies に追加するとき(npm v4 以下では --save (= -S) のオプションが必要、npm v5 以上ではデフォルトになったため不要) npm install <package> npm install --save <package> # デフォルトで入っているオプションなので使う必要なし npm install -S <package> # デフォルトで入っているオプションなので使う必要なし # package.json の devDependencies に追加するとき --save-dev (= -D) npm install --save-dev <package> npm install -D <package>バージョン指定のインストール
# バージョンに関する指定 npm install <package>@x.y.z # バージョンを指定する場合 npm install <package>@latest # 最新版を指定する場合( @latest とつけなくても指定しなければ最新版がインストールされる)package.json に記述されるバージョン情報について
dependencies または devDependencies に記述されているバージョン情報の チルダ
^
や キャレット~
は以下の意味になります。
- バージョン固定
- ex)
3.2.1
npm install
すると3.2.1
のパッケージをインストールする- チルダ
~
: マイナーバージョンまでの挙動を保証
- ex)
~3.2.1
npm install
すると3.2.x
の中の最新バージョンをインストールする- キャレット
^
: メジャーバージョンまでの挙動を保証
- ex)
^3.2.1
npm install
すると3.x.x
の中の最新バージョンをインストールする- 指定なし: 全てのバージョンでの挙動を保証
- ex)
*
npm install
するとx.x.x
の中の最新バージョンをインストールするnpm のバージョン管理は セマンティック バージョニング に準拠しているはずのため、ほとんどがキャレット
^
指定のバージョンが package.json に記述されます。
npm install <package>
とnpm install -D <package>
の違い
npm install <package>
を行うと package.json のdependencies
という項目にパッケージ名とそのバージョン情報が記述されます。
また-D
や--save-dev
とオプジョンをつけてnpm install -D <package>
と実行するとdevDependencies
という項目にパッケージ名とそのバージョン情報が記述されます。一般的に、開発環境でしか使用しないパッケージについては
npm install -D <package>
としてdevDependencies
に記述し、そうではないパッケージについてはnpm install <package>
としてdependencies
に記述します。
dependencies
とdevDependencies
のどちらに記述されるかによって、npm install
とnpm install --production
の挙動が異なります。オプションなしの
npm install
コマンドを実行をすると package.json のdependencies
とdevDependencies
の両方に記述されているパッケージをインストールします。一方で
--production
のオプションをつけてnpm install --production
と実行するとdependencies
に記述されているパッケージのみインストールし、devDependencies
のパッケージに関してはインストールしません。
--production
のオプションをつけることによって開発環境でしか使わないパッケージを除外できるので、本番環境デプロイ時に参照されることのないパッケージをインストールしなくて済むようになります。
実行するコマンド package.json の記述先 それぞれの使い分け npm install <package>
dependencies
開発環境以外でも使用されるパッケージ
ex) expressnpm install -D <package>
devDependencies
開発環境のみで使用されるパッケージ
ex) eslint
実行するコマンド インストール対象 実行するタイミング npm install
・ dependencies
・devDependencies
開発環境 npm install --production
・ dependencies
のみ本番環境など開発環境でしか使われないパッケージをインストールしたくないとき 正直、パッケージを
dependencies
とdevDependencies
のどちらに入れるべきかはケースによって異なると思うので、実際にnpm install --production
をするときになってから考えればいいと思います。【参考】
(【package.json】dependencies, devDependencies の使い分けを考える)[https://qiita.com/karur4n/items/3d9d28f6f21c3533020d]
npm install
の実行時の package-lock.json の有無による挙動の整理
package-lock.json が存在しないとき
- package.json に基づいてインストールされる
- 実際にインストールしたバージョンの内容で package-lock.json が作成される
package-lock.json が存在するとき
- package-lock.json に基づいてインストールされる
- package.json で指定されたバージョンとの矛盾があれば package.json が優先される
- 実際にインストールしたバージョンの内容で package-lock.json が更新される
つまり、package-lock.json は実際にインストールしたバージョンが常に記載されるということである。
package-lock.json を使う方法
npm ci
というコマンドを実行するとpackage-lock.json
から node_modules を作成してくれる。
「node_modules を作成する」という意味合い的にはnpm install
とnpm ci
と一緒である。# package.json のキャレットやチルダからできるだけ最新のバージョンで node_modules を作成するとき npm install # package-lock.json のバージョンで node_modules を作成するとき npm ci
npm ci
とnpm install
の要点をまとめると以下のようなことがあげられる
npm ci
はnpm install
と同じように全依存パッケージをインストールするnpm install
はpackage-lock.json
を更新することがあるnpm ci
は package-lock.json を更新しないnpm ci
は node_modules を削除してからインストールする
npm ci
の使い所最も使用頻度の高いユースケースとしては CI 実行時があげられる。
理由としては、package-lock.json が勝手に更新されるのを防いだり、node_modules の更新によって動作が変わってしまうことを防ぐためである。
同様の理由から、新規参画者が git clone して動作を確認する場合などもnpm ci
が用いられる。(※ ただし CI の速度を求めるなら
npm ci
は実行時間がやや長いため node_modules をキャッシュしてnpm install
を用いることもある。)よくある使い所
- CI 実行時
- git clone 直後の動作確認
- 何かのトラブルで node_modules を空にしてやり直すとき
- 過去の状態を復元するとき結論
- package-lock.json は
npm install
で実際にインストールした内バージョンの内容が記述されている- package-lock.json を参照して node_modules を作成するときは
npm ci
コマンドを実行する- 主なユースケースとしては CI 実行時、git clone 後の動作確認などで使用する
セマンティック バージョングについて (脱線)
アプリ(主にAPI)のバージョンの付け方に関するルールに
セマンティック バージョング
= Semver (Semantic Versioning) というガイドラインが存在する。セマンティック バージョニング 2.0.0
概要
バージョンナンバーは、メジャー.マイナー.パッチ とし、バージョンを上げるには、
- APIの変更に互換性のない場合はメジャーバージョンを、
- 後方互換性があり機能性を追加した場合はマイナーバージョンを、
- 後方互換性を伴うバグ修正をした場合はパッチバージョンを上げます。 プレリリースやビルドナンバーなどのラベルに関しては、メジャー.マイナー.パッチ の形式を拡張する形で利用することができます。
npm install
実行後に作成される package.json や package-lock.json にバージョン情報が記載されるが、そのバージョン情報はこちらのセマンティック バージョニングのルールに準拠してバージョン管理されている。npm-check-updates での package.json に記載されているパッケージのバージョンアップ
package.json に記載されているパッケージのバージョン情報をアップデートしたい場合は以下の手順を踏む必要があります。
npm outdated
で新しいバージョンがリリースされてないか確認する- 新しいバージョンがリリースされていた場合、該当のパッケージを package.json から削除する(これがいっぱいあるとしんどい)
npm install
で再度パッケージをインストールする
npm update
やnpm update <package>
というコマンドもありますが、package.json の内容から依存関係のバージョンの記載の範囲内の最新版をインストールして package-lock.json や node_modules を更新するだけで、package.json のdependencies
やdevDependencies
に記載されているバージョン情報は最新のものには更新されません。(正直、グローバルインストールしたパッケージをバージョンアップする場合を除いて
npm update
が必要となるケースがあまりわかりません。基本的にnpm install
でこと足りるという認識です。)
npm-check-updates
というパッケージを使用すればとても以下のコマンドだけの手順でアップデートが可能です。
ncu
コマンドの実行 (アップデート情報の確認)ncu -u
コマンドの実行(package.json の更新)npm install
コマンドの実行(更新された package.json をもとにパッケージをインストール)実行例は以下の通りです。
cowsay
の古いパッケージを package.json ごとアップデートします。# ncu でアップデート可能なパッケージの確認(ほとんどの記事ではグローバルインストールしていますが使用頻度は多くないと思うので npx での実行でもいいと思います) $ npx -p npm-check-updates -c "ncu" npx: 285個のパッケージを6.971秒でインストールしました。 Checking package.json [====================] 2/2 100% cowsay ^1.2.0 → ^1.4.0 Run ncu -u to upgrade package.json # ncu -u を実行すると package.json が更新される $ npx -p npm-check-updates -c "ncu -u" npx: 285個のパッケージを6.971秒でインストールしました。 Checking package.json [====================] 2/2 100% cowsay ^1.2.0 → ^1.4.0 Run npm install to install new versions. # 更新された package.json をもとに npm install の実行 $ npm install(参考)
npm installしたパッケージの更新確認とアップデート(npm-check-updates)gibo
gibo
とは .gitignore を自動的に作るツールである。
同じようなツールとして gitignore.io というサービスがある。メリット・デメリットそれぞれあるが、一度リポジトリを引っ張ってくればオフラインで完結できる
gibo
の方が優秀のように思える。
直感的にはgitignore.io
のほうがわかりやすい。正直どちらでもよさそう。
(参考)
giboでgitignoreを自動生成する
.gitignoreを自動的に作る(gibo, gitignore.io を使う)EditorConfig
どんな IDE・エディタ でもコーディンングスタイルを定義、維持するツール。
.editorconfig
というファイルにルールを定義する。正直、開発するときはチームで IDE・エディタを合わせるので、あんまり必要性は感じない(今のところ VSCode 一強)。
(参考)
どんなエディタでもEditorConfigを使ってコードの統一性を高めるpackage.json の config の使い方について(脱線)
package.json の config に項目を追加すると npm-scripts 実行時に環境変数として
$npm_package_config_xxx
(xxx
はプロパティ名) という形で使用できるようになります。
使い方は以下のような方法で使うことができます。package.json{ "name": "foo", "config": { "foo": "bar", "dev": { "port": 8080 } }, "scripts": { "start": "node ./index.js", "dev": "http-server -p $npm_package_config_dev_port" } }index.jsconsole.log(process.env.npm_package_config_dev_port); // 8080 console.log(process.env.npm_package_config_foo_bar); // baz正直、使い所はあまりないと思います。
ローカルの設定を package.json に書き込むことになってしまい、git で package.json が競合することになります。npm-scripts はコマンドライン引数をコマンドに渡せるためコマンドライン引数を使ったほうがいいです。
使用しているプロジェクトもたまにあるのでとりあげました。
yarn について(脱線)
以下の意見にとても納得しました。
ご参考まで。ちなみに: 似たような CLI として Facebook が開発した Yarn がある。これは npm の色々な欠点(スピードなど)を補うように作られたものであり、かなり人気がある。npm パッケージの README でしばしば npm と yarn でインストールする方法が両方書かれていたり、時には「yarn を使用することを推奨する」と書かれていたりする。しかし、npm も改善されてきており、わざわざ yarn をインストールして使用するメリットはあまりないと筆者は考えている。特に初心者にとっては、スタンダードでないツールを使用すると無駄に学ぶことが増えるのでおすすめしない。
【初心者向け】NPMとpackage.jsonを概念的に理解する (抜粋)
https://qiita.com/righteous/items/e5448cb2e7e11ab7d477
- 投稿日:2020-10-18T00:50:27+09:00
【初心者】そろそろ適当に npm install するのを卒業する!!
はじめに
ネットに転がっている記事などで
npm install
のコマンドをよく分からず実行してきましたが、そろそろその状態から卒業したかったので備忘録をかねてこちらの記事を投稿しました。
npm install
コマンドに関することをメインにお伝えしますが、package.json や node_modules など npm を語る上で基本的なことについても触れていきたいと思います。動作環境
# Node.js のバージョン確認 $ node -v v14.13.1 # npm のバージョン確認 $ npm -v 6.14.8ちなみに npm 自体のアップデートは以下のコマンドになります。
npm はメジャーバージョンが異なるとかなり挙動が異なるので、参照する記事の npm のバージョンにも注意が必要です。# npm のアップデート $ npm update -g npmnpm install を説明する前に知っておきたいこと
この辺を理解しておかないと適当に
npm install
してしまうことになるので、先に説明します。
知っている方は飛ばしても大丈夫です。Node.js
Node.js とはサーバサイドで動く JavaScript のことです。
npm
npm(Node Package Manager)とは Node.js のパッケージ(Package)を管理する(Manager)ツールです。
Node.jsのパッケージ(Package)とは予め用意された便利な機能(各種フレームワークやライブラリ)をまとめたものです。
yarn
yarn とは2016年にリリースされた npm と互換性のあるパッケージマネージャーです。
今回は yarn コマンドについては触れませんが、以下の意見にとても納得しました。
ご参考まで。ちなみに: 似たような CLI として Facebook が開発した Yarn がある。これは npm の色々な欠点(スピードなど)を補うように作られたものであり、かなり人気がある。npm パッケージの README でしばしば npm と yarn でインストールする方法が両方書かれていたり、時には「yarn を使用することを推奨する」と書かれていたりする。しかし、npm も改善されてきており、わざわざ yarn をインストールして使用するメリットはあまりないと筆者は考えている。特に初心者にとっては、スタンダードでないツールを使用すると無駄に学ぶことが増えるのでおすすめしない。
【初心者向け】NPMとpackage.jsonを概念的に理解する (抜粋)
https://qiita.com/righteous/items/e5448cb2e7e11ab7d477package.json
package.json とは Node.js ベースの JavaScript アプリ開発において、自身のパッケージ(= プロジェクトそのもの)を管理するために使われるファイルのことです。
npm init
とコマンド実行すると package.json が作成されます。package-lock.json
簡単に説明すると package.json を用いてパッケージをインストールした結果が記載されるファイルです。
詳しくは【参考】のリンク先を参照してください。npm init
package.json は普通に touch コマンドなどでも作成できますが、基本的には
npm init
とコマンド実行して package.json を作成します。(【脱線】npm init しないとどうなる?)
# 何もない空のディレクトリ $ ls (標準出力なし) # npm init による package.json の作成(質問をされるが一旦すべて Enter で通過する = npm init -y コマンド実行時と同じ挙動) $ npm init # package.json が作成されている $ ls package.jsonpackage.json の初期値は以下のようなものになります。
package.json{ "name": "<current directory name>", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }package.json にはいろいろ項目がありますが
name
,version
,description
,keywords
,author
,license
などのデータは単なる自身のパッケージ(= プロジェクトそのもの)のメタデータであり、自身のパッケージ(= プロジェクトそのもの)を公開しなのであれば、気にする必要はありません。
dependencies
,devDependencies
,scripts
,config
の4つぐらいを抑えれば、あとは必要に応じて調べて設定すれば問題ないかと思います。【参考】
package.jsonの構造npm と package.json の関係
公開されているパッケージ(ライブラリやフレームワークなど)は
npm
コマンドによってインストールすることができます。package.json が存在するディレクトリで
npm
コマンドによってパッケージをインストールすると、自動的に package.json が更新されます。また、人間が package.json を直接編集することはありません。
すべて
npm
コマンド経由で更新します。(大事)node_modules
node_modules とは package.json を元にしてインストールされる各種パッケージのがインストールされるディレクトリ先のことです。
package.json さえあれば、
npm install
コマンドの実行によって node_modules が生成することができるため、通常.gitignore
に指定されるディレクトリです。node_modules はいろんなパッケージがインストールされているため、とても容量の大きなディレクトリになるので、その管理を package.json や package-lock.json にまかせて node_modules 自体の管理は git で管理対象外にするケースがほとんどです。
npm install
ここからがこの記事の本題になります。
パッケージを node_modules にインストールするには
npm install
とコマンド実行します。この
npm install
というコマンドですが、これがいろいろと種類があってややこしいので詳しく説明していきます。とりあえずよく使うコマンド一覧
引数なしでの実行する場合
# 引数なし実行(package.json のあるディレクトリで実行する) npm installグローバルインストール
# グローバルインストール npm install -g <package> npm install --global <package>ローカルインストール
# ローカルインストール # package.json の dependencies に追加するとき(npm v4 以下では --save (= -S) のオプションが必要、npm v5 以上ではデフォルトになったため不要) npm install <package> npm install --save <package> # デフォルトで入っているオプションなので使う必要なし npm install -S <package> # デフォルトで入っているオプションなので使う必要なし # package.json の devDependencies に追加するとき --save-dev (= -D) npm install --save-dev <package> npm install -D <package>バージョン指定のインストール
# バージョンに関する指定 npm install <package>@x.y.z # バージョンを指定する場合 npm install <package>@latest # 最新版を指定する場合( @latest とつけなくても指定しなければ最新版がインストールされる)引数のない
npm install
の挙動引数のない
npm install
コマンド実行時の挙動は、引数のあるnpm install <package>
とわけて考えたほうがわかりやすいです。引数のない
npm install
コマンド実行をすると、カレントディレクトリにある package.json に記述されている情報を元に、そこに記述されている パッケージを node_modules (インストール先)にインストールします。なので、あらかじめ package.json にインストールしたいパッケージ情報を記述しておく必要があります。
そこで実行するのが引数のある
npm install <package>
になります。引数のある
npm install <package>
の種類大きな分類としてグローバルインストールとローカルインストールに分けられます。
また、ローカルインストールのなかでも package.json の記述先の違いで2つに分けられます。全体で見るとグローバルインストール1種類、ローカルインストール2種類の合計3種類になります。
大分類 package.json の記述先 コマンド グローバルインストール - npm install -g <package>
ローカルインストール dependencies npm install <package>
ローカルインストール devDependencies npm install -D <package>
ちなみに「グローバルインストール」と「ローカルインストール」の両方とも自身のPCの環境へのインストールのことです。
グローバルインストール
グローバルインストールすると自身の PC の環境ならどこでもインストールしたパッケージ(コマンド)が実行できます。
ただし、グローバルインストールしても
nodebrew
などで複数の Node.js のバージョンを管理している場合は、それぞれのバージョンでnpm install -g <package>
したパッケージが、それぞれ別のものとして管理されるので注意が必要です。【参考】
【npm】 パッケージのインストール先の確認(npm list)グローバルインストールの挙動
# 未インストールなので cowsay コマンドは実行できない $ cowsay "hoge" zsh: command not found: cowsay # cowsay コマンドをグローバルインストール $ npm install -g cowsay $ cowsay "hoge" ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||ローカルインストール
ローカルインストールすると node_modules と同じディレクトリにある場合にパッケージ(コマンド)が実行できます。
【参考】
npm でローカルインストールしたパッケージを CLI でコマンド実行する方法(npm-srcipts, npx)ローカルインストールの挙動(
-D
オプションなしの場合)# 適当なパッケージ(cowsay)をインストール $ npm install cowsay + cowsay@1.4.0 # dependencies の項目に cowsay が追加されていることが確認できる $ cat package.json { (省略) + "dependencies": { + "cowsay": "^1.4.0" + }, (省略) } # node_modules にインストールされるのでパスを通しながら実行可能 $ ./node_modules/.bin/cowsay "hoge" ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||ローカルインストールの挙動(
-D
オプションありの場合)# 適当なパッケージ(typescript)を -D のオプションをつけてインストール $ npm install -D typescript + typescript@4.0.3 # devDependencies の項目の typescript が追加されていることが確認できる $ cat package.json { (省略) "dependencies": { "cowsay": "^1.4.0" }, + "devDependencies": { + "typescript": "^4.0.3" + }, (省略) } # -D のオプションをつけても変わらずに node_modules にインストールされるのでパスを通しながら実行可能 $ ./node_modules/.bin/tsc --version Version 4.0.3グローバルインストール と ローカルインストール使い分け
では、パッケージをインストールする場合、グローバルインストール と ローカルインストール のどちらにインストールすべきでしょうか?
状況によりますが、闇雲にグローバルインストールするのは避けたほうがいいです。
グローバルインストールされたパッケージは環境が異なると使用することができません。
例えば、PC を買い替えたらそのパッケージを使用することができないですし、他人の PC でもそのパッケージを使用することができません。
そいういうときにローカルインストールで作成される package.json だけを共有すれば、どの環境でも
npm install
を実行するだけで、同じパッケージを使用することができます。なので、ローカルインストールをお勧めします。
ローカルインストール時に
-D
オプションをつけるべきケース一般的に、開発環境でしか使用しないパッケージについては
npm install -D <package>
として、そうではないパッケージについてはnpm install <package>
とします。パッケージ名の引数をとらない
npm install
には--production
とオプションをつけることで、npm install -D <package>
でインストールしたパッケージを除いて node_modules にインストールします。
--production
のオプションをつけることによって開発環境でしか使わないパッケージを除外できるので、本番環境デプロイ時に参照されることのないパッケージをインストールしなくて済むようになります。ローカルインストール時の
-D
オプションの有無による違いをまとめると以下のようになります。
実行するコマンド package.json の記述先 それぞれの使い分け npm install <package>
dependencies
開発環境以外でも使用されるパッケージ
ex) expressnpm install -D <package>
devDependencies
開発環境のみで使用されるパッケージ
ex) eslintまた、パッケージ名の引数をとらない
npm install
実行時の--production
オプションの有無による違いをまとめると以下のようになります。
実行するコマンド インストール対象 実行するタイミング npm install
・ dependencies
・devDependencies
開発環境 npm install --production
・ dependencies
のみ本番環境など開発環境でしか使われないパッケージをインストールしたくないとき いろいろ書いていますが、ケースバイケースなので、実際に
npm install --production
をするときになってから考えればいいと思います。
- 投稿日:2020-10-18T00:50:27+09:00
【初心者】そろそろ適当に npm install するのを卒業したい!!
はじめに
ネットに転がっている記事などで
npm install
のコマンドをよく分からず実行してきましたが、そろそろその状態から卒業したかったので備忘録をかねてこちらの記事を投稿しました。
npm install
コマンドに関することをメインにお伝えしますが、package.json や node_modules など npm を語る上で基本的なことについても触れていきたいと思います。この記事の目標
この記事の目的は、以下の4つの
npm install
コマンドを実行したときの挙動と、これら4つのコマンドを必要な状況に応じて使いわけるようになることです。
npm install
npm install -g <package>
npm install <package>
npm install -D <package>
つまり、適当に
npm install
するのを卒業することです。動作環境
# Node.js のバージョン確認 $ node -v v14.13.1 # npm のバージョン確認 $ npm -v 6.14.8ちなみに npm 自体のアップデートは以下のコマンドになります。
npm はメジャーバージョンが異なるとかなり挙動が異なるので、参照する記事の npm のバージョンにも注意が必要です。# npm のアップデート $ npm update -g npmnpm install を説明する前に知っておきたいこと
この辺を理解しておかないと適当に
npm install
してしまうことになるので、先に説明します。
知っている方は飛ばしても大丈夫です。Node.js
Node.js とはサーバサイドで動く JavaScript のことです。
npm
npm(Node Package Manager)とは Node.js のパッケージ(Package)を管理する(Manager)ツールです。
Node.jsのパッケージ(Package)とは予め用意された便利な機能(各種フレームワークやライブラリ)をまとめたものです。
yarn
yarn とは2016年にリリースされた npm と互換性のあるパッケージマネージャーです。
今回は yarn コマンドについては触れませんが、以下の意見にとても納得しました。
ご参考まで。ちなみに: 似たような CLI として Facebook が開発した Yarn がある。これは npm の色々な欠点(スピードなど)を補うように作られたものであり、かなり人気がある。npm パッケージの README でしばしば npm と yarn でインストールする方法が両方書かれていたり、時には「yarn を使用することを推奨する」と書かれていたりする。しかし、npm も改善されてきており、わざわざ yarn をインストールして使用するメリットはあまりないと筆者は考えている。特に初心者にとっては、スタンダードでないツールを使用すると無駄に学ぶことが増えるのでおすすめしない。
【初心者向け】NPMとpackage.jsonを概念的に理解する (抜粋)
https://qiita.com/righteous/items/e5448cb2e7e11ab7d477package.json
package.json とは Node.js ベースの JavaScript アプリ開発において、自身のパッケージ(= プロジェクトそのもの)を管理するために使われるファイルのことです。
npm init
とコマンド実行すると package.json が作成されます。package-lock.json
簡単に説明すると package.json を用いてパッケージをインストールした結果が記載されるファイルです。
詳しくは【参考】のリンク先を参照してください。npm init
package.json は普通に touch コマンドなどでも作成できますが、基本的には
npm init
とコマンド実行して package.json を作成します。(【脱線】npm init しないとどうなる?)
# 何もない空のディレクトリ $ ls (標準出力なし) # npm init による package.json の作成(質問をされるが一旦すべて Enter で通過する = npm init -y コマンド実行時と同じ挙動) $ npm init # package.json が作成されている $ ls package.jsonpackage.json の初期値は以下のようなものになります。
package.json{ "name": "<current directory name>", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }package.json にはいろいろ項目がありますが
name
,version
,description
,keywords
,author
,license
などのデータは単なる自身のパッケージ(= プロジェクトそのもの)のメタデータであり、自身のパッケージ(= プロジェクトそのもの)を公開しなのであれば、気にする必要はありません。
dependencies
,devDependencies
,scripts
,config
の4つぐらいを抑えれば、あとは必要に応じて調べて設定すれば問題ないかと思います。【参考】
package.jsonの構造npm と package.json の関係
公開されているパッケージ(ライブラリやフレームワークなど)は
npm
コマンドによってインストールすることができます。package.json が存在するディレクトリで
npm
コマンドによってパッケージをインストールすると、自動的に package.json が更新されます。また、人間が package.json を直接編集することはありません。
すべて
npm
コマンド経由で更新します。(大事)node_modules
node_modules とは package.json を元にしてインストールされる各種パッケージのがインストールされるディレクトリ先のことです。
package.json さえあれば、
npm install
コマンドの実行によって node_modules が生成することができるため、通常.gitignore
に指定されるディレクトリです。node_modules はいろんなパッケージがインストールされているため、とても容量の大きなディレクトリになるので、その管理を package.json や package-lock.json にまかせて node_modules 自体の管理は git で管理対象外にするケースがほとんどです。
npm install
ここからがこの記事の本題になります。
パッケージを node_modules にインストールするには
npm install
とコマンド実行します。この
npm install
というコマンドですが、これがいろいろと種類があってややこしいので詳しく説明していきます。とりあえずよく使うコマンド一覧
引数なしでの実行する場合
# 引数なし実行(package.json のあるディレクトリで実行する) npm installグローバルインストール
# グローバルインストール npm install -g <package> npm install --global <package>ローカルインストール
# ローカルインストール # package.json の dependencies に追加するとき(npm v4 以下では --save (= -S) のオプションが必要、npm v5 以上ではデフォルトになったため不要) npm install <package> npm install --save <package> # デフォルトで入っているオプションなので使う必要なし npm install -S <package> # デフォルトで入っているオプションなので使う必要なし # package.json の devDependencies に追加するとき --save-dev (= -D) npm install --save-dev <package> npm install -D <package>バージョン指定のインストール
# バージョンに関する指定 npm install <package>@x.y.z # バージョンを指定する場合 npm install <package>@latest # 最新版を指定する場合( @latest とつけなくても指定しなければ最新版がインストールされる)引数のない
npm install
の挙動引数のない
npm install
コマンド実行時の挙動は、引数のあるnpm install <package>
とわけて考えたほうがわかりやすいです。引数のない
npm install
コマンド実行をすると、カレントディレクトリにある package.json に記述されている情報を元に、そこに記述されている パッケージを node_modules (インストール先)にインストールします。なので、あらかじめ package.json にインストールしたいパッケージ情報を記述しておく必要があります。
そこで実行するのが引数のある
npm install <package>
になります。引数のある
npm install <package>
の種類大きな分類としてグローバルインストールとローカルインストールに分けられます。
また、ローカルインストールのなかでも package.json の記述先の違いで2つに分けられます。全体で見るとグローバルインストール1種類、ローカルインストール2種類の合計3種類になります。
大分類 package.json の記述先 コマンド グローバルインストール - npm install -g <package>
ローカルインストール dependencies npm install <package>
ローカルインストール devDependencies npm install -D <package>
ちなみに「グローバルインストール」と「ローカルインストール」の両方とも自身のPCの環境へのインストールのことです。
グローバルインストール
グローバルインストールすると自身の PC の環境ならどこでもインストールしたパッケージ(コマンド)が実行できます。
ただし、グローバルインストールしても
nodebrew
などで複数の Node.js のバージョンを管理している場合は、それぞれのバージョンでnpm install -g <package>
したパッケージが、それぞれ別のものとして管理されるので注意が必要です。【参考】
【npm】 パッケージのインストール先の確認(npm list)グローバルインストールの挙動
# 未インストールなので cowsay コマンドは実行できない $ cowsay "hoge" zsh: command not found: cowsay # cowsay コマンドをグローバルインストール $ npm install -g cowsay $ cowsay "hoge" ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||ローカルインストール
ローカルインストールすると node_modules と同じディレクトリにある場合にパッケージ(コマンド)が実行できます。
【参考】
npm でローカルインストールしたパッケージを CLI でコマンド実行する方法(npm-srcipts, npx)ローカルインストールの挙動(
-D
オプションなしの場合)# 適当なパッケージ(cowsay)をインストール $ npm install cowsay + cowsay@1.4.0 # dependencies の項目に cowsay が追加されていることが確認できる $ cat package.json { (省略) + "dependencies": { + "cowsay": "^1.4.0" + }, (省略) } # node_modules にインストールされるのでパスを通しながら実行可能 $ ./node_modules/.bin/cowsay "hoge" ______ < hoge > ------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||ローカルインストールの挙動(
-D
オプションありの場合)# 適当なパッケージ(typescript)を -D のオプションをつけてインストール $ npm install -D typescript + typescript@4.0.3 # devDependencies の項目の typescript が追加されていることが確認できる $ cat package.json { (省略) "dependencies": { "cowsay": "^1.4.0" }, + "devDependencies": { + "typescript": "^4.0.3" + }, (省略) } # -D のオプションをつけても変わらずに node_modules にインストールされるのでパスを通しながら実行可能 $ ./node_modules/.bin/tsc --version Version 4.0.3グローバルインストール と ローカルインストール使い分け
では、パッケージをインストールする場合、グローバルインストール と ローカルインストール のどちらにインストールすべきでしょうか?
状況によりますが、闇雲にグローバルインストールするのは避けたほうがいいです。
グローバルインストールされたパッケージは環境が異なると使用することができません。
例えば、PC を買い替えたらそのパッケージを使用することができないですし、他人の PC でもそのパッケージを使用することができません。
そいういうときにローカルインストールで作成される package.json だけを共有すれば、どの環境でも
npm install
を実行するだけで、同じパッケージを使用することができます。なので、ローカルインストールをお勧めします。
ローカルインストール時に
-D
オプションをつけるべきケース一般的に、開発環境でしか使用しないパッケージについては
npm install -D <package>
として、そうではないパッケージについてはnpm install <package>
とします。パッケージ名の引数をとらない
npm install
には--production
とオプションをつけることで、npm install -D <package>
でインストールしたパッケージを除いて node_modules にインストールします。
--production
のオプションをつけることによって開発環境でしか使わないパッケージを除外できるので、本番環境デプロイ時に参照されることのないパッケージをインストールしなくて済むようになります。ローカルインストール時の
-D
オプションの有無による違いをまとめると以下のようになります。
実行するコマンド package.json の記述先 それぞれの使い分け npm install <package>
dependencies
開発環境以外でも使用されるパッケージ
ex) expressnpm install -D <package>
devDependencies
開発環境のみで使用されるパッケージ
ex) eslintまた、パッケージ名の引数をとらない
npm install
実行時の--production
オプションの有無による違いをまとめると以下のようになります。
実行するコマンド インストール対象 実行するタイミング npm install
・ dependencies
・devDependencies
開発環境 npm install --production
・ dependencies
のみ本番環境など開発環境でしか使われないパッケージをインストールしたくないとき いろいろ書いていますが、ケースバイケースなので、実際に
npm install --production
をするときになってから考えればいいと思います。【参考】
【package.json】dependencies, devDependencies の使い分けを考えるさいごに
いかがだったでしょうか?
もうこれで、以下の4つのnpm install
コマンドはいけるはずです!!
npm install
npm install -g <package>
npm install <package>
npm install -D <package>
卒業おめでとうございます!!