diff --git a/examples/async-universal/.babelrc b/examples/async-universal/.babelrc new file mode 100644 index 0000000000..86c445f545 --- /dev/null +++ b/examples/async-universal/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015", "react"] +} diff --git a/examples/async-universal/client/index.js b/examples/async-universal/client/index.js new file mode 100644 index 0000000000..b108676ce2 --- /dev/null +++ b/examples/async-universal/client/index.js @@ -0,0 +1,21 @@ +import 'babel-polyfill' +import React from 'react' +import { render } from 'react-dom' +import { Provider } from 'react-redux' +import { Router, browserHistory } from 'react-router' + +import configureStore from '../common/store/configureStore' +import routes from '../common/routes' + +const initialState = window.__INITIAL_STATE__ +const store = configureStore(initialState) +const rootElement = document.getElementById('app') + +render( + + + {routes} + + , + rootElement +) diff --git a/examples/async/actions/index.js b/examples/async-universal/common/actions/index.js similarity index 98% rename from examples/async/actions/index.js rename to examples/async-universal/common/actions/index.js index 6553788c63..b3c32cd68d 100644 --- a/examples/async/actions/index.js +++ b/examples/async-universal/common/actions/index.js @@ -29,7 +29,7 @@ function requestPosts(reddit) { function receivePosts(reddit, json) { return { type: RECEIVE_POSTS, - reddit, + reddit: reddit, posts: json.data.children.map(child => child.data), receivedAt: Date.now() } diff --git a/examples/async-universal/common/components/App.js b/examples/async-universal/common/components/App.js new file mode 100644 index 0000000000..38b41083cc --- /dev/null +++ b/examples/async-universal/common/components/App.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react' + +export default class App extends Component { + render() { + return ( +
+
+

Redux async universal example

+

Code on Github

