- 投稿日:2020-02-14T23:38:01+09:00
[Node.js][JavaScript]CryptoAPIの違いでハマったのでまとめ
Overview
Node.jsはJavaScriptで書けるから、Webの中では"Write once, run anywhere"的な美味しいこともある。
しかし、各環境にbuiltinされているAPIを使ったときはそうはいかない時がある。
今回は暗号化のCryptoで不覚にも1日ハマったのでその記録を残しておく。Target reader
- Node.jsで暗号化したデータをブラウザで復号化したいと思っている方。
Prerequisite
- AESの概要は理解していること。
- 今回はAES256-CBCを使用する。
- 記憶が正しければAES192はブラウザのAPIでサポートされていない旨のエラーが出たため。
Body
どうして片方のAPIで統一しないの?
これはいい質問だ。実際のところ、Node.jsのcryptoをブラウザで実行したことがある。
どうして採用されなかったのか?なぜなら100KBほどバンドルサイズが増えたから。
詳しく知りたい場合は、この方の記事を読んでみるといいかもしれない。
https://engineering.mixmax.com/blog/requiring-node-builtins-with-webpack一言でいうと、以下のブラウザ用cryptoがバンドルされてしまったため。
https://github.com/crypto-browserify/crypto-browserifyブラウザのAPIを使えば100KBのバンドルを回避できるのだから、別々のAPIを使用するのは当然といってもいい。
もしかしたら差分を吸収するI/Fのパッケージがあるかもしれないが調べてないNode.jsのCrypto
基本的には公式ドキュメントのコードがそのまま使用できる。
https://nodejs.org/api/crypto.html#crypto_class_cipher大して見どころはないが、私のソースも載せておく。
ライブラリの方のソース。nodeCrypto.jsimport crypto from 'crypto'; function createCipheriv(algorithm, key, iv) { console.log("crypt.key:", key); console.log("crypt.iv:", iv); const cipher = crypto.createCipheriv(algorithm, key, iv); return cipher; } function createDecipheriv(algorithm, key, iv) { console.log("decrypt.key:", key); console.log("decrypt.iv:", iv); const decipher = crypto.createDecipheriv(algorithm, key, iv); return decipher; } async function cryptByNodeApi(cipher, plainText) { console.log('平文: ' + plainText); let encrypted = cipher.update(plainText, 'utf8', 'hex'); encrypted += cipher.final('hex'); console.log('暗号化:', encrypted); return encrypted; } async function decryptByNodeApi(decipher, encrypted) { // 復号 let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); console.log('復号化: ', decrypted); return decrypted; } export { createCipheriv, createDecipheriv, cryptByNodeApi, decryptByNodeApi }実行部分のソースの抜粋。
import { cryptByNodeApi, decryptByNodeApi, createCipheriv, createDecipheriv } from './libs/nodeCrypto'; export default function App() { async function handleClickNodeToBrowser() { const algorithm = 'aes-256-cbc'; const key = crypto.randomBytes(32); const iv = Buffer.alloc(16, 0); // NodeのCryptoAPIで暗号化 const cipher = createCipheriv(algorithm, key, iv); const encrypted = Buffer.from(await cryptByNodeApi(cipher, plainText), "hex").buffer; // Nodeのcipherに該当するものを作る const keyForbrowser = await importKeyByBrowserApi(key); // ブラウザのCryptoAPIで復号化 await decryptByBrowserApi(encrypted, keyForbrowser, iv); } };注意点として以下のことがあげられる。
- 公式ドキュメントとは異なりAESの256bit(32Byte)なのでキーは32Byteになる。
- IVは16Byte固定。
- ソースでは0固定にしているが本来は値を与えること。
- cryptByNodeApi()ではhexにしているため、ブラウザAPIへの入力に合わせるためArrayBufferを取り出している。
ブラウザAPIの方はArrayBufferを与えないとエラーになるが、実際何がArrayBufferでなくてはいけないのかわからなくてハマった
SubtleCrypto.decrypt()のドキュメントを見るとBufferSourceとなっており、リンク先に行かないと気が付かない罠。data is a BufferSource containing the data to be decrypted (also known as ciphertext).
BrowserのCrypto
基本的には公式ドキュメント先のコードがそのまま使用できる。
https://github.com/mdn/dom-examples/blob/master/web-crypto/encrypt-decrypt/aes-cbc.js大して見どころはないが、私のソースも載せておく。
ライブラリの方のソース。browserCrypto.jsasync function cryptByBrowserApi(plainText, key, iv) { console.log('平文: ' + plainText); console.log("crypt.key:", key); console.log("crypt.iv:", iv); const encrypted = await window.crypto.subtle.encrypt( { name: "AES-CBC", iv }, key, new TextEncoder().encode(plainText) ); console.log('暗号化:', encrypted); console.log('暗号化:', Buffer.from(encrypted).toString('hex')); return encrypted; } async function decryptByBrowserApi(encrypted, key, iv) { console.log("decrypt.encrypted:", encrypted); console.log("decrypt.key:", key); console.log("decrypt.iv:", iv); const decrypted = await window.crypto.subtle.decrypt( { name: "AES-CBC", iv, }, key, encrypted ); const plainText = new TextDecoder().decode(decrypted); console.log('復号化:', plainText); return plainText; } async function importKeyByBrowserApi(rawKey) { const key = await window.crypto.subtle.importKey( "raw", rawKey, "AES-CBC", true, ["encrypt", "decrypt"] ); return key; } async function generateKeyByBrowserApi() { const key = window.crypto.subtle.generateKey( { name: "AES-CBC", length: 256 }, true, ["encrypt", "decrypt"] ); return key; } export { cryptByBrowserApi, decryptByBrowserApi, generateKeyByBrowserApi, importKeyByBrowserApi }注目ポイントは、importKey()とdecrypt()の二つを使用しないといけないところ。
importKey()であっているのだろうか?rawKeyは正しく指定しているのか?ArrayBufferじゃないといけないのエラーって何?
複数の誤りでエラーポイントが特定できず完成までに1日も消耗してしまった。rawの中身については公式ドキュメントのソースの1行目に具体的にある。
https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#Rawconst rawKey = window.crypto.getRandomValues(new Uint8Array(16));しかし、次の行で
function importSecretKey(rawKey) {
ともなっており、rawKeyは引数しかないと思ってしまった。
Uint8Array(16)ってちゃんとあるのに
ブラウザで暗号化する場合、key指定不要のgenerateKey()を利用するため、Node.jsのkeyを使えるのかもその時はわかっていなかった。加えて生成されるCryptoKeyの中身が見れないのが、問題解決を遅らせた。
CryptoKeyがおかしいのか、decrypt()がおかしいのか見当がつかなかった。
これを間違わなければ1時間もあれば終わるようなもの。。。Conclusion
JavaScriptは型を宣言しないとはいえ、builtinAPIはTypeScriptの型が見みれる。(複数の入力があるためどれがどれに対応するかはわからないが)
それにもかかわらず何とかなるだろうと、詳しく見ずにリトライを繰り返したのがよくなかった。丁寧に見ていけば大丈夫…なはず。Node.jsは怖くない
Have a great day!
Appendices
今回のコードをブラウザで動かせるようにしたソースコード。
自分用なので少し不親切なのに注意。ブラウザで動作確認(Node.APIはbrowserifyが使用される)
terminalnpm start
純粋なNode.APIでの確認
terminalnode -r esm ./src/cli.js
- 投稿日:2020-02-14T23:36:59+09:00
vue-routerで親子間でのイベント処理
概要
vue-router使用時の「子→親」あるいは「子1→子2」のイベント渡しの記述方法について自分用メモ
ソースコードは簡略化したもので、ちゃんとテストしていないので動かないかも
サンプルコード
this.$router.app.$on
を扱うのがポイント。それ以外は通常のイベントバスのやり方と同じ。主要な部分
app.js//(前略) const app = new Vue({ router, // これが「ルーターインスタンスを root インスタンスに router オプションとして」かな? el: '#app', render: h => h(App) });親コンポーネント
parent.vue<template> <div> <child1></child1> <child2></child2> </div> </template> <script> // 子2つを配置するのみ import component_child1 from './child1.vue' import component_child2 from './child2.vue' export default { components: { 'child1': component_child1, 'child2': component_child2 } }; </script>子コンポーネント(2つ)
目標とする動作は下記。
- child1でボタンを押すと
test_method_child1
が実行される。test_method_child1
内でtest_event_global
がスローされ、child1は非表示。- child2でイベントの解除(重要、後述。参考資料を参照。)
- child2ではイベントを
test_event_global
をtest_event_child2
としておく。- child2でイベントを受け取ったら、
v-on:test_event_child2
でtest_method_child2
が実行。- child2が表示され、Hello Worldが見える。
child1.vue<template> <div v-show="visible_child1"> <button v-on:click="test_method_child1">Run Test</button> </div> </template> <script> export default { data() { return { visible_child1 : true // 表示/非表示制御 }; }, methods: { test_method_child1() { this.visible_child1 = false; // イベントバスを this.$router.app として、イベントをスロー this.$router.app.$emit('test_event_global'); } } } </script>child2.vue<template> <div v-on:test_event_child2="test_method_child2" v-show="visible_child2"> Hello world !! </div> </template> <script> export default { data() { return { visible_child2 : false // 表示/非表示制御 }; }, mounted() { // イベントバスを this.$router.app として、コンポーネントのイベントに紐づけている this.$router.app.$off('test_event_global'); this.$router.app.$on('test_event_global', this.test_event_child2); }, methods: { test_method_child2() { this.visible_child2 = true; } } } </script>注意点
解除が行われない、というより .$off をしないと蓄積するようです
ページ遷移してcreatedやmountedが何度も呼ばれると、そのたびに蓄積されてしまうらしい。
上記は例。$onでメソッドを直接叩くこともできるはず。
表示/非表示みたいなことを実現するだけならv-onは取ってしまってchild2.vue<div v-show="visible_child2"> (中略) this.$router.app.$on('test_event_global', this.test_method_child2);とすることも。
参考資料
- 投稿日:2020-02-14T20:23:21+09:00
初心者によるプログラミング学習ログ 239日目
100日チャレンジの239日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
239日目は
おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) February 13, 2020
239日目
・youtubeで、javascriptのfizzbuzz問題模写
・udemyで、javascript講座
・photoshopで、バナー作成模写#早起きチャレンジ#駆け出しエンジニアと繋がりたい#100DaysOfCode
- 投稿日:2020-02-14T19:31:11+09:00
ユーザー目線のVueコンポーネント実装
この記事について
Vueを開発・運用してアプリケーションの規模が段々増えてきました。
チームの人員も増やしていく中で慣れていないエンジニアがコンポーネントの実装に関わるというケースが増えてきて、どんなデータ構造やアクション定義を作るのが良いのか悩んでいるケースを見受けるようになりました。
今回は自分がコンポーネントを実装する時に考えていることを簡単に紹介しておきます。
コンポーネントはユーザーに最も近いコード
コンポーネント思考で実装していくと、ユーザーと近い距離に居るコードは各コンポーネント郡になります。言い換えるとコンポーネントとユーザーは密接な関係にあり、お互いの共通認識が合っていることが望ましいということになります。
利用するユーザーの思い浮かべるデータ構造や行動と実装されたコードの変数や関数が同じであるということです。つまりコンポーネントを使うユーザー側の気持ちがわからなければコンポーネントの実装も困難ということです。
サンプルです。
<template> <div> ...何かしらのコンテンツ <button @click="toggleFavorite"> <template v-if="favorite"> いいね!を解除 </template> <template v-else> いいね!する </template> </button> </div> </template> <script lang="ts"> import Vue from 'vue' export default Vue.extend({ data() { return { favorite: false } }, methods: { toggleFavorite() { this.favorite = !this.favorite } } }) </script>何かしらのコンテンツに対していいね!したりするサンプルです。この実装には2つの問題点があります。
- ユーザーにとってはいいね!なのにコード上ではお気に入りという変数名になっている
- いいね!を変更する処理がコード上では同一の関数で定義されている
どちらも動作上は問題ありません。バグもありません。ですが後々の機能追加や変更などで破壊的変更をする必要が出てしまうコードで良いコードとは言えません。何故ならユーザーの行動原理から外れている実装だからです。
ユーザー側の名称でデータ構造を定義
favorite
をいいね!として扱う場合、機能追加で「お気に入りを追加したい」と言われた時にどうでしょうか?favorite2
と定義してやり過ごすことはできますが、今後コードを見た人が混乱してしまうと思います。古くから運用しているサービスになると歴史上データベースやAPIが
favorite
になっているケースがあります。サンプルではバックエンドと連携していませんが、こういったアプリケーションのサービス内の名称とユーザー側の名称が違う問題はそのままコンポーネントの実装にまで影響してしまうケースが多くあります。統一することが理想ですがデータベースごと名称の変更を全て反映するのは規模によっては難しいです。でも差異を無くしておくことはできます。
フロントエンドは比較的変更がやりやすい分類なのでこういったものは先にフロントエンド側で吸収しておくことで、機能追加や改善・インフラ整備の時に役立つことになるでしょう。
ユーザーの行動に沿った関数を定義
toggleFavorite
という関数は効率的です。ですがユーザーの意思とは連動していません。極端な例ですが、例えば「誤動作を避ける為にいいねする時だけ確認のアラートをして欲しい」といったユーザーの要望を反映する時にどうでしょうか?両方の機能を内包している為、片方のアクションに対して何か付加価値を付け足す時に拡張しにくいと感じませんか?
ユーザーにとって「いいね!する」という行動と「いいね!を解除する」という行動は全く別の意思から行われるアクションです。こういったユーザーの行動ごとに特殊な処理を挟むケースはフロントエンドではとても多いです。経験上Vuexでステート管理すると更に複雑化していきます。
「関数は最低限の方が...」「HTML上では同じ場所にあるボタンなのに...」と思うこともあるかもしれません。ですが、あくまでアプリケーションは利用するユーザーの為に動くべきであり、機能追加や変更もユーザーの行動結果から発生していくものです。
修正後のコードです。
<template> <div> ...何かしらのコンテンツ <button v-if="like" @click="unlikeContent"> いいね!を解除 </button> <button v-else @click="likeContent"> いいね! </button> </div> </template> <script lang="ts"> import Vue from 'vue' export default Vue.extend({ data() { return { like: false } }, methods: { likeContent() { this.like = true }, unlikeContent() { this.like = false } } }) </script>コンポーネントの実装 = ユーザーの意思
この考え方はテストケースも作成しやすい利点があります。
toggleFavorite
は現実のユーザーの行動と反しています。likePost
やunlikePost
を実行してデータ構造をチェックする方がよりユーザーの行動に近いテストケースになると思います。同じようにバグの発生源を特定する上でも有効です。問い合わせなどのユーザーの行った行動がそのまま再現手順に近くなり、関連するデータやメソッドに対してデバッグを行えば見つかりやすくなります。
実装上ではとても小さなことですが、こういった考えが規模の大きくなったアプリケーションにおいて効果的になっていきます。まずはユーザーの気持ちになって脳内でコンポーネントを操作するイメージを掴むことが大事です。
- 投稿日:2020-02-14T18:08:12+09:00
【Nuxt.js】Vuex基礎編:コードをスッキリさせよう
前置き
Happy Valentine❤️?
ということで(?)本日はVuex!
3つに分けて書きます✍️
・vuexとは何か
・メリットは
・簡単な使い方公開予定日を過ぎ、
申し訳ございません?♀️
自社開発アプリを進めておりました?Vuexとは
状態管理のライブラリです。
って言ってもイメージ沸かないので…簡単に言うとデータ保存ができるもの
・ログイン情報が
ページ遷移後も保持されたり、
・APIのデータ引っ張ってきて保存とか、
・methodsに何度も同じ処理書かずに済む
とかとか。
$emitも何回も書かなくていいし楽!
とにかく楽!!!そんな感じです。状態保持をするのでstoreに書きます✍️
メリット
上記の内容がそのままメリット
コードもまとまって楽ちんなのは
このあとの未使用・使用の比較
を見ていただければ分かります?使い方
とにかくまずインストール
ターミナルnpm install vuex --savehttps://vuex.vuejs.org/ja/installation.html
使い方(超簡単ver.)
まずは分かりやすく
components使わず
pagesだけで完結させます。store/index.jsexport const state = () => ({ counter: 0 }) export const mutations = { increase(state) { state.counter++ }, decrease(state) { state.counter-- } }index.vue<template> <div class="container"> <button @click="increase">increase</button> <button @click="decrease">decrease</button> {{ $store.state.counter }} </div> </template> <script> export default { methods: { increase () { this.$store.commit('increase') }, decrease () { this.$store.commit('decrease') }, }, } </script>??
今度はvuexを使用しない場合
それぞれの対応箇所が分かりますね。
・dataがstate
・methodsがmutationsindex.vue<template> <div class="container"> <button @click="increase">increase</button> <button @click="decrease">decrease</button> {{ counter }} </div> </template> <script> export default { data () { return { counter: 0, } }, methods: { increase () { this.counter++; }, decrease () { this.counter--; }, }, } </script>使い方(Components使用ver.)
作るものは全く同じです。
コンポーネント化するだけです!?Point
・index.jsはバグるので新しいjsファイルを作成。
・そのため呼び出し方が変わります。
・gettersでstateの状態を取得する必要あり。counter.jsexport const state = () => ({ counter: 0 }) export const mutations = { increase(state) { state.counter++ }, decrease(state) { state.counter-- } } export const getters = { counter: state => { return state.counter } }Counter.vue(子コンポーネント)
ここで呼び出し方が
'ファイル名/定義した物' に変わります?Counter.vue<template> <div class="container"> <button @click="$store.commit('counter/increase')">increase</button> <button @click="$store.commit('counter/decrease')">decrease</button> {{ counter }} </div> </template> <script> export default { computed: { counter () { return this.$store.getters['counter/counter'] }, }, } </script>index.vue(親ページ)
親には何も書かず超スッキリ??index.vue<template> <div class="container"> <Counter /> </div> </template> <script> import Counter from '~/components/Counter.vue'; export default { components: { Counter, }, } </script>??
vuexを使わないと
$emitとか親でどう処理するかとか
いちいち分けたり何度も書く必要が出てきます。Counter.vue(子コンポーネント)<template> <div class="container"> <button @click="$emit('increase')">increase</button> <button @click="$emit('decrease')">decrease</button> {{ counter }} </div> </template> <script> export default { props: { counter: Number }, } </script>index.vue(親ページ)<template> <div class="container"> <Counter :counter="counter" @increase="increase" @decrease="decrease" /> </div> </template> <script> import Counter from '~/components/Counter.vue'; export default { components: { Counter, }, data () { return { counter: 0 } }, methods: { increase () { this.counter++; }, decrease () { this.counter--; } }, } </script>記事が公開したときにわかる様に、
note・Twitterフォローをお願いします?
https://twitter.com/aLizlab
- 投稿日:2020-02-14T17:00:17+09:00
Leafletのマーカー(アイコン)にgeoJSONから読み込んだデータ(番号・数字・文字列)を表示する
まとめ
・stackoverflowのこの記事を参考に、Awesome-Markersプラグインを改修してマーカー(アイコン)にgeoJSONから読み込んだデータを載っけた。
やろうとしたこと
以下のようなGeoJSONファイルを.getJSON()でJavaScriptに取り込み、Leaflet上で"number"をアイコン上に表示させる。
{ "type": "FeatureCollection", "crs": { "type": "name", "properties": { "name": "EPSG:4326" } }, "features": [ { "type": "Feature", "properties": { "title": "皇居", "number": 1 }, "geometry": { "type": "Point", "coordinates": [ 139.75441656790684, 35.68826105264495 ] } }, ] }実装イメージ(画像引用元)
やり方
1. プラグインのインストール
チュートリアルや技術ブログ(例えばkitanoteさんとか)などを参照し、Awesome Markersプラグインをインストールします。
GitHubから必要なファイルをダウンロードし、適したディレクトリに保存します。
.htmlの<header><header/>
部分で、先ほど保存した.jsファイル、.cssファイルにパスを通し、合わせて、フォント(Awesome Font)の参照先をURLで指定します。<!-- plugin --> <!--プラグインの.jsファイル、.cssファイルにパスを通す--> <link rel="stylesheet" href="leaflet.awesome-markers.cssの置き場所を指定"> <script src="leaflet.awesome-markers.jsの置き場所を指定"></script> <!-- フォント(Awesome Font)の参照先を指定 --> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp" crossorigin="anonymous"> <!-- plugin -->2. stackoverflowの記事を参照しつつプラグインを改修する
Leaflet Awesome-Markers (Adding Numbers)の二人目の回答者であるrockXrock氏の2., 3. 及び5.の説明に従い、leaflet.awesome-markers.js を改修します。
- change leaflet.awesome-markers.js line 2, add html:""
- change leaflet.awesome-markers.js line 80,
- comment out line 45 and 47.
3. JSONファイルを取り込んでLeafletで表示させるスクリプトを書く
rockXrock氏の4. の説明を参考に、アイコンの設定をJavaScriptに記載する
4.when creating icon, call like before
$.getJSON("ファイル名もしくはパス", function(data) { var geojson = L.geoJson(data, { pointToLayer: function (feature, coordinates) { return L.marker(coordinates, {icon: L.AwesomeMarkers.icon({icon: '', markerColor: 'darkblue', prefix: 'fa', html: (feature.properties.number)})})}, }); geojson.addTo(map); });
$.getJson("ファイル名もしくはパス", data){}
でjsonファイルを読み込み、その後の処理を定義します。
var geojson = L.geoJson(data, {})
でgeojsonという名の関数を定義します。
L.geoJson()はリーフレットのライブラリのうちGeoJSONを扱うオブジェクトです。公式ドキュメントを参考に、{}
内にオプションを入れていきます。
pointToLayer: function (feature, coordinates) {return L.marker(coordinates, {})
で、JSONファイル(ここではfeature)からcoordinatesフィールドにある緯度経度データを読み取り、緯度経度に基づいてマーカーを表示させます。
ここの{}
内で、先ほど編集したAwesome Markersプラグインのアイコン設定を参照させます。最後に、
geojson.addTo(map);
で、定義したgeojson関数を地図に追加しています。アイコンの設定の詳細を確認しますと、、、
{icon:L.AwesomeMarkers.icon({ icon: '', markerColor: 'darkblue', prefix: 'fa', html: (feature.properties.number)})})}
L.AwesomeMarkers.icon()
で先ほど改修したleaflet.awesome-markers.js内に定義されているicon関数を呼び出します。
html: (feature.properties.number)
でマーカーに表示させるデータのフィールドを指定します。今回は、読み込んだJSONファイル(feature)の中のプロパティのうち、numberフィールドを指定しました。4. 実装結果
桁数が多いとはみ出したりしますが、登録されたデータから引っ張ってきた情報をアイコンに載せることに成功しました。
失敗した方法
アイコンの設定方法には、
var
を使って設定を事前に関数として定義し、それを{icon: 名前}
で引用するという方法もありますが、以下の通りnumberedIcon
として外出ししてみましたが、データの継承の問題?でfeatureが定義されてないよ!という以下のエラー文が出てしまいました。
Uncaught ReferenceError: feature is not defined
これを回避するため、今回はL.geoJSON()
の中に直接アイコン設定を書き込んだ次第です。var numberedIcon = L.AwesomeMarkers.icon({ icon: '', markerColor: 'darkblue', prefix: 'fa', html: (feature.properties.number) }); $.getJSON("ファイル名もしくはパス", function(data) { var geojson = L.geoJSON(data, { pointToLayer: function (feature, coordinates) { return L.marker(coordinates, {icon:numberedIcon})})}, }); geojson.addTo(map); });
- 投稿日:2020-02-14T14:12:22+09:00
Nuxt.js - Firebaseへのデプロイ & 独自ドメインの設定
Nuxt.jsで作成したSPAアプリケーションをFirebaseにデプロイする方法をいつも忘れるので、メモしておきます。
プロジェクトの追加
Firebaseへ行き、Googleアカウントでログイン後、コンソールに移動します。
"プロジェクトを追加"から、プロジェクトの追加を行います。
・Googleアナリティクスの設定
ここでは、とりあえず無効にしておきます。
有効にすると、アナリティクスアカウントの設定が必要です。
これで完了です。
firebase-tools
次に、firebase-toolsのインストールを行います。
$ npm install -g firebase-toolsインストール後、ログインします。
ブラウザが開くので、GoogleアカウントでログインすればOKです ;)
$ firebase loginデプロイしたいプロジェクトのディレクトリに移動し、init
$ firebase initいくつかの質問があります。
今回はHostingを利用するので、スペースで選択し、Enter。
次に、"Use an existing project" を選択します。
冒頭の「プロジェクトを追加」で追加した、プロジェクト名を選択します。
"dist"と入力。
SPAとして設定するかを聞かれます。
私は、SPAで作成しているので、yを入力します。
これで設定は完了です!
Deploy
$ npm run generate $ firebase deploy完了すると、最後にURLが表示されるので、アクセスしてみます。
無事、Firebaseにデプロイすることができました :)
コードを追加したり、修正した場合には、再び以下のコマンドを打てばOKです ;)
$ npm run generate $ firebase deploy独自ドメインの設定
Firebaseのコンソールページに行き、独自ドメインを設定したいプロジェクトを選択します。
次に、左サイドバーのHostingを選択。
"カスタムドメインを追加"をクリック。
あとは、お名前.comなどで取得したドメインを入力し、従えばOKです ;)
反映するのに、少し時間がかかると思います。
説明不足だと感じた点や、変更点が出てきましたら、随時更新していきたいと思います。
この記事が参考になりましたら幸いです。
Thanks,
@ShogoMurakami
- 投稿日:2020-02-14T12:14:58+09:00
リンク画像にマウスホバーすると画像がふわっと(重要)変化する方法
これからすること
ユーザビリティの観点から、リンクできる画像やボタンなどのパーツには ”リンクできそう” な見た目や演出が要求されるのですが顧客受けが良いのか
画像にマウスのカーソルを乗せるとふわっと変化する
を求められる場面が結構あります。
ホバーした時に画像を変えるだけなら$(function(){ $('a img').hover(function(){ $(this).attr('src',$(this).attr('src').replace('_off','_on')); },function(){ $(this).attr('src',$(this).attr('src').replace('_on','_off')); }); });img01_on と img01_off という2種類の画像を用意して JSに ↑ を書くだけで対応できるのですがふわっとすることなく一瞬で変わってしまします。
解決方法(JS不要)
See the Pen poJgWeQ by sphenisc (@sphenisc) on CodePen.
CSSでホバーしていない状態の画像を上から被せておき、ホバーするとimgタグで入れた方の画像が現れる仕組みになっています。
transition: all 1s;の部分をいじれば変化のさせ方を変えることができます。
ul li a:hover::after { opacity: 0; }の部分に
height:0;
を追加するだけでカーテンが上がるような動きにできます。
- 投稿日:2020-02-14T12:00:40+09:00
JSで要素間を線で繋いだり動かしたりライブラリ調査@202002
JSでオブジェクト間に線を引きたい調査メモ
diagramflowjs
削除や追加、リネームなど機能が豊富な感じで実用向けな感じ
https://lambot.blob.core.windows.net/github/diagramflowjs/index.html
SimpleFlowchart
バインバイン動く
svg.connectable.js
ソースコードなどが合って使いやすい
http://jillix.github.io/svg.connectable.js/
svg.draggy.js
SVGドラック簡単に追加できて便利
http://jillix.github.io/svg.draggy.js/
leader-line
結構いろんな線があるのでウェブで線を引きたいときには使いたい感じ
- 投稿日:2020-02-14T11:25:01+09:00
【JavaScript】Vue.js(Element UI)を使用している際に「async-validator」単体で使う場合のカスタムバリデーション実装方法
概要
Vue.js(Element (Element UI))を使用している場合に「async-validator」単体で使う実装方法例を少々
インストール(npm)
npm i async-validator※筆者はElement (Element UI)(Vue.jsのコンポーネントライブラリ)を使用しているので別途インストールしなくても使える状況ですので単体でインストールしたことはないです
実装例
簡単なアカウント登録画面の例として以下のように実装してみました
form.vue<template> <div class="form"> <div class="text-center">アカウント登録</div> <input type="email" v-model="ruleForm.email" placeholder="メールアドレス*"> <input type="password" v-model="ruleForm.password" placeholder="パスワード*"> <input type="password" v-model="ruleForm.passwordConf" placeholder="パスワード(確認)*"> <div class="text-center"> <input type="button" @click="beforeSubmit" value="登録"> </div> </div> </template> <script> import schema from 'async-validator'; export default { data() { // カスタムバリデーション定義する場合はここに記述 var validatePasswordConf = (rule, value, callback) => { if (value === '') { callback(new Error('パスワード(確認)を入力してください。')); } else if (value !== this.ruleForm.password) { callback(new Error('二つのパスワードが異なります。')); } else { callback(); } }; return { // フォーム用変数 ruleForm: { email: '', password: '', passwordConf: '', }, // バリデーションルール定義(項目毎) rules: { email: [ { required: true, message: 'メールアドレスを入力してください。', trigger: 'blur' }, { type: "email", message: '正しいメールアドレスを入力してください。', trigger: 'blur' }, { min: 1, max: 255, message: '1文字以上255文字以内で入力してください。', trigger: 'blur' } ], password: [ { required: true, message: 'パスワードを入力してください。', trigger: 'blur' }, { min: 1, max: 50, message: '1文字以上50文字以内で入力してください。', trigger: 'blur' } ], passwordConf: [ // 上記で定義したカスタムバリデーションを使用 { validator: validatePasswordConf, trigger: 'blur' } ], }, } }, methods: { beforeSubmit() { var validator = new schema(this.rules); validator.validate(this.ruleForm, (errors, fields) => { if(errors) { // バリデーションエラー時 return alert(errors[0]["message"]); } // バリデーションパス return alert("登録しました"); }); }, } } </script>バリデーションのルール詳細は「async-validator」のREADMEとかを参考にしてください。
参考URL
- 投稿日:2020-02-14T11:08:59+09:00
[Rails] scriptタグ内でRuby/Railsのメソッドを使用する際のescape_javascriptについて
この記事を書くきっかけ
jQueryを書いていると、scriptタグ内でRailsのメソッド(正確にはapplication_helper内のメソッド)を使用したいときがあります。
正しい書き方をしないと正確に呼び出せないため、失敗した書き方と成功した書き方をまとめます。前提
current_userの属性によってpathを切り替えるメソッドを
def hogehoge_path_for
とし、application_helper.rbに定義します。application_helper.rbdef hogehoge_path_for if ... hoge_path else hogehoge_path end endこれをとあるviewのscriptタグ内で呼び出します。
let path = hogehoge_path_forの返り値
としたいです。失敗例1
あるview<script> let path = <% hogehoge_path_for %>; </script>普通はこう考えるはずですが、これだと呼び出せません。
失敗例2
あるview<script> let path = <% hogehoge_path_for %>; </script>これも呼び出せません。
失敗例3
escape_javascript
というメソッドを使用します。
なお、これはaliasとしてj
の使用が許されています。あるview<script> let path = <% escape_javascript hogehoge_path_for %>; </script>上記は下記と同じです。
あるview<script> let path = <% j hogehoge_path_for %>; </script>どちらも失敗します。
失敗例4
=をつけます。
あるview<script> let path = <%= escape_javascript hogehoge_path_for %>; </script>上記は下記と同じです。
あるview<script> let path = <%= j hogehoge_path_for %>; </script>これもどちらも失敗します。
失敗例5
=を外して、
''
で囲います。あるview<script> let path = '<% escape_javascript hogehoge_path_for %>'; </script>上記は下記と同じです。
あるview<script> let path = '<% j hogehoge_path_for %>'; </script>これもどちらも失敗します。
成功例
=
をつけて''
で囲います!あるview<script> let path = '<%= escape_javascript hogehoge_path_for %>'; </script>上記は下記と同じです。
あるview<script> let path = '<%= j hogehoge_path_for %>'; </script>
これで無事呼び出すことができました。
- 投稿日:2020-02-14T03:33:30+09:00
TensorFlow.jsを使ってjavascript上でkerasモデルを使う
javascriptでTensorFlowモデルを使えるようにするTensorFlow.jsの基本的な使い方をまとめます
公式チュートリアル
https://www.tensorflow.org/js/tutorialstensorflowjs APIドキュメント
https://js.tensorflow.org/api/latest/実装例
https://github.com/tensorflow/tfjs-examples試行環境
Windows10
Node.js版とブラウザ版
tensorflowjsはC++で書かれたtensorflowのwrapperですが、クライアントサイドのブラウザで動作するjavascript版とサーバーサイドで動作するNode.js版があります。Node.js版はローカル環境に最適化された追加ライブラリで高速に動作しますしCUDAを使ってGPUもフルに使えるようになっているんだそうです。ブラウザ版はCPUのみで比較してもNode.jsと比べると速度は劣りますがクライアントのリソースを使えるのが大きなメリットです。
pandasが使えるpython版と比べるとNode.js版でも使い勝手は悪いので、pythonでモデルを作ってコンバートしたものをNode.jsをバックエンドとする環境でNode.js上で使うか、サーバーサイドの負荷を低減するためにブラウザ上のjavascriptで使うというやり方が多くなるのではないかと思います。
python版tensorflowで作ったmodelを変換する
1. python版のモデルを用意する
model.save()で保存したh5ファイルを変換することが出来ます。
とりあえず入力1次元、出力1次元の回帰モデルを作って保存してみます。pythonimport numpy as np import pandas as pd import tensorflow as tf physical_devices = tf.config.list_physical_devices('GPU') if len(physical_devices) > 0: tf.config.experimental.set_memory_growth(physical_devices[0], True) print('memory growth:', tf.config.experimental.get_memory_growth(physical_devices[0])) x = np.array([1,2,3,4]).reshape(4, 1) y = np.array([1,3,5,7]).reshape(4, 1) inputs = tf.keras.layers.Input(shape=(1)) outputs = tf.keras.layers.Dense(1, activation='linear')(inputs) model = tf.keras.models.Model(inputs=inputs, outputs=outputs) opt = tf.optimizers.Adam(lr=0.1) model.compile(optimizer=opt, loss='mse') model.fit(x, y, epochs=100, batch_size=1, verbose=0) print(model.predict(x)) model.save('../model.h5') pd.DataFrame(x).to_csv('../x.csv')2. モデルを変換するtensorflowjsパッケージをインストールする
pythonパッケージのtensorflowjsをpip installして使用します。ただし2020年2月現在、tensorflow 2.0.1~2.1.0とは依存パッケージが競合してしまいますので、tensorflowjs用のconda環境を作って専用にした方が良いと思います。なお、tensorflow 2.1.0で作ったモデルも現状は問題なく変換できるようです。
terminalpip install tensorflowjs
3. モデルを変換する
tensorflowjsをインストールしたpythonにPATHが通った環境で、tensorflowjs_converterを使って変換します。以下の場合、カレントディレクトリのhoge.h5ファイルを読み込んで、/model/以下にモデル構造を記述したjsonファイルと重みを記録したbinファイルを書き出してくれます。
terminaltensorflowjs_converter --input_format keras hoge.h5 model
ブラウザ版tensorflowjsで使う
tf.loadLayersModel()でjsonファイルのPATHを指定してやると保存したモデルを読んで、tf.tensorを放り込むと推定してくれます。ただし、ブラウザ上で動作するjavascriptはローカルのファイルを読めなくなっているので、面倒ですが適当なウェブサーバーを立ててドキュメントルート以下に上記で変換したモデルをフォルダごと置いてブラウザでURLを読み込むようにします。
フォルダ構造└─document root │ index.html │ ├─model model.json group1-shard1of1.binindex.html<!DOCTYPE html> <html lang="jp"> <head> <meta charset="utf-8"> </head> <body> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.0/dist/tf.min.js"></script> <script> async function run(){ // load model const path = "http://localhost/model/model.json"; const model = await tf.loadLayersModel(path); // predict const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]); y_pred = await model.predict(xs); y_pred.print(); // convert to array const values = await y_pred.data(); const arr = await Array.from(values); console.log(arr); } run(); </script> </body> </html>Node.js版で使う
1. Node.jsをインストールする
Node.jsがまだ入ってないようならインストールします
https://nodejs.org/ja/インストールするとPATHが通ってコマンドプロンプトから呼べるようになります
コマンドプロンプト> node -v v12.14.1 > npm -v 6.13.42. Node.jsにプロジェクトを作る
NodeJS環境を初期化します。適当なフォルダを作ってフォルダ内でnpm initすると必要なファイルが作成されます。package nameなどを聞かれますがすべてdefaultで大丈夫です。最後にIs this OK?にyesと回答すれば実行されます。
コマンドプロンプトmkdir tensorflow-demo cd tensorflow-demo/ npm init
3. Node.jsプロジェクトにtensorflowjsをインストールする
プロジェクトのディレクトリで以下を実行すればインストールされます。tfjs-nodeはNode.jsに最適化したライブラリで、ブラウザ版で実行するより高速に動作します。
コマンドプロンプトnpm install @tensorflow/tfjs-node
GPU+CUDAがある環境ではnpm install @tensorflow/tfjs-node-gpuすればGPUを使った高速演算ができると書かれているんですが、CUDA10.0をインストールしたWin10でやってみたらMODULE_NOT_FOUNDで動きませんでした。多分PATHが通ってないとかだと思いますが未確認です。
4. Node.jsでpython版modelを使う
tf.loadLayersModel()でjsonファイルのPATHを指定してやると保存したモデルを読んで、tf.tensorを放り込むと推定してくれます。ブラウザ上で動作する場合とは異なりウェブサーバーを立てる必要はありません。
フォルダ構造└─project folder │ index.js │ ├─model model.json group1-shard1of1.binindex.js// import const tf = require('@tensorflow/tfjs'); require('@tensorflow/tfjs-node'); async function run(){ // load model const path = "http://localhost/tensorflowjs/tfjs_001/model/model.json" const model = await tf.loadLayersModel(path); // predict y_pred = await model.predict(tf.tensor2d([1, 2, 3, 4], [4, 1])); y_pred.print(); // convert to array const values = await y_pred.data(); const arr = await Array.from(values); console.log(arr); } run();index.jsと同じディレクトリからindex.jsを実行するとデモコードが動きます。
コマンドプロンプトnode index.js
実行結果Tensor [[0.9999999], [3 ], [5.0000005], [7.0000005]] [ 0.9999998807907104, 3, 5.000000476837158, 7.000000476837158 ]
推定結果が表示されれば正常です。
tensorの計算
python版のtensorflowは適当に配列を放り込めばlistだろうがnp.arrayだろうが適当に処理してくれましたが、tensorflowjsではtf.tensor型に変換してからでないと扱えません。tf.tensorにするとjavascript標準の四則演算は出来なくなりますが、tf.add()やtf.sub()などで高速な演算が出来るようになっていますので不足はないと思われます。
四則演算以外にも三角関数とか最大最小とか色々計算できます。
詳しくは以下のAPIドキュメントを参照してください。
https://js.tensorflow.org/api/latest/tensorとscalarの四則演算const x = tf.tensor1d([1,2,3]) const a = tf.scalar(4) x.add(a).print() x.sub(a).print() x.mul(a).print() x.div(a).print()tensor同士の計算も同じようにできます。
tensor同士の四則演算const x1 = tf.tensor1d([1,2,3]) const x2 = tf.tensor1d([2,3,4]) console.log('add') x1.add(x2).print(); console.log('sub') x1.sub(x2).print(); console.log('mul') x1.mul(x2).print(); console.log('div') x1.div(x2).print();tf.tensorを配列に戻すにはdataSync()メソッドを使います。
配列に戻すconst b = tf.tensor1d([1,2,3]) console.log('b') b.print() const b_value = b.dataSync(); console.log('b_value') console.log(b_value)メモリの解放
tf.dispose()
openGLでtensorflowjsを使う場合、明示的に変数を消去しないとメモリが解放されない為、明示的にメモリを解放するためにtf.dispose()が用意されています。tf.memory()でメモリが正常にリリースされたか確認できるようになっていますので、tf.dispose()した場合としない場合を比較してみましょう。
tf.dispose()を使わない場合for (let i = 0; i < 100; i++) { const a = tf.tensor1d([1,2,3]); } console.log(tf.memory());tf.dispose()を使った場合for (let i = 0; i < 100; i++) { const a = tf.tensor1d([1,2,3]); a.dispose() } console.log(tf.memory());上記をそれぞれ実行するとtf.dispose()を使った場合にはtensorflowjsが保持するメモリがゼロになっている事が確認できます。
tf.tidy()
複数の演算を続けて行った場合に計算途中で生じる変数も残ってしまう為、tf.dity()でこれが残らないように処理するようです。
tf.tidy()の使い方// y = 2 ^ 2 + 1 const y = tf.tidy(() => { // a, b, and one will be cleaned up when the tidy ends. const one = tf.scalar(1); const a = tf.scalar(2); const b = a.square(); console.log('numTensors (in tidy): ' + tf.memory().numTensors); // The value returned inside the tidy function will return // through the tidy, in this case to the variable y. return b.add(one); }); console.log('numTensors (outside tidy): ' + tf.memory().numTensors); y.print();CSVファイルの読み込み
CSVファイルはtf.data.csv(source)で読み込めます。sourceにPATHを指定して読み込みますが、ブラウザ上で動作するjavascriptはhttpで指定されたPATHしか読めないようになっていますので、ローカルでテストする際にもhttpサーバーに置いて実行する必要があります。Node.jsの場合はそういう制限はないので普通にローカルで実行できます。
take()メソットでデータの頭出し、toArray()メソッドで配列への変換ができます。
test.jsconst df = tf.data.csv("http://localhost/hoge.csv") console.log(df.take(10).toArray())グラフを描く
javascriptにはpython勢の大好きなmatplotlibがないんですが、ありがたいことに可視化の為のメソッドを用意してくれています。tfvisをインポートすれば簡単にグラフを描画できます。
tfvis APIドキュメント
https://js.tensorflow.org/api_vis/latest/Node.jsサーバーを立てる
ブラウザ版のテストをする際、Node.jsを使えば簡単にhttpサーバーを立てることができます
コマンドプロンプトnpm install http-server -g
document_rootにするフォルダで以下を実行http-server -g
- 投稿日:2020-02-14T02:11:38+09:00
[JavaScript] スプレッド構文
See the Pen リストにリストの追加 by mykysyk (@mykysyk) on CodePen.
- 投稿日:2020-02-14T00:24:18+09:00
お絵かきできるSNSを作りたい!4
今回は少しでもアプリに近づけるためにmanifest.jsonを追加したいと思います。
これを追加することでAndroidでページを開いた際、ホーム画面にタイルを置きませんか?とメッセージを表示することが出来るんだとか。
場所はHEADの中に一行以下の記述を書くだけです。
<link rel="manifest" href="manifest.json">manifest.jsonの中身は↓こんな感じです。
{ "lang": "ja", "name": "sakura", "short_name": "お絵かき", "icons": [{ "src": "icon.png", "sizes": "129x129", "type": "image/png" }], "start_url": "https://127.0.0.1/", "display": "standalone" }言語、アプリ名・短縮アプリ名とタイルに置く画像を指定します。
start_urlはサーバがないので適当です。最後に自分がやりたそうにしていることをまとめておきます。
・PWA
・線の太さを変える
・線の色を変える
・画像を保存
・レイヤー
・戻る(Ctrl+Z)
・進む(Ctrl+Y)
・Webプッシュ通知対応
・好きな画像を下絵にしてトレースできるようにする