20211202のNode.jsに関する記事は11件です。

Node.jsのeventemitterとそのデザインパターンについてまとめてみた

はじめに この記事ではeventEmitterとeventEmitterに関わるデザインパターンについて紹介しています。 evenEmitterをマスターすることで、よりプロのプログラマー(略してプロプロ)になれると思います。 eventemitterとは まずイベントエミッターとはなにか説明します。 これ、独自にイベントを作成し、指定したタイミングに関数を発火することができます イベント駆動開発でよく用いられます。 イメージで言うと、フロントエンドでいうと、ボタンを押したときにonpushに登録された関数が発火しますよね。それです! eventemitterを用いると、自作でイベントを作って、好きなタイミングで発火することができるようになります。 Node.jsではeventemitterがストリームの裏側で作られ、実行されています。 EventEmitterの実行例 const EventEmitter = require("events") const eventEmitter = new EventEmitter() const bakuhatsu=() => { console.log("ドッカーーーン") } eventEmitter.on("爆発",bakuhatsu) eventEmitter.once("爆発", () => { console.log("一回だけドッカーーーン") }) eventEmitter.emit("爆発") //ドッカーーーン //一回だけドッカーーーン eventEmitter.emit("爆発") //ドッカーーーン console.log("爆発解除") eventEmitter.off("爆発",bakuhatsu) eventEmitter.emit("爆発") //(何もなし) EventEmitter の主要なインスタンスメソッド 最初に簡単に用法をまとめておきます。 on(イベント名、関数名)でイベントを登録し、 emit(イベント名)でイベントに指定した関数を実行! off(イベント,関数名)でイベントに登録した関数の削除 on(event,listener) 指定したイベントに新しいリスナを登録します。 (第一引数に文字列 イベントの名前(任意)  第二引数にコールバック関数 第一引数に登録したイベントが呼ばれたときに発火する) 例 eventEmitter.on("爆発",()=>{console.log("ドッカーーーン")}) once(event,listener) 指定したイベントに新しいリスナを登録します。 (第一引数に文字列 イベントの名前(任意)  第二引数にコールバック関数 第一引数に登録したイベントが呼ばれたときに発火する) これはon()メソッドと同様です。 このリスナは、イベントが一回発行されるとリスナが削除されます。なので、そのイベントの最初の一回しか第二引数に登録したコールバック関数が実行されません。 例 eventEmitter.once("爆発",()=>{console.log("一回だけドッカーーーン")}) off(event,listener) on()メソッドで登録したイベントの特定のリスナを削除します。 (第一引数に文字列 イベントの名前(任意)  第二引数にコールバック関数 on()メソッドで登録した第二引数) 例 eventEmitter.off("爆発",()=>{console.log("ドッカーーーン")}) emit(event,[...args]) on()やonce()メソッドで追加したイベントを発火します。 (第一引数に文字列 イベントの名前(任意)  第二引数には引数 on()やonce()で登録したコールバック関数に引数が必要な場合のみ使用する) 例 eventEmitter.emit("爆発") //今回は引数なし 使い方は、以上です ここからは、eventemitterを利用するデザインパターンについて説明していきます。 まずかんたんにデザインパターンについて説明しておきます。 デザインパターンとは オブジェクト指向言語で使われる設計パターンのことです。オブジェクト指向では再利用性の高いクラスが必須です。 ではどのような方向性で設計していけばよいのか。。。。 それを示してくれるのがデザインパターンです。 代表的なデザインパターンは23種類あります。(今回は詳しく説明しない) ここではeventemitterで用いられるObserverパターンについて解説していきます。 Observerパターンについて ObserverパターンはNode.js以外でもよく用いられる代表的なデザインパターンです。英語のObserverとは、観察者や監視役という意味があります。 このパターンでは、監視対象に対して発生した何らかのイベントが、監視役(Observer)に逐一通知されます。 監視役(Observer)はあらかじめ監視対象に対して監視を行うための登録処理を行い、監視対象はイベントの発生タイミングで登録済みの監視役(Observer)に対して通知処理を実行します。 それぞれのSubjectには複数のObserverを登録できます。 簡単な例を上げて説明します。 中学校を想定します。ここでは、一人の生徒が監視対象です。先生は一人の生徒「たかひろ」くんに対してそれぞれの処理を仕込みます。 それぞれの先生は、自分の専門の処理を「たかひろ」くんに仕込みます。 そして「たかひろ」くんは登録された処理に従って、 数学の勉強がわからないときは数学の先生に、 野球の打撃の成績が悪いときは、筋肉の先生に、(「たかひろ」くんは野球部) 熱があって、吐き気もするときは、保健室の先生に、 通知を出します。 ここまでがObserverパターンです。 先生が生徒に処理を仕込み、生徒が通知を出すところまででひとくくりです。 ここでこの例に従ってeventemitterのコードを書いてみます。 const EventEmitter = require("events") const eventEmitter = new EventEmitter() const helpme=(teacher)=>{ console.log(teacher+"、助けてください") } eventEmitter.on("数学がわからない", helpme) eventEmitter.on("最近打率が悪い", helpme) eventEmitter.on("体調が悪い", helpme) // 先生(Observer)によってイベントが仕込まれる class takahiro { static suugaku_nervous() { eventEmitter.emit("数学がわからない","数学得意な先生") } static sports_nervous() { eventEmitter.emit("最近打率が悪い","筋肉ムキムキな先生") } static condition_nervous() { eventEmitter.emit("体調が悪い","保健室の先生") } } //「たかひろ」くんが先生にイベントを通知する takahiro.suugaku_nervous() //***実行結果*** //数学得意な先生、助けて! デザインパターンとeventemitterの話はここまでです。 実際のコード 最後に実際に使われているコードの紹介をしておきます。 const http = require("http") var fs = require("fs") //serverのeventEmitterを作成 const server = http.createServer() //requestというイベントに関数を仕込む //serverにアクセスがあったときに発火する server.on("request", (req, res) => { fs.readFile("./helloWorld.html", "utf-8", function (error, data) { res.writeHead(200, { "Content-Type": "text/html" }) res.write("requestに登録されたイベント発火") res.write(data) res.end() }) }) //listeningというイベントに関数を仕込む //これはserver.listen(8000)したときに発火する server.on("listening", (res, req) => {}) //errorというイベントに関数を仕込む //エラーが起こったときに発火する server.on("error", err =>{ console.error(err); }) //closeというイベントに関数を仕込む //これはserver.close()したときに発火する(ここではserver.close()はこめんとあうとした) server.on("close", () => {}) server.listen(8000) // server.close() helloEarth.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>Hello,Earth! And,Hello universe!</h1> </body> </html> localhost:8000にアクセスした結果 何気なくhttpモジュールで使っていた関数にもeventEmitterが仕込まれていました。自分が使っている関数や処理がeventEmitterのものかどうか見極める事ができれば、プログラミングのレベルもぐんぐん上がっていくと思いますl 最後に Streamの記事を書くつもりがいつマニカeventEmitter の記事を書いていました。 次回はStreamの記事を書きます!! おいらの記事を最後まで見てくれてありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【準備時間5分】JavaScriptを学びつつ戦う!RoboCodeやろうぜ!

