- 投稿日:2019-02-26T23:41:11+09:00
Re:ゼロから始めるポートフォリオ開発【Vue.js】
概要
Web初心者がVue.jsでポートフォリオを作ってみた
2019/2/26 レスポンシブ対応してないので注意準備
作成物
まずコンポーネントを作ろう
とりあえず全ページ共通で使用するものをコンポーネントとして用意すればいいっしょ?
という軽い気持ちで作成。ヘッダー
Header.vue<template> <div> <header class="header"> <router-link to="/">Top</router-link> <router-link to="/about">About</router-link> <router-link to="/career">Career</router-link> <router-link to="/portfolio">Portfolio</router-link> <router-link to="/contact">Contact</router-link> </header> </div> </template>ページタイトル(画面毎に文言が変わるためpropsを設定)
Pagetitle.vue<template> <div class="pagetitle"> <p> {{pagetitle}} </p> </div> </template> <script> export default{ name:'pagetitle', props:{ pagetitle:{ type:String, default:'' } } } </script>ヘッダーは画面によって文言やデザインが変わる必要は無いのでApp.vueに記載
App.vue<template> <div id="app"> <Header></Header> <router-view/> </div> </template> <script> import Header from '@/components/Header' export default{ name:'header', components:{ Header } } </script>ページの作成
HTMLとCSSでページを作成する。
作成したページタイトルコンポーネントに引数を入れて呼び出す。トップ画面
Top.vue<template> <div class="top"> <Pagetitle pagetitle='Top'></Pagetitle> <p id="text">Doragon's Portfolio</p> </div> </template> <script> import Pagetitle from '@/components/Pagetitle.vue' export default{ name:'top', components:{ Pagetitle } } </script>Router
作成したページをRouterに設定。
router.jsimport Vue from 'vue' import Router from 'vue-router' import Top from './pages/Top.vue' import About from './pages/About.vue' import Career from './pages/Career.vue' import Contact from './pages/Contact.vue' import Portfolio from './pages/Portfolio.vue' Vue.use(Router) export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ { path: '/', name: 'top', component: Top }, { path: '/about', name: 'about', component: About }, { path: '/career', name: 'career', component: Career }, { path: '/portfolio', name: 'portfolio', component: Portfolio }, { path: '/contact', name: 'contact', component: Contact } ] })main.jsにrouterの宣言を忘れずに。
main.jsimport Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ router, render: h => h(App) }).$mount('#app')作成したポートフォリオを公開する
今回はgithub pagesにデプロイする。
ソースコードを公開する代わりに無料でwebページを公開してくれる優しいやつ。vue.config.jsをディレクトリ直下に作成
このままbuildすると/dist直下にビルドファイルが作成されてしまう。
github pagesでは/docs直下のものが公開対象となるため設定を変更する。vue.config.jsmodule.exports = { baseUrl: process.env.NODE_ENV === 'production' ? '/portfolio/' : '/', outputDir: 'docs', };コマンドラインでビルドを行う
$npm run buildgithub
/docs直下にビルドされたファイルが作成された事を確認した後、masterにpush。
githubのsettingを以下のようにする。
反映まで少し時間がかかるので注意
せっかちな人は以下の記事を参考にしてください。
https://qiita.com/shge/items/ac20f45c9e8e0b4f33cc一言
レスポンシブ対応がまだ出来ていないので後々やります・・・。
SASSも使いたかったけど、全く使っていない・・・。
HTMLとCSSとJSはまだまだ初心者レベルなので修行が必要だと痛感した。
Node.jsも勉強してwebに強くなりたい。参考文献
https://qiita.com/plus_kyoto/items/a01578b782f17f573510
https://qiita.com/shge/items/ac20f45c9e8e0b4f33cc
- 投稿日:2019-02-26T19:53:50+09:00
複数のフォームデータを、javascriptを使って1つのボタンで送信する方法
複数フォームの内容をワンクリックで送信する方法を書きます。
結論
submitボタンのフォームにhiddenでフォームの数だけinputを作り、
ボタンを押した時にjavascriptを呼び出して、
入力されているフォームの内容を、json形式にして、
hiddenのinputのvalueにセットする。そうすることで、複数フォームの内容をワンクリックで送信することができる。
例えば、
<select id="select"> <option value="0" >0</option> <option value="1" >1</option> <option value="2" >2</option> <option value="3" >3</option> <option value="4" >4</option> </select> <textarea id="textarea"></textarea> <form method="POST" action="/worktimes/edit/" id="post_form"> <input type="hidden" name="form1" value="" /> <input type="hidden" name="form2" value="" /> <button type="submit" >送信</button> </form>という2つのフォームがあったら、
呼び出したjsファイル内で下記の処理を書く// 送信ボタンが押された時に呼び出されるjsファイル内の関数 $('#post_form').submit(function () { var selectJsonData = get_select_data_by_json(); // selectの内容をjsonで取得 $("input[name='form1']").val(selectJsonData); // form1のvalueにselectの内容をセット var textareaJsonData = get_textarea_data_by_json(); // textareaの内容をjsonで取得 $("input[name='form2']").val(textareaJsonData); // form2のvalueにtextareaの内容をセット return true; }); function get_select_data_by_json() { var select = $( '#select' + i ).val(); // idを元にselectのvalueの内容を取得 return JSON.stringify(select); // JSON形式でリターン } function get_textarea_data_by_json() { var element = document.getElementById( 'textarea' ); // idを元にselectの内容を取得 var textarea = element.value; // selectのvlueを取得 return JSON.stringify(textarea); // JSON形式でリターン }保存ボタンクリック→js関数実行→フォームのvalueに値セット→アクション実行
という処理の流れなので、
コントローラ側に値が渡される。
- 投稿日:2019-02-26T17:38:24+09:00
プログラミング初心者の2月
この記事について
プログラミングとは何なのか1も知らなかった人間が新しい道を歩き始めた経緯を簡単に記録します。
(初の投稿で自分記録用として書いているので間違えているところや勘違いしているところも多いと思います。)プログラミングを始めたきっかけ
私は大学の時工学部所属で勉強はしていましたが、プログラミングや機械、電子、物理などの工学系ではなくどちらかというとデザインが重視される建築設計をやっていました。その後、建築の模型製作の仕事を過ぎ物の表現、具現に興味を持つことになりました。建築模型の場合、最終の提出用ぐらいが実際多くの人に見られみんながものを短く感じさせることが可能かと思います。世界を動かすとか世界的に使われたいとかの大きいことではなくても使ってくれる人々から「お、こういう機能も使えるんだ。」とか「前より使いやすくなったな。」を言われることやりたいと思うところプログラミングを勉強させていただくいい機会が来て本格的にこちらの道に関しての勉強を始めました。
勉強の進み
この文章を書くのはちょうど私が勉強始めて1か月が経っている時点です。
パソコンを使ってないわけではありませんが、目に見えるように具現化されているのを主に使ったため、そのプログラムがどういう原理で動いているのかについてはあまり考えてみたことがありませんでした。少しでも手をかけてみたことを整理してみると....
-Python 3.7
-HTML(css,bootstrap)
-MySQL
-Bottleどれも基本の中での基本的なところですが、それぞれを組み合わせての使い方について勉強させていただきました。
最初は本やネットの講座、先生の説明でしたが、やっぱり基本的なことを勉強した後時間をかけて自分でtitleをつけて書いて動かしての添削が効果的だったと思います。先生と一緒に書いたり本のどおりやっているときに「わー!できた。面白い!」と言っても翌日、勉強したことの応用の課題が与えられたら「ああ、私なんかアホやな、、、」と落ち込むことがかなりあったからです。もちろん自分ができそうな機能ばかりいれてしまってそれ以上の勉強進行にはよくないのでないかと悩みましたが、まず自分ができることもなかったらこれ以上のことを聞いても実際使えないと思ったので新しいものを勉強させてもらいながら時間を取って自分なりのものを作っています。少しずづ新しいものをいれながら(もちろん、見た目的にも機能的にも人に見せれるものではありませんが...)楽しんでいます。
3月やってみたいこと
上記のプログラム以外にも最近JavaScriptを始めようとしています。
書き方としてPythonと違いどちらにも慣れないのが一番心配ではありますが(フランス語とドイツ語を同時に勉強したときの感覚です。)、やっぱり単独で使うよりシナージ効果が出るのではないかと楽しみです。今まで勉強してきたものを全部混ぜて自分なりで書いてみているものが2つあります。こちらをもうちょっと実用的に使えることができるようになったら知り合いに使わせてもらいたいです。
可能な状況が来るかどうかは知りませんが、私がこちらの勉強を始める前に模型製作作業の中でレーザカッターをつかったり3D模型を作ったりAutoCADで設計図を書いたりすることが多かったです。これを使ってラズベリーパイのケースだったりこういう回路などのケースも作ってみたいと思います!
ETC.
始めったばかりものの記事を読んでいただきありがとうございます。
新しい道へと挑戦するところですが、楽しみながら難しいところも乗り越えていきたいと思います。
- 投稿日:2019-02-26T16:31:25+09:00
S3でWebSite Hostingした時にIPアドレスで制限をかける
S3にstaticなHTMLをおいて、それを特定のIPからのみ見えるようにする方法。
いつも迷ってしまうので備忘録で書いておく。
Access Control List
は変更する必要なし。(List objects
の everyone も-
のまま)
バケットの中のファイルもPrivateな設定のままでOK。{ "Version": "2012-10-17", "Id": "Policy1551163444222", "Statement": [ { "Sid": "Stmt1551163442413", "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": "arn:aws:s3:::[Bucket Name]/*", "Condition": { "NotIpAddress": { "aws:SourceIp": "[IP]" } } }, { "Sid": "Stmt1551164678580", "Effect": "Allow", "Principal": "*", "Action": "s3:*", "Resource": "arn:aws:s3:::[Bucket Name]/*", "Condition": { "IpAddress": { "aws:SourceIp": "[IP]" } } } ] }いつも思ったように動かなくてもがくのは
Resource
部分の最後に/*
をつけ忘れて、ファイル見れないじゃん…ってなる。
- 投稿日:2019-02-26T00:25:50+09:00
僕の考えた最強のService Workerキャッシュ戦略で爆速サービスを作った
はじめに
聖地の写真を共有するサービス「Holy Place Photo」にService Workerによるキャッシュを実装しました。
そのためネットワークがオフラインでも下記のようにサクサク動きます!!!!
Service Worker Demo pic.twitter.com/t04xSRl7B2
— tiwu (@tiwu_official) 2019年2月25日また、LighthouseのPWAのスコアは100を取れました。
この記事ではService Workerを利用したキャッシュの設計、Laravel + Vue.jsのWebアプリケーションでの実装を解説していきたいと思います。
注意書き
- 僕の考えた最強のService Workerのキャッシュの設計のため唯一の正解ではありません
- キャッシュの設計はサービスによって違うと思うのでこの記事は一つの参考にしてください
Service Worker
まずService Workerについて簡単に解説します。
※今回はスコープについてはあまり説明しません
Service Workerとは
Service WorkerはWebページのバックグラウンドで動くスクリプトです。
オフライン対応・バックグラウンド同期・プッシュ通知などの機能を持ちます。
Progressive Web Appとセットでよく聞くと思います。
ここで注意してほしいのはService Workerはリクエストに
addEventListener
を貼ることができるだけで、キャッシュする機能はService Workerには含まれていないという点です(キャッシュについてはCache Apiの項目で詳しく説明します)詳しくはこちら。
Service Worker の紹介 | Web Fundamentals | Google Developers
Service Workerのライフサイクル
Service Workerはウェブページとは異なるライフサイクルで動作します。
Service Workerの登録(Install)
Serivce Workerを登録するためには
navigator.serviceWorker.register
を利用します。app.jsif ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js').then(() => { console.log('登録成功'); },() => { console.log('登録失敗'); }); }); }sw.jsself.addEventListener('install', (event) => { console.log('インストール'); });登録が成功すると
install
が実行されます。この
install
は一度だけ発生します。
install
は静的なアセットをキャシュするのによく使われます。Service Workerによる制御(Activate)
install
が成功、またはすでにinstall
が成功しているページを表示するとactivate
が実行されます。※
install
が成功 = 初回のアクセス時
※install
が成功しているページを表示する = 2回目以降のアクセス時
activate
は古いキャッシュを削除したりするのによく使われます。
activate
が完了するとService Worker はそのスコープ内のすべてのページを制御します。た だ し !
Service Workerを登録時点では(初回のアクセス時)制御されず、次に読み込まれた際に制御されるようになります。
初回にService Workerの制御が始まらない理由は「Service Worker 登録」に記載されています。
新しい Service Worker スレッドを開始してリソースのダウンロードとキャッシュをバックグラウンドで実行すると、ユーザーがはじめてサイトにアクセスしたときに迅速なインタラクティブ エクスペリエンスを提供するという目標に逆行することになります。
初回のアクセスの際はブラウザはバックグラウンドでスクリプトを実行している場合ではないぞ、と書かれています(個人的解釈)
初回アクセス時にService Workerの制御を開始させる
実は
activate
時にclients.claim()
を実行すると、強制的にService Workerの制御を開始できます。sw.jsself.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim()); });Service Workerの更新
Service Workerが更新されていると判断される条件は、1ByteでもService Workerのスクリプトに変化があった際です。
更新を検知すると新しいService Workerは待機状態になります。
待機状態から新しいService Workerの制御を開始するためには、すべてのタブを閉じるか、現在開いているページから別のページに移動する必要があります(ページの更新・リロードするだけでは制御は開始されません)
そのため、新しいService Workerの制御を開始させるためには1step必要になります。
Service Workerの更新時に即座に制御を開始させる
install
時にskipWaiting()
を実行すると、新しいService Workerの制御を即座に開始させられます。sw.jsself.addEventListener('install', (event) => { event.waitUntil(self.skipWaiting()); });Service Workerによるオフライン対応
なぜ、Service Workerを導入するとオフライン対応が可能になるのでしょうか?
それはService Workerはネットワークへのアクセスを捕まえることができるためです。
Service Workerが制御している状態で、ネットワークへのアクセス(ページ遷移、リソースの取得など)が発生すると、
fetch
イベントが発火します。sw.jsself.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request).then((response) => { if (response) { return response; } return fetch(event.request); }) ); });Service Workerはこの
fetch
イベントに対してaddEventListener
を貼ることができます。上記例ではキャッシュにあったらキャッシュから返して、なかったら
fetch
を実行してネットワークへアクセスし取得します。このように
fetch
内でキャッシュから返すことができるため、あらかじめ全てのリソース(HTML,CSS,JS,IMGなど)をキャッシュしていれば、オフライン状態でもWebページが見れるようになります。Cache API
次にCache APIについて解説します。
2度目になりますが、Cache APIはService Workerの機能ではありません。
キャッシュは削除されない限り有効期限切れにはなりません。
キャッシュは
caches.open(CACHE_NAME)
を実行しキャッシュオブジェクトを取得して利用します。詳しくはこちら。
https://developer.mozilla.org/ja/docs/Web/API/Cache
キャッシュの登録
キャッシュへの登録は
add
,addAll
,put
があります。const CACHE_NAME = 'v1'; caches.open(CACHE_NAME).then((cache) => { return cache.add('/hoge.png'); }); caches.open(CACHE_NAME).then((cache) => { return cache.addAll([ '/fuga.png', '/piyo.png' ]); }); self.addEventListener('fetch', (event) => { event.respondWith( caches.open(CACHE_NAME).then((cache) => { return cache.match(event.request).then((response) => { return response || fetch(event.request).then((response) => { cache.put(event.request, response.clone()); return response; }); }); }) ); });
add
はURLを受け取り、ネットワークへアクセスして取得後、レスポンスオブジェクトをキャッシュに追加します。
addAll
は配列を受け取り、ネットワークへアクセスして取得後、レスポンスオブジェクトをキャッシュに追加します。
put
は取得済みのレスポンスオブジェクトをキャッシュに格納します。
add
はfetch
+put
のイメージです。上記例ではService Workerの
fetch
内で、キャッシュから取得できなかった場合、fetch
して、
レスポンスオブジェクトをput
を利用しキャッシュに追加しています。キャッシュの削除
削除は
delete
を利用します。caches.open(cacheName).then((cache) => { cache.delete('hoge.png'); });キャッシュの利用
追加のところで出ていますが
match
を利用します。self.addEventListener('fetch', (event) => { event.respondWith( caches.open(CACHE_NAME).then((cache) => { return cache.match(event.request).then((response) => { return response || fetch(event.request).then((response) => { cache.put(event.request, response.clone()); return response; }); }); }) ); });
match
やdelete
にはオプションが利用できます。
- ignoreSearch: trueを指定するとクエリストリングを無視するようになります
- ignoreMethod: trueを指定するとGET,HEAD以外のMETHODが利用できます
僕の考えた最強のService Workerキャッシュ戦略
ということで僕の考えた最強のService Workerキャッシュ戦略を解説していきたいと思います。
今回のキャッシュ戦略のポイントは下記になります。
- 次のページに遷移する際に必要なリソースは全てキャッシュから返す
- HTMLの有効期限は1日にする
- アセット(CSS,JS)は無期限
- ただしサードパーティのリソースは除く
- 初回のユーザーもService Workerの制御下に置く
1つずつ解説していきたいと思います。
次のページに遷移する際に必要なリソースは全てキャッシュから返す
次のページで必要なリソースは主に動的なHTMLと、静的なアセット(CSS,JS)とサードパーティのリソースに分類されます。
動的なHTMLは有効期限を1日にして、静的なアセット(CSS,JS)は無期限キャッシュするようにします。
より頻繁に更新されるようなサイトでは、HTMLの有効期限はより短くていいかもしれません。
動的なHTMLのキャッシュ
document.querySelectorAll('.js-sw-fetch').forEach((e) => { const url = e.getAttribute('href') caches.open(cacheName).then((cache) => { cache.match(url).then((response) => { if (!response) { cache.add(url); localStorage.setItem(url, Date.now()); } else { const time = localStorage.getItem(url); if (!time || Date.now() - time > 60 * 60 * 24 * 1000) { cache.delete(url).then(() => { cache.add(url); localStorage.setItem(url, Date.now()); }); } } }); }); });画面内にあるリンクをDOMから引っこ抜いて、Cache APIを使ってキャッシュに登録します。
キャッシュにない場合は
add
を利用して保存し、ローカルストレージに遷移するURLをkeyにしてタイムスタンプを保存します。キャッシュにある場合は、有効期限が切れていたら
delete
を利用してキャッシュを削除して、add
を利用しキャッシュに保存します。静的なアセット(CSS,JS)
今回FWにLaravelを採用しており、フロントのビルドにLaravel Mixを利用しています。
アセットのバージョン管理にはLaravel Mixの
mix.version()
を利用しています。そのためビルドされるファイルには
id=12345
といったクエリパラメーターが付与されます。これは
mix-manifest.json
で管理されており、bladeで利用するmix()
はこのmix-manifest.json
を見ています。{ "/js/app.js": "/js/app.js?id=27081b345ad90ec7716a", "/css/app.css": "/css/app.css?id=ebe23bcc66ba35ecfe6c" }画面表示時に必要なアセットをコントローラーからviewに埋め込み、JSで引っこ抜いてキャッシュに登録させます。
<?php namespace App\Http\Controllers; class Controller extends Controller { /** * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function index() { $manifests = json_decode(file_get_contents(public_path('/mix-manifest.json')), true); // swもビルドされているため、除外 $files = array_filter($manifests, function($key) { return $key !== '/sw.js'; }, ARRAY_FILTER_USE_KEY); $swCacheList = json_encode(array_values($files)); return view('index', compact('swCacheList'); } }<div id="sw" data-sw-cache-list="{{$swCacheList}}"></div>JSON.parse(document.getElementById('wrapper').dataset.swCacheList).forEach((url) => { caches.open(cacheName).then((cache) => { cache.match(url).then((response) => { if (!response) { cache.delete(url.split('?')[0], {ignoreSearch: true}).then(() => { cache.add(url); }); } }); }); });
cache.delete(url.split('?')[0], {ignoreSearch: true})
により、URLパラメーターが違う同じファイルのキャッシュを削除することで、無期限にキャッシュしているファイルをキャッシュから削除しています。管理しきれる間は上記方法でいいと思いますが、管理しきれなくなってきた場合は、HTMLをキャッシュする際に、HTMLをパースしてJSとCSSのURLを取得してキャッシュするという動的な方法が良いかもしれません。
初回のユーザーもService Workerの制御下に置く
上記の解説の通りService Workerは通常初回アクセスのユーザーは制御下におきません。
今回は初回のユーザーも制御下に置くために、
activate
時にclients.claim()
を実行します。また、Service Workerの更新時も初回から更新したService Workerの制御下に置くために、
install
時にskipWaiting()
を実行します。Service Workerの制御の開始を検知する
install
後の制御の開始や、更新後の制御の開始はcontrollerchange
イベントが発火します。そのため、Service Workerの制御中に何か処理をしたい場合は下記のように書くことで実現できます。
const controllerChange = new Promise((resolve) => { navigator.serviceWorker.addEventListener('controllerchange', resolve); }); navigator.serviceWorker.register('/sw.js').then(() => { return navigator.serviceWorker.ready; }).then(() => { if (navigator.serviceWorker.controller) { // 更新時 return navigator.serviceWorker.controller; } // 初回 return controllerChange; }).then(() => { // Service Workerの制御下 });おわりに
サクサクなのは本当に気持ちよく、やってやった感が高かったです。
作った後に、quicklinkという、
Intersection Observer
とrequestIdleCallback
とnavigator.connection.effectiveType
とprefetch
使って、リンクを高速に先読みするライブラリを知りました・・・。最強のService Workerキャッシュ戦略は最強(改)にできそうです・ω・