From ba62215d9ef8655743ce7b1380056755943e3d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kurk=C3=B3=20Mih=C3=A1ly?= Date: Tue, 14 Nov 2017 19:34:00 +0200 Subject: cmd, dashboard: dashboard using React, Material-UI, Recharts (#15393) * cmd, dashboard: dashboard using React, Material-UI, Recharts * cmd, dashboard, metrics: initial proof of concept dashboard * dashboard: delete blobs * dashboard: gofmt -s -w . * dashboard: minor text and code polishes --- dashboard/assets/.eslintrc | 52 +++++++++ dashboard/assets/components/Common.jsx | 52 +++++++++ dashboard/assets/components/Dashboard.jsx | 169 ++++++++++++++++++++++++++++++ dashboard/assets/components/Header.jsx | 87 +++++++++++++++ dashboard/assets/components/Home.jsx | 89 ++++++++++++++++ dashboard/assets/components/Main.jsx | 109 +++++++++++++++++++ dashboard/assets/components/SideBar.jsx | 106 +++++++++++++++++++ dashboard/assets/index.jsx | 36 +++++++ dashboard/assets/package.json | 22 ++++ dashboard/assets/public/dashboard.html | 17 +++ dashboard/assets/webpack.config.js | 36 +++++++ 11 files changed, 775 insertions(+) create mode 100644 dashboard/assets/.eslintrc create mode 100644 dashboard/assets/components/Common.jsx create mode 100644 dashboard/assets/components/Dashboard.jsx create mode 100644 dashboard/assets/components/Header.jsx create mode 100644 dashboard/assets/components/Home.jsx create mode 100644 dashboard/assets/components/Main.jsx create mode 100644 dashboard/assets/components/SideBar.jsx create mode 100644 dashboard/assets/index.jsx create mode 100644 dashboard/assets/package.json create mode 100644 dashboard/assets/public/dashboard.html create mode 100644 dashboard/assets/webpack.config.js (limited to 'dashboard/assets') diff --git a/dashboard/assets/.eslintrc b/dashboard/assets/.eslintrc new file mode 100644 index 000000000..04ae15d01 --- /dev/null +++ b/dashboard/assets/.eslintrc @@ -0,0 +1,52 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// React syntax style mostly according to https://github.com/airbnb/javascript/tree/master/react +{ + "plugins": [ + "react" + ], + "parser": "babel-eslint", + "parserOptions": { + "ecmaFeatures": { + "jsx": true, + "modules": true + } + }, + "rules": { + "react/prefer-es6-class": 2, + "react/prefer-stateless-function": 2, + "react/jsx-pascal-case": 2, + "react/jsx-closing-bracket-location": [1, {"selfClosing": "tag-aligned", "nonEmpty": "tag-aligned"}], + "react/jsx-closing-tag-location": 1, + "jsx-quotes": ["error", "prefer-double"], + "no-multi-spaces": "error", + "react/jsx-tag-spacing": 2, + "react/jsx-curly-spacing": [2, {"when": "never", "children": true}], + "react/jsx-boolean-value": 2, + "react/no-string-refs": 2, + "react/jsx-wrap-multilines": 2, + "react/self-closing-comp": 2, + "react/jsx-no-bind": 2, + "react/require-render-return": 2, + "react/no-is-mounted": 2, + "key-spacing": ["error", {"align": { + "beforeColon": false, + "afterColon": true, + "on": "value" + }}] + } +} diff --git a/dashboard/assets/components/Common.jsx b/dashboard/assets/components/Common.jsx new file mode 100644 index 000000000..5129939c5 --- /dev/null +++ b/dashboard/assets/components/Common.jsx @@ -0,0 +1,52 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// isNullOrUndefined returns true if the given variable is null or undefined. +export const isNullOrUndefined = variable => variable === null || typeof variable === 'undefined'; + +export const LIMIT = { + memory: 200, // Maximum number of memory data samples. + traffic: 200, // Maximum number of traffic data samples. + log: 200, // Maximum number of logs. +}; +// The sidebar menu and the main content are rendered based on these elements. +export const TAGS = (() => { + const T = { + home: { title: "Home", }, + chain: { title: "Chain", }, + transactions: { title: "Transactions", }, + network: { title: "Network", }, + system: { title: "System", }, + logs: { title: "Logs", }, + }; + // Using the key is circumstantial in some cases, so it is better to insert it also as a value. + // This way the mistyping is prevented. + for(let key in T) { + T[key]['id'] = key; + } + return T; +})(); + +export const DATA_KEYS = (() => { + const DK = {}; + ["memory", "traffic", "logs"].map(key => { + DK[key] = key; + }); + return DK; +})(); + +// Temporary - taken from Material-UI +export const DRAWER_WIDTH = 240; diff --git a/dashboard/assets/components/Dashboard.jsx b/dashboard/assets/components/Dashboard.jsx new file mode 100644 index 000000000..740acf959 --- /dev/null +++ b/dashboard/assets/components/Dashboard.jsx @@ -0,0 +1,169 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import {withStyles} from 'material-ui/styles'; + +import SideBar from './SideBar.jsx'; +import Header from './Header.jsx'; +import Main from "./Main.jsx"; +import {isNullOrUndefined, LIMIT, TAGS, DATA_KEYS,} from "./Common.jsx"; + +// Styles for the Dashboard component. +const styles = theme => ({ + appFrame: { + position: 'relative', + display: 'flex', + width: '100%', + height: '100%', + background: theme.palette.background.default, + }, +}); + +// Dashboard is the main component, which renders the whole page, makes connection with the server and listens for messages. +// When there is an incoming message, updates the page's content correspondingly. +class Dashboard extends Component { + constructor(props) { + super(props); + this.state = { + active: TAGS.home.id, // active menu + sideBar: true, // true if the sidebar is opened + memory: [], + traffic: [], + logs: [], + shouldUpdate: {}, + }; + } + + // componentDidMount initiates the establishment of the first websocket connection after the component is rendered. + componentDidMount() { + this.reconnect(); + } + + // reconnect establishes a websocket connection with the server, listens for incoming messages + // and tries to reconnect on connection loss. + reconnect = () => { + const server = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/api"); + + server.onmessage = event => { + const msg = JSON.parse(event.data); + if (isNullOrUndefined(msg)) { + return; + } + this.update(msg); + }; + + server.onclose = () => { + setTimeout(this.reconnect, 3000); + }; + }; + + // update analyzes the incoming message, and updates the charts' content correspondingly. + update = msg => { + console.log(msg); + this.setState(prevState => { + let newState = []; + newState.shouldUpdate = {}; + const insert = (key, values, limit) => { + newState[key] = [...prevState[key], ...values]; + while (newState[key].length > limit) { + newState[key].shift(); + } + newState.shouldUpdate[key] = true; + }; + // (Re)initialize the state with the past data. + if (!isNullOrUndefined(msg.history)) { + const memory = DATA_KEYS.memory; + const traffic = DATA_KEYS.traffic; + newState[memory] = []; + newState[traffic] = []; + if (!isNullOrUndefined(msg.history.memorySamples)) { + newState[memory] = msg.history.memorySamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value); + while (newState[memory].length > LIMIT.memory) { + newState[memory].shift(); + } + newState.shouldUpdate[memory] = true; + } + if (!isNullOrUndefined(msg.history.trafficSamples)) { + newState[traffic] = msg.history.trafficSamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value); + while (newState[traffic].length > LIMIT.traffic) { + newState[traffic].shift(); + } + newState.shouldUpdate[traffic] = true; + } + } + // Insert the new data samples. + if (!isNullOrUndefined(msg.memory)) { + insert(DATA_KEYS.memory, [isNullOrUndefined(msg.memory.value) ? 0 : msg.memory.value], LIMIT.memory); + } + if (!isNullOrUndefined(msg.traffic)) { + insert(DATA_KEYS.traffic, [isNullOrUndefined(msg.traffic.value) ? 0 : msg.traffic.value], LIMIT.traffic); + } + if (!isNullOrUndefined(msg.log)) { + insert(DATA_KEYS.logs, [msg.log], LIMIT.log); + } + + return newState; + }); + }; + + // The change of the active label on the SideBar component will trigger a new render in the Main component. + changeContent = active => { + this.setState(prevState => prevState.active !== active ? {active: active} : {}); + }; + + openSideBar = () => { + this.setState({sideBar: true}); + }; + + closeSideBar = () => { + this.setState({sideBar: false}); + }; + + render() { + // The classes property is injected by withStyles(). + const {classes} = this.props; + + return ( +
+
+ +
+
+ ); + } +} + +Dashboard.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Dashboard); diff --git a/dashboard/assets/components/Header.jsx b/dashboard/assets/components/Header.jsx new file mode 100644 index 000000000..7cf57c9c0 --- /dev/null +++ b/dashboard/assets/components/Header.jsx @@ -0,0 +1,87 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import {withStyles} from 'material-ui/styles'; +import AppBar from 'material-ui/AppBar'; +import Toolbar from 'material-ui/Toolbar'; +import Typography from 'material-ui/Typography'; +import IconButton from 'material-ui/IconButton'; +import MenuIcon from 'material-ui-icons/Menu'; + +import {DRAWER_WIDTH} from './Common.jsx'; + +// Styles for the Header component. +const styles = theme => ({ + appBar: { + position: 'absolute', + transition: theme.transitions.create(['margin', 'width'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + }, + appBarShift: { + marginLeft: DRAWER_WIDTH, + width: `calc(100% - ${DRAWER_WIDTH}px)`, + transition: theme.transitions.create(['margin', 'width'], { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + }, + menuButton: { + marginLeft: 12, + marginRight: 20, + }, + hide: { + display: 'none', + }, +}); + +// Header renders a header, which contains a sidebar opener icon when that is closed. +class Header extends Component { + render() { + // The classes property is injected by withStyles(). + const {classes} = this.props; + + return ( + + + + + + + Go Ethereum Dashboard + + + + ); + } +} + +Header.propTypes = { + classes: PropTypes.object.isRequired, + opened: PropTypes.bool.isRequired, + open: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(Header); diff --git a/dashboard/assets/components/Home.jsx b/dashboard/assets/components/Home.jsx new file mode 100644 index 000000000..f67bac555 --- /dev/null +++ b/dashboard/assets/components/Home.jsx @@ -0,0 +1,89 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Grid from 'material-ui/Grid'; +import {LineChart, AreaChart, Area, YAxis, CartesianGrid, Line, ResponsiveContainer} from 'recharts'; +import {withTheme} from 'material-ui/styles'; + +import {isNullOrUndefined, DATA_KEYS} from "./Common.jsx"; + +// ChartGrid renders a grid container for responsive charts. +// The children are Recharts components extended with the Material-UI's xs property. +class ChartGrid extends Component { + render() { + return ( + + { + React.Children.map(this.props.children, child => ( + + + {React.cloneElement(child, {data: child.props.values.map(value => ({value: value}))})} + + + )) + } + + ); + } +} + +ChartGrid.propTypes = { + spacing: PropTypes.number.isRequired, +}; + +// Home renders the home component. +class Home extends Component { + shouldComponentUpdate(nextProps) { + return !isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.memory]) || + !isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.traffic]); + } + + render() { + const {theme} = this.props; + const memoryColor = theme.palette.primary[300]; + const trafficColor = theme.palette.secondary[300]; + + return ( + + + + + + + + + + + + + + + + + + + ); + } +} + +Home.propTypes = { + theme: PropTypes.object.isRequired, + shouldUpdate: PropTypes.object.isRequired, +}; + +export default withTheme()(Home); diff --git a/dashboard/assets/components/Main.jsx b/dashboard/assets/components/Main.jsx new file mode 100644 index 000000000..b119d1ffd --- /dev/null +++ b/dashboard/assets/components/Main.jsx @@ -0,0 +1,109 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import {withStyles} from 'material-ui/styles'; + +import {TAGS, DRAWER_WIDTH} from "./Common.jsx"; +import Home from './Home.jsx'; + +// ContentSwitch chooses and renders the proper page content. +class ContentSwitch extends Component { + render() { + switch(this.props.active) { + case TAGS.home.id: + return ; + case TAGS.chain.id: + return null; + case TAGS.transactions.id: + return null; + case TAGS.network.id: + // Only for testing. + return null; + case TAGS.system.id: + return null; + case TAGS.logs.id: + return
{this.props.logs.map((log, index) =>
{log}
)}
; + } + return null; + } +} + +ContentSwitch.propTypes = { + active: PropTypes.string.isRequired, + shouldUpdate: PropTypes.object.isRequired, +}; + +// styles contains the styles for the Main component. +const styles = theme => ({ + content: { + width: '100%', + marginLeft: -DRAWER_WIDTH, + flexGrow: 1, + backgroundColor: theme.palette.background.default, + padding: theme.spacing.unit * 3, + transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + marginTop: 56, + overflow: 'auto', + [theme.breakpoints.up('sm')]: { + content: { + height: 'calc(100% - 64px)', + marginTop: 64, + }, + }, + }, + contentShift: { + marginLeft: 0, + transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + }, +}); + +// Main renders a component for the page content. +class Main extends Component { + render() { + // The classes property is injected by withStyles(). + const {classes} = this.props; + + return ( +
+ +
+ ); + } +} + +Main.propTypes = { + classes: PropTypes.object.isRequired, + opened: PropTypes.bool.isRequired, + active: PropTypes.string.isRequired, + shouldUpdate: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Main); diff --git a/dashboard/assets/components/SideBar.jsx b/dashboard/assets/components/SideBar.jsx new file mode 100644 index 000000000..ef077f1e0 --- /dev/null +++ b/dashboard/assets/components/SideBar.jsx @@ -0,0 +1,106 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import {withStyles} from 'material-ui/styles'; +import Drawer from 'material-ui/Drawer'; +import {IconButton} from "material-ui"; +import List, {ListItem, ListItemText} from 'material-ui/List'; +import ChevronLeftIcon from 'material-ui-icons/ChevronLeft'; + +import {TAGS, DRAWER_WIDTH} from './Common.jsx'; + +// Styles for the SideBar component. +const styles = theme => ({ + drawerPaper: { + position: 'relative', + height: '100%', + width: DRAWER_WIDTH, + }, + drawerHeader: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + padding: '0 8px', + ...theme.mixins.toolbar, + transitionDuration: { + enter: theme.transitions.duration.enteringScreen, + exit: theme.transitions.duration.leavingScreen, + } + }, +}); + +// SideBar renders a sidebar component. +class SideBar extends Component { + constructor(props) { + super(props); + + // clickOn contains onClick event functions for the menu items. + // Instantiate only once, and reuse the existing functions to prevent the creation of + // new function instances every time the render method is triggered. + this.clickOn = {}; + for(let key in TAGS) { + const id = TAGS[key].id; + this.clickOn[id] = event => { + event.preventDefault(); + console.log(event.target.key); + this.props.changeContent(id); + }; + } + } + + render() { + // The classes property is injected by withStyles(). + const {classes} = this.props; + + return ( + +
+
+ + + +
+ + { + Object.values(TAGS).map(tag => { + return ( + + + + ); + }) + } + +
+
+ ); + } +} + +SideBar.propTypes = { + classes: PropTypes.object.isRequired, + opened: PropTypes.bool.isRequired, + close: PropTypes.func.isRequired, + changeContent: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(SideBar); diff --git a/dashboard/assets/index.jsx b/dashboard/assets/index.jsx new file mode 100644 index 000000000..1e5fdc892 --- /dev/null +++ b/dashboard/assets/index.jsx @@ -0,0 +1,36 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +import React from 'react'; +import {hydrate} from 'react-dom'; +import {createMuiTheme, MuiThemeProvider} from 'material-ui/styles'; + +import Dashboard from './components/Dashboard.jsx'; + +// Theme for the dashboard. +const theme = createMuiTheme({ + palette: { + type: 'dark', + }, +}); + +// Renders the whole dashboard. +hydrate( + + + , + document.getElementById('dashboard') +); diff --git a/dashboard/assets/package.json b/dashboard/assets/package.json new file mode 100644 index 000000000..53376e5c8 --- /dev/null +++ b/dashboard/assets/package.json @@ -0,0 +1,22 @@ +{ + "dependencies": { + "babel-core": "^6.26.0", + "babel-eslint": "^8.0.1", + "babel-loader": "^7.1.2", + "babel-preset-env": "^1.6.1", + "babel-preset-react": "^6.24.1", + "babel-preset-stage-0": "^6.24.1", + "classnames": "^2.2.5", + "eslint": "^4.5.0", + "eslint-plugin-react": "^7.4.0", + "material-ui": "^1.0.0-beta.18", + "material-ui-icons": "^1.0.0-beta.17", + "path": "^0.12.7", + "prop-types": "^15.6.0", + "recharts": "^1.0.0-beta.0", + "react": "^16.0.0", + "react-dom": "^16.0.0", + "url": "^0.11.0", + "webpack": "^3.5.5" + } +} diff --git a/dashboard/assets/public/dashboard.html b/dashboard/assets/public/dashboard.html new file mode 100644 index 000000000..e064a2b51 --- /dev/null +++ b/dashboard/assets/public/dashboard.html @@ -0,0 +1,17 @@ + + + + + + + + Go Ethereum Dashboard + + + + + +
+ + + diff --git a/dashboard/assets/webpack.config.js b/dashboard/assets/webpack.config.js new file mode 100644 index 000000000..13f8c3fbc --- /dev/null +++ b/dashboard/assets/webpack.config.js @@ -0,0 +1,36 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +const path = require('path'); + +module.exports = { + entry: './index.jsx', + output: { + path: path.resolve(__dirname, 'public'), + filename: 'bundle.js', + }, + module: { + loaders: [ + { + test: /\.jsx$/, // regexp for JSX files + loader: 'babel-loader', // The babel configuration is in the package.json. + query: { + presets: ['env', 'react', 'stage-0'] + } + }, + ], + }, +}; -- cgit v1.2.3