JavaScriptを学びつつ戦う!RoboCodeやろうぜ! ※Windows + Node.js環境を想定して書いています。 Git https://github.com/mokemokepyonpyon/robocode-js/tree/gh-pages Download https://github.com/mokemokepyonpyon/robocode-js/archive/refs/heads/gh-pages.zip RoboCodeとは Robocodeとは元々Java言語でプログラミングする対戦型ロボットシミュレータです。 自分プログラミングしたロボットを、世界中のプログラマが開発したロボットと闘わせることができるんです! youchenleeさんが作成したJS言語バージョンを 更に初心者向けに改良したものが本記事で紹介しているものです。 はじめかた クローンor解凍したZIPの中にある、install.batを起動します。 ※中身はただのnpm installです。   start.batを起動します。 ※中身はただのnode server.jsです。   RoboCodeを起動します。 ※中身はただの http://localhost:3000/index.html へのショートカットです   画面上で戦車が戦い始めたら準備完了です。 遊び方 基本はオートバトルでステージ0~2 とファイナルステージの敵機を倒していく遊びとなります。     自機の強化 ステージ0はデフォルトのままでは勝てると思いますが、ステージ2以降は自機を強化しなければいけません。 public / robot / myrobot.jsをカスタマイズしましょう。 ★参考情報★ 自己情報 ・ me.id ・ me.x ・ me.y ・ me.hp ・ me.angle-現在の角度(タンク角度+タレット角度) ・ me.tank_angle ・ me.turret_angle 敵情報 ・ enemy-spot [N] .id ・ enemy-spot [N] .hp ・ enemy-spot [N] .angle-敵に対する角度(方向) シーケンシャルアクション: ・turn_left(角度) ・turn_right(角度) ・move_forwards(距離) ・move_backwards(距離) ・move_opposide(distance)-このアクションはOnWallCollide()でのみ使用できます 並列アクション: ・turn_turret_left(角度) ・turn_turret_right(角度) ・shoot(攻撃) ・yell(メッセージ) イベント: OnIdle()-アイドル時にトリガーされます(実装する必要があります) OnWallCollide()-タンクが壁に衝突したとき OnHit()-弾丸が当たったとき OnEnemySpot()-砲塔が敵に直接面している場合(発砲しない理由はないようです!)     おわりに 一通りクリアしたら、攻撃と防衛に分かれて、友人と対戦してみるのはいかがでしょう。 攻撃 myrobot.jsをカスタマイズして、ボスを倒す 防衛 boss-0.js~boss-3.jsのいずれかをカスタマイズして、倒されないように防衛する 地味に盛り上がりますよ。 私が学生だった頃はこの遊びはめちゃめちゃ流行ってました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

領域展開「Gradle」で Node.js を制する

