├── .gitignore ├── Brocfile.js ├── LICENSE ├── README.md ├── package.json └── src ├── __tests__ └── sum-test.js ├── app ├── actions │ └── LinksActions.js ├── components │ ├── About.js │ ├── App.js │ ├── Home.js │ └── UsefulLinks.js ├── lib │ └── tools.js └── stores │ └── LinksStore.js ├── index.html ├── index.js ├── lib └── sum.js └── styles └── app.scss /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .DS_Store 4 | tmp 5 | bower_components 6 | /dist/ 7 | /coverage/ 8 | -------------------------------------------------------------------------------- /Brocfile.js: -------------------------------------------------------------------------------- 1 | var rimraf = require('rimraf'), 2 | mergeTrees = require('broccoli-merge-trees'), 3 | Funnel = require('broccoli-funnel'), 4 | compileSass = require('broccoli-sass'), 5 | babelTranspiler = require('broccoli-babel-transpiler'), 6 | fastBrowserify = require('broccoli-fast-browserify'), 7 | uglifyJavaScript = require('broccoli-uglify-js'), 8 | gzipFiles = require('broccoli-gzip'), 9 | env = require('broccoli-env').getEnv(); 10 | 11 | // copy index.html 12 | var staticFiles = new Funnel('src', { 13 | files: ['index.html'] 14 | }); 15 | 16 | // convert SCSS into CSS 17 | var cssFiles = compileSass(['src'], 'styles/app.scss', 'styles/app.css'); 18 | 19 | // exclude tests from build 20 | var jsFiles = new Funnel('src', { 21 | exclude: [new RegExp(/__tests__/)], 22 | include: [new RegExp(/\.js$/)] 23 | }); 24 | 25 | // transpile ES6/7 into ES5 26 | jsFiles = babelTranspiler(jsFiles, { 27 | stage: 0, 28 | sourceMaps: 'inline', // add sourcemaps for ES6 debugging 29 | plugins: ['typecheck'] // add Flow types checking! 30 | }); 31 | 32 | // transpile for the browser 33 | jsFiles = fastBrowserify(jsFiles, { 34 | debug: env !== 'production', // add sourcemaps if not for production 35 | bundles: { 36 | 'index.js': { 37 | entryPoints: ['index.js'] 38 | } 39 | } 40 | }); 41 | 42 | if (env === 'production') { 43 | jsFiles = uglifyJavaScript(jsFiles); 44 | jsFiles = gzipFiles(jsFiles, { 45 | extensions: ['js', 'css'], 46 | keepUncompressed: true 47 | }); 48 | } 49 | 50 | rimraf.sync('./dist'); 51 | module.exports = mergeTrees([staticFiles, cssFiles, jsFiles], {overwrite: true}); 52 | 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jaroslaw Zabiello 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # react-es7 3 | 4 | 5 | Example of [React](http://facebook.github.io/react/) + [Flow](http://flowtype.org/) types + [Reflux](https://www.npmjs.com/package/reflux) + [routing](https://github.com/rackt/react-router) + [ES6 with experimental ES7 syntax](https://babeljs.io/docs/usage/experimental/) + [Semantic-UI](http://semantic-ui.com/) 6 | 7 | * All JavaScript files are transpiled with [Babel](https://babeljs.io/) library with enabled [experimental ES7 syntax](http://babeljs.io/docs/usage/transformers/#es7-experimental-). 8 | * The project support [Flow](http://flowtype.org/) type annotations 9 | * For using experimental ES7 code in [Jest](http://facebook.github.io/jest/docs/tutorial-react.html) tests 10 | * The workflow and communication between components is built using [Reflux](https://github.com/spoike/refluxjs), a slighty [improved](http://blog.krawaller.se/posts/reflux-refinement/) Flux paradigm. 11 | * Unit testing is provided by [Jest](https://facebook.github.io/jest/). 12 | * Using [Semantic-UI](http://semantic-ui.com/) framework with React components ([react-semantify](http://react-components.com/component/react-semantify)). 13 | 14 | If for some reasons you can't or don' want to use Node for file processing, you can use [hipertracker/react-babel](https://github.com/hipertracker/react-babel). It uses only the browser but with AMD. All ES6 files are transpiled only once (during module loading), and cached. 15 | 16 | ## Install 17 | 18 | ``` 19 | npm install -g broccoli-cli 20 | npm install 21 | ``` 22 | 23 | ## Usage 24 | 25 | 26 | * fast processing in memory: 27 | 28 | ``` 29 | broccoli serve 30 | ``` 31 | or 32 | 33 | ``` 34 | npm start 35 | ``` 36 | 37 | * with watcher and generating files: 38 | 39 | ``` 40 | broccoli-timepiece dist 41 | http-server dist/ -p 4200 42 | ``` 43 | 44 | Open `http://localhost:4200` 45 | 46 | 47 | * building files for production (minified, mangled and compressed): 48 | 49 | ``` 50 | BROCCOLI_ENV=production broccoli build dist 51 | ``` 52 | 53 | Test and code coverage: 54 | 55 | ``` 56 | $ npm test 57 | ``` 58 | 59 | Also code coverage in the browser: 60 | 61 | ``` 62 | npm run coverage 63 | ``` 64 | 65 | ## Todo 66 | * Replace [Reflux](https://www.npmjs.com/package/reflux) with [nuclear-js](https://github.com/optimizely/nuclear-js) 67 | * Add examples of [Flow](http://flowtype.org/) types checking 68 | * Add more [prop validation](http://facebook.github.io/react/docs/reusable-components.html) examples 69 | * Add more unit tests showing [Jest](http://facebook.github.io/jest/) testing ES5/ES7 code with [Flow](http://flowtype.org/) types checking. 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-es7", 3 | "version": "0.1.1", 4 | "description": "Example of using React + Flow types + Reflux + router + ES6 with experimental ES7 syntax + Jest + Semantic-UI", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "BABEL_JEST_STAGE=0 jest --coverage", 8 | "coverage": "cd coverage/lcov-report && open http://localhost:8080 && http-server", 9 | "bundle": "broccoli build dist", 10 | "start": "broccoli serve" 11 | }, 12 | "jest": { 13 | "scriptPreprocessor": "/node_modules/babel-jest", 14 | "testFileExtensions": [ 15 | "es6", 16 | "js" 17 | ], 18 | "moduleFileExtensions": [ 19 | "js", 20 | "json", 21 | "es6" 22 | ] 23 | }, 24 | "author": "Jaroslaw Zabiello ", 25 | "license": "MIT", 26 | "devDependencies": { 27 | "babel": "^5.5.6", 28 | "babel-jest": "^5.2.0", 29 | "babel-plugin-typecheck": "0.0.3", 30 | "broccoli": "^0.16.2", 31 | "broccoli-babel-transpiler": "^5.0.0", 32 | "broccoli-env": "0.0.1", 33 | "broccoli-fast-browserify": "^0.2.6", 34 | "broccoli-funnel": "^0.2.3", 35 | "broccoli-gzip": "^0.2.1", 36 | "broccoli-merge-trees": "^0.2.1", 37 | "broccoli-sass": "^0.6.6", 38 | "broccoli-source-map": "^0.2.0", 39 | "broccoli-timepiece": "^0.3.0", 40 | "broccoli-uglify-js": "^0.1.3", 41 | "jest-cli": "^0.4.12", 42 | "rimraf": "^2.3.3" 43 | }, 44 | "dependencies": { 45 | "lodash": "^3.9.3", 46 | "react": "^0.13.3", 47 | "react-router": "^0.13.3", 48 | "react-semantify": "^0.3.1", 49 | "reflux": "^0.2.7" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/__tests__/sum-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../lib/sum'); // or jest.autoMockOff(); 2 | 3 | // cannot use es6 modules syntax because 4 | // jest.dontMock & jest.autoMockOff() 5 | // do not understand ES6 modules yet 6 | const sum = require('../lib/sum'); 7 | 8 | describe('sum', function () { 9 | it('adds 1 + 2 to equal 3', () => expect(sum(1, 2)).toBe(3)) 10 | }); 11 | -------------------------------------------------------------------------------- /src/app/actions/LinksActions.js: -------------------------------------------------------------------------------- 1 | import Reflux from 'reflux' 2 | 3 | // Each action is like an event channel for one specific event. Actions are called by components. 4 | // The store is listening to all actions, and the components in turn are listening to the store. 5 | // Thus the flow is: User interaction -> component calls action -> store reacts and triggers -> components update 6 | const LinksActions = Reflux.createActions([ 7 | 'loadData', 8 | 'loadDataSuccess' 9 | ]); 10 | 11 | const data = [ 12 | { 13 | url: 'http://facebook.github.io/react/', 14 | title: 'React home page' 15 | }, 16 | { 17 | "url": "https://github.com/enaqx/awesome-react", 18 | "title": "A collection of awesome React libraries, resources and shiny things." 19 | }, 20 | { 21 | "url": "https://reactjsnews.com/", 22 | "title": "ReactJS News and tutorials from the community" 23 | }, 24 | { 25 | "url": "http://spoike.ghost.io/deconstructing-reactjss-flux/", 26 | "title": "Reflux - deconstructing ReactJS's Flux" 27 | }, 28 | { 29 | "url": "https://github.com/echenley/react-news", 30 | "title": "Hacker News clone written in React + Reflux + Firebase" 31 | }, 32 | { 33 | "url": "https://github.com/WRidder/react-spa", 34 | "title": "Community site SPA based on ReactJS + Reflux" 35 | }, 36 | { 37 | "url": "http://react-components.com", 38 | "title": "Searchable database of React components" 39 | }, 40 | { 41 | "url": "http://www.reactjsx.com/", 42 | "title": "Search reusable React components" 43 | }, 44 | { 45 | "url": "http://formatjs.io/react/", 46 | "title": "React mixin for internationalization" 47 | }, 48 | { 49 | "url": "http://isomorphic.net/", 50 | "title": "Isomorphic JavaScript - The future of web app development" 51 | }, 52 | { 53 | "url": "https://code.tutsplus.com/courses/getting-started-with-reactjs", 54 | "title": "Getting Started With React.js [screencasts]" 55 | }, 56 | { 57 | "url": "https://egghead.io/series/react-flux-architecture", 58 | "title": "React: Flux Architecture [screencasts]" 59 | }, 60 | { 61 | "url": "https://www.facebook.com/groups/228321510706889/", 62 | "title": "ReactJS (Facebook group)" 63 | }, 64 | { 65 | "url": "http://react-bootstrap.github.io/", 66 | "title": "Bootstrap 3 in React" 67 | }, 68 | { 69 | "url": "http://react-components.com/component/react-semantify", 70 | "title": "Semantic-UI in React" 71 | }, 72 | { 73 | "url": "https://github.com/sterpe/quantum-flux", 74 | "title": "https://github.com/sterpe/quantum-flux" 75 | }, 76 | { 77 | "url": "http://react.rocks/", 78 | "title": "Collection of examples and tutorials" 79 | }, 80 | { 81 | "url": "https://reactjsnews.com/react-style-guide-patterns-i-like/", 82 | "title": "React Style Guide" 83 | } 84 | ]; 85 | 86 | LinksActions.loadData.listen(function () { 87 | // here is the place for external communication with servers... 88 | LinksActions.loadDataSuccess(data); 89 | }); 90 | 91 | export default LinksActions 92 | -------------------------------------------------------------------------------- /src/app/components/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Segment} from 'react-semantify' 3 | 4 | class About extends React.Component { 5 | render() { 6 | return ( 7 | 8 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ligula eros, sollicitudin nec libero 9 | non, cursus fringilla lectus. Aliquam volutpat velit feugiat pellentesque lobortis. Pellentesque 10 | pulvinar ornare ligula, sit amet imperdiet nibh tincidunt et. Suspendisse suscipit dolor vel turpis 11 | viverra ullamcorper. Fusce in enim elementum, hendrerit enim ut, pretium ipsum. Sed malesuada justo nec 12 | augue sodales, eu luctus dolor vulputate. Nunc ut consequat ipsum. Nam tempor fermentum risus at dictum. 13 | 14 | ) 15 | } 16 | } 17 | 18 | export default About -------------------------------------------------------------------------------- /src/app/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactRouter, {RouteHandler, Link} from 'react-router' 3 | import {Menu, Segment, Section} from 'react-semantify' 4 | 5 | class App extends React.Component { 6 | render() { 7 | const styles= { 8 | margin: 10 9 | }; 10 | return ( 11 |
12 | 13 | 14 | 15 | Home 16 | 17 | 18 | 19 | About 20 | 21 | 22 | 23 | 24 | Git:   25 | 26 | https://github.com/hipertracker/react-es7 27 | 28 | 29 |
30 | ) 31 | } 32 | } 33 | 34 | export default App -------------------------------------------------------------------------------- /src/app/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Segment} from 'react-semantify' 3 | 4 | import UsefulLinks from './UsefulLinks' 5 | 6 | class Home extends React.Component { 7 | render() { 8 | return ( 9 |
10 | 11 | 12 | 13 |
14 | ) 15 | } 16 | } 17 | 18 | export default Home -------------------------------------------------------------------------------- /src/app/components/UsefulLinks.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react' 2 | import Reflux from 'reflux' 3 | 4 | import LinksActions from '../actions/LinksActions' 5 | import LinksStore from '../stores/LinksStore' 6 | import {m} from '../lib/tools' 7 | 8 | const UsefulLinks = React.createClass({ 9 | mixins: [Reflux.listenTo(LinksStore, 'linksUpdate')], 10 | propTypes: { 11 | title: PropTypes.string.isRequired 12 | }, 13 | getInitialState() { 14 | return { 15 | links: [] 16 | } 17 | }, 18 | linksUpdate(data) { 19 | this.setState({links: data.links}); 20 | }, 21 | componentWillMount() { 22 | LinksActions.loadData(); 23 | }, 24 | render() { 25 | if (!this.state.links.length) { 26 | return ; 27 | } 28 | 29 | const styles = { 30 | item: { 31 | paddingLeft: 30 32 | } 33 | }; 34 | const style = m(styles.item, this.props.style); 35 | const items = this.state.links.map((item, key) => 36 |
  • 37 | {item.title} ({item.url}) 38 |
  • ); 39 | return ( 40 |
    41 | {this.props.title} 42 |
      {items}
    43 |
    44 | ); 45 | } 46 | }); 47 | 48 | export default UsefulLinks 49 | -------------------------------------------------------------------------------- /src/app/lib/tools.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import _ from 'lodash' 3 | 4 | // mounting a React component to a selector id location 5 | export function mount(component, location, argv = {}) { 6 | if (!location) { 7 | location = document.body; 8 | } else { 9 | location = document.getElementById(location); 10 | } 11 | if (!React.isValidElement(component)) { 12 | component = React.createFactory(component); 13 | } 14 | React.render(component(argv), location); 15 | } 16 | 17 | // merging properties, used in React component for aggregating styles 18 | export function m() { 19 | let res = {}; 20 | _.each(arguments, (arg) => arg && _.merge(res, arg)); 21 | return res; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/app/stores/LinksStore.js: -------------------------------------------------------------------------------- 1 | import Reflux from 'reflux' 2 | 3 | import LinksActions from '../actions/LinksActions' 4 | 5 | const LinksStore = Reflux.createStore({ 6 | listenables: [LinksActions], 7 | 8 | onLoadDataSuccess: function (data) { 9 | this.trigger({links: data}); 10 | } 11 | }); 12 | 13 | export default LinksStore 14 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Test 8 | 9 | 10 | 11 | 12 | 13 |
    14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {mount} from './app/lib/tools'; 3 | 4 | // For mounting App component into
    use: 5 | // 6 | // import App from './app/components/App'; 7 | // mount(App, 'app'); 8 | // 9 | // else 10 | 11 | import App from './app/components/App'; 12 | import Home from './app/components/Home'; 13 | import About from './app/components/About'; 14 | 15 | import Router, {Route, DefaultRoute} from 'react-router' 16 | 17 | const routes = ( 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | 25 | Router.run(routes, function (Handler) { 26 | React.render(, document.getElementById('app')); 27 | }); 28 | 29 | // or HTML5 pushstate history: 30 | //Router.run(routes, Router.HistoryLocation, function (Handler) { 31 | // React.render(, document.getElementById('app')); 32 | //}); 33 | -------------------------------------------------------------------------------- /src/lib/sum.js: -------------------------------------------------------------------------------- 1 | // ES6 code with Flow types 2 | 3 | const sum = (a:number, b:number) => a + b; 4 | 5 | export default sum 6 | 7 | -------------------------------------------------------------------------------- /src/styles/app.scss: -------------------------------------------------------------------------------- 1 | $bgColor: white; 2 | body { 3 | background-color: $bgColor; 4 | } --------------------------------------------------------------------------------