- 投稿日:2021-01-28T22:13:36+09:00
React入門!!の前に知っておきたいNode.jsとJavaScriptの知識
対象読者とこの記事から何が得られるのか
そろそろReactの勉強をはじめてみたい、勉強したいと思っているけど、以下のようなことを考えて前に進めない方向けの記事です。
- JavaScriptの知識レベルはこれくらいで足るのか?
- 他に知っておくことはないのかな?
この記事を読むことでReact入門の前に知っておきたいNode.jsとJavaScriptの知識を得ることができると思います。
Node.jsをざっと知る
Node.jsを簡単に言うと
JavaScriptをブラウザではなくPythonやRubyなどと同じようにターミナル上で動かすことができるようにするための実行環境のことであり、バックエンド言語としてJavaScriptを使うことができ、ファイル操作などのOSの機能にアクセスできます。
公式ページだと以下のように記載されています。
Node.js はスケーラブルなネットワークアプリケーションを構築するために設計された非同期型のイベント駆動の JavaScript 環境です。
Node.jsが注目を浴びたのはC10K問題の解決策である(イベント駆動により大量のリクエストを同時に処理できるスケーラビリティを備えている・ノンブロッキングI/Oモデルを採用しており、I/Oの処理を待たずに次の処理を始めることができるので、大量のデータ処理が可能)というのが理由です。またバックエンドも同じ言語(JavaScript)で書けたら効率的ではないかというのも理由の一つです。フロントエンド開発はJavaScriptほぼ一択ということもあり、こういう流れになったのではないかと思います。
なぜReactアプリ開発でNode.jsが必要なのか
ではなぜバックエンド開発のために開発されたNode.jsがReactアプリ(フロントエンド)開発で必要になってきたのでしょうか。
Reactのような大規模なアプリケーションを開発するとなると、様々なパッケージが必要になり、そのパッケージたちが特定のバージョンで依存しあっています。Node.jsはそのパッケージのインストールと整合性の管理を解決するために必要であり、npm(Node Package Manager)がその解決を担っています。
もともとはバックエンド開発のためにnpmは使われるパッケージ管理システムであったが、フロントエンド用のパッケージを提供するのにも使われるようになり、最近では使用用途としてはフロントエンドのほうが多くなってきているそうです。
その他以下のような用途でも使われます。
- JavaScriptやCSSファイルをバンドルできる
- ブラウザ実行時にpolyfill※するのではなく 最初からコンパイルしておける
- ローカル環境で開発用のHTTPサーバを起動してアプリケーションを稼働させることができる
- ユニットテストやE2Eテストなどのテストを実行できる
- ローカル環境で構文解析等実行できる
※polyfillとは最近の機能をサポートしていない古いブラウザーで、その機能を使えるようにするためのコードです。
Node.jsをインストール
公式サイトからインストールしたり、MacならHomebrew・Windowsならwingetなどでインストールできますが、プロジェクトごとに異なるバージョンの環境を共存させる必要があるので、バージョンマネージャー(nvmやnodenv)を使ってインストールしましょう。
Node.jsでプロジェクト作成
作成したいディレクトリに移動してターミナルで以下コマンドでプロジェクトを作成できます。
対話形式でプロジェクト名など聞かれますので、全て入力するとpackage.jsonが作成されます。$ npm init
-y
をつければ、全てデフォルトのままプロジェクト作成できます。$ npm init -ynpm-scripts
package.jsonのscriptsに記載されているコマンドを以下で実行できます。
$ npm run xxxでは試しにnpm-scriptsでプログラムを実行してみましょう。
まずindex.js
を作成する。index.jsconsole.log("Hello World!!");package.jsonに
start
でのコマンドを追加する。
node.jsではnode
+ファイル名
でそのファイルのプログラムを実行できます。"scripts": { "start": "node index.js" }以下コマンドで実行する。
$ npm run start以下出力されれば成功です。
Hello World!!start,stop,restart,testの場合は
run
を省略して実行できます。$ npm startReactアプリ開発ではcreate-react-appというコマンドで新規プロジェクトが作成できますが、こちらで作られたプロジェクトだと以下が記載されています。たとえば
npm run start
を実行するとreact-scripts start
が実行され、ローカル環境でReactアプリが起動できます。react-scripts
はcreate-react-appで作られたプロジェクトの中でBabelやwebpackなどが裏で動かすことができます。"scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" },Yarnとは
npmコマンドのFacebookによる改良版で記述が短い・実行が速いなどのメリットがある。
Yarnがグローバルインストールされていた場合はCreate React App使用時にyarnがデフォルトになるので、npmコマンドを使いたいなら--use-npm
オプションを指定しましょう。Yarnのコマンド
- yarn (install) はプロジェクトのルートディレクトリに存在するpackage.jsonの内容を参照して、依存関係のあるパッケージをすべてインストールする
- yarn add xxx で指定したパッケージをインストールする
- yarn remove xxx で指定したパッケージをアンインストールする
- yarn upgrade xxx で指定したパッケージを最新バージョンに更新する
- yarn info xxx で指定したパッケージの情報を表示する
上記の
npm run xxx
は以下コマンドで同じことができます。yarn xxxJavaScriptをざっと知る
変数宣言について
- varは再宣言も再代入も可。スコープはブロックをすり抜ける。
- letは再代入のみ可。スコープはブロック内。
- constはどちらも不可。スコープはブロック内。
constを第一選択肢として、どうしても仕方ない場合のみletを使う。
varは使用しないようにしましょう。var name1 = "瀬戸熊"; name1 = "佐々木" // 再代入可 var name1 = "滝沢" // 再宣言可 let name2 = "瀬戸熊"; name2 = "佐々木" // 再代入可 let name2 = "滝沢" // 再宣言不可:'name2' has already been declared const name3 = "瀬戸熊"; name3 = "佐々木" // 再代入不可:"name3" is read-only const name3 = "滝沢" // 再宣言不可:'name3' has already been declaredスコープに関してもvarを使うと以下のようになり、安全ではないのでletやconstを使うべきである。
var name1 = "高宮"; if (true) { var name1 = "二階堂"; var name2 = "魚谷"; console.log(name1); // 二階堂 console.log(name2); // 魚谷 } console.log(name1); // 二階堂(ブロック内でも書き換え可能なので危険) console.log(name2); // 魚谷(ブロック内で定義したものがブロック外でも参照できる)letだとブロック内で書き換えや定義しても、ブロック外に影響を与えない。
let name1 = "高宮"; if (true) { let name1 = "二階堂"; let name2 = "魚谷"; console.log(name1); // 二階堂 console.log(name2); // 魚谷 } console.log(name1); // 高宮 console.log(name2); // name2 is not definedデータ型について
プリミティブ型
以下7種類ある。
- Boolean型
- Number型
- BigInt型
- String型
- Symbol型
- Null型
- Undefined型
falsyな値
MDNによると、falsyとは偽値 (falsy または falsey な値) は、 Boolean コンテキストに現れたときに偽とみなされる値です。
以下8種類あります。
- false
- 0
- -0
- 0n
- Nan
- ""(空文字)
- null
- undefined
以下はifブロックを実行しません。
if (false){} if (null){} if (undefined){} if (0){} if (-0){} if (0n){} if (NaN){} if (""){}関数
関数宣言文と関数式
定義の方法は関数宣言文による定義と関数式による定義があるがconstを使った
関数式
による定義が推奨されています。
無名関数は定義時に名前を与えられない関数のことで、関数式は変数に無名関数を入れているようなもの。
変数に関数式を代入することになるので、関数宣言文と違って先に定義しておかないと使えません。// 関数宣言文による定義 function Introduce1(name) { return `私の名前は${name}です。` } // 関数式による定義 const Introduce2 = function (name) { return `私の名前は${name}です。` };アロー関数
アロー関数は引数が一つだと括弧の省略ができる。(推奨はされていないらしいが・・・)
また、retern文が1行だとreturnも省略できます。// アロー関数式 const Introduce3 = (name) => { return `私の名前は${name}です。` }; // アロー関数式、さらに省略記法 const Introduce4 = name => `私の名前は${name}です。`;デフォルト引数
デフォルト値が設定された引数は省略が可能。
以下の例では第二引数を省略するとage
が18で代用される。const Introduce5 = (name, age = 18) => `私の名前は${name}です。${age}歳です。`; console.log(Introduce5("白鳥", 32)); // 私の名前は白鳥です。32歳です。 console.log(Introduce5("松本")); // 私の名前は松本です。18歳です。Rest Parameters
最後の引数に...を付けることで残りの引数を配列として受け取れる。
const Names = (name1, name2, ...rest) => { console.log(name1); // 瀬戸熊 console.log(name2); // 佐々木 console.log(rest); // ["滝沢","二階堂","高宮"] }; Names('瀬戸熊', '佐々木', '滝沢', '二階堂', '高宮');Reactで開発する上で良く使う構文
プロパティ名のショートハンド
プロパティのキー名と値を同じにする。
const name = "瀬戸熊"; const obj = { name }; // = const obj = { name: name }; console.log(name); // { name: "瀬戸熊" }分割代入
配列とオブジェクトの値を分割して代入する。
const data = [180, 70] const [height, weight] = data; console.log(`私の身長は${height}cmで、体重は${weight}kgです。 `); // 私の身長は180cmで、体重は70kgです。 const user = { name: "瀬戸熊", age: 50 }; const { name, age } = user; console.log(`私の名前は${name}です。${age}歳です。`); // 私の名前は瀬戸熊です。50歳です。スプレッド構文
const names1 = ["瀬戸熊", "佐々木", "滝沢"]; const names2 = [...names1, "二階堂", "高宮"]; console.log(names2); // [ "瀬戸熊","佐々木","滝沢","二階堂","高宮" ] const users1 = { name: "瀬戸熊", age: 48, sex: "男" }; const users2 = { ...users1, group: "renmei", grade: "A1" }; console.log(users2); // { name: "瀬戸熊", age: 50, sex: "男", group: "renmei", grade: "A1" }オブジェクトのコピー
分割代入を用いてオブジェクトをコピーできます。
しかし、こちらはシャローコピーと言ってオブジェクトの深さが1段階までしか有効ではない。オブジェクトの深さが2段階目の値を変更すると、コピー元の値まで変更されてしまう。
const user1 = { name: "瀬戸熊", age: 50, sex: "男" }; const user2 = { ...user1 }; console.log(user2); // { name: "瀬戸熊", age: 50, sex: "男" } console.log(user2 === users1); // false const user3 = { name: "瀬戸熊", age: 50, sex: "男", group: { group: "連盟", grade: "A1" } }; const user4 = { ...user3 }; console.log(user4); // user4: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '連盟', grade: 'A1' } } user4.group.group = "協会"; console.log(user3); // user3: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '協会', grade: 'A1' } } console.log(user4); // user4: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '協会', grade: 'A1' } }完全なコピー(ディープコピー)の方法はいくつかあるが、JSONパースを用いる方法は以下の通り。
プロパティにDateオブジェクトや関数が入ってた場合はうまく動かないので注意。const user3 = { name: "瀬戸熊", age: 50, sex: "男", group: { group: "連盟", grade: "A1" } }; const user4 = JSON.parse(JSON.stringify(user3)); console.log(user4); // user4: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '連盟', grade: 'A1' } } user4.group.group = "協会"; console.log(user3); // user3: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '連盟', grade: 'A1' } } console.log(user4); // user4: { name: '瀬戸熊', age: 50, sex: '男', group: { group: '協会', grade: 'A1' } }他にはLodashのcloneDeep()を使う方法などある。
ショートサーキット評価(短絡評価)
- || は左辺がfalsyな値だと評価が右辺に渡される。
- ?? は左辺がnullかundefinedだと評価が右辺に渡される。
- && は左辺がtruthyな値だと評価が右辺に渡される
|| はちょっと前に賛否ありましたが、一応よくでてくるので記載しておきます・・・。
const name1 = '瀬戸熊'; const name2 = '佐々木'; true && console.log(name1); // 瀬戸熊 false && console.log(name1); // 出力なし true || console.log(name2); // 出力なし false || console.log(name2); // 佐々木 null ?? console.log(name2); // 佐々木 undifined ?? console.log(name2); // 佐々木配列・オブジェクトの処理
map,filter,find,findIndex,every,some
const dataset = [1, 2, 3, 4, 5, 6, 7, 8, 9]; console.log(dataset.map((data) => data * 3)); // [ 3, 6, 9, 12, 15, 18, 21, 24, 27 ] console.log(dataset.filter((data) => data > 5)); // [ 6, 7, 8, 9 ] console.log(dataset.find((data) => data > 5)); // 6 console.log(dataset.findIndex((data) => data > 5)); // 5 console.log(dataset.every((data) => data > 5)); // false console.log(dataset.some((data) => data > 5)); // true
- map():与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成する。
- filter():与えられた関数によって実装されたテストに合格したすべての配列からなる新しい配列を生成する。
- find():提供されたテスト関数を満たす配列内の 最初の要素の値を返します。見つからなかった場合はundefinedを返す。
- findIndex():配列内の指定されたテスト関数を満たす最初の要素の位置を返します。テスト関数を満たす要素がない場合を含め、それ以外の場合は-1 を返します。
- every():配列内のすべての要素が指定された関数で実装されたテストに合格するかどうかを真偽値で返します。
- some():配列の少なくとも一つの要素が、指定された関数で実装されたテストに合格するかどうかを真偽値で返します。
reduce,sort
- reduce():配列の各要素に対して (引数で与えられた) reducer関数を実行して、単一の出力値を生成します。
- sort(): 配列の要素をソートします。既定のソート順は昇順で、要素を文字列に変換してから、UTF-16コード単位の値の並びとして比較します。
const dataset = [1, 2, 3, 4, 5, 6, 7, 8, 9]; console.log(dataset.reduce((a, b) => a + b)); // 45 console.log(dataset.sort((a, b) => a > b ? -1 : 1)); // [ 9, 8, 7, 6, 5, 4, 3, 2, 1 ]forEach,for...of
- forEach():メソッドは与えられた関数を、配列の各要素に対して一度ずつ実行します。
- for...of:反復可能オブジェクトなどに対して、反復的な処理をするループを作成します。
本来なら使わないほうがいいが、どうしても使う場合はforEach推奨。
const dataset = [1, 2, 3, 4, 5, 6, 7, 8, 9]; dataset.forEach((data) => { if (data % 2 === 0) { console.log(`${data}は偶数です。`); } }); for (let data of dataset) { if (data % 2 === 0) { console.log(`${data}は偶数です`); } }includes
特定の要素が配列に含まれているかどうかを true または false で返します。
const dataset = [1, 2, 3, 4, 5]; console.log(dataset.includes(1)); // true console.log(dataset.includes(7)); // falseまた、以下のような||演算子が繰り返し使われるコードを完結に書けます。
if ( x === 'a' || x === 'b' || x === 'c' ) { console.log('ok') } if (['a', 'b', 'c'].includes(x)) { console.log('ok') }Object.keys,values,entries
- Object.keys():プロパティのキーのリストを配列で取得できる
- Object.values():プロパティ値のリストを配列で取得できる
- Object.entries():プロパティのキーと値が対になった2次元配列を取得できる
const user = { id: 1, name: '瀬戸熊', age: 50, }; console.log(Object.keys(user)); // [ 'id', 'name', 'age'] console.log(Object.values(user)); // [ 1, '瀬戸熊', 50 ] console.log(Object.entries(user)); // [ // [ 'id', 1 ], // [ 'name', '瀬戸熊' ], // [ 'age', 50 ], // ]非同期処理
Promise
PromiseはES2015から導入されたJavaScriptの組み込みオブジェクトで、非同期処理の最終的な完了処理 (もしくは失敗) およびその結果の値を表現するものであり、Promiseを使うことによって、非同期処理の完了を待って次の処理を行うというのがJavaScriptでも簡単(それ以前はコールバック関数で書くしかなかったので、連続して非同期処理を行う場合にコールバック地獄となる)にできるようになります。
以下例文
- 最初のresolve()に渡したものが.then()の引数のvalueになり、その.then()内でreturnしたものが次の.thenのvalueになる。
- reject()に渡したものが、errorとしてcatch()で受け取れる。
- finally()に渡された関数は必ず最後に実行される。(ES2018から)
const promise = new Promise((resolve, reject) => { if (isSuccess) { resolve('成功1'); } else { reject(new Error('失敗')); } }); promise.then((value) => { console.log(value); return '成功2'; }) .then((value) => { console.log(value); }) .catch((error) => { console.error(error); }) .finally(() => { console.log('完了'); });axiosを使ってAPIをたたいてみる
async/awaitを使わない場合const getUser = (userId) => { return new Promise(function (resolve, reject) { axios .get(`https://jsonplaceholder.typicode.com/users/${userId}`) .then((response) => resolve(response.data)) .catch((error) => reject(error.response.status)); }); }; getUser(1) .then((user) => { console.log(user); }) .catch((error) => { console.log(error); }) .finally(() => { });async/awaitを使った場合
関数宣言時にasyncキーワードを付与するとその関数は、返される値がPromise.resolve()によってラップされたものになる。
asyncをつけた非同期関数内では他の非同期関数をawaitをつけて呼び出すことができる。const getUser2 = async (userId) => { try { const response = await axios.get( `https://jsonplaceholder.typicode.com/users/${userId}` ); return response.data; } catch (error) { throw error.response.status; } }; const main = async () => { try { const user = await getUser2(1); console.log(user); } catch (error) { console.error(error); } finally { } }; main();モジュール
名前付きエクスポート
1ファイルでいくらでもエクスポートできる。
src/export.jsconst NAME = "瀬戸熊"; const AGE = 50; const Introduce = (name) => `私の名前は${name}です` export { NAME, AGE, Introduce }; or export const NAME = "瀬戸熊"; export const AGE = 50; export const Introduce = (name) => `私の名前は${name}です`名前付きエクスポートの場合は{}をつけてインポートする
src/inport.jsimport { NAME, AGE, Introduce } from "./export"デフォルトエクスポート
1ファイル1回までしかエクスポートできない
src/export.jsexport const NAME = "瀬戸熊"; export const AGE = 50; const Introduce = (name) => `私の名前は${name}です` export default Introduce;デフォルトエクスポートの場合は{}はつけないでインポートする。名前も自由に命名できる。
src/inport.jsimport Introduce, { NAME, AGE } from "./export"まとめ
React入門の前に知っておきたいNode.jsとJavaScriptの知識を簡単にまとめました。
今回記載した項目が大体頭に入っていれば、Reactに入門してもよいのではないかと思います。このほかにもクラスやthisの扱いなど学ぶべきものはいっぱいありますが、Reactが関数コンポーネント主体になってきたのであまり使わない印象があるので、一旦は飛ばしてもよいかと思い省略しました。(理解はしておいたほうがいいですが)
あとTypeScriptはReactでの開発においてマストなものになりつつあるので、早めにTypeScriptでReactを書けるように学んでいったほうがいいと思います。
間違っていたり、これも理解しておくべきなどありましたら、指摘していただけると幸いです。
最後まで読んでいただきありがとうございました!!
参考
MDN
Polyfill (ポリフィル)
Falsy (偽値)
Array.prototype.map()
Array.prototype.filter()
Array.prototype.find()
Array.prototype.findIndex()
Array.prototype.every()
Array.prototype.some()
Array.prototype.reduce()
Array.prototype.sort()
Array.prototype.includes()
PromiseQiita
Node.jsとはなにか?なぜみんな使っているのか?
Promiseの使い方、それに代わるasync/awaitの使い方りあクト
- 投稿日:2021-01-28T17:32:15+09:00
inputのtype別に、puppeteerでの入力方法をまとめた
inputタグに設定できるtype属性は今のところ、以下の20種類
button, checkbox, color, date, datetime-local, email, file, hidden, image, month, number, password, radio, search, submit, tel, text, time, url, weekpuppeteerで簡単に入力できるもの、専用の入力方法が用意されているの、クセが強いもの、など様々だったのでまとめた。
なお、本ページの例は全タグにidがふってある優しい世界なので、要素の特定は別途頑張ってください。確認環境
- Windows10 2004
- node v14.15.4
- puppeteer 5.5.0
- Chromium 90.0.4400.0
text系
以下のtypeを、まとめてtext系と呼ぶことにする。
email, number, password, search, tel, text, urlhtmlだとこんなかんじ
<input type="text" id="text1"> <input type="email" id="email1"> <input type="search" id="search1"> <input type="tel" id="tel1"> <input type="url" id="url1"> <input type="number" id="number1"> <input type="password" id="password1">submit時にvalidationがかかるもの、入力した文字が見えないものなど、それぞれ違いはあるが、入力する上では全て
type()
を利用すれば良いawait page.type("#text1", "text"); await page.type("#email1", "example@example.com"); await page.type("#search1", "search text"); await page.type("#tel1", "09012345678"); await page.type("#url1", "https://example.com/"); await page.type("#number1", "42"); await page.type("#password1", "yourpassword");button系
以下のtypeを、まとめてbutton系と呼ぶことにする。
button, image, reset, submithtmlだと以下
<input type="reset" id="reset1"> <input type="button" id="button1" value="ボタン"> <input type="submit" id="submit1"> <input type="image" id="image1" src="image.png" alt="image-alt-text">それぞれ役割があるが、入力(クリック)はどれも
click()
でOKawait page.click("#button1"); await page.click("#image1"); await page.click("#reset1"); await page.click("#submit1");radio, checkbox
ラジオボタンやチェックボックスも、素直に
click()
でOK<input type="radio" id="radio1"> <input type="checkbox" id="checkbox1">await page.click("#radio1"); await page.click("#checkbox1");file
<input type="file" id="file1">type="file"には、専用のメソッドである
waitForFileChooser()
が用意されている。
使い方は、waitForNavigation()
と同様に、クリック前に実行しておき、resolve後、ファイルを指定する。headlessでなくてもファイル選択のUIは表示されない。const [fileChooser] = await Promise.all([ page.waitForFileChooser(), page.click('#file1'), ]); await fileChooser.accept(["/path/to/file"]);もしくは、以下でも可。個人的にはネストが深くならないこちらのほうが好み。読みやすさは大差ない。
const fileChooserPromise = page.waitForFileChooser(); await page.click('#file1'); await (await fileChooserPromise).accept(["/path/to/file"]);range
スライドバーが表示されるtype="range"
<input type="range" id="range1">単純な値指定は難しいため、
focus()
でフォーカスを合わせて1、右矢印キー
か左矢印キー
を適切な回数押すと良い。(1回でstep属性の値だけ変化、デフォルト1)await (await page.$("#range1")).focus(); for(let i = 0; i < 20; i++) { await page.keyboard.press("ArrowRight"); }color
<input type="color" id="color1">puppeteerの闇を感じることができるtype="color"。
クリックするとカラーピッカーが表示されるが、puppeteerに専用のメソッドは用意されていない2ため、このカラーピッカーを操作する必要がある。やってみた結果が以下。
await (await page.$("#color1")).click(); await page.waitForTimeout(300) await page.keyboard.press("Tab") await page.keyboard.press("Tab") await page.keyboard.press("Tab") await page.keyboard.type("255") await page.keyboard.press("Tab") await page.keyboard.type("0") await page.keyboard.press("Tab") await page.keyboard.type("0") await page.keyboard.press("Enter")まずクリックでカラーピッカーを表示させ、タブを3回押すことでRGB値の入力欄に移動。
タブで移動しながらR,G,Bそれぞれの値を入れることで入力している。クリック後300ms待っているのは、カラーピッカーが表示されるまでに若干時間がかかるため。ここは環境によって変える必要があるだろう。
DOM要素ならwaitForSelector()
などで待つことができるが、カラーピッカーはDOM要素でない(・・・よね?)ため仕方なくwaitForTimeout()
を利用している。もうお気づきだろうと思うが、完全にChromiumのカラーピッカーのUIに依存した書き方になっているため、Firefoxではおそらく動かない(未検証)し、今後Chromiumのバージョンアップで動作しなくなる可能性もある。闇だ。
なお、puppeteerでは、ページ上でJavaScriptを動作させることもできるため、以下の書き方もできる。これなら1行だ。
await (await page.$("#color1")).evaluate((node) => {node.value = "#FF0000"});ただ、この書き方だと、changeイベントが発火しないため、テスト内容によっては利用できない。
date系
以下のtypeを、まとめてdate系と呼ぶことにする。
date, datetime-local, month, time, weekhtmlでは以下。
<input type="date" id="date1"> <input type="datetime-local" id="datetime-local1"> <input type="month" id="month1"> <input type="time" id="time1"> <input type="week" id="week1">まず、入力ボックスの右側のカレンダーや時計をクリックするとピッカーが表示されるが、これを使おうとしてはいけない。マウスでしか開けない(多分)し、ピッカーはDOM要素でないため、開いた後の操作が難しい。ブラウザごとの差異も激しい。
キーボードで日付/時刻を入力するのが最善だ。その上で、以下の点に気をつける必要がある。3
- 項目が一意に特定可能になると、次の項目に自動で遷移する
- 「月」に5を入力すると自動で「日」にフォーカスが移るが、1を入力してもフォーカスはそのまま(10-12月があるため)
- 最大275760年9月13日まで入力できる4
- つまり「年」に4桁入力しても、「月」にフォーカスを移してくれない
- min,max属性の指定によっては、入力不可な項目が出てくる
- type="date"で、min="2020-01-01" max="2020-12-31"の場合、「年」は2020に固定され、フォーカスが当たるといきなり「月」の入力になる。
- ロケールによって年月日の順番が異なる
1.の解決策は2つ。
1つ目は、タブや右矢印などでフォーカスを移すこと。
「月」に1と入力したあとでタブを押せば、フォーカスが「日」に移ってくれる。
ただし、puppeteer上では月をtype()
したあと、月が1のときのみタブをpress()
してまた日をtype()
する、という少し面倒な書き方になる。5
おすすめは次の2つ目だ。「月」を0埋めし、必ず2桁入力する。puppeteerのコードとしては、タブを押すよりだいぶ簡単になる。[^js_padding]2.はほとんど1.と同種の問題だ。
「年」を入力したあと、タブでフォーカスを移してもよいが「年」の頭に00を加えて6桁入力するのが楽だ。type()
のみですむ。
また、自動化対象のコードを変更できる場合、maxを指定してしまっても良い。max="9999-12-31"としておく6と、4桁入力した時点で「月」にフォーカスが移ってくれる。あと7900年後くらいまでは困る人もいないだろう。3.は少し厄介だ。
「年」が入力不可だと分かっているのなら単純に「月」と「日」だけ入力すればよい(dateの場合)のだが、例えば「現在の日付から半年後まで」という仕様の場合、1-6月と7-12月で年の入力可否が変わってしまう。「1年後の前日の日付まで」という仕様なら1月1日のみCIが落ちるかもしれない。7
解決策としては、minとmax属性を読み込んで場合分け、が愚直な方法だろうか。
尤も、自動テストという文脈なら、テストの外部要因(時刻)でテスト内容や結果が変わるテストはイマイチなので、現在時刻をDIできる設計にするのが望ましい。84.は解決策を調査中である。ブラウザ上で、年月日がどの順番で表示されているのか、を取得する方法があれば知りたい。
もしくは、Chromiumの起動オプションでロケールを指定して年月日の順番を固定させる方法が利用できるかもしれない。(未調査)ということを踏まえて、入力するコードは以下となる。(3と4は検討外)
await page.type("#date1", "0020210127"); await page.type("#datetime-local1", "00202101270812"); await page.type("#month1", "00202012"); await page.type("#time1", "0212"); await page.type("#week1", "00201943");3や4が問題になる場合、changeイベントが発火しない、などの差分が許容できるなら、以下の書き方も利用できる。
await (await page.$("#date1")).evaluate((node) => {node.value = "2021-01-27"}); await (await page.$("#datetime-local1")).evaluate((node) => {node.value = "2021-01-27T08:12"}); await (await page.$("#month1")).evaluate((node) => {node.value = "2020-12"}); await (await page.$("#time1")).evaluate((node) => {node.value = "02:12"}); await (await page.$("#week1")).evaluate((node) => {node.value = "2019-W43"});hidden
hiddenは普通はpuppeteerから基本的に書き換えないし、書き換えられない。
どうしてもやりたければ、evaluate()
でやるしかない。ブラウザ自動操作の枠を超えているような気もするが。<input type="hidden" id="hidden1">await (await page.$("#hidden1")).evaluate((node) => {node.value = "hidden-value"});おまけ
inputとともによく使われる以下についても簡単に。
textarea tag
<textarea id="textarea_tag1"></textarea>textareaは、text系と同じで、
type()
でOKawait page.type("#textarea_tag1", "textarea");button tag
<button id="button_tag1">button</button>ボタンは、名前の通りbutton系、
click()
でOKawait page.click("#button_tag1");select tag
<select id="select1"> <option value="1" id="select1_1">option_1</option> <option value="2" id="select1_2">option_2</option> <option value="3" id="select1_3">option_3</option> </select> <select id="select_multi1" multiple> <option value="1" id="select_multi1_1">option_1</option> <option value="2" id="select_multi1_2">option_2</option> <option value="3" id="select_multi1_3">option_3</option> </select>selectは、専用のメソッドが用意されている。
select()
だ。
selectはmultipleか否かでUIが大きく異なるがselect()
は両方に対応している。
第2引数以降に選択するoptionのvalueを指定する。await page.select("#select1", "2"); await page.select("#select_multi1", "2", "3");
- 投稿日:2021-01-28T16:33:08+09:00
Node.js + Hapi + Couchbase ~ Node.js + NoSQL(Couchbase) アプリ開発 ステップバイステップガイド (3)
はじめに
本シリーズの前回の記事では、WEBアプリケーション・フレームワークとして、Expressを用いました。
今回は、Hapiを使った例を紹介します。利用バージョン
- Couchbase Server: 6.6
- Node.js: 15.4.0
- Node.js モジュール
- couchbase: 3.1.1
- hapi: 18.1.0
- uuid: 8.3.2
実装
Couchbase Server
以下、前提事項を記します。
バケット
利用するバケット(
MyFirstBucket
)が存在しているインデックス
検索で利用する項目に対して、インデックスが作成されている。
CREATE INDEX def_index_type ON MyFirstBucket(type)ユーザー
利用するバケットに対するアクセス権のあるユーザー(
Administrator
)を利用する。Node.js
ライブラリインストール
npm install couchbase hapi uuid --saveソースコード
app.jsconst Hapi = require("hapi"); const Couchbase = require("couchbase"); const UUID = require("uuid"); const server = Hapi.Server({"host":"localhost","port":3000,routes:{cors: {origin: ['*']}}}); var cluster = new Couchbase.Cluster( 'couchbase://localhost', { username: 'Administrator', password: 'password' } ); const bucket = cluster.bucket("MyFirstBucket"); const collection = bucket.defaultCollection(); const typeName = "user"; const statement = "SELECT `"+bucket._name+"`.* FROM `"+bucket._name+"` WHERE type = '"+typeName+"'"; const selectUsers = async (key) => { const result = await cluster.query(statement); return result.rows; } const upsertUser = async (doc) => { try { const key = UUID.v4(); const result = await collection.upsert(key, doc); } catch (error) { console.error(error); } }; const init = async() => { await server.start(error=>{ if(error){ throw error; } }); return server; }; init().then(server => { console.log('Server running at:', server.info.uri); }).catch(err => { console.log(err); }); server.route({ method:"GET", path:"/users", handler: async (request,h)=>{ const rows = await selectUsers(); return h.response(rows); } }); server.route({ method:"POST", path:"/user", handler: async (request, h)=>{ try { request.payload["type"] = typeName; const result = await collection.upsert(UUID.v4(),request.payload); return h.response(request.payload); } catch (error) { console.error(error); return h.response("").code(500); } } });実行確認
サーバー起動
$ node app.jsアクセス結果
最後に
次のステップとして、Veu.jsを用いたフロントエンド・アプリケーションとの連携を予定しています(今回作成したPOSTのAPI
/user
については、そちらで利用例を紹介します)。参考情報
Hapiは、バージョン17で、APIの大きな変更が行われています。新しいAPIについて、下記の記事を参考にしました(この記事では、データベースとしてMongoDBが使われています)。
Developing RESTful APIs with HapiCouchbase SDKも、メジャーアップデートにより、APIが大きく変更されています。
過去に発表された下記の二つの連続した記事では、古いバージョンが用いられていますが、基本構成の参考にしています。Create a RESTful API with Node.js, Hapi, and Couchbase NoSQL
- 投稿日:2021-01-28T16:17:55+09:00
MySQL に ssl 認証で接続
次のファイルがあるフォルダーで実行します。
client-cert.pem client-key.pem server-ca.pemコマンド
ssl_connect.shhost="example.com" user="scott" pass="secret" # mysql --ssl-ca=server-ca.pem --ssl-cert=client-cert.pem \ --ssl-key=client-key.pem \ --host=${host} --user=${user} --password=${pass}Python3
show_tables.py#! /usr/bin/python # # show_tables.py # # Jan/28/2021 import pymysql.cursors # host_aa='example.com' user_aa='scott' pass_aa='secret' db_aa='dbfirst' # connection = pymysql.connect(host=host_aa, user=user_aa, password=pass_aa, db=db_aa, charset='utf8', cursorclass=pymysql.cursors.DictCursor, ssl={'key': './client-key.pem', 'cert': './client-cert.pem', 'ca': './server-ca.pem','check_hostname': False}) cursor = connection.cursor() cursor.execute("show tables") result = cursor.fetchall() # print(result) for rr in result: print(rr) cursor.close() connection.close()Node.js
show_tables.js#! /usr/bin/node // --------------------------------------------------------------- // show_tables.js // // Jan/28/2021 // // --------------------------------------------------------------- 'use strict' var fs = require("fs") // --------------------------------------------------------------- console.error ("*** 開始 ***") var mysql = require('mysql') var connection = mysql.createConnection ({ host: 'example.com', user: 'scott', password: 'secret', database : 'dbfirst', ssl : { ca : fs.readFileSync('./server-ca.pem'), key : fs.readFileSync('./client-key.pem'), cert : fs.readFileSync('./client-cert.pem'), } }) connection.query("show tables", function (err, rows) { if (err) throw err console.log (rows.length) rows.forEach(function(row) { console.log(row) }) connection.end() console.error ("*** 終了 ***") }) // ---------------------------------------------------------------Go
show_tables.go// ---------------------------------------------------------------- // // show_tables.go // // Jan/29/2021 // // ---------------------------------------------------------------- package main import ( "crypto/tls" "database/sql" "fmt" "log" "os" "github.com/go-sql-driver/mysql" ) func main() { fmt.Fprintf (os.Stderr,"*** 開始 ***\n") cert, err := tls.LoadX509KeyPair("./client-cert.pem", "./client-key.pem") if err != nil { log.Fatal(err) } clientCert := []tls.Certificate{cert} mysql.RegisterTLSConfig("custom", &tls.Config{ Certificates: clientCert, InsecureSkipVerify: true, }) db, err := sql.Open("mysql", "scott:secret@tcp(example.com:3306)/dbfirst?tls=custom") if err != nil { log.Fatal(err) } err = db.Ping() if err != nil { log.Fatal(err) } sql_str := "show tables" rows, err := db.Query(sql_str) if err != nil { fmt.Println(err) } defer rows.Close() for rows.Next() { var name string if err := rows.Scan(&name); err != nil { fmt.Println(err) } fmt.Printf ("%s\n",name) } if err := rows.Err(); err != nil { fmt.Println(err) } fmt.Fprintf (os.Stderr,"*** 終了 ***\n") } // ----------------------------------------------------------------参考ページ
MySQLでクライアント証明書を使う
- 投稿日:2021-01-28T12:49:14+09:00
CognitoのJWTをNode.js(Typescript)で検証する方法
JWT検証の実装
npm i jsonwebtoken jwks-rsa npm i -D @types/jsonwebtoken
index.tsimport jwt, { JwtHeader, SigningKeyCallback } from "jsonwebtoken"; import jwksClient from "jwks-rsa"; var client = jwksClient({ jwksUri: "https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json", }); function getKey(header: JwtHeader, callback: SigningKeyCallback) { if (!header.kid) throw new Error("not found kid!"); client.getSigningKey(header.kid, function (err, key) { if (err) throw err; callback(null, key.getPublicKey()); }); } const token = "{jwtToken}"; jwt.verify(token, getKey, function (err, decoded) { if (err) throw err; console.log(decoded); });npx ts-node index.tsキャッシュについて
毎回Cognitoのjwksにアクセスしなくて良いように、デフォルトでキャッシュ有効になってます。便利!
https://github.com/auth0/node-jwks-rsa#cachingAmplifyでのJWTの取り方
import { Auth } from "aws-amplify"; ..... const session = await Auth.currentSession() const jwt = session.getAccessToken().getJwtToken() console.log(jwt);JWTって
JSON Web Token
の略だから、このメソッド名Token
被ってない!?
- 投稿日:2021-01-28T12:07:07+09:00
Lambda(Node.js)でタイムアウト指定できるfetch
request-promiseは非推奨ですよって話
request-promise
に依存しているrequest
パッケージが非推奨になったことから芋づる式に影響するみたいです。
https://github.com/request/request/issues/3142fetch使おう
以下のパッケージで
fetch
を使います
https://www.npmjs.com/package/node-fetchタイムアウトするためのざっくりとした流れ
AbortController
という非同期処理を中断させるインターフェースを使用します。タイムアウトさせたい非同期処理に
AbortController
インスタンスのsignal
を渡して、setTimeout()
で指定時間にabort()
を外から呼ぶことで中断させるといった感じです。(Goのcontext
と似てますね)サンプルうまくいかない問題
READMEの通りLambdaで動かしてみましたが、うまく
abort()
が動きませんでした。npmのサイトには以下のようにバージョンは
>= v8.0.0
とあったので、Lambda(nodejs12.x
)でもいけると思ったんですが、、NOTE: You may cancel streamed requests only on Node >= v8.0.0
const AbortController = require('abort-controller'); const controller = new AbortController(); const timeout = setTimeout(() => { controller.abort(); // ここが空になる }, 150); try { const response = await fetch('https://example.com', {signal: controller.signal}); const data = await response.json(); } catch (error) { // errorが空で返るのでAbortErrorを判定できない if (error instanceof fetch.AbortError) { console.log('request was aborted'); } } finally { clearTimeout(timeout); }最終的にLambdaで動いたタイムアウト指定できるfetch
以下のパッケージで生成した
AbortController
が動きました
https://www.npmjs.com/package/node-abort-controllerasync function postWithTimeout(url, body, ms) { const fetch = require("node-fetch"); const AbortController = require("node-abort-controller"); const controller = new AbortController(); const timeout = setTimeout(() => { controller.abort(); }, ms); const options = { method: "post", headers: { "Content-Type": "application/json", }, body: JSON.stringify(body), signal: controller.signal, // シグナルを渡しておく }; try { const response = await fetch(url, options); const body = await response.json(); return JSON.stringify(body); } catch (error) { if (error.type == "aborted") { console.log("the user aborted a request"); throw new Error(JSON.stringify({ type: "abort" })); } throw new Error(JSON.stringify({ type: "other" })); } finally { clearTimeout(timeout); } }まとめ
request-promise
からの切り替えをお考えのあなたに向けたfetchな記事になれば幸いです
- 投稿日:2021-01-28T11:31:27+09:00
nvm for windowsの設定手順の備忘録
はじめに
- Windows環境上で、Nodejsの開発環境を構築する機会があったので、その備忘録です。
- Node.jsの管理ツールとして、nvm for windowsを使用しました。
nvm for windowsのインストール
- nvm for windowsのインストーラをダウンロードし、インストールします(全てデフォルト設定でOK)。
Node.jsのインストール
- コマンドプロンプトを起動し、下記のコマンドを実行します。実行後、インストール可能なバージョンが一覧表示されます。
cmd# インストール可能なバージョンを表示 $ nvm list available
- バージョンを指定して、インストールします。
cmd# v10.16.2をインストール $ nvm install 10.16.2 $ node --vesion # v10.16.2
- 使用中のバージョンを確認します。
cmd$ nvm list # *10.16.2
- 別バージョンをインストールしてます。
cmd# v12.13.1をインストール $ nvm install 12.13.1
- インストール済みバージョンを確認します。
cmd$ nvm list # 12.13.1 # *10.16.2 # ⇒ v10.16.2を使用中
- バージョンを切り替えます。
cmd# バージョン指定 $ nvm use 12.13.1 $ nvm list # *12.13.1 # 10.16.2 $ node --version # v12.13.1
- 投稿日:2021-01-28T11:31:27+09:00
【Windows】Node.js 環境構築手順の備忘録
はじめに
- Windows環境上で、Nodejsの開発環境を構築する機会があったので、その備忘録です。
- Node.jsの管理ツールとして、nvm for windowsを使用しました。
nvm for windowsのインストール
- nvm for windowsのインストーラをダウンロードし、インストールします(全てデフォルト設定でOK)。
Node.jsのインストール
- コマンドプロンプトを起動し、下記のコマンドを実行します。実行後、インストール可能なバージョンが一覧表示されます。
cmd# インストール可能なバージョンを表示 $ nvm list available
- バージョンを指定して、インストールします。
cmd# v10.16.2をインストール $ nvm install 10.16.2 $ node --vesion # v10.16.2
- 使用中のバージョンを確認します。
cmd$ nvm list # *10.16.2
- 別バージョンをインストールしてます。
cmd# v12.13.1をインストール $ nvm install 12.13.1
- インストール済みバージョンを確認します。
cmd$ nvm list # 12.13.1 # *10.16.2 # ⇒ v10.16.2を使用中
- バージョンを切り替えます。
cmd# バージョン指定 $ nvm use 12.13.1 $ nvm list # *12.13.1 # 10.16.2 $ node --version # v12.13.1
- 投稿日:2021-01-28T11:31:27+09:00
【Windows】Nodeインストール手順の備忘録
はじめに
- Windows環境上で、Nodeの開発環境を構築する機会があったので、その備忘録です。
- Nodeの管理ツールとして、nvm for windowsを使用しました。
nvm for windowsのインストール
- nvm for windowsのインストーラをダウンロードし、インストールします(全てデフォルト設定でOK)。
Node.jsのインストール
- コマンドプロンプトを起動し、インストール可能なバージョンを確認します。
cmd# インストール可能なバージョンを表示 $ nvm list available
- バージョンを指定して、インストールします。
cmd# v10.16.2をインストール $ nvm install 10.16.2 $ node --vesion # v10.16.2
- 使用中のバージョンを確認します。
cmd$ nvm list # *10.16.2
- 別バージョンをインストールします。
cmd# v12.13.1をインストール $ nvm install 12.13.1
- インストール済みバージョンを確認します。
cmd$ nvm list # 12.13.1 # *10.16.2 # ⇒ v10.16.2を使用中
- バージョンを切り替えます。
cmd# バージョン指定 $ nvm use 12.13.1 $ nvm list # *12.13.1 # 10.16.2 $ node --version # v12.13.1
- 投稿日:2021-01-28T09:12:06+09:00
node + expressでJWT (2021年1月)
随分昔に同じ趣旨の記事を書いたのですが、再度書き直してみます(あまり変わってない)。
準備
作業場作成
ひとまず作業場所を確保し、必要なモジュールをインストール。body-parserはもういらない。
mkdir jwt-test cd jwt-test npm init -y npm install express jsonwebtoken実装前に検証
実装に入る前にJWTの生成と検証のコア部分をワンライナーで検証。
生成
なにやらややこしそうに思うが、[ヘッダ].[ペイロード].[署名]をそれぞれbase64でエンコードしているだけ。
署名部はsecret(ここではmy_secretという文字列)を利用してHS256で署名している。node -e "console.log(require('jsonwebtoken').sign({username:'hoge'},'my_secret'))" eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImhvZ2UiLCJpYXQiOjE2MTE3OTA3Nzl9.MbcALpRUEu9KxGZ5S1qLoieb41_dr-i2o__QVnlVTowサイン時に、 sign({username:'hoge'},'my_secret',, { expiresIn: '1h' })とすることで有効期限を設定可能。
検証
node -e "console.log(require('jsonwebtoken').verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImhvZ2UiLCJpYXQiOjE2MTE3OTA3Nzl9.MbcALpRUEu9KxGZ5S1qLoieb41_dr-i2o__QVnlVTow','my_secret'))" { username: 'hoge', iat: 1611790779 }メモ
大規模?な利用シーンでは署名と検証に公開鍵技術を利用する場合もあると思うが、jsonwebtokenは対応しているよう。詳しくは本家サイトを見る。
実装
では、簡単なJWTを利用したAPI認証機能を実装する。主な機能は以下の感じ。
- /loginにusername, passwordをPOSTで送信し、認証OKならtoken(JWT)を発行。
- /protectedへのアクセスにはAuthorizationヘッダにBearer+tokenを付与し認証OKならコンテンツを戻す。
- 認証機構はverifyToken()という外部関数とし実装し、app.get('/protecte')のミドルウエアとして適用する。
では実装します。
index.jsvar express = require('express'); var app = express(); var jwt = require('jsonwebtoken'); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.listen(3000, function () { console.log("App start on port 3000"); }) //認証無しAPI app.get('/', function (req, res) { res.json({ status: "OK" }); }) //認証+Tokenの発行 app.post('/login', function (req, res) { //ID,PW取得 var username = req.body.username; var password = req.body.password; //認証 //実際はDB等と連携 if (username === "hoge" && password === "password") { //token生成(フォマットは適当だが、有効期限を設定) const token = jwt.sign({ username: username }, 'my_secret', { expiresIn: '1h' }); res.json({ token: token }); } else { res.json({ error: "auth error" }); } }) //認証有りAPI app.get('/protected', verifyToken, function (req, res) { res.send("Protected Contents"); }) function verifyToken(req, res, next) { const authHeader = req.headers["authorization"]; //HeaderにAuthorizationが定義されているか if (authHeader !== undefined) { //Bearerが正しく定義されているか if (authHeader.split(" ")[0] === "Bearer") { try { const token = jwt.verify(authHeader.split(" ")[1], 'my_secret'); //tokenの内容に問題はないか? //ここでは、usernameのマッチと有効期限をチェックしているが必要に応じて発行元、その他の確認を追加 //有効期限はverify()がやってくれるみたいだがいちおう・・・ if (token.username === "hoge" && Date.now() < token.exp * 1000) { console.log(token); //問題がないので次へ next(); } else { res.json({ error: "auth error" }) } } catch (e) { //tokenエラー console.log(e.message); res.json({ error: e.message }) } } else { res.json({ error: "header format error" }); } } else { res.json({ error: "header error" }); } }実装できたら実行。
node index.js動作確認
curlを利用して動作確認をしてみます。
tokenの取得
まずは認証してtokenを取得します。
curl -s -X POST -H 'Content-Type: application/json' -d '{"username":"hoge","password":"password"}' http://localhost:3000/login {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImhvZ2UiLCJpYXQiOjE2MTE3OTEyMDksImV4cCI6MTYxMTc5NDgwOX0.Gy_3r3T-AQG8iL28LE1xzVNMEJDKTtgTyMRiaSNpQiM"}curl -X POST -d "username=hoge&password=password" http://localhost:3000/login でも可。
tokenの利用
取得したtokenを利用して/protectedにアクセスしてみます。
curl -X GET http://localhost:3000/protected -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImhvZ2UiLCJpYXQiOjE2MTE3OTEyMDksImV4cCI6MTYxMTc5NDgwOX0.Gy_3r3T-AQG8iL28LE1xzVNMEJDKTtgTyMRiaSNpQiM" Protected Contentsうまく表示されました。
その他
リフレッシュはどうする?
調べ中。とりあえず下記を見る。まあ、実運用ではexpiredであれば再ログインさせ、そのときに再取得しればいいとは思う。
https://solidgeargroup.com/refresh-token-autenticacion-jwt-implementacion-nodejs/
- 投稿日:2021-01-28T01:12:55+09:00
anyenvとnodenvを使ったNode.jsの環境構築
本記事では、
anyenv
とnodenv
を使ってMacにNode環境を構築する。nodenvとは
プロジェクト(ディレクトリ)ごとに、Node.jsのバージョンを管理することができるバージョン管理ツールのこと。
なぜnodenvを使用するのか?
仮に、
nodenv
を使用せずに、あるプロジェクトAのためにNodeのv7系を固定でインストールすると、別のプロジェクトBにおいてv8系を使用する必要があった際にはNodeのアップデートが必要となってしまう。
nodenv
を使用したバージョン管理をしていると、上記のようなプロジェクトごとに異なるバージョンを使用する際の手間をなくすことができる。
今回は
noodenv
を包括するanyenv
を使ってNodeのバージョン管理を行える環境を構築していく。nodenv
の他にもRubyやPythonのバージョン管理を行う〇〇envも存在し、anyenv
はこれらのバージョン管理ツールを包括的に管理するツールである。anyenvのインストール
以下のコマンドを実行し
anyenv
をインストールする% brew install anyenv
nodenvをインストールする前に
anyenvの初期化
が必要なため初期化を行う。% anyenv install --init ... Do you want to checkout ? [y/N]: y <- yと答える remote: Total 62 (delta 1), reused 1 (delta 0), pack-reused 57 Unpacking objects: 100% (62/62), done. Completed!
anyenv
のインストールに成功したので、次にnodenv
のインストールを行う。nodenvのインストール
以下のコマンドを実行し
nodenv
をインストールする% anyenv install nodenv ... Resolving deltas: 100% (76/76), done. ~ Install nodenv succeeded! Please reload your profile (exec $SHELL -l) or open a new session.プロファイルの更新のため、以下のコマンドも続けて実行する。
% exec $SHELL -lインストール可能なバージョンを以下コマンドの実行結果から確認することができればインストールに成功。
% nodenv install -l 0.1.14 0.1.15 ... ...
※
nodenv
コマンドの実行に失敗した時の対処方.zshrc ファイルが未作成のため、
nodenv
のパスが設定されていなく失敗する場合は、以下の対処を行うことを勧める
vi ~/.zshrc
を実行eval "$(anyenv init -)"
を書き込み保存する- 再度、
anyenv install nodenv
を実行する
Node.jsのインストール
以下のコマンドを実行しNode.jsをインストールする。今回は
12.20.0
をインストールする。% nodenv install 12.20.0
インストール済みのバージョンは以下のコマンドから確認できる。
12.20.0
が表示されれば無事にNode.js
のインストールに成功している。% nodenv whence npm 12.20.0グローバルのNode.jsのバージョンを指定
以下のコマンドで、インストール済の
12.20.0
をグローバルなバージョンとして指定する。% nodenv global 12.20.0動作確認
node
コマンドを実行し、12.20.0
が表示されれば無事にNode環境の構築ができている。% node -v v12.20.0
ディレクト毎にバージョンを切り替えたい場合
今回は
12.20.0
のNodeバージョンを使用できるようにしたが、特定のディレクトリでは8.0.0
を使用したくなったときの手順を最後に紹介する。
- Node.js
8.0.0
をインストール% nodenv install 8.0.0
8.0.0
を使用したディレクトに移動する。(今回はnode-8
)% cd node-8
- localオプションで
8.0.0
を指定% nodenv local 8.0.0
- 対象ディレクトリ配下で
node -v
を実行し8.0.0
が表示されれば成功% node -v 8.0.0
nodenv local 8.0.0
実行時には、指定したバージョンが記載された.node-version
ファイルが生成される。このファイルに指定された値を変更することで使用するバージョンも変更することができる。参考記事