- 投稿日:2020-05-14T23:16:27+09:00
【Vue】v-ifと$refsによるTypeErrorの対処法
v-if
を設定したコンポーネントに対してthis.$refs
で要素を取得しようとするとunderfinedになります。以下、具体例と対処法です。
開発環境はVue.js 2.6.11
です。コード
例は適当です。最低限の部分しか書いていません。
<template> <div> <!-- 編集部分 --> <status-edit v-if="isEditing" ref="editArea" /> <!-- ビュー部分 --> <status-view v-if="!isEditing" /> <!-- ボタン --> <button v-if="!isEditing" @click="pushEdit">編集</button> </div> </template> <script> import statusView from "./StatusView" import statusEdit from "./StatusEdit" export default { components: { statusView, statusEdit }, data() { return { isEditing: false } }, methods: { pushEdit() { // isEditingをtrueにすることで編集画面/表示画面を切り替える this.isEditing = true // statusEditコンポーネントの中のfetchDateメソッドを実行 this.$refs.editArea.fetchData() } } } </script>エラー
[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'fetchData' of undefined" vue.runtime.esm.js:1927 TypeError: Cannot read property 'fetchData' of undefined「編集」ボタンを押すと、このようなエラーが出ます。
「this.$refs.editArea
がunderfinedになっているから、fetchDate
なんてプロパティは読み込めないよ」という意味です。原因
v-if="false"
のとき、statusEditコンポーネントはページ上に存在していません。
そして、メソッド内の処理は非同期に行われます。
つまり、コンポーネントが描画されていない状態でthis.$refs.editArea
を取得しようとするので、当然underfinedになってしまうのです。解決法
async/awaitを使う
methods: { async pushEdit() { await (this.isEditing = true) this.$refs.editArea.fetchData() } }
async/await
でコンポーネントが描画された後、$refs
でコンポーネントを取得しメソッドを実行する。v-ifをv-showに変える
<status-edit v-show="isEditing" ref="editArea" />
v-show
はdisplay: none;
によって見えていないだけで描画はされているので、コンポーネント内のメソッドを実行することができる。参考リンク
- 投稿日:2020-05-14T23:16:27+09:00
【Vue.js】v-ifと$refsによるTypeErrorの対処法
v-if
を設定したコンポーネントに対してthis.$refs
で要素を取得しようとするとunderfinedになります。以下、具体例と対処法です。
開発環境はVue.js 2.6.11
です。コード
例は適当です。最低限の部分しか書いていません。
<template> <div> <!-- 編集部分 --> <status-edit v-if="isEditing" ref="editArea" /> <!-- ビュー部分 --> <status-view v-if="!isEditing" /> <!-- ボタン --> <button v-if="!isEditing" @click="pushEdit">編集</button> </div> </template> <script> import statusView from "./StatusView" import statusEdit from "./StatusEdit" export default { components: { statusView, statusEdit }, data() { return { isEditing: false } }, methods: { pushEdit() { // isEditingをtrueにすることで編集画面/表示画面を切り替える this.isEditing = true // statusEditコンポーネントの中のfetchDateメソッドを実行 this.$refs.editArea.fetchData() } } } </script>エラー
[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'fetchData' of undefined" vue.runtime.esm.js:1927 TypeError: Cannot read property 'fetchData' of undefined「編集」ボタンを押すと、このようなエラーが出ます。
「this.$refs.editArea
がunderfinedになっているから、fetchData
なんてプロパティは読み込めないよ」という意味です。原因
v-if="false"
のとき、statusEditコンポーネントはページ上に存在していません。
そして、メソッド内の処理は非同期に行われます。
つまり、コンポーネントが描画されていない状態でthis.$refs.editArea
を取得しようとするので、当然underfinedになってしまうのです。解決法
async/awaitを使う
methods: { async pushEdit() { await (this.isEditing = true) this.$refs.editArea.fetchData() } }
async/await
でコンポーネントが描画されるのを待ちます。その後、$refs
でコンポーネントを取得してメソッドを実行します。v-ifをv-showに変える
<status-edit v-show="isEditing" ref="editArea" />
v-show
はdisplay: none;
によって見えていないだけで描画はされているので、コンポーネント内のメソッドを実行することができます。参考リンク
- 投稿日:2020-05-14T22:31:00+09:00
Vue(Vue-CLI)とTypeScriptでhighchartsのグラフ表示
Vue(Vue-CLI)とTypeScriptでhighchartsのグラフを表示させます。
プロジェクトの作成
Vue CLI UIでプロジェクトを作成します。パッケージマネージャーとしてyarnを選択しています。
package.jsonにhighchartsの追加
package.jsonのdependenciesに
highcharts
とhighcharts-vue
を追加します。package.json"dependencies": { "core-js": "^3.6.4", "highcharts": "^8.1.0", "highcharts-vue": "^1.3.5", "vue": "^2.6.11", "vue-class-component": "^7.2.3", "vue-property-decorator": "^8.4.1", "vue-router": "^3.1.6" }追加後、
yarn
コマンドを実行し、追加します。yarnグラフの作成
2019年の平均気温を表示するグラフを作成します。
./src/components/Graph.vue<template> <div> <highcharts :options="graph"></highcharts> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; import { Chart } from 'highcharts-vue'; export type DataType = { graph: any; } @Component ({ components: { highcharts: Chart }, }) export default class Graph extends Vue { data (): DataType { return { graph: { title: { text: '2019年の平均気温' }, subtitle: { text: '引用:<a href="http://www.data.jma.go.jp/obd/stats/etrn/view/monthly_s1.php?prec_no=44&block_no=47662&year=2019&month=&day=&view=">気象庁</a>' }, xAxis: { categories: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], crosshair: true }, yAxis: { title: false, labels: { format: '{value} ℃' }, opposite: false, }, credits: { enabled: false }, tooltip: { pointFormat: '{series.name}:{point.y:.1f} ℃' }, series: [{ name: '平均気温', type: 'column', data: [5.6, 7.2, 10.6, 13.6, 20.0, 21.8, 24.1, 28.4, 25.1, 19.4, 13.1, 8.5], marker: { enabled: true }, }], } } } } </script>グラフの読み込み
App.vue
に上記で作成したグラフを読み込ませます。./src/App.vue<template> <div id="app"> <Graph></Graph> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; import Graph from '@/components/Graph.vue'; @Component ({ components: { Graph, }, }) export default class App extends Vue { } </script> <style> </style>グラフの表示
参考
こちらの記事を参考にさせていただきました。
Vue.js+TypeScript+Nuxt.js環境で、highcharts-vueを使うためのtips
- 投稿日:2020-05-14T18:11:53+09:00
【Nuxt.js】vue-js-modalの導入方法
概要
Nuxtにvue-js-modalを導入するのに少しハマった部分があったので、導入方法を残しておきます。
導入
インストール
yarnを使ってインストール
$ yarn add vue-js-modal
vue-js-modal.jsを作成
plugins
ディレクトリ内にvue-js-modal.js
ファイルを作成します。plugins/vue-js-modal.jsimport Vue from "vue" import VModal from "vue-js-modal" Vue.use(VModal)nuxt.config.js
以下ハマり部分。
nuxt.config.js
を変更していきます。nuxt.config.jsexport default { plugins: [ "~/plugins/vue-js-modal", ] }とやれば導入出来るはずだったのですが、私の環境では動かず、以下のようにすることで動きました。
nuxt.config.jsexport default { plugins: [ { src: "~/plugins/vue-js-modal", ssr: false }, ] }明示的にssrをfalseにすることでコンポーネント内でmodalを使用することが出来ました。
- 投稿日:2020-05-14T14:30:28+09:00
【個人開発】MIDIデバイスで音楽を配信できるWebサービスを作った
はじめに
なんか色々あってWebサービスをリリースすることになったのでついでに記事を書くことにしました。
何ができるのか、どんな感じに実装したか等を説明したいと思います。作ったもの
サービス名は、midiesです。友達に考えてもらいました。
アイコンはMIDI端子とWi-Fiを組み合わせた感じで、まあ悪くはないかなと思ってます。このサービスの特徴は、MIDIデバイス(仮想でも可)の信号をブラウザだけでライブ配信できることです。
配信機能はパソコンやAndroid端末の一部ブラウザのみ対応しており、受信と再生は多分全てのブラウザが対応してます。
ただし、IEだけは動作の保証をしません(一応再生はできたっぽい)。
送受信者のネットワークが安定していれば1秒未満の遅延で配信が可能です。
配信と再生画面のスクショです。パソコンのみですがスマホにもちゃんと対応しています。
技術的な話
通信部分の解説(というか開発の流れ)
最初は、NETDUETTOを真似できないかなとか思って作り始めました。
とりあえず何回も使ったことのある、WebSocketを使って実装しました。
東京都内同士での通信で遅延が大体40~100 msくらいでセッションはできなくもないって感じです。
欠点は短時間に大量のデータを送信するとパケットは詰まってというか、一気にデータが流れたりしたんですね(要はラグい)。
多少和音を弾くくらいだと問題はなかったですが不安定な感じでした。次に、WebRTCに初めて挑戦してみました。まあ色々頑張ってVuexで状態管理できる感じのを実装しました。
データチャネルを使って通信したんですがこちらはWebSocketよりラグが酷かったですね。
なんか設定をミスったのか知りませんが諦めました。最後に、FirebaseのRealtime Databaseを使いました。遅延は150~200 msって感じで安定性はWebSocketと変わらない感じでした。
まあ、通信にWebSocketを使ってるので当たり前ですね。そこで、セッションはもう諦めてライブ配信を実装することにしました。
また、サーバー使うの面倒だし全部Firebaseで実装することにしました(この時点で完成したら公開しようと思った)。
ラグの軽減のために色々試しましたが最終的に採用したのは以下の手法です。送信側は、大量のパケットを送信しないためにまずデータを50 ms間スタックしてから送信します。
受信側は、それをsetTimeoutを使って(1000+Δt-Δt')ms後に再生するようにします。
ここでΔtは、データの前後の送信時間の差、Δt'は、データの前後の受信時間の差です。
こうすることにより、再生時のラグを緩和することが可能です(間違ってたら教えてください)。
ちなみに、送信側のスタック時間が50 msなのは30、50、100、500、1000 msの中で一番安定した気がするからです。
30 ms以上の時点で和音を1回で送信できるのでかなり効率的でした。
時間が長くなると不安定になる原因はわかりませんでした。データサイズが大きいからというわけではない気がします。
受信側の1000 msは500 ms以上で再生に問題がある場合、ネットワーク環境に問題がある気がしたので
なんとなく倍の1000 msをデフォルトに設定しました。500 msを用意したのは全体の遅延が1秒以内だと宣伝したかったからです。この手法を実装したあとは、ユーザー、投稿等の機能を頑張って実装しました。
細かい解説はしませんが何を使って何を実装したかは説明します。フロントエンド
みんな大好き...かは知りませんがVueを使ってます。
UIフレームワークはVuetifyを使っていてシンプルな感じに作ったつもりです。
PWAにも一応対応していますがWorkboxデフォルトのPrecacheだけですね。音の再生にはhowler.jsを使っていてVuexで管理してます。
PWA対応したのでVuexは要らないと言えば要らないですが、それでもロードに時間がかかるのでありだと思いました(Vuexからのロードは一瞬なので)。
他にもMIDI.jsというものがありますが今回は多分使わなくていいかなと思いました、というか途中で変えたくなかった。
それと音源ファイルを後ほど説明するFirebaseのStorageで管理しようかと思ったんですがなんとなくやめました。肝心のMIDIデバイスへのアクセスは、Web MIDI APIを使っています。
ブラウザでMIDIデバイスを操作できるって何を目指してるんですかね?
まあ、おかげでこういうサービスが作れたんですけど。バックエンド
有名なmBaaSであるFirebaseを使っています。
実際に使うのは初めてだったので、間違った使い方もあると思いますが何をどう使ったのか軽く説明します。Hosting
Vueでビルドしたファイルをホスティングしてます。特に言うことはないです。
Authentication
ツイッター認証を使用しています。こちらも特に言うことはないです。
Firestore
ユーザー情報と投稿詳細が保存されます。
データ構造はユーザー削除をしやすいようにするため、サブコレクションを選択しました。
ユーザードキュメントに投稿とお気に入り一覧のサブコレクションがある感じです。
こちらをかなり参考にしました。具体的な実装方法が気になる方は見てみてください。Realtime Database
投稿のMIDIデータが保存されます。Firestoreも考えましたが遅延時間がかなり不安定だったので断念しました。
もし、このサービスが人気になればすぐストレージが足りなくなるので、ライブ配信が終了したら自動でFirestoreに移すなり方法を考える必要があります(Realtime Databaseはストレージ料金が高いので)。
また、ライブ配信中のホスト状態を共有するためにオンラインであるか、配信が終了したかのフラグも保存してます。
これらは、次に説明するFunctionsのトリガーに使用しています。Functions
以下の4つの関数を作成しました。
- Realtime Databaseでオフラインになったら1分後にオンラインに復帰したか確認して、してなかったら終了フラグを立ててFirestoreの投稿詳細に再生時間を追加する。
- Firestoreで投稿が削除されたらRealtime Databaseも削除する。それと同時に、Firestoreで全ユーザーのお気に入り一覧からその投稿を削除する。
- ユーザーのお気に入り一覧に投稿が追加または削除されたら、その投稿のお気に入り数を変化させる。
- Authenticationでユーザーが削除されたらFirestoreでサブコレクションを含む全てのデータを削除する。
改善点
- アップロード機能の実装
- これはそんなに複雑ではないですが、とりあえずユーザーが増えたりしたら実装します。
- フォロー機能の実装
- お気に入り機能とたいして変わらないですが面倒なので、こちらもユーザーが増えたりしたら実装します。
- MIDIファイルの対応
- インポートやエクスポートができると便利そうですよね。これにより、投稿データの保存をStorageに任せることができるようになるのでユーザーが増えれば節約にもなります(JSONでも可能ではある)。また、リアルタイムでの楽譜表示なんかもできるようになります。ただ、MIDIファイル自体よくわからなかったので諦めました。バイナリはちょっと...
- 検索機能の実装
- Firestoreって全文検索できないんですよね。N-gramでの実装も考えましたがとりあえず今後も実装の予定はありません。ただ、どこかのデータベースから曲名だけ引っ張ってきてタグみたいにするのはありかなと記事を書きながら思いました。これはいつか実装するかも知れないです。
- タイトルの自動生成(実装しました)
SSRは無理なのでFunctions使うしかないと思います。シェアする時にタイトルがわからないのは不便なのでまあユーザーが増えたら実装します。mountedでの自動生成でも問題なく検索で表示されるみたいです。やったことあるのでそのうち対応します。終わりに
今回初めて自分が1から作ったWebサービスを公開することになりました。
もともとは軽く作ってGitHubに公開する程度のつもりでしたが、楽しくなっちゃって1ヶ月以上かけてここまで作ってしまいました...
今までも学校のイベントで使うウェブサイトだったり、友達が運営してるSNSのフロントエンドだけだったりと色々作ってきましたが、それに比べると早く完成しました。1年以上色々作ってるしもういい加減慣れてきた感じなんですかね。
FirebaseはNoSQLに戸惑ったくらいで使いやすかったです。ただ、SQLは本当に便利なんだなとも思いました。今のところは既存の動画投稿サイト等と比べると遅延がかなり少ないことと、音源を変更可能であるというメリットしかありませんが少しでも使っていただけると嬉しいです。また、音源の種類は増やしたいと思っているのでオススメとかあったら教えてください。
最後まで読んでいただきありがとうございました。参考文献
setIntervalとsetTimeoutを調べた結果余分なことになった
WebRTCの簡易シグナリング
WebRTCのデータチャネル解説
Firebaseドキュメント
Cloud Firestoreで「いいね」機能を実装するときの勘所
CloudFunctionsを使ってFirestoreのサブコレクションを削除する
- 投稿日:2020-05-14T11:00:13+09:00
LaravelとVueでSPAを作るときにTypescriptとpugを使う設定をする
Laravel、Vue.js、Typescript、pugでSPAを作ったときのメモ。
docker --version Docker version 19.03.8, build afacb8b扱ったDockerのバージョンは19.03.8。
Fig.1project ├── db ├── web ├── Dockerfile ├── docker-compose.yml └── .env以下の手順で
Fig.1
のような構成のレポジトリを作り、web
ディレクトリの中にLaravelをインストールする。レポジトリと各ファイルを作成
mkdir project_name && cd $_ && touch {docker-compose.yml,Dockerfile,.env}プロジェクトのディレクトリを作成し(
mkdir project_name
)、作成したディレクトリに移動し(cd $_
) 1 、docker-compose.yml
,Dockerfile
,.env
ファイルを作成する(touch {docker-compose.yml,Dockerfile,.env}
)。この時点で
Fig.2
の構成になる。Fig.2project_name ├── Dockerfile ├── docker-compose.yml └── .env各ファイルは以下の内容で書く。
docker-compose.yml
docker-compose.ymlversion: '3' services: web: build: . container_name: ${PROJECT}-web ports: - 80:80 - 3000:3000 volumes: - ./web:/var/www/html/${PROJECT} depends_on: - db db: image: mysql:8 container_name: ${PROJECT}-mysql restart: always environment: MYSQL_DATABASE: ${PROJECT} MYSQL_USER: ${DB_USER} MYSQL_PASSWORD: ${DB_PASSWORD} MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} TZ: ${TZ} ports: - 3306:3306 volumes: - ./db:/var/lib/mysqlDockerfile
DockerfileFROM php:7.4-fpm COPY --from=node:12 COPY --from=composer:latest /usr/bin/composer /usr/bin/composer RUN apt-get update \ && apt-get install -y wget git zip unzip vim libpq-dev \ && : 'Install PHP Extensions' \ && docker-php-ext-install pdo_mysql pdo_pgsql \ WORKDIR /var/www/html/project_nameマルチステージビルド2で Node.js と Composer の Docker公式イメージ を利用する。
.env
.envPROJECT=project_name DB_USER=user_name DB_PASSWORD=password TZ=Asia/Tokyoコンテナを起動
docker-compose up -dこの時点で
Fig.1
の構成になる。コンテナに入る
docker-compose exec web bashコンテナの中では
composer
やnpm
が使える。# composer --version Composer version 1.10.6 2020-05-06 10:28:10 # node --version v12.16.3 # npm --version 6.14.5Laravelをインストール
# composer create-project --prefer-dist laravel/laravel . // バージョンを指定する場合 (e.g. Laravel6 を指定する場合) # composer create-project --prefer-dist laravel/laravel . "6.*"Laravelの設定
web/config/app.php
app.php<?php return [ // 他の設定 'timezone' => 'Asia/Tokyo', 'locale' => 'ja', // 他の設定 ];設定値を変更。
web/.env
.env// 他の設定 DB_CONNECTION=mysql DB_HOST=db DB_PORT=3306 DB_DATABASE=project_name DB_USERNAME=user_name DB_PASSWORD=password // 他の設定データベースに接続するための情報を入れる。
MySQLに接続する場合はDB_CONNECTION=mysql
。
DB_HOST
はdocker-compose.yml
のservices
で設定したdb
とする。
他の設定も.env
で設定したものと同じ。パッケージをインストール
# npm i # npm i vue vue-router pug pug-plain-loader --save-dev # npm i -g typescript # tsc --inittypescriptをグローバルにインストールすると
tsc
コマンドが使えるようになり、# tsc --version Version 3.8.3
tsc --init
とするとtsconfig.json
が生成される。3
tsc --init
をgit init
と混同してtsc init
としないように注意。4ファイルを編集
web/webpack.mix.js
webpack.mix.jsconst mix = require('laravel-mix') /* |-------------------------------------------------------------------------- | Mix Asset Management |-------------------------------------------------------------------------- | | Mix provides a clean, fluent API for defining some Webpack build steps | for your Laravel application. By default, we are compiling the Sass | file for the application as well as bundling up all the JS files. | */ mix .webpackConfig({ module: { rules: [{ test: /\.pug$/, oneOf: [ { resourceQuery: /^\?vue/, use: ['pug-plain-loader'] }, { use: ['raw-loader', 'pug-plain-loader'] } ] }] } }) .browserSync({ proxy: '0.0.0.0:80', open: false, files: [ 'resources/**/*', 'public/**/*' ] }) .ts('resources/ts/app.ts', 'public/js/app.js') .sass('resources/sass/app.scss', 'public/css') .version()pugを扱うための設定も記載する。5
BrowserSyncの設定を記入。6 7
mix.js()
と書かれていた部分をmix.ts()
変えるだけ。8 あとは扱うファイルの変更に従って引数のファイルもjs
からts
に変更する。web/resources/ts/app.ts
app.tsimport Vue from 'vue' import router from './router' import App from './App.vue' new Vue({ el: '#app', router, components: { App }, template: '<App />' })
web/resources/js/app.js
をweb/resources/ts/app.ts
に書き換える。
ここでApp.vue
とrouter.ts
を読み込む。web/resources/ts/App.vue
App.vue<template lang="pug"> div main RouterView </template>ルートコンポーネント。
ここで Vue Router が提供するRouterView
9 を使う。web/resources/ts/router.ts
router.tsimport Vue from 'vue' import VueRouter from 'vue-router' import Foo from './pages/Foo.vue' import Bar from './pages/Bar.vue' Vue.use(VueRouter) const routes: any = [{ path: '/foo', component: Foo }, { path: '/bar', component: Bar } }] const router = new VueRouter({ mode: 'history', routes }) export default routerルーティングの定義の設定。
History モード 10にすることでURLがハッシュなしで設定できる。web/resources/ts/pages/Foo.vue
Foo.vue<template lang="pug"> .foo h1 Foo </template>web/resources/ts/pages/Bar.vue
Bar.vue<template lang="pug"> .bar h1 Bar </template>web/routes/web.php
web.php<?php Route::get('/{any?}', function () { return view('index'); })->where('any', '.+');web/resources/views/index.blade.php
index.blade.php<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>project_name</title> <script src="{{ mix('js/app.js') }}" defer></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"> </head> <body> <div id="app"></div> </body> </html>web/vue-shim.d.ts
vue-shim.d.tsdeclare module "*.vue" { import Vue from "vue"; export default Vue; }
Typescript: IDE reports TS2307: Cannot find module error for Vue components imports
とエラーが出たので作成する。11サーバーを起動
# php artisan serve --host 0.0.0.0 --port 80ビルド
# npm run watch-pollhttp://localhost:3000 にアクセスしてブラウザで確認する。
参考
- 投稿日:2020-05-14T01:08:14+09:00
Vuexでストア内容の永続化を部分的に行う
方法
ストアの永続化にはvuex-persistedstateを使い、ストアのインスタンス生成時のplugins指定で
createPersistedState()
にオプションとしてpaths
を付ける。
https://github.com/robinvdvleuten/vuex-persistedstateimport createPersistedState from "vuex-persistedstate"; const store = new Vuex.Store({ // ... plugins: [createPersistedState({ paths: ['※パス'] ... })], });※ パスはステート内のパスになります。
例:namespaced: true
でモジュールとしてUser.jsを作成した場合は'User'
オプションの詳細はAPI説明で確認ください。
参考にしたページ
- 投稿日:2020-05-14T00:37:58+09:00
vue-cliで環境ごとに異なる静的ファイルをコピーする
最近地味に困ってあれこれ調べたことの記録です。
public配下においた静的ファイルをビルド対象によって切り替えたい
vue-cli
のプロジェクトで静的ファイルをpublic
配下に置いていたのですが、production
で切り替えたい事案が発生しました。具体的にはvue-router
でhistoryモードを使っていたがために.htaccess
を置いていたのですが、開発環境ではサブディレクトリでデプロイ先が切られていたために、ルートディレクトリを指定したかったのです。public/.htaccess<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /test/ [L] <-- 本番では / にしたい… </IfModule>最初ここに
<%= PUBLIC_PATH %>
とか書いてみたのですが、そのまま文字列として出力されてしまいました。どのみちAPIのスタブJSONとかも置いていたので、これらが出力されるのも困るのでこの方法は早々に諦めました。手動でビルドしたファイルを毎回編集したり削除したりするのは死んでも嫌だったので、vue-cli
先生にはそのくらいの気は利かせてくれるようにしたいと思います。「そもそもここに
.htaccess
置くな」と言われれば最もだとは思います。スタブもjsonじゃなくてexpressとかでやった方がスマートではあるのですが、まぁそれはそれとして…。ビルドモードごとに異なる静的ファイルを参照させる
public
配下に静的ファイルを置くのではなくて、ビルド時に環境ごとに用意したディレクトリから静的ファイルを配置するように変えてみます。ディレクトリを用意する
特に指定された箇所があるわけではないので、自分で
src/static/
ディレクトリを作成し、その中で環境ごとにそれぞれサブディレクトリを掘ります。今回は本番環境とそれ以外の環境で分けたかったので、src
配下に以下のようにディレクトリを切りました(不要なところは省略)。% tree -d . ├── static │ ├── develop │ └── production各ディレクトリに、それぞれビルド時にコピーしたいファイルを配置していきます。
プロダクションと開発環境のビルドを分ける
npm run build
だとproduction
でビルドされるので、develop
用のビルドコマンドを追記してそれぞれにビルド出来るようにします。package.json(抜粋)"scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "build:develop": "vue-cli-service build --mode develop", <--追加 "lint": "vue-cli-service lint" },
.env
もそれぞれ用意しておきます。.env.production(抜粋)NODE_ENV=production.env.develop(抜粋)NODE_ENV=developmentこれで
npm run build:develop
を打ったときには、development
でビルドしてくれるようになっているかと思います。ビルドモードに応じたディレクトリ内容を
dist
へコピーする
vue-cli
はデフォルトでpublic
からコピーするようになっているようですが、先程追加したディレクトリからビルドモードに応じたファイルをコピーするように追記します。前述のようにproduction
かそれ以外かで分けられれば今回はOKだったので、以下のように追記しました。vue.config.js(抜粋)const isProduction = process.env.NODE_ENV === "production"; // <-- productionビルドかどうかを判定 const path = require("path"); // <-- ディレクトリ操作をしてなければ追記 module.exports = { assetsDir: "assets", lintOnSave: false, chainWebpack: config => { // 静的ファイルのコピー処理 config.plugin("copy").tap(args => { args[0].push({ from: path.resolve( __dirname, isProduction ? "src/static/production" : "src/static/develop" <--先程作成したディレクトリをそれぞれ指定 ), to: path.resolve(__dirname, "dist"), toType: "dir", ignore: [".DS_Store"] }); return args; }); }, };コピー処理は以下を参照して記述しました。
https://stackoverflow.com/questions/60304092/how-to-copy-assets-with-vue-cli-service-build-commandビルドしてみる
npm run build
時にはsrc/static/production
内が出力され、npm run build:develop
ではsrc/static/develop
が出力されるようになっているのが確認出来ればOKです。今回はproductionか否かしか判定していませんが、環境がもっと増えた場合は判定を追加すればコピー元ディレクトリもそれぞれに用意出来るかと思います。