20200628のNode.jsに関する記事は17件です。

【Node.js入門】ファイル&jsonファイルI/Oをやってみた♬

常識だからだと思うけど、Node.js始めたのでファイル入出力やろうとすると、割と動かないし、記事がもう一息なので、自分で整理しておこうと思う。
今回は、テキストファイル書き出し/読込とJsonファイルの書き出し/読込をやろうと思う。
参考は以下のとおり
【参考】
Node.jsを使ったやっつけのファイル操作
[Node.js]ファイルの作成、書き込み、追記をする
JSのObjectをforEachで処理する方法

やったこと

・テキストファイル書き出し/読込
・Jsonファイル書き出し/読込

・テキストファイル書き出し/読込

これは、参考①で取り上げているので、まんま動くかなと思ったが、少し違うような気もしたので、参考②を見つつ、実際動かして以下のコードで動くことを確認した。

コード解説

まず、参考①と②を比較するとvarなのかconstなのか。。以下の参考④から、スコープが以下のとおりだということです。
なので、今回はconstで足りているようです。
【参考】
var/let/constの使い分けのメモ

  • var;
    • 関数内のどこでもvarの宣言を書ける
    • 関数のどこで宣言しても、先頭で定義したものとしてみなされる
    • 関数スコープである
    • 使いたい変数は先頭部分で全て定義するのが定石
  • let;
    • ブロックスコープ
    • スコープを狭く出来るため影響範囲を狭められる
  • const;
    • 再代入不可能な変数を作る (つまり定数にできる)
    • letと同じブロックスコープ

したがって、流動性に関しては、var>let>constの順に融通性があるようです。因みに以下の関数内のletはconstにするとisExistを変更している部分でエラーが出ます。

const fs = require('fs');

Node.jsの予約語を注意しつつ、以下のような関数名(参考⑥から関数の命名規則;関数名は先頭小文字のキャメルケース。例:sendMessage)にしました。
【参考】
予約語
JavaScriptの命名規則を現役エンジニアが解説【初心者向け】
以下がファイルの存在確認の関数です。

function checkFile(filePath) {
  let isExist = false;
  try {
    fs.statSync(filePath);
    isExist = true;
  } catch(err) {
    isExist = false;
  }
  return isExist;
}

以下は、ファイルにstreamを書き込む関数です。streamは文字列です。

function writeFile(filePath, stream) {
  let result = false;
  try {
    fs.writeFileSync(filePath, stream);
    return true;
  } catch(err) {
    return false;
  }
}

以下がファイルの読み込み関数です。読み込まれた文字列がcontentに格納されリターンされます。

function readFile(filePath) {
  let content = new String();
  if(checkFile(filePath)) {;
    content = fs.readFileSync(filePath, 'utf8');
  }
  return content;
};

以下がファイルを削除する関数です。

function delFile(filePath){
  let result = false;
  try {
    fs.unlinkSync(filePath);
    return true;
  } catch(err) {
    return false;
  }
}

以下は既存ファイルにアペンドする関数です。

function addFile(filePath, stream) {
  let result = false;
  try {
    fs.appendFileSync(filePath, stream);
    return true;
  } catch(err) {
    return false;
  }
}

ということで、以下で一連の関数を実行しています。

const check_result = checkFile("file_");
console.log("check_result", check_result)
const write_result = writeFile("file_", "aaa")
console.log("write_result", write_result)
const add_result = addFile("file_", "\n"+"bbb")
console.log("add_result", add_result)
const read_result = readFile("file_")
console.log("read_result",typeof(read_result))
console.log(read_result)

結果は以下のとおり、あんまりおもしろくはないけど、正しく実行できています。
ちなみに、read_resultはstr型です。

check_result true
write_result true
add_result true
read_result string
aaa
bbb

・Jsonファイル書き出し/読込

次は、利用度の高いと思われるJsonファイルの書き出し/読込をやります。参考③のまんまで、以下のコードで実行できます。
実は、まず以下の参考⑥をやりましたが、jsonObject.list.forEach((obj)が動きませんでした。ということで参考③にたどり着きました。
【参考】
Node.jsでJSONを読み込んで加工して書き出す
早速、動くコードを見てみます。
まず、参考⑥を動かします。以下は無事に読み込みました。

var obj = JSON.parse(fs.readFileSync('package.json', 'utf8'));
console.log("jsonObject",obj)

結果は、以下のとおりで、Jsonファイルです。

package.json
jsonObject {
  name: 'test-linebot',
  version: '1.0.0',
  description: '',
  main: 'index.js',
  scripts: { test: 'echo "Error: no test specified" && exit 1' },
  keywords: [],
  author: '',
  license: 'ISC',
  dependencies: {
    '@line/bot-sdk': '^7.0.0',
    express: '^4.17.1',
    'node-tfidf': '0.0.2',
    'tf-idf.js': '^0.3.2'
  }
}

そして、参考③のコードも以下のように動きます。

// こういうオブジェクトがあったとしてね
var obj = { tanuki:'pon-poko', kitsune:'kon-kon', neko:'nyan-nyan' };

// こうすればOK
Object.keys(obj).forEach(function (key){
  console.log(key + "" + obj[key] + "と鳴いた!");
});

出力もコード(keyとobj[key]の内容)も見やすいです。

tanukiはpon-pokoと鳴いた!
kitsuneはkon-konと鳴いた!
nekoはnyan-nyanと鳴いた!

そして、以下でいよいよjsonファイルに書き出します。

const result = {};
Object.keys(obj).forEach(function(key){
    result[key] = obj[key];
    //console.log(key,obj[key])
});
fs.writeFileSync('output.json', JSON.stringify(result));

output.jsonファイルの中身は以下のとおりです。
{"tanuki":"pon-poko","kitsune":"kon-kon","neko":"nyan-nyan"}
次に、Jsonファイルの読み書きの関数を以下のように定義しました。
まず、読み込み関数は以下のとおりです。

function readJF(filePath) {
  let content = {};
  if(checkFile(filePath)) {;
    content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
  }
  return content;
};

次に、書込み関数は以下のとおりです。
Jsonファイルの追記のコードは、場合によりそうなので、適切なものが思いつきませんでした。一応、参考⑦にいくつかの例があります。
【参考】
How do I add to an existing json file in node.js

function writeJF(filePath,obj) {
    let result_ = false;
    const result = {};
    Object.keys(obj).forEach(function(key){
        result[key] = obj[key];
        //console.log(key,obj[key])
    });
    fs.writeFileSync(filePath, JSON.stringify(result));
    result_ = true;
    return result_;
};

最後に以下のとおり、実行してみました。

const readJ_result = readJF("package.json")
console.log("readJ_result",readJ_result)
console.log("readJ_result.name")
console.log(readJ_result['name'])
var writeJ_result = writeJF("test_.json",readJ_result)

結果は以下のとおりです。

readJ_result {
  name: 'test-linebot',
  version: '1.0.0',
  description: '',
  main: 'index.js',
  scripts: { test: 'echo "Error: no test specified" && exit 1' },
  keywords: [],
  author: '',
  license: 'ISC',
  dependencies: {
    '@line/bot-sdk': '^7.0.0',
    express: '^4.17.1',
    'node-tfidf': '0.0.2',
    'tf-idf.js': '^0.3.2'
  }
}
readJ_result.name
test-linebot

そして、test_.jsonの中身は以下のとおりで、以下に示すようにpackage.jsonと同一内容です。しかし、見た目が構造化されていないことが分かります。

test_.json
{"name":"test-linebot","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"keywords":[],"author":"","license":"ISC","dependencies":{"@line/bot-sdk":"^7.0.0","express":"^4.17.1","node-tfidf":"0.0.2","tf-idf.js":"^0.3.2"}}
package.json
{
  "name": "test-linebot",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@line/bot-sdk": "^7.0.0",
    "express": "^4.17.1",
    "node-tfidf": "0.0.2",
    "tf-idf.js": "^0.3.2"
  }
}

そこで、参考のとおり、fs.writeFileSync(filePath, JSON.stringify(result, null, '\t'));と改行コードを入れると以下のように成形されました。
※indentが少し広いですが、...
【参考】
JSON.stringify()

{
    "name": "test-linebot",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
        "@line/bot-sdk": "^7.0.0",
        "express": "^4.17.1",
        "node-tfidf": "0.0.2",
        "tf-idf.js": "^0.3.2"
    }
}

というわけで、fs.writeFileSync(filePath, JSON.stringify(result, null, ' '));

{
 "name": "test-linebot",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
 },
 "keywords": [],
 "author": "",
 "license": "ISC",
 "dependencies": {
  "@line/bot-sdk": "^7.0.0",
  "express": "^4.17.1",
  "node-tfidf": "0.0.2",
  "tf-idf.js": "^0.3.2"
 }
}

fs.writeFileSync(filePath, JSON.stringify(result, null, ' '));
やっと答えにたどり着きました。
※ここは趣味の世界ですね

{
  "name": "test-linebot",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@line/bot-sdk": "^7.0.0",
    "express": "^4.17.1",
    "node-tfidf": "0.0.2",
    "tf-idf.js": "^0.3.2"
  }
}

まとめ

・ファイルとjsonファイルの書込み/読込を整理してみた

・jsonファイルの追記はやれそうだが、まだ適当な関数に昇華できていない
・ファイルを利用してbotを充実したいと思う

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsのコードを定期実行する(crontabを利用)

これまでのおさらい

今回のお話

  • node.jsをcrontabで定期実行する。

