- 投稿日:2021-02-13T22:43:15+09:00
数値の小数点以下以外をカンマ区切りにする
この記事を書く数時間前、数値をカンマ区切りにしたいと思って手っ取り早くQiitaで手法を調べて使用したところ、「小数点以下を3桁までしか表示しない(
toLocaleString
)」や「小数点以下の数列もカンマで区切ってしまう(正規表現)」など、小数点以下について考慮していないものばかりでちょっと面喰いました。なんてこった.jsvar n = 123456789.01234; function separate(num){ return String(num).replace( /(\d)(?=(\d\d\d)+(?!\d))/g, '$1,'); } separate(n); // "123,456,789.01,234" n.toLocaleString(); // "123,456,789.012"カンマ区切りをするようなものは大きな数値であることが殆どなので、小数点以下を考慮する必要があまりないのでしょうね。ですがいずれにせよ、私の場合は考慮したかったので、以下のように正規表現を書き換えてみました。
これでどうだ.jsconst comma = num => isNaN(num) || String(Number(num)).replace(/(?<!\.\d*?)(\d)(?=(\d{3})+(?!\d))/g, '$1,'); comma(n); // "123,456,789.01234"否定後読みによってピリオドが前方に無いことを確認させています。また数値に見えて数値でない文字列(
123.456.789
など)をはじくため、isNaN
で数値かどうかのチェックもしています。「これで完璧!」と思いきや、一つ問題が。
SafariとIEでは否定後読みが(というよりは後読み系が)できません。
IEは別にどうでもいいとして、Safari未サポートは問題ですね。
ですので、否定後読みを無理矢理できるようにする裏ワザ(Javascriptでの正規表現の後読みの代替 - @yumarule さん)か、あるいは愚直にピリオド以下を取り外して実数部分をカンマ区切りした後に結合させるかのどちらかをしなければいけません。
これにて一件落着.js/* 文字をsplitで配列に変換し、reverseで反転させてjoinで結合する。 すると、Safari・IEでも使える否定先読みで問題を解決できる。 再度反転させればできあがり。 */ const comma_2 = num => isNaN(num) || String(Number(num)).split('').reverse().join('').replace(/(\d{3})(?!\d*?\.|$)/g, '$1,').split('').reverse().join(''); comma_2(n); // "123,456,789.01234" /* ピリオド以下を一度取り外す */ const comma_3 = num => { var a = String(Number(num)).split('.'); return isNaN(num) || a[0].replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,') + (a[1] ? '.' + a[1] : ''); }; comma_3(n); // "123,456,789.01234"こんな具合ですね。
Safariでも後読み実装されないかな…。
追記:
@il9437 さんが正規表現ではなくtoLocaleString
で書いたものをコメント欄の方にご提示くださいました。comma_3
のような形式の関数ですが、こちらは非常に直感的で分かりやすいものとなっています。ぜひご確認ください。
- 投稿日:2021-02-13T22:40:05+09:00
【JavaScript】Vue-chartjsを使い始める
現場では、Charjsを使用して、グラフを描いています。
勉強ノードをメモします。Chart.jsの公式サイト:https://www.chartjs.org/
Chartjsを導入する方法:
- 外部スクリプト
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script>
- インストール
開発ディレクトリ内、npmで下記のコマンドでインストールする。
(スタンドアロン版、バンドル版両方インストールされる)$ npm install chart.js --save下記のnode_modulesモジュールとpacage-lock.jsonパッケージが生成される
スタンドアロン版とバンドル版
スタンドアロン版
ファイル:
dist/Chart.js
dist/Chart.min.js
(時間軸を使用したい場合は、Moment.jsをインストールする必要)バンドル版(時間軸を使用可。Moment.jsインストール不要。)
ファイル:
dist/Chart.bundle.js
dist/Chart.bundle.min.js必要に応じて、pathを確認、index.htmlファイルに導入する。例えば:Chart.js
<script src="node_modules/chart.js/dist/Chart.js"></script>以上の設定は準備でしたら、例のコードを練習します。
index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <!-- 外部スクリプト方法 --> <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script> --> <!-- インストール方法 --> <script src="node_modules/chart.js/dist/Chart.js"></script> </head> <body> <p>chart_</p> <!-- canvas要素を設置 --> <canvas id="myChart" width="400" height="400"></canvas> <script> // 要素またはコンテキストを取得 var ctx = document.getElementById("myChart"); // チャートタイプをインスタンスかする var myChart = new Chart(ctx, { // 棒グラフ type: 'bar', // データを設置 data: { // データのラベル labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], datasets: [{ // 凡例 label: '# of Votes', // データの量 data: [12, 19, 3, 5, 2, 3], // 各データの色 backgroundColor: [ 'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 206, 86, 0.2)', 'rgba(75, 192, 192, 0.2)', 'rgba(153, 102, 255, 0.2)', 'rgba(255, 159, 64, 0.2)' ], // 棒枠色 borderColor: [ 'rgba(255,99,132,1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)' ], // 棒枠線の厚さ borderWidth: 1 }] }, // オプション options: { // スケール設定 scales: { // 縦スケールを設定 yAxes: [{ ticks: { // ゼロから表示 beginAtZero:true } }] } } }); </script> </body> </html>
- 投稿日:2021-02-13T22:40:05+09:00
【JavaScript】Vue-chartjsを使い始める ーー実装編
現場では、Charjsを使用して、グラフを描いています。
勉強ノードをメモします。Chart.jsの公式サイト:https://www.chartjs.org/
Chartjsを導入する方法:
- 外部スクリプト
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script>
- インストール
開発ディレクトリ内、npmで下記のコマンドでインストールする。
(スタンドアロン版、バンドル版両方インストールされる)$ npm install chart.js --save下記のnode_modulesモジュールとpacage-lock.jsonパッケージが生成される
スタンドアロン版とバンドル版
スタンドアロン版
ファイル:
dist/Chart.js
dist/Chart.min.js
(時間軸を使用したい場合は、Moment.jsをインストールする必要)バンドル版(時間軸を使用可。Moment.jsインストール不要。)
ファイル:
dist/Chart.bundle.js
dist/Chart.bundle.min.js必要に応じて、pathを確認、index.htmlファイルに導入する。例えば:Chart.js
<script src="node_modules/chart.js/dist/Chart.js"></script>以上の設定は準備でしたら、例のコードを練習します。
index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <!-- 外部スクリプト方法 --> <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script> --> <!-- インストール方法 --> <script src="node_modules/chart.js/dist/Chart.js"></script> </head> <body> <p>chart_</p> <!-- canvas要素を設置 --> <canvas id="myChart" width="400" height="400"></canvas> <script> // 要素またはコンテキストを取得 var ctx = document.getElementById("myChart"); // チャートタイプをインスタンスかする var myChart = new Chart(ctx, { // 棒グラフ type: 'bar', // データを設置 data: { // データのラベル labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], datasets: [{ // 凡例 label: '# of Votes', // データの量 data: [12, 19, 3, 5, 2, 3], // 各データの色 backgroundColor: [ 'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 206, 86, 0.2)', 'rgba(75, 192, 192, 0.2)', 'rgba(153, 102, 255, 0.2)', 'rgba(255, 159, 64, 0.2)' ], // 棒枠色 borderColor: [ 'rgba(255,99,132,1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)' ], // 棒枠線の厚さ borderWidth: 1 }] }, // オプション options: { // スケール設定 scales: { // 縦スケールを設定 yAxes: [{ ticks: { // ゼロから表示 beginAtZero:true } }] } } }); </script> </body> </html>
- 投稿日:2021-02-13T21:31:25+09:00
Nuxt.jsでAdobe Fonts(TypeKit)を使う
Nuxt.jsでもAdobe Fontsを使いたい
静的なHTMLページをNuxt.jsで作り直そうと思ったのですが、Adobe Fontsの埋め込みコードを発行しても、以下のようにピュアなJS用のコードしか発行されない。
AdobeFontsが生成するJS(function (d) { var config = { kitId: '各自異なるID', scriptTimeout: 3000, async: true, }, h = d.documentElement, t = setTimeout(function () { h.className = h.className.replace(/\bwf-loading\b/g, '') + ' wf-inactive' }, config.scriptTimeout), tk = d.createElement('script'), f = false, s = d.getElementsByTagName('script')[0], a h.className += ' wf-loading' tk.src = 'https://use.typekit.net/' + config.kitId + '.js' tk.async = true tk.onload = tk.onreadystatechange = function () { a = this.readyState if (f || (a && a != 'complete' && a != 'loaded')) return f = true clearTimeout(t) try { Typekit.load(config) } catch (e) {} } s.parentNode.insertBefore(tk, s) })(document)これだとプロジェクトにそのまま読み込めない。
Googleで調べてみる
Cannot find name 'Typekit'
Nuxt.jsでいい感じにAdobe fontsのwebフォントを読み込む
こちらの記事の方法でもできるはできるのですが、
Cannot find name 'Typekit'.
エラーが出てしまい、buildができない。Vue.jsでのやり方があった
Vue.jsとBootstrapVue上でAdobeの日本語ウェブフォントを使う
pluginとして、Adobe Fontsで生成されたJSを単体ファイルとして作成し、それを読み込むという方法。これならいけそう!
解決
まずはAdobe Fontsで生成されたJSをpluginsで作成
plugins/typekit.js/* eslint-disable */ // めちゃめちゃ怒られるのでESLintは切っておく ;(function (d) { var config = { kitId: '各自異なるID', scriptTimeout: 3000, async: true, }, h = d.documentElement, t = setTimeout(function () { h.className = h.className.replace(/\bwf-loading\b/g, '') + ' wf-inactive' }, config.scriptTimeout), tk = d.createElement('script'), f = false, s = d.getElementsByTagName('script')[0], a h.className += ' wf-loading' tk.src = 'https://use.typekit.net/' + config.kitId + '.js' tk.async = true tk.onload = tk.onreadystatechange = function () { a = this.readyState if (f || (a && a != 'complete' && a != 'loaded')) return f = true clearTimeout(t) try { Typekit.load(config) } catch (e) {} } s.parentNode.insertBefore(tk, s) })(document)作成した
typekit.js
をグローバルに読み込むnuxt.config.jsexport default { ~ 省略 ~ plugins: [ { src: '~/plugins/typekit.js', mode: 'client' } ], ~ 省略 ~ }ここで
mode: 'client'
オプションをつけておかないと、SSR時に「documentが見つからないよ!」となるので注意。おしまい
有料だし、あんまりAdobe Fontsを使うことはないかもしれないけどAdobe CC契約している人はぜひ。
- 投稿日:2021-02-13T20:06:38+09:00
VueとLaravelでStripeElementsを使ってクレジットカード情報を更新する方法
毎日投稿59日目
毎日記事を更新しているのですが、意外とネタは尽きないものです。
さて今回は、タイトルの通りStripeでクレジットカードの変更を行っていきます。
StripeのAPIについてはそこまで詳しく説明は致しません!
恐らくクレジットカードの情報を変更したいなと思った方は、Stripeの基礎的な部分は理解していると思うので。
イメージとしてはこんな感じです。
また、HTMLやCSSの記述が多いのでここらへんはコピペしてください。
話はこの辺にして早速説明を見ていきましょう!
StripeElementsの設置
Stripeでクレジットカードの情報を変更するには、カード番号、有効期限、セキュリティコードを入力してもらいトークンを作成します。
そのトークンを使ってクレジットカードの情報を変更します。
とりあえずStripeElementsの設置
Stripe.vue<template> <div class="group"> <label for="card-element"> クレジットカード情報 </label> <div id="card-element"></div> </div> </template> <script> export default { data() { return { stripe: null, card: null, token: '' } }, async mounted() { this.stripe = window.Stripe(process.env.VUE_APP_STRIPE_PUBLIC_KEY) const elements = this.stripe.elements() this.card = await elements.create('card', { hidePostalCode: true, style: { base: { iconColor: '#666EE8', color: '#31325F', lineHeight: '40px', fontWeight: 300, fontFamily: 'Helvetica Neue', fontSize: '15px', '::placeholder': { color: '#CFD7E0' } } } }) this.card.mount('#card-element') }, } </script>これでクレジットカードを入力する要素が表示されると思います。
必要項目の入力
次に、住所や名前を入力してもらうフォームを作成します。
ここに関しては先ほどの記述を一旦消してコピペしてください。
CSSの記述も多いので。
Stripe.vue<template> <div class="creditcard-change"> <div class="cell"> <form> <div class="group"> <label for="card-element"> クレジットカード情報 </label> <div id="card-element"></div> </div> <div class="group"> <label> <span>氏名</span> <input id="name" name="name" type="text" class="field" placeholder="YOSHIHIRO FUJIWARA" autocomplete="name" /> </label> <label> <span>メールアドレス</span> <input id="email" v-model="email" type="email" name="email" class="field" placeholder="zaemonia@example.com" autocomplete="email" /> </label> </div> <div class="group"> <label> <span>郵便番号</span> <input id="postal-code" name="address_line1" type="text" class="field" placeholder="1234567" maxlength="7" autocomplete="postal-code" /> </label> <label> <span>都道府県</span> <input id="address-state" name="address_state" type="text" class="field" placeholder="神奈川県" autocomplete="address-level1" /> </label> <label> <span>市区町村</span> <input id="address-city" name="address_city" type="text" class="field" placeholder="相模原市緑区二本松" autocomplete="address-level2" /> </label> <label> <span>番地</span> <input id="address-line1" name="address-line1" type="text" class="field" placeholder="1-34-10" autocomplete="address-line1" /> </label> <label> <span>マンション・アパート名</span> <input id="address-line2" name="address-line2" type="text" class="field" placeholder="マンションズタワー502" autocomplete="address-line2" /> </label> </div> <b-button class="recaptcha-button-v3" type="is-success" disabled="true" style="width: 100%; padding: 25px 0" @click="submit" > クレジットカードを変更する </b-button> </form> </div> </div> </template> <script> export default { data() { return { stripe: null, card: null, token: '' } }, async mounted() { this.stripe = window.Stripe(process.env.VUE_APP_STRIPE_PUBLIC_KEY) const elements = this.stripe.elements() this.card = await elements.create('card', { hidePostalCode: true, style: { base: { iconColor: '#666EE8', color: '#31325F', lineHeight: '40px', fontWeight: 300, fontFamily: 'Helvetica Neue', fontSize: '15px', '::placeholder': { color: '#CFD7E0' } } } }) this.card.mount('#card-element') }, methods: { submit() { const options = { name: document.getElementById('name').value, email: document.getElementById('email').value, address_line1: document.getElementById('address-line1').value, address_line2: document.getElementById('address-line2').value, address_city: document.getElementById('address-city').value, address_state: document.getElementById('address-state').value, address_zip: document.getElementById('postal-code').value, address_country: 'JP' } Object.keys(options).forEach((value) => { if (value === '') { alert('未入力の項目があります') } }) this.stripe.createToken(this.card, options).then((result) => { // エラーの場合 if (result.error) { alert(result.error.message) // 成功の場合 } else { this.token = result.token.id } }) }, } } </script> <style scoped> .creditcard-change { padding: 120px 0; display: flex; align-items: center; justify-content: center; flex-wrap: wrap; min-height: 100%; width: 550px; margin: 0 auto; } .creditcard-change > div { flex: 0 0 100%; } /* クレジットカード入力欄 */ .cell { margin-top: 30px; background: #e6ebf1; padding: 30px; } .group { background: white; box-shadow: 0 7px 14px 0 rgba(49, 49, 93, 0.1), 0 3px 6px 0 rgba(0, 0, 0, 0.08); border-radius: 4px; margin-bottom: 20px; } .cell label { position: relative; color: #8898aa; font-weight: 300; height: 100%; line-height: 40px; margin-left: 20px; display: flex; flex-direction: row; } .group label:not(:last-child) { border-bottom: 1px solid #f0f5fa; } label > span { width: 120px; text-align: right; margin-right: 30px; } .field { background: transparent; font-weight: 300; border: 0; color: #31325f; outline: none; flex: 1; padding-right: 10px; padding-left: 10px; cursor: text; } .field::-webkit-input-placeholder { color: #cfd7e0; } .field::-moz-placeholder { color: #cfd7e0; } .outcome { float: left; width: 100%; padding-top: 8px; min-height: 24px; text-align: center; } </style>
b-button
初めて気になった方はこちらの記事をご覧ください。初心者必見!サイト制作は楽してなんぼ。CSSフレームワークBuefyの紹介!!
Buefyはほんとにおススメなのでぜひ使ってください!
クレジットの更新
submit
で取得したresult.token.id
をバックエンドに送ってください。適当に
axios
とかで。そしたら、Laravelのコントローラーでクレジットカードを更新します。
StripeController.phppublic function creditUpdate(Request $request) { // ユーザーのクレジットカード情報の更新 require_once(__DIR__.'/../../../vendor/autoload.php'); $secret_key = config('app.STRIPE_SECRET_KEY'); $stripe = new \Stripe\StripeClient($secret_key); // カスタマーID、トークンの取得 $customer_id = $request->id; $token = $request->token; $customer = $stripe->customers->retrieve( $customer_id, [] ); // クレジットを登録していない場合、処理を終了 if(!$customer) { return response()->json([ 'message' => 'The customer is undefiend', ], 400); } // ソースの作成 $new_card = $stripe->customers->createSource( $customer_id, ['source' => $token] ); $customer->default_source = $new_card->id; $customer->save(); return response()->json([ 'data' => $new_card, 'message' => 'Updating customer`s paymentmethods is success', ], 200); }
$stripe->customers->createSource
に先ほど取得したトークンをソースとして登録することでクレジットカードの情報を更新することができます。
save()
忘れないように!!実際に変更をされているかはStripeの画面から確認して見て下さい!
いかがだったでしょうか??
記述が多いので嫌かもしれないですが、本当にコピペしてもらって結構です。
むしろコピペしてください!
また、クレジットカードの情報を取得したい場合はStripeの公式ドキュメントを参照してください!
以上、「VueとLaravelでStripeElementsを使ってクレジットカード情報を更新する方法」でした!
良ければ、LGTM、コメントお願いします。
また、何か間違っていることがあればご指摘頂けると幸いです。
他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!
Thank you for reading
- 投稿日:2021-02-13T20:02:53+09:00
【Vue3】オブジェクトのプロパティを追加してもリアクティブに動作するようになりました
【Vue.js】オブジェクトのプロパティを追加してもリアクティブに動作しなかった時の備忘録にて、
Vue2.x
において、オブジェクトのプロパティを追加してもリアクティブに動作しないこと、またその解決策について書きました。
Vue3
で同じことをしようとすると、どのような挙動になるのか確認してみました。結論を先に言ってしまうと、Vue3
からオブジェクトにプロパティを追加した場合もリアクティブに動作するようになりました。Vue3環境の準備
vue-cliコマンドを利用して
Vue3
をインストールしていきます。vue create
コマンドでVue 3 Preview
を選択します。(vue-cliがv4.5以上でないとVue3
環境は作成できません。)vue create study-vue3 ? Please pick a preset: Default (Vue 3 Preview) ([Vue 3] babel, eslint)(ディレクトリ名は
study-vue3
としました。)
npm run serve
を実行して以下の画面が確認できれば、インストールは無事に完了しています。HelloWorldを書き換える
オブジェクトのプロパティが
v-if
に指定されるようHelloWorld.vue
を以下に書き換えます。<template> <div> <button @click="show">表示する</button> <p v-if="hoge.isShow">表示</p> </div> </template> <script> import { reactive } from 'vue' export default { name: 'HelloWorld', setup() { const hoge = reactive({}) const show = () => { hoge.isShow = true } return { hoge, show } } } </script>
Vue2.x
の場合、ボタンを押しても表示の文字が出ないことは、【Vue.js】オブジェクトのプロパティを追加してもリアクティブに動作しなかった時の備忘録の記事で説明しましたが...
Vue3
から導入されたreactiveメソッド
によってこの課題が解決されました。
reactiveメソッド
から返されるオブジェクトは、プロパティが追加された場合でもリアクティブに動作させる事が可能になります。??Reactivity API
The reactive conversion is "deep"—it affects all nested properties.Vue3環境でVue2.xコードを書いて動作確認してみる
Vue2.x
では、リアクティブに動作しなかったコードを、Vue3
環境で動かすとどうなるのか確認してみました。<template> <div> <button @click="show">表示する</button> <p v-if="hoge.isShow">表示</p> </div> </template> <script> export default { name: 'HelloWorld', data () { return { hoge: { // isShow: false, コメントアウトで未定義にしています } } }, methods: { show () { this.hoge.isShow = true } } } </script>
Vue3
ではリアクティブなプロパティとして認識されるようになっていました。
この挙動になる理由について記載されたリファレンス等は見つかり次第追記していきます。?
- 投稿日:2021-02-13T20:01:18+09:00
【Vue】Vuetify.jsにコントリビュートするための準備
Vuetify.js
にコントリビュートしてみたい!という方に開発環境の構築を本記事でまとめました。コードをcloneする
git clone https://github.com/vuetifyjs/vuetify.git以降のコマンドはすべてcloneで取得した
vuetify
ディレクト配下で実行しています。yarnコマンドを使用できるよう準備
- npmからyarnをインストールする
npm install -g yarnパッケージをインストールする
yarnビルドの実行
yarn build開発サーバの起動
- 起動する前に、
packages/vuetify/dev/Playgroud.vue
を以下に書き換えるpackages/vuetify/dev/Playgroud.vue<template> <v-container> <p>ただいま開発環境の動作確認中</p> </v-container> </template> <script> export default { data: () => ({ // }), } </script>
- 開発サーバを起動する
yarn dev
- ブラウザから http://localhost:8080/#/page1 にアクセス
Playgroud.vue
の内容がブラウザから確認できれば、vuetify.jsの開発環境構築は完了です。
issue
で上がっているバグを再現する際は、Playground.vue
にコピペすると大抵は再現確認できるので、改修作業を行う際はPlaygroud.vue
を利用しましょう。?PRを出す際は、既にMergeされているPRを参考に作成すると、レビューを見てもらいやすくなります。報告内容を
Fixes:xxx
だけにしないで、修正前後の挙動の違いについても記載すると良さそうです。参考記事:https://vuetifyjs.com/en/getting-started/contributing/#working-with-github
- 投稿日:2021-02-13T19:44:04+09:00
ES2015における{ClassName}構文についての解説
ES2015における{ClassName}構文についての解説
1.前提知識となりますが、クラスとは、function型のインスタンスである。例として、任意のクラスClass01を定義し、typeof Class01を実行して戻り値が'function'であることをそれを証明します。補足の例として、下記構文を参考に。
// 下にあるClass01定義と同じクラス(constructor)を定義している let Class01 = class Class01 { p1: string = ''; } class Class01 { p1: string = ''; }2.{ClassName}についての説明です、同じ効果を持つ二つの構文を参考に
// 下にある構文と同じ効果を持つ let v1 = { Class01 }; let v1: { Class01: typeof Class01 } = { Class01: Class01 //プロパティClass01: function型の値Class01 }3.補足:Typeofオペレーターは実行時のみ働く仕様のようです。(同じfunctionなのにtypeof Class01 <> typeof Class02)
class Class02 { p2: string = ''; } let v2: { Class01: typeof Class01 } = { Class01: Class02 //エラー:typeof Class01 <> typeof Class02 }
- 投稿日:2021-02-13T18:56:34+09:00
【Express】静的ファイルをホスティングする方法
プログラミング勉強日記
2021年2月13日
静的ファイルのホスティングをする方法を間違えていたため、以下のようなエラーが出た。net::ERR_ABORTED 404 (Not Found)Expressで静的ファイルをホスティングする方法
Expressで、 画像、CSSファイル、JSファイルなどの静的ファイルをホスティングするためには標準で組み込まれている
express.static
を使用する。関数のシグネチャexpress.static(root, [options])
express.static
の基本的な使い方は、Applicationオブジェクトのuse()
を使ってミドルウェアの設定を行う。ファイルの構造app.js public/ +-- images/ +-- js/ +-- css/app.jsapp.use(express.static(__dirname + '/public'));app.jsの全体のコードvar express = require('express'); var app = express(); app.use(express.static('public', { hidden: true })); app.listen(process.env.PORT || 7000);参考文献
Serving static files in Express
javascript - JSファイルはnet::ERR_ABORTED 404(Not Found)を取得します
- 投稿日:2021-02-13T18:50:14+09:00
Node.js からC++関数への引数、返り値まとめ
はじめに
- node.jsからC++関数を利用する際に、引数・返り値の渡し方について困ったことはありませんか?
- この記事では、
node-addon-api
を使う場合の、引数・返り値の受け渡し方をまとめています。環境構築がまだの方は?
- この記事の内容をトライする前に、以下の記事で環境構築を完了させてください!
目次
1. 関数の基本形
- node-addon-apiを利用する場合、ラップされるC++の関数は以下のように定義します。
Napi::Value 関数名(const Napi::CallbackInfo& info);
- 引数の個数、型に関係なく、引数は
const Napi::CallbackInfo& info
と記述- 返り値は、型に関係なく
Napi::Value
と記述- 以下は、関数宣言と関数定義の例です。
- 引数、返り値ともに
void
です。void.h#include <napi.h> Napi::Value func(const Napi::CallbackInfo& info);void.cpp#include <napi.h> Napi::Value func(const Napi::CallbackInfo& info) { // Do nothing. return env.Null(); }2. 値の受け渡し
- js ↔︎ C++でやり取りするにあたって必要な引数、返り値の処理についてまとめました。
引数の型対応表
C++型 napi型 int .AsNapi::Number().Int32Value() double .AsNapi::Number().DoubleValue() std::string .AsNapi::String().ToString() 返り値の型対応表
C++型 napi型 int, double return Napi::Number::New(env, C++変数名) std::string return Napi::String::New(env, C++変数名)
- jsは数値型が1種類しかないため、数値であればNapi::Number型で良い
関数例
- 例として、jsから2つの引数(a,b)をC++で受け取り、その和をjsに返却する関数add()を考えてみましょう。
example.cpp#include <napi.h> Napi::Value add(const Napi::CallbackInfo& info) { // お約束 Napi::Env env = info.Env(); // 引数は、配列infoから取り出す。 double a = info[0].As<Napi::Number>().DoubleValue(); double b = info[1].As<Napi::Number>().DoubleValue(); // C++で行いたい処理を行う double ans = a + b; // 返り値は、Napi::○○ 型にキャストして返却する return Napi::Number::New(env, ans); }
- jsファイルからは次のように見えます
example.js// (前処理は省略) let ans = add(1,2) console.log(ans); // >> expected: 33. 配列の受け渡し
引数に配列を渡す( js → C++ )
array.cppNapi::Value setArr(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Napi::Array arr = info[0].As<Array>(); // C++の配列 std::vector<double> vec(arr.Length(), 0.0); // for文で要素を順に代入 for(size_t i = 0; i < arr.Length(); i++){ Napi::Value val = arr[i]; vec[i] = val.As<Napi::Number>().DoubleValue(); } return env.Null(); }返り値に配列を渡す( C++ → js )
array.cppNapi::Value getArr(const CallbackInfo& info){ Napi::Env env = info.Env(); // C++の配列 std::vector<double> vec = {1.0, 0.5, 0.25}; // for文で要素を順に代入 Napi::Array outArr = Napi::Array::New(env, vec.size()); for (size_t i = 0; i < vec.size(); i++) { outArr[i] = Napi::Number::New(env, vec[i]); } return outArr; }
- jsファイルからは次のように利用します。
array.js// (前処理は省略) setArr([1,2,3,4,5]); var arr = getArr(); console.log(arr); // >> expected: [1.0, 0.5, 0.25]4. (応用)異なるプリミティブ型を配列に入れて返却する
- C++で複数の返り値を返したい場合に有効です。
- リターンコードと計算結果の組み合わせなどを返却できます。
array2.cppNapi::Value getReturns(const CallbackInfo& info){ Napi::Env env = info.Env(); // do Something C++ // 返り値として、 1.0 と "aabbcc" を返却する例 const int returnArgNums = 2; int zero = 0; int one = 1; Napi::Array retArr = Napi::Array::New(env, returnArgNums); retArr[zero] = Napi::Number::New(env, 1.0); retArr[one] = Napi::String::New(env, "aabbcc"); return retArr; }array2.js// (前処理は省略) var arr = getReturns(); console.log("ret1 =", arr[0], "ret2 =", arr[1]); // >> expected: ret1 = 1 ret2 = aabbcc5. 試してみよう
- 前回の記事 で作成したプロジェクトの
wrapper.h
,wrapper.cpp
,index.js
を下記のコードで上書きすると、本記事の内容を試すことができます!├── node_modules
├── package-lock.json
├── package.json
├── index.js <-- 上書き
├── addon.cc
├── wrapper.cc <-- 上書き
├── wrapper.h <-- 上書き
└── binding.gypサンプルコードはこちら
wrapper.hを開く
wrapper.h#ifndef WRAPPER #define WRAPPER #include <napi.h> // 必要なヘッダ class Wrapper : public Napi::ObjectWrap<Wrapper> { public: static Napi::Object Init(Napi::Env env, Napi::Object exports); static Napi::Object NewInstance(Napi::Env env, const Napi::CallbackInfo& info); Wrapper(const Napi::CallbackInfo& info); ~Wrapper(); Napi::Value getNum(const Napi::CallbackInfo& info); Napi::Value add(const Napi::CallbackInfo& info); // <-- 追加 Napi::Value setArr(const Napi::CallbackInfo& info); // <-- 追加 Napi::Value getArr(const Napi::CallbackInfo& info); // <-- 追加 Napi::Value getReturns(const Napi::CallbackInfo& info); // <-- 追加 private: double m_value; }; #endif
wrapper.ccを開く
wrapper.cc#include "wrapper.h" #include <napi.h> using namespace Napi; // ---------------------------------------------------------- // // ---------------------のり付け部分--------------------------- // // ---------------------------------------------------------- // // new() の定義 Napi::Object Wrapper::NewInstance(Napi::Env env, const Napi::CallbackInfo &info) { Napi::EscapableHandleScope scope(env); // jsからコンストラクタに渡されるArgsは infoに配列として入っている const std::initializer_list<napi_value> initArgList = {info[0]}; // ここでWrapper:::Wrapper()が呼ばれる Napi::Object obj = env.GetInstanceData<Napi::FunctionReference>()->New(initArgList); // gcにメモリ解放されないようにスコープを除外する return scope.Escape(napi_value(obj)).ToObject(); } // メンバ関数のバインド Napi::Object Wrapper::Init(Napi::Env env, Napi::Object exports) { Napi::Function func = DefineClass( env, "Wrapper", { // ここにメソッドを登録する InstanceMethod("getNum", &Wrapper::getNum), InstanceMethod("add", &Wrapper::add), // <-- 追加 InstanceMethod("setArr", &Wrapper::setArr), // <-- 追加 InstanceMethod("getArr", &Wrapper::getArr), // <-- 追加 InstanceMethod("getReturns", &Wrapper::getReturns), // <-- 追加 }); Napi::FunctionReference *constructor = new Napi::FunctionReference(); *constructor = Napi::Persistent(func); env.SetInstanceData(constructor); exports.Set("Wrapper", func); return exports; } // ---------------------------------------------------------- // // --------------- Wrapperクラスの定義はこれより下 --------------- // // ---------------------------------------------------------- // // コンストラクタ Wrapper::Wrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Wrapper>(info) { m_value = 0.0; }; Wrapper::~Wrapper(){}; // メンバ関数 Napi::Value Wrapper::getNum(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); return Napi::Number::New(env, this->m_value); } // 引数・返り値の受け渡し Napi::Value Wrapper::add(const Napi::CallbackInfo& info) { // お約束 Napi::Env env = info.Env(); // 引数は、配列infoから取り出す。 double a = info[0].As<Napi::Number>().DoubleValue(); double b = info[1].As<Napi::Number>().DoubleValue(); // C++で行いたい処理を行う double ans = a + b; // 返り値は、Napi::○○ 型にキャストして返却する return Napi::Number::New(env, ans); } // 配列の受け取り Napi::Value Wrapper::setArr(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Napi::Array arr = info[0].As<Array>(); // C++の配列 std::vector<double> vec(arr.Length(), 0.0); // for文で要素を順に代入 for(size_t i = 0; i < arr.Length(); i++){ Napi::Value val = arr[i]; vec[i] = val.As<Napi::Number>().DoubleValue(); } return env.Null(); } // 配列の返却 Napi::Value Wrapper::getArr(const CallbackInfo& info){ Napi::Env env = info.Env(); // C++の配列 std::vector<double> vec = {1.0, 0.5, 0.25}; // for文で要素を順に代入 Napi::Array outArr = Napi::Array::New(env, vec.size()); for (size_t i = 0; i < vec.size(); i++) { outArr[i] = Napi::Number::New(env, vec[i]); } return outArr; } // プリミティブ型が混在した配列の返却 Napi::Value Wrapper::getReturns(const CallbackInfo& info){ Napi::Env env = info.Env(); // do Something C++ // 返り値として、 1.0 と "aabbcc" を返却する例 const int returnArgNums = 2; int zero = 0; int one = 1; Napi::Array retArr = Napi::Array::New(env, returnArgNums); retArr[zero] = Napi::Number::New(env, 1.0); retArr[one] = Napi::String::New(env, "aabbcc"); return retArr; }
index.jsを開く
index.js// addon.cc内の NODE_API_MODULE(addon, InitAll) が呼ばれる var Wrapper = require('bindings')('addon'); // addon.cc内の CreateObject() が呼ばれる var obj = new Wrapper() // wrapper.cc内で登録した getNum()が呼ばれる console.log(obj.getNum()); // ---- 本記事で追加した関数 ---- // let ans = obj.add(1,2) console.log(ans); // >> expected: 3 obj.setArr([1,2,3,4,5]); var arr = obj.getArr(); console.log(arr); // >> expected: [1.0, 0.5, 0.25] var arr = obj.getReturns(); console.log("ret1 =", arr[0], "ret2 =", arr[1]); // >> expected: ret1 = 1 ret2 = aabbcc // ---- 本記事で追加した関数 ---- //その他
- 返り値にユーザー定義型やオブジェクトを返す場合は このあたり が参考になるかもしれません。
- 投稿日:2021-02-13T18:30:53+09:00
音楽理論の学習・音楽制作支援ウェブアプリを作りました。
こんにちは!クフルダモノーツのYoshito Kimura(k1mu)です!
音楽理論の学習・音楽制作支援ウェブアプリ「O-TO」を作りました。
音楽を20年以上やっている僕が「あったら良いな」と思う機能を具現化しました。
プログラミングは得意ではないので、技術的に優れているかは分かりません。
ただ、機能的にはめちゃくちゃ便利だと思います。(๑˃̵ᴗ˂̵)و「O-TO」について
基本的には音楽に関する「音楽の本質と関係無い思考を肩代わりしてくれるアプリ」です。
たとえば、音符の長さの計算を頑張っても、言って音楽が良くなるわけではありません。むしろ疲れます。
音楽理論の学習も、難しい文章から読み解くより、視覚的に分かりやすく勉強できた方が楽です。そういった“創造の本質”とは関係の無い部分に、余計な頭脳と時間を使わないために活用してもらえればな と思います。
作った経緯
個人的に音楽の作業には「コレは無駄じゃ無いか?」と感じるものがたくさんありました。
たとえば、ディレイタイムを計算したり、五度圏で音の流れを考える作業などです。
これらの作業は、音楽の良し悪しには関係ありません。
むしろ、瞬時に結果が分かった方が試行錯誤の時間が多く確保できます。また、僕は音楽理論の学習に苦戦した経験から
「もっと音楽の専門知識を活用する敷居を下げたい」気持ちがずっとありました。
(音楽理論関係のブログを書いているのもそのため)
https://khufrudamonotes.com/category/music-theory-and-dtmそういうわけで、「音楽制作や音楽理論の学習を補助するウェブアプリ」の目指して出来上がったのが「O-TO(Ongaku Tools)」です。
現在提供している機能と、その説明
コードネーム
主な和音の構成音を視覚的に確認できます。
コードを学ぶ方は、解説図を見ながら表示を切り替えてみると、コードネームの仕組みが分かってくると思います。最終的には(特にジャズをやる方は)、頭の中でコードの構成音がパッと把握出来た方が良いかもしれません。
ただ、僕自身は出来るまで10年近くかかった気がするので、補助輪としても使えると思います。スケール
主要な音階の構成音を視覚的に確認できます。
こちらも、初学者の方がスケールを学ぶのに役立つはずです。少し変わったスケールも登録しておいたので
色々なスケールを試してみたいときに、いちいちキーの変更を頭の中で考えなくて良いので楽な筈です。モーダル・インターチェンジ
モーダルインターチェンジの候補を絞り込む機能です。
コード知識がある人は、特定のコード上で使用できるスケールを探すのにも使えます。
たとえば、ドミナント7thコード上でアドリブできるスケールと
テンションノートの関係などは視覚的に理解しやすいと思います。コード進行
良く使用されるコード進行を50種類以上まとめました。
オリジナル曲を作る方なんかは、ネタに困った時に便利だと思います。
しかも、表示を全てのキーに切り替えられます。
(ピアノの黒鍵に当たる異名同音をちゃんと処理しているところが、個人的な頑張りポイントです。)ダイアトニック・コード
主なダイアトニックコードの一覧表です。
こちらも全てのキーに表示を切り替えられます。ちなみに、僕は大体この表の環境を概ね「1つの環境」として頭の中で捉えていて、
この環境から離れる場合に「転調している」と考える場合が多いです。転調の間隔
転調の間隔を簡単に調べられます。
転調と言っても、キーが半音上がるくらいは簡単に分かると思います。
しかし、モードチェンジを絡めた複雑な転調とそのキー(調号)の判定を頭の中でやるのはなかなか大変だと思います。この機能を使えば、どんな複雑な関係にあるキーの転調も一瞬で分かります。
音価の計算
シンプルに音価の計算ができるだけです。
ここまで紹介した機能は、音楽を長くやっていればある程度脳内で対応できる人も多いと思います。
しかし、各BPMごとの音価を暗記している人は少ないんじゃないでしょうか。
ある意味一番便利な機能かもしれません。メトリック・モジュレーション
メトリックモジュレーションに関係する情報を調べられます。
メトリックモジュレーションは、整合性を持ったテンポチェンジなどに使われる手法です。おまけで音符を「分音符のみで表記した場合」や「符点音符のみで表記した場合」も計算される機能をつけました。
「現在の記譜法」の上手くできている部分と、そうでない部分を感じれる気がします。笑プログラミングの技術的な内容など
こちらの記事にまとめました!(๑˃̵ᴗ˂̵)و
・プログラミング何も分からない初心者が、独学でウェブアプリを作るまでの軌跡 まとめ
https://qiita.com/k0419/items/292d10f729992e7e22e3
- 投稿日:2021-02-13T18:28:47+09:00
Vueでローディング画面の実装
概要
Vueでローディング画面を作成します。
実装
ローディングのモジュールはいくつかありますが、今回は
vue-loaders
というモジュールを利用します。
アニメーションの種類が多かったのでこれにしました。導入方法はこちら
github今回は
mounted()
のタイミングでローディングを開始し、mounted()
が終わったタイミングでローディングを終了したいと思います。store
store/loading.jsconst state ={ loading: true, //true:ローディングを表示, false:ローディング非表示 }; const getters ={ loading: state => state.loading ? state.loading: '', }; const mutations ={ setLoading(state,loading){ state.loading = loading; }, }; const actions = { async startLoad(context){ context.commit('setLoading', true); }, async endLoad(context){ context.commit('setLoading', false); } }; export default{ namespaced: true, state, getters, mutations, actions }ローディングが開始したら
startLoad
を発火、終わったらendLoad
を発火して非表示にします。template
<template> <div class="new-article"> <div class="loader-space" v-show="loading"> <vue-loaders-ball-beat color="#FFF" class="loader"></vue-loaders-ball-beat> </div> <div class="contents" v-show="!loading"> <!--ローディング後に表示したいコンテンツ--> </div> </div> </template> <script> export default { computed: { loading(){ return this.$store.getters["loading/loading"]; }, }, methods: { async getPost() { const post = this.$store.getters["newtimeline/post"]; if(!post){ await this.$store.dispatch('newtimeline/getPost'); } }, }, mounted() { console.log('mounted start'); this.$store.dispatch('loading/startLoad') //ローディング開始をする loading = true; .then(()=>this.getPost()) //getPost()メソッドを開始 .then(()=>this.$store.dispatch('loading/endLoad')); //ローディング終了 loading = false; }, } </script>非同期処理で動かさないと
endLoad()
の方が先に走ってしまうので、非同期で実装しましょう。最後に
割と簡単に実装することができました。
面白いアニメーションもあり、使ってて楽しかったです。参考文献
- 投稿日:2021-02-13T18:17:40+09:00
【Vue.js】はじめてのVue.js〜概要+インスタンス+ディレクティブ〜
今回はJavaScriptのフレームワークである「Vue.js」についての記事です。学習途中で至らない点等あると思うのですが、備忘録兼自分と同じ初学者の方向けにまとめてみました。
現在はVue.jsを用いてポートフォリオを書き換えつつ、学習を進めています。記述したコードも用いているので、是非参考にしてみてください。ポートフォリオはこちら、GitHubはこちらのページをご覧ください。ポートフォリオ内の学習スキルを記述した以下の部分と
ヘッダーのアイコン部分を
の2つを例にインスタンス、ディレクティブの書き方をまとめています。目次
概要
- Vue.jsとは
- メリット
- 環境準備
インスタンス
- インスタンスとは
- 書き方
- elオプションオブジェクト
- dataオプションオブジェクト
- methodsオプションオブジェクト
ディレクティブ
- ディレクティブとは
- v-bind
- v-on
- v-html
- v-for
最後に
1. 概要
1.1 Vue.jsとは
JavaScriptのフレームワーク1の一種。DOM操作を自動化して効率良く、そしてコードを複雑にしすぎることなくシンプルにフロントエンドの開発が行うことができる。
Vue.jsという土台の上で、ライブラリを用いて必要な機能を追加していくことで、効率良く開発を進めることができる。1.2 メリット
扱うデータやイベントが多くなっても、コードが複雑になりにくいことが挙げられる。理由は大きく分けて2つある。
①DOM操作を自動で行ってくれる
今回の記事で紹介するインスタンスやディレクティブを用いることで実現することができる。詳細は以下の「2.インスタンス」「3.ディレクティブ」を参照。②DOM要素を部品化して使い回すことができる
こちらは次の記事で紹介するコンポーネントを用いることで実現することができる。詳細は次回更新する記事に記載。1.3 環境準備
HTMLファイルにCDN2を読み込む。至ってシンプル。HTMLのファイル名をindex.htmlとする。
index.html<!-- 開発バージョン、便利なコンソールの警告が含まれています --> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>または
index.html<!-- 本番バージョン、サイズと速度のために最適化されています --> <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>2. インスタンス
2.1 インスタンスとは
一言で表すと、複製したオブジェクト3のこと。
2.2 書き方
new Vue({...}) によって、Vueオブジェクトのインスタンスを作る。今回はskillsというインスタンスを作成している。
main.jsconst skills = new Vue({ el:'#skills', data:{ skills:[ { id:'frontend_skills', }, //他のバックエンド、DataBase等も同様なので省略 ], }, }) const header = new Vue({ el:'header', methods:{ scroll:function(event){ switch(event.currentTarget){ case $('.about-icon')[0]: const about = $('#about').offset().top; $("html, body").animate({ scrollTop: about - 20 }, 200); break; //他のcaseも同様なので省略 } } } })newは演算子の一つ、Vue({...})は、オブジェクトを複製するための特別な関数(コンストラクタ)である。コンストラクタの引数には、オブジェクトを渡す。Vue.jsではこのオブジェクトを、オプションオブジェクトという。オプションオブジェクトに決められたプロパティを設定することで、Vue.jsの機能を利用することができる。
skillsインスタンスでは
elオプションオブジェクト
、dataオプションオブジェクト
を、headerインスタンスではelオプションオブジェクト
、methodsオプションオブジェクト
を使用している。2.3 elオプションオブジェクト
element(要素)の略称。DOM要素を設定する。
#skills
では、id属性がskillsの要素を設定している。この要素内でのみインスタンスの中身を使用することができる。2.4 dataオプションオブジェクト
データを設定する。このデータにはVueインスタンスのプロパティとしてアクセスできる。
index.html<div id="skills"> <p>{{ skills[0].id }}</p> </div>このように記述することで、インスタンスのdataオプションオブジェクト内のskills配列のインデックス番号0番目要素のidプロパティの値を表示することができる。
{{...}}をMustache構文といい、インスタンスのdataオプションオブジェクト内のデータとHTMLを結びつけることができる。これをデータバインディングという。
ただし、Mustache構文では値をテキストデータとして扱うため、プロパティの値に<br>
や<span>
要素を記述しても、文字列として表示されてしまう。HTMLへの変換はv-htmlディレクティブ
を用いる必要がある。2.5 methodsオプションオブジェクト
elで設定したDOM要素内で関数を使用したい場合に用いる。
v-onディレクティブ
で指定することで関数を使用することができる。3. ディレクティブ
3.1 ディレクティブとは
「2.4 dataオプションオブジェクト」で紹介したように、Mustache構文を用いることで、DOM要素とインスタンスのデータをバインディングすることができた。
しかし、DOM要素の属性とインスタンスのデータをバインディングする際にMustache構文は使うことができない。
そこで登場するのがディレクティブ。一言で表すと、Vue.jsで使用する特別な属性のこと。3.2 v-bind
上記の例で、インスタンスのdataオプションオブジェクト内のskills配列のインデックス番号0番目要素のidプロパティの値を表示するのではなく、
<p>
のid属性に設定したい場合は、以下のように記述する。index.html<div id="skills"> <p v-bind:id="skills[0].id">v-bindディレクティブ</p> </div>v-bind:の後に属性を指定し、属性値にインスタンスのdataオプションオブジェクトのプロパティを記述する。
動的なclass属性のバインディングは少し特殊なのでメモしておく。
v-bind:class
にオブジェクトを渡すことでクラスを動的に切り替えることができる。その際、クラスの付与をデータプロパティの真偽性によって決めている。参考までに、ドキュメントの例を載せておく。
以下のような要素があったとする。isActive
がtrue
の場合はactive
クラスが付与され、hasError
がtrue
の場合はtext-denger
クラスが付与されることを表している。<div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }" ></div>インスタンス内のdataオプションオブジェクトは以下のものだったとする。
data: { isActive: true, hasError: false }すると、以下のように特定のクラスのみを付与することができる。
<div class="static active"></div>3.3 v-on
「2.5 methodsオプションオブジェクト」で紹介したmethodsオプションオブジェクトをDOM要素内で使用する際に用いる。
index.html<header> <span v-on:click="scroll"><i class="fas fa-user-alt"></i></span> <!-- 他のアイコンも同様なので省略 --> </header>main.jsconst header = new Vue({ el:'header', methods:{ scroll:function(event){ switch(event.currentTarget){ case $('.about-icon')[0]: const about = $('#about').offset().top; $("html, body").animate({ scrollTop: about - 20 }, 200); break; //他のcaseも同様なので省略 } } } })methodsオプションオブジェクトを使用したいDOM要素に
v-onディレクティブ
を記述する。
v-on:click="scroll"
のように、v-on:イベントの発生条件:"イベントハンドラ4"の形で記述する。3.4 v-html
「2.4 dataオプションオブジェクト」で紹介したように、Mustache構文を用いることでDOM要素とインスタンスのデータをバインディングすることができるが、値をテキストデータとして扱ってしまう。値に
<br>
や<span>
を使用してDOM要素内で反映させたい場合、v-htmlディレクティブを用いる。main.jsconst skills = new Vue({ el:'#skills', data:{ skills:[ { show:'<strong>フロントエンド</strong>', }, //他のバックエンド、DataBase等も同様なので省略 ], }, })index.html<div id="skills"> <p v-html="skills[0].show">v-htmlディレクティブ</p> </div>このように記述することで、インスタンスのdataオプションオブジェクト内のskills配列のインデックス番号0番目要素のshowプロパティの値を生のHTMLとして出力することができるため、
<strong>
が反映される。3.5 v-for
繰り返し処理を行うために用いる。
例えば、以下のskills配列のshowプロパティの値を全てDOM要素にバインディングして表示したいとする。main.jsconst skills = new Vue({ el:'#skills', data:{ skills:[ { id:'frontend_skills', show:'フロントエンド', }, { id:'backend_skills', show:'バックエンド', }, { id:'database_skills', show:'DataBase', }, { id:'infrastructure_skills', show:'インフラ', }, { id:'others', show:'その他', }, { id:'stydying', show:'学習中', }, ], }, })「2.4 dataオプションオブジェクト」で紹介したように、Mustache構文を用いて以下のように記述することができる。
index.html<div id="skills"> <p v-bind:id="skills[0].id">{{ skills[0].show }}</p> <p v-bind:id="skills[1].id">{{ skills[1].show }}</p> <p v-bind:id="skills[2].id">{{ skills[2].show }}</p> <p v-bind:id="skills[3].id">{{ skills[3].show }}</p> <p v-bind:id="skills[4].id">{{ skills[4].show }}</p> <p v-bind:id="skills[5].id">{{ skills[5].show }}</p> </div>しかし、変更が生じた場合、上記の書き方だと全ての要素を変えないといけないため非効率である。そこで
v-forディレクティブ
を用いる。以下のように書き換えることができる。index.html<div id="skills"> <p v-for="skill in skills" v-bind:id="skill.id">{{ skill.show }}</p> </div>
v-for="skill in skills"
で、skills配列の中身を取り出し、skillという変数に格納し、配列の要素の数だけ繰り返すことを表している。以降skill.プロパティ名
でデータにアクセスしている。4. 最後に
ポートフォリオを修正する中で使用したディレクティブのみ紹介しています。今後他のディレクティブを使用した際は、再度記事を更新します。
調べて概要を理解してコードを書く。これを繰り返すことが理解の1番の近道だと思うので、今後も継続していきます。
来週はDOM要素を部品化をするための
コンポーネント
を紹介します。
フレームワークとは、Webアプリケーションやシステムを開発するのに必要な機能が予め用意された骨組みのこと。便利ではあるが、フレームワーク毎にルールが存在する。 ↩
CDNとは、「コンテンツデリバリーネットワーク」(Content Delivery Network) の略で、世界中に張り巡らされた配信ネットワークを利用して、Webサイトにアクセスしようとするユーザーに効率的かつ高速にWebコンテンツを配信する仕組み。
配信元のサーバー(オリジンサーバー)にエンドユーザーのリクエストが集中するとサーバーへの負担が大きくなってしまう。そこで、オリジンサーバーのウェブコンテンツのコピーを取得した複数のキャッシュサーバーでウェブコンテンツを配信することで、オリジンサーバーへの負担を軽減している。 ↩データや処理のまとまり。配列との違いは、データや処理に名前(キー)を指定することができる点。特徴は「継承:変数を持たせてオブジェクトを複製することができる」、「多態性:インスタンス内でオブジェクトの内容を上書きすることができる」、「カプセル化:変数のアクセス権の指定」がある。 ↩
指定したイベント(click,hover等)が発生した際にプログラムが実行する処理。 ↩
- 投稿日:2021-02-13T18:13:12+09:00
ES2015における分割代入の使い方まとめ
ES2015における分割代入の使用例
使う場面:
1.複数の変数の宣言・代入を一つの文の中で行うことができる
2.データの代入元がオブジェクトのプロパティ群、或いは配列となります。使い方:
1.オブジェクトの分割代入
1.宣言なしで代入のみを行う
- データ元となるオブジェクトのプロパティ名と一致する変数にのみ対して代入を行う。
- {}がブロック構文と解釈されないために、 ()で囲む必要がある。
const obj1 = { a: 1, b: 2, c: 3 }; let b, c; ({ b, c } = obj1); console.log(`b:${b}`); // 出力結果:b:2 console.log(`c:${c}`); // 出力結果:c:32.宣言と代入を同時に行う
- 変数宣言に使うキーワードconst、letを使うこと
- ()をつける必要がない
- オブジェクトのプロパティ名と一致する変数名が必要
const obj1 = { a: 1, b: 2, c: 3 }; const { b, c } = obj1; console.log(`b:${b}`); // 出力結果:b:2 console.log(`c:${c}`); // 出力結果:c:32.配列の分割代入
1.宣言なしで代入のみを行う
- 左辺に並べた変数を[]で囲む
- 右側の配列要素に対応して順番に代入していきます。
- 残りの要素を捕捉して配列型変数に渡せる
const obj1 = [1, 2, 3, 4]; let a, b, rest; [a, b, ...rest] = obj1; console.log(`a:${a}`); // 出力結果:a:1 console.log(`b:${b}`); // 出力結果:b:2 console.log(`rest:${rest}`); // 出力結果:rest:3,42.宣言と代入を同時に行う
- 変数宣言に使うキーワードconst、letを使うこと
const obj1 = [1, 2, 3, 4]; const [a, b, ...rest] = obj1; console.log(`a:${a}`); // 出力結果:a:1 console.log(`b:${b}`); // 出力結果:b:2 console.log(`rest:${rest}`); // 出力結果:rest:3,4応用
配列分割代入を使うと、変数の値の入れ替えは簡単にできにできます。
let a = 5, b = 10; [a, b] = [b, a]; // 一時的な変数を使わずに済む
- 投稿日:2021-02-13T15:58:35+09:00
vue.jsでaxiosを使用してログイン時にレスポンスからユーザ情報を取得する
初めに
RailsとVue.jsを使用してアプリを作成していて、ログイン時にvue側でユーザ情報を取得したいと思ったのでやってみた。
謎にハマってかなり時間がかかったので、備忘録として残しておく。
やり方
取得するときのvue.jsの処理。簡潔にするために色々と省略している。
methods: { loginUser: function () { axios.post('api/auth/sign_in', this.user).then((response) => { }, (error) => { console.log(error) }) }この処理を行うと以下の結果が返ってくる。今回はテストで「Advanced REST client」を使ってる。
そうするとユーザ情報を持っているdataが返ってくる。まずdataは以下のコードで取得することができる。
response.data['data']これをさっきの処理に入れてコンソールに出力する。
methods: { loginUser: function () { axios.post('api/auth/sign_in', this.user).then((response) => { // 追加 console.log(response.data['data']) }, (error) => { console.log(error) }) }画像のようにデータが取得できるので、この後は自分が欲しい値を選択するだけ。例えばidが欲しいならこんな感じ。末尾のキーを変更すればOK。
response.data['data'].id後はこれをlocalStrageとか実装で使ってるやつにうまく当てはめて使用すればいい。
response.dataの中にさらにdataあるとか思わなくてめちゃくちゃ沼って時間かかった。最初にresponse.data.idを試してデータが取得できなかった時点で丁寧にvalueとkeyを確認してやってくべきだった。
- 投稿日:2021-02-13T15:39:12+09:00
Nuxt.jsとTypescriptとSCSSでかんたんなページ作成入門
公式ドキュメントに沿ってNuxt.jsでページを作成していきます。
日本語版があるのはありがたいですね。テキストエディタはおすすめされているVS Codeを利用します。OSはMacOS Big Surです。
プロジェクトの作成
対話式で各種設定が行われます。
UI frameworkは少し悩みましたが無しにしてみました。# yarn create nuxt-app nuxt-practice yarn create v1.22.10 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... [4/4] ? Building fresh packages... success Installed "create-nuxt-app@3.5.2" with binaries: - create-nuxt-app [#######################################################################################################################################] 342/342 create-nuxt-app v3.5.2 ✨ Generating Nuxt.js project in nuxt-practice ? Project name: nuxt-practice ? Programming language: TypeScript ? Package manager: Yarn ? UI framework: None開発サーバーの起動
# cd nuxt-practice # yarn dev ? Are you interested in participating? Noこれで、 http://localhost:3000/ にサーバーが立ち上がって、Nuxt.jsの空ページが表示されます。
ページの作成
codeコマンドで、カレントディレクトリをVS Codeで開けるようにしておくと便利です。
# code .NuxtLinkでルーティングも出来ます。
pages/about.vue<template> <NuxtLink to="/">Home page</NuxtLink> </template>pages/index.vue<template> <main> <h1>Home page</h1> <NuxtLink to="/about"> About Page </NuxtLink> </main> </template>scssの追加
sassパッケージのインストール
yarn add -D sass sass-loader@10 fibersグローバルに読み込ませたいcssを設定しておきます。
// Global CSS: https://go.nuxtjs.dev/config-css css: [ { src: '~/assets/style.scss', lang: 'scss' }, ],SCSSぽい入れ子のCSSを作成します。
assets/style.scssmain { margin: 20px; h1 { color: red; } }無事CSSが反映されました。
機能追加
少しVueぽい書き方で動きをつけてみます。
pages/index.vue<template> <main> <h1>Home page</h1> <NuxtLink to="/about"> About Page </NuxtLink> <br><br> <button v-on:click="counter += 1">Click!</button> <p>The button above has been clicked {{ counter }} times.</p> </main> </template> <script> export default { data() { return{ counter: 0 } }, mounted() { alert('mounted!'); } } </script>ボタンをクリックするとカウントアップするようになりました。
- 投稿日:2021-02-13T15:02:14+09:00
GASとJSを使ってサイドバーで進行状況や途中結果を表示させる
TL;DR
やたら時間のかかるGASの処理に対して、サイドバーを使って進行状況や途中結果を表示させるようにします。
大まかな方針
私が考える限り、実現方法はだいたい3通りありそうです。
- 計算がある程度進むたびにサイドバーを起動しなおす
- サイドバーの更新と計算を非同期に実行する
- 計算のキックをサイドバーに任せる
で試したところ、3.が一番良さげな結果でした。
ひとまず1から順に説明していきます。ひとまず、スプレッドシート上で何かやる、という想定です。1. 計算がある程度進むたびにサイドバーを起動しなおす
この方法は以下の記事でほぼ解説されています。
ただこれ、やってみるとわかるんですがサイドバーが再起動するたびチラつくんです。
単純に見づらいのもありますが、サイドバーを表示に使うだけじゃなくて、そこからコピペしたりサイドバー上で何か操作するような用途まで考えるとちょっと厳しいです。2. サイドバーの更新と計算を非同期に実行する
なので再起動はせず、サイドバーはサイドバーで動かし、サイドバー内で表示を更新することを考えます。
つまり、以下の2つを同時にやります。
- GASで計算をする
- サイドバー上のJavascriptで計算を読み取り、表示する。
この場合当たり前ですが、表示部分はGASではなくJSでやることになります。なのでそっち側のデバッグにはブラウザのデベロッパーツールなんかを使います。
先に全部のコードを書いてしまいます。まずはGAS側。
gas1.gsconst sheet = SpreadsheetApp.getActive().getSheetByName('作業用シート'); const cell = sheet.getRange(1, 1); // サイドバーからキックされて進捗を返す function getProgress() { return cell.getValue(); } // 実際にする計算 function calc() { for (let i = 0 ; i < 10 ; i++) { Utilities.sleep(2000); // 何か計算してる代わり cell.setValue(cell.getValue() + 1); } } // 状態の初期化 function init() { cell.setValue(0); } // スタート function start() { // 初期化 init(); // サイドバー表示 const html = HtmlService.createHtmlOutputFromFile('サイドバー'); SpreadsheetApp.getUi().showSidebar(html); // 計算 calc(); }こっちはサイドバーのHTMLです。
サイドバー.html<!DOCTYPE html> <html> <head> <base target="_top"> <script> function run() { id = setInterval(function() { google.script.run.withSuccessHandler(function(response) { if (response >= 10) { clearInterval(id); document.getElementById("progress").innerHTML = '終了'; } else { document.getElementById("progress").innerHTML = response.toString(); } }).getProgress(); }, 1000); } </script> </head> <body onload="run()"> 現在: <span id="progress"></span> </body> </html>非同期なのでたまにシート上の内容とサイドバーの表示がずれますが、それはそういうものということで。
以下はコードの説明です。GAS側
計算本体はこの
calc()
です。ここにやりたいことを書きます。// 実際にする計算 function calc() { for (let i = 0 ; i < 10 ; i++) { Utilities.sleep(2000); // 何か計算してる代わり cell.setValue(cell.getValue() + 1); } }一方で、サイドバーから叩かれたら途中結果を返すのがこの
getProgress()
です。// サイドバーからキックされて進捗を返す function getProgress() { return cell.getValue(); }そして、すべての計算の開始はここです。サイドバーを起動しつつ、計算を開始します。
// スタート function start() { // 初期化 init(); // サイドバー表示 const html = HtmlService.createHtmlOutputFromFile('サイドバー'); SpreadsheetApp.getUi().showSidebar(html); // 計算 calc(); }サイドバー側
サイドバー側はちょっとややこしいので、多めに説明します。
サイドバーからGASを叩く仕組み
サイドバー内のJavascriptからGASの
foo()
を叩きたい場合、google.script.run.foo()
と書けば可能です。
ただし、この関数はfoo()
の返り値を返しません。返り値を受け取る場合、withSuccessHandler()
にハンドラとなるコールバック関数を登録して、そのコールバックの引数として返り値を受け取ります。
つまり、コールバック関数function success(response) {...}
みたいなものを用意したとすると、google.script.run.withSuccessHandler(success).foo()
と呼び出すことでsuccess()
の引数にfoo()
の結果が返ります。この例ではGAS側の
getProgress()
を使って状況を取得したいので、google.script.run.withSuccessHandler(success).getProgress()
を呼び出すことになります。
setInterval()
で定期的に状況をチェックする
setInterval(bar, time)
とやればtime
(ミリ秒単位)ごとにbar
がキックされます。
これと、さっきのgoogle.script.run
を組み合わせて、定期的にGAS側のgetProgress()
を叩き、状況をチェックします。
なお処理終了が確認できたらclearInterval()
を叩きます。使用方法は上のコードを参照。問題点とかハマりポイントとか
この方法で済んでしまうことも多々あると思うのですが、1つだけ注意点があります。
google.script.run
で叩かれる関数は別プロセスで走ります。つまり、GAS側と同じコードを叩いてもGAS側の計算途中の状態をgoogle.script.run
で叩いても直接読み取ることはできません。
ただしスプレッドシートの状態は共有できます。なので、GAS側で途中の状態を返す関数(この例ではgetProgress()
)はスプレッドシートから現在の計算状態を読み取る必要があります。計算の状態がスプレッドシートにすぐ出ないか、出てもぱっと読み取れない場合、途中経過の値をどこか専用の別シートに書き出す方法もありますが、それできるんなら別にサイドバー要らないって場合も多いです。
3. 計算のキックをサイドバーに任せる
2の方法だとうまくハマらない場合に使えるのがこちらの方法です。私はこれでやることが多いです。
簡単に言うと、計算の主導権をサイドバー側にしてしまう方法です。こちらも先にコードを貼ります。まずはGAS側。
gas2.gs// 実際にする計算 function calc(value) { Utilities.sleep(2000); // 何か計算してる代わり value += 1; return value; } // 状態の初期化 function init() { } // スタート function start() { // 初期化 init(); // サイドバー表示 const html = HtmlService.createHtmlOutputFromFile('サイドバー2'); SpreadsheetApp.getUi().showSidebar(html); }随分スッキリしました。ここではスプレッドシートを直接読み書きしていません。
(実際にはcalc()
内で色々操作するんでしょうが)次はサイドバーのHTMLです。
サイドバー2.html<!DOCTYPE html> <html> <head> <base target="_top"> <script> function iterate(value) { google.script.run.withSuccessHandler(function(response) { if (response >= 10) { document.getElementById("progress").innerHTML = '終了'; } else { document.getElementById("progress").innerHTML = response.toString(); iterate(response); } }).calc(value); } function run() { iterate(0); } </script> </head> <body onload="run()"> 現在: <span id="progress"></span> </body> </html>考え方
この方法の基本的な考え方は、以下のようになります。
- サイドバーから
calc()
をキックcalc()
では1区切りごとの計算だけやって結果を返す- サイドバーは
calc()
の結果を受け取って表示、続きがあるならcalc()
をまたキック- 2〜3を繰り返す
で、これを何も考えずにwhile文で書くとこんな感じでしょう。
function run() { while (現在の状態が終了条件を満たさない) { var r = calc(); 表示(r); var 現在の状態 = なんか計算(r); } }これを、敢えて再帰で書き直します。
function iterate(前の状態) { if (終了条件を満たさない) { var r = calc(前の状態); var 現在の状態 = なんか計算(r); iterate(現在の計態); } } function run() { iterate(初期状態); }で、これをサイドバー内のスクリプトで実現します。
しかしcalc()
はGAS側なので、google.script.run
を使って呼び出します。function iterate(前の状態) { if (終了条件を満たさない) { var r = google.script.run.calc(前の状態); var 現在の状態 = なんか計算(r); iterate(現在の計態); } } function run() { iterate(初期状態); }しかしこれは動きません。
上の2の方法のところで説明したとおり、google.script.run
でGAS側関数を呼び出したときの返り値はwithSuccessHandler()
を使ってコールバックで受け取らないといけません。
つまり、var r = google.script.run.calc(前の状態); var 現在の状態 = なんか計算(r); iterate(現在の計態);この返り値
r
を受け取って以降の処理はコールバック関数として別にする必要があります。
すると、こうなります。function iterate(前の状態) { // コールバック関数 var success = function(r) { var 現在の状態 = なんか計算(r); iterate(現在の計態); } if (終了条件を満たさない) { google.script.run.withSuccessHandler(success).calc(前の状態); } } function run() { iterate(初期状態); }最初に示したコードと多少形が違いますが、理屈は同じです。
さらなる工夫
途中でユーザーの確認を入れてもいい場合
で、3の方法も穴がないかと言うとそんなことはないです。
自分が色々やってみると、全体の計算がやたら重くて時間がかかる場合に意味不明のエラーで止まることがあります。
これは原因を突き止められていなくてあくまで想像なのですが…G Suiteではなく無料でGoogleスプレッドシートを使っている場合、GASで一度に6分以上計算をするとタイムアウトします。それと同様の原因で、計算リソースを食いすぎると止められてしまうのかもしれません。で、そういう場合の対処として、計算途中でユーザとのインタラクションを入れていい場合はこんな工夫ができます。
コールバック関数でiterate()
を呼ぶ代わりに、計算を継続するか確認するボタンを表示するshowButton()
を呼び出します。var success = function(r) { var 現在の状態 = なんか計算(r); // iterate(現在の計態); <- 以前のもの showButton(現在の計算); // <- こうする }
showButton()
の中では、<button data-status=現在の状態 onClick="iterate(this.dataset.status)">続きを計算する</button>みたいなボタンを生成します。
これで、
calc()
を呼び出す前にユーザに続きを計算するか確認できるようになります。
経験上、これを入れると意味不明なエラーで落ちることはほぼないです。
また、元々全部の計算をする必要がなくて、必要な結果が得られたら止めたい(検索とか)場合にもこの方法は有効です。
withFailureHandler()
は積極的に使ったほうがいい上のコードでは一切使っていませんが、
google.script.run
でGAS側の関数がエラーで落ちた場合のエラーハンドラを登録するwithFailureHandler()
があります。
単純な異常処理のためだけでなく、これをちゃんと使ったほうがデバッグの生産性も格段に上がるので、実際にここにあるようなコードを書くときは積極的に使った方がいいです。
- 投稿日:2021-02-13T13:23:08+09:00
innerHTML()で作成した要素へのクリックイベントを追加するには(jQuery)
main.js$("#update_profile").html("<input type='button' id='update' value='更新'>")index.html<div id="update_profile"></div>main.js$("#update").click(function() { console.log("click"); })#updateに対して直接Clickイベントを発生させても上手くいきません
これはjsファイル読み込み時には#updateが存在していないからですね
この場合はon()を使ってclickイベントを追加していきます。main.js$("#update_profile").on("click","#update", function() { console.log("click"); });on()には第一引数にはイベントを第二引数には実際にclickイベントを追加したい要素を間接的に指定しております。
もちろんですが親要素の#update_profileはjsファイルを読み込む前に存在していないといけないです。
- 投稿日:2021-02-13T12:45:38+09:00
【Step-By-Step】Node.jsからC++クラスを利用する
はじめに
この記事は、javascriptからC++を呼び出す処理が必要になった時の備忘録です。
node-addon-apiというラッピングライブラリを活用します。
この記事の内容を「マネすれば動く」ように意識して書いています。
Electronなどのデスクトップアプリに応用できます。事前準備
- 下記のパッケージは事前にインストールしておいてください
- npm
- node.js
応用記事はこちら!
1. Node.js からC++関数への引数、返り値まとめ
2. Coming soon...
目次
1. プロジェクトの新規作成
- 空のフォルダを新規作成します。今回は例として、
napi_sample
というフォルダ名にしました。- 作成したフォルダに移動し、下記のコマンドを実行し、必要モジュールをインストールします。
terminal$ npm init -y $ npm install node bindings node-addon-api
- コマンド実行後、下記のようなフォルダ構成となります。
カレントディレクトリ
├── node_modules
├── package-lock.json
└── package.json
- さらに、下記のようなpackage.jsonが自動的に作成されます。
package.json{ "name": "doit_myself", "version": "1.0.0", "description": "", "main": "index.js", //<-- 開始時にこの.jsファイルが読み込まれる "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "bindings": "^1.5.0", "node": "^15.8.0", "node-addon-api": "^3.1.0" } }index.jsを作成する
カレントディレクトリ
├── node_modules
├── package-lock.json
├── package.json
└── index.js <-- 新規作成
- 起動時に読み込まれるindex.jsを追加しましょう。
- 下記のようなサンプルとします。
index.jsconsole.log("Hello! node.js");ターミナルで動作確認する
- ここまでの環境構築がうまくいっているか確認します。
- index.jsがあるディレクトリで下記のコマンドを入力してください。
terminal$ node . >> Hello! node.js2. Cppファイルを追加する
- ここからは、jsで利用するためのCppラッパークラスを作成していきます。
- wrapper.h, wrapper.cc, addon.ccの順に説明します。
カレントディレクトリ
├── node_modules
├── package-lock.json
├── package.json
├── index.js
├── addon.cc <-- 新規作成
├── wrapper.cc <-- 新規作成
└── wrapper.h <-- 新規作成
※ 拡張子.cc はC++ファイルのことです。本質は .cppと変わりません。ラッパークラスの作成
- ネイティブC++をラッピングするクラスを作成します。
- このクラスの目的は、jsから渡された引数をC++で解釈できる形にし、C++の返り値をjsが利用できる形式に変換して渡すことです。
- 下記のwrapper.h, wrapper.ccをテンプレとしてお使いください。
wrapper.h#ifndef WRAPPER #define WRAPPER #include <napi.h> // 必要なヘッダ class Wrapper : public Napi::ObjectWrap<Wrapper> { public: static Napi::Object Init(Napi::Env env, Napi::Object exports); static Napi::Object NewInstance(Napi::Env env, const Napi::CallbackInfo& info); Wrapper(const Napi::CallbackInfo& info); ~Wrapper(); Napi::Value getNum(const Napi::CallbackInfo& info); private: double m_value; }; #endifwrapper.cc#include "wrapper.h" #include <napi.h> using namespace Napi; // ---------------------------------------------------------- // // ---------------------のり付け部分--------------------------- // // ---------------------------------------------------------- // // new() の定義 Napi::Object Wrapper::NewInstance(Napi::Env env, const Napi::CallbackInfo &info) { Napi::EscapableHandleScope scope(env); // jsからコンストラクタに渡されるArgsは infoに配列として入っている const std::initializer_list<napi_value> initArgList = {info[0]}; // ここでWrapper:::Wrapper()が呼ばれる Napi::Object obj = env.GetInstanceData<Napi::FunctionReference>()->New(initArgList); // gcにメモリ解放されないようにスコープを除外する return scope.Escape(napi_value(obj)).ToObject(); } // メンバ関数のバインド Napi::Object Wrapper::Init(Napi::Env env, Napi::Object exports) { Napi::Function func = DefineClass( env, "Wrapper", { // ここにメンバ関数を登録する InstanceMethod("getNum", &Wrapper::getNum), // InstanceMethod("jsから呼び出す際の関数名", "呼び出したいC++メンバ関数名"), }); Napi::FunctionReference *constructor = new Napi::FunctionReference(); *constructor = Napi::Persistent(func); env.SetInstanceData(constructor); exports.Set("Wrapper", func); return exports; } // ---------------------------------------------------------- // // --------------- Wrapperクラスの定義はこれより下 --------------- // // ---------------------------------------------------------- // // コンストラクタ Wrapper::Wrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Wrapper>(info) { m_value = 0.0; }; Wrapper::~Wrapper(){}; // メンバ関数 Napi::Value Wrapper::getNum(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); return Napi::Number::New(env, this->m_value); }
- 補足
- wrapper.cpp内の関数
Napi::Function func = DefineClass()
において、自作のC++メンバ関数を登録する必要があります。サンプルコードInstanceMethod("getNum", &Wrapper::getNum),
のように、InstanceMethod("jsから呼び出す際の関数名", "呼び出したいC++メンバ関数名")
として登録しなければなりません。jsとの結合用cppファイルを作る
- 次に、Wrapperクラスをjsモジュールとしてエクスポートするための処理をaddon.ccに記述します。
- こちらも詳細説明は省略します。テンプレとしてお使いください。
addon.cc#include <napi.h> #include "wrapper.h" #include <iostream> // jsオブジェクトが初期化された時 new()の呼び出し Napi::Object CreateObject(const Napi::CallbackInfo& info) { return Wrapper::NewInstance(info.Env(), info); } // js内でexport()が呼び出されたとき Napi::Object InitAll(Napi::Env env, Napi::Object exports) { // 関数定義 Napi::Object new_exports = Napi::Function::New(env, CreateObject); return Wrapper::Init(env, new_exports); } // jsへバインドするためのマクロ // jsで、 export('bindings')('addon')と記述したとき、上記のInitAll()が呼び出される NODE_API_MODULE(addon, InitAll)3. Cppファイルをビルドする
- 作成してたcppをビルドするための設定ファイルを作ります。
- binding.gyp というファイルです。
- VisualStudioのprojectのプロパティ設定、CMakeLists.txtと似たような設定をします。
binding.gypの追加
カレントディレクトリ
├── node_modules
├── package-lock.json
├── package.json
├── index.js
├── addon.cc
├── wrapper.cc
├── wrapper.h
└── binding.gyp <-- 新規作成binding.gyp{ "targets": [ { # ↓addon.cc内の NODE_API_MODULE(addon, InitAll) と同名にする "target_name": "addon", "cflags!": [ "-fno-exceptions" ], "cflags_cc!": [ "-fno-exceptions" ], # ↓必要な.ccファイルを全て記述する "sources": [ "addon.cc", "wrapper.cc"], "include_dirs": [ "<!@(node -p \"require('node-addon-api').include\")" ], "defines": [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], } ] }
- 上記ファイルをコピペいただければ問題ないです。
- 注意点として sources セクションには、使用する .cc (or .cpp) 拡張子のファイルを全て登録してください。
ビルド実行
- 下記コマンドを実行し、ビルドしてください。
terminal$ npm install . >> gyp info ok と表示されればビルド完了4. JavaScriptからビルドしたCppクラスを使う
- お待たせしました。最後に index.jsから wrapper.ccのクラスを使ってみましょう。
index.jsの書き換え
カレントディレクトリ
├── node_modules
├── package-lock.json
├── package.json
├── index.js <-- 書き換え
├── addon.cc
├── wrapper.cc
├── wrapper.h
└── binding.gyp
- index.jsを以下のように書き換えてください。
index.js// addon.cc内の NODE_API_MODULE(addon, InitAll) が呼ばれる var Wrapper = require('bindings')('addon'); // addon.cc内の CreateObject() が呼ばれる var obj = new Wrapper() // wrapper.cc内で登録した getNum()が呼ばれる console.log(obj.getNum());index.jsの実行
ターミナルで次のように実行します。
terminal$ node . >> 0 と表示されれば成功 >> Wrapper.m_valueの値が表示されています。お疲れ様でした。
node-addon-api は"お約束ごと"が多いので、
私はこの記事のようなテンプレを作り、使いまわしています。
参考になれば幸いです。参考リンク
- ドキュメント
- node-addon-examples
- 本格的に学びたい方は、このexampleを順にやっていくと良いでしょう
Others
- コンストラクタに複数の引数を渡す
- メンバ関数の引数、返り値について
- 別のC++クラスを利用する
- 投稿日:2021-02-13T11:55:32+09:00
50行でちゃんと動くリマインダーアプリ「Notification-CLI」をリリースしました
50行でちゃんと動くリマインダーアプリ「Notification-CLI」をリリースしました
以下かんたんな日本語版クイックスタートになります。
Minimalistic Command Line Notification Application under 50 Lines
50行で実装のシンプルなコマンドラインベースのリマインダーアプリケーション。
クイックスタート Quick Start
This will nofity you when 2021 February 20, PM 6:00.
以下で2021年2月20日18時に通知をします。
./noc.js -d 2021022018 --desktopVery much minimalistic.
とてもミニマリスティック。
インストール Installation
git clone https://github.com/yuis-ice/notification-cli.git cd notification-cli chmod 755 ./noc.js npm i必要環境 Requirements
- node.js v13.10.1 or higher
# node.js [nvm-sh/nvm](https://github.com/nvm-sh/nvm) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash bash nvm install v13.10.1 node -v使用例 Examples
This will only notify you by command line console output:
コマンドライン出力で通知します。
./noc.js -d 2021022018The output be like:
出力はこんな感じです。
$ ./noc.js -d 202102120234 2021-02-12T02:33:16+09:00 Jobs started... 2021-02-12T02:33:16+09:00 You will be notified at: 2021-02-12T02:34:00+09:00 2021-02-12T02:34:00+09:00 Notified. $You can abbreviate your seconds, minutes, hours and so on:
秒、分などを省略しても動きます。
./noc.js -d 2022This will notify you when PM 11:00 in the day:
その日の23時になったら通知します。
./noc.js -d 23 --format HHAn alias makes your code much more minimalistic:
エイリアスを定義すると、よりシンプルになります。
alias notify="./noc.js --format MM,DD,HH" # Specifying absolute path recommended notify -d 2,20,18This will run your command:
シェルコマンド、外部コマンドも実行できます。
./noc.js -d 2021022018 -c "firefox.exe sound.mp3"No logs on background be like:
バックグラウンドでマルチインスタンスに使用するのもいいですね。
./noc.js -d 2021022018 --desktop --hide-log --log "" &My favorite format <3
僕のお気に入り。
./noc.js -d 2021022018 -c "firefox.exe sound.mp3" --desktop -t "your appointment soon"コマンドラインオプション Command line Options
$ ./noc.js Usage: notification-cli [options] Options: -d, --date <date> specify date to fire (e.g. "2022010100" for 2020/1/1 00:00) (default: null) -f, --format <format> specify date format (default: "YYYYMMDDHHmmss") -D, --desktop enables desktop notification -t, --title <text> specify title (default: "Notification-CLI") -m, --message <text> specify message (default: ":)") -c, --exec-command <command> specify command to run (e.g. firefox.exe ringtone.mp3) (default: null) -l, --log <text> specify console log message (default: "Notified.") -H, --hide-log hide infomation logs -h, --help display help for command一言
コードを書くよりreadmeを書く時間のほうが長くなってしまった例。
momentjsとnode-scheduleのおかげでimportやコマンドラインオプション部分を除くメインのコードはなんと10行程度で収まっています。
ソースコードを見られたら少し恥ずかしいかもしれない。対してソースコードのシンプリシティは初学者にとってそのコードを研究する敷居を低くしてくれると思うので、node.jsあるいはプログラミング初学者の人に積極的に参考にして頂ければ嬉しいと思う。
- 投稿日:2021-02-13T11:29:19+09:00
LaravelでFullcalendarに登録した内容を更新する方法
はじめに
前回に続き、今回はFullcalendarで登録した情報を更新する方法についてご説明します。
LaravelでFullcalendarを実装する方法また、今回はカレンダー上の登録したイベントをクリックするとモーダルが出てきて、そのモーダル上で更新ができるようにします。モーダルでは更新前の情報も確認できるようにしたいと思います。
-各バージョン
-Laravel 6.x
-PHP 7.4.9
-MySQL 5.7.30
-Fullcalendar v5Fullcalendarはバージョンによって記述方法が異なるので注意してください。
v4の記事を参考にしてもうまくいかないことが多かったです。なお、フォルダ名とうは登録時に作成したものをそのまま使用しておりますのでご了承ください。
更新用のモーダルを用意する
モーダルはJavaScriptのプアグインであるMicromodal.jsを使用しました。
モーダルに関しては自作のものではなく、プラグインを利用した方が利点が多いというツイートを見かけたので、今回は自作をせずにプラグインを使用しています。Micoromodal.jsのダウンロードは以下から行えます。
(https://micromodal.now.sh/)今回はCDNで読み込ませました。
event.blade.php<!-- Micromodal.js --> <script src="https://unpkg.com/micromodal/dist/micromodal.min.js"></script>続いて、モーダル用のbladeを作成し、更新フォームを作っていきます。
こちらはこの後@include
でevent.blade.php内で読み込ませるので、直接event.blade.php内に書いても問題ないです。また、
header
に関してはMicromodal.jsの公式ドキュメントを参考にしています。
フォームはmain
タグ内に書いていきます。moda.blade.php<div class="modal micromodal-slide" id="modal-1" aria-hidden="true"> <div class="modal__overlay" tabindex="-1" data-micromodal-close> <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title"> <header class="modal__header"> <h2>Editing my task list</h2> <button class="modal__close" aria-label="Close modal" data-micromodal-close></button> </header> <main> <form method="POST" action="{{ route('editEvent') }}"> @csrf <input type="hidden" id="id" value="" name="id"> <input type="text" id="edit_title" name="title" value=""> <input type="date" id="edit_start" name="start" value=""> <input type="color" id="edit_color" name="textColor" value=""> </form> </main> </div> </div> </div>通常だと
value
に更新前の情報を入れることで、フォームで確認できるのですが、今回はJSで指定するので空にしています。続いて、カレンダー上のイベントをクリックした際に、モーダルが出てくるように設定していきます。
カレンダー上のイベントをクリックした時の挙動は、Fullcalendarの公式ドキュメントを確認すると、eventClick
というプロパティが用意されているようなので、こちらを前回作成したJSに追加していきます。
eventClickevent.blade.php<script> $(document).ready(function () { $('#calendar').fullCalendar({ // はじめりの曜日を月曜日に変更 デフォルトは日曜日になっており、日=0,月=1になる firstDay: 1, headerToolbar: { right: 'prev,next' }, events: '/home', // ここから追加 eventClick: function(info){ document.getElementById("id").value = info.id; document.getElementById("edit_title").value = info.title document.getElementById("edit_start").value = info.start._i document.getElementById("edit_color").value = info.textColor MicroModal.show('modal-1'); } }); }); </script>
info
の中には更新前の情報が入っているので、その情報をそれぞれ先ほど指定したフォーム内のid
属性をgetElementById
で取得し、value
値に指定しています。
また、start
に関しては、console.log(info.start)
で見てみると、_isAMomentObject: true, _i: "2021-02-06", _f: "YYYY-MM-DD", _isUTC: true, _pf:
となっており、_i
の箇所に日付が格納されているのがわかると思います。
そのため、info.start._i
とすることで日付を取得するようにしています。これで、更新前の情報が表示されるようになりました。
また、event.blade.php内でmodal.blade.phpを忘れずに読み込んでください。
私は前回作成した新規登録用のフォームの真下に書きました。event.blade.php@include('modal')コントローラー、ビューを追加する
先ほど
action="{{ route('editEvent') }}
で指定したルートを設定します。web.phpRoute::post('/editEvent', 'EventController@editEvent');続いてコントローラーです。
EventController.phpuse App\Models\Event; //冒頭で宣言する public function editEvent(Request $request) { // 送信されてきたidをEventテーブルに登録されているデータと紐付ける $event = Event::find($request->input('id')); $event->title = $request->input('title'); $event->start = $request->input('start'); $event->textColor = $request->input('textColor'); $event->save(); return redirect('/home'); }以上で更新用モーダルの完成です!
さいごに
今回は、前回作成したFullcalendarの更新処理を、モーダルを利用して作ってみました。
また次回以降は登録したデータの削除方法についても書いていきたいと思います!最後まで読んでいただきありがとうございました!
- 投稿日:2021-02-13T04:55:22+09:00
Javascriptのカリー化と部分適用とパイプライン演算子の話。
皆さんアロー関数大好きですよね。
アロー関数かっこいいですよね。const plus = (x, y) => x + y引数を2つ受け取って和を返す関数を作りました。さてカリー化しましょう。
は?カリー化ってなにって感じですよね...考えるな、感じろ。
というわけで理論的な説明は置いといてカリー化を感じてください。
const curriedPlus = x => y => x + yさて問題です。関数curriedPlusの説明として正しいのはどちらでしょう?
1. 引数を2つ受け取って和を返す関数。
2. 引数を1つ受け取って(引数一号くんと名付けましょう)、「引数を1つ受け取って引数一号くんとの和を返す関数」を返す関数。勘のいいあなたならもうお気づきでしょう。答えは2です。
const curriedPlus = x => (y => x + y)ほら、関数が返ってるように見えるでしょ?
例えばxに10を代入してみましょう。
const curriedPlus10 = curriedPlus(10) //これは下の文と同義です。 const curriedPlus10 = y => 10 + yじゃあそろそろ和を求めましょう。関数curriedPlus10の引数に3をわたす、つまり、yに3を代入しましょう。
curriedPlus10(3) /* 13 */え???カリー化って毎回curriedPlus10みたいな中間関数を作るの???
いいえ、説明のために作成しただけです。curriedPlus(10)(3) /* 13 */もしいきなり上の記述を見たら何が起きているのか理解に時間がかかったかもしれません。
しかし、今ならわかりますね?
curriedPlus(10)が返却する関数の引数に3という引数を与えているのです。さて、カリー化を感じていただけたでしょうか?
ここまでくれば、カリー化の定義を聞いても「???」とはならないでしょう。カリー化とは複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。
by Wikipediaで、カリー化の何が美味しいの?ってなると「部分適用」の話になるんですけど、ここまで読んでくれたみなさんはすでに部分適用を経験しています。
そう、「curriedPlus10」です。こいつです。
「部分適用とは、複数の引数をとる関数の一部の引数に実引数を適用する操作のこと」なんですね。
by Wikipediaあれ?最初のクイズの答えで1は間違いって言ってたくせにWikipediaは「複数の引数をとる関数」って言ってるぞ?
説明しましょう。
const plus = (x, y) => x + y const curriedPlus10 = y => plus(10, y)これが部分適用で、これをカリー化と組み合わせてるわけですね。
で、なぜこの話をしたかというとES Nextで定義されているパイプライン演算子を使用するときにこの部分適用が効いてくるからなんですね。
パイプライン演算子は「|>」こういうやつです。
実験的なパイプライン演算子 |> (現在はステージ 1 です) は、式の値を関数に接続します。これによって、読みやすい方法で一連の関数呼び出しを作成することができます。結果的に、単一の引数を用いた関数呼び出しの糖衣構文となり、次のように書くことができます。
by MDN Web Docsf(g(h(i(10)))) // と記述するところを 10 |> i |> h |> g |> f // のように記述できるんです。こういうときに可読性を上げてくれそうですよね!おいおい、カリー化と部分適用の話はどこいった?今からしますよ。
const plus = (x, y) => x + y const double = num => num * 2このような関数たちが定義されているとして、
double(plus(10, 3))をパイプライン演算子を使って表現してみましょう。
3 |> plus(10, ...) |> doubleあれ。こまりました。「...」の部分はどうしたらいいのだろう?
パイプライン演算子は「単一の引数を用いた関数呼び出しの糖衣構文」でしたね。
引数が2つの関数だから1つしか引数を渡さなかったら当然エラーになりますね...ここでカリー化、部分適用が効くんですね。
const curriedPlus = x => y => x + y const double = num => num * 2 3 |> curriedPlus(10) |> double /* 26 */なんでこれはうまく行くのか?
「curiiedPlus(10)」は引数を1つ受け取って10足して返す関数だからですね。
そう、「単一の引数を用いた関数呼び出し」になっているんですね。以上、ありがとうございました!!!
- 投稿日:2021-02-13T04:55:22+09:00
駆け出しエンジニアよ、これがカリー化だ。
駆け出しエンジニアよ、これがカリー化だ。
などと偉そうに言っている私もWebエンジニアになって11ヶ月でまだまだ駆け出しエンジニアの域を出ていないのですが、少しだけ得意なJSについて書くので大目に見ていただければと思います。
本記事では以下について言及しています。
- アロー関数
- カリー化
- 部分適用
- パイプライン演算子
皆さんアロー関数大好きですよね。
アロー関数かっこいいですよね。const plus = (x, y) => x + y引数を2つ受け取って和を返す関数を作りました。さてカリー化しましょう。
は?カリー化ってなにって感じですよね...考えるな、感じろ。
というわけで理論的な説明は置いといてカリー化を感じてください。
const curriedPlus = x => y => x + yさて問題です。関数curriedPlusの説明として正しいのはどちらでしょう?
1. 引数を2つ受け取って和を返す関数。
2. 引数を1つ受け取って(引数一号くんと名付けましょう)、「引数を1つ受け取って引数一号くんとの和を返す関数」を返す関数。勘のいいあなたならもうお気づきでしょう。答えは2です。
const curriedPlus = x => (y => x + y)ほら、関数が返ってるように見えるでしょ?
例えばxに10を代入してみましょう。
const curriedPlus10 = curriedPlus(10) //これは下の文と同義です。 const curriedPlus10 = y => 10 + yじゃあそろそろ和を求めましょう。関数curriedPlus10の引数に3をわたす、つまり、yに3を代入しましょう。
curriedPlus10(3) /* 13 */え???カリー化って毎回curriedPlus10みたいな中間関数を作るの???
いいえ、説明のために作成しただけです。curriedPlus(10)(3) /* 13 */もしいきなり上の記述を見たら何が起きているのか理解に時間がかかったかもしれません。
しかし、今ならわかりますね?
curriedPlus(10)が返却する関数の引数に3という引数を与えているのです。さて、カリー化を感じていただけたでしょうか?
ここまでくれば、カリー化の定義を聞いても「???」とはならないでしょう。カリー化とは複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。
by Wikipediaで、カリー化の何が美味しいの?ってなると「部分適用」の話になるんですけど、ここまで読んでくれたみなさんはすでに部分適用を経験しています。
そう、「curriedPlus10」です。こいつです。
「部分適用とは、複数の引数をとる関数の一部の引数に実引数を適用する操作のこと」なんですね。
by Wikipediaあれ?最初のクイズの答えで1は間違いって言ってたくせにWikipediaは「複数の引数をとる関数」って言ってるぞ?
説明しましょう。
const plus = (x, y) => x + y const curriedPlus10 = y => plus(10, y)これが部分適用で、これをカリー化と組み合わせてるわけですね。
で、なぜこの話をしたかというとES Nextで定義されているパイプライン演算子を使用するときにこの部分適用が効いてくるからなんですね。
パイプライン演算子は「|>」こういうやつです。
実験的なパイプライン演算子 |> (現在はステージ 1 です) は、式の値を関数に接続します。これによって、読みやすい方法で一連の関数呼び出しを作成することができます。結果的に、単一の引数を用いた関数呼び出しの糖衣構文となり、次のように書くことができます。
by MDN Web Docsf(g(h(i(10)))) // と記述するところを 10 |> i |> h |> g |> f // のように記述できるんです。こういうときに可読性を上げてくれそうですよね!おいおい、カリー化と部分適用の話はどこいった?今からしますよ。
const plus = (x, y) => x + y const double = num => num * 2このような関数たちが定義されているとして、
double(plus(10, 3))をパイプライン演算子を使って表現してみましょう。
3 |> plus(10, ...) |> doubleあれ。こまりました。「...」の部分はどうしたらいいのだろう?
パイプライン演算子は「単一の引数を用いた関数呼び出しの糖衣構文」でしたね。
引数が2つの関数だから1つしか引数を渡さなかったら当然エラーになりますね...ここでカリー化、部分適用が効くんですね。
const curriedPlus = x => y => x + y const double = num => num * 2 3 |> curriedPlus(10) |> double /* 26 */なんでこれはうまく行くのか?
「curiiedPlus(10)」は引数を1つ受け取って10足して返す関数だからですね。
そう、「単一の引数を用いた関数呼び出し」になっているんですね。以上、ありがとうございました!!!
- 投稿日:2021-02-13T04:55:22+09:00
カリー化と部分適用とパイプライン演算子の話。
皆さんアロー関数大好きですよね。
アロー関数かっこいいですよね。const plus = (x, y) => x + y引数を2つ受け取って和を返す関数を作りました。さてカリー化しましょう。
は?カリー化ってなにって感じですよね...考えるな、感じろ。
というわけで理論的な説明は置いといてカリー化を感じてください。
const curriedPlus = x => y => x + yさて問題です。関数curriedPlusの説明として正しいのはどちらでしょう?
1. 引数を2つ受け取って和を返す関数。
2. 引数を1つ受け取って(引数一号くんと名付けましょう)、「引数を1つ受け取って引数一号くんとの和を返す関数」を返す関数。勘のいいあなたならもうお気づきでしょう。答えは2です。
const curriedPlus = x => (y => x + y)ほら、関数が返ってるように見えるでしょ?
例えばxに10を代入してみましょう。
const curriedPlus10 = curriedPlus(10) //これは下の文と同義です。 const curriedPlus10 = y => 10 + yじゃあそろそろ和を求めましょう。関数curriedPlus10の引数に3をわたす、つまり、yに3を代入しましょう。
curriedPlus10(3) /* 13 */え???カリー化って毎回curriedPlus10みたいな中間関数を作るの???
いいえ、説明のために作成しただけです。curriedPlus(10)(3) /* 13 */もしいきなり上の記述を見たら何が起きているのか理解に時間がかかったかもしれません。
しかし、今ならわかりますね?
curriedPlus(10)が返却する関数の引数に3という引数を与えているのです。さて、カリー化を感じていただけたでしょうか?
ここまでくれば、カリー化の定義を聞いても「???」とはならないでしょう。カリー化とは複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。
by Wikipediaで、カリー化の何が美味しいの?ってなると「部分適用」の話になるんですけど、ここまで読んでくれたみなさんはすでに部分適用を経験しています。
そう、「curriedPlus10」です。こいつです。
「部分適用とは、複数の引数をとる関数の一部の引数に実引数を適用する操作のこと」なんですね。
by Wikipediaあれ?最初のクイズの答えで1は間違いって言ってたくせにWikipediaは「複数の引数をとる関数」って言ってるぞ?
説明しましょう。
const plus = (x, y) => x + y const curriedPlus10 = y => plus(10, y)これが部分適用で、これをカリー化と組み合わせてるわけですね。
で、なぜこの話をしたかというとES Nextで定義されているパイプライン演算子を使用するときにこの部分適用が効いてくるからなんですね。
パイプライン演算子は「|>」こういうやつです。
実験的なパイプライン演算子 |> (現在はステージ 1 です) は、式の値を関数に接続します。これによって、読みやすい方法で一連の関数呼び出しを作成することができます。結果的に、単一の引数を用いた関数呼び出しの糖衣構文となり、次のように書くことができます。
by MDN Web Docsf(g(h(i(10)))) // と記述するところを 10 |> i |> h |> g |> f // のように記述できるんです。こういうときに可読性を上げてくれそうですよね!おいおい、カリー化と部分適用の話はどこいった?今からしますよ。
const plus = (x, y) => x + y const double = num => num * 2このような関数たちが定義されているとして、
double(plus(10, 3))をパイプライン演算子を使って表現してみましょう。
3 |> plus(10, ...) |> doubleあれ。こまりました。「...」の部分はどうしたらいいのだろう?
パイプライン演算子は「単一の引数を用いた関数呼び出しの糖衣構文」でしたね。
引数が2つの関数だから1つしか引数を渡さなかったら当然エラーになりますね...ここでカリー化、部分適用が効くんですね。
const curriedPlus = x => y => x + y const double = num => num * 2 3 |> curriedPlus(10) |> double /* 26 */なんでこれはうまく行くのか?
「curiiedPlus(10)」は引数を1つ受け取って10足して返す関数だからですね。
そう、「単一の引数を用いた関数呼び出し」になっているんですね。以上、ありがとうございました!!!
- 投稿日:2021-02-13T02:10:32+09:00
Firebaseでメールアドレス、パスワード入力なしで「emailVerified」をtrueにする方法
皆さんこんにちは!
今日は、Firebaseの
emailVerified
について書いていきます。この
emailVerified
はメール認証を行っているかを示すものです。これでユーザーの動作やページの制御を行えます。
これを行うには
sendEmailVerification
と言う関数を用いるのですが、通常メールアドレスとパスワードの入力を要求されます。ユーザーからしたらめんどくさいですよね。
また、
emailVerified
はフロントからは変更を行うことができません。それをメールに送ったリンクをクリックしたらメール認証完了という感じにしたいと思います。
それではやっていきましょう!
FirebaseFunctionsの設定
最初にFirebaseFunctionsで独自の関数を作成していきます。
functins/index.jsconst functions = require('firebase-functions') const admin = require('firebase-admin') admin.initializeApp() // メールアドレス認証(emailVerified)の更新 exports.updateEmailVerified = functions.region('asia-northeast1').https.onCall((data, context) => { const uid = context.auth.uid if (!uid) { return } admin .auth() .updateUser(uid, { emailVerified: true }) .then() })この
updateUser
でemailVerified
をtrue
にすることができます。メール認証を行うメールの送信
const actionCodeSettings = { url: window.location.origin + '/actionemailverified', handleCodeInApp: true } const user = this.$auth.currentUser // メール送信 await user .sendEmailVerification(actionCodeSettings) .then(() => { // }) .catch((error) => { // })アクションコードの設定などは、下記の記事に詳しく書いているので是非ご覧ください。
Firebase初心者向け!Firebaseでパスワードを使わずにメールリンク認証を行う方法
関数の呼び出し
sendEmailVerification
で送信したURL先でfunctions\index.js
で定義した関数を呼び出します。action.jsconst func = this.$function.httpsCallable('updateEmailVerified') func() .then(() => { alert('メール認証が完了しました') user.reload() }) .catch((error) => { // })いかがだったでしょうか?
ちなみにメールアドレスを変更した際は、自動的に
emailVerified
はfalse
になりますのでユーザーには再度認証を行ってもらう必要があります。このようにして
emailVerified
を更新することができます。あまり情報がなかったので書いておきました。
皆さんのお力になれれば幸いです。
以上、「Firebaseでメールアドレス、パスワード入力なしで「emailVerified」をtrueにする方法」でした!
良ければ、LGTM、コメントお願いします。
また、何か間違っていることがあればご指摘頂けると幸いです。
他にも毎日初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!
Thank you for reading
- 投稿日:2021-02-13T02:03:30+09:00
【Deno】相対パスでファイル操作をするときに気を付けること
Denoでファイル操作
Denoには標準でファイル操作を行うことのできる関数がいくつか備わっています。
例えば、これらのものです。Deno.readFileSync("filepath") // filepathの読み込み Deno.writeFileSync("filepath") //filepathに書き込みこれらによって、Denoで様々なファイル操作(読み書き・コピー・削除など)が簡単に行えます。
そして、これらの関数の第1引数には対象のファイルパスを入れるのですが、ここには絶対パスだけでなく相対パスも使うことができます。test/ ̩̩├data/ │ └test.txt └main.ts上記のようなディレクトリ構成になっている場合、
main.ts
を以下のようにして実行します。main.tsconst text = Deno.readTextFileSync("./data/test.txt"); console.log(text); export {}するとdataフォルダ内の
test.txt
ファイルの中身が出力されます。相対パスの落とし穴
Denoのファイル操作関数の引数に与える相対パスのルートは、カレントディレクトリとなります。
下記の各コマンドはどれも同じmain.ts
を実行しているが、test.txt
が読み込めるのは一番上のカレントディレクトリをtest
直下にした時のみです。/user/test$ deno run -A main.ts // OK /user$ deno run -A ./test/main.ts // NG /user/test/data$ deno run -A ../main.ts // NGただこのようなコマンドは誰でも実行してしまうのではないでしょうか?
別ディレクトリで作業してて、カレントディレクトリを移動するほどでもないけどmain.ts
を実行したいなって時とか。解決案:
pathResolver
の作成
pathResolver
とは、その名の通りパスを解決する(相対パス→絶対パス)ものです。
相対パスで問題が起こるなら、絶対パスに変換しちゃおうぜって考えです。
main.ts
を次のように変えてみましょう。main.tsimport * as path from "https://deno.land/std@0.79.0/path/mod.ts"; function pathResolver(meta: ImportMeta): (p: string) => string { return (p) => path.fromFileUrl(new URL(p, meta.url)); } const resolve = pathResolver(import.meta); const text = Deno.readTextFileSync(resolve("./data/test.txt")); console.log(text);このようにすることで、resolve関数が相対パスを絶対パスに変換してくれるので、どのカレントディレクトリから実行しても同じ結果になります。
pathResolver
の解説
pathResolver
はimport.meta
を引数にとります。import.meta
は、そのファイル自体のファイルパスを格納しており、import.meta.url
で取得できます。これで取得できるのはファイルのURLなので、file:///~
で始まるものになります。
pathResolver
はそのmeta.url
を格納した新しい関数を返します。main.ts
ではこの関数の名前をresolve
としています。この
resolve
関数に相対パスを渡してあげればURLクラスがいい感じにパスの関係を計算してくれます。ファイルにアクセスするには最初のfile://
の部分は邪魔なので、Denoの標準モジュールstd
のpath
にあるfromFileUrl
関数で消して素のファイルパスを返します。注意点
solver
はファイルごとに生成する必要があります。importしてはいけません。なぜならimport.meta
はファイルによって違うからです。まとめ
ファイル操作系...というか相対パスを扱うときには
pathResolver
を使用しましょう。意図しないバグの発生を抑えることができます。ちなみにimport文の相対パスは勝手に解決してくれるので
pathResolver
は使わなくて大丈夫です。参考にしたコード
この
pathResolver
は、僕がDenoでサーバを立てるときにお世話になっているモジュールservestのコードをヒントに作りました。servestでは
pathResolver
を次のようにしています。
実際のコード(Github)export function pathResolver(meta: ImportMeta): (p: string) => string { return (p) => new URL(p, meta.url).pathname; }この記事内で示したものと外形は同じですが、pathモジュールを使っている所に違いがあります。
じつは、上記のコード1つ欠点があってWindowsではうまく動作しないということです。Linux系(たぶんmacも)はパスの最初がスラッシュ
/
で始まっているときに絶対パスとみなします。
一方、Windows系は絶対パスの最初はドライブ名で始まるということになっています。
new URL().pathname
では、import.meta.url
から取得したfile:///~
付きパスのfile://
しかとってくれません。Linux系だとスラッシュから始まることで絶対パスを表すので問題ないですが、Windowsではおかしくなります。
この問題を解決するのにpath
モジュールを使用しています。
(このpathモジュールを探すまでに時間かかったんだよなぁ…)/*** Linux系では ***/ const urlLinux = "file:///home/test/test.txt"; const pathLinux = new URL("",urlLinux).pathname; console.log(pathLinux); // 出力:/home/test/test.txt /*** Windowsでは ***/ const urlWin = "file:///c:/users/test/test.txt"; const pathWin = new URL("",urlWin).pathname; console.log(pathWin); // 出力:/c:/users/test/test.txt // ↑パスの形式としておかしい余談
ちなみにDenoのファイル操作系関数はURLクラスの引数にも対応してるっぽいので
pathResolver
はいらないっぽい?(未確認)ただ、URLクラス未対応だけどファイルパスを引数にとる関数とかには使えるよ。
- 投稿日:2021-02-13T01:30:39+09:00
Optional Chaining のエラーをもう一度整理
前回の反省点として、Optional Chainingに問題をすり替えてしまって、結果、混沌としてしまったです。
よくよく考えれば、ts-loaderもbabelも Optional Chaining はサポートしているのであって、こいつらはローダー君は指定されたバージョンのjavascriptへ変換するのがお仕事です。
つまり、ここが原因でエラーがどうとか話しすと話が混乱するのですな。ということで、もう一度出力されているエラーを見てみる。
storybook をビルドして発生するエラー
ERROR in ./src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts& (./node_modules/vue-docgen-loader/lib??ref--12!./node_modules/ts-loader??ref--4-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts&) 8:36 Module parse failed: Unexpected token (8:36) File was processed with these loaders: * ./node_modules/vue-docgen-loader/lib/index.js * ./node_modules/vue-docgen-loader/lib/index.js * ./node_modules/ts-loader/index.js * ./node_modules/vue-loader/lib/index.js You may need an additional loader to handle the result of these loaders. | const canvas = ref(); | watch(canvas, () => { > const gl = canvas.value?.getContext("webgl"); | if (gl) { | gl.clearColor(0.0, 0.0, 0.0, 1.0); @ ./src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts& 1:0-236 1:252-255 1:257-490 1:257-490 @ ./src/components/_sandbox/WebGL.vue @ ./src/components/_sandbox/WebGL.stories.ts @ ./src sync ^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(js|jsx|ts|tsx))$ @ ./.storybook/generated-stories-entry.js @ multi ./node_modules/@storybook/core/dist/server/common/polyfills.js ./node_modules/@storybook/core/dist/server/preview/globals.js ./.storybook/storybook-init-framework-entry.js ./node_modules/@storybook/addon-docs/dist/frameworks/common/config.js-generated-other-entry.js ./node_modules/@storybook/addon-docs/dist/frameworks/vue/config.js-generated-other-entry.js ./node_modules/@storybook/addon-links/dist/preset/addDecorator.js-generated-other-entry.js ./node_modules/@storybook/addon-actions/dist/preset/addDecorator.js-generated-other-entry.js ./node_modules/@storybook/addon-actions/dist/preset/addArgs.js-generated-other-entry.js ./node_modules/@storybook/addon-backgrounds/dist/preset/addDecorator.js-generated-other-entry.js ./node_modules/@storybook/addon-backgrounds/dist/preset/addParameter.js-generated-other-entry.js ./node_modules/@storybook/addon-knobs/dist/preset/addDecorator.js-generated-other-entry.js ./.storybook/preview.js-generated-config-entry.js ./.storybook/generated-stories-entry.js (webpack)-hot-middleware/client.js?reload=true&quiet=false&noInfo=undefined
Module parse failed: Unexpected token (8:36)
ということは、どうやら期待されていないトークンに遭遇したらしい。
次に、const gl = canvas.value?.getContext("webgl");
という、ソースコードの場所を明示してくれている。
で、webpackというヤツはルールに従ってloaderがバケツリレーをしながら javascript に置き換えるので、「誰が音を上げた」というのが問題になる。
スタックトレース的なものが表示されているが、コイツが昇順なのか降順なのか、想像はできるが確定できないので、少し手を変えてみる。エラーを表示している場所を探す
スクリプト系の言語なら、エラーを表示しているコードを探せるかも、って事で
You may need an additional loader to handle the result of these loaders.
を検索してみるとwebpack/lib/ModuleParseError.js
で検索されていることがわかる。
こいう時のコツとしては、runtimeのエラー文字列っぽくないものを探すのがコツ。で、このエラー文字列は ModuleParseError の constructor() で使っている(生成している)ことが分かる。
じゃぁ、どこから呼び出されているのかを調べたくなるので、console.trace()
を追加してみて、再度ビルドしてみる。Trace at new ModuleParseError (/Users/teru/git/uilib/node_modules/webpack/lib/ModuleParseError.js:27:12) at handleParseError (/Users/teru/git/uilib/node_modules/webpack/lib/NormalModule.js:469:19) at /Users/teru/git/uilib/node_modules/webpack/lib/NormalModule.js:503:5 at /Users/teru/git/uilib/node_modules/webpack/lib/NormalModule.js:358:12 at /Users/teru/git/uilib/node_modules/loader-runner/lib/LoaderRunner.js:373:3 at iterateNormalLoaders (/Users/teru/git/uilib/node_modules/loader-runner/lib/LoaderRunner.js:214:10) at iterateNormalLoaders (/Users/teru/git/uilib/node_modules/loader-runner/lib/LoaderRunner.js:221:10) at /Users/teru/git/uilib/node_modules/loader-runner/lib/LoaderRunner.js:236:3 at context.callback (/Users/teru/git/uilib/node_modules/loader-runner/lib/LoaderRunner.js:111:13) at Object.module.exports (/Users/teru/git/uilib/node_modules/vue-docgen-loader/lib/index.js:28:5) at LOADER_EXECUTION (/Users/teru/git/uilib/node_modules/loader-runner/lib/LoaderRunner.js:119:14) at runSyncOrAsync (/Users/teru/git/uilib/node_modules/loader-runner/lib/LoaderRunner.js:120:4) at iterateNormalLoaders (/Users/teru/git/uilib/node_modules/loader-runner/lib/LoaderRunner.js:232:2) at iterateNormalLoaders (/Users/teru/git/uilib/node_modules/loader-runner/lib/LoaderRunner.js:221:10) at /Users/teru/git/uilib/node_modules/loader-runner/lib/LoaderRunner.js:236:3 at context.callback (/Users/teru/git/uilib/node_modules/loader-runner/lib/LoaderRunner.js:111:13)ふむ。。。 あ〜 vue-docgen-loader が居るな。。。
こいつ何なんだろうか。。。
まぁ、それは後で調べるにして、もうちょい情報が欲しいから、この constructor() に渡されるパラメータをダンプしてみよう。
定義はこんな感じModuleParseError(module, source, err, loaders)
だったんで。。。module
/* NormalModule */ { dependencies: [], blocks: [], variables: [], type: 'javascript/auto', context: '/Users/teru/git/uilib/src/components/_sandbox', debugId: 1422, hash: undefined, renderedHash: undefined, resolveOptions: {}, factoryMeta: {}, warnings: [], errors: [], buildMeta: { tsLoaderFileVersion: 1 }, buildInfo: { cacheable: true, fileDependencies: Set { '/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue' }, contextDependencies: Set {}, assets: undefined, assetsInfo: undefined }, reasons: [ ModuleReason { module: [NormalModule], dependency: [HarmonyImportSideEffectDependency], explanation: undefined, _chunks: null }, ModuleReason { module: [NormalModule], dependency: [HarmonyImportSpecifierDependency], explanation: undefined, _chunks: null }, ModuleReason { module: [NormalModule], dependency: [HarmonyImportSideEffectDependency], explanation: undefined, _chunks: null }, ModuleReason { module: [NormalModule], dependency: [HarmonyExportImportedSpecifierDependency], explanation: undefined, _chunks: null } ], _chunks: SortableSet [Set] { _sortFn: [Function: sortById], _lastActiveSortFn: null, _cache: undefined, _cacheOrderIndependent: undefined }, id: null, index: null, index2: null, depth: null, issuer: NormalModule { dependencies: [ [HarmonyCompatibilityDependency], [HarmonyInitDependency], [ConstDependency], [HarmonyImportSideEffectDependency], [HarmonyExportHeaderDependency], [HarmonyExportExpressionDependency], [HarmonyImportSpecifierDependency], [ConstDependency], [HarmonyImportSideEffectDependency], [HarmonyExportImportedSpecifierDependency] ], blocks: [], variables: [], type: 'javascript/auto', context: '/Users/teru/git/uilib/src/components/_sandbox', debugId: 1421, hash: undefined, renderedHash: undefined, resolveOptions: {}, factoryMeta: {}, warnings: [], errors: [], buildMeta: { exportsType: 'namespace' }, buildInfo: { cacheable: true, fileDependencies: Set {}, contextDependencies: Set {}, assets: undefined, assetsInfo: undefined, strict: true, exportsArgument: '__webpack_exports__' }, reasons: [ [ModuleReason], [ModuleReason], [ModuleReason], [ModuleReason] ], _chunks: SortableSet [Set] { _sortFn: [Function: sortById], _lastActiveSortFn: null, _cache: undefined, _cacheOrderIndependent: undefined }, id: null, index: null, index2: null, depth: null, issuer: NormalModule { dependencies: [Array], blocks: [], variables: [], type: 'javascript/auto', context: '/Users/teru/git/uilib/src/components/_sandbox', debugId: 1217, hash: undefined, renderedHash: undefined, resolveOptions: {}, factoryMeta: {}, warnings: [], errors: [], buildMeta: [Object], buildInfo: [Object], reasons: [Array], _chunks: [SortableSet [Set]], id: null, index: null, index2: null, depth: null, issuer: [NormalModule], profile: undefined, prefetched: false, built: true, used: null, usedExports: null, optimizationBailout: [], _rewriteChunkInReasons: undefined, useSourceMap: true, _source: [OriginalSource], request: '/Users/teru/git/uilib/node_modules/vue-docgen-loader/lib/index.js??ref--12!/Users/teru/git/uilib/node_modules/vue-loader/lib/index.js??vue-loader-options!/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue', userRequest: '/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue', rawRequest: './WebGL.vue', binary: false, parser: [Parser], generator: JavascriptGenerator {}, resource: '/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue', matchResource: undefined, loaders: [Array], error: null, _sourceSize: null, _buildHash: 'd57de076f7ac96f23ab1c0ce9011f9e8', buildTimestamp: 1613107043574, _cachedSources: Map {}, lineToLine: false, _lastSuccessfulBuildMeta: [Object], _ast: null }, profile: undefined, prefetched: false, built: true, used: null, usedExports: null, optimizationBailout: [], _rewriteChunkInReasons: undefined, useSourceMap: true, _source: OriginalSource { _value: 'import mod from "-!../../../node_modules/vue-docgen-loader/lib/index.js??ref--12!../../../node_modules/ts-loader/index.js??ref--4-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./WebGL.vue?vue&type=script&lang=ts&"; export default mod; export * from "-!../../../node_modules/vue-docgen-loader/lib/index.js??ref--12!../../../node_modules/ts-loader/index.js??ref--4-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./WebGL.vue?vue&type=script&lang=ts&"', _name: '/Users/teru/git/uilib/node_modules/vue-docgen-loader/lib/index.js??ref--12!/Users/teru/git/uilib/node_modules/vue-loader/lib/loaders/pitcher.js??ref--4!/Users/teru/git/uilib/node_modules/ts-loader/index.js??ref--4-0!/Users/teru/git/uilib/node_modules/vue-loader/lib/index.js??vue-loader-options!/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts&' }, request: '/Users/teru/git/uilib/node_modules/vue-docgen-loader/lib/index.js??ref--12!/Users/teru/git/uilib/node_modules/vue-loader/lib/loaders/pitcher.js??ref--4!/Users/teru/git/uilib/node_modules/ts-loader/index.js??ref--4-0!/Users/teru/git/uilib/node_modules/vue-loader/lib/index.js??vue-loader-options!/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts&', userRequest: '/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts&', rawRequest: './WebGL.vue?vue&type=script&lang=ts&', binary: false, parser: Parser { _pluginCompat: [SyncBailHook], hooks: [Object], options: {}, sourceType: 'auto', scope: undefined, state: undefined, comments: undefined }, generator: JavascriptGenerator {}, resource: '/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts&', matchResource: undefined, loaders: [ [Object], [Object], [Object], [Object] ], error: null, _sourceSize: null, _buildHash: '76f6da385d20205e0eefa56e89ec04d1', buildTimestamp: 1613107043951, _cachedSources: Map {}, lineToLine: false, _lastSuccessfulBuildMeta: { exportsType: 'namespace' }, _ast: null }, profile: undefined, prefetched: false, built: true, used: null, usedExports: null, optimizationBailout: [], _rewriteChunkInReasons: undefined, useSourceMap: true, _source: SourceMapSource { _value: 'import { defineComponent, ref, watch } from "@vue/composition-api";\n' + 'export default defineComponent({\n' + ' name: "WebGL",\n' + ' props: {},\n' + ' setup: (props, ctx) => {\n' + ' const canvas = ref();\n' + ' watch(canvas, () => {\n' + ' const gl = canvas.value?.getContext("webgl");\n' + ' if (gl) {\n' + ' gl.clearColor(0.0, 0.0, 0.0, 1.0);\n' + ' gl.clear(gl.COLOR_BUFFER_BIT);\n' + ' }\n' + ' });\n' + ' return { canvas };\n' + ' }\n' + '});\n', _name: '/Users/teru/git/uilib/node_modules/vue-docgen-loader/lib/index.js??ref--12!/Users/teru/git/uilib/node_modules/vue-docgen-loader/lib/index.js??ref--12!/Users/teru/git/uilib/node_modules/ts-loader/index.js??ref--4-0!/Users/teru/git/uilib/node_modules/vue-loader/lib/index.js??vue-loader-options!/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts&', _sourceMap: { version: 3, file: '/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue.ts', sourceRoot: '', sources: [Array], names: [], mappings: 'AAKA,OAAO,EAAE,eAAe,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAEnE,eAAe,eAAe,CAAC;IAC7B,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,EAAE;IACT,KAAK,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACpB,MAAM,MAAM,GAAG,GAAG,EAAqB,CAAC;QACxC,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE;YACjB,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;YAC7C,IAAI,EAAE,EAAE;gBACN,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;gBAClC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC;aAC/B;QACH,CAAC,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;CACF,CAAC,CAAC', sourcesContent: [Array] }, _originalSource: undefined, _innerSourceMap: undefined, _removeOriginalSource: undefined }, request: '/Users/teru/git/uilib/node_modules/vue-docgen-loader/lib/index.js??ref--12!/Users/teru/git/uilib/node_modules/vue-docgen-loader/lib/index.js??ref--12!/Users/teru/git/uilib/node_modules/ts-loader/index.js??ref--4-0!/Users/teru/git/uilib/node_modules/vue-loader/lib/index.js??vue-loader-options!/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts&', userRequest: '/Users/teru/git/uilib/node_modules/vue-docgen-loader/lib/index.js??ref--12!/Users/teru/git/uilib/node_modules/ts-loader/index.js??ref--4-0!/Users/teru/git/uilib/node_modules/vue-loader/lib/index.js??vue-loader-options!/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts&', rawRequest: '-!../../../node_modules/vue-docgen-loader/lib/index.js??ref--12!../../../node_modules/ts-loader/index.js??ref--4-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./WebGL.vue?vue&type=script&lang=ts&', binary: false, parser: Parser { _pluginCompat: SyncBailHook { _args: [Array], taps: [Array], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, hooks: { evaluateTypeof: [HookMap], evaluate: [HookMap], evaluateIdentifier: [HookMap], evaluateDefinedIdentifier: [HookMap], evaluateCallExpressionMember: [HookMap], statement: [SyncBailHook], statementIf: [SyncBailHook], label: [HookMap], import: [SyncBailHook], importSpecifier: [SyncBailHook], export: [SyncBailHook], exportImport: [SyncBailHook], exportDeclaration: [SyncBailHook], exportExpression: [SyncBailHook], exportSpecifier: [SyncBailHook], exportImportSpecifier: [SyncBailHook], varDeclaration: [HookMap], varDeclarationLet: [HookMap], varDeclarationConst: [HookMap], varDeclarationVar: [HookMap], canRename: [HookMap], rename: [HookMap], assigned: [HookMap], assign: [HookMap], typeof: [HookMap], importCall: [SyncBailHook], call: [HookMap], callAnyMember: [HookMap], new: [HookMap], expression: [HookMap], expressionAnyMember: [HookMap], expressionConditionalOperator: [SyncBailHook], expressionLogicalOperator: [SyncBailHook], program: [SyncBailHook], hotAcceptCallback: [SyncBailHook], hotAcceptWithoutCallback: [SyncBailHook] }, options: {}, sourceType: 'auto', scope: undefined, state: undefined, comments: undefined }, generator: JavascriptGenerator {}, resource: '/Users/teru/git/uilib/src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts&', matchResource: undefined, loaders: [ { options: [Object], ident: 'ref--12', loader: '/Users/teru/git/uilib/node_modules/vue-docgen-loader/lib/index.js' }, { loader: '/Users/teru/git/uilib/node_modules/vue-docgen-loader/lib/index.js', options: [Object], ident: 'ref--12' }, { loader: '/Users/teru/git/uilib/node_modules/ts-loader/index.js', options: [Object], ident: 'ref--4-0' }, { loader: '/Users/teru/git/uilib/node_modules/vue-loader/lib/index.js', options: {}, ident: 'vue-loader-options' } ], error: null, _sourceSize: null, _buildHash: '', buildTimestamp: 1613107043952, _cachedSources: Map {}, lineToLine: false, _lastSuccessfulBuildMeta: {}, _ast: null }source
import { defineComponent, ref, watch } from "@vue/composition-api"; export default defineComponent({ name: "WebGL", props: {}, setup: (props, ctx) => { const canvas = ref(); watch(canvas, () => { const gl = canvas.value?.getContext("webgl"); if (gl) { gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); } }); return { canvas }; } });err
SyntaxError: Unexpected token (8:36) at Parser.pp$4.raise (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:2825:15) at Parser.pp.unexpected (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:689:10) at Parser.pp$3.parseExprAtom (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:2270:12) at Parser.pp$3.parseExprSubscripts (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:2089:21) at Parser.pp$3.parseMaybeUnary (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:2066:19) at Parser.pp$3.parseExprOps (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:2010:21) at Parser.pp$3.parseMaybeConditional (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:1993:21) at Parser.pp$3.parseMaybeAssign (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:1968:21) at Parser.pp$3.parseMaybeConditional (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:1998:30) at Parser.pp$3.parseMaybeAssign (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:1968:21) at Parser.pp$1.parseVar (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:1228:26) at Parser.pp$1.parseVarStatement (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:1092:10) at Parser.pp$1.parseStatement (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:842:19) at Parser.pp$1.parseBlock (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:1161:23) at Parser.pp$3.parseFunctionBody (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:2671:24) at Parser.pp$3.parseArrowExpression (/Users/teru/git/uilib/node_modules/acorn/dist/acorn.js:2634:10) { pos: 260, loc: Position { line: 8, column: 36 }, raisedAt: 261 }loaders
[ './node_modules/vue-docgen-loader/lib/index.js', './node_modules/vue-docgen-loader/lib/index.js', './node_modules/ts-loader/index.js', './node_modules/vue-loader/lib/index.js' ]ここにきて、ようやくエラーの根源を発見!(実は、当初のビルドで表示されているんだけどね)
acorn
が例外投げてるっぽい。
で、acorn
って、何者だ?あと、errのスタックトレースが acorn 内で終わっているので、そもそも、これ呼び出しているのは誰?って事で調べてみる。
エラーの根源
もう一度、整理すると。
- ModuleParseError でエラーが構成されているが、これは発生したエラーを受け、表示用にエラーを構成しているもので、エラーそのものではない。
- ModuleParseError を誰かが呼び出している。
- エラーの発生は
acorn
で確定。ということで、ModuleParseError って誰が呼び出しているのかを調べてみる。
ModuleParseError の生成箇所を追う
とりあえず、検索してみると
NormalModule.js
で生成していることが分かる。
そして、NormalModule.build() 内で、 doBuild() を呼び出し、ここのコールバックで生成している。
で、条件としては、this.parser.parse()
でエラーが発生した時、または、this.parser.parse()
が例外を発生させた時のみ。
念の為、この2つのケースにconsole.log()
を入れてみて確認してみると、catch節で ModuleParseError が生成されることを確認した。acorn って何者?
で、javascriptのパーサーでした。
npm で週4千万ダウンロードされてる、神様的なライブラリですね。
いつもお世話になっているのに、知らなくてゴメンね。
で、この子は、文字列とパラメータを与えると、構文解析して、ESTree Specにくれるものみたいです。https://github.com/acornjs/acorn/tree/master/acorn
つまり、この子が理解できないと例外を発生させたのですが、最新の acorn 8.0.5 は ECMA12までサポートしてます。
storybook が使っている acorn はというと。。。 storybook -> webpack -> acorn で 7.1.0 か。。。
どうなんだろう? これかな?そういえば NormalModule って?
そうそう、エラー出しているのは NormalModule の
this.parser.parse()
だったっけ。
ところで、こいつのthis.parser
って何者なんだ?
acron にも parse() って関数あるんだけど、引数違うから、まだ途中に誰か居るな。ということで、 NormalModule.parser は誰なのか追ってみる。
どうやら、
this.parser
に代入している箇所は、constructor() と updateCacheModule() だが、取り敢えず、何者かを知りたいので、constructor に絞って探してみる。
(あ〜、動的型付け言語は追いづれ〜〜〜)
試しに、 "new NormalModule" で検索してみると、NormalModuleFactory.js
がヒットするので、何となくコレっぽい。let createdModule = this.hooks.createModule.call(result); if (!createdModule) { if (!result.request) { return callback(new Error("Empty dependency (no request)")); } createdModule = new NormalModule(result); }が、こいつ、条件付きで new されるみたいで、hook とかいうヤツが生成する場合がある(どっちが多いんだか分からん)。
ちと、 NormalModule って、資料ないのかと調べてみる。
https://webpack.js.org/api/loaders/
あれ? webpack 本家サイトで "NormalObject" が登場するのはここしかない。
NormalObject を生成するファクトリについては解説しているが。。。
https://webpack.js.org/api/normalmodulefactory-hooks/何だ? 知ってて当然で、知らない人はお断り的なヤツなんか?
まぁ、実際はwebpackの内部クラスで、webpack作っている方々以外は触るもんじゃないんでしょう。
で、NormalModuleを置き換えるプラグイン的な切り口については解説してますな。
https://webpack.js.org/plugins/normal-module-replacement-plugin/ここまでくると、デバッガで動かさないと分からん。。。
ひとます、置いといて。。。え〜っと、何やってるんだっけ?(苦笑)
残りは vue-docgen-loader
こいつは、vue を解析して、propertyやらeventやらを抽出する便利なヤツです。
こいつが動くってことは、誰かが呼び出しているはずなので探してみると。。。。
@storybook/addon-docs の中で、webpackFilnal() 関数の中におりました。
まともに動かないのを覚悟で、 vue-docgen-loader を module.rules に追加している箇所をコメントアウトしてみると。ERROR in ./src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts& (./node_modules/ts-loader??ref--4-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts&) 8:36 Module parse failed: Unexpected token (8:36) File was processed with these loaders: * ./node_modules/ts-loader/index.js * ./node_modules/vue-loader/lib/index.js You may need an additional loader to handle the result of these loaders. | const canvas = ref(); | watch(canvas, () => { > const gl = canvas.value?.getContext("webgl"); | if (gl) { | gl.clearColor(0.0, 0.0, 0.0, 1.0); @ ./src/components/_sandbox/WebGL.vue?vue&type=script&lang=ts& 1:0-174 1:190-193 1:195-366 1:195-366 @ ./src/components/_sandbox/WebGL.vue @ ./src/components/_sandbox/WebGL.stories.ts @ ./src sync ^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(js|jsx|ts|tsx))$ @ ./.storybook/generated-stories-entry.js @ multi ./node_modules/@storybook/core/dist/server/common/polyfills.js ./node_modules/@storybook/core/dist/server/preview/globals.js ./.storybook/storybook-init-framework-entry.js ./node_modules/@storybook/addon-docs/dist/frameworks/common/config.js-generated-other-entry.js ./node_modules/@storybook/addon-docs/dist/frameworks/vue/config.js-generated-other-entry.js ./node_modules/@storybook/addon-links/dist/preset/addDecorator.js-generated-other-entry.js ./node_modules/@storybook/addon-actions/dist/preset/addDecorator.js-generated-other-entry.js ./node_modules/@storybook/addon-actions/dist/preset/addArgs.js-generated-other-entry.js ./node_modules/@storybook/addon-backgrounds/dist/preset/addDecorator.js-generated-other-entry.js ./node_modules/@storybook/addon-backgrounds/dist/preset/addParameter.js-generated-other-entry.js ./node_modules/@storybook/addon-knobs/dist/preset/addDecorator.js-generated-other-entry.js ./.storybook/preview.js-generated-config-entry.js ./.storybook/generated-stories-entry.js (webpack)-hot-middleware/client.js?reload=true&quiet=false&noInfo=undefinedまだエラー出てるけど、vue-docgen-loader がエラー出てるんじゃないのね。
で、更に、webpackの設定周りを追っていると、iframe-webpack.config.js
にぶちあたり、"generated-stories-entry.js" という、エラーで見かけた文字列を発見。こりゃ、まだ続くな。。。
取り敢えず、これまでの解析結果は。。。
- storybook で Optional Chaining を使うとエラーになる
- tsconfig.json で target を ES6 にするとエラーはなくなる
- エラーの発生に関連するものは。。。
- acron(例外発生場所)
- webpack の NormalModule.build() (エラーハンドリング場所)
- vue-docgen-loader