そーす

I'm a programmer in Fukuoka. Please contact me saubre.app[at]gmail.com or Twitter DM.

オブジェクトの配列の重複削除

const merge = (newList, oldList, key) =>
  Object.values([...oldList, ...newList].reduce((a, b) => {
    a[b[key]] = b;
    return a;
  }, {}));

const l1 = [
{ id: '1', value: 'l1v1' },
{ id: '2', value: 'l1v2' },
{ id: '3', value: 'l1v3' },
];

const l2 = [
{ id: '2', value: 'l2v1' },
{ id: '4', value: 'l2v2' },
{ id: '5', value: 'l2v3' },
]

console.log(merge(l1, l2, 'id'));
0: {id: "1", value: "l1v1"}
1: {id: "2", value: "l1v2"}
2: {id: "3", value: "l1v3"}
3: {id: "4", value: "l2v2"}
4: {id: "5", value: "l2v3"}

重複があった場合は第一引数(new)の方を残す感じ。重複チェックしたいオブジェクトのキーをkeyに指定。

なんか他にいい方法ありますかね。

Proton-native

Proton Native - React Native for the desktop, cross compatible

https://proton-native.js.org/SVG/Artboard%201.svg

Reactを使ってデスクトップアプリが作れるライブラリが公開されていました。

なんでも

Create native desktop applications through a React syntax, on all platforms Same syntax as React Native

だそうです。

libuiというマルチプラットフォームGUIライブラリがあるのですがそれをnodeから使えるようにするlibui-nodeというライブラリがあって、Proton-nativeではProton-nativeのオーナーがlibui-nodeをフォークしているものを取り込んでいるようです。

このあたりはReact Nativeっぽさがありますね。

Quick Start

Proton Native - React Native for the desktop, cross compatible

してみました。

f:id:saburesan:20180216075211p:plain

これだと面白くないので、リポジトリに含まれてたDemo.jsの中身をコピってみます。

https://gyazo.com/d6ce18deadab61313bf91b809744a59e

色々試してみた

import React, { Component } from 'react';

import { render, Window, App, TextInput, Dialog, Box, Button, Checkbox, Picker, ProgressBar, Text } from 'proton-native';

class Example extends Component {
  state = {
    formText: '',
  }

  render() {
    return (
      <App>
        <Window title="Example" height={500} width={500}>
          <Box>
            <TextInput stretchy={false} onChanged={(formText) => this.setState({ formText })}/>
            <Text>{this.state.formText}</Text>
            <Button onClicked={() => Dialog('Error', {title: "Button Clicked"})}>Button</Button>
            <Box padded height={100} vertical={false}>
              <TextInput stretchy={false} onChanged={(text) => Dialog('Error', {title: text})}/>
              <Button onClicked={() => Dialog('Error', {title: "Button Clicked"})}>Button</Button>
            </Box>
            <Checkbox>This is a checkbox</Checkbox>
            <Picker>
              <Picker.Item>Option 1</Picker.Item>
              <Picker.Item>Option 2</Picker.Item>
              <Picker.Item>Option 3</Picker.Item>
            </Picker>
            <ProgressBar value={-1} />
          </Box>
        </Window>
      </App>
    );
  }
}

render(<Example />);

https://gyazo.com/3858156a592e22b9e5772c6e55c0a74e

まとめ

React/ReactNativeのようなstyleは今のところ変更できないようです。

今後に期待ですね。

アウトプットして起きたこと

ブログやらLTやらで少しづつアウトプット出してて身に起きたことです。

名前を覚えられる

前にReact Nativeについての発表を何回かしたのですが、同僚がある勉強会(私は行ってないです)に行ったときに名刺交換で

「React Nativeの〇〇さんがいる会社ですね!」

って言われたそうで。嬉しい。

採用の応募がある

React, React Native, Kotlinなど流行りのものをやっていると「福岡 React Native」とかで引っかかるんですかね。

福岡で働きたいって人からちょいちょい採用の連絡が来ます。

外国人が多いですね。

弊社、インドネシア向けの事業しかやってないので今のところ基本的に外国人採用が多いです。

応募お待ちしております!

仕事の依頼がある

これも国内外関わらず来ます。

海外から仕事の依頼があったのはびっくりしましたね。