前回書いたコードは、「BME280センサーの値を読み取り、Google スプレッドシートに書き込む」というものでした。
これは一回だけ実行する作りになっています。
このままでは、毎回手動で実行する必要があります。

// デバイスに接続
const Obniz = require("obniz"); // デバイスに接続
var obniz = new Obniz("****-****"); //Obnizの番号を指定
var webclient = require("request");


obniz.onconnect = async function () {
  const ifttt_event = "Record"; //イベント名
  const ifttt_secret_key = "あなたのキーを書く"; //キー
  const IFTTT_URL_GoogleSheets = 'https://maker.ifttt.com/trigger/' + ifttt_event + '/with/key/' + ifttt_secret_key;

  const bme280 = obniz.wired("BME280", {vio:0, vcore:1, gnd:2, csb:3, sdi: 4, sck: 5, sdo:6 });
  await bme280.applyCalibration();
  await bme280.setIIRStrength(1);

  val = await bme280.getAllWait();


  //obniz画面表示
  obniz.display.clear();
  obniz.display.print("temperature:" + val.temperature.toFixed(1)) //気温
  obniz.display.print("humidity:" + val.humidity.toFixed(1)) //湿度
  obniz.display.print("pressure:" + val.pressure.toFixed(1)) //気圧



  //送信データ作成
  const p1 = val.temperature.toFixed(1);
  const p2 = val.humidity.toFixed(1);
  const p3 = val.pressure.toFixed(1);

  //IFTTTリクエスト
  webclient.post({
    url: IFTTT_URL_GoogleSheets,
    headers: {
    "content-type": "application/json"
    },
    body: JSON.stringify({'value1': p1, 'value2':p2, 'value3':p3})
    }, function (error, response, body){
    console.log(body);
  });

  obniz.close();//Obniz切断
}

Node.jsのコードを定期実行する方法

今回は、crontabで試してみます。

crontabの設定

crontabでは、コマンドのフルパスが必要です。

whichコマンドで、nodeコマンドのフルパスを調べます。
/home/pi/.nodebrew/current/bin/node の部分を使います。
~/myapp/BME280.js の部分が実行するファイルです。

pi@raspberrypi:~/myapp $ which node
/home/pi/.nodebrew/current/bin/node

crontabは、-eオプションが編集です。

pi@raspberrypi:~/myapp $ crontab -e

先程調べたnodeのフルパスを指定し、実行するファイルを指定します。
以下の設定を登録しました。
*/10 の部分が、10分周期で実行の意味となります。
参考:crontabの書き方

*/10 * * * * /home/pi/.nodebrew/current/bin/node ~/myapp/BME280.js

-lオプションがリスト表示です。

pi@raspberrypi:~/myapp $ crontab -l

(略)

*/10 * * * * /home/pi/.nodebrew/current/bin/node ~/myapp/BME280.js

実行結果

Google スプレッドシートのデータを見てみます。

crontabで実行されたコードによって、10分周期に気温、湿度、気圧が記録されています。

10minits.PNG

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「Cannot extend an interface 'NodeJS.EventEmitter'.…」の解決方法メモ(vue-cli4+TypeScript+Electron)

はじめに

vue-cli 4とElectronでデスクトップアプリケーションを開発しようとした際に、@types/nodeのEventEmitterに関するエラーが発生した。無事解決できたのでメモとしてまとめる。

環境・バージョン

Vue:2.6.11
Vue CLI:4.4.6
@types/node:(14.0.14)→12.6.9(本記事の解決策)
Electron:9.0.0

発生した事象

"vue-cli3 + TypeScript + Electronでアプリを開発する"を参考に(ほぼコマンドや設定そのままで設定)プロジェクトを作成すると、最後のアプリケーションビルドの段階で以下のようなエラーが発生した。

$ npm run electron:build
~~~省略~~~
ERROR  Failed to compile with 19 errors
~~~省略~~~
ERROR in C:(作業ディレクトリ)/node_modules/electron/electron.d.ts(1659,31):
1659:31 Cannot extend an interface 'NodeJS.EventEmitter'. Did you mean 'implements'?
    1657 |   }
    1658 | 
  > 1659 |   class BrowserWindow extends NodeJS.EventEmitter {
         |                               ^
    1660 | 
    1661 |     // Docs: http://electronjs.org/docs/api/browser-window
    1662 | 
~~~省略~~~

どうやらinterfaceは継承できないと怒られているらしい。

調査・解決

ググってみると同じ内容のエラーに関するissueがGithubに立てられていた。
electron.d.ts does not work with @types/node v13.1.0 · Issue #21612 · electron/electron · GitHub
どうやら@types/nodeの12系→13系のバージョンアップの際に、EventEmitterがclassからinterfaceに変更されたことが原因らしい。

ということで、12系の@types/nodeをインストールする。

$ npm install -S @types/node@12.6.9

そして、もう一度ビルドを試す。

$ npm run electron:build
~~~省略~~~
DONE  Build complete!

できた!ビルドしたアプリもちゃんと開ける。
image.png

備考

現時点でpackage-lock.jsonを見てみると、Electronの@types/nodeの要求バージョンは12.0.12となっているので、そちらのバージョンを入れたほうが安心かもしれない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人メモ】Express環境ハンズオン

完全に個人用メモです。

  1. Node.jsとnpmがinstall済みであることを確認する
    node -v, npm -vコマンド実行

  2. expressのインストール
    作業ディレクトリでnpm initを実行してpackage.json作成
    npm install express express-generator --save

  3. expressアプリケーションの雛形作成
    ./node_modules/.bin/express --view=pug sample

  4. アプリ立ち上げ
    cd sample
    npm install
    npm start
    ブラウザからhttp://localhost:3000/につなぐと「Welcome to Express」が表示される。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ルートディレクトリ(/)とホームディレクトリ(~)のちゃんとした理解

ものすごい細かいことだけど、パスの指定方法での~(チルダ)と/(スラッシュ)の理解が曖昧で気持ち悪い思いをしたのでメモ。

/: ルートディレクトリ
~:今のユーザーのホームディレクトリ
~taro: taroというユーザーのホームディレクトリ

スラッシュの意味合い

ルートディレクトリの/と、各ファイルやディレクトリの前につく/は意味合いが違っている模様。

  • 前者:ルートディレクトリそのもの
  • 後者:ディレクトリを区切るもの

なので、一見ルートディレクトリのせいで「ディレクトリとは末尾にスラッシュが付いているもの」という勘違いを(少なくも筆者は)しちゃうが、hogehoge/がディレクトリなのではなくhogehogeがディレクトリなのだ。ホームディレクトリを~/だと思ってしまっている人は多いのではないか?

~ユーザー名

また~taroでtaroさんのホームディレクトリを指定できるのは初耳で、結構ググったけどこれについて深く語っている記事は見当たらなかった。こういう指定の仕方があるんですね。

スクリーンショット 2020-06-28 15.57.03.png

teratailで教えてもらった?
https://teratail.com/questions/273447

Node.jsのSass特有の書き方

今回がそもそも、RailsアプリでBootstrapを導入しようとして疑問にわいたことだったのだが、Node.jsのSass特有の書き方として、以下のような相対パス?の書き方が使えるらしい。

@import '~bootstrap/scss/bootstrap';

上記はmyapp/node_modules/bootstrap/dist/css/bootstrap.cssを指定できる。モジュールをユーザーに見立てているのかな?

まとめ

こういうのって「そういうもの」と暗記する人が多いと思うんだけど、数学の公式と一緒で「なぜそうなるのか?」という部分を理解したほうが知識を応用できると思うんです。

今回のものを踏まえると、WebのURLでhttps://example.jphttps://example.jp/のどっちを正規URLとすべきかという問題は、前者のほうが本来は正しいのではないかと考えちゃう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm install時のエラー解決 No Xcode or CLT version detected!

環境

macOS Catalina 10.15.4
npm 6.14.5

以下のエラーが出た時の解決

No receipt for 'com.apple.pkg.CLTools_Executables' found at '/'.

No receipt for 'com.apple.pkg.DeveloperToolsCLILeo' found at '/'.

No receipt for 'com.apple.pkg.DeveloperToolsCLI' found at '/'.

gyp: No Xcode or CLT version detected!
gyp ERR! configure error
gyp ERR! stack Error: `gyp` failed with exit code: 1
gyp ERR! stack     at ChildProcess.onCpExit (/Users/izumi/.anyenv/envs/nodenv/versions/14.3.0/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:351:16)
gyp ERR! stack     at ChildProcess.emit (events.js:315:20)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:276:12)
gyp ERR! System Darwin 19.4.0
(省略)
gyp ERR! node -v v14.3.0
gyp ERR! node-gyp -v v5.1.0
gyp ERR! not ok

原因

Xcode Command Line Toolsの割当がおかしかった

$ xcode-select -p
/Library/Developer/CommandLineTools

解決方法

Xcodeがインストール済かどうかで対処方法が変わります。

Xcodeインストール済の場合

xcode-select -sでXcode内のCommandLineToolsにセットし直したら解決

$ sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
$ xcode-select -p
/Applications/Xcode.app/Contents/Developer

Xcode未インストールの場合

多分以下で解決するはず
(この方法実際に試せていないので解決しなかったらごめんなさい)

$ xcode-select --install
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScript の event を良い感じの type safety に実装する

TypeScript における代表的な event 実装方法

