├── .bowerrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmignore
├── .nvmrc
├── .travis.yml
├── README.md
├── app
├── index.html
├── robots.txt
├── scripts
│ ├── actions.js
│ ├── app.js
│ ├── components
│ │ └── App.js
│ ├── reducer.js
│ ├── rxflux.js
│ └── utils.js
└── styles
│ └── main.scss
├── bower.json
├── gulpfile.js
└── package.json
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "app/bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "react"
4 | ],
5 | "ecmaFeatures": {
6 | "jsx": true,
7 | "modules": true
8 | },
9 | "env": {
10 | "browser": true,
11 | "amd": true,
12 | "es6": true,
13 | "node": true
14 | },
15 | "rules": {
16 | "comma-dangle": 1,
17 | "quotes": [ 1, "single" ],
18 | "no-undef": 1,
19 | "global-strict": 0,
20 | "no-extra-semi": 1,
21 | "no-underscore-dangle": 0,
22 | "no-console": 1,
23 | "no-unused-vars": 1,
24 | "no-trailing-spaces": [1, { "skipBlankLines": true }],
25 | "no-unreachable": 1,
26 | "no-alert": 0,
27 | "react/jsx-uses-react": 1
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | .sass-cache/
4 | app/bower_components/
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 5.11.1
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '4.1.0'
4 | before_install:
5 | - currentfolder=${PWD##*/}
6 | - if [ "$currentfolder" != 'react-webpack-template' ]; then cd .. && eval "mv $currentfolder react-webpack-template" && cd react-webpack-template; fi
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rxflux
2 |
3 | A simple effective Redux style framework using Rx
4 |
5 | *This is an example of how you might create a simple Flux/Redux style framework using nothing other than [RxJS](http://reactivex.io/rxjs)*
6 |
7 | I am sharing this for educational purposes.
8 |
9 | ## Installation
10 |
11 | Switch to use node 5.11.1 or above.
12 |
13 | ```bash
14 | $ nvm use
15 | ```
16 |
17 | Install dependencies
18 |
19 | ```bash
20 | $ npm i
21 | ```
22 |
23 | ## Run
24 |
25 | Run development mode
26 |
27 | ```bash
28 | $ npm start
29 | ```
30 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Redurx
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/robots.txt:
--------------------------------------------------------------------------------
1 | # robotstxt.org/
2 |
3 | User-agent: *
4 |
--------------------------------------------------------------------------------
/app/scripts/actions.js:
--------------------------------------------------------------------------------
1 | import { actionCreator } from './rxflux';
2 | import { Observable } from 'rxjs/Rx';
3 | import { map } from 'lodash';
4 |
5 | export const loadGithubFollowers = actionCreator((payload) => {
6 | const url = `https://api.github.com/users/${payload.username}/followers`;
7 | return {
8 | type: 'GITHUB_FOLLOWERS_LOADING',
9 | payload: Observable.ajax(url)
10 | .map((xhr) => map(xhr.response, 'login'))
11 | .map((followers) => ({
12 | type: 'GITHUB_FOLLOWERS_LOADED',
13 | payload: followers
14 | }))
15 | };
16 | });
17 |
18 | export const changeName = actionCreator((payload) => ({
19 | type: 'NAME_CHANGED',
20 | payload
21 | }));
22 |
--------------------------------------------------------------------------------
/app/scripts/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import App from './components/App';
5 | import { createStore } from './rxflux';
6 | import { log } from './utils';
7 |
8 | window.React = React;
9 |
10 | const container = document.getElementById('app');
11 |
12 | const initState = { name: 'Harry' };
13 |
14 | createStore(initState)
15 | .do(log)
16 | .subscribe((state) =>
17 | ReactDOM.render(, container)
18 | );
19 |
20 |
--------------------------------------------------------------------------------
/app/scripts/components/App.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | import { changeName, loadGithubFollowers } from '../actions';
4 |
5 | const handleChangeName = (data) => () =>
6 | changeName(data);
7 |
8 | const handleLoadFollowers = (data) => () =>
9 | loadGithubFollowers(data);
10 |
11 |
12 | function renderUsers(users) {
13 | if (!users) return;
14 | return (
15 | { users.map((user, index) => - {user}
) }
16 | );
17 | }
18 |
19 | export default function App(props) {
20 | const { isLoading, name, users } = props;
21 | return (
22 |
23 | { isLoading ?
24 |
Loading...
:
25 |
{ name }
}
26 | { renderUsers(users) }
27 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | App.propTypes = {
36 | name: PropTypes.string,
37 | users: PropTypes.array,
38 | isLoading: PropTypes.bool
39 | };
40 |
--------------------------------------------------------------------------------
/app/scripts/reducer.js:
--------------------------------------------------------------------------------
1 | export default function reducer(state, action) {
2 | switch (action.type) {
3 | case 'GITHUB_FOLLOWERS_LOADING':
4 | return {
5 | ...state,
6 | isLoading: true
7 | };
8 | case 'GITHUB_FOLLOWERS_LOADED':
9 | return {
10 | ...state,
11 | isLoading: false,
12 | users: action.payload,
13 | };
14 | case 'NAME_CHANGED':
15 | return {
16 | ...state,
17 | isLoading: false,
18 | name: action.payload
19 | };
20 | default:
21 | return state;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/scripts/rxflux.js:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs/Observable';
2 | import { Subject } from 'rxjs/Subject';
3 | import 'rxjs/add/operator/do';
4 | import 'rxjs/add/operator/mergeMap';
5 | import 'rxjs/add/operator/scan';
6 | import 'rxjs/add/operator/startWith';
7 | import reducer from './reducer';
8 | import { isObservable } from './utils';
9 |
10 | const action$ = new Subject();
11 |
12 | export const createStore = (initState) =>
13 | action$
14 | .flatMap((action) => isObservable(action) ? action : Observable.from([action]))
15 | .startWith(initState)
16 | .scan(reducer);
17 |
18 | export const actionCreator = (func) => (...args) => {
19 | const action = func.call(null, ...args);
20 | action$.next(action);
21 | if (isObservable(action.payload))
22 | action$.next(action.payload);
23 | return action;
24 | };
25 |
--------------------------------------------------------------------------------
/app/scripts/utils.js:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs/Observable';
2 | export const isObservable = obs => obs instanceof Observable;
3 | export const log = console.log.bind(console);
4 |
5 |
--------------------------------------------------------------------------------
/app/styles/main.scss:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | background: #fafafa;
4 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
5 | color: #333;
6 | }
7 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redurx",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var $ = require('gulp-load-plugins')();
5 | var sync = $.sync(gulp).sync;
6 | var del = require('del');
7 | var browserify = require('browserify');
8 | var watchify = require('watchify');
9 | var source = require('vinyl-source-stream');
10 |
11 | var bundler = {
12 | w: null,
13 | init: function() {
14 | this.w = watchify(browserify({
15 | entries: ['./app/scripts/app.js'],
16 | insertGlobals: true,
17 | cache: {},
18 | packageCache: {}
19 | }));
20 | },
21 | bundle: function() {
22 | return this.w && this.w.bundle()
23 | .on('error', $.util.log.bind($.util, 'Browserify Error'))
24 | .pipe(source('app.js'))
25 | .pipe(gulp.dest('dist/scripts'));
26 | },
27 | watch: function() {
28 | this.w && this.w.on('update', this.bundle.bind(this));
29 | },
30 | stop: function() {
31 | this.w && this.w.close();
32 | }
33 | };
34 |
35 | gulp.task('styles', function() {
36 | return $.rubySass('app/styles/main.scss', {
37 | style: 'expanded',
38 | precision: 10,
39 | loadPath: ['app/bower_components']
40 | })
41 | .on('error', $.util.log.bind($.util, 'Sass Error'))
42 | .pipe($.autoprefixer('last 1 version'))
43 | .pipe(gulp.dest('dist/styles'))
44 | .pipe($.size());
45 | });
46 |
47 | gulp.task('scripts', function() {
48 | bundler.init();
49 | return bundler.bundle();
50 | });
51 |
52 | gulp.task('html', function() {
53 | var assets = $.useref.assets();
54 | return gulp.src('app/*.html')
55 | .pipe(assets)
56 | .pipe(assets.restore())
57 | .pipe($.useref())
58 | .pipe(gulp.dest('dist'))
59 | .pipe($.size());
60 | });
61 |
62 | gulp.task('images', function() {
63 | return gulp.src('app/images/**/*')
64 | .pipe($.cache($.imagemin({
65 | optimizationLevel: 3,
66 | progressive: true,
67 | interlaced: true
68 | })))
69 | .pipe(gulp.dest('dist/images'))
70 | .pipe($.size());
71 | });
72 |
73 | gulp.task('fonts', function() {
74 | return gulp.src(['app/fonts/**/*'])
75 | .pipe(gulp.dest('dist/fonts'))
76 | .pipe($.size());
77 | });
78 |
79 | gulp.task('extras', function () {
80 | return gulp.src(['app/*.txt', 'app/*.ico'])
81 | .pipe(gulp.dest('dist/'))
82 | .pipe($.size());
83 | });
84 |
85 | gulp.task('serve', function() {
86 | gulp.src('dist')
87 | .pipe($.webserver({
88 | livereload: true,
89 | port: 9000
90 | }));
91 | });
92 |
93 | gulp.task('set-production', function() {
94 | process.env.NODE_ENV = 'production';
95 | });
96 |
97 | gulp.task('minify:js', function() {
98 | return gulp.src('dist/scripts/**/*.js')
99 | .pipe($.uglify())
100 | .pipe(gulp.dest('dist/scripts/'))
101 | .pipe($.size());
102 | });
103 |
104 | gulp.task('minify:css', function() {
105 | return gulp.src('dist/styles/**/*.css')
106 | .pipe($.minifyCss())
107 | .pipe(gulp.dest('dist/styles'))
108 | .pipe($.size());
109 | });
110 |
111 | gulp.task('minify', ['minify:js', 'minify:css']);
112 |
113 | gulp.task('clean', del.bind(null, 'dist'));
114 |
115 | gulp.task('bundle', ['html', 'styles', 'scripts', 'images', 'fonts', 'extras']);
116 |
117 | gulp.task('clean-bundle', sync(['clean', 'bundle']));
118 |
119 | gulp.task('build', ['clean-bundle'], bundler.stop.bind(bundler));
120 |
121 | gulp.task('build:production', sync(['set-production', 'build', 'minify']));
122 |
123 | gulp.task('serve:production', sync(['build:production', 'serve']));
124 |
125 | gulp.task('default', ['build']);
126 |
127 | gulp.task('watch', sync(['clean-bundle', 'serve']), function() {
128 | bundler.watch();
129 | gulp.watch('app/*.html', ['html']);
130 | gulp.watch('app/styles/**/*.scss', ['styles']);
131 | gulp.watch('app/images/**/*', ['images']);
132 | gulp.watch('app/fonts/**/*', ['fonts']);
133 | });
134 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rxflux",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "lodash": "^4.13.1",
6 | "react": "^15.1.0",
7 | "rxjs": "^5.0.0-beta.9"
8 | },
9 | "devDependencies": {
10 | "babel-plugin-transform-object-rest-spread": "^6.8.0",
11 | "babelify": "^6.0.2",
12 | "browserify": "^8.1.3",
13 | "del": "^1.1.1",
14 | "gulp": "^3.8.10",
15 | "gulp-autoprefixer": "^2.1.0",
16 | "gulp-bower": "^0.0.10",
17 | "gulp-cache": "^0.2.4",
18 | "gulp-imagemin": "^2.1.0",
19 | "gulp-load-plugins": "^0.8.0",
20 | "gulp-minify-css": "^0.4.5",
21 | "gulp-ruby-sass": "^1.0.0-alpha",
22 | "gulp-size": "^1.2.0",
23 | "gulp-sync": "^0.1.4",
24 | "gulp-uglify": "^1.1.0",
25 | "gulp-useref": "^1.1.1",
26 | "gulp-util": "^3.0.3",
27 | "gulp-webserver": "^0.9.0",
28 | "vinyl-source-stream": "^1.0.0",
29 | "watchify": "^2.3.0"
30 | },
31 | "scripts": {
32 | "start": "gulp watch"
33 | },
34 | "engines": {
35 | "node": ">=5.11.1"
36 | },
37 | "browserify": {
38 | "transform": [
39 | "babelify"
40 | ],
41 | "plugins": ["transform-object-rest-spread"]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------