失礼しました... はじめに オークファンの大きめのデータを担当するバックエンドシステムでは Kotlin + Spring Boot + Gradle の構成がよく採用されています。少し前まではこの構成で作成されるのは、バッチプログラムや Web API がほとんどでしたが、最近さらに Vue.js を追加して UI を持った Single Page Application (SPA) の Web アプリも作成するようになってきました。 Spring Boot のプロジェクトを Spring Initializr で作成すると、Gradle Wrapper が付属しています。これにより、開発環境では JDK のみ用意するだけでプロジェクトのテストやビルドなどをお手軽に実行できていました。 ところが、JavaScript フレームワークである Vue.js がプロジェクトに追加されたことにより、別途 Node.js 環境を用意しなければいけなくなってしまい、これが結構面倒なうえに、プロジェクトメンバーに開発環境を構築してもらう際にトラブルが発生することが少なくありませんでした。 このような場合には Docker にご登場いただくのが一般的ですが、ここではあえて Docker を使わない縛りを課し、すでにプロジェクトに入っている Gradle での問題解決方法を開示していこうと思います。 個人的には Maven や Gradle (Ant は〇ソ...) 支配下の Java や Kotlin でこれまで戦うことが多かったので、npm (yarn は使いやすいですね...) の支配する不慣れな Node.js に対して有利に戦えるといいなと思ったのが発端でした。 プロジェクトの前提は以下としました。 開発環境に必要なのは JDK のみ Node.js まわりで必要なものは Gradle Plugin for Node で調達 幅広い OS (ここでは macOS、Linux、Windows を想定) で開発可能 今回のサンプルプロジェクトの構築は以下の順序で進めていきます。 Spring Boot プロジェクトの作成 Spring Boot プロジェクトのサブプロジェクト化 Vue.js サブプロジェクトの追加 Spring Boot と Vue.js のサブプロジェクト間の連携 プロジェクトのビルドと実行 サンプルでは Spring Boot と Vue.js を使用しますが、他のものであっても Gradle と Node.js のプロジェクトであれば今回の方法は適用可能なはずです。 コマンドは macOS や Linux の書式で記載しますが、パスの指定をスラッシュ (/) からバックスラッシュ (\) に変更すれば Windows でも実行可能です。 Spring Boot プロジェクトの作成 まず Spring Boot の雛形プロジェクトを Spring Initializr で生成します。 https://start.spring.io/ に Web ブラウザでアクセスし、以下のように選択します。(Group や Artifact などは適宜変更いただいて問題ありません。) GENERATE ボタンをクリックしてプロジェクトをダウンロードして展開します。 $ unzip spring-boot-vue-app.zip プロジェクトのディレクトリ構成は以下のようになっています。 初期の Gradle の設定ファイル settings.gradle.kts と build.gradle.kts は以下の内容になっています。 settings.gradle.kts rootProject.name = "spring-boot-vue-app" build.gradle.kts import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("org.springframework.boot") version "2.5.7" id("io.spring.dependency-management") version "1.0.11.RELEASE" kotlin("jvm") version "1.5.31" kotlin("plugin.spring") version "1.5.31" } group = "io.aucfan.sample" version = "0.0.1-SNAPSHOT" java.sourceCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } dependencies { implementation("org.springframework.boot:spring-boot-starter") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") } tasks.withType<KotlinCompile> { kotlinOptions { freeCompilerArgs = listOf("-Xjsr305=strict") jvmTarget = "11" } } tasks.withType<Test> { useJUnitPlatform() } 雛形プロジェクトはフラットに 1 つのプロジェクトのみで構成されていますが、これを以下のサブプロジェクトに分割していきます。 spring-boot-vue-app ├── web-flux-server (Spring Boot サブプロジェクト) │   ├── build.gradle.kts │   └── src ├── web-vue2-ui (Vue.js サブプロジェクト) │ └── build.gradle.kts ├── build.gradle.kts └── settings.gradle.kts Spring Boot プロジェクトのサブプロジェクト化 初期の Spring Boot プロジェクトを web-flux-server サブプロジェクトに移動します。以下のように web-flux-server ディレクトリを作成し、src ディレクトリを作成したディレクトリ下に移動します。(設定ファイルは YAML 形式で記述したいので、こっそり application.properties を application.yml に変更しています。) settings.gradle.kts の末尾にサブプロジェクトの情報を追記します。 settings.gradle.kts rootProject.name = "spring-boot-vue-app" include( "web-flux-server", ) プロジェクト直下の build.gradle.kts を以下の内容と、サブプロジェクト下の web-flux-server/build.gradle.kts に分割します。変更の概要は次のとおりです。 org.springframework.boot と io.spring.dependency-management のプラグインの行の末尾に apply false を追記 group と version の定義を allprojects 内に移動 dependencies 以降を subprojects 内に移動 java.sourceCompatibility 行を subprojects 内に移動 repositories を subprojects 内にもコピー subprojects 内で Kotlin 関連のプラグインを apply web-flux-server ディレクトリ内にサブプロジェクト用の build.gradle.kts を作成 親プロジェクト build.gradle.kts の subprojects から Spring 関連の dependencies と tasks を web-flux-server/build.gradle.kts へ移動 web-flux-server/build.gradle.kts で Spring 関連のプラグインを apply web-flux-server/build.gradle.kts に WebFlux や開発用、デプロイ用の dependencies と tasks を追加 build.gradle.kts import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { // apply false を付加してデフォルトでは術式を発動させないようにする id("org.springframework.boot") version "2.5.7" apply false id("io.spring.dependency-management") version "1.0.11.RELEASE" apply false kotlin("jvm") version "1.5.31" kotlin("plugin.spring") version "1.5.31" } allprojects { group = "io.aucfan.sample" version = "0.0.1-SNAPSHOT" } repositories { mavenCentral() } // サブプロジェクト共通設定 subprojects { // Kotlin 関連のプラグインを発動させる apply(plugin = "kotlin") apply(plugin = "org.jetbrains.kotlin.plugin.spring") java.sourceCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") } tasks.withType<KotlinCompile> { kotlinOptions { freeCompilerArgs = listOf("-Xjsr305=strict") jvmTarget = "11" } } tasks.withType<Jar> { // JAR ファイル名の基本部分が <プロジェクト名>-<サブプロジェクト名> となるように設定 archiveBaseName.set(listOf(rootProject.name, project.name).joinToString("-")) } } web-flux-server/build.gradle.kts // Spring 関連のプラグインを発動させる apply(plugin = "org.springframework.boot") apply(plugin = "io.spring.dependency-management") dependencies { // サブプロジェクトでは developmentOnly がそのままでは呼び出せないので強制召喚 val developmentOnly = configurations.getByName("developmentOnly") // WebFlux に必要 implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") // 開発ツール developmentOnly("org.springframework.boot:spring-boot-devtools") testImplementation("org.springframework.boot:spring-boot-starter-test") } tasks.withType<Test> { useJUnitPlatform() } // JAR に起動スクリプトを埋め込んで単体で実行可能にする tasks.withType<org.springframework.boot.gradle.tasks.bundling.BootJar> { launchScript() } Vue.js サブプロジェクトの追加 Spring Boot プロジェクトをサブプロジェクトに移動できたので、同様に Vue.js サブプロジェクトも追加していきます。 本来であればこちらは Node.js の領域なのですが、Gradle 領域を展開していきます。 Node.js まわりを Gradle で管理するために Gradle Plugin for Node を導入します。 以下のように web-vue2-ui ディレクトリを作成し、サブプロジェクト用の build.gradle.kts を作成します。 settings.gradle.kts に web-vue2-ui サブプロジェクトを追加します。 settings.gradle.kts rootProject.name = "spring-boot-vue-app" include( "web-flux-server", "web-vue2-ui", ) 親プロジェクト直下の build.gradle.kts に Gradle Plugin for Node を apply false で追加します。 build.gradle.kts import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("org.springframework.boot") version "2.5.7" apply false id("io.spring.dependency-management") version "1.0.11.RELEASE" apply false // Gradle Plugin for Node を追加 (デフォルトでは発動させない) id("com.github.node-gradle.node") version "3.1.1" apply false kotlin("jvm") version "1.5.31" kotlin("plugin.spring") version "1.5.31" } // (後略) Vue.js サブプロジェクト用の web-vue2-ui/build.gradle.kts に以下を記述します。ここでは、web-vue2-ui/.cache/ ディレクトリ下に Node.js、npm、yarn がダウンロードされて配置されるように設定しています。 web-vue2-ui/build.gradle.kts import com.github.gradle.node.NodeExtension import com.github.gradle.node.npm.proxy.ProxySettings // Gradle Plugin for Node を発動させる apply(plugin = "com.github.node-gradle.node") // サブプロジェクトでは node が呼び出せないので強制召喚 configure<NodeExtension> { download.set(true) version.set("14.18.1") npmVersion.set("6.14.15") yarnVersion.set("1.22.17") distBaseUrl.set("https://nodejs.org/dist") npmInstallCommand.set("ci") workDir.set(file("${project.projectDir}/.cache/nodejs")) npmWorkDir.set(file("${project.projectDir}/.cache/npm")) yarnWorkDir.set(file("${project.projectDir}/.cache/yarn")) nodeProjectDir.set(file("${project.projectDir}")) nodeProxySettings.set(ProxySettings.SMART) } Node.js、npm、yarn の本体が .cache ディレクトリに取得されるように Gradle Plugin for Node 経由で yarn コマンドを素振りしておきます。 $ ./gradlew :web-vue2-ui:yarn 以下のように nodejs、npm、yarn ディレクトリが設定したとおりに作成され、Node.js まわりを閉じ込めることに成功しました。 これで yarn コマンドが呼び出せるようになったので、Vue.js プロジェクトを作成していきます。今回は Vue CLI を使用して Vue.js 2 のシンプルなプロジェクトを作成してみます。一般的には Vue CLI は global にインストールすることがほとんどだと思いますが、ここでは使い捨てのプロジェクト内にインストールします。 以下のように web-vue2-ui ディレクトリに入って、yarn コマンドで使い捨てプロジェクトを作成します。 (Windows の場合は yarn のディレクトリが少し異なり、.cache\yarn\yarn-v1.22.17\yarn となるようです。) $ cd web-vue2-ui $ .cache/yarn/yarn-v1.22.17/bin/yarn init yarn init v1.22.17 question name (web-vue2-ui): spring-boot-vue-app-ui question version (1.0.0): 0.0.1 question description: Spring Boot + Vue application question entry point (index.js): question repository url: question author: question license (MIT): question private: success Saved package.json 作成された使い捨てプロジェクトに Vue CLI を追加します。前述のとおり、ふつうは yarn global add @vue/cli とするのですが、Vue CLI が Gradle 領域の外に逃げてしまうので、ここでは global を指定していません。 $ .cache/yarn/yarn-v1.22.17/bin/yarn add @vue/cli 一時的に vue コマンドが使用できるようになったので、以下のようにシンプルな Vue.js 2 のプロジェクトを作成します。 $ ./node_modules/.bin/vue create spring-boot-vue-app-ui Vue CLI v4.5.15 ? Please pick a preset: Default ([Vue 2] babel, eslint) ? Pick the package manager to use when installing dependencies: Yarn web-vue2-ui/spring-boot-vue-app-ui 下にプロジェクトが生成されたので、1 階層上に移動させます。その際に不必要になった一時的な使い捨てプロジェクト用の node_modules、package.json、yarn.lock は削除してしまいます。また、作成された Vue.js プロジェクト内に Git 用の隠しディレクトリ .git が作成されてしまうので、こちらは移動せずに削除し、.gitignore ファイルのみ移動しています。 $ rm -rf node_modules package.json yarn.lock $ mv spring-boot-vue-app-ui/* . $ mv spring-boot-vue-app-ui/.gitignore . $ rm -rf spring-boot-vue-app-ui .cache ディレクトリを Git 管理対象外にするために、web-vue2-ui/.gitignore の方に定義を追加しておきます。 web-vue2-ui/.gitignore .DS_Store node_modules /dist # 以下を追記 /.cache # (後略) 最終的に web-vue2-ui サブプロジェクトのディレクトリ構成は以下のようになります。 (作成した Vue.js プロジェクトによって構成は変わる場合があります。) vue コマンドで生成された package.json の内容は以下です。 web-vue2-ui/package.json { "name": "spring-boot-vue-app-ui", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "core-js": "^3.6.5", "vue": "^2.6.11" }, "devDependencies": { "@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-service": "~4.5.0", "babel-eslint": "^10.1.0", "eslint": "^6.7.2", "eslint-plugin-vue": "^6.2.2", "vue-template-compiler": "^2.6.11" }, "eslintConfig": { "root": true, "env": { "node": true }, "extends": [ "plugin:vue/essential", "eslint:recommended" ], "parserOptions": { "parser": "babel-eslint" }, "rules": {} }, "browserslist": [ "> 1%", "last 2 versions", "not dead" ] } 開発用のサーバーを起動して動作を確認しておきます。 $ .cache/yarn/yarn-v1.22.17/bin/yarn serve Web ブラウザで http://localhost:8080/ にアクセスすると以下の画面が表示されます。 この後で起動する Spring Boot の Web サーバーもデフォルトでは 8080 ポートを使用するので、衝突を避けるために開発サーバを Ctrl + C で停止しておきます。(./gradlew :web-vue2-ui:yarn_serve でも開発サーバーは起動できますが、Ctrl + C でプロセスが停止できないのでここでは使用していません。) Spring Boot と Vue.js のサブプロジェクト間の連携 親プロジェクトのディレクトリに戻り、Git リポジトリの初期化を実行しておきます。 $ cd .. $ git init web-vue2-ui/package.json を以下のように変更します。 web-vue2-ui/package.json { "name": "spring-boot-vue-app-ui", "version": "0.0.1", "private": true, "scripts": { "serve": "vue-cli-service serve --port 8081 --watch --mode development", "build": "vue-cli-service build --dest ../web-flux-server/src/main/resources/static/", "lint": "vue-cli-service lint" }, (中略) } vue-cli-service serve --port 8081 --watch --mode development の部分で Vue.js の開発サーバーを設定しています。--port 8081 でポート番号を 8080 から 8081 に変更して帳を下ろしています。これは前述のとおり Spring Boot の WebFlux サーバーもデフォルトでは 8080 ポートを使用するので、衝突を避けるためです。--watch でソースファイルの変更を監視するようにし、--mode development で開発環境であることを設定しています。 vue-cli-service build --dest ../web-flux-server/src/main/resources/static/ の部分では Vue.js のビルドファイルの出力先を、デフォルトの dist から Spring Boot サブプロジェクトのリソースディレクトリに変更しています。 また、親プロジェクトのバージョンに合わせて、version を 0.0.1 に変更しています。 親プロジェクトの .gitignore に以下を追加して、Vue.js ビルドファイルを Git 管理対象外にしておきます。 # (前略) # Vue.js build files /web-flux-server/src/main/resources/static/ Gradle Plugin for Node 経由で Vue.js サブプロジェクトをビルドしてみます。 $ ./gradlew :web-vue2-ui:yarn_build Spring Boot サブプロジェクト側の web-flux-server/src/main/resources/static/ ディレクトリ下に Web ページ用のファイルが生成されます。 Vue.js サブプロジェクトがビルドできたので、Gradle タスク間の依存関係を設定します。 まず、yarn build の前に yarn install が実行されるようにします。(毎回 yarn install を実行する必要はないのですが、すでに必要なパッケージがインストールされている場合は何もしないので、ここでは気にせずに設定してしまいます。) web-vue2-ui/build.gradle.kts import com.github.gradle.node.NodeExtension import com.github.gradle.node.npm.proxy.ProxySettings apply(plugin = "com.github.node-gradle.node") // サブプロジェクトでは node が呼び出せないので強制召喚 configure<NodeExtension> { download.set(true) version.set("14.18.1") npmVersion.set("6.14.15") yarnVersion.set("1.22.17") distBaseUrl.set("https://nodejs.org/dist") npmInstallCommand.set("ci") workDir.set(file("${project.projectDir}/.cache/nodejs")) npmWorkDir.set(file("${project.projectDir}/.cache/npm")) yarnWorkDir.set(file("${project.projectDir}/.cache/yarn")) nodeProjectDir.set(file("${project.projectDir}")) nodeProxySettings.set(ProxySettings.SMART) } // 以下を追記 // yarn build 前に yarn install を実行する (Gradle Plugin for Node 経由の実行なので _ を付加) tasks.getByName("yarn_build") { dependsOn("yarn_install") } 次に、Spring Boot サブプロジェクトでリソースが処理される前に Vue.js サブプロジェクトをビルドするようにします。 web-flux-server/build.gradle.kts import org.springframework.boot.gradle.tasks.bundling.BootJar apply(plugin = "org.springframework.boot") apply(plugin = "io.spring.dependency-management") dependencies { // サブプロジェクトでは developmentOnly がそのままでは呼び出せないので強制召喚 val developmentOnly = configurations.getByName("developmentOnly") implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") developmentOnly("org.springframework.boot:spring-boot-devtools") testImplementation("org.springframework.boot:spring-boot-starter-test") } tasks.withType<Test> { useJUnitPlatform() } tasks.withType<BootJar> { launchScript() } // 以下を追記 // リソース処理の前に Vue.js サブプロジェクトのビルドを実行 tasks.withType<ProcessResources> { dependsOn(":web-vue2-ui:yarn_build") } これだけでは Spring Boot サブプロジェクト内のリソースに配置されたファイルを公開できないので、IndexHandler.kt と IndexRouterConfiguration.kt ファイルを以下のように作成します。 IndexHandler.kt には以下の内容を記述します。 IndexHandler.kt package io.aucfan.sample.spring.boot.vue import org.springframework.beans.factory.annotation.Value import org.springframework.core.io.Resource import org.springframework.http.MediaType import org.springframework.stereotype.Component import org.springframework.web.reactive.function.server.ServerRequest import org.springframework.web.reactive.function.server.ServerResponse import org.springframework.web.reactive.function.server.bodyValueAndAwait @Component class IndexHandler { @Value("classpath:/static/index.html") private lateinit var indexHtml: Resource suspend fun index(request: ServerRequest): ServerResponse = ServerResponse.ok() .contentType(MediaType.TEXT_HTML) .bodyValueAndAwait(indexHtml) } IndexRouterConfiguration.kt には以下を記述します。 IndexRouterConfiguration.kt package io.aucfan.sample.spring.boot.vue import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.io.ClassPathResource import org.springframework.web.reactive.function.server.RouterFunctions import org.springframework.web.reactive.function.server.coRouter @Configuration class IndexRouterConfiguration { @Bean fun indexRouter(indexHandler: IndexHandler) = coRouter { GET("/", indexHandler::index) } fun staticResourceRouter() = RouterFunctions.resources("/**", ClassPathResource("static/")) } プロジェクトのビルドと実行 以上で準備ができたので、親プロジェクトのディレクトリで (テストを省略して) ビルドを実行します。 $ ./gradlew clean build -x test Spring Boot サブプロジェクトの web-flux-server の build/libs/ ディレクトリ下に spring-boot-vue-app-web-flux-server-0.0.1-SNAPSHOT.jar が具現化されます。 spring-boot-vue-app-web-flux-server-0.0.1-SNAPSHOT.jar を JVM に流し込んで実行してみます。 $ cd web-flux-server/build/libs/ $ java -jar spring-boot-vue-app-web-flux-server-0.0.1-SNAPSHOT.jar Web ブラウザで http://localhost:8080/ にアクセスすると以下の画面が再度表示されます。(この時点では前述の Vue.js の開発用サーバーは http://localhost:8081/ でのアクセスに変更されています。) ここまでのプロジェクト構築では Spring Boot の WebFlux サーバーに何の Web API も実装されていません。ここから Web API を Kotlin で実装し、Vue.js から axios で利用するように開発を進めていくことになります。 おわりに 今回ご紹介したプロジェクトで作成された Web アプリをデプロイする最もシンプルな方法は以下のような手順になります。 開発環境に JDK を導入 プロジェクトを git clone プロジェクトのディレクトリで ./gradlew clean build -x test 作成された JAR を稼働環境に配置 稼働環境に JRE を導入 JAR を実行 (サービス化も可能) 実際のプロジェクトではさらに、JAR ファイルの外側から読み込む環境依存の設定ファイルの配置や、Docker イメージにして自動デプロイなどもろもろ追加されていきます。 このように Node.js 関連の儀式を明示的に行うことなく、Gradle 領域内にうまく閉じ込めることができたので、今までどおり心穏やかにデプロイ作業を進めることができています。(とはいえ開発時は Node.js 領域内に入って戦うことになるのですが...) 他にも以下のようなメリットがあるので、今後の SPA プロジェクトでも適している場面があれば採用していこうと思います。 開発環境構築時も同様に Node.js 環境を別途用意する必要がない Vue.js 2 から Vue.js 3 や Nuxt.js、React、Next.js などに変更する場合も別領域サブプロジェクトを展開して対応可能 Node.js から見ると領域は外界と隔絶されているので、UI のサブプロジェクト内では異なる Node.js のバージョンを採用可能 最後に... 劇場版「呪術廻戦 0」 12 月 24 日公開です。ぜひ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