TypeScript は言語仕様としては event 機能を持っていません。
なので class に event を実装する場合、自前で実装するか EventEmitter のような補助クラスを使うことになりますが、TypeScript ですのでやはり type safety に実装したいところです。
type safety に実装する代表的な方法は次の通りです。

  • EventEmitter + 型定義 (event 一つ一つに特化した on(), once(), off(), removeAllListeners() あたりのメソッドを定義していく)
  • StrictEventEmitter を導入する
  • TypedEvent を用意する

それぞれの特徴は次の通りです。

方法 共通クラス導入方法 event 表現方法 event 実装コスト 表現力
EventEmitter + 型定義 不要1 引数
StrictEventEmitter パッケージ追加 引数
TypedEvent 自前実装 プロパティ 2

この中では StrictEventEmitter が断然お勧めです。今後のデファクトスタンダードになるのかなとも思います。

StrictEventEmitter に対して気になる点

StrictEventEmitter に対して、2点ほど気になる点があります。

  • EventEmitter の継承が必要
  • emit が public アクセス可能

前者はオブジェクト指向設計の観点からあまり適切ではないですね。設計が濁ります。概念として抽象・具象の関係にあるものが継承されるべきです。

後者は前者よりも切実です。event の発火をクラス外部からも行えてしまいます。prefix としてアンダースコアでも付いているならまだしも、他のメソッドと全く同じようにぶらさげているのでは「発火しても構わないよ」と言っているようなものです。全然 safety ではありませんね。
例えば event の発生条件を勘違いしたチームメンバーが「なぜかこのタイミングでは event が通知されないので手動で発生させる」なんてコメントと共に外部から emit() を叩いてしまうかもしれないわけです。

ちなみに後者は、他の2つの方法についても同じく生じる問題です。

EventEmitter + EventPort というアプローチ

ということで、私はこれらのいずれの方法でもなく、下記の EventPort というクラスを自前実装して EventEmitter と組み合わせて使用しています。

events.ts
import { EventEmitter } from "events";
export { EventEmitter };

/**
 * A port to deliver an event to listeners.
 */
export class EventPort<T extends (...args: any[]) => void> {
    /**
     * Initialize an instance of EventPort<T> class.
     * @param name The name of the event.
     * @param emitter An instance of EventEmitter class.
     */
    public constructor(name: string | symbol, emitter: EventEmitter) {
        this._name = name;
        this._emitter = emitter;
    }

    private readonly _name: string | symbol;
    private readonly _emitter: EventEmitter;

    /**
     * Gets the name of the event.
     */
    public get name() {
        return this._name;
    }

    /**
     * Adds a listener.
     * @param listener The listener to be added.
     */
    public on(listener: T) {
        this._emitter.on(this._name, listener);
    }

    /**
     * Adds a listener that will be called only once.
     * @param listener The listener to be added.
     */
    public once(listener: T) {
        this._emitter.once(this._name, listener);
    }

    /**
     * Removes a listener.
     * @param listener The listener to be removed.
     */
    public off(listener: T) {
        this._emitter.off(this._name, listener);
    }

    /**
     * Removes the all listeners.
     * @param listener
     */
    public removeAllListeners() {
        this._emitter.removeAllListeners(this._name);
    }
}


declare module "events" {
    interface EventEmitter {
        emit<T extends (...args: any[]) => void>(port: EventPort<T>, ...args: Parameters<T>): boolean;
        emit(name: string | symbol, ...args: any): boolean;
    }
}

(EventEmitter as any).prototype._emit = (EventEmitter as any).prototype.emit;
(EventEmitter as any).prototype.emit = function (event: any, ...args: any[]) {
    const name = event instanceof EventPort ? event.name : event;
    return (this as any)._emit(name, ...args);
};

下記は使用例です。

import { EventEmitter, EventPort } from "./events";

class Hoge {
    public constructor() {
        this._eventEmitter = new EventEmitter();
        this._fugaCalledEvent = new EventPort("fugaCalled", this._eventEmitter);
        this._piyoCalledEvent = new EventPort("piyoCalled", this._eventEmitter);
    }

    private readonly _eventEmitter: EventEmitter;
    private readonly _fugaCalledEvent: EventPort<(value: string) => void>;
    private readonly _piyoCalledEvent: EventPort<(a: number, b: number) => void>;

    public get fugaCalledEvent() {
        return this._fugaCalledEvent;
    }

    public get piyoCalledEvent() {
        return this._piyoCalledEvent;
    }

    public fuga(value: string) {
        this._eventEmitter.emit(this._fugaCalledEvent, value);
    }

    public piyo(a: number, b: number) {
        this._eventEmitter.emit(this._piyoCalledEvent, a, b);
    }
}


const hoge = new Hoge();
hoge.fugaCalledEvent.on(value => console.warn(`value: ${value}`));
hoge.piyoCalledEvent.on((a, b) => console.warn(`a: ${a}, b: ${b}`));

hoge.fuga("Hello.");
// value: Hello

hoge.piyo(1, 2);
// a: 1, b: 2

EventPort クラスは TypedEvent クラスのようにプロパティとして event を表現します。
TypedEvent クラスとの大きな違いは、

  • 外部に本当に公開したい機能だけを持つ
  • event の引数ではなく listener の型を指定する形にすることで表現力の弱さを解消
  • 処理は EventEmitter に委譲
  • event の発火は EventEmitter を通じて本体クラスが行う

の4点です。
また、本体クラスは EventEmitter の継承は行わず、単に集約の関係に留めます。これにより、誤った継承関係の排除だけでなく emit() の隠蔽も達成しています。

ちなみに emit() については、第一引数に EventPort を受け取れるようにし、可変長引数を Conditional Types でフィットさせるよう拡張しています。

ご覧の通り、listener 登録も event 発火も type safety です。
image.png
image.png

まとめ

以上を踏まえて表を更新してみました。

方法 共通クラス導入方法 event 表現方法 event 実装コスト 表現力 設計汚染 emit 隠蔽
EventEmitter + 型定義 不要1 引数 不十分
StrictEventEmitter パッケージ追加 引数 不十分
TypedEvent 自前実装 プロパティ 2 不十分
EventPort 自前実装 プロパティ 完全

右端2列が気にならない方は StrictEventEmitter、気になる方は EventPort が良いと思います。
EventPort は自前実装の形になりますが、非常に単純なつくりですので問題にならないと思います。

あとは event 表現方法ですかね。どっちが優れているというものではなく、好みの問題が大きいかなと思います。
ちなみに私は元々 C#er なので、プロパティ形式の方がどちらかというとしっくりきます。


  1. Node.js 上で動作させる、或いは WebPack 等でバンドルしてブラウザ上で動作させる場合。それ以外の場合はパッケージ追加が必要。 

  2. event の引数の表現力がやや低く、名前が固定、一つしか持たせられない、などの欠点がある。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NestでTypeORM環境を構築してサンプル実装してみる

背景

前回は純粋なTypeORM環境をCLIで構築してみましたが、TypeORMはNestというフレームワークのORMとして利用されているとのことを知ったので、今度はNestでTypeORM環境を構築してみようと思う。
https://qiita.com/yusuke-ka/items/195e6bba4f21a659b424

Nest環境の構築

前回同様、DBは以前インストールしたpostgresql(windows)を利用する。
https://qiita.com/yusuke-ka/items/448843020c0406363ba5#%E6%BA%96%E5%82%99

pgadmin4でデータベースインスタンスだけ作っておく。

image.png

データベース名("nest_typeorm"とした)を入力して作成。

ここからは、コード エディタ(VS Code)上での作業。

諸事情により、まずは作業用のnode環境を作る。

> mkdir work
> cd work

nestのインストールは通常は下記のコマンドで実施する。
(linux環境の場合は下記でやってしまってOK.)

yarn global add @nestjs/cli

本当は↑でインストールしたいところだけど、windowsだとそのままだと使えずパスを通してやる必要があり面倒なので、↓でインストール。

yarn add @nestjs/cli

続いて、nestで新しいプロジェクトを作る。

以下のようにすれば、今いるフォルダを直接プロジェクトのフォルダにできるけど、すでにpackage.jsonが存在していて怒られるので(globalインストールしていればpackage.jsonがないので実行できる)、今いるフォルダは作業用として、下にプロジェクトを作る。

> npx nest new .

↓今回はこっちで実行。

> npx nest new nest

npmかyarnかを聞かれるので、今回はyarnを選択。

yarnでnestコマンドが使えるように、作成されたnestプロジェクト以下のpackage.jsonにscriptを追加しておく。
(npxで実行するのであれば下記の修正は不要だけど、なんとなくyarnでやりたい。)

package.json
  "scripts": {
    "nest" : "nest",
    ...
  },

typeORM関連の依存をインストールしておく。

> yarn add @nestjs/typeorm typeorm pg

最後に設定ファイル(app.module.ts)を更新。

app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'nest_typeorm',
      entities: [],
      synchronize: true,
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

デフォルトで、AppControllerやAppServiceなどがimportされているが、これらは最初から入っているサンプルコードなので消してもよい。

今回は使わないので消しておいた。

ついでにsrc以下にある下記のファイルもいらないので削除。
app.controller.ts
app.controller.spec.ts
app.service.ts

これでNest環境のベースは整った。

Nestでサンプル実装してみる

最初にCLIのジェネレータでコードのひな型を作っておく。

> yarn nest g service TestObject
> yarn nest g controller TestObject
> yarn nest g module TestObject

test-objectフォルダが作成され、その下に各ひな形が配置される。

