├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .nvmrc ├── LICENSE ├── README.md ├── example ├── package.json ├── public │ ├── favicon.ico │ └── index.html └── src │ ├── app.css │ ├── app.js │ ├── index.css │ └── index.js ├── package.json ├── src ├── action-types.js ├── actions.js ├── config.js ├── index.js ├── lib │ ├── api.js │ └── utils.js └── reducer.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | webpack.config*.js 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | }, 7 | "extends": "airbnb", 8 | "rules": { 9 | "arrow-parens": 0, 10 | "global-require": [0], 11 | "max-len": [0], 12 | "import/extensions": 0, 13 | "import/imports-first": 0, 14 | "import/no-dynamic-require": 0, 15 | "import/no-extraneous-dependencies": 0, 16 | "import/no-unresolved": 0, 17 | "no-multi-assign": 0, 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # builds and modules 2 | node_modules 3 | build 4 | 5 | # misc 6 | .DS_Store 7 | .env 8 | *.log 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | 4 | .DS_Store 5 | .env 6 | *.log 7 | 8 | .babelrc 9 | .eslint* 10 | .npmignore 11 | webpack.* 12 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 6.9.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Oliver Benns 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redux-ghost 2 | [![npm version](https://badge.fury.io/js/redux-ghost.svg)](https://badge.fury.io/js/redux-ghost) 3 | ## Installation 4 | ```npm install --save redux-ghost``` 5 | 6 | ## Getting Started 7 | 8 | ### Step 1 9 | Enable your Ghost blog to expose a public api following [this tutorial](http://api.ghost.org/docs/ajax-calls-from-an-external-website). Make note of your `client_id`, `client_secret` and `host` (e.g. `http://localhost:2368`). 10 | 11 | ### Step 2 12 | For a quick test, follow the [example](https://github.com/oliverbenns/redux-ghost/tree/master/example), otherwise configure redux ghost with your credentials in `step 1` and give the Ghost reducer to redux. Ensure you also include the [thunk middleware](https://github.com/gaearon/redux-thunk). 13 | 14 | ``` 15 | import { createStore, combineReducers, applyMiddleware } from 'redux'; 16 | import thunk from 'redux-thunk'; 17 | import ReduxGhost, { reducer as ghostReducer } from 'redux-ghost'; 18 | 19 | ReduxGhost.config({ 20 | host: '', // e.g. http://localhost:2368 21 | clientId: '', // e.g. ghost-frontend 22 | clientSecret: '', // e.g. 4837a41df11b 23 | }); 24 | 25 | const rootReducer = combineReducers({ 26 | blog: ghostReducer, 27 | }); 28 | 29 | const store = createStore(rootReducer, null, applyMiddleware(thunk)); 30 | ``` 31 | 32 | ### Step 3 33 | Set up your store as you normally would, and fire off those actions. 34 | ``` 35 | import React from 'react'; 36 | import { connect } from 'react-redux'; 37 | import { bindActionCreators } from 'redux'; 38 | import { actions } from 'redux-ghost'; 39 | 40 | const App = ({ actions, blog }) => { 41 | return ( 42 |
43 | 44 | {blog.posts.data && blog.posts.data.map((post, index) => ( 45 |
46 |

{post.title}

47 |

Published on: {post.published_at}

48 |

Slug: {post.slug}

49 |
50 | ))} 51 |
52 | ); 53 | } 54 | 55 | const mapStateToProps = ({ blog }) => ({ 56 | blog, 57 | }); 58 | 59 | const mapDispatchToProps = (dispatch) => ({ 60 | actions: bindActionCreators(actions, dispatch) 61 | }); 62 | 63 | export default connect( 64 | mapStateToProps, 65 | mapDispatchToProps, 66 | )(App); 67 | 68 | ``` 69 | 70 | ## Actions 71 | 72 | Call your actions how you like, I prefer binding mine to props using [bindActionCreators](http://redux.js.org/docs/api/bindActionCreators.html). 73 | 74 | Actions available: 75 | 76 | | Action | Arguments | 77 | | ------------- | ----------------------------------------------------------------------------- | 78 | | getPosts | [options (object)](https://api.ghost.org/docs/posts) | 79 | | getPost | id (string / integer), [options (object)](https://api.ghost.org/docs/postsid) | 80 | | getPostBySlug | slug (string), [options (object)](https://api.ghost.org/docs/postsslugslug) | 81 | | getTags | [options (object)](https://api.ghost.org/docs/tags) | 82 | | getTag | id (string / integer), [options (object)](https://api.ghost.org/docs/tagsid) | 83 | | getTagBySlug | slug (string), [options (object)](https://api.ghost.org/docs/tagsslugslug) | 84 | | getUsers | [options (object)](https://api.ghost.org/docs/users) | 85 | | getUser | id (string / integer), [options (object)](https://api.ghost.org/docs/usersid) | 86 | | getUserBySlug | slug (string), [options (object)](https://api.ghost.org/docs/usersslugslug) | 87 | | reset | | 88 | 89 | ## Development 90 | 91 | ### Build 92 | Run `nvm use; redux-ghost; npm install; npm run build`. Any further file changes will require another `npm run build`. 93 | 94 | ### Example 95 | `nvm use; npm link; cd example; npm link redux-ghost; npm install; npm start`. 96 | 97 | Open [`http://localhost:3030/`](http://localhost:3030/). 98 | 99 | Any new builds will automatically refresh the example with updates. 100 | 101 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "private": true, 4 | "devDependencies": { 5 | "react-scripts": "0.8.4" 6 | }, 7 | "dependencies": { 8 | "react": "^15.4.2", 9 | "react-dom": "^15.4.2", 10 | "react-json-tree": "^0.10.0", 11 | "react-redux": "^5.0.1", 12 | "redux": "^3.6.0", 13 | "redux-thunk": "^2.1.0" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oliverbenns/redux-ghost/5c3bab0adad13477de311c13df7024322ce42f9c/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | React App 17 | 18 | 19 |
20 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /example/src/app.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | max-width: 800px; 3 | padding: 20px; 4 | margin: 0 auto; 5 | } 6 | 7 | .wrapper button { 8 | margin-right: 10px; 9 | } 10 | 11 | .wrapper button:last-child { 12 | margin-right: 0; 13 | } 14 | -------------------------------------------------------------------------------- /example/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { bindActionCreators } from 'redux'; 4 | import { actions } from 'redux-ghost'; 5 | 6 | import JsonTree from 'react-json-tree'; 7 | 8 | import './app.css'; 9 | 10 | const App = ({ actions, blog }) => { 11 | return ( 12 |
13 |

Redux Ghost Blog

14 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.

15 |

Posts

16 | 17 | 18 | 19 | 20 | 21 | 22 |

Tags

23 | 24 | 25 | 26 | 27 |

Users

28 | 29 | 30 | 31 | 32 |

Reset

33 | 34 | 35 | 36 |
37 | ); 38 | } 39 | 40 | const mapStateToProps = ({ blog }) => ({ 41 | blog, 42 | }); 43 | 44 | const mapDispatchToProps = (dispatch) => ({ 45 | actions: bindActionCreators(actions, dispatch) 46 | }); 47 | 48 | export default connect( 49 | mapStateToProps, 50 | mapDispatchToProps, 51 | )(App); 52 | -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore, combineReducers, applyMiddleware } from 'redux'; 5 | import thunk from 'redux-thunk'; 6 | import ReduxGhost, { reducer as ghostReducer } from 'redux-ghost'; 7 | 8 | import App from './app'; 9 | import './index.css'; 10 | 11 | ReduxGhost.config({ 12 | host: 'http://localhost:2368', 13 | clientId: 'ghost-frontend', 14 | clientSecret: '9921c6ca9c2d', 15 | }); 16 | 17 | const rootReducer = combineReducers({ 18 | blog: ghostReducer, 19 | }); 20 | 21 | const showDevTools = typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(); 22 | 23 | const store = createStore( 24 | rootReducer, 25 | showDevTools, 26 | applyMiddleware(thunk), 27 | ); 28 | 29 | ReactDOM.render( 30 | , 31 | document.getElementById('root') 32 | ); 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-ghost", 3 | "version": "0.1.0", 4 | "description": "Redux state handler and api wrapper for Ghost Blog", 5 | "main": "./build/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/oliverbenns/redux-ghost" 9 | }, 10 | "scripts": { 11 | "build": "babel src --out-dir build", 12 | "clean": "rimraf dist build", 13 | "lint": "eslint src", 14 | "example": "npm --prefix ./example install && npm --prefix ./example start", 15 | "prepublish": "npm run clean && npm run build" 16 | }, 17 | "keywords": [ 18 | "react", 19 | "reactjs", 20 | "flux", 21 | "redux", 22 | "react-redux", 23 | "redux-ghost", 24 | "ghost", 25 | "blog" 26 | ], 27 | "author": "Oliver Benns (http://github.com/oliverbenns)", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/oliverbenns/redux-ghost/issues" 31 | }, 32 | "homepage": "https://github.com/oliverbenns/redux-ghost", 33 | "dependencies": { 34 | "isomorphic-fetch": "^2.2.1", 35 | "qs": "^6.3.0" 36 | }, 37 | "devDependencies": { 38 | "babel-cli": "^6.18.0", 39 | "babel-core": "^6.21.0", 40 | "babel-eslint": "^7.1.1", 41 | "babel-loader": "^6.2.10", 42 | "babel-preset-es2015": "^6.18.0", 43 | "babel-preset-react": "^6.16.0", 44 | "babel-preset-stage-0": "^6.16.0", 45 | "babel-register": "^6.18.0", 46 | "eslint": "^3.12.2", 47 | "eslint-config-airbnb": "^14.1.0", 48 | "eslint-plugin-import": "^2.2.0", 49 | "eslint-plugin-jsx-a11y": "^4.0.0", 50 | "eslint-plugin-react": "^6.9.0", 51 | "react": "^16.2.0", 52 | "react-dom": "^15.4.1", 53 | "react-redux": "^5.0.1", 54 | "redux": "^3.6.0", 55 | "rimraf": "^2.5.4", 56 | "webpack": "^1.14.0" 57 | }, 58 | "peerDependencies": { 59 | "react": "^16.2.0", 60 | "react-redux": "^4.3.0 || ^5.0.0-beta", 61 | "redux": "^3.0.0", 62 | "redux-thunk": "^2.1.0" 63 | }, 64 | "files": [ 65 | "README.md", 66 | "build", 67 | "dist" 68 | ], 69 | "npmName": "redux-ghost", 70 | "npmFileMap": [ 71 | { 72 | "basePath": "/dist/", 73 | "files": [ 74 | "*.js" 75 | ] 76 | } 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /src/action-types.js: -------------------------------------------------------------------------------- 1 | export const GET_POSTS = '@@redux-ghost/GET_POSTS'; 2 | export const GET_POST = '@@redux-ghost/GET_POST'; 3 | export const GET_POST_SLUG = '@@redux-ghost/GET_POST_SLUG'; 4 | export const GET_TAGS = '@@redux-ghost/GET_TAGS'; 5 | export const GET_TAG = '@@redux-ghost/GET_TAG'; 6 | export const GET_TAG_SLUG = '@@redux-ghost/GET_TAG_SLUG'; 7 | export const GET_USERS = '@@redux-ghost/GET_USERS'; 8 | export const GET_USER = '@@redux-ghost/GET_USER'; 9 | export const GET_USER_SLUG = '@@redux-ghost/GET_USER_SLUG'; 10 | export const RESET = '@@redux-ghost/RESET'; 11 | -------------------------------------------------------------------------------- /src/actions.js: -------------------------------------------------------------------------------- 1 | import 'isomorphic-fetch'; 2 | import { request } from './lib/api'; 3 | import { isId } from './lib/utils'; 4 | import { 5 | GET_POSTS, 6 | GET_POST, 7 | GET_POST_SLUG, 8 | GET_TAGS, 9 | GET_TAG, 10 | GET_TAG_SLUG, 11 | GET_USERS, 12 | GET_USER, 13 | GET_USER_SLUG, 14 | RESET, 15 | } from './action-types'; 16 | 17 | const pending = (type) => ({ 18 | type, 19 | status: 'loading', 20 | }); 21 | 22 | const success = (type, data) => ({ 23 | type, 24 | status: 'success', 25 | data, 26 | }); 27 | 28 | const fail = (type, error) => ({ 29 | type, 30 | status: 'error', 31 | error, 32 | }); 33 | 34 | const getData = (type, uri, options) => (dispatch) => { 35 | dispatch(pending(type)); 36 | 37 | return request(uri, options) 38 | .then(data => dispatch(success(type, data))) 39 | .catch(error => dispatch(fail(type, error))); 40 | }; 41 | 42 | // Posts 43 | const getPosts = (options) => getData(GET_POSTS, '/posts/', options); 44 | const getPost = (id, options) => { 45 | if (!isId(id)) { 46 | return fail(GET_POST, 'Invalid Id'); 47 | } 48 | 49 | return getData(GET_POST, `/posts/${id}/`, options); 50 | }; 51 | 52 | const getPostBySlug = (slug, options) => { 53 | if (!slug) { 54 | return fail(GET_POST_SLUG, 'Slug parameter is null'); 55 | } 56 | 57 | return getData(GET_POST_SLUG, `/posts/slug/${slug}/`, options); 58 | }; 59 | 60 | // Tags 61 | const getTags = (options) => getData(GET_TAGS, '/tags/', options); 62 | const getTag = (id, options) => { 63 | if (!isId(id)) { 64 | return fail(GET_TAG, 'Invalid Id'); 65 | } 66 | 67 | return getData(GET_TAG, `/tags/${id}/`, options); 68 | }; 69 | 70 | const getTagBySlug = (slug, options) => { 71 | if (!slug) { 72 | return fail(GET_TAG_SLUG, 'Slug parameter is null'); 73 | } 74 | 75 | return getData(GET_TAG_SLUG, `/tags/slug/${slug}/`, options); 76 | }; 77 | 78 | // Users 79 | const getUsers = (options) => getData(GET_USERS, '/users/', options); 80 | const getUser = (id, options) => { 81 | if (!isId(id)) { 82 | return fail(GET_USER, 'Invalid Id'); 83 | } 84 | 85 | return getData(GET_USER, `/users/${id}/`, options); 86 | }; 87 | 88 | const getUserBySlug = (slug, options) => { 89 | if (!slug) { 90 | return fail(GET_USER_SLUG, 'Slug parameter is null'); 91 | } 92 | 93 | return getData(GET_USER_SLUG, `/users/slug/${slug}/`, options); 94 | }; 95 | 96 | const reset = () => ({ 97 | type: RESET, 98 | }); 99 | 100 | export default { 101 | getPosts, 102 | getPost, 103 | getPostBySlug, 104 | getTags, 105 | getTag, 106 | getTagBySlug, 107 | getUsers, 108 | getUser, 109 | getUserBySlug, 110 | reset, 111 | }; 112 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export let host = null; // .e.g 'http://localhost:2368', 2 | export let clientId = null; // .e.g 'ghost-frontend', 3 | export let clientSecret = null; // .e.g '4837a41df11b', 4 | 5 | export const config = (options = {}) => { 6 | host = options.host; 7 | clientId = options.clientId; 8 | clientSecret = options.clientSecret; 9 | }; 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import actions from './actions'; 2 | import reducer from './reducer'; 3 | import { config } from './config'; 4 | 5 | // @TODO: Not sure if this is the best way to export. 6 | 7 | export default { 8 | config, 9 | actions, 10 | reducer, 11 | }; 12 | 13 | export { 14 | actions, 15 | reducer, 16 | }; 17 | -------------------------------------------------------------------------------- /src/lib/api.js: -------------------------------------------------------------------------------- 1 | import qs from 'qs'; 2 | import { clientId, clientSecret, host } from '../config'; 3 | 4 | const constructUrl = (path, params) => { 5 | const queryParams = qs.stringify({ 6 | ...params, 7 | client_id: clientId, 8 | client_secret: clientSecret, 9 | }); 10 | 11 | return `${host}/ghost/api/v0.1${path}?${queryParams}`; 12 | }; 13 | 14 | const checkStatus = (response) => { 15 | if (response.status >= 200 && response.status < 300) { 16 | return response; 17 | } 18 | 19 | const error = new Error(response.statusText); 20 | error.response = response; 21 | 22 | throw error; 23 | }; 24 | 25 | const parseJson = (response) => response.json(); 26 | 27 | export const request = (path, options = {}) => { 28 | const url = constructUrl(path, options); 29 | 30 | return fetch(url, options) 31 | .then(checkStatus) 32 | .then(parseJson); 33 | }; 34 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | export const isId = (id) => typeof id === 'string' || typeof id === 'number'; 2 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_POSTS, 3 | GET_POST, 4 | GET_POST_SLUG, 5 | GET_TAGS, 6 | GET_TAG, 7 | GET_TAG_SLUG, 8 | GET_USERS, 9 | GET_USER, 10 | GET_USER_SLUG, 11 | RESET, 12 | } from './action-types'; 13 | 14 | const initialData = { data: null, error: null, loading: false, meta: null }; 15 | 16 | const initialState = { 17 | posts: initialData, 18 | post: initialData, 19 | tags: initialData, 20 | tag: initialData, 21 | users: initialData, 22 | user: initialData, 23 | }; 24 | 25 | const createStateHandler = (state, action) => (key) => { 26 | const isSingle = !(key.substr(key.length - 1) === 's'); 27 | 28 | const statusHandlers = { 29 | error: () => ({ 30 | ...state, 31 | [key]: { 32 | ...state[key], 33 | data: null, 34 | error: action.error || 'Unknown Error', 35 | loading: false, 36 | }, 37 | }), 38 | loading: () => ({ 39 | ...state, 40 | [key]: { 41 | ...state[key], 42 | loading: true, 43 | error: null, 44 | }, 45 | }), 46 | success: () => ({ 47 | ...state, 48 | [key]: { 49 | ...state[key], 50 | data: isSingle ? action.data[isSingle ? `${key}s` : key][0] : action.data[isSingle ? `${key}s` : key], 51 | meta: action.data.meta || null, 52 | error: null, 53 | loading: false, 54 | }, 55 | }), 56 | }; 57 | 58 | return statusHandlers[action.status] ? statusHandlers[action.status]() : state; 59 | }; 60 | 61 | const reducer = (state, action) => { 62 | if (typeof state === 'undefined') { 63 | return initialState; 64 | } 65 | 66 | const updateKey = createStateHandler(state, action); 67 | 68 | const reducers = { 69 | [GET_POSTS]: () => updateKey('posts'), 70 | [GET_POST]: () => updateKey('post'), 71 | [GET_POST_SLUG]: () => updateKey('post'), 72 | [GET_TAGS]: () => updateKey('tags'), 73 | [GET_TAG]: () => updateKey('tag'), 74 | [GET_TAG_SLUG]: () => updateKey('tag'), 75 | [GET_USERS]: () => updateKey('users'), 76 | [GET_USER]: () => updateKey('user'), 77 | [GET_USER_SLUG]: () => updateKey('user'), 78 | [RESET]: () => ({ 79 | ...initialState, 80 | }), 81 | }; 82 | 83 | return reducers[action.type] ? reducers[action.type]() : state; 84 | }; 85 | 86 | export default reducer; 87 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var webpack = require('webpack') 3 | 4 | var env = process.env.NODE_ENV 5 | 6 | var reactExternal = { 7 | root: 'React', 8 | commonjs2: 'react', 9 | commonjs: 'react', 10 | amd: 'react' 11 | } 12 | 13 | var reduxExternal = { 14 | root: 'Redux', 15 | commonjs2: 'redux', 16 | commonjs: 'redux', 17 | amd: 'redux' 18 | } 19 | 20 | var reactReduxExternal = { 21 | root: 'ReactRedux', 22 | commonjs2: 'react-redux', 23 | commonjs: 'react-redux', 24 | amd: 'react-redux' 25 | } 26 | 27 | var config = { 28 | externals: { 29 | 'react': reactExternal, 30 | 'redux': reduxExternal, 31 | 'react-redux': reactReduxExternal 32 | }, 33 | module: { 34 | loaders: [ 35 | { 36 | test: /\.js$/, 37 | loaders: ['babel-loader'], 38 | exclude: /node_modules/ 39 | } 40 | ] 41 | }, 42 | plugins: [ 43 | new webpack.optimize.OccurenceOrderPlugin(), 44 | new webpack.DefinePlugin({ 45 | 'process.env.NODE_ENV': JSON.stringify(env) 46 | }) 47 | ] 48 | } 49 | 50 | if (env === 'production') { 51 | config.plugins.push( 52 | new webpack.optimize.UglifyJsPlugin({ 53 | compressor: { 54 | pure_getters: true, 55 | unsafe: true, 56 | unsafe_comps: true, 57 | warnings: false 58 | } 59 | }) 60 | ) 61 | config.plugins.push(new webpack.optimize.DedupePlugin()) 62 | } 63 | 64 | module.exports = config 65 | --------------------------------------------------------------------------------