javascriptは、jQueryを少し触った程度の私ですが、Riot.jsの公式でも紹介されているこちらのLive Ajax Searchをベースに、ES2015やRiot.jsを使ってwebアプリを作成しました。
この記事では、どんな資料を参考にしたか、どんなことに躓いたか、備忘的に記したいと思います。
完成したwebアプリはこちらに公開していますので、興味がお有りでしたら遊んでみてください。
(はじめてgithubにリポジトリを公開しました。.gitignore
を作る前にcommit
していたため、node_modulesフォルダもアップロードされてしまい、ドキドキしながら対処しました。)
git clone https://github.com/higeaaa/riot-webpack.git
cd riot-webpack
npm install
npm start
環境
- node v7.2.0
- npm v3.10.9
"devDependencies": {
"babel": "^6.5.2",
"babel-core": "^6.21.0",
"babel-loader": "^6.2.10",
"babel-preset-es2015-riot": "^1.1.0",
"eslint": "^3.13.1",
"eslint-loader": "^1.6.1",
"eslint-plugin-riot": "^0.1.6",
"file-loader": "^0.9.0",
"font-awesome": "^4.7.0",
"font-awesome-webpack": "0.0.4",
"node-sass": "^4.3.0",
"riot": "^3.0.7",
"riotjs-loader": "^4.0.0",
"url-loader": "^0.5.7",
"webpack": "^1.14.0",
"webpack-dev-server": "^1.16.2"
}
環境構築
webpackの設定
たくさん困った結果がwebpack.config.js
にあらわれています。
- cors
- ES6からES5にトランスパイルされない
- font-awesomeを使いたい
const webpack = require('webpack')
module.exports = {
entry: './app/main.js',
output: {
path: '/public',
publicPath: '/public/',
filename: 'bundle.js',
crossOriginLoading: 'anonymous'
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.ProvidePlugin({ riot: 'riot' })
],
module: {
preLoaders: [
{
// test : /\.js$|.tag$|.es6$/, <= k-kuwaharaさんからご指摘いただき、以下に修正
test : /\.js|\.tag|\.es6$/,
exclude: /node_modules/,
loader : 'eslint-loader'
},
{
test : /\.tag$/,
exclude: /node_modules/,
loader : 'riotjs-loader'
}
],
loaders: [
{
test : /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: "url-loader?limit=10000&minetype=application/font-woff"
},
{ test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: "file-loader"
}
],
postLoaders: [
{
// test : /\.js$|\.tag$/, <= k-kuwaharaさんからご指摘いただき、以下に修正
test : /\.js|\.tag$/,
exclude: /node_modules/,
loader : 'babel-loader',
query : {
presets: ['es2015-riot']
}
}
]
},
devServer: {
historyApiFallback: true,
watchOptions: { aggregateTimeout: 300, poll: 1000 },
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
}
}
}
1. CORS問題 => 解決
fetch()をすると、chromeにCORSがなんちゃらと怒られ、APIで取得して表示ができない。
CORSについてはこちらがわかりやすかった。
webpack.config.js
のdevServer
のheader
にいろいろ設定しましたが、うまくいかなかったので、結局google chromeの拡張でCORS Toggleを利用することで、解決しました。
2. ES6からES5にトランスパイルされない => 未解決 => 解決
CORS問題が解決し、動いているのを確認していたら、javascriptがES5にトランスパイルされておらず、chromeがES6を直に読み込んで動いていることがわかりました。例えば、ファイルsearch.tag
に<script type='babel'>
とすると、riotのイベントハンドラとして作ったsearch(e){}
が、syntax errorとなってしまい、困ってしまいました。
(ちなみに、<script>
とすると、riotjs-loader
がthis.search = function(e) {}
としてくれます。)
その後、loader
のquery
にいろいろなtype
やpresets
を設定しましたが、うまくいかず。また、preLoader
(最初に読み込むローダーを指定する)にriotjs-loader
、postLoader
(最後に読み込むローダーを指定する)にbabel-loader
と設定しましたが、やはりbabelがES5にトランスパイルしてくれません。
調べていたところ、こちらで話題になってもいましたが、結局解決できず、ES6のままブラウザに読み込ますようにします。
<<2017/1/24 追記>>
webpack.config.jsの設定誤りであることがご指摘でわかりました。上記のwebpack.config.jsには反映済みです。k-kuwaharaさん、ありがとうございました。
3. font-awesomeを使いたい => 解決
なんとなく使ってみたかった。公式HPのget-startedを見ると、CDNを利用するか、ダウンロード、railsならgemがあるよと書いてある。調べてみるとNPMのパッケージがあったので使った。
webpack.config.js
でローダーの設定をして、jsファイルでrequireすればよい。今回は、webpackが最初に読み込むjsファイルmain.js
でrequireする。
sassを使いたい
なんとなく使ってみたかった。node-sassを入れて、各tagファイルのstyle
にtype='scss'
と追記する。
アプリ作成
基本はこちらのLive Ajax Searchをベースに、itunes APIを利用して、itunesにある曲を検索する、という簡単なアプリを作りました。riotを体感することも楽しかったのですが、debounce()
やfetch()
に触れることができて、楽しかったです。
アプリは、2つのtagファイルで構成されます。
-
search.tag
: 検索フォームやitunes APIを使って情報を取得する -
result-cards
:search.tag
で取得した検索結果をカード形式で表示する
<search>
<form onsubmit={ search }>
<label>
<input type="search"
oninput={ search }
onchange={ search }
ref="s"
placeholder=" Search music tracks"
id="search">
</label>
</form>
<div if={ isLoading } class="loader">
<div class="circle"> </div>
</div>
<p class="error" if={ error }>{ error }</p>
<script>
require('./results-cards.tag');
var lastSearch = null;
var searchUrl = null;
// Reset tag attributes to hide the errors and cleaning the results list
let reset = () => {
this.results = [];
this.error = false;
searchUrl = null;
}
// generate url for itunes API.
let getApiUrl = searchWord => {
let urlParams = {
country: 'jp',
lang: 'ja_jp',
media: 'music',
entity: 'song',
app: 'music',
limit: 15,
term: searchWord
};
let url = 'https://itunes.apple.com/search';
let keyValue, pat, key, value;
for (key in urlParams) {
key = encodeURIComponent(key);
value = encodeURIComponent(urlParams[key]);
keyValue = key + '=' + value;
url += (url.length > 32 ? '&' : '?') + keyValue;
};
return url;
}
// Debounce the api requests
let doApiRequest = debounce( searchUrl => {
fetch(searchUrl, { method: 'get', mode: 'cors'})
.then( res => { return res.json() })
.then( data => {
reset();
var r;
if (this.refs.s.value) {
if (data.resultCount) this.results = data.results;
else this.error = data.Error;
}
this.isLoading = false;
this.update();
r = this.results;
riot.mount('results-cards', { r });
});
}, 300, false);
// Public API/method
this.results = [];
// Search callback
search(e) {
let searchWord = this.refs.s.value;
if (!searchWord) {
reset();
setTimeout(function(){
var r = [];
riot.mount('results-cards', { r });
}, 1000);
} else if (lastSearch !== searchWord) {
reset();
this.isLoading = true;
searchUrl = getApiUrl(searchWord)
doApiRequest(searchUrl);
};
lastSearch = searchWord ;
}
</script>
<style>
省略
</style>
</search>
<results-cards riot-tag='resultCards'>
<div if={ opts.r.length } class="container">
<a each={ opts.r } href="{ collectionViewUrl }" target="_blank">
<div class="content">
<img class="artwork" src={ artworkUrl100 } alt="">
<div class="description">
<label>{ trackName }</label>
<span>{ collectionName } </span>
<span>{ artistName }</span>
<div class="logos">
<img src="/public/img/Get_it_on_iTunes_Badge_JP_1214.svg" alt="">
<img if={ isStreamable } src="/public/img/JP_Listen_on_Apple_Music_Badge.svg" alt="">
</div>
</div>
</div>
</a>
</div>
<style type="scss">
省略
</style>
</results-cards>
Riotの感想は次の通りです。
- イベントハンドラがわかりやすい
- refsがわかりやすい
1. イベントハンドラがわかりやすい
input oninput={search}
でinput要素に入力があったらsearch(e)
が発火する、という具合でとてもわかりやすい。
2. refsがわかりやすい
input ref='s'
とすれば、javascriptからthis.refs.s.value
でinput要素に入力された値が取得できる、という具合でとてもわかりやすい。
また、次のことを備忘として記します。
3. itunes API
4. cssのflexbox
3. itunes API
アップルが詳しい。ただ、entity
をsongにしてもmusicにしても検索結果は一緒だった。検索結果の順番の指定方法はわからなかった。
results-cardsでapple musicでも視聴可能な場合、apple musicのロゴを表示している。この判定にitunes APIの戻り値にあるisStreamable
がtrue
であればapple musicで視聴可能としました。ドキュメントに記載はありませんでしたが、数曲調べたところ、多分あっていると思います。
4. cssのflexbox
results-cards
の表示や、カードの中身について、flexboxを利用しました。コリスが詳しい。
親要素をdisplay: 'flex'
など、中身をどう配置したいかを設定し、あとは子要素で設定する。floatよりもなんとなくイメージがつかみやすい。
次にやること
- Rails5のAPIと、このようなjsの環境を使って何か作りたいです。
あとがき
riotは、javascriptを学んでいくとやれることが簡単にできそうな気がして、わくわくします。