さらに以下のコマンドでモデルのひな形も作成。
(こちらは、明示的にフォルダを指定しないと、「test-object」フォルダに入らなかったので、「test-object/TestObject」のように指定。)

> yarn nest g class test-object/TestObject

test-objectフォルダ以下は下のようになる。

 |- src
   |- test-object
     |- test-object.ts
     |- test-object.controller.ts
     |- test-object.controller.spec.ts
     |- test-object.module.ts
     |- test-object.service.ts
     |- test-object.service.spec.ts
     |- test-object.spec.ts

~.spec.tsが一緒に生成されるがテスト用のコードみたいなので、今回は触れない(今度試してみようと思う)。

また、ジェネレートコマンドを実行すると、app.module.tsが自動的に更新されてしまう。
生成したcontrollerやserviceが自動でimportされるみたい。

今回はモジュール化するためのファイルも作っていて、直接app.module.tsにはimportしなくてよいはずなので、以下のように修正しておく。

app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TestObjectModule } from './test-object/test-object.module';
import { TestObject } from './test-object/test-object';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'nest_typeorm',
      entities: [TestObject],
      synchronize: true,
    }),
    TestObjectModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

controllerやserviceは直接ここには書かずに、moduleだけを指定する感じ。
ただ、TypeOrmModuleのentitiesには、モデルオブジェクトを直接書いておかないとコンパイル時にエラーになったので注意。

ここからは中身を実装していく。

とその前に、各ファイルの役割を確認しておく。
サンプルのコードとかを見る限り、なんとなくこんな(↓)感じのイメージかな。

test-object.ts : モデルを定義するファイル
test-object.service.ts : TypeORMの機能を直接使ってDBを操作するファイル
test-object.controller.ts : APIを定義するファイル。serviceの提供するメソッドを呼び出す。
test-object.module.ts : 定義したserviceとかcontrollerとかを一つのモジュールにして提供するファイル。

ということで、まずはモデルファイルを実装。

test-object.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class TestObject {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  attr1: string;
}

シンプルに属性はidとattr1だけ。

続いてサービスの実装。

test-object.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { TestObject } from './test-object';

@Injectable()
export class TestObjectService {
  constructor(
    @InjectRepository(TestObject)
    private readonly repository: Repository<TestObject>,
  ) {}

  async all(): Promise<TestObject[]> {
    return this.repository.find();
  }

  async one(id: number): Promise<TestObject> {
    return this.repository.findOne(id);
  }

  async create(data: Partial<TestObject>): Promise<TestObject> {
    return this.repository.save(data);
  }

  async update(id: number, data: Partial<TestObject>): Promise<void> {
    const origin = await this.repository.findOne(id);
    const updateData = Object.assign(origin, data); // 上書き
    this.repository.save(updateData);
  }

  async remove(id: number): Promise<void> {
    const obj = await this.repository.findOne(id);
    this.repository.remove(obj);
  }
}

こちらもシンプルなCRUDだけを実装してみました。
(read系は全件取得と単独指定で取得の2つ)

次は、このサービスを呼び出すコントローラの実装。

test-object.controller.ts
import {
  Controller,
  Get,
  Post,
  Param,
  Body,
  Delete,
  HttpCode,
  HttpStatus,
  Put,
} from '@nestjs/common';
import { TestObjectService } from './test-object.service';
import { CreateTestDataDTO, UpdateTestDataDTO } from './test-object.dto';
import { TestObject } from './test-object';

@Controller('test-object')
export class TestObjectController {
  constructor(private readonly service: TestObjectService) {}

  @Get()
  @HttpCode(HttpStatus.OK)
  all(): Promise<TestObject[]> {
    return this.service.all();
  }

  @Get(':id')
  @HttpCode(HttpStatus.OK)
  one(@Param('id') id: number): Promise<TestObject> {
    return this.service.one(id);
  }

  @Post('create')
  @HttpCode(HttpStatus.CREATED)
  async create(
    @Body() createTestDataDto: CreateTestDataDTO,
  ): Promise<TestObject> {
    return this.service.create(createTestDataDto);
  }

  @Put('update/:id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async update(
    @Param('id') id: number,
    @Body() updateTestDataDto: UpdateTestDataDTO,
  ): Promise<void> {
    this.service.update(id, updateTestDataDto);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async remove(@Param('id') id: number): Promise<void> {
    this.service.remove(id);
  }
}

こちらもCRUD操作のAPIを定義。HTTPリクエストをここで処理している感じですね。

更新系の操作は引数にData Transfer Object(DTO)を渡している、これは、src/test-objectの下にtest-object.dto.tsとして別途定義した。

test-object.dto.ts
export class CreateTestDataDTO {
  attr1: string;
}

export class UpdateTestDataDTO {
  attr1: string;
}

最後に、serviceやcontrollerをモジュール化するファイルの実装。

test-object.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TestObjectService } from './test-object.service';
import { TestObjectController } from './test-object.controller';
import { TestObject } from './test-object';

@Module({
  imports: [TypeOrmModule.forFeature([TestObject])],
  exports: [TypeOrmModule],
  providers: [TestObjectService],
  controllers: [TestObjectController],
})
export class TestObjectModule {}

このモジュールを大元のapp.module.tsでimportする感じになっている。

実行

起動して確認してみる。

> yarn start:dev

確認はいつもの「Advanced REST client」。
http://localhost:3000/test-objectに各種リクエストを送ってみる。

説明は省略(全部問題なく動きました。)。

作成

image.png

全件取得

image.png

更新

image.png

1件取得

image.png

削除

image.png

さいごに

今回はNestでTypeORM環境を構築してサンプル実装してみた。

前回、素のTypeORM環境をCLIで作成したときよりは時間がかかったけど、構造がなんとなく理解できたら、すんなりとコードを書くことができた。

そこそこの規模のコードを書くなら、こういったフレームワークを利用して書いた方が、コードを整理できて良さそうですね。テストのフレームワークも付いてるみたいだし。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NestでTypeORM環境を構築してサンプル実装(CRUD+トランザクション+ロック)してみる

背景

前回は純粋なTypeORM環境をCLIで構築してみましたが、TypeORMはNestというフレームワークのORMとして利用されているとのことを知ったので、今度はNestでTypeORM環境を構築してみようと思う。
https://qiita.com/yusuke-ka/items/195e6bba4f21a659b424

Nest環境の構築

前回同様、DBは以前インストールしたpostgresql(windows)を利用する。
https://qiita.com/yusuke-ka/items/448843020c0406363ba5#%E6%BA%96%E5%82%99

pgadmin4でデータベースインスタンスだけ作っておく。

image.png

データベース名("nest_typeorm"とした)を入力して作成。

ここからは、コード エディタ(VS Code)上での作業。

諸事情により、まずは作業用のnode環境を作る。

> mkdir work
> cd work

nestのインストールは通常は下記のコマンドで実施する。
(linux環境の場合は下記でやってしまってOK.)

yarn global add @nestjs/cli

本当は↑でインストールしたいところだけど、windowsだとそのままだと使えずパスを通してやる必要があり面倒なので、↓でインストール。

yarn add @nestjs/cli

続いて、nestで新しいプロジェクトを作る。

以下のようにすれば、今いるフォルダを直接プロジェクトのフォルダにできるけど、すでにpackage.jsonが存在していて怒られるので(globalインストールしていればpackage.jsonがないので実行できる)、今いるフォルダは作業用として、下にプロジェクトを作る。

> npx nest new .

↓今回はこっちで実行。

> npx nest new nest

npmかyarnかを聞かれるので、今回はyarnを選択。

yarnでnestコマンドが使えるように、作成されたnestプロジェクト以下のpackage.jsonにscriptを追加しておく。
(npxで実行するのであれば下記の修正は不要だけど、なんとなくyarnでやりたい。)

package.json
  "scripts": {
    "nest" : "nest",
    ...
  },

typeORM関連の依存をインストールしておく。

> yarn add @nestjs/typeorm typeorm pg

最後に設定ファイル(app.module.ts)を更新。

app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'nest_typeorm',
      entities: [],
      synchronize: true,
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

デフォルトで、AppControllerやAppServiceなどがimportされているが、これらは最初から入っているサンプルコードなので消してもよい。

今回は使わないので消しておいた。

ついでにsrc以下にある下記のファイルもいらないので削除。
app.controller.ts
app.controller.spec.ts
app.service.ts

これでNest環境のベースは整った。

Nestでサンプル実装してみる

最初にCLIのジェネレータでコードのひな型を作っておく。

> yarn nest g service TestObject
> yarn nest g controller TestObject
> yarn nest g module TestObject

test-objectフォルダが作成され、その下に各ひな形が配置される。

さらに以下のコマンドでモデルのひな形も作成。
(こちらは、明示的にフォルダを指定しないと、「test-object」フォルダに入らなかったので、「test-object/TestObject」のように指定。)

> yarn nest g class test-object/TestObject

test-objectフォルダ以下は下のようになる。

 |- src
   |- test-object
     |- test-object.ts
     |- test-object.controller.ts
     |- test-object.controller.spec.ts
     |- test-object.module.ts
     |- test-object.service.ts
     |- test-object.service.spec.ts
     |- test-object.spec.ts

~.spec.tsが一緒に生成されるがテスト用のコードみたいなので、今回は触れない(今度試してみようと思う)。

また、ジェネレートコマンドを実行すると、app.module.tsが自動的に更新されてしまう。
生成したcontrollerやserviceが自動でimportされるみたい。