草を生やしてこどものモチベーションを上げる【おまけ】heroku編~svg画像をpng画像に変換する~

この記事は シリーズ物の番外編です。 pixelaの画像を使いたかったのだけれども、gasだけでsvgからpngに変換することが難しく。 パワープレイでpngに変換したお話 草を生やしてこどものモチベーションを上げる【その1】仕様〜設計編 草を生やしてこどものモチベーションを上げる【その2】実装編 草を生やしてこどものモチベーションを上げる【その3】テスト編 草を生やしてこどものモチベーションを上げる【おまけ】heroku編 草を生やしてこどものモチベーションを上げる【その4】運用編 課題 & 解決方法(再び) 子供(特に長男)が、「今何をやっているか?」を忘れがちで、宿題や準備が進みません。 宿題の途中で何か別のことをしたり、、、、 子供用のToDoリストを見える化してみます ポイントで釣ってどうにかならないだろうか、、、、と思っている 仕様 タスクを完了にするとポイントが貯まる 毎日のルーティンタスクを発生させる&おわったことをアーカイブする おまけ編、svgファイルの変換 上述の作業の中で、「Trelloのカード画像にpixela画像を差し込む」をやりたかったのですが、 pixelaは公式にはsvgを返却するAPIしかありませんでした。 というわけで、自動テストで培った技術を利用して(嘘です。puppeteerもkoaも初めて使いました。初心者です。) 公式の画面をキャプチャした画像を返却するだけのWebアプリを急造りしました。 利用技術 heroku puppeteer koa 参考にした記事 - HerokuにPuppeteerの実行環境を構築する - koaでサーバ開発クイックスタート コード index.js const Koa = require('koa'); const Router = require('koa-router'); const bodyParser = require('koa-bodyparser'); const app = new Koa(); const router = new Router(); const mime = require('mime-types') const fs = require('fs'); router.get('/', async (ctx, next) => { const pixela_user = ctx.request.query['pixela-user'] const pixela_graph = ctx.request.query['pixela-graph'] url = "https://pixe.la/v1/users/" + pixela_user + "/graphs/" + pixela_graph + "?mode=short&appearance=dark" const filename = getNowStr()+'.png' await crawler(filename, url); // クローラーの実行 var mimeType = mime.lookup(filename); ctx.response.set("content-type", mimeType); const src = fs.createReadStream(filename); ctx.body = src; }); app.use(router.routes()); app.use(router.allowedMethods()); app.use(bodyParser()); app.listen(process.env.PORT || 3000); // ここからはクローラーのロジック const puppeteer = require('puppeteer'); // Heroku環境かどうかの判断 const LAUNCH_OPTION = process.env.DYNO ? { args: ['--no-sandbox', '--disable-setuid-sandbox'] } : { headless: false }; const crawler = async (filename,url) => { const browser = await puppeteer.launch(LAUNCH_OPTION) const page = await browser.newPage() await page.goto(url) // スクリーンショットを保存 img = await page.screenshot({path: filename}) await browser.close() } function getNowStr(){ var date = new Date(); return date.toTimeString() } 利用方法 http://xxxxxx.herokuapp.com/?pixela-user=who&pixela-graph=something にアクセスすると、png画像が表示されます。 Trelloに登録する場合はURLで登録できるので、上述のURLをカバー画像を更新するURLとしてAPI経由でPOSTすればOKです。 まとめ herokuにselenium載せるのはやったことあったのですが、puppeteerも意外と簡単にできるんじゃ、、、、と思ってやってみましたがとても簡単でした。 お勉強になりました。 勢いで作ってみましたが、、、、β版があるとお知らせいただきました。 公式の方から、「アルヨ!」とTwitterでレスいただきました。 Qiitaに書いてもいいよ!と言っていただいたのでこちらに書いておきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

discord.js 連投対策をする

今回はDiscord.jsで連投対策をしていきます 必要なもの discord.js@12(改良すれば13でもいい) Node.js12以降 コード const discord = require('discord.js'); const client = new discord.Client() const spam = []; //spamオブジェクト作る const maxtime = 1; //時間を定義しておく client.on("message", message => { //メッセージイベントを発火 if (message.author.bot) return; //BOTの場合は処理を実行しない if (new Date() - spam[message.author.id] <= maxtime * 1000) { //現在の時刻から保存された時刻を引いた数がmaxtime(ミリ秒なので*1000している)以下だった場合実行 //ここから下が連投された場合の処理 message.reply("YOURSPAM!!") //メンション付きで警告 }else{ //スパムでない時の処理 } spam[message.author.id] = new Date(); //userオブジェクトにchannelIDのプロパティを作り現在の時刻を代入する }) client.login("YOURTOKEN"); //clientをloginさせる 解説 個別に質問したい場合などはBURI#9515まで
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsのeventemitterとそのデザインパターンについてまとめてみた

はじめに この記事ではeventEmitterとeventEmitterに関わるデザインパターンについて紹介しています。 evenEmitterをマスターすることで、よりプロのプログラマー(略してプロプロ)になれると思います。 eventemitterとは まずイベントエミッターとはなにか説明します。 これ、独自にイベントを作成し、指定したタイミングに関数を発火することができます イベント駆動開発でよく用いられます。 イメージで言うと、フロントエンドでいうと、ボタンを押したときにonpushに登録された関数が発火しますよね。それです! eventemitterを用いると、自作でイベントを作って、好きなタイミングで発火することができるようになります。 Node.jsではeventemitterがストリームの裏側で作られ、実行されています。 EventEmitterの実行例 const EventEmitter = require("events") const eventEmitter = new EventEmitter() const bakuhatsu=() => { console.log("ドッカーーーン") } eventEmitter.on("爆発",bakuhatsu) eventEmitter.once("爆発", () => { console.log("一回だけドッカーーーン") }) eventEmitter.emit("爆発") //ドッカーーーン //一回だけドッカーーーン eventEmitter.emit("爆発") //ドッカーーーン console.log("爆発解除") eventEmitter.off("爆発",bakuhatsu) eventEmitter.emit("爆発") //(何もなし) EventEmitter の主要なインスタンスメソッド 最初に簡単に用法をまとめておきます。 on(イベント名、関数名)でイベントを登録し、 emit(イベント名)でイベントに指定した関数を実行! off(イベント,関数名)でイベントに登録した関数の削除 on(event,listener) 指定したイベントに新しいリスナを登録します。 (第一引数に文字列 イベントの名前(任意)  第二引数にコールバック関数 第一引数に登録したイベントが呼ばれたときに発火する) 例 eventEmitter.on("爆発",()=>{console.log("ドッカーーーン")}) once(event,listener) 指定したイベントに新しいリスナを登録します。 (第一引数に文字列 イベントの名前(任意)  第二引数にコールバック関数 第一引数に登録したイベントが呼ばれたときに発火する) これはon()メソッドと同様です。 このリスナは、イベントが一回発行されるとリスナが削除されます。なので、そのイベントの最初の一回しか第二引数に登録したコールバック関数が実行されません。 例 eventEmitter.once("爆発",()=>{console.log("一回だけドッカーーーン")}) off(event,listener) on()メソッドで登録したイベントの特定のリスナを削除します。 (第一引数に文字列 イベントの名前(任意)  第二引数にコールバック関数 on()メソッドで登録した第二引数) 例 eventEmitter.off("爆発",()=>{console.log("ドッカーーーン")}) emit(event,[...args]) on()やonce()メソッドで追加したイベントを発火します。 (第一引数に文字列 イベントの名前(任意)  第二引数には引数 on()やonce()で登録したコールバック関数に引数が必要な場合のみ使用する) 例 eventEmitter.emit("爆発") //今回は引数なし 使い方は、以上です ここからは、eventemitterを利用するデザインパターンについて説明していきます。 まずかんたんにデザインパターンについて説明しておきます。 デザインパターンとは オブジェクト指向言語で使われる設計パターンのことです。オブジェクト指向では再利用性の高いクラスが必須です。 ではどのような方向性で設計していけばよいのか。。。。 それを示してくれるのがデザインパターンです。 代表的なデザインパターンは23種類あります。(今回は詳しく説明しない) ここではeventemitterで用いられるObserverパターンについて解説していきます。 Observerパターンについて ObserverパターンはNode.js以外でもよく用いられる代表的なデザインパターンです。英語のObserverとは、観察者や監視役という意味があります。 このパターンでは、監視対象に対して発生した何らかのイベントが、監視役(Observer)に逐一通知されます。 監視役(Observer)はあらかじめ監視対象に対して監視を行うための登録処理を行い、監視対象はイベントの発生タイミングで登録済みの監視役(Observer)に対して通知処理を実行します。 それぞれのSubjectには複数のObserverを登録できます。 簡単な例を上げて説明します。 中学校を想定します。ここでは、一人の生徒が監視対象です。先生は一人の生徒「たかひろ」くんに対してそれぞれの処理を仕込みます。 それぞれの先生は、自分の専門の処理を「たかひろ」くんに仕込みます。 そして「たかひろ」くんは登録された処理に従って、 数学の勉強がわからないときは数学の先生に、 野球の打撃の成績が悪いときは、筋肉の先生に、(「たかひろ」くんは野球部) 熱があって、吐き気もするときは、保健室の先生に、 通知を出します。 ここまでがObserverパターンです。 先生が生徒に処理を仕込み、生徒が通知を出すところまででひとくくりです。 ここでこの例に従ってeventemitterのコードを書いてみます。 const EventEmitter = require("events") const eventEmitter = new EventEmitter() const helpme=(teacher)=>{ console.log(teacher+"、助けてください") } eventEmitter.on("数学がわからない", helpme) eventEmitter.on("最近打率が悪い", helpme) eventEmitter.on("体調が悪い", helpme) // 先生(Observer)によってイベントが仕込まれる class takahiro { static suugaku_nervous() { eventEmitter.emit("数学がわからない","数学得意な先生") } static sports_nervous() { eventEmitter.emit("最近打率が悪い","筋肉ムキムキな先生") } static condition_nervous() { eventEmitter.emit("体調が悪い","保健室の先生") } } //「たかひろ」くんが先生にイベントを通知する takahiro.suugaku_nervous() //***実行結果*** //数学得意な先生、助けて! デザインパターンとeventemitterの話はここまでです。 実際のコード 最後に実際に使われているコードの紹介をしておきます。 const http = require("http") var fs = require("fs") //serverのeventEmitterを作成 const server = http.createServer() //requestというイベントに関数を仕込む //serverにアクセスがあったときに発火する server.on("request", (req, res) => { fs.readFile("./helloWorld.html", "utf-8", function (error, data) { res.writeHead(200, { "Content-Type": "text/html" }) res.write("requestに登録されたイベント発火") res.write(data) res.end() }) }) //listeningというイベントに関数を仕込む //これはserver.listen(8000)したときに発火する server.on("listening", (res, req) => {}) //errorというイベントに関数を仕込む //エラーが起こったときに発火する server.on("error", err =>{ console.error(err); }) //closeというイベントに関数を仕込む //これはserver.close()したときに発火する(ここではserver.close()はこめんとあうとした) server.on("close", () => {}) server.listen(8000) // server.close() helloEarth.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>Hello,Earth! And,Hello universe!</h1> </body> </html> localhost:8000にアクセスした結果 何気なくhttpモジュールで使っていた関数にもeventEmitterが仕込まれていました。自分が使っている関数や処理がeventEmitterのものかどうか見極める事ができれば、プログラミングのレベルもぐんぐん上がっていくと思いますl 最後に Streamの記事を書くつもりがいつマニカeventEmitter の記事を書いていました。 次回はStreamの記事を書きます!! おいらの記事を最後まで見てくれてありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsを安全にアップデートするためにやったこと

はじめに はじめまして、リンクアンドモチベーションのプラットフォームチームに所属している菊池です。 弊社で開発しているモチベーションクラウドのフロントエンドビルド環境は長らくNode.js 8を使っており、「さすがにやばい」という感覚と、シンプルにモダンなライブラリをインストール出来ない、セキュリティリスクなどの観点から当時のActive LTSである14系へのアップデート作業をすることになりました1。 しかしながら、(みなさんもご存知の通り)Node.jsのアップデート作業は簡単には終わりませんでした。 今回は1度の失敗を経て、安全にアップデートするためにやったことを紹介したいと思います。 ※ この記事はモチベーションクラウドシリーズアドベントカレンダー2021の3日目の記事です。 1度目のアップデート 1度目のアップデート作業ではNode.jsのアップデートに合わせて、関連するライブラリのアップデートも行いました。 というのもNode.jsのバージョンが変わるとうまくビルドできないライブラリがあり(たとえばnode-sass)、それらを含めてライブラリ全体を更新するためです。 具体的にはyarn upgradeコマンドを使用しました。 yarn upgrade このコマンドはpackage.jsonに指定されている依存パッケージをsemverの範囲内で最新のバージョンに更新します。 また同時期にESLintの整備も進めており、Node.jsアップデート後に対応可能なルールの導入と、そのコード修正も合わせて行いました。 これは先のライブラリ更新に伴ってUIのリグレッションテストを実施した方が良いという判断もあり、どうせリグレッションテストをするなら一緒に混ぜ込んでしまおう、と考えたのです。 軽く動作確認したところではいい感じに動作し、「お、いけそう」と思っていたのですが、、 失敗する このアップデート作業は結果的には失敗しました。 リグレッションテストを実施したものの、リリース後に一部のUI崩れや挙動不具合が判明したのです。 テストに漏れがあったとも言えますが、それ以前に以下の過程に問題があったと考えました。 ① Node.jsのアップデートに合わせてライブラリの全更新を行ったこと フロントエンドの依存パッケージを一括更新したことで、UIライブラリに影響が及んでしまいました。 具体的には、Vue-Multiselectというライブラリを使用しており、ライブラリのCSSを読み込んでいた箇所がアップデートによってパスが変わり、上手く読み込めなくなっていました。 ② 別の修正を「検証方法が同じ」という理由で一緒に混ぜてしまったこと 今回の場合は「Node.jsをアップデートする作業」に「ESLintへの対応」を混ぜてしまいましたが、この2つは目的も緊急度も全く異なります。 検証という過程が同じという理由で混ぜてしまうと、影響範囲が余計に広がるだけでなく、問題があって切り戻したときに(実際に今回それが起きましたが)いずれもやり直さなくてなりません。 そこで、より安全にアップデートするために「影響を減らす」方法を再検討しました。 どうなっていれば影響を減らせるか 改めて、影響範囲が小さくなる理想パターンを洗い出しました。それが以下の表です。 Node.jsはあくまでビルド時の実行環境であり、そのバージョンが変わっても出力結果(HTML/JS/CSS/etc..)が同じであればブラウザでの動作は変わらないということです。 出来るだけ影響を減らす(表の①に近づける)ため、以下の対応方針を取ることにしました。 アップデートに必要な最小限のライブラリのみアップデートする Node.jsアップデート以外の修正は別で対応する 最小限のライブラリのみアップデートする 色々と調査した結果、Sassのビルドツールであるnode-sassだけアップデートすれば良いことが分かりました。 package.json { "devDependencies": { ... "node-sass": "^4.14.1", ... } } ただし、単にpackage.jsonを上記のように変えてもアプリケーション直下のnode_modules/node-sassは更新されますが、依存パッケージが使用しているnode-sassまでアップデートすることは出来ません。 この「依存パッケージの依存パッケージ」のバージョンを指定するためにはYarnのselective version resolutionsが利用できます2。 package.json { "devDependencies": { ... "node-sass": "^4.14.1", ... }, "resolutions": { "node-sass": "^4.14.1" } } これでnode-sassのみアップデートすることが出来ました。 2度目のアップデート この状態でビルドを行い、ビルド出力結果を比較しました。 その結果、差分が発生したのは以下の内容でした。 ① 一部の正規表現からバックスラッシュのエスケープがなくなった Before match(/([^\\/\\\\]+)\\.vue$/) After match(/([^/\\\\]+)\\.vue$/) JSでは正規表現の[]の中ではスラッシュはエスケープ不要なので、おそらくNode.jsのバージョンアップに伴い出力が修正されたのだと思われます。 ② CSSのtransparent表現が変わった Before box-shadow: 0 0 0 0 transparent; After box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); 実質CSSの表現としては変わらないため、おそらくnode-sassのバージョンアップに伴い出力が変更されたのだと思われます。 とまあ、差分ゼロとまではいかないものの、コードの出力結果の差分を大分減らすことができました。 この時点でほぼほぼ問題は起きないだろうと考えていましたが、 とはいえ変更差分があるにはあるので、念の為リグレッションテストをして、2度目のリリースを行いました。 結果、問題も発生せずに無事リリースを完了することが出来ました。 まとめ 今回のNode.jsアップデート作業で改めて感じたのが「小さくリリースすること」「影響範囲を減らすこと」の大事さです。 つい「どうせならあれも入れよう」「あれも一緒にやっちゃおう」となってしまいがちですが、本来の目的や緊急度を整理して、本当に一緒にやるべきか、後から対応できないか、考えてみるのが良いと思います。 2021年12月現在のActive LTSは16となっています。https://nodejs.org/en/about/releases/ ↩ npmでもライブラリを使うことで可能なようです。https://github.com/rogeriochaves/npm-force-resolutions ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm実行時に /usr/bin/env: ‘bash\r’: No such file or directory

事象 WindowsでWSLでnpm実行時に下記エラーが発生。 $ npm -v /usr/bin/env: ‘bash\r’: No such file or directory コマンドプロンプトでは問題ない。 >npm -v 8.1.0 原因 WSL上では、Windowsにインストールしたものは稼働せず、WSL実行用にインストールが必要。 対応 上記リンクに従い実行 nvmのインストール $ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash 確認 $ command -v nvm nvm Node.js LTS版 インストール $ nvm install --lts 確認 $ nvm ls -> v16.13.1 default -> lts/* (-> v16.13.1) iojs -> N/A (default) unstable -> N/A (default) node -> stable (-> v16.13.1) (default) stable -> 16.13 (-> v16.13.1) (default) lts/* -> lts/gallium (-> v16.13.1) lts/argon -> v4.9.1 (-> N/A) lts/boron -> v6.17.1 (-> N/A) lts/carbon -> v8.17.0 (-> N/A) lts/dubnium -> v10.24.1 (-> N/A) lts/erbium -> v12.22.7 (-> N/A) lts/fermium -> v14.18.2 (-> N/A) lts/gallium -> v16.13.1 $ npm --version 8.1.2 実行できるようになった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

卒業した推しメンに電話して次の推しメン候補をSMSで教えてもらう

長らく応援してきた乃木坂46の大園桃子さんが2021年9月4日にグループを卒業、そして芸能界を引退しました。毎回全力投球していた握手会(最近ではオンラインのお話し会)もなくなってしまい、今は推しロスで夢も希望もない毎日を過ごしています。 次の推しメンは見つかるのだろうか。。。 そんな気持ちになれないよ...と未練たらたらな自分から脱却するためにTwilioで「推しメンに電話すると卒業の旨が告げられて次の推しメン候補が送られてくる」サービスを作りました。デモはこちら(音声Onにしてください)。 仕組みはとてもシンプルです。Twilioの電話番号を1つ購入してTwilio Studioで次のようなフローを組みました。 以下3つだけです。 電話がかかってきたらSay/Playでアナウンスを流す Functionでランダムに現役の乃木坂メンバーを選ぶ Send Messageで選んだメンバーの名前をSMSで送る Functionの中身もシンプルです。 const got = require('got'); exports.handler = function(context, event, callback) { (async () => { try { const response = await got('http://xxxxx.sakamichi46.com/sakamichi46api/api/nogizaka46/profile'); const members = JSON.parse(response.body); callback(null, members[Math.floor( Math.random() * members.length )].name); } catch (error) { console.log(error); } })(); }; 乃木坂46のメンバーは自作のWeb APIで取得してます。 推しメンに電話!!とテンションがあがった後、淡々としたアナウンスで卒業を改めて認識させられて悲しみが沸いたあと、次の推しメン候補がきて、一歩前に進んでみようと思う。そんな自分のためだけの自己満サービスです。 ちなみにデモ動画で次の推しメン候補として送られてきた「新内眞衣」さんは来年1月末で卒業です orz 今年は乃木坂ちゃんの卒業ラッシュが辛すぎました。そろそろ5期生が入るので来年からの新生乃木坂に期待です...!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

COCORO AIR対応家電を、Node.jsから操作する話

この記事は記事は道メン(身内向け)アドベントカレンダーの2日目の記事です。 はじめに Qiitaの記事投稿は100万年ぶりです。なずなです。 ある程度いろいろなプログラムを書けるようになってきたので、そろそろアウトプットが必要な頃合いでしょうということで投稿します。 空気清浄機が欲しかったので買いました。 購入したのはシャープの加湿空気清浄機 KI-LS40-Wです。 特徴は気化式の加湿機能を備えている点と、COCORO AIRに対応にしており、外部から操作が可能な点です。 ...今、「外部から操作が可能」と言いました。 つまり、APIが存在してるということです。 botから制御ができたら、さらに便利になるのではないでしょうか? ということで早速調べたのですが、公式に提供されているものはなく、実際にスマートフォン等の端末のCOCORO AIRアプリの通信を覗き見て解析する必要がある、ということがわかりました。 調べた結果ヒットした記事はこれだけ。 シャープの加湿空気清浄機をアプリ外から操作する(https://rcmdnk.com/blog/2021/03/03/computer-iot-raspberrypi/) 記事にあったのは私の触ったことが無いPythonのサンプルコードでした。 「じゃあPythonを勉強しよう」でもよかったのですが、いろんな都合でNode.jsばかり触っているのと時間があまり取れないので「じゃあNode.js版も作ろう」ということに。 プロキシを利用していろいろ取得 mitmproxyを利用して通信を覗き見する部分は完全に同一であるためスキップします。 ちなみにWSLでmitmproxy導入して見てやろうと思ったらポートフォワーディング関係で沼った話もありますが、それは別の機会に。素直にWindows版使うのがいいかもしれません。 コード コードはこちら(Github) npm installして、state_sample.jsonやら.env_sampleやらのファイル名から末尾の_sampleを消していい感じにします。 先に挙げたページを見ると、何を取得すればいいのかわかるかと思います。 流れ JSESSIONIDの取得(get_JSESSIONID)→コマンドをシャープのサーバーへ送信(sendCommand) sendCommandの引数として、JSESSIONIDと操作用のコードを渡しています。 非同期処理周りなにもわからないのでGithubCopilotの力を借りています。 サンプルコードでは、毎回JSESSIONIDを取得しているので、あまりスマートなやり方ではないです。有効期限切れるまで保持して切れたら更新する、とか前回使用したJSESSIONIDを再利用してみて、エラーが返ったら再取得して再試行するとかがいい気がします。公式のアプリは後者のやり方をしてたはず。 応用例 私はIFTTTとDiscordBotと組み合わせて、毎朝おやすみモードから自動運転モードに切り替えるような使い方をしています。 最後に あんまり内容が濃いものではなかった気もしますが、ここまで。ありがとうございました。 最後、関係ないんですがおすすめのReact入門の記事や書籍あれば教えてほしいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SHARPのCOCORO AIR対応家電を、Node.jsから操作する話

この記事は記事は道メン(身内向け)アドベントカレンダーの2日目の記事です。 はじめに Qiitaの記事投稿は100万年ぶりです。なずなです。 ある程度いろいろなプログラムを書けるようになってきたので、そろそろアウトプットが必要な頃合いでしょうということで投稿します。 空気清浄機が欲しかったので買いました。 購入したのはシャープの加湿空気清浄機 KI-LS40-Wです。 特徴は気化式の加湿機能を備えている点と、COCORO AIRに対応にしており、外部から操作が可能な点です。 ...今、「外部から操作が可能」と言いました。 つまり、APIが存在してるということです。 botから制御ができたら、さらに便利になるのではないでしょうか? ということで早速調べたのですが、公式に提供されているものはなく、実際にスマートフォン等の端末のCOCORO AIRアプリの通信を覗き見て解析する必要がある、ということがわかりました。 調べた結果ヒットした記事はこれだけ。 シャープの加湿空気清浄機をアプリ外から操作する(https://rcmdnk.com/blog/2021/03/03/computer-iot-raspberrypi/) 記事にあったのは私の触ったことが無いPythonのサンプルコードでした。 「じゃあPythonを勉強しよう」でもよかったのですが、いろんな都合でNode.jsばかり触っているのと時間があまり取れないので「じゃあNode.js版も作ろう」ということに。 プロキシを利用していろいろ取得 mitmproxyを利用して通信を覗き見する部分は完全に同一であるためスキップします。 ちなみにWSLでmitmproxy導入して見てやろうと思ったらポートフォワーディング関係で沼った話もありますが、それは別の機会に。素直にWindows版使うのがいいかもしれません。 コード コードはこちら(Github) npm installして、state_sample.jsonやら.env_sampleやらのファイル名から末尾の_sampleを消していい感じにします。 先に挙げたページを見ると、何を取得すればいいのかわかるかと思います。 流れ JSESSIONIDの取得(get_JSESSIONID)→コマンドをシャープのサーバーへ送信(sendCommand) sendCommandの引数として、JSESSIONIDと操作用のコードを渡しています。 非同期処理周りなにもわからないのでGithubCopilotの力を借りています。 サンプルコードでは、毎回JSESSIONIDを取得しているので、あまりスマートなやり方ではないです。有効期限切れるまで保持して切れたら更新する、とか前回使用したJSESSIONIDを再利用してみて、エラーが返ったら再取得して再試行するとかがいい気がします。公式のアプリは後者のやり方をしてたはず。 応用例 私はIFTTTとDiscordBotと組み合わせて、毎朝おやすみモードから自動運転モードに切り替えるような使い方をしています。 最後に あんまり内容が濃いものではなかった気もしますが、ここまで。ありがとうございました。 最後、関係ないんですがおすすめのReact入門の記事や書籍あれば教えてほしいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む