ちょっとしたフロントエンドJavaScriptのサンプルで気軽にBabelを使いたい

# テンプレ

コンパイルに時間がかかるので実用性はほとんどありませんが，実はブラウザ上でコンパイルもできます！

```html:Babel
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.0/babel.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.9.1/polyfill.min.js"></script>

<script type="text/babel">
  /* ここにコードを書く */
</script>
```

```html:React+Redux+ReduxSaga
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.1/react-redux.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/redux-saga/0.14.0/redux-saga.min.js"></script>
```

# 例

```html
<!DOCTYPE html>

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Date Calculator</title>

<link href="https://cdn.muicss.com/mui-0.9.7/css/mui.min.css" rel="stylesheet" type="text/css">

<style>

html, body {
  background: #A9D0F5;
  height: 100%;
  min-height: 800px;
}
@media (min-width: 544px) {
  html, body {
    min-height: 580px;
  }
}

#main {
  background: white;
  width: 80%;
  height: 100%;
  margin: auto;
  padding-top: 20px;
  display: flex;
  align-items: center;
}

</style>

<!-- Compiler and Polyfills -->
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.0/babel.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.9.1/polyfill.min.js"></script>

<!-- React + Redux + ReduxSaga -->
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.1/react-redux.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/redux-saga/0.14.0/redux-saga.min.js"></script>

<!-- Other libraries -->
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/muicss/0.9.7/react/mui-react.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment-with-locales.min.js"></script>

<script type="text/babel">

/**
 * Initializations
 */

moment.locale('ja');


/**
 * Actions
 */

function createActionCreators(actions) {
  const creators = {};
  for (const [key, type] of Object.entries(actions)) {
    const name = key.toLowerCase().replace(/_(.)/g, (_, c) => c.toUpperCase());
    creators[name] = payload => ({ type, payload });
  }
  return creators;
}

const PREFIX = '@@example/';
const DATE_ADD_CHANGED    = PREFIX + 'DATE_ADD_CHANGED';
const DATE_ADD_CALCULATE  = PREFIX + 'DATE_ADD_CALCULATE';
const DATE_DIFF_CHANGED   = PREFIX + 'DATE_DIFF_CHANGED';
const DATE_DIFF_CALCULATE = PREFIX + 'DATE_DIFF_CALCULATE';

const {
  dateAddChanged, dateAddCalculate,
  dateDiffChanged, dateDiffCalculate,
} = createActionCreators({
  DATE_ADD_CHANGED, DATE_ADD_CALCULATE,
  DATE_DIFF_CHANGED, DATE_DIFF_CALCULATE,
});


/**
 * Reducers
 */

const { combineReducers } = Redux;

function dateAddControl(
  state = { from: '', span: '', answer: null },
  { type, payload }
) {
  switch (type) {
    case DATE_ADD_CHANGED:
      return { ...state, ...payload };
    case DATE_ADD_CALCULATE:
      const fromDate = moment(state.from);
      const number = parseInt(state.span, 10);
      const answer =
        fromDate.isValid() && !isNaN(number)
        ? fromDate.add(state.span, 'days').format('ll')
        : null;
      return { ...state, answer };
    default:
      return state;
  }
}

function dateDiffControl(
  state = { from: '', to: '', answer: null },
  { type, payload }
) {
  switch (type) {
    case DATE_DIFF_CHANGED:
      return { ...state, ...payload };
    case DATE_DIFF_CALCULATE:
      const fromDate = moment(state.from);
      const toDate = moment(state.to);
      const answer =
        fromDate.isValid() && toDate.isValid()
        ? toDate.diff(fromDate, 'days')
        : null;
      return { ...state, answer };
    default:
      return state;
  }
}

const rootReducer = combineReducers({ dateAddControl, dateDiffControl });


/**
 * Sagas
 */

const { delay } = ReduxSaga;
const { put, take, call, fork } = ReduxSaga.effects;

function createLazySaga(inputAction, ms, outputActionCreator) {
  return function* () {
    let task;
    while (true) {
      const { payload } = yield take(inputAction);
      if (task && task.isRunning()) {
        task.cancel();
      }
      task = yield fork(function* () {
        yield call(delay, ms);
        yield put(outputActionCreator(payload));
      });
    }
  };
}

function* rootSaga() {
  yield fork(createLazySaga(DATE_ADD_CHANGED, 250, dateAddCalculate));
  yield fork(createLazySaga(DATE_DIFF_CHANGED, 250, dateDiffCalculate));
}


/**
 * Components
 */

const { Container, Input, Panel, Form } = mui.react;

const PaddedInput = props => (
  <Input {...props} style={{ padding: '10px' }} />
);
const StaticForm = props => (
  <Form {...props} onSubmit={e => e.preventDefault()} />
);
const CenterBlock = ({ children, ...props }) => (
  <div {...props} style={{ display: 'table', width: '100%', height: '120px' }}>
    <div style={{ display: 'table-cell', textAlign: 'center', verticalAlign: 'middle' }}>
      { children }
    </div>
  </div>
);
const Large = props => (
  <span {...props} style={{ fontSize: '40px' }} />
);

const AddTool = ({ from, span, answer, dateAddChanged }) => (
  <Panel>
    <legend>N日後の日付を計算します</legend>
    <StaticForm inline={true}>
      <PaddedInput type="date" value={from} onChange={e => dateAddChanged({from: e.target.value})} />
      の
      <PaddedInput type="number" value={span} onChange={e => dateAddChanged({span: e.target.value})} />
      日後は
      <CenterBlock>
        {answer ? <Large>{answer}</Large> : ''}
      </CenterBlock>
    </StaticForm>
  </Panel>
);

const DiffTool = ({ from, to, answer, dateDiffChanged }) => (
  <Panel>
    <StaticForm inline={true}>
      <legend>日付の差を計算します</legend>
      <PaddedInput type="date" value={from} onChange={e => dateDiffChanged({from: e.target.value})} />
      と
      <PaddedInput type="date" value={to} onChange={e => dateDiffChanged({to: e.target.value})} />
      の差は
      <CenterBlock>
        {answer ? <Large>{answer}日</Large> : ''}
      </CenterBlock>
    </StaticForm>
  </Panel>
);


/**
 * Containers
 */

const { bindActionCreators } = Redux;
const { connect } = ReactRedux;

const App = connect(
  state => state,
  dispatch => bindActionCreators({ dateAddChanged, dateDiffChanged }, dispatch),
)(
  ({ dateAddControl, dateDiffControl, dateAddChanged, dateDiffChanged }) => (
    <Container>
      <AddTool {...dateAddControl} dateAddChanged={dateAddChanged} />
      <DiffTool {...dateDiffControl} dateDiffChanged={dateDiffChanged} />
    </Container>
  )
);


/**
 * Executor
 */

const { createStore, applyMiddleware, compose } = Redux;
const { Provider } = ReactRedux;
const createSagaMiddleware = ReduxSaga.default;

const sagaMiddleware = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(sagaMiddleware)));

sagaMiddleware.run(rootSaga);
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('main'));

</script>

<div id="main">
  <div style="display: table; width: 100%; height: 120px;">
    <div style="display: table-cell; text-align: center; vertical-align: middle;">
      <span style="font-size: 40px;">Now Loading ...</span>
    </div>
  </div>
</div>
```

- [Live Demo](https://mpyw-yattemita.github.io/react-redux-saga-standalone-examples/date-calculator/)
- [ソースコードをGitHhubで閲覧](https://github.com/mpyw-yattemita/react-redux-saga-standalone-examples/blob/gh-pages/date-calculator/index.html)