今回はモジュール化するためのファイルも作っていて、直接app.module.tsにはimportしなくてよいはずなので、以下のように修正しておく。

app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TestObjectModule } from './test-object/test-object.module';
import { TestObject } from './test-object/test-object';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'nest_typeorm',
      entities: [TestObject],
      synchronize: true,
    }),
    TestObjectModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

controllerやserviceは直接ここには書かずに、moduleだけを指定する感じ。
ただ、TypeOrmModuleのentitiesには、モデルオブジェクトを直接書いておかないとコンパイル時にエラーになったので注意。

ここからは中身を実装していく。

とその前に、各ファイルの役割を確認しておく。
サンプルのコードとかを見る限り、なんとなくこんな(↓)感じのイメージかな。

test-object.ts : モデルを定義するファイル
test-object.service.ts : TypeORMの機能を直接使ってDBを操作するファイル
test-object.controller.ts : APIを定義するファイル。serviceの提供するメソッドを呼び出す。
test-object.module.ts : 定義したserviceとかcontrollerとかを一つのモジュールにして提供するファイル。

ということで、まずはモデルファイルを実装。

test-object.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class TestObject {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  attr1: string;
}

シンプルに属性はidとattr1だけ。

続いてサービスの実装。

test-object.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { TestObject } from './test-object';

@Injectable()
export class TestObjectService {
  constructor(
    @InjectRepository(TestObject)
    private readonly repository: Repository<TestObject>,
  ) {}

  async all(): Promise<TestObject[]> {
    return this.repository.find();
  }

  async one(id: number): Promise<TestObject> {
    return this.repository.findOne(id);
  }

  async create(data: Partial<TestObject>): Promise<TestObject> {
    return this.repository.save(data);
  }

  async update(id: number, data: Partial<TestObject>): Promise<void> {
    const origin = await this.repository.findOne(id);
    const updateData = Object.assign(origin, data); // 上書き
    this.repository.save(updateData);
  }

  async remove(id: number): Promise<void> {
    const obj = await this.repository.findOne(id);
    this.repository.remove(obj);
  }
}

こちらもシンプルなCRUDだけを実装してみました。
(read系は全件取得と単独指定で取得の2つ)

次は、このサービスを呼び出すコントローラの実装。

test-object.controller.ts
import {
  Controller,
  Get,
  Post,
  Param,
  Body,
  Delete,
  HttpCode,
  HttpStatus,
  Put,
} from '@nestjs/common';
import { TestObjectService } from './test-object.service';
import { CreateTestDataDTO, UpdateTestDataDTO } from './test-object.dto';
import { TestObject } from './test-object';

@Controller('test-object')
export class TestObjectController {
  constructor(private readonly service: TestObjectService) {}

  @Get()
  @HttpCode(HttpStatus.OK)
  all(): Promise<TestObject[]> {
    return this.service.all();
  }

  @Get(':id')
  @HttpCode(HttpStatus.OK)
  one(@Param('id') id: number): Promise<TestObject> {
    return this.service.one(id);
  }

  @Post('create')
  @HttpCode(HttpStatus.CREATED)
  async create(
    @Body() createTestDataDto: CreateTestDataDTO,
  ): Promise<TestObject> {
    return this.service.create(createTestDataDto);
  }

  @Put('update/:id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async update(
    @Param('id') id: number,
    @Body() updateTestDataDto: UpdateTestDataDTO,
  ): Promise<void> {
    this.service.update(id, updateTestDataDto);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async remove(@Param('id') id: number): Promise<void> {
    this.service.remove(id);
  }
}

こちらもCRUD操作のAPIを定義。HTTPリクエストをここで処理している感じですね。

更新系の操作は引数にData Transfer Object(DTO)を渡している、これは、src/test-objectの下にtest-object.dto.tsとして別途定義した。

test-object.dto.ts
export class CreateTestDataDTO {
  attr1: string;
}

export class UpdateTestDataDTO {
  attr1: string;
}

最後に、serviceやcontrollerをモジュール化するファイルの実装。

test-object.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TestObjectService } from './test-object.service';
import { TestObjectController } from './test-object.controller';
import { TestObject } from './test-object';

@Module({
  imports: [TypeOrmModule.forFeature([TestObject])],
  exports: [TypeOrmModule],
  providers: [TestObjectService],
  controllers: [TestObjectController],
})
export class TestObjectModule {}

このモジュールを大元のapp.module.tsでimportする感じになっている。

実行

起動して確認してみる。

> yarn start:dev

確認はいつもの「Advanced REST client」。
http://localhost:3000/test-objectに各種リクエストを送ってみる。

説明は省略(全部問題なく動きました。)。

作成

image.png

全件取得

image.png

更新

image.png

1件取得

image.png

削除

image.png

トランザクション管理

ついでにトランザクション管理の実装がどうなるかも試してみる。

test-object.service.tsで更新の処理をトランザクション管理するように変更。

test-object.service.ts
...
import { Repository, Connection } from 'typeorm';
...

@Injectable()
export class TestObjectService {
  constructor(
    ...
    @InjectConnection()
    private readonly connection: Connection,
  ) {}

  ...

  async update(id: number, data: Partial<TestObject>): Promise<void> {
    const queryRunner = this.connection.createQueryRunner();

    await queryRunner.connect();
    await queryRunner.startTransaction();
    try {
      const origin = await queryRunner.manager.findOne(TestObject, id);
      const updateData = Object.assign(origin, data); // 上書き
      await queryRunner.manager.save(updateData);
      await queryRunner.commitTransaction();
    } catch (err) {
      console.log(err);
      await queryRunner.rollbackTransaction();
    } finally {
      await queryRunner.release();
    }
  }

  ...
}

typeORMのConnectionをimportするようにして、コンストラクタで注入。

更新処理のメソッドの中で、connectionから取得したqueryRunnerで、接続、トランザクションの開始、コミット、リリースといった処理を記述する(try-finallyを使う)。

あとは、repository.findOne(...)とかrepository.save(...)で呼び出していた部分を、
queryRunner.manager.findOne(...)とかqueryRunner.manager.save(...)のような感じで呼び出すように変更するだけ。

注意点としては、repository.findOne(...)とqueryRunner.manager.findOne(...)で引数が違っていること。
repositoryはどのモデルか知っているのに対し、connectionは知らないので、第一引数でモデルを渡してやる必要がある。

トランザクション管理については、他にもやり方はあるみたいだけど、自分にはこのやり方が一番安心できる。

続いて、ロック(SELECT... FOR UPDATE)も試してみる。

上でトランザクション管理するように書き換えた更新の処理で、更新対象のオブジェクトを取得してくる部分をロックしながら取得するように変えてみる。

test-object.service.ts
  ...
  async update(id: number, data: Partial<TestObject>): Promise<void> {
    ...
    try {
      const origin = await queryRunner.manager
        .getRepository(TestObject)
        .createQueryBuilder('test-object')
        .useTransaction(true)
        .setLock('pessimistic_write')
        .where('test-object.id = :id', { id: id })
        .getOne();
      ...
    } catch (err) {
      ...
    } finally {
      ...
    }
  }
  ...
}

今のところ、QueryBuilderで実装する必要があるようなので若干面倒だけど、上記のようにすればロックできそう。

エラーにならないかだけ、動作確認しておく。

更新

image.png

取得して確認

image.png

ちゃんとロックできているかは確認していないが、エラーなくデータが更新されることは確認できた。

一応、SQLのログを出力してみると、"FOR UPDATE"が付いていた。

image.png

ちなみに、SQLのログを出力する設定は下記。

app.module.ts
...
@Module({
  imports: [
    TypeOrmModule.forRoot({
      ...
      logging: true,
    }),
  ],
  ...
})
...

さいごに

今回はNestでTypeORM環境を構築してサンプル実装してみた。

前回、素のTypeORM環境をCLIで作成したときよりは時間がかかったけど、構造がなんとなく理解できたら、すんなりとコードを書くことができた。

そこそこの規模のコードを書くなら、こういったフレームワークを利用して書いた方が、コードを整理できて良さそうですね。テストのフレームワークも付いてるみたいだし。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

令和元年? JavaScript International Date フォーマットで和暦を表示させるには (再)

この記事は 平成最後の日、2019年4月31日に別のアカウントからポストしたものをこちらに移行した記事です。今更感があるかもしれないですが ECMA 402 について知るのは悪くないと思います〜。


令和元年おめでとうございます ?

さて、今回は新年号にふさわしく International Date format で和暦を表示する方法について書いていきたいと思います。

JavaScript Intl.DateTimeFormat オブジェクトを使えば、言語とローケルに特化した日付と時刻のフォーマットが可能になります。

The ECMAScript Internationalization API(国際標準化 API) は、日付と時刻そして貨幣の表示のローカライズを JavaScript で可能にするために作られたもので、2010 年に初版が発行されて現在は第6版が最新です。この API は最新のブラウザではすでにサポート済です。(参照 Can I use)

Intl オブジェクトのプロパティの一つ、DateTimeFormat が言語に応じた日付と時刻のフォーマットを可能にします。

ということは日本のローカルタイムを令和で表現できるということなのです。ただし、ブラウザがアップデートされていればの話ですが。

結論を言っておきますと、現時点で最新の Chrome ではまだ令和が和暦に追加されていませんので、この先のコードを正式にアウトプットするには、Chrome Canary 版が必要です。(この記事を急いで書いたので、しかも私のローケルでは時差でまだ平成ということもあり他のブラウザを試していませんが?‍♀️ Firefox では今のところまだのようです。Mozilla の Emma さん情報ありがとうございました。)
(現在はサポート済)

