あいつの日誌β

働きながら旅しています。

getstorybook で Unsupported Project type. (code: UNDETECTED)

以下のコマンドを実行すると

mkdir example-project && cd $_
npm init -y
getstorybook

以下のようなエラーが表示される

 getstorybook - the simplest way to add a storybook to your project. 

 • Detecting project type. ✓
    Unsupported Project type. (code: UNDETECTED)
    Visit http://getstorybook.io for more information.

原因

getstorybook コマンドは package.json の中身を見て判断しています。

https://github.com/storybooks/getstorybook/blob/master/lib/detect.js

どうやら最低限 react.js が install されていれば良さそうです ということで先に react を install します

npm install --save react react-dom

この状態で retry します。

getstorybook

npm run storybook を実行できるようになります。おしまい

React Storybook で Material-UI を試す

create-react-app material-ui-storybook && cd $_
getstorybook
npm install --save react-tap-event-plugin material-ui
npm install --save-dev  storybook-addon-material-ui

edit `src/stories/index.js

 import React from 'react';
-import { storiesOf, action, linkTo } from '@kadira/storybook';
+import { storiesOf, action, linkTo, addDecorator } from '@kadira/storybook';
 import Button from './Button';
 import Welcome from './Welcome';
+import {muiTheme} from 'storybook-addon-material-ui';
+import RaisedButton from 'material-ui/RaisedButton';
 
 storiesOf('Welcome', module)
   .add('to Storybook', () => (
@@ -15,3 +17,9 @@ storiesOf('Button', module)
   .add('with some emoji', () => (
     <Button onClick={action('clicked')}>😀 😎 👍 💯</Button>
   ));
+
+storiesOf('Material-UI', module)
+  .addDecorator(muiTheme())
+  .add('RaiseButton', () => (
+    <RaisedButton label="Default" />
+  ));

open http://localhost:9009

Google App Engine の let's encrypt を更新する手順の備忘録

3ヶ月前に okamuuu.com を作った時についでに https に対応させたんですが、忘れた頃に更新しないといけないので備忘録

手元の MacOSX 上で最初にデプロイできるか確認

とりあえず自分のアプリがデプロイできるかどうかを確認します。 この3ヶ月でマシンを新調していたので gcloud コマンドがはいってなかった...

which gcloud
gcloud not found

ここからダウンロードする。 https://cloud.google.com/sdk/docs/quickstart-mac-os-x

そのあと ./install.sh を実行して SHELL を再起動します。

事前にデプロイの手順を確認する理由は証明書を作成する手順を実行している最中に指定されたファイルを GAE にアップする必要があるためです。

Google clouls shell で GAE 上で証明書を作成します。

let's encrypt を準備します。すでに letsencrypt ディレクトリができていたら前回すでに実行しているので git pull しておきましょう。

git clone https://github.com/letsencrypt/letsencrypt && cd letsencrypt

証明書を作成します。モジュールが入っていない場合は結構時間がかかります。

cd ~/letsencrypt
sudo ./letsencrypt-auto -a manual certonly

以下ダイアログ。

  • Emailアドレスを入力
  • 規約に同意
  • Electronic Frontier Foundation にアドレスを Share するかに答える
  • 該当するドメインを指定
  • IP を記録する事に同意
Enter email address (used for urgent renewal and security notices)  If you
really want to skip this, you can run the client with
--register-unsafely-without-email but make sure you then backup your account key
from /etc/letsencrypt/accounts   (Enter 'c' to cancel):okamuuu@gmail.com
-------------------------------------------------------------------------------
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf. You must agree
in order to register with the ACME server at
https://acme-v01.api.letsencrypt.org/directory
-------------------------------------------------------------------------------
(A)gree/(C)ancel: A
-------------------------------------------------------------------------------
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about EFF and
our work to encrypt the web, protect its users and defend digital rights.
-------------------------------------------------------------------------------
(Y)es/(N)o: Y
Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c'
to cancel):okamuuu.com
Are you OK with your IP being logged?
-------------------------------------------------------------------------------
(Y)es/(N)o: Y

そうすると下記ようのようなチャレンジコードが画面に表示されます。

Make sure your web server displays the following content at
http://okamuuu.com/.well-known/acme-challenge/123456789xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx before continuing:
123456789xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

これは http://okamuuu.com/.well-known/acme-challenge/123456789xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx に Request したら 123456789xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy を返すようにしてね。という事を言っています。

この画面を表示させたまま(Enter をまだ押さない)状態で GAE に Static file をアップします。その後 Enter を押します。

その後に Cloud Shell の次のコマンドで公開鍵証明書の中身を取得しておきます。

sudo less /etc/letsencrypt/live/okamuuu.com/fullchain.pem

次に秘密鍵

sudo openssl rsa -inform pem -in /etc/letsencrypt/live/okamuuu.com/privkey.pem -outform pem | less

上記の2つの鍵を Google Console 上で SSL 証明書として登録すれば作業完了

参考

http://blog1.erp2py.com/2016/06/gaessllets-encrypt.html

webpack.config.js で複数のエントリーポイントを指定する

mkdir practice-webpack && cd $_
mkdir -p src/pages www/pages
touch webpack.config.js
npm init -y
npm install --save-dev webpack

create webpack.config.js

const path = require('path')
const webpack = require('webpack')

module.exports = { 
  devtool: 'inline-source-map',
  entry: {
    "app": "./src/app.js",
    "pages/posts": "./src/pages/posts.js"
  },  
  output: {
    publicPath: "/",
    path: path.resolve('www'),
    filename: "[name].bundle.js",
  },  
}

create src/app.js and src/pages/posts.js

echo 'console.log("app")' > src/app.js
echo 'console.log("posts")' > src/pages/posts.js 

webpack を実行する

$(npm bin)/webpack

動作確認

node www/app.bundle.js
node www/pages/posts.bundle.js

Webpack 2: Module not found: Error: Cannot resolve 'file' or 'directory' node_modules/@kadira/storybook/dist/server/addons.js

あらすじ

ある日 webpack の version を上げたら extensions に empty な '' を指定するのだめと怒られるのでその指定を削除したら storybook がなんかエラーを吐き出すようになった時のメモです。

状況

React + Redux で既存の Single Page Application で作っているプロジェクトに storybook を導入しています。 .storybook/webpack.config.js にエイリアアス設置し、 ../webpack.config.js を読み込んんでいます。

原因

現行の webpack は version 2 なのですが storybook が使っているのはどうやら違うらしい。 node_modules/webpacknode_modules/webpack-core名前空間が異なっているから混在できているのが原因?

とりあえず同一の webpack.config.js を version の違う webpack で見ているのが原因らしいので解消したいです。

解消した方法

.storybook/webpack.config.jsエイリアスではなく以下のように実ファイルを保存しました。

const config = require("../webpack.config.js")

config.resolve.extensions.push('') // empty extention を追加する

module.exports = config

おしまい

redux 使わずに react と react-router で SPA したい

あらすじ

redux-form といった ecosystem が要らない場合はこういうやり方もあるんではないかと思います。

やり方

create-react-app practice-react-router-spa && cd $_
touch src/Posts.js src/api.js
npm install --save react-router

edit src/App.js

import React from 'react'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'

const NotFound = () => (<div><span>NOT FOUND</span></div>)

import Posts from './Posts'

export default () => (
  <Router history={browserHistory}>
    <Route path="/">
      <IndexRoute component={Posts.List} />
      <Route path="create" component={Posts.Create} />
    </Route>
    <Route path="*" component={NotFound}/>
  </Router>
)

create src/Posts.js

import React, {Component} from 'react'
import { Link, browserHistory } from 'react-router'
import api from './api'

class List extends Component {

  constructor(props) {
    super(props)
    this.state = { posts: [] }
  }

  componentWillMount() {
    api.getPosts().then((result) => {
      this.setState({posts: result.posts})
    })
  }

  render() {

    return (
      <div>
        <h2>Posts</h2>
        <div><Link to="/create">create</Link></div>
        {this.state.posts.map((p, index) => (
          <div key={index}>
            <h3>{p.title}</h3>
            <p>{p.body}</p>
          </div>
        ))}
      </div>
    )
  }
}

class Create extends Component {

  handleSubmit(e) {
    e.preventDefault()
    const params = {
      title : e.target.title.value,
      body: e.target.body.value
    }
    api.createPost({params}).then(() => {
      browserHistory.push("/")
    })
  }

  render() {
    return (
      <div>
        <h2>Create Post</h2>
        <form onSubmit={this.handleSubmit.bind(this)}>
          <div>
            <label htmlFor="Title">Title</label>
            <input id="Title" name="title" type="text" />
          </div>

          <div>
            <label htmlFor="Body">Body</label>
            <textarea id="Body" name="body" rows="10"/>
          </div>
          <button type="submit">submit</button>
        </form>
      </div>
    )
  }
}

export default { List, Create }

create src/api.js

const posts = [{
  "title": "laboriosam dolor voluptates",
  "body": "doloremque ex facilis sit sint culpa\nsoluta assumenda eligendi non ut eius\nsequi ducimus vel quasi\nveritatis est dolores"
  }
]

function getPosts() {
  return new Promise((resolve) => {
    return resolve({posts: posts})
  })
}

function createPost({params}) {
  return new Promise((resolve) => {
    posts.push(params)
    return resolve()
  })

  // return axios.post(`${BASE_URL}/api/posts`, params).then((res) => {
  //   return { "post": res.data }
  // })
}

export default { getPosts, createPost }

実行

npm start

社内のランディングページなどで試験的に React.js を使って社内用の component を蓄積しつつ React.js に慣れてからの redux を導入するとか、とりあえず React.js でシンプルなページを作るところから始めるのもいいと思います。

おしまい

突然ですがクイズです。 ES6 の module exports の機能として正しいものを選びなさい。

HTML コーダーさんに React components を storybook に追加してもらう作業をしているのですが module exports がわかりづらいので問題をつくってみました。

問題1

関数毎に export されている場合

export const One = () => (
  <div>one</div>
)

export const Two = () => (
  <div>two</div>
)

以下の記述で呼び出しができる。マルかバツか?

import { One, Two } from '../components/Export'

問題2

export default でオブジェクトに包んで返している

const Three = () => (
  <div>Three</div>
)

const Four = () => (
  <div>Four</div>
)

export default { Three, Four }

以下の記述で呼び出しができる。マルかバツか?

import Default from '../components/ExportDefault'

Default.Three
Default.Four

問題3

export default で関数を返している

const Five = () => (
  <div>Five</div>
)

export default Five

以下の記述で呼び出しができる。マルかバツか?

import Default2 from '../components/ExportDefault2'

Default.Five

答え

以下で調べてみてください

create-react-app practice-es2015-exports && cd $_
getstorybook
mkdir src/components

Export.js

cat << EOS > src/components/Export.js
import React from 'react'
export const One = () => (
  <div>one</div>
)

export const Two = () => (
  <div>two</div>
)
EOS

ExportDefault.js

cat << EOS > src/components/ExportDefault.js
import React from 'react'
const Three = () => (
  <div>Three</div>
)

const Four = () => (
  <div>Four</div>
)

export default { Three, Four }
EOS

ExportDefault2.js

cat << EOS > src/components/ExportDefault2.js
import React from 'react'
const Five = () => (
  <div>Five</div>
)

export default Five
EOS

stories

cat << EOS > src/stories/index.js
import React from 'react';
import { storiesOf, action, linkTo } from '@kadira/storybook';
import { One, Two } from '../components/Export'
import Default from '../components/ExportDefault'
import Default2 from '../components/ExportDefault2'

storiesOf('Answer', module)
  .add('=>', () => (
    <div>
      <One />
      <Two />
      <Default.Three />
      <Default.Four />
      <Default2 />
    </div>
  )); 
EOS