相手は日本語話せない感じですが、

「ブログとかGithub見たぜ〜、うちのチームに入らないか?」

ってくる感じ、これがインターネッツ!!

まとめ

アウトプットすると何故か海外とのつながりができる。

プログラミングってすごい。

TypeScriptでstyled-componentsに独自のPropsを渡したい

www.styled-components.com

最近はStyled-Component使ってます。

もともとstyleだけを分ける方法は使って無くて、

// Styleで分ける方法
const styles = {
  title: {
    fontSize: '2rem',
   color: '#333',
  },
};

export = ({ children }: Props) => (
  <main>
    <h1 style={styles.title}>{children}</h1>
  </main>
);
// コンポーネント毎に分ける方法
const Title = ({ text }: { text: string }) => (
  <h1 style={{ fontSize: '2rem', color: '#333'}}>
    {text}
  </h1>
);

export = ({ text }: Props) => (
  <main>
    <Title/>{text}</Title>
  </main>
);

こんな感じでコンポーネント毎に分ける方が好みでした。

コンポーネント名で判別できる方がわかりやすいですし、見た目もスッキリします。

しかしながらコンポーネントを分けていく上記の書き方だとコード数が増えるのが悩みでした。

styled-componentはそんな悩みを解決してくれたやつでした。

const Title = Styled.h1`
  font-size: 2rem;
  color: #333;
`;

export = ({ text }: Props) => (
  <main>
    <Title>{text}</Title>
  </main>
)

いい感じに私がやりたかったことを実現してくれています。

あとcss本来書き方(ハイフン使える)で記述できる点やhoverやメディアクエリも書くことができるのは大きいですね。

新たな悩み

styled-componentsにはstyleを定義しているテンプレートリテラルの中でPropsを受け取ることができます。

このお陰でPropsによってスタイルを変えることが容易になっています。

const Title = Styled.h1`
  font-size: 2rem;
  color: ${({ active }: any)=> active ? '#AAA' : '#333'};
`;

export = ({ text, active }: Props) => (
  <main>
    <Title active={active}>{text}</Title>
  </main>
)

しかし、TypeScritpだとTitleactiveが渡せません(エラーが出ます)。

理由はh1のPropsにactiveが定義されていないからです。

じゃあコンパイル通すにはどうするんだって話ですが、とりあえず

1.Propsで変化するStyleだけ別で書く

2.Object spreadで回避

3.コンポーネントを返す関数(前に私がやっていた方法)を定義する

かなーと思いました。

Propsで変化するStyleだけ別で書く

const Title = Styled.h1`
  font-size: 2rem;
`;

export = ({ text, active }: Props) => (
  <main>
    <Title style={{ color: ${active ? '#AAA' : '#333' }}}>{text}</Title>
  </main>
)

うーん、微妙。

そもそも1つのコンポーネントのスタイルを別々で定義するのはバグの温床っぽい感じがするので嫌ですね。

Object spreadで回避

const Title = Styled.h1`
  font-size: 2rem;
  color: ${({ active }: any)=> active ? '#AAA' : '#333'};
`;

export = ({ text, active }: Props) => (
  <main>
    <Title {...{active}}>{text}</Title>
  </main>
);

コンパイルは通りますが、TitleのPropsのタイプチェックはもちろん行われていません。

TypeScript使ってタイプチェック無視する書き方は出来る限り避けたいですね…

[追記] これコンパイル通りません。onClickなどの存在するPropsを追加するとコンパイルが通るようです。

コンポーネントを返す関数(前に私がやっていた方法)を定義する