+
+
+ {this.props.children} +
+ ) + } +} diff --git a/examples/async/components/Picker.js b/examples/async-universal/common/components/Picker.js similarity index 90% rename from examples/async/components/Picker.js rename to examples/async-universal/common/components/Picker.js index be78acb182..2f469af982 100644 --- a/examples/async/components/Picker.js +++ b/examples/async-universal/common/components/Picker.js @@ -6,7 +6,7 @@ export default class Picker extends Component { return ( -

{value}

+

{(value) ? value : 'Select a subreddit below'}

onChange(e.target.value)} + value={value}> + {options.map(option => + ) + } + +
+ ) + } +} + +Picker.propTypes = { + options: PropTypes.arrayOf( + PropTypes.string.isRequired + ).isRequired, + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired +} diff --git a/examples/async-with-routing/components/Posts.js b/examples/async-with-routing/components/Posts.js new file mode 100644 index 0000000000..dd3285dab9 --- /dev/null +++ b/examples/async-with-routing/components/Posts.js @@ -0,0 +1,17 @@ +import React, { PropTypes, Component } from 'react' + +export default class Posts extends Component { + render() { + return ( + + ) + } +} + +Posts.propTypes = { + posts: PropTypes.array.isRequired +} diff --git a/examples/async-with-routing/containers/Reddit.js b/examples/async-with-routing/containers/Reddit.js new file mode 100644 index 0000000000..8cff005988 --- /dev/null +++ b/examples/async-with-routing/containers/Reddit.js @@ -0,0 +1,112 @@ +import React, { Component, PropTypes } from 'react' +import { connect } from 'react-redux' +import { selectReddit, fetchPostsIfNeeded, invalidateReddit } from '../actions' +import Picker from '../components/Picker' +import Posts from '../components/Posts' + +class Reddit extends Component { + + constructor(props) { + super(props) + this.handleChange = this.handleChange.bind(this) + this.handleRefreshClick = this.handleRefreshClick.bind(this) + } + + componentDidMount() { + const { dispatch, params } = this.props + if (params.id) { + dispatch(selectReddit(params.id)) + dispatch(fetchPostsIfNeeded(params.id)) + } + } + + componentWillReceiveProps(nextProps) { + const { dispatch, params } = this.props + + if (nextProps.params.id !== params.id) { + dispatch(selectReddit(nextProps.params.id)) + if (nextProps.params.id) { + dispatch(fetchPostsIfNeeded(nextProps.params.id)) + } + } + + } + + handleChange(nextReddit) { + this.context.router.push(`/${nextReddit}`) + } + + handleRefreshClick(e) { + e.preventDefault() + + const { dispatch, selectedReddit } = this.props + dispatch(invalidateReddit(selectedReddit)) + dispatch(fetchPostsIfNeeded(selectedReddit)) + } + + render() { + const { selectedReddit, posts, isFetching, lastUpdated } = this.props + const isEmpty = posts.length === 0 + + return ( +
+ +

+ {lastUpdated && + + Last updated at {new Date(lastUpdated).toLocaleTimeString()}. + {' '} + + } + {!isFetching && selectedReddit && + + Refresh + + } +

+ {isEmpty + ? (isFetching ?

Loading...

:

Empty.

) + :
+ +
+ } +
+ ) + } +} + +Reddit.contextTypes = { + router: PropTypes.object +} + +Reddit.propTypes = { + selectedReddit: PropTypes.string.isRequired, + posts: PropTypes.array.isRequired, + isFetching: PropTypes.bool.isRequired, + lastUpdated: PropTypes.number, + dispatch: PropTypes.func.isRequired +} + +function mapStateToProps(state) { + const { selectedReddit, postsByReddit } = state + const { + isFetching, + lastUpdated, + items: posts + } = postsByReddit[selectedReddit] || { + isFetching: false, + items: [] + } + + return { + selectedReddit, + posts, + isFetching, + lastUpdated + } +} + +export default connect(mapStateToProps)(Reddit) diff --git a/examples/counter/index.html b/examples/async-with-routing/index.html similarity index 73% rename from examples/counter/index.html rename to examples/async-with-routing/index.html index 1eff16a567..b9b31b6ff1 100644 --- a/examples/counter/index.html +++ b/examples/async-with-routing/index.html @@ -1,7 +1,7 @@ - Redux counter example + Redux async with routing example
diff --git a/examples/async/index.js b/examples/async-with-routing/index.js similarity index 59% rename from examples/async/index.js rename to examples/async-with-routing/index.js index 12bcb25c37..2c8735b9b7 100644 --- a/examples/async/index.js +++ b/examples/async-with-routing/index.js @@ -1,15 +1,19 @@ import 'babel-polyfill' import React from 'react' import { render } from 'react-dom' +import Router from 'react-router/lib/Router' +import browserHistory from 'react-router/lib/browserHistory' import { Provider } from 'react-redux' -import App from './containers/App' +import routes from './routes' import configureStore from './store/configureStore' const store = configureStore() render( - + + {routes} + , document.getElementById('root') ) diff --git a/examples/async-with-routing/package.json b/examples/async-with-routing/package.json new file mode 100644 index 0000000000..805503889b --- /dev/null +++ b/examples/async-with-routing/package.json @@ -0,0 +1,52 @@ +{ + "name": "redux-async-with-routing-example", + "version": "0.0.0", + "description": "Redux async with routing example", + "scripts": { + "start": "node server.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/reactjs/redux.git" + }, + "keywords": [ + "react", + "reactjs", + "hot", + "reload", + "hmr", + "live", + "edit", + "webpack", + "flux" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/reactjs/redux/issues" + }, + "homepage": "http://redux.js.org", + "dependencies": { + "babel-polyfill": "^6.3.14", + "isomorphic-fetch": "^2.1.1", + "react": "^0.14.7", + "react-dom": "^0.14.7", + "react-redux": "^4.2.1", + "react-router": "^2.0.0", + "redux": "^3.2.1", + "redux-logger": "^2.4.0", + "redux-thunk": "^1.0.3" + }, + "devDependencies": { + "babel-core": "^6.3.15", + "babel-loader": "^6.2.0", + "babel-preset-es2015": "^6.3.13", + "babel-preset-react": "^6.3.13", + "babel-preset-react-hmre": "^1.0.1", + "expect": "^1.6.0", + "express": "^4.13.3", + "node-libs-browser": "^0.5.2", + "webpack": "^1.9.11", + "webpack-dev-middleware": "^1.2.0", + "webpack-hot-middleware": "^2.2.0" + } +} diff --git a/examples/async-with-routing/reducers/index.js b/examples/async-with-routing/reducers/index.js new file mode 100644 index 0000000000..d6836ee0ff --- /dev/null +++ b/examples/async-with-routing/reducers/index.js @@ -0,0 +1,61 @@ +import { combineReducers } from 'redux' +import { + SELECT_REDDIT, INVALIDATE_REDDIT, + REQUEST_POSTS, RECEIVE_POSTS +} from '../actions' + +function selectedReddit(state = '', action) { + switch (action.type) { + case SELECT_REDDIT: + return action.reddit || '' + default: + return state + } +} + +function posts(state = { + isFetching: false, + didInvalidate: false, + items: [] +}, action) { + switch (action.type) { + case INVALIDATE_REDDIT: + return Object.assign({}, state, { + didInvalidate: true + }) + case REQUEST_POSTS: + return Object.assign({}, state, { + isFetching: true, + didInvalidate: false + }) + case RECEIVE_POSTS: + return Object.assign({}, state, { + isFetching: false, + didInvalidate: false, + items: action.posts, + lastUpdated: action.receivedAt + }) + default: + return state + } +} + +function postsByReddit(state = { }, action) { + switch (action.type) { + case INVALIDATE_REDDIT: + case RECEIVE_POSTS: + case REQUEST_POSTS: + return Object.assign({}, state, { + [action.reddit]: posts(state[action.reddit], action) + }) + default: + return state + } +} + +const rootReducer = combineReducers({ + postsByReddit, + selectedReddit +}) + +export default rootReducer diff --git a/examples/async-with-routing/routes.js b/examples/async-with-routing/routes.js new file mode 100644 index 0000000000..2d1dd4abe8 --- /dev/null +++ b/examples/async-with-routing/routes.js @@ -0,0 +1,13 @@ +import React from 'react' +import Route from 'react-router/lib/Route' +import IndexRoute from 'react-router/lib/IndexRoute' + +import App from './components/App' +import Reddit from './containers/Reddit' + +export default( + + + + +) diff --git a/examples/counter/server.js b/examples/async-with-routing/server.js similarity index 95% rename from examples/counter/server.js rename to examples/async-with-routing/server.js index d655597867..9aeb674633 100644 --- a/examples/counter/server.js +++ b/examples/async-with-routing/server.js @@ -10,7 +10,7 @@ var compiler = webpack(config) app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })) app.use(webpackHotMiddleware(compiler)) -app.get("/", function(req, res) { +app.get("*", function(req, res) { res.sendFile(__dirname + '/index.html') }) diff --git a/examples/async-with-routing/store/configureStore.js b/examples/async-with-routing/store/configureStore.js new file mode 100644 index 0000000000..465d94919a --- /dev/null +++ b/examples/async-with-routing/store/configureStore.js @@ -0,0 +1,22 @@ +import { createStore, applyMiddleware } from 'redux' +import thunkMiddleware from 'redux-thunk' +import createLogger from 'redux-logger' +import rootReducer from '../reducers' + +export default function configureStore(initialState) { + const store = createStore( + rootReducer, + initialState, + applyMiddleware(thunkMiddleware, createLogger()) + ) + + if (module.hot) { + // Enable Webpack hot module replacement for reducers + module.hot.accept('../reducers', () => { + const nextRootReducer = require('../reducers').default + store.replaceReducer(nextRootReducer) + }) + } + + return store +} diff --git a/examples/async/webpack.config.js b/examples/async-with-routing/webpack.config.js similarity index 91% rename from examples/async/webpack.config.js rename to examples/async-with-routing/webpack.config.js index 2a6d9582c4..35062a810d 100644 --- a/examples/async/webpack.config.js +++ b/examples/async-with-routing/webpack.config.js @@ -13,7 +13,7 @@ module.exports = { publicPath: '/static/' }, plugins: [ - new webpack.optimize.OccurrenceOrderPlugin(), + new webpack.optimize.OccurenceOrderPlugin(), new webpack.HotModuleReplacementPlugin() ], module: { diff --git a/examples/async/.babelrc b/examples/async/.babelrc index d0962f5695..064a80f366 100644 --- a/examples/async/.babelrc +++ b/examples/async/.babelrc @@ -1,8 +1,4 @@ { - "presets": ["es2015", "react"], - "env": { - "development": { - "presets": ["react-hmre"] - } - } + "presets": ["es2015-webpack", "react"], + "plugins": ["transform-object-rest-spread"] } diff --git a/examples/async/index.html b/examples/async/index.html deleted file mode 100644 index b89496eae0..0000000000 --- a/examples/async/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redux async example - - -
-
- - - diff --git a/examples/async/package.json b/examples/async/package.json index 6eb76c1b18..f2742b824f 100644 --- a/examples/async/package.json +++ b/examples/async/package.json @@ -1,51 +1,38 @@ { "name": "redux-async-example", - "version": "0.0.0", "description": "Redux async example", "scripts": { - "start": "node server.js" + "start": "node server.dev.js", + "build": "webpack --config webpack.config.prod.js" }, "repository": { "type": "git", "url": "https://github.com/reactjs/redux.git" }, - "keywords": [ - "react", - "reactjs", - "hot", - "reload", - "hmr", - "live", - "edit", - "webpack", - "flux" - ], "license": "MIT", - "bugs": { - "url": "https://github.com/reactjs/redux/issues" - }, "homepage": "http://redux.js.org", "dependencies": { "babel-polyfill": "^6.3.14", "isomorphic-fetch": "^2.1.1", - "react": "^0.14.7", - "react-dom": "^0.14.7", - "react-redux": "^4.2.1", - "redux": "^3.2.1", + "react": "^15.1.0", + "react-dom": "^15.1.0", + "react-redux": "^4.4.5", + "redux": "^3.5.2", "redux-logger": "^2.4.0", "redux-thunk": "^1.0.3" }, "devDependencies": { "babel-core": "^6.3.15", "babel-loader": "^6.2.0", - "babel-preset-es2015": "^6.3.13", + "babel-plugin-transform-object-rest-spread": "^6.8.0", + "babel-preset-es2015-webpack": "^6.4.1", "babel-preset-react": "^6.3.13", - "babel-preset-react-hmre": "^1.1.1", "expect": "^1.6.0", "express": "^4.13.3", + "html-webpack-plugin": "^2.20.0", "node-libs-browser": "^0.5.2", - "webpack": "^1.9.11", - "webpack-dev-middleware": "^1.2.0", - "webpack-hot-middleware": "^2.9.1" + "webpack": "^2.1.0-beta.13", + "webpack-dev-middleware": "^1.6.1", + "webpack-hot-middleware": "^2.10.0" } } diff --git a/examples/async/server.dev.js b/examples/async/server.dev.js new file mode 100644 index 0000000000..0c6d3a166a --- /dev/null +++ b/examples/async/server.dev.js @@ -0,0 +1,23 @@ +const webpack = require('webpack') +const express = require('express') +const webpackDevMiddleware = require('webpack-dev-middleware') +const webpackHotMiddleware = require('webpack-hot-middleware') +const config = require('./webpack.config.dev') + +const app = express() +const port = 3000 +const compiler = webpack(config) + +app.use(webpackDevMiddleware(compiler, { + noInfo: true, + publicPath: config.output.publicPath +})) +app.use(webpackHotMiddleware(compiler)) + +app.listen(port, (error) => { + if (error) { + console.error(error) + } else { + console.info('🌎 Open http://localhost:%s/ in a web browser', port) + } +}) diff --git a/examples/async/server.js b/examples/async/server.js deleted file mode 100644 index d655597867..0000000000 --- a/examples/async/server.js +++ /dev/null @@ -1,23 +0,0 @@ -var webpack = require('webpack') -var webpackDevMiddleware = require('webpack-dev-middleware') -var webpackHotMiddleware = require('webpack-hot-middleware') -var config = require('./webpack.config') - -var app = new (require('express'))() -var port = 3000 - -var compiler = webpack(config) -app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })) -app.use(webpackHotMiddleware(compiler)) - -app.get("/", function(req, res) { - res.sendFile(__dirname + '/index.html') -}) - -app.listen(port, function(error) { - if (error) { - console.error(error) - } else { - console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port) - } -}) diff --git a/examples/async/src/actions/index.js b/examples/async/src/actions/index.js new file mode 100644 index 0000000000..185829539f --- /dev/null +++ b/examples/async/src/actions/index.js @@ -0,0 +1,59 @@ +import fetch from 'isomorphic-fetch' + +export function selectReddit(reddit) { + return { + type: 'SELECT_REDDIT', + reddit + } +} + +export function invalidateReddit(reddit) { + return { + type: 'INVALIDATE_REDDIT', + reddit + } +} + +function requestPosts(reddit) { + return { + type: 'REQUEST_POSTS', + reddit + } +} + +function receivePosts(reddit, json) { + return { + type: 'RECEIVE_POSTS', + posts: json.data.children.map(child => child.data), + receivedAt: Date.now(), + reddit + } +} + +function fetchPosts(reddit) { + return dispatch => { + dispatch(requestPosts(reddit)) + return fetch(`https://www.reddit.com/r/${reddit}.json`) + .then(response => response.json()) + .then(json => dispatch(receivePosts(reddit, json))) + } +} + +function shouldFetchPosts(state, reddit) { + const posts = state.postsByReddit[reddit] + if (!posts) { + return true + } + if (posts.isFetching) { + return false + } + return posts.didInvalidate +} + +export function fetchPostsIfNeeded(reddit) { + return (dispatch, getState) => { + if (shouldFetchPosts(getState(), reddit)) { + return dispatch(fetchPosts(reddit)) + } + } +} diff --git a/examples/async/containers/App.js b/examples/async/src/components/App.js similarity index 70% rename from examples/async/containers/App.js rename to examples/async/src/components/App.js index 6109878fca..7e98a9792c 100644 --- a/examples/async/containers/App.js +++ b/examples/async/src/components/App.js @@ -1,8 +1,8 @@ import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' -import { selectReddit, fetchPostsIfNeeded, invalidateReddit } from '../actions' -import Picker from '../components/Picker' -import Posts from '../components/Posts' +import * as actions from '../actions' +import Picker from './Picker' +import Posts from './Posts' class App extends Component { constructor(props) { @@ -12,27 +12,30 @@ class App extends Component { } componentDidMount() { - const { dispatch, selectedReddit } = this.props - dispatch(fetchPostsIfNeeded(selectedReddit)) + this.maybeFetchPosts() } - componentWillReceiveProps(nextProps) { - if (nextProps.selectedReddit !== this.props.selectedReddit) { - const { dispatch, selectedReddit } = nextProps - dispatch(fetchPostsIfNeeded(selectedReddit)) + componentDidUpdate(prevProps) { + if (this.props.selectedReddit !== prevProps.selectedReddit) { + this.maybeFetchPosts() } } + maybeFetchPosts(invalidateCache = false) { + const { selectedReddit } = this.props + if (invalidateCache) { + this.props.invalidateReddit(selectedReddit) + } + this.props.fetchPostsIfNeeded(selectedReddit) + } + handleChange(nextReddit) { - this.props.dispatch(selectReddit(nextReddit)) + this.props.selectReddit(nextReddit) } handleRefreshClick(e) { e.preventDefault() - - const { dispatch, selectedReddit } = this.props - dispatch(invalidateReddit(selectedReddit)) - dispatch(fetchPostsIfNeeded(selectedReddit)) + this.maybeFetchPosts(true) } render() { @@ -73,7 +76,9 @@ App.propTypes = { posts: PropTypes.array.isRequired, isFetching: PropTypes.bool.isRequired, lastUpdated: PropTypes.number, - dispatch: PropTypes.func.isRequired + selectReddit: PropTypes.func.isRequired, + invalidateReddit: PropTypes.func.isRequired, + fetchPostsIfNeeded: PropTypes.func.isRequired } function mapStateToProps(state) { @@ -95,4 +100,4 @@ function mapStateToProps(state) { } } -export default connect(mapStateToProps)(App) +export default connect(mapStateToProps, actions)(App) diff --git a/examples/async/src/components/Picker.js b/examples/async/src/components/Picker.js new file mode 100644 index 0000000000..4b3572e148 --- /dev/null +++ b/examples/async/src/components/Picker.js @@ -0,0 +1,25 @@ +import React, { PropTypes } from 'react' + +export default function Picker({ value, onChange, options }) { + return ( + +

