- 投稿日:2020-02-28T23:06:02+09:00
nuxtでDOMException: Failed to execute 'appendChild' on 'Node'エラー
- 投稿日:2020-02-28T23:05:41+09:00
Vue.js研修資料
私
証券営業 → テックエキスパート卒業生 → インターネット広告会社(エンジニア6ヶ月目)
Twitter → https://twitter.com/ar_tokki
言語
python Vuejs AWS
機械学習と統計の勉強中
TECH::EXPERTを卒業後、転職して半年間の振り返り
https://qiita.com/tokki7127/items/2eb5bbd3b1bb54e33824Vue.jsチートシート
https://qiita.com/tokki7127/items/4e0429e0f0ab34930302この資料について
・2020年の新卒に向けた研修資料である
・目的はVue.jsへの導入
・プロゲートで【HTML・CSS・JavaScript】を一通り終えた後を想定して作った
・この講義の後は下のサイトのイントロコースをやってもらう
https://www.vuemastery.com/
イントロコース完了後にVueUIの Vuetifyについて講義を行う(キータで投稿予定)
https://vuetifyjs.com/ja/Vue.jsとは
JavaScriptの3大フレームワークの1つ
左から順番にAngular・React・Vue.js
利用している企業
Angular
・YouTube Google Udemy Nike PayPal
React
・Facebook Instagram Netflix Dropbox
Vue.js
・Xiaomi Alibaba DeNA LINE情報の比較
Angular React Vue 開発元 Google+コミュニティ Facebook+コミュニティ コミュニティ主体 初期リリース日 2010/10(Angular.js) 2013/07 2013/12 機能 フルスタック ユーザーインターフェイス ユーザーインターフェイス ルーティング 同梱 React Router Vue Router 適切な開発規模 大 中〜やや大 小〜中 エコシステム 多い 豊富 小〜中 学習コスト 高い 中 やや低〜中 構文上の特徴 TypeScript JSX 単一コンポーネントファイルシステム Googleトレンドのトピックの推移
Vue.jsのメリット
学習コストが低い
公式ドキュメントがしっかりしている
日本語ドキュメントが多い軽量なフレームワークである
基本機能に絞ることで簡単に作れる
DOM操作を自動的に行ってくれる
小中規模のWebサイトを作るのに向いている
SPA(Single Page Application)開発につよい
簡単かつシンプルで日本語ドキュメントもある!!→社内システムを作るのにちょうどいい
Vueの勉強の仕方
公式サイト:https://jp.vuejs.org/index.html
勉強を始める前になぜ Vue.jsの動画を見ると感覚的に理解できる有名な本
amazon:https://www.amazon.co.jp/s?k=vue&__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&ref=nb_sb_noss_1オススメのサイト:https://www.vuemastery.com/
英語ではあるがイントロコースを終了する頃にはある程度理解できているはずである
https://www.vuemastery.com/courses/intro-to-vue-js/vue-instanceVueを支えるシステム
コンポーネントシステム:部分ごとに切り分けてコードを作成することで使い回しが可能(ヘッダーとかサイドバーなど)
リアクティブシステム:状態の変化を自動的に画面に反映する仕組み
レンタリングシステム:仮想DOMによる高速レンタリング
この辺はおいおい理解すれば良い基本のコード(オススメ作業場所:https://jsfiddle.net/)
hello.html<!DOCTYPE html> <title>始めてのVue.js</title> <script src="https://unpkg.com/vue@2.5.17"></script> <div id="app" ></div> <script> new Vue({ template: '<p>{{msg}}</p>', data: {msg: 'hello world'} }).$mount('#app') </script> <style> p{background-color: red;} </style>vue.ファイル
app.vue<template> <p class="message">メッセージ:{{ msg }}</p> </template> <script> export default{ data(){ return{msg: 'こんにちわ'} } } </script> <style> .message{background-color: red;} </style>vue端子を出力したい時は@vue/cli @vue/cli-service-globalが必要になる
$を除いたコマンドを順番にうてばおそらく使えるはず
$ npm install -g @vue/cli @vue/cli-service-global
$ vue–version
$ vueserve –o用語について
フレームワークという単語自体は英語で「枠組み・構造」を意味しています。
つまり、JavaScriptでWebサービスやアプリを開発するうえで、
サーバーとの連携、ルーティング、データの送受信(CRUD)など、全体的な処理の流れを構造化するわけです。
そのため、フレームワークを使えば足りない部分だけを集中して開発すれば良いので、
とても効率が良いうえ複数人が一緒に作業しやすくなるメリットもあります。SPAとは単一のページでコンテンツの切り替えを行うWebアプリケーションのことです。
Vue.jsはSPA(Single Page Application)開発に使われていることが非常に多いです。
SPAで開発すると、ページ遷移をする時はJavaScriptを使用してHTMLの一部を差し替えて切り替えます。
従来のWebページではページ遷移の時は全体を切り替える必要があったため、
SPAによって新しいUIの実現やスピードの向上が可能となりました。
パフォーマンスの良さから今は多くの企業でWebサイト・アプリケーションにSPAを採用しており、
SPA開発に使われるVue.jsの人気も高まっています。
DeNAやGMOペパボなど、大手IT企業の開発現場でもVue.jsは採用されています。DOMとは「Document Object Model」の略
直訳すると、「ドキュメントを物として扱うモデル」になる。
プログラムからHTMLやXMLを自由に操作するための仕組みだ。
例えばブラウザに表示される文字の色を変更したり、大きくしたりと、Webページの見た目をプログラムで処理をしたい場合があるだろう、
しかし何もしていない状態のHTMLファイルではJavaScriptから手を出す事が出来ない。
そこでファイルの特定の部分に目印を付けて「この部分」に「こういう事をしたい」という処理を可能にするための
取り決めがDOMである公開した目的
エンジニア経験6ヶ月で研修を任せてもらえたので、資料を作成した。
どうせ作った資料だからいろんな人に見てもらいたい
後、スーパーエンジニアにアドバイスもらえたらなんて、、、
- 投稿日:2020-02-28T20:10:11+09:00
【Nuxt.js】Vuex基礎編②stateを複数使ってみよう
前置き
今回は前回の基礎編に続き、
stateが複数ある場合の書き方です✍️
基礎編でVuexの基本的な解説はしています!
また基礎編のコードに追記するので
そちらを確認しながらやってみてください?Vuex基礎編はこちら
https://note.com/aliz/n/n497914c981c8#i8l88やりたいこと
基礎編のカウンターを2つに増やします!
これだけ!!!NGパターン
まずはNGパターンから。
まずはstateにsubCounterを追加。
mutationsなどにも同様に
subCounterについて追記します✍️が!
これだと後述したsubCounterに
全てがまとまってしまいます。。。counter.jsexport const state = () => ({ counter: 0, subCounter: 0, }) export const mutations = { setIncrease(state) { state.counter++ }, setDecrease(state) { state.counter-- }, setIncrease(state) { state.subCounter++ }, setDecrease(state) { state.subCounter-- }, } export const getters = { counter: state => { return state.counter }, subCounter: state => { return state.subCounter } }Counter.vue<template> <div> <div class="container"> <button @click="$store.commit('counter/setIncrease')">Increase</button> <button @click="$store.commit('counter/setDecrease')">Decrease</button> {{ counter }} </div> <div class="container"> <button @click="$store.commit('counter/setIncrease')">Increase</button> <button @click="$store.commit('counter/setDecrease')">Decrease</button> {{ subCounter }} </div> </div> </template> <script> export default { computed: { counter () { return this.$store.getters['counter/counter'] }, subCounter () { return this.$store.getters['counter/subCounter'] }, }, } </script>index.vue<template> <div class="container"> <Counter /> </div> </template> <script> import Counter from '~/components/Counter.vue'; export default { components: { Counter, }, } </script>OKパターン
ということで
mutationsを一つに統一しましょう⭕️
第二引数indexを使って
・0の場合はcounterを
・1の場合はsubCounterを
変動させます。こんかいはその2パターンで良いので
if文のelse ifまであればOKですね??if文if (条件式1) { 条件式1がtrueの時の処理 } else if (条件式2) { 条件式1がtrueの時の処理 } else { 上記以外の全ての時の処理 }追加の引数の書き方は公式参照です✍️
https://vuex.vuejs.org/ja/guide/mutations.html#追加の引数を渡してコミットする?変更箇所のみ記載します?
counter.jsexport const mutations = { setIncrease(state, index) { if (index === 0) { state.counter++ } else if (index === 1) { state.subCounter++ } }, setDecrease(state, index) { if (index === 0) { state.counter-- } else if (index === 1) { state.subCounter-- } }, }Counter.vue
ここで第二引数に0, 1を入れて
counterとsubCounterの区別をしています?Counter.vue<template> <div> <div class="container"> <button @click="$store.commit('counter/setIncrease', 0)">Increase</button> <button @click="$store.commit('counter/setDecrease', 0)">Decrease</button> {{ counter }} </div> <div class="container"> <button @click="$store.commit('counter/setIncrease', 1)">Increase</button> <button @click="$store.commit('counter/setDecrease', 1)">Decrease</button> {{ subCounter }} </div> </div> </template>これで完成です??
完成コード
counter.jsexport const state = () => ({ counter: 0, subCounter: 0, }) export const mutations = { setIncrease(state, index) { if (index === 0) { state.counter++ } else if (index === 1) { state.subCounter++ } }, setDecrease(state, index) { if (index === 0) { state.counter-- } else if (index === 1) { state.subCounter-- } }, } export const getters = { counter(state) { return state.counter }, subCounter(state) { return state.subCounter } }Counter.vue<template> <div> <div class="container"> <button @click="$store.commit('counter/setIncrease', 0)">Increase</button> <button @click="$store.commit('counter/setDecrease', 0)">Decrease</button> {{ counter }} </div> <div class="container"> <button @click="$store.commit('counter/setIncrease', 1)">Increase</button> <button @click="$store.commit('counter/setDecrease', 1)">Decrease</button> {{ subCounter }} </div> </div> </template> <script> export default { computed: { counter () { return this.$store.getters['counter/counter'] }, subCounter () { return this.$store.getters['counter/subCounter'] }, }, } </script>index.vue<template> <div class="container"> <Counter /> </div> </template> <script> import Counter from '~/components/Counter.vue'; export default { components: { Counter, }, } </script>記事が公開したときにわかる様に、
note・Twitterフォローをお願いします?
https://twitter.com/aLizlab
- 投稿日:2020-02-28T20:09:09+09:00
【Nuxt.js】Vuex基礎編②stateを複数使ってみよう
前置き
今回は前回の基礎編に続き、
stateが複数ある場合の書き方です✍️
基礎編でVuexの基本的な解説はしています!
また基礎編のコードに追記するので
そちらを確認しながらやってみてください?Vuex基礎編はこちら
https://note.com/aliz/n/n497914c981c8#i8l88やりたいこと
基礎編のカウンターを2つに増やします!
これだけ!!!NGパターン
まずはNGパターンから。
まずはstateにsubCounterを追加。
mutationsなどにも同様に
subCounterについて追記します✍️が!
これだと後述したsubCounterに
全てがまとまってしまいます。。。counter.jsexport const state = () => ({ counter: 0, subCounter: 0, }) export const mutations = { setIncrease(state) { state.counter++ }, setDecrease(state) { state.counter-- }, setIncrease(state) { state.subCounter++ }, setDecrease(state) { state.subCounter-- }, } export const getters = { counter: state => { return state.counter }, subCounter: state => { return state.subCounter } }Counter.vue<template> <div> <div class="container"> <button @click="$store.commit('counter/setIncrease')">Increase</button> <button @click="$store.commit('counter/setDecrease')">Decrease</button> {{ counter }} </div> <div class="container"> <button @click="$store.commit('counter/setIncrease')">Increase</button> <button @click="$store.commit('counter/setDecrease')">Decrease</button> {{ subCounter }} </div> </div> </template> <script> export default { computed: { counter () { return this.$store.getters['counter/counter'] }, subCounter () { return this.$store.getters['counter/subCounter'] }, }, } </script>index.vue<template> <div class="container"> <Counter /> </div> </template> <script> import Counter from '~/components/Counter.vue'; export default { components: { Counter, }, } </script>OKパターン
ということで
mutationsを一つに統一しましょう⭕️
第二引数indexを使って
・0の場合はcounterを
・1の場合はsubCounterを
変動させます。こんかいはその2パターンで良いので
if文のelse ifまであればOKですね??if文if (条件式1) { 条件式1がtrueの時の処理 } else if (条件式2) { 条件式1がtrueの時の処理 } else { 上記以外の全ての時の処理 }追加の引数の書き方は公式参照です✍️
https://vuex.vuejs.org/ja/guide/mutations.html#追加の引数を渡してコミットする?変更箇所のみ記載します?
counter.jsexport const mutations = { setIncrease(state, index) { if (index === 0) { state.counter++ } else if (index === 1) { state.subCounter++ } }, setDecrease(state, index) { if (index === 0) { state.counter-- } else if (index === 1) { state.subCounter-- } }, }Counter.vue
ここで第二引数に0, 1を入れて
counterとsubCounterの区別をしています?Counter.vue<template> <div> <div class="container"> <button @click="$store.commit('counter/setIncrease', 0)">Increase</button> <button @click="$store.commit('counter/setDecrease', 0)">Decrease</button> {{ counter }} </div> <div class="container"> <button @click="$store.commit('counter/setIncrease', 1)">Increase</button> <button @click="$store.commit('counter/setDecrease', 1)">Decrease</button> {{ subCounter }} </div> </div> </template>これで完成です??
完成コード
counter.jsexport const state = () => ({ counter: 0, subCounter: 0, }) export const mutations = { setIncrease(state, index) { if (index === 0) { state.counter++ } else if (index === 1) { state.subCounter++ } }, setDecrease(state, index) { if (index === 0) { state.counter-- } else if (index === 1) { state.subCounter-- } }, } export const getters = { counter(state) { return state.counter }, subCounter(state) { return state.subCounter } }Counter.vue<template> <div> <div class="container"> <button @click="$store.commit('counter/setIncrease', 0)">Increase</button> <button @click="$store.commit('counter/setDecrease', 0)">Decrease</button> {{ counter }} </div> <div class="container"> <button @click="$store.commit('counter/setIncrease', 1)">Increase</button> <button @click="$store.commit('counter/setDecrease', 1)">Decrease</button> {{ subCounter }} </div> </div> </template> <script> export default { computed: { counter () { return this.$store.getters['counter/counter'] }, subCounter () { return this.$store.getters['counter/subCounter'] }, }, } </script>index.vue<template> <div class="container"> <Counter /> </div> </template> <script> import Counter from '~/components/Counter.vue'; export default { components: { Counter, }, } </script>記事が公開したときにわかる様に、
note・Twitterフォローをお願いします?
https://twitter.com/aLizlab
- 投稿日:2020-02-28T18:44:30+09:00
Vue.jsを使用して書いたChrome extensionにおけるi18n対応
Chrome extensionの開発でVue.jsを使用したときの国際化対応方法についてのメモ
基本的なi18nの対応方法
vue-plugin-webextension-i18nをインストールする
% npm install --save-dev vue-plugin-webextension-i18nVueのインスタンスにプラグインを追加する
import i18n from 'vue-plugin-webextension-i18n' Vue.use(i18n);manifest.jsonに以下を記述する
... default_locale: "en", ...リソースファイルを配置する
manifest.jsonと同じ階層に_localesというフォルダを作り、さらにその下にlocaleごとのフォルダを配置する。
% tree -L 1 . ├── _locales ├── assets ├── common ├── config ├── ... └── manifest.json% tree ./_locales/ ./_locales/ ├── en │ └── messages.json └── ja └── messages.jsonリソースファイル(messages.json)に文言を追加する
- en/messages.json
{ "save": { "message": "Save" } }
- ja/messages.json
{ "save": { "message": "保存" } }Vueコンポーネント側からリソースファイルを使用する
以下のように記述することで、messages.jsonに足した文言にアクセスできる。
<button>{{ $i18n('save') }}</button>Tips
Formatsの機能を使う
- 言語によって語順が異なり、文言の中に変数を埋め込みたい場合は、以下のように記述できる。
messages.json
... }, "showItems": { "message": "Show $NUMBER$ items", "placeholders": { "number": { "content": "$1", "example": 2 } } }, ...Vue component
{{ $i18n('showItems', [itemLength]) }}※第2引数は配列になっており、[A, B, C]と記述したらmessages.json側では \$1, \$2, $3に渡る
参考
- 投稿日:2020-02-28T17:08:42+09:00
Lambda+API Gateway+CloudFrontとVueでOGP画像の自動生成をする
Lambda+API Gateway+CloudFrontとVueを使ってフロントエンドのみでOGP画像の自動生成をしてみたので備忘録。
構成
まずVueでSVGを返すページを用意しておく。
Lambda側はchrome-aws-lambda
でスクリーンショットを撮って、base64で返すようにする。よくあるLambda@Edgeを使ったダイナミックレンダリングを行いつつ、Edgeで返すMetaタグの
og:image
やtwitter:image
のURLへのアクセスがあったら、用意しておいたSVGページをLambdaでスクリーンショット撮ってAPI Gateway経由でpngにして返す、というちょっと面倒くさい構成。バックエンド側でLambdaを起動させてスクリーンショット撮ってS3に保存とかでもよかったのだけど、今回はあくまでもアクセスがあったらOGP画像を返すようにしたかったので、こんな感じの構成にした。
VueでSVG生成
VueでSVGを生成するのはこちらの記事を参考にさせていただいた。
Vue.jsとFirebaseでOGP画像生成系のサービスを爆速で作ろう<template> <div class="hello"> <svg ref="svgCard"> <text transform="translate(103.29 347.281)" fill="#e51f4e" font-size="29" font-family="HiraginoSans-W5, Hiragino Sans" letter-spacing="-0.002em"> <tspan x="0" y="26">{{ data.content }}</tspan> </text> </svg> </div> </template> <script> import { mapActions } from 'vuex' export default { name: 'Svg', data () { return { data: {} } }, beforeMount () { this.fetchData(response => { this.data = response }) }, methods: { ...mapActions([ 'fetchData', ]) }, } </script>svgタグの中にvueのデータを埋め込めるので、APIから持ってきたデータを表示できるようにする。
注意が必要なのが、svgタグではテキストを自動で折り返してくれないので、途中で切って配列にしてv-for
で回すとかしないといけない。あとはこのページをrouterに登録する。
import Svg from '@/views/Svg.vue' Vue.use(VueRouter) const routes = [ ... { path: '/path/to/svg', name: 'Svg', component: Svg } ]Lambda+API Gateway
次にスクリーンショットを撮るLambdaを作る。ほんとはserverless frameworkで作りたかったのだけど、serverlessで作ると何故かchromeが動いてくれなかったのと、API Gateway側の設定がイマイチ把握しきれなかったので、今回はコンソールでポチポチした。
Lambda
const chromeLambda = require("chrome-aws-lambda"); const defaultViewport = { width: 1200, height: 630 }; exports.handler = async event => { const browser = await chromeLambda.puppeteer.launch({ args: chromeLambda.args, executablePath: await chromeLambda.executablePath, defaultViewport }); const sleep = (time) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(); }, time); }); } const page = await browser.newPage(); const url = "https://" + process.env.DOMAIN + "/path/to/svg"; await page.goto(url, { waitUntil: "networkidle0" }); sleep(1000) const buffer = await page.screenshot({ encoding: "base64", type: "png" }); return { "statusCode": 200, "headers": {"Content-Type": "image/png"}, "isBase64Encoded": true, "body": buffer }; };Lambda側のソースコードはこんな感じ。
chrome-aws-lambda
はLambda Layerを使わせてもらった。
https://github.com/shelfio/chrome-aws-lambda-layer#available-regionsAPI Gateway
API Gateway側は適当なリソースを作って、GETメソッドを用意する。
/path/to/svg - GET - 統合リクエスト
で上で作ったLambdaに繋いで、Lambda プロキシ統合の使用
にチェックを入れる- HTTP リクエストヘッダーに
Accept
を追加する- メソッドレスポンスのコンテンツタイプに
image/png
を追加する- APIの設定で、バイナリメディアタイプに
image/png
を追加するAPIキーや使用量プランは必要に応じて設定して、ステージにデプロイする。ここでは仮に
prod
ステージにデプロイしたと仮定して進める。これでcurlコマンドでAPI Gatewayを叩くと画像が返ってくるようになる。
curl -H "Accept: image/png" --output test.png https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/path/to/svgCloudFront
この状態でブラウザでアクセスすると、Acceptヘッダーがリクエストに含まれないのでjsonが返ってきてしまう。CloudFrontを経由させることで、Acceptヘッダーを付けつつ、一度アクセスのあった画像はキャッシュしてもらえる。
まずDistributionsの作成してOriginを追加する。
Origin Domain Name: xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com Origin Path: /prod Origin Custom Headers: Accept: image/pngBehavior側はOrigin or Origin Groupで先ほど追加したOriginを選択する。
CloudFrontのデプロイが完了したら、CloudFrontのURL経由でブラウザで画像が表示されるようになる。
Lambda@EdgeでOGPタグ
Lambda@Edgeを使ったOGPの生成はこのあたりを参考に。
Lambda@EdgeでSPAのOGPを動的に設定する
SSRをやめる。OGP対応はLambda@Edgeでダイナミックレンダリングする。今回は1個目の記事のような感じで、botからのアクセスだった場合はバックエンドのAPIにアクセスしてタイトルとかを整えつつ、上で用意した画像のURLを含んだOGPタグを生成して返すようにした。
botの種類はこのあたり。
const crawlers = [ "Googlebot", "facebookexternalhit", "Twitterbot", "bingbot", "msnbot", "Slackbot", "Discordbot" ];まとめ
とまあ、こんな具合でOGP用の画像を自動生成できた。あとは必要に応じてコールドスタート対策もしておきたいところ。
Serverless Frameworkで行うLambdaのコールドスタート対策いつもながら先人の知恵や知識に感謝。
参考URL
https://scottbartell.com/2019/03/25/automating-og-images-with-aws-lambda/
https://codissimo.sinumo.tech/2019/12/27/serverless-puppeteer-with-aws-lambda-layers-and-node-js/
https://qiita.com/junara/items/5563ad7ee133ce736ed0
https://qiita.com/kodai-saito/items/9051d2b30a29c7d64f7d
- 投稿日:2020-02-28T16:45:55+09:00
Vue.js入門2(私的メモ)
Vue.jsの環境構築
「開発マシンを汚さないから」って理由だけで、ロクに使ったこともないDockerでVue.jsの環境構築をしてみた。
結果、めちゃくちゃ時間かかった。参考サイト通りにやったのに。
CDNで手っ取り早く勉強し始めた方が良かったと後悔。
とはいえ、手を出してしまったからにはDockerの勉強もしたいなー。
超初心者にオススメの教材(Webサイトでも書籍でも動画でも)あったら教えていただけると幸いです。準備
mkdir フォルダ・ファイルを置くディレクトリ名 cd 上のディレクトリ名Dockerfileの作成
ベースイメージを指定して、npmコマンドを使えるようにする。
FROM node:10.13-alpine WORKDIR /app RUN apk update && \ npm install && \ npm install -g npm && \ npm install -g @vue/cli CMD ["/bin/ash"]docker-compose.ymlの作成
ラクチンに複数のコンテナを起動できるよう設定しておく。
version: '3' services: web: container_name: test_vue_app build: . ports: - 8080:8080 volumes: - .:/app tty: true stdin_open: trueサービスをビルド(構築)
上記二つのファイルが存在するディレクトリで。
docker-compose buildコンテナ起動
-dはデタッチモードといって、バックグラウンドでコンテナを実行し続けてくれる。
ターミナルで作業を続けたい場合はつける。docker-compose up -dコンテナに入ってプロジェクトを作成する
node, vueが入ったサービス(web)でシェルを起動させる。
docker-compose exec web /bin/ash /app # /app # ls Dockerfile docker-compose.ymlプロジェクトを作成する。
/app # vue create プロジェクト名プロジェクト内に移動して、サーバを起動
プロジェクト内に移動してから。
/app # cd プロジェクト名 /app/プロジェクト名 # npm run serve確認
指定したポート番号でページにアクセス。
例)http://localhost:8080/ホストのポート番号 : コンテナのポート番号
なので、例えばホスト側のポート番号を7070に指定すれば、http://localhost:7070/ でアクセスできる。version: '3' services: web: container_name: test_vue_app build: . ports: - 7070:8080 volumes: - .:/app tty: true stdin_open: trueポート番号を変更する
試してみたが、アクセスできずに断念。
何故や。参考文献
- 投稿日:2020-02-28T15:54:47+09:00
SublimeTextをVue.jsに対応させる手順
はじめに
SublimeTextでVue.jsを扱う際に拡張子(.vue)ファイルをシンタックスハイライトしてくれるプラグイン導入手順です
環境
- OS:macOS Catalina v10.15.1
- SublimeText:V3.2.2
前提条件
パッケージコントローラをインストールしていること
※インストールがまだの場合は以下のページを参照
SublimeTextの日本語化手順Gitリポジトリの追加
コマンドパレットを開く
<ショートカットキー>
Win/Linux : ctrl+shift+p
Mac : cmd+shift+p
<メニューから選択>
メニューバー > Tools > Command Paletteを選択「Package Control: Add Repository」を入力、選択してEnter
画面下部のURLに
「https://github.com/vuejs/vue-syntax-highlight.git」
を入力してEnter
プラグインのインストール
コマンドパレットを開いて「Package Control: Install Package」を入力、選択してEnter
「Vue Syntax Highlight」を入力、選択してEnter
※候補が2つ出てきましたがキャメルケースの名前の方を選択しました
以上でプラグインの導入は完了です
あとはSublimeTextを再起動して拡張子「.vue」ファイルを開くとしっかりとシンタックスハイライトしてくれています
- 投稿日:2020-02-28T15:20:33+09:00
Vue.jsで外部HTMLファイルをinclude(っぽく)する
概要
Componentに分けたいけど分けられない。諸事情でHTMLファイルをincludeしたい。
HTMLファイルを直接弄りたいとか言われましても方法
- 外部HTMLファイルを非同期で読み込んで展開する
具体例
<template> <main> <section v-html="external"></section> </main> </template> <script lang="ts"> import { Vue, Component } from "vue-property-decorator"; @Component export default class App extends Vue { external: string = ""; public mounted() { this.loadExternalHtml(); } public loadExternalHtml() { fetch("external.html").then(res => { res.text().then(html => { this.external = html; }); }); } }includeっぽくはなる。
これでガリガリHTMLを弄ってもおk
- 投稿日:2020-02-28T14:56:35+09:00
Vue.jsを使って簡単な計算ツールを作った
はじめに
グランブルファンタジーというゲーム内のイベントである古戦場にて、目的の貢献度を貯めるためにはどれくらいのトリガーが必要か?を計算するツールを作りました。
作り自体は簡単なため、初めてVue.jsを勉強&アウトプットするにはいい題材でした。実際に動かしたもの
http://ayama.main.jp/battlefieldRun.htmlコード
https://github.com/guranytou/battlefieldRuns一番困った部分
battlefieldRun.html<fieldset class="inputFieldset"> <div id="app"> <div> <span>目標累計貢献度</span> <input type="text" v-model="input" id="contributionInput"></input> </div> <div id="trigerDigest"> <div><span>肉の消化方法</span></div> <div> <input type="radio" class="hellRadio" name="hellRadio" id="hell95" value="Hell95" v-model="trigerDigest"/> <label class="hellRadioLabel" for="hell95">95HELL</label> <input type="radio" class="hellRadio" name="hellRadio" id="hell100" value="Hell100" v-model="trigerDigest"/> <label class="hellRadioLabel" for="hell100">100HELL</label> <input type="radio" class="hellRadio" name="hellRadio" id="hell150" value="Hell150" v-model="trigerDigest"/> <label class="hellRadioLabel" for="hell150">150HELL</label> <input type="radio" class="hellRadio" name="hellRadio" id="both" value="both" v-model="trigerDigest"/> <label class="hellRadioLabel" for="both">95&100HELL</label> </div> <div> <span>必要なトリガー数:</span><span>{{ output }}</span> </div> </div> </fieldset>battlefieldRun.js(function () { var triger = new Vue({ el: '#app', data: { input: 0, trigerDigest: 'false' }, computed: { output: function(){ const Hell95NeedTriger = 10; const Hell100NeedTriger = 20; const Hell150NeedTriger = 20; const ExTriger = 4; const Hell95Contribution = 51500 * (Hell95NeedTriger / ExTriger) + 910000; const Hell100Contribution = 51500 * (Hell100NeedTriger / ExTriger) + 2680000; const Hell150Contribution = 51500 * (Hell150NeedTriger / ExTriger) + 3600000; let ans = 0; const ThisInput = String(this.input); const InputContribution = removeComma(ThisInput); function removeComma(value) { var num = value.replace(/,/g, ""); return parseInt(num); } if(this.trigerDigest === 'Hell95'){ // 計算式 ans = Math.floor(InputContribution / Hell95Contribution * Hell95NeedTriger); return ans.toLocaleString() }else if(this.trigerDigest === 'Hell100'){ // 計算式 ans = Math.floor(InputContribution / Hell100Contribution * Hell100NeedTriger); return ans.toLocaleString() }else if(this.trigerDigest === 'Hell150'){ ans = Math.floor(InputContribution / Hell150Contribution * Hell150NeedTriger); return ans.toLocaleString() }else if(this.trigerDigest === 'both'){ // 計算式 ans = Math.floor((InputContribution / 2 / Hell95Contribution * Hell95NeedTriger) + (InputContribution / 2 / Hell100Contribution * Hell100NeedTriger)); return ans.toLocaleString() }else{ return 0; } } } }) }());振り返り
・カンマをinput/outputにいれるのに地味に困った
→ここは本題ではなかったので、今回はinputに外部のJQueryを利用、outputにはtoLocaleString()を利用した
・computedとmethodsのどちらを利用すればいいのか分からなくて困った
→入れたらすぐに発火するものが作りたかったのでcomputedを採用。またcomputedとmethodsの違いの勉強が出来てよかった
・必要な部分だけさらっと勉強して書き始めたので、まだまだVue.jsへの理解が足りないように感じた
→座学もちゃんと増やす。簡単な題材のアウトプット(FizzBuzz、ToDoリスト作成etc)をする。・疑問点:Computed内でif文の分岐をしている例文が見当たらなかったが、他の書き方があったりするのだろうか
- 投稿日:2020-02-28T14:30:34+09:00
【JavaScript】ElementUIのFormComponentを使用したフォームでリクエスト送信時、二重送信されないように画面をロックさせる
フォーム送信時はユーザーに操作させたくない
フォームの送信ボタンを押したときは二重にリクエストが飛ばないようにボタンを押せなくしたり、そもそも画面の項目に触れてほしくないので画面をロック状態にするようにしたい
以下は過去行ってた二重送信対応策(jQuery)
【JavaScript】フォーム等の送信ボタンに対する二重送信防止対応策 - QiitaElementUIのコンポーネントたちを使用して実現させる
今回はElementUI(Vue.jsのコンポーネントライブラリ)のFormコンポーネントとloadingコンポーネントを組み合わせて、フォームのリクエスト送信時に画面をロックさせる
サンプル
See the Pen loading form deactivate By ElementUI by sola-msr (@sola-msr) on CodePen.
対応部分
サンプルでは
v-loading.fullscreen.lock
をボタン部分に追加し、送信時にローディング描写をON(true
)、送信完了(正常系レスポンス受け取り時)にOFF(false
)をセットしています。
上記の対応だけではまだ送信ボタンを押下することが出来る状態ですので、追加の対応としてフォーム全体を非活性化(disabled
)させるように、el-form
部分にdisabled
を追加し、これも送信時と送信完了時にON/OFFさせるようにしています。<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="120px" class="demo-ruleForm" :disabled="this.disabledForm"> <el-form-item label="Name" prop="name"> <el-input type="text" v-model="ruleForm.name" autocomplete="off"></el-input> </el-form-item> <el-form-item label="Password" prop="pass"> <el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input> </el-form-item> <el-form-item label="Confirm" prop="checkPass"> <el-input type="password" v-model="ruleForm.checkPass" autocomplete="off"></el-input> </el-form-item> <el-form-item label="Activity zone" prop="region"> <el-select v-model="ruleForm.region" placeholder="Activity zone"> <el-option label="Zone one" value="shanghai"></el-option> <el-option label="Zone two" value="beijing"></el-option> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')" v-loading.fullscreen.lock="fullscreenLoading">Submit</el-button> <el-button @click="resetForm('ruleForm')">Reset</el-button> </el-form-item> </el-form>
methods
で変数fullscreenLoading
とdisabledForm
にtrue
/false
をセットする関数(openFullScreen()
とcloseFullScreen()
)を定義し、submitForm
関数の中で各々のタイミングで呼び出すようにしてます。// 略 methods: { openFullScreen() { this.fullscreenLoading = true; this.disabledForm = true; }, closeFullScreen() { this.fullscreenLoading = false; this.disabledForm = false; }, submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { // ローディング描写とフォーム非活性化 this.openFullScreen(); // イメージしやすいようにあえて3秒間ローディング描写させるようにしてます setTimeout(() => { alert('submit!'); // ローディング描写とフォーム非活性化をそれぞれ解除 this.closeFullScreen(); }, 3000); } else { // ここでもcloseFullScreen()を実行したほうがいいかもしれません console.log('error submit!!'); return false; } }); }, // 略おわり
- ローディング描写中にさらにプログレスバーを表示させる方法を知りたい(切実)
参考URL
- 投稿日:2020-02-28T14:13:32+09:00
Vuex開発メモ
Vuexを勉強して、何となく理解できた気がするのでここらでまとめておきます。
Vuexとは?
Vuexは、Vue.jsの状態管理用ライブラリです。
Vue.jsには、propsという、親テンプレートから子テンプレートへ値を渡すことができる機能が備わっていますが、孫要素やそれよりもさらに下の要素が増えていくと、propsを連鎖させることになってしまい、開発効率が落ちてしまったり管理が大変になるというデメリットがあります。それを解決してくれるのがVuexです。Vuexをプロジェクトに導入することで、それぞれのコンポーネントは同じ値をVuexのストア内で共有することになるので、必要な時にストアから値を取得するだけでその値を使うことができるようになります。
一般的には中規模以上から使われ始めるようですが、ある程度プロジェクトができあがってから導入するのは移行が面倒なので、規模が大きくなる見込みがあるのなら使った方がよさそうです。
Vuexのデータフローについて
Vuexは、大きく分けて3つの要素から成り立っています。Actions、Mutations、Stateです。
Actionsが外部APIとの通信、MutationsがStateの書き換え、Stateがプロジェクトの状態管理を担っています。Vuexでは、下の図のように、コンポーネントがActionsに問い合わせる、ActionsがMutationsに問い合わせる、MutationsがStateを書き換える、Stateの値をコンポーネントに送るといった、一方向のデータフローを取っています。
Actionsを使用しない場合はコンポーネントからMutationsに直接問い合わせることもできます。
デモアプリ作成
ここからは、実際にカウンターアプリを作りながら説明していきます。
プロジェクトの作成
コンソールを開いて、下のように打ち込んでください。
vue create counter
Please pick a preset
と表示されたら、
Manually select features
を選択
そのあとにCheck the features needed for your project
と表示されたらvuex
をえらんでインストールします。
後はデフォルトのままで大丈夫です。cd vuexDemo yarn serveの後、localhost:8080にアクセスできればプロジェクトの作成は成功です。
コンポーネントの作成
今回は、デフォルトでプロジェクトに用意されているHelloWorld.vueをそのまま使います。
一旦HelloWorld.vue内に書かれている必要のないコードをすべて消してしまいましょう。
以下のような状態にします。
src/assets/components/HelloWorld.vue<template> <div> </div> </template> <script> export default { name: 'HelloWorld', } </script> <style scoped> </style>一旦Vuexを使わずに作ってみましょう。
このような感じになると思います。src/assets/components/HelloWorld.vue<template> <div> <p>{{ count }}</p> <button type="button" @click="countup">countup</button> <button type="button" @click="countdown">countdown</button> </div> </template> <script> export default { name: "HelloWorld", data() { return { count: 0 }; }, methods: { countup() { this.count++; }, countdown() { this.count--; } } }; </script> <style scoped> button { margin-left: 20px; } </style>countupボタンを押したら1足されて、countdownボタンを押したら1引かれるという機能を作りました。
以降はVuexを使って同じ機能を作っていきます。
stateの作成
まずは状態を管理するStateを作っていきましょう。
既にstore/index.js
内に一部書かれているので、そのまま使います。
デフォルトではこのようになっていると思います。src/store/index.jsimport Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { }, mutations: { }, actions: { }, modules: { } })stateに値を追加しましょう。
src/store/index.jsimport Vue from 'vue' //省略 state: { count: 0 }, //省略これをHelloWorld.vueから読み込みます。
stateの値を読み込むには、$store.state.値で読み込むことができます。
src/components/HelloWorld.vue<template> <div> <p>{{ $store.state.count }}</p> <!--変更--> <button type="button" @click="countup">countup</button> <button type="button" @click="countdown">countdown</button> </div> </template> <script> export default { name: "HelloWorld", //------------------必要なくなるので削除する------------- data() { return { count: 0 }; }, //-----------------------ここまで削除------------------- methods: { countup() { this.count++; }, countdown() { this.count--; } } }; </script> <style scoped> button { margin-left: 20px; } </style>では、実際の表示を見てみましょう。
ボタンを押しても数字は変わりませんが、storeに記述したcount:0
が反映されていると思います。mutationの作成
ここからは、mutationを作って、stateの変更ができるようにします。
src/store/index.js//省略 mutations: { countup: function (state) { state.count++; }, countdown: function (state) { state.count--; } }, //省略stateの変更をする場合は、関数の第一引数にstateを渡すのを忘れないようにしてください。
state.値
で管理されている値を取得できます。それでは、mutationで作った関数をコンポーネントから呼び出していきましょう。
$store.commit('関数名')で呼び出すことができます。
src/components/HelloWorld.vue<template> <div> <p>{{ $store.state.count }}</p> <button type="button" @click="$store.commit('countup')">countup</button> <!--変更--> <button type="button" @click="$store.commit('countdown')">countdown</button> <!--変更--> </div> </template> <script> export default { name: "HelloWorld", //-------------------必要なくなるので削除する----------------------- methods: { countup() { this.count++; }, countdown() { this.count--; } } //----------------------ここまで削除------------------------------- }; </script> <style scoped> button { margin-left: 20px; } </style>確認してみましょう。
stateが書き換わって、表示されている数字が増えたり減ったりしていることがわかります。余談ですが、関数の第二引数(payload)に値を渡してあげることで、stateを任意の値に変更することもできます。
actionsの作成
外部APIとの通信をしないのであればactionsを作る必要はありませんが、mutationsの関数の呼び出し方のみ説明しておきます。
actionsの機能についてもっと知りたい方はこちらをご覧ください。actionsにmutationsの関数を呼び出すための関数を書いていきます。
src/store/index.js//省略 actions: { countupAction(context) { context.commit('countup') }, countdownAction(context) { context.commit('countdown') } }, //省略これでmutationsのcountupとcountdownを呼び出す準備ができました。
このactionsで定義した関数をコンポーネントから呼び出します。コンポーネントからactionsの関数を呼び出すには、
$store.dispatch('関数名')で呼び出すことができます。
ボタンクリック時に呼び出せるよう、HelloWorld.vueを書き換えていきます。src/components/HelloWorld.vue<template> <div> <p>{{ $store.state.count }}</p> <button type="button" @click="$store.dispatch('countupAction')">countup</button> <!--変更--> <button type="button" @click="$store.dispatch('countdownAction')">countdown</button> <!--変更--> </div> </template> <script> export default { name: "HelloWorld", methods: { countup() { this.count++; }, countdown() { this.count--; } } }; </script> <style scoped> button { margin-left: 20px; } </style>確認してみましょう。
それぞれの関数を呼び出せていることがわかります。最後に
Vuexは、初めは理解するのが難しいかもしれませんが、わかるとかなり直感的に操作できます。
規模が大きくなると必須の技術になってくるので、ぜひ使ってみてください。もしミス等ありましたらコメントまたは編集リクエストでお伝えください。
- 投稿日:2020-02-28T14:13:32+09:00
Vuexのデータフローを理解して使ってみる
Vuexを勉強して、何となく理解できた気がするのでここらでまとめておきます。
Vuexとは?
Vuexは、Vue.jsの状態管理用ライブラリです。
Vue.jsには、propsという、親テンプレートから子テンプレートへ値を渡すことができる機能が備わっていますが、孫要素やそれよりもさらに下の要素が増えていくと、propsを連鎖させることになってしまい、開発効率が落ちてしまったり管理が大変になるというデメリットがあります。それを解決してくれるのがVuexです。Vuexをプロジェクトに導入することで、それぞれのコンポーネントは同じ値をVuexのストア内で共有することになるので、必要な時にストアから値を取得するだけでその値を使うことができるようになります。
一般的には中規模以上から使われ始めるようですが、ある程度プロジェクトができあがってから導入するのは移行が面倒なので、規模が大きくなる見込みがあるのなら使った方がよさそうです。
Vuexのデータフローについて
Vuexは、大きく分けて3つの要素から成り立っています。Actions、Mutations、Stateです。
Actionsが外部APIとの通信、MutationsがStateの書き換え、Stateがプロジェクトの状態管理を担っています。Vuexでは、下の図のように、コンポーネントがActionsに問い合わせる、ActionsがMutationsに問い合わせる、MutationsがStateを書き換える、Stateの値をコンポーネントに送るといった、一方向のデータフローを取っています。
Actionsを使用しない場合はコンポーネントからMutationsに直接問い合わせることもできます。
デモアプリ作成
ここからは、実際にカウンターアプリを作りながら説明していきます。
プロジェクトの作成
コンソールを開いて、下のように打ち込んでください。
vue create counter
Please pick a preset
と表示されたら、
Manually select features
を選択
そのあとにCheck the features needed for your project
と表示されたらvuex
をえらんでインストールします。
後はデフォルトのままで大丈夫です。cd vuexDemo yarn serveの後、localhost:8080にアクセスできればプロジェクトの作成は成功です。
コンポーネントの作成
今回は、デフォルトでプロジェクトに用意されているHelloWorld.vueをそのまま使います。
一旦HelloWorld.vue内に書かれている必要のないコードをすべて消してしまいましょう。
以下のような状態にします。
src/assets/components/HelloWorld.vue<template> <div> </div> </template> <script> export default { name: 'HelloWorld', } </script> <style scoped> </style>一旦Vuexを使わずに作ってみましょう。
このような感じになると思います。src/assets/components/HelloWorld.vue<template> <div> <p>{{ count }}</p> <button type="button" @click="countup">countup</button> <button type="button" @click="countdown">countdown</button> </div> </template> <script> export default { name: "HelloWorld", data() { return { count: 0 }; }, methods: { countup() { this.count++; }, countdown() { this.count--; } } }; </script> <style scoped> button { margin-left: 20px; } </style>countupボタンを押したら1足されて、countdownボタンを押したら1引かれるという機能を作りました。
以降はVuexを使って同じ機能を作っていきます。
stateの作成
まずは状態を管理するStateを作っていきましょう。
既にstore/index.js
内に一部書かれているので、そのまま使います。
デフォルトではこのようになっていると思います。src/store/index.jsimport Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { }, mutations: { }, actions: { }, modules: { } })stateに値を追加しましょう。
src/store/index.jsimport Vue from 'vue' //省略 state: { count: 0 }, //省略これをHelloWorld.vueから読み込みます。
stateの値を読み込むには、$store.state.値で読み込むことができます。
src/components/HelloWorld.vue<template> <div> <p>{{ $store.state.count }}</p> <!--変更--> <button type="button" @click="countup">countup</button> <button type="button" @click="countdown">countdown</button> </div> </template> <script> export default { name: "HelloWorld", //------------------必要なくなるので削除する------------- data() { return { count: 0 }; }, //-----------------------ここまで削除------------------- methods: { countup() { this.count++; }, countdown() { this.count--; } } }; </script> <style scoped> button { margin-left: 20px; } </style>では、実際の表示を見てみましょう。
ボタンを押しても数字は変わりませんが、storeに記述したcount:0
が反映されていると思います。mutationの作成
ここからは、mutationを作って、stateの変更ができるようにします。
src/store/index.js//省略 mutations: { countup: function (state) { state.count++; }, countdown: function (state) { state.count--; } }, //省略stateの変更をする場合は、関数の第一引数にstateを渡すのを忘れないようにしてください。
state.値
で管理されている値を取得できます。それでは、mutationで作った関数をコンポーネントから呼び出していきましょう。
$store.commit('関数名')で呼び出すことができます。
src/components/HelloWorld.vue<template> <div> <p>{{ $store.state.count }}</p> <button type="button" @click="$store.commit('countup')">countup</button> <!--変更--> <button type="button" @click="$store.commit('countdown')">countdown</button> <!--変更--> </div> </template> <script> export default { name: "HelloWorld", //-------------------必要なくなるので削除する----------------------- methods: { countup() { this.count++; }, countdown() { this.count--; } } //----------------------ここまで削除------------------------------- }; </script> <style scoped> button { margin-left: 20px; } </style>確認してみましょう。
stateが書き換わって、表示されている数字が増えたり減ったりしていることがわかります。余談ですが、関数の第二引数(payload)に値を渡してあげることで、stateを任意の値に変更することもできます。
actionsの作成
外部APIとの通信をしないのであればactionsを作る必要はありませんが、mutationsの関数の呼び出し方のみ説明しておきます。
actionsの機能についてもっと知りたい方はこちらをご覧ください。actionsにmutationsの関数を呼び出すための関数を書いていきます。
src/store/index.js//省略 actions: { countupAction(context) { context.commit('countup') }, countdownAction(context) { context.commit('countdown') } }, //省略これでmutationsのcountupとcountdownを呼び出す準備ができました。
このactionsで定義した関数をコンポーネントから呼び出します。コンポーネントからactionsの関数を呼び出すには、
$store.dispatch('関数名')で呼び出すことができます。
ボタンクリック時に呼び出せるよう、HelloWorld.vueを書き換えていきます。src/components/HelloWorld.vue<template> <div> <p>{{ $store.state.count }}</p> <button type="button" @click="$store.dispatch('countupAction')">countup</button> <!--変更--> <button type="button" @click="$store.dispatch('countdownAction')">countdown</button> <!--変更--> </div> </template> <script> export default { name: "HelloWorld", methods: { countup() { this.count++; }, countdown() { this.count--; } } }; </script> <style scoped> button { margin-left: 20px; } </style>確認してみましょう。
それぞれの関数を呼び出せていることがわかります。最後に
Vuexは、初めは理解するのが難しいかもしれませんが、わかるとかなり直感的に操作できます。
規模が大きくなると必須の技術になってくるので、ぜひ使ってみてください。もしミス等ありましたらコメントまたは編集リクエストでお伝えください。
- 投稿日:2020-02-28T13:05:57+09:00
Vue.js入門1(私的メモ)
Vue.jsはプログレッシブなフレームワーク
プログレッシブなフレームワークってどゆこと。
とりあえず、放置。フロントエンドの変遷
フロントエンドの担うことがめちゃくちゃ増えてる。
時期 フロントエンドの役割 サーバーの役割 JavaScriptライブラリ・フレームワーク Webシステム初期 装飾 HTMLの生成 なし Ajax期 Ajaxを中心としてインタラクション HTMLの生成 + API jQuery, prototype.js etc. 現在 アプリケーションのプレゼンテーション全般 API Vue.js, React, Angular etc. Vue.jsの特徴
JavaScriptフレームワーク入門者は、とりあえずVue.jsを。
- 学習コストの低さ
- コンポーネント志向によるUIの構造化
シズテム全体をコンポーネント(機能を構成する部品)の集合体とみなす
保守性の向上や再利用性の高さなどがメリット- リアクティブなデータバインディング
対象のDOM要素とデータを結びつける(バインディング)ことで、効率的に内容を更新
Vue.jsの設計思想
Vue.jsの根底には、プログレッシブフレームワークという考え方がある。
プログレッシブフレームワークとは、アプリケーションの段階的(プログレッシブ)な要求変化に応じて問題解決できる方法を提供する、という思想。
最小のコストでアプリケーション開発を進められ、ユーザーの要望に沿った機能拡張などに適宜対応することができる。
つまり、必要な段階で必要なものを利用する柔軟性をVue.jsは持っている。Vue.jsを支える技術
- コンポーネントシステム
HTML, CSS, JavaScriptの三つを組み合わせた一つのコンポーネント(部品)として切り出せる- リアクティブシステム
状態変化をVue.jsが検知(監視)して、自動的にDOM側に反映できるような仕組み- レンダリングシステム
仮想DOMによるDOMの高速レンダリングを提供している参考文献
- 投稿日:2020-02-28T12:16:23+09:00
nuxt generateで静的サイトをつくる時の開発環境について(ファイル監視など)
はじめに
皆さんこんにちは。
Nuxt.js、とてもいいですよね。煩雑な設定が少なくすぐに実装に取りかかれるので、アウトプット速度がすごく向上します。今回は、nuxt generateコマンドで静的サイトをビルドする際の開発環境構築で感じた課題と、その解決方法について軽めの記事を書きたいと思います。
ツッコミどころや改善案などあればぜひコメントお願いします。
TL;DR
- 単一ファイルコンポーネント等を使ったVueでの開発をしつつ静的なHTMLを書き出せるnuxt generateはとても便利で使い所も多いが、開発中のファイル監視と自動更新は自前で実装せねばならず、したとしても毎回generateをすることになるので時間がかかる。
- 上記を解決するために、開発時は静的ファイルを生成せずnuxtコマンドで開発を進め、静的ファイルを生成したい時のみnuxt generateを叩くという開発方法を検討した。
- その際、画像ファイル等の参照でハマった
nuxt generateについて
nuxt.jsそのものに比べ、こちらのコマンドは比較的新しい機能ということもあり知名度がやや落ちるかと思ったので、概要について触れておきます。
前述の通りnuxt generateは、nuxt.jsで実装した内容を、静的なHTMLとjsファイルとして書き出して、そのまま静的Webサイトとして使用できるようにしたAPIプロパティです。
これを使うメリットとして、クライアントサイドでの描画がなくなるためSEO上安心ということ、パフォーマンスがやや向上することがあげられます。
また個人的には、多言語サイトを構築する際に、SSRを実装しなくてもjsonから文言を取得して、手軽に各言語のHTMLを書き出せる点に大きなメリットを感じています。(nuxt-i18n使用時)
ディレクトリ構造自体は通常のnuxt開発と同じように、以下のような形で組めば、pagesの中身のvueファイルが全てHTMLで吐き出されるイメージです。
> components > assets > static > pages package.json nuxt.config.jsnuxt generateの弱点
nuxt generateは、一括でファイルを生成するという機能である以上、変更箇所に応じて部分的にビルドをするような監視機能は備わっていません。
そこで、自前でsrcファイルの監視をして更新をしなければならないのですが、毎回nuxt generateを叩かなければいけないため、ビルドに多くの時間がかかってしまいます。
npm scriptsやその他ビルドツールであればscssが変更されればそこのみ、jsが変更されればそこのみビルドするといった形で細かく監視ができるので、その点においてはnuxt generateの弱点と言えそうです。
実装
解決案
そこで、開発時には通常のnuxtコマンドを使ってホットリロードサクサクナウヤングな開発をしつつ、実際に静的HTMLとしてテストしたい時のみnuxt generateを使えばいいんじゃないか、と思い当たりました。
そこで最初に書いたpackage.jsonは以下のような形です。
"scripts", { "dev": "nuxt", "build": "run-s mock:**", "build:clean": "rimraf ./dist/**", "build:gen": "nuxt generate", "build:browse": "browser-sync start --config bs-config.js" }npm run devコマンドでは単にnuxtコマンドを実行し、
npm run buildコマンドでは一旦distディレクトリを綺麗にしてからnuxt generateし、それが完了したらbrowser-syncでローカルサーバを立ち上げています。つまづいたところ
当初assetsフォルダとstaticフォルダの違いがよくわかっておらず、画像等は適当にassetsに突っ込んでいました。
しかし、nuxt開発においてassetsファイルの参照は、
<img src="~assets/images/hoge.png">のように記述しなければならず、そのままnuxt generateをしても参照ができません。
面倒だけどパス置換のスクリプトを挟むか・・・?とも思いましたが、よくよく調べるとstaticフォルダ内の参照は、/staticを省いて以下のようにかけるようです。(直接は関係ありませんがassetsの中身はwebpackでコンパイルするものを突っ込むようです)<img src="/images/hoge.png">しかも、nuxt generateでファイルが書き出されるdistフォルダにはしっかりとimagesフォルダができており、どうやらnuxt generateではstaticに全ての参照ファイルを突っ込むのが正解のようです。
余談
これで開発環境は構築できたのですが、よくみると、distフォルダの中のHTMLが想定通りのものになっていません。
//想定したもの index.html hoge.html //書き出されたもの index.html hoge |__index.htmlこれは、nuxt.config.jsでgenerateプロパティのsubFoldersをfalseにすると解決できました。
nuxt.config.jsexport default { //省略 generate: { subFolders: false }, }
- 投稿日:2020-02-28T12:14:21+09:00
[Vue warn]: `createComponent` has been renamed to `defineComponent`.
- 投稿日:2020-02-28T11:11:15+09:00
.NET Core + Vue.jsのプロジェクト作成
自分用のメモ代わりにやったことを書いておきます。
ほぼ参考にさせていただいたページのままですが、そちらのほうではMVCプロジェクトで作成していますが、ここではWebAPIプロジェクトで作成するので若干の違いがあります。環境
- Visual Studio 2019
- .NET Core 3.1.1
- vue cli 4.2.2
プロジェクト作成
ASP.NET Core WebAPIプロジェクトを作成
とりあえず認証等は何もつけずにAPIのみ
Vue.jsプロジェクト作成
作成したプロジェクトのフォルダで以下のコマンドを実行
vue create client-app
default (babel, eslint)
を選択.NET Coreプロジェクトファイルの編集
csprojファイルを以下のように編集
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> </PropertyGroup> <ItemGroup> <None Remove="client-app\**" /> <Content Remove="client-app\**" /> <None Include="client-app\**" Exclude="client-app\node_modules\**" /> </ItemGroup> <Target Name="ExecNpmInstall" BeforeTargets="Build" Condition="'$(Configuration)' == 'Debug' And !Exists('client-app\node_modules')"> <Exec WorkingDirectory="client-app\" Command="npm install" /> </Target> <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish"> <Exec WorkingDirectory="client-app" Command="npm install" /> <Exec WorkingDirectory="client-app" Command="npm run build" /> <ItemGroup> <DistFiles Include="client-app\dist\**" /> <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)"> <RelativePath>%(DistFiles.Identity)</RelativePath> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> </ResolvedFileToPublish> </ItemGroup> </Target> </Project>.NET Core側のSPA用Configとデバッグ用コードの追加
SPA用のパッケージが入っていないのでnugetで以下のパッケージをインストール
- Microsoft.AspNetCore.SpaServices.Extensions
ConfigureServicesにSPA用の記述を追加
Startup.cspublic void ConfigureServices(IServiceCollection services) { services.AddControllers(); // 追加 services.AddSpaStaticFiles(configuration => { configuration.RootPath = @"client-app/dist"; }); }ConfigureにSPA用の記述を追加
Startup.cspublic void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); // 追加 app.UseSpaStaticFiles(); app.UseSpa(spa => { spa.Options.SourcePath = "client-app"; if (env.IsDevelopment()) { spa.UseProxyToSpaDevelopmentServer(async () => { var pi = new ProcessStartInfo { FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd" : "npm", Arguments = $"{(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "/c npm " : "")}run serve", WorkingDirectory = "client-app", RedirectStandardError = true, RedirectStandardInput = true, RedirectStandardOutput = true, UseShellExecute = false, }; var p = Process.Start(pi); var lf = app.ApplicationServices.GetService<ILoggerFactory>(); var logger = lf.CreateLogger("npm"); var tcs = new TaskCompletionSource<int>(); _ = Task.Run(() => { var line = ""; while ((line = p.StandardOutput.ReadLine()) != null) { if (line.Contains("DONE Compiled successfully in ")) { tcs.SetResult(0); } logger.LogInformation(line); } }); _ = Task.Run(() => { var line = ""; while ((line = p.StandardError.ReadLine()) != null) { logger.LogError(line); } }); await Task.WhenAny(Task.Delay(20000), tcs.Task); return new Uri("http://localhost:8080"); }); } }); }一応ここまでで動作はしますが、デフォルトだとデバッグ実行した時にWeatherForecastにアクセスするようになっているためプロジェクトのプロパティで以下の画像の
ブラウザの起動
の右にあるテキストボックスを空にします。
実行
デバッグ実行して問題がなければVue.jsプロジェクトのほうのページが開きます。
参考
- 投稿日:2020-02-28T03:16:21+09:00
Vue.jsを触る際に知っておきたい仮想DOMの話
記事の概要
昨今、様々なプロダクトにおいて使われているVue.js。
人気の理由の1つにリアクティブなブラウザの描画があげられます。ブラウザの再描画を支えている技術が「仮想DOM」なのですが、Vue.jsからフロントエンドを触り始めた自分にとっては「そもそもDOMって何?」状態だったので、「仮想DOMとその背景」についての解説記事を書きました。
技術的な記事ですが、歴の浅いフロントエンドエンジニアがDOM周りについて最低限の知識を身に付けられる事を目的に大枠の理解を優先しましたので、ご了承ください。
そもそもDOMとは
仮想DOMについて知る前に、そもそもDOM(仮想DOMとの区別の為、今後はリアルDOMと呼びます)とはなんなのかを知りましょう。
リアルDOMとは「Document Object Model」の略で、JavaScript側からHTMLを操作する事の出来る仕組みであり、実際にブラウザで描画されているものです。リアルDOMを操作する事によって、JavaScriptからHTMLを弄れるため、web上にて見た目の変化が可能になります。
リアルDOMの特徴
リアルDOMには以下の特徴があります。
・HTMLを階層構造として扱う(DOMツリー)
・各要素はノードと呼ばれるイメージとして下記のHTMLの場合
<html> <header> <h1>タイトル<h1> </header> <body> <div> <p>内容</p> <button>送信</button> </div> </body> </html>
リアルDOM上ではこの様な階層構造(DOMツリー)として扱います。またこの1つ1つの要素がノードです。
詳しくはこちらを参考にしてください。
https://kuroeveryday.blogspot.com/2018/11/difference-between-dom-and-node-and-element.htmlリアルDOMのみの流れ
1 HTMLドキュメント
2 リアルDOM(DOMツリー)
3 webページ
の順番で情報は流れていきます。webページに変化を加えようとした際は、その都度htmlを解析して、DOMツリーを再構築します。
その為、webページへの変化が多いと、再描画まで時間がかかってしまいます。仮想DOMとは
その名の通り、仮のDOMです。メモリ空間などで擬似的に作ったものです。
今までブラウザで持っていたDOMツリーをJavaScriptのオブジェクトとして扱い、差分のみをリアルDOMに与えてくれる役割を持ちます。仮想DOMを用いた流れ
仮想DOMを用いる事により、情報の順番は
1 HTMLドキュメント
2 仮想DOM
3 リアルDOM
4 webページといった形になります。
仮想DOM内部の仕組み
1 DOMツリーを新旧で2つ用意している
2 HTMLドキュメントが書き変わる
3 仮想DOMを再構築し、新旧DOMでの差分を検出する
4 差分のみをリアルDOMに反映する仮想DOMによる恩恵
描画のスピードです!笑
仮想DOMの存在によって、リアルDOMでは差分のみの読み込みで描画ができる為、ブラウザの再描画のスピードが格段に早くなり、リアクティブな変化にも耐えうるようになりました。まとめ
Vue.jsでのリアクティブな変化は仮想DOMの働きによって実現されていました。
普段の業務でフレームワークを使う際に仮想DOMなどの様に、裏側の原理について必ずしも理解しておく必要はないと思いますが、現段階でなぜここまでVue.jsが広まっているのか?を知る一旦にはなりそうです。
- 投稿日:2020-02-28T00:34:41+09:00
vue.jsでmomentを使ってみた
Vue.js(Vue CLI)でmoment.jsを使ってみたときのメモです。
やっておくこと
基本的なところはスッとばします。
$ vue create vue-moment-sample $ yarn add momentサンプルソース
今回はサンプルなので、コンポーネントではなく
App.vue
にて試しています。.src/App.vue<template> <div id="app"> <h1>MOMENT SAMPLE</h1> <h2>現在日</h2> <p>{{ getTime | moment }}</p> <h2>生まれた日</h2> <p>{{ "1990-10-23 00:00:00" | moment }}</p> <h2>生まれてから</h2> <p>{{ elapsedDate(getTime , "1990-10-23") }}日</p> </div> </template> <script> import moment from "moment" //利用するコンポーネントでインポート export default { name: 'App', computed: { getTime() { return moment() } }, methods: { elapsedDate(a, b) { return moment(a).diff(moment(b), 'days', false) } }, filters: { moment(date_str) { moment.locale('ja') return moment(date_str).format('YYYY-MM-DD') } } } </script>フォーマットについて
moment().format('YYYY/MM/DD')
というような形でフォーマットを指定する。日本語でよく使いそうなフォーマットを整理してみた。
日時 フォーマット 備考 2020/2/28 YYYY/M/D
2020-02-28(金) YYYY-MM-DD(dd)
moment.locale('ja')
が必要2月28日 金曜日 MMMDo dddd
moment.locale('ja')
が必要2/28 14:25 M/D HH:mm
0時を24時として扱いたい場合は HH
の代わりにkk
を用いる2/28 午後 14:25 M/D(dd) a hh:mm
英語表記の場合は、 a
でam/pm、A
でAM/PMわかったことメモ
filters
として定義してフォーマット(YYYY-MM-DD
など)を整えるのに使える- 時間の加算・減算などの処理のときも、活躍しそう
- 日本語表記など特定語表記を使いたい場合は
moment.locale()
を使う調べてることメモ
いったんググっても出てこなかったところは、少しずつ使いながら調べていく。
main.js
などで一度moment.locale('ja')
しておけば良いのか?vue-moment
やvue-cli-plugin-moment
などはどう使う?参考
- 投稿日:2020-02-28T00:34:41+09:00
vue.jsでmomentを使いこなしたい
Vue.js(Vue CLI)でmoment.jsを使ってみたときのメモです。
必要なこと
基本的なところはスッとばします。
$ vue create vue-moment-sample $ yarn add momentソースコード
.src/App.vue<template> <div id="app"> <h1>MOMENT SAMPLE</h1> <h2>現在日</h2> <p>{{ getTime | moment }}</p> <h2>生まれた日</h2> <p>{{ "1990-10-23 00:00:00" | moment }}</p> <h2>生まれてから</h2> <p>{{ elapsedDate(getTime , "1990-10-23") }}日</p> </div> </template> <script> import moment from "moment" export default { name: 'App', computed: { getTime() { return moment() } }, methods: { elapsedDate(a, b) { return moment(a).diff(moment(b), 'days', false) } }, filters: { moment(date_str) { moment.locale('ja') return moment(date_str).format('YYYY-MM-DD') } } } </script>わかったことメモ
filters
として定義してフォーマット(YYYY-MM-DD
など)を整えるのに使える- 時間の加算・減算などの処理のときも、活躍しそう
- 日本語表記など特定語表記を使いたい場合は
moment.locale()
を使う調べてることメモ
いったんググっても出てこなかったところは、少しずつ使いながら調べていく。
main.js
などで一度moment.locale('ja')
しておけば良いのか?vue-moment
やvue-cli-plugin-moment
などはどう使う?