? Using DateTimeFormat

ローケルと言語が設定されていない場合は DateTimeFormat はデフォルトの設定で表示されます。

ですので、米国サンフランシスコにいる私が自分のマシン上のブラウザでこのコードを動かすとこのように今日の日付を en-US で表示します。

new Intl.DateTimeFormat().format(Date.now())

(本当は私のタイムゾーンではまだ4月31日なのですが、日付が変わった程で?)

"5/1/2019"

?? Specifying Locales

ここでlocales 引数を使い、ローケル識別子(言語コードと、国、地域コード)を設定することによって日付と時刻をローカライズしてみましょう。

例えば、ロシアの場合。

new Intl.DateTimeFormat('ru-RU').format(Date.now())
// "02.04.2019" と表示される

そして日本の場合。

new Intl.DateTimeFormat('ja-JP').format(Date.now())
// "2019/5/1"

しかし、これではグレゴリオ暦のままです。完全に和暦で表示するにはどうしたらいいでしょうか。

ローケル識別子 ja-JP だけでは足りませんので -u-ca-japanese という拡張キーを追加し、 ja-JP-u-ca-japanese としましょう。

なんだか長いのですが、
-u 拡張キーの識別は
-ca カレンダー・タイプの
-japanese 日本のカレンダーを表示

という意味になります。詳しくは Unicode Technical Standard #35 で。

エラリー ジャンクリストフ さん、拡張キーについて詳しい情報のあるリンクを教えてくださって、ありがとうございました!)

new Intl.DateTimeFormat('ja-JP-u-ca-japanese').format(Date.now())
// 昨日までは、"31/4/30"
// 今日は "1/5/1"  

さて和暦っぽい年号ではありますが、元号が表示されていません。どうしたら良いでしょうか。

元号を表示するには options 引数の、この場合 eralong に設定しましょう。

new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {era:'long'}).format(Date.now())
// "令和1/5/1"

さてこれで、"令和1/5/1" と表示されたら、お使いのブラウザが無事に令和をサポートしています?

"平成31年5月1日" と表示された方はブラウザをアップデートをする(もしくは出るまで待つ)。待てども待てども表示されないブラウザはバグを提出しましょう。

(注、令和にはいってから Chrome 上でのこの和暦の表記が「令和n年n月n日」から「令和n/n/nn」のように変更になったようです。いつ変更になったのかは私はわかりませんがたぶん元年の途中からだと思います。)

せっかくですのでタイのローケルでも試してみましょう。

new Intl.DateTimeFormat('th-TH-u-nu-thai', {era:'long'}).format(Date.now())
// "๑ ๕ พุทธศักราช ๒๕๖๒"

なんか読めないけどイイ!?
タイはお釈迦様が入滅した翌年を仏滅紀元元年としており今年は 2562 年のようです。

というわけで、もっと詳しく知りたい方は MDN Web Docs をどうぞ。

ちなみに、Is it Reiwa (令和) yet? というしょぼいテストサイトも作ってみました。
https://reiwa-yet.glitch.me/.

それでは、今元号ともよろしくおねがいします。


元記事は、4月1日に元号が発表された直後に英語で書いたものです。今回、令和元年のはじまりを記念して日本語でも書きました。
https://girliemac.com/blog/2019/04/02/javascript-i18n-reiwa-era/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptのコンパイルからブラウザ反映までを自動でやりたい

目的

TypeScripはそのままでは実行できないので、コンパイルしてJavaScriptに変換しなければならない。
またローカルサーバで開発している場合、コンパイルされる毎にブラウザの更新もかけないといけない。
めんどい!!--->自動化したい!!!

環境

環境
windows10
node.js v12.13.0
npm v6.12.0
typescript v3.9.5

今回はnode.jsを使ってTypeScripがインストールされている状態を前提とする。
環境構築にはこちらも参考に。
TypeScriptのインストールから実行まで

初期フォルダ構成はこんな感じ。

TS_Project
  ├─ node_modules
  ├─ app.ts
  ├─ index.html
  ├─ package-lock.json
  └─ package.json

コンパイルの自動化

TSファイルに変更があった場合、自動でコンパイルされるようにする。

まずTypeScript(以下TS)プロジェクトを初期化する
package.jsonが存在するディレクトリで以下のコマンドを実行

コマンドプロンプト
npx tsc --init

tsconfig.jsonが追加されていることを確認。
これにはTSファイルがどのようにコンパイルされるかなどをコントロールする設定が記載されている。

TS_Project
  ├─ node_modules
  ├─ app.ts
  ├─ index.html
  ├─ package-lock.json
  ├─ package.json
  └─ tsconfig.json  <- 追加

後は簡単。

コマンドプロンプト
npx tsc --watch

を実行するだけ。これでTSプロジェクト内のTSファイルに変更が起きた場合、自動でコンパイルしてくれる監視下になる。
--watch-wでも可。

コンパイルまでは自動でできた。

ブラウザを更新の自動化

次にローカルサーバを立てている場合に、ファイルを更新すると自動的にブラウザを更新するようにしたい。
そこで「Lite-Server」をインストールする。

コマンドプロンプト
npm install --save-dev lite-server

package.jsonに追加されていることを確認する。

  "devDependencies": {
    "lite-server": "^2.5.4"
  }

「Lite-Server」をインストールしたら、package.jsonのプロパティにある"scripts"配下に以下を設定する。

 "scripts": {
    "start": "lite-server"  <- 追加
  }

設定後はコマンドプロンプトにnpm startと打ち込むことでサーバが立つようになる。
もちろん、ファイルの更新があったときは自動でブラウザを更新してくれるようになる。

実際の動き

npx tsc -wnpm startで常に監視状態にしておく。
htmlとtsを用意する。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./app.js" defer></script> <!-- 追加!! -->
    <title>初めてのTypeScript</title>
</head>
<body>

</body>
</html>
app.ts
console.log('Hello TypeScript')

app.tsファイルを編集すると自動でコンパイル->ブラウザ更新までをしてくれている。

img.gif

「Live Server」を使う手もある

今回はブラウザの自動更新に「Lite-Server」を使ったが、「Visual Studio Code」を使った開発をしているならLive Serverを使う方がお手軽かもしれない。VSCodeの標準拡張機能なので。
まぁそれはまた別のお話で。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RESTAPIが作れるようになるまで【Heroku,Node.js使用】

注意

記載途中です。内容に大幅な変更もあると思います。
(私自身完全に理解できていない状態での記事作成になるため。)
色々知識を得た結果、ejs(テンプレートエンジン)を使用すればWebアプリが作れることが判明したので、これはRESTAPIが作れるようになるまでに変更します。

はじめに

HTMLやJavascript、C#もしくはjavaは分かるけど、サーバーをどう構築すれば良いか分からないレベルの人なら参考になると思います。
(恐らくSES事業を経験してると、そのレベルにはなると思います。)

具体的には、docker、AWS、Heroku、node.js、Nginxの辺りが完全に理解できていないレベルの人です。

Gitが分からない人は、ソースツリーを使用して、コミットプッシュマージクローンプルリク辺りを理解するところから始める方が良いと思います。とりあえず、下記サイトを参考にどうぞ。

SourceTreeの使い方 GitHubとの連携方法

実施内容

Herokuに登録

まずはHerokuが無料でDB使えるということが分かったので、下記サイトを参考にしつつHerokuに会員登録しました。

【HEROKUとは】これを読めばOK!デプロイの仕方まで徹底解説

Node.jsインストール、Atomインストール

Herokuに登録後、Node.jsが必要そうだったので、下記サイトを参考にインストールしました。また、Node.jsの編集にAtomが必要なので、Atomインストールしてない人は、このタイミングで一緒にインストールしましょう。

Node.jsの勉強会でお手軽にWebアプリを作った話

RestAPIを知る

恐らくここが1番重要な内容だと思います。私はHTTPサーバーを作る理由が謎だったので、RestAPIの存在を知ったとき、体に電流が走りました。

Webアプリ初心者は、何を作れば良いか分からなかったらRestAPIを作れば良いと思います。RestAPIは下記サイトが参考になると思います。

RESTful APIとは何なのか

ちなみにRestAPIが何故重要なのかを説明すると、HTMLで画面作っていると、データを大量に持たせたい場面が出てくると思います。Javascriptの変数に値を直で持たせるのも1つのやり方だとは思いますが、データが増えるたびにJavascriptを書き換えるのは、HTTPサーバーに再度アップロードが必要な点で効率が悪いと感じます。そこでデータをJsonで返すRestAPIを作ってしまえば、アプリをアップロードしなおさなくてもデータを増やせると考えました。

HerokuでサンプルAPIを作成

下記サイトを参考にしました。

裸の窓使いがHerokuにNodeでRestAPIで「こんにちは」するまで

なお、こちらでは、サンプルを使用してのAPIになるので、自分専用の新たなAPIが欲しい場合は、やり方を調べる必要があります。これからの作業の意味を理解するためにも、まずは、サンプルAPIの理解をお願いします。

Gitリポジトリの作成

こちらに関しては、上記サンプル(裸の窓使いがHerokuにNodeでRestAPIで「こんにちは」するまで)と比較した結果、見つけた差異です。下記を実施しました。

Create a new Git repository
Initialize a git repository in a new or existing directory

