tag:crieit.net,2005:https://crieit.net/tags/Vue.js/feed 「Vue.js」の記事 - Crieit Crieitでタグ「Vue.js」に投稿された最近の記事 2021-11-23T01:20:11+09:00 https://crieit.net/tags/Vue.js/feed tag:crieit.net,2005:PublicArticle/17774 2021-11-22T02:31:55+09:00 2021-11-23T01:20:11+09:00 https://crieit.net/posts/d5ca0b7ff1f49b32066b352d05c4d40b 匿名掲示板作ってみた <p>匿名掲示板を作り、アルファ版として公開してみました。<br /> Crieitユーザーの皆さんにもぜひご意見いただきたいです。</p> <p>            -‐ '´ ̄ ̄`ヽ、へ    /\<br />              / /" `ヽ ヽ   ̄  ̄ ̄ ̄   ><br />          //, '/     ヽハ  、 ヽ ̄  ̄>/<br />          〃 {<em>{⌒    ⌒リ| l │ i| / /<br />          レ!小l●    ● 从 |、i| / <br />           ヽ|l⊃ 、</em>,、<em>, ⊂⊃ |ノ│|___</em>__/<br />         /⌒ヽ<strong>|ヘ   ゝ._)   j /⌒i !<br />       \ /:::::| l>,、 __, イァ/  /│<br /> .        /:::::/| | ヾ:::|三/::{ヘ、</strong>∧ |<br />        `ヽ< | |  ヾ∨:::/ヾ:::彡' |</p> <p>その名も、「よろずネットワーク」です。<br /> 作った目的は、「世の中のトレンドがわかって」「自由でゆるくて」「ネットの面白かった時代を再現できるような」掲示板を作りたいと思ったことです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.yorozu.network">よろずネットワーク</a></p> <p>ちなみに、よろずネットワークではAAを綺麗に投稿できます。<br /> <a href="https://crieit.now.sh/upload_images/b211ec485fd073472cd23c247404335f619a810ec0487.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b211ec485fd073472cd23c247404335f619a810ec0487.png?mw=700" alt="image.png" /></a></p> <p>コメント欄はこんな感じです。<br /> <a href="https://crieit.now.sh/upload_images/b211ec485fd073472cd23c247404335f619a81667ab12.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b211ec485fd073472cd23c247404335f619a81667ab12.png?mw=700" alt="image.png" /></a></p> <p>スレにはyoutube動画やニコニコ動画も貼り付けできます。<br /> <a href="https://crieit.now.sh/upload_images/b211ec485fd073472cd23c247404335f619a818d09a67.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b211ec485fd073472cd23c247404335f619a818d09a67.png?mw=700" alt="image.png" /></a></p> <p><strong>技術スタック</strong><br /> サーバー:Conoha VPS(ubuntu 20.04LTS) ×複数<br /> DB: Mariadb on Conoha<br /> バックエンドAPI:Django Rest Framework<br /> フロントエンド: Quasar(Nuxtjsみたいなやつ) でSSR(PWAモード)<br /> ドメイン:google domain<br /> CI/CD:github actions</p> <p>毎日アバター画像が自動生成されたり、返信がくると通知が来たり、カテゴリごとに毎時ランキングを見られたりなど、掲示板として使いやすいものを意識して作ったので、是非遊びに来てみてください!</p> <p> <a target="_blank" rel="nofollow noopener" href="https://www.yorozu.network">よろずネットワーク</a></p> Y O tag:crieit.net,2005:PublicArticle/16786 2021-03-28T18:20:44+09:00 2021-08-08T16:13:11+09:00 https://crieit.net/posts/VSCode-Web 【VSCode】拡張機能一覧(主にWeb系システム開発用) <p>個人用メモ。<br /> 主にPHPを使用して開発する環境で使用している拡張機能一覧。<br /> (他のも混じっているけど)<br /> (後で追加・変更・削除するかも)</p> <h1 id="VSCode"><a href="#VSCode">VSCode</a></h1> <ul> <li><p>Bookmarks<br />  あらかじめソースコード中にブックマークを設定しておくと、他の場所からブックマークまで一瞬で移動することが出来る。</p></li> <li><p>indent-rainbow<br />  インデントを色付きで表示してくれる。</p></li> <li><p>Japanese Language Pack for Visual Studio Code<br />  VSCodeの日本語化。</p></li> </ul> <h1 id="HTML"><a href="#HTML">HTML</a></h1> <ul> <li><p>Auto Close Tag<br />  自動的に閉じタグを追記してくれる。</p></li> <li><p>Auto Rename Tag<br />  タグを変更すると、対応する閉じタグを自動的に変更してくれる。</p></li> <li><p>HTML Snippets<br />  HTMLの予測変換を表示してくれる。</p></li> <li><p>HTMLHint<br />  HTMLの文法チェックをしてくれる。</p></li> </ul> <h1 id="PHP(Laravel)"><a href="#PHP%28Laravel%29">PHP(Laravel)</a></h1> <ul> <li><p>Bracket Pair Colorizer 2<br />  メソッドやArrayなどで、対となるカッコを色付きで表示してくれる。</p></li> <li><p>php cs fixer<br />  PHPソースを自動整形してくれる。<br />  ※別途、「php-cf-fixer.phar」のインストールが必要。</p></li> <li><p>PHP Debug<br />  PHPをステップ実行してデバッグできるようになる。</p></li> <li><p>PHP IntelliSense<br />  PHPの予測変換を表示してくれる。</p></li> </ul> <p>【2021.8.8 追加】<br /> * Laravel Blade Snippets<br />  Laravelのbladeファイル内のタグやディレクティブを色付きで表示してくれる。</p> <ul> <li>Laravel Blade formatter<br />  Laravelのbladeファイル用のフォーマッタ。<br />  blade独自のディレクティブもインデントしてくれる(これ重要)。</li> </ul> <h1 id="JavaScript(Node.js,Vue.js)"><a href="#JavaScript%28Node.js%2CVue.js%29">JavaScript(Node.js,Vue.js)</a></h1> <ul> <li><p>ESLint<br />  JavaScriptの構文チェックをリアルタイムで実行してくれる。<br />  ※Vue.jsで使用するには設定が必要。</p></li> <li><p>JavaScript (ES6) code snippets<br />  JavaScriptの予測変換を表示してくれる。</p></li> <li><p>Vetur<br />  Vue.jsコードのシンタックスハイライトやコード補完、リント、フォーマットを行ってくれる。</p></li> </ul> <h1 id="CSS(Sass,SCSS)"><a href="#CSS%28Sass%2CSCSS%29">CSS(Sass,SCSS)</a></h1> <ul> <li><p>IntelliSense for CSS class names in HTML<br />  CSSクラス名を入力するときに、入力補完してくれる。</p></li> <li><p>SCSS Formatter<br />  SCSSコードを自動整形してくれる。</p></li> </ul> <h1 id="その他"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96">その他</a></h1> <ul> <li><p>Log File Highlighter<br />  ログファイルの内容を色付きで表示してくれる。</p></li> <li><p>MySQL<br />  VSCodeでMySQLを使用可能にする。</p></li> <li><p>Rainbow CSV<br />  CSVファイルをカラムごとに色分けして表示してくれる。</p></li> <li><p>Regex Previewer<br />  正規表現の実行結果をプレビュー表示してくれる。</p></li> <li><p>SFTP<br />  サーバへ自動的にソースファイルをアップロードしてくれる。</p></li> </ul> <h1 id="【参考】"><a href="#%E3%80%90%E5%8F%82%E8%80%83%E3%80%91">【参考】</a></h1> <p>「Visual Studio Code」をインストールしてPHPコードのデバッグ環境を設定する方法<br /> <a target="_blank" rel="nofollow noopener" href="https://incloop.com/visualstudiocodeのデバッグ設定/">https://incloop.com/visualstudiocodeのデバッグ設定/</a></p> <p>Visual Studio Codeで作るPHP開発環境のおすすめ拡張機能17選<br /> <a target="_blank" rel="nofollow noopener" href="https://wonwon-eater.com/vscode-php-plugin/">https://wonwon-eater.com/vscode-php-plugin/</a></p> <p>VScodeの日本語化ができない 変わらない時の対処法[Visual Studio Code]<br /> <a target="_blank" rel="nofollow noopener" href="https://rabotiku-sato.com/vscode-japanese-setting">https://rabotiku-sato.com/vscode-japanese-setting</a></p> <p>vscodeでVue.jsを書くときに使っているプラグインとか<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/dayoshix/items/c61a75a971331418c348">https://qiita.com/dayoshix/items/c61a75a971331418c348</a></p> <p>【HTML編】Visual Studio Code おすすめプラグイン紹介 #02<br /> <a target="_blank" rel="nofollow noopener" href="https://so-da.tech/tech/vscode/vscd3/">https://so-da.tech/tech/vscode/vscd3/</a></p> <p>VSCodeのAuto Rename Tagで閉じタグも自動変更<br /> <a target="_blank" rel="nofollow noopener" href="https://tech.pjin.jp/blog/2020/04/27/vscode_extension_auto_rename_tag/">https://tech.pjin.jp/blog/2020/04/27/vscode_extension_auto_rename_tag/</a></p> <p>VSCode で HTML の文法チェックを行う拡張機能 HTMLHint<br /> <a target="_blank" rel="nofollow noopener" href="https://loumo.jp/archives/26229">https://loumo.jp/archives/26229</a></p> <p>VSCode拡張機能『indent-rainbow』でインデントを虹色にする方法<br /> <a target="_blank" rel="nofollow noopener" href="https://onedarling.site/programming/tool/vscode-indent-rainbow/">https://onedarling.site/programming/tool/vscode-indent-rainbow/</a></p> <p>【2020年版】VSCodeでhtml/css/jsの拡張機能おすすめ<br /> <a target="_blank" rel="nofollow noopener" href="https://uetani33.net/vscode-web-extensions/#toc_id_3_2">https://uetani33.net/vscode-web-extensions/#toc_id_3_2</a></p> <p>VSCode使い必見!?使って便利な Visual Studio Code 拡張機能10選<br /> <a target="_blank" rel="nofollow noopener" href="https://www.geekfeed.co.jp/geekblog/vscode_extension">https://www.geekfeed.co.jp/geekblog/vscode_extension</a></p> <p>【超便利】VSCodeでMySQLを利用する方法<br /> <a target="_blank" rel="nofollow noopener" href="https://newmtube07.com/vscode-mysql/">https://newmtube07.com/vscode-mysql/</a></p> <p>[Visual Studio Code] PHPのコードを整形する「php cs fixer」の設定<br /> <a target="_blank" rel="nofollow noopener" href="https://www.searchlight8.com/visual-studio-code-php/">https://www.searchlight8.com/visual-studio-code-php/</a></p> <p>VSCode(Visual Studio Code)でSFTP・FTP経由でファイルを自動アップロードや同期できる拡張機能「SFTP」が超便利<br /> <a target="_blank" rel="nofollow noopener" href="https://www.karelie.net/vscode-sftp/">https://www.karelie.net/vscode-sftp/</a></p> <p>Windows10でVisual Studio Code + vue-cliの開発環境構築メモ<br /> <a target="_blank" rel="nofollow noopener" href="https://belhb.hateblo.jp/entry/2020/08/08/142540">https://belhb.hateblo.jp/entry/2020/08/08/142540</a></p> <p>【2021.8.8 追加】<br /> LaravelでVisual Studio Codeを使う時に入れておきたい拡張機能3選<br /> <a target="_blank" rel="nofollow noopener" href="https://biz.addisteria.com/laravel-vscode/">https://biz.addisteria.com/laravel-vscode/</a></p> <p>Laravel blade formatter VSCode Extensionを作った<br /> <a target="_blank" rel="nofollow noopener" href="https://shufo.dev/2020/10/03/published-vscode-blade-formatter/">https://shufo.dev/2020/10/03/published-vscode-blade-formatter/</a></p> <p>VSCodeでBladeテンプレートを整形する<br /> <a target="_blank" rel="nofollow noopener" href="https://blog.nplpl.com/310">https://blog.nplpl.com/310</a></p> acmz tag:crieit.net,2005:PublicArticle/16152 2020-10-19T18:06:59+09:00 2020-10-19T18:06:59+09:00 https://crieit.net/posts/Vuex 【Vuex】別モジュールのステート、メソッド呼び出し <pre><code class="js">const moduleA = { // ... actions: { testA(context) { // state console.log(context.rootState.moduleName.stateName) // mutation context.commit("moduleName/mutationName", "引数", {root: true}); // action context.dispatch("moduleName/actionName", "引数", {root: true}); } } </code></pre> みみみみみ tag:crieit.net,2005:PublicArticle/16070 2020-09-24T10:25:23+09:00 2020-09-24T10:25:23+09:00 https://crieit.net/posts/unlock-bank ハッカー専用パズルゲームを作ったので全てネタバレする <blockquote> <p>当記事は <a target="_blank" rel="nofollow noopener" href="https://zenn.dev/kinmi/articles/b6646b4902dbda585c0b">ハッカー専用パズルゲームを作ったので全てネタバレする</a> のクロス投稿です</p> </blockquote> <h1 id="💰作ったもの💰"><a href="#%F0%9F%92%B0%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE%F0%9F%92%B0">💰作ったもの💰</a></h1> <p><a href="https://crieit.now.sh/upload_images/aa565ccbf6a415ffff3d41f0f20fe93f5f6bf02787aff.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/aa565ccbf6a415ffff3d41f0f20fe93f5f6bf02787aff.png?mw=700" alt="UNLOCK BANK" /></a><br /> <a target="_blank" rel="nofollow noopener" href="https://unlock-bank.vercel.app/">UNLOCK BANK</a></p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">@銀行口座をHackしたくてウズウズしている皆さん「違法行為はしたくないけどハッキングはしたい…」そんな貴方の悩みを解決するゲームをご用意しました。このゲームでは何をやっても合法です。是非、不正ログインしてみてください💰<a target="_blank" rel="nofollow noopener" href="https://t.co/LyIpYLCRrP">https://t.co/LyIpYLCRrP</a> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/UNLOCK_BANK?src=hash&ref_src=twsrc%5Etfw">#UNLOCK_BANK</a></p>— きんみ | ツイッター大喜利サイト🎍ついぎり🎍作りました🙄 (@_kinmi) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/_kinmi/status/1307988548591611904?ref_src=twsrc%5Etfw">September 21, 2020</a></blockquote> <h2 id="仕様"><a href="#%E4%BB%95%E6%A7%98">仕様</a></h2> <ul> <li>Account Number(数字8桁) と Password(数字4桁) によるログイン認証</li> <li>Account Number は全て存在する(8桁なので全口座数は1億口)</li> <li>Password を3回間違えるとロック(再試行不可)される</li> <li><em>[裏設定]</em> 銀行口座を模している為、もちろん<strong>パスワードが流出したら他者もログイン可能</strong></li> </ul> <p>ユーザーはどんな攻撃手法を用いても良い。<br /> 最近話題の1段階認証を突破してみようというパズルゲーム。</p> <h2 id="システム構成"><a href="#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E6%A7%8B%E6%88%90">システム構成</a></h2> <ul> <li>JSフレームワーク: Nuxt.js 2.14.5(Vue.js 2.x)</li> <li>トランスパイル: TypeScript 4.0</li> <li>CSSフレームワーク: TailwindCSS</li> <li>ホスティング: Vercel</li> </ul> <p>お馴染みの構成を選択したが、Vue.jsの使用については少し後悔している(後述)</p> <h1 id="以下ネタバレ"><a href="#%E4%BB%A5%E4%B8%8B%E3%83%8D%E3%82%BF%E3%83%90%E3%83%AC">以下ネタバレ</a></h1> <h2 id="想定する解法"><a href="#%E6%83%B3%E5%AE%9A%E3%81%99%E3%82%8B%E8%A7%A3%E6%B3%95">想定する解法</a></h2> <p>基本的には以下の2つと考えている。<br /> - <strong>☠️リバースブルートフォース攻撃☠️</strong><br /> 件の金融事件で脚光を浴びた攻撃手法。<br /> 暗証番号の総当たり(ブルートフォース攻撃)は一般的なシステムだと試行回数に制限を設けているが、ユーザーIDの様な公開情報については秘匿性が低いため無制限に試行できるシステムが多い。そこを突いてパスワードは固定のままIDの方を総当たりするという攻撃。<br /> 特に当ゲームのような、認証情報に数値しか許容していないシステムだと少ない試行回数で突破できるため致命的な脆弱性となる。</p> <ul> <li><strong>😈リバースエンジニアリング😈</strong><br /> JavaScriptのみで構成している為、コードを解析してパスワードの取得が可能。<br /> 当初はNode.jsサーバー等を用意して認証処理を隠蔽するつもりだったがガチ攻撃された場合、サーバーか私の財布が死ぬと思い断念。<br /> しかし「ハッカー専用」と銘打っている以上、コードを覗くだけで突破されては面白くないので悪足掻きとして難読化を施している。</li> </ul> <h2 id="仕組みについて"><a href="#%E4%BB%95%E7%B5%84%E3%81%BF%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">仕組みについて</a></h2> <h3 id="🔐パスワード生成"><a href="#%F0%9F%94%90%E3%83%91%E3%82%B9%E3%83%AF%E3%83%BC%E3%83%89%E7%94%9F%E6%88%90">🔐パスワード生成</a></h3> <p>仕様として、口座番号とパスワードの紐付きを作らないといけない(パスワードが流出したら誰でもログインできるように)<br /> 最初は愚直に1億口座分のパスワードマッピングを定数で用意しようと考えた。<br /> しかし上述のコードを隠蔽できない事情によりリテラル値で持つのは避けたかった為、下記のライブラリを採用した。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/davidbau/seedrandom">seedrandom.js</a><br /> これを用いてシード値から再現性のある乱数を生成(※1)する。</p> <p>下記のように口座番号をシード値とした乱数を生成した後、整形してパスワードとしている。</p> <pre><code class="ts">const seedrandom = require('seedrandom') function authenticated(account: string, password: string): boolean { const rng = seedrandom(account) const _pass = String(Math.round(rng() * 10000)).padStart(4, '0') return _pass === password } </code></pre> <p>これで口座番号とパスワードの紐付きを再現している。</p> <h4 id="seedrandom.jsの使用感"><a href="#seedrandom.js%E3%81%AE%E4%BD%BF%E7%94%A8%E6%84%9F">seedrandom.jsの使用感</a></h4> <ul> <li>🙆‍♂️ <strong>良かった所</strong> <ul> <li>直感的に使える</li> <li>複数の乱数アルゴリズムに対応している</li> <li><code>Math.random()</code>をラップできる為、テストにも使えそう</li> </ul></li> <li>🙅‍♂️ <strong>頑張って〜</strong> <ul> <li>型定義がない(2020/09現在、<a target="_blank" rel="nofollow noopener" href="https://github.com/davidbau/seedrandom/pull/70">issue</a>にて対応中)</li> </ul></li> </ul> <p>個人的には「再現性のある乱数を生成する処理」というのは<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kinmi/items/ddd213bf7a09f67f68ee">バーコードバトラーライクのゲーム</a>を作る際に重宝するので、有難いライブラリ。</p> <h3 id="状態管理, 認証処理"><a href="#%E7%8A%B6%E6%85%8B%E7%AE%A1%E7%90%86%2C+%E8%AA%8D%E8%A8%BC%E5%87%A6%E7%90%86">状態管理, 認証処理</a></h3> <p>本筋と外れるが、クライアントで認証処理を行う為に口座番号と暗証番号をグローバル管理する必要があった。<br /> Vuexはオーバースペックなので状態を<a target="_blank" rel="nofollow noopener" href="https://ja.nuxtjs.org/guide/plugins/#%E7%B5%B1%E5%90%88%E3%81%95%E3%82%8C%E3%81%9F%E6%B3%A8%E5%85%A5">Inject</a>するプラグインを自作してstoreとして使用した。</p> <blockquote> <p>~/plugins/auth.ts</p> </blockquote> <pre><code class="ts">import Vue from 'vue' import { Plugin } from '@nuxt/types' import { Seedrandom } from '../types/seedrandom' const seedrandom = require('seedrandom') as Seedrandom type InjectTypeAuth = { /** * 口座番号 */ accountNumber: string /** * 暗証番号 */ password: string /** * 認証処理 * seedから4桁の乱数(先頭0埋め)を生成し、passwordとの比較結果を返却する * @param {string} seed シード値となる値 * @param {string} password パスワード */ authenticated(seed: string, password: string): boolean } declare module '@nuxt/types' { interface Context { $auth: InjectTypeAuth } interface NuxtAppOptions { $auth: InjectTypeAuth } } declare module 'vue/types/vue' { interface Vue { $auth: InjectTypeAuth } } type State = { accountNumber: string password: string } /********************************************** * 認証情報プラグイン * @param {Context} ctx * @param {(key: string, value: any) => void} inject */ const AuthPlugin: Plugin = (_ctx, inject) => { /** * Observable properties */ const state = Vue.observable({ accountNumber: '', password: '', } as State) function authenticated(seed: string, password: string): boolean { const rng = seedrandom(seed) const _pass = String(Math.round(rng() * 10000)).padStart(4, '0') if (_pass === password) state.password = _pass return _pass === password } /** * Injection */ inject('auth', { get accountNumber() { return state.accountNumber }, set accountNumber(accountNumber: string) { state.accountNumber = accountNumber }, get password() { return state.password }, authenticated, }) } export default AuthPlugin </code></pre> <p>※ 実際の乱数生成処理はもう少しノイズを入れてるので、このまま動かしても同じパスワードは生成されません</p> <p>この<code>authenticated</code>をEnterボタン押下時と、直アクセスを防ぐ目的でログイン後ページ内の<code>validate()</code>hookでも呼び出して認証処理としている。</p> <p>⚠️注意<br /> <em>当然ですが、フロントエンドでパスワードの一致チェック等の認証処理を行ってはいけません。</em></p> <p>Vue3のRCが外れて正式リリースとなったが、<a target="_blank" rel="nofollow noopener" href="https://v3.vuejs.org/api/basic-reactivity.html">Reactivity API</a>等を活用したVuex(ver 5)のリリースはまだ先の様なので暫く状態管理はこの手法に落ち着きそう。</p> <h2 id="攻撃方法の紹介"><a href="#%E6%94%BB%E6%92%83%E6%96%B9%E6%B3%95%E3%81%AE%E7%B4%B9%E4%BB%8B">攻撃方法の紹介</a></h2> <p>以上を踏まえ、解法の一つであるリバースブルートフォース攻撃の実施方法を紹介していく。<br /> 尚、当ゲームでは外部からブラウザ操作しやすいように主要な要素に対してid属性を付与している。<br /> <a href="https://crieit.now.sh/upload_images/8e2ef469b048d0e22a0b6e053a8a9f7d5f6bf3fec95cd.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8e2ef469b048d0e22a0b6e053a8a9f7d5f6bf3fec95cd.png?mw=700" alt="振っているID一覧" /></a></p> <h3 id="前提知識: JavaScript の場合"><a href="#%E5%89%8D%E6%8F%90%E7%9F%A5%E8%AD%98%3A+JavaScript+%E3%81%AE%E5%A0%B4%E5%90%88">前提知識: JavaScript の場合</a></h3> <p>「<strong>システム構成</strong>」の節でも触れたが、Vue.jsの採用を後悔したのはここ。</p> <p>システムがHTML+PureJSの構成であれば、ブラウザの開発者コンソールから下記の様に実行して入力値を動的に与える事が可能。</p> <pre><code class="js">const digit1 = document.getElementById('digit1') digit1.value = "1" </code></pre> <p>これは一般的な方法だし、私も当初このやり方を想定していた。<br /> しかしVue.jsの場合、inputイベントを検知してVueインスタンス内に保有するデータを更新する。<br /> 直接input要素のvalue値を書き換えてもイベントは発火せず、データは更新されない。<br /> この挙動を想定しておらず、ユーザーに無駄なハードルを与えることとなってしまった。</p> <p>ではどうするのか。<br /> 当ゲームには数値をカウントアップ/カウントダウンするボタンを実装している。<br /> <a href="https://crieit.now.sh/upload_images/ffc2f4e46d2b2c361e83cb2dd2287bde5f6bf42460c65.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ffc2f4e46d2b2c361e83cb2dd2287bde5f6bf42460c65.png?mw=700" alt="これ" /></a></p> <p><strong>金融システムって誰得UI多いよな</strong> という遊び心で実装したボタンだが、Vue内のデータを書き換えるイベントに直結している。<br /> このボタンをプログラムからクリックすることが出来れば口座番号入力の自動化が可能となる。</p> <p>ここから先の解説は、既にUNLOCK成功された<a target="_blank" rel="nofollow noopener" href="https://twitter.com/yoneapp">@yoneapp</a>さんが記事を書かれている為、そちらに任せることとする。<br /> <a target="_blank" rel="nofollow noopener" href="https://zenn.dev/yoneapp/articles/ec2892c7e2e5c499684d">リバースブルートフォース攻撃を使ってUNLOCK BANKの口座に不正ログインして優勝する</a>(Zenn)<br /> (改めて解説記事の執筆ありがとうございます🙇‍♂️)</p> <h3 id="前提知識: Vue.js の場合"><a href="#%E5%89%8D%E6%8F%90%E7%9F%A5%E8%AD%98%3A+Vue.js+%E3%81%AE%E5%A0%B4%E5%90%88">前提知識: Vue.js の場合</a></h3> <p>Vue.jsには <a target="_blank" rel="nofollow noopener" href="https://github.com/vuejs/vue-devtools">vue-devtools</a> というデバッグ用のブラウザ拡張が存在する。<br /> これを使えばコンポーネント構成を覗きつつ、各種データの書き換えやイベント発火が可能となるが通常はProduction環境で開けない。<br /> しかし、開発者ツールを使いこれをこじ開ける方法がある。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/POPOPON/items/60010faae1eb4e4e67ac">本番公開されているサイトで Vue devtools を使う裏技</a>(Qiita)</p> <p>厳密にはソースの難読化を施してるのため上記記事と全く同じではないが、ソースを検索すれば<code>devtool</code>というキーワードは見つかるはず。<br /> あとはその後ろにブレークポイントを張り、<code>_0x1bd719.devtools = true</code> を実行すれば開発者ツールを開けることが可能。</p> <p><img src="https://storage.googleapis.com/zenn-user-upload/y04xwot067stonsyjc0dxwtwrbj6" alt="ProductionでDevtoolsを開いた図" /></p> <p>図の状態(Devtoolsでコンポーネントを選択した状態)だと、開発者コンソール上で<code>$vm0</code>というオブジェクトが使用出来る。これは選択したコンポーネントのVueインスタンスであり、コンポーネント内に存在するデータやメソッドが全て内包されている。<br /> あとはそこからアタリを付けて、自動化プログラムを組めば良い。<br /> 実際には下記をイジれば入力操作の自動化が可能となる。</p> <pre><code class="js">$vm0.accountNumbers // 口座番号(1桁ずつ格納した配列) $vm0.password // パスワード $vm0.enter() // Enterボタン押下時の処理を実行 </code></pre> <p>⚠️注意<br /> <em>初めてこれ(Productionでdevtoolsが使えること)を知った方はセキュリティ面に不安を覚えるかもしれませんが、それはお門違いです。</em><br /> <em>フロントエンドで保持するデータはユーザーから自由に改ざんされる前提であるべきです。</em></p> <h3 id="前提知識: Nuxt.js の場合"><a href="#%E5%89%8D%E6%8F%90%E7%9F%A5%E8%AD%98%3A+Nuxt.js+%E3%81%AE%E5%A0%B4%E5%90%88">前提知識: Nuxt.js の場合</a></h3> <p>Nuxt.jsで作られたアプリケーションは<code>window</code>直下に<code>$nuxt</code>というオブジェクトが作られる。<br /> そこには全ての情報が含まれており、上記のようにDevtoolsを開かずとも開発者コンソールからデータの書き換えやメソッドの実行が可能。<br /> しかし内包する情報量が膨大な為、開発者ではない第三者が操作したい対象を探すのは一苦労かもしれない。<br /> ちなみに当ゲームでは下記が自動化に必要な対象となる。</p> <pre><code class="js">window.$nuxt.$children[1].$children[0].$children[0].accountNumbers window.$nuxt.$children[1].$children[0].$children[0].password window.$nuxt.$children[1].$children[0].$children[0].enter() </code></pre> <h3 id="その他の手法"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96%E3%81%AE%E6%89%8B%E6%B3%95">その他の手法</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://www.selenium.dev/documentation/ja/">Selenium</a> や <a target="_blank" rel="nofollow noopener" href="https://github.com/puppeteer/puppeteer">Puppeteer</a> 等を用いたブラウザ操作の自動化があげられる。<br /> 今のところ、これらを使ってUNLOCKしたという報告は観測していない。ブラウザの開発者ツールが万能すぎる。</p> <p>⚠️注意<br /> <em>悪意が無くともサーバーに高負荷をかける行為は法律により罰せられる可能性があります。</em><br /> <em>しかし、当ゲームで行う分には問題ありません。存分に攻撃してください。</em></p> <h3 id="更にネタバレ"><a href="#%E6%9B%B4%E3%81%AB%E3%83%8D%E3%82%BF%E3%83%90%E3%83%AC">更にネタバレ</a></h3> <p>もしproduction環境でログイン後のページを確認する必要が出た場合を考慮して、<br /> - <strong>Account Number</strong>: 1145-1419<br /> - <strong>Password</strong>: 1919</p> <p>と入力したら開発コンソールに上記口座のパスワードが出力されるようになっている。<br /> 攻撃するのは面倒くさいけどログインしてみたい、という方はどうぞ。</p> <h2 id="確率について"><a href="#%E7%A2%BA%E7%8E%87%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">確率について</a></h2> <p>数値4桁のパスワードというのは 0000 ~ 9999 の1万パターンしかない。<br /> パスワードを固定にして、口座番号を総当たりした場合の確率は下記で求められる。</p> <p><em>1 - ( 9999 / 10000 )^x</em><br /> x=試行回数</p> <p>(間違ってたらご指摘ください)<br /> 確率を表にするとこうなる。<br /> <div class="table-responsive"><table> <thead> <tr> <th>試行回数</th> <th>HITする確率</th> </tr> </thead> <tbody> <tr> <td>3回</td> <td>0.03%未満</td> </tr> <tr> <td>10回</td> <td>約0.1%</td> </tr> <tr> <td>100回</td> <td>約1%</td> </tr> <tr> <td>1,000回</td> <td>約10%</td> </tr> <tr> <td>10,000回</td> <td>約63%</td> </tr> <tr> <td>100,000回</td> <td>約99%</td> </tr> </tbody> </table></div></p> <p>つまり、口座番号8桁(1億パターン)全て試行せずとも、10万回程度で1口座はUNLOCKできてしまう。<br /> 下5桁総当たりすればいい計算で、実際にTwitterでUNLOCKされた方の反応も「思ったより早かった」という声が多かった。<br /> 当ゲームはパスワードを乱数によって生成しているが、これが本当の銀行口座の場合、パスワードの偏りが生じるはずなので更にUNLOCKは容易となるだろう。</p> <h1 id="おわり"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A">おわり</a></h1> <p>ネタのつもりで作ったんですが思いのほか反響があって、解説記事を書いて頂いたり、難読化をデコードしてリバースエンジニアリングまでやって下さってる方もいて、個人開発冥利に尽きるなと思いました。<br /> 他にも「こんなやり方あるよ」という方がいましたらご連絡ください。</p> <h1 id="注釈"><a href="#%E6%B3%A8%E9%87%88">注釈</a></h1> <p>※1: JavaScriptにはシード値から乱数を生成する機能が備わっていない</p> きんみ tag:crieit.net,2005:PublicArticle/16051 2020-09-07T14:22:14+09:00 2020-09-07T23:02:38+09:00 https://crieit.net/posts/Nuxt-js-lint Nuxt.jsにlintを導入してみた <h2 id="動機"><a href="#%E5%8B%95%E6%A9%9F">動機</a></h2> <p>以前からなんとなくeslintは導入してあーだこーだしてたのですが、ポートフォリオサイトやブログサイトをNetlifyからFirebaseに移植する際に、github actions(CI)を使うようになり、せっかくだからもう少し本格的にlintやtestを導入してみようということで、色々調べて導入してみました。</p> <p>その手順を初心者なりに備忘録としてまとめてみました。<br /> (だいぶ我流な部分もありますが多めに見ていただければ幸いです)</p> <p>アドバイスなどは大歓迎です。</p> <p>知識が増えたら随時更新します。</p> <p><a target="_blank" rel="nofollow noopener" href="https://blog.taka1156.site">https://blog.taka1156.site</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/taka1156/nuxt-blog">https://github.com/taka1156/nuxt-blog</a> - アトミックデザインの形にプロジェクトを見直し中</p> <p>↑ブログのプロジェクトで導入してみました。(Nuxt.js + Firebase + microCMS 構成)</p> <h2 id="環境"><a href="#%E7%92%B0%E5%A2%83">環境</a></h2> <ul> <li>mac OS Catalina</li> <li>Node.js v12.16.1</li> <li>npm v6.14.3</li> <li>yarn v1.22.0</li> </ul> <h2 id="手順"><a href="#%E6%89%8B%E9%A0%86">手順</a></h2> <ol> <li><p>これらをインストール</p> <ul> <li><p>eslint関連<br /> <code>yarn add eslint eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue babel-eslint</code></p></li> <li><p>stylelint関連<br /> <code>yarn add stylelint stylelint-config-standard stylelint-config-prettier stylelint-prettier</code></p></li> <li><p>prettier<br /> <code>yarn add prettier</code></p></li> </ul> <p>※ なんでprettierを入れたか<br /> eslintだけだとエラーは出すが修正はしない項目(コード整形関連)もあるので併用する。<br /> (逆にprettierは細かい命名規則や処理に関するエラーは出さない?)</p></li> <li><p>設定ファイルを書く</p> <ul> <li>.eslintrc.json</li> </ul> <pre><code class="json">{ "parserOptions": { "parser": "babel-eslint", "sourceType": "module" }, "extends": [ "eslint:recommended", "plugin:vue/recommended", "plugin:prettier/recommended", "prettier/vue" ], "plugins": [ "vue" ], "env": { "jest": true, "browser": true, "es6": true, "node": true }, "globals": {}, "rules": { "camelcase": [2,{"properties": "always"}], "quotes": [2,"single", { "avoidEscape": true } ], "eqeqeq": [2, "always", {"null": "ignore"}], "prefer-const": 2, "vue/component-name-in-template-casing": "off", "vue/no-v-html": "off" } } </code></pre> <ul> <li>.stylelintrc.json</li> </ul> <pre><code class="json">{ "extends": ["stylelint-config-standard", "stylelint-prettier/recommended"], "rules": { "color-no-invalid-hex": true, "color-hex-case": "lower", "selector-class-pattern": "^(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:\\[.+\\])?$" } } </code></pre> <ul> <li>.eslintignore, .stylelintignore<br /> ※ storybook-static、coverage は storybookやjestを導入していると生成されるファイル</li> </ul> <pre><code class="txt"># ビルドファイル dist storybook-static coverage # 設定、モジュール .nuxt .storybook node_modules </code></pre> <ul> <li>.prtteirrc.json</li> </ul> <pre><code class="json">{ "printWidth": 85, "tabWidth": 2, "singleQuote": true, "semi": true, "bracketSpacing": true, "arrowParens": "avoid" } </code></pre></li> <li><p>scriptsを追加</p> <pre><code class="json">"scripts": { "lint-js": "eslint --ext .js,.vue .", "lint-style": "stylelint \"**/*.vue\" \"**/*.css\"", "lint-js:fix": "eslint --ext .js,.vue . --fix", "lint-style:fix": "stylelint \"**/*.vue\" \"**/*.css\" --fix", "lint:check": "yarn lint-js & yarn lint-style", "lint:fix": "yarn lint-js && yarn lint-style", ... } </code></pre></li> <li><p>お好みでvscode設定を追加<br /> 保存時に勝手にlint処理が走るようになるので便利<br /> (vscode/setting.jsonに記述)</p> <pre><code class="json">{ "comments": "保存時にeslint、stylelintを自動実行", "eslint.alwaysShowStatus": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": true, "source.fixAll.stylelint": true }, } </code></pre></li> </ol> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>eslint、stylelintについてざっくりと使い方やルールの指定方法についてわかってきたので、<br /> 積極的に活用して読みやすいコードを意識していきたいですね。</p> <p>また、eslintルールについても、気になるものがあればまとめてみようと思います。</p> taka1156 tag:crieit.net,2005:PublicArticle/15900 2020-05-17T06:58:19+09:00 2023-07-07T18:45:02+09:00 https://crieit.net/posts/pay-web-service-release-knowledge-summary 有料のWebサービスをリリースするまでに取り組んだこと・知見をまとめました【個人開発】 <h1 id="1. 作ったサービス"><a href="#1.+%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9">1. 作ったサービス</a></h1> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117180/dbadeb45-61fc-48d6-3a8c-d7908b6270d8.png" alt="twikeshi-ogp.png" /></p> <p><strong>ツイ消し職人 | ツイ消しツールの決定版!</strong><br /> <strong><a target="_blank" rel="nofollow noopener" href="https://twikeshi.net/">https://twikeshi.net/</a></strong></p> <blockquote> <p>ツイ消しツールの決定版!<br /> ツイ消し職人は 今までのツイートを全消しできる「ツイート一括削除ツール」 です。<br /> 3200件を超える大量のツイートを全て削除できます。<br /> 無料のツールなどでうまく削除できなかった方は是非ご利用ください。<br /> 既存のフォロワーをそのままに、Twitterをやり直すことができます。</p> </blockquote> <h1 id="2. 自己紹介"><a href="#2.+%E8%87%AA%E5%B7%B1%E7%B4%B9%E4%BB%8B">2. 自己紹介</a></h1> <p>こんにちは、ひろと申します。<br /> 現在はフリーランスエンジニアとして活動しています。</p> <h1 id="3. なぜ作ったのか"><a href="#3.+%E3%81%AA%E3%81%9C%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%8B">3. なぜ作ったのか</a></h1> <p>私は2020年4月にフリーランスとして独立しました。<br /> それに伴い、学生時代から使っていたTwitterアカウントの運用を変えようと思い、今までのツイートを削除してやり直すことにしました。<br /> アカウントを作り直す選択肢もあったのですが、フォロワーを減らしたくなかったため、ツイ消しの道を選びました。<br /> 調べてみるとツイートの一括削除ツールがいくつか見つかったため、それを使ってツイ消しをすることにしました。</p> <p>しかし、<strong>既存のツールではツイートの削除ができませんでした。</strong><br /> 私の今までのツイート数は<strong>19万件</strong>で、Twitterアーカイブをダウンロードしたところzipファイルのサイズは<strong>31GB</strong>。<br /> 私が<strong>ツイ廃</strong>だったのが全ての原因です。</p> <p>普通のツイ消しサービスは、API制限の関係で3,200ツイートが削除の上限となってしまいます。私の19万ツイートに対してはあまりに無力すぎました。</p> <p>もちろん、API制限を回避するためにTwitterアーカイブをアップロードして削除を行うサービスもあります。<br /> しかし、31GBのzipファイルを送りつけると必ず500エラーが返ってきてしまい、私の試した範囲では、まともに動くものはありませんでした。海外の有料サービスでさえダメでした。(具体的なサービス名は出しませんが、日本円で1,600円払いました。手痛い出費です)</p> <p>そこで私は、「ツイ廃でもツイートを削除できるサービス」が必要だと思い、ツイ消し職人の開発を始めました。</p> <h1 id="4. リリースするまでに取り組んだこと"><a href="#4.+%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%99%E3%82%8B%E3%81%BE%E3%81%A7%E3%81%AB%E5%8F%96%E3%82%8A%E7%B5%84%E3%82%93%E3%81%A0%E3%81%93%E3%81%A8">4. リリースするまでに取り組んだこと</a></h1> <p>取り組んだ全てのことを記載しています。</p> <h2 id="一. サービスの命名"><a href="#%E4%B8%80.+%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AE%E5%91%BD%E5%90%8D">一. サービスの命名</a></h2> <p>最初は「ツイートクリーナー」という名前にしていました。<br /> 開発中盤に「ツイ消し職人」という名前を思いつき、変更しました。<br /> ランサーズなどで名前を募集するのも良いと思います。</p> <h2 id="二. ドメインの取得"><a href="#%E4%BA%8C.+%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%81%AE%E5%8F%96%E5%BE%97">二. ドメインの取得</a></h2> <p>ムームードメインでtwikeshi.netを取得しました。<br /> 欲しいドメインが埋まっている場合は、twikeshi-app.netのように工夫するのもオススメです。</p> <h2 id="三. 商標登録"><a href="#%E4%B8%89.+%E5%95%86%E6%A8%99%E7%99%BB%E9%8C%B2">三. 商標登録</a></h2> <p>登録しました。<br /> 今は<a target="_blank" rel="nofollow noopener" href="https://toreru.jp/">Toreru</a>などの便利なサービスがあり、ものすごく簡単に出願できます。<br /> 48,000円で5年間有効になります。<br /> 商標権の取る取らないを選択するのは自由ですが、後から商標を第三者に取得されて商標権の侵害警告を受けた場合、サービス名やドメインを変えなければならないリスクがあることは認識しておく必要があります。</p> <h2 id="四. プライシング"><a href="#%E5%9B%9B.+%E3%83%97%E3%83%A9%E3%82%A4%E3%82%B7%E3%83%B3%E3%82%B0">四. プライシング</a></h2> <p>海外の同じようなサービスを参考に値付けを行いました。<br /> 現在は700円(税込)で提供しています。</p> <p>その後、プライシングに関する本を3冊読んで(<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/gp/product/B07KWRPBP9/">この本</a>と<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/gp/product/B013JEK6YS/">この本</a>と<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/gp/product/B077S7BDD9/">この本</a>)考え方が変わったので、そのうち値上げするかもしれません。</p> <p><strong>間違っても、本来ターゲットでない人を取り込むために値下げするのはやめてください</strong>。<br /> 例えば、私は友人達に「ツイ消し職人の適正価格はいくらだと思う?」と質問をすると、2人が「100円」と答えました。<br /> しかし、断言しますが彼らは<strong>100円でも絶対に利用しません</strong>。何故ならば、彼らはこのツールの価値を理解していないからです。ツイ消しをしようと思ったことがない人に相場感を聞いても意味がありません。<br /> 逆に、本っっ当にツイ消しをしたくて困っている人からすれば、このツールが例え1万円でも喜んでお金を払うはずです。</p> <h2 id="五. 技術選定"><a href="#%E4%BA%94.+%E6%8A%80%E8%A1%93%E9%81%B8%E5%AE%9A">五. 技術選定</a></h2> <h3 id="1. バックエンド"><a href="#1.+%E3%83%90%E3%83%83%E3%82%AF%E3%82%A8%E3%83%B3%E3%83%89">1. バックエンド</a></h3> <p>バックエンドはLaravelで開発しました。<br /> Laravelはコードもドキュメントも読みやすいため、使っていて楽しいですね。</p> <div class="table-responsive"><table> <thead> <tr> <th align="left">役割</th> <th align="left">技術</th> </tr> </thead> <tbody> <tr> <td align="left">PHPフレームワーク</td> <td align="left">Laravel</td> </tr> <tr> <td align="left">データベース</td> <td align="left">MySQL</td> </tr> <tr> <td align="left">Twitterログイン</td> <td align="left">Laravel Socialite</td> </tr> <tr> <td align="left">Twitter APIライブラリ</td> <td align="left">TwitterOAuth</td> </tr> <tr> <td align="left">メール送信</td> <td align="left">SendGrid</td> </tr> </tbody> </table></div> <h3 id="2. フロントエンド"><a href="#2.+%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89">2. フロントエンド</a></h3> <p>CSSフレームワークにはMaterializeを採用しました。初めて使ったのですが、ドキュメントが分かりやすく情報量も多いのでオススメです。<br /> 今回はフロントで処理をする必要が無かったので、基本的にJavaScriptは使っていません。ファイルアップロードの画面は、アニメーションを付けるためにVue.jsを使いました。Vue.jsは以前から使っていたので、特に困ることはありませんでした。<br /> 次はNuxt.jsに挑戦するために勉強中です。</p> <div class="table-responsive"><table> <thead> <tr> <th align="left">役割</th> <th align="left">技術</th> </tr> </thead> <tbody> <tr> <td align="left">CSSフレームワーク</td> <td align="left">Materialize</td> </tr> <tr> <td align="left">JavaScriptフレームワーク</td> <td align="left">Vue.js</td> </tr> <tr> <td align="left">決済</td> <td align="left">Stripe Checkout</td> </tr> </tbody> </table></div> <h3 id="3. インフラ"><a href="#3.+%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9">3. インフラ</a></h3> <p>サーバーにはレンタルサーバーを採用しています。</p> <div class="table-responsive"><table> <thead> <tr> <th align="left">役割</th> <th align="left">技術</th> </tr> </thead> <tbody> <tr> <td align="left">レンタルサーバー</td> <td align="left">エックスサーバー</td> </tr> </tbody> </table></div> <p>Heroku / VPS / AWS EC2 / GCP App Engineなどの選択肢もありましたが、主にコストと運用の観点から除外しました。個人開発は自分でインフラを選べるのが良いですね。<br /> 今後もどんどんサービスを作っていく予定なので、サーバー費がかさむのはイヤだし、サービスを作る度に環境を構築するのも避けたかったのです。</p> <p>もちろん要件によってはレンタルサーバーが使えない場合もあります(ミドルウェアの設定変更や追加インストールが必要な場合など)。</p> <p>例えば、FFmpegを使って動画のエンコード等を行う場合です。<br /> まずrootが使えないのでインストールができません。自前でmakeして無理やり手動インストールすることも不可能ではなさそうですが、なるべく避けたいところです。また、動画のエンコード処理はCPUを食うので、運営に利用を停止される可能性があります。</p> <p>このようにrootユーザーが使えないと困る場合は、状況に応じて各サービスを比較検討しましょう。<br /> サーバーはHerokuだけどストレージにはS3を使って、DBにはCloud SQLを使うといったトリッキーなこともできます。柔軟な発想で最適な構成を作りましょう。</p> <p>参考に、私の考える主なインフラサービスのメリット・デメリットをまとめておきます。<br /> ※App Engineは詳しくないので簡易的な記載になってます</p> <div class="table-responsive"><table> <thead> <tr> <th align="left">インフラサービス</th> <th align="left">メリット</th> <th align="left">デメリット</th> </tr> </thead> <tbody> <tr> <td align="left">VPS(IaaS)</td> <td align="left">・安い・root使える</td> <td align="left">・借りる度にお金がかさむ・環境構築や設定が必要</td> </tr> <tr> <td align="left">EC2(IaaS)</td> <td align="left">・root使える・マイクロサービス沢山ある</td> <td align="left">・高い・借りる度にお金がかさむ・環境構築や設定が必要</td> </tr> <tr> <td align="left">App Engine(PaaS)</td> <td align="left">・環境構築不要</td> <td align="left">・高い・借りる度にお金がかさむ</td> </tr> <tr> <td align="left">Heroku(PaaS)</td> <td align="left">・安い・環境構築不要</td> <td align="left">・借りる度にお金がかさむ・30秒タイムアウト辛い</td> </tr> <tr> <td align="left">レンタルサーバー(ほぼPaaS)</td> <td align="left">・安い・1台でアプリ沢山動かせる・環境構築ほぼ不要</td> <td align="left">・root使えない</td> </tr> </tbody> </table></div> <h2 id="六. 設計"><a href="#%E5%85%AD.+%E8%A8%AD%E8%A8%88">六. 設計</a></h2> <p>小規模なサービスなので、ここにはほぼ時間をかけていません。<br /> ワイヤーフレームなどは作らず、実際に画面をコーディングしてレイアウトを決めました。<br /> DB設計もパパッと考えて終わり。<br /> 開発の中で必要になったときに都度、テーブルやカラム・画面を増やしていきました。<br /> サービスによっては色々な機能を思いつくと思いますが、まずはスモールスタートでリリースすることをゴールにしましょう。</p> <h2 id="七. 開発"><a href="#%E4%B8%83.+%E9%96%8B%E7%99%BA">七. 開発</a></h2> <p>一番時間をかけたのはこの工程です。<br /> 他の仕事が並行していたため正確ではありませんが、全体で2〜3週間はかかったと思います。<br /> 伝えたい情報がある場合はコメントを書いています。<br /> 有料サービスのみ必要になる項目には「☆」を付けています。</p> <h3 id="1. サービスの機能開発"><a href="#1.+%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AE%E6%A9%9F%E8%83%BD%E9%96%8B%E7%99%BA">1. サービスの機能開発</a></h3> <h4 id="一. Twitterログイン"><a href="#%E4%B8%80.+Twitter%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3">一. Twitterログイン</a></h4> <p>ツイ消し職人では、決済完了時とツイート削除完了時に確認メールを送信しています。<br /> そのため、Twitter AppのAdditional permissionsとして、Request email addressにチェックを入れています。</p> <h4 id="二. ☆決済"><a href="#%E4%BA%8C.+%E2%98%86%E6%B1%BA%E6%B8%88">二. ☆決済</a></h4> <p>Stripe Checkoutは神。<br /> JavaScriptをちょろっと書くだけで決済を提供できます。返金もボタンポチるだけです。<br /> 週次で売上を銀行口座に入金してくれるところも良いですね。至れり尽くせり。<br /> ツイ消し職人はクレジットカード、Google Pay、Apple Payに対応しています。</p> <h4 id="三. アーカイブアップロード"><a href="#%E4%B8%89.+%E3%82%A2%E3%83%BC%E3%82%AB%E3%82%A4%E3%83%96%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89">三. アーカイブアップロード</a></h4> <p>Twitterが生成したデータを読み取らないといけないので、アーカイブのどのファイルに何の情報があるのかを全て自分で調べました。<br /> そして、ツイートの削除に本当に必要なファイルだけをアップロードさせることで、ファイルサイズを31GB→200MBまで減らすことができました。</p> <p>アーカイブからはツイートの削除に必要な情報を正規表現で抽出する必要があります。<br /> 最初は、JavaScriptを使いフロント側で情報を抽出し、サーバーには最低限のデータだけ送るようにする予定だったのですが、少し時間がかかりそうだったので諦めました。</p> <p>FileReader.readAsText()に100MBのファイルを食わせるとクラッシュしてしまうことが判明し、ファイルをチャンクして処理する必要が出てきたためです。<br /> コンソールにエラーは出力されず、サイレントでクラッシュするので問題の特定に時間がかかりました。<br /> サーバ側で抽出処理をやっても特に問題はないので、サーバ側で処理するようにしました。</p> <h4 id="四. ツイート削除"><a href="#%E5%9B%9B.+%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E5%89%8A%E9%99%A4">四. ツイート削除</a></h4> <p>ノーコメント</p> <h4 id="五. 非同期処理"><a href="#%E4%BA%94.+%E9%9D%9E%E5%90%8C%E6%9C%9F%E5%87%A6%E7%90%86">五. 非同期処理</a></h4> <p>アップロードされてそのままツイートの削除を行うと、画面がタイムアウトしてしまいます。<br /> そのため削除処理はLaravelのキューを使って非同期にしています。<br /> 失敗時の再実行もできるようになるので便利ですね。</p> <h4 id="六. メール送信"><a href="#%E5%85%AD.+%E3%83%A1%E3%83%BC%E3%83%AB%E9%80%81%E4%BF%A1">六. メール送信</a></h4> <p>必ずユーザーに到達するように<a target="_blank" rel="nofollow noopener" href="https://sendgrid.kke.co.jp/">SendGrid</a>を使っています。<br /> 返信や問い合わせを受けるためにはメールサーバーの設定が必要なので注意してください。<br /> サーバーが用意できない場合は、G Suiteなどのホスティングサービスを利用しましょう。</p> <h4 id="七. ログ出力 + Slack通知"><a href="#%E4%B8%83.+%E3%83%AD%E3%82%B0%E5%87%BA%E5%8A%9B+%2B+Slack%E9%80%9A%E7%9F%A5">七. ログ出力 + Slack通知</a></h4> <p>本番での例外発生時にはSlackにスタックトレースを飛ばすようにしています。<br /> 他にも、ツイート削除処理成功時など、正常系でも重要なものはSlackに通知を飛ばしてます。</p> <p>ログは、出せる項目をなるべく出すようにしています。<br /> Laravelのログ出力について記事書いてるので興味あったら読んでください↓<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/_hiro_dev/items/cea556897a36fcec31bf">【Laravel】ログのフォーマットを変更してIPアドレスやユーザー名などを出力する</a></p> <h3 id="2. リリース準備"><a href="#2.+%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E6%BA%96%E5%82%99">2. リリース準備</a></h3> <h4 id="一. LP(トップページ)作成"><a href="#%E4%B8%80.+LP%28%E3%83%88%E3%83%83%E3%83%97%E3%83%9A%E3%83%BC%E3%82%B8%29%E4%BD%9C%E6%88%90">一. LP(トップページ)作成</a></h4> <p>ユーザーに効果的に訴求できる文言を考える必要があります。<br /> デザイナーの人は腕の見せどころだと思います。<br /> 文字や画像・アニメーションを使っていい感じのレイアウトにしましょう。</p> <p><a target="_blank" rel="nofollow noopener" href="https://peraichi.com/">ペライチ</a>などのツールを使っても良いと思います。<br /> コンバージョンに直結するので、一番力を入れるべき部分です。外注も考えましょう。</p> <p>ツイ消し職人のようなツール系のサービスの場合は、そんなに凝らなくても結構コンバージョンします。</p> <h4 id="二. 利用規約・プライバシーポリシーの制定"><a href="#%E4%BA%8C.+%E5%88%A9%E7%94%A8%E8%A6%8F%E7%B4%84%E3%83%BB%E3%83%97%E3%83%A9%E3%82%A4%E3%83%90%E3%82%B7%E3%83%BC%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC%E3%81%AE%E5%88%B6%E5%AE%9A">二. 利用規約・プライバシーポリシーの制定</a></h4> <p><a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/gp/product/B07Q721691">この本</a>が大変参考になりました。コピペできるひな形データも付いてくるのでオススメです。</p> <h4 id="三. ☆特定商取引法に基づく表示の作成"><a href="#%E4%B8%89.+%E2%98%86%E7%89%B9%E5%AE%9A%E5%95%86%E5%8F%96%E5%BC%95%E6%B3%95%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%8F%E8%A1%A8%E7%A4%BA%E3%81%AE%E4%BD%9C%E6%88%90">三. ☆特定商取引法に基づく表示の作成</a></h4> <p>同上。<br /> 有料サービスの場合は必須です。<br /> 本名や住所、電話番号を晒さないといけないので、ここが一番の難関ではないでしょうか。<br /> 私の場合、IP電話アプリ(SMARTalk)を使い050から始まる電話番号を載せています。</p> <h4 id="四. Googleアナリティクス・Search Console設定"><a href="#%E5%9B%9B.+Google%E3%82%A2%E3%83%8A%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B9%E3%83%BBSearch+Console%E8%A8%AD%E5%AE%9A">四. Googleアナリティクス・Search Console設定</a></h4> <p>ノーコメント</p> <h4 id="五. meta description設定"><a href="#%E4%BA%94.+meta+description%E8%A8%AD%E5%AE%9A">五. meta description設定</a></h4> <p>Googleの検索結果でタイトルとともに出るやつです。<br /> meta keywordは不要です。</p> <h4 id="六. ファビコン設定"><a href="#%E5%85%AD.+%E3%83%95%E3%82%A1%E3%83%93%E3%82%B3%E3%83%B3%E8%A8%AD%E5%AE%9A">六. ファビコン設定</a></h4> <p>GIMPで作りました。<br /> サービスのロゴがある場合はファビコンにも活かせます。</p> <h4 id="七. OGP設定"><a href="#%E4%B8%83.+OGP%E8%A8%AD%E5%AE%9A">七. OGP設定</a></h4> <p>GIMPで作りました。<br /> OGP画像を動的に生成するサービスでは、トップページ用の画像を同じ方法で作るのも良いかも。<br /> CTRに直結するので、ここも外注を検討しましょう。</p> <h4 id="八. サイトマップ設定"><a href="#%E5%85%AB.+%E3%82%B5%E3%82%A4%E3%83%88%E3%83%9E%E3%83%83%E3%83%97%E8%A8%AD%E5%AE%9A">八. サイトマップ設定</a></h4> <p>Search Consoleで送信します。<br /> <a target="_blank" rel="nofollow noopener" href="http://www.sitemapxml.jp/">sitemap.xml Editor</a>を使うと簡単に作成できます。<br /> サイトが新しく、外部からのリンクが少ない場合はあったほうが良いみたいです。</p> <h4 id="九. SNSシェアボタンの設置"><a href="#%E4%B9%9D.+SNS%E3%82%B7%E3%82%A7%E3%82%A2%E3%83%9C%E3%82%BF%E3%83%B3%E3%81%AE%E8%A8%AD%E7%BD%AE">九. SNSシェアボタンの設置</a></h4> <p>↓こういうのです。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117180/71ddf9f6-9556-80d9-1554-2d0fb875a11e.png" alt="ツイ消し職人 - twikeshi.net.png" /><br /> ユーザーに拡散してもらえる仕組みを作っておくことは重要です。</p> <h4 id="十. お問い合わせフォームの設置"><a href="#%E5%8D%81.+%E3%81%8A%E5%95%8F%E3%81%84%E5%90%88%E3%82%8F%E3%81%9B%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%81%AE%E8%A8%AD%E7%BD%AE">十. お問い合わせフォームの設置</a></h4> <p>自分で作るのがめんどくさい場合は、<a target="_blank" rel="nofollow noopener" href="https://www.google.com/intl/ja_jp/forms/about/">Googleフォーム</a>や<a target="_blank" rel="nofollow noopener" href="https://form.run/ja">formrun</a>などを活用しましょう。<br /> お問い合わせフォームの代わりに、チャットサポートツールを入れるのもオススメです。</p> <h3 id="3. リリース"><a href="#3.+%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9">3. リリース</a></h3> <h4 id="一. デプロイ"><a href="#%E4%B8%80.+%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4">一. デプロイ</a></h4> <p>Laravel + レンタルサーバーの場合はgit pullすればほぼ終わりです。<br /> あとは.env書いてマイグレーションしてキャッシュ系のコマンドを叩くだけです。<br /> もちろん、GitHub ActionsなどのCIを設定するのも良いと思います。</p> <p>私の場合は、以下のようなデプロイスクリプトを用意しています。</p> <pre><code>#!/bin/sh git pull composer install --optimize-autoloader --no-dev php artisan config:cache php artisan view:cache php artisan route:cache </code></pre> <h4 id="二. テスト"><a href="#%E4%BA%8C.+%E3%83%86%E3%82%B9%E3%83%88">二. テスト</a></h4> <p>本番環境で全ての機能がうまく動くことを確認しました。<br /> 中規模〜大規模サービスの場合は、検証環境の用意とテスト自動化がされていないと運用がしんどくなります。</p> <h2 id="八. リリース後"><a href="#%E5%85%AB.+%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E5%BE%8C">八. リリース後</a></h2> <h3 id="1. 知り合い・友人への拡散"><a href="#1.+%E7%9F%A5%E3%82%8A%E5%90%88%E3%81%84%E3%83%BB%E5%8F%8B%E4%BA%BA%E3%81%B8%E3%81%AE%E6%8B%A1%E6%95%A3">1. 知り合い・友人への拡散</a></h3> <p>LINE, Twitter, Facebookなどで拡散して使ってもらいましょう。</p> <h3 id="2. プレスリリースを出す"><a href="#2.+%E3%83%97%E3%83%AC%E3%82%B9%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%82%92%E5%87%BA%E3%81%99">2. プレスリリースを出す</a></h3> <p>お金があれば<a target="_blank" rel="nofollow noopener" href="https://prtimes.jp/">PR TIMES</a>などの有名サイトに出すのがオススメです。<br /> (もしくは、法人なら<a target="_blank" rel="nofollow noopener" href="https://prtimes.jp/startup_free/">スタートアップチャレンジ</a>の条件を満たすと無料になります)</p> <p>私はお金がなかったので、<a target="_blank" rel="nofollow noopener" href="https://www.value-press.com/">valuepress</a>のフリープランで配信しました。<br /> 配信は無料ですが、プレスリリースの内容を修正する場合はお金がかかるので気を付けましょう。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.value-press.com/pressrelease/241519">3,200件を超えるツイートを一括削除できるツイ消しサービス「ツイ消し職人」を提供開始</a></p> <h3 id="3. アプリ紹介サイトに登録"><a href="#3.+%E3%82%A2%E3%83%97%E3%83%AA%E7%B4%B9%E4%BB%8B%E3%82%B5%E3%82%A4%E3%83%88%E3%81%AB%E7%99%BB%E9%8C%B2">3. アプリ紹介サイトに登録</a></h3> <p>私の場合、<a target="_blank" rel="nofollow noopener" href="https://anymake.app/">AnyMake</a>や<a target="_blank" rel="nofollow noopener" href="https://www.makepost.net/">makepost</a>、<a target="_blank" rel="nofollow noopener" href="https://www.eggineer.com/">Eggineer</a>、<a target="_blank" rel="nofollow noopener" href="https://applishow.com/">Applishow</a>を活用しています。</p> <h3 id="4. 新聞の広告枠に出稿する"><a href="#4.+%E6%96%B0%E8%81%9E%E3%81%AE%E5%BA%83%E5%91%8A%E6%9E%A0%E3%81%AB%E5%87%BA%E7%A8%BF%E3%81%99%E3%82%8B">4. 新聞の広告枠に出稿する</a></h3> <p>リリース直後にスポーツ新聞の方から電話があり、新聞とサイトに広告を載せないかと打診がありました。<br /> 条件が合わなかったためお断りしましたが、人によっては選択肢になり得ると思います。</p> <h3 id="5. アフィリエイト広告に出稿する"><a href="#5.+%E3%82%A2%E3%83%95%E3%82%A3%E3%83%AA%E3%82%A8%E3%82%A4%E3%83%88%E5%BA%83%E5%91%8A%E3%81%AB%E5%87%BA%E7%A8%BF%E3%81%99%E3%82%8B">5. アフィリエイト広告に出稿する</a></h3> <p>検討しましたが、結局使いませんでした。<br /> お金がある場合は、<a target="_blank" rel="nofollow noopener" href="https://www.a8.net/">A8.net</a>などの大手ASPを使うのが良いと思います。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.monetrack.com/">マネートラック</a>ならば初期費用0円・月額費用0円で始められるようです。</p> <h3 id="6. SNS広告に出稿する"><a href="#6.+SNS%E5%BA%83%E5%91%8A%E3%81%AB%E5%87%BA%E7%A8%BF%E3%81%99%E3%82%8B">6. SNS広告に出稿する</a></h3> <p>以前利用していました。<br /> 私の場合はTwitterユーザーをターゲットにしたサービスなので、Twitter広告と相性が良いです。<br /> 月予算は安めでもかなりコンバージョンに繋がったので、広告費は普通に回収できています。</p> <h3 id="7. リスティング広告に出稿する"><a href="#7.+%E3%83%AA%E3%82%B9%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E5%BA%83%E5%91%8A%E3%81%AB%E5%87%BA%E7%A8%BF%E3%81%99%E3%82%8B">7. リスティング広告に出稿する</a></h3> <p>以前利用していました。<br /> Twitter広告よりも予算が必要で効果が薄かったため、すぐにやめました。</p> <h3 id="8. 保守開発"><a href="#8.+%E4%BF%9D%E5%AE%88%E9%96%8B%E7%99%BA">8. 保守開発</a></h3> <p>本番環境でのエラーを監視し、新しく発現したバグがあれば修正しましょう。<br /> 手元で再現しないエラーは...ユーザー問い合わせを待つしか無い。。</p> <p>もちろん、機能追加などサービス改善のための開発は怠らないようにしましょう。<br /> 大幅リニューアルや作り直しなどの選択肢もあります。</p> <h3 id="9. ブログなどでの発信"><a href="#9.+%E3%83%96%E3%83%AD%E3%82%B0%E3%81%AA%E3%81%A9%E3%81%A7%E3%81%AE%E7%99%BA%E4%BF%A1">9. ブログなどでの発信</a></h3> <p>この記事のことですね。<br /> Qiita, Crieit, Zenn, Note, ブログなどの選択肢があります。<br /> サービスを知ってもらうだけでなく、転職活動などでも役に立ちます。</p> <p>Noteに記事を投稿しているのでこちらも参考にしてください。<br /> <a target="_blank" rel="nofollow noopener" href="https://note.com/_hiro_dev/n/n3332c056a65a">たった2週間で作ったWebサービスで月11万円の売上を達成した話</a></p> ひろ⚡個人開発 tag:crieit.net,2005:PublicArticle/15826 2020-04-14T13:52:22+09:00 2020-04-14T13:52:22+09:00 https://crieit.net/posts/Google 独学初心者がGoogle風の書籍検索サービスを作ってみた【個人開発】 <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436810/670938fb-e2c7-aed0-1def-f1c2cce69233.png" alt="GeekleDesktop.png" /><br /> 独学でプログラミングを学習しています。<br /> 「作りながら学ぶのがいい」ということでGoogle風の技術書検索サービスを開発しました。</p> <p>アイデア的に似ているサービスはありますが、自分が欲しいものとは少し違っていたので作りました。</p> <p>初学者なりに試行錯誤したので、アプリの仕組みや作った方法をまとめます。</p> <p>プログラミング学習中の人や個人開発を考えている人のモチベUPに貢献できたらいいなと思います。</p> <h1 id="作ったサービス「技術書検索サービス」"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%80%8C%E6%8A%80%E8%A1%93%E6%9B%B8%E6%A4%9C%E7%B4%A2%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%80%8D">作ったサービス「技術書検索サービス」</a></h1> <p>作ったのは「Geekle(ギークル)」という技術書検索サービス。</p> <p>-> <a target="_blank" rel="nofollow noopener" href="https://geekle.jp/"> Geekle(ギークル) | 技術書検索サービス</a><br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436810/b77cdf01-e3c8-f3b4-6440-37640986c44e.png" alt="Geekle___エンジニア向けの技術書検索サイト.png" /></p> <h3 id="サービス概要"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E6%A6%82%E8%A6%81">サービス概要</a></h3> <p>Geekleは、<strong>評価の高い技術書を検索できる</strong>サービスです。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436810/c8b9a26b-86c0-4e4e-5a82-016182b53434.png" alt="Geekle全体.png" /></p> <p>例えば、「Pythonを勉強したい!どの書籍がいんだろうか!」ってなったときに「Python」というキーワード(キーワードはQiitaのタグにあるものに限定されます:サジェストで表示される)で検索すると、</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436810/f118bf6b-2d83-42d6-8bc2-74f776c50a79.png" alt="Geekle___エンジニア向けの技術書検索サイト.png" /></p> <p>その技術キーワードに対する「評価の高い書籍リスト」が検索結果として表示されます。検索結果の並びはQiitaでの言及記事数をはじめ、複数の要素から総合的に算出。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436810/4954cc89-6397-45c6-840e-d513cb7ea4e1.png" alt="Pythonのおすすめ本568冊___Geekle.png" /></p> <p>学びたい技術がある時に購入候補となる書籍を絞り込むのに使えます!</p> <h2 id="開発の動機"><a href="#%E9%96%8B%E7%99%BA%E3%81%AE%E5%8B%95%E6%A9%9F">開発の動機</a></h2> <p>プログラミングを学習する中で、たくさんの書籍を購入してきました。</p> <p>そのときに候補となる本をピックアップして、一つずつ評判を確認し、本屋で立ち読みして買ってたので、「○○という技術を学ぼう→その技術の評判の良い技術書がすぐわかるサービスがあったらなー」と思っていました。</p> <p>Qiita上の書籍情報を見られるサービスはすでにいくつもありますが、既存のモノは「Qiita上の書籍情報を可視化すると面白い」というコンセプトであるため、キーワード(タグ)に対して関連性が薄い書籍も多く表示される仕様となっていました(それが書籍との偶発的な出会いになって面白いというコンセプト)。</p> <p>自分の希望は「今、学ぶのに適した書籍の候補」をストレートに見つけられるサービスだったので、関連性の薄い書籍はない方がよかったし、なるべく情報が新しい評価の高い書籍が表示されて欲しかった。</p> <p>そこで、自分の希望を実現したモノを作ってみました。</p> <h2 id="どう作ったか"><a href="#%E3%81%A9%E3%81%86%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%8B">どう作ったか</a></h2> <p>使った技術とか</p> <ul> <li>アプリ:Laravel + Vue.js</li> <li>バッチ(DB作成):生PHP</li> <li>インフラ:AWS(EC2・RDS・Rout53・ALB)</li> <li>制作期間:4ヶ月ちょっと</li> </ul> <p>裏側の仕組みをどう作ったのか解説します。</p> <p>自分なりに色々と考えて作りましたが、独学初心者なので生暖かく読んでください...。あと、「もっとこうすればいいのに!」とかあればアドバイスして頂けると嬉しいです。</p> <h3 id="書籍DBについて"><a href="#%E6%9B%B8%E7%B1%8DDB%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">書籍DBについて</a></h3> <p>書籍のDBは、QiitaのAPIで記事データを取得して、書籍へのリンクらしきURLを解析し、そこから書籍の識別番号(ISBN・ASIN)を抽出後、番号を使ってopneBDやGoogleBookApiなどで書籍データを取得して構築しています。</p> <p>これらの方法は</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/jabba/items/edefda09121877b79760">技術書ランキングサイトをQiita記事の集計から作ったら、約4000冊の技術本がいい感じに並んだ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/Yuhsak/items/03967437b8a6438eb625">【個人開発】 技術書籍のレビューや「読みたい」を記録するサービスを作った</a></li> </ul> <p>の記事を参考にさせていただきました。</p> <p>独学の自分がサービス開発できたのはこういったノウハウが公開・共有されていたからです。先人の皆様ありがとうございます。僕もこの記事でやったことを共有します( ̄^ ̄)ゞ</p> <h3 id="書籍の順位付けを工夫"><a href="#%E6%9B%B8%E7%B1%8D%E3%81%AE%E9%A0%86%E4%BD%8D%E4%BB%98%E3%81%91%E3%82%92%E5%B7%A5%E5%A4%AB">書籍の順位付けを工夫</a></h3> <p>Qiitaの記事についているタグをそのまま書籍のタグとして、Qiitaで言及されている記事数をカウントすることでタグごとに書籍の順位づけを行っています。</p> <p>しかし、Qiitaの記事数のみで順位付けを行うと、普遍的な名著(リーダブルコードなど)が多くのタグで上位を独占してしまるというデメリットがありました。</p> <p>検索ワードが「PHP」でも「Ruby」でもリーダブルコードが一番上になってしまう。</p> <p>また、出版から時間が経過している書籍の方が言及される記事数が多く、新しい書籍は言及される記事数が少ないため、新しい良本が上位に表示できなかったり、PHPとLaravelのように「言語」と「フレームワーク」の検索結果がほぼ同じになるという問題点がありました。</p> <p>僕が作りたいのは「学習したい技術ごとに今選ぶべき良書がわかるサービス」だったため、なるべく情報の新しい良本が上位表示されて欲しいし、PHPなら生PHPの書籍が上位、LaravelならLaravelの書籍が上位という結果になって欲しかった。</p> <p>そこで、Qiitaの記事数に加えて</p> <p>・検索ワード(タグ)の文字が書籍のタイトルに含まれる場合はプラス点<br /> ・出版日時が古くなればなるほどマイナス点</p> <p>などを加えて傾斜配点をかける事で検索結果を調整しています。</p> <p>試行錯誤の結果、悪くない結果がでるようになりました。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436810/f5a9e246-bae2-76ae-62b9-a4f90cec3403.png" alt="PHPLaravel.png" /><br /> (言語とフレームワークも異なる検索結果になってる)</p> <p>今後も改良して精度を上げていきます。</p> <h3 id="デザインとか"><a href="#%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%A8%E3%81%8B">デザインとか</a></h3> <p>デザインは、最初にカラー(紫・黒・白)を決めました。</p> <p>柱となる色は紫をにしたのは、「プログラミングって現在の魔法っぽい→魔法は紫っぽい」という発想から。技術書を読んで勉強することを魔法を学んでる感じだと捉えると学習が楽しくなる気がします(中二心)。</p> <p>レイアウトは、たくさんのサービスを並べて特徴を抽象化し、取り入れていくというやり方で考えました。</p> <p>具体的に説明すると、書籍検索サービスは画像付きでコンテンツを表示するサービスになるよね→Amazonプライムビデオやネットフリックスなど画像付きでコンテンツを大量に並べてる有名サイトをいくつもみる→あ、横並びにしてスライドするUIがデフォルトっぽいなー(これが抽象化)→自分のサービスに取り入れるという流れ。</p> <p>「基本となっている型を探す」という感じ。</p> <h3 id="今後の改良/グロース"><a href="#%E4%BB%8A%E5%BE%8C%E3%81%AE%E6%94%B9%E8%89%AF%2F%E3%82%B0%E3%83%AD%E3%83%BC%E3%82%B9">今後の改良/グロース</a></h3> <p>やりたいことはたくさんあります。</p> <p>・SEOに強い構成にする<br /> ・SPA化<br /> ・レビューを集める仕組み構築<br /> ・技術書のセール情報の掲載</p> <p>などなど。自分の技術力向上やサービスグロースの実験の場としても使っていきたい。</p> <h3 id="振り返り"><a href="#%E6%8C%AF%E3%82%8A%E8%BF%94%E3%82%8A">振り返り</a></h3> <p>当初、2ヶ月くらいで作れるだろうと思っていたのですが、DBを作るのに時間がかかってしまい、想定の倍以上の時間がかかってしまいました。</p> <p>開発中はわからないことだらけで苦しい時もりましたが、やっぱりプログラミングは楽しいですね!今回のアプリ開発を通してエンジニアになりたいという気持ちがより強くなりました。</p> <p>もっともっと勉強して色々なアプリ作っていきたいです。</p> <p>ここまで読んでくれた方ありがとうございます!<br /> ぜひ試しに使ってみてください(≧∀≦)</p> <p><a target="_blank" rel="nofollow noopener" href="https://geekle.jp/">Geekle(ギークル) | 技術書検索サービス</a></p> Jin tag:crieit.net,2005:PublicArticle/15740 2020-03-01T08:40:20+09:00 2020-03-01T08:40:59+09:00 https://crieit.net/posts/CodePen-Vue-js CodePenでVue.jsの最小アプリケーションを作成する <p>CodePenを使うとブラウザで直接JavaScriptのコードを書いてアプリケーションを動かすことが出来ます。サーバーも不要ですし、ちょっとしたアプリケーションであればこれだけでも簡単に作成&公開が可能です。</p> <p>CodePenのトップページにCREATEメニューがあります。そちらでPenをクリックして新規作成します。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e5af495a0a6e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e5af495a0a6e.png?mw=700" alt="" /></a></p> <p>するとPenの設定画面が開きます。ここのJSメニューで、JavaScriptのライブラリなどのCDNを追加することが出来ます。例えば今回はVueで検索してVue.jsを追加します。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e5af2b7e8c4f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e5af2b7e8c4f.png?mw=700" alt="" /></a></p> <p>同様にCSSのフレームワークなども追加できます。設定できたら「Save & Close」で確定します。</p> <p>あとは実際にHTMLとJavaScriptを書いていきます。ひとまずロジックもなにもない、単なる最小の表示を作ってちゃんと動作しているか確かめます。</p> <pre><code class="javascript">new Vue({ el: '#app', data() { return { count: 0 } } }) </code></pre> <pre><code class="html"><div id="app"> <span>{</span><span>{</span> count <span>}</span><span>}</span> </div> </code></pre> <p>これで下記のようにcountの値が表示されているので問題なさそうです。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e5af3719015e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e5af3719015e.png?mw=700" alt="" /></a></p> <p>次に値をかえるロジックを入れていきます。</p> <pre><code class="javascript">new Vue({ el: '#app', data() { return { count: 0 } }, methods: { increment() { this.count = this.count + 1 } } }) </code></pre> <pre><code class="html"><div id="app"> <div><span>{</span><span>{</span> count <span>}</span><span>}</span></div> <button @click="increment">+</button> </div> </code></pre> <p>ボタンを押すと値が増えていくロジックを追加することが出来ました。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e5af41a232ee.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e5af41a232ee.png?mw=700" alt="" /></a></p> <p>実際に試してみることも出来ます。</p> <p><a target="_blank" rel="nofollow noopener" href="https://codepen.io/dala00/pen/oNXwzrx">https://codepen.io/dala00/pen/oNXwzrx</a></p> <p>あとはCrieitやQiitaなどは埋め込みも可能です。</p> <p><a href="https://crieit.net/posts/CodePen-5b5b9093e817b">CodePenの埋め込み</a></p> <p class="codepen" data-height="365" data-theme-id="light" data-default-tab="js,result" data-user="dala00" data-slug-hash="oNXwzrx" style="height: 365px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Vue.js simplest application"> <span>See the Pen <a target="_blank" rel="nofollow noopener" href="https://codepen.io/dala00/pen/oNXwzrx"> Vue.js simplest application</a> by dala00 (<a target="_blank" rel="nofollow noopener" href="https://codepen.io/dala00">@dala00</a>) on <a target="_blank" rel="nofollow noopener" href="https://codepen.io">CodePen</a>.</span> </p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/15711 2020-02-06T23:52:50+09:00 2020-02-06T23:52:50+09:00 https://crieit.net/posts/Vue-js-Netlify Vue.js+Netlifyで最小のアプリを作る <p>Vue.jsで最小のアプリケーションを作成して、Netlifyにデプロイするまでを解説します。1週間以内くらいで1ページだけの簡単なWebアプリケーションを作ろう、と思ったらこれくらいで十分可能です。複雑なアプリケーションにしなければデータベースもブラウザ上のLocal Storageに保存する形であれば用意する必要もありませんし、とても楽です。</p> <h2 id="準備するもの"><a href="#%E6%BA%96%E5%82%99%E3%81%99%E3%82%8B%E3%82%82%E3%81%AE">準備するもの</a></h2> <p>今回の最小のアプリケーションを作成するためには下記の準備が必要です。インストールやユーザー登録をしておきましょう。</p> <p>npmのインストール<br /> Yarn(好みで必要であれば)<br /> GitHubのユーザー登録<br /> Netlifyのユーザー登録</p> <h2 id="Vue CLI"><a href="#Vue+CLI">Vue CLI</a></h2> <p>まずはPCにVue CLIをインストールします。Vue CLIを利用すると、コマンドで簡単にVue.jsのプロジェクトを作成できるようになります。インストール方法は下記のどちらかのコマンドです。</p> <pre><code>npm install -g @vue/cli </code></pre> <p>もしくは</p> <pre><code>yarn global add @vue/cli </code></pre> <h2 id="プロジェクトを新規作成"><a href="#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E6%96%B0%E8%A6%8F%E4%BD%9C%E6%88%90">プロジェクトを新規作成</a></h2> <p>Vue CLIを使ってVue.jsのプロジェクトを新規登録します。下記のコマンドを実行します。</p> <pre><code>vue create hello-world </code></pre> <p>するとプロジェクトにどのような機能を入れるかを選択しながら作成することが出来ます。必要なものを選択していきます。よく分からない場合は最初にdefaultを選択しておけば良いと思います。</p> <p>例として、僕はManually select featuresで下記を選択しました。</p> <h3 id="Check the features needed for your project"><a href="#Check+the+features+needed+for+your+project">Check the features needed for your project</a></h3> <p>Babel, TypeScript, Linter/Formatter, Unit Testing, E2E Testing</p> <p>TypeScriptと2つのTestingを追加しました。1ページの軽いアプリケーションなのでRouterは選択しませんでした。Testingは今回は使いませんが、もし使うことになった際に一から設定するのは面倒なのでとりあえず雛形を作ってもらっておいたほうが楽かなと思いいつも追加しています。とりあえずシンプルなアプリケーションを作るだけであれば不要でしょう。</p> <h3 id="Use class-style component syntax?"><a href="#Use+class-style+component+syntax%3F">Use class-style component syntax?</a></h3> <p>Vueコンポーネントの使い方が変わります。好きな形を選択しましょう。Vue.js3でまた新しいカタチも出てくるのですがまだ選択できませんのでとりあえず好きなもので良いと思います。シンプルなアプリケーション用ですのであまり考える必要はないでしょう。</p> <h3 id="Use Babel alongside TypeScript"><a href="#Use+Babel+alongside+TypeScript">Use Babel alongside TypeScript</a></h3> <p>TypeScriptの場合? だけでしょうか。そのままYで良いと思います。</p> <h3 id="Pick a linter / formatter config"><a href="#Pick+a+linter+%2F+formatter+config">Pick a linter / formatter config</a></h3> <p>これもLinter/Formatterを選択している場合だけでしょうか。TSLintはもう使わないほうが良いのでそれ以外であれば好きなのを選べば良いと思います。僕はPrettierを選んでいます。</p> <h3 id="Pick additional lint features"><a href="#Pick+additional+lint+features">Pick additional lint features</a></h3> <p>いつLintするかの設定です。とりあえずLint on saveで良いと思います。</p> <h3 id="Pick a unit testing solution"><a href="#Pick+a+unit+testing+solution">Pick a unit testing solution</a></h3> <p>Unitテストで利用するパッケージの選択です。GitHubでのスター数が多いので適当にいつもJestにしています。</p> <h3 id="Pick a E2E testing solution"><a href="#Pick+a+E2E+testing+solution">Pick a E2E testing solution</a></h3> <p>同上でcypressを選択しました。</p> <h3 id="Where do you prefer placing config for Babel, ESLint, etc.?"><a href="#Where+do+you+prefer+placing+config+for+Babel%2C+ESLint%2C+etc.%3F">Where do you prefer placing config for Babel, ESLint, etc.?</a></h3> <p>いろんな設定がpackage.jsonに詰め込まれるのは嫌なのでIn dedicated config filesにしました。</p> <h3 id="Save this as a preset for future projects?"><a href="#Save+this+as+a+preset+for+future+projects%3F">Save this as a preset for future projects?</a></h3> <p>最後にこの設定を今後も使うかの選択です。怖いのでそのままNにしました。</p> <p>これで完了です。待っていれば先程指定したアプリケーション名のフォルダにプロジェクトが作成されます。E2Eテストとかを入れているとかなり時間がかかるかもしれません。</p> <p>問題なければ最後に表示されるように、cdコマンドでフォルダ内に移動し、serveコマンドを実行すればもうアプリケーションが実行できます。</p> <pre><code>cd vue-netlify-sample yarn serve </code></pre> <p>問題なければURLが表示されるのでアクセスすれば表示されます。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c22357df8f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c22357df8f.png?mw=700" alt="" /></a></p> <h2 id="Gitにコミットする"><a href="#Git%E3%81%AB%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%81%99%E3%82%8B">Gitにコミットする</a></h2> <p>とりあえずプロジェクトを作成した直後の状態をGitにコミットしておくと良いでしょう。通常は<code>git init</code>する必要がありますが、Vue CLIの場合はそれもやってくれているっぽいのでそのままコミットできます。<code>git add</code>も必要ですがそれもやってくれているっぽいので、例えばVSCodeの場合はそのままメッセージを入力してコミットできるっぽいです。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c23103f0ba.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c23103f0ba.png?mw=700" alt="" /></a></p> <h2 id="GitHubにリポジトリをpushする"><a href="#GitHub%E3%81%AB%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%82%92push%E3%81%99%E3%82%8B">GitHubにリポジトリをpushする</a></h2> <p>さきほど作成したリポジトリとそのコミットを、GitHubのリモートリポジトリに登録します。まずは新しいリポジトリをGitHub上に作成します。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edb295a7d4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edb295a7d4.png?mw=700" alt="" /></a></p> <p>設定はこんな感じです。名前だけ入力するだけで大丈夫です。公開したくなければPrivateを選択しておきましょう。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c23ac4b13b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c23ac4b13b.png?mw=700" alt="image.png" /></a></p> <p>新規作成できると、空のリポジトリが作成されます。この状態ではリポジトリの初期化方法が色々と書かれています。その中に既存のローカルリポジトリを使う方法が書かれていますので、それをローカルのプロジェクト上で実行します。</p> <pre><code>git remote add origin [email protected]:dala00/vue-netlify-sample.git git push -u origin master </code></pre> <p>問題なければGitHub上の画面を更新するとpushしたファイル一覧が表示されるようになります。</p> <h2 id="Netlifyにデプロイする"><a href="#Netlify%E3%81%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%99%E3%82%8B">Netlifyにデプロイする</a></h2> <p>続いてNetlifyで先程作成したGitHubのリポジトリを連携させて、簡単にデプロイできるようにします。まずは「New site from Git」ボタンを実行します。するとGitHub上のリポジトリ一覧が表示されます。</p> <p>ただ、新規登録時やNetlify上でのアプリケーション作成時のGitHub連携方法によってはもしかしたら表示されていないかもしれません。すべてのリポジトリの連携を許可する設定であれば表示されていると思いますが、特定のリポジトリだけ許可する設定で連携した場合は作成したばかりのGitHubリポジトリは表示されていません。その場合は画面の下に</p> <pre><code>Can’t see your repo here? Configure the Netlify app on GitHub. </code></pre> <p>というメッセージのリンクがありますので、そちらでGitHubに移動して許可するリポジトリを追加する必要があります。下記のような感じで追加します。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c248c97082.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c248c97082.png?mw=700" alt="" /></a></p> <p>問題なく選択できたらアプリケーションの新規登録画面に遷移します。Build commandに<code>yarn build</code>、Publish directoryに<code>dist</code>と入力して登録を行います。</p> <p>登録が完了するとデプロイ一覧に下記のようにデプロイ中である旨のステータスが表示されています。完了すると緑色のPublishedというステータスに変わるのでしばらく待ちましょう。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edcb5e368f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edcb5e368f.png?mw=700" alt="" /></a></p> <p>問題なければ上部にURLが表示され、アクセスできるようになります。下記が実際にデプロイしたURLです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://eager-neumann-0e6dfa.netlify.com/">https://eager-neumann-0e6dfa.netlify.com/</a></p> <p>eager-neumann-0e6dfaの部分はSite settingsのsite nameという項目で変更することもできます。独自ドメインを持っていればそれを利用することもできます。</p> <h2 id="アプリケーションを更新する"><a href="#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B">アプリケーションを更新する</a></h2> <p>アプリケーションに機能を追加してそれをデプロイしたい場合は、追加分をコミットしてGitHubにpushすると自動的にNetlify側も更新してくれます。</p> <p>例えば試しに下記のようなよくあるインクリメントのテスト機能を書いてみます。例えばTypeScriptの場合はApp.vueを下記のように変更します。(主にcountを使ったところを追記しています)</p> <pre><code class="html"><template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" /> <div> <button @click="increment">Increment</button> </div> <div><span>{</span><span>{</span> count <span>}</span><span>}</span></div> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import HelloWorld from './components/HelloWorld.vue' @Component({ components: { HelloWorld } }) export default class App extends Vue { count = 0 increment() { this.count++ } } </script> </code></pre> <p>問題なければ下記のようにボタンを押すと数値が増えていく機能が出来上がります。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c27d6e740a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c27d6e740a.png?mw=700" alt="" /></a></p> <p>あとはコミットしてpushすると再度Netlify上でデプロイが走りアプリケーション上に反映されます。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>このようにJavaScriptとGitHub、Netlifyを使うだけだと非常に簡単にアプリケーションを作ることができます。サンプルや、ずっと運用していくわけではない簡単なアプリケーションであればおすすめです。</p> <p>今回作成したプロジェクトはGitHubで公開しています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/dala00/vue-netlify-sample">https://github.com/dala00/vue-netlify-sample</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/15702 2020-01-31T21:28:18+09:00 2020-01-31T21:28:18+09:00 https://crieit.net/posts/Laravel-Vue-js-Toast Laravel+Vue.jsでToastを使ったフラッシュメッセージを表示する <p>LaravelとVue.jsで開発していると「更新しました」といったフラッシュメッセージを表示したい時があります。ただ、表示用のデザインが提供されているわけでもないし、全部のページに表示用の記述を入れたくないのでなるべく共通テンプレート内だけに書いておきたい、でもそれだと微妙に変な位置になってしまってかっこ悪くなる、というパターンもあります。</p> <p>そんな時、トーストを使うと場所を気にしなくていいし、時間が経つと勝手に消えてくれるため、じゃまにならずにわかりやすいメッセージを表示することができるので便利です。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3419fbc1efc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3419fbc1efc.png?mw=700" alt="" /></a></p> <h2 id="使用するプラグイン"><a href="#%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3">使用するプラグイン</a></h2> <p>今回は下記のVue.js用に作成されているプラグインを利用しました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/shakee93/vue-toasted">shakee93/vue-toasted: 🖖 Responsive Touch Compatible Toast plugin for VueJS 2+</a></p> <p>まずはインストールします。</p> <pre><code>yarn add vue-toasted </code></pre> <p>そしてapp.ts等で利用するための設定を行います。</p> <pre><code class="typescript">import Toasted from 'vue-toasted' Vue.use(Toasted) </code></pre> <p>これで準備は完了です。あとはVueコンポーネント内で下記のような感じで呼び出すことが出来ます。</p> <pre><code class="typescript">this.$toasted.show('hello billo') </code></pre> <p>TypeScriptを利用している場合はちゃんと上記も定義してくれているっぽいです。</p> <h2 id="表示用のコンポーネントを作成"><a href="#%E8%A1%A8%E7%A4%BA%E7%94%A8%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90">表示用のコンポーネントを作成</a></h2> <p>さて、実際に <code>$toasted.show</code>を実行するだけのVueコンポーネントを作成します。そのようにすることで、通常のLaravelのフラッシュメッセージを表示するときと同様、そのフラッシュメッセージが存在する時にそのコンポーネントを配置するだけで勝手にメッセージを表示できるようになります。</p> <p>今回は下記のようなToast.tsというファイルでコンポーネントを作りました。テンプレートは利用していないのでtsファイル等で構いません。(JavaScriptの場合は適宜型などを外せばそのまま使えると思います)</p> <pre><code class="typescript">import { Vue, Component, Prop } from 'vue-property-decorator' @Component export default class Toast extends Vue { @Prop(String) message!: string @Prop({ type: String, default: 'default' }) type!: string mounted() { this.$toasted.show(this.message, { type: this.type, duration: 5000, position: 'bottom-left' }) } render() { return '' } } </code></pre> <p>あとはapp.jsで <code>new Vue</code>する際にcomponentsパラメータの中に上記のコンポーネントを含ませれば利用できるようになります。</p> <p>共通テンプレート内でVueコンポーネントを動作させられる箇所(例えば#app内など)に下記のように配置すればフラッシュメッセージが表示されるようになります。</p> <pre><code class="html">@if (Session::has('success')) <toast message="<span>{</span><span>{</span> session('success') <span>}</span><span>}</span>" type="success"></toast> @endif </code></pre> <p>typeはvue-toastedにそのまま渡しているだけですが、いくつかあるようなので増やしたい場合はvue-toastedのドキュメントを見てみてください。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/15434 2019-09-29T11:28:32+09:00 2019-09-29T11:29:48+09:00 https://crieit.net/posts/Web-5d9016d040fdf フロント明るくないおじさんがWebアプリ作った話【悟空語ジェネレーター】 <blockquote> <p>2019年2月に<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kinmi/items/c66aa98718acad84621b">Qiitaへ投稿</a>した記事のクロス投稿です。</p> </blockquote> <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>近年、個人開発がとても盛んになってきました。<br /> クラウドサービスのリッチ化に伴い、個人でも簡単にWebサービスを運営できる時代となり、<br /> 個人開発者の熱意が更なる個人開発者の熱意を生む、素晴らしい連鎖が発生しています。</p> <p>特に去年、2018年は爆発的な個人開発ブームでした。</p> <p><em>「企業の一兵隊だった自分でも、何者かになれるんじゃないか」</em></p> <p><em>「世界は俺のプルリクを待っているのではないか」</em></p> <p>そう思わせてくれる1年でした。</p> <p>かく言う私も、その熱意にあてられ去年1年間、<br /> ひたすら<strong>Qiitaを傍観</strong>していました。</p> <p>私の業務はずっとサーバーサイドのJava開発。<br /> フロント明るくないおじさんです。<br /> はじめまして。きんみと申します。以後よろしくお願いします。</p> <h1 id="作ったもの"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE">作ったもの</a></h1> <p>とは言え、私も兵隊の端くれ。<br /> 血湧き肉躍る開発は大好物なわけでして。<br /> Web開発の真似事のような事はやっていました。<strong>年末頃</strong>から。<br /> その集大成がこちらになります。</p> <p><strong>悟空語ジェネレーター</strong><br /> <a target="_blank" rel="nofollow noopener" href="https://goku-lang.netlify.com/">https://goku-lang.netlify.com/</a><br /> <img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F127547%2F0fb19b09-cfc8-e408-bfe9-48c66a21397d.gif?ixlib=rb-1.2.2&auto=compress%2Cformat&gif-q=60&w=1400&fit=max&s=d00e45fecad196cc77e243f27355def9" alt="悟空語ジェネレーター" /><br /> 日本語を入力すると、悟空語に変換します。<br /> 初回はブラックボックスなサービスとして遊んで頂きたいので、<br /> 変換ルールについては最後に書きます。</p> <h1 id="使った技術"><a href="#%E4%BD%BF%E3%81%A3%E3%81%9F%E6%8A%80%E8%A1%93">使った技術</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://jp.vuejs.org/index.html">Vue.js</a><br /> JavaScript三大フレームワークの1つ。<br /> 公式ドキュメントの日本語がとても丁寧で、初学者の私でもとっつきやすいと感じたため使ってます。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.npmjs.com/package/kuromoji">kuromoji.js</a><br /> JavaScript製 形態素解析ライブラリ。<br /> 形態素とは文節の最小単位で、日本語文字列をこの単位で分解して、読み仮名や品詞情報を付与してくれます。<br /> <a target="_blank" rel="nofollow noopener" href="https://takuyaa.github.io/kuromoji.js/demo/tokenize.html">こちら</a>のデモサイトが分かりやすいです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.netlify.com/">Netlify</a><br /> 静的ホスティングサービス。<br /> ちょー簡単にWEBサイト作れます。</p> <p>ということで、このサービスは静的なサイトです。<br /> 冒頭に話した「リッチなクラウドサービス」は、ほぼ<strong>使ってません</strong>。<br /> 流行りのNetlifyを使ってますが、別にどこだろう動きます。<br /> <strong>故・Yahoo!ジオシティーズ</strong>でも多分動きます。</p> <p>紆余曲折を経ての結果ですが、ちょっと後悔しています。<br /> 後述します。</p> <h1 id="仕組み"><a href="#%E4%BB%95%E7%B5%84%E3%81%BF">仕組み</a></h1> <ol> <li>入力値をkuromoji.jsで形態素解析</li> <li>各形態素の読み仮名情報をローマ字に変換</li> <li>変換ルールに従い、正規表現でゴリゴリ変換</li> <li>変換された形態素はひらがなへ戻す</li> <li>変換されなかった形態素は元キーワードへ戻す</li> <li>全形態素を文字列結合</li> </ol> <p>たったこれだけです。</p> <h1 id="ハマったところ"><a href="#%E3%83%8F%E3%83%9E%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%93%E3%82%8D">ハマったところ</a></h1> <h2 id="1. フロントゼンゼンワカラナイ"><a href="#1.+%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%BC%E3%83%B3%E3%82%BC%E3%83%B3%E3%83%AF%E3%82%AB%E3%83%A9%E3%83%8A%E3%82%A4">1. フロントゼンゼンワカラナイ</a></h2> <p>学生時代にHP作った事ありましたし、少しですが業務でjQuery等をイジる事もあります。<br /> しかし、いざ勉強をしてみるとそのカオスっぷりに腰が抜けました。<br /> <strong>フロントなんて刺身にたんぽぽ乗せてるだけ</strong>と思ってた自分を殴りたくなりました。</p> <p>まずは基本となるHTML5、というより<strong>脱TABLEタグ</strong>から入り、<br /> ES6、Vue.js、React等の公式ドキュメントや入門記事をサーフィンして<br /> Hello Worldな日々を4〜5日程続けました。<br /> 広く浅く現代の技術に触れて、無知の知を得る事に徹しました。</p> <h2 id="2. 形態素解析"><a href="#2.+%E5%BD%A2%E6%85%8B%E7%B4%A0%E8%A7%A3%E6%9E%90">2. 形態素解析</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/torao@github/items/45ad9640cf94d3169cae">React + Kuromoji を使ってブラウザ上で形態素解析</a><br /> こちらを参考に、Vue.js上でkuromoji.jsを動かせるようにしました。</p> <p>まずは<code>npm install kuromoji</code>した後、辞書を公開ディレクトリへコピー。</p> <pre><code>$ cd project-directory $ cp -a node_modules/kuromoji/dict public/ </code></pre> <p>下記のようにしてVue.js上でkuromoji.jsを動かしました。</p> <pre><code><template> <div id="app"> <input id="inputText" v-model="inputText"> <button v-on:click="conv">変換</button> <p>outputToken is: <span>{</span><span>{</span> outputToken <span>}</span><span>}</span></p> </div> </template> <script> import kuromoji from "kuromoji" export default { name: 'App', data() { return { inputText: "形態素解析される文字列", outputToken: [], builder: kuromoji.builder({ dicPath: "/dict" }) } }, methods: { conv: async function(event) { var vm = this this.builder.build(await function(err, tokenizer){ if(err){ throw err } else { var token = tokenizer.tokenize(vm.inputText) vm.outputToken = token } }); } } } </script> </code></pre> <p>ポイントは下記です。</p> <p><code>var vm = this</code></p> <p>通常、単一コンポーネント内ではthisでプロパティを参照するが、</p> <p>builder内でも参照できるようにグローバル変数に格納する。</p> <p><code>async / await</code></p> <p>builder.build は非同期処理の為、async / awaitを使う。</p> <p><code>vm.outputToken = token</code></p> <p>形態素解析後のトークンをプロパティへ格納する。</p> <p>この勘所が全く分からずハマり、Vue.js上でkuromoji.jsを動かす事は一度諦めて、下記を検討しました。</p> <ul> <li>代替品として<a target="_blank" rel="nofollow noopener" href="https://github.com/rakuten-nlp/rakutenma/blob/master/README-ja.md">Rakuten MA</a>の使用</li> <li>GoogleCloudFunctions(GCF)にkuromoji.jsを置いてAPI化</li> <li>GCFに<a target="_blank" rel="nofollow noopener" href="https://github.com/miurahr/pykakasi">Pykakasi</a>(Python製 形態素解析ライブラリ)を置いてAPI化</li> <li><a target="_blank" rel="nofollow noopener" href="https://dev.classmethod.jp/server-side/mecab-using-python3-ja/">MecabをPythonで叩く</a>API作成</li> <li>外部APIサービス(<a target="_blank" rel="nofollow noopener" href="https://developer.yahoo.co.jp/webapi/jlp/ma/v1/parse.html">Yahoo!JAPAN テキスト解析WebAPI</a>等)の使用</li> </ul> <p>箇条書きで書きましたがそれぞれ、調査して、コードを書いて、ハマって、諦めてを繰り返してます。<br /> 1行につき1〜2日は費やしています。<br /> もっと頑張れば、いずれも実現可能だったかもしれませんが、最終的に「ブラウザ上でkuromoji.jsを実行」に回帰しました。<br /> 選定した訳ではなく、現段階の私の技術力ではこれしか出来なかったのです。</p> <h2 id="3. ローマ字変換"><a href="#3.+%E3%83%AD%E3%83%BC%E3%83%9E%E5%AD%97%E5%A4%89%E6%8F%9B">3. ローマ字変換</a></h2> <p>今回の処理は、<br /> 入力値をローマ字に変換 → 解析 → ひらがなへ戻す<br /> というフローになります。<br /> 便利なJS製のカナ/ローマ字変換ライブラリがいくつかありましたが、<br /> どれもヘボン式で不可逆でした(とうきょう → tokyo → ときょ)<br /> また、解析は正規表現で行うため[sa,<strong>shi</strong>,su,se,so]のような不規則な変化は避けたく<br /> 可逆なローマ字変換処理を自前で作成しました(とうきょう → toukyou)</p> <h2 id="4. CSSフレームワーク"><a href="#4.+CSS%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF">4. CSSフレームワーク</a></h2> <p>このアプリはドラゴンボールのネタアプリです。<br /> もちろん、CSSフレームワークは<strong>Bulma</strong>(ブルマ)を使ってます(ドヤ</p> <p>言いたかっただけです。特にハマってません。</p> <p>BootstrapからjQuery依存を省いた軽量なCSSフレームワークです。<br /> <a target="_blank" rel="nofollow noopener" href="https://bulma.io/documentation/">公式ドキュメント</a>や、下記記事を参考にサクっと導入出来ました。素晴らしい。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/ochiochi/items/de1afd2d3fc8f6d3ea55">CSSフレームワーク BULMA チュートリアル①</a></p> <h1 id="マーケティング"><a href="#%E3%83%9E%E3%83%BC%E3%82%B1%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0">マーケティング</a></h1> <p>フロント技術の学習が目的だった為、開発以外の何かを行うつもりはなかったです。<br /> しかし、せっかくWebアプリを公開するならば、使ってもらえるよう努力したい。<br /> これもまたWebの学習。「やらない」はただの機会損失、と思い</p> <p><strong>悟空なりきりアカウントをフォローしてみました。</strong></p> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">やったぜ!!!よろしくな!! <a target="_blank" rel="nofollow noopener" href="https://t.co/kVgRtneAMp">https://t.co/kVgRtneAMp</a></p>— きんみ / 🎍ついぎり🎍リリースしました🎉 (@_kinmi) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/_kinmi/status/1087275299161616384?ref_src=twsrc%5Etfw">2019年1月21日</a></blockquote> <p>今のところ、<strong>使って頂いた形跡はありません。</strong></p> <p><em>2019/4追記<br /> いつの間にかこちらのなりきりさん、アカウント削除されてました。。</em></p> <p>しかし、Twitterで公開したことで、多くのフォロワーさんに使って頂き、バグ報告も多数頂戴しました。<br /> テスト不足が露呈した結果ですが、ローカルで動かしていただけじゃ気付かず終わっていたと思います。<br /> 公開して得られる学びの多さを痛感しました。</p> <h1 id="公開後のバグ対応"><a href="#%E5%85%AC%E9%96%8B%E5%BE%8C%E3%81%AE%E3%83%90%E3%82%B0%E5%AF%BE%E5%BF%9C">公開後のバグ対応</a></h1> <h2 id="① iPhoneでは表示されない問題"><a href="#%E2%91%A0+iPhone%E3%81%A7%E3%81%AF%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C">① iPhoneでは表示されない問題</a></h2> <p>Twitterで公開した直後の出来事でした。</p> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">見れないです・・・iPhoneだからですかね(´・ω・`)</p>— 無職やめ太郎(本名) (@Yametaro1983) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/Yametaro1983/status/1091833791142715393?ref_src=twsrc%5Etfw">2019年2月2日</a></blockquote> <p>はい、Chromeでしかテストしてません。。。<br /> Safariで開いてみたところ、ブランクページが表示されました。</p> <p>変換処理で多用していた正規表現の「<a target="_blank" rel="nofollow noopener" href="http://js-next.hatenablog.com/entry/2015/11/20/083622">後読み</a>」が原因でした。<br /> 2019/2現在、対応しているブラウザはChromeだけのようです。<br /> ここでJSが落ち、レンダリング自体されない事態に陥ってました。</p> <p>先読みは使えるとの事だったので下記を参考に、文字列を反転させてマッチさせるという荒技を使いました。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/yumarule/items/a37520974e39b25b7a6f">Javascriptでの正規表現の後読みの代替</a></p> <h2 id="② ぱい◯いでか美さん対応"><a href="#%E2%91%A1+%E3%81%B1%E3%81%84%E2%97%AF%E3%81%84%E3%81%A7%E3%81%8B%E7%BE%8E%E3%81%95%E3%82%93%E5%AF%BE%E5%BF%9C">② ぱい◯いでか美さん対応</a></h2> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ぱいぱいでか美 <a target="_blank" rel="nofollow noopener" href="https://t.co/ESyx59epVx">https://t.co/ESyx59epVx</a> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/%E6%82%9F%E7%A9%BA%E8%AA%9E%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AC%E3%83%BC%E3%82%BF%E3%83%BC?src=hash&ref_src=twsrc%5Etfw">#悟空語ジェネレーター</a>これは「ぺぇぺぇでか美」にならへんのかいw</p>— 無職やめ太郎(本名) (@Yametaro1983) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/Yametaro1983/status/1091859171635412992?ref_src=twsrc%5Etfw">2019年2月3日</a></blockquote> <p>想定外です。ワケが分からない。<br /> ぱい系ワードは沢山テストしたのに、何故でか美さんだけ変換されないのか、、、</p> <p>原因は「ぱいぱいでか」を識別不可なワードとして解析され、<br /> 本来カタカナである読み仮名情報がひらがなになっていました。<br /> 私の仕様把握不足です。<br /> 前提としていた仕様が崩れ、<strong>デスマーチ</strong>が始まります。</p> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">やべぇ…でか美さん手強い…原因っぽいの直したら「ぺぇぱいでか美」ってなった、、どんな処理通ったらこうなるんや… <a target="_blank" rel="nofollow noopener" href="https://t.co/gwF0SffzKS">pic.twitter.com/gwF0SffzKS</a></p>— きんみ / 🎍ついぎり🎍リリースしました🎉 (@_kinmi) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/_kinmi/status/1091997573915656199?ref_src=twsrc%5Etfw">2019年2月3日</a></blockquote> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ぐあぁぁあ!!!ちくしょーーーー!!! <a target="_blank" rel="nofollow noopener" href="https://t.co/oLCeRtNyD9">pic.twitter.com/oLCeRtNyD9</a></p>— きんみ / 🎍ついぎり🎍リリースしました🎉 (@_kinmi) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/_kinmi/status/1092001619837743105?ref_src=twsrc%5Etfw">2019年2月3日</a></blockquote> <p>現在は正常に「ぺぇぺぇでか美」となるのでご安心ください。</p> <h1 id="やらなかった事/やれなかった事"><a href="#%E3%82%84%E3%82%89%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E4%BA%8B%2F%E3%82%84%E3%82%8C%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E4%BA%8B">やらなかった事/やれなかった事</a></h1> <h2 id="1. バックエンド開発"><a href="#1.+%E3%83%90%E3%83%83%E3%82%AF%E3%82%A8%E3%83%B3%E3%83%89%E9%96%8B%E7%99%BA">1. バックエンド開発</a></h2> <p>当初の学習目標は「Firebase Hosting + Cloud Functionsを使ったサーバレス開発」でした。<br /> しかし、結果としてはバックエンド処理の無い、<strong>文字通りのサーバレス</strong>となってしまいました。</p> <p>辞書ファイルをキャッシュするため、初回の変換は超絶重いうえに、挙動のクライアント環境依存が強すぎます。。<br /> 友人はスマホの速度制限が来ていたのか、<strong>5分</strong>かかったと言っていました。<br /> 別の友人は<strong>Android版Edgeなるもの</strong>を使っていて「動かないよ」と言ってました。<br /> やはり、コアな処理は裏に持たせるべきですね。</p> <p>また、ユーザが変換したワードの収集が出来ません。<br /> リリース後のメンテナンス作業を見越した場合、生きたデータは貴重です。<br /> こんな小さなネタアプリでも、バックエンド処理は作るべきでした。</p> <h2 id="2. OGP作成"><a href="#2.+OGP%E4%BD%9C%E6%88%90">2. OGP作成</a></h2> <p>ここは捨てました。ついでにファビコンもVueデフォルトのままです。<br /> 作り出すと拘ってしまい、リリース延期に繋がると思ったからです。<br /> バズを狙う場合は必須ですが、、、</p> <p>ただ、こちらの記事を拝見して<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/serinuntius/items/3017fb6ef51cd47352f6">Vue.jsとFirebaseでOGP画像生成系のサービスを爆速で作ろう</a><br /> OGPとしてSNSに共有するのは技術的にも面白そうと思ったので、近いうち導入するかもしれません。</p> <h1 id="モチベーション管理"><a href="#%E3%83%A2%E3%83%81%E3%83%99%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E7%AE%A1%E7%90%86">モチベーション管理</a></h1> <p>私のモチベ傾向として、完成までの道筋が見えたら熱が冷めます。<br /> 手探りでやっている時が一番楽しいんですよね。<br /> 過去にはリリースまで行かず、中途半端に終わったスマホアプリが幾つかあります。<br /> 今回も二の舞になるのではないか、という懸念があったので、<br /> 完成が見えた段階で<a target="_blank" rel="nofollow noopener" href="https://qiita.com/jabba">@jabba</a>さん作の<a target="_blank" rel="nofollow noopener" href="https://www.mobet.gq/">mobet</a> を使いました。</p> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">週末振休含めて4連休なので٩(๑òωó๑)۶今お遊びで作ってる「悟空語ジェネレーター」を公開する。 <a target="_blank" rel="nofollow noopener" href="https://t.co/Nuhjm7PCtp">https://t.co/Nuhjm7PCtp</a> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/%E3%83%A2%E3%83%99%E3%83%83%E3%83%88?src=hash&ref_src=twsrc%5Etfw">#モベット</a> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/_kinmi?ref_src=twsrc%5Etfw">@_kinmi</a>さんから</p>— きんみ / 🎍ついぎり🎍リリースしました🎉 (@_kinmi) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/_kinmi/status/1087988495120023552?ref_src=twsrc%5Etfw">2019年1月23日</a></blockquote> <p>しかし、残念ながらこの4連休に<strong>インフルエンザにかかるという痛恨のミス</strong>を犯してしまい未達成です。<br /> 「未達成と判定するまで(期日から判定まで五日間の猶予があります)には完成させる」をモチベーションにリリースまで漕ぎ着けました。</p> <h1 id="変換ルール"><a href="#%E5%A4%89%E6%8F%9B%E3%83%AB%E3%83%BC%E3%83%AB">変換ルール</a></h1> <p>最後に変換ルールです。<br /> 当アプリの発想は下記ブログエントリーからです。<br /> <a target="_blank" rel="nofollow noopener" href="http://junk.hatenablog.jp/entry/2018/02/15/034507">野沢雅子語の活用けぇ(活用形)</a><br /> 変換ルールはこちらを肉付けした形になります。</p> <p><strong>1. 母音[a, i] + [i, e] => 母音[e] + 'ぇ'</strong><br /> e.g.) 最初 → さいしょ → saisyo → selesyo → せぇしょ</p> <p><strong>2. 品詞:動詞 に含まれる 'る' => 'っ' 文末の場合は 'っぞ'</strong><br /> 文末かつ、動詞+助動詞の場合は後続の助動詞を削除しています。<br /> kuromoji.jsでは単語の原型(サ行変格活用前、等)のワードも取得でき、そちらもチェックしています。<br /> e.g.) するから → surukara → sultukara → すっから<br /> します(する + ます) → suru → sultuzo → すっぞ<br /> 帰ります(かえる + ます) → kaeru → keleltuzo → けぇっぞ(ルール1と2の併用)</p> <p><strong>3. 一人称 => 「おら」、二人称 => 「おめぇ」</strong><br /> Wikipediaから一人称と二人称の一覧を作り、固定値で変換かけてます。<br /> 「貴様」が代名詞として判断されなかったので、「品詞=代名詞」の条件はつけてません。<br /> ベジータの台詞を悟空語に変換したかったので。</p> <p>上記がデフォルト挙動であり、なまりレベル:普通です。<br /> なまりレベル:強い/超 は上記に加えて固有の変換ルールが加わります。</p> <p><strong>強い:母音[o] + [i, e] => 母音[e] + 'ぇ'</strong><br /> e.g.) におい → nioi → niele → にえぇ</p> <p><strong>超:3文字のローマ字(ltu 等)以外の母音全て => 母音[e]</strong><br /> e.g.) オッス!おら孫悟空!→ oltusu!orasongokuu! → eltuse!eresengekee! → えっせ!えれせんげけぇ!<br /> (※1)</p> <p>なまりレベル:超 は文字だとよく分からないですが、<strong>声に出す</strong>と意外と読めます。<br /> 他のルール発見された方は教えてください!</p> <h1 id="おわり"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A">おわり</a></h1> <p>次は、すげぇWEBせぇとつくっぞ。<br /> (次は、凄いWEBサイト作る。)(※2)</p> <h1 id="その他参考サイト"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96%E5%8F%82%E8%80%83%E3%82%B5%E3%82%A4%E3%83%88">その他参考サイト</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/hrdaya/items/291276a5a20971592216">JavaScriptで正規表現(文字列置換え編)</a></p> <p>※1 :「孫悟空」だと大丈夫ですが、「悟空」は「さとるそら」と読んでしまいます。<br /> ※2 : なまりレベル:強</p> きんみ tag:crieit.net,2005:PublicArticle/15391 2019-09-12T21:30:14+09:00 2020-09-21T16:31:53+09:00 https://crieit.net/posts/Laravel-6-Vue-js-React Laravel 6 でVue.jsやReactを使う <p>Laravel 6 が先日リリースされました。Laravel 5 との違いとして、プロジェクトの初期化時にVue.jsのリソースが自動生成されなくなりました。そのため、引き続きLaravel 6でも使用したい場合は追加で導入する必要があります。</p> <h2 id="laravel/uiをインストール"><a href="#laravel%2Fui%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">laravel/uiをインストール</a></h2> <p>まずはフロントエンドの初期化を行うためのパッケージとして<code>laravel/ui</code>というものがありますので、それをインストールします。</p> <pre><code class="sh">composer require laravel/ui --dev </code></pre> <h2 id="JavaScriptとCSSの生成"><a href="#JavaScript%E3%81%A8CSS%E3%81%AE%E7%94%9F%E6%88%90">JavaScriptとCSSの生成</a></h2> <p>uiライブラリを使って各種JavaScript、CSSのファイルを生成します。</p> <h3 id="Vue.jsを使う場合"><a href="#Vue.js%E3%82%92%E4%BD%BF%E3%81%86%E5%A0%B4%E5%90%88">Vue.jsを使う場合</a></h3> <p>下記のコマンドで生成します。</p> <pre><code class="sh">php artisan ui vue </code></pre> <p>packages.jsonにパッケージが追加されるのでインストールします。</p> <pre><code class="sh">yarn </code></pre> <p>念の為正常にビルドできるか一度試してみます。</p> <pre><code class="sh">yarn hot </code></pre> <p><code>DONE Compiled successfully</code>と表示されればOKです。hotコマンドの場合はjsファイルを修正すれば自動的にビルド&リロードを行ってくれるのでこのまま修正して実際に表示してみます。welcome.blade.phpのbodyの閉じタグの前に下記を追記してビルドされたJavaScriptを読み込みます。appというIDの要素内でVue.jsを有効化させているため、その中にコンポーネントを配置しています。</p> <pre><code class="html"><div id="app"> <example-component></example-component> </div> <script src="<span>{</span><span>{</span>mix('js/app.js')<span>}</span><span>}</span>"></script> </code></pre> <p>これで画面の端っこに表示されます。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5d7a370b0de7c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5d7a370b0de7c.png?mw=700" alt="" /></a></p> <h3 id="Reactを使う場合"><a href="#React%E3%82%92%E4%BD%BF%E3%81%86%E5%A0%B4%E5%90%88">Reactを使う場合</a></h3> <p>下記のコマンドで生成します。</p> <pre><code class="sh">php artisan ui react </code></pre> <p>packages.jsonにパッケージが追加されるのでインストールします。</p> <pre><code class="sh">yarn </code></pre> <p>念の為正常にビルドできるか一度試してみます。</p> <pre><code class="sh">yarn hot </code></pre> <p><code>DONE Compiled successfully</code>と表示されればOKです。hotコマンドの場合はjsファイルを修正すれば自動的にビルド&リロードを行ってくれるのでこのまま修正して実際に表示してみます。welcome.blade.phpのbodyの閉じタグの前に下記を追記してビルドされたJavaScriptを読み込みます。また、コンポーネントはexampleというIDの要素に挿入されるように指定されているため、その要素も表示したい場所に追加しておきます。</p> <pre><code class="html"><div id="example"></div> <script src="<span>{</span><span>{</span>mix('js/app.js')<span>}</span><span>}</span>"></script> </code></pre> <p>これで画面の端っこに表示されます。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5d7a370b0de7c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5d7a370b0de7c.png?mw=700" alt="" /></a></p> <h2 id="参考リンク"><a href="#%E5%8F%82%E8%80%83%E3%83%AA%E3%83%B3%E3%82%AF">参考リンク</a></h2> <p>公式の説明<br /> <a target="_blank" rel="nofollow noopener" href="https://laravel.com/docs/6.0/frontend">JavaScript & CSS Scaffolding - Laravel - The PHP Framework For Web Artisans</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/15377 2019-09-04T18:29:26+09:00 2019-09-04T18:29:26+09:00 https://crieit.net/posts/Nuxt-js-Web-marked-highlightjs-Markdown Nuxt.js製のWebサービスにmarked+highlightjsでMarkdownで書いた読書メモを表示できるようにしてみた <p>先日、開発中の<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読用の書籍管理アプリ</a>の新機能として、<br /> Markdownで書ける読書メモを追加したので、その時の備忘録。</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">✅読書中のメモが残せる読書メモ機能✨読書メモを残せる機能を追加しました‼️マークダウンで書けて、全メモをクリップボードにコピーできちゃいます😊はてなブログなどマークダウンが使えるなら、コピペで記事も書くことも😍(続く <a target="_blank" rel="nofollow noopener" href="https://t.co/K3pjr4noVo">pic.twitter.com/K3pjr4noVo</a></p>— めもらば@公式 (@MemoryLoverz) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz/status/1168227417950806016?ref_src=twsrc%5Etfw">September 1, 2019</a></blockquote> <h3 id="全体の流れ"><a href="#%E5%85%A8%E4%BD%93%E3%81%AE%E6%B5%81%E3%82%8C">全体の流れ</a></h3> <p>やりかたとしては、こんな感じ<br /> 1. Markdownの文字列を<a target="_blank" rel="nofollow noopener" href="https://github.com/markedjs/marked">marked</a>を使ってHTML化<br /> 2. <code><pre></code>の部分は、markedで<a target="_blank" rel="nofollow noopener" href="https://github.com/highlightjs/highlight.js/">highlightjs</a>を使うように設定<br /> 3. Markdown用のCSSを追加して見た目を調整</p> <h3 id="VueのSFCで書くとこんな感じ"><a href="#Vue%E3%81%AESFC%E3%81%A7%E6%9B%B8%E3%81%8F%E3%81%A8%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98">VueのSFCで書くとこんな感じ</a></h3> <pre><code class="html"><template> <div class="marked" v-html="markedHtml"></div> </template> <script lang="ts"> import { Component, Vue, Prop } from "nuxt-property-decorator"; import marked from "marked"; import hljs from "highlightjs"; @Component({ components: {} }) export default class NoteItem extends Vue { // マークダウンで書かれたテキスト @Prop({ required: true }) memo!: string; created() { // markedでhighlightjsを利用するように設定 marked.setOptions({ langPrefix: "", highlight: function(code, lang) { return hljs.highlightAuto(code, [lang]).value; } }); } private get markedHtml() { // markedを実行した結果を返す return marked(this.memo); } } </script> </code></pre> <p>ポイントとしてはこんな感じ。</p> <ol> <li><code>created()</code>内でmarkedの設定。ハイライト処理にhighlightjsを使うように。</li> <li>computedの<code>markedHtml()</code>内でmarkedを実行</li> <li><code><div v-html="markedHtml"></code>でHTMLを表示</li> </ol> <h3 id="マークダウン用のCSSを追加する"><a href="#%E3%83%9E%E3%83%BC%E3%82%AF%E3%83%80%E3%82%A6%E3%83%B3%E7%94%A8%E3%81%AECSS%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B">マークダウン用のCSSを追加する</a></h3> <p>上記のままだと、Webサイトに適用しているCSSがそのまま適用されてしまうので、<br /> こんなになってしまい、あまりよい感じではない...</p> <p><img width="500" alt="スクリーンショット 2019-09-03 23.08.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/7489db5b-e4cb-ae85-0db5-621a247e1287.png"></p> <p>なので、<code>.marked</code>というクラスは以下の要素にマークダウン用のCSSを設定して調整する。</p> <p>0から作ると大変なので、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/skkzsh/items/99e30bbbfe69f379b583">@skkzshさんのQiita記事</a>に書いてある、<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/simonlc/Markdown-CSS">simonlc/Markdown-CSS</a>や<a target="_blank" rel="nofollow noopener" href="https://github.com/kottkrig/Markdown-CSS">kottkrig/Markdown-CSS</a>を参考に設定していく。</p> <p>できたのがこんな感じ。<br /> あとはサイトのデザインと合わせて、少しずつ調整〜</p> <pre><code class="scss">// **************************** // * Marked // **************************** .marked { p { margin: 1em 0; } img { max-width: 100%; } h1, h2, h3, h4, h5, h6 { font-weight: normal; line-height: 1em; } h4, h5, h6 { font-weight: bold; } h1 { font-size: 2.5em; } h2 { font-size: 2em; } h3 { font-size: 1.5em; } h4 { font-size: 1.2em; } h5 { font-size: 1em; } h6 { font-size: 0.9em; } ol { margin: 1em 0; padding: 0 0 0 2em; } ul { margin: 1em 0; padding: 0 0 0 2em; list-style: disc; } dd { margin: 0 0 0 2em; } table { margin: 10px 0 15px 0; border-collapse: collapse; } td, th { border: 2px solid #ccb8a3; padding: 3px 10px; } th { padding: 5px 10px; } blockquote { padding-left: 1em; margin: 0; color: #666666; border-left: 0.3em #f6f0e5 solid; } } </code></pre> <p>このCSSを適用するとこんな感じに!!<br /> いい感じになった(<em>´ω`</em>)</p> <p><img width="500" alt="スクリーンショット 2019-09-03 23.14.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/8a3f19a2-3524-f864-4504-ff174683c8cf.png"></p> <p>いろいろ便利なライブラリがあるのでサクッとできてステキ♪</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%EF%BC%81%EF%BC%81">こんなのつくってます!!</a></h2> <p>上記の読書メモ機能を追加した、積読用の読書管理アプリ「積読ハウマッチ」!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="25%"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://mizchi.hatenablog.com/entry/2018/10/23/221446">大量のテキストを食っても速い Markdown Editor 作った - mizchi's blog</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/bmf_san/items/fe2b4b4591dd17ee7103">React+marked+highlight.jsでマークダウンエディタをつくる - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/tadnakam/items/1323d03743fc0101aa50">コードのハイライト表示 JS ライブラリ 3種 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://kannokanno.hatenablog.com/entry/2013/06/19/132042">JavaScript - Markdownパーサーのshowdown.js、markdown-js、markedを簡単比較 - ぼっち勉強会</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/skkzsh/items/99e30bbbfe69f379b583">Markdownでスタイルシート - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15310 2019-08-09T08:49:16+09:00 2019-08-09T08:49:16+09:00 https://crieit.net/posts/Web-5d4cb4fcdd570 積読を解消をうながすWebサービス『積読ハウマッチ』をリリースしました!!! <p>4月くらいから作っていたWebサービスをついにリリースしました♪<br /> 積んでる本の総額がわかる読書管理サービスです!</p> <p>積読が多い自分を戒めるためのWebサービス( ゚д゚)!</p> <p>■積んでる本の総額がわかる書籍サービス|積読ハウマッチ<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">https://tsundoku.site</a></p> <p><a href="https://crieit.now.sh/upload_images/f86129c7aa3dee1104223eaa7d236fb55d4cb2a04b063.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f86129c7aa3dee1104223eaa7d236fb55d4cb2a04b063.png?mw=700" width="25%"/></a></p> <h3 id="思いついたときのツイート"><a href="#%E6%80%9D%E3%81%84%E3%81%A4%E3%81%84%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AE%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88">思いついたときのツイート</a></h3> <p>思い返すと、完全にネタ的なおもいつきではじまった感(<em>´ω`</em>)</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">おもしろそうなアプリを思いついてしまった。。自分がダメージを受けるが作りたい。。</p>— きらぷか📚積読ハウマッチ開発中【事前登録はじめました】 (@kira_puka) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka/status/1116553164541399040?ref_src=twsrc%5Etfw">April 12, 2019</a></blockquote> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ちなみに、おもしろそうっていうのは、「くだらない!!」的なおもしろさで、すごい!とかイケてる!とかはまったくない</p>— きらぷか📚積読ハウマッチ開発中【事前登録はじめました】 (@kira_puka) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka/status/1116563595632893954?ref_src=twsrc%5Etfw">April 12, 2019</a></blockquote> <h3 id="こんなサービスです"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%A7%E3%81%99">こんなサービスです</a></h3> <p>メインの画面はこんな感じ。<br /> 本を登録して、ステータスを変更していく、積読を管理するサービスです。</p> <p><a href="https://crieit.now.sh/upload_images/6f44c811b94a08fffcc0368201d8e27c5d4cb15aeb1a7.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6f44c811b94a08fffcc0368201d8e27c5d4cb15aeb1a7.png?mw=700" alt="スクリーンショット 2019-07-26 9.31.32.png" /></a></p> <p>ほかとちがうのは、<strong>本の総額がわかる</strong>ところ<br /> <strong>いま、いくら積んでいるかがわかってしまうおそろしさ((((;゚Д゚))))ガクガク</strong></p> <h3 id="コンセプト"><a href="#%E3%82%B3%E3%83%B3%E3%82%BB%E3%83%97%E3%83%88">コンセプト</a></h3> <p><a href="https://crieit.now.sh/upload_images/d08ca6a468920cdc81745d2f2204e6e75d4cb188bc65c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d08ca6a468920cdc81745d2f2204e6e75d4cb188bc65c.png?mw=700" alt="スクリーンショット 2019-07-26 9.29.58.png" /></a></p> <p>おもいつきではあるんですが、もともと積読が多いなと。。</p> <p>本で買うのですが、積み上がっている本を見て、ふと、<br /> **「これっていくらなんだろ?」 **<br /> とおもったのが発端。読まないとお金を積んでるだけだなと。。</p> <h3 id="使い方のかんたんな説明"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9%E3%81%AE%E3%81%8B%E3%82%93%E3%81%9F%E3%82%93%E3%81%AA%E8%AA%AC%E6%98%8E">使い方のかんたんな説明</a></h3> <p>使い方はシンプル</p> <p>検索して、</p> <p><a href="https://crieit.now.sh/upload_images/9908182b569e1b60b5c10c20a5db01955d4cb1a589772.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9908182b569e1b60b5c10c20a5db01955d4cb1a589772.png?mw=700" alt="スクリーンショット 2019-07-26 9.30.25.png" /></a></p> <p>持ってる本を積んで、</p> <p><a href="https://crieit.now.sh/upload_images/c7c59729dab973076b8d04714dca49845d4cb1aeab425.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c7c59729dab973076b8d04714dca49845d4cb1aeab425.png?mw=700" alt="スクリーンショット 2019-07-26 9.30.34.png" /></a></p> <p>総額を見て、現実を受け止め、</p> <p><a href="https://crieit.now.sh/upload_images/86b1b913fe42a2fe8c240f23889fb5a35d4cb1bd36d82.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/86b1b913fe42a2fe8c240f23889fb5a35d4cb1bd36d82.png?mw=700" alt="スクリーンショット 2019-07-26 9.30.42.png" /></a></p> <p>積読を減らすことをがんばる!</p> <p><a href="https://crieit.now.sh/upload_images/6325d3a9f673b1a0bf303c5f03c332af5d4cb1c68507f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6325d3a9f673b1a0bf303c5f03c332af5d4cb1c68507f.png?mw=700" alt="スクリーンショット 2019-07-26 9.30.49.png" /></a></p> <p>持ってる本を登録して、読みはじめたり、<br /> 読み終わったりしたらステータスを変える感じです!</p> <h3 id="積んでる金額を見るだけだとつらいので。。"><a href="#%E7%A9%8D%E3%82%93%E3%81%A7%E3%82%8B%E9%87%91%E9%A1%8D%E3%82%92%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E3%81%A0%E3%81%A8%E3%81%A4%E3%82%89%E3%81%84%E3%81%AE%E3%81%A7%E3%80%82%E3%80%82">積んでる金額を見るだけだとつらいので。。</a></h3> <p>ランキング機能もついています(<em>´ω`</em>)</p> <p><strong>「こんなに積んでるけど、ほかのひとはどれくらいなんだろ?」</strong></p> <p>っていうのが発端。</p> <p>他の人のをみて「まだまだ積んでも大丈夫(<em>´ω`</em>)」と思いたいなと。。</p> <p><a href="https://crieit.now.sh/upload_images/3bc3f7432fd83b0febe269579571f63d5d4cb1d68abd3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3bc3f7432fd83b0febe269579571f63d5d4cb1d68abd3.png?mw=700" alt="スクリーンショット 2019-07-26 9.50.00.png" /></a></p> <p><strong>読まれてる本、買われてる本のランキング</strong>もあるので、<br /> どんな本が人気かもわかるように♪</p> <p><a href="https://crieit.now.sh/upload_images/1f090737658ae37b78842e05183582f85d4cb1f61cd9f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1f090737658ae37b78842e05183582f85d4cb1f61cd9f.png?mw=700" alt="スクリーンショット 2019-08-07 12.45.00.png" /></a></p> <p><strong>新着もあるので、新しく積まれたり読まれたりした本を、<br /> ぼーっとながめるのも楽しい感じに(<em>´ω`</em>)</strong></p> <h3 id="Twitterでシェアして積読金額を晒せる..."><a href="#Twitter%E3%81%A7%E3%82%B7%E3%82%A7%E3%82%A2%E3%81%97%E3%81%A6%E7%A9%8D%E8%AA%AD%E9%87%91%E9%A1%8D%E3%82%92%E6%99%92%E3%81%9B%E3%82%8B...">Twitterでシェアして積読金額を晒せる...</a></h3> <p>SNSでシェアした時の画像も力をいれてみました(<em>´ω`</em>)<br /> <strong>積んでる金額を晒すと、さらに読まないといけない感が出るかも(・・?</strong><br /> (積んでるのに、ドヤ顔のおじさんが...)</p> <p><a href="https://crieit.now.sh/upload_images/40e3db845e5af712e49912ca6a9b867b5d4cb20c64d89.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/40e3db845e5af712e49912ca6a9b867b5d4cb20c64d89.png?mw=700" alt="スクリーンショット 2019-07-26 10.01.46.png" /></a></p> <p>もちろん、読んだ本も共有できるので、<br /> 「こんなに読んだんだ!がんばった!」<br /> もシェアできます♪ <strong>独学や自己研鑽をがんばってるひとにも!</strong></p> <p><a href="https://crieit.now.sh/upload_images/eefdda10ec1cab2aaaa17a5e3766bd3c5d4cb21ab8da4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/eefdda10ec1cab2aaaa17a5e3766bd3c5d4cb21ab8da4.png?mw=700" alt="スクリーンショット 2019-07-26 10.01.53.png" /></a></p> <p>(こっちはすっきりしたおじさん...)</p> <h3 id="まだまだ作れてないところはたくさん。。"><a href="#%E3%81%BE%E3%81%A0%E3%81%BE%E3%81%A0%E4%BD%9C%E3%82%8C%E3%81%A6%E3%81%AA%E3%81%84%E3%81%A8%E3%81%93%E3%82%8D%E3%81%AF%E3%81%9F%E3%81%8F%E3%81%95%E3%82%93%E3%80%82%E3%80%82">まだまだ作れてないところはたくさん。。</a></h3> <p>とりあえず、ファーストリリースができたんですが、<br /> <strong>まだまだ作りたい部分はたくさん。。!!</strong></p> <ul> <li>読んだ感想やメモを記録できるようにしたい</li> <li>どれくらい読んだかを日々の進捗を記録できるようにしたい</li> <li>読み終える目標の日を決めて、アラートしてもらいたい</li> </ul> <p>積読の消化を促すような機能をどんどんいれれたらとおもっています!</p> <p>見てるだけでも楽しいので、まずはちらっとみてもらえたら♪<br /> おもしろそうだなと思ったら、ぜひ一緒に積み比べしましょう!</p> <p>良い積読ライフを(<em>´ω`</em>)</p> <p>■積んでる本の総額がわかる書籍サービス|積読ハウマッチ<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">https://tsundoku.site</a></p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15140 2019-06-21T22:04:13+09:00 2019-10-24T11:12:00+09:00 https://crieit.net/posts/Laravel-Vue-js Laravel+Vue.jsでユーザーのフォロー機能を作る <p>LaravelとVue.jsを使い、TwitterやQiitaのように画面を切り替えずにボタンクリックでユーザーのフォローとフォロー解除をできるようにしてみます。</p> <p><a href="https://crieit.now.sh/upload_images/7acc9a9348b6e80a7825f766114688995d0cd4c4961b9.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7acc9a9348b6e80a7825f766114688995d0cd4c4961b9.png?mw=700" alt="" /></a></p> <p>今回は下記の構成で作ってみます。</p> <ul> <li>Laravel5.8</li> <li>Vue.js2.5</li> <li>Bootstrap4.2</li> </ul> <p>LaravelとVue.jsはもうちょっとバージョンが低くても大丈夫だと思います。Bootstrapについては、今回通信中のローディング表示にspinnerを使っているため最新のものが望ましいです。(4.0だと動きませんが、そこの部分のCSSをコピペしてくれば一応動きます)</p> <h2 id="テーブルの作成"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90">テーブルの作成</a></h2> <p>ユーザーはLaravelのデフォルトのもので大丈夫です。ただし今回の例はモデルを<code>app/Models</code>に移動していますので、同様の構成にしておく必要があります。ただ、他の設定ファイルの修正などもあり面倒ではありますので、逆に当記事に書いている<code>use App\Models\User</code>等を<code>use App\User</code>等に読み替えて試していただいたほうが楽かもしれません。</p> <p>あとはフォローするユーザーとフォローされるユーザーを繋げるための中間テーブルを作ります。</p> <p>マイグレーションは下記のような感じです。普通の命名規則としては双方のテーブルの単数名を組み合わせるのですが、そうするとuser_userになってしまい変な感じとわかりにくい感じがしたので適当に変えています。</p> <pre><code class="php">Schema::create('follow_users', function (Blueprint $table) { $table->unsignedInteger('user_id'); $table->unsignedInteger('followed_user_id')->index(); $table->unique(['user_id', 'followed_user_id']); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('followed_user_id')->references('id')->on('users')->onDelete('cascade'); }); </code></pre> <h2 id="モデルの準備"><a href="#%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AE%E6%BA%96%E5%82%99">モデルの準備</a></h2> <h3 id="中間テーブル作成"><a href="#%E4%B8%AD%E9%96%93%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E4%BD%9C%E6%88%90">中間テーブル作成</a></h3> <p>直接テーブルを操作したいので一応モデルも作っておきます。中間テーブル用のPivotを継承し、今回のテーブル構成に合わせて設定もいくつか行っています。</p> <pre><code class="php">namespace App\Models; use Illuminate\Database\Eloquent\Relations\Pivot; class FollowUser extends Pivot { protected $table = 'follow_users'; public $timestamps = false; protected $guarded = []; } </code></pre> <h3 id="リレーションの設定"><a href="#%E3%83%AA%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E8%A8%AD%E5%AE%9A">リレーションの設定</a></h3> <p>ユーザー(User) 中間テーブル(FollowUser) フォローされるユーザー(User)</p> <p>という構成のリレーションを設定します。具体的にはLaravelのマニュアルでいうところの<code>Many To Many</code>で、<code>belongsToMany</code>で設定します。</p> <p>ユーザーのモデルに下記のメソッドを追加します。</p> <pre><code class="php"> public function followUsers() { return $this->belongsToMany(self::class, 'follow_users', 'user_id', 'followed_user_id') ->using(FollowUser::class); } </code></pre> <p>普通はこんなに設定は不要でマニュアル通りで良いのですが、今回は同じテーブル同士のMany To Manyのためちょっと設定が増えています。</p> <h2 id="フォロー、フォロー解除処理作成"><a href="#%E3%83%95%E3%82%A9%E3%83%AD%E3%83%BC%E3%80%81%E3%83%95%E3%82%A9%E3%83%AD%E3%83%BC%E8%A7%A3%E9%99%A4%E5%87%A6%E7%90%86%E4%BD%9C%E6%88%90">フォロー、フォロー解除処理作成</a></h2> <p>FollowUserControllerに下記のフォローとフォロー解除用のメソッドを追加します。</p> <pre><code class="php"> public function store(Request $request) { $followedUser = User::findOrFail($request->input('id')); FollowUser::firstOrCreate([ 'user_id' => Auth::id(), 'followed_user_id' => $followedUser->id, ]); return response()->json(['result' => true]); } public function destroy($id) { $followedUser = User::findOrFail($id); $user = Auth::user(); $user->followUsers()->detach($followedUser->id); return response()->json(['result' => true]); } </code></pre> <p>ファイルの戦闘のnamespaceのあとに上記で利用しているクラスのuseも追加しておきます。</p> <pre><code class="php">use App\Models\FollowUser; use App\Models\User; </code></pre> <p>上記のような感じで、attach, detachを使うことで割当と解除を行うことができます。上記だとattachを使っていませんが、複数のタブを開いていて変な操作をした場合などに重複エラーが出てしまい、わざわざそのための処理を入れるのも面倒だったためfirstOrCreate呼び出しで完結するようにしています。</p> <p>各メソッドの最初のユーザー取得も実際には不要ですが、一応変なデータが出来ないようにユーザーの存在チェック代わりに入れています。</p> <h2 id="Vue.js側でボタン作成"><a href="#Vue.js%E5%81%B4%E3%81%A7%E3%83%9C%E3%82%BF%E3%83%B3%E4%BD%9C%E6%88%90">Vue.js側でボタン作成</a></h2> <p>サーバー側は出来たので、あとは実際にフォローをトグルするためのUIをVue.jsで作っていきます。</p> <h3 id="スクリプトの定義部分"><a href="#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E5%AE%9A%E7%BE%A9%E9%83%A8%E5%88%86">スクリプトの定義部分</a></h3> <p>ひとまずコンポーネントのスクリプトの定義周りは下記のような形にしました。(TypeScriptで書いているので、JavaScriptの場合は適宜置き換えてください)</p> <pre><code class="typescript">import { Vue, Component, Prop } from 'vue-property-decorator' import axios from 'axios' @Component export default class UserFollow extends Vue { @Prop({ type: String, required: true }) id!: string @Prop({ type: Boolean, default: false }) following!: boolean currentFollowing = this.following sending = false </code></pre> <p>最初のフォロー状態はプロパティで受け取りますが、あとはボタン操作によって切り替えるため、コンポーネント内ではcurrentFollowingを使っていきます。ローディング表示や、ボタンの連打などを防ぐためsendingフラグを入れています。</p> <h3 id="フォロー、アンフォローの実行"><a href="#%E3%83%95%E3%82%A9%E3%83%AD%E3%83%BC%E3%80%81%E3%82%A2%E3%83%B3%E3%83%95%E3%82%A9%E3%83%AD%E3%83%BC%E3%81%AE%E5%AE%9F%E8%A1%8C">フォロー、アンフォローの実行</a></h3> <p>フォローボタン、フォロー解除ボタンを押した時のメソッドを追加します。(エラー処理は省略しているため適宜入れてください)</p> <pre><code class="typescript"> async follow() { if (this.sending) { return } this.sending = true const data = { id: this.id } await axios.post('/follow-users', data) this.currentFollowing = true this.sending = false } async unfollow() { if (this.sending) { return } this.sending = true await axios.delete(`/follow-users/${this.id}`) this.currentFollowing = false this.sending = false } </code></pre> <h3 id="ボタンの配置"><a href="#%E3%83%9C%E3%82%BF%E3%83%B3%E3%81%AE%E9%85%8D%E7%BD%AE">ボタンの配置</a></h3> <p>あとは上記を呼び出すためのボタンを配置します。</p> <pre><code class="html"><template> <div> <button v-if="currentFollowing" type="button" class="btn btn-point btn-raised" @click="unfollow"> <div v-if="sending" class="spinner-border spinner-border-sm" role="status"> <span class="sr-only">Sending...</span> </div> <div v-else>フォロー中</div> </button> <button v-else type="button" class="btn btn-default btn-raised" @click="follow"> <div v-if="sending" class="spinner-border spinner-border-sm" role="status"> <span class="sr-only">Sending...</span> </div> <div v-else> フォローする <i class="material-icons">add</i> </div> </button> </div> </template> </code></pre> <p>sendingがtrueの場合はspinnerのくるくるローディングが表示されます。また、フォロー状態によってボタンも切り替わります。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>以上のような感じでスムーズに使えるフォロー、フォロー解除ボタンを作ってみました。参考にできる部分があれば是非利用してみてください。その他何かこんなものの作り方を教えて欲しい! みたいなあればお気軽にコメントいただければと思います。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14926 2019-04-15T22:03:32+09:00 2019-04-15T22:03:32+09:00 https://crieit.net/posts/nend-Vue nendの広告を表示するVueコンポーネント <p>nendの広告をVueコンポーネントとして作成し、Nuxt.js上で表示してみました。AdSenseの審査が通らない間使っていたのですが、通ったので必要なくなりました。そのためせっかくなので晒しておきます。</p> <p>ただ、nend広告の貼り付けタグは癖があるので結構めちゃくちゃなことをやっています。場合によっては怒られる可能性もあるのでご注意を。</p> <h2 id="そもそもSPAでは使えない"><a href="#%E3%81%9D%E3%82%82%E3%81%9D%E3%82%82SPA%E3%81%A7%E3%81%AF%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84">そもそもSPAでは使えない</a></h2> <p>nend広告の貼り付けタグはそもそもSPAでは使えません。</p> <h3 id="scriptタグで構成されている"><a href="#script%E3%82%BF%E3%82%B0%E3%81%A7%E6%A7%8B%E6%88%90%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B">scriptタグで構成されている</a></h3> <p>全てscriptタグで構成されています。こんな感じです。</p> <pre><code class="html"><script type="text/javascript"> var nend_params = {"media":メディアID,"site":サイトID,"spot":位置,…}; </script> <script type="text/javascript" src="メインスクリプト"></script> </code></pre> <p>Vueコンポーネント上にscriptタグを貼っても基本的に無視されてしまいますので動きません。ただ、JavaScriptのcreateElementでスクリプトタグを作って配置することで動作させる事は可能ですので今回もそのようにしました。具体的にはこんな感じです。</p> <pre><code class="javascript"> const script = document.createElement('script') script.src = 'URL' const container = <Element>this.$refs.container container.appendChild(script) </code></pre> <h3 id="nend_paramsが無いと怒られる"><a href="#nend_params%E3%81%8C%E7%84%A1%E3%81%84%E3%81%A8%E6%80%92%E3%82%89%E3%82%8C%E3%82%8B">nend_paramsが無いと怒られる</a></h3> <p>そう、見れば分かるのですがnend_paramsがグローバルに配置されています。そのため、Vueコンポーネント内に隠蔽ができません…。</p> <p><code>window.nend_params</code> に入れ、その直後にscriptタグを配置する、という連動するのかどうかも良くわからない流れでやらなければいけなくなっています。</p> <h3 id="document.writeできないと怒られる"><a href="#document.write%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%E3%81%A8%E6%80%92%E3%82%89%E3%82%8C%E3%82%8B">document.writeできないと怒られる</a></h3> <p>そう、一番の問題はこれでした。document.writeを使っているのですが、Vueコンポーネントはdocumentとは全く関係ない場所で動いているので、そもそもdocument.writeが不可能です。nend広告はSPAを完全に切っていて時代錯誤すぎます。</p> <p>仕方ないのでdocument.writeを一旦置き換え、終わったらもとに戻す、という方法をとりました。</p> <pre><code class="javascript"> const write = window.document.write window.document.write = str => { container.innerHTML += str } container.appendChild(script) </code></pre> <p>ここまでしてようやく表示することができました。もう正直この時点で正式な方法で貼っているとは思えない状態です。</p> <h2 id="ひとつしか配置できない"><a href="#%E3%81%B2%E3%81%A8%E3%81%A4%E3%81%97%E3%81%8B%E9%85%8D%E7%BD%AE%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84">ひとつしか配置できない</a></h2> <p>前述の方法で表示できるようにはなったのですが、そもそもscriptタグの配置で実行しているので、いつ処理が完了しているのか誰にも分かりません。そのため、1ページに二つ以上広告を配置すると1つ目の処理が終わってない状態で二つ目の処理が動いてしまい、どこのinnerHTMLに値が追加されていくのか全くわからないカオスな状態となります。</p> <h3 id="キューを使った"><a href="#%E3%82%AD%E3%83%A5%E3%83%BC%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F">キューを使った</a></h3> <p>とにかく、ひとつずつ処理をしなければならないのでキュー方式で動かすことにしました。</p> <ol> <li>他のキューがあったら待つ</li> <li>配置する</li> <li>読み込みが完了するまで待つ</li> </ol> <p>という流れです。コールバックは当然無いので、containerの中にimgタグが現れるまで待つ、という手法を取りました。最大5秒にしていますがwhile & <code>await setTimeout</code>してるのでどんな感じで処理が積まれていっているのか謎です。</p> <h2 id="同じ広告を配置できない"><a href="#%E5%90%8C%E3%81%98%E5%BA%83%E5%91%8A%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84">同じ広告を配置できない</a></h2> <p>同じ広告を配置できないことが分かりました。本番であればまあ広告枠を作ればいいので問題ないのですが、テスト広告も同じものを配置できないため、1ページ内に複数の広告枠がある場合は仕方がないので大きさも無視して別のものを表示していかなければなりません。</p> <p>当然、1ページ内にたくさん広告がある場合はテスト表示できません。</p> <h2 id="成果物"><a href="#%E6%88%90%E6%9E%9C%E7%89%A9">成果物</a></h2> <p>下記が全容になります。ぐちゃぐちゃですので参考にはしないでください。</p> <pre><code class="html"><template> <span ref="container"></span> </template> <script lang="ts"> import { Vue, Component, Prop } from 'nuxt-property-decorator' import { waitFor } from '../client/lib/wait' declare var window: any const testSpots = [111111, 222222, 333333, 444444] function pushQueue(component, time) { if (window.nendAdComponents === undefined) { window.nendAdComponents = [] } window.nendAdComponents = window.nendAdComponents.filter( row => row.location === location.href ) window.nendAdComponents.push({ component, finished: false, location: location.href, time }) } function isCurrentQueue(component) { const current = window.nendAdComponents.find(row => !row.finished) if (!current) { return false } return current.component === component } function getQueueNo(component) { for (let i = 0; i < window.nendAdComponents.length; i++) { if (window.nendAdComponents[i].component === component) { return i } } return 0 } function isQueueEnabled(component, time) { for (let i = 0; i < window.nendAdComponents.length; i++) { const row = window.nendAdComponents[i] if (row.component === component && row.time === time) { return true } } return false } function finishQueue(component) { const current = window.nendAdComponents.find( row => row.component === component ) if (current) { current.finished = true } } @Component export default class Nend extends Vue { @Prop(String) spot: string adHtml = '' async mounted() { const time = Date.now() pushQueue(this, time) await waitFor(() => isCurrentQueue(this), 5000) if (!isQueueEnabled(this, time)) { return } this.adHtml = '' window.nend_params = { media: process.env.NEND_MEDIA, site: process.env.NEND_SITE, spot: process.env.NEND_SPOT === '' ? this.spot : process.env.NEND_SPOT, type: 1, oriented: 1 } if (window.nend_params.media == '11') { const queueNo = getQueueNo(this) window.nend_params.spot = testSpots[queueNo] } const script = document.createElement('script') script.src = 'メインスクリプトURL' const container = <Element>this.$refs.container const write = window.document.write window.document.write = str => { container.innerHTML += str } container.appendChild(script) await waitFor(() => { const img = container.querySelector('img') return img !== undefined && img !== null }, 5000) window.document.write = write finishQueue(this) } } </script> </code></pre> <p>waitForはこんなのです。</p> <pre><code class="javascript">export async function waitFor( conditionFunc: (() => boolean), maxMilliSeconds: number ) { let waited = 0 while (!conditionFunc() && waited < maxMilliSeconds) { await wait(1000) waited += 1000 } } export function wait(milliSeconds: number) { return new Promise(resolve => { setTimeout(() => { resolve() }, milliSeconds) }) } </code></pre> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>こんな感じでとりあえず表示することはできましたが常時心配で仕方がないです。ページ切り替えの際に問題が出るパターンなどもありそうですし。(まあ広告なのでそんな厳密に動かなくても良いのですが)</p> <p>とりあえずはやく設置タグを変えたほうがいいように思いますね。せめて関数呼び出し方式にしてくれないと厳しい…。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14896 2019-04-01T07:14:06+09:00 2019-04-01T11:02:55+09:00 https://crieit.net/posts/c4e13ccbb7ff6ffbec992d58cf06261c エイプリルフールはじめました <p>リリースしてからはじめてのエイプリールフールだったのでちょっと遊んでみました。ふわふわと飛んでいるのはくりえいとくんです。</p> <p><a href="https://crieit.now.sh/upload_images/cf9086966f7925dc9e21645fb9bf8e125ca0d778569a6.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/cf9086966f7925dc9e21645fb9bf8e125ca0d778569a6.png?mw=700" alt="" /></a></p> <p>元々</p> <p>Crieit</p> <p>という文字が顔に見えたのでそれをそのまま絵にしてみたらおばけになった、という感じです。クリックすると色々喋ります。</p> <p><img src="/img/aprilfool/crieit.png" alt="" /></p> <p>特筆することも無いので動き回らせているプログラムでも貼っておきます。</p> <h2 id="おばけクラス"><a href="#%E3%81%8A%E3%81%B0%E3%81%91%E3%82%AF%E3%83%A9%E3%82%B9">おばけクラス</a></h2> <p><code>move()</code>で動かし、<code>getX()</code>と<code>getY()</code>で表示するための位置を取得しているだけのよくあるクラスです。</p> <p>Goast.ts</p> <pre><code class="javascript">const ymax = 50 const width = 100 export default class Ghost { id: number private x = 0 private y = 0 private basey = 0 private ax = 0 private angle = 0 constructor() { this.id = Date.now() if (Math.round(Math.random()) === 0) { this.x = -width this.ax = Math.floor(Math.random() * 2 + 2) } else { this.x = window.innerWidth this.ax = -Math.floor(Math.random() * 2 + 2) } this.basey = Math.floor(Math.random() * window.innerHeight) this.calculateY() } calculateY() { this.y = this.basey + Math.sin(this.angle) * ymax } move() { this.x += this.ax this.angle += Math.PI / 50 if (this.angle >= Math.PI * 2) { this.angle -= Math.PI * 2 } this.calculateY() } finished() { if (this.ax < 0 && this.x < -width) { return true } if (this.ax > 0 && this.x > window.innerWidth) { return true } return false } getX() { return this.x } getY() { return Math.floor(this.y) } getAx() { return this.ax } } </code></pre> <h2 id="おばけたちの管理コンポーネント"><a href="#%E3%81%8A%E3%81%B0%E3%81%91%E3%81%9F%E3%81%A1%E3%81%AE%E7%AE%A1%E7%90%86%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88">おばけたちの管理コンポーネント</a></h2> <p>これも特に特筆することのない単なるゲームループ的なコンポーネントです。おばけを管理して画面からはみ出したら削除したりしています。</p> <pre><code class="html"><template> <div> <AprilfoolGhost v-for="ghost in ghosts" :key="ghost.id" :ghost="ghost"></AprilfoolGhost> </div> </template> <script lang="ts"> import { Vue, Component, Prop } from 'vue-property-decorator' import Aprilfool from './Aprilfool.vue' import AprilfoolGhost from './AprilfoolGhost.vue' import Ghost from './Ghost' @Component({ components: { AprilfoolGhost } }) export default class AprilfoolGhosts extends Aprilfool { ghosts: Ghost[] = [] mounted() { if (!this.isAprilfool()) { return } setInterval(() => { if (this.ghosts.length < 10) { this.ghosts.push(new Ghost()) } }, 5000) setInterval(() => { this.moveAll() }, 41) } moveAll() { const ghosts: Ghost[] = [] this.ghosts.forEach((ghost, index) => { ghost.move() if (!ghost.finished()) { ghosts.push(ghost) } }) this.ghosts = ghosts } } </script> </code></pre> <h2 id="おばけコンポーネント"><a href="#%E3%81%8A%E3%81%B0%E3%81%91%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88">おばけコンポーネント</a></h2> <p>もう動きはおばけクラス、管理は前述のコンポーネントで行われているので、ここでは表示したりクリックイベントを処理したりするだけです。これで一通り完了です。面倒だったので表示はfixedにしてスクロールは考慮しないようにしています。</p> <pre><code class="html"><template> <div> <a @click.prevent="showMessage()" :style="getStyle()"> <img src="/img/aprilfool/crieit.png" :class="getClass()"> </a> <div :style="getFukidashiStyle()" class="fukidashi"> <div class="d-flex justify-content-center align-items-center"> <div v-html="message"></div> </div> </div> </div> </template> <script lang="ts"> import { Vue, Component, Prop } from 'vue-property-decorator' import Ghost from './Ghost' const messages = [ 'Happy Halloween!', 'Trick or Treat??', : : ] @Component export default class AprilfoolGhost extends Vue { @Prop(Object) ghost: Ghost isMessageShown = false message = '' getStyle() { return { left: `${this.ghost.getX()}px`, top: `${this.ghost.getY()}px` } } getClass() { return { reverse: this.ghost.getAx() < 0 } } getFukidashiStyle() { return { left: this.ghost.getX() - 200 + 'px', top: this.ghost.getY() - 200 + 'px', opacity: this.isMessageShown ? 1 : 0 } } showMessage() { if (this.isMessageShown) { return } this.message = messages[Math.floor(Math.random() * messages.length)] this.isMessageShown = true setTimeout(() => { this.isMessageShown = false }, 5000) } } </script> <style scoped> div > * { display: block; position: fixed; opacity: 0.8; } .reverse { transform: scale(-1, 1); } .fukidashi { background: url(/img/aprilfool/fukidashi.png); width: 274px; height: 200px; opacity: 0; transition-duration: 0.2s; } .fukidashi > div { margin-left: 40px; margin-top: 20px; width: 200px; height: 100px; font-size: 1.8rem; } </style> </code></pre> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>さあ、みなさんも新元号のことは忘れてエイプリールフールやりましょう。今からでも間に合います!!!!!(?)</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14740 2019-01-22T09:15:07+09:00 2019-01-22T09:15:07+09:00 https://crieit.net/posts/TypeScript-Vue TypeScriptでVueのクラスを継承する <p>TypeScriptでVue.jsの単一コンポーネントを作る場合、Vueクラスを基底とした継承クラスを作る。ただ、似たようなコンポーネントは同じメソッドなどが並ぶため使いまわしたいと思う時がある。</p> <p>試してみると特に問題なく継承用の共通クラスを作って使うことができたようなのでメモ。(適当にやってみただけなので問題があれば教えてください)</p> <h2 id="ベースとなるクラスを作成する"><a href="#%E3%83%99%E3%83%BC%E3%82%B9%E3%81%A8%E3%81%AA%E3%82%8B%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">ベースとなるクラスを作成する</a></h2> <p>まずはベースとなる、共通メソッドを含めたクラスを定義する。テンプレートは書かないのでtsファイルに書く。</p> <pre><code class="typescript">import { Vue, Component } from 'vue-property-decorator' @Component export default class BaseEditor extends Vue { isiPhone() { if (window.navigator.userAgent.match(/iPhone/)) { return true } return false } } </code></pre> <h2 id="継承する"><a href="#%E7%B6%99%E6%89%BF%E3%81%99%E3%82%8B">継承する</a></h2> <p>あとは継承して使うだけ。</p> <pre><code class="html"><script lang="ts"> import { Vue, Component, Prop } from 'vue-property-decorator' import BaseEditor from '../../lib/BaseEditor' @Component export default class BoardEditor extends BaseEditor { </code></pre> <h2 id="Mixinは?"><a href="#Mixin%E3%81%AF%EF%BC%9F">Mixinは?</a></h2> <p>一応Vue.jsにはMixinという仕組みもあるのだが、TypeScriptだと結構面倒っぽい。色々方法はありそうだが、継承したコンポーネント側でもメソッドの定義を書かないといけなかったり、複数のMixinを使うために独自に関数を用意しなければならなかったりと、今のところ厳しそうな感じがある。今後ちゃんと使えるようになるのだろうか…?</p> <h2 id="テンプレートも共通化したい場合は"><a href="#%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%82%E5%85%B1%E9%80%9A%E5%8C%96%E3%81%97%E3%81%9F%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AF">テンプレートも共通化したい場合は</a></h2> <p>テンプレートも使いまわしたい場合は継承するのではなく、共通のコンポーネントを書いてそれを各コンポーネントに配置したほうが良さそう。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14713 2019-01-07T21:18:45+09:00 2019-01-22T09:18:29+09:00 https://crieit.net/posts/Vue-js-5c3343a5de603 Vue.jsの単一コンポーネントで雑にロジックだけ実行 <p>Vue.jsの単一コンポーネントはtemplateを書かなくても良い。なぜかというと、templateを使わずにrenderメソッドで直接描画することもできるため。そのため、その仕組を使うことで特に何も描画せず、ロジックだけ実行するということも可能。</p> <p>例えばhighlight.jsを使った例。下記のような<code>highlight.vue</code>というコンポーネントを作成する。</p> <pre><code class="html"><script lang="ts"> import { Vue, Component } from 'vue-property-decorator' import highlight from 'highlight.js' @Component export default class Highlight extends Vue { mounted() { highlight.initHighlightingOnLoad() } } </script> </code></pre> <p>これを、実際に描画するテンプレートの最後に配置するだけ。配置したページにだけ、配置したコンポーネントの処理が実行される。</p> <pre><code class="html"><highlight /> </code></pre> <p>ただまあ、特にhighlight.jsの場合など、ディレクティブを使ったほうが良さそうだったりするので、これは雑なやり方になる。仕事とかでやると「なんで何もしてないのに勝手にコードハイライトされてんだー!?」みたいになる場合もありそうな感じ。</p> <p>また、本番では何も言われないが開発中はコンソールに「テンプレートもrenderもないよ!」というエラーが出ている。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14707 2019-01-01T23:26:06+09:00 2019-01-02T04:11:28+09:00 https://crieit.net/posts/Vue-js Vue.jsで謹賀新年(失敗作) <p>なんか面白いものを作ろうと思ったけどだめだった…。失敗作だけどとりあえず投稿…。</p> <p data-height="383" data-theme-id="0" data-slug-hash="YdEMGm" data-default-tab="result" data-user="dala00" data-pen-title="VueYear" class="codepen">See the Pen <a target="_blank" rel="nofollow noopener" href="https://codepen.io/dala00/pen/YdEMGm/">VueYear</a> by dala00 (<a target="_blank" rel="nofollow noopener" href="https://codepen.io/dala00">@dala00</a>) on <a target="_blank" rel="nofollow noopener" href="https://codepen.io">CodePen</a>.</p> だら@Crieit開発者