├── .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 | [](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 |
actions.getPosts()}>Load Posts
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 |
actions.getPosts()}>Load Posts
17 |
actions.getPosts({ fields: 'title, slug' })}>Load Posts (title & slug only)
18 |
actions.getPost('1')}>Load Single Post
19 |
actions.getPost('1', { include: 'authors' })}>Load Single Post (with author data)
20 |
actions.getPostBySlug('my-post')}>Load Single Post by giving the slug
21 |
22 |
Tags
23 |
actions.getTags()}>Load Tags
24 |
actions.getTag(1)}>Load Single Tag
25 |
actions.getTagBySlug('my-tag')}>Load Single Tag by giving the slug
26 |
27 |
Users
28 |
actions.getUsers()}>Load Users
29 |
actions.getUser(1)}>Load Single User
30 |
actions.getUserBySlug('my-user')}>Load Single User by giving the slug
31 |
32 |
Reset
33 |
actions.reset()}>Reset
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 |
--------------------------------------------------------------------------------