はじめに
Vue.js&Nuxt.js超入門(秀和システム)を読んで勉強したのでアプリを作った。
「囀暦」というアプリで「てんれき」と読む。Twitterでつぶやかれている内容の中で「○月○日にxxします!」という告知をカレンダー上にプロットするもの。以前にもRailsでちょこっと作っていたが今回改めて作り直してみた次第。
教科書通り作るのとはわけが違う。エラー&エラーに悩まされましたのでここにまとめます。
構成
- Vue.js(フロント)
- Nuxt.js(バック)
- Firebase(認証)
また、ライブラリとして下記を使用。
- Materialize
- fullcalendar
初期設定
$ npx create-nuxt-app tenreki
> Generating Nuxt.js project in /Users/hoshito/RubymineProjects/tenreki
? Project name tenreki
? Project description My primo Nuxt.js project
? Use a custom server framework none
? Choose features to install (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Use a custom UI framework none
? Use a custom test framework none
? Choose rendering mode spa
? Author name hoshito
? Choose a package manager npm
ディレクトリ構成
初めて扱うフレームワークだったので何をどこに置いたらいいのか、あのファイルどこにいったっけと探すのが大変だった。
nuxt.config.jsはいろいろ書いたので全部のせておく。
import pkg from './package'
require('dotenv').config();
const {
CONSUMER_KEY,
CONSUMER_SECRET,
ACCESS_TOKEN_KEY,
ACCESS_TOKEN_SECRET
} = process.env;
export default {
mode: 'spa',
/*
** Headers of the page
*/
head: {
title: pkg.name,
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: pkg.description }
],
script: [
{ src: "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js" },
{ src: "https://unpkg.com/popper.js"},
{ src: "https://unpkg.com/tooltip.js"}
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"}
]
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: [
],
/*
** Plugins to load before mounting the App
*/
plugins: [
{ src: '~/plugins/calendar.js', ssr: false }
],
/*
** Nuxt.js modules
*/
modules: [
'@nuxtjs/dotenv',
],
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {
config.node = {
fs: "empty" // TwitterAPIを使うのに必要
};
}
},
serverMiddleware: [
'~/server'
],
env : {
CONSUMER_KEY,
CONSUMER_SECRET,
ACCESS_TOKEN_KEY,
ACCESS_TOKEN_SECRET
}
}
躓いたところを一つずつ記載。
環境変数を使う
npm install --save @nuxtjs/dotenv
- nuxt.config.jsに情報を記載
-
dotenv
とかenv
とか書かれているところ - この設定だけでけっこう散らばって記載しているけどこれがベストなのかは疑問
-
- .envファイルに環境変数を記載(このファイルはコミットしない)
- 下のような感じ
CONSUMER_KEY="xxx" CONSUMER_SECRET="xxx" ACCESS_TOKEN_KEY="xxx" ACCESS_TOKEN_SECRET="xxx"
- 下のような感じ
サーバ起動前に環境変数を設定する方法もありそうだったが、毎回設定するよりは外部ファイルに置いておく方が好みなのでこの方法を取った。
localStorage, sessionStorageを使用する際はSPAモードで実行
これは書籍にもあった。SPAとSSRの違いがよく分かっていないので(サーバレンダリングの有無だけ?)もう少し勉強する必要がある。
今回はsessionStorageにTwitterのトークン情報を保存している。
We would not normally recommend using LocalStorage, although there may be a few exceptions to this.
Using SessionStorage is normally more preferable than LocalStorage when thinking in terms of security.
Twitterのdevelopページにこう書かれていたので、localStorageではなくsessionStorageを使った。
TwitterAPIを使用するにあたって
npm install --save twitter
-
npm install --save axios fs net tls
(依存するライブラリたち)
fsが使えずに下記のようなエラーが出た。
ERROR in ./node_modules/request/lib/har.js Module not found: Error: Can't resolve 'fs' in '/Users/hoshito/RubymineProjects/tenreki/node_modules/request/lib'
fsはファイルシステムのことで、SPAつまりクライアント側でファイルシステムモジュールを使おうとしてエラーになっていた。
下のように書くとエラーを回避できた。
extend(config, ctx) {
config.node = {
fs: "empty" // TwitterAPIを使うのに必要
};
}
SPAだとこれが動かずSSRだとこれが動かず、というのが多く混乱してしまう。
node-sassは(今だけ?)使えない
sassを使いたくてnpm install node-sass
をしたところ脆弱性エラーで進めなかった。調べてみると最近出たエラーっぽい。解決策はいろいろ出てきている。
https://github.com/sass/node-sass/issues/2625
https://zatsuzen.com/nodejs/node-sass/
package.jsonでnode-gyp
を書き換えろ、という内容であるが、私のpackage.jsonは以下のようなものであり、どこをどう書き換えればよいのか分からずsassの導入を断念しました。
{
"name": "tenreki",
"version": "1.0.0",
"description": "My primo Nuxt.js project",
"author": "hoshito",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate"
},
"dependencies": {
"@fullcalendar/core": "^4.1.0",
"@fullcalendar/daygrid": "^4.1.0",
"@fullcalendar/vue": "^4.1.0",
"@nuxtjs/dotenv": "^1.3.0",
"axios": "^0.18.0",
"cross-env": "^5.2.0",
"firebase": "^5.10.1",
"fs": "0.0.1-security",
"net": "^1.0.2",
"nuxt": "^2.4.0",
"sass": "^1.19.0",
"tar": "^4.4.8",
"tls": "0.0.1",
"twitter": "^1.7.1"
},
"devDependencies": {
"nodemon": "^1.18.9"
}
}
ビジネスロジック
serverMiddlewareを使った。
https://ja.nuxtjs.org/api/configuration-servermiddleware/
page/list.vue
から自身にAPIを叩くように使っている。
methods: {
getTweets(token, secret) {
const self = this;
const params = {token: token, secret: secret};
axios.post("/api/tweets", params).then(res => {
self.events = res.data;
}).catch(err => {
console.log(err);
});
},
import express from 'express'
import bodyParser from 'body-parser'
import {getClient, getTweetInfoForFullCalendar} from '../plugins/twitter'
const app = express()
// requestでjsonを扱えるように設定
app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())
app.post('/tweets', (req, res) => {
let client = getClient(req.body.token, req.body.secret);
// TODO: リクエストパラメータに入れる
let params = {owner_screen_name: "hoshitostar", slug: "Love", count: 200};
client.get('lists/statuses', params).then(tweets => {
let result = getTweetInfoForFullCalendar(tweets);
res.send(result);
}).catch((errors) => {
// TODO: error処理
console.log(errors);
});
})
module.exports = {
path: '/api',
handler: app
}
これも最初全然リクエストを投げられず、投げることができたと思ったらリクエストパラメータ取れなくて苦労した。
server/index.js
はRailsでいう(別にRailsじゃなくてもいいけど)Controllerの役割を担っていると思ったので、ロジックはplugins/twitter
に入れた。本当はclient.get...
の部分も含めて外に出そうと思ったけど、client.getの非同期処理でハマってしまったのでやめにした。
// TwitterAPIのクライアントクラスを取得
function getClient(token, secret) {
const Twitter = require('twitter');
return new Twitter({
consumer_key: process.env.CONSUMER_KEY,
consumer_secret: process.env.CONSUMER_SECRET,
access_token_key: token,
access_token_secret: secret
});
}
// --- 省略 ---
export {getClient, getTweetInfoForFullCalendar}
余談
2019/04/08から技術系のBlogとTwitterを始めてます。このQiitaの記事はブログに記載していたものをまとめたものになります。
Blog: https://hoshitostar.hatenablog.jp/
Twitter: @hoshitostar
Qiita vs ブログの話題はたまに見るけどやっぱり難しいですね。今回はまとまった記事になったのでQiitaに投稿しました。もっと良い書き方があれば教えていただけると嬉しいです。
アウトプットがんばるぞー