$ cd my-project/
$ git init
$ heroku git:remote -a 新しく作成したアプリ名

Node.jsを作成

ここからは、自分でNode.jsを作成し、それをデプロイする作業かなと考えました。私はDBからデータを取得してJsonを返すRestAPIが欲しかったので、以下を実施しました。

1. Node.jsでREST APIを作るための学習

以下のサイトが非常に参考になりました。

Node.jsを利用した単純なREST APIの書き方

2. GitHubと連携

以下のサイトが参考になりました。

heroku 初級編 - GitHub から deploy してみよう -
Step 4: HerokuとGitHubを連携させて自動デプロイ環境を作ろう!

3. nodemanについて

Node.jsの学習をするにあたって、出てきたnodemanが何なのかは以下のサイトで理解できました。恐らくGitHubとの連携の次に疑問になるワードだと思います。

nodemonとは?

4. herokuのエラーログ確認

いざGithubを使用してherokuにデプロイしたアプリを開いたときに、アプリ画面に以下のコマンドが出てきました。

heroku logs --tail

これはherokuにデプロイしたアプリのログを確認するためのコマンドみたいです。これはローカルのアプリフォルダにcdしている状態で使えば確認できます。
終了するときはCtrl+cです。

5. Starting process with command `nmp start`エラーについて

herokuのエラーログ確認したタイミングで見つけたエラーです。これに関しては、index.jsのscriptsを以下のように修正して解決しました。どうやら、これを指定しないと、スタートプロセスが見つからないからエラーになるとか。

"scripts": {
  "start": "node index.js"
},

6. ”Web process failed to bind to $PORT”エラーについて

startを指定した後に再度実行したら別のエラーが発生しました。調べたら、以下のサイトが出てきました。

Herokuで”Web process failed to bind to $PORT”が出力されるとき

いくつか疑問が出てきたので、単語も調べました。

Heroku Postgresをインストール

Heroku アプリに PostgreSQL を導入する

新たにEJSに挑戦するので、いったんこの記事を中断します。現時点で意見がある方がいれば参考意見を頂ければ嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webアプリが作れるようになるまで【Heroku,Node.js使用】

注意

記載途中です。内容に大幅な変更もあると思います。
(私自身完全に理解できていない状態での記事作成になるため。)

はじめに

HTMLやJavascript、C#もしくはjavaは分かるけど、サーバーをどう構築すれば良いか分からないレベルの人なら参考になると思います。
(恐らくSES事業を経験してると、そのレベルにはなると思います。)

具体的には、docker、AWS、Heroku、node.js、Nginxの辺りが完全に理解できていないレベルの人です。

Gitが分からない人は、ソースツリーを使用して、コミットプッシュマージクローンプルリク辺りを理解するところから始める方が良いと思います。とりあえず、下記サイトを参考にどうぞ。

SourceTreeの使い方 GitHubとの連携方法

実施内容

Herokuに登録

まずはHerokuが無料でDB使えるということが分かったので、下記サイトを参考にしつつHerokuに会員登録しました。

【HEROKUとは】これを読めばOK!デプロイの仕方まで徹底解説

Node.jsインストール、Atomインストール

Herokuに登録後、Node.jsが必要そうだったので、下記サイトを参考にインストールしました。また、Node.jsの編集にAtomが必要なので、Atomインストールしてない人は、このタイミングで一緒にインストールしましょう。

Node.jsの勉強会でお手軽にWebアプリを作った話

RestAPIを知る

恐らくここが1番重要な内容だと思います。私はHTTPサーバーを作る理由が謎だったので、RestAPIの存在を知ったとき、体に電流が走りました。

Webアプリ初心者は、何を作れば良いか分からなかったらRestAPIを作れば良いと思います。RestAPIは下記サイトが参考になると思います。

RESTful APIとは何なのか

ちなみにRestAPIが何故重要なのかを説明すると、HTMLで画面作っていると、データを大量に持たせたい場面が出てくると思います。Javascriptの変数に値を直で持たせるのも1つのやり方だとは思いますが、データが増えるたびにJavascriptを書き換えるのは、HTTPサーバーに再度アップロードが必要な点で効率が悪いと感じます。そこでデータをJsonで返すRestAPIを作ってしまえば、アプリをアップロードしなおさなくてもデータを増やせると考えました。

HerokuでサンプルAPIを作成

下記サイトを参考にしました。

裸の窓使いがHerokuにNodeでRestAPIで「こんにちは」するまで

なお、こちらでは、サンプルを使用してのAPIになるので、自分専用の新たなAPIが欲しい場合は、やり方を調べる必要があります。これからの作業の意味を理解するためにも、まずは、サンプルAPIの理解をお願いします。

Gitリポジトリの作成

こちらに関しては、上記サンプル(裸の窓使いがHerokuにNodeでRestAPIで「こんにちは」するまで)と比較した結果、見つけた差異です。下記を実施しました。

Create a new Git repository
Initialize a git repository in a new or existing directory

$ cd my-project/
$ git init
$ heroku git:remote -a 新しく作成したアプリ名

Node.jsを作成

ここからは、自分でNode.jsを作成し、それをデプロイする作業かなと考えました。私はDBからデータを取得してJsonを返すRestAPIが欲しかったので、以下を実施しました。

1. Node.jsでREST APIを作るための学習

以下のサイトが非常に参考になりました。

Node.jsを利用した単純なREST APIの書き方

2. GitHubと連携

以下のサイトが参考になりました。

heroku 初級編 - GitHub から deploy してみよう -
Step 4: HerokuとGitHubを連携させて自動デプロイ環境を作ろう!

3. nodemanについて

Node.jsの学習をするにあたって、出てきたnodemanが何なのかは以下のサイトで理解できました。恐らくGitHubとの連携の次に疑問になるワードだと思います。

nodemonとは?

4. herokuのエラーログ確認

いざGithubを使用してherokuにデプロイしたアプリを開いたときに、アプリ画面に以下のコマンドが出てきました。

heroku logs --tail

これはherokuにデプロイしたアプリのログを確認するためのコマンドみたいです。これはローカルのアプリフォルダにcdしている状態で使えば確認できます。
終了するときはCtrl+cです。

5. Starting process with command `nmp start`エラーについて

herokuのエラーログ確認したタイミングで見つけたエラーです。これに関しては、index.jsのscriptsを以下のように修正して解決しました。どうやら、これを指定しないと、スタートプロセスが見つからないからエラーになるとか。

"scripts": {
  "start": "node index.js"
},

6. ”Web process failed to bind to $PORT”エラーについて

startを指定した後に再度実行したら別のエラーが発生しました。調べたら、以下のサイトが出てきました。

Herokuで”Web process failed to bind to $PORT”が出力されるとき

いくつか疑問が出てきたので、単語も調べました。

Heroku Postgresをインストール

Heroku アプリに PostgreSQL を導入する

一旦ここで切ります。一日では全て理解できませんでした。現時点で意見がある方がいれば参考意見を頂ければ嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptのインストールから実行まで

目的

細かい設定は後回しにしてとりあえずTypeScriptをインストールして実行する。

環境

windows10
node.js v12.13.0
npm v6.12.0
typescript v3.9.5

今回はnode.jsを使ってTypeScriptのインストールを行うため、先にnode.jsをインストールしておく。
node.js(公式サイト) -> https://nodejs.org/ja/

TypeScriptのインストール準備

TypeScriptのプロジェクトを作りたいディレクトリを初期化する。

コマンドプロンプト
npm init

初期化時に色々設定を聞かれるがすべてデフォルトでOKなので、Enter連打。
初期化が完了すると、package.jsonが作られる。

TS_Project
  └─ package.json

TypeScriptのインストール

次に、package.jsonと同じディレクトリで次のコマンドを実行する。

npm install typescript

するとpackage.json内にtypescriptが追加されていることが確認できる。

package.json
  "dependencies": {
    "typescript": "^3.9.5"
  }

ここまでで、TypeScriptのインストールは終了。
フォルダ構成は以下のようになっている。

TS_Project
  ├─ node_modules
  ├─ package-lock.json
  └─ package.json

次からはTypeScriptのファイルを作成し、コンパイル、実行までを行う。

TypeScriptのファイルを作成

TypeScript(以下TS)ファイルを作成(今回はapp.ts)する。
今回はコンソールに文字を出力する簡単なもの。

app.ts
console.log('Hello TypeScript');

htmlファイルに<script>で読み込む(今回はindex.html)。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./app.ts" defer></script> <!-- 追加!! -->
    <title>初めてのTypeScript</title>
</head>
<body>

</body>
</html>

ここで注意してほしいところは<script>で読み込むファイルはapp.tsではなくapp.jsになるというところ。
これはTSファイルはコンパイラ後にJSファイルとして出力される。htmlファイルはそのJSファイルを使うためである。

この時点でのフォルダ構成は以下になる。

TS_Project
  ├─ node_modules
  ├─ app.ts
  ├─ index.html
  ├─ package-lock.json
  └─ package.json

見ての通り現時点ではまだapp.jsファイルはない。

TypeScriptのコンパイル

app.tsがあるディレクトリで以下のコマンドを実行する。

コマンドプロンプト
npx tsc app.ts

※npxはnpm バージョン5.2.0 以降で使えるコマンド
コンパイルが終了するとapp.jsファイルが生成される。

先ほどのhtmlファイルを実行してコンソールを開くと「Hello TypeScript」が表示されている。

image.png

最終的なフォルダ構成は以下。