{value}

+ +
+ ) +} + +Picker.propTypes = { + options: PropTypes.arrayOf( + PropTypes.string.isRequired + ).isRequired, + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired +} diff --git a/examples/async/src/components/Posts.js b/examples/async/src/components/Posts.js new file mode 100644 index 0000000000..3896894f37 --- /dev/null +++ b/examples/async/src/components/Posts.js @@ -0,0 +1,15 @@ +import React, { PropTypes } from 'react' + +export default function Posts({ posts }) { + return ( +
    + {posts.map((post, i) => +
  • {post.title}
  • + )} +
+ ) +} + +Posts.propTypes = { + posts: PropTypes.array.isRequired +} diff --git a/examples/async/src/components/Root.js b/examples/async/src/components/Root.js new file mode 100644 index 0000000000..c1646422a0 --- /dev/null +++ b/examples/async/src/components/Root.js @@ -0,0 +1,15 @@ +import React, { PropTypes } from 'react'; +import { Provider } from 'react-redux' +import App from './App' + +export default function Root({ store }) { + return ( + + + + ) +} + +Root.propTypes = { + store: PropTypes.object.isRequired +} diff --git a/examples/async/src/index.js b/examples/async/src/index.js new file mode 100644 index 0000000000..b3ac112969 --- /dev/null +++ b/examples/async/src/index.js @@ -0,0 +1,17 @@ +import 'babel-polyfill' +import React from 'react' +import { render } from 'react-dom' +import Root from './components/Root' +import configureStore from './store/configureStore' + +const rootEl = document.createElement('div') +document.body.appendChild(rootEl) + +const store = configureStore() +render(, rootEl) + +if (module.hot) { + module.hot.accept('./components/Root', () => { + render(, rootEl) + }) +} diff --git a/examples/async/src/reducers/index.js b/examples/async/src/reducers/index.js new file mode 100644 index 0000000000..d31c805184 --- /dev/null +++ b/examples/async/src/reducers/index.js @@ -0,0 +1,61 @@ +import { combineReducers } from 'redux' + +function selectedReddit(state = 'reactjs', action) { + switch (action.type) { + case 'SELECT_REDDIT': + return action.reddit + default: + return state + } +} + +function posts(state = { + isFetching: false, + didInvalidate: false, + items: [] +}, action) { + switch (action.type) { + case 'INVALIDATE_REDDIT': + return { + ...state, + didInvalidate: true + } + case 'REQUEST_POSTS': + return { + ...state, + isFetching: true, + didInvalidate: false + } + case 'RECEIVE_POSTS': + return { + ...state, + isFetching: false, + didInvalidate: false, + items: action.posts, + lastUpdated: action.receivedAt + } + default: + return state + } +} + +function postsByReddit(state = { }, action) { + switch (action.type) { + case 'INVALIDATE_REDDIT': + case 'RECEIVE_POSTS': + case 'REQUEST_POSTS': + return { + ...state, + [action.reddit]: posts(state[action.reddit], action) + } + default: + return state + } +} + +const reducer = combineReducers({ + postsByReddit, + selectedReddit +}) + +export default reducer diff --git a/examples/async/src/store/configureStore.js b/examples/async/src/store/configureStore.js new file mode 100644 index 0000000000..f28e73ce5b --- /dev/null +++ b/examples/async/src/store/configureStore.js @@ -0,0 +1,20 @@ +import { createStore, applyMiddleware } from 'redux' +import thunk from 'redux-thunk' +import createLogger from 'redux-logger' +import reducer from '../reducers' + +export default function configureStore() { + const store = createStore( + reducer, + applyMiddleware(thunk, createLogger()) + ) + + if (module.hot) { + // Enable Webpack hot module replacement for reducers + module.hot.accept('../reducers', () => { + store.replaceReducer(reducer) + }) + } + + return store +} diff --git a/examples/async/webpack.config.dev.js b/examples/async/webpack.config.dev.js new file mode 100644 index 0000000000..475e66497a --- /dev/null +++ b/examples/async/webpack.config.dev.js @@ -0,0 +1,20 @@ +var path = require('path') +var webpack = require('webpack') +var HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + devtool: 'cheap-module-eval-source-map', + entry: ['./src/index', 'webpack-hot-middleware/client'], + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new HtmlWebpackPlugin({ title: 'Redux async example' }), + new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"development"' }) + ], + module: { + loaders: [{ + test: /\.js$/, + loaders: ['babel'], + include: path.join(__dirname, 'src') + }] + } +} diff --git a/examples/async/webpack.config.prod.js b/examples/async/webpack.config.prod.js new file mode 100644 index 0000000000..3141e9c04c --- /dev/null +++ b/examples/async/webpack.config.prod.js @@ -0,0 +1,25 @@ +var path = require('path') +var webpack = require('webpack') +var HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + entry: './src/index', + output: { + path: path.join(__dirname, 'dist'), + filename: '[name].[hash].js', + publicPath: '/' + }, + plugins: [ + new HtmlWebpackPlugin({ title: 'Redux async example' }), + new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }), + new webpack.optimize.OccurrenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin() + ], + module: { + loaders: [{ + test: /\.js$/, + loaders: ['babel'], + include: path.join(__dirname, 'src') + }] + } +} diff --git a/examples/counter/.babelrc b/examples/counter/.babelrc index d0962f5695..064a80f366 100644 --- a/examples/counter/.babelrc +++ b/examples/counter/.babelrc @@ -1,8 +1,4 @@ { - "presets": ["es2015", "react"], - "env": { - "development": { - "presets": ["react-hmre"] - } - } + "presets": ["es2015-webpack", "react"], + "plugins": ["transform-object-rest-spread"] } diff --git a/examples/counter/package.json b/examples/counter/package.json index bb69e653c9..7ec41f4a30 100644 --- a/examples/counter/package.json +++ b/examples/counter/package.json @@ -3,7 +3,8 @@ "version": "0.0.0", "description": "Redux counter example", "scripts": { - "start": "node server.js", + "start": "node server.dev.js", + "build": "webpack --config webpack.config.prod.js", "test": "cross-env NODE_ENV=test mocha --recursive --compilers js:babel-register", "test:watch": "npm test -- --watch" }, @@ -25,19 +26,20 @@ "devDependencies": { "babel-core": "^6.3.15", "babel-loader": "^6.2.0", - "babel-preset-es2015": "^6.3.13", + "babel-plugin-transform-object-rest-spread": "^6.8.0", + "babel-preset-es2015-webpack": "^6.4.1", "babel-preset-react": "^6.3.13", - "babel-preset-react-hmre": "^1.1.1", "babel-register": "^6.3.13", "cross-env": "^1.0.7", "enzyme": "^2.0.0", "expect": "^1.6.0", "express": "^4.13.3", + "html-webpack-plugin": "^2.21.0", "mocha": "^2.2.5", "node-libs-browser": "^0.5.2", "react-addons-test-utils": "^0.14.7", - "webpack": "^1.9.11", - "webpack-dev-middleware": "^1.2.0", - "webpack-hot-middleware": "^2.9.1" + "webpack": "^2.1.0-beta.13", + "webpack-dev-middleware": "^1.6.1", + "webpack-hot-middleware": "^2.10.0" } } diff --git a/examples/counter/server.dev.js b/examples/counter/server.dev.js new file mode 100644 index 0000000000..0c6d3a166a --- /dev/null +++ b/examples/counter/server.dev.js @@ -0,0 +1,23 @@ +const webpack = require('webpack') +const express = require('express') +const webpackDevMiddleware = require('webpack-dev-middleware') +const webpackHotMiddleware = require('webpack-hot-middleware') +const config = require('./webpack.config.dev') + +const app = express() +const port = 3000 +const compiler = webpack(config) + +app.use(webpackDevMiddleware(compiler, { + noInfo: true, + publicPath: config.output.publicPath +})) +app.use(webpackHotMiddleware(compiler)) + +app.listen(port, (error) => { + if (error) { + console.error(error) + } else { + console.info('🌎 Open http://localhost:%s/ in a web browser', port) + } +}) diff --git a/examples/counter/components/Counter.js b/examples/counter/src/components/Counter.js similarity index 100% rename from examples/counter/components/Counter.js rename to examples/counter/src/components/Counter.js diff --git a/examples/counter/index.js b/examples/counter/src/index.js similarity index 68% rename from examples/counter/index.js rename to examples/counter/src/index.js index 60cac9cf27..a0b9319f6c 100644 --- a/examples/counter/index.js +++ b/examples/counter/src/index.js @@ -4,8 +4,10 @@ import { createStore } from 'redux' import Counter from './components/Counter' import counter from './reducers' +const rootEl = document.createElement('div') +document.body.appendChild(rootEl) + const store = createStore(counter) -const rootEl = document.getElementById('root') function render() { ReactDOM.render( @@ -14,9 +16,15 @@ function render() { onIncrement={() => store.dispatch({ type: 'INCREMENT' })} onDecrement={() => store.dispatch({ type: 'DECREMENT' })} />, - rootEl + rootEl ) } render() store.subscribe(render) + +if (module.hot) { + module.hot.accept('./components/Counter', () => { + render(, rootEl) + }) +} diff --git a/examples/counter/reducers/index.js b/examples/counter/src/reducers/index.js similarity index 100% rename from examples/counter/reducers/index.js rename to examples/counter/src/reducers/index.js diff --git a/examples/counter/test/.eslintrc b/examples/counter/src/test/.eslintrc similarity index 100% rename from examples/counter/test/.eslintrc rename to examples/counter/src/test/.eslintrc diff --git a/examples/counter/test/components/Counter.spec.js b/examples/counter/src/test/components/Counter.spec.js similarity index 100% rename from examples/counter/test/components/Counter.spec.js rename to examples/counter/src/test/components/Counter.spec.js diff --git a/examples/counter/test/reducers/counter.spec.js b/examples/counter/src/test/reducers/counter.spec.js similarity index 100% rename from examples/counter/test/reducers/counter.spec.js rename to examples/counter/src/test/reducers/counter.spec.js diff --git a/examples/counter/webpack.config.dev.js b/examples/counter/webpack.config.dev.js new file mode 100644 index 0000000000..2b2991a2b2 --- /dev/null +++ b/examples/counter/webpack.config.dev.js @@ -0,0 +1,20 @@ +var path = require('path') +var webpack = require('webpack') +var HtmlWebpackPlugin = require('html-webpack-plugin') + +module.exports = { + devtool: 'cheap-module-eval-source-map', + entry: [ './src/index', 'webpack-hot-middleware/client' ], + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new HtmlWebpackPlugin({ title: 'Redux counter example' }), + new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"development"' }) + ], + module: { + loaders: [ { + test: /\.js$/, + loaders: [ 'babel' ], + include: path.join(__dirname, 'src') + } ] + } +} diff --git a/examples/counter/webpack.config.prod.js b/examples/counter/webpack.config.prod.js new file mode 100644 index 0000000000..01314b78b7 --- /dev/null +++ b/examples/counter/webpack.config.prod.js @@ -0,0 +1,25 @@ +var path = require('path') +var webpack = require('webpack') +var HtmlWebpackPlugin = require('html-webpack-plugin') + +module.exports = { + entry: './src/index', + output: { + path: path.join(__dirname, 'dist'), + filename: '[name].[hash].js', + publicPath: '/' + }, + plugins: [ + new HtmlWebpackPlugin({ title: 'Redux counter example' }), + new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }), + new webpack.optimize.OccurrenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin() + ], + module: { + loaders: [ { + test: /\.js$/, + loaders: [ 'babel' ], + include: path.join(__dirname, 'src') + } ] + } +}