- 投稿日:2020-06-28T23:12:34+09:00
【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.jsonjsonObject { 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.jsfunction 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を充実したいと思う
- 投稿日:2020-06-28T22:32:46+09:00
Node.jsのコードを定期実行する(crontabを利用)
これまでのおさらい
- Raspberry piにNode.jsをインストールする
- Node.jsでrequestモジュールを使ってHTTPリクエストを実行する
- Node.jsでIFTTTのWebhooksを使う(requestモジュール利用)
- Node.jsからobnizでBME280(気温、湿度、気圧)センサーを動かす
今回のお話
- 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の書き方
- node-cronを使う方法 ⇒ 参考:Node Cron
今回は、crontabで試してみます。
crontabの設定
crontabでは、コマンドのフルパスが必要です。
whichコマンドで、nodeコマンドのフルパスを調べます。
/home/pi/.nodebrew/current/bin/node
の部分を使います。
~/myapp/BME280.js
の部分が実行するファイルです。pi@raspberrypi:~/myapp $ which node /home/pi/.nodebrew/current/bin/nodecrontabは、-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分周期に気温、湿度、気圧が記録されています。
- 投稿日:2020-06-28T18:16:51+09:00
「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!備考
現時点でpackage-lock.jsonを見てみると、Electronの@types/nodeの要求バージョンは12.0.12となっているので、そちらのバージョンを入れたほうが安心かもしれない。
- 投稿日:2020-06-28T16:32:39+09:00
【個人メモ】Express環境ハンズオン
完全に個人用メモです。
Node.jsとnpmがinstall済みであることを確認する
node -v, npm -vコマンド実行expressのインストール
作業ディレクトリでnpm initを実行してpackage.json作成
npm install express express-generator --saveexpressアプリケーションの雛形作成
./node_modules/.bin/express --view=pug sampleアプリ立ち上げ
cd sample
npm install
npm start
ブラウザからhttp://localhost:3000/につなぐと「Welcome to Express」が表示される。
- 投稿日:2020-06-28T16:08:13+09:00
ルートディレクトリ(/)とホームディレクトリ(~)のちゃんとした理解
ものすごい細かいことだけど、パスの指定方法での
~
(チルダ)と/
(スラッシュ)の理解が曖昧で気持ち悪い思いをしたのでメモ。
/
: ルートディレクトリ
~
:今のユーザーのホームディレクトリ
~taro
: taroというユーザーのホームディレクトリスラッシュの意味合い
ルートディレクトリの
/
と、各ファイルやディレクトリの前につく/
は意味合いが違っている模様。
- 前者:ルートディレクトリそのもの
- 後者:ディレクトリを区切るもの
なので、一見ルートディレクトリのせいで「ディレクトリとは末尾にスラッシュが付いているもの」という勘違いを(少なくも筆者は)しちゃうが、
hogehoge/
がディレクトリなのではなくhogehoge
がディレクトリなのだ。ホームディレクトリを~/
だと思ってしまっている人は多いのではないか?~ユーザー名
また
~taro
でtaroさんのホームディレクトリを指定できるのは初耳で、結構ググったけどこれについて深く語っている記事は見当たらなかった。こういう指定の仕方があるんですね。teratailで教えてもらった?
https://teratail.com/questions/273447Node.jsのSass特有の書き方
今回がそもそも、RailsアプリでBootstrapを導入しようとして疑問にわいたことだったのだが、Node.jsのSass特有の書き方として、以下のような相対パス?の書き方が使えるらしい。
@import '~bootstrap/scss/bootstrap';上記は
myapp/node_modules/bootstrap/dist/css/bootstrap.css
を指定できる。モジュールをユーザーに見立てているのかな?まとめ
こういうのって「そういうもの」と暗記する人が多いと思うんだけど、数学の公式と一緒で「なぜそうなるのか?」という部分を理解したほうが知識を応用できると思うんです。
今回のものを踏まえると、WebのURLで
https://example.jp
とhttps://example.jp/
のどっちを正規URLとすべきかという問題は、前者のほうが本来は正しいのではないかと考えちゃう。
- 投稿日:2020-06-28T14:18:26+09:00
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/DeveloperXcode未インストールの場合
多分以下で解決するはず
(この方法実際に試せていないので解決しなかったらごめんなさい)$ xcode-select --install
- 投稿日:2020-06-28T13:21:05+09:00
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.tsimport { 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: 2EventPort クラスは TypedEvent クラスのようにプロパティとして event を表現します。
TypedEvent クラスとの大きな違いは、
- 外部に本当に公開したい機能だけを持つ
- event の引数ではなく listener の型を指定する形にすることで表現力の弱さを解消
- 処理は EventEmitter に委譲
- event の発火は EventEmitter を通じて本体クラスが行う
の4点です。
また、本体クラスは EventEmitter の継承は行わず、単に集約の関係に留めます。これにより、誤った継承関係の排除だけでなく emit() の隠蔽も達成しています。ちなみに emit() については、第一引数に EventPort を受け取れるようにし、可変長引数を Conditional Types でフィットさせるよう拡張しています。
ご覧の通り、listener 登録も event 発火も type safety です。
まとめ
以上を踏まえて表を更新してみました。
方法 共通クラス導入方法 event 表現方法 event 実装コスト 表現力 設計汚染 emit 隠蔽 EventEmitter + 型定義 不要1 引数 高 高 有 不十分 StrictEventEmitter パッケージ追加 引数 低 高 有 不十分 TypedEvent 自前実装 プロパティ 低 中2 無 不十分 EventPort 自前実装 プロパティ 低 高 無 完全 右端2列が気にならない方は StrictEventEmitter、気になる方は EventPort が良いと思います。
EventPort は自前実装の形になりますが、非常に単純なつくりですので問題にならないと思います。あとは event 表現方法ですかね。どっちが優れているというものではなく、好みの問題が大きいかなと思います。
ちなみに私は元々 C#er なので、プロパティ形式の方がどちらかというとしっくりきます。
- 投稿日:2020-06-28T11:43:00+09:00
NestでTypeORM環境を構築してサンプル実装してみる
背景
前回は純粋なTypeORM環境をCLIで構築してみましたが、TypeORMはNestというフレームワークのORMとして利用されているとのことを知ったので、今度はNestでTypeORM環境を構築してみようと思う。
https://qiita.com/yusuke-ka/items/195e6bba4f21a659b424Nest環境の構築
前回同様、DBは以前インストールしたpostgresql(windows)を利用する。
https://qiita.com/yusuke-ka/items/448843020c0406363ba5#%E6%BA%96%E5%82%99pgadmin4でデータベースインスタンスだけ作っておく。
データベース名("nest_typeorm"とした)を入力して作成。
ここからは、コード エディタ(VS Code)上での作業。
諸事情により、まずは作業用のnode環境を作る。
> mkdir work > cd worknestのインストールは通常は下記のコマンドで実施する。
(linux環境の場合は下記でやってしまってOK.)yarn global add @nestjs/cli本当は↑でインストールしたいところだけど、windowsだとそのままだと使えずパスを通してやる必要があり面倒なので、↓でインストール。
yarn add @nestjs/cli続いて、nestで新しいプロジェクトを作る。
以下のようにすれば、今いるフォルダを直接プロジェクトのフォルダにできるけど、すでにpackage.jsonが存在していて怒られるので(globalインストールしていればpackage.jsonがないので実行できる)、今いるフォルダは作業用として、下にプロジェクトを作る。
> npx nest new .↓今回はこっちで実行。
> npx nest new nestnpmか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.tsimport { 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 TestObjecttest-objectフォルダが作成され、その下に各ひな形が配置される。
さらに以下のコマンドでモデルのひな形も作成。
(こちらは、明示的にフォルダを指定しないと、「test-object」フォルダに入らなかったので、「test-object/TestObject」のように指定。)> yarn nest g class test-object/TestObjecttest-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.tsimport { 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.tsimport { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity() export class TestObject { @PrimaryGeneratedColumn() id: number; @Column() attr1: string; }シンプルに属性はidとattr1だけ。
続いてサービスの実装。
test-object.service.tsimport { 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.tsimport { 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.tsexport class CreateTestDataDTO { attr1: string; } export class UpdateTestDataDTO { attr1: string; }最後に、serviceやcontrollerをモジュール化するファイルの実装。
test-object.module.tsimport { 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に各種リクエストを送ってみる。説明は省略(全部問題なく動きました。)。
作成
全件取得
更新
1件取得
削除
さいごに
今回はNestでTypeORM環境を構築してサンプル実装してみた。
前回、素のTypeORM環境をCLIで作成したときよりは時間がかかったけど、構造がなんとなく理解できたら、すんなりとコードを書くことができた。
そこそこの規模のコードを書くなら、こういったフレームワークを利用して書いた方が、コードを整理できて良さそうですね。テストのフレームワークも付いてるみたいだし。
- 投稿日:2020-06-28T11:43:00+09:00
NestでTypeORM環境を構築してサンプル実装(CRUD+トランザクション+ロック)してみる
背景
前回は純粋なTypeORM環境をCLIで構築してみましたが、TypeORMはNestというフレームワークのORMとして利用されているとのことを知ったので、今度はNestでTypeORM環境を構築してみようと思う。
https://qiita.com/yusuke-ka/items/195e6bba4f21a659b424Nest環境の構築
前回同様、DBは以前インストールしたpostgresql(windows)を利用する。
https://qiita.com/yusuke-ka/items/448843020c0406363ba5#%E6%BA%96%E5%82%99pgadmin4でデータベースインスタンスだけ作っておく。
データベース名("nest_typeorm"とした)を入力して作成。
ここからは、コード エディタ(VS Code)上での作業。
諸事情により、まずは作業用のnode環境を作る。
> mkdir work > cd worknestのインストールは通常は下記のコマンドで実施する。
(linux環境の場合は下記でやってしまってOK.)yarn global add @nestjs/cli本当は↑でインストールしたいところだけど、windowsだとそのままだと使えずパスを通してやる必要があり面倒なので、↓でインストール。
yarn add @nestjs/cli続いて、nestで新しいプロジェクトを作る。
以下のようにすれば、今いるフォルダを直接プロジェクトのフォルダにできるけど、すでにpackage.jsonが存在していて怒られるので(globalインストールしていればpackage.jsonがないので実行できる)、今いるフォルダは作業用として、下にプロジェクトを作る。
> npx nest new .↓今回はこっちで実行。
> npx nest new nestnpmか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.tsimport { 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 TestObjecttest-objectフォルダが作成され、その下に各ひな形が配置される。
さらに以下のコマンドでモデルのひな形も作成。
(こちらは、明示的にフォルダを指定しないと、「test-object」フォルダに入らなかったので、「test-object/TestObject」のように指定。)> yarn nest g class test-object/TestObjecttest-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.tsimport { 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.tsimport { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity() export class TestObject { @PrimaryGeneratedColumn() id: number; @Column() attr1: string; }シンプルに属性はidとattr1だけ。
続いてサービスの実装。
test-object.service.tsimport { 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.tsimport { 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.tsexport class CreateTestDataDTO { attr1: string; } export class UpdateTestDataDTO { attr1: string; }最後に、serviceやcontrollerをモジュール化するファイルの実装。
test-object.module.tsimport { 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に各種リクエストを送ってみる。説明は省略(全部問題なく動きました。)。
作成
全件取得
更新
1件取得
削除
トランザクション管理
ついでにトランザクション管理の実装がどうなるかも試してみる。
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で実装する必要があるようなので若干面倒だけど、上記のようにすればロックできそう。
エラーにならないかだけ、動作確認しておく。
更新
取得して確認
ちゃんとロックできているかは確認していないが、エラーなくデータが更新されることは確認できた。
一応、SQLのログを出力してみると、"FOR UPDATE"が付いていた。
ちなみに、SQLのログを出力する設定は下記。
app.module.ts... @Module({ imports: [ TypeOrmModule.forRoot({ ... logging: true, }), ], ... }) ...さいごに
今回はNestでTypeORM環境を構築してサンプル実装してみた。
前回、素のTypeORM環境をCLIで作成したときよりは時間がかかったけど、構造がなんとなく理解できたら、すんなりとコードを書くことができた。
そこそこの規模のコードを書くなら、こういったフレームワークを利用して書いた方が、コードを整理できて良さそうですね。テストのフレームワークも付いてるみたいだし。
- 投稿日:2020-06-28T10:55:31+09:00
令和元年? 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 引数の、この場合
era
をlong
に設定しましょう。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/
- 投稿日:2020-06-28T10:54:31+09:00
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 -w
とnpm 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.tsconsole.log('Hello TypeScript')
app.ts
ファイルを編集すると自動でコンパイル->ブラウザ更新までをしてくれている。「Live Server」を使う手もある
今回はブラウザの自動更新に「Lite-Server」を使ったが、「Visual Studio Code」を使った開発をしているならLive Serverを使う方がお手軽かもしれない。VSCodeの標準拡張機能なので。
まぁそれはまた別のお話で。
- 投稿日:2020-06-28T10:52:59+09:00
RESTAPIが作れるようになるまで【Heroku,Node.js使用】
注意
記載途中です。内容に大幅な変更もあると思います。
(私自身完全に理解できていない状態での記事作成になるため。)
色々知識を得た結果、ejs(テンプレートエンジン)を使用すればWebアプリが作れることが判明したので、これはRESTAPIが作れるようになるまでに変更します。はじめに
HTMLやJavascript、C#もしくはjavaは分かるけど、サーバーをどう構築すれば良いか分からないレベルの人なら参考になると思います。
(恐らくSES事業を経験してると、そのレベルにはなると思います。)具体的には、docker、AWS、Heroku、node.js、Nginxの辺りが完全に理解できていないレベルの人です。
Gitが分からない人は、ソースツリーを使用して、コミットプッシュマージクローンプルリク辺りを理解するところから始める方が良いと思います。とりあえず、下記サイトを参考にどうぞ。
実施内容
Herokuに登録
まずはHerokuが無料でDB使えるということが分かったので、下記サイトを参考にしつつHerokuに会員登録しました。
【HEROKUとは】これを読めばOK!デプロイの仕方まで徹底解説
Node.jsインストール、Atomインストール
Herokuに登録後、Node.jsが必要そうだったので、下記サイトを参考にインストールしました。また、Node.jsの編集にAtomが必要なので、Atomインストールしてない人は、このタイミングで一緒にインストールしましょう。
RestAPIを知る
恐らくここが1番重要な内容だと思います。私はHTTPサーバーを作る理由が謎だったので、RestAPIの存在を知ったとき、体に電流が走りました。
Webアプリ初心者は、何を作れば良いか分からなかったらRestAPIを作れば良いと思います。RestAPIは下記サイトが参考になると思います。
ちなみに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を作るための学習
以下のサイトが非常に参考になりました。
2. GitHubと連携
以下のサイトが参考になりました。
heroku 初級編 - GitHub から deploy してみよう -
Step 4: HerokuとGitHubを連携させて自動デプロイ環境を作ろう!3. nodemanについて
Node.jsの学習をするにあたって、出てきたnodemanが何なのかは以下のサイトで理解できました。恐らくGitHubとの連携の次に疑問になるワードだと思います。
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”が出力されるとき
いくつか疑問が出てきたので、単語も調べました。
require('path')について
pathって何だろうと思って調べた結果、以下のサイトが引っかかった。
webpack.config.jsで思ったpath.resolveって何のためにあるの?process.env.PORTについて
どうやらこれに関しては、以下のサイトを見ると、ポート番号指定ということが判明した。
Node.js Expressフレームワークの「ポート指定部分」に注力してみたHeroku Postgresをインストール
新たにEJSに挑戦するので、いったんこの記事を中断します。現時点で意見がある方がいれば参考意見を頂ければ嬉しいです。
- 投稿日:2020-06-28T10:52:59+09:00
Webアプリが作れるようになるまで【Heroku,Node.js使用】
注意
記載途中です。内容に大幅な変更もあると思います。
(私自身完全に理解できていない状態での記事作成になるため。)はじめに
HTMLやJavascript、C#もしくはjavaは分かるけど、サーバーをどう構築すれば良いか分からないレベルの人なら参考になると思います。
(恐らくSES事業を経験してると、そのレベルにはなると思います。)具体的には、docker、AWS、Heroku、node.js、Nginxの辺りが完全に理解できていないレベルの人です。
Gitが分からない人は、ソースツリーを使用して、コミットプッシュマージクローンプルリク辺りを理解するところから始める方が良いと思います。とりあえず、下記サイトを参考にどうぞ。
実施内容
Herokuに登録
まずはHerokuが無料でDB使えるということが分かったので、下記サイトを参考にしつつHerokuに会員登録しました。
【HEROKUとは】これを読めばOK!デプロイの仕方まで徹底解説
Node.jsインストール、Atomインストール
Herokuに登録後、Node.jsが必要そうだったので、下記サイトを参考にインストールしました。また、Node.jsの編集にAtomが必要なので、Atomインストールしてない人は、このタイミングで一緒にインストールしましょう。
RestAPIを知る
恐らくここが1番重要な内容だと思います。私はHTTPサーバーを作る理由が謎だったので、RestAPIの存在を知ったとき、体に電流が走りました。
Webアプリ初心者は、何を作れば良いか分からなかったらRestAPIを作れば良いと思います。RestAPIは下記サイトが参考になると思います。
ちなみに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を作るための学習
以下のサイトが非常に参考になりました。
2. GitHubと連携
以下のサイトが参考になりました。
heroku 初級編 - GitHub から deploy してみよう -
Step 4: HerokuとGitHubを連携させて自動デプロイ環境を作ろう!3. nodemanについて
Node.jsの学習をするにあたって、出てきたnodemanが何なのかは以下のサイトで理解できました。恐らくGitHubとの連携の次に疑問になるワードだと思います。
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”が出力されるとき
いくつか疑問が出てきたので、単語も調べました。
require('path')について
pathって何だろうと思って調べた結果、以下のサイトが引っかかった。
webpack.config.jsで思ったpath.resolveって何のためにあるの?process.env.PORTについて
どうやらこれに関しては、以下のサイトを見ると、ポート番号指定ということが判明した。
Node.js Expressフレームワークの「ポート指定部分」に注力してみたHeroku Postgresをインストール
一旦ここで切ります。一日では全て理解できませんでした。現時点で意見がある方がいれば参考意見を頂ければ嬉しいです。
- 投稿日:2020-06-28T01:29:25+09:00
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.jsonTypeScriptのインストール
次に、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.tsconsole.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」が表示されている。
最終的なフォルダ構成は以下。
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を翻訳してみた
- 投稿日:2020-06-28T01:23:10+09:00
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ファイルが表示されます。
- 投稿日:2020-06-28T01:23:10+09:00
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ファイルが表示されます。
- 投稿日:2020-06-28T01:16:26+09:00
Gulpを使ってScss/Sassをコンパイルする方法
ゆっくり見て行ってください!(’’)
自己紹介
HAL大阪四年生4年IT学科Web開発専攻所属(現2020年)
記事について
Sassのコンパイルをいちからやろうと思って遊んでいたら楽しくなってしまったのでまとめておきます
難しいお話は私もわからないので参考記事にて閲覧ください!!
↓下記から記事開始
Gulpとは
gulpはNode.jsをベースとしたビルドシステムヘルパーです。
gulpを使えばさまざまな作業を自動化することができます。
gulpの一番の特徴はサイトのトップページで「ストリーミングビルドシステム」と自ら名乗っているように、ファイルの処理をストリームで行うというところです。この特徴によって複雑なタスクも細かくカスタマイズして書くことができます。
Gulpを使うところ
今回の記事ではSass/Scssをコンパイルする為に使用しています。
実行環境の構築とgulpのインストール
node.js
サイトへ行きインストーラをダウンロードし、実行してください。
正常にインストールされたか確認プラグインを使うために0.10以上が必要なのでそれ以上のバージョンが入っているか確認してください
$ node -vnpm
↓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
上記でインストールした
npm
でgulp
をインストールしていきます。
gulp
のインストールは通常ですとローカルとグローバルの両方にインストールする必要があります。
グローバルにインストールしたgulp
は、ローカルにインストールしたgulp
を実行することが役割です。プロジェクトによって使用するgulpのバージョンが異なる場合でも、無理にgulpのバージョンを合わせることなく、タスクを実行することができます。
$ npm install -g gulp$ npm install --save-dev gulp正常にインストールできているかの確認を行ってください
gulp -vSass/Scssを監視するタスクを書いてみる
gulp-sass
Sassファイルをコンパイルする為のプラグイン
gulp-sass
をインストールします。$ npm i gulp--sass --save-dev
gulp
のタスクはgulpfile.js
に書いていきます。
gulpfile.js
を作成し下記のサンプルコードを記入します。
タスクの内容はScssファイルを監視するものです。gulpfile.jsconst 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
を実行していきます。$ gulpGulpを使って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.jsconst 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
ファイルを確認して正しくコンパイルされていることを確認しましょう。
今回の参考資料
ご挨拶
最後まで読んでいただきありがとうございました。
文面等見にくい部分が多々あるかと思いますが、「このやり方おすすめ!」や「私はこのやり方やってるよ!」等ご意見くださると嬉しいです。
もっと成長できるように、たくさん勉強しながら記事を書いていこうと思っていますので、よろしくお願い致します。SNS
*上記の投稿は個人によるもので、団体を代表するものではありません。ご了承くださいますようお願い致します。