よく分かってないでvue-cliで作ったVue.js製SPAをアップデートした話
前回 => https://qiita.com/yamanoku/items/41df5c05c5c89714ea3c
特にきっかけはないのですが、表題のまんまのことやりましたので記録です。
以前の環境について
- Vue v1.0.0
- vue-cli v2.5.0(おそらく)
- vue-router v0.7.13
で作ってました。vue-router
がまず v2.0.0 でもないというのが驚きですが、いかんせん Vue.js どころか諸々のスキルが低すぎたのもあったので、言われるがままなすがままにこの環境のままでつくりました。
使用したvue-cli
テンプレートはwebpack-simple
でした。
一応動きます。 https://github.com/yamanoku/vue_portfolio_templete/tree/ver1.0.0
強制アプデして直そうとした
まず以前の内容からブランチを切ってnpm-check-updates
をかけpackage.json
周りを強制アップデートしてから、諸々インストールして、dev を走らせましたが、早速webpack からぶっ壊れる。
webpack
resolveLoader
箇所のroot
をmodules
に変更
resolveLoader: {
root: path.join(__dirname, 'node_modules'),
},
resolveLoader: {
modules: [path.join(__dirname, 'src'), 'node_modules'],
},
module
箇所の loader 部分に-loader
接尾語? が必要
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.html$/,
loader: 'vue-html-loader'
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: "file-loader?name=[name].[ext]"
}
]
},
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue'
},
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/
},
{
test: /\.json$/,
loader: 'json'
},
{
test: /\.html$/,
loader: 'vue-html'
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: "file?name=[name].[ext]"
}
]
},
webpack.optimize.OccurenceOrderPlugin()
=>webpack.optimize.OccurrenceOrderPlugin()
- 一番ひどい
1つ1つ調べながら対応していたのもあってなかなかbuild
やdev
が通らず苦労しました。
webpack のほうを修正して npm-scripts 周りが動作するようになったので動かし始めたのですが、お次はVue1.x => Vue2.0.0へのアプデ修正周りです。
Vue.js
vue-migration-helper https://github.com/vuejs/vue-migration-helper
世の中にはマイグレーションヘルパーツールなる優れたものがあるのでこちらを使用しました(変更した部分に関しては後述)。 ターミナルで吐かれたエラーを1つずつ治し、ようやく 0 になってコンソールもエラーなくなったのですが、肝心の vue-router 部分が動かず、ここで立ち往生しました。
1つ1つ見直していけば問題判明できそうというのもありましたが、個人のプロダクトで特に機能も複雑化していないものだったので、1 つずつ手直しするよりもvue-cli で1から作り直してやろうという気持ちが勝ったのでやり直しました。
ちなみにですが立ち往生した作業途中もブランチ切って上げてます。どんなのか気になる人は参考までに見てみてください。 https://github.com/yamanoku/vue_portfolio_templete/tree/fail/updates
vue-cli から作り直し
グローバルにあるvue-cli
をアップデートして、前回はwebpack-simple
テンプレートだったので今回はwebpack
を選択しました。余談ですがいつの間にかpwa
なるものも追加されていたのですね。
さっそくassets
内に必要ファイルを格納しました。以下変更部分
src
内各コンポーネント =>assets/components
- 画像各種
- ソース内で使うもの =>
assets/img
- json 内で使用する部分 =>
static
- ソース内で使うもの =>
- json ファイル
src/data/list.json
=>assets/data/list.json
App.vue
と main.js
は既存のままですが、router 部分の記述はsrc/router/index.js
に移動しました。
Vue.js 変更箇所
v-for
で回した時に:key
指定
before
<template v-for="list in lists">
<div
class="detail"
v-if="$route.path === '/work/' + list.id + '/detail'"
:style="{ backgroundImage: 'url('+ list.image +')' }"
></div
></template>
after
<template v-for="list in lists">
<div
class="detail"
v-if="$route.path === '/work/' + list.id + '/detail'"
:style="{ backgroundImage: 'url('+ list.image +')' }"
:key="list.id"
></div
></template>
2.2.0 以降で、コンポーネントで v-for を使用するとき、key は必須です
v-for
で作成したエイリアス値の属性値での設定
before
<a href="{{list.url}}" target="_blank">{{list.url}}</a>
after
<a :href="list.url" target="_blank">{{list.url}}</a>
{{ xxx }}
指定が不要になる。
this.$eval
廃止
以下記事参考にしました。ありがとうございます。
https://qiita.com/tmiame/items/34823b22cd3829321120#eval
以前のは json の画像 URL 部分も一緒に引っかかるのをどうにかしたかったんですがこれで解決しました。
vue-router
設定時
router.start
before
router.start(App, '#app');
after
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>',
});
Vue インスタンスにrouter
プロパティを渡す。
router.map
before
router.map({
'/home': {
component: Main,
},
'/work/:number/detail': {
component: Detail,
},
'/profile': {
component: Profile,
},
'*': {
component: NotFound,
},
});
router.redirect({
'/': '/home',
});
after
export default new Router({
routes: [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'Main',
component: Main,
},
{
path: '/work/:number/detail',
name: 'Works',
component: Detail,
},
{
path: '/profile',
name: 'Profile',
component: Profile,
},
{
path: '*',
name: 'NotFound',
component: NotFound,
},
],
});
redirect 部分も一緒に配列に追加。
hashbang
機能
不要となりましたのでこちらは削除。
vue-router コンポーネント使用時
v-link
廃止
before
<a class="detail_btn" v-link="{ path: '/home' }">Back Home</a>
after
<router-link class="detail_btn" to="/home">Back Home</router-link>
path 指定での記述ではなくなりました。
名前付きルート指定
before
<a v-link="'/work/' + list.id + '/detail'" class="filetype"
>[{{ list.type }}]</a
>
after
<router-link
:to="{ name: 'Works', params: { number: list.id }}"
class="filetype"
>[{{ list.type }}]</router-link
>
router.map
設定時にname
を指定しておくと、ここでルートを特定できて非常に楽になりました。
上記設定でたとえば href="work/001/detail"
を書き出してくれます。
https://router.vuejs.org/ja/essentials/named-routes.html
transition
指定
before
<article transition="fade"></article>
after
<transition name="fade"></transition>
どこかのタグに対してtransition
属性として付与するのではなく、<transition>
タグで独立。
いままで各コンポーネントごとでかけてましたが一括化するためにApp.vue
にのみに変更・設定しておきました。
<template>
<div id="app">
<transition name="fade">
<router-view />
</transition>
</div>
</template>
.v-transition
廃止
.v-enter-active
や.v-leave-active
に変更。
ほか修正したこととか
アプデに伴い、webpack や Vue.js と関係なく修正した部分について
flex-box
=> grid layout
before
.container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
width: 95%;
margin: auto;
}
.container::before {
display: block;
content: '';
order: 1;
width: 22.75%;
}
.container::after {
display: block;
content: '';
width: 22.75%;
}
.card {
width: 22.75%;
margin-bottom: 2.5%;
background: #456a8e;
border-radius: 3px;
box-shadow: 0 1px 3px rgba(100, 100, 100, 0.25);
transition: all 0.25s ease-in-out;
}
after
.container {
width: 95%;
margin: auto;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-column-gap: 20px;
grid-row-gap: 20px;
padding-bottom: 20px;
}
.card {
background: #456a8e;
border-radius: 3px;
transition: all 0.25s ease-in-out;
}
IE11 は.card
がwidth:100%
になるが、コード量的には減ってすっきり。
ただ、grid-row-gap
が%指定しても空きができなかった。grid-column-gap
はできたけど両方に指定はできないか?
transition
効果
下から上に切り替わるような効果にしていたが、もうちょっとスムーズになるように調整したのと、タブレット〜スマホサイズ時は左から右に切り替わるようにした。
.fade-enter-active {
transition: all 0.75s ease;
}
.fade-leave-active {
transition: all 0.75s cubic-bezier(1, 0.5, 0.8, 1);
}
@media screen and (min-width: 769px) {
.fade-enter,
.fade-leave-to {
transform: translateY(100vh);
}
}
@media screen and (max-width: 768px) {
.fade-enter,
.fade-leave-to {
transform: translateX(-250vw);
}
}
あとheight: 100%
での SPA になってるのでページ下部までいって詳細ページに遷移する時にガクッとなる動きがあり違和感があったため、router のscrollBehavior
を使って詳細ページ時はページトップへ遷移(setTimeout
で transition 部分の時間と調整)して、それ以外はページ位置を保持しておくという設定もつけておきました。
routes: [
...
{
path: '/work/:number/detail',
name: 'Works',
component: Detail,
meta: { scrollToTop: true }
},
...
],
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return new Promise(resolve => {
if (to.matched.some(m => m.meta.scrollToTop)) {
setTimeout(() => {
resolve({ x: 0, y: 0 })
}, 750)
}
})
}
}
まだ精度としては微妙なのでスタイル部分も合わせて見直す必要ありそうだけど、まぁいいかとなってます。
ToDo
-
vue-head
入れて meta 情報周り整える - 左メニュー一覧のレイアウト作成
- テンプレートから作成したが、これを個人単位でメンテできる構成にできるか。
- pwa 化できるか。これはおそらく Nuxt.js でやったほうがよさそう
雑感
- CLI ツールとは便利なものであり、
vue-cli
は未だに初学者に優しいテンプレート作成ツールである。 - ただ、どんな便利な CLI ツールにしろアップデートされた機能・廃止された機能ほかも合わせて調整、最悪マイグレーションヘルパーみたいなのがあればいいが、そういうのはない。
- たぶん CLI ツールを使い運用していくにはバージョンアップの都度、テンプレートも更新できているか、きちんと保守できるかどうかなど徹底的に付き合う必要はある。
vue-cli
3.0 以降でこの辺の煩雑な機能などを見直す予定とのこと(下部関連リンク参考)
- 今回のは大した機能もなく1からやり直せる範囲だったのでどうでもよかったが、規模が大きかったり機能が煩雑だったりするとこれの比ではない苦労や地獄が待っている可能性がある。
- なので個人的な1発ネタや突発性のある簡易的なツール、練習やどのような構成で動かすかの勉強としては自由に使っても良い。
- ただ自分できちんと動かしたり保守できたりするにはそれらに頼らない環境構築をしなければならない(上記 ToDo 3件目で言及済み)。
- CLI ツールがどのようなものかを理解すればいいけど現状の自分にはそこまで読み解く力はない。
- これは
vue-cli
、Vue.js に限らず、あらゆる点で大切なことだと思う。 - You know, I learned something today.(サウスパーク並の感想)
関連リンク
Future of Vue.js (vue-cli について)
https://speakerdeck.com/player/7d437d38c31b46318998d120b2d9c929?slide=60
vue-router 日本語ドキュメント