[{"data":1,"prerenderedAt":6377},["ShallowReactive",2],{"articles-page-5":3},{"count":4,"content":5},63,[6,410,765,1343,3046,3330,3753,4184,4690,6210],{"id":7,"title":8,"body":9,"category":395,"createdAt":397,"description":398,"extension":399,"index":400,"meta":401,"navigation":402,"path":403,"publish":402,"seo":404,"series":400,"seriesTitle":400,"stem":405,"tag":406,"thumbnail":400,"updatedAt":400,"__hash__":409},"articles\u002Farticles\u002Ffirebase-emulator-port-token.md","ポートが使用されていてFirebaseエミュレータが起動できない時の対処法",{"type":10,"value":11,"toc":393},"minimark",[12,16,27,30,36,43,49,263,266,269,284,287,360,363,377,380,386,389],[13,14,15],"p",{},"こんにちはjunです、最近Firebaseを使用したアプリケーション開発をしています。エミュレータがあるのでそこでFirestoreやAuthenticationのテストをしています。ある日、開発の続きをやろうとしてエミュレータを起動させました。",[17,18,23],"pre",{"className":19,"code":21,"language":22},[20],"language-text","firebase emulators:start\n","text",[24,25,21],"code",{"__ignoreMap":26},"",[13,28,29],{},"しかし",[17,31,34],{"className":32,"code":33,"language":22},[20],"⚠  firestore: Port 8081 is not open on localhost, could not start Firestore Emulator.\n⚠  firestore: To select a different host\u002Fport, specify that host\u002Fport in a firebase.json config file:\n      {\n        \u002F\u002F ...\n        \"emulators\": {\n          \"firestore\": {\n            \"host\": \"HOST\",\n            \"port\": \"PORT\"\n          }\n        }\n      }\ni  emulators: Shutting down emulators.\n\nError: Could not start Firestore Emulator, port taken.\n",[24,35,33],{"__ignoreMap":26},[13,37,38,39,42],{},"あらら、、起動に失敗してしまいました。エミュレーター二回目の起動の時によく発生します。原因は",[24,40,41],{},"Could not start Firestore Emulator, port taken.","とある様にポートが使用中だからです。",[13,44,45,48],{},[24,46,47],{},"firebase.json","にはエミュレーターのポートを定義できます。私の場合は以下の通りです。",[17,50,54],{"className":51,"code":52,"language":53,"meta":26,"style":26},"language-json shiki shiki-themes material-theme-ocean","\"emulators\": {\n    \"auth\": {\n        \"port\": 9099\n    },\n    \"functions\": {\n        \"port\": 5001\n    },\n    \"firestore\": {\n        \"port\": 8081\n    },\n    \"database\": {\n        \"port\": 8082\n    },\n    \"ui\": {\n        \"enabled\": true\n    }\n},\n","json",[24,55,56,78,96,114,120,134,148,153,167,181,186,200,214,219,233,248,254],{"__ignoreMap":26},[57,58,61,65,69,71,75],"span",{"class":59,"line":60},"line",1,[57,62,64],{"class":63},"sAklC","\"",[57,66,68],{"class":67},"sfyAc","emulators",[57,70,64],{"class":63},[57,72,74],{"class":73},"s0W1g",": ",[57,76,77],{"class":63},"{\n",[57,79,81,84,88,90,93],{"class":59,"line":80},2,[57,82,83],{"class":63},"    \"",[57,85,87],{"class":86},"sJ14y","auth",[57,89,64],{"class":63},[57,91,92],{"class":63},":",[57,94,95],{"class":63}," {\n",[57,97,99,102,106,108,110],{"class":59,"line":98},3,[57,100,101],{"class":63},"        \"",[57,103,105],{"class":104},"s5Dmg","port",[57,107,64],{"class":63},[57,109,92],{"class":63},[57,111,113],{"class":112},"sx098"," 9099\n",[57,115,117],{"class":59,"line":116},4,[57,118,119],{"class":63},"    },\n",[57,121,123,125,128,130,132],{"class":59,"line":122},5,[57,124,83],{"class":63},[57,126,127],{"class":86},"functions",[57,129,64],{"class":63},[57,131,92],{"class":63},[57,133,95],{"class":63},[57,135,137,139,141,143,145],{"class":59,"line":136},6,[57,138,101],{"class":63},[57,140,105],{"class":104},[57,142,64],{"class":63},[57,144,92],{"class":63},[57,146,147],{"class":112}," 5001\n",[57,149,151],{"class":59,"line":150},7,[57,152,119],{"class":63},[57,154,156,158,161,163,165],{"class":59,"line":155},8,[57,157,83],{"class":63},[57,159,160],{"class":86},"firestore",[57,162,64],{"class":63},[57,164,92],{"class":63},[57,166,95],{"class":63},[57,168,170,172,174,176,178],{"class":59,"line":169},9,[57,171,101],{"class":63},[57,173,105],{"class":104},[57,175,64],{"class":63},[57,177,92],{"class":63},[57,179,180],{"class":112}," 8081\n",[57,182,184],{"class":59,"line":183},10,[57,185,119],{"class":63},[57,187,189,191,194,196,198],{"class":59,"line":188},11,[57,190,83],{"class":63},[57,192,193],{"class":86},"database",[57,195,64],{"class":63},[57,197,92],{"class":63},[57,199,95],{"class":63},[57,201,203,205,207,209,211],{"class":59,"line":202},12,[57,204,101],{"class":63},[57,206,105],{"class":104},[57,208,64],{"class":63},[57,210,92],{"class":63},[57,212,213],{"class":112}," 8082\n",[57,215,217],{"class":59,"line":216},13,[57,218,119],{"class":63},[57,220,222,224,227,229,231],{"class":59,"line":221},14,[57,223,83],{"class":63},[57,225,226],{"class":86},"ui",[57,228,64],{"class":63},[57,230,92],{"class":63},[57,232,95],{"class":63},[57,234,236,238,241,243,245],{"class":59,"line":235},15,[57,237,101],{"class":63},[57,239,240],{"class":104},"enabled",[57,242,64],{"class":63},[57,244,92],{"class":63},[57,246,247],{"class":63}," true\n",[57,249,251],{"class":59,"line":250},16,[57,252,253],{"class":63},"    }\n",[57,255,257,260],{"class":59,"line":256},17,[57,258,259],{"class":63},"}",[57,261,262],{"class":73},",\n",[13,264,265],{},"とりあえず8081と8082でどんなプロセスが動いているか確かめましょう。（この時はfirestoreだけでなくdatabaseも取られていました。）",[13,267,268],{},"Macで任意ポートで使用されているプロセスを調べる時は以下の様なコマンドを打ちます。",[17,270,274],{"className":271,"code":272,"language":273,"meta":26,"style":26},"language-bash shiki shiki-themes material-theme-ocean","lsof -i:PORT\n","bash",[24,275,276],{"__ignoreMap":26},[57,277,278,281],{"class":59,"line":60},[57,279,280],{"class":104},"lsof",[57,282,283],{"class":67}," -i:PORT\n",[13,285,286],{},"なので今回は",[17,288,290],{"className":271,"code":289,"language":273,"meta":26,"style":26},"lsof -i:8081\nOMMAND  PID       USER   FD   TYPE            DEVICE SIZE\u002FOFF NODE NAME\njava    2322       jun   93u  IPv6 0x3f5922436f29fd5      0t0  TCP localhost:sunproxyadmin (LISTEN)\n",[24,291,292,299,328],{"__ignoreMap":26},[57,293,294,296],{"class":59,"line":60},[57,295,280],{"class":104},[57,297,298],{"class":67}," -i:8081\n",[57,300,301,304,307,310,313,316,319,322,325],{"class":59,"line":80},[57,302,303],{"class":104},"OMMAND",[57,305,306],{"class":67},"  PID",[57,308,309],{"class":67},"       USER",[57,311,312],{"class":67},"   FD",[57,314,315],{"class":67},"   TYPE",[57,317,318],{"class":67},"            DEVICE",[57,320,321],{"class":67}," SIZE\u002FOFF",[57,323,324],{"class":67}," NODE",[57,326,327],{"class":67}," NAME\n",[57,329,330,333,336,339,342,345,348,351,354,357],{"class":59,"line":98},[57,331,332],{"class":104},"java",[57,334,335],{"class":112},"    2322",[57,337,338],{"class":67},"       jun",[57,340,341],{"class":67},"   93u",[57,343,344],{"class":67},"  IPv6",[57,346,347],{"class":112}," 0x3f5922436f29fd5",[57,349,350],{"class":67},"      0t0",[57,352,353],{"class":67},"  TCP",[57,355,356],{"class":67}," localhost:sunproxyadmin",[57,358,359],{"class":73}," (LISTEN)\n",[13,361,362],{},"なんだろう？とりえずエミュレーター系だと思う。なのでこのプロセスをkillする",[17,364,366],{"className":271,"code":365,"language":273,"meta":26,"style":26},"kill 2322\n",[24,367,368],{"__ignoreMap":26},[57,369,370,374],{"class":59,"line":60},[57,371,373],{"class":372},"sdLwU","kill",[57,375,376],{"class":112}," 2322\n",[13,378,379],{},"そしてもう一度起動",[17,381,384],{"className":382,"code":383,"language":22},[20],"firebase emulators:start\n┌─────────────────────────────────────────────────────────────┐\n│ ✔  All emulators ready! It is now safe to connect your app. │\n│ i  View Emulator UI at http:\u002F\u002Flocalhost:4002                │\n└─────────────────────────────────────────────────────────────┘\n",[24,385,383],{"__ignoreMap":26},[13,387,388],{},"OK!これで起動完了。以上がFirestore、Databaseでポートが取られて起動できない時の対処法です。Authとかは起きないのに、なぜかfirestoreとDatabaseだけ発生します。。",[390,391,392],"style",{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}",{"title":26,"searchDepth":98,"depth":98,"links":394},[],[396],"ministack","2021-05-31","ポートが使用されていてFirebaseエミュレータが起動的無い時の対処法","md",null,{},true,"\u002Farticles\u002Ffirebase-emulator-port-token",{"title":8,"description":398},"articles\u002Ffirebase-emulator-port-token",[407,408],"infrastructure","firebase","WZCWojY2PQc1aA0-QDiGPzMKO6InsuB6Hbr1wZtO8aI",{"id":411,"title":412,"body":413,"category":755,"createdAt":397,"description":757,"extension":399,"index":400,"meta":758,"navigation":402,"path":759,"publish":402,"seo":760,"series":400,"seriesTitle":400,"stem":761,"tag":762,"thumbnail":763,"updatedAt":400,"__hash__":764},"articles\u002Farticles\u002Fstupid-ie-implement.md","IE終了まであと１年！IE対応に関していろいろ思うこと。これまでの経験を踏まえて適当に語る",{"type":10,"value":414,"toc":731},[415,426,434,437,440,462,465,468,471,483,492,495,498,501,504,512,515,524,527,530,534,537,542,545,549,556,567,570,574,589,592,595,598,601,604,607,610,613,616,619,622,626,634,637,640,644,647,651,654,657,660,663,666,670,673,676,680,689,692,695,698,701,704,707,710,719,723],[13,416,417,418,425],{},"こんにちはjunです。2021年5月19日にとても嬉しいことがおきました。Twitterを見るとなぜかトレンドに「IE終了」という文字がありました。詳しくみてみるとなんと",[419,420,424],"a",{"href":421,"rel":422},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=0Q_1Lfgpb5s",[423],"nofollow","マイクロソフトが2022年6月にIEのサポートを終了する","という旨のyoutube動画をUPしたのです。夢でなかったのです。ついに苦しいIE対応をしなくてい良いという大義名分が来年から得られることになったのです。あまりにも嬉しくて速攻で会社のslackに共有したほどでした。",[13,427,428,429],{},"サムネイルの写真もTwitterでバズって回ってきたものでとても面白かったです。ちなみに",[419,430,433],{"href":431,"rel":432},"https:\u002F\u002Ftwitter.com\u002Ftowernter\u002Fstatus\u002F1396443278657064962",[423],"オリジナルはこのツイートです。",[13,435,436],{},"さてこの記事は技術記事じゃありません。ただIEサポート終了がうれしすぎて何か記事にできないかと思い、わざわざ新しいカテゴリーを作成しました。この「小言」は技術とは関係ないIT系に関する私の意見とかを述べるカテゴリーにします。あまり使わないかもしれませんが。まあQiitaでいうポエムみたいなものだと思ってください。",[13,438,439],{},"今回の記事では題目にある通り後１年のIEサポート終了が迫る中、我々webエンジニアがどうIEに向き合うべきかを私なりに意見を述べようと思います。順序的に",[441,442,443,447,450,453,456,459],"ul",{},[444,445,446],"li",{},"これからの流れと情報の整理",[444,448,449],{},"IE対応の背景、なぜ対応が必要なのか",[444,451,452],{},"なぜ対応が嫌なのか",[444,454,455],{},"IE対応肯定派の意見について",[444,457,458],{},"その反論（小言）",[444,460,461],{},"最終まとめ",[13,463,464],{},"みたいな感じで行こうと思います。それでは早速いきましょう。",[466,467,446],"h2",{"id":446},[13,469,470],{},"エンジニアたるもの必ず情報源を確認しましょう。まず上記のあった通りのマイクロソフトのサポート終了は日本時間2022年6月16日にセキュリティサポートを終了するとのことです。それ以降はどんなバグ。脆弱性が起きてもサポートをしないとのことです。",[13,472,473,474,477,478,482],{},"公式youtubeはこちら（",[419,475,421],{"href":421,"rel":476},[423],"）で、概要的なニュースはこちら（",[419,479,480],{"href":480,"rel":481},"https:\u002F\u002Fwww.atmarkit.co.jp\u002Fait\u002Farticles\u002F1503\u002F11\u002Fnews134.html",[423],"）を参考にするといいです。",[13,484,485,486,491],{},"ただしこのサポート終了はLTSB\u002FLTSC以外のwindows10におけるInternet Explore11であり、",[419,487,490],{"href":488,"rel":489},"https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fwindows\u002Fwhats-new\u002Fltsc\u002F",[423],"Windows 10 LTSC(Long-Term Servicing Chanel)","とWindows 10 Serverという製品に含まれるIE11は対象でありません。これらの製品のIEはなんと最長のもので2029年までサポートされています。",[13,493,494],{},"えっ！じゃあ苦労は2029年まで続くの！！？と心臓が止まりそうになったかもしれませんが、このLTSB\u002FLTSCというのはエンタープライズつまり企業向けの長期サポートのwindows10です。導入には10万以上かかる法人向けのwindowsです。つまりウェブサイトをみるだけ、インターネットを使うだけにPCを買っている一般の方々がインストールしているwindowsではありません。",[13,496,497],{},"そのためまとめますと、「LTSB\u002FLTSC以外の、一般の方々がインストールしているwindows」のIEサポートが2022年6月16日（日本時間）に終了するということです。OK?よかったですねー。",[466,499,449],{"id":500},"ie対応の背景なぜ対応が必要なのか",[13,502,503],{},"いくつかの原因はありますが理由として",[441,505,506,509],{},[444,507,508],{},"社内で使用しているシステムがIEしか対応していないので、既定ブラウザになっている。",[444,510,511],{},"日本ではなぜか10%ぐらいのシェアがあり、使用しているユーザーもそこそこいる",[13,513,514],{},"があります。特に前者のシステムがIEにしか対応していないというのはかなり大きく、マイクロソフトが長年IE11を残したり、Edgeの中にもIEモードを含ませていたり、windows10に入れている原因です。流石に強制的にIE11を消して社内システムが使えなくなった！なんてなったら流石にやばいです。",[13,516,517,518,523],{},"2020年の夏あたりに",[419,519,522],{"href":520,"rel":521},"https:\u002F\u002Fwww.nikkei.com\u002Farticle\u002FDGXMZO61372400Q0A710C2EE8000\u002F",[423],"「マイナポータルがIEでしか使用できない」","という事件がありましたが、それもその一つです。PCにてマイナンバーカードの情報を物理的に読み取って使用するにはJavaかIEのAvtiveXしかない。という縛りがあり基本的にマイナポータルはスマホからのアクセスを前提にして、PCブラウザでの対応を後回しにしたという感じです。これも技術的な制約上、IEを使用せざる得ない理由の一つです。",[13,525,526],{},"後者はちょっと自分でなんとかしてくれよと感じがしますが、正直ユーザーからしてみれば「ブラウザってなんすか？」というものぐらいです笑。なんか昔からインターネットみる時はこのアイコンだったしぐらいしか知らない人も多く、日本では前者の理由も加わってIEを使用している個人や会社がいます。私の取引先も私が指摘するまでIEを使用していました。（とくに役所や学校が多いです）",[13,528,529],{},"「それでもさぁ！」と言いたいお気持ちはわかります。まずはなぜ必要となっているのかの背景は以上の通りです。",[466,531,533],{"id":532},"なぜ対応が嫌なのかすべきでないのか","なぜ対応が嫌なのか、すべきでないのか",[13,535,536],{},"そんな事情がありながらも、我々webディベロッパーはなぜIE対応をしたくないのでしょうか。技術的・工数的な視点で述べていきます。",[538,539,541],"h3",{"id":540},"cssが崩れる","CSSが崩れる",[13,543,544],{},"まずはこれですね。web上でのデザインを表現するためにCSSを使用しますが、「chrome、safariではきちんと表示されているのに、IEだけ違う！」という事件がおきます。理由としてはIEがそのCSSプロパティをサポートしていない、または特定の記述が必要だからです。他にもIEにしかないCSSのバグもあり、対処のためにトリッキーな書き方をする必要があります。",[538,546,548],{"id":547},"es6が使えない","ES6が使えない",[13,550,551,552,555],{},"ES6はjavascriptだと思ってください。javascriptにはES5,ES6といったものがあります。ES6の方が新しくスマートでより良い記述が可能です。例えばES5では変数宣言に",[24,553,554],{},"var","しか使用できず、定数などは大文字にして表記すると言った運用面でカバーしたり、クラスの書き方も関数を使用してとっつきにくい感じでした。",[13,557,558,559,562,563,566],{},"しかしES6では",[24,560,561],{},"const","での定数宣言、",[24,564,565],{},"class{}","を用いた表現ができる様になりました。それがそのまま使えればいいのですが、はい。。IEです。なんとIEではES6がサポートされておらず構文エラーを起こしてスクリプトが停止します。そのため渋々ES5の書き方をするということもありますが、Vue,Reactなどのjsフレームワーク、その他のnpmパッケージなどで取得できるライブラリでES5の書き方をしていないものがあります。というよりES6の方が開発がスムーズにいきます。",[13,568,569],{},"そのため開発を効率的に行うjsライブラリを使用できなかったり、IE確認で時間を取られます。（そのくせIEのデバッガーは遅くてよく止まります。）",[538,571,573],{"id":572},"javascriptファイルが重くなるビルドの負担が大きい","javascriptファイルが重くなる・ビルドの負担が大きい",[13,575,576,577,582,583,588],{},"ES6が使えないので最新のjsは諦めて、旧石器時代のjsの構成でやる...?というのは酷です。そのためどこかの神様が",[419,578,581],{"href":579,"rel":580},"https:\u002F\u002Fbabeljs.io\u002F",[423],"Babel","、",[419,584,587],{"href":585,"rel":586},"https:\u002F\u002Fpolyfill.io\u002Fv3\u002F",[423],"Polyfill","というES6とES5の互換性を作ってくれるライブラリを作り出しました。BabelはES6の書き方をES5に変えてくれ、Polyfillはjsの言語特徴を利用してES6の機能をES5に追加します。",[13,590,591],{},"それであれば万事解決...とはなりません。とてもありがたいのですが弱点としてjsのバンドルサイズが重くなったり、babelによる書き換えのためにnode.jsの環境が必要となったりとまだまだ開発に難があります。中にはBabel、Polyfillを使用しても解決できずIEだけ動かないというものも少しあります。Babel、Polyfillも万能ではありません。",[13,593,594],{},"これらの措置はIEのためだけに行うものであり、他のモダンブラウザであれば不要なことです。同じjsファイルでも上記の処理をしなければ100kbも軽くなったりすることもあります。",[538,596,597],{"id":597},"開発工数がかかる",[13,599,600],{},"上記の様にIEだけの特別なチェックや開発、ファイルが必要となったりで開発側の負担が大きいです。負担が大きいということは工数もそれだけ伸びるので費用もかさみます。開発者だけが苦労するだけでなく、工数も伸びてしまい費用がかさみ、その割には利用者は全体の数％とあまり得でない気もします。",[538,602,603],{"id":603},"公式が使うなと言っている",[13,605,606],{},"開発したマイクロソフト自身が「IEは技術的負債」とまでいい、段階的にEdge（IEの後継、いわゆるモダンブラウザ）に移行する様にユーザーに呼びかけています。つまり公式自ら「IEは危ないから使わないで！」としているのに使うのはいかがなものかとあります。",[538,608,609],{"id":609},"セキュリティーリスクがある",[13,611,612],{},"IEは現在セキュリティーサポートのみでアップデートはされていません。もしIEに脆弱性が発生したら（しかたなく）マイクロソフトが対応して、セキュリティパッチを出すと言った感じです。その際に危惧されるのが「ゼロデイ攻撃」と呼ばれるものです。「ゼロデイ攻撃」はそのマイクロソフトのパッチが適用される前、脆弱性情報が発表される前に素早く対象のマシンを攻撃するものです。セキュリティパッチが効く前を狙った攻撃です。",[13,614,615],{},"開発元がしっかりしていればいいのですが、2020年1月のIEにおける脆弱性対応にパッチ提供に３週間かかったりなどもうサポートのやる気なさを感じさせるぐらいになっている。セキュリティバグも多いのでIEに限定した攻撃というものも存在している。わざわざそんな危険なブラウザ使う人も少なくなるだろうし、というかIEが原因でwebサイトの情報が漏れるなんでまっぴらゴメンです。ならばIEでは使えない様にした方がいいというものも納得できます。",[466,617,455],{"id":618},"ie対応肯定派の意見について",[13,620,621],{},"このようにIEにはデメリットが多いですが、「IEに対応すべきだ！」という恐ろしい方々が存在します。「これだから、IT知らない人は..」「開発者の気持ちも考えずにまったっく..」と言いたいですが彼らの意見には、耳を傾かせるべき内容もありました。",[538,623,625],{"id":624},"シェア数としても母数が多ければそれなりにいる","シェア数％としても母数が多ければそれなりにいる",[13,627,628,633],{},[419,629,632],{"href":630,"rel":631},"https:\u002F\u002Fyaruzou.net\u002Fbrowser-share",[423],"IEのシェアは日本では7.4%、世界は1.94%と言われています。"," インターネット利用者は統計的に世界でPCで40億人、日本では１億800万人はいるそうです。もちろん全員がwindows,PC,ブラウザを使用しているとは言えませんが単純にパーセンテージをかけてもIE使用者は最大約、日本で80万、世界では7700万となります。まあ無茶な計算なのでもう少し低いでしょうけど。",[13,635,636],{},"だたし1万人はIEを利用していそうです。どうでしょうか？この数はあなたの顧客となりうる数ですがを捨てられますか？といわれると「うーん」となります。",[13,638,639],{},"ただしビジネスであればこんなチンケな数より、メジャーブラウザを利用している人をターゲットにした方が工数と利益率が最適になりそうです。ただしビジネス意外にもwebサイトは使用されます。次の理由につながります。",[538,641,643],{"id":642},"情報の公共性を担保すべきだ","情報の公共性を担保すべきだ！",[13,645,646],{},"では仮に1万人のユーザーは少なくともIEを使用して様々なサイトを見ているとしましょう。webサイトにはビジネスでなく公共的なもの、例えば政府広報や自治体のサイトなどがあります。その場合デジタルデバイドなく、全ての人に情報が渡るべきサイトの場合は対応について少し考える必要がありそうです。",[538,648,650],{"id":649},"開発が面倒ってそれあなた開発者の感想ですよね","開発が面倒って、それあなた（開発者）の感想ですよね",[13,652,653],{},"あー。はい。確かに技術選定をしてライブラリとか適切に入れて、チェックしたりIEだけ専用のファイルを読み込んだり、Babel、Polyfillを使用すればなんとかなります。開発工数は伸びるけど確かに「できなくはないです」。逆に開発者がだだこねて「最新の技術じゃないと嫌だ！」「お金があってもIE対応は嫌！」となっては顧客的には納得いきません。顧客にとってみれば動いていればいいんですから、機能が豊富なwebアプリならともかくwebサイト作成ぐらいならやれば？という意見もあります。",[466,655,458],{"id":656},"その反論小言",[13,658,659],{},"対応すべき派はこんな感じです。他にあったら追加しようと思います。ではその反論や私の意見を述べようと思います。",[538,661,662],{"id":662},"セキュリティリスクのあるものは使うべきでない",[13,664,665],{},"一番はこれです。いきなりジョーカーみたいなものですが、セキュリティリスクのあるブラウザ（アプリ）を使用するのはいかがなものかと思います。ましてやIEが原因なのに情報漏洩をwebサイトのせいにされたらたまったものじゃありません。Dropbox、Boxといったクラウドストレージサービス、AWSやGCPなどは特に嫌がると思います。（Dropboxは2020年10月からIEサポートしていません）というよりもログインを必要とする様な機密情報を扱うサイトでは使用させるべきでないです。webアプリ系のサイトはIEを使用させなくても良いと思います。",[538,667,669],{"id":668},"マイクロソフトがieを残しているのは企業のため","マイクロソフトがIEを残しているのは企業のため",[13,671,672],{},"window8を強制的にwindows10にアップデートした過去がある伝説のマイクロソフトさんですが、この様なことをするならIEがいつの間にか削除されていてもおかしくないと思います。でもIEは明示的にLTSB\u002FLTSCに残したり、EdgeにもIEモードたる物を残しています。それはなぜか？",[13,674,675],{},"理由としては対応理由の一つであった「IEしか対応していないシステムがある」からです。つまり企業のシステムが対応できるまで一応、IEを利用できる様にしておこうという措置です。webサイトを見るブラウザの一つでなく、IEでしか動かないものを動かすための救済ブラウザなのです。企業のシステムはお金や大人の事情ですぐには代替できません。一方ユーザーのwebサイト閲覧はどうですか？最悪、Edgeのアイコンおすだけですよ？？IEを残しているのは企業や特定環境のシステム利用のためであり、一般的なユーザーのサイト閲覧アプリとしての利用は正直想定していないと思います。",[538,677,679],{"id":678},"公共性と言っても横浜市のコロナの予約サイトはie非対応だったよ","公共性と言っても横浜市のコロナの予約サイトはIE非対応だったよ",[13,681,682,683,688],{},"これ結構大きいかなと思います。",[419,684,687],{"href":685,"rel":686},"https:\u002F\u002Fv-yoyaku.jp\u002F141003-yokohama",[423],"横浜市のコロナワクチン予約サイト","はマニュアルのPDFにも書かれている通り、IE対応されていません。公共性の高いコンテンツはIE対応すべきとの意見は確かにありますが、コロナという、ましてや最初の利用者が高齢者・中年（IEの利用率が高め）に対してIE非対応としたのは大きな判断だったと思います。",[13,690,691],{},"真意は開発者と受注者しかわかりませんが、多分開発の速度的な問題だったと思います。前述の通りIEの場合はIEでのチェックが必要となり、工数が増え結果的に時間がかかります。さらにIEだけ動作不良がある、予約できないとなったらかなりバッシングされそうです。であれば早く予約システムを簡単でも作成して、動作が保証されるものを開発するならばIEを対応しないのは妥当だと思います。",[13,693,694],{},"ブラウザの「ブ」の字もわからない人は怒ったり、マニュアル読んでもよくわからないかもしれませんが、IE利用者のシェアと開発スピード・質を天秤にかけたら仕方なかったんじゃないかと思います。",[13,696,697],{},"私はこの対応判断は素晴らしいと思います。日本のIE非対応化の大義名分の一つになり得そうです。",[538,699,700],{"id":700},"必要なのは啓蒙である",[13,702,703],{},"IE使うな問題は過去の古いものから新しいものへの移行です。これはソフトウェアだけの問題でなく、他にもあると思います。今まで使いやすかった方法や物をやめて、今すぐ新しいものにしてください！という経験はありませんか？物だけでなく、考え方とかもそうです。「昔はOKだったけど今はだめ」、「これからはこうすべき」というものはあると思います。",[13,705,706],{},"古い物、使い慣れた物を使いづけたい気持ちはわかります。学習コストもあれば愛着もあるかもしれません。しかしIEはそれらを凌駕するほどのデメリットと、切り替えたことによるメリットを知らないからIEを使用するユーザーがいるのだと思います。",[13,708,709],{},"先述の横浜市コロナワクチン予約サイトの様に、マニュアルで明記したりして明示的にIEを使わない様に教えることもできます。なんらかのきっかけがあれば、きっとユーザーは乗り換えてくれると思います。特にセキュリティリスクなどデメリット部分をしれば、IEを止めるきっかけになると思います。",[13,711,712,713,718],{},"一応マイクロソフトには",[419,714,717],{"href":715,"rel":716},"https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fmicrosoft-edge\u002Fweb-platform\u002Fie-to-microsoft-edge-redirection",[423],"自サイトをIEで開くとEdgeで開く様に転送する","様にサイトを登録してくれる機能を提供しています。申請を行い、XMLを書く必要がありますがEdgeで開く様にしたり、サイトに「IE非対応です」と書いて啓蒙するのが一番いいと思います。",[466,720,722],{"id":721},"まとめ結局ieは対応す","まとめ。結局IEは対応す..",[13,724,725,726,730],{},"まとめますと、これから作られるwebサイト・アプリはIE対応しなくていいと思います。対応しても一般ユーザー的にも2022年6月ですし、その期間のために工数を増やしても意味ない気がします。最近、チェックでIEを開くとEdge移行用固定ヘッダーがでてきたりしてすごいことになっています。結局はユーザーに対する啓蒙が一番だと思います。今後webディベロッパーが行うべきはことは、 ",[727,728,729],"strong",{},"IE対応はしなくてもいいが、IEで見ちゃった人のためにEdgeに転送したりIEで見れないことをサイトで伝えること。そしてできたらブラウザ移行に関する情報を提供すること"," だと思います。以上！めちゃ長くなりましたが記事はこれでおしまいです。なにか意見あればぜひコメントください。それでは。",{"title":26,"searchDepth":98,"depth":98,"links":732},[733,734,735,743,748,754],{"id":446,"depth":80,"text":446},{"id":500,"depth":80,"text":449},{"id":532,"depth":80,"text":533,"children":736},[737,738,739,740,741,742],{"id":540,"depth":98,"text":541},{"id":547,"depth":98,"text":548},{"id":572,"depth":98,"text":573},{"id":597,"depth":98,"text":597},{"id":603,"depth":98,"text":603},{"id":609,"depth":98,"text":609},{"id":618,"depth":80,"text":455,"children":744},[745,746,747],{"id":624,"depth":98,"text":625},{"id":642,"depth":98,"text":643},{"id":649,"depth":98,"text":650},{"id":656,"depth":80,"text":458,"children":749},[750,751,752,753],{"id":662,"depth":98,"text":662},{"id":668,"depth":98,"text":669},{"id":678,"depth":98,"text":679},{"id":700,"depth":98,"text":700},{"id":721,"depth":80,"text":722},[756],"myopinion","IE終了まであと１年！IE対応に関していろいろ思うこと",{},"\u002Farticles\u002Fstupid-ie-implement",{"title":412,"description":757},"articles\u002Fstupid-ie-implement",[],"_mix\u002Fgoobye-ie.jpg","JPkFztuijEVQoNoDuLW-xoIv397ZP1pLMXyJLr-Dyz8",{"id":766,"title":767,"body":768,"category":1331,"createdAt":1332,"description":1333,"extension":399,"index":400,"meta":1334,"navigation":402,"path":1335,"publish":402,"seo":1336,"series":400,"seriesTitle":400,"stem":1337,"tag":1338,"thumbnail":1341,"updatedAt":400,"__hash__":1342},"articles\u002Farticles\u002Fbag-html-break-tag.md","white-space： pre;で要素内で生じる文章の隙間、インテンドの原因。",{"type":10,"value":769,"toc":1329},[770,777,783,786,984,998,1012,1019,1145,1151,1154,1157,1320,1326],[13,771,772,773,776],{},"こんにちはjunです。ある日、vue.jsを使用して",[24,774,775],{},"textarea","の中の文章をリアルタイムにレンダーするような機能を実装しました。その時、改行文章に謎のインテンドが入り、困りました。こんな感じです。",[778,779],"image-render",{":src":780,":width":781,":center":782},"'_mix\u002Fsch-2021-05-04 21.53.14.png'","'300px'","true",[13,784,785],{},"ソースは以下の感じ（かなり簡略化してます。）",[17,787,791],{"className":788,"code":789,"language":790,"meta":26,"style":26},"language-vue shiki shiki-themes material-theme-ocean","\u003Ctemplate>\n\u003Cdiv>\n    \u003Cdiv style=\"white-space:pre;\">\n        {{test}}\n        \u003Ctextarea v-model=\"test\"cols=\"30\" rows=\"10\">\u003C\u002Ftextarea>\n    \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript>\nexport default {\n    data(){\n        return{\n            test:''\n        }\n    }\n}\n\u003C\u002Fscript>\n","vue",[24,792,793,805,814,836,841,891,900,909,917,926,937,945,952,962,967,971,976],{"__ignoreMap":26},[57,794,795,798,802],{"class":59,"line":60},[57,796,797],{"class":63},"\u003C",[57,799,801],{"class":800},"s-wAU","template",[57,803,804],{"class":63},">\n",[57,806,807,809,812],{"class":59,"line":80},[57,808,797],{"class":63},[57,810,811],{"class":800},"div",[57,813,804],{"class":63},[57,815,816,819,821,824,827,829,832,834],{"class":59,"line":98},[57,817,818],{"class":63},"    \u003C",[57,820,811],{"class":800},[57,822,823],{"class":86}," style",[57,825,826],{"class":63},"=",[57,828,64],{"class":63},[57,830,831],{"class":67},"white-space:pre;",[57,833,64],{"class":63},[57,835,804],{"class":63},[57,837,838],{"class":59,"line":116},[57,839,840],{"class":73},"        {{test}}\n",[57,842,843,846,848,851,853,855,858,860,863,865,867,870,872,875,877,879,882,884,887,889],{"class":59,"line":122},[57,844,845],{"class":63},"        \u003C",[57,847,775],{"class":800},[57,849,850],{"class":86}," v-model",[57,852,826],{"class":63},[57,854,64],{"class":63},[57,856,857],{"class":67},"test",[57,859,64],{"class":63},[57,861,862],{"class":86},"cols",[57,864,826],{"class":63},[57,866,64],{"class":63},[57,868,869],{"class":67},"30",[57,871,64],{"class":63},[57,873,874],{"class":86}," rows",[57,876,826],{"class":63},[57,878,64],{"class":63},[57,880,881],{"class":67},"10",[57,883,64],{"class":63},[57,885,886],{"class":63},">\u003C\u002F",[57,888,775],{"class":800},[57,890,804],{"class":63},[57,892,893,896,898],{"class":59,"line":136},[57,894,895],{"class":63},"    \u003C\u002F",[57,897,811],{"class":800},[57,899,804],{"class":63},[57,901,902,905,907],{"class":59,"line":150},[57,903,904],{"class":63},"\u003C\u002F",[57,906,811],{"class":800},[57,908,804],{"class":63},[57,910,911,913,915],{"class":59,"line":155},[57,912,904],{"class":63},[57,914,801],{"class":800},[57,916,804],{"class":63},[57,918,919,921,924],{"class":59,"line":169},[57,920,797],{"class":63},[57,922,923],{"class":800},"script",[57,925,804],{"class":63},[57,927,928,932,935],{"class":59,"line":183},[57,929,931],{"class":930},"s6cf3","export",[57,933,934],{"class":930}," default",[57,936,95],{"class":63},[57,938,939,942],{"class":59,"line":188},[57,940,941],{"class":800},"    data",[57,943,944],{"class":63},"(){\n",[57,946,947,950],{"class":59,"line":202},[57,948,949],{"class":930},"        return",[57,951,77],{"class":63},[57,953,954,957,959],{"class":59,"line":216},[57,955,956],{"class":800},"            test",[57,958,92],{"class":63},[57,960,961],{"class":63},"''\n",[57,963,964],{"class":59,"line":221},[57,965,966],{"class":63},"        }\n",[57,968,969],{"class":59,"line":235},[57,970,253],{"class":63},[57,972,973],{"class":59,"line":250},[57,974,975],{"class":63},"}\n",[57,977,978,980,982],{"class":59,"line":256},[57,979,904],{"class":63},[57,981,923],{"class":800},[57,983,804],{"class":63},[13,985,986,987,990,991,994,995,997],{},"とても簡単で",[24,988,989],{},"v-model","を使用してその",[24,992,993],{},"data()","を表示してあるだけです。そしてCSSでは",[24,996,831],{},"を指定して、改行コードが存在したらそこで改行するようにします。",[811,999,1003,1004],{"className":1000},[1001,1002],"alert","alert-success","\nCSS の white-space プロパティは、要素内のホワイトスペースをどのように扱うかを設定します。\n",[1005,1006,1007],"small",{},[419,1008,1011],{"href":1009,"target":1010},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fja\u002Fdocs\u002FWeb\u002FCSS\u002Fwhite-space","_blank","mozilla.org",[13,1013,1014,1015,1018],{},"ただし上図のように謎のインテンドが最初の改行にあります。なぜ生じしてしまうのかを調べたところ、 ",[727,1016,1017],{},"エディター上でのインテンド"," が原因でした。以下のようにファイルを直してみます。",[17,1020,1022],{"className":788,"code":1021,"language":790,"meta":26,"style":26},"\u003Ctemplate>\n\u003Cdiv>\n    \u003Cdiv style=\"white-space:pre;\">\n{{test}}\n        \u003Ctextarea v-model=\"test\"cols=\"30\" rows=\"10\">\u003C\u002Ftextarea>\n    \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003C\u002Fscript>\n",[24,1023,1024,1032,1040,1058,1063,1105,1113,1121,1129,1134],{"__ignoreMap":26},[57,1025,1026,1028,1030],{"class":59,"line":60},[57,1027,797],{"class":63},[57,1029,801],{"class":800},[57,1031,804],{"class":63},[57,1033,1034,1036,1038],{"class":59,"line":80},[57,1035,797],{"class":63},[57,1037,811],{"class":800},[57,1039,804],{"class":63},[57,1041,1042,1044,1046,1048,1050,1052,1054,1056],{"class":59,"line":98},[57,1043,818],{"class":63},[57,1045,811],{"class":800},[57,1047,823],{"class":86},[57,1049,826],{"class":63},[57,1051,64],{"class":63},[57,1053,831],{"class":67},[57,1055,64],{"class":63},[57,1057,804],{"class":63},[57,1059,1060],{"class":59,"line":116},[57,1061,1062],{"class":73},"{{test}}\n",[57,1064,1065,1067,1069,1071,1073,1075,1077,1079,1081,1083,1085,1087,1089,1091,1093,1095,1097,1099,1101,1103],{"class":59,"line":122},[57,1066,845],{"class":63},[57,1068,775],{"class":800},[57,1070,850],{"class":86},[57,1072,826],{"class":63},[57,1074,64],{"class":63},[57,1076,857],{"class":67},[57,1078,64],{"class":63},[57,1080,862],{"class":86},[57,1082,826],{"class":63},[57,1084,64],{"class":63},[57,1086,869],{"class":67},[57,1088,64],{"class":63},[57,1090,874],{"class":86},[57,1092,826],{"class":63},[57,1094,64],{"class":63},[57,1096,881],{"class":67},[57,1098,64],{"class":63},[57,1100,886],{"class":63},[57,1102,775],{"class":800},[57,1104,804],{"class":63},[57,1106,1107,1109,1111],{"class":59,"line":136},[57,1108,895],{"class":63},[57,1110,811],{"class":800},[57,1112,804],{"class":63},[57,1114,1115,1117,1119],{"class":59,"line":150},[57,1116,904],{"class":63},[57,1118,811],{"class":800},[57,1120,804],{"class":63},[57,1122,1123,1125,1127],{"class":59,"line":155},[57,1124,904],{"class":63},[57,1126,801],{"class":800},[57,1128,804],{"class":63},[57,1130,1131],{"class":59,"line":169},[57,1132,1133],{"emptyLinePlaceholder":402},"\n",[57,1135,1136,1138,1141,1143],{"class":59,"line":183},[57,1137,797],{"class":63},[57,1139,1140],{"class":73},"\u002F",[57,1142,923],{"class":800},[57,1144,804],{"class":63},[13,1146,1147,1150],{},[24,1148,1149],{},"{{text}}","の箇所を一番左につけることでなぜか、改行の際のインテンドがなくなりました。",[778,1152],{":src":1153,":width":781,":center":782},"'_mix\u002Fsch-2021-05-04 22.01.03.png'",[13,1155,1156],{},"またはこのようにタグを改行させず、一行内に書くことでも解決できます。",[17,1158,1160],{"className":788,"code":1159,"language":790,"meta":26,"style":26},"\u003Ctemplate>\n\u003Cdiv>\n    \u003Cdiv style=\"white-space:pre;\">{{test}}\u003C\u002Fdiv>\n    \u003Ctextarea v-model=\"test\"cols=\"30\" rows=\"10\">\u003C\u002Ftextarea>\n\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript>\nexport default {\n    data(){\n        return{\n            test:''\n        }\n    }\n}\n\u003C\u002Fscript>\n",[24,1161,1162,1170,1178,1206,1248,1256,1264,1272,1280,1286,1292,1300,1304,1308,1312],{"__ignoreMap":26},[57,1163,1164,1166,1168],{"class":59,"line":60},[57,1165,797],{"class":63},[57,1167,801],{"class":800},[57,1169,804],{"class":63},[57,1171,1172,1174,1176],{"class":59,"line":80},[57,1173,797],{"class":63},[57,1175,811],{"class":800},[57,1177,804],{"class":63},[57,1179,1180,1182,1184,1186,1188,1190,1192,1194,1197,1200,1202,1204],{"class":59,"line":98},[57,1181,818],{"class":63},[57,1183,811],{"class":800},[57,1185,823],{"class":86},[57,1187,826],{"class":63},[57,1189,64],{"class":63},[57,1191,831],{"class":67},[57,1193,64],{"class":63},[57,1195,1196],{"class":63},">",[57,1198,1199],{"class":73},"{{test}}",[57,1201,904],{"class":63},[57,1203,811],{"class":800},[57,1205,804],{"class":63},[57,1207,1208,1210,1212,1214,1216,1218,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246],{"class":59,"line":116},[57,1209,818],{"class":63},[57,1211,775],{"class":800},[57,1213,850],{"class":86},[57,1215,826],{"class":63},[57,1217,64],{"class":63},[57,1219,857],{"class":67},[57,1221,64],{"class":63},[57,1223,862],{"class":86},[57,1225,826],{"class":63},[57,1227,64],{"class":63},[57,1229,869],{"class":67},[57,1231,64],{"class":63},[57,1233,874],{"class":86},[57,1235,826],{"class":63},[57,1237,64],{"class":63},[57,1239,881],{"class":67},[57,1241,64],{"class":63},[57,1243,886],{"class":63},[57,1245,775],{"class":800},[57,1247,804],{"class":63},[57,1249,1250,1252,1254],{"class":59,"line":122},[57,1251,904],{"class":63},[57,1253,811],{"class":800},[57,1255,804],{"class":63},[57,1257,1258,1260,1262],{"class":59,"line":136},[57,1259,904],{"class":63},[57,1261,801],{"class":800},[57,1263,804],{"class":63},[57,1265,1266,1268,1270],{"class":59,"line":150},[57,1267,797],{"class":63},[57,1269,923],{"class":800},[57,1271,804],{"class":63},[57,1273,1274,1276,1278],{"class":59,"line":155},[57,1275,931],{"class":930},[57,1277,934],{"class":930},[57,1279,95],{"class":63},[57,1281,1282,1284],{"class":59,"line":169},[57,1283,941],{"class":800},[57,1285,944],{"class":63},[57,1287,1288,1290],{"class":59,"line":183},[57,1289,949],{"class":930},[57,1291,77],{"class":63},[57,1293,1294,1296,1298],{"class":59,"line":188},[57,1295,956],{"class":800},[57,1297,92],{"class":63},[57,1299,961],{"class":63},[57,1301,1302],{"class":59,"line":202},[57,1303,966],{"class":63},[57,1305,1306],{"class":59,"line":216},[57,1307,253],{"class":63},[57,1309,1310],{"class":59,"line":221},[57,1311,975],{"class":63},[57,1313,1314,1316,1318],{"class":59,"line":235},[57,1315,904],{"class":63},[57,1317,923],{"class":800},[57,1319,804],{"class":63},[13,1321,1322,1323,1325],{},"なぜ",[24,1324,831],{},"で謎インテンドが生じてしまうのかわかりませんが、改行コードが入る箇所をエディターで整形する際は気をつけたほうがいいかもしれません。",[390,1327,1328],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":26,"searchDepth":98,"depth":98,"links":1330},[],[396],"2021-05-24","white-space： pre;で要素内で生じる、文章の隙間、インテンドの原因。",{},"\u002Farticles\u002Fbag-html-break-tag",{"title":767,"description":1333},"articles\u002Fbag-html-break-tag",[1339,1340,790],"html","css","_mix\u002Fsch-2021-05-04 21.53.14.png","Uglb2nJDSJTaiJVJS_4tGrhobJMBKtzio_BSkBLsen0",{"id":1344,"title":1345,"body":1346,"category":3032,"createdAt":3034,"description":3035,"extension":399,"index":400,"meta":3036,"navigation":402,"path":3037,"publish":402,"seo":3038,"series":400,"seriesTitle":400,"stem":3039,"tag":3040,"thumbnail":3044,"updatedAt":400,"__hash__":3045},"articles\u002Farticles\u002Fimplement-recaptcha.md","reCAPTCHAのフロントエンド実装とバックエンド実装（PHP・Laravel）をスクラッチで行う方法",{"type":10,"value":1347,"toc":3018},[1348,1351,1354,1357,1360,1364,1367,1370,1373,1376,1380,1389,1393,1400,1403,1406,1417,1420,1423,1602,1605,1616,1625,1629,1632,1870,1884,1887,1890,2384,2395,2417,2420,2423,2426,2429,2440,2443,2641,2652,2677,2694,2697,2736,2747,2750,2753,2798,2805,2808,2816,2841,2849,2858,2866,2872,2880,2883,3003,3006,3009,3012,3015],[13,1349,1350],{},"こんにちはjunです。皆さんはフォームを作成する時にBot・スパム対策を行っていますか？フォームというのは少し知識があれば、簡単にbot的に送信することができます。",[13,1352,1353],{},"curlでPOSTすることもあれば、javascriptを実行して機械的にフォームを送信することがきるので、スパム（嫌がらせ）やサーバーへの過剰負荷の原因となります。",[13,1355,1356],{},"この様な機械的な操作を防ぐために、よく「ロボットではありません」「画像に表示されている文字を入力してください」みたいなbotでは簡単に処理できないものを用意します。",[13,1358,1359],{},"しかしこの様な機能は自分で実装するのは大変です。そんな時に便利なのがreCAPTCHAです。",[466,1361,1363],{"id":1362},"recaptchaとは","reCAPTCHAとは？",[13,1365,1366],{},"reCAPTCHAはGoogleが無料で提供しているBot対策ツールです。現在v3までリリースされており、v2は画像を選択させたりチェックを入れるといったユーザーのアクションでbotかどうかを検証します。v3はその様なアクションを必要とせず、必要なスクリプトを入れるだけで検証ができます。これからの実装の場合はv3を入れることをお勧めします。",[466,1368,1369],{"id":1369},"実装内容",[13,1371,1372],{},"今回の解説ではv3でのフロントエンドの実装とバックエンドの実装を解説していきます。そして利用するreCAPTCHAはエンタープライズではなく、無料のものを利用します。バックエンドにはLaravel6(php)を用いて説明します。バックエンドは基本的にやることはどの言語・フレームワークでも特に差異はありません。reCAPTCHAを使う時にライブラリを使用することもありますが、いうてそれほど難しくないので今回はライブラリを使用しないスクラッチで実装します。",[13,1374,1375],{},"それでは解説を始めます。",[466,1377,1379],{"id":1378},"recaptchaのキーを手に入れる","reCAPTCHAのキーを手に入れる",[13,1381,1382,1383,1388],{},"reCAPTCHAはGooglenのAPIを使用することでbotか検証を行います。reCAPTCHAを利用するためにGoogleアカウントとreCAPTCHAのキーを登録します。",[419,1384,1387],{"href":1385,"rel":1386},"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fadmin\u002Fcreate",[423],"reCAPTCHAの登録ページ","にて保護対象のドメインを設定します。",[778,1390],{":src":1391,":width":1392},"'_mix\u002Fsch-2021-05-21 23.16.06.png'","'100%'",[13,1394,1395,1396,1399],{},"ラベルは管理用の名前、タイプはv3を使用します。保護対象のドメインを登録します。ドメインは複数設定できます。この時、本番のドメインと",[24,1397,1398],{},"localhost","を登録しておくと開発・本番で利用できます。",[13,1401,1402],{},"設定を終わって「保存」しますと、キーが表示されますので保存しておきます。",[778,1404],{":src":1405,":width":1392},"'_mix\u002Frecaptcha-key.jpeg'",[13,1407,1408,1412,1413,1416],{},[1409,1410,1411],"em",{},"このサイトキーは、ユーザー表示するHTMLコードで利用します。"," という方のキーはタグで利用し、正直みられても問題ありません。フロント側のキーはドメインと合わせて保護対処のサイトであるかのチェックのためにあるだけだからです。逆にバックの方である ",[1409,1414,1415],{},"このサイトキーは、サイトとreCAPTCHA間の通信で利用します。"," は漏れてはいけません。",[466,1418,1419],{"id":1419},"フロントエンドの実装",[13,1421,1422],{},"それではフロントエンドを実装していきます。かなり簡略化して書いています。以下の様なフォームがあったとしましょう。",[17,1424,1427],{"className":1425,"code":1426,"language":1339,"meta":26,"style":26},"language-html shiki shiki-themes material-theme-ocean","\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n      \u003C!---省略-->\n    \u003C\u002Fhead>\n\n    \u003Cbody>\n        \u003Cform method=\"post\">\n            \u003Cinput type=\"text\" name=\"test\" value=\"\">\n            \u003Cinput type=\"submit\" value=\"送信\">\n        \u003C\u002Fform>\n    \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[24,1428,1429,1442,1450,1459,1465,1473,1477,1486,1507,1547,1577,1586,1594],{"__ignoreMap":26},[57,1430,1431,1434,1437,1440],{"class":59,"line":60},[57,1432,1433],{"class":63},"\u003C!",[57,1435,1436],{"class":800},"DOCTYPE",[57,1438,1439],{"class":86}," html",[57,1441,804],{"class":63},[57,1443,1444,1446,1448],{"class":59,"line":80},[57,1445,797],{"class":63},[57,1447,1339],{"class":800},[57,1449,804],{"class":63},[57,1451,1452,1454,1457],{"class":59,"line":98},[57,1453,818],{"class":63},[57,1455,1456],{"class":800},"head",[57,1458,804],{"class":63},[57,1460,1461],{"class":59,"line":116},[57,1462,1464],{"class":1463},"sC9rS","      \u003C!---省略-->\n",[57,1466,1467,1469,1471],{"class":59,"line":122},[57,1468,895],{"class":63},[57,1470,1456],{"class":800},[57,1472,804],{"class":63},[57,1474,1475],{"class":59,"line":136},[57,1476,1133],{"emptyLinePlaceholder":402},[57,1478,1479,1481,1484],{"class":59,"line":150},[57,1480,818],{"class":63},[57,1482,1483],{"class":800},"body",[57,1485,804],{"class":63},[57,1487,1488,1490,1493,1496,1498,1500,1503,1505],{"class":59,"line":155},[57,1489,845],{"class":63},[57,1491,1492],{"class":800},"form",[57,1494,1495],{"class":86}," method",[57,1497,826],{"class":63},[57,1499,64],{"class":63},[57,1501,1502],{"class":67},"post",[57,1504,64],{"class":63},[57,1506,804],{"class":63},[57,1508,1509,1512,1515,1518,1520,1522,1524,1526,1529,1531,1533,1535,1537,1540,1542,1545],{"class":59,"line":169},[57,1510,1511],{"class":63},"            \u003C",[57,1513,1514],{"class":800},"input",[57,1516,1517],{"class":86}," type",[57,1519,826],{"class":63},[57,1521,64],{"class":63},[57,1523,22],{"class":67},[57,1525,64],{"class":63},[57,1527,1528],{"class":86}," name",[57,1530,826],{"class":63},[57,1532,64],{"class":63},[57,1534,857],{"class":67},[57,1536,64],{"class":63},[57,1538,1539],{"class":86}," value",[57,1541,826],{"class":63},[57,1543,1544],{"class":63},"\"\"",[57,1546,804],{"class":63},[57,1548,1549,1551,1553,1555,1557,1559,1562,1564,1566,1568,1570,1573,1575],{"class":59,"line":183},[57,1550,1511],{"class":63},[57,1552,1514],{"class":800},[57,1554,1517],{"class":86},[57,1556,826],{"class":63},[57,1558,64],{"class":63},[57,1560,1561],{"class":67},"submit",[57,1563,64],{"class":63},[57,1565,1539],{"class":86},[57,1567,826],{"class":63},[57,1569,64],{"class":63},[57,1571,1572],{"class":67},"送信",[57,1574,64],{"class":63},[57,1576,804],{"class":63},[57,1578,1579,1582,1584],{"class":59,"line":188},[57,1580,1581],{"class":63},"        \u003C\u002F",[57,1583,1492],{"class":800},[57,1585,804],{"class":63},[57,1587,1588,1590,1592],{"class":59,"line":202},[57,1589,895],{"class":63},[57,1591,1483],{"class":800},[57,1593,804],{"class":63},[57,1595,1596,1598,1600],{"class":59,"line":216},[57,1597,904],{"class":63},[57,1599,1339],{"class":800},[57,1601,804],{"class":63},[13,1603,1604],{},"reCAPTCHAのフロントエンド 実装では",[441,1606,1607,1610,1613],{},[444,1608,1609],{},"reCAPTCHAのソースを読み込む",[444,1611,1612],{},"送信ボタンを押したらreCAPTCHAと通信してトークンを手に入れるスクリプトを書く",[444,1614,1615],{},"reCAPTCHAのトークンをフォーム内容と一緒にバックエンドに送信する。",[13,1617,1618,1619,1624],{},"以上の実装を必要とします。",[419,1620,1623],{"href":1621,"rel":1622},"https:\u002F\u002Fdevelopers.google.com\u002Frecaptcha\u002Fdocs\u002Fv3#programmatically_invoke_the_challenge",[423],"本家のドキュメント","を参考にして進めていきましょう。",[538,1626,1628],{"id":1627},"recaptchaのスクリプト読み込みとhtml調整","reCAPTCHAのスクリプト読み込みとHTML調整",[13,1630,1631],{},"まずはreCAPTCHAを有効にするためのスクリプトを読み込みます。そして一部フォームを編集します。",[17,1633,1635],{"className":1425,"code":1634,"language":1339,"meta":26,"style":26},"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n      \u003C!---省略-->\n        \u003Cscript src=\"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY\">\u003C\u002Fscript>\u003C!---追加-->\n    \u003C\u002Fhead>\n\n    \u003Cbody>\n        \u003Cform method=\"post\" id=\"test-form\">\u003C!---追加-->\n            \u003Cinput type=\"text\" name=\"test\" value=\"\">\n            \u003Cinput type=\"hidden\" name=\"recaptcha\" value=\"\">\u003C!---追加-->\n            \u003Cinput type=\"submit\" value=\"送信\">\n        \u003C\u002Fform>\n    \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[24,1636,1637,1647,1655,1663,1667,1694,1702,1706,1714,1746,1780,1818,1846,1854,1862],{"__ignoreMap":26},[57,1638,1639,1641,1643,1645],{"class":59,"line":60},[57,1640,1433],{"class":63},[57,1642,1436],{"class":800},[57,1644,1439],{"class":86},[57,1646,804],{"class":63},[57,1648,1649,1651,1653],{"class":59,"line":80},[57,1650,797],{"class":63},[57,1652,1339],{"class":800},[57,1654,804],{"class":63},[57,1656,1657,1659,1661],{"class":59,"line":98},[57,1658,818],{"class":63},[57,1660,1456],{"class":800},[57,1662,804],{"class":63},[57,1664,1665],{"class":59,"line":116},[57,1666,1464],{"class":1463},[57,1668,1669,1671,1673,1676,1678,1680,1683,1685,1687,1689,1691],{"class":59,"line":122},[57,1670,845],{"class":63},[57,1672,923],{"class":800},[57,1674,1675],{"class":86}," src",[57,1677,826],{"class":63},[57,1679,64],{"class":63},[57,1681,1682],{"class":67},"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY",[57,1684,64],{"class":63},[57,1686,886],{"class":63},[57,1688,923],{"class":800},[57,1690,1196],{"class":63},[57,1692,1693],{"class":1463},"\u003C!---追加-->\n",[57,1695,1696,1698,1700],{"class":59,"line":136},[57,1697,895],{"class":63},[57,1699,1456],{"class":800},[57,1701,804],{"class":63},[57,1703,1704],{"class":59,"line":150},[57,1705,1133],{"emptyLinePlaceholder":402},[57,1707,1708,1710,1712],{"class":59,"line":155},[57,1709,818],{"class":63},[57,1711,1483],{"class":800},[57,1713,804],{"class":63},[57,1715,1716,1718,1720,1722,1724,1726,1728,1730,1733,1735,1737,1740,1742,1744],{"class":59,"line":169},[57,1717,845],{"class":63},[57,1719,1492],{"class":800},[57,1721,1495],{"class":86},[57,1723,826],{"class":63},[57,1725,64],{"class":63},[57,1727,1502],{"class":67},[57,1729,64],{"class":63},[57,1731,1732],{"class":86}," id",[57,1734,826],{"class":63},[57,1736,64],{"class":63},[57,1738,1739],{"class":67},"test-form",[57,1741,64],{"class":63},[57,1743,1196],{"class":63},[57,1745,1693],{"class":1463},[57,1747,1748,1750,1752,1754,1756,1758,1760,1762,1764,1766,1768,1770,1772,1774,1776,1778],{"class":59,"line":183},[57,1749,1511],{"class":63},[57,1751,1514],{"class":800},[57,1753,1517],{"class":86},[57,1755,826],{"class":63},[57,1757,64],{"class":63},[57,1759,22],{"class":67},[57,1761,64],{"class":63},[57,1763,1528],{"class":86},[57,1765,826],{"class":63},[57,1767,64],{"class":63},[57,1769,857],{"class":67},[57,1771,64],{"class":63},[57,1773,1539],{"class":86},[57,1775,826],{"class":63},[57,1777,1544],{"class":63},[57,1779,804],{"class":63},[57,1781,1782,1784,1786,1788,1790,1792,1795,1797,1799,1801,1803,1806,1808,1810,1812,1814,1816],{"class":59,"line":188},[57,1783,1511],{"class":63},[57,1785,1514],{"class":800},[57,1787,1517],{"class":86},[57,1789,826],{"class":63},[57,1791,64],{"class":63},[57,1793,1794],{"class":67},"hidden",[57,1796,64],{"class":63},[57,1798,1528],{"class":86},[57,1800,826],{"class":63},[57,1802,64],{"class":63},[57,1804,1805],{"class":67},"recaptcha",[57,1807,64],{"class":63},[57,1809,1539],{"class":86},[57,1811,826],{"class":63},[57,1813,1544],{"class":63},[57,1815,1196],{"class":63},[57,1817,1693],{"class":1463},[57,1819,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844],{"class":59,"line":202},[57,1821,1511],{"class":63},[57,1823,1514],{"class":800},[57,1825,1517],{"class":86},[57,1827,826],{"class":63},[57,1829,64],{"class":63},[57,1831,1561],{"class":67},[57,1833,64],{"class":63},[57,1835,1539],{"class":86},[57,1837,826],{"class":63},[57,1839,64],{"class":63},[57,1841,1572],{"class":67},[57,1843,64],{"class":63},[57,1845,804],{"class":63},[57,1847,1848,1850,1852],{"class":59,"line":216},[57,1849,1581],{"class":63},[57,1851,1492],{"class":800},[57,1853,804],{"class":63},[57,1855,1856,1858,1860],{"class":59,"line":221},[57,1857,895],{"class":63},[57,1859,1483],{"class":800},[57,1861,804],{"class":63},[57,1863,1864,1866,1868],{"class":59,"line":235},[57,1865,904],{"class":63},[57,1867,1339],{"class":800},[57,1869,804],{"class":63},[13,1871,1872,1875,1876,1879,1880,1883],{},[24,1873,1874],{},"\u003Cscript src=\"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY\">\u003C\u002Fscript>","の",[24,1877,1878],{},"YOUR_FRONT_KEY","に先ほど取得したフロント用のキーを入れます。\n",[24,1881,1882],{},"\u003Cinput type=\"hidden\" name=\"recaptcha\" value=\"\">","にはreCAPTCHAのトークンを挿入してバックエンドに送ります。HTMLフォームであればこの様にしますが、axiosなどの場合はトークンの値をjsを用いて送信するので、このHTMLは要りません。",[538,1885,1886],{"id":1886},"トークンを取得するスクリプトを記述",[13,1888,1889],{},"「送信ボタン」を押した時にreCAPTCHAにAPIを飛ばしてbotかどうかの判定用トークンを取得します。以下の様なスクリプトを記述します。",[17,1891,1893],{"className":1425,"code":1892,"language":1339,"meta":26,"style":26},"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n      \u003C!---省略-->\n      \u003Cscript src=\"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY\">\u003C\u002Fscript>\n    \u003C\u002Fhead>\n\n    \u003Cbody>\n        \u003Cform method=\"post\" id=\"test-form\">\u003C!---追加-->\n            \u003Cinput type=\"text\" name=\"test\" value=\"\">\n            \u003Cinput type=\"hidden\" name=\"recaptcha\" value=\"\">\u003C!---追加-->\n            \u003Cinput type=\"submit\" value=\"送信\">\n        \u003C\u002Fform>\n    \u003C\u002Fbody>\n\n    \u003Cscript>\n        function checkCaptcha(e) {\n            e.preventDefault();\n            grecaptcha.ready(function() {\n                grecaptcha.execute('YOUR_FRONT_KEY', {action: 'submit'}).then(function(token) {\n                    document.getElementById(\"recaptcha\").value=token;\n                    document.getElementById(\"test-form\").submit();\n                });\n            });\n        }\n        document.getElementById(\"test-form\").addEventListener('submit', checkCaptcha);\n        \u003C\u002Fscript>\n\u003C\u002Fhtml>\n",[24,1894,1895,1905,1913,1921,1925,1948,1956,1960,1968,1998,2032,2068,2096,2104,2112,2116,2124,2144,2162,2182,2242,2274,2301,2311,2321,2326,2366,2375],{"__ignoreMap":26},[57,1896,1897,1899,1901,1903],{"class":59,"line":60},[57,1898,1433],{"class":63},[57,1900,1436],{"class":800},[57,1902,1439],{"class":86},[57,1904,804],{"class":63},[57,1906,1907,1909,1911],{"class":59,"line":80},[57,1908,797],{"class":63},[57,1910,1339],{"class":800},[57,1912,804],{"class":63},[57,1914,1915,1917,1919],{"class":59,"line":98},[57,1916,818],{"class":63},[57,1918,1456],{"class":800},[57,1920,804],{"class":63},[57,1922,1923],{"class":59,"line":116},[57,1924,1464],{"class":1463},[57,1926,1927,1930,1932,1934,1936,1938,1940,1942,1944,1946],{"class":59,"line":122},[57,1928,1929],{"class":63},"      \u003C",[57,1931,923],{"class":800},[57,1933,1675],{"class":86},[57,1935,826],{"class":63},[57,1937,64],{"class":63},[57,1939,1682],{"class":67},[57,1941,64],{"class":63},[57,1943,886],{"class":63},[57,1945,923],{"class":800},[57,1947,804],{"class":63},[57,1949,1950,1952,1954],{"class":59,"line":136},[57,1951,895],{"class":63},[57,1953,1456],{"class":800},[57,1955,804],{"class":63},[57,1957,1958],{"class":59,"line":150},[57,1959,1133],{"emptyLinePlaceholder":402},[57,1961,1962,1964,1966],{"class":59,"line":155},[57,1963,818],{"class":63},[57,1965,1483],{"class":800},[57,1967,804],{"class":63},[57,1969,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988,1990,1992,1994,1996],{"class":59,"line":169},[57,1971,845],{"class":63},[57,1973,1492],{"class":800},[57,1975,1495],{"class":86},[57,1977,826],{"class":63},[57,1979,64],{"class":63},[57,1981,1502],{"class":67},[57,1983,64],{"class":63},[57,1985,1732],{"class":86},[57,1987,826],{"class":63},[57,1989,64],{"class":63},[57,1991,1739],{"class":67},[57,1993,64],{"class":63},[57,1995,1196],{"class":63},[57,1997,1693],{"class":1463},[57,1999,2000,2002,2004,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2028,2030],{"class":59,"line":183},[57,2001,1511],{"class":63},[57,2003,1514],{"class":800},[57,2005,1517],{"class":86},[57,2007,826],{"class":63},[57,2009,64],{"class":63},[57,2011,22],{"class":67},[57,2013,64],{"class":63},[57,2015,1528],{"class":86},[57,2017,826],{"class":63},[57,2019,64],{"class":63},[57,2021,857],{"class":67},[57,2023,64],{"class":63},[57,2025,1539],{"class":86},[57,2027,826],{"class":63},[57,2029,1544],{"class":63},[57,2031,804],{"class":63},[57,2033,2034,2036,2038,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066],{"class":59,"line":188},[57,2035,1511],{"class":63},[57,2037,1514],{"class":800},[57,2039,1517],{"class":86},[57,2041,826],{"class":63},[57,2043,64],{"class":63},[57,2045,1794],{"class":67},[57,2047,64],{"class":63},[57,2049,1528],{"class":86},[57,2051,826],{"class":63},[57,2053,64],{"class":63},[57,2055,1805],{"class":67},[57,2057,64],{"class":63},[57,2059,1539],{"class":86},[57,2061,826],{"class":63},[57,2063,1544],{"class":63},[57,2065,1196],{"class":63},[57,2067,1693],{"class":1463},[57,2069,2070,2072,2074,2076,2078,2080,2082,2084,2086,2088,2090,2092,2094],{"class":59,"line":202},[57,2071,1511],{"class":63},[57,2073,1514],{"class":800},[57,2075,1517],{"class":86},[57,2077,826],{"class":63},[57,2079,64],{"class":63},[57,2081,1561],{"class":67},[57,2083,64],{"class":63},[57,2085,1539],{"class":86},[57,2087,826],{"class":63},[57,2089,64],{"class":63},[57,2091,1572],{"class":67},[57,2093,64],{"class":63},[57,2095,804],{"class":63},[57,2097,2098,2100,2102],{"class":59,"line":216},[57,2099,1581],{"class":63},[57,2101,1492],{"class":800},[57,2103,804],{"class":63},[57,2105,2106,2108,2110],{"class":59,"line":221},[57,2107,895],{"class":63},[57,2109,1483],{"class":800},[57,2111,804],{"class":63},[57,2113,2114],{"class":59,"line":235},[57,2115,1133],{"emptyLinePlaceholder":402},[57,2117,2118,2120,2122],{"class":59,"line":250},[57,2119,818],{"class":63},[57,2121,923],{"class":800},[57,2123,804],{"class":63},[57,2125,2126,2129,2132,2135,2139,2142],{"class":59,"line":256},[57,2127,2128],{"class":86},"        function",[57,2130,2131],{"class":372}," checkCaptcha",[57,2133,2134],{"class":63},"(",[57,2136,2138],{"class":2137},"s7ZW3","e",[57,2140,2141],{"class":63},")",[57,2143,95],{"class":63},[57,2145,2147,2150,2153,2156,2159],{"class":59,"line":2146},18,[57,2148,2149],{"class":73},"            e",[57,2151,2152],{"class":63},".",[57,2154,2155],{"class":372},"preventDefault",[57,2157,2158],{"class":800},"()",[57,2160,2161],{"class":63},";\n",[57,2163,2165,2168,2170,2173,2175,2178,2180],{"class":59,"line":2164},19,[57,2166,2167],{"class":73},"            grecaptcha",[57,2169,2152],{"class":63},[57,2171,2172],{"class":372},"ready",[57,2174,2134],{"class":800},[57,2176,2177],{"class":86},"function",[57,2179,2158],{"class":63},[57,2181,95],{"class":63},[57,2183,2185,2188,2190,2193,2195,2198,2200,2202,2205,2208,2211,2213,2216,2218,2220,2222,2224,2226,2229,2231,2233,2235,2238,2240],{"class":59,"line":2184},20,[57,2186,2187],{"class":73},"                grecaptcha",[57,2189,2152],{"class":63},[57,2191,2192],{"class":372},"execute",[57,2194,2134],{"class":800},[57,2196,2197],{"class":63},"'",[57,2199,1878],{"class":67},[57,2201,2197],{"class":63},[57,2203,2204],{"class":63},",",[57,2206,2207],{"class":63}," {",[57,2209,2210],{"class":800},"action",[57,2212,92],{"class":63},[57,2214,2215],{"class":63}," '",[57,2217,1561],{"class":67},[57,2219,2197],{"class":63},[57,2221,259],{"class":63},[57,2223,2141],{"class":800},[57,2225,2152],{"class":63},[57,2227,2228],{"class":372},"then",[57,2230,2134],{"class":800},[57,2232,2177],{"class":86},[57,2234,2134],{"class":63},[57,2236,2237],{"class":2137},"token",[57,2239,2141],{"class":63},[57,2241,95],{"class":63},[57,2243,2245,2248,2250,2253,2255,2257,2259,2261,2263,2265,2268,2270,2272],{"class":59,"line":2244},21,[57,2246,2247],{"class":73},"                    document",[57,2249,2152],{"class":63},[57,2251,2252],{"class":372},"getElementById",[57,2254,2134],{"class":800},[57,2256,64],{"class":63},[57,2258,1805],{"class":67},[57,2260,64],{"class":63},[57,2262,2141],{"class":800},[57,2264,2152],{"class":63},[57,2266,2267],{"class":73},"value",[57,2269,826],{"class":63},[57,2271,2237],{"class":73},[57,2273,2161],{"class":63},[57,2275,2277,2279,2281,2283,2285,2287,2289,2291,2293,2295,2297,2299],{"class":59,"line":2276},22,[57,2278,2247],{"class":73},[57,2280,2152],{"class":63},[57,2282,2252],{"class":372},[57,2284,2134],{"class":800},[57,2286,64],{"class":63},[57,2288,1739],{"class":67},[57,2290,64],{"class":63},[57,2292,2141],{"class":800},[57,2294,2152],{"class":63},[57,2296,1561],{"class":372},[57,2298,2158],{"class":800},[57,2300,2161],{"class":63},[57,2302,2304,2307,2309],{"class":59,"line":2303},23,[57,2305,2306],{"class":63},"                }",[57,2308,2141],{"class":800},[57,2310,2161],{"class":63},[57,2312,2314,2317,2319],{"class":59,"line":2313},24,[57,2315,2316],{"class":63},"            }",[57,2318,2141],{"class":800},[57,2320,2161],{"class":63},[57,2322,2324],{"class":59,"line":2323},25,[57,2325,966],{"class":63},[57,2327,2329,2332,2334,2336,2338,2340,2342,2344,2346,2348,2351,2353,2355,2357,2359,2361,2364],{"class":59,"line":2328},26,[57,2330,2331],{"class":73},"        document",[57,2333,2152],{"class":63},[57,2335,2252],{"class":372},[57,2337,2134],{"class":73},[57,2339,64],{"class":63},[57,2341,1739],{"class":67},[57,2343,64],{"class":63},[57,2345,2141],{"class":73},[57,2347,2152],{"class":63},[57,2349,2350],{"class":372},"addEventListener",[57,2352,2134],{"class":73},[57,2354,2197],{"class":63},[57,2356,1561],{"class":67},[57,2358,2197],{"class":63},[57,2360,2204],{"class":63},[57,2362,2363],{"class":73}," checkCaptcha)",[57,2365,2161],{"class":63},[57,2367,2369,2371,2373],{"class":59,"line":2368},27,[57,2370,1581],{"class":63},[57,2372,923],{"class":800},[57,2374,804],{"class":63},[57,2376,2378,2380,2382],{"class":59,"line":2377},28,[57,2379,904],{"class":63},[57,2381,1339],{"class":800},[57,2383,804],{"class":63},[13,2385,2386,2387,2390,2391,2394],{},"フォームの送信ボタンが押された時（submitイベント発火時）に",[24,2388,2389],{},"checkCaptcha","の関数が実行される様に設定します。",[24,2392,2393],{},"e.preventDefault();","を使用してそのままフォームが送信されない様にします。",[13,2396,2397,2398,2401,2402,2405,2406,2409,2410,2412,2413,2416],{},"reCAPTCHAのスクリプトによって",[24,2399,2400],{},"grecaptcha","というオブジェクトが使用できる様になり、その中の",[24,2403,2404],{},"grecaptcha.execute()","にてAPIを実行します。第一引数にフロントのキー、第二引数にアクションを入力します。Promiseなので",[24,2407,2408],{},"then(token)","内のコールバックでトークンを",[24,2411,1882],{},"に突っ込みます。そしてフォームを",[24,2414,2415],{},"submit()","にて送信します。",[13,2418,2419],{},"これでフロントの実装は完了です。フロントでの動きをみてreCAPTCHAはbotかどうかを判断し、この送信を一意なトークンで保存しているのです。トークンはバックエンドでの検証で利用します。",[466,2421,2422],{"id":2422},"バックエンドの実装",[13,2424,2425],{},"それではバックエンドの実装をすすめます。Laravelのコントローラーでの記述を想定しています。バリデーションなどは各自設定してください。",[13,2427,2428],{},"バックエンドで行うことは",[441,2430,2431,2434,2437],{},[444,2432,2433],{},"フロントからきたトークンをreCAPTCHAのAPIに送信",[444,2435,2436],{},"reCAPTCHAの結果を取得する",[444,2438,2439],{},"結果（スコア）を用いてbotかの判断をする",[13,2441,2442],{},"以上となります。コードは以下の通りです。",[17,2444,2448],{"className":2445,"code":2446,"language":2447,"meta":26,"style":26},"language-php shiki shiki-themes material-theme-ocean","class Controller extends BaseController\n{\n    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;\n\n    public function checkRecaptcha(Request $request){\n        try {\n            $client = new \\GuzzleHttp\\Client([\n                'headers' => [\n                    'Content-Type' => 'application\u002Fjson',\n                ],\n            ]);\n    \n            $promise = $client->postAsync('https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi\u002Fsiteverify',\n            [\n                'form_params' =>[\n                    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n                    'response'=>$request->recaptcha\n                ]\n            ]);\n    \n            $res = Promise\\Utils::settle($promise)->wait();\n            $isFulfilled = isset($res[0]['value']);\n            if(!$isFulfilled) throw new \\Exception('RECAPTCHA SERVER returns error');\n    \n            $result = json_decode($res[0]['value']->getBody()->getContents(),true);\n            \n            if(isset($result['error-codes'])){\n                if($result['error-codes'][0] === 'timeout-or-duplicate') return false;\n                throw new \\Exception('RECAPTCHA SERVER returns error:'.$result['error-codes'][0]);\n            }\n\n            return $result['score'] > 0.5 && $result['success'];\n        }catch (\\Exception $e) {\n            report($e);\n            return false;\n        }\n    }\n}\n","php",[24,2449,2450,2455,2459,2464,2468,2473,2478,2483,2488,2493,2498,2503,2508,2513,2518,2523,2528,2533,2538,2542,2546,2551,2556,2561,2565,2570,2575,2580,2585,2591,2597,2602,2608,2614,2620,2626,2631,2636],{"__ignoreMap":26},[57,2451,2452],{"class":59,"line":60},[57,2453,2454],{},"class Controller extends BaseController\n",[57,2456,2457],{"class":59,"line":80},[57,2458,77],{},[57,2460,2461],{"class":59,"line":98},[57,2462,2463],{},"    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;\n",[57,2465,2466],{"class":59,"line":116},[57,2467,1133],{"emptyLinePlaceholder":402},[57,2469,2470],{"class":59,"line":122},[57,2471,2472],{},"    public function checkRecaptcha(Request $request){\n",[57,2474,2475],{"class":59,"line":136},[57,2476,2477],{},"        try {\n",[57,2479,2480],{"class":59,"line":150},[57,2481,2482],{},"            $client = new \\GuzzleHttp\\Client([\n",[57,2484,2485],{"class":59,"line":155},[57,2486,2487],{},"                'headers' => [\n",[57,2489,2490],{"class":59,"line":169},[57,2491,2492],{},"                    'Content-Type' => 'application\u002Fjson',\n",[57,2494,2495],{"class":59,"line":183},[57,2496,2497],{},"                ],\n",[57,2499,2500],{"class":59,"line":188},[57,2501,2502],{},"            ]);\n",[57,2504,2505],{"class":59,"line":202},[57,2506,2507],{},"    \n",[57,2509,2510],{"class":59,"line":216},[57,2511,2512],{},"            $promise = $client->postAsync('https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi\u002Fsiteverify',\n",[57,2514,2515],{"class":59,"line":221},[57,2516,2517],{},"            [\n",[57,2519,2520],{"class":59,"line":235},[57,2521,2522],{},"                'form_params' =>[\n",[57,2524,2525],{"class":59,"line":250},[57,2526,2527],{},"                    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n",[57,2529,2530],{"class":59,"line":256},[57,2531,2532],{},"                    'response'=>$request->recaptcha\n",[57,2534,2535],{"class":59,"line":2146},[57,2536,2537],{},"                ]\n",[57,2539,2540],{"class":59,"line":2164},[57,2541,2502],{},[57,2543,2544],{"class":59,"line":2184},[57,2545,2507],{},[57,2547,2548],{"class":59,"line":2244},[57,2549,2550],{},"            $res = Promise\\Utils::settle($promise)->wait();\n",[57,2552,2553],{"class":59,"line":2276},[57,2554,2555],{},"            $isFulfilled = isset($res[0]['value']);\n",[57,2557,2558],{"class":59,"line":2303},[57,2559,2560],{},"            if(!$isFulfilled) throw new \\Exception('RECAPTCHA SERVER returns error');\n",[57,2562,2563],{"class":59,"line":2313},[57,2564,2507],{},[57,2566,2567],{"class":59,"line":2323},[57,2568,2569],{},"            $result = json_decode($res[0]['value']->getBody()->getContents(),true);\n",[57,2571,2572],{"class":59,"line":2328},[57,2573,2574],{},"            \n",[57,2576,2577],{"class":59,"line":2368},[57,2578,2579],{},"            if(isset($result['error-codes'])){\n",[57,2581,2582],{"class":59,"line":2377},[57,2583,2584],{},"                if($result['error-codes'][0] === 'timeout-or-duplicate') return false;\n",[57,2586,2588],{"class":59,"line":2587},29,[57,2589,2590],{},"                throw new \\Exception('RECAPTCHA SERVER returns error:'.$result['error-codes'][0]);\n",[57,2592,2594],{"class":59,"line":2593},30,[57,2595,2596],{},"            }\n",[57,2598,2600],{"class":59,"line":2599},31,[57,2601,1133],{"emptyLinePlaceholder":402},[57,2603,2605],{"class":59,"line":2604},32,[57,2606,2607],{},"            return $result['score'] > 0.5 && $result['success'];\n",[57,2609,2611],{"class":59,"line":2610},33,[57,2612,2613],{},"        }catch (\\Exception $e) {\n",[57,2615,2617],{"class":59,"line":2616},34,[57,2618,2619],{},"            report($e);\n",[57,2621,2623],{"class":59,"line":2622},35,[57,2624,2625],{},"            return false;\n",[57,2627,2629],{"class":59,"line":2628},36,[57,2630,966],{},[57,2632,2634],{"class":59,"line":2633},37,[57,2635,253],{},[57,2637,2639],{"class":59,"line":2638},38,[57,2640,975],{},[13,2642,2643,2644,2647,2648,2651],{},"recaptchaとのAPI通信には",[24,2645,2646],{},"Guzzle","を使用していますが、とにかくAPI通信ができれば大丈夫です。APIは",[24,2649,2650],{},"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi\u002Fsiteverify","にPOSTを送信します。POSTには以下の値が必要です、",[17,2653,2655],{"className":2445,"code":2654,"language":2447,"meta":26,"style":26},"'form_params' =>[\n    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n    'response'=>$request->recaptcha\n]\n",[24,2656,2657,2662,2667,2672],{"__ignoreMap":26},[57,2658,2659],{"class":59,"line":60},[57,2660,2661],{},"'form_params' =>[\n",[57,2663,2664],{"class":59,"line":80},[57,2665,2666],{},"    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n",[57,2668,2669],{"class":59,"line":98},[57,2670,2671],{},"    'response'=>$request->recaptcha\n",[57,2673,2674],{"class":59,"line":116},[57,2675,2676],{},"]\n",[13,2678,2679,2682,2683,2686,2687,2689,2690,2693],{},[24,2680,2681],{},"env('RECAPTCHA_SERVER_KEY')","はバックエンドで使用するrecaptchaキーです。",[24,2684,2685],{},"$request->recaptcha","は",[24,2688,1882],{},"で挿入されたフロントで取得したrecaptchaのトークンです。このトークンとキーを合わせて、 ",[727,2691,2692],{},"保護対象のサーバーであり、検証を行うフォーム送信","　を判別しています。",[13,2695,2696],{},"通信が成功すると以下の様なレスポンスが戻ります。",[17,2698,2700],{"className":2445,"code":2699,"language":2447,"meta":26,"style":26},"[\n  \"success\"=> true, \n  \"score\"=> 0.8,\n  \"action\"=> string,\n  \"challenge_ts\"=> timestamp,\n  \"hostname\"=> string,\n]\n",[24,2701,2702,2707,2712,2717,2722,2727,2732],{"__ignoreMap":26},[57,2703,2704],{"class":59,"line":60},[57,2705,2706],{},"[\n",[57,2708,2709],{"class":59,"line":80},[57,2710,2711],{},"  \"success\"=> true, \n",[57,2713,2714],{"class":59,"line":98},[57,2715,2716],{},"  \"score\"=> 0.8,\n",[57,2718,2719],{"class":59,"line":116},[57,2720,2721],{},"  \"action\"=> string,\n",[57,2723,2724],{"class":59,"line":122},[57,2725,2726],{},"  \"challenge_ts\"=> timestamp,\n",[57,2728,2729],{"class":59,"line":136},[57,2730,2731],{},"  \"hostname\"=> string,\n",[57,2733,2734],{"class":59,"line":150},[57,2735,2676],{},[13,2737,2738,2739,2742,2743,2746],{},"一番重要なのは",[24,2740,2741],{},"\"score\"=> 0.8","です。このスコアは入力したリクエストがbotか人間かのスコアを示しており、1に近いほど人間が入力しています。逆に0.1あたりはbotの入力です。どこまで厳しくするかはお任せしますが、私は0.5以上であれば人間のリクエストであるとしています。",[24,2744,2745],{},"return $result['score'] > 0.5"," としてfalseであればリクエストを拒否したり、エラーを返す様にします。フォーム系で汎用的に使用できる様に私はサービスプロバイダにしています。",[538,2748,2749],{"id":2749},"エラー処理",[13,2751,2752],{},"エラーの場合は以下の様なレスポンスがきます。（例です）",[17,2754,2756],{"className":2445,"code":2755,"language":2447,"meta":26,"style":26},"[\n  \"success\"=> false, \n  \"action\"=> string,\n  \"challenge_ts\"=> timestamp,\n  \"hostname\"=> string,\n  \"error-codes\": [\n      0=>'timeout-or-duplicate'\n  ] \n]\n",[24,2757,2758,2762,2767,2771,2775,2779,2784,2789,2794],{"__ignoreMap":26},[57,2759,2760],{"class":59,"line":60},[57,2761,2706],{},[57,2763,2764],{"class":59,"line":80},[57,2765,2766],{},"  \"success\"=> false, \n",[57,2768,2769],{"class":59,"line":98},[57,2770,2721],{},[57,2772,2773],{"class":59,"line":116},[57,2774,2726],{},[57,2776,2777],{"class":59,"line":122},[57,2778,2731],{},[57,2780,2781],{"class":59,"line":136},[57,2782,2783],{},"  \"error-codes\": [\n",[57,2785,2786],{"class":59,"line":150},[57,2787,2788],{},"      0=>'timeout-or-duplicate'\n",[57,2790,2791],{"class":59,"line":155},[57,2792,2793],{},"  ] \n",[57,2795,2796],{"class":59,"line":169},[57,2797,2676],{},[13,2799,2800,2801,2804],{},"このエラーはAPIの通信が失敗したり、必要なパラメーターが不足していたりなどのエラーです。 ",[727,2802,2803],{},"リクエストがBotである"," という意味ではないので注意。Botかの判定はあくまで成功時に取得するscoreで判定します。",[538,2806,2807],{"id":2807},"エラーの説明",[13,2809,2810,2813,2815],{},[727,2811,2812],{},"missing-input-secret",[24,2814,2681],{},"のようなサーバー側のrecaptchaのキーを忘れている。",[17,2817,2819],{"className":2445,"code":2818,"language":2447,"meta":26,"style":26},"'form_params' =>[\n    'secret'=>env('RECAPTCHA_SERVER_KEY'), \u002F\u002F このへん\n    'response'=>$request->recaptcha\n]\n",[24,2820,2821,2825,2833,2837],{"__ignoreMap":26},[57,2822,2823],{"class":59,"line":60},[57,2824,2661],{},[57,2826,2827,2830],{"class":59,"line":80},[57,2828,2829],{},"    'secret'=>env('RECAPTCHA_SERVER_KEY'),",[57,2831,2832],{}," \u002F\u002F このへん\n",[57,2834,2835],{"class":59,"line":98},[57,2836,2671],{},[57,2838,2839],{"class":59,"line":116},[57,2840,2676],{},[13,2842,2843,2846,2848],{},[727,2844,2845],{},"invalid-input-secret",[24,2847,2681],{},"が不正。間違っているキーを使用している。キーが正しいか、保護対象のドメインとして登録しているかを確認。",[13,2850,2851,2854,2857],{},[727,2852,2853],{},"missing-input-response",[24,2855,2856],{},"'response'=>$request->recaptcha","を忘れている、空文字。",[13,2859,2860,2863,2865],{},[727,2861,2862],{},"invalid-input-response",[24,2864,2856],{},"の値が不正。型などを確認。",[13,2867,2868,2871],{},[727,2869,2870],{},"bad-request","\nPOSTで送っているかを確認。",[13,2873,2874,2877,2879],{},[727,2875,2876],{},"timeout-or-duplicate",[24,2878,2856],{},"の値を二回送っているか、フロントのトークンが２分以上経過した。",[13,2881,2882],{},"フロントで取得したトークンはバックエンドでのこの検証を行うともう一度利用することができません。またこのトークンは",[17,2884,2888],{"className":2885,"code":2886,"language":2887,"meta":26,"style":26},"language-javascript shiki shiki-themes material-theme-ocean","grecaptcha.execute('YOUR_FRONT_KEY', {action: 'submit'}).then(function(token) {\n    document.getElementById(\"recaptcha\").value=token;\n    document.getElementById(\"test-form\").submit();\n});\n","javascript",[24,2889,2890,2940,2969,2995],{"__ignoreMap":26},[57,2891,2892,2894,2896,2898,2900,2902,2904,2906,2908,2910,2912,2914,2916,2918,2920,2922,2924,2926,2928,2930,2932,2934,2936,2938],{"class":59,"line":60},[57,2893,2400],{"class":73},[57,2895,2152],{"class":63},[57,2897,2192],{"class":372},[57,2899,2134],{"class":73},[57,2901,2197],{"class":63},[57,2903,1878],{"class":67},[57,2905,2197],{"class":63},[57,2907,2204],{"class":63},[57,2909,2207],{"class":63},[57,2911,2210],{"class":800},[57,2913,92],{"class":63},[57,2915,2215],{"class":63},[57,2917,1561],{"class":67},[57,2919,2197],{"class":63},[57,2921,259],{"class":63},[57,2923,2141],{"class":73},[57,2925,2152],{"class":63},[57,2927,2228],{"class":372},[57,2929,2134],{"class":73},[57,2931,2177],{"class":86},[57,2933,2134],{"class":63},[57,2935,2237],{"class":2137},[57,2937,2141],{"class":63},[57,2939,95],{"class":63},[57,2941,2942,2945,2947,2949,2951,2953,2955,2957,2959,2961,2963,2965,2967],{"class":59,"line":80},[57,2943,2944],{"class":73},"    document",[57,2946,2152],{"class":63},[57,2948,2252],{"class":372},[57,2950,2134],{"class":800},[57,2952,64],{"class":63},[57,2954,1805],{"class":67},[57,2956,64],{"class":63},[57,2958,2141],{"class":800},[57,2960,2152],{"class":63},[57,2962,2267],{"class":73},[57,2964,826],{"class":63},[57,2966,2237],{"class":73},[57,2968,2161],{"class":63},[57,2970,2971,2973,2975,2977,2979,2981,2983,2985,2987,2989,2991,2993],{"class":59,"line":98},[57,2972,2944],{"class":73},[57,2974,2152],{"class":63},[57,2976,2252],{"class":372},[57,2978,2134],{"class":800},[57,2980,64],{"class":63},[57,2982,1739],{"class":67},[57,2984,64],{"class":63},[57,2986,2141],{"class":800},[57,2988,2152],{"class":63},[57,2990,1561],{"class":372},[57,2992,2158],{"class":800},[57,2994,2161],{"class":63},[57,2996,2997,2999,3001],{"class":59,"line":116},[57,2998,259],{"class":63},[57,3000,2141],{"class":73},[57,3002,2161],{"class":63},[13,3004,3005],{},"の実行から２分以内で利用する必要があります。そのためページがリロードされた瞬間ときに実行していると、フォーム入力中に時間切れになったります。そのためsubmit時に実行することをお勧めします。",[466,3007,3008],{"id":3008},"実装まとめ",[13,3010,3011],{},"以上がrecaptchaの実装方法です。recaptchaはあくまでBotかどうかの判断のみをしているので、実際にリクエストを通すかはアプリケーション側の仕事です。フロントとバックでの実装が少し面倒ですが、recaptchaの機能を自前で実装しようとするとそこそこ、面倒なのと実績のあるGoogle様に検証してもらうのも結構安心です。",[13,3013,3014],{},"バックエンドはLaravelを想定しますが、他のフレームワークや言語でもやることは特に変わりません。上手くご自身の環境に置き換えてください。",[390,3016,3017],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}",{"title":26,"searchDepth":98,"depth":98,"links":3019},[3020,3021,3022,3023,3027,3031],{"id":1362,"depth":80,"text":1363},{"id":1369,"depth":80,"text":1369},{"id":1378,"depth":80,"text":1379},{"id":1419,"depth":80,"text":1419,"children":3024},[3025,3026],{"id":1627,"depth":98,"text":1628},{"id":1886,"depth":98,"text":1886},{"id":2422,"depth":80,"text":2422,"children":3028},[3029,3030],{"id":2749,"depth":98,"text":2749},{"id":2807,"depth":98,"text":2807},{"id":3008,"depth":80,"text":3008},[3033],"devstack","2021-05-21","reCAPTCHAのbot検証をフロントエンド とLaravelでのバックエンドの実装を行います。",{},"\u002Farticles\u002Fimplement-recaptcha",{"title":1345,"description":3035},"articles\u002Fimplement-recaptcha",[2447,3041,3042,3043],"js","laravel","security","_mix\u002Flaravel-recaptcha.jpg","AYarFTQUZE-KIUOU5A3co7uCUDW9hqT091VEnFYUN6Q",{"id":3047,"title":3048,"body":3049,"category":3320,"createdAt":3321,"description":3322,"extension":399,"index":400,"meta":3323,"navigation":402,"path":3324,"publish":402,"seo":3325,"series":400,"seriesTitle":400,"stem":3326,"tag":3327,"thumbnail":400,"updatedAt":400,"__hash__":3329},"articles\u002Farticles\u002Fclick-event-on-lable.md","label要素と関連付けされたinput要素にクリックイベントを付与すると2回実行される。",{"type":10,"value":3050,"toc":3314},[3051,3054,3253,3261,3268,3272,3275,3282,3286,3295,3299,3305,3308,3311],[13,3052,3053],{},"こんにちはjunです。フォームとしての要素、ボタンとしての要素を掛け合わせて以下のようなものを作成していました。",[17,3055,3057],{"className":1425,"code":3056,"language":1339,"meta":26,"style":26},"\u003Clabel class=\"c-button\" for=\"form-input\">\n    \u003Cinput data-product_id=\"1\" id=\"form-input\" type=\"checkbox\" name=\"form-input\" value=\"\">\n    \u003Cspan class=\"text\">まとめて購入\u003C\u002Fspan>\n\u003C\u002Flabel>\n\n\u003Cscript>\n    $('.c-button').on('click',function(){\n        \u002F\u002F ...\n    })\n\u003C\u002Fscript>\n",[24,3058,3059,3092,3149,3176,3184,3188,3196,3232,3237,3245],{"__ignoreMap":26},[57,3060,3061,3063,3066,3069,3071,3073,3076,3078,3081,3083,3085,3088,3090],{"class":59,"line":60},[57,3062,797],{"class":63},[57,3064,3065],{"class":800},"label",[57,3067,3068],{"class":86}," class",[57,3070,826],{"class":63},[57,3072,64],{"class":63},[57,3074,3075],{"class":67},"c-button",[57,3077,64],{"class":63},[57,3079,3080],{"class":86}," for",[57,3082,826],{"class":63},[57,3084,64],{"class":63},[57,3086,3087],{"class":67},"form-input",[57,3089,64],{"class":63},[57,3091,804],{"class":63},[57,3093,3094,3096,3098,3101,3103,3105,3108,3110,3112,3114,3116,3118,3120,3122,3124,3126,3129,3131,3133,3135,3137,3139,3141,3143,3145,3147],{"class":59,"line":80},[57,3095,818],{"class":63},[57,3097,1514],{"class":800},[57,3099,3100],{"class":86}," data-product_id",[57,3102,826],{"class":63},[57,3104,64],{"class":63},[57,3106,3107],{"class":67},"1",[57,3109,64],{"class":63},[57,3111,1732],{"class":86},[57,3113,826],{"class":63},[57,3115,64],{"class":63},[57,3117,3087],{"class":67},[57,3119,64],{"class":63},[57,3121,1517],{"class":86},[57,3123,826],{"class":63},[57,3125,64],{"class":63},[57,3127,3128],{"class":67},"checkbox",[57,3130,64],{"class":63},[57,3132,1528],{"class":86},[57,3134,826],{"class":63},[57,3136,64],{"class":63},[57,3138,3087],{"class":67},[57,3140,64],{"class":63},[57,3142,1539],{"class":86},[57,3144,826],{"class":63},[57,3146,1544],{"class":63},[57,3148,804],{"class":63},[57,3150,3151,3153,3155,3157,3159,3161,3163,3165,3167,3170,3172,3174],{"class":59,"line":98},[57,3152,818],{"class":63},[57,3154,57],{"class":800},[57,3156,3068],{"class":86},[57,3158,826],{"class":63},[57,3160,64],{"class":63},[57,3162,22],{"class":67},[57,3164,64],{"class":63},[57,3166,1196],{"class":63},[57,3168,3169],{"class":73},"まとめて購入",[57,3171,904],{"class":63},[57,3173,57],{"class":800},[57,3175,804],{"class":63},[57,3177,3178,3180,3182],{"class":59,"line":116},[57,3179,904],{"class":63},[57,3181,3065],{"class":800},[57,3183,804],{"class":63},[57,3185,3186],{"class":59,"line":122},[57,3187,1133],{"emptyLinePlaceholder":402},[57,3189,3190,3192,3194],{"class":59,"line":136},[57,3191,797],{"class":63},[57,3193,923],{"class":800},[57,3195,804],{"class":63},[57,3197,3198,3201,3203,3205,3208,3210,3212,3214,3217,3219,3221,3224,3226,3228,3230],{"class":59,"line":150},[57,3199,3200],{"class":372},"    $",[57,3202,2134],{"class":73},[57,3204,2197],{"class":63},[57,3206,3207],{"class":67},".c-button",[57,3209,2197],{"class":63},[57,3211,2141],{"class":73},[57,3213,2152],{"class":63},[57,3215,3216],{"class":372},"on",[57,3218,2134],{"class":73},[57,3220,2197],{"class":63},[57,3222,3223],{"class":67},"click",[57,3225,2197],{"class":63},[57,3227,2204],{"class":63},[57,3229,2177],{"class":86},[57,3231,944],{"class":63},[57,3233,3234],{"class":59,"line":155},[57,3235,3236],{"class":1463},"        \u002F\u002F ...\n",[57,3238,3239,3242],{"class":59,"line":169},[57,3240,3241],{"class":63},"    }",[57,3243,3244],{"class":73},")\n",[57,3246,3247,3249,3251],{"class":59,"line":183},[57,3248,904],{"class":63},[57,3250,923],{"class":800},[57,3252,804],{"class":63},[13,3254,3255,3257,3258,3260],{},[24,3256,3065],{},"をクリックするとチェックボックスにチェックが入り、さらにクリックイベントも走るという仕組みです。",[24,3259,1514],{},"だけをクリックしても同じ動作になります。",[13,3262,3263,3264,3267],{},"しかしチェックをしてみると挙動が何故か変。私の場合はON・OFF的な処理をしていたので、inputをクリックすると正常に動き、labelだと処理が行われていないという現象に見舞われました。原因はタイトル通り、上記のような構成をjqueryで実装すると、labelをクリックすると",[24,3265,3266],{},"$('.c-button')","へのクリックイベントが2回走ることが原因です。対処としては3つほどあります。",[466,3269,3271],{"id":3270},"_1changeイベントに変更する","1：changeイベントに変更する",[13,3273,3274],{},"input要素を活かしたい場合はこれがベストだと思います。changeイベントはinputなどの入力値が変更された時に発火するイベントです。for属性を用いてinputと関連付けされいるので、labelをクリックすることでinputの値（チェックされたか）を変更させることができます。一方でinputだけをクリックしても発火します。",[13,3276,3277,3278,3281],{},"なぜクリックイベントが2回起きるのかがわかりませんが、for属性あたりとかがなんか関係しているのかもしれません。 ",[727,3279,3280],{},"change属性であれば値が変更されたか否かでの発火条件となる","ので問題なくなります。",[466,3283,3285],{"id":3284},"_2inputだけにする","2:inputだけにする",[13,3287,3288,3290,3291,3294],{},[24,3289,3266],{},"を",[24,3292,3293],{},"$('.c-button input')","にします。そうすれば一応inputだけクリックした時に発火します。ただし、labelでボタンのスタイルを表現していたりする場合はこの手は微妙です。",[466,3296,3298],{"id":3297},"_3inputでなくアイコン画像にする","3:inputでなくアイコン・画像にする",[13,3300,3301,3302,3304],{},"もし",[24,3303,1514],{},"がフォーム送信的な意味をもたず実装しているならば（見た目だけ）、画像・アイコンで実装するというてもあります。その時はクラスを付与してアイコンを変えると言ったことが必要です。",[466,3306,3307],{"id":3307},"まとめ",[13,3309,3310],{},"上記の解決法では１が一番ベストです。vanillajsで上記のような構成を作るとなぜか発生しないので、jqueryの仕様なのかもしれません。",[390,3312,3313],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":26,"searchDepth":98,"depth":98,"links":3315},[3316,3317,3318,3319],{"id":3270,"depth":80,"text":3271},{"id":3284,"depth":80,"text":3285},{"id":3297,"depth":80,"text":3298},{"id":3307,"depth":80,"text":3307},[396],"2021-04-30","要注意なクリックイベント。input要素にクリックイベントをつけている場合は注意。",{},"\u002Farticles\u002Fclick-event-on-lable",{"title":3048,"description":3322},"articles\u002Fclick-event-on-lable",[3041,1339,3328],"jquery","EaIYy3tweEkOCbIAFQ9TqAjE0pXI6pYISdYQKN-YfEw",{"id":3331,"title":3332,"body":3333,"category":3743,"createdAt":3744,"description":3332,"extension":399,"index":400,"meta":3745,"navigation":402,"path":3746,"publish":402,"seo":3747,"series":400,"seriesTitle":400,"stem":3748,"tag":3749,"thumbnail":3751,"updatedAt":400,"__hash__":3752},"articles\u002Farticles\u002Fvps-ssh-first.md","VPSでやっておきたいSSHの最低限のセキュティ設定。",{"type":10,"value":3334,"toc":3731},[3335,3338,3341,3344,3358,3370,3373,3377,3380,3386,3389,3392,3401,3404,3407,3418,3421,3424,3427,3433,3436,3442,3449,3455,3485,3489,3501,3508,3514,3517,3520,3523,3526,3532,3535,3541,3548,3554,3563,3569,3577,3583,3590,3606,3609,3612,3619,3625,3643,3646,3652,3655,3661,3664,3669,3672,3675,3678,3684,3687,3690,3696,3706,3712,3715,3721,3724,3728],[13,3336,3337],{},"こんにちはjunです。webエンジニアとして働いていますが会社の人数が少なく、インフラの構築をすることがあります。と言ってもLAMP環境を作成するぐらいですけど。",[13,3339,3340],{},"構築は慣れてきましたがその中で、私が一番考えるのはセキュリティーです。構築するアプリケーションによってはセキュリティー設定はことなりますが、今回は自分でサーバを構築する時に最低限やった方がいいSSHの設定をメモがてら記事にしようと思います。",[13,3342,3343],{},"今回行う設定は以下の通りです。",[441,3345,3346,3349,3352,3355],{},[444,3347,3348],{},"rootユーザーのリモートログイン禁止化",[444,3350,3351],{},"パスワード認証禁止と鍵認証化",[444,3353,3354],{},"鍵認証の実装方法",[444,3356,3357],{},"ポート変更とfirewallの設定",[811,3359,3361,3362],{"className":3360},[1001,1002],"\nなお今回説明する環境\n",[441,3363,3364,3367],{},[444,3365,3366],{},"サービス：GMO conoha VPS",[444,3368,3369],{},"OS：centos8",[13,3371,3372],{},"この設定をすべき理由や背景などから話すため、さっさと方法を知りたい場合は「root以外のユーザーを作成していく」から見てください。",[466,3374,3376],{"id":3375},"sshはめちゃくちゃ狙われている","SSHはめちゃくちゃ狙われている",[13,3378,3379],{},"SSHはリモートでサーバーを操作できる口のため、一番厳重にしなければなりません。あまり運用していないサーバー、購入したばかりでドメインと紐づけられていなくても世界中からBOTによる不正アクセス試行が発生します。私がVPSで購入してイメージが立った時も早速ログを見たところ以下の様になっていました。",[17,3381,3384],{"className":3382,"code":3383,"language":22},[20],"April  8 12:10:04 xxx-xxx-xx-xxx sshd[6887]: Failed password for root from 209.141.36.197 port 54506 ssh2\nApril  8 12:10:04 xxx-xxx-xx-xxx sshd[6891]: Failed password for invalid user oracle from 209.141.36.197 port 54518 ssh2\nApril  8 12:10:04 xxx-xxx-xx-xxx sshd[6892]: Failed password for invalid user test from 209.141.36.197 port 54512 ssh2\nApril  8 12:10:04 xxx-xxx-xx-xxx sshd[6889]: Failed password for root from 209.141.36.197 port 54520 ssh2\nApril  8 12:10:06 xxx-xxx-xx-xxx sshd[6904]: Failed password for root from 222.187.238.136 port 58496 ssh2\nApril  8 12:10:10 xxx-xxx-xx-xxx sshd[6904]: Failed password for root from 222.187.238.136 port 58496 ssh2\nApril  8 12:16:26 xxx-xxx-xx-xxx sshd[6909]: Failed password for root from 221.181.185.220 port 28787 ssh2\nApril  8 12:16:30 xxx-xxx-xx-xxx sshd[6909]: Failed password for root from 221.181.185.220 port 28787 ssh2\nApril  8 12:16:33 xxx-xxx-xx-xxx sshd[6909]: Failed password for root from 221.181.185.220 port 28787 ssh2\nApril  8 12:16:37 xxx-xxx-xx-xxx sshd[6911]: Failed password for root from 221.181.185.220 port 55152 ssh2\nApril  8 12:16:41 xxx-xxx-xx-xxx sshd[6911]: Failed password for root from 221.181.185.220 port 55152 ssh2\nApril  8 12:16:45 xxx-xxx-xx-xxx sshd[6911]: Failed password for root from 221.181.185.220 port 55152 ssh2\nApril  8 12:16:49 xxx-xxx-xx-xxx sshd[6913]: Failed password for root from 221.181.185.220 port 39938 ssh2\nApril  8 12:16:53 xxx-xxx-xx-xxx sshd[6913]: Failed password for root from 221.181.185.220 port 39938 ssh2\nApril  8 12:16:56 xxx-xxx-xx-xxx sshd[6913]: Failed password for root from 221.181.185.220 port 39938 ssh2\nApril  8 12:27:50 xxx-xxx-xx-xxx sshd[6918]: Failed password for root from 222.187.239.109 port 32720 ssh2\nApril  8 12:27:54 xxx-xxx-xx-xxx sshd[6918]: Failed password for root from 222.187.239.109 port 32720 ssh2\nApril  8 12:27:57 xxx-xxx-xx-xxx sshd[6918]: Failed password for root from 222.187.239.109 port 32720 ssh2\n",[24,3385,3383],{"__ignoreMap":26},[13,3387,3388],{},"これはSSHの接続失敗の履歴です、同じIPで定間隔でrootユーザーによるパスワード接続を試みています。これはパスワードリスト攻撃・総当たり攻撃というものです。確率は低いですがrootのパスワードが万が一にあった場合、サーバの最上権限であるrootが第三者に掌握されます。",[13,3390,3391],{},"ドメインに紐づけられておらずIPだけでもこの様にBOTがインターネットを徘徊して、脆弱なサーバを狙っています。VPSを購入したらすぐにデフォルトの設定を変えて、比較的安全なものに変更しましょう。",[811,3393,3396,3397,3400],{"className":3394},[1001,3395],"alert-info","\nターミナルで",[24,3398,3399],{},"whois","コマンドを使用してIPのwois情報を調べられます。登録された国などもわかります。ちなみに上記のIPのほとんどが中国でした。\n",[466,3402,3403],{"id":3403},"安全にするために変更する設定",[13,3405,3406],{},"デフォルトの設定から以下の変更を行います。",[441,3408,3409,3412,3415],{},[444,3410,3411],{},"rootによるリモートログインを禁止にする。",[444,3413,3414],{},"SSHをパスワード認証でなく、鍵認証にする",[444,3416,3417],{},"ポートを変更する",[13,3419,3420],{},"これらの設定は最低限行うべきものです。より強固にする場合は、色々と他に設定しますがまずはこれでいきましょう。",[538,3422,3423],{"id":3423},"root以外のユーザーを作成していく",[13,3425,3426],{},"最終的にはrootのリモートログインを禁止するため別の操作ユーザーを作成していきます。まずはrootでログインしましましょう。",[17,3428,3431],{"className":3429,"code":3430,"language":22},[20],"ssh root@123.456.78.901\n（IPなどは自分のものに置き換えてください）\n",[24,3432,3430],{"__ignoreMap":26},[13,3434,3435],{},"開発用・SSH接続用ユーザーを作成します。",[17,3437,3440],{"className":3438,"code":3439,"language":22},[20],"# useradd develop\n# passwd develop\n",[24,3441,3439],{"__ignoreMap":26},[13,3443,3444,3445,3448],{},"これでユーザーとそのパスワードが設定されました。次にそのユーザーを",[24,3446,3447],{},"wheel","グループに所属させます。",[17,3450,3453],{"className":3451,"code":3452,"language":22},[20],"# usermod -G wheel develop\n",[24,3454,3452],{"__ignoreMap":26},[13,3456,3457,3459,3460,3463,3464,3467,3468,3471,3472,3475,3476,3478,3479,3481,3482,3484],{},[24,3458,3447],{},"グループに所属することで",[24,3461,3462],{},"develop","が",[24,3465,3466],{},"sudo","を使用できる様になり、また",[24,3469,3470],{},"su","にてrootになることができます。",[24,3473,3474],{},"\u002Fetc\u002Fsudoers","では",[24,3477,3447],{},"グループで行える操作などを定義してあり、基本的にはrootを使用せずdevelopでsshログインをして操作します。yumで何かインストールしたい時とかは",[24,3480,3466],{},"したり",[24,3483,3470],{},"でrootになります。",[3486,3487,3488],"h4",{"id":3488},"wheelのみがsuできる様にする",[13,3490,3491,3492,3494,3495,3497,3498,3500],{},"デフォルトではsuでどのユーザーもrootになれます。",[24,3493,3462],{},"意外にもユーザは存在し、そのユーザーが狙われることもあります。そのため",[24,3496,3447],{},"グループに属しているユーザーだけが",[24,3499,3470],{},"を用いてrootになれる様にしましょう。",[13,3502,3503,3504,3507],{},"suの設定は",[24,3505,3506],{},"\u002Fetc\u002Fpam.d\u002Fsu","に記述されています。",[17,3509,3512],{"className":3510,"code":3511,"language":22},[20],"# vi \u002Fetc\u002Fpam.d\u002Fsu\n\n--------\n# コメントアウトしているこの箇所を外す\n# auth            required        pam_wheel.so use_uid\n↓\nauth            required        pam_wheel.so use_uid\n--------\n",[24,3513,3511],{"__ignoreMap":26},[13,3515,3516],{},"コメントアウトされている箇所を外します。これでsuでrootになるためにwheelグループに属したユーザーから行うという制限ができました。",[538,3518,3519],{"id":3519},"develop用のssh鍵を作成",[13,3521,3522],{},"現在sshはパスワードでログインできますが、最終的にはパスワード認証を禁止にします。そしてよりセキュアな鍵認証式に変更します。基本的にログインの手間やセキュリティ的に鍵認証にした方がいいです。鍵認証の原理は今回は省きます。それではdevelop用の鍵を作成します。",[13,3524,3525],{},"クライアント側で秘密鍵と公開書きを作成します。",[17,3527,3530],{"className":3528,"code":3529,"language":22},[20],"% ssh-keygen\nGenerating public\u002Fprivate rsa key pair.\nEnter file in which to save the key (\u002FUsers\u002Fyou\u002F.ssh\u002Fid_rsa): #id_rsaというファイル名でよければそのままEnter\nEnter passphrase (empty for no passphrase): 　#パースフレーズを設定する場合は入力。なければEnter\nEnter same passphrase again:\n",[24,3531,3529],{"__ignoreMap":26},[13,3533,3534],{},"これで秘密鍵と公開鍵が生成されます。秘密鍵をクライアントに置いておき、公開鍵をサーバに転送します。",[17,3536,3539],{"className":3537,"code":3538,"language":22},[20],"scp .\u002Fid_rsa.pub develop@123.456.78.902:~\u002F\n",[24,3540,3538],{"__ignoreMap":26},[13,3542,3543,3544,3547],{},"サーバー側に戻ります。developのホームディレクトリに",[24,3545,3546],{},".ssh","というディレクトリがあるかを確認します。",[17,3549,3552],{"className":3550,"code":3551,"language":22},[20],"$ ls -a ~\u002F\n",[24,3553,3551],{"__ignoreMap":26},[13,3555,3556,3558,3559,3562],{},[24,3557,3546],{},"は隠しフォルダなので",[24,3560,3561],{},"ls -a","を使用します。無い場合はディレクトリを作成します。今回は無かったとしましょう。",[17,3564,3567],{"className":3565,"code":3566,"language":22},[20],"$ mkdir ~\u002F.ssh\n$ chmod 700 ~\u002F.ssh \n",[24,3568,3566],{"__ignoreMap":26},[13,3570,3571,3573,3574,3576],{},[24,3572,3546],{},"は権限を700にしましょう。そうで無いと権限エラーによってdevelopに対するsshを行うことができません。そしてクライアントから転送された公開側の名前を変更して",[24,3575,3546],{},"配下に置いておきます。",[17,3578,3581],{"className":3579,"code":3580,"language":22},[20],"$ cp ~\u002Fid_rsa.pub ~\u002F.ssh\u002Fauthorized_keys\n$ chmod 600 ~\u002F.ssh\u002Fauthorized_keys\n$ rm ~\u002Fid_rsa.pub\n",[24,3582,3580],{"__ignoreMap":26},[13,3584,3585,3586,3589],{},"ここでも",[24,3587,3588],{},"authorized_keys","は600権限を付けないと使用できません。",[811,3591,3593,3596,3603],{"className":3592},[1001,3395],[13,3594,3595],{},"authorized_keysにリネームする理由",[13,3597,3598,3599,2141],{},"The authorized_keys file in SSH specifies the SSH keys that can be used for logging into the user account for which the file is configured.(",[419,3600,3602],{"target":1010,"href":3601},"https:\u002F\u002Fwww.ssh.com\u002Facademy\u002Fssh\u002Fauthorized-keys-file#:~:text=The%20authorized_keys%20file%20in%20SSH,keys%20and%20needs%20proper%20management.","参照",[13,3604,3605],{},"authorized_keysファイルはその公開鍵に紐づく秘密鍵を用いてログインできるユーザーを特定します。簡単な話、authorized_keysにしておけばsshが自動的に鍵を判別してくれるのでその名前にしておく。",[13,3607,3608],{},"これでdevelopは鍵認証を用いてログインできる様になりました。",[538,3610,3611],{"id":3611},"rootのリモートログインとパスワード認証を禁止にする",[13,3613,3614,3615,3618],{},"developで鍵認証で接続できることを確認したら次はrootのリモートログインとパスワード認証を禁止にします。",[24,3616,3617],{},"\u002Fetc\u002Fssh\u002Fsshd_config","を編集していきます。",[17,3620,3623],{"className":3621,"code":3622,"language":22},[20],"# vi \u002Fetc\u002Fssh\u002Fsshd_config\n\n---------- 以下行を追加 -----------\nPermitRootLogin no\n---------- 以上行を追加 -----------\n\n--------------以下を変更--------------\nPasswordAuthentication yes\n#RSAAuthentication yes\n#PubkeyAuthentication yes\n--------------↓--------------\nPasswordAuthentication no\nRSAAuthentication yes\nPubkeyAuthentication yes\n------------------------------------------\n",[24,3624,3622],{"__ignoreMap":26},[13,3626,3627,3630,3631,3634,3635,3638,3639,3642],{},[24,3628,3629],{},"PermitRootLogin no","という記述をつけます。名の通りルートのログインを禁止にさせました。そして",[24,3632,3633],{},"PasswordAuthentication no","としパスワード認証を禁止。",[24,3636,3637],{},"RSAAuthentication yes","と",[24,3640,3641],{},"PubkeyAuthentication yes","のコメントアウトを外して鍵認証のみでつなげる様に明示的に設定します。",[13,3644,3645],{},"これであとはsshdをリスタートさせれば反映させます。",[17,3647,3650],{"className":3648,"code":3649,"language":22},[20],"# systemctl restart sshd\n",[24,3651,3649],{"__ignoreMap":26},[13,3653,3654],{},"試しにチェックしましょう。",[17,3656,3659],{"className":3657,"code":3658,"language":22},[20],"ssh root@xxx.xxx.xxx\nroot@xxx.xxx.xxx: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).\n\nssh develop@xxx.xxx.xxx\ndevelop@xxx.xxx.xxx: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).\n",[24,3660,3658],{"__ignoreMap":26},[13,3662,3663],{},"OKです。逆にdevelopで鍵認証で接続したのに弾かれたら設定をミスっています。",[811,3665,3668],{"className":3666},[1001,3667],"alert-danger","\n接続系を設定するときは2つのターミナルタブを開いていておき、片方はずっとroot化できる状態でサーバーにログインしておき、片方で接続テストをしましょう。もし失敗してどのユーザー・方法でも接続できなくなると二度とサーバーに接続できなくなります。\n",[538,3670,3671],{"id":3671},"sshの接続ポートを変更する",[13,3673,3674],{},"sshはデフォルトで22のポートで接続します。しかしこのポートはwell-knownポートとして知られており、攻撃者もデフォルトのポートを狙ってきます。そこでsshのポートを変更してさらに攻撃されにくくします。sshに接続するためにはユーザー名、鍵・パスワード、ポートが合わないといけないからです。",[13,3676,3677],{},"まずはsshdのデフォルトポートを変更します。変更するポート番号は49513～65535あたりから自由に設定しましょう。",[17,3679,3682],{"className":3680,"code":3681,"language":22},[20],"# vi \u002Fetc\u002Fssh\u002Fsshd_config\n--------------以下を変更--------------\n#Port 22    \n--------------↓--------------\nPort 49510\n------------------------------------------\n",[24,3683,3681],{"__ignoreMap":26},[13,3685,3686],{},"そしてfirewallが有効な場合、sshによる22は許可していてもカスタムしたポートは許可していないことがあります。これではカスタムポートへ接続する前にfirewallで弾かれるので設定します。",[13,3688,3689],{},"まずはsshdのfirewallの設定ファイルをコピーします。",[17,3691,3694],{"className":3692,"code":3693,"language":22},[20],"cp \u002Fusr\u002Flib\u002Ffirewalld\u002Fservices\u002Fssh.xml \u002Fetc\u002Ffirewalld\u002Fservices\u002F\n",[24,3695,3693],{"__ignoreMap":26},[13,3697,3698,3701,3702,3705],{},[24,3699,3700],{},"\u002Fetc\u002Ffirewalld\u002Fservices\u002F","にて追加の設定を行うことができます。コピーした",[24,3703,3704],{},"ssh.xml","を編集します。",[17,3707,3710],{"className":3708,"code":3709,"language":22},[20],"# vi \u002Fetc\u002Ffirewalld\u002Fservices\u002F\n\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n  \u003Cservice>\n    \u003Cshort>SSH\u003C\u002Fshort>\n    \u003Cdescription>Secure Shell (SSH) is a protocol ...\u003C\u002Fdescription>\n    \u003Cport protocol=\"tcp\" port=\"22\"\u002F>\n    \u003Cport protocol=\"tcp\" port=\"49510\"\u002F> # 追加\n\u003C\u002Fservice>\n",[24,3711,3709],{"__ignoreMap":26},[13,3713,3714],{},"上記の設定でsshサービスによる49510ポートの接続が許可されました。\nそして設定が終わったらfirewallをリロードします。またsshdも設定を変えたのでリロードします。",[17,3716,3719],{"className":3717,"code":3718,"language":22},[20],"# firewall-cmd --reload\n# systemctl restart sshd\n",[24,3720,3718],{"__ignoreMap":26},[13,3722,3723],{},"これでOKです。ポートをカスタムのものに指定して接続して通れば問題ありません。そして22（デフォルト）で接続して弾かれるかも確認しましょう。",[466,3725,3727],{"id":3726},"以上","以上！",[13,3729,3730],{},"以上がsshの最低限必要な設定です。インフラ系では結構基礎的な内容だそうです。sshは狙われており、適切に設定することで安全に使用することができます。",{"title":26,"searchDepth":98,"depth":98,"links":3732},[3733,3734,3742],{"id":3375,"depth":80,"text":3376},{"id":3403,"depth":80,"text":3403,"children":3735},[3736,3739,3740,3741],{"id":3423,"depth":98,"text":3423,"children":3737},[3738],{"id":3488,"depth":116,"text":3488},{"id":3519,"depth":98,"text":3519},{"id":3611,"depth":98,"text":3611},{"id":3671,"depth":98,"text":3671},{"id":3726,"depth":80,"text":3727},[396],"2021-04-25",{},"\u002Farticles\u002Fvps-ssh-first",{"title":3332,"description":3332},"articles\u002Fvps-ssh-first",[3043,407,3750],"network","_mix\u002Fcyber-security-3400657_640.jpg","0p7STSITYfCR0ETcCcYcMeNhmXEtc_cRA-W1MKHcN_8",{"id":3754,"title":3755,"body":3756,"category":4175,"createdAt":4176,"description":3755,"extension":399,"index":400,"meta":4177,"navigation":402,"path":4178,"publish":402,"seo":4179,"series":400,"seriesTitle":400,"stem":4180,"tag":4181,"thumbnail":4182,"updatedAt":400,"__hash__":4183},"articles\u002Farticles\u002Fbootstrap-vue-asset-save.md","bootstrap-vue,iconのバンドルを減らす。開発時に気をつけたいbootstrap-vueの使い方",{"type":10,"value":3757,"toc":4165},[3758,3761,3764,3767,3770,3799,3802,3805,3814,3859,3866,3873,3981,3988,3991,4001,4093,4096,4099,4106,4135,4142,4146,4149,4152,4159,4162],[13,3759,3760],{},"こんにちはjunです。Nuxt.js or Vue.js x Bootstrap-vue の構成を使って管理画面UIを作成する機会が多く、それらの融合性や使いやすさに虜になっています。しかしやけにビルドの時間がかかるなーと思っていたら、なんとバンドルサイズが1MBを超いたという事実に気付き、慌てて対処しました。（他のライブラリもあります）",[13,3762,3763],{},"今回はそのbootstrap-vueを使ったアプリのバンドルサイズを減らす方法として、開発時から気をつけたいことについて述べます。",[466,3765,3766],{"id":3766},"そのままいれるな",[13,3768,3769],{},"ドキュメントの最初の方にある通り、bootstrap-vueとboo-strap-iconを以下のようにいれるとかなりサイズを食います。",[17,3771,3773],{"className":2885,"code":3772,"language":2887,"meta":26,"style":26},"import Vue from 'vue'\nimport { BootstrapVue, IconsPlugin } from 'bootstrap-vue';\n\nVue.use(BootstrapVue);\nVue.use(IconsPlugin);\n",[24,3774,3775,3780,3785,3789,3794],{"__ignoreMap":26},[57,3776,3777],{"class":59,"line":60},[57,3778,3779],{},"import Vue from 'vue'\n",[57,3781,3782],{"class":59,"line":80},[57,3783,3784],{},"import { BootstrapVue, IconsPlugin } from 'bootstrap-vue';\n",[57,3786,3787],{"class":59,"line":98},[57,3788,1133],{"emptyLinePlaceholder":402},[57,3790,3791],{"class":59,"line":116},[57,3792,3793],{},"Vue.use(BootstrapVue);\n",[57,3795,3796],{"class":59,"line":122},[57,3797,3798],{},"Vue.use(IconsPlugin);\n",[13,3800,3801],{},"理由はnode_modulesのBootstrap-vueの全てを入れているためです。別に全てのbootstrapモジュールを使用しているならば問題ありませんが、基本的にそんな状況はないと思います。importでモジュールを読み込む時は原則、必要なものだけインポートして置くことがベストです。それを意識するだけでも自然とバンドルサイズが小さくなります。",[466,3803,3804],{"id":3804},"必要なものだけ指定する",[13,3806,3807,3808,3813],{},"やり方は",[419,3809,3812],{"href":3810,"rel":3811},"https:\u002F\u002Fbootstrap-vue.org\u002Fdocs#vue-cli-3",[423],"公式ドキュメント","にもありますが、必要なコンポーネントだけ使用する場合は以下のようにします。",[17,3815,3818],{"className":2885,"code":3816,"filename":3817,"language":2887,"meta":26,"style":26},"import {  \n    BButton,BSpinner,BTable,BAlert\n} from 'bootstrap-vue';\n\nVue.component('b-button',BButton);\nVue.component('b-spinner',BSpinner);\nVue.component('b-table',BTable);\nVue.component('b-alert',BAlert);\n","parent.js",[24,3819,3820,3825,3830,3835,3839,3844,3849,3854],{"__ignoreMap":26},[57,3821,3822],{"class":59,"line":60},[57,3823,3824],{},"import {  \n",[57,3826,3827],{"class":59,"line":80},[57,3828,3829],{},"    BButton,BSpinner,BTable,BAlert\n",[57,3831,3832],{"class":59,"line":98},[57,3833,3834],{},"} from 'bootstrap-vue';\n",[57,3836,3837],{"class":59,"line":116},[57,3838,1133],{"emptyLinePlaceholder":402},[57,3840,3841],{"class":59,"line":122},[57,3842,3843],{},"Vue.component('b-button',BButton);\n",[57,3845,3846],{"class":59,"line":136},[57,3847,3848],{},"Vue.component('b-spinner',BSpinner);\n",[57,3850,3851],{"class":59,"line":150},[57,3852,3853],{},"Vue.component('b-table',BTable);\n",[57,3855,3856],{"class":59,"line":155},[57,3857,3858],{},"Vue.component('b-alert',BAlert);\n",[13,3860,3861,3862,3865],{},"上記はapp.jsのような元締めのファイルでグローバルコンポーネントとして登録する場合です。こうすれば配下のファイルで",[24,3863,3864],{},"\u003Cb-badge>\u003C\u002Fb-badge>","という風に使用できます。",[13,3867,3868,3869,3872],{},"グローバルでなく、局所的に単体のvueファイルで使用したい場合は",[24,3870,3871],{},"Vue.component()","でなく、そのvueファイルにいて以下のようにします。",[17,3874,3877],{"className":788,"code":3875,"filename":3876,"language":790,"meta":26,"style":26},"\u003Ctemplate>\n  \u003CBButton variant=\"danger\">Clikc\u003C\u002FBButton>\n\u003C\u002Ftemplate>\n\nimport { BButton } from 'bootstrap-vue';\n\u003Cscript>\nexport default{\n  components:{\n    BButton\n  }\n}\n\u003C\u002Fscript>\n","child.vue",[24,3878,3879,3887,3918,3926,3930,3935,3943,3951,3959,3964,3969,3973],{"__ignoreMap":26},[57,3880,3881,3883,3885],{"class":59,"line":60},[57,3882,797],{"class":63},[57,3884,801],{"class":800},[57,3886,804],{"class":63},[57,3888,3889,3892,3895,3898,3900,3902,3905,3907,3909,3912,3914,3916],{"class":59,"line":80},[57,3890,3891],{"class":63},"  \u003C",[57,3893,3894],{"class":800},"BButton",[57,3896,3897],{"class":86}," variant",[57,3899,826],{"class":63},[57,3901,64],{"class":63},[57,3903,3904],{"class":67},"danger",[57,3906,64],{"class":63},[57,3908,1196],{"class":63},[57,3910,3911],{"class":73},"Clikc",[57,3913,904],{"class":63},[57,3915,3894],{"class":800},[57,3917,804],{"class":63},[57,3919,3920,3922,3924],{"class":59,"line":98},[57,3921,904],{"class":63},[57,3923,801],{"class":800},[57,3925,804],{"class":63},[57,3927,3928],{"class":59,"line":116},[57,3929,1133],{"emptyLinePlaceholder":402},[57,3931,3932],{"class":59,"line":122},[57,3933,3934],{"class":73},"import { BButton } from 'bootstrap-vue';\n",[57,3936,3937,3939,3941],{"class":59,"line":136},[57,3938,797],{"class":63},[57,3940,923],{"class":800},[57,3942,804],{"class":63},[57,3944,3945,3947,3949],{"class":59,"line":150},[57,3946,931],{"class":86},[57,3948,934],{"class":930},[57,3950,77],{"class":63},[57,3952,3953,3956],{"class":59,"line":155},[57,3954,3955],{"class":104},"  components",[57,3957,3958],{"class":63},":{\n",[57,3960,3961],{"class":59,"line":169},[57,3962,3963],{"class":73},"    BButton\n",[57,3965,3966],{"class":59,"line":183},[57,3967,3968],{"class":63},"  }\n",[57,3970,3971],{"class":59,"line":188},[57,3972,975],{"class":63},[57,3974,3975,3977,3979],{"class":59,"line":202},[57,3976,904],{"class":63},[57,3978,923],{"class":800},[57,3980,804],{"class":63},[13,3982,3983,3984,3987],{},"グローバルでやっていたものを",[24,3985,3986],{},"components","配下で入れてあげるだけです。",[538,3989,3990],{"id":3990},"アイコンも同様",[13,3992,3993,3994,3997,3998,4000],{},"アイコンが豊富な故にサイズも大きいです。",[24,3995,3996],{},"Vue.use(IconsPlugin);","とすると全てのアイコンがインポートされるので、これも以下のように",[24,3999,3986],{},"で使用します。",[17,4002,4004],{"className":788,"code":4003,"language":790,"meta":26,"style":26},"\u003Ctemplate>\n  \u003CBIconXCircleFill variant=\"danger\">\n\u003C\u002Ftemplate>\n\nimport { BIconXCircleFill } from 'bootstrap-vue';\n\u003Cscript>\nexport default{\n  components:{\n    BIconXCircleFill\n  }\n}\n\u003C\u002Fscript>\n",[24,4005,4006,4014,4033,4041,4045,4050,4058,4066,4072,4077,4081,4085],{"__ignoreMap":26},[57,4007,4008,4010,4012],{"class":59,"line":60},[57,4009,797],{"class":63},[57,4011,801],{"class":800},[57,4013,804],{"class":63},[57,4015,4016,4018,4021,4023,4025,4027,4029,4031],{"class":59,"line":80},[57,4017,3891],{"class":63},[57,4019,4020],{"class":800},"BIconXCircleFill",[57,4022,3897],{"class":86},[57,4024,826],{"class":63},[57,4026,64],{"class":63},[57,4028,3904],{"class":67},[57,4030,64],{"class":63},[57,4032,804],{"class":63},[57,4034,4035,4037,4039],{"class":59,"line":98},[57,4036,904],{"class":63},[57,4038,801],{"class":800},[57,4040,804],{"class":63},[57,4042,4043],{"class":59,"line":116},[57,4044,1133],{"emptyLinePlaceholder":402},[57,4046,4047],{"class":59,"line":122},[57,4048,4049],{"class":73},"import { BIconXCircleFill } from 'bootstrap-vue';\n",[57,4051,4052,4054,4056],{"class":59,"line":136},[57,4053,797],{"class":63},[57,4055,923],{"class":800},[57,4057,804],{"class":63},[57,4059,4060,4062,4064],{"class":59,"line":150},[57,4061,931],{"class":86},[57,4063,934],{"class":930},[57,4065,77],{"class":63},[57,4067,4068,4070],{"class":59,"line":155},[57,4069,3955],{"class":104},[57,4071,3958],{"class":63},[57,4073,4074],{"class":59,"line":169},[57,4075,4076],{"class":73},"    BIconXCircleFill\n",[57,4078,4079],{"class":59,"line":183},[57,4080,3968],{"class":63},[57,4082,4083],{"class":59,"line":188},[57,4084,975],{"class":63},[57,4086,4087,4089,4091],{"class":59,"line":202},[57,4088,904],{"class":63},[57,4090,923],{"class":800},[57,4092,804],{"class":63},[13,4094,4095],{},"アイコンはグローバルに登録するよりも、必要になったら都度入れとく方がいいです。",[466,4097,4098],{"id":4098},"適宜必要なモジュールを入れる",[13,4100,4101,4102,4105],{},"BadgeやButtonは上記のような",[24,4103,4104],{},"Vue.commponent","で十分ですが、ModalやCollapseは追加のプラグインなどが必要です。例えばModalは以下のモジュールをセットしておく必要があります。",[17,4107,4109],{"className":2885,"code":4108,"language":2887,"meta":26,"style":26},"import { BModal,VBModal,ModalPlugin } from 'bootstrap-vue';\n\nVue.use(ModalPlugin);\nVue.component('b-modal',BModal);\nVue.directive('b-modal', VBModal)\n",[24,4110,4111,4116,4120,4125,4130],{"__ignoreMap":26},[57,4112,4113],{"class":59,"line":60},[57,4114,4115],{},"import { BModal,VBModal,ModalPlugin } from 'bootstrap-vue';\n",[57,4117,4118],{"class":59,"line":80},[57,4119,1133],{"emptyLinePlaceholder":402},[57,4121,4122],{"class":59,"line":98},[57,4123,4124],{},"Vue.use(ModalPlugin);\n",[57,4126,4127],{"class":59,"line":116},[57,4128,4129],{},"Vue.component('b-modal',BModal);\n",[57,4131,4132],{"class":59,"line":122},[57,4133,4134],{},"Vue.directive('b-modal', VBModal)\n",[13,4136,4137,4138,4141],{},"こうすることでModalのコンポーネントや",[24,4139,4140],{},"this.$bvModal.show('select-file-modal');","のようなディレクティブや動きを実装できます。",[538,4143,4145],{"id":4144},"どこにかいてある","どこにかいてある？",[13,4147,4148],{},"それぞのコンポーネントのドキュメントの最後の方にある、Component referenceにあります。さらにいうとその下のImporting individual componentsに詳しく、依存関係が書いてあるのでその通りにインポートしてあげましょう。",[466,4150,4151],{"id":4151},"開発時初期から個別インポートを考える",[13,4153,4154,4155,4158],{},"もしこれに気づかず後から単体読み込みにしようと思ったら大変なことになります。 ",[727,4156,4157],{},"必要なコンポーネントの洗い出しとそのチェックが必要になるからです。"," ファイル検索でなんとからならくもないですが、後からの修正はかなり大変です。",[13,4160,4161],{},"であれば最初から個別のインポートで行っていくほうが、必要になった時にエラーで教えてくれるので対処がしやすいです。端末の性能や通信速度が上がっているとはいえ、まだ1Mを超えるのは気が引けます。塵も積もれば山となる精神で不要なインポートは避けましょう。",[390,4163,4164],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}",{"title":26,"searchDepth":98,"depth":98,"links":4166},[4167,4168,4171,4174],{"id":3766,"depth":80,"text":3766},{"id":3804,"depth":80,"text":3804,"children":4169},[4170],{"id":3990,"depth":98,"text":3990},{"id":4098,"depth":80,"text":4098,"children":4172},[4173],{"id":4144,"depth":98,"text":4145},{"id":4151,"depth":80,"text":4151},[396],"2021-03-29",{},"\u002Farticles\u002Fbootstrap-vue-asset-save",{"title":3755,"description":3755},"articles\u002Fbootstrap-vue-asset-save",[1340,790],"_mix\u002Fsch-2021-03-29-22.48.45.png","eqyqBM0LwYz49ZyHXkO-fsX2wiHR3pqkEe-fvzvZ4Hg",{"id":4185,"title":4186,"body":4187,"category":4680,"createdAt":4681,"description":4186,"extension":399,"index":400,"meta":4682,"navigation":402,"path":4683,"publish":402,"seo":4684,"series":400,"seriesTitle":400,"stem":4685,"tag":4686,"thumbnail":4688,"updatedAt":400,"__hash__":4689},"articles\u002Farticles\u002Flaravel-mixx-heroku-fail.md","Laravel mixのアセットがherokuで動かない時の対処法",{"type":10,"value":4188,"toc":4671},[4189,4192,4195,4198,4202,4209,4212,4215,4219,4233,4237,4240,4243,4247,4261,4276,4289,4300,4304,4311,4321,4485,4488,4635,4644,4647,4650,4661,4668],[13,4190,4191],{},"こんにちはjunです。最近よくLaravelの開発を行っていて、プロトタイプとかをherokuにあげるのですがLaravel mixのアセットが動かない（main.jsが404）でちょっと困ったので、今回はその対処法を書きたいと思います。",[13,4193,4194],{},"Laravel\bmixの説明を軽くするので、さっさと解決策みたいひとは「ビルドアセットをgitignoreしていた」から参照してください。なお使用環境は以下の通りです。",[13,4196,4197],{},"Laravel 6\nPHP 7.4\nNode.js 12.21（開発）\nNode.js 14.16（heroku）",[466,4199,4201],{"id":4200},"laravel-mixって","Laravel Mixって？",[13,4203,4204,4205,4208],{},"Laravel MixはLaravelが公式に出している、JS・CSSのアセットコンパイラとモジュールバンドラです。",[24,4206,4207],{},"resource","配下のjsやsassをコンパイルしたり、バンドルするwebpackの設定がすでにLaraveに最適化されています。webpackをほとんど触らずともvue、react、sass、そのほかjsライブラリをもちいた開発ができます。",[13,4210,4211],{},"resource 配下のアセットはLaravel Mixによってpublic配下に自動的に吐かれるようになっています。開発時にはnpm run watchをしていますが、よく見るとpublic配下に開発用ビルドしたアセットファイルが逐一置かれています。",[13,4213,4214],{},"大体のLaraveフロントエンドではこのMixを用いて開発していることが多いと思います。しかしherokuや本番環境にあげた時にちょっと問題がおきました。",[466,4216,4218],{"id":4217},"mainjs-is-not-found","main.js is not found",[13,4220,4221,4222,4224,4225,4228,4229,4232],{},"Laravel Mixでフロントを構築して、ある程度バック含めて完成したのでherokuにデプロイしてみました。ビルドは普通に成功し、満を辞して目的の画面をみても",[24,4223,4218],{},"となってしましました。",[24,4226,4227],{},"main.js","にはビルドしたvue.jsのプロジェクトがあるはずなのにと思い、サーバーに入って",[24,4230,4231],{},"public\u002Fjs","配下をみてみたところ、何もありませんでした。",[538,4234,4236],{"id":4235},"原因その１ビルドアセットをgitignoreしていた","原因その１：ビルドアセットをgitignoreしていた",[13,4238,4239],{},"ビルドされたアセットはpublic配下に置かれますが、ファイル量が膨大だったり、開発モードと本番モードが混じる可能性があること、あとどうせコロコロ変わるのでバージョン管理から外そうと思い、public\u002Fjsとpublic\u002Fcssはバージョン管理から外していました。",[13,4241,4242],{},"herokuは基本的にgitを用いてデプロイします。管理対象外のファイルはもちろんherokuサーバー上にないので、アセットがあるはずもありません。であればherokuにデプロイした時にLaravel Mixを叩いて、ビルドすれば解決します。",[538,4244,4246],{"id":4245},"原因その２laravel-mixがdevdependencies","原因その２：Laravel MixがdevDependencies",[13,4248,4249,4250,3463,4253,4256,4257,4260],{},"Herokuでは",[24,4251,4252],{},"NODE_ENV",[24,4254,4255],{},"production","（本番環境）で定義されていると、",[24,4258,4259],{},"devDependencies","がプリーニングされてしまい、ビルドがうまく走りません。",[811,4262,4264,4265,4268],{"className":4263},[1001,1002],"\nデフォルトでは、Heroku は ​package.json​ の ​dependencies​ および ​devDependencies​ に記載されているすべての依存関係をインストールします。\n",[13,4266,4267],{},"インストールおよび​ビルドステップ​を実行した後、 Heroku はアプリケーションをデプロイする前に、​devDependencies​ に宣言されているパッケージを取り除きます。",[13,4269,4270],{},[1005,4271,4272],{},[419,4273,4274],{"href":4274,"rel":4275},"https:\u002F\u002Fdevcenter.heroku.com\u002Fja\u002Farticles\u002Fnodejs-support#skip-pruning",[423],[13,4277,4278,4279,4284,4285,4288],{},"インストールおよび​",[419,4280,4283],{"href":4281,"rel":4282},"https:\u002F\u002Fdevcenter.heroku.com\u002Farticles\u002Fnodejs-support#heroku-specific-build-steps",[423],"ビルドステップ","​を実行した後、 Heroku はアプリケーションをデプロイする前に、",[24,4286,4287],{},"​devDependencies​"," に宣言されているパッケージを取り除きます。",[13,4290,4291,4292,4295,4296,4299],{},"対策としては",[24,4293,4294],{},"package.json","をいじって",[24,4297,4298],{},"dependencies","に移動します。デフォルトでLaravel Mixはdevの方にインストールされます。それを移動してプッシュすればひとまずLaravel Mixのビルドがうまくいきます。",[538,4301,4303],{"id":4302},"原因その３npm-run-productionを動かすように設定","原因その３：npm run productionを動かすように設定",[13,4305,4306,4307,4310],{},"その２に加えて、今度はherokuデプロイの際にLaravel Mixのビルドスクリプトがキックされるように設定します。herokuはNode.jsのプロジェクトがあり、そのスクリプトに",[24,4308,4309],{},"npm run build","がある場合はそれを実行してくれます。",[13,4312,4313,4314,4316,4317,4320],{},"初期の",[24,4315,4294],{},"のスクリプトは",[24,4318,4319],{},"build","がないので、mixが実行されません。",[17,4322,4325],{"className":51,"code":4323,"filename":4324,"language":53,"meta":26,"style":26},"\"scripts\": {\n    \"dev\": \"npm run development\",\n    \"development\": \"cross-env NODE_ENV=development node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\",\n    \"watch\": \"npm run development -- --watch\",\n    \"watch-poll\": \"npm run watch -- --watch-poll\",\n    \"hot\": \"cross-env NODE_ENV=development node_modules\u002Fwebpack-dev-server\u002Fbin\u002Fwebpack-dev-server.js --inline --hot --disable-host-check --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\",\n    \"prod\": \"npm run production\",\n    \"production\": \"cross-env NODE_ENV=production node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --no-progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\"\n},\n","pakage.json",[24,4326,4327,4340,4361,4381,4401,4421,4441,4461,4479],{"__ignoreMap":26},[57,4328,4329,4331,4334,4336,4338],{"class":59,"line":60},[57,4330,64],{"class":63},[57,4332,4333],{"class":67},"scripts",[57,4335,64],{"class":63},[57,4337,74],{"class":73},[57,4339,77],{"class":63},[57,4341,4342,4344,4347,4349,4351,4354,4357,4359],{"class":59,"line":80},[57,4343,83],{"class":63},[57,4345,4346],{"class":86},"dev",[57,4348,64],{"class":63},[57,4350,92],{"class":63},[57,4352,4353],{"class":63}," \"",[57,4355,4356],{"class":67},"npm run development",[57,4358,64],{"class":63},[57,4360,262],{"class":63},[57,4362,4363,4365,4368,4370,4372,4374,4377,4379],{"class":59,"line":98},[57,4364,83],{"class":63},[57,4366,4367],{"class":86},"development",[57,4369,64],{"class":63},[57,4371,92],{"class":63},[57,4373,4353],{"class":63},[57,4375,4376],{"class":67},"cross-env NODE_ENV=development node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js",[57,4378,64],{"class":63},[57,4380,262],{"class":63},[57,4382,4383,4385,4388,4390,4392,4394,4397,4399],{"class":59,"line":116},[57,4384,83],{"class":63},[57,4386,4387],{"class":86},"watch",[57,4389,64],{"class":63},[57,4391,92],{"class":63},[57,4393,4353],{"class":63},[57,4395,4396],{"class":67},"npm run development -- --watch",[57,4398,64],{"class":63},[57,4400,262],{"class":63},[57,4402,4403,4405,4408,4410,4412,4414,4417,4419],{"class":59,"line":122},[57,4404,83],{"class":63},[57,4406,4407],{"class":86},"watch-poll",[57,4409,64],{"class":63},[57,4411,92],{"class":63},[57,4413,4353],{"class":63},[57,4415,4416],{"class":67},"npm run watch -- --watch-poll",[57,4418,64],{"class":63},[57,4420,262],{"class":63},[57,4422,4423,4425,4428,4430,4432,4434,4437,4439],{"class":59,"line":136},[57,4424,83],{"class":63},[57,4426,4427],{"class":86},"hot",[57,4429,64],{"class":63},[57,4431,92],{"class":63},[57,4433,4353],{"class":63},[57,4435,4436],{"class":67},"cross-env NODE_ENV=development node_modules\u002Fwebpack-dev-server\u002Fbin\u002Fwebpack-dev-server.js --inline --hot --disable-host-check --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js",[57,4438,64],{"class":63},[57,4440,262],{"class":63},[57,4442,4443,4445,4448,4450,4452,4454,4457,4459],{"class":59,"line":150},[57,4444,83],{"class":63},[57,4446,4447],{"class":86},"prod",[57,4449,64],{"class":63},[57,4451,92],{"class":63},[57,4453,4353],{"class":63},[57,4455,4456],{"class":67},"npm run production",[57,4458,64],{"class":63},[57,4460,262],{"class":63},[57,4462,4463,4465,4467,4469,4471,4473,4476],{"class":59,"line":155},[57,4464,83],{"class":63},[57,4466,4255],{"class":86},[57,4468,64],{"class":63},[57,4470,92],{"class":63},[57,4472,4353],{"class":63},[57,4474,4475],{"class":67},"cross-env NODE_ENV=production node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --no-progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js",[57,4477,4478],{"class":63},"\"\n",[57,4480,4481,4483],{"class":59,"line":169},[57,4482,259],{"class":63},[57,4484,262],{"class":73},[13,4486,4487],{},"ちょうどprodというのがあるのでbuildに変えてやりましょう。",[17,4489,4491],{"className":51,"code":4490,"filename":4324,"language":53,"meta":26,"style":26},"\"scripts\": {\n    \"dev\": \"npm run development\",\n    \"development\": \"cross-env NODE_ENV=development node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\",\n    \"watch\": \"npm run development -- --watch\",\n    \"watch-poll\": \"npm run watch -- --watch-poll\",\n    \"hot\": \"cross-env NODE_ENV=development node_modules\u002Fwebpack-dev-server\u002Fbin\u002Fwebpack-dev-server.js --inline --hot --disable-host-check --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\",\n    \"build\": \"npm run production\",\n    \"production\": \"cross-env NODE_ENV=production node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --no-progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\"\n},\n",[24,4492,4493,4505,4523,4541,4559,4577,4595,4613,4629],{"__ignoreMap":26},[57,4494,4495,4497,4499,4501,4503],{"class":59,"line":60},[57,4496,64],{"class":63},[57,4498,4333],{"class":67},[57,4500,64],{"class":63},[57,4502,74],{"class":73},[57,4504,77],{"class":63},[57,4506,4507,4509,4511,4513,4515,4517,4519,4521],{"class":59,"line":80},[57,4508,83],{"class":63},[57,4510,4346],{"class":86},[57,4512,64],{"class":63},[57,4514,92],{"class":63},[57,4516,4353],{"class":63},[57,4518,4356],{"class":67},[57,4520,64],{"class":63},[57,4522,262],{"class":63},[57,4524,4525,4527,4529,4531,4533,4535,4537,4539],{"class":59,"line":98},[57,4526,83],{"class":63},[57,4528,4367],{"class":86},[57,4530,64],{"class":63},[57,4532,92],{"class":63},[57,4534,4353],{"class":63},[57,4536,4376],{"class":67},[57,4538,64],{"class":63},[57,4540,262],{"class":63},[57,4542,4543,4545,4547,4549,4551,4553,4555,4557],{"class":59,"line":116},[57,4544,83],{"class":63},[57,4546,4387],{"class":86},[57,4548,64],{"class":63},[57,4550,92],{"class":63},[57,4552,4353],{"class":63},[57,4554,4396],{"class":67},[57,4556,64],{"class":63},[57,4558,262],{"class":63},[57,4560,4561,4563,4565,4567,4569,4571,4573,4575],{"class":59,"line":122},[57,4562,83],{"class":63},[57,4564,4407],{"class":86},[57,4566,64],{"class":63},[57,4568,92],{"class":63},[57,4570,4353],{"class":63},[57,4572,4416],{"class":67},[57,4574,64],{"class":63},[57,4576,262],{"class":63},[57,4578,4579,4581,4583,4585,4587,4589,4591,4593],{"class":59,"line":136},[57,4580,83],{"class":63},[57,4582,4427],{"class":86},[57,4584,64],{"class":63},[57,4586,92],{"class":63},[57,4588,4353],{"class":63},[57,4590,4436],{"class":67},[57,4592,64],{"class":63},[57,4594,262],{"class":63},[57,4596,4597,4599,4601,4603,4605,4607,4609,4611],{"class":59,"line":150},[57,4598,83],{"class":63},[57,4600,4319],{"class":86},[57,4602,64],{"class":63},[57,4604,92],{"class":63},[57,4606,4353],{"class":63},[57,4608,4456],{"class":67},[57,4610,64],{"class":63},[57,4612,262],{"class":63},[57,4614,4615,4617,4619,4621,4623,4625,4627],{"class":59,"line":155},[57,4616,83],{"class":63},[57,4618,4255],{"class":86},[57,4620,64],{"class":63},[57,4622,92],{"class":63},[57,4624,4353],{"class":63},[57,4626,4475],{"class":67},[57,4628,4478],{"class":63},[57,4630,4631,4633],{"class":59,"line":169},[57,4632,259],{"class":63},[57,4634,262],{"class":73},[13,4636,4637,4638,4640,4641,4643],{},"こうすれば",[24,4639,4309],{}," が ",[24,4642,4456],{}," を実行して本番用のアセットを作ってくれます。これで解決です。",[466,4645,4646],{"id":4646},"ビルドアセットがうまく出力されない時に考えるべきこと",[13,4648,4649],{},"今回はいくつかのファイルをビルドする必要があり、デプロイ作業が自動で行われる状況でした。開発環境ではアセットがあるのに、本番だとなくなっているという場合は以下のことを考えましょう。",[441,4651,4652,4655,4658],{},[444,4653,4654],{},"ファイルはある？（サーバーに入ってlsしてチェック）",[444,4656,4657],{},"ビルドに必要なモジュールがインストールされているか？",[444,4659,4660],{},"ビルドスクリプトはキックされているか？",[13,4662,4663,4664,4667],{},"大体こうゆうときはビルドがうまくいっていないので、そこからチェックしていくといいです。ちなみに私が検索した時のワードは ",[727,4665,4666],{},"「heroku laravel mix ビルド」"," でした。それではまた。",[390,4669,4670],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":26,"searchDepth":98,"depth":98,"links":4672},[4673,4674,4679],{"id":4200,"depth":80,"text":4201},{"id":4217,"depth":80,"text":4218,"children":4675},[4676,4677,4678],{"id":4235,"depth":98,"text":4236},{"id":4245,"depth":98,"text":4246},{"id":4302,"depth":98,"text":4303},{"id":4646,"depth":80,"text":4646},[3033],"2021-03-27",{},"\u002Farticles\u002Flaravel-mixx-heroku-fail",{"title":4186,"description":4186},"articles\u002Flaravel-mixx-heroku-fail",[3041,4687,3042,407],"webpack","_mix\u002F8_laravel-icon.3f70de72f3.jpg","zvECdzfredOonr51btVrUrKqFCKw247gmW-jXba47XU",{"id":4691,"title":4692,"body":4693,"category":6201,"createdAt":6202,"description":4692,"extension":399,"index":400,"meta":6203,"navigation":402,"path":6204,"publish":402,"seo":6205,"series":400,"seriesTitle":400,"stem":6206,"tag":6207,"thumbnail":6208,"updatedAt":400,"__hash__":6209},"articles\u002Farticles\u002Fgoogle-map-vuejs.md","Google Maps APIとVue.js を使ってGoogle Map 上にルートを描画する",{"type":10,"value":4694,"toc":6188},[4695,4698,4702,4711,4720,4724,4727,4731,4740,4746,4750,4766,5072,5078,5081,5085,5088,5113,5116,5125,5136,5189,5192,5195,5202,5487,5495,5498,5506,5521,5527,5530,5535,5570,5573,5576,5756,5762,5765,5790,5793,5796,5799,5802,5809,5812,5931,5941,5944,5947,5950,5953,5957,5960,5963,5978,5985,6148,6166,6169,6172,6176,6179,6182,6185],[13,4696,4697],{},"こんにちはjunです。自主開発で地図上に描画する機能を実装するときに「Vueを用いてGoogle Map上にラインを描画できないかな？」と思いその方法を調べた結果、なんとか実装できました。今回の記事ではGoogle Map APIを用いてVue（またはVanilla js）で地図の表示とラインの描画機能を実装したいと思います。Google Map APIの取得の仕方とVue.jsのインストールなどはある程度省きます。",[466,4699,4701],{"id":4700},"google-map-apiを取得する","Google Map APIを取得する",[13,4703,4704,4705,4710],{},"詳細な取得方法は省略しますが、最低限必要なものだけ解説します。自前のGoogleアカウントを用いて、",[419,4706,4709],{"href":4707,"rel":4708},"https:\u002F\u002Fcloud.google.com\u002Fmaps-platform?hl=ja",[423],"Google Cloud Platform","でGoogle Map APIを取得します。",[13,4712,4713,4714,4719],{},"毎月$200分までのリクエストは無料なので開発環境には十分でしょう。そしてweb上にGoogleMapを表示するには",[419,4715,4718],{"href":4716,"rel":4717},"https:\u002F\u002Fconsole.cloud.google.com\u002Fapis\u002Flibrary\u002Fmaps-backend.googleapis.com?",[423],"Maps JavaScript API","が必要になります。",[778,4721],{":src":4722,":width":4723,":center":782},"'_mix\u002Fsch-2021-03-07-17.15.12.png'","'500px'",[13,4725,4726],{},"このAPIを有効にしてAPIキーを手に入れます。まずAPI側のセットアップは以上です。",[466,4728,4730],{"id":4729},"vue側の準備","Vue側の準備",[13,4732,4733,4734,4739],{},"今回は",[419,4735,4738],{"href":4736,"rel":4737},"https:\u002F\u002Fcli.vuejs.org\u002F",[423],"Vue CLI","を用いて開発します。vue cliを用いて適当にプロジェクトを作成します。",[17,4741,4744],{"className":4742,"code":4743,"language":22},[20],"$ vue create vue_map\n",[24,4745,4743],{"__ignoreMap":26},[538,4747,4749],{"id":4748},"api-用のjsを読み込む","api 用のJSを読み込む",[13,4751,4752,4755,4756,3638,4758,4761,4762,4765],{},[24,4753,4754],{},"\u002Fsrc"," 配下にある",[24,4757,4227],{},[24,4759,4760],{},"\u002Fpublic","配下にある",[24,4763,4764],{},"index.html","に以下のコードを挿入します。",[17,4767,4770],{"className":1425,"code":4768,"filename":4769,"language":1339,"meta":26,"style":26},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"\">\n  \u003Chead>\n    \u003Cmeta charset=\"utf-8\">\n    \u003Cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    \u003Cmeta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    \u003Clink rel=\"icon\" href=\"\u003C%= BASE_URL %>favicon.ico\">\n    \n　　\u003C!-- このスクリプトを挿入-->\n    \u003Cscript src=\"http:\u002F\u002Fmaps.google.com\u002Fmaps\u002Fapi\u002Fjs?key=YOUR_API_KEY&language=ja\">\u003C\u002Fscript>\n   \n　　 \u003Ctitle>\u003C%= htmlWebpackPlugin.options.title %>\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cnoscript>\n      \u003Cstrong>We're sorry but \u003C%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.\u003C\u002Fstrong>\n    \u003C\u002Fnoscript>\n    \u003Cdiv id=\"app\">\u003C\u002Fdiv>\n    \u003C!-- built files will be auto injected -->\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n","public\u002Findex.html",[24,4771,4772,4782,4797,4805,4826,4858,4888,4921,4925,4930,4953,4958,4977,4986,4994,5003,5020,5028,5051,5056,5064],{"__ignoreMap":26},[57,4773,4774,4776,4778,4780],{"class":59,"line":60},[57,4775,1433],{"class":63},[57,4777,1436],{"class":800},[57,4779,1439],{"class":86},[57,4781,804],{"class":63},[57,4783,4784,4786,4788,4791,4793,4795],{"class":59,"line":80},[57,4785,797],{"class":63},[57,4787,1339],{"class":800},[57,4789,4790],{"class":86}," lang",[57,4792,826],{"class":63},[57,4794,1544],{"class":63},[57,4796,804],{"class":63},[57,4798,4799,4801,4803],{"class":59,"line":98},[57,4800,3891],{"class":63},[57,4802,1456],{"class":800},[57,4804,804],{"class":63},[57,4806,4807,4809,4812,4815,4817,4819,4822,4824],{"class":59,"line":116},[57,4808,818],{"class":63},[57,4810,4811],{"class":800},"meta",[57,4813,4814],{"class":86}," charset",[57,4816,826],{"class":63},[57,4818,64],{"class":63},[57,4820,4821],{"class":67},"utf-8",[57,4823,64],{"class":63},[57,4825,804],{"class":63},[57,4827,4828,4830,4832,4835,4837,4839,4842,4844,4847,4849,4851,4854,4856],{"class":59,"line":122},[57,4829,818],{"class":63},[57,4831,4811],{"class":800},[57,4833,4834],{"class":86}," http-equiv",[57,4836,826],{"class":63},[57,4838,64],{"class":63},[57,4840,4841],{"class":67},"X-UA-Compatible",[57,4843,64],{"class":63},[57,4845,4846],{"class":86}," content",[57,4848,826],{"class":63},[57,4850,64],{"class":63},[57,4852,4853],{"class":67},"IE=edge",[57,4855,64],{"class":63},[57,4857,804],{"class":63},[57,4859,4860,4862,4864,4866,4868,4870,4873,4875,4877,4879,4881,4884,4886],{"class":59,"line":136},[57,4861,818],{"class":63},[57,4863,4811],{"class":800},[57,4865,1528],{"class":86},[57,4867,826],{"class":63},[57,4869,64],{"class":63},[57,4871,4872],{"class":67},"viewport",[57,4874,64],{"class":63},[57,4876,4846],{"class":86},[57,4878,826],{"class":63},[57,4880,64],{"class":63},[57,4882,4883],{"class":67},"width=device-width,initial-scale=1.0",[57,4885,64],{"class":63},[57,4887,804],{"class":63},[57,4889,4890,4892,4895,4898,4900,4902,4905,4907,4910,4912,4914,4917,4919],{"class":59,"line":150},[57,4891,818],{"class":63},[57,4893,4894],{"class":800},"link",[57,4896,4897],{"class":86}," rel",[57,4899,826],{"class":63},[57,4901,64],{"class":63},[57,4903,4904],{"class":67},"icon",[57,4906,64],{"class":63},[57,4908,4909],{"class":86}," href",[57,4911,826],{"class":63},[57,4913,64],{"class":63},[57,4915,4916],{"class":67},"\u003C%= BASE_URL %>favicon.ico",[57,4918,64],{"class":63},[57,4920,804],{"class":63},[57,4922,4923],{"class":59,"line":155},[57,4924,2507],{"class":73},[57,4926,4927],{"class":59,"line":169},[57,4928,4929],{"class":1463},"　　\u003C!-- このスクリプトを挿入-->\n",[57,4931,4932,4934,4936,4938,4940,4942,4945,4947,4949,4951],{"class":59,"line":183},[57,4933,818],{"class":63},[57,4935,923],{"class":800},[57,4937,1675],{"class":86},[57,4939,826],{"class":63},[57,4941,64],{"class":63},[57,4943,4944],{"class":67},"http:\u002F\u002Fmaps.google.com\u002Fmaps\u002Fapi\u002Fjs?key=YOUR_API_KEY&language=ja",[57,4946,64],{"class":63},[57,4948,886],{"class":63},[57,4950,923],{"class":800},[57,4952,804],{"class":63},[57,4954,4955],{"class":59,"line":188},[57,4956,4957],{"class":73},"   \n",[57,4959,4960,4963,4966,4968,4971,4973,4975],{"class":59,"line":202},[57,4961,4962],{"class":63},"　　 \u003C",[57,4964,4965],{"class":800},"title",[57,4967,1196],{"class":63},[57,4969,4970],{"class":73},"\u003C%= htmlWebpackPlugin.options.title %>",[57,4972,904],{"class":63},[57,4974,4965],{"class":800},[57,4976,804],{"class":63},[57,4978,4979,4982,4984],{"class":59,"line":216},[57,4980,4981],{"class":63},"  \u003C\u002F",[57,4983,1456],{"class":800},[57,4985,804],{"class":63},[57,4987,4988,4990,4992],{"class":59,"line":221},[57,4989,3891],{"class":63},[57,4991,1483],{"class":800},[57,4993,804],{"class":63},[57,4995,4996,4998,5001],{"class":59,"line":235},[57,4997,818],{"class":63},[57,4999,5000],{"class":800},"noscript",[57,5002,804],{"class":63},[57,5004,5005,5007,5009,5011,5014,5016,5018],{"class":59,"line":250},[57,5006,1929],{"class":63},[57,5008,727],{"class":800},[57,5010,1196],{"class":63},[57,5012,5013],{"class":73},"We're sorry but \u003C%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.",[57,5015,904],{"class":63},[57,5017,727],{"class":800},[57,5019,804],{"class":63},[57,5021,5022,5024,5026],{"class":59,"line":256},[57,5023,895],{"class":63},[57,5025,5000],{"class":800},[57,5027,804],{"class":63},[57,5029,5030,5032,5034,5036,5038,5040,5043,5045,5047,5049],{"class":59,"line":2146},[57,5031,818],{"class":63},[57,5033,811],{"class":800},[57,5035,1732],{"class":86},[57,5037,826],{"class":63},[57,5039,64],{"class":63},[57,5041,5042],{"class":67},"app",[57,5044,64],{"class":63},[57,5046,886],{"class":63},[57,5048,811],{"class":800},[57,5050,804],{"class":63},[57,5052,5053],{"class":59,"line":2164},[57,5054,5055],{"class":1463},"    \u003C!-- built files will be auto injected -->\n",[57,5057,5058,5060,5062],{"class":59,"line":2184},[57,5059,4981],{"class":63},[57,5061,1483],{"class":800},[57,5063,804],{"class":63},[57,5065,5066,5068,5070],{"class":59,"line":2244},[57,5067,904],{"class":63},[57,5069,1339],{"class":800},[57,5071,804],{"class":63},[13,5073,5074,5077],{},[24,5075,5076],{},"http:\u002F\u002Fmaps.google.com\u002Fmaps\u002Fapi\u002Fjs","からAPIのソースを手に入れます。できたらnpmあたりでローカルに予めインストールしたいなと思ったのですが、node.js専用だったりとひとまず開発したかったので、今回は外部ファイルとします。このときVue.jsより早く読み込まれるようにしましょう。",[13,5079,5080],{},"スクリプトのurlにはAPIキーを含めます。公開環境で使用する場合は必ずAPIキーが自サイトのみで使用される条件を設定しておきましょう。でないと第三者に好き勝手にキーを使われて、請求だけはあなたのクレジットカードにきます。",[538,5082,5084],{"id":5083},"プラグインとしてvueに登録しておく","プラグインとしてVueに登録しておく",[13,5086,5087],{},"上記のソースを読み込めばwindow.googleを見ると、今回使用するAPIを操作できるクラスなりメソッドがあります。",[17,5089,5091],{"className":2885,"code":5090,"language":2887,"meta":26,"style":26},"console.lgo(window.google);\n\u002F**\n*{maps: {…}}\n*\u002F\n",[24,5092,5093,5098,5103,5108],{"__ignoreMap":26},[57,5094,5095],{"class":59,"line":60},[57,5096,5097],{},"console.lgo(window.google);\n",[57,5099,5100],{"class":59,"line":80},[57,5101,5102],{},"\u002F**\n",[57,5104,5105],{"class":59,"line":98},[57,5106,5107],{},"*{maps: {…}}\n",[57,5109,5110],{"class":59,"line":116},[57,5111,5112],{},"*\u002F\n",[13,5114,5115],{},"例えば地図を特定のDOMにマウントするときは以下のように呼びます。",[17,5117,5119],{"className":2885,"code":5118,"language":2887,"meta":26,"style":26},"window.google.maps.Map(document.getElementById('map'),{option});\n",[24,5120,5121],{"__ignoreMap":26},[57,5122,5123],{"class":59,"line":60},[57,5124,5118],{},[13,5126,5127,5128,5131,5132,5135],{},"毎回、",[24,5129,5130],{},"window.google.maps","とやるのは面倒なので",[24,5133,5134],{},"this.$gm","と呼び出せるようにプラグイン化しておきます。",[17,5137,5140],{"className":2885,"code":5138,"filename":5139,"language":2887,"meta":26,"style":26},"import Vue from 'vue'\nimport App from '.\u002FApp.vue'\n\n\u002F\u002Fこれ\nVue.prototype.$gm = window.google.maps;\n\nVue.config.productionTip = false\nnew Vue({\n  render: h => h(App),\n}).$mount('#app')\n","src\u002Fmain.js",[24,5141,5142,5146,5151,5155,5160,5165,5169,5174,5179,5184],{"__ignoreMap":26},[57,5143,5144],{"class":59,"line":60},[57,5145,3779],{},[57,5147,5148],{"class":59,"line":80},[57,5149,5150],{},"import App from '.\u002FApp.vue'\n",[57,5152,5153],{"class":59,"line":98},[57,5154,1133],{"emptyLinePlaceholder":402},[57,5156,5157],{"class":59,"line":116},[57,5158,5159],{},"\u002F\u002Fこれ\n",[57,5161,5162],{"class":59,"line":122},[57,5163,5164],{},"Vue.prototype.$gm = window.google.maps;\n",[57,5166,5167],{"class":59,"line":136},[57,5168,1133],{"emptyLinePlaceholder":402},[57,5170,5171],{"class":59,"line":150},[57,5172,5173],{},"Vue.config.productionTip = false\n",[57,5175,5176],{"class":59,"line":155},[57,5177,5178],{},"new Vue({\n",[57,5180,5181],{"class":59,"line":169},[57,5182,5183],{},"  render: h => h(App),\n",[57,5185,5186],{"class":59,"line":183},[57,5187,5188],{},"}).$mount('#app')\n",[13,5190,5191],{},"これで前準備が完了しました。",[466,5193,5194],{"id":5194},"とりあえず地図をマウントする",[13,5196,5197,5198,5201],{},"準備が整ったのでvueを用いてGoogle Mapをマウントしましょう。インストール直後にある",[24,5199,5200],{},"App.vue","を以下のように書きます。",[17,5203,5206],{"className":788,"code":5204,"filename":5205,"language":790,"meta":26,"style":26},"\u003Ctemplate>\n  \u003Cdiv id=\"app\" class=\"mt-4\">\n      \u003Cdiv id=\"map\">\u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default {\n  name: 'App',\n  data(){\n    return{\n      map:undefined,\n    }\n  },\n  mounted(){\n   const map = new this.$gm.Map(document.getElementById('map'),{\n      center: { lat: 34.855273888888888, lng: 135.30649 }, \u002F\u002F自由な緯度・経度を入力\n      zoom: 10,\n    });\n　　this.map = map;\n  }\n}\n\u003C\u002Fscript>\n","src\u002FApp.vue",[24,5207,5208,5216,5245,5268,5276,5284,5288,5296,5304,5320,5327,5334,5342,5346,5351,5358,5405,5438,5450,5458,5471,5475,5479],{"__ignoreMap":26},[57,5209,5210,5212,5214],{"class":59,"line":60},[57,5211,797],{"class":63},[57,5213,801],{"class":800},[57,5215,804],{"class":63},[57,5217,5218,5220,5222,5224,5226,5228,5230,5232,5234,5236,5238,5241,5243],{"class":59,"line":80},[57,5219,3891],{"class":63},[57,5221,811],{"class":800},[57,5223,1732],{"class":86},[57,5225,826],{"class":63},[57,5227,64],{"class":63},[57,5229,5042],{"class":67},[57,5231,64],{"class":63},[57,5233,3068],{"class":86},[57,5235,826],{"class":63},[57,5237,64],{"class":63},[57,5239,5240],{"class":67},"mt-4",[57,5242,64],{"class":63},[57,5244,804],{"class":63},[57,5246,5247,5249,5251,5253,5255,5257,5260,5262,5264,5266],{"class":59,"line":98},[57,5248,1929],{"class":63},[57,5250,811],{"class":800},[57,5252,1732],{"class":86},[57,5254,826],{"class":63},[57,5256,64],{"class":63},[57,5258,5259],{"class":67},"map",[57,5261,64],{"class":63},[57,5263,886],{"class":63},[57,5265,811],{"class":800},[57,5267,804],{"class":63},[57,5269,5270,5272,5274],{"class":59,"line":116},[57,5271,4981],{"class":63},[57,5273,811],{"class":800},[57,5275,804],{"class":63},[57,5277,5278,5280,5282],{"class":59,"line":122},[57,5279,904],{"class":63},[57,5281,801],{"class":800},[57,5283,804],{"class":63},[57,5285,5286],{"class":59,"line":136},[57,5287,1133],{"emptyLinePlaceholder":402},[57,5289,5290,5292,5294],{"class":59,"line":150},[57,5291,797],{"class":63},[57,5293,923],{"class":800},[57,5295,804],{"class":63},[57,5297,5298,5300,5302],{"class":59,"line":155},[57,5299,931],{"class":930},[57,5301,934],{"class":930},[57,5303,95],{"class":63},[57,5305,5306,5309,5311,5313,5316,5318],{"class":59,"line":169},[57,5307,5308],{"class":800},"  name",[57,5310,92],{"class":63},[57,5312,2215],{"class":63},[57,5314,5315],{"class":67},"App",[57,5317,2197],{"class":63},[57,5319,262],{"class":63},[57,5321,5322,5325],{"class":59,"line":183},[57,5323,5324],{"class":800},"  data",[57,5326,944],{"class":63},[57,5328,5329,5332],{"class":59,"line":188},[57,5330,5331],{"class":930},"    return",[57,5333,77],{"class":63},[57,5335,5336,5339],{"class":59,"line":202},[57,5337,5338],{"class":800},"      map",[57,5340,5341],{"class":63},":undefined,\n",[57,5343,5344],{"class":59,"line":216},[57,5345,253],{"class":63},[57,5347,5348],{"class":59,"line":221},[57,5349,5350],{"class":63},"  },\n",[57,5352,5353,5356],{"class":59,"line":235},[57,5354,5355],{"class":800},"  mounted",[57,5357,944],{"class":63},[57,5359,5360,5363,5366,5369,5372,5375,5378,5380,5383,5385,5388,5390,5392,5394,5396,5398,5400,5402],{"class":59,"line":250},[57,5361,5362],{"class":86},"   const",[57,5364,5365],{"class":73}," map",[57,5367,5368],{"class":63}," =",[57,5370,5371],{"class":63}," new",[57,5373,5374],{"class":63}," this.",[57,5376,5377],{"class":73},"$gm",[57,5379,2152],{"class":63},[57,5381,5382],{"class":372},"Map",[57,5384,2134],{"class":800},[57,5386,5387],{"class":73},"document",[57,5389,2152],{"class":63},[57,5391,2252],{"class":372},[57,5393,2134],{"class":800},[57,5395,2197],{"class":63},[57,5397,5259],{"class":67},[57,5399,2197],{"class":63},[57,5401,2141],{"class":800},[57,5403,5404],{"class":63},",{\n",[57,5406,5407,5410,5412,5414,5417,5419,5422,5424,5427,5429,5432,5435],{"class":59,"line":256},[57,5408,5409],{"class":800},"      center",[57,5411,92],{"class":63},[57,5413,2207],{"class":63},[57,5415,5416],{"class":800}," lat",[57,5418,92],{"class":63},[57,5420,5421],{"class":112}," 34.855273888888888",[57,5423,2204],{"class":63},[57,5425,5426],{"class":800}," lng",[57,5428,92],{"class":63},[57,5430,5431],{"class":112}," 135.30649",[57,5433,5434],{"class":63}," },",[57,5436,5437],{"class":1463}," \u002F\u002F自由な緯度・経度を入力\n",[57,5439,5440,5443,5445,5448],{"class":59,"line":2146},[57,5441,5442],{"class":800},"      zoom",[57,5444,92],{"class":63},[57,5446,5447],{"class":112}," 10",[57,5449,262],{"class":63},[57,5451,5452,5454,5456],{"class":59,"line":2164},[57,5453,3241],{"class":63},[57,5455,2141],{"class":800},[57,5457,2161],{"class":63},[57,5459,5460,5463,5465,5467,5469],{"class":59,"line":2184},[57,5461,5462],{"class":63},"　　this.",[57,5464,5259],{"class":73},[57,5466,5368],{"class":63},[57,5468,5365],{"class":73},[57,5470,2161],{"class":63},[57,5472,5473],{"class":59,"line":2244},[57,5474,3968],{"class":63},[57,5476,5477],{"class":59,"line":2276},[57,5478,975],{"class":63},[57,5480,5481,5483,5485],{"class":59,"line":2303},[57,5482,904],{"class":63},[57,5484,923],{"class":800},[57,5486,804],{"class":63},[13,5488,5489,5494],{},[419,5490,5493],{"href":5491,"rel":5492},"https:\u002F\u002Fdevelopers.google.com\u002Fmaps\u002Fdocumentation\u002Fjavascript\u002Freference\u002Fmap?hl=ja",[423],"Mapsクラス","のレファランスの通り、第一引数にマウント対象のDOMを入れ、第二引数にはオプションを入れます。DOMが必要なのでmounted()で初期処理を行います。",[13,5496,5497],{},"オプションには",[441,5499,5500,5503],{},[444,5501,5502],{},"center：初期表示の地図の中央座標",[444,5504,5505],{},"zoom：拡大の縮尺（10ぐらいがちょうどいい）",[13,5507,5508,5509,5512,5513,5516,5517,5520],{},"を入れておきましょう。",[24,5510,5511],{},"lat","は緯度、",[24,5514,5515],{},"lng","は経度を示しています。このインスタンスは後で使い回すので",[24,5518,5519],{},"this.map = map"," としてインスタンスを入れておきましょう。ひとまずこの処理を実装してブラウザで見てみましょう。",[17,5522,5525],{"className":5523,"code":5524,"language":22},[20],"$ npm run serve\n",[24,5526,5524],{"__ignoreMap":26},[778,5528],{":src":5529,":width":1392},"'_mix\u002Fsch-2021-03-07-17.39.26-768x352.png'",[13,5531,5532,5534],{},[24,5533,1398],{},"をみたとき、このようになっていれば問題ありません。ストリートビューや前面表示がない方がいい人はオプションを以下のようにしておきます。",[17,5536,5538],{"className":2885,"code":5537,"filename":5139,"language":2887,"meta":26,"style":26},"new this.$gm.Map(document.getElementById('map'),{\n      center: { lat: 34.855273888888888, lng: 135.30649 },\n      zoom: 10,\n      streetViewControl:false, \u002F\u002F ストリートビュー非表示\n      fullscreenControl:false  \u002F\u002F フルスクリーン非表示\n});\n",[24,5539,5540,5545,5550,5555,5560,5565],{"__ignoreMap":26},[57,5541,5542],{"class":59,"line":60},[57,5543,5544],{},"new this.$gm.Map(document.getElementById('map'),{\n",[57,5546,5547],{"class":59,"line":80},[57,5548,5549],{},"      center: { lat: 34.855273888888888, lng: 135.30649 },\n",[57,5551,5552],{"class":59,"line":98},[57,5553,5554],{},"      zoom: 10,\n",[57,5556,5557],{"class":59,"line":116},[57,5558,5559],{},"      streetViewControl:false, \u002F\u002F ストリートビュー非表示\n",[57,5561,5562],{"class":59,"line":122},[57,5563,5564],{},"      fullscreenControl:false  \u002F\u002F フルスクリーン非表示\n",[57,5566,5567],{"class":59,"line":136},[57,5568,5569],{},"});\n",[466,5571,5572],{"id":5572},"地図にクリックイベントを追加",[13,5574,5575],{},"Google Mapの地図にはクリックやドラッグなどのイベントリスナーを追加できます。特定の位置やマーカーをクリックしたら何やかんやするというのができます。まずは簡単にクリックした地図位置の緯度・経度を取得してみましょう。以下のようにします。",[17,5577,5579],{"className":2885,"code":5578,"filename":5139,"language":2887,"meta":26,"style":26},"\u003Ctemplate>\n  \u003Cdiv id=\"app\" class=\"mt-4\">\n      \u003Cdiv id=\"map\">\u003C\u002Fdiv>\n      {{position}}\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\n\nexport default {\n  name: 'App',\n  data(){\n      return{\n        map:undefined,\n        position:undefined\n      }\n  },\n  methods:{\n   clickOnMap(mapEvent){\n    console.log(mapEvent.latLng.toString())\n    this.position = mapEvent.latLng.toString();\n   }\n  },\n  mounted(){\n   const map = new this.$gm.Map(document.getElementById('map'),{\n      center: { lat: 34.855273888888888, lng: 135.30649 }, \u002F\u002F自由な緯度・経度を入力\n      zoom: 10,\n    });\n\n    \u002F\u002Fリスナーの登録\n    map.addListener('click',(mapsMouseEvent)=>{\n      return this.clickOnMap(mapsMouseEvent);\n    });\n\n　　this.map = map;\n  }\n}\n",[24,5580,5581,5586,5591,5596,5601,5606,5611,5615,5620,5624,5629,5634,5639,5644,5649,5654,5659,5663,5668,5673,5678,5683,5688,5692,5697,5702,5707,5711,5716,5720,5725,5730,5735,5739,5743,5748,5752],{"__ignoreMap":26},[57,5582,5583],{"class":59,"line":60},[57,5584,5585],{},"\u003Ctemplate>\n",[57,5587,5588],{"class":59,"line":80},[57,5589,5590],{},"  \u003Cdiv id=\"app\" class=\"mt-4\">\n",[57,5592,5593],{"class":59,"line":98},[57,5594,5595],{},"      \u003Cdiv id=\"map\">\u003C\u002Fdiv>\n",[57,5597,5598],{"class":59,"line":116},[57,5599,5600],{},"      {{position}}\n",[57,5602,5603],{"class":59,"line":122},[57,5604,5605],{},"  \u003C\u002Fdiv>\n",[57,5607,5608],{"class":59,"line":136},[57,5609,5610],{},"\u003C\u002Ftemplate>\n",[57,5612,5613],{"class":59,"line":150},[57,5614,1133],{"emptyLinePlaceholder":402},[57,5616,5617],{"class":59,"line":155},[57,5618,5619],{},"\u003Cscript>\n",[57,5621,5622],{"class":59,"line":169},[57,5623,1133],{"emptyLinePlaceholder":402},[57,5625,5626],{"class":59,"line":183},[57,5627,5628],{},"export default {\n",[57,5630,5631],{"class":59,"line":188},[57,5632,5633],{},"  name: 'App',\n",[57,5635,5636],{"class":59,"line":202},[57,5637,5638],{},"  data(){\n",[57,5640,5641],{"class":59,"line":216},[57,5642,5643],{},"      return{\n",[57,5645,5646],{"class":59,"line":221},[57,5647,5648],{},"        map:undefined,\n",[57,5650,5651],{"class":59,"line":235},[57,5652,5653],{},"        position:undefined\n",[57,5655,5656],{"class":59,"line":250},[57,5657,5658],{},"      }\n",[57,5660,5661],{"class":59,"line":256},[57,5662,5350],{},[57,5664,5665],{"class":59,"line":2146},[57,5666,5667],{},"  methods:{\n",[57,5669,5670],{"class":59,"line":2164},[57,5671,5672],{},"   clickOnMap(mapEvent){\n",[57,5674,5675],{"class":59,"line":2184},[57,5676,5677],{},"    console.log(mapEvent.latLng.toString())\n",[57,5679,5680],{"class":59,"line":2244},[57,5681,5682],{},"    this.position = mapEvent.latLng.toString();\n",[57,5684,5685],{"class":59,"line":2276},[57,5686,5687],{},"   }\n",[57,5689,5690],{"class":59,"line":2303},[57,5691,5350],{},[57,5693,5694],{"class":59,"line":2313},[57,5695,5696],{},"  mounted(){\n",[57,5698,5699],{"class":59,"line":2323},[57,5700,5701],{},"   const map = new this.$gm.Map(document.getElementById('map'),{\n",[57,5703,5704],{"class":59,"line":2328},[57,5705,5706],{},"      center: { lat: 34.855273888888888, lng: 135.30649 }, \u002F\u002F自由な緯度・経度を入力\n",[57,5708,5709],{"class":59,"line":2368},[57,5710,5554],{},[57,5712,5713],{"class":59,"line":2377},[57,5714,5715],{},"    });\n",[57,5717,5718],{"class":59,"line":2587},[57,5719,1133],{"emptyLinePlaceholder":402},[57,5721,5722],{"class":59,"line":2593},[57,5723,5724],{},"    \u002F\u002Fリスナーの登録\n",[57,5726,5727],{"class":59,"line":2599},[57,5728,5729],{},"    map.addListener('click',(mapsMouseEvent)=>{\n",[57,5731,5732],{"class":59,"line":2604},[57,5733,5734],{},"      return this.clickOnMap(mapsMouseEvent);\n",[57,5736,5737],{"class":59,"line":2610},[57,5738,5715],{},[57,5740,5741],{"class":59,"line":2616},[57,5742,1133],{"emptyLinePlaceholder":402},[57,5744,5745],{"class":59,"line":2622},[57,5746,5747],{},"　　this.map = map;\n",[57,5749,5750],{"class":59,"line":2628},[57,5751,3968],{},[57,5753,5754],{"class":59,"line":2633},[57,5755,975],{},[13,5757,5758,5761],{},[24,5759,5760],{},"map.addListener('click',(mapsMouseEvent)=>{...})","とDOMにイベントを追加する時と似ていますね。第二引数に実行するコールバックを入れます。ちなみにコールバックの引数には自動的に地図上の座標なり他の情報が入っています。",[13,5763,5764],{},"このイベントリスナーには以下のメソッドが実行されるようになっており、クリック位置の経度、緯度を取得します。",[17,5766,5768],{"className":2885,"code":5767,"filename":5139,"language":2887,"meta":26,"style":26},"clickOnMap(mapEvent){\n        console.log(mapEvent.latLng.toString())\n        this.position = mapEvent.latLng.toString();\n},\n",[24,5769,5770,5775,5780,5785],{"__ignoreMap":26},[57,5771,5772],{"class":59,"line":60},[57,5773,5774],{},"clickOnMap(mapEvent){\n",[57,5776,5777],{"class":59,"line":80},[57,5778,5779],{},"        console.log(mapEvent.latLng.toString())\n",[57,5781,5782],{"class":59,"line":98},[57,5783,5784],{},"        this.position = mapEvent.latLng.toString();\n",[57,5786,5787],{"class":59,"line":116},[57,5788,5789],{},"},\n",[13,5791,5792],{},"クリックすると以下のようになります。（真ん中の赤いのは気にしないでください。）",[778,5794],{":src":5795,":width":1392},"'_mix\u002Fsch-2021-03-07-18.02.03-768x326.png'",[13,5797,5798],{},"左下に座標が表示されました。ひとまずこれで地図に対するイベントリスナーの登録と情報の取得については理解できたと思います。",[466,5800,5801],{"id":5801},"ラインの描画",[13,5803,5804,5805,5808],{},"それでは今度はクリックした際にラインを描画できるようにします。ラインを描画するためには",[24,5806,5807],{},"google.maps.Polyline","クラスのインスタンスを作成し、先ほどのMapインスタンスにセットすることで描画ができます。",[13,5810,5811],{},"上記の通り地図上の情報を取得できたように、今度は緯度経度の情報を元に地図に何かセットします。まずはMapの初期表示と同時にラインを描画しましょう。",[17,5813,5815],{"className":2885,"code":5814,"filename":5139,"language":2887,"meta":26,"style":26},"...  \nmounted(){\n  const map = new this.$gm.Map(document.getElementById('map'),{\n    center: { lat: 34.855273888888888, lng: 135.30649 },\n    zoom: 10,\n  });\n  map.addListener('click',(mapsMouseEvent)=>{\n    return this.clickOnMap(mapsMouseEvent);\n  });\n  const LINE = new this.$gm.Polyline({\n      path:[\n        { lat: 34.855273888888888, lng: 135.30649 },\n        { lat: 34.854465, lng: 135.8 },\n      ],\n      geodesic: true,\n      strokeColor: \"#FF0000\",\n      strokeOpacity: 1.0,\n      strokeWeight: 2,\n  })\n  LINE.setMap(map);\n  this.map = map;\n} \n...\n",[24,5816,5817,5822,5827,5832,5837,5842,5847,5852,5857,5861,5866,5871,5876,5881,5886,5891,5896,5901,5906,5911,5916,5921,5926],{"__ignoreMap":26},[57,5818,5819],{"class":59,"line":60},[57,5820,5821],{},"...  \n",[57,5823,5824],{"class":59,"line":80},[57,5825,5826],{},"mounted(){\n",[57,5828,5829],{"class":59,"line":98},[57,5830,5831],{},"  const map = new this.$gm.Map(document.getElementById('map'),{\n",[57,5833,5834],{"class":59,"line":116},[57,5835,5836],{},"    center: { lat: 34.855273888888888, lng: 135.30649 },\n",[57,5838,5839],{"class":59,"line":122},[57,5840,5841],{},"    zoom: 10,\n",[57,5843,5844],{"class":59,"line":136},[57,5845,5846],{},"  });\n",[57,5848,5849],{"class":59,"line":150},[57,5850,5851],{},"  map.addListener('click',(mapsMouseEvent)=>{\n",[57,5853,5854],{"class":59,"line":155},[57,5855,5856],{},"    return this.clickOnMap(mapsMouseEvent);\n",[57,5858,5859],{"class":59,"line":169},[57,5860,5846],{},[57,5862,5863],{"class":59,"line":183},[57,5864,5865],{},"  const LINE = new this.$gm.Polyline({\n",[57,5867,5868],{"class":59,"line":188},[57,5869,5870],{},"      path:[\n",[57,5872,5873],{"class":59,"line":202},[57,5874,5875],{},"        { lat: 34.855273888888888, lng: 135.30649 },\n",[57,5877,5878],{"class":59,"line":216},[57,5879,5880],{},"        { lat: 34.854465, lng: 135.8 },\n",[57,5882,5883],{"class":59,"line":221},[57,5884,5885],{},"      ],\n",[57,5887,5888],{"class":59,"line":235},[57,5889,5890],{},"      geodesic: true,\n",[57,5892,5893],{"class":59,"line":250},[57,5894,5895],{},"      strokeColor: \"#FF0000\",\n",[57,5897,5898],{"class":59,"line":256},[57,5899,5900],{},"      strokeOpacity: 1.0,\n",[57,5902,5903],{"class":59,"line":2146},[57,5904,5905],{},"      strokeWeight: 2,\n",[57,5907,5908],{"class":59,"line":2164},[57,5909,5910],{},"  })\n",[57,5912,5913],{"class":59,"line":2184},[57,5914,5915],{},"  LINE.setMap(map);\n",[57,5917,5918],{"class":59,"line":2244},[57,5919,5920],{},"  this.map = map;\n",[57,5922,5923],{"class":59,"line":2276},[57,5924,5925],{},"} \n",[57,5927,5928],{"class":59,"line":2303},[57,5929,5930],{},"...\n",[13,5932,5933,5936,5937,5940],{},[24,5934,5935],{},"this.$gm.Polyline","でラインインスタンスを作成し、オプションをオブジェクト形式で指定すればラインの色、パス（２点以上の座標）、太さなどを指定できます。最後に",[24,5938,5939],{},"LINE.setMap(map)","で地図にはめると、以下のように描画されます。",[778,5942],{":src":5943,":width":1392},"'_mix\u002Fsch-2021-03-10-21.17.39-768x346.png'",[13,5945,5946],{},"地図に赤線が表示されました。今は２点の適当な座標間ですが細かくパスを設定すれば以下のようにも描画できます。",[778,5948],{":src":5949,":width":781,":center":782},"'_mix\u002Fsch-2021-03-10-21.19.41.png'",[13,5951,5952],{},"この座標情報は友人のGPSから引っ張ってきたもので、詳細な座標情報が配列であったのでこれほど細かく描画されます。",[466,5954,5956],{"id":5955},"クリックした任意の地点でラインを描画していく","クリックした任意の地点でラインを描画していく。",[13,5958,5959],{},"初期表示でラインを描画することができたので、次はユーザーが任意の地点をクリックしたらその地点に向かってラインが描画されるようにしましょう。",[13,5961,5962],{},"先ほどクリックイベントから座標情報をしたのを覚えていますか？それを応用します。",[5964,5965,5966,5969,5972,5975],"ol",{},[444,5967,5968],{},"クリック位置から緯度・経度を取得",[444,5970,5971],{},"Google Map用の座標インスタンスに変換",[444,5973,5974],{},"ラインインスタンスにその座標（パス）を追加する。",[444,5976,5977],{},"クリックされたら繰り返す。",[13,5979,5980,5981,5984],{},"クリックイベントに付与した",[24,5982,5983],{},"drawLine","メソッドこんな感じです。",[17,5986,5988],{"className":2885,"code":5987,"filename":5139,"language":2887,"meta":26,"style":26},"data(){\n  return{\n    map:undefined,\n    bounds:undefined,\n    path:[],\n    line:undefined\n  }\n},\nmethods:{\n  drawLine(mapEvent){\n    let latLng = mapEvent.latLng;\n\n    \u002F\u002F １点目をクリック\n    if(this.line === undefined){\n      const newLine = new this.$gm.Polyline({\n        path:[latLng],\n        geodesic: true,\n        strokeColor: \"#FF0000\",\n        strokeOpacity: 1.0,\n        strokeWeight: 2,\n      })\n      newLine.setMap(this.map);\n      this.path.push(latLng);\n      this.line = newLine;\n      return;\n    }\n\n    \u002F\u002F ２点目以降\n    this.path.push(latLng);\n    this.line.setPath([...this.path]);\n    return;\n  },\n},\n",[24,5989,5990,5995,6000,6005,6010,6015,6020,6024,6028,6033,6038,6043,6047,6052,6057,6062,6067,6072,6077,6082,6087,6092,6097,6102,6107,6112,6116,6120,6125,6130,6135,6140,6144],{"__ignoreMap":26},[57,5991,5992],{"class":59,"line":60},[57,5993,5994],{},"data(){\n",[57,5996,5997],{"class":59,"line":80},[57,5998,5999],{},"  return{\n",[57,6001,6002],{"class":59,"line":98},[57,6003,6004],{},"    map:undefined,\n",[57,6006,6007],{"class":59,"line":116},[57,6008,6009],{},"    bounds:undefined,\n",[57,6011,6012],{"class":59,"line":122},[57,6013,6014],{},"    path:[],\n",[57,6016,6017],{"class":59,"line":136},[57,6018,6019],{},"    line:undefined\n",[57,6021,6022],{"class":59,"line":150},[57,6023,3968],{},[57,6025,6026],{"class":59,"line":155},[57,6027,5789],{},[57,6029,6030],{"class":59,"line":169},[57,6031,6032],{},"methods:{\n",[57,6034,6035],{"class":59,"line":183},[57,6036,6037],{},"  drawLine(mapEvent){\n",[57,6039,6040],{"class":59,"line":188},[57,6041,6042],{},"    let latLng = mapEvent.latLng;\n",[57,6044,6045],{"class":59,"line":202},[57,6046,1133],{"emptyLinePlaceholder":402},[57,6048,6049],{"class":59,"line":216},[57,6050,6051],{},"    \u002F\u002F １点目をクリック\n",[57,6053,6054],{"class":59,"line":221},[57,6055,6056],{},"    if(this.line === undefined){\n",[57,6058,6059],{"class":59,"line":235},[57,6060,6061],{},"      const newLine = new this.$gm.Polyline({\n",[57,6063,6064],{"class":59,"line":250},[57,6065,6066],{},"        path:[latLng],\n",[57,6068,6069],{"class":59,"line":256},[57,6070,6071],{},"        geodesic: true,\n",[57,6073,6074],{"class":59,"line":2146},[57,6075,6076],{},"        strokeColor: \"#FF0000\",\n",[57,6078,6079],{"class":59,"line":2164},[57,6080,6081],{},"        strokeOpacity: 1.0,\n",[57,6083,6084],{"class":59,"line":2184},[57,6085,6086],{},"        strokeWeight: 2,\n",[57,6088,6089],{"class":59,"line":2244},[57,6090,6091],{},"      })\n",[57,6093,6094],{"class":59,"line":2276},[57,6095,6096],{},"      newLine.setMap(this.map);\n",[57,6098,6099],{"class":59,"line":2303},[57,6100,6101],{},"      this.path.push(latLng);\n",[57,6103,6104],{"class":59,"line":2313},[57,6105,6106],{},"      this.line = newLine;\n",[57,6108,6109],{"class":59,"line":2323},[57,6110,6111],{},"      return;\n",[57,6113,6114],{"class":59,"line":2328},[57,6115,253],{},[57,6117,6118],{"class":59,"line":2368},[57,6119,1133],{"emptyLinePlaceholder":402},[57,6121,6122],{"class":59,"line":2377},[57,6123,6124],{},"    \u002F\u002F ２点目以降\n",[57,6126,6127],{"class":59,"line":2587},[57,6128,6129],{},"    this.path.push(latLng);\n",[57,6131,6132],{"class":59,"line":2593},[57,6133,6134],{},"    this.line.setPath([...this.path]);\n",[57,6136,6137],{"class":59,"line":2599},[57,6138,6139],{},"    return;\n",[57,6141,6142],{"class":59,"line":2604},[57,6143,5350],{},[57,6145,6146],{"class":59,"line":2610},[57,6147,5789],{},[13,6149,6150,6151,6153,6154,6157,6158,6161,6162,6165],{},"１点目の時はインスタンスを作成し",[24,6152,993],{},"に格納します。そしてクリックごとに",[24,6155,6156],{},"path","に挿入していき、",[24,6159,6160],{},"this.line.setPath([...this.path])","で更新したパスの配列を展開、インスタンスのパスも更新します。するとクリックするたびにその地点に向かって線画描画されます。静止画ですが",[727,6163,6164],{},"VUE","という文字を一筆でかいてみました笑",[778,6167],{":src":6168,":width":1392},"'_mix\u002Fscp-2021-03-10-21.42.12-768x408.png'",[13,6170,6171],{},"末尾のパスを消して再セットすればやり直し機能ができますし、特定のパスの座標を変更すれば途中の変更もできます。今回は面倒なのでやりませんでした。パスのインスタンスを複数管理できれば、複数ライン・レイヤーなど高度なライン描画を実現できます。詳細な実装をしたらまた記事を書いてみます。",[466,6173,6175],{"id":6174},"vueじゃなくてもよくね","Vueじゃなくてもよくね？",[13,6177,6178],{},"はい。その通りです。ほとんどがGoogle map javascript apiから提供されたオブジェクトをいじっているだけです。なのでVanilla.jsな環境でも普通に描画機能は実装できます。今回はvue.jsを利用して自主開発していたのでvue.jsを使っただけです。",[466,6180,6181],{"id":6181},"いろいろ試してみる",[13,6183,6184],{},"Google Map APIには他にもマーカ・カスタムデザインマーカを入れたり、DOM要素を地図に入れ込むなんてこともできるそうです。これらの描画情報をJSONに格納して、DBに入れておけば地図共有アプリなんかもできそうですね。いろいろやってみます。閲覧ありがとうございました。",[390,6186,6187],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}",{"title":26,"searchDepth":98,"depth":98,"links":6189},[6190,6191,6195,6196,6197,6198,6199,6200],{"id":4700,"depth":80,"text":4701},{"id":4729,"depth":80,"text":4730,"children":6192},[6193,6194],{"id":4748,"depth":98,"text":4749},{"id":5083,"depth":98,"text":5084},{"id":5194,"depth":80,"text":5194},{"id":5572,"depth":80,"text":5572},{"id":5801,"depth":80,"text":5801},{"id":5955,"depth":80,"text":5956},{"id":6174,"depth":80,"text":6175},{"id":6181,"depth":80,"text":6181},[3033],"2021-03-10",{},"\u002Farticles\u002Fgoogle-map-vuejs",{"title":4692,"description":4692},"articles\u002Fgoogle-map-vuejs",[3041,790],"_mix\u002Fscp-2021-03-10-21.42.12-768x408.png","oZOhHuAchOiO9qVocS-_1TA4WK0zOIDu_l3spKlDDsE",{"id":6211,"title":6212,"body":6213,"category":6369,"createdAt":6370,"description":6212,"extension":399,"index":400,"meta":6371,"navigation":402,"path":6372,"publish":402,"seo":6373,"series":400,"seriesTitle":400,"stem":6374,"tag":6375,"thumbnail":400,"updatedAt":400,"__hash__":6376},"articles\u002Farticles\u002Fssh-on-xserver.md","XserverでSSH接続を行い、ターミナルで操作する",{"type":10,"value":6214,"toc":6362},[6215,6218,6221,6225,6233,6236,6239,6242,6245,6248,6251,6254,6258,6261,6264,6270,6277,6283,6287,6296,6302,6305,6311,6324,6334,6337,6340,6346,6349,6353,6356,6359],[13,6216,6217],{},"こんにちはJunです。vagrant、Docker、VPSなどを構築しているとわかりますが、レンタルサーバーは手軽に構築できて便利です。エンジニア的には自由に環境を構築できた方がいろんなアプリを作ることができますが、ブログ程度であればXserverで十分です。",[13,6219,6220],{},"DBや細かい設定もGUIで完結できる様になっているので初心者にはもってこいです。しかし私が別で構築しているブログを今管理しているXserverに移行する時、画像やDBが膨大すぎて手動・GUIでは移行に限界がありました。そのためSSHを使用してXserverに繋いてで操作することにしました。今回はその忘備録です。",[466,6222,6224],{"id":6223},"sshを有効にする","SSHを有効にする",[13,6226,6227,6228,6232],{},"「SSHとはなんぞや？」という人は",[419,6229,6231],{"href":6230},"\u002Farticles\u002Fconnect-with-scp-ssh","こちらの記事","でわかりやすく説明したものがあるのでご覧ください。",[13,6234,6235],{},"最初にXserverでSSH接続を有効にする必要があります。Xserverサーバーパネルにログインして、「SSH設定」という箇所のページを開きます。",[778,6237],{":src":6238,":width":1392},"'_mix\u002Fsch-2021-01-11-14.59.00.png'",[13,6240,6241],{},"そこで「変更」をONにして「設定する」をクリック。しますとSSHが有効になります。",[466,6243,6244],{"id":6244},"秘密鍵を生成する",[13,6246,6247],{},"SSHは鍵交換という方法を用いてサーバーへのログインを管理しています。自身のPCからXserverのサーバーへアクセスするには鍵を発行します。「公開鍵認証用鍵ペアの生成」をクリックします。以下の画面が表示されるので、「パスフレーズ」（パスワードの様なもの）を入れて「確認画面」へ進みます。",[778,6249],{":src":6250,":width":1392},"'_mix\u002Fsch-2021-01-11-17.43.09.png'",[13,6252,6253],{},"確認が終了すると~~~.keyという拡張子のファイルがダウンロードされます。このファイルは鍵ファイルといい、サーバーの中にアクセスする際に使用されます。",[466,6255,6257],{"id":6256},"sshの設定をする","SSHの設定をする",[13,6259,6260],{},"ここからはローカル（自分のPC）の話になります。SSHでアクセスするために、ローカルでのSSH設定を行います。またローカルにはsshdがあり、今回はMacOSターミナルでの操作を前提としています。",[13,6262,6263],{},"ターミナルを立ち上げ、ひとまずホームディレクトリに移動し、さらにDownloadsへ移動し、先ほどダウンロードしたkeyファイルを見つけます。",[17,6265,6268],{"className":6266,"code":6267,"language":22},[20],"jun@MacBook-Pro % cd ~\njun@MacBook-Pro ~ % cd downloads\njun@MacBook-Pro downloads % ls\n...\nserver.key\n...\n",[24,6269,6267],{"__ignoreMap":26},[13,6271,6272,6273,6276],{},"keyファイルをひとまずホームディレクトリ配下にある",[24,6274,6275],{},".ssh\u002F","へ移動させます。",[17,6278,6281],{"className":6279,"code":6280,"language":22},[20],"jun@MacBook-Pro downloads % mv server.key ~\u002F.ssh\u002F\n",[24,6282,6280],{"__ignoreMap":26},[811,6284,6286],{"className":6285},[1001,3395],"\nTIPS:\nsshdがある環境ではユーザーごとに.sshというディレクトリが与えられ、そこで個人のssh設定を行います。しかし.sshは隠しファイルなので、ホームディレクトリでlsをしても普通には出てきません。ls -a とすることで隠しファイルを含めて表示させると、.sshというディレクトリを確認できます。\n",[13,6288,6289,6291,6292,6295],{},[24,6290,3546],{},"に移動してkeyを確認。そして権限を変更します。権限を変更しないと ",[24,6293,6294],{},"WARNING: UNPROTECTED PRIVATE KEY FILE!"," と怒られます。",[17,6297,6300],{"className":6298,"code":6299,"language":22},[20],"jun@MacBook-Pro .ssh % ls\nconfig      server.key\n\njun@MacBook-Pro .ssh % chmod 400 server.key\n",[24,6301,6299],{"__ignoreMap":26},[13,6303,6304],{},"これでOKです。lsをした時にconfigというファイルがあったと思います。そのファイルにsshの設定が記述されていますので、以下の様に編集をします。",[17,6306,6309],{"className":6307,"code":6308,"language":22},[20],"jun@MacBook-Pro .ssh % vi config\n\nHost my-site\n HostName svExample.xserver.jp\n Port 10022\n user idName\n IdentitiesOnly yes\n IdentityFile \u002FUsers\u002Fjunjiishii\u002F.ssh\u002Fserver.key\n",[24,6310,6308],{"__ignoreMap":26},[13,6312,6313,6316,6317,6320,6321,6323],{},[24,6314,6315],{},"HostName","はxserverのホスト名を入力し。",[24,6318,6319],{},"user","にはサーバーIDを入力します。",[24,6322,6315],{},"はxserverのサーバーパネルの「サーバー情報」、サーバーIDは「パスワード変更」で確認できます。",[13,6325,6326,6329,6330,6333],{},[24,6327,6328],{},"config","で設定しておくとsshコマンドを打つ時、Hostの名前でつまり、",[24,6331,6332],{},"ssh my-site"," とするだけで設定した値の接続先にsshしてくれます。",[466,6335,6336],{"id":6336},"xserverへsshをする",[13,6338,6339],{},"それではsshを行ってみます。",[17,6341,6344],{"className":6342,"code":6343,"language":22},[20],"ssh my-site\nEnter passphrase for key '\u002FUsers\u002Fjun\u002F.ssh\u002Fjunji1996.key': ＃設定したパースフレーズを入力\n\n#もし、以下の文章が表示されたら yesをおす\nECDSA key fingerprint is SHA256:V0GmZVuUlP6tQw5MYbGelfcq+IQwq\u002F6+HnH1OPAcaLo.\nAre you sure you want to continue connecting (yes\u002Fno\u002F[fingerprint])? yes\n\n[idName@svExample ~]$\n",[24,6345,6343],{"__ignoreMap":26},[13,6347,6348],{},"ターミナルの表記がとなればxserverへ入れて、その中を操作していることを示しており、ログイン成功です。以上がxserverへssh接続する方法です。",[466,6350,6352],{"id":6351},"sshは何がいいの","SSHは何がいいの？",[13,6354,6355],{},"以上がxserverでSSHを行う手順です。",[13,6357,6358],{},"基本的にレンタルサーバーでwordpressを使ってブログを書くぐらいならFTP程度で十分であり、SSHを使うこともないと思います。しかし膨大な量のデータを移行したり、phpmyadmin（データベースの操作ができる）で出力・挿入不可なほどのデータベース移行をする際にはコマンドで打てる環境があるとやりやすいです。",[13,6360,6361],{},"また鍵交換方式は鍵ファイルさえ流出しなければ不正にサーバーに侵入されるリスクがパスワード方式よりも低いです。",{"title":26,"searchDepth":98,"depth":98,"links":6363},[6364,6365,6366,6367,6368],{"id":6223,"depth":80,"text":6224},{"id":6244,"depth":80,"text":6244},{"id":6256,"depth":80,"text":6257},{"id":6336,"depth":80,"text":6336},{"id":6351,"depth":80,"text":6352},[396],"2021-01-11",{},"\u002Farticles\u002Fssh-on-xserver",{"title":6212,"description":6212},"articles\u002Fssh-on-xserver",[3750,407],"rRtfurJyt2-gDA2kIQLYcHALvE4zCwVc87qoUW28CXY",1780987153773]