├── .babelrc
├── .eslintrc
├── .gitignore
├── .npmignore
├── README.MD
├── docs
└── API.MD
├── modules
├── ReduxAsyncConnect.js
├── asyncConnect.js
└── index.js
├── npm-scripts
└── postinstall.js
├── package.json
├── scripts
├── build.js
└── release.sh
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | }
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | { "extends": "eslint-config-airbnb",
2 | "env": {
3 | "browser": true,
4 | "node": true,
5 | "mocha": true
6 | },
7 | "rules": {
8 | "react/no-multi-comp": 0,
9 | "react/jsx-boolean-value": 0,
10 | "import/default": 0,
11 | "import/no-duplicates": 0,
12 | "import/named": 0,
13 | "import/namespace": 0,
14 | "import/no-unresolved": 0,
15 | "import/no-named-as-default": 2,
16 | "comma-dangle": 0, // not sure why airbnb turned this on. gross!
17 | "indent": [2, 2, {"SwitchCase": 1}],
18 | "no-console": 0,
19 | "no-alert": 0
20 | },
21 | "plugins": [
22 | "react", "import"
23 | ],
24 | "settings": {
25 | "import/parser": "babel-eslint",
26 | "import/resolve": {
27 | "moduleDirectory": ["node_modules", "modules"]
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | .idea
4 | npm-debug.log
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .babelrc
2 | .eslintrc
3 | .travis.yml
4 | __tests__
5 | scripts
6 | index.html
7 | karma.conf.js
8 | tests.webpack.js
9 | webpack.config.js
10 | .idea
11 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | ReduxAsyncConnect for React Router
2 | ============
3 | [](https://www.npmjs.com/package/redux-async-connect)
4 |
5 | How do you usually request data and store it to redux state?
6 | You create actions that do async jobs to load data, create reducer to save this data to redux state,
7 | then connect data to your component or container.
8 |
9 | Usually it's very similar routine tasks.
10 |
11 | Also, usually we want data to be preloaded. Especially if you're building universal app,
12 | or you just want pages to be solid, don't jump when data was loaded.
13 |
14 | This package consist of 2 parts: one part allows you to delay containers rendering until some async actions are happening.
15 | Another stores your data to redux state and connect your loaded data to your container.
16 |
17 | ## Installation & Usage
18 |
19 | Using [npm](https://www.npmjs.com/):
20 |
21 | $ npm install redux-async-connect
22 |
23 | ```js
24 | import { Router, browserHistory } from 'react-router';
25 | import { ReduxAsyncConnect, asyncConnect, reducer as reduxAsyncConnect } from 'redux-async-connect'
26 | import React from 'react'
27 | import { render } from 'react-dom'
28 | import { createStore, combineReducers } from 'redux';
29 |
30 | // 1. Connect your data, similar to react-redux @connect
31 | @asyncConnect({
32 | lunch: (params, helpers) => Promise.resolve({id: 1, name: 'Borsch'})
33 | })
34 | class App extends React.Component {
35 | render() {
36 | // 2. access data as props
37 | const lunch = this.props.lunch
38 | return (
39 |
{lunch.name}
40 | )
41 | }
42 | }
43 |
44 | // 3. Connect redux async reducer
45 | const store = createStore(combineReducers({reduxAsyncConnect}), window.__data);
46 |
47 | // 4. Render `Router` with ReduxAsyncConnect middleware
48 | render((
49 |
50 | } history={browserHistory}>
51 |
52 |
53 |
54 | ), el)
55 | ```
56 |
57 | ### Server
58 |
59 | ```js
60 | import { renderToString } from 'react-dom/server'
61 | import { match, RoutingContext } from 'react-router'
62 | import { ReduxAsyncConnect, loadOnServer, reducer as reduxAsyncConnect } from 'redux-async-connect'
63 | import createHistory from 'history/lib/createMemoryHistory';
64 | import {Provider} from 'react-redux';
65 | import { createStore, combineReducers } from 'redux';
66 |
67 | app.get('*', (req, res) => {
68 | const history = createHistory();
69 | const store = createStore(combineReducers({reduxAsyncConnect}));
70 |
71 | match({ routes, location: req.url }, (err, redirect, renderProps) => {
72 |
73 | // 1. load data
74 | loadOnServer(renderProps, store).then(() => {
75 |
76 | // 2. use `ReduxAsyncConnect` instead of `RoutingContext` and pass it `renderProps`
77 | const appHTML = renderToString(
78 |
79 |
80 |
81 | )
82 |
83 | // 3. render the Redux initial data into the server markup
84 | const html = createPage(appHTML, store)
85 | res.send(html)
86 | })
87 | })
88 | })
89 |
90 | function createPage(html, store) {
91 | return `
92 |
93 |
94 |
95 | ${html}
96 |
97 |
98 |
99 |
100 |
101 | `
102 | }
103 | ```
104 |
105 | ## [API](/docs/API.MD)
106 |
107 | ## Comparing with other libraries
108 |
109 | There are some solutions of problem described above:
110 |
111 | - [**AsyncProps**](https://github.com/rackt/async-props)
112 | It solves the same problem, but it doesn't work with redux state. Also it's significantly more complex inside,
113 | because it contains lots of logic to connect data to props.
114 | It uses callbacks against promises...
115 | - [**react-fetcher**](https://github.com/markdalgleish/react-fetcher)
116 | It's very simple library too. But it provides you only interface for decorating your components and methods
117 | to fetch data for them. It doesn't integrated with React Router or Redux. So, you need to write you custom logic
118 | to delay routing transition for example.
119 | - [**react-resolver**](https://github.com/ericclemmons/react-resolver)
120 | Works similar, but isn't integrated with redux.
121 |
122 | **Redux Async Connect** uses awesome [Redux](https://github.com/rackt/redux) to keep all fetched data in state.
123 | This integration gives you agility:
124 |
125 | - you can react on fetching actions like data loading or load success in your own reducers
126 | - you can create own middleware to handle Redux Async Connect actions
127 | - you can connect to loaded data anywhere else, just using simple redux @connect
128 | - finally, you can debug and see your data using Redux Dev Tools
129 |
130 | Also it's integrated with [React Router](https://github.com/rackt/react-router) to prevent routing transition
131 | until data is loaded.
132 |
133 | ## Contributors
134 | - [Rodion Salnik](https://github.com/sars)
135 | - [Rezonans team](https://github.com/Rezonans)
136 |
137 | ## Collaboration
138 | You're welcome to PR, and we appreciate any questions or issues, please [open an issue](https://github.com/Rezonans/redux-async-connect/issues)!
139 |
--------------------------------------------------------------------------------
/docs/API.MD:
--------------------------------------------------------------------------------
1 | API
2 | ============
3 |
4 | ## ReduxAsyncConnect
5 | It allows to delay route transition until all promises returned by reduxAsyncConnect methods of components defined within corresponding route are resolved or rejected.
6 |
7 | #### Props
8 | ##### `components` (required)
9 | The list of components corresponding current route. It not instances...
10 | Usually it is provided by react Router component or match method from react router.
11 |
12 | ##### `params` (required)
13 | Router params.
14 | Usually it is provided by react Router component or match method from react router.
15 |
16 | ##### `render` (required)
17 | Function that accepts props and uses to render components.
18 | Usually it's just (by default)
19 |
20 | ##### `helpers`
21 | Any helpers you may want pass to your reduxAsyncConnect static method.
22 | For example some fetching library.
23 |
24 | #### `reduxAsyncConnect` static method
25 | You can define such static method in your container components, that are connected with router.
26 | It allows you to delay rendering until this promise will be resolved or rejected.
27 | It accepts params from router, redux store and helpers
28 | It can return promise. If it returns something else, ReduxAsyncConnect will not delay rendering.
29 | _**Example:**_
30 | ```js
31 | static reduxAsyncConnect(params, store, helpers) {
32 | const {dispatch, getState} = store;
33 | if (!isLoaded(getState())) {
34 | return dispatch(loadWidgets());
35 | }
36 | }
37 | ```
38 |
39 | ## asyncConnect decorator
40 | This is the function that uses to decorate your container components that is connected with router.
41 | It should provide mapStateToProps object like that:
42 |
43 | ```js
44 | @asyncConnect({
45 | lunches: (params, helpers) => helpers.client.get('/lunches')
46 | })
47 | export default class Home extends Component {
48 | // ...
49 | }
50 | ```
51 |
52 | The interface is similar to react-redux connect. The keys of this object will be used to connect data returned from from promise to corresponding prop in the component.
53 | So in example above you'll have this.props.lunches
54 |
55 | The value of each key should be function, that accepts params from router and helpers. Redux store exists in helpers by default.
56 | This function can return:
57 | - _**undefined**_ In this case we'll do nothing
58 | - _**promise**_ In this case we'll store data from this promise to redux state to appropriate key and will ask ReduxAsyncConnect to delay rendering
59 | - _**other value**_ In this case we'll store this data to redux state to appropriate key immediately
60 |
61 | ## reducer
62 | This reducer MUST be mounted to `reduxAsyncConnect` key in combineReducers.
63 | It uses to store information about global loading and all other data to redux store.
64 |
65 | ## redux state
66 | You'll have the following in your `reduxAsyncConnect` key in redux state:
67 | _(the [key] here is corresponding to mapStateToProps object's keys passed to asyncConnect decorator)_
68 |
69 | - _**loaded**_ It's global loading identifier. Useful for page preloader
70 | - _**[key].loading**_ Identifies that promise resolving in progress
71 | - _**[key].loaded**_ Identifies that promise was resolved
72 | - _**[key].data**_ Data, returned from resolved promise
73 | - _**[key].error**_ Errors, returned from rejected promise
74 |
75 | ## redux actions
76 | There are some actions you can react on:
77 | - **LOAD** data loading for particular key is started
78 | - **LOAD_SUCCESS** data loading process successfully finished. You'll have data returned from promise
79 | - **LOAD_FAIL** data loading process was failed. You'll have error returned from promise
80 | - **CLEAR** data for particular key was cleared
81 | - **BEGIN_GLOBAL_LOAD** loading for all components began
82 | - **END_GLOBAL_LOAD** loading for all components finished
83 |
--------------------------------------------------------------------------------
/modules/ReduxAsyncConnect.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import RouterContext from 'react-router/lib/RouterContext';
3 | import { beginGlobalLoad, endGlobalLoad } from './asyncConnect';
4 | import { connect } from 'react-redux';
5 |
6 | const { array, func, object, any } = React.PropTypes;
7 |
8 | /**
9 | * We need to iterate over all components for specified routes.
10 | * Components array can include objects if named components are used:
11 | * https://github.com/rackt/react-router/blob/latest/docs/API.md#named-components
12 | *
13 | * @param components
14 | * @param iterator
15 | */
16 | function eachComponents(components, iterator) {
17 | for (let i = 0, l = components.length; i < l; i++) { // eslint-disable-line id-length
18 | if (typeof components[i] === 'object') {
19 | for (let [key, value] of Object.entries(components[i])) {
20 | iterator(value, i, key);
21 | }
22 | } else {
23 | iterator(components[i], i);
24 | }
25 | }
26 | }
27 |
28 | function filterAndFlattenComponents(components) {
29 | const flattened = [];
30 | eachComponents(components, (Component) => {
31 | if (Component && Component.reduxAsyncConnect) {
32 | flattened.push(Component);
33 | }
34 | });
35 | return flattened;
36 | }
37 |
38 | function asyncConnectPromises(components, params, store, helpers) {
39 | return components.map(Component => Component.reduxAsyncConnect(params, store, helpers))
40 | .filter(result => result && result.then instanceof Function);
41 | }
42 |
43 | export function loadOnServer({ components, params }, store, helpers) {
44 | return Promise.all(asyncConnectPromises(filterAndFlattenComponents(components), params, store, helpers))
45 | .catch(error => console.error('reduxAsyncConnect server promise error: ', error)).then(() => {
46 | store.dispatch(endGlobalLoad());
47 | });
48 | }
49 |
50 | let loadDataCounter = 0;
51 |
52 | class ReduxAsyncConnect extends React.Component {
53 | static propTypes = {
54 | components: array.isRequired,
55 | params: object.isRequired,
56 | render: func.isRequired,
57 | beginGlobalLoad: func.isRequired,
58 | endGlobalLoad: func.isRequired,
59 | helpers: any
60 | };
61 |
62 | static contextTypes = {
63 | store: object.isRequired
64 | };
65 |
66 | static defaultProps = {
67 | render(props) {
68 | return ;
69 | }
70 | };
71 |
72 | isLoaded() {
73 | return this.context.store.getState().reduxAsyncConnect.loaded;
74 | }
75 |
76 | constructor(props, context) {
77 | super(props, context);
78 |
79 | this.state = {
80 | propsToShow: this.isLoaded() ? props : null
81 | };
82 | }
83 |
84 | componentDidMount() {
85 | const dataLoaded = this.isLoaded();
86 |
87 | if (!dataLoaded) { // we dont need it if we already made it on server-side
88 | this.loadAsyncData(this.props);
89 | }
90 | }
91 |
92 | componentWillReceiveProps(nextProps) {
93 | this.loadAsyncData(nextProps);
94 | }
95 |
96 | shouldComponentUpdate(nextProps, nextState) {
97 | return this.state.propsToShow !== nextState.propsToShow;
98 | }
99 |
100 | loadAsyncData(props) {
101 | const { components, params, helpers } = props;
102 | const store = this.context.store;
103 | const promises = asyncConnectPromises(filterAndFlattenComponents(components), params, store, helpers);
104 |
105 | loadDataCounter++;
106 |
107 | if (promises.length) {
108 | this.props.beginGlobalLoad();
109 | (loadDataCounterOriginal => {
110 | Promise.all(promises).catch(error => console.error('reduxAsyncConnect server promise error: ', error))
111 | .then(() => {
112 | // We need to change propsToShow only if loadAsyncData that called this promise
113 | // is the last invocation of loadAsyncData method. Otherwise we can face situation
114 | // when user is changing route several times and we finally show him route that has
115 | // loaded props last time and not the last called route
116 | if (loadDataCounter === loadDataCounterOriginal) {
117 | this.setState({propsToShow: props});
118 | }
119 | this.props.endGlobalLoad();
120 | });
121 | })(loadDataCounter);
122 | } else {
123 | this.setState({propsToShow: props});
124 | }
125 | }
126 |
127 | render() {
128 | const {propsToShow} = this.state;
129 | return propsToShow && this.props.render(propsToShow);
130 | }
131 | }
132 |
133 | export default connect(null, {beginGlobalLoad, endGlobalLoad})(ReduxAsyncConnect);
134 |
--------------------------------------------------------------------------------
/modules/asyncConnect.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 |
3 | export const LOAD = 'reduxAsyncConnect/LOAD';
4 | export const LOAD_SUCCESS = 'reduxAsyncConnect/LOAD_SUCCESS';
5 | export const LOAD_FAIL = 'reduxAsyncConnect/LOAD_FAIL';
6 | export const CLEAR = 'reduxAsyncConnect/CLEAR';
7 | export const BEGIN_GLOBAL_LOAD = 'reduxAsyncConnect/BEGIN_GLOBAL_LOAD';
8 | export const END_GLOBAL_LOAD = 'reduxAsyncConnect/END_GLOBAL_LOAD';
9 |
10 | export function reducer(state = {loaded: false}, action = {}) {
11 | const stateSlice = state[action.key];
12 |
13 | switch (action.type) {
14 | case BEGIN_GLOBAL_LOAD:
15 | return {
16 | ...state,
17 | loaded: false
18 | };
19 | case END_GLOBAL_LOAD:
20 | return {
21 | ...state,
22 |
23 | loaded: true
24 | };
25 | case LOAD:
26 | return {
27 | ...state,
28 | [action.key]: {
29 | ...stateSlice,
30 | loading: true,
31 | loaded: false
32 | }
33 | };
34 | case LOAD_SUCCESS:
35 | return {
36 | ...state,
37 | [action.key]: {
38 | ...stateSlice,
39 | loading: false,
40 | loaded: true,
41 | data: action.data
42 | }
43 | };
44 | case LOAD_FAIL:
45 | return {
46 | ...state,
47 | [action.key]: {
48 | ...stateSlice,
49 | loading: false,
50 | loaded: false,
51 | error: action.error
52 | }
53 | };
54 | case CLEAR:
55 | return {
56 | ...state,
57 | [action.key]: {
58 | loaded: false,
59 | loading: false
60 | }
61 | };
62 | default:
63 | return state;
64 | }
65 | }
66 |
67 | export function clearKey(key) {
68 | return {
69 | type: CLEAR,
70 | key
71 | };
72 | }
73 |
74 | export function beginGlobalLoad() {
75 | return { type: BEGIN_GLOBAL_LOAD };
76 | }
77 |
78 | export function endGlobalLoad() {
79 | return { type: END_GLOBAL_LOAD };
80 | }
81 |
82 | function load(key) {
83 | return {
84 | type: LOAD,
85 | key
86 | };
87 | }
88 |
89 | export function loadSuccess(key, data) {
90 | return {
91 | type: LOAD_SUCCESS,
92 | key,
93 | data
94 | };
95 | }
96 |
97 | function loadFail(key, error) {
98 | return {
99 | type: LOAD_FAIL,
100 | key,
101 | error
102 | };
103 | }
104 |
105 | function componentLoadCb(mapStateToProps, params, store, helpers) {
106 | const dispatch = store.dispatch;
107 |
108 | const promises = Object.keys(mapStateToProps).reduce((result, key) => {
109 | let promiseOrResult = mapStateToProps[key](params, {...helpers, store});
110 |
111 | if (promiseOrResult !== undefined) {
112 | if (promiseOrResult.then instanceof Function) {
113 | dispatch(load(key));
114 | promiseOrResult = promiseOrResult
115 | .then(nextData => dispatch(loadSuccess(key, nextData)),
116 | err => dispatch(loadFail(key, err)));
117 |
118 | return [...result, promiseOrResult];
119 | }
120 |
121 | dispatch(loadSuccess(key, promiseOrResult));
122 | }
123 | return [...result];
124 | }, []);
125 |
126 | return promises.length === 0 ? null : Promise.all(promises);
127 | }
128 |
129 | export function asyncConnect(mapStateToProps) {
130 | return Component => {
131 | Component.reduxAsyncConnect = (params, store, helpers) => componentLoadCb(mapStateToProps, params, store, helpers);
132 |
133 | const finalMapStateToProps = state => {
134 | return Object.keys(mapStateToProps).reduce((result, key) => ({...result, [key]: state.reduxAsyncConnect[key]}), {});
135 | };
136 |
137 | return connect(finalMapStateToProps)(Component);
138 | };
139 | }
140 |
--------------------------------------------------------------------------------
/modules/index.js:
--------------------------------------------------------------------------------
1 | import ReduxAsyncConnect, {loadOnServer} from './ReduxAsyncConnect';
2 | import {reducer, clearKey, loadSuccess, asyncConnect} from './asyncConnect';
3 |
4 | export {
5 | loadOnServer,
6 | ReduxAsyncConnect,
7 | reducer,
8 | clearKey,
9 | loadSuccess,
10 | asyncConnect
11 | };
12 |
--------------------------------------------------------------------------------
/npm-scripts/postinstall.js:
--------------------------------------------------------------------------------
1 | var execSync = require('child_process').execSync;
2 | var stat = require('fs').stat;
3 |
4 | function exec(command) {
5 | execSync(command, { stdio: [0, 1, 2] });
6 | }
7 |
8 | stat('lib', function (error, stat) {
9 | if (error || !stat.isDirectory()) {
10 | exec('npm run build');
11 | }
12 | });
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redux-async-connect",
3 | "version": "0.1.13",
4 | "description": "It allows you to request async data, store them in redux state and connect them to your react component.",
5 | "main": "lib/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+ssh://git@github.com/Rezonans/redux-async-connect.git"
9 | },
10 | "scripts": {
11 | "build": "babel ./modules -d lib --ignore '__tests__'",
12 | "lint": "eslint modules",
13 | "start": "babel-node example/server.js",
14 | "test": "npm run lint && karma start",
15 | "prepublish": "node ./npm-scripts/postinstall.js"
16 | },
17 | "keywords": [
18 | "redux",
19 | "react",
20 | "connect",
21 | "async",
22 | "props"
23 | ],
24 | "author": "Rodion Salnik (http://brocoders.com)",
25 | "license": "MIT",
26 | "bugs": {
27 | "url": "https://github.com/Rezonans/redux-async-connect/issues"
28 | },
29 | "homepage": "https://github.com/Rezonans/redux-async-connect",
30 | "peerDependencies": {
31 | "react": "0.14.x",
32 | "react-router": "2.x.x",
33 | "react-redux": "4.x.x"
34 | },
35 | "devDependencies": {
36 | "babel-cli": "^6.4.5",
37 | "babel-core": "^6.4.5",
38 | "babel-eslint": "^5.0.0-beta6",
39 | "babel-loader": "^6.2.1",
40 | "babel-preset-es2015": "^6.3.13",
41 | "babel-preset-react": "^6.3.13",
42 | "babel-preset-stage-0": "^6.3.13",
43 | "eslint": "1.4.1",
44 | "eslint-config-rackt": "1.0.0",
45 | "eslint-plugin-react": "3.3.2",
46 | "expect": "^1.12.0",
47 | "express": "^4.13.3",
48 | "gzip-size": "^3.0.0",
49 | "history": "^1.13.1",
50 | "karma": "^0.13.3",
51 | "karma-browserstack-launcher": "^0.1.3",
52 | "karma-chrome-launcher": "^0.2.0",
53 | "karma-firefox-launcher": "^0.1.6",
54 | "karma-mocha": "^0.2.0",
55 | "karma-mocha-reporter": "^1.0.4",
56 | "karma-sourcemap-loader": "^0.3.5",
57 | "karma-webpack": "^1.7.0",
58 | "mocha": "^2.0.1",
59 | "pretty-bytes": "^2.0.1",
60 | "react": "^0.14.2",
61 | "react-dom": "^0.14.2",
62 | "react-router": "2.0.0-rc5",
63 | "rimraf": "^2.4.2",
64 | "webpack": "^1.12.6",
65 | "webpack-dev-middleware": "^1.2.0",
66 | "eslint-config-airbnb": "^3.1.0"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | var execSync = require('child_process').execSync;
2 | var readFileSync = require('fs').readFileSync;
3 | var prettyBytes = require('pretty-bytes');
4 | var gzipSize = require('gzip-size');
5 |
6 | function exec(command) {
7 | execSync(command, { stdio: [0, 1, 2] });
8 | }
9 |
10 | exec('npm run build');
11 |
--------------------------------------------------------------------------------
/scripts/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if ! [ -e scripts/release.sh ]; then
4 | echo >&2 "Please run scripts/release.sh from the repo root"
5 | exit 1
6 | fi
7 |
8 | update_version() {
9 | echo "$(node -p "p=require('./${1}');p.version='${2}';JSON.stringify(p,null,2)")" > $1
10 | echo "Updated ${1} version to ${2}"
11 | }
12 |
13 | validate_semver() {
14 | if ! [[ $1 =~ ^[0-9]\.[0-9]+\.[0-9](-.+)? ]]; then
15 | echo >&2 "Version $1 is not valid! It must be a valid semver string like 1.0.2 or 2.3.0-beta1"
16 | exit 1
17 | fi
18 | }
19 |
20 | current_version=$(node -p "require('./package').version")
21 |
22 | printf "Next version (current is $current_version)? "
23 | read next_version
24 |
25 | validate_semver $next_version
26 |
27 | next_ref="v$next_version"
28 |
29 | # npm test -- --single-run
30 |
31 | update_version 'package.json' $next_version
32 |
33 | node scripts/build.js
34 |
35 | git commit -am "Version $next_version"
36 |
37 | git tag $next_ref
38 | git tag latest -f
39 |
40 | git push origin master
41 | git push origin $next_ref
42 | git push origin latest -f
43 |
44 | npm publish
45 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 |
3 | module.exports = {
4 |
5 | output: {
6 | library: 'ReduxAsyncConnect',
7 | libraryTarget: 'modules'
8 | },
9 |
10 | externals: [
11 | {
12 | react: {
13 | root: 'React',
14 | commonjs2: 'react',
15 | commonjs: 'react',
16 | amd: 'react'
17 | },
18 | 'react-router': {
19 | root: 'ReactRouter',
20 | commonjs2: 'react-router',
21 | commonjs: 'react-router',
22 | amd: 'react-router'
23 | }
24 | }
25 | ],
26 |
27 | module: {
28 | loaders: [
29 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel' }
30 | ]
31 | },
32 |
33 | node: {
34 | Buffer: false
35 | }
36 |
37 | };
38 |
--------------------------------------------------------------------------------