- 投稿日:2020-08-09T23:19:51+09:00
jQueryでできる基本的なこと(初心者)
この記事について
progateでjQueryを学習した後にアウトプットの為に書いている記事です。Javascriptに精通している方には参考にならないと思うのでスルーしてください。
モーダルウィンドウの表示
モーダルウィンドウとは、ウィンドウ内で指定された操作を完了したり、キャンセル操作するまで他の画面にすることが出来ないウィンドウの事です。モーダルウィンドウの表示もjQueryで実装することができます。
sample.html<!--モーダル部分のみ抜粋しています--> <div class="signup-modal-wrapper" id="signup-modal"> <div class="modal"> <div> <i class="fa fa-2x fa-times" id="close-modal"></i> </div> <div id="signup-form"> <h2>Emailで新規登録</h2> <form action="#"> <input class="form-control" type="text" placeholder="メールアドレス"> <input class="form-control" type="password" placeholder="パスワード"> <div id="submit-btn">新規登録</div> </form> </div> </div> </div>sample.css.signup-modal-wrapper { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background-color: rgba(0, 0, 0, 0.6); z-index: 100; /*cssで非表示にしておきます*/ display: none; }script.js$(function() { //クリックイベントを作成 $('.signup-show').click(function() { //モーダルを表示するようにする $('#signup-modal').fadeIn(); }); //閉じるボタンが押されたら消えるようにする $('#close-modal').click(function() { $('#signup-modal').fadeOut(); }); });ホバーで文字を表示する
jQueryで画像や文字にマウスを乗せると文字が表示される機能を実装できます。以下の方法で実装しております。
①ホバー時に表示させたい文章はcssで非表示にする。
②「display: block;」を含むclassを作成しておく。
③hoverイベントをJavascriptのファイルに記入。
④ホバー時に「display: block;」を含むclassを追加する(addClassメソッドで)
⑤ホバーが外れる時は追加したclassを取り除く(removeメソッドで)sample.html<!-- レッスン一覧部分 --> <div class="lesson-wrapper"> <div class="container"> <div class="heading"> <h2>Learn Where to Get Started!</h2> </div> <div class="lessons"> <div class="lesson"> <div class="lesson-icon"> <img src="https://prog-8.com/images/html/advanced/html.png"> <p>HTML & CSS</p> </div> <p class="text-contents">ウェブページの作成に使用される言語です。HTMLとCSSを組み合わせることで、静的なページを作り上げることができます。</p> </div> <div class="lesson"> <div class="lesson-icon"> <img src="https://prog-8.com/images/html/advanced/jQuery.png"> <p>jQuery</p> </div> <p class="text-contents">素敵な動きを手軽に実装できるJavaScriptライブラリです。 アニメーション効果をつけたり、Ajax(エイジャックス)を使って外部ファイルを読み込んだりと色々なことができます。</p> </div> <div class="lesson"> <div class="lesson-icon"> <img src="https://prog-8.com/images/html/advanced/ruby.png"> <p>Ruby</p> </div> <p class="text-contents">オープンソースの動的なプログラミング言語で、 シンプルさと高い生産性を備えています。大きなWebアプリケーションから小さな日用ツールまで、さまざまなソフトウェアを作ることができます。</p> </div> <div class="lesson"> <div class="lesson-icon"> <img src="https://prog-8.com/images/html/advanced/php.png"> <p>PHP</p> </div> <p class="text-contents">HTMLだけではページの内容を変えることはできません。PHPはHTMLにプログラムを埋め込み、それを可能にします。</p> </div> </div> </div> </div> <!-- レッスン一覧ここまで -->sample.css.text-contents { margin: 3% auto; width: 80%; font-size: 12px; color: #b3aeb5; /*非表示にしておく*/ display: none; } /*表示させる時のclass*/ .text-show { display: block; }script.js$(function() { // 言語一覧 $('.lesson').hover(function() { //ホバーされているオブジェクトにクラスを追加(文章は子要素なのでfindメソッドを使用) $(this).find('.text-contents').addClass('text-show'); }, //ホバーが外された時にクラスを取り除く function() { $(this).find('.text-contents').removeClass('text-show'); }); } );アコーディオン
Webアプリケーションにおけるアコーディオンとはクリックすると文字がスライドして表示され、もう一度クリックすると非表示になる機能のことです。この動作が楽器のアコーディオンに似ているからそう呼ばれているのだと思います。
以下の手順で実装できます。
①(アコーディオンで表示する)文章をCSSで非表示にしておく。
②クリックする部分には同一のclassを付与しておく。
③クリックして表示する時はaddClassメソッドを用いてclassを追加し、表示させる。非表示にする時はremoveClassでclassを取り除くこのような手順で実装する為に、hasClassメソッドを用います。
hasClassメソッド
hasClassメソッドは、オブジェクトが引数に使用しているclassを持っているか判定し、trueかfalseで返すメソッドです。このメソッドとif文による条件分岐を利用してアコーディオンを実装します。
アコーディオン実装のコード
sample.html<div class="faq-wrapper"> <div class="container"> <div class="heading"> <h2>FAQ</h2> </div> <div class="faq"> <ul id="faq-list"> <li class="faq-list-item"> <h3 class="question">Progateの公式キャラクターはなんですか?</h3> <span>+</span> <div class="answer"> <p>にんじゃわんこといいます。忍者の格好をしたわんこです。ネコではありません。</p> </div> </li> <li class="faq-list-item"> <h3 class="question">にんじゃわんこはオスですか?それともメスですか?</h3> <span>+</span> <div class="answer"> <p>にんじゃわんこはオスです。</p> </div> </li> <li class="faq-list-item"> <h3 class="question">にんじゃわんこは何歳ですか?</h3> <span>+</span> <div class="answer"> <p>にんじゃわんこは14歳です。</p> </div> </li> </ul> </div> </div> </div>sample.css#faq-list { width: 500px; margin: 0 auto; padding: 0; list-style: none; } .faq-list-item { margin:10px; border-bottom:1px solid #ccc; position:relative; cursor:pointer; text-align: left; } .faq-list-item h3 { font-size: 14px; } .faq-list-item span { position:absolute; top:0; right:5px; color:#ccc; font-size:13px; } .answer { font-size: 12px; padding: 5px 0px; margin-bottom: 15px; /*非表示にしておく*/ display: none; }script.js$(function() { // FAQ $('.faq-list-item').click(function() { //何度も書くのが面倒臭いので、前もって変数にする。 var $answer = $(this).find('.answer') //もしactiveクラスがある場合(既に表示されている場合)はactiveクラスを取り除いて非表示にする。 if ($answer.hasClass('active')) { $answer.removeClass('active'); $answer.slideUp(); //非表示の時は"+"にするというコード $(this).find('span').text('+'); } else { //activeクラスがない場合(非表示の場合)はactiveクラスを追加する。 $answer.addClass('active'); $answer.slideDown(); //表示の時は"-"にするというコード $(this).find('span').text('-'); } }); });ホバーの時と同様にaddClassとremoveClassを用いていますが、クラスの追加及び除去では表示の変化は起きません(あくまでも表示か非表示かを判断するための目印)。表示や非表示はslideUpとslideDownで行なっております。
感想
Javascriptのコースではコンソール上の変化しか見ていなかったので、いまいち実力がついている気がしなかったが、Webアプリケーションで使用されている機能の実装方法を今回の学習で身に付けたので実感が湧いた。今後もjQueryの学習を進めていきたいと思う。
- 投稿日:2020-08-09T22:35:41+09:00
Kinx Tips - 実行形式ファイルを作る
Kinx Tips - 実行形式ファイルを作る
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。今回は Tips です。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
皆さん、実行ファイルを作る時どうしてますか? ...そう、普通は C や C++ で書いてコンパイルしますね。もっと簡単に作れるといいですね。
Kinx は独立した exe を作ることはサポートしていませんが、Kinx のワールドの中であれば exe にして単体実行コマンドのように見せかけて実行できるモジュールを作れるようにしています。
その辺を少々。
コマンド
kxrepl.exe
、kxtest.exe
実は v0.13.1 リリースより
kxrepl.exe
とkxtest.exe
というコマンドが同梱されています。Linux 版では.exe
はついて無くてkxrepl
とkxtest
です。
kxrepl
を実行してみましょう。$ kxrepl kinx[ 0]> .quitREPL が動作しましたね。
次は
kxtest
です。$ kxtest -v -T declaration.md Test Cout = 11 Entry: doc/spec/statement/declaration.md Suite: Declaration statement Case[ 0]: Normal case ................................... successful ( 0.10s) Case[ 1]: With initializer .............................. successful ( 0.09s) Case[ 2]: With initializer of expression ................ successful ( 0.09s) Case[ 3]: Multiple variable declaration ................. successful ( 0.10s) Case[ 4]: Constant value (1) ............................ successful ( 0.07s) Case[ 5]: Constant value (2) ............................ successful ( 0.10s) Case[ 6]: Constant value (3) ............................ successful ( 0.07s) Case[ 7]: Constant value (4) ............................ successful ( 0.07s) Case[ 8]: Constant value (5) ............................ successful ( 0.09s) Case[ 9]: Destructuring assignment (1) .................. successful ( 0.09s) Case[10]: Destructuring assignment (2) .................. successful ( 0.07s) <Test Result> Total Test Cases: 11 Successful : 11 Failed : 0 Warning : 0SpecTest が動作しましたね。
もう一つ、試しにやってみましょう。
$ diff -s kxrepl kxtest Files kxrepl and kxtest are identical「ファイルは同一です」 というメッセージがでました。というか、そうなんです。この 2 つは バイナリとして全く同じ です。
--exec
オプションの話この話の前に、一つ Kinx のオプションに関する情報を書いておきます。
--exec
というオプションです。README にも書いてある通り、このオプションによって以下の 2 つのオプションがサポートされています。
--exec:repl
... REPL を実行する。--exec:specttest
... SpecTest を実行する。このメカニズムは、以下の通りになっています。
--exec:xxx
を認識したら、Kinx の実行ファイルのあるフォルダから見てlib/exec/xxx.kx
、またはlib/exec/3rdparty/xxx.kx
を探す。- そのファイルがあったら、そのファイルがスクリプトファイルとして指定されたとみなして実行する。
なので、REPL と SpecTest はそれぞれ
lib/exec/repl.kx
とlib/exec/spectest.kx
ファイルを探してそれを実行する、という動作をしているのです。この仕組みによって、REPL と SpecTest の修正はバイナリを修正せずに実施できるようにもなっています。kxrepl
さて、kxrepl のバイナリ(= kxtest のバイナリ)ですが、どういう作りになっているのでしょう(概ね見当は付くと思いますが)。答えはこうです。
- 自分自身の実行ファイル名(
name
としましょう)を取得(argv[0]
から取得できます)。- コマンドライン引数の先頭に
--exec:name
を割り込ませて引数リストを更新。- Kinx のメインロジックに制御を渡す。
するとどうでしょう。自動的に
lib/exec/name.kx
(またはlib/exec/3rdparty/name.kx
)を参照して実行してくれるのです!ということで、
kxrepl
はlib/exec/kxrepl.kx
を、kxtest
はlib/exec/kxtest.kx
を自動的に実行してくれるという寸法です。ん?、そんなファイルあったっけ?はい、追加したんです(=あります)。中身を見てみましょう。
lib/exec/kxrepl.kxusing exec.repl;lib/exec/kxtest.kxusing exec.spectest;これだけです。中で
using
するだけのファイルを用意して実現しました。using
の検索パスに従ってrepl.kx
とspectest.kx
を見つけてくれるので、これで正しく動作します。というわけで、kxrepl.exe
をrepl.exe
に書き換えても同様に動きます。ということで、もうお分かりですね。
オリジナル
exe
を作ろうオリジナル
exe
ファイルの作り方は以下の通りです。動作には Kinx の dll と各種ライブラリが必要なので、kinx.exe
と同じ場所に、作成したexe
を置かなくてはなりませんが、やりたいことをコマンド一発でできるようにはなります。具体的な例で。
kxcat.exe
cat
コマンドみたいなものとして、kxcat
コマンドを作ってみましょう。指定されたファイルを指定された順に出力します。cat
コマンドと名前が重ならないようにkxcat
にしておきましょう。一先ずオプションは無しで、ファイルだけ複数受け付けるようにします。lib/exec/3rdparty/kxcat.kx$$.each { // Ignoring the script file name. if (_2 > 0) { System.print(File.load(_1)); } };さて、そうしたら
kxtest.exe
をコピーして名前を変えましょう。Windows でも Linux でもコマンドは違いますが、やりたいことは同じです。Windows$ copy /y kxtest.exe kxcat.exeLinux$ cp -f kxtest kxcatコピーしたらそのまま
kxcat
コマンドを実行!$ ./kxcat README.md ChangeLog.md <p align="right"> <img src="https://github.com/Kray-G/kinx/workflows/Unit%20Test/badge.svg?branch=master"/> <img src="http://img.shields.io/badge/license-MIT-blue.svg?style=flat"/> </p> ...(省略) ## V0.1.0 (1st Preview Release) * Initial Release.できましたね。
インストールされたもので実行したい場合、Linux では以下の場所に
kinx
コマンドのバイナリがあるので、例えば上記の場合、kxcat
コマンドは同じ位置に配置してください。kxrepl
、kxtest
も同じ場所に配置してあります。$ which kinx kxrepl kxtest /usr/bin/kinx /usr/bin/kxrepl /usr/bin/kxtestおわりに
C でがっつり書いてコンパイルして実行ファイルを作る、でもいいんですけど、スクリプトでササっと書いたのを実行形式ファイルにしたいですよね。かといって、必要なライブラリとか dll とかを全部含めると結構なサイズになったりするので、ここは割り切って Kinx 自体が存在する前提でコマンド化できるようにしてみました。
これはこれで便利かなー、と思います。
あと、やればすぐできるんですけど、例えば kinx.dll の位置を環境変数で指定したり、オプションで渡したりできると、
.exe
ファイル自体はどこにおいても良くなるのでそのくらいの対応は今後するかも入れません(要望があれば)。xxx.exe
と同じ場所にあるxxx.kx
を実行するとかね。そのほうが便利かなー。dll
の場所は何かしらの方法で指定しておく必要はありますが。ではでは、また次回。
- 投稿日:2020-08-09T22:02:44+09:00
自分への未来のメッセージをプログラムで書いてみる【超初心者向け】
前置き
前回からの続き
未来考えるの大変だから臨機応変にメッセージを組むプログラムを書いてみます。基本的な機能
- とある値を元にメッセージを変化させる
- とある値を元にメッセージの表示方法を変化させる
この二つの機能を元にメッセージを組んでみます。とある値というのは個人で考えて意味を持つ値にしてください。例えば一年後の目標の達成度や何かの指標を決めておき、その値によってメッセージが変わるようにします。
とある値を元にメッセージを変化させる
プログラムの基本「if文」を使ってプログラミングをします。
言葉の通り、もし〇〇だったら××を行うという処理を書くことができます。
以下の場合だと「a」という中身が「1」であるかを判定し、「aは1です」と表示されます。let a = 1; if( a === 1 ){ console.log("aは1です"); } else { console.log("aは1ではありません"); } // 出力結果:「aは1です」以下の場合だと「a」という中身が「5」なので、「aは1ではありません」と表示されます。
let a = 5; if( a === 1 ){ console.log("aは1です"); } else { console.log("aは1ではありません"); } // 出力結果:「aは1ではありません」ここでいう「a」の中身の値がとある値ということにすれば、こんな感じにメッセージを書くことができます。
function checkBHAGDriven(achievement) { let bHAGDrivenMessage = "BHAG Drivenの達成度は" + achievement + "か。"; if(achievement >= 80){ bHAGDrivenMessage += "まじすごい。考えられない。理想にしているところも達成してしまうパワーを手に入れたようやな" + "あとは理想を広げ考え高めることが必要だと思う。と言っても一年前の自分には考えられない領域にいるんかな。自由に高みを目指すんやで。"; } else if(achievement < 80 && achievement >= 50){ bHAGDrivenMessage += "ぼちぼち目標達成に拘って達成できるようになってきたかな。でもまだ自分の理想は達成できてないんちゃう?" + "もっと自分出して周りを巻き込めるような野郎になるんやで。"; }else if(achievement < 50 && achievement >= 0){ bHAGDrivenMessage += "まだまだBHAGに取り組めていないようやな。もっとたくさんのことを行動ファーストでチャレンジせなあかん。" + "君は考えすぎでやらない理由を考えてしまうんやから、たまには頭空っぽにして行動する意識くらいの方がちょうどええんやで。"; }else if(achievement < 0){ bHAGDrivenMessage += "何してんの?"; } return bHAGDrivenMessage; }とある値を元にメッセージの表示方法を変化させる
メッセージの表示には
console.log()
を使っていますが、別の書き方で表示方法が変わります。
本来の使い方は違いますが、見かけが変わるので使ってみます。これをif文使って処理してみます
/** * 達成度に応じてメッセージの表示方法を変えて出力する * @param {*} bHagAchievement BHAGDrivenの達成度 * @param {*} icebergAchievement IcebergMindの達成度 * @param {*} growingTogetherAchievement GrowingTogetherの達成度 * @param {*} featureLetter 出力する手紙 */ function outputYourFeatureLetter( bHagAchievement, icebergAchievement, growingTogetherAchievement, featureLetter) { if(bHagAchievement >= 80 && icebergAchievement >= 80 && growingTogetherAchievement >= 80){ console.log(featureLetter); } else if(bHagAchievement >= 50 || icebergAchievement >= 50 || growingTogetherAchievement >= 50){ console.warn(featureLetter); } else { console.error(featureLetter); } }達成度が低いと赤くメッセージが出るようになるので、よろしくない状態を煽ります笑
どんなメッセージ?
最終的に書いてみたプログラムはこちらに置いてありますのでぜひ実行してみてください。
https://github.com/taka-guevara/FutureLetter/blob/master/futureLetter.js
実行の仕方はこちら達成度を値に設定しておくとその値に応じてメッセージが変わります。
達成度が悪いとこんな感じ
おまけ:今回の扱った達成度
私の勤めている株式会社POLのバリューをどれほど達成しているかを指標にしました。
あくまで私の主観であり感覚値です。
興味のある方はこちらをご覧ください。
- 投稿日:2020-08-09T21:19:20+09:00
正規表現入門 第三回 数量詞を用いた検索
数量詞とは
- 「数量を示す単語または句」のことを言う。
- メタ文字と同様、数量詞もそのものを検索する場合はエスケープが必要です。
先頭の文字を検索 "^"
- 文字列の「先頭」を検索するには「^」を使います。
- mオプションを加えると、改行直後の値も対象になります。
3-1(先頭の文字を検索).jsconsole.log("This is a pen!".match(/^This/)); console.log("This is a pen!".match(/^is/)); console.log("This is a pen!\nThis is a ball!".match(/^This/g)); console.log("This is a pen!\nThis is a ball!".match(/^This/gm)); console.log("This is a pen!".match(/^This\s../)); console.log("This is a pen.This is a ball.".replace(/^This/, "That"));実行結果
[ 'This', index: 0, input: 'This is a pen!', groups: undefined ]
null
[ 'This' ]
[ 'This', 'This' ]
[ 'This is', index: 0, input: 'This is a pen!', groups: undefined ]
That is a pen.This is a ball.末尾を検索 "$"
- 文字列の「末尾」を検索するには「$」を使います。
3-2(末尾を検索).jsconsole.log("This is a pen!".match(/pen!$/)); console.log("This is a pen!".match(/is$/)); console.log("This is a pen!\nThis is a ball!".match(/!$/g)); console.log("This is a pen!\nThis is a ball!".match(/!$/gm)); console.log("This is a pen!".match(/\spen!$/)); console.log("This is a pen.This is a pen.".replace(/pen.$/, "ball."));実行結果
[ 'pen!', index: 10, input: 'This is a pen!', groups: undefined ]
null
[ '!' ]
[ '!', '!' ]
[ ' pen!', index: 9, input: 'This is a pen!', groups: undefined ]
This is a pen.This is a ball.
- 投稿日:2020-08-09T19:26:56+09:00
【Nuxt】SSR・SSG・SPAにおける『nuxt build』と『nuxt generate』の実行結果の違いまとめ
NuxtにはSSR(Server Side Rendering)、SSG(Static Site Generator)1、SPA(Single Page Application)の3種類のモードが用意されています。
また、本番環境でNuxtアプリケーションを実行するにあたり
nuxt build
とnuxt generate
の2つのコマンドが用意されています。
nuxt build
はアプリケーションをWebpackでビルドし、JSとCSSをミニファイするコマンド2です。ビルドファイルの出力先は.nuxt
配下です。
nuxt generate
は静的ウェブサイトへデプロイする静的ファイルを生成するコマンド2です。静的ファイルの出力先はdist
配下です。
静的ウェブサイトのホスティングサービスではNetlifyやAmazon S3などが有名です。SSRでアプリケーションを運用する場合、
nuxt build
でファイルをビルド後、nuxt start
でNuxtアプリケーションをサーバー上で起動という流れになります。
一方、SSGやSPAではnuxt generate
で静的ファイルを生成後、静的ウェブサイトにアップロードするという流れになります。では、SSGで
nuxt build
を実行したり、SSRでnuxt generate
を実行したりするとどうなるでしょうか。今回はSSR・SSG・SPAにおける
nuxt build
とnuxt generate
の出力の違いについて紹介します。
なお、Nuxtは2.14.1
を利用します。NuxtにおけるSSR・SSG・SPAの設定方法について
Nuxtでは
nuxt.config.js
のmodeプロパティとtargetプロパティによってアプリケーションのモードが管理されています。
mode
にはuniversal
とspa
、target
にはserver
とstatic
の設定値が用意されています。
mode
をspa
にするとSPAになります。
mode
をuniversal
にした場合、target
をstatic
にすればSSG、target
をserver
にすればSSRになります。Nuxtアプリケーションのモードの初期設定は、新規作成時の対話の回答によって決定されます。
? Rendering mode:
はmode
の設定値に関する質問、? Deployment target:
はtarget
の設定値に関する質問です。検証に利用するサンプルアプリケーションについて
SSR・SSG・SPAにおける
nuxt build
とnuxt generate
の挙動を確認するにあたり、今回はNuxt公式ドキュメントで紹介されているCustom Routesを利用します。Custom Routesはユーザー一覧画面と各ユーザーの詳細画面が用意されているシンプルなアプリケーションです。
各パターンにおける実行結果の違いについて
2パターンの
mode
(universal
・spa
)と2パターンのtarget
(server
・static
)を組み合わせた、計4パターンでnuxt build
とnuxt generate
を実行しました。以下にパターンごとの結果を掲載します。
『mode: universal』『target: server』の場合
SSRに該当するパターンです。
nuxt build
の実行ログは以下の通りです。ℹ Production build ℹ Bundling for server and client side ℹ Target: server ✔ Builder initialized ✔ Nuxt files generated ✔ Client Compiled successfully in 5.36s ✔ Server Compiled successfully in 539.09ms Hash: 5a5cd4d0c2a79cef9011 Version: webpack 4.44.1 Time: 5358ms Built at: 2020/08/08 15:59:26 Asset Size Chunks Chunk Names ../server/client.manifest.json 7.83 KiB [emitted] LICENSES 389 bytes [emitted] app.dce2e9b.js 55.3 KiB 0 [emitted] [immutable] app node_modules/commons.48315ec.js 168 KiB 1 [emitted] [immutable] node_modules/commons pages/index.9ce75e2.js 1.51 KiB 2 [emitted] [immutable] pages/index pages/users/_id.5722ee2.js 1.45 KiB 3 [emitted] [immutable] pages/users/_id runtime.84feac3.js 2.35 KiB 4 [emitted] [immutable] runtime + 2 hidden assets Entrypoint app = runtime.84feac3.js node_modules/commons.48315ec.js app.dce2e9b.js Hash: 583b6fd0b31f116a80e2 Version: webpack 4.44.1 Time: 540ms Built at: 2020/08/08 15:59:26 Asset Size Chunks Chunk Names pages/index.js 6.63 KiB 1 [emitted] pages/index pages/users/_id.js 6.59 KiB 2 [emitted] pages/users/_id server.js 86.6 KiB 0 [emitted] app server.manifest.json 307 bytes [emitted] + 3 hidden assets Entrypoint app = server.js server.js.map ℹ Ready to run nuxt start ✨ Done in 9.20s.実行ログをもとに、SSRモードの
nuxt build
の内容をまとめると以下のようになります。
- サーバーサイドとクライアントサイドのビルドが実行される
- Targetはserverに設定される
nuxt generate
の実行ログは以下の通りです。ℹ Production build ℹ Bundling for server and client side ℹ Target: static ✔ Builder initialized ✔ Nuxt files generated ✔ Client Compiled successfully in 5.25s ✔ Server Compiled successfully in 578.63ms Hash: 413f8a0717bc47e1c0fb Version: webpack 4.44.1 Time: 5253ms Built at: 2020/08/08 16:01:35 Asset Size Chunks Chunk Names ../server/client.manifest.json 7.83 KiB [emitted] LICENSES 389 bytes [emitted] app.c20583b.js 55.5 KiB 0 [emitted] [immutable] app node_modules/commons.48315ec.js 168 KiB 1 [emitted] [immutable] node_modules/commons pages/index.9ce75e2.js 1.51 KiB 2 [emitted] [immutable] pages/index pages/users/_id.5722ee2.js 1.45 KiB 3 [emitted] [immutable] pages/users/_id runtime.84feac3.js 2.35 KiB 4 [emitted] [immutable] runtime + 2 hidden assets Entrypoint app = runtime.84feac3.js node_modules/commons.48315ec.js app.c20583b.js Hash: cef8c098609baff3310b Version: webpack 4.44.1 Time: 579ms Built at: 2020/08/08 16:01:35 Asset Size Chunks Chunk Names pages/index.js 6.63 KiB 1 [emitted] pages/index pages/users/_id.js 6.59 KiB 2 [emitted] pages/users/_id server.js 86.6 KiB 0 [emitted] app server.manifest.json 307 bytes [emitted] + 3 hidden assets Entrypoint app = server.js server.js.map ℹ Generating output directory: dist/ ℹ Generating pages ✔ Generated route "/" ✔ Generated route "/users/3" ✔ Generated route "/users/5" (略) ✔ Client-side fallback created: 200.html実行ログをもとに、SSRモードの
nuxt generate
の内容をまとめると以下のようになります。
- サーバーサイドとクライアントサイドのビルドが実行される
- Targetはstaticに設定される
- dist配下に各ページの静的ファイルが作成される
『mode: universal』『target: static』の場合
SSGに該当するパターンです。
nuxt build
の実行ログは以下の通りです。ℹ Production build ℹ Bundling for server and client side ℹ Target: full static ✔ Builder initialized ✔ Nuxt files generated ✔ Client Compiled successfully in 6.34s ✔ Server Compiled successfully in 561.19ms Hash: 009f5cce6a4034eb2970 Version: webpack 4.44.1 Time: 6345ms Built at: 2020/08/08 15:51:05 Asset Size Chunks Chunk Names ../server/client.manifest.json 7.86 KiB [emitted] LICENSES 389 bytes [emitted] app.edced78.js 58.5 KiB 0 [emitted] [immutable] app node_modules/commons.501805f.js 168 KiB 1 [emitted] [immutable] node_modules/commons pages/index.fe29326.js 1.51 KiB 2 [emitted] [immutable] pages/index pages/users/_id.7d0c948.js 1.45 KiB 3 [emitted] [immutable] pages/users/_id runtime.715f042.js 2.35 KiB 4 [emitted] [immutable] runtime + 2 hidden assets Entrypoint app = runtime.715f042.js node_modules/commons.501805f.js app.edced78.js Hash: b97105c83cc5f7b09c53 Version: webpack 4.44.1 Time: 562ms Built at: 2020/08/08 15:51:06 Asset Size Chunks Chunk Names pages/index.js 6.63 KiB 1 [emitted] pages/index pages/users/_id.js 6.59 KiB 2 [emitted] pages/users/_id server.js 87.7 KiB 0 [emitted] app server.manifest.json 307 bytes [emitted] + 3 hidden assets Entrypoint app = server.js server.js.map ℹ Ready to run nuxt generate ✨ Done in 12.29s.実行ログをもとに、SSGモードの
nuxt build
の内容をまとめると以下のようになります。
- サーバーサイドとクライアントサイドのビルドが実行される
- Targetはfull staticに設定される
nuxt generate
の実行ログは以下の通りです。✔ Skipping webpack build as no changes detected ℹ Generating output directory: dist/ ℹ Generating pages with full static mode ✔ Generated route "/" ✔ Generated route "/users/4" ✔ Generated route "/users/6" (略) ✔ Client-side fallback created: 200.html ✨ Done in 3.19s.実行ログをもとに、SSGモードの
nuxt generate
の内容をまとめると以下のようになります。
- dist配下に各ページの静的ファイルが作成される
『mode: spa』『target: server』の場合
デプロイ先をサーバーに指定したSPAです。
nuxt build
の実行ログは以下の通りです。ℹ Production build ℹ Bundling only for client side ℹ Target: static ✔ Builder initialized ✔ Nuxt files generated ✔ Client Compiled successfully in 5.23s Hash: 286e8db275c753c4ebd5 Version: webpack 4.44.1 Time: 5227ms Built at: 2020/08/08 16:04:10 Asset Size Chunks Chunk Names ../server/client.manifest.json 7.79 KiB [emitted] LICENSES 389 bytes [emitted] app.43c0549.js 55.4 KiB 0 [emitted] [immutable] app node_modules/commons.48315ec.js 168 KiB 1 [emitted] [immutable] node_modules/commons pages/index.9ce75e2.js 1.51 KiB 2 [emitted] [immutable] pages/index pages/users/_id.5722ee2.js 1.45 KiB 3 [emitted] [immutable] pages/users/_id runtime.84feac3.js 2.35 KiB 4 [emitted] [immutable] runtime + 1 hidden asset Entrypoint app = runtime.84feac3.js node_modules/commons.48315ec.js app.43c0549.js ℹ Generating output directory: dist/ ℹ Generating pages ✔ Generated route "/" ✔ Client-side fallback created: 200.html ✨ Done in 8.36s.実行ログをもとに、SPA(target: server)モードの
nuxt build
の内容をまとめると以下のようになります。
- クライアントサイドのビルドが実行される
- Targetはstaticに設定される
- dist配下にルート(/)の静的ファイル(index.html)が作成される
nuxt generate
の実行ログは以下の通りです。ℹ Production build ℹ Bundling only for client side ℹ Target: static ✔ Builder initialized ✔ Nuxt files generated ✔ Client Compiled successfully in 5.15s Hash: 286e8db275c753c4ebd5 Version: webpack 4.44.1 Time: 5155ms Built at: 2020/08/08 16:05:27 Asset Size Chunks Chunk Names ../server/client.manifest.json 7.79 KiB [emitted] LICENSES 389 bytes [emitted] app.43c0549.js 55.4 KiB 0 [emitted] [immutable] app node_modules/commons.48315ec.js 168 KiB 1 [emitted] [immutable] node_modules/commons pages/index.9ce75e2.js 1.51 KiB 2 [emitted] [immutable] pages/index pages/users/_id.5722ee2.js 1.45 KiB 3 [emitted] [immutable] pages/users/_id runtime.84feac3.js 2.35 KiB 4 [emitted] [immutable] runtime + 1 hidden asset Entrypoint app = runtime.84feac3.js node_modules/commons.48315ec.js app.43c0549.js ℹ Generating output directory: dist/ ℹ Generating pages ✔ Generated route "/" ✔ Client-side fallback created: 200.html ✨ Done in 7.95s.実行ログをもとに、SPA(target: server)モードの
nuxt generate
の内容をまとめると以下のようになります。
- クライアントサイドのビルドが実行される
- Targetはstaticに設定される
- dist配下にルート(/)の静的ファイル(index.html)が作成される
『mode: spa』『target: static』の場合
デプロイ先を静的ウェブサイトに指定したSPAです。
nuxt build
の実行ログは以下の通りです。ℹ Production build ℹ Bundling only for client side ℹ Target: static ✔ Builder initialized ✔ Nuxt files generated ✔ Client Compiled successfully in 5.23s Hash: 286e8db275c753c4ebd5 Version: webpack 4.44.1 Time: 5236ms Built at: 2020/08/08 16:06:27 Asset Size Chunks Chunk Names ../server/client.manifest.json 7.79 KiB [emitted] LICENSES 389 bytes [emitted] app.43c0549.js 55.4 KiB 0 [emitted] [immutable] app node_modules/commons.48315ec.js 168 KiB 1 [emitted] [immutable] node_modules/commons pages/index.9ce75e2.js 1.51 KiB 2 [emitted] [immutable] pages/index pages/users/_id.5722ee2.js 1.45 KiB 3 [emitted] [immutable] pages/users/_id runtime.84feac3.js 2.35 KiB 4 [emitted] [immutable] runtime + 1 hidden asset Entrypoint app = runtime.84feac3.js node_modules/commons.48315ec.js app.43c0549.js ℹ Ready to run nuxt generate ✨ Done in 7.75s.実行ログをもとに、SPA(target: static)モードの
nuxt build
の内容をまとめると以下のようになります。
- クライアントサイドのビルドが実行される
- Targetはstaticに設定される
nuxt generate
の実行ログは以下の通りです。ℹ Doing webpack rebuild because nuxt.config.js modified ℹ Production build ℹ Bundling only for client side ℹ Target: static ✔ Builder initialized ✔ Nuxt files generated ✔ Client Compiled successfully in 6.29s Hash: 47d36e3c6f1bf3363c94 Version: webpack 4.44.1 Time: 6296ms Built at: 2020/08/08 16:07:19 Asset Size Chunks Chunk Names ../server/client.manifest.json 7.79 KiB [emitted] LICENSES 389 bytes [emitted] app.ef33196.js 55.4 KiB 0 [emitted] [immutable] app node_modules/commons.48315ec.js 168 KiB 1 [emitted] [immutable] node_modules/commons pages/index.9ce75e2.js 1.51 KiB 2 [emitted] [immutable] pages/index pages/users/_id.5722ee2.js 1.45 KiB 3 [emitted] [immutable] pages/users/_id runtime.84feac3.js 2.35 KiB 4 [emitted] [immutable] runtime + 1 hidden asset Entrypoint app = runtime.84feac3.js node_modules/commons.48315ec.js app.ef33196.js ℹ Generating output directory: dist/ ℹ Generating pages ✔ Generated route "/" ✔ Client-side fallback created: 200.html ✨ Done in 8.93s.実行ログをもとに、SPA(target: static)モードの
nuxt generate
の内容をまとめると以下のようになります。
- クライアントサイドのビルドが実行される
- Targetはstaticに設定される
- dist配下にルート(/)の静的ファイル(index.html)が作成される
まとめ
実行結果をまとめると以下のようになります。
- プロセスは異なるものの、
nuxt build
ではビルドファイル、nuxt genearte
では静的ファイルが生成される結果は同じ- SPA(target: server)のときに限り、
nuxt build
でビルドファイルだけでなく静的ファイルも生成されるtarget
が異なっていてもmode
が同じであれば成果物は変わらないmode
が異なる、つまりSPAかSPAじゃない(SSR or SSG)かで成果物が異なるnuxt build
による成果物は、SSR/SSGの場合はサーバーとクライアント、SPAの場合はクライアントのみのビルドファイルnuxt generate
による成果物は、SSR/SSGの場合は各画面、SPAの場合はルートのみの静的ファイルTwitter(@nishina555)やってます。フォローしてもらえるとうれしいです!
参考記事
SSGは『静的化』や『静的ファイル生成』などとも呼ばれることがあります。 ↩
- 投稿日:2020-08-09T18:33:26+09:00
【備忘】google apps script でslack通知
usage
sendSlack("hoge");関数
function getSlackURL(){ return "*** slack webhook ***"; }function sendSlack(text){ const channel = "#channel_name"; const data = { 'channel': channel, 'username': 'bot', 'text': text, 'icon_emoji': ':ghost:' }; const option = { "method": "post", "payload": JSON.stringify(data), "muteHttpExceptions": true }; UrlFetchApp.fetch(getSlackURL(), option); }
- 投稿日:2020-08-09T18:17:58+09:00
Vue.jsで名前の表示/非表示を切り替える
はじめに
ボタンを押すと画面上にある名前の表示と非表示の切り替えができるプログラムを書いていきます。
ソースコード
全体のソースコードは以下の通りになります。(headタグやvueの読み込み箇所は省きます。)
HTMLファイルに書くソースコード
<div id="app"> <button @click="toggleBtn">クリックしてね</button> <p v-if="show"> {{name}} </p> </div>jsファイルに書くソースコード
var app = new Vue({ el: "#app", data: { name: "Naoki", show: true, }, methods: { toggleBtn: function () { this.show = !this.show; }, }, });ソースコードの解説
コードを書く順番で見ていきます。
条件分岐で名前を表示させる
<!-- 条件分岐 --> <p v-if="show"> {{name}} <!-- nameの出力 --> </p>
show
がtrueの場合、name
のデータが出力されます。//保持しているデータ data: { name: "Naoki", show: true, },
name
に"Naoki"という文字データとshow
にブーリアン型の true が入っている状態です。ここでの処理内容は
v-if="show"
で条件分岐が行われ、今のshow
のデータの中身はtrueで条件を満たしているので、name
に入っている"Naoki"という文字データが画面上に出力されることになります。
※コメント欄に補足説明があるので、よろしければご覧ください。クリックで名前の表示/非表示を切り替える
<!-- クリックイベントでの関数の呼び出し --> <button @click="toggleBtn">クリックしてね</button>ボタンをクリックすると
toggleBtn
の処理が呼び出されます。// 関数の設定 methods: { toggleBtn: function () { this.show = !this.show; }, },toggleBtnが呼び出されると、反転した
show
がshow
に代入されます。
ちなみに、this
というのは、jsファイルのapp
を指しています。ここでの処理内容は、ボタンをクリックすると
toggleBtn
の処理が呼び出されて、show
データのtrue
とfalse
が切り替わる仕組みになっています。この切り替えによって、名前の表示/非表示ができるようになるというわけです。おわりに
はじめてQiitaに記事を書いたのですが、こんなに簡単な処理内容でも文字にして説明するとなると全然スムーズにいかず、かなり頭を使いました。人に教えることに慣れていないので、これからもっと練習して人に教えるスキルとプログラミングスキルを向上させていきたいと思います。
- 投稿日:2020-08-09T18:02:46+09:00
vue-youtubeを使用して、複数のモーダル上で動画を再生させる
実装結果
git映像
実装サイトURL
https://miwa-vue-youtube.netlify.app/
実装コード
- ディレクトリ
src ├── App.vue ├── assets │ └── scss │ └── main.scss ├── components │ ├── MovieButton.vue │ └── MovieModal.vue ├── main.js └── plugins └── vue-youtube.js
- バージョン
"@vue/cli": "4.4.6", "vue": "^2.6.11", "vue-youtube": "^1.4.0"
コードの内容を見る
- src/App.vue
<template> <div id="app"> <movie-button :btn-num="1" :movie-id="movieData[0]" @modal-open="modalOpen($event)" /> <movie-button :btn-num="2" :movie-id="movieData[1]" @modal-open="modalOpen($event)" /> <movie-button :btn-num="3" :movie-id="movieData[2]" @modal-open="modalOpen($event)" /> <movie-modal :modal-content="modalContent" :is-open="isOpen" @modal-close="modalClose()" /> </div> </template> <script> import MovieButton from "./components/MovieButton"; import MovieModal from "./components/MovieModal"; export default { name: "App", components: { MovieButton, MovieModal }, data() { return { isOpen: false, modalContent: {}, movieData: ["r8bECyGsw6Q", "2MqjzMeD3Uo", "4Vsi174LRgg"] }; }, methods: { modalOpen(event) { console.log(event); this.modalContent = event; this.isOpen = true; }, modalClose() { this.isOpen = false; } } }; </script> <style lang="scss"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
- src/components/MovieButton.vue
<template> <transition name="fade" mode="out-in"> <div class="modal" v-if="isOpen"> <div class="modal__overlay" @click="onClick()"></div> <div class="modal__body"> <p>モーダル</p> <div class="youtube__wrapper"> <youtube :video-id="modalContent.movieId" ref="youtube"></youtube> </div> <button type="button" @click="onClick()">ボタン閉じる</button> </div> </div> </transition> </template> <script> export default { props: { modalContent: { type: Object }, isOpen: { type: Boolean } }, methods: { onClick() { this.$emit("modal-close"); } } }; </script> <style lang="scss" scoped> .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; } .modal__overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: #0008; } .modal__body { position: relative; top: 50%; right: 0; bottom: 0; left: 0; max-width: 1000px; margin: auto; background: #fff; transform: translateY(-50%); } .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .fade-enter, .fade-leave-to { opacity: 0; } </style>
- src/components/MovieModal.vue
<template> <button type="button" @click="onClick()">動画{{ btnNum }}</button> </template> <script> export default { props: { btnNum: { default: 0, type: Number }, movieId: { type: String } }, methods: { onClick() { this.$emit("modal-open", { movieId: this.movieId }); } } }; </script> <style lang="scss" scoped></style>
- src/plugins/vue-youtube.js
import Vue from "vue"; import VueYoutube from "vue-youtube"; Vue.use(VueYoutube);
- src/main.js
import Vue from "vue"; import App from "./App.vue"; import "./plugins/vue-youtube.js"; Vue.config.productionTip = false; require("@/assets/scss/main.scss"); new Vue({ render: h => h(App) }).$mount("#app");
- src/assets/main.scss
.youtube__wrapper { position: relative; width: 100%; margin: 0 auto; height: 0; padding-bottom: 56.25%; overflow: hidden; background: #aaa; iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } }実装手順
1. vue-youtubeインストール
$ npm install vue-youtube // or yarn add vue-youtube2. vue-youtube記載
- src/plugins/vue-youtube.js
import Vue from "vue"; import VueYoutube from "vue-youtube"; Vue.use(VueYoutube);
- src/main.js
import "./plugins/vue-youtube.js";※Nuxt.jsの場合、pluginsに記載
export default{ plugins: ["~plugins/vue-youtube.js"] }参考リンク
3. ボタン追加
- src/components/MovieButton.vue
<template> <button type="button" @click="onClick()">動画{{ btnNum }}</button> </template> <script> export default { props: { btnNum: { default: 0, type: Number }, movieId: { type: String } }, methods: { onClick() { this.$emit("modal-open", { movieId: this.movieId }); } } }; </script> <style lang="scss" scoped></style>
- src/App.vue
<template> <div id="app"> <movie-button :btn-num="1" :movie-id="movieData[0]" @modal-open="modalOpen($event)" /> <movie-button :btn-num="2" :movie-id="movieData[1]" @modal-open="modalOpen($event)" /> <movie-button :btn-num="3" :movie-id="movieData[2]" @modal-open="modalOpen($event)" /> </div> </template> <script> import MovieButton from "./components/MovieButton"; export default { components: { MovieButton }, data() { return { isOpen: false, modalContent: {}, movieData: ["r8bECyGsw6Q", "2MqjzMeD3Uo", "4Vsi174LRgg"] }; }, methods: { modalOpen(event) { this.modalContent = event; this.isOpen = true; } } }; </script>
movie-button
について
btn-num
ボタンの番号を識別させるためのもの(今回の実装では必要なし)movie-id
動画のidで動画の内容を識別させるためのもの(管理しやすくするため、data内のmovieDataに登録)@modal-open="modalOpen($event)"
子コンポーネントのクリックイベントを検知して、実行(モーダルを開くためのもの)参考
4. モーダル実装
- src/components/MovieModal.vue
<template> <transition name="fade" mode="out-in"> <div class="modal" v-if="isOpen"> <div class="modal__overlay" @click="onClick()"></div> <div class="modal__body"> <p>モーダル</p> <div class="youtube__wrapper"> <youtube :video-id="modalContent.movieId" ref="youtube"></youtube> </div> <button type="button" @click="onClick()">ボタン閉じる</button> </div> </div> </transition> </template> <script> export default { props: { modalContent: { type: Object }, isOpen: { type: Boolean } }, methods: { onClick() { this.$emit("modal-close"); } } }; </script> <style lang="scss" scoped> .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; } .modal__overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: #0008; } .modal__body { position: relative; top: 50%; right: 0; bottom: 0; left: 0; max-width: 1000px; margin: auto; background: #fff; transform: translateY(-50%); } .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .fade-enter, .fade-leave-to { opacity: 0; } </style>
- src/App.vue
<template> <div id="app"> <movie-modal :modal-content="modalContent" :is-open="isOpen" @modal-close="modalClose()" /> </div> </template> <script> import MovieModal from "./components/MovieModal"; export default { name: "App", components: { MovieModal }, data() { return { isOpen: false, modalContent: {}, movieData: ["r8bECyGsw6Q", "2MqjzMeD3Uo", "4Vsi174LRgg"] }; }, methods: { modalClose() { this.isOpen = false; } } }; </script>
- src/assets/main.scss
.youtube__wrapper { position: relative; width: 100%; margin: 0 auto; height: 0; padding-bottom: 56.25%; overflow: hidden; background: #aaa; iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } }
movie-modal
について
modal-content
モーダルに送る情報(今回はmovieId
のみ)is-open
モーダルが開いているか判定させるもの@modal-close="modalClose()"
子コンポーネントのクリックイベントを検知して、実行(モーダルを閉じるためのもの)参考リンク
+α
1. モーダルを開いたときに自動で再生を行う
PCのみ自動再生を行いたい場合
autoplay = 1
を追加<template> <youtube :video-id="modalContent.movieId" ref="youtube" :player-vars="playerVars" ></youtube> </template> <script> export default { data() { return { playerVars: { autoplay: 1 } }; }, } </script>参考リンク
autoplay = 1
のみだとSPなどのデバイスで対応することができないため、SPでも対応させたい場合、無音でインライン再生に変更を行うことで自動再生を行うことができる。<template> <youtube :video-id="modalContent.movieId" ref="youtube" :player-vars="playerVars" @ready="ready" ></youtube> </template> <script> export default { data() { return { playerVars: { playsinline: 1 } }; }, methods: { async fetchYoutube() { await (this.isOpen = ture); this.$refs.youtube.fetchData(); }, ready() { const youtubePlayer = this.$refs.youtube.player youtubePlayer.mute() youtubePlayer.playVideo() } } }; </script>参考リンク
- events - vue-youtube - npm
- iframe 組み込みの YouTube Player API リファレンス | YouTube IFrame Player API
- パラメータ一覧 - iframe 組み込みの YouTube Player API リファレンス
- スマホ(iOS)でも動画を自動再生させよう!(iFrame Player API、videoタグ対応) | 株式会社 エヴォワークス -EVOWORX-
2. 字幕を自動で追加を行う
cc_lang_pref = 1
に設定を行うことで字幕を追加できるexport default { data() { return { playerVars: { cc_lang_pref: 1 // cc_lang_prefを1に行う } }; }, }参考リンク
詰まった部分について
1. youtubeのスタイルが一部ずれる
- vueコンポーネント内に
.youtube__wrapper { position: relative; width: 100%; margin: 0 auto; height: 0; padding-bottom: 56.25%; overflow: hidden; background: #aaa; iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } }のような記載を行うと
iframe
内のコンポーネントにスタイルが当たらないため以下のような状態になる。
また、スタイルを当てていないとモーダルを閉じる時、動画の高さがなくなり、以下のような状態になる。
なので、グローバルcssの場所に以下を記載
.youtube__wrapper { position: relative; width: 100%; margin: 0 auto; height: 0; padding-bottom: 56.25%; overflow: hidden; background: #aaa; iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } }参考リンク
2. v-ifを用いると
this.$refs
が取得できない
v-if
を用いるとコンポーネントが描画されていない状態でthis.$refsの内容を取得しようとするので、取得できないエラーになる。
なので、async/await
を用いることになり、コンポーネント描画 → $refs取得を行うことができるようにするasync fetchYoutube() { await (this.isOpen = ture); // isOpenフラグがtrueになったら this.$refs.youtube.fetchData(); // this.$refs.youtubeの取得を行う },参考リンク
3. モーダルが上下中央によらない
.modal__body { position: absolute; top: 0; right: 0; bottom: 0; left: 0; max-width: 1000px; margin: auto; background: #fff; }のような状態で配置した場合、
のような状態で、高さが取得されないため、
position: relative
とtop: 50%
などで変更を行う.modal__body { position: relative; /* relativeに変更 */ top: 50%; /* 50%に変更 */ right: 0; bottom: 0; left: 0; max-width: 1000px; margin: auto; background: #fff; transform: translateY(-50%); /* -50%を追加 */ }
- 投稿日:2020-08-09T16:24:53+09:00
Vue-CLI 3でnpm run serveが失敗するときの解決方法
事象
Dockerコンテナ内の環境にて、
npm run serve
を実行すると、下記のようなエラーが発生していました。ERROR Failed to compile with 2 errors This relative module was not found: * ./src/main.js in multi (webpack)-dev-server/client/index.js (webpack)/hot/dev-server.js ./src/main.js, multi (webpack)/hot/dev-server.js (webpack)-dev-server/client/index.js ./src/main.js環境
- Microsoft Windows 10 Pro
- Docker for Windows
- Visual Studio Code
- node.js : 12.18.2
- npm : 6.14.5
原因
筆者の場合、
npm install
を実行してもpackage.json
内に含まれているdevDependencies
がインストールされておらず、必要なライブラリ(ここでは@vue/cli-service
)が存在していなかったというのが原因でした。解決策
node.js側の環境変数である
NODE_ENV
をdevelopment
(あるいはdev
)にしてからnpm install
を実行する必要がありました。
NODE_ENV
を設定する方法は、①.コマンドラインからexport
する方法と、docker-composeを使っている場合は②.docker-compose.xml
に記載する方法があります。①.
export
コマンドで設定する下記のコマンドで
NODE_ENV
に環境値を設定できます。# NODE_ENVを設定 $ export NODE_ENV=development # 設定した値を確認する $ echo $NODE_ENV development②.
docker-compose.xml
のenvironmentキーに設定するdocker-composeでコンテナを立ち上げている場合、
environment
キーを使って設定することができます。version: '3' services: web: build: . ports: - "8080:8080" environment: - NODE_ENV=development tty: true
export
で設定したときと同様に、$NODE_ENV
をechoすると設定値を確認すると、development
が設定されているのが確認できるはずです。問題の原因を特定するには?
今回の場合、
package.json
に記載されているパッケージが正しくインストールできていると早とちりし、「パスが通っていない」か「windowsでシンボリックリンクが使えないのが悪さしている」と思い込んでいたのが、原因の特定が遅れてしまった原因でした。ちなみに、パッケージが正しくインストールされているかは、
npm list --depth=0
で確認することができます。# インストールされているパッケージの一覧(一階層のみ)を表示 $ npm list --depth=0 project@0.1.0 /app +-- core-js@3.6.5 `-- vue@2.6.11 # core-js と vue しか入ってないやん!もし、筆者と同様に
npm run serve
が正しく動作しない場合は、このコマンドで確認してみるといいかもしれません。
- 投稿日:2020-08-09T15:29:47+09:00
テクトロジーによる実践的組織構造学
今回の記事では、ソ連の革命家、医師、哲学者、小説作家であったアレクサンダーボグダノフが提唱したテクトロジーと呼ばれる実践的組織構造学について紹介する。
テクトロジーでは組織が安定、成長、破綻する環境、条件について詳細に解説し、安定した組織を生成する手法について解説している。
テクトロジーの概念を拝借し、創造的な組織創造の手法を紹介したいと思う。テクトロジーにおける組織の定義
テクトロジーでは、組織をオープンクローズ型の成長する有機体システムと定義している。
組織とは以下の要素で構成されている。
ビジョン・・・組織の目指すべき方向性。
経済・・・組織のボディ。巨大であるほど収容できる人の人数が増加する
金融・・・組織を循環する血液。
生産・・・もの、サービスを生産し、組織の経済を巨大化する手段。これらの有機的な要素が相互作用し、成長することで組織という有機体が構成されていると考える。
テクトロジーにおける成長する有機体システムとは
テクトロジーでは、組織をオープンクローズ型の成長する有機体システムと定義している。
テクトロジーにおける組織の定義を中国の陰陽論によって説明することができる。
陰陽論とは、原初は混沌(カオス)の状態であると考え、この混沌の中から光に満ちた明るい澄んだ気、すなわち陽の気が上昇して天となり、重く濁った暗黒の気、すなわち陰の気が下降して地となった。この二気の働きによって万物の事象を理解し、また将来までも予測しようというのが陰陽思想である。
組織が外部からエネルギーを取り入れ、出力するインプット、アウトプットの運動を陰陽論における陽の気と捉えることができる。
逆に組織内部に沈殿し、成長し、ヒエラルキーを形成する秩序生成を担う運動を陰陽論における陰の気と捉えることができる。テクトロジーにおける生産の定義
テクトロジーでは、組織における生産活動は以下の3つに分類されている。
人の生産・・・人に教育を施し、組織活動に従事する生産者を作成する
モノ、サービスの生産・・・外部から取得した資材を用いて、モノ、サービスの生産を行う。
アイデア・・モノ、サービスを生成するための知識、アイディアを作成する。
組織における生産活動を高めることで、組織の経済を成長させることができる。テクトロジーおける組織のフォーム(形態)について
現実の世界で、人が活動を行う場合、必ず外部からの影響、抵抗を受ける。
外部からの影響、抵抗を抑えるために、組織は環境に合わせた最適なフォーム(形態)を取る必要がある。
魚やイルカなど、魚と哺乳類で種族は異なるが、水の抵抗を抑えるために同様の流線形フォルムを取っている。
組織のフォーム(形態)は外部環境によって決定される。
外部環境からの抵抗を最小限にするために、外部との接触の最小化、不要な組織的機能の削除などが求められる。
最適なフォーム(形態)によって、組織は外部からの抵抗を減少させ、健全に成長することができる。テクトロジーにおける組織が不安定化する条件
テクトロジーにおける組織が不安定化する条件として以下の2点が挙げられる。
・外部からのエネルギー取得の減少・・外部から人、モノ、金の循環が減少することで組織のサイズ、経済を維持することできなくなる。
・ヒエラルキーシステムの固定化・・・ヒエラルキーシステムが巨大化し、組織が硬直化してしまう。
組織不安定化を回避する手法として以下の手段が有効とされている。
生産手段を研究、開発、更新を行い、組織の経済成長のスピードを増加させる。
組織が硬直化の原因になっているヒエラルキーシステムを解体し、適切なサイズに組み替える。まとめ
アレクサンダーボグダノフがテクトロジーに関するアイディアを発表した時期は1920年代である。
独学で組織が破綻する条件、環境を発見し、持続可能な成長のコンセプトを提唱したアレクサンダーボグダノフの先見性は恐るべきものである。
ソ連は軍事、IT、経済においてアメリカと張り合うことができた超大国だった。
ソ連時代に考えられたアイディア、思想などは現代においても見直されるべきものだと思われる。
- 投稿日:2020-08-09T14:24:50+09:00
Deno の公式レジストリの登録方法が手動から自動になりました
Deno の公式レジストリの登録方法が変わりました! 本記事ではその登録方法について解説します。
(この記事は Deno の公式レジストリに自分の書いたモジュールを登録してみたい人向けの記事です。npm にモジュールを登録した経験がある人などが主な対象読者です。)
Deno の公式レジストリとは
Deno の公式レジストリは https://deno.land/x にあります。(特に固有の名称などはありません。単にレジストリと呼ばれることが多いです。)
これまで、ここに自分の作ったモジュールを登録するには、公式ホームページのレポジトリにある JSON ファイルに PR を出して、
手動でマージしてもらうことが必要でした。(旧来のワークフローでは手動のレビューとマージをしていました)
この若干面倒だった登録フローが、8/3のアップデートで、
PR を出す必要のない自動的なワークフローに置き換わりました。本記事では、その登録方法を解説します。
新レジストリの概要
まず新レジストリに登録されるものは GitHub レポジトリのタグのみになります。これまでは branch名 (master など) や commit hash などでも import 出来ていましたが、これらの import は出来なくなります。
OKimport { myFunc } from "https://deno.land/x/my_module@v1.0.0/my_func.ts";NGimport { myFunc } from "https://deno.land/x/my_module@master/my_func.ts"; import { myFunc } from "https://deno.land/x/my_module@1e587a0/my_func.ts";そしてタグの登録方法が少し特殊で、npm のようなコマンドによる登録ではなく、github から Webhook で Deno レジストリの API を叩くことでタグが登録される仕組みになっています。
以下ではこの Webhook の登録方法を解説します。
Webhook の登録方法
レジストリに登録したいレポジトリの Settings から Webhook のページに行き、以下のように Webhook を設定しましょう。
- Payload URL:
https://api.deno.land/webhook/gh/モジュール名
(モジュール名の部分は登録したいモジュール名に置き換える)- Content type:
application/json
- Events:
Let me select individual events.
を選びBranch or tag creation
のみにチェック- その他はデフォルト設定
この設定をすることで、タグを作った時に Deno レジストリのタグ登録用 API に POST リクエストが飛ぶようになります。
この状態で実際にタグを作ってみましょう
git tag v0.1.0 git push origin v0.1.0Webhook がうまく飛ぶと、Webhook 設定ページで以下のように、リクエストとレスポンスのログを見ることが出来ます。
この例では、筆者の kt3k/deno_license_checker という github レポジトリを deno.land/x/license_checker として登録しています。
登録がうまくいくと、deno.land 上の当該ページ ( https://deno.land/x/モジュール名 ) 上で作ったタグのバージョンが追加されます。
モジュール登録の注意点
モジュールの各タグはイミュータブルになるという原則があります。したがって、同じタグを2回以上登録して上書きするような事は出来ません。何かを変えたい場合は必ずバージョンを上げましょう。
また、当然ですが、モジュールの乗っ取りも出来ないようになっています。ある GitHub レポジトリに紐付いたモジュール名に別の GitHub レポジトリからタグ登録しようとしてもエラーになります。
npm との違い
npm では
npm publish
というコマンドでパッケージのバージョンを登録する仕組みでしたが、これに相当するコマンドは用意されていません。Webhook を設定したレポジトリで、タグを切ってプッシュすることがイコール新しいバージョンを公開することになります。なお npm の場合は publish する人の認証のために npm コマンドにログインすることが必要でした。Deno のレジストリではそのような認証の仕組みはありません。Deno のレジストリの場合は GitHub の特定のレポジトリとモジュールがリンクされる (かつそのリンクが変わることはない) ため GitHub 上での認証がモジュールの認証を兼ねていると言えます。
まとめ
本記事では、Deno の新しいレジストリでのモジュールの登録方法を紹介しました。Webhook を設定してレジストリに publish するという仕組みは若干目新しいですが、慣れれば難しい設定ではありません。ぜひ Deno の新しいレジストリにモジュールを登録してみましょう ?
- 投稿日:2020-08-09T13:52:24+09:00
【javascript】書籍のアウトプット
こちらの記事は以下の書籍を参考にアウトプットとして執筆しました。
文字の検索
最初の文字が$かどうかの判断はこう書く
if(price[0]=='$'){ }最初の3文字の場合はこうかもしれない
if(phone.substr(0,3)===user.areaCode){ }substr
str.substr(start[, length])
引数 説明 start 最初の文字の位置 length 取り出す数 返り値は、指定された部分が入った新しい文字列
出典:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/substr
先のコードはphoneの最初の文字から3文字を抜き出している。
ここまでの2つのコードは何をしているのかわかりにくい
そこで自己文書化**が使える文字列の検索では以下の3つのメソッドが使えるようになった
新しいメソッド 説明 includes その文字が含まれているか startsWith その文字で始まっているか endsWith その文字で終わっているか 戻り値はbool値
これらは大文字小文字を区別するこれらは第2引数に検索し始める位置を指定できる(インデックス)
第2引数にindexOfメソッドを使えば効率よく使える
indexOfは検索してマッチした文字の位置を返り値としている文字列のパディング
特定の文字で特定の長さだけ埋めるというもの
例えば以下は10進数のIPアドレスを2進数へ変換する関数のコードfunction binaryIP(decimalIPStr){ return decimalIPStr.split('.').map(function(octet){ return Number(octet).toString(2) }).join('.') }Array.prototype.map()
条件に合う新しい配列を生成する
構文var new_array = arr.map(function callback(currentValue[, index[, array]]) { // 新しい配列の要素を返す }[, thisArg])
コールバック引数 説明 currentValue 現在処理中の値 index 現在処理中の要素の配列内のインデックス array mapが実行されている配列 thisArg callbackを実行するときにthisとして使う値 このコードでは以下のようなことをしている
.split('.')
でIPアドレスを4つの配列に分割- map(function(octed)により新しいい配列を生成
- octedは現在処理中の値
- toString()で引数に入る進数に変換する
以上のコードでは0梅がされないなどうまく行かない
そこでrepeatを使うfunction binaryIP(decimalIPStr){ return decimalIPStr.split('.').map(function(octet){ let bin= Number(octet).toString(2) return '0'.repeat(8-bin.length)+bin }).join('.') }repeat
repeatが呼び出した文字列を繰り返す
'0'.repeat(4);//'0000'0が4回繰り返される
- 投稿日:2020-08-09T11:27:45+09:00
【JS】空文字判定の注意
空文字("")を判定したい!!
function isEmptyString(str) { return !Boolean(str); } console.log(isEmptyString("")); // => true console.log(isEmptyString(0)); // => true console.log(isEmptyString()); // => true空文字でなくても暗黙的な型変換が起こってしまう。そのため、
falsy
な値はこの関数でtrue
を返してしまう。こうしましょ
function isEmptyString(str) { // `string`型かつ`length`が0の場合に`true`を返す return typeof str === "string" && str.length === 0; } console.log(isEmptyString("")); // => true console.log(isEmptyString(0)); // => false console.log(isEmptyString()); // => false参考
- 投稿日:2020-08-09T10:08:11+09:00
聞いたことはあるがよくわからないJavaScript周辺のあれこれ
JavaScriptを学習していると、よくわからない概念やライブラリに出会う機会が多いです。
その中でも特によく耳にするものをざっくりまとめてみました。(ホントにざっくり)
各内容をもっと掘り下げた参考記事も貼っているので気になる方はそちらも読んでみてください。ECMAScript
ECMAScriptとはJavaScriptの言語仕様の取り決め。
よく耳にするES2015
やES6
といった用語はJavaScriptのバージョンを表し、ここで出てくるES
がECMAScriptのこと。【JavaScript】JavaScript、その前に〜ECMAScriptとは?
npm
Node Package Manager、すなわちNode.jsのパッケージを管理するもの。
npmのおかげで、npm install 〇〇
と打つだけで便利なライブラリを簡単にインストールして利用することができる。yarn
2016年にFacebookが公開したかなり新しめのJavaScriptパッケージマネージャ。
役割はnpmとほぼ同じだが、npmと比べてインストール・セキュリティ・バージョン管理の面で優れている。package.json
パッケージマネージャを用いてプロジェクトを作成する際に、プロジェクトが依存するパッケージに関する情報(さらにはプロジェクト全体に関する情報)を記録するファイルがpackage.json。
プロジェクトを動作させるために必要なパッケージをdependencies属性とdevDependencies属性に記述しておけば、npm install
コマンドを打つだけでプロジェクト環境を復元できるため、非常に便利。【初心者向け】NPMとpackage.jsonを概念的に理解する
Babel
BabelはJavaScriptのコンパイラ。
これを使うとJavaScriptのコードを新しい書き方から古い書き方へと変換してくれる。
ブラウザによって対応しているJavaScriptのバージョンや仕様が異なるので、各ブラウザの環境に合わせて記法を変換する必要がある。【5分でなんとなく理解!】Babel入門
webpackとBabelの基本を理解する(1) ―Babel編―webpack
webpackはモジュールバンドラ。
モジュールバンドラとは、複数のファイルを1つにまとめて出力してくれるツールのこと。
webpackはJSファイルだけでなく、CSSや画像ファイルも1つにまとめてくれる。
webpackを使えば、開発時には機能ごとにファイルを分割して開発を進めることができ、読み込み時には1つのファイルとして読み込めるので、非常に便利。webpackってどんなもの?
webpackとBabelの基本を理解する(1) ―webpack編―ESLint
ESLint は JavaScript のための静的検証ツール。
コードを実行する前に明らかなバグを見つけたり、括弧やスペースの使い方などのスタイルを統一したりするのに役立つ。
- 投稿日:2020-08-09T04:25:13+09:00
GASで毎朝その日の予定とかを通知するLINE Bot [第4回]
この記事について
前回の続きです。
コード解説
前回の続きをどんどん解説していきます。まずはこれ。
Flex Message
let flex = { 'type': 'bubble', 'size': 'giga', 'body': { 'type': 'box', 'layout': 'vertical', 'contents': [ { 'type': 'text', 'text': today, 'weight': 'bold', 'size': 'xxl', 'flex': 0 }, { 'type': 'box', 'layout': 'horizontal', 'contents': [ { 'type': 'filler' }, { 'type': 'text', 'text': weather, 'size': 'lg', 'color': '#444444', 'flex': 0, 'gravity': 'center' } ] }, { 'type': 'box', 'layout': 'horizontal', 'contents': [ { 'type': 'filler' }, { 'type': 'text', 'text': temp_l + '℃', 'size': 'lg', 'color': '#3f51b5', 'flex': 0 }, { 'type': 'text', 'text': '/', 'size': 'lg', 'color': '#444444', 'margin': 'xs', 'flex': 0 }, { 'type': 'text', 'text': temp_h + '℃', 'size': 'lg', 'color': '#f44336', 'margin': 'xs', 'flex': 0, 'gravity': 'center' } ] }, { 'type': 'separator', 'margin': 'xl', 'color': '#808080' }, { 'type': 'box', 'layout': 'vertical', 'contents': [ //EVENTS ], 'margin': 'xl' } ] } };これがBotの送信するデータの中核になるわけです。これはFlex Messageというもので、LINEのメッセージをCSS Flexboxライクのフォーマットで表示するものなのだそう。
無の状態からこのデータを作ったわけではなく、LINEがシミュレータを公開してくれている。
右上のView as JSON
でJSONデータを見ることができます。これをコピペして変数に置き換えるなどの処理をするだけでFlex Messageのデータを作れます。祝日・ごみ収集日の表示
if (holiday != '') { flex.body.contents[1].contents.splice(0, 0, { 'type': 'text', 'text': holiday, 'size': 'md', 'color': '#808080', 'flex': 0, 'gravity': 'center' }); }
holiday
の中身があるときに実行されます。
splice(start, count, data)
でJSONデータに割り込む形でデータを挿入します。start
で割り込む位置、count
で割り込む際に元データから消去するstart
からの要素の数を、data
に挿入するデータを指定します。
flex.body.contents[1].contents[0]
の位置にデータを割り込ませ、count
は0なので元のデータは1ずつずれて更新されます。
if (garbage[d.getDay()]) { let line = holiday == '' ? 1 : 2; flex.body.contents[line].contents.splice(0, 0, { 'type': 'text', 'text': garbage[d.getDay()], 'size': 'md', 'color': '#808080', 'flex': 0, 'gravity': 'center' }); }ごみ収集日が0(=false)以外である時に実行されます。
その日が祝日なら2行目、祝日でないなら1行目に表示させるため、三項演算子を用いてline
を指定しています。
こちらも同じくsplice(start, count, data)
で挿入。予定の表示
予定にはTimeTreeに登録したデータを利用します。
const props = PropertiesService.getScriptProperties(); const TIMETREE_TOKEN = props.getProperty('TIMETREE_TOKEN'); const opt = { 'headers': { 'Authorization': 'Bearer ' + TIMETREE_TOKEN }, 'method': 'get' };
props
には、GAS拡張サービスのPropertiesService
でgetScriptProperties()
を実行し、プロジェクトのプロパティに保存したAPIのトークンの配列を読み込んで代入しています。
getProperty(key)
で指定したキーに対応する値を返します。
opt
には、UrlFetchApp.fetch(url, option)
のオプションを設定しています。
主にデータを取得する際に用いられるGETリクエストでは、オプションには基本的に以下のような内容を利用できます。{ "method": "get", "headers": { /* header */ } }
"method"
は"get"
,"headers"
にはヘッダというものを入れます。今回のHTTPリクエストではAPIが要求するリクエストタイプ(
'method': 'get'
)とAPIキー(トークン)をAPI側に送信する必要があるため、ヘッダに認証情報を入れています。このトークンは、IDやパスワードなしにAPIが利用可能なので、Bearerトークンとよばれており、
'Authorization': 'Bearer XXXXXXXX
という記法で認証を受けるのが一般的です。const calendars = JSON.parse(UrlFetchApp.fetch('https://timetreeapis.com/calendars', opt).getContentText()).data; const z = (t) => ('0' + t).slice(-2);TimeTree API > カレンダー一覧の取得
https://timetreeapis.com/calendars
TimeTree API ドキュメント
calendars
にはリクエストして返ってきたカレンダー一覧のJSONデータが入ります。
const z = (t) => ('0' + t).slice(-2);
ここでは、t
が1文字の'X'
だった時、'0X'
となる「ゼロ埋め」の処理になっています。
slice(n, m)
は、配列のn番目からm番目まで(m番目は含まない)をコピーする関数です。
m
を省略し、n
に負の数を入れると、最後からn番目までをコピーします。
第3回でも書きましたが、文字列は配列の一種なのでこの関数を利用できます。最後から2文字分を取得できるわけです。アロー関数V8 という記法で、
z
に関数を定義しています。アロー関数let x = (a, b) => { return a * b; } //1行にすると、その計算結果がそのまま返される let x = (a, b) => a * b;
let ev_exists = false; for (let calendar of calendars) { let cal = JSON.parse(UrlFetchApp .fetch('https://timetreeapis.com/calendars/' + calendar.id + '/upcoming_events?timezone=Asia/Tokyo&days=1', opt) .getContentText()).data; for (let event of cal) { let {title, start_at, end_at, all_day} = event.attributes; start_at = new Date(start_at); end_at = new Date(end_at); let time = all_day ? '終日' : z(start_at.getHours()) + ':' + z(start_at.getMinutes()) + '-' + z(end_at.getHours()) + ':' + z(end_at.getMinutes()); let schedule = { 'type': 'box', 'layout': 'horizontal', 'contents': [ { 'type': 'text', 'text': time, 'flex': 0, 'color': '#808080', 'gravity': 'center', 'size': 'md' }, { 'type': 'text', 'text': title, 'size': 'lg', 'weight': 'bold', 'color': '#606060', 'flex': 0, 'gravity': 'center', 'margin': 'lg' } ], 'margin': 'sm' }; flex.body.contents[4].contents.push(schedule); ev_exists = true; } }第3回では
for-in
関数を紹介しましたが、今回はfor-of
関数V8 です。for-oflet json = {a: 1, b: 3, c: 5}; let x = 0; for (let data of json) { x += data; } // x => 9;配列(連想配列含む)の要素ひとつひとつに対して処理を行う反復処理です。
配列の要素の数だけ実行されます。data
には要素のデータが入ります。let cal = JSON.parse(UrlFetchApp .fetch('https://timetreeapis.com/calendars/' + calendar.id + '/upcoming_events?timezone=Asia/Tokyo&days=1', opt) .getContentText()).data;ここでは、取得した全種類のカレンダーひとつひとつのその日の予定を取得します。
そこから先は、
- 予定をひとつひとつ取り出して
- 分割代入で時間とタイトルと終日かどうかのデータを取得して
- 三項演算子で
time
に終日なら'終日'
、そうでないなら時間をゼロ埋めして代入して- JSONデータ作って
- Flex Messageに突っ込む
だけです。
もし予定が何もないなら、ev_exists
はfalse
となり、以下のコードが実行されます。if (!ev_exists) { flex.body.contents.splice(3, 2); }
splice(start, count, data)
についてはすでにご紹介しましたが、ここでは、data
を省略すると、start
からcount
の分だけデータが削除されるという仕様を使っています。
予定の上に引かれている罫線と、予定を格納するボックスを削除しています。Messaging APIに送信
さあ、いよいよ大詰めです。
const LINE_TOKEN = props.getProperty('LINE_TOKEN'); const payload = { 'messages': [ { 'type': 'flex', 'altText': today, 'contents': flex } ] }; const opt_line = { 'headers': { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Bearer ' + LINE_TOKEN }, 'method': 'post', 'payload': JSON.stringify(payload) }; UrlFetchApp.fetch('https://api.line.me/v2/bot/message/broadcast', opt_line);
PropertiesService
から、保存しているMessaging APIのトークンをLINE_TOKEN
に代入します。
今回のHTTPリクエストの種類はPOST
なので、GAS側からデータを送信します。
payload
にデータを代入します。
opt_line
にUrlFetchApp.fetch(url, option)
のオプションを設定します。
データを送信するので、今回はヘッダに'Content-Type'
を設定します。データの形式です。
また、TimeTree APIと同様に認証情報を設定します。
POST
のリクエストの場合は、'method': 'post'
となります。
'payload'
にデータを入れることで、Massaging APIにメッセージのデータが送信されます。まとめ
いかがでしたでしょうか。記事を書いている途中から力尽きそうになっていましたが、書ききることができました。
分割代入やアロー関数など、まだあまり使用例が多くないものをご紹介できたかと思います。
ご感想やご指摘などありましたらコメントいただけますと幸いです。
それでは、よいプログラミングライフを!
- 投稿日:2020-08-09T04:25:00+09:00
GASで毎朝その日の予定とかを通知するLINE Bot [第3回]
この記事について
前回の続きです。初回でご紹介したコードの解説をしていきます。知識のある方にはつまらない記事になるかと思います。
V8 ランタイムにする理由
let
,const
やアロー関数などといった、現在では標準的になったコードを使用するためです。
また、少しでも動作が低負荷でメモリも消費せず標準的なコードにするための選択でもあります。
解説中にV8 ランタイムからの機能が登場した際には、V8 と付けることにします。コード解説
それでは、解説をしていきます。
// Change these variables fit to your condition const garbage = [0, '資源ごみ', '可燃ごみ', 'カン・ビン', '不燃ごみ', '可燃ごみ', 'プラスチックごみ']; const area = '13101'; const d = new Date(); const today = (d.getMonth() + 1) + '月' + d.getDate() + '日(' + '日月火水木金土'[d.getDay()] + ')';最初にユーザに変更してもらう項目、実行時の日付などを宣言しておきます。
'日月火水木金土'[d.getDay()]
変わっている箇所と言えばここでしょうか。文字列は文字コードの配列を扱いやすくしたものなので、所詮は配列と同じように処理できます。// これと同じことをしている const array = ['日', '月', '火', '水', '木', '金', '土']; const d = new Date(); const today = (d.getMonth() + 1) + '月' + d.getDate() + '日(' + array[d.getDay()] + ')';祝日の取得
const [event_holiday] = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com').getEventsForDay(d); const holiday = event_holiday ? event_holiday.getTitle() : '';GASの拡張サービス
CalendarApp
を使用します。
getCalendarById(id)
でカレンダーを取得します。
Googleカレンダーには、公式で祝日のカレンダーが存在します。
日本の祝日には、ja.japanese#holiday@group.v.calendar.google.com
というIDのカレンダーが用意されているので、これを使います。
getEventsForDay(day)
でその日のイベントを取得します。2つ以上の祝日が同じ日に来ることはないという前提で、event_holiday
にはこの配列の1つ目の値を取り出して代入しています。
event_holiday
の宣言方法に、分割代入V8 を使用しています。配列の任意の要素を簡潔な表記で変数に代入する機能です。分割代入let array = [3, 5, 7, 9, 11]; let [first, second, third] = array; // first => 3, second => 5, third =>7 let [saisyo] = array; // 3 // これと同じ let saisyo = array[0]; // 3また、
holiday
には、三項演算子を使用しています。祝日がない日のevent_holiday
は空配列の中身を参照するためundefined
になります。これは条件式上ではfalse
と同じ挙動をします(0も同じ)。三項演算子let check = false; let result = check ? "trueだよ" : "falseだよ"; // "falseだよ" check = undefined; // 0, nullなどでも同じ result = check ? "trueだよ" : "falseだよ"; // "falseだよ";天気情報の取得
let content = UrlFetchApp.fetch('https://static.tenki.jp/static-api/history/forecast/' + area + '.js').getContentText(); content = JSON.parse(content.substring(content.indexOf('(') + 1, content.indexOf(');'))); let {max_t: temp_h = "不明", min_t: temp_l = "不明", t: weather = "不明"} = content; const words = { '時々': '|', '一時': '|', 'のち': '»', '晴': '☀', '曇': '☁', '雨': '☔', '雪': '⛄' }; for (let key in words) { weather = weather.replace(key, words[key]); }拡張サービス
UrlFetchApp
を使用します。
fetch(url, option)
でHTTPリクエストを送信することができます。第2引数のない状態では、特にオプションのないGETリクエストになります。リンク先のデータをそのままもらいます。
UrlFetchApp.fetch(url, option)
ではレスポンスデータなるものが返ってきます。データを見たいのでgetContentText()
で文字列として取得します。天気情報の取得にはtenki.jpのデータの変なところから引っ張ってきています。
自分で使っていたときは気象庁のページのWebスクレイピングだったのですが、公開するにあたり一般化が非常に困難だと判明したので代替策を探っていました。当初はWebスクレイピングにしようと思っていたのですが、ページのサイズが大きいので高負荷になってしまう懸念があり、APIを探し回っていたところたまたま発見しました。
tenki.jpで天気予報を見ていると画面上部に自分が最近閲覧した地域の天気予報が小さく表示されるんですが、それを取得するAPIがありました。URLのフォーマットは
https://static.tenki.jp/static-api/history/forecast/XXXXX.js
XXXXXには標準地域コードが入ります。で、返ってくるのが
'__r__XXXXX({"i":"画像番号","j":"標準地域コード","max_t":"最高気温","min_t":"最低気温","n":"地域名","p":"降水確率","t":"天気"});'
画像番号はサイト表示用の画像の番号ですね。
https://static.tenki.jp/images/icon/forecast-days-weather/XX.png
で表示できるみたい。
今回はこのデータをありがたく使わせていただくことにしました。
substring
で両端の要らない文字を捨てて、JSON.parse(str)
(文字列をJSONデータに)に通すと、以下のデータができあがります。content{ "i":"画像番号", "j":"標準地域コード", "max_t":"最高気温", "min_t":"最低気温", "n":"地域名", "p":"降水確率", "t":"天気" }ここでまた、分割代入の登場です。
let {max_t: temp_h = "不明", min_t: temp_l = "不明", t: weather = "不明"} = content;
ここでは、さらに高度なことをやっています。
前提として、以下のJSONデータを宣言しておきます。let json = {a: 1, b: 3, c: 5};
連想配列やJSONデータの分割代入let {a, b, c} = json; // a => 1, b => 3, c => 5配列の分割代入では
[ ]
を使用して宣言しましたが、連想配列(JSON)では{ }
に入れて宣言します。
任意の名前の変数への代入let {a, b: beta, c: charlie} = json; // a => 1, beta => 3, charlie => 5
変数の初期値V8 の設定
変数の初期値は他にも関数などで適用可能です。let {a, b: beta, c: charlie, d: delta = 10} = json; // a => 1, beta => 3, charlie => 5, delta => 10
次に、天気の内容を絵文字に変換します。ここでは、for-in
関数V8 を使用します。
先程のjson
を使ってご説明します。for-inlet s = ''; let x = 0; for (let key in json) { s += key; x += json[key]; } // s => 'abc', x => 9これは、配列(連想配列含む)の要素ひとつひとつに対して処理を行う反復処理です。
配列の要素の数だけ実行されます。key
には要素のインデックスまたはキーが入ります。
今回は連想配列なのでキーが入ります。'晴'
とか。
次回に続きます。
- 投稿日:2020-08-09T04:24:50+09:00
GASで毎朝その日の予定とかを通知するLINE Bot [第2回]
この記事について
前回の続きです。前回は汚いコードを見せつけるだけという愚行を繰り広げたわけですが、この先で丁寧に解説してまいります。
今回は、Botの作成からMessaging APIのトークン取得,TimeTree APIのトークン取得,使用する方に合わせて書き換える箇所をご紹介します。LINE Botを作成
さっそく、肝心のBotを作っていきましょう。
こちらから自分のLINEアカウントでログインし、以下の項目を入力してチャネル(=Bot)を作成します。
項目 内容 チャネルの種類 Messaging API プロバイダー 自分のLINEアカウント チャネル名 任意のBot名 チャネル説明 適当な説明 大業種 自分の業種 小業種 自分の業種 メールアドレス 自分のメールアドレス 規約類をよく読んで項目にチェックし、
作成
をクリック。
Botの作成が完了するとBotの設定画面に移動するので、Messaging API設定の項目に移動し、ボットのベーシックID
またはその下のQRコードで友だち追加をしておきます。LINE Messaging APIのトークンを取得
先程のページの最下部に チャネルアクセストークン という項目があります。
チャネルアクセストークン(長期)
の右側にある発行
をクリックすると、このBotのアクセストークンが発行されます。これをコピーします。発行されたアクセストークンを、開いているGAS プロジェクトに登録します。
今回は、GASの標準機能のPropertiesService
を使用します。
ファイル
→プロジェクトのプロパティ
スクリプトのプロパティ
の行の追加
をクリック
プロパティ
にLINE_TOKEN
、値
にコピーしたトークンを貼り付け以上でLINE Botの作成,プロジェクトへの登録は終わりです。
TimeTree APIのトークンを取得
こちらから自分のアカウントでログインし、
トークンの作成
をクリック。
トークン名を入力する画面になりますが、好きな名前で大丈夫です。
名前で後に影響が出ることはありません。判別しやすければOK。
ガイドラインをよく読みチェックを入れ作成
をクリック。
作成したトークンが表示されますが、再表示されないという仕様なので要注意(また新しく発行すればOK)。作成したトークンをLINE Messaging APIと同様にGAS プロジェクトに登録します。
ファイル
→プロジェクトのプロパティ
スクリプトのプロパティ
の行の追加
をクリック
プロパティ
にTIMETREE_TOKEN
、値
にコピーしたトークンを貼り付け最終的に
スクリプトのプロパティ
欄が以下のような状態になっていれば完了です。
コードを変更する箇所
前回ご紹介したコードで、使用する方によって変えなければいけない箇所のご説明。
notify// Change these variables fit to your condition const garbage = [0, '資源ごみ', '可燃ごみ', 'カン・ビン', '不燃ごみ', '可燃ごみ', 'プラスチックごみ']; const area = '13101';
garbage
には各曜日において収集されるごみの分別を配列で設定します。
回収がない日は0
を設定します。
日曜始まりです。
area
には天気情報を表示したい地域の標準地域コードを設定します。
標準地域コード…国が定める地域ごとに割り振られたコードです。こちらから参照できます。初回の実行
初回の実行で、Google カレンダーとの連携など、いくつかの権限が求められます。
自動実行では許可できないため、必ず一度は実行しましょう。
実行する関数をnotify
に設定し、▶
をクリックしてみてください。
承認が要求されます。GASの承認欲求を満たしてあげましょう。
許可を確認
をクリック。
GASの開発中にログインしているGoogleアカウントでログインすると、以下のような表示が出ることがあります。これは、Googleが確認していないアプリケーションで連携を行なうためです。
左下の詳細
をクリックします。
(スクリーンショットを撮り忘れたので「無題のプロジェクト」になっています、すみません)
Morning Assistant(安全ではないページ)に移動
をクリックすると、このアプリに許可する内容が表示されます。
問題なければ許可
をクリックします。するとBotが実行されます。トリガーを設定する
毎朝このBotを実行させるには、トリガーを設定する必要があります。
矢印の指しているボタンをクリックすると、トリガーの一覧が表示されます。
画面右下のトリガーを追加
をクリックし、下の画像のように設定します。
時刻を選択の項目には、自分が通知を受け取りたい時間を選択します。以上でBotが機能するようになりました。次回はコードの詳しい解説をしていきます。
「動けばいいよ!」とか、「解説は要らないよ!」という方は、ここまで見るだけでOKです。お疲れ様でした!
- 投稿日:2020-08-09T04:24:38+09:00
GASで毎朝その日の予定とかを通知するLINE Bot [第1回]
この記事について
Qiita初投稿のうえ、プログラミング初心者です。至らぬ点がたくさんあるとは思いますが暖かく見守ってください…
Google Apps Scriptを用いて毎朝LINEでその日のいろいろを教えてくれるBotを作ったので解説も兼ねてご紹介します。
第1回, 第2回ではセットアップの説明を、第3回以降ではコードの詳細な解説をしていきます。この記事で取り扱う内容
- Google Apps Script (GAS)の基本
- JavaScriptの基本~発展
- LINE Messaging API
- Flex Message
- TimeTree API(予定取得)
- Googleカレンダー(祝日取得)
実際どんな感じ?
表示されるもの
- 日付
- 祝日
- 収集があるごみ
- 天気(気温)
- 予定(TimeTreeから)
GASプロジェクトを作成
こちらから新規のGASプロジェクトを作成します。
アドレスバーに
script.new
で新規プロジェクトを作成できます。
.new ドメインを利用したショートカット一覧(英語)注意すること
V8 ランタイムを搭載したプロジェクトでのみ機能します(新規プロジェクトは最初から搭載しています)。
既存のプロジェクトを使う場合は、それがV8 ランタイムを搭載したプロジェクトであることを確認してください。
プロジェクトを開いた際にこのプロジェクトは Chrome V8 を搭載した新しい Apps Script ランタイムで実行しています。
という表示があれば問題ありません(詳細)。ええい、これが完成したコードだ!
このコードをコピーしてGASプロジェクトにドーン!!
コード.gsfunction notify() { // Change these variables fit to your condition const garbage = [0, '資源ごみ', '可燃ごみ', 'カン・ビン', '不燃ごみ', '可燃ごみ', 'プラスチックごみ']; const area = '13101'; const d = new Date(); const today = (d.getMonth() + 1) + '月' + d.getDate() + '日(' + '日月火水木金土'[d.getDay()] + ')'; const [event_holiday] = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com').getEventsForDay(d); const holiday = event_holiday ? event_holiday.getTitle() : ''; let content = UrlFetchApp.fetch('https://static.tenki.jp/static-api/history/forecast/' + area + '.js').getContentText(); content = JSON.parse(content.substring(content.indexOf('(') + 1, content.indexOf(');'))); let {max_t: temp_h = "不明", min_t: temp_l = "不明", t: weather = "不明"} = content; const words = { '時々': '|', '一時': '|', 'のち': '»', '晴': '☀', '曇': '☁', '雨': '☔', '雪': '⛄' }; for (let key in words) { weather = weather.replace(key, words[key]); } let flex = { 'type': 'bubble', 'size': 'giga', 'body': { 'type': 'box', 'layout': 'vertical', 'contents': [ { 'type': 'text', 'text': today, 'weight': 'bold', 'size': 'xxl', 'flex': 0 }, { 'type': 'box', 'layout': 'horizontal', 'contents': [ { 'type': 'filler' }, { 'type': 'text', 'text': weather, 'size': 'lg', 'color': '#444444', 'flex': 0, 'gravity': 'center' } ] }, { 'type': 'box', 'layout': 'horizontal', 'contents': [ { 'type': 'filler' }, { 'type': 'text', 'text': temp_l + '℃', 'size': 'lg', 'color': '#3f51b5', 'flex': 0 }, { 'type': 'text', 'text': '/', 'size': 'lg', 'color': '#444444', 'margin': 'xs', 'flex': 0 }, { 'type': 'text', 'text': temp_h + '℃', 'size': 'lg', 'color': '#f44336', 'margin': 'xs', 'flex': 0, 'gravity': 'center' } ] }, { 'type': 'separator', 'margin': 'xl', 'color': '#808080' }, { 'type': 'box', 'layout': 'vertical', 'contents': [ //EVENTS ], 'margin': 'xl' } ] } }; if (holiday != '') { flex.body.contents[1].contents.splice(0, 0, { 'type': 'text', 'text': holiday, 'size': 'md', 'color': '#808080', 'flex': 0, 'gravity': 'center' }); } if (garbage[d.getDay()]) { let line = holiday == '' ? 1 : 2; flex.body.contents[line].contents.splice(0, 0, { 'type': 'text', 'text': garbage[d.getDay()], 'size': 'md', 'color': '#808080', 'flex': 0, 'gravity': 'center' }); } const props = PropertiesService.getScriptProperties(); const TIMETREE_TOKEN = props.getProperty('TIMETREE_TOKEN'); const opt = { 'headers': { 'Authorization': 'Bearer ' + TIMETREE_TOKEN }, 'method': 'get' }; const cals = JSON.parse(UrlFetchApp.fetch('https://timetreeapis.com/calendars', opt).getContentText()).data; const z = (t) => ('0' + t).slice(-2); let ev_exists = false; for (let calendar of calendars) { let cal = JSON.parse(UrlFetchApp .fetch('https://timetreeapis.com/calendars/' + calendar.id + '/upcoming_events?timezone=Asia/Tokyo&days=1', opt) .getContentText()).data; for (let event of cal) { let {title, start_at, end_at, all_day} = event.attributes; start_at = new Date(start_at); end_at = new Date(end_at); let time = all_day ? '終日' : z(start_at.getHours()) + ':' + z(start_at.getMinutes()) + '-' + z(end_at.getHours()) + ':' + z(end_at.getMinutes()); let schedule = { 'type': 'box', 'layout': 'horizontal', 'contents': [ { 'type': 'text', 'text': time, 'flex': 0, 'color': '#808080', 'gravity': 'center', 'size': 'md' }, { 'type': 'text', 'text': title, 'size': 'lg', 'weight': 'bold', 'color': '#606060', 'flex': 0, 'gravity': 'center', 'margin': 'lg' } ], 'margin': 'sm' }; flex.body.contents[4].contents.push(schedule); ev_exists = true; } } if (!ev_exists) { flex.body.contents.splice(3, 2); } const LINE_TOKEN = props.getProperty('LINE_TOKEN'); const payload = { 'messages': [ { 'type': 'flex', 'altText': today, 'contents': flex } ] }; const opt_line = { 'headers': { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Bearer ' + LINE_TOKEN }, 'method': 'post', 'payload': JSON.stringify(payload) }; UrlFetchApp.fetch('https://api.line.me/v2/bot/message/broadcast', opt_line); }もちろん、これだけでは機能しません。
まだまだ設定することがありますが、長くなりましたので、その手順は次の記事にてご紹介します。
- 投稿日:2020-08-09T00:09:38+09:00
AnglarのビルドとFirebaseのデプロイを連動させる
この記事はAngular+Firebaseでチャットアプリを作るのエントリーです。
前記事:WEBアプリでFirebaseのデプロイ環境を構築するこの記事で行うこと
前回の記事ではFirebaseのステージング運用とデプロイ方法について書きましたが、今回の記事ではAngularとFirebaseのデプロイを連動させる方法について書いていきます。
Angularのデプロイ
Angularに限らず、近年のJavaScriptアプリケーションでは開発環境と本番環境のビルドファイルを分けて作成することが多いです。開発環境ではデバッグをする必要があるため、SourceMapの表示を許容し、デバッグのしやすさを重視します。一方、本番環境ではファイルの読み込み速度や機密性が重視されるため、ファイルをminifyし、余計な情報を減らす作業が必要になります。
このビルド作業の使い分けは自力でやろうとすると面倒が多いため、webpackやgulpといったタスクランナーを使って管理することがデファクトスタンダードになっています。
Angularでも例に漏れず、裏側ではwebpackを使ってタスク処理をしているようですが、webpack.config.js
ではなく、angular.json
にビルド方式やテストツールの設定情報を記載しています。
「自前のwebpackでやりたい!」という方向けの@angular-builders/custom-webpack
というものもあるので、自分で設定を書きたい方は以下のリンクを参考にしてください。参考:Angular + カスタマイズWebpack 開発環境構築
実装内容
AngularとFirebaseを連動させてデプロイを行う場合は、AngularFireの
ng deploy
を使用します。このコマンドにより、開発環境に応じたビルド、デプロイを実現できるようになります。
AngularFireについては既に以前の記事でインストール方法等について紹介していますので、この記事から参照された方はそちらを確認してください。参考:https://angular.io/guide/deployment
注1)
ng deploy
が@angular/cli
に導入されたのはv8.3.0以降となります。それ以前の@angular/cli
を利用している場合は使用できませんのでご注意ください。
注2)@angular/cli
のng deploy
は2020年の5月にバグが報告され、修正されています。下記の実装はそれ以前の@angular/cli
だとエラーがでますので、最新の状態にしてください。
参考:https://github.com/angular/angular-cli/issues/17613なお、今回使用するFirebaseのプロジェクトは2つで、次の様に設定します。
環境 Project ビルド方式 開発環境 開発用 開発用 ステージング環境 開発用 本番用 本番環境 本番用 本番用 開発環境にデプロイする
まず、
angular.json
のソースコードを確認します。angular.json"deploy": { "builder": "@angular/fire:deploy", "options": {}, }
@angular/cli
v8.3.0以降のangular.json
であれば、最初からng deploy
のための設定が記載されています。とりあえずこの状態でng deploy
を実行すると、ng build --prod
コマンドを打った時と同じ挙動をします。ただ、開発環境ではデバッグをする必要があるため、
--prod
をしないように設定する必要があります。angular.json"deploy": { "builder": "@angular/fire:deploy", "options": { "buildTarget": "NgChat:build", }, }これで開発環境の設定は完了です。
ng deploy
をして、開発環境の挙動を確認します。実行結果
ステージング環境にデプロイする
まず、staging環境用の環境設定ファイルを作成します。
開発環境のsrc/environments/environment.ts
をコピーし、production
をtrueにします。src/environments/environment.staging.tsexport const environment = { production: true, firebase: { apiKey: '<your-key>', authDomain: '<your-project-authdomain>', databaseURL: '<your-database-URL>', projectId: '<your-project-id>', storageBucket: '<your-storage-bucket>', messagingSenderId: '<your-messaging-sender-id>', appId: '<your-app-id>', measurementId: '<your-measurement-id>', }, };次に
angular.json
でビルド情報を設定します。buildオブジェクトにあるconfigurationsを見ると、すでにproduction用の設定は記載されているので、この内容をstaging用に修正します。
*設定ファイルパス以外は変更していません。angular.json"configurations": { "production": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.staging.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "budgets": [ { "type": "initial", "maximumWarning": "2mb", "maximumError": "5mb" }, { "type": "anyComponentStyle", "maximumWarning": "6kb", "maximumError": "10kb" } ] } }ここに書いてある設定情報が何を意味しているのかをざっくりまとめると、以下のようになります。
項目名 説明 fileReplacements ファイル差し替え。主にenvironmentファイルに使われる。 optimization 最適化する。{scripts: true, styles: false} というような個別設定も可能 outputHashing 出力ファイル名にハッシュをつける。キャッシュバスター。"none","all","media","bundles" sourceMap sourceMapを出力する。{scripts: true, styles: true, hidden: false, vendor: true}というような個別設定も可能 extractCss グローバル指定のcssを展開する namedChunks 遅延読み込みのファイルに名前をつける extractLicenses 利用ライブラリのライセンスファイルをまとめる vendorChunk ライブラリだけで単独のファイルにする。ライブラリは変更頻度が低いため。 buildOptimizer aot利用時、@angular-devkit/build-optimizerを有効にする budgets 生成ファイルのファイルサイズ制限を設定できる。気軽に巨大なライブラリをimportすると使わないコードが大量に含まれてしまったりするのを警告する。 この内容は以下記事からの抜粋です。他のパラメータについてはそちらを参照してみてください。
参考:angular.jsonの中身ここで指定した内容をもとにデプロイできるよう、
angular.json
のデプロイ情報に追記します。angular.json"deploy": { "builder": "@angular/fire:deploy", "options": { "buildTarget": "NgChat:build" }, "configurations": { "staging": { "buildTarget": "NgChat:build:production" } } }これで準備が整いました。
あとは-c
オプション(--configuration
のエイリアス)をつけてデプロイします。ng deploy -c staging
実行結果
デプロイ後の画面(ソースマップが確認できず、エラーは表示される)
本番環境にデプロイする
最後に本番環境のデプロイ設定します。
Angularのプロジェクトにあるenvironment.prod.ts
ファイルにfirebaseの情報を追記します。この際、本番用プロジェクトのパラメータを使用してください。src/environments/environment.prod.tsexport const environment = { production: true, firebase: { apiKey: '<your-key>', authDomain: '<your-project-authdomain>', databaseURL: '<your-database-URL>', projectId: '<your-project-id>', storageBucket: '<your-storage-bucket>', messagingSenderId: '<your-messaging-sender-id>', appId: '<your-app-id>', measurementId: '<your-measurement-id>', }, };次に
angular.json
に本番用プロジェクトの追加をします。
serve
やtest
は開発・ステージング環境でのみ使用するため、本番用のプロジェクトからは除外しています。angular.json"NgChatProd": { "projectType": "application", "schematics": {}, "root": "", "sourceRoot": "src", "prefix": "app", "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/NgChat", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", "aot": true, "assets": [ "src/favicon.ico", "src/assets" ], "styles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css", "src/styles.css" ], "scripts": [] }, "configurations": { "production": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "budgets": [ { "type": "initial", "maximumWarning": "2mb", "maximumError": "5mb" }, { "type": "anyComponentStyle", "maximumWarning": "6kb", "maximumError": "10kb" } ] } } }, "deploy": { "builder": "@angular/fire:deploy", "options": {} } } }この設定だけだどhostingデプロイ時にエラーがでるため、Firebase側の設定ファイルにも追記をします。
今回、ステージング用のFirebaseプロジェクトは使用しないため、開発用と本番用の2プロジェクトを作成します。.firebaserc{ "projects": { "dev": "開発用FirebaseプロジェクトID", "prod": "本番用FirebaseプロジェクトID" }, "targets": { "開発用FirebaseプロジェクトID": { "hosting": { "NgChat": [ "開発用FirebaseプロジェクトID" ] } }, "本番用FirebaseプロジェクトID": { "hosting": { "NgChatProd": [ "本番用FirebaseプロジェクトID" ] } } } }firebase.json{ "target": "NgChat", "public": "dist/NgChat", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ], "rewrites": [ { "source": "**", "destination": "/index.html" } ] }, { "target": "NgChatProd", "public": "dist/NgChat", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ], "rewrites": [ { "source": "**", "destination": "/index.html" } ] }これで本番環境の設定は完了です。
ng deploy
の後にプロジェクト名をいれて実行します。ng deploy NgChatProd実行結果
デプロイ後の画面(ソースマップが確認できず、エラーは表示される)
本記事をもって「Angular+Firebaseでチャットアプリを作る」のエントリーはすべて終了です。
お疲れ様でした。ソースコード
この時点でのソースコード
※apiKeyは削除しているので、試すときは自身で作成したapiKeyを入れてください。