TS_Project
  ├─ node_modules
  ├─ app.js
  ├─ app.ts
  ├─ index.html
  ├─ package-lock.json
  └─ package.json

以上がTypeScriptのインストール->コンパイル->実行の流れとなる。
ここまででとりあえずは当初の目的は達成できた。
次からはもう少し細かく設定をしていく。

TypeScriptのプロジェクトの設定

これまでの手順ではTSファイルが複数存在する場合、各ファイルを指定してコンパイルしていくことになり現実的ではない。
プロジェクトが大きくなるにつれ、よりTypeScriptの細かな設定が必要となる。
そのためTSプロジェクトを設定したいディレクトリで以下のコマンドを実行し、初期化を行う。

コマンドプロンプト
npx tsc --init

初期化が終わると、tsconfig.jsonが生成される。
今後はこのファイルがあるフォルダ+サブフォルダはTSによって管理されるようになる。

ちなみにtsconfig.jsonにはTSファイルがどのようにコンパイルされるかなどをコントロールする設定が記載されている。
これでコンパイル時にファイルを指定せずに、プロジェクト内にあるTSファイルはすべてコンパイルされるようになる(node_modules内のファイルは除外)。

コマンドプロンプト
npx tsc

tsconfig.jsonの中身についてはこちらも参考に。
初期化直後のtsconfig.jsonを翻訳してみた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS lightsailでディレクトリルートを変更する

AWS lightsail + node.jsでルートディレクトリを変更したい

node.jsをインストールしたlinuxベースのAWS lightsailで、
gitクローンしたディレクトリをディレクトリルートに指定します。

laightsailの環境は
・nodejs v12.16.1
・apache v2.4.41(unix)

sudo vi /opt/bitnami/apache2/conf/bitnami/bitnami.conf

で、viエディタを開きます。

escキーを押してから

/DocumentRoot

でドキュメントルートを指定している行にジャンプします。
(普通にカーソル移動でもokです。)

ここで、

DocumentRoot "gitクローンしたファイルパス"

IPアドレスやドメインをたたけば、htmlファイルが表示されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS lightsailでルートディレクトリを変更する

AWS lightsail + node.jsでルートディレクトリを変更したい

node.jsをインストールしたlinuxベースのAWS lightsailで、
gitクローンしたディレクトリをディレクトリルートに指定します。

laightsailの環境は
・nodejs v12.16.1
・apache v2.4.41(unix)

sudo vi /opt/bitnami/apache2/conf/bitnami/bitnami.conf

で、viエディタを開きます。

escキーを押してから

/DocumentRoot

でドキュメントルートを指定している行にジャンプします。
(普通にカーソル移動でもokです。)

ここで、

DocumentRoot "gitクローンしたファイルパス"

IPアドレスやドメインをたたけば、htmlファイルが表示されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gulpを使ってScss/Sassをコンパイルする方法

ゆっくり見て行ってください!(’’)

自己紹介

HAL大阪四年生4年IT学科Web開発専攻所属(現2020年)

記事について

Sassのコンパイルをいちからやろうと思って遊んでいたら楽しくなってしまったのでまとめておきます
難しいお話は私もわからないので参考記事にて閲覧ください!!


↓下記から記事開始


Gulpとは

gulpはNode.jsをベースとしたビルドシステムヘルパーです。

gulpを使えばさまざまな作業を自動化することができます。
gulpの一番の特徴はサイトのトップページで「ストリーミングビルドシステム」と自ら名乗っているように、ファイルの処理をストリームで行うというところです。

この特徴によって複雑なタスクも細かくカスタマイズして書くことができます。

Gulpを使うところ

今回の記事ではSass/Scssをコンパイルする為に使用しています。

実行環境の構築とgulpのインストール

node.js

サイトへ行きインストーラをダウンロードし、実行してください。
正常にインストールされたか確認

プラグインを使うために0.10以上が必要なのでそれ以上のバージョンが入っているか確認してください

$ node -v

npm

↓npmでパッケージをインストールする際には、次のコマンドを実行します。
$ npm install

または、

$ npm i

でも正常にインストールされます。

実行すると現在いるフォルダにnode_modulesフォルダが作られ、そこにパッケージがインストールされます。
正常にインストールできているかの確認を行ってください

$ npm -v

ローカルインストールとグローバルインストール

npmではパッケージをローカルとグローバルのどちらにインストールするか、選ぶことができます。

↓ローカルにインストールした場合(デフォルト)

インストールしたパッケージは、そのプロジェクトのフォルダ内でのみ利用できます。

$ npm install パッケージ名
↓グローバルにインストールした場合

インストールしたパッケージは、そのマシン上のどこからでも利用できるようになります。
グローバルにインストールしたい場合には-gオプションを付け、次のように実行します。

$ npm install -g パッケージ名

プロジェクトごとに利用するパッケージの管理

installコマンドのオプション

package.jsonでインストールするパッケージの管理をするにはnpm installを実行する際に
--saveまたは--save-devというオプションを付けます。

--saveオプション

作成しているプログラム自体にそのパッケージが必要なときに付けます

--save-devオプション

gulpやgulpで使うプラグインなど、開発時だけ必要となるもののときに付けます。

$ npm intsall --save-dev パッケージ名

package.jsonで利用するパッケージを管理しておくことで、ライブラリを直接コピーせずに、使うライブラリの情報だけを共有することができます。
プロジェクトの環境を共有する際、共有された側は最初にnpm installを実行すれば、プロジェクトに必要なパッケージをインストールすることができるのです。

npmコマンドにはショートカットがある

参考URL:npmjs.com

gulp

上記でインストールしたnpmgulpをインストールしていきます。

gulpのインストールは通常ですとローカルとグローバルの両方にインストールする必要があります。
グローバルにインストールしたgulpは、ローカルにインストールしたgulpを実行することが役割です。

プロジェクトによって使用するgulpのバージョンが異なる場合でも、無理にgulpのバージョンを合わせることなく、タスクを実行することができます。

$ npm install -g gulp
$ npm install --save-dev gulp

正常にインストールできているかの確認を行ってください

gulp -v

Sass/Scssを監視するタスクを書いてみる

gulp-sass

Sassファイルをコンパイルする為のプラグインgulp-sassをインストールします。

$ npm i gulp--sass --save-dev

gulpのタスクはgulpfile.jsに書いていきます。

gulpfile.jsを作成し下記のサンプルコードを記入します。
タスクの内容はScssファイルを監視するものです。

gulpfile.js
const gulp = require('gulp');
const sass = require('gulp-sass');

gulp.task('sass', function(done){
    // stream
    gulp.src('./sass/**/*.scss') //タスクで処理するソースの指定
    .pipe(sass()) //処理させるモジュールを指定
    .pipe(gulp.dest('./style/css/')); //保存先を指定

    console.log('sass compile');
    done();
});

//defaultタスクは、タスク名を指定しなかったときに実行されるタスクです。
gulp.task('default', ['sass']);

では、コマンドでgulpとうちgulpを実行していきます。

$ gulp

Gulpを使ってSass/Scssファイルを永久的に監視すさせる方法

先ほどのgulpfile.js のソースの下にプラスで下記のコードを書きます。

gulpfile.js
//~~~~~~~~~~~~~~~~~~  略  ~~~~~~~~~~~~~~~~~//

gulp.task('watch', function(done){
    gulp.watch('./sass/*.scss', gulp.task('sass'));
    //watch task
    console.log('watch start');
    done();
});

gulp.task('default', ['sass']);

では、コマンドでgulpを実行していきます。

$ gulp watch

前回と違い、Sass/Scssを保存するたびに更新していくことがわかります。

↓おまけ

コンパイル時にScssファイルをまとめてimportする方法があります。

まずstyle.scssへ下記のソースを記述します

style.scss
// page_styleの下層
@import './sub/sub.scss';

subファイルの下層のsub.scssファイルをimportします。

style.scss
// page_styleの下層
@import './sub/*.scss';

subファイルの下層の全てのSass\Scssファイルをimportします。

そして、ファイルの下層全てをまとめてimportさせたい場合

gulp-sass-glob

gulp-sass-globというプラグインをインストールします。

$ npm i gulp-sass-glob --save-dev

インストールが終わったら、先ほどのコードに下記のコードを追加して行ってください。
const sassGlob = require("gulp-sass-glob");
`pipe(sassGlob())`を下記のように追加します。

`pipe(sassGlob())`は必ずsass()の上に入れるようにしてください。

gulpfile.js
const sassGlob = require("gulp-sass-glob");

gulp.task('sass', function(done){
    // stream
    gulp.src('./sass/**/*.scss')
    .pipe(sassGlob()) // Sassの@importにおけるglobを有効にする
    .pipe(sass())
    .pipe(gulp.dest('./style/css/')); 

    console.log('sass compile');
    done();
});

動いているか確認していきます

$ gulp

エラーが出なかったら。
style.cssファイルを確認して正しくコンパイルされていることを確認しましょう。


今回の参考資料

gulpとは何か
gulp-sass-glob

ご挨拶

最後まで読んでいただきありがとうございました。
文面等見にくい部分が多々あるかと思いますが、「このやり方おすすめ!」や「私はこのやり方やってるよ!」等ご意見くださると嬉しいです。
もっと成長できるように、たくさん勉強しながら記事を書いていこうと思っていますので、よろしくお願い致します。

SNS

Twitter
Facebook
Github


*上記の投稿は個人によるもので、団体を代表するものではありません。ご了承くださいますようお願い致します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む