diff --git a/.gitignore b/.gitignore index 2161e0a..33d6e23 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules tmp bower_components /dist/ +/coverage/ diff --git a/Brocfile.js b/Brocfile.js index 24ed08d..82f33d5 100644 --- a/Brocfile.js +++ b/Brocfile.js @@ -2,53 +2,39 @@ var rimraf = require('rimraf'), mergeTrees = require('broccoli-merge-trees'), Funnel = require('broccoli-funnel'), compileSass = require('broccoli-sass'), - //esTranspiler = require('broccoli-6to5-transpiler'), - esTranspiler = require('broccoli-babel-transpiler'), + babelTranspiler = require('broccoli-babel-transpiler'), fastBrowserify = require('broccoli-fast-browserify'), uglifyJavaScript = require('broccoli-uglify-js'), gzipFiles = require('broccoli-gzip'), env = require('broccoli-env').getEnv(); +// copy index.html var staticFiles = new Funnel('src', { files: ['index.html'] }); -// build vendor/* -var jqueryFiles = new Funnel('bower_components/jquery/dist', { - include: [ - new RegExp(/\.min\.(js|map)$/), - ], - destDir: 'vendor/jquery' - }), - semanticUIFiles = new Funnel('bower_components/semantic-ui/dist', { - include: [ - new RegExp(/\.min\.(js|css)$/), - new RegExp(/themes/) - ], - destDir: 'vendor/semantic-ui' - }), - fontAwesomeFiles = new Funnel('bower_components/font-awesome', { - include: [ - new RegExp(/min\.css$/), - new RegExp(/fonts/) - ], - destDir: 'vendor/font-awesome' - }); -var vendorFiles = mergeTrees([jqueryFiles, semanticUIFiles, fontAwesomeFiles]); - +// convert SCSS into CSS var cssFiles = compileSass(['src'], 'styles/app.scss', 'styles/app.css'); +// exclude tests from build var jsFiles = new Funnel('src', { exclude: [new RegExp(/__tests__/)], include: [new RegExp(/\.js$/)] }); -jsFiles = esTranspiler(jsFiles, {experimental: true}); +// transpile ES6/7 into ES5 +jsFiles = babelTranspiler(jsFiles, { + stage: 0, + sourceMaps: 'inline', // add sourcemaps for ES6 debugging + plugins: ['typecheck'] // add Flow types checking! +}); +// transpile for the browser jsFiles = fastBrowserify(jsFiles, { + debug: env !== 'production', // add sourcemaps if not for production bundles: { 'index.js': { - entryPoints: ['**/index.js'] + entryPoints: ['index.js'] } } }); @@ -62,5 +48,5 @@ if (env === 'production') { } rimraf.sync('./dist'); -module.exports = mergeTrees([staticFiles, vendorFiles, jsFiles, cssFiles], {overwrite: false}); +module.exports = mergeTrees([staticFiles, cssFiles, jsFiles], {overwrite: true}); diff --git a/README.md b/README.md index 40398aa..8b1cbc8 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,22 @@ # react-es7 -Example of [React](http://facebook.github.io/react/) + [Reflux](https://www.npmjs.com/package/reflux) + [routing](https://github.com/rackt/react-router) + [ES6 with experimental ES7 syntax](https://6to5.org/docs/usage/experimental/) + [Semantic-UI](http://semantic-ui.com/) +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/) -* All JavaScript files are transpiled with [6to5](https://6to5.org/) library with enabled [experimental ES7 syntax](https://6to5.org/features.html). -* For using experimental ES7 code in [Jest](http://facebook.github.io/jest/docs/tutorial-react.html) tests, the project is using [my fork](https://github.com/hipertracker/6to5-jest) of `6to5-jest` package. +* All JavaScript files are transpiled with [Babel](https://babeljs.io/) library with enabled [experimental ES7 syntax](http://babeljs.io/docs/usage/transformers/#es7-experimental-). +* The project support [Flow](http://flowtype.org/) type annotations +* For using experimental ES7 code in [Jest](http://facebook.github.io/jest/docs/tutorial-react.html) tests * 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. * Unit testing is provided by [Jest](https://facebook.github.io/jest/). * Using [Semantic-UI](http://semantic-ui.com/) framework with React components ([react-semantify](http://react-components.com/component/react-semantify)). -If for some reasons you can't or don' want to use Node for file processing, you can use pure AMD version of that project: [hipertracker/react-es7-amd](https://github.com/hipertracker/react-es7-amd). It uses only a browser. +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. ## Install ``` npm install -g broccoli-cli npm install -bower install ``` ## Usage @@ -28,11 +28,10 @@ bower install ``` broccoli serve ``` +or -* with additional [LiveReload](https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei): - -``` -broccoli serve --live-reload-port 35729 +``` +npm start ``` * with watcher and generating files: @@ -51,16 +50,20 @@ Open `http://localhost:4200` BROCCOLI_ENV=production broccoli build dist ``` -Test: +Test and code coverage: ``` $ npm test ``` -Currently just a simple example of using unit tests in React for files using ES6/ES7 syntax. +Also code coverage in the browser: -## Todo +``` +npm run coverage +``` +## Todo +* Replace [Reflux](https://www.npmjs.com/package/reflux) with [nuclear-js](https://github.com/optimizely/nuclear-js) +* Add examples of [Flow](http://flowtype.org/) types checking * Add more [prop validation](http://facebook.github.io/react/docs/reusable-components.html) examples -* Add more unit tests showing [Jest](http://facebook.github.io/jest/) testing ES5/ES7 code. -* Add [Yeoman](http://yeoman.io/) project with options to choose SASS/Less/Stylus and Bootstrap/Semantic-UI. +* Add more unit tests showing [Jest](http://facebook.github.io/jest/) testing ES5/ES7 code with [Flow](http://flowtype.org/) types checking. diff --git a/bower.json b/bower.json deleted file mode 100644 index f2fa9dc..0000000 --- a/bower.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "react-es7", - "version": "0.0.7", - "homepage": "https://github.com/hipertracker/react-es7", - "authors": [ - "Jaroslaw Zabiello " - ], - "description": "Example of using React + Reflux + ES6 with experimental ES7 syntax + Jest", - "main": "src/index.js", - "license": "MIT", - "private": true, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "jquery": "~2.1.3", - "semantic-ui": "~1.5.2", - "font-awesome": "~4.2.0" - } -} diff --git a/package.json b/package.json index 4f8238d..44a34ab 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,16 @@ { "name": "react-es7", - "version": "0.0.11", - "description": "Example of using React + Reflux + router + ES6 with experimental ES7 syntax + Jest + Semantic-UI", + "version": "0.1.1", + "description": "Example of using React + Flow types + Reflux + router + ES6 with experimental ES7 syntax + Jest + Semantic-UI", "main": "src/index.js", "scripts": { - "test": "jest", + "test": "BABEL_JEST_STAGE=0 jest --coverage", + "coverage": "cd coverage/lcov-report && open http://localhost:8080 && http-server", "bundle": "broccoli build dist", - "serve": "broccoli serve" + "start": "broccoli serve" }, "jest": { - "scriptPreprocessor": "/node_modules/6to5-jest", + "scriptPreprocessor": "/node_modules/babel-jest", "testFileExtensions": [ "es6", "js" @@ -17,39 +18,34 @@ "moduleFileExtensions": [ "js", "json", - "es6", - "jsx" + "es6" ] }, - "author": "", - "license": "ISC", + "author": "Jaroslaw Zabiello ", + "license": "MIT", "devDependencies": { - "6to5": "*", - "6to5-jest": "hipertracker/6to5-jest", - "broccoli": "^0.13.3", - "broccoli-6to5-transpiler": "^0.1.1", - "broccoli-babel-transpiler": "^4.0.0", - "broccoli-cli": "0.0.1", + "babel": "^5.5.6", + "babel-jest": "^5.2.0", + "babel-plugin-typecheck": "0.0.3", + "broccoli": "^0.16.2", + "broccoli-babel-transpiler": "^5.0.0", "broccoli-env": "0.0.1", - "broccoli-fast-browserify": "^0.2.3", - "broccoli-filter": "^0.1.10", - "broccoli-funnel": "^0.1.6", - "broccoli-gzip": "^0.2.0", + "broccoli-fast-browserify": "^0.2.6", + "broccoli-funnel": "^0.2.3", + "broccoli-gzip": "^0.2.1", "broccoli-merge-trees": "^0.2.1", - "broccoli-sass": "hipertracker/broccoli-sass", + "broccoli-sass": "^0.6.6", + "broccoli-source-map": "^0.2.0", "broccoli-timepiece": "^0.3.0", "broccoli-uglify-js": "^0.1.3", - "browserify": "^8.0.1", - "http-server": "^0.7.4", - "jest-cli": "^0.2.1" + "jest-cli": "^0.4.12", + "rimraf": "^2.3.3" }, "dependencies": { - "lodash": "*", - "react": "^0.12.2", - "react-router": "*", - "react-semantify": "jessy1092/react-semantify", - "reactify": "*", - "reflux": "*", - "rimraf": "^2.2.8" + "lodash": "^3.9.3", + "react": "^0.13.3", + "react-router": "^0.13.3", + "react-semantify": "^0.3.1", + "reflux": "^0.2.7" } } diff --git a/src/__tests__/sum-test.js b/src/__tests__/sum-test.js index d60c641..6bdfdbb 100644 --- a/src/__tests__/sum-test.js +++ b/src/__tests__/sum-test.js @@ -1,9 +1,10 @@ -jest.dontMock('../sum'); // or jest.autoMockOff(); +jest.dontMock('../lib/sum'); // or jest.autoMockOff(); -import sum from '../sum'; +// cannot use es6 modules syntax because +// jest.dontMock & jest.autoMockOff() +// do not understand ES6 modules yet +const sum = require('../lib/sum'); describe('sum', function () { - it('adds 1 + 2 to equal 3', function () { - expect(sum(1, 2)).toBe(3); - }); + it('adds 1 + 2 to equal 3', () => expect(sum(1, 2)).toBe(3)) }); diff --git a/src/app/actions/LinksActions.js b/src/app/actions/LinksActions.js index 656f8f1..2faf1b0 100644 --- a/src/app/actions/LinksActions.js +++ b/src/app/actions/LinksActions.js @@ -1,4 +1,4 @@ -import * as Reflux from 'reflux' +import Reflux from 'reflux' // Each action is like an event channel for one specific event. Actions are called by components. // The store is listening to all actions, and the components in turn are listening to the store. @@ -88,4 +88,4 @@ LinksActions.loadData.listen(function () { LinksActions.loadDataSuccess(data); }); -export default LinksActions; +export default LinksActions diff --git a/src/app/components/About.js b/src/app/components/About.js index d7a097d..442a76d 100644 --- a/src/app/components/About.js +++ b/src/app/components/About.js @@ -1,14 +1,18 @@ -import * as React from 'react' +import React from 'react' import {Segment} from 'react-semantify' -const About = React.createClass({ +class About extends React.Component { render() { return ( - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ligula eros, sollicitudin nec libero non, cursus fringilla lectus. Aliquam volutpat velit feugiat pellentesque lobortis. Pellentesque pulvinar ornare ligula, sit amet imperdiet nibh tincidunt et. Suspendisse suscipit dolor vel turpis viverra ullamcorper. Fusce in enim elementum, hendrerit enim ut, pretium ipsum. Sed malesuada justo nec augue sodales, eu luctus dolor vulputate. Nunc ut consequat ipsum. Nam tempor fermentum risus at dictum. Sed et diam metus. Donec ac nisi suscipit, tristique enim sit amet, hendrerit ante. Pellentesque nec augue malesuada, ultrices dolor a, placerat ex. Pellentesque imperdiet feugiat nunc. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ligula eros, sollicitudin nec libero + non, cursus fringilla lectus. Aliquam volutpat velit feugiat pellentesque lobortis. Pellentesque + pulvinar ornare ligula, sit amet imperdiet nibh tincidunt et. Suspendisse suscipit dolor vel turpis + viverra ullamcorper. Fusce in enim elementum, hendrerit enim ut, pretium ipsum. Sed malesuada justo nec + augue sodales, eu luctus dolor vulputate. Nunc ut consequat ipsum. Nam tempor fermentum risus at dictum. ) } -}); +} -export default About; \ No newline at end of file +export default About \ No newline at end of file diff --git a/src/app/components/App.js b/src/app/components/App.js index 40356c3..195c542 100644 --- a/src/app/components/App.js +++ b/src/app/components/App.js @@ -1,13 +1,10 @@ -import * as React from 'react' -import * as ReactRouter from 'react-router' +import React from 'react' +import ReactRouter, {RouteHandler, Link} from 'react-router' import {Menu, Segment, Section} from 'react-semantify' -const RouteHandler = ReactRouter.RouteHandler; -const Link = ReactRouter.Link; - -const App = React.createClass({ +class App extends React.Component { render() { - const styles = { + const styles= { margin: 10 }; return ( @@ -32,6 +29,6 @@ const App = React.createClass({ ) } -}); +} -export default App; \ No newline at end of file +export default App \ No newline at end of file diff --git a/src/app/components/Home.js b/src/app/components/Home.js index 27167ec..20f9ca1 100644 --- a/src/app/components/Home.js +++ b/src/app/components/Home.js @@ -1,16 +1,18 @@ -import * as React from 'react' +import React from 'react' import {Segment} from 'react-semantify' import UsefulLinks from './UsefulLinks' -const Home = React.createClass({ +class Home extends React.Component { render() { return ( - - - +
+ + + +
) } -}); +} -export default Home; \ No newline at end of file +export default Home \ No newline at end of file diff --git a/src/app/components/UsefulLinks.js b/src/app/components/UsefulLinks.js index 08aaa9d..f04fd01 100644 --- a/src/app/components/UsefulLinks.js +++ b/src/app/components/UsefulLinks.js @@ -1,16 +1,14 @@ -import * as React from 'react' -import * as Reflux from 'reflux' +import React, {PropTypes} from 'react' +import Reflux from 'reflux' import LinksActions from '../actions/LinksActions' import LinksStore from '../stores/LinksStore' import {m} from '../lib/tools' -const T = React.PropTypes; - const UsefulLinks = React.createClass({ mixins: [Reflux.listenTo(LinksStore, 'linksUpdate')], propTypes: { - title: T.string.isRequired + title: PropTypes.string.isRequired }, getInitialState() { return { @@ -20,13 +18,14 @@ const UsefulLinks = React.createClass({ linksUpdate(data) { this.setState({links: data.links}); }, - componentDidMount() { + componentWillMount() { LinksActions.loadData(); }, render() { if (!this.state.links.length) { return ; } + const styles = { item: { paddingLeft: 30 @@ -35,7 +34,7 @@ const UsefulLinks = React.createClass({ const style = m(styles.item, this.props.style); const items = this.state.links.map((item, key) =>
  • - {item.title} ({item.url}) + {item.title} ({item.url})
  • ); return (
    @@ -46,4 +45,4 @@ const UsefulLinks = React.createClass({ } }); -export default UsefulLinks; +export default UsefulLinks diff --git a/src/app/lib/tools.js b/src/app/lib/tools.js index 63e4d70..123ddda 100644 --- a/src/app/lib/tools.js +++ b/src/app/lib/tools.js @@ -1,5 +1,5 @@ -import * as React from 'react' -import * as _ from 'lodash' +import React from 'react' +import _ from 'lodash' // mounting a React component to a selector id location export function mount(component, location, argv = {}) { @@ -12,12 +12,12 @@ export function mount(component, location, argv = {}) { component = React.createFactory(component); } React.render(component(argv), location); -}; +} // merging properties, used in React component for aggregating styles export function m() { let res = {}; _.each(arguments, (arg) => arg && _.merge(res, arg)); return res; -}; +} diff --git a/src/app/stores/LinksStore.js b/src/app/stores/LinksStore.js index 0bdf6e8..5878097 100644 --- a/src/app/stores/LinksStore.js +++ b/src/app/stores/LinksStore.js @@ -1,4 +1,4 @@ -import * as Reflux from 'reflux' +import Reflux from 'reflux' import LinksActions from '../actions/LinksActions' @@ -10,4 +10,4 @@ const LinksStore = Reflux.createStore({ } }); -export default LinksStore; +export default LinksStore diff --git a/src/index.html b/src/index.html index 290ada4..72e06b0 100644 --- a/src/index.html +++ b/src/index.html @@ -5,8 +5,8 @@ Test - - + + diff --git a/src/index.js b/src/index.js index 701d097..8275050 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -const React = require('react'); +import React from 'react' import {mount} from './app/lib/tools'; // For mounting App component into
    use: @@ -12,9 +12,7 @@ import App from './app/components/App'; import Home from './app/components/Home'; import About from './app/components/About'; -const Router = require('react-router'); -const Route = Router.Route; -const DefaultRoute = Router.DefaultRoute; +import Router, {Route, DefaultRoute} from 'react-router' const routes = ( diff --git a/src/lib/sum.js b/src/lib/sum.js new file mode 100644 index 0000000..168805a --- /dev/null +++ b/src/lib/sum.js @@ -0,0 +1,6 @@ +// ES6 code with Flow types + +const sum = (a:number, b:number) => a + b; + +export default sum + diff --git a/src/sum.js b/src/sum.js deleted file mode 100644 index d615c71..0000000 --- a/src/sum.js +++ /dev/null @@ -1,3 +0,0 @@ -// ES6 code -var sum = (a, b) => a + b; -export default sum;