diff --git a/.babelrc.js b/.babelrc.js index 80a5d26..f6ca7f4 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -5,14 +5,14 @@ module.exports = { [ '@babel/env', { - modules: BABEL_ENV === 'commonjs' ? 'cjs' : false, + modules: (BABEL_ENV === 'commonjs' || BABEL_ENV === 'wepy') ? 'cjs' : false, loose: true, targets: NODE_ENV === 'test' ? { node: 'current' } : {} } ] ], plugins: [ - '@babel/plugin-transform-runtime', + BABEL_ENV !== 'wepy' && '@babel/plugin-transform-runtime', // don't use `loose` mode here - need to copy symbols when spreading '@babel/plugin-proposal-object-rest-spread', NODE_ENV === 'test' && '@babel/transform-modules-commonjs' diff --git a/__tests__/model.test.js b/__tests__/model.test.js index 4ae72d3..d9e3938 100644 --- a/__tests__/model.test.js +++ b/__tests__/model.test.js @@ -26,8 +26,8 @@ describe('model', () => { test('should dynamic load model', () => { const app = balloon(); app.model({ - namespace: 'a', - state: { count: 0 }, + namespace: 'views.a', + state: { count: 1 }, reducers: { 'COUNT_A_ADD': (state, { payload }) => { return Object.assign({}, state, { count: state.count + payload }); @@ -35,12 +35,22 @@ describe('model', () => { }, actions: { addCountForA: ['COUNT_A_ADD'] + }, + selectors: () => { + console.log('a selectors'); + const getA = state => state.views.a; + + return { getA }; } }); app.run(); + + console.log('app.state1:', app.getState()); + console.log('app.selectors.getA:', app.selectors.getA(app.getState())); + app.model({ - namespace: 'b', - state: { count: 0 }, + namespace: 'views.b', + state: { count: 2 }, reducers: { 'COUNT_B_ADD': (state, { payload }) => { return Object.assign({}, state, { count: state.count + payload }); @@ -48,13 +58,23 @@ describe('model', () => { }, actions: { addCountForB: ['COUNT_B_ADD'] + }, + selectors: () => { + console.log('b selectors'); + const getB = state => state.views.b; + + return { getB }; } }); - const { store, actions } = app; - expect(store.getState()).toEqual({ a: { count: 0 }, b: { count: 0 } }); - store.dispatch(actions.addCountForB(4)); - expect(store.getState()).toEqual({ a: { count: 0 }, b: { count: 4 } }); + console.log('app.state2:', app.getState()); + console.log('app.selectors.getB:', app.selectors.getB(app.getState())); + + // const { store, actions } = app; + // + // expect(store.getState()).toEqual({ a: { count: 0 }, b: { count: 0 } }); + // store.dispatch(actions.addCountForB(4)); + // expect(store.getState()).toEqual({ a: { count: 0 }, b: { count: 4 } }); }); test('should unload model', () => { diff --git a/__tests__/sagaModules.test.js b/__tests__/sagaModules.test.js index f83258d..fa44e65 100644 --- a/__tests__/sagaModules.test.js +++ b/__tests__/sagaModules.test.js @@ -1,4 +1,5 @@ -import { runSaga } from 'redux-saga'; +import { runSaga, stdChannel } from 'redux-saga'; +import EventEmitter from 'events'; import { addSagaModule, delSagaModule, @@ -41,21 +42,15 @@ describe('sagaModules', () => { describe('run sagas', () => { function createEmitter() { - const listeners = []; + const emitter = new EventEmitter(); + const channel = stdChannel(); + emitter.on('action', channel.put); return { - subscribe(callback) { - listeners.push(callback); - return () => { - const index = listeners.indexOf(callback); - if (index >= 0) { - listeners.splice(index, 1); - } - }; - }, + channel, emit(action) { - listeners.forEach(callback => callback(action)); + emitter.emit('action', action); } }; } @@ -83,10 +78,10 @@ describe('sagaModules', () => { test('should run sagas: sagas is plain object', (done) => { const dispatched = []; - const { subscribe, emit } = createEmitter(); + const { channel, emit } = createEmitter(); const runSagaMock = (saga) => { return runSaga({ - subscribe, + channel, dispatch: (action) => { dispatched.push(action); } @@ -121,16 +116,16 @@ describe('sagaModules', () => { ])); done(); }, - 20 + 100 ); }); test('should run sagas: sagas is function, and return plain object', (done) => { const dispatched = []; - const { subscribe, emit } = createEmitter(); + const { channel, emit } = createEmitter(); const runSagaMock = (saga) => { return runSaga({ - subscribe, + channel, dispatch: (action) => { dispatched.push(action); } diff --git a/examples/react/package.json b/examples/react/package.json index c22fdec..58b5038 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -28,7 +28,7 @@ "react-dom": "^16.6.3", "react-redux": "^6.0.0", "react-router-dom": "^4.3.1", - "redux-balloon": "^0.13.0", + "redux-balloon": "^1.0.0", "seamless-immutable": "^7.1.4", "serve-favicon": "^2.5.0", "throttle-debounce": "^2.0.1", diff --git a/gulpfile.js b/gulpfile.js index 4e0559a..419d15c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -5,18 +5,40 @@ const replace = require('gulp-replace'); gulp.task('buildWePY', function (callback) { runSequence( - 'buildBabel', + 'copySrc', 'replaceSagaReference', + 'buildBabel', callback ); }); -gulp.task('buildBabel', shell.task([ - 'cross-env BABEL_ENV=commonjs babel src --out-dir wepy' +gulp.task('copySrc', shell.task([ + 'cp -r src wepy' ])); gulp.task('replaceSagaReference', function () { gulp.src('./wepy/sagaImports.js') - .pipe(replace('redux-saga', 'redux-saga/dist/redux-saga')) + .pipe( + replace( + 'import * as ReduxSaga from \'redux-saga\';', + 'import * as ReduxSaga from \'redux-saga/dist/redux-saga.umd\';' + ) + ) + .pipe( + replace( + 'import * as effects from \'redux-saga/effects\';', + '' + ) + ) + .pipe( + replace( + 'const SagaEffects = effects;', + 'const SagaEffects = ReduxSaga.effects;' + ) + ) .pipe(gulp.dest('./wepy')); }); + +gulp.task('buildBabel', shell.task([ + 'cross-env BABEL_ENV=wepy babel wepy --out-dir wepy' +])); diff --git a/package.json b/package.json index 8e58436..ded2af1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redux-balloon", - "version": "0.13.0", + "version": "1.0.0", "description": "Lightweight business framework for Redux apps", "license": "MIT", "repository": { @@ -54,7 +54,7 @@ "invariant": "^2.2.4", "redux": "^4.0.1", "redux-actions": "^2.6.4", - "redux-saga": "^0.16.2", + "redux-saga": "^1.1.3", "reselect": "^4.0.0", "seamless-immutable": "^7.1.4" }, diff --git a/src/sagaImports.js b/src/sagaImports.js index e8d5d16..0415cf9 100644 --- a/src/sagaImports.js +++ b/src/sagaImports.js @@ -1,8 +1,11 @@ import * as ReduxSaga from 'redux-saga'; +import * as effects from 'redux-saga/effects'; +const SagaEffects = effects; const createSagaMiddleware = ReduxSaga.default; +const reduxSagaExport = { ...ReduxSaga, effects: SagaEffects }; export { createSagaMiddleware, - ReduxSaga + reduxSagaExport as ReduxSaga }; diff --git a/src/sagaModules.js b/src/sagaModules.js index 9445e5d..2c54ea6 100644 --- a/src/sagaModules.js +++ b/src/sagaModules.js @@ -32,7 +32,7 @@ function runSagaModules(modules, runSaga, opts, extras) { forEachObjIndexed((mod, namespace) => { const sagas = mod[0]; const saga = createSaga(sagas, namespace, opts, _extras); - runSaga(saga).done.catch(err => { + runSaga(saga).toPromise().catch(err => { if (!(err instanceof SagaError)) { err = new SagaError(err, { namespace }); } @@ -69,21 +69,30 @@ function createWatcher(sagas, namespace, opts, extras) { } return function* () { - const { takeEvery, takeLatest, throttle } = sagaEffects; + const typeWhiteList = [ + 'takeEvery', + 'takeLatest', + 'takeLeading', + 'throttle', + 'debounce' + ]; const keys = Object.keys(sagasObj); for (const key of keys) { + // takeEvery is default let type = 'takeEvery'; - let ms; let saga = sagasObj[key]; + let opts; if (isArray(saga)) { saga = sagasObj[key][0]; - const opts = sagasObj[key][1]; + opts = sagasObj[key][1]; type = opts.type; - if (type === 'throttle') { - ms = opts.ms; + if (!typeWhiteList.includes(type)) { + throw new Error( + `only support these types: [${typeWhiteList}], but got: ${type}. namespace: ${namespace}, key: ${key}` + ); } } const handler = handleActionForHelper( @@ -94,14 +103,13 @@ function createWatcher(sagas, namespace, opts, extras) { ); switch (type) { - case 'takeLatest': - yield takeLatest(key, handler); - break; case 'throttle': - yield throttle(ms, key, handler); + case 'debounce': + yield sagaEffects[type](opts.ms, key, handler); break; default: - yield takeEvery(key, handler); + // takeEvery, takeLatest, takeLeading + yield sagaEffects[type](key, handler); } } };