kintone UI Component を React で試す　第３回

前回は CodeSandbox で kintone UI Component を試してみました。
今回は最後に、実際にローカル環境にReact, Babel, Webpack をインストールして開発環境をセットアップしてみます。
※本記事は [Hamamatsu.js #6 (2019.03.22)](https://connpass.com/event/119039/) のLT登壇資料に加筆修正したものです。元記事は[こちら](https://docs.google.com/presentation/d/e/2PACX-1vThGLvEkyC1VlkVASCRgf3fY9vu2Tp97d0m-bZPjSSRU4RMpnUYNwRMxGxTpz13JV4BrS8G4zJdOCkn/pub?start=false&loop=false&delayms=3000)。

# Webフロントエンド開発環境の適用
kintoneカスタマイズには [JSEdit for kintone](https://developer.cybozu.io/hc/ja/articles/205452653) というプラグイン設定画面で、JavaScript、CSSファイルを作成・編集出来るプラグインがあります。
ちょっとしたカスタマイズには便利ですが、今回は最新のWebフロントエンド環境をセットアップしていきます。

今回、[Creating a React App… From Scratch.](https://blog.usejournal.com/creating-a-react-app-from-scratch-f3c693b84658) を参考にセットアップします。
React環境のセットアップは [Create React App](https://github.com/facebook/create-react-app) を使うと簡単にアプリの開発環境を自動でセットアップしてくれますが、今回手作業でセットアップすることでどんな仕組みで動いているのかが分かるかと思います。

今回の環境：
 macOS 10.13.6
 node v11.12.0
 npm 6.7.0

*Node,npm は自分の環境に合わせて事前にインストールしているものとします。*

### インストールの流れ

 1. mkdir my-app ・・・プロジェクトディレクトリを作成
 2. cd my-app
 3. npm init -y ・・・プロジェクトの初期化
 4. mkdir public
 5. mkdir src ・・・ソースコード用ディレクトリ作成
 6. touch ./public/index.html
 7. Babelセットアップ ・・・下記参照
 8. touch .babelrc
 9. Webpackセットアップ ・・・下記参照
 10. Reactパッケージインストール ・・・下記参照
 11. touch webpack.config.js
 12. touch ./src/index.js
 13. touch ./src/App.js
 14. touch ./src/App.css
 15. npm run dev ・・・コンパイル
 16. webpack-dev-server --mode development ・・・サーバー実行
 17. localhost:3000 ・・・ブラウザで確認
 
### Babelセットアップ
`npm install --save-dev @babel/core@7.1.0 @babel/cli@7.1.0 @babel/preset-env@7.1.0 @babel/preset-react@7.0.0`

### Webpackセットアップ
`npm install --save-dev webpack@4.19.1 webpack-cli@3.1.1 webpack-dev-server@3.1.8 style-loader@0.23.0 css-loader@1.0.0 babel-loader@8.0.2`

### Reactパッケージインストール
```bash
npm install --save-dev react@16.5.2 react-dom@16.5.2
```

### package.json 編集
```package.json
   "dev": "webpack --mode development",
```

### babelプラグイン追加インストール
[@babel/plugin-proposal-class-properties](https://babeljs.io/docs/en/babel-plugin-proposal-class-properties)

`npm install --save-dev @babel/plugin-proposal-class-properties`

.babelrc

```.babelrc
{
    "presets": ["@babel/env", "@babel/preset-react"],
    "plugins": [
        "@babel/plugin-proposal-class-properties"
    ]
}
```

# kintone開発環境(for React + Bable + Webpack)
kintone UI Component をインストールします。

`npm install --save-dev kintone-ui-component`

# プロジェクトフォルダ構成
<img width="367" alt="スクリーンショット 2019-03-28 7.47.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/214523/7a620abf-cbcf-48fb-da2d-81d758f7dfbd.png">

# ソースコード

```index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App.js";

kintone.events.on(['app.record.create.show', 'app.record.edit.show'], event => {
	console.log(App);
	kintone.app.record.setFieldShown('client', true);
	kintone.app.record.setFieldShown('spot', false);
  let menu = kintone.app.record.getHeaderMenuSpaceElement();
  ReactDOM.render(<App />, menu);
});
```

```App.js
import React, { Component } from "react";
import { RadioButton } from "kintone-ui-component";
import "./App.css";

class App extends Component{
  constructor(opts) {
      super(opts);
      let items = [
        {
          label: "顧客",
          value: "client",
          isDisabled: false
        },
        {
          label: "スポット",
          value: "spot",
          isDisabled: false
        }
      ];
      this.state = {
        items: items,
        value: "client"
      };
  }
    
  render() {
    return (
      <div className="kintone-UI">
        <RadioButton
          name="radio"
          items={this.state.items}
          value={this.state.value}
          onChange={value => {
            this.setState({ value });
            this.myOnChange(value);
          }}
        />
      </div>
    );
  }
  myOnChange = val => {
    console.log(val);
    kintone.app.record.setFieldShown('client', false);
    kintone.app.record.setFieldShown('spot', false);
    if (val === 'client') {
      kintone.app.record.setFieldShown('client', true);
    } else {
      kintone.app.record.setFieldShown('spot', true);
    }
  };
}

export default App;
```

```index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title>React Starter</title>
</head>
<body>
  <div id="root"></div>
  <noscript>
    You need to enable JavaScript to run this app.
  </noscript>
  <script src="../dist/bundle.js"></script>
</body>
</html>
```

```App.css
.App {
    margin: 1rem;
    font-family: Arial, Helvetica, sans-serif;
}
.kuc-input-radio-item {
    display: inline-block;
}
```


### 設定ファイル

```package.json
{
  "name": "my-app2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.1.0",
    "@babel/core": "^7.1.0",
    "@babel/preset-env": "^7.1.0",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.2",
    "css-loader": "^1.0.0",
    "kintone-ui-component": "^0.1.4",
    "react": "^16.5.2",
    "react-dom": "^16.5.2",
    "style-loader": "^0.23.0",
    "webpack": "^4.19.1",
    "webpack-cli": "^3.1.1",
    "webpack-dev-server": "^3.1.8"
  }
}
```

```webpack.config.js
const path = require("path");
const webpack = require("webpack");

module.exports = {
  entry: "./src/index.js",
  mode: "development",
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/,
        loader: "babel-loader",
        options: { presets: ["@babel/env"] }
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  },
  resolve: { extensions: ["*", ".js", ".jsx"] },
  output: {
    path: path.resolve(__dirname, "dist/"),
    publicPath: "/dist/",
    filename: "bundle.js"
  },
  devServer: {
    contentBase: path.join(__dirname, "public/"),
    port: 3000,
    publicPath: "http://localhost:3000/dist/",
    hotOnly: true
  },
  plugins: [new webpack.HotModuleReplacementPlugin()]
};
```

### コンパイル

`npm run dev`

# kintone-customize-uploader インストール
kintoneのカスタマイズファイルを開発コンソールから自動でアップロードする [kintone-customize-uploader](https://github.com/kintone/customize-uploader) をインストールします。管理画面を開かなくてもカスタマイズしたJSファイルをデプロイ出来ます。

```bash
npm install --save-dev @kintone/customize-uploader

./node_modules/.bin/kintone-customize-uploader init
? アプリIDを入力してください: アプリID
? カスタマイズの適用範囲を選択してください: ALL
dest/customize-manifest.json を生成しました
```
### マニフェストファイルの修正
```customize-manifest.json
{
    "app": "アプリID",
    "scope": "ALL",
    "desktop": {
        "js": ["dist/bundle.js"],
        "css": []
    },
    "mobile": {
        "js": []
    }
}
```

```bash
$ ./node_modules/.bin/kintone-customize-uploader dest/customize-manifest.json
? kintoneのドメインを入力してください (example.cybozu.com): xxxxx.cybozu.com
? ログイン名を入力してください: [ログイン名]
? パスワードを入力してください: [パスワード]
カスタマイズのアップロードを開始します
dist/bundle.js をアップロードしました!
JavaScript/CSS ファイルをアップロードしました!
JavaScript/CSS カスタマイズの設定を変更しました!
運用環境への反映の完了を待っています...
運用環境への反映の完了を待っています...
運用環境に反映しました!
```

`--watch` オプションを付けることで、コンパイルしたファイルの更新を検知して自動でデプロイまでやってくれます。便利ですね。

# テストアプリについて
テストアプリの他に[営業支援パック](https://jp.cybozu.help/k/ja/user/basic/sales_pack.html)が必要になります。アプリストアから追加をお願いします。

| フィールド名      | フィールドコード    | フォームタイプ      |関連付けるアプリ |  関連付けるアプリ |
|:----------------:|:-----------------:|:------------------:|:------------------:|:------------------:|
| 取引先顧客        | client            | ルックアップ        | 顧客管理（営業支援パック）| コピー元のフィールド |
| スポット顧客      | spot              | 文字列（１行）      |                   |              |           

<img width="868" alt="スクリーンショット 2019-03-28 7.46.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/214523/1ab8264d-1d03-9953-c640-bb03b58e26f7.png">


# スクリーンショット
良い例では無いですが、とりあえずサンプルということで。

詳細画面
<img width="650" alt="スクリーンショット 2019-03-22 17.07.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/214523/43d4508a-61d7-3f9a-c644-7a746cf2d91f.png">

編集画面　その１
<img width="454" alt="スクリーンショット 2019-03-22 17.07.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/214523/1fcf70a1-7462-ea86-1992-809b39f3e0ed.png">

編集画面　その２
<img width="407" alt="スクリーンショット 2019-03-22 17.07.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/214523/4f7d852e-2f3b-c39c-d8cc-9933ef3b1edf.png">


# 参考リンク

- React https://reactjs.org/ (A JavaScript library for building user interfaces)
- Babel https://babeljs.io/ (Babel is a JavaScript compiler.)
- webpack https://webpack.js.org/ (webpack is a static module bundler for modern JavaScript applications.)
- kintone UI Component https://kintone.github.io/kintone-ui-component/latest/