- 投稿日:2019-03-07T23:30:07+09:00
Reactのチュートリアル
最終目標
フロントエンドとバックエンドのDB排他を調べてみたい。
今回の目標
Tutorial: Intro to Reactを実施する
https://reactjs.org/tutorial/tutorial.html
https://mae.chab.in/archives/2943環境
Ubuntu 18.04.2 LTS node v8.10.0 npm 3.5.2 create-react-app v2.1.5詳細は、https://qiita.com/y_ohr/items/8afd0852b24356059f2b 参照。
Tutorial: Intro to React
ローカルでやります。
react-app作成
$ create-react-app tutorial-react-app $ cd tutorial-react-app/src配下削除
$ cd src/ $ rm -f * $ cd ..src/index.css作成
src/index.cssbody { font: 14px "Century Gothic", Futura, sans-serif; margin: 20px; } ol, ul { padding-left: 30px; } .board-row:after { clear: both; content: ""; display: table; } .status { margin-bottom: 10px; } .square { background: #fff; border: 1px solid #999; float: left; font-size: 24px; font-weight: bold; line-height: 34px; height: 34px; margin-right: -1px; margin-top: -1px; padding: 0; text-align: center; width: 34px; } .square:focus { outline: none; } .kbd-navigation .square:focus { background: #ddd; } .game { display: flex; flex-direction: row; } .game-info { margin-left: 20px; }src/index.js作成
src/index.jsclass Square extends React.Component { render() { return ( <button className="square"> {/* TODO */} </button> ); } } class Board extends React.Component { renderSquare(i) { return <Square />; } render() { const status = 'Next player: X'; return ( <div> <div className="status">{status}</div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); } } class Game extends React.Component { render() { return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{/* status */}</div> <ol>{/* TODO */}</ol> </div> </div> ); } } // ======================================== ReactDOM.render( <Game />, document.getElementById('root') );index.jsの先頭に3行追加。ここからはdiffにて。
src/index.jsdiff --git a/src/index.js b/src/index.js index 9a06c07..70f7c67 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,7 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; + class Square extends React.Component { render() { return (Passing Data Through Props
src/index.jsdiff --git a/src/index.js b/src/index.js index 70f7c67..43522c3 100644 --- a/src/index.js +++ b/src/index.js @@ -6,7 +6,7 @@ class Square extends React.Component { render() { return ( <button className="square"> - {/* TODO */} + {this.props.value} </button> ); } @@ -14,7 +14,7 @@ class Square extends React.Component { class Board extends React.Component { renderSquare(i) { - return <Square />; + return <Square value={i} />; } render() {Making an Interactive Component
src/index.jsdiff --git a/src/index.js b/src/index.js index 43522c3..b908669 100644 --- a/src/index.js +++ b/src/index.js @@ -3,10 +3,19 @@ import ReactDOM from 'react-dom'; import './index.css'; class Square extends React.Component { + constructor(props) { + super(props); + this.state = { + value: null, + }; + } + render() { return ( - <button className="square"> - {this.props.value} + <button + className="square" + onClick={() => this.setState({ value: 'X' })}> + {this.state.value} </button> ); }Lifting State Up
src/index.jsdiff --git a/src/index.js b/src/index.js index b908669..01c54da 100644 --- a/src/index.js +++ b/src/index.js @@ -3,27 +3,38 @@ import ReactDOM from 'react-dom'; import './index.css'; class Square extends React.Component { - constructor(props) { - super(props); - this.state = { - value: null, - }; - } - render() { return ( <button className="square" - onClick={() => this.setState({ value: 'X' })}> - {this.state.value} + onClick={() => this.props.onClick()}> + {this.props.value} </button> ); } } class Board extends React.Component { + constructor() { + super(); + this.state = { + squares: Array(9).fill(null), + }; + } + + handleClick(i) { + const squares = this.state.squares.slice(); + squares[i] = 'X'; + this.setState({ squares: squares }); + } + renderSquare(i) { - return <Square value={i} />; + return ( + <Square + value={this.state.squares[i]} + onClick={() => this.handleClick(i)} + /> + ); } render() {Function Components
src/index.jsdiff --git a/src/index.js b/src/index.js index 01c54da..5f16f3d 100644 --- a/src/index.js +++ b/src/index.js @@ -2,16 +2,14 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; -class Square extends React.Component { - render() { - return ( - <button - className="square" - onClick={() => this.props.onClick()}> - {this.props.value} - </button> - ); - } +function Square(props) { + return ( + <button + className="square" + onClick={props.onClick}> + {props.value} + </button> + ); } class Board extends React.Component {Taking Turns
src/index.jsdiff --git a/src/index.js b/src/index.js index 5f16f3d..fa7e58a 100644 --- a/src/index.js +++ b/src/index.js @@ -17,13 +17,17 @@ class Board extends React.Component { super(); this.state = { squares: Array(9).fill(null), + xIsNext: true, }; } handleClick(i) { const squares = this.state.squares.slice(); - squares[i] = 'X'; - this.setState({ squares: squares }); + squares[i] = this.state.xIsNext ? 'X' : 'O'; + this.setState({ + squares: squares, + xIsNext: !this.state.xIsNext, + }); } renderSquare(i) { @@ -36,7 +40,7 @@ class Board extends React.Component { } render() { - const status = 'Next player: X'; + const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); return ( <div>Declaring a Winner
src/index.jsdiff --git a/src/index.js b/src/index.js index fa7e58a..34c275d 100644 --- a/src/index.js +++ b/src/index.js @@ -23,6 +23,10 @@ class Board extends React.Component { handleClick(i) { const squares = this.state.squares.slice(); + if(calculateWinner(squares) || squares[i]){ + return; + } + squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ squares: squares, @@ -40,7 +44,13 @@ class Board extends React.Component { } render() { - const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + const winner = calculateWinner(this.state.squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + } return ( <div> @@ -87,3 +97,23 @@ ReactDOM.render( <Game />, document.getElementById('root') ); + +function calculateWinner(squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; +}Lifting State Up, Again
src/index.jsdiff --git a/src/index.js b/src/index.js index 34c275d..65a0010 100644 --- a/src/index.js +++ b/src/index.js @@ -13,48 +13,18 @@ function Square(props) { } class Board extends React.Component { - constructor() { - super(); - this.state = { - squares: Array(9).fill(null), - xIsNext: true, - }; - } - - handleClick(i) { - const squares = this.state.squares.slice(); - if(calculateWinner(squares) || squares[i]){ - return; - } - - squares[i] = this.state.xIsNext ? 'X' : 'O'; - this.setState({ - squares: squares, - xIsNext: !this.state.xIsNext, - }); - } - renderSquare(i) { return ( <Square - value={this.state.squares[i]} - onClick={() => this.handleClick(i)} + value={this.props.squares[i]} + onClick={() => this.props.onClick(i)} /> ); } render() { - const winner = calculateWinner(this.state.squares); - let status; - if (winner) { - status = 'Winner: ' + winner; - } else { - status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); - } - return ( <div> - <div className="status">{status}</div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} @@ -76,14 +46,56 @@ class Board extends React.Component { } class Game extends React.Component { + constructor(props) { + super(props); + this.state = { + history: [{ + squares: Array(9).fill(null), + }], + xIsNext: true, + }; + } + + handleClick(i) { + const history = this.state.history; + const current = history[history.length - 1]; + const squares = current.squares.slice(); + if (calculateWinner(squares) || squares[i]) { + return; + } + + squares[i] = this.state.xIsNext ? 'X' : 'O'; + this.setState({ + history: history.concat([{ + squares: squares, + }]), + xIsNext: !this.state.xIsNext, + }); + } + render() { + const history = this.state.history; + const current = history[history.length - 1]; + const winner = calculateWinner(current.squares); + + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + } + return ( <div className="game"> <div className="game-board"> - <Board /> + <Board + squares={current.squares} + onClick={(i) => this.handleClick(i)} + /> + </div> <div className="game-info"> - <div>{/* status */}</div> + <div>{status}</div> <ol>{/* TODO */}</ol> </div> </div>Showing the Past Moves
src/index.jsdiff --git a/src/index.js b/src/index.js index 65a0010..5afcc7d 100644 --- a/src/index.js +++ b/src/index.js @@ -78,6 +78,15 @@ class Game extends React.Component { const current = history[history.length - 1]; const winner = calculateWinner(current.squares); + const moves = history.map((step, move) => { + const desc = move ? 'Go to move #' + move : 'Go to game start'; + return ( + <li> + <button onClick={() => this.jumpTo(move)}>{desc}</button> + </li> + ); + }); + let status; if (winner) { status = 'Winner: ' + winner; @@ -96,7 +105,7 @@ class Game extends React.Component { </div> <div className="game-info"> <div>{status}</div> - <ol>{/* TODO */}</ol> + <ol>{moves}</ol> </div> </div> );Implementing Time Travel
src/index.jsdiff --git a/src/index.js b/src/index.js index 5afcc7d..0412658 100644 --- a/src/index.js +++ b/src/index.js @@ -52,12 +52,13 @@ class Game extends React.Component { history: [{ squares: Array(9).fill(null), }], + stepNumber: 0, xIsNext: true, }; } handleClick(i) { - const history = this.state.history; + const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { @@ -69,19 +70,27 @@ class Game extends React.Component { history: history.concat([{ squares: squares, }]), + stepNumber: history.length, xIsNext: !this.state.xIsNext, }); } + jumpTo(step) { + this.setState({ + stepNumber: step, + xIsNext: (step % 2) === 0, + }); + } + render() { const history = this.state.history; - const current = history[history.length - 1]; + const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move : 'Go to game start'; return ( - <li> + <li key={move}> <button onClick={() => this.jumpTo(move)}>{desc}</button> </li> );感想
- 写経
- 思ったより時間がかかった=充実したチュートリアルだった。
- inputで動的に仮想DOMが書き換わるようなプログラムを書いてみたい
以上
- 投稿日:2019-03-07T18:37:51+09:00
Nuxt.jsで開発をする場合にまずやるべきこと
概要
僕がNuxt.jsで開発をする場合にまずやることを紹介したいと思います。
インストール
僕はcreate-nuxt-appでインストールします。
npx create-nuxt-app <my-project>入れる拡張は
1. Use a custom server framework → express
2. Choose features to install → Linter / Formatter, Prettier, Axios
3. Use a custom UI framework → bootstrap
4. Use a custom test framework → jest
5. Choose rendering mode → Universalが個人的に好きです。
次にとりあえず入れとけなパッケージもインストールします。
とりあえずsassを入れます。npm i -D node-sass sass-loder次にsass変数をグローバルに使うためのパッケージを入れます。
npm i @nuxtjs/style-resourcesFont Awesome入れます。公式のgitにnuxt用の導入方法が載っています。
npm i @fortawesome/fontawesome-svg-core @fortawesome/free-regular-svg-icon @fortawesome/free-solid-svg-icons @fortawesome/vue-fontawesome適当に書き換える
package.jsonにnpm scriptを一つ追加します。lintfixは絶対追加したほうがいいです、エラー内容を人力で追っていくのは時間の無駄です。
herokuにデプロイする場合はそれも追記します。package.json{ // lintfixを追加 "lintfix": "eslint --fix --ext .js,.vue --ignore-path .gitignore .", // herokuデプロイ用 "heroku-postbuild": "npm run build" }, }.eslintrc.jsにルールを追加します。よく分かってないのですがconsole.logしたら時たまエラーになるので、それの回避です。
.eslintrc.jsrules: { 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' }sass変数のあれやこれやの設定はこれとこれをみたら良いですよ(宣伝)。
終わり
毎回苦労して初期設定してた気がするけど、そんな大した作業量でもなっかたです。
半年くらい前はeslintrcをゴニョゴニョ編集してた気がしますが進化してるっぽいですね。
あと最新のnuxt.js ver2.40以降はランダムにホットリロードが失敗するというちょっとムカつくバグがあるので気をつけてください。
- 投稿日:2019-03-07T18:34:05+09:00
[ES6] import でディレクトリー内すべてのファイルを読み込む
課題
export default しているファイルを読み込むときに以下の書き方をしてたけど
hogeディレクトリ全体を一括で読み込みたいと思った。import hoge1 from './hoge/hoge1.js' import hoge2 from './hoge/hoge2.js'
hoge/index.jsで import 書いてまとめるのもめんどくさい。方法
これでいいのかわからないけど、以下の方法でやってみたら動いた。
import fs from "fs" const hoges = {} fs.readdirSync("./hoge").forEach(async file => { const module = await import(`./hoge/${file}`) hoges[file] = module.default }) hoges.hoge1.hogeFunction()ファイル形式を選択したいときはフィルタリングすればいけるし、サブディレクトリ読み込みたいときは再帰的に読めばいいと思う。
- 投稿日:2019-03-07T12:11:16+09:00
Express + passport(-twitter) + (PM2)で最速でTwitter認証機能付きWebAppを作る
いくつかTwitter認証を使うWebApplicationを作る過程でTwitterでの認証をテンプレート化させコピペで作れるようにしたのでそれをご紹介します。
この記事で出来るようになること
- Twitter認証機能を持ったExpressAppをサクッと作れるようになる
- WebAppとしても,ネイティブ使うAPIエンドポイントとしても使える
- (後日出来るようになるかも) Swift4でのiOSネイティブからの当APIの使い方がサクッとわかる
前提
コピペで作るためには以下を満たしておく必要があります。
TwitterAppを作成できる
- 以前にTwitterDeveloperの大規模な変更があって、TwitterAPPを作るためには作文を書かなきゃいけなかった気がします。
- TwitterDeveloperにアクセスしてAppのリストとCreate an app ボタンが表示されればOK.
express-generatorが導入されている
Expressプロジェクトをコマンド一つで生成してくれる便利な奴
導入とか(公式)PM2が導入されている
ProcessManager(多分)
nodeプロジェクトを手軽にデーモン化させられる奴(起動/ホットリロード/クラッシュ時の再起動/startup等々)
導入とか(公式)ドメインが用意されていてWebAppを公開できるようになっている
長くなるので省きますが、https://xxx.your.domain/ とかにアクセスして生成したてのExpressを起動した際にExpress Welcome to Expressが表示されればOK.
今回は
https://qiita.your.domain/というURLでAPIを公開してみたいと思います。TwitterAppを作成
twitterDeveloperからCreate an appを押し、App作成画面へ移ります。
必要事項を記述し、
Allow this application to be used to sign in with Twitter項目のEnable Sign in with Twitterにチェックを入れます。
Callback URLs項目がアクティブになるので、ここにhttps://qiita.your.domain/auth/twitter/callbackを入力します。
※ /auth/twitter/callbackの部分は本来任意ですが、これを変更する場合後述のルーティングのコードも変更する必要があります。項目記述後、流れに沿ってAppを作成し最終的に自分のAppsDashboardに作成したAppが表示されていれば準備完了。
AppsDashboardからAppを開き、keys and tokensのConsumer API Keysを確認できるようにしておきます。
実装
Express
まずはExpressを初期化します。
$ express -e qiita-twitter $ cd qiita-twitter $ npm i次にpm2を初期化します
$ pm2 init $ vi ecosystem.config.jspm2
pm2の設定を変更します。
ecosystem.config.jsmodule.exports = { apps: [ { name: 'qiita-twitter', script: 'npm', args: 'start', watch: true, env: { PORT: 3000, HOST_NAME: 'qiita.your.domain', USE_SSL: true, AUTH: { twitter: { active: true, CONSUMER_KEY: 'Your CONSUMER_KEY', CONSUMER_SECRET: 'Your CONSUMER_SECRET', }, } } } ] };nameはpm2上で管理するApp名です。
pm2 logs qiita-twitterとかするとログを見れたりします。scriptとargsは起動時のコマンドです。
$ npm startで起動するので、このように記述します。watchはホットリロード(ファイルを変更すると自動再起動してくれるやつ)を使うか否かです。
trueにすると起動ディレクトリ以下全てのファイルを、trueの代わりに['routes', 'models']みたいにするとroutesとmodelsディレクトリ以下を監視してくれるようです。で、重要なのがenvです。
USE_SSLとかは後ほどコールバックURLを組み立てるときに使っていますが、そこでURLを直打ちしてしまえば必要ありません。
AUTH=>twitter=>activeみたいになってるのは、他にもlocal認証(よくあるメアドとPWの認証)やFaceBook認証とか使う時に分かるやすくなるメリットがあります。
この辺はコードと相談しながらお好みで変更しましょう。
gitで管理する場合ecosystem.config.jsはgitignoreに突っ込むなりしてgit監視下から外しましょう
個人的にはecosystem.config.js.sampleというenvの値を空白にしたものを用意しておき、これをgitに上げておくのがベターかなと思います。
休憩
一先ずここで起動確認をしておきましょう。
$ pm2 start ecosystem.config.js $ pm2 logs qiita-twitter適当なブラウザで
https://qiita.your.domainにアクセスしてExpress Welcome to Experssが表示されていればOKです。
Twitter認証
いよいよTwitter認証のroutingを実装します。
以下個人的に使っているディレクトリ構成となります。
お試す前にコードを読んで各自改変することをお勧めします。
(contorllers内でrouting設定しちゃってたりと健康被害を及ぼす可能性があります)packageインストール
$ npm i -S express-session passport passport-twitterルーティング
$ mkdir controllers $ touch controllers/authController.js $ touch controllers/userController.jscontrollers/authController.jsconst passport = require('passport'); const TwitterStrategy = require('passport-twitter').Strategy; const uc = require('./userController'); module.exports = { initialize: function(app) { this.app = app; this.authSettings = JSON.parse(process.env.AUTH); app.use(passport.initialize()); app.use(passport.session()); passport.serializeUser(uc.userSerialize); passport.deserializeUser(uc.userDeserialize); this.twitterActivate(); }, twitterActivate: function() { if(!this.authSettings.twitter.active) { console.log('Twitter Auth is not activated'); return; } let consumerKey = this.authSettings.twitter.CONSUMER_KEY; let consumerSecret = this.authSettings.twitter.CONSUMER_SECRET; let callbackURL = process.env.USE_SSL ? 'https' : 'http'; callbackURL += '://'; callbackURL += process.env.HOST_NAME; callbackURL += callbackURL.endsWith('/') ? '' : '/'; callbackURL += 'auth/twitter/callback'; passport.use(new TwitterStrategy({ consumerKey: consumerKey, consumerSecret: consumerSecret, callbackURL: callbackURL }, uc.twitterAuth)); this.app.get('/auth/twitter', passport.authenticate('twitter')); this.app.get('/auth/twitter/callback', passport.authenticate('twitter'), async function(req, res) { const user = req.session.passport.user; res.json(user); }); console.log('Twitter Auth is activated'); }, }controllers/userController.jsconst ac = require('./authController'); module.exports = { twitterAuth: async function(token, tokenSecret, profile, done) { //本来ここでユーザー検索して、存在したらそのObjectを返す //存在しなければ新規ユーザー作成 //みたいなことをする。 //tokenとtokenSecretそのままobjectに格納してログイン状態として扱うのは良くないです。 const userObj = { twitterId: profile.id, screenName: profile.username, token: token, tokenSecret: tokenSecret }; //ログ表示もいくない。 console.log(userObj); done(null, userObj); }, userSerialize: async function(user, done) { done(null, user); }, userDeserialize: async function(id, done) { console.log('user deserializing'); console.log(id); done(null, id); }, };app.jsから読み込み/初期化
$ vi app.js最後にapp.jsを編集し、読み込み・初期化を行います。
app.js//最魚のrequire群でsessionとauthControllerを読み込み(6行目くらい) const session = require('express-session'); const authController = require('./controllers/authController'); //requireしたモジュール系をuseしている部分でsessionの読み込みをしてからauthControllerをinitialize(順番大事)を追加(23行目くらい) app.use(session({ secret: 'kokoikeapi', resave: false, saveUninitialized: true })); authController.initialize(app);動作確認
最後にコールバックしてきたら、ユーザーのTweet一覧を表示してみましょう.
さらに体に良くないですが、authController内でTwitterモジュールを読み込んでお試し表示してみましょう。
$ npm i -S twittercontrollers/authController.js//4行目くらいでrequireしておきます。 const Twitter = require('twitter'); // 36行目くらいの/auth/twitter/callbackのルーティングの処理の中身を変更 const user = req.session.passport.user; const consumer = JSON.parse(process.env.AUTH).twitter; const client = new Twitter({ consumer_key: consumer.CONSUMER_KEY, consumer_secret: consumer.CONSUMER_SECRET, access_token_key: user.token, access_token_secret: user.tokenSecret }); const params = { screen_name: user.screenName}; client.get('statuses/user_timeline', params, function(err, tweets, response) { if(!err) { res.json(tweets); } else { res.json(err); } }); //res.json(user);これで認証が終わってcallbackしてくると自分のツイート一覧のjsonがresponseに流れます。
本来の使い方としては、userController時点でuserデータを作成し、doneにそのuserデータを渡しておきます。
すると、authControllerで取得するuserにはidが含まれているので、これを使ってユーザーのプロフィールページやトップページにリダイレクトするといいと思います。controllers/authController.jsthis.app.get('/auth/twitter/callback', passport.authenticate('twitter'), async function(req, res) { //トップへリダイレクトする場合 //res.redirect('/'); //user個別ページへリダイレクトする const user = req.session.passport.user; res.redirect('/users/' + user.id); //みたいな。 });
- 投稿日:2019-03-07T10:58:45+09:00
TypeScriptでSyntaxError: Unexpected token {
ちょいハマりした。
コイツ。
(node:93711) UnhandledPromiseRejectionWarning: /Users/username/project/src/entity/SomeEntity.ts:1 (function (exports, require, module, __filename, __dirname) { import { ^ SyntaxError: Unexpected token { at new Script (vm.js:79:7) at createScript (vm.js:251:10) at Object.runInThisContext (vm.js:303:10) at Module._compile (internal/modules/cjs/loader.js:657:28) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Module.require (internal/modules/cjs/loader.js:637:17) at require (internal/modules/cjs/helpers.js:22:18) (node:93711) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1) (node:93711) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.こういうエラーがいちばん嫌い。
環境
typescript ^3.3.3333
typeorm ^0.2.14経緯
typeormセットアップして、
それ用のEntityを作って、
tscして、
node dist/index.jsしたら上記エラーが出た。
エラー内容的にはNodeに対応してない文法が存在しているって感じだけど、そもそもtsからjsにコンパイル済みだし、意味不明。
typescriptのコンフィグにもちゃんと下記は入れてる。
tsconfig.json"module": "commonjs", "target": "es6",意味わかんねーよバーカ!
結論
typeormのドキュメントが分かりづらい。
typeormを利用する際、下記のようなconfigがプロジェクトルートに必要になる。
(厳密に言うと必須ではない。コード内に書くこともできるので)ormconfig.json{ "type": "mysql", "host": "localhost", "port": 3306, "username": "root", "password": "hogerahogera", "database": "mogeramogera", "synchronize": true, "logging": false, "entities": [ "src/entity/**/*.ts" ], "migrations": [ "src/migration/**/*.ts" ], "subscribers": [ "src/subscriber/**/*.ts" ] }で、entitiesプロパティがプロジェクト内のEntity(他で言うmodel。要はテーブルを表す)の場所を指す。
のだが、僕のようにコンパイルしてからコンパイル後のファイルをnodeで動かしてる人の場合、上記の設定のままだとコンパイル後のjsファイルから、コンパイル前のtsファイルであるentityを読みに行ってしまう。
https://stackoverflow.com/questions/50361948/syntaxerror-unexpected-token-import-typeorm-entity
そんなん知るかよって感じ。
まあ徹頭徹尾ドキュメントを読んでない僕が悪いっちゃ悪いのだが、もうちょっと書きようはあると思う。
一応それっぽいことは、ドキュメント内のここに書いてあった。
修正後はこんな感じになります。
ormconfig.json{ "type": "mysql", "host": "localhost", "port": 3306, "username": "root", "password": "calamity1jane", "database": "i-sharing", "synchronize": true, "logging": false, "entities": [ "dist/entity/**/*.js" <- ここが変わってる ], "migrations": [ "src/migration/**/*.ts" ], "subscribers": [ "src/subscriber/**/*.ts" ] }うまく動いた!
typescriptになれてる人ならハマらないのかな?
こういうconfig系で何度も躓くので、実践的なtsプロジェクトの作り方とか落ち着いたらまとめます。
- 投稿日:2019-03-07T10:58:45+09:00
TypeScriptとTypeORMの組み合わせでSyntaxError: Unexpected token {
ちょいハマりした。
コイツ。
(node:93711) UnhandledPromiseRejectionWarning: /Users/username/project/src/entity/SomeEntity.ts:1 (function (exports, require, module, __filename, __dirname) { import { ^ SyntaxError: Unexpected token { at new Script (vm.js:79:7) at createScript (vm.js:251:10) at Object.runInThisContext (vm.js:303:10) at Module._compile (internal/modules/cjs/loader.js:657:28) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Module.require (internal/modules/cjs/loader.js:637:17) at require (internal/modules/cjs/helpers.js:22:18) (node:93711) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1) (node:93711) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.こういうエラーがいちばん嫌い。
環境
typescript ^3.3.3333
typeorm ^0.2.14経緯
typeormセットアップして、
それ用のEntityを作って、
tscして、
node dist/index.jsしたら上記エラーが出た。
エラー内容的にはNodeに対応してない文法が存在しているって感じだけど、そもそもtsからjsにコンパイル済みだし、意味不明。
typescriptのコンフィグにもちゃんと下記は入れてる。
tsconfig.json"module": "commonjs", "target": "es6",意味わかんねーよバーカ!
結論
typeormのドキュメントが分かりづらい。
typeormを利用する際、下記のようなconfigがプロジェクトルートに必要になる。
(厳密に言うと必須ではない。コード内に書くこともできるので)ormconfig.json{ "type": "mysql", "host": "localhost", "port": 3306, "username": "root", "password": "hogerahogera", "database": "mogeramogera", "synchronize": true, "logging": false, "entities": [ "src/entity/**/*.ts" ], "migrations": [ "src/migration/**/*.ts" ], "subscribers": [ "src/subscriber/**/*.ts" ] }で、entitiesプロパティがプロジェクト内のEntity(他で言うmodel。要はテーブルを表す)の場所を指す。
のだが、僕のようにコンパイルしてからコンパイル後のファイルをnodeで動かしてる人の場合、上記の設定のままだとコンパイル後のjsファイルから、コンパイル前のtsファイルであるentityを読みに行ってしまう。
https://stackoverflow.com/questions/50361948/syntaxerror-unexpected-token-import-typeorm-entity
そんなん知るかよって感じ。
まあ徹頭徹尾ドキュメントを読んでない僕が悪いっちゃ悪いのだが、もうちょっと書きようはあると思う。
一応それっぽいことは、ドキュメント内のここに書いてあった。
修正後はこんな感じになります。
ormconfig.json{ "type": "mysql", "host": "localhost", "port": 3306, "username": "root", "password": "calamity1jane", "database": "i-sharing", "synchronize": true, "logging": false, "entities": [ "dist/entity/**/*.js" <- ここが変わってる ], "migrations": [ "src/migration/**/*.ts" ], "subscribers": [ "src/subscriber/**/*.ts" ] }うまく動いた!
typescriptになれてる人ならハマらないのかな?
こういうconfig系で何度も躓くので、実践的なtsプロジェクトの作り方とか落ち着いたらまとめます。
- 投稿日:2019-03-07T10:17:13+09:00
AWS Lambda�(Node.js)でGoogle Cloud Natural Language APIを使用してエラー"Failed to load gRPC"が発生したときの対応方法
環境/ライブラリ
- AWS Lambda(Node.js 8.10)
- @google-cloud/language
事象
AWS Lambda(Node.js)上の関数で、Google Cloud Natural Language APIを呼び出すとエラー"Failed to load gRPC"が発生する。
エラーログ例
Error: Failed to load gRPC binary module because it was not installed for the current system Expected directory: node-v57-linux-x64-glibc Found: [node-v57-darwin-x64-unknown] This problem can often be fixed by running "npm rebuild" on the current system Original error: Cannot find module '/opt/nodejs/node_modules/grpc/src/node/extension_binary/node-v57-linux-x64-glibc/grpc_node.node' at Object.<anonymous> (/opt/nodejs/node_modules/grpc/src/grpc_extension.js:53:17) at Module._compile (module.js:652:30) at Object.Module._extensions..js (module.js:663:10) at Module.load (module.js:565:32) at tryModuleLoad (module.js:505:12) at Function.Module._load (module.js:497:3) at Module.require (module.js:596:17) at require (internal/module.js:11:18) at Object.<anonymous> (/opt/nodejs/node_modules/grpc/src/client_interceptors.js:144:12) at Module._compile (module.js:652:30) code: 'MODULE_NOT_FOUND対応方法
以下のコマンドでリビルドする。targetはNode.jsのバージョンに合わせる
npm rebuild grpc —target=8.1.0 --target_arch=x64 --target_platform=linux --target_libc=glibc感想
こういうエラーが起きると、Pythonにすれば良かったと思う(逆もまた然り)
参照
- 投稿日:2019-03-07T00:50:07+09:00
node.jsでタイムゾーンの変換処理にdate-fns-timezoneを利用する
date-fns-timezone
node.jsで日付処理するにはdate-fnsを利用すると便利に実装できます。
参考: https://www.webprofessional.jp/date-fns-javascript-date-library/
ですがこのライブラリのみだとタイムゾーンの計算がうまくできず何か使えるものがないか調べてみたところ、date-fns-timezoneというライブラリが良さげだったので使ってみました。
今回やりたいこと
日付がUTCで格納されているデータソース(DynamoDB)に対して日本時間の日付範囲でクエリをかけたい
指定日に含まれるデータを取得するDynamoDBクエリを組み立てるconst targetDate = new Date(); // '2019-03-06T11:36:25.533Z' (ここはUTC) // 日本時間の0時 const d1 = startOfDay(targetDate); // '2019-03-05T15:00:00.000Z' // 日本時間の翌日0時 const d2 = endOfDay(targetDate); // '2019-03-06T15:00:00.000Z' const param = { TableName : 'Comment', KeyConditionExpression: "#key= :key AND #date >= :d1 AND #date < :d2", ExpressionAttributeNames:{ "#key": "key", "#date": "date" }, ExpressionAttributeValues: { ":key": key, ":d1": d1, ":d2": d2, } };使ってみる
date-fns-timezoneのインストール
npmからインストールできます。
$ npm install date-fns-timezone依存ライブラリとして date-fns もインストールされるのでこちらも利用します。
今回利用したのはライブラリバージョンは以下です。
- date-fns-timezone : 0.1.4
- date-fns : 1.30.1
日本時間での0時を計算する
// import const { startOfDay, addDays } = require('date-fns'); const { convertToTimeZone } = require('date-fns-timezone'); // タイムゾーン定義 const timeZone = "Asia/Tokyo"; // 現在時刻(UTC)を取得 const targetDate = new Date(); // TimeZone付きDateに変換 const zonedTargetDate = convertToTimeZone(targetDate, { timeZone: timeZone }); // date-fnsでstartOfDayを計算 const startDate = startOfDay(zonedTargetDate); const endDate = addDays(startDate, 1); // 文字列化 const d1 = startDate.toISOString(); // => '2019-03-05T15:00:00.000Z' const d2 = endDate.toISOString(); // => '2019-03-06T15:00:00.000Z'convertToTimeZoneを利用するとTimeZone情報を持ったDateに拡張されるため、date-fnsのstartOfDay()などもそのまま使えるのが便利ですね。
指定のフォーマットでフォーマットするにはdate-fns-timezoneのformatToTimeZoneも使えます。詳細ドキュメントはこちら。
https://www.npmjs.com/package/date-fns-timezone
- 投稿日:2019-03-07T00:12:14+09:00
サブディレクトリ URL で起動する Angular の Web アプリで API 通信を実装したらハマった話+解消手順
概要
Angular (+ express) + Node.js でバックエンドに API 通信をさせた時にエラーでハマった経緯と、その解消策の備忘録
先に結論から言うと
「Nginx の設定をミスったら、バックエンド通信に失敗するよ」と言う話。
経緯
Angular + express で MEAN スタックで作成した Web アプリ。
今までは普通に、開発 ➡️ サーバへデプロイ ➡️ 実機で動作確認、で問題なく動いていた。
ただし、ドメイン取得して、サーバのトップレベルで動作する Web サイトとして作っていた。今回は、サブディレクトリでのみ動作する Web サイトとして開発。
つまり...
https://example.com▶︎ 別の Web サイト(静的 HTML ページ)https://example.com/my-app▶︎ 今回の Angular サイトとしようとした。
実際に、Angular でアプリを開発して配置。事象と解消手順
事象1: 画面表示でエラー発生
https://example.com/my-appにアクセスしたら、テキストだけ出るけど装飾系が描画されない。と言う中途半端な状態で画面表示。表示が壊れている状態。。。
もちろん、https://example.comは問題なく規定の Web ページが表示される。▶︎ 解消: 「base href」を設定したら解消
index.html の
<base href="/">が<base href="/my-app/">となると解消する。
しかし、ローカル環境では必要ない...。
なので、以下のとおり、package.json のビルドコマンドに--base-hrefオプションを指定することで、ビルド時に「base href」を変え分けられるようにした。package.json: "scripts": { // 起動コマンド "dev": "node ./dist/server/server.js", // ビルドコマンド (ng build に --base-href オプション追加) "build": "npm run build:client && npm run build:server", "build:client": "ng build --prod --base-href=/my-app/ && ng run my-app:server", "build:server": "tsc --project server --outDir dist --allowjs true", : }, :
事象2: バックエンドへの通信時にエラー発生
「事象1」が解消し、画面表示はできるようになったが、
今度は、API でバックエンドにリスト取得する処理を実装したら以下のエラー事象が発生。ローカルだと取得できてたのに、サーバだとリスト取得できない。。。(本来なら JSON のリストデータが返却される予定)
ブラウザコンソールを開いてみたら、以下のエラーログを吐いていた。# 発生したエラー error: SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse... headers: t {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ} message: "Http failure during parsing for https://iexample.com/portal/api/my-app/api/heros" name: "HttpErrorResponse" ok: false status: 200 statusText: "OK" url: "https://example.com/my-app/api/heros"バックエンド通信で、HTTP 結果コードは 200 が返っているが、返却された後に何かエラーが出ている。
errorの部分を開いて詳しく見てみるとSyntaxError: Unexpected token < in JSON at position 0 at JSON.parse (<anonymous>) at XMLHttpRequestさらに、すぐ下に
text: "<!DOCTYPE html><html lang="ja"><head>↵ <meta charset="utf-8">↵ <title>My App</title>↵JSON データ、もしくは、 JSON のリストデータが返却されるべき箇所で、HTML を返しているために、JSON.parse() でエラーが発生したことが原因らしい。
長く悩んだが、よく考えたらそりゃそうだ。Web サービスで画面用のルーティングしか設定してなかった。
▶︎ 解消: Nginx のルーティングに API 専用のルートを追加したら解消
/etc/nginx/conf.d/default.confserver { # http を https にリダイレクト listen 80; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name localhost; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # *** 追加: 以下の API 用ルーティングを追加 ↓↓↓ ****** location /my-app/api/ { proxy_pass http://127.0.0.1:3000/api/; proxy_redirect default; } # *** 追加 ↑↑↑ ********************************* location /my-app/ { proxy_pass http://127.0.0.1:3000/; proxy_redirect default; } location / { root /var/www; index index.php index.html index.htm; } # 404, 50x 等 エラー系のルートが以下にある }設定後、
nginx -s reloadで Nginx を再起動。
Angualar も再デプロイして起動し直し、ブラウザから再度アクセスしてみたら、
エラーは出なくなり、問題なく目的の JSON リストデータが返却されて、画面に表示された。ハマりどころ
そもそも「Angular の設定方法やロジックに原因がある」として、ずーーーーっと、アプリ設定のネット記事を漁っていたが、そこが間違いだった。
原因の切り分けフロー
HTTP 結果は 200 で、エラーの吐き方が、JSON パースエラー: クラサバ通信はできている
→ リクエストとレスポンスの機構を改めて確認: SSR で実装しているので、リクエスト/レスポンスは HTML を返却するのと JSON データを返却する2種類がある
→ ローカル環境で、ブラウザに GET メソッドの API を叩いてみる: JSON データが返り、画面に表示されることを確認
→ 公開サーバで、同じくブラウザに GET メソッドの API を叩いてみる: トップページが再表示された = HTML が返却された
→ Web サーバのリクエスト URI に対するルーティング先を修正すべきことに気づく
→ Nginx の config を修正して動作確認
→ 問題解消 !!!こんな感じだった。
エラーの切り分けを丁寧に行い、対応していくことは大事だ。
以上、Web サービスの設定は、大した内容ではないが間違えると結構ハマる。