const Title = ({ active, children }: { active: boolean, children: any }) => {
  const Cmp = Styled.h1`
    font-size: 2rem;
    color: ${({ active }: any)=> active ? '#AAA' : '#333'};
  `;
  return <Cmp>{children</Cmp>
}

export = ({ text, active }: Props) => (
  <main>
    <Title active={active}>{text}</Title>
  </main>
);

うーん、って感じです。

結論

悩んでます。

無印のクラックプレッツェルチョコレートが美味い

www.muji.net

これがマジで美味い。

美味すぎる。

今日大名店のやつ買い占めてきた。

2つ以上で10%OFFだったので。

1つ100円なんで買ってみてください。

Kotlinでa == 1 && a == 2 && a == 3

前回の記事はJavaScripta == 1 && a == 2 && a == 3が成り立つにはどうすれば良いか、という内容でしたが元記事のブコメ

プロパティある言語(C#, Swiftなど)ならトリッキーなことしなくても普通にできちゃう

とりあえずKotlinのDelegation。

一番楽ですぐ思いつく方法はコレじゃないでしょうか

import kotlin.reflect.KProperty

class Delegate {
    var count = 0

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        count++
        return count
    }
}

fun main(args: Array<String>) {
    val a by Delegate()
    if ( a == 1 && a == 2 && a == 3) {
        println("true")
    }
}

JavaScriptの比較演算子の話

stackoverflow.com

これ、よく思いつくなーと感心しました。

普段等価演算==使うことないのですが、==の場合だと上記の回答のようなことができるようです。

valueOfでも大丈夫だって書いてあったのですが、toStringvalueOfどっちも定義されている場合、toStringが呼ばれることはありませんでした。

% node
> const a = { toString: () => console.log("toString"), valueOf: () => console.log("valueOf") };
undefined
> a
{ toString: [Function: toString], valueOf: [Function: valueOf] }
> a == 1
valueOf
false
> a == "1"
valueOf
false
> a === 1
false
> a === "1"
false
>

valueOfのリファレンスには

developer.mozilla.org

文字列型の文脈にあるオブジェクトは toString メソッドを通じて変換されますが、

とあるんですが、

> console.log(a + " string")
valueOf
undefined string
undefined

文字列の文脈って上記のようなやつは違うんですかね。

Next.jsに動的ルーティングを追加する「next-routes」

Nextjsにはデフォルトでルーティング機能があります。

[root_directory]/pages/ディレクトリ構成がそのままルーティングになります。

しかし、RESTfulなURIにする場合はサーバーの設定をカスタマイズして手動でしなければなりません。

そのような動的なルーティングを追加するためのモジュールがnext-routesです。

github.com

動的なルーティングを追加

たとえば、/users/show?id=ryohlan/users/ryohlanというルーティングを貼りたい場合は

# routes.js
const routes = module.exports = require('next-routes')()

routes
.add('/users/:id', '/users/show')

このように定義して、

# server.js
const next = require('next')
const routes = require('./routes')
const app = next({dev: process.env.NODE_ENV !== 'production'})
const handler = routes.getRequestHandler(app)

const {createServer} = require('http')
app.prepare().then(() => {
  createServer(handler).listen(3000)
})

という風にサーバーの設定を変更することで

/users/ryohlanへのアクセスを/users/show?id=ryohlanで受けることができます。

クリックで遷移の時

Nextjsではクリック遷移の時はNextimport Link from 'next/link'でLinkコンポーネントを使うのですが、

next-routesにはLinkコンポーネントもあるので代用することで動的なルーティングを使うことができる

# routes.js
const routes = module.exports = require('next-routes')()

routes
.add('users_show', '/users/:id', '/users/show') # 第1引き数が/で始まらない場合はルーティングのIDとして使うことができる

# pages/users/show.js
import {Link} from '../routes'

export default () => (
  <div>
    <Link route='users_show' params={{id: 'ryohlan'}}>
      <a>Hello world</a>
    </Link>
  </div>
)

react-native-firebaseのiOSのワークアラウンド

react-nativeでFirebaseを簡単に使えるようになるreact-native-firebaseというライブラリがあります。

rnfirebase.io

ですが、react-native v0.51.0ではiOSの設定がドキュメント通りでは動きません。

そのワークアラウンドについて書いておきます。

RNFirebaseをpodからLibrariesに直接インポートする

ドキュメント通りにreact-native linkをするとPodfile

   pod 'RNFirebase', :path => '../node_modules/react-native-firebase'

が追加されていると思いますが、これを消します。

次に、Finderを開いてnode_modules/react-native-firebase/ios/RNFirebase.xcodeprojXcodeLibrariesにドロップします。

f:id:saburesan:20171213005611p:plain 

f:id:saburesan:20171213005643p:plain

Linked Frameworks and LibrariesにlibRNFirebase.aを追加

f:id:saburesan:20171213010021p:plain

とりあえずこれで動きます。

FirebaseMessagingを追加する時

rnfirebase.io

FirebaseMessagingを追加するステップでAppDelegate.m#import "RNFirebaseMessaging.h"を追加する作業があるのですが、

このヘッダが見つからないのでビルドが通りません。

これはXcodeHeader Search Pathに以下のパスを追加する必要があります。

$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/messaging

f:id:saburesan:20171213010542p:plain

これでFirebaseMessagingのビルドが通るはずです。

KotlinJSでReactNative書くのは今のところ無理っぽい

会社のブログにて、Kotlin x ReactNativeに挑戦した結果を書きました。

anect.hatenablog.com

KotlinJS面白い。

まだまだ発展途上なので今後に期待。

KotlinJSを動かす

先日Kotlin1.2がリリースされました。

blog.jetbrains.com

バックエンド、Webフロントエンド、Androidでコードの共通化ができるようになったとこのことです。

今回はその機能は触らないんですが、前にリリースされたKotlinJSとKotlinNativeには興味がありました。

で最近はずっとReactNative, ReactSPAばっかりやってて、ふと思いました。

React NativeのAndroidのコードはKotlin化は余裕

KotlinJSでKotlinでJSが吐き出せる

KotlinNativeでiOSが動かせる

「これはReactNativeはKotlinだけで動かせるのでは?」

ということで、色々試してみます。

KotlinJSを動かす

まずここからです。先は長い。

とりあえずハロワ

プロジェクト作成

f:id:anect:20171205163310p:plain f:id:anect:20171205163331p:plain

エントリポイントを作成

fun main(args: Array<String>) {
    console.log("Hello, World")
}

⌘9でビルド

f:id:anect:20171206102359p:plain

outディレクトリにコンパイル結果が入ってます。エラーは無かったようです。

HelloWorld.js

HelloWorld.js見てみましょう

if (typeof kotlin === 'undefined') {
  throw new Error("Error loading module 'HelloWorld'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'HelloWorld'.");
}
var HelloWorld = function (_, Kotlin) {
  'use strict';
  function main(args) {
    console.log('Hello, World');
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;
}(typeof HelloWorld === 'undefined' ? {} : HelloWorld, kotlin);

Kotlinというグローバルオブジェクトが無いと駄目のようです。

kotlin.js

f:id:anect:20171206102606p:plain

outディレクトリにlibというディレクトリもできており、そのなかにkotlin.jsというのがあります。

これをグローバルオブジェクトとして定義するとHelloWorld.jsが動きそうです。

実行

> const kotlin = require('./out/production/HelloWorld/lib/kotlin');
undefined
> require('./out/production/HelloWorld/HelloWorld');
Hello, World
{}

動きました。

他に何ができるかドキュメントを読んでみます。

JSのコードをKotlin上で書く

Calling JavaScript from Kotlin - Kotlin Programming Language

js()を使うとJSのコードが動くみたいです。ちょっと試してみましょう。

fun main(args: Array<String>) {
    val time: String = js(" new Date().toString()")
    console.log(time)
}

コンパイル結果

if (typeof kotlin === 'undefined') {
  throw new Error("Error loading module 'HelloWorld'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'HelloWorld'.");
}
var HelloWorld = function (_, Kotlin) {
  'use strict';
  function main(args) {
    var time = (new Date()).toString(); // 新しいコード
    console.log(time);
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;
}(typeof HelloWorld === 'undefined' ? {} : HelloWorld, kotlin);
> const kotlin = require('./out/production/HelloWorld/lib/kotlin');
undefined
> require('./out/production/HelloWorld/HelloWorld');
Wed Dec 06 2017 10:27:43 GMT+0900 (JST)
{}

動きました。

nodeで動かす前提でkotlin.jsをrequireするコードも追加してみましょう。

fun main(args: Array<String>) {
    js("require('./out/production/HelloWorld/lib/kotlin')")
    val time: String = js(" new Date().toString()")
    console.log(time)
}

コンパイル結果

if (typeof kotlin === 'undefined') {
  throw new Error("Error loading module 'HelloWorld'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'HelloWorld'.");
}
var HelloWorld = function (_, Kotlin) {
  'use strict';
  function main(args) {
    require('./out/production/HelloWorld/lib/kotlin');
    var time = (new Date()).toString();
    console.log(time);
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;a
}(typeof HelloWorld === 'undefined' ? {} : HelloWorld, kotlin);
> const kotlin = require('./out/production/HelloWorld/lib/kotlin');
undefined
> require('./out/production/HelloWorld/HelloWorld');
Wed Dec 06 2017 10:37:43 GMT+0900 (JST)
{}

動いたけど…違う。自動でrequireして欲しい…

調べてみるとKotlin Compilerの設定でCommonJSの形にもできるようです。

f:id:anect:20171206105011p:plain

早速CommonJSにしてコンパイルしてみます。

(function (_, Kotlin) {
  'use strict';
  function main(args) {
    console.log('Hello, World');
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;
}(module.exports, require('kotlin')));

おっ、requireしてくれてる。 しかしデフォルトではlibkotlin.jsが吐かれるのでこれだとエラーが出ます。

そこで先程のKotlin Compilerの設定でDestination directoryという設定がありましたが、これをnode_modulesに変更します。

f:id:anect:20171206105650p:plain

コンパイルすると

(function (root, factory) {
  if (typeof define === 'function' && define.amd)
    define(['exports', 'kotlin'], factory);
  else if (typeof exports === 'object')
    factory(module.exports, require('kotlin'));
  else {
    if (typeof kotlin === 'undefined') {
      throw new Error("Error loading module 'HelloWorld'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'HelloWorld'.");
    }
    root.HelloWorld = factory(typeof HelloWorld === 'undefined' ? {} : HelloWorld, kotlin);
  }
}(this, function (_, Kotlin) {
  'use strict';
  function main(args) {
    console.log('Hello, World');
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;
}));

大分変わりましたね。

ちゃんとnode_modulesにコンパイル結果が入ってます。

f:id:anect:20171206110107p:plain

実行してみます。

% node out/production/HelloWorld/HelloWorld.js
Hello, World

やったぜ。

KotlinでNodeのサーバー立てる

JSのライブラリをKotlin側から使うのはどうするのでしょうか。

とりあえず雰囲気で書いてみる

interface Http {
    fun createServer(onRequest: (req: Any, res: Res) -> Unit): Proxy

    interface Proxy {
        fun listen(port: Int, callback: () -> Unit)
    }

    interface Res {
        fun writeHead(statusCode: Int)
        fun end(message: String)
    }
}

fun main(args: Array<String>) {
    val http: Http = js("require('http')")
    http.createServer({ _, res ->
        res.writeHead(200)
        res.end("Hello World")
    }).listen(8080, { console.log(it)})
}

コンパイルは通る。

実行してみる。

% node out/production/HelloWorld/HelloWorld.js
/Users/Ryohlan/dev/kotlin/HelloWorld/out/production/HelloWorld/HelloWorld.js:37
    http.createServer_jnnk04$(main$lambda).listen_n53o35$(8080, main$lambda_0);
         ^

TypeError: http.createServer_jnnk04$ is not a function
    at main (/Users/Ryohlan/dev/kotlin/HelloWorld/out/production/HelloWorld/HelloWorld.js:37:10)
    at /Users/Ryohlan/dev/kotlin/HelloWorld/out/production/HelloWorld/HelloWorld.js:43:3
    at Object.<anonymous> (/Users/Ryohlan/dev/kotlin/HelloWorld/out/production/HelloWorld/HelloWorld.js:46:2)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)

ですよねー。

そもそもcreateServer_jnnk04$ってなってしまってるのでなんとかしないと

external

これはKotlinコンパイラにそれが生のJSインターフェースだと伝える手段です。

これをHttpにつけてコンパイルしてみます。

external interface Http {
    fun createServer(onRequest: (req: Any, res: Res) -> Unit): Proxy

    interface Proxy {
        fun listen(port: Int, callback: () -> Unit)
    }

    interface Res {
        fun writeHead(statusCode: Int)
        fun end(message: String)
    }
}

fun main(args: Array<String>) {
    val http: Http = js("require('http')")
    http.createServer({ _, res ->
        res.writeHead(200)
        res.end("Hello World")
    }).listen(8080, { console.log("localhost:8080")})
}
% node out/production/HelloWorld/HelloWorld.js
localhost:8080

f:id:anect:20171206112907p:plain

やったぜ。

ついでにrequireもexternalにします。

しかし問題が。

requireは読み込むモジュールごとに返り値が違うので通常だとコンパイルが通りません。

Dynamic Type

これはKotlinJSのための型定義です。

dynamic型を使うとその型はKotlinのタイプチェックから無視されるのでコンパイルが通ります。

今回だと

external fun require(path:String):dynamic

fun main(args: Array<String>) {
    val http = require("http")
    http.createServer({ _, res ->
        res.writeHead(200)
        ...

このようにして使うとcreateServerは補完には出ませんがタイプチェックを無視されるのでコンパイルは通ります。

補完を効かせるためにhttpの型をつけて最終的には以下のようになります。

external interface Http {
    fun createServer(onRequest: (req: Any, res: Res) -> Unit): Proxy

    interface Proxy {
        fun listen(port: Int, callback: (message: String) -> Unit)
    }

    interface Res {
        fun writeHead(statusCode: Int)
        fun end(message: String)
    }
}

external fun require(path:String):dynamic

fun main(args: Array<String>) {
    val http: Http = require("http")
    http.createServer({ _, res ->
        res.writeHead(200)
        res.end("Hello World")
    }).listen(8080, { console.log("localhost:8080")})
}

@JsModuleでrequireいらず

ドキュメント見てたら@JsModuleを使うとrequire要らずということが分かりました。

なので更にスマートに

@JsModule("http")
external object Http { // interfaceだと実装がないのでコンパイル通らない。
    fun createServer(onRequest: (req: Any, res: Res) -> Unit): Proxy

    interface Proxy {
        fun listen(port: Int, callback: (message: String) -> Unit)
    }

    interface Res {
        fun writeHead(statusCode: Int)
        fun end(message: String)
    }
}

fun main(args: Array<String>) =
        Http.createServer{ _, res ->
            res.writeHead(200)
            res.end("Hello World")
        }.listen(8080, { console.log("localhost:8080")})

コンパイル

(function (_, Kotlin, $module$http) {
  'use strict';
  var Unit = Kotlin.kotlin.Unit;
  function main$lambda(f, res) {
    res.writeHead(200);
    res.end('Hello World');
    return Unit;
  }
  function main$lambda_0(it) {
    console.log('localhost:8080');
    return Unit;
  }
  function main(args) {
    $module$http.createServer(main$lambda).listen(8080, main$lambda_0);
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;
}(module.exports, require('kotlin'), require('http')));

Next.js+FirebaseHostingで構築するサーバレスWebアプリケーション

github.com

Next.jsというReactアプリケーションをデフォルトでServerSideRenderingしてくれるライブラリがあります。

これをFirebaseHosting上にFirebaseFunctionsを使って構築することで、無料でサーバレスSPAを作る事ができます。

Next.jsをclone

Next.jsはサンプルがとても豊富です。

github.com

今回使うサンプルはwith-firebase-hostingです。

github.com

cloneしたらwith-firebase-hostingディレクトリをコピーして使いましょう。

Hello World

まずは依存ライブラリをインストールします。

npm i

そして

npm run next

でローカルサーバが立ち上がります。

デフォルトではlocalhost:3000ですね

f:id:saburesan:20171114103843p:plain

これは単にnextjsを起動しているだけで、Firebaseは全く関係ありません。

デプロイ

事前にFirebaseでプロジェクトを作っておいてください。

プロジェクトを作ったら.firebasercの<project-name-here>をプロジェクト名に置き換えます

// .firebaserc
{
  "projects": {
    "default": "<project-name-here>"
  }
}

これでFirebaseの設定は完了です。

npm run deploy

これで設定したプロジェクトにデプロイされます。

(npm run serveは恐らく動かないと思います。)

デプロイが完了するとアップデート先のURLがログに出るのでアクセスしてみると、

先程ローカルで動かした内容が表示されてるかと思います。

もし404や500が出た場合はFirebase consoleのFunctionsでログを見ることができるので確認して見てください。

f:id:saburesan:20171114110616p:plain

プロジェクトの構造

app

実際にコードを書いていくディレクトリです。

nextjsのルートディレクトリです。

普段使うnextjsと違うのはnext.config.jsファイルでコンパイルの保存先がfunctions/nextになっているところくらいでしょうか。

functions

デフォルトではこのディレクトリがFirebaseにデプロイされます。

appのコンパイル先でもあります。

public

これはFirebaseHostingのルートとなるディレクトリです。

placdholder.htmlというのが入ってますが、基本的に触ることはないです。

FirebaseHostingの設定でpublicは必須です。

存在していなければデプロイに失敗します。

Deployment Configuration  |  Firebase

firebase.json

Firebaseの細かい設定を書きます。

{
  "hosting": {
    "public": "public",
    "rewrites": [
      {
        "source": "**/**",
        "function": "next"
      }
    ]
  },
  "functions": {
    "source": "functions"
  }
}

デフォルトでは上のようになっています。

hostingの設定ですが、publicは必須でrewritewですべてのアクセスに対してFirebaseFunctionsのnext関数を起動するようになっています。

functionsの設定でFirebaseFunctionsのソースを指定しています。functions/index.jsの内容は

const functions = require('firebase-functions')
const next = require('next')

var dev = process.env.NODE_ENV !== 'production'
var app = next({ dev, conf: { distDir: 'next' } })
var handle = app.getRequestHandler()

exports.next = functions.https.onRequest((req, res) => {
  console.log('File: ' + req.originalUrl) // log the page.js file that is being requested
  return app.prepare().then(() => handle(req, res))
})

となっていて、exports.nextでnextをエクスポートしています。

writesの"function":"next"はこのnextを呼んでいます。

面倒なところ

デフォルトではfunctionsがデプロイされますが、functionsに含まれるappのコンパイルの成果物distはpagesに紐づくファイルだけなので、

例えばapp/package.jsonのdependenciesはfunctions/package.jsonに書き足して上げないといけません。

画像を扱う場合はapp/staticというフォルダに入れて使うのがnextjsでは一般的なのですが、このstaticもコピーしなければなりません。

他にも、起動サーバーをカスタマイズする場合などもコピーして…

という感じになってしまいます。

appにまとめる

一応、今私はfunctionsの内容をappに移して運用しています。

そうなるとデプロイに不要なファイルが多く含まれるので、firebase.jsonのignoreで指定しています。

app/index.jsはfunctionsのindex.jsをそのまま使い、サーバーをカスタマイズする場合は

exports.start = () => {
  app.prepare().then(() => {
    createServer((req, res) => {
      ...
    }).listen(port, (err) => {
      if (err) throw err;
      console.log(`> Ready on http://localhost:${port}`);
    });
  });
};

という感じにしました。

package.json

"scritps" : {
  "dev": "node -e \"require('./').start()\"",
  ...
}

"dependencies": {
  "firebase-admin": "^5.5.0",    // 追加
  "firebase-functions": "^0.7.3", // 追加
  ...
}

firebase.json

{
  "hosting": {
    "public": "public",
    "rewrites": [
      {
        "source": "**/**",
        "function": "next"
      }
    ],
    "ignore": [
      "**/.*",
      "**/node_modules/**",
      "app/src",
      "app/flow-typed",
      "app/pages"
    ]
  },
  "functions": {
    "source": "app"
  }
}

package.json追記したりstaticコピーしてくるよりこっちのほうがいいなーと思いました。

よくわからない問題

firebase serveが上手く動かない

404だったり、変更が全然反映されなかったり…

謎です。

ローカルでは問題無く動くが、デプロイすると動かない

デプロイするとstyled-jsxとbabel-runtimeが無いというエラーが出ます。

ローカルではnode_modulesに入っているのですが…

追加でインストールすると動くようになりました。

まとめ

ハマリポイントありますが、無料でWebアプリ作れるのは良いですね〜

ちなみに、github pagesだった自分のポートフォリオをNext.js+FirebaseHostingに移行しました。

ryo-hlan.firebaseapp.com

サーバレスなんで、アクセスの感覚が空くとやっぱり遅いけどインスタンスが起動しているときなら速度は問題無さそう。

f:id:saburesan:20171114164328p:plain

まぁコンテンツ少ないんでね…

そのうちブログも移行します。

AndroidにHostsを書き換えずにPCのHostsを反映させる

PC側のHostsを変更

変更してください。

ローカルPCにプロキシサーバを立てる

なんでも良いです。

node作りたい、と思っていましたが先駆者がいらっしゃったのでありがたく使わせていただきました。   qiita.com

ペッと貼って起動

% node proxy-server.js 
http proxy server started on port 8080

デフォルトPortは8080です。

Androidにプロキシサーバの設定をする

AndroidWifi設定からProxyサーバーをlocalhost:8080に設定します。

f:id:saburesan:20171102093653p:plain

Portforwardingする

Android端末を開発者モードにする必要があります。

GoogleChromehttp://chrome://inspect#devicesにアクセスします。

Port forwardingからAndroidlocalhost:8080をPCの8080に接続するように設定します。

f:id:saburesan:20171102094026p:plain

完了

PCのHostsの設定がAndroid側にも反映されてると思います。

React Native Debuggerがすごすぎて笑ってしまった

github.com

何も言わずに入れて欲しい。

react-native-debuggerはReact Native公式のChrome Debuggerを元に作られたデバッグツールです。

Reduxを利用している場合に力を発揮します。

というかRedux使ってないと旨味無いかもです…

でもRedux入れてるなら絶対使って欲しい。

とりあえずスクリーンショット載せます。

f:id:saburesan:20171027193500p:plain

まだ試した機能は多くないでですが、使った感じで利点をならべるとすると

  • Action, Stateの可視化が素晴らしい
  • 時系列順にAction、Stateのログが取れる
  • 任意のタイミングのActionによるStateのDiffが取れる
  • 任意のタイミングのActionまで状態を戻すことができる
  • Debugger上でActionを発行できる

こんな感じです。

時系列順にAction、Stateのログが取れる。StateのDiffが取れる

f:id:saburesan:20171028104354p:plain

図の「Actionのログ」が発行されたActionを時系列に並べたものです。

「ActionやStateの状態」は「Actionのログ」から選択したActionに関する詳細が見れます。

例えば、図だとSETUP_PAGE_STATE#SET_SETUP_FETCH_STATEを選択して、

右側のパネルのタブでActionを選択すると発行されたActionの詳細を見ることが出来ます。

Stateタブではその左Actionが発行された後のStateの状態が、

DiffタブではActionによって起こったStateのDiffを表示してくれます。

また、それぞれの表示形式は「Tree」「Chart」「Raw」の3パターンから選べ、

デフォルトはTree(図の状態)です。

これをChartに変えると…

f:id:saburesan:20171028104817p:plain

このような表示になります。

Rawはただのテキスト表示です。

任意のタイミングのActionまで状態を戻すことができる。

Actionのログは詳細を見るだけでなく、「Jump」「Skip」という動作が可能です。

Jump

https://gyazo.com/d94ec482b603027187b5632ecc260a00

JumpはStateの状態をそのActionが発行されたタイミングまで戻すことが出来ます。 また、そのタイミングから時系列順にActionを再生することも出来ます。

Skip

Skipは任意のActionの発行を無効にします。

あるアクションがバグを引き起こしているかどうかの検証などに使えそうです。

Debugger上でActionを発行できる

https://gyazo.com/4584122879b6ebf6ad53a478ade7b06d

Debugger上でActionを発行する機能があります。

Debugger上で発行したActionもActionのログに記録されJump, Skipなどの操作が可能です。

ちなみに、ActionはSkipしてからSweepボタンを押すと完全にログから削除できます。

まだまだな所

全然使い物にならねーってほどではないのですが、偶に起きるエラーです。

不安定

ちょくちょく接続エラーになります。その時はアプリ側の再起動が必要です。

Navigation系でページを戻ったりはできない

react-navigationを使っているのですが、Jumpで前のページの状態まで持っていくとエラーになります。

List系の'same key'エラーたまになる

Viewの更新が偶にうまくいかないことがあります。

まとめ

まだまだ試せていない機能が多いのですが、少し使っただけでもデバッグが非常に楽になりました。

f:id:saburesan:20171028121918p:plain

デバッガ上でテストする機能があり、Jest, Mocha, Tape, Avaのテンプレートから選べるというすごく有用そうな機能もあるので試してみたいと思います。