├── index.js ├── sample └── basic │ ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html │ ├── src │ ├── Home.js │ ├── index.js │ ├── services │ │ └── async.js │ ├── registry.js │ ├── App.js │ ├── reser │ │ ├── services │ │ │ ├── util │ │ │ │ ├── proto.js │ │ │ │ └── index.js │ │ │ ├── storage │ │ │ │ ├── local.js │ │ │ │ └── index.js │ │ │ └── store.js │ │ ├── loader.js │ │ ├── provider.js │ │ ├── collection.js │ │ └── index.js │ └── serviceWorker.js │ ├── .gitignore │ ├── package.json │ └── README.md ├── babel.config.js ├── .eslintrc.json ├── src ├── provider.js ├── services │ ├── storage │ │ ├── index.js │ │ └── local.js │ ├── util │ │ ├── proto.js │ │ └── index.js │ └── store.js ├── collection.js ├── loader.js └── index.js ├── LICENSE ├── .gitignore ├── package.json └── README.md /index.js: -------------------------------------------------------------------------------- 1 | exports = module.exports = require('./lib').default -------------------------------------------------------------------------------- /sample/basic/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ethan0007/reser/master/sample/basic/public/favicon.ico -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true) 3 | return { 4 | presets: [ 5 | '@babel/preset-env', 6 | '@babel/preset-react' 7 | ], 8 | plugins: [ 9 | '@babel/plugin-syntax-dynamic-import', 10 | '@babel/plugin-proposal-class-properties' 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /sample/basic/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /sample/basic/src/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withService } from './reser'; 3 | 4 | function Home(props) { 5 | const { async } = props.services 6 | if (async) console.log(async); 7 | return
8 |

Home

9 |
10 | { 11 | async && 12 | 13 | } 14 |
15 |
16 | } 17 | 18 | export default withService('async')(Home) -------------------------------------------------------------------------------- /sample/basic/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /sample/basic/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import * as serviceWorker from './serviceWorker'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | 8 | // If you want your app to work offline and load faster, you can change 9 | // unregister() to register() below. Note this comes with some pitfalls. 10 | // Learn more about service workers: https://bit.ly/CRA-PWA 11 | serviceWorker.unregister(); 12 | -------------------------------------------------------------------------------- /sample/basic/src/services/async.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withNewService } from '../reser' 3 | 4 | function name(state = { asyncName: 'Foo' }, action) { 5 | return state 6 | } 7 | 8 | function AsyncPage(props) { 9 | return
Async View
10 | } 11 | 12 | function NewService() { 13 | this.name = 'Kevin' 14 | } 15 | 16 | function registry(services) { 17 | services.add(NewService, 'newservice') 18 | } 19 | 20 | export default class Async { 21 | 22 | static reducer = name 23 | static persist = true 24 | 25 | title = 'Async Service' 26 | 27 | View = withNewService(registry)(AsyncPage) 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sample/basic/src/registry.js: -------------------------------------------------------------------------------- 1 | 2 | import { UtilService } from './reser' 3 | 4 | function hello(state = { name: 'default' }, action) { 5 | return state 6 | } 7 | 8 | class Test { 9 | static service = 'test' 10 | static reducer = hello 11 | static persist = true 12 | static mergetest = { 13 | b: 2 14 | } 15 | name = 'Test' 16 | } 17 | 18 | const FuncService = function (params) { 19 | this.name = 'Func' 20 | } 21 | 22 | FuncService.mergetest = { 23 | a: 1 24 | } 25 | 26 | export default function (services) { 27 | 28 | services.add(Test) 29 | 30 | services.add(FuncService, 'func') 31 | 32 | services.add(() => import('./services/async'), 'async') 33 | 34 | } 35 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended" 9 | ], 10 | "parser": "babel-eslint", 11 | "globals": { 12 | "Atomics": "readonly", 13 | "SharedArrayBuffer": "readonly" 14 | }, 15 | "parserOptions": { 16 | "ecmaVersion": 2018, 17 | "sourceType": "module" 18 | }, 19 | "plugins": [ 20 | "react-hooks" 21 | ], 22 | "rules": { 23 | "quotes": [ 24 | "error", 25 | "single" 26 | ], 27 | "semi": [ 28 | "error", 29 | "never" 30 | ], 31 | "react-hooks/rules-of-hooks": "error", 32 | "react-hooks/exhaustive-deps": "warn" 33 | } 34 | } -------------------------------------------------------------------------------- /src/provider.js: -------------------------------------------------------------------------------- 1 | import { ServiceProvider as BaseServiceProvider } from 'jservice' 2 | import Loader from './loader' 3 | 4 | class ServiceProvider extends BaseServiceProvider { 5 | 6 | createServices(serviceNames, callback) { 7 | let services = {} 8 | for (let i = 0; i < serviceNames.length; i++) { 9 | const name = serviceNames[i] 10 | let service = this.service(name) 11 | if (service instanceof Loader) { 12 | if (!service.value) 13 | service.load(true).then(isNew => isNew && callback()) 14 | service = service.value 15 | } 16 | services[name] = service 17 | } 18 | return services 19 | } 20 | 21 | } 22 | 23 | export default ServiceProvider 24 | -------------------------------------------------------------------------------- /src/services/storage/index.js: -------------------------------------------------------------------------------- 1 | import Local from './local' 2 | 3 | class Storage { 4 | static service = 'storage' 5 | 6 | adapter = null 7 | 8 | constructor(provider, config) { 9 | const result = typeof config === 'function' ? 10 | config() : this._defaultConfig() 11 | this.adapter = result.adapter 12 | } 13 | 14 | _defaultConfig() { 15 | return { 16 | adapter: new Local() 17 | } 18 | } 19 | 20 | setItem(key, val) { 21 | return this.adapter.setItem(key, val) 22 | } 23 | 24 | getItem(key) { 25 | return this.adapter.getItem(key) 26 | } 27 | 28 | getAllKeys() { 29 | return this.adapter.getAllKeys() 30 | } 31 | 32 | } 33 | 34 | export default Storage 35 | -------------------------------------------------------------------------------- /sample/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react-dom": "^16.8.6", 7 | "react-scripts": "3.0.1" 8 | }, 9 | "scripts": { 10 | "start": "react-scripts start", 11 | "build": "react-scripts build", 12 | "test": "react-scripts test", 13 | "eject": "react-scripts eject" 14 | }, 15 | "eslintConfig": { 16 | "extends": "react-app" 17 | }, 18 | "browserslist": { 19 | "production": [ 20 | ">0.2%", 21 | "not dead", 22 | "not op_mini all" 23 | ], 24 | "development": [ 25 | "last 1 chrome version", 26 | "last 1 firefox version", 27 | "last 1 safari version" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/services/storage/local.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * A polyfill for asyncStorage that uses localStorage 4 | */ 5 | 6 | class Storage { 7 | 8 | constructor() { 9 | try { 10 | localStorage.setItem('____', '') 11 | localStorage.removeItem('____') 12 | this.available = true 13 | } catch (e) { 14 | this.available = false 15 | } 16 | } 17 | 18 | setItem(key, val) { 19 | if (this.available) localStorage.setItem(key, val) 20 | return Promise.resolve() 21 | } 22 | 23 | getItem(key) { 24 | let val = null 25 | if (this.available) val = localStorage.getItem(key) 26 | return Promise.resolve(val) 27 | } 28 | 29 | getAllKeys() { 30 | return Promise.resolve(this.available ? 31 | Object.keys(localStorage) : []) 32 | } 33 | 34 | } 35 | 36 | export default Storage 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 RhaldKhein 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | lib 3 | scratch 4 | sample/**/engine 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | # next.js build output 66 | .next -------------------------------------------------------------------------------- /src/services/util/proto.js: -------------------------------------------------------------------------------- 1 | import _set from 'lodash.set' 2 | 3 | export function get(obj, path, defaultValue = null) { 4 | return String.prototype.split.call(path, /[,[\].]+?/) 5 | .filter(Boolean) 6 | .reduce((a, c) => (Object.hasOwnProperty.call(a, c) ? a[c] : defaultValue), obj) 7 | } 8 | 9 | export function set(obj, val) { 10 | return _set(obj, val) 11 | } 12 | 13 | export function pick(object, keys) { 14 | return keys.reduce((obj, key) => { 15 | if (object && object.hasOwnProperty(key)) { 16 | obj[key] = object[key] 17 | } 18 | return obj 19 | }, {}) 20 | } 21 | 22 | export function isEmpty(obj) { 23 | return [Object, Array].includes((obj || {}).constructor) && !Object.entries((obj || {})).length 24 | } 25 | 26 | export function mapObject(object, mapFn) { 27 | return Object.keys(object).reduce(function (result, key) { 28 | result[key] = mapFn(object[key]) 29 | return result 30 | }, {}) 31 | } 32 | 33 | export function isFunction(fn) { 34 | return typeof fn === 'function' 35 | } 36 | 37 | export function isConstructor(fn) { 38 | return typeof fn === 'function' && fn.hasOwnProperty('prototype') 39 | } 40 | 41 | export function invert(obj) { 42 | let ret = {} 43 | for (let key in obj) ret[obj[key]] = key 44 | return ret 45 | } -------------------------------------------------------------------------------- /sample/basic/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withContainer } from './reser' 3 | import registry from './registry' 4 | import { BrowserRouter as Router, Route, Link } from "react-router-dom" 5 | import Home from './Home' 6 | 7 | function Index() { 8 | return

Home

; 9 | } 10 | 11 | function About() { 12 | return

About

; 13 | } 14 | 15 | function Users() { 16 | return

Users

; 17 | } 18 | 19 | function App({ container }) { 20 | // console.log(container) 21 | const util = container.provider.get('util') 22 | const res = util.getStatics('mergetest', { 23 | merge: true 24 | }) 25 | console.log(res); 26 | util.loadAsyncServices().then(res => { 27 | console.log(res) 28 | }) 29 | return ( 30 | container.isReady && 31 |
32 | 33 |
34 |

Reser

35 | 48 |
49 |
50 | 51 | 52 | 53 |
54 |
55 | 56 |
57 |
58 |
59 | ) 60 | } 61 | 62 | export default withContainer(registry)(App); 63 | -------------------------------------------------------------------------------- /src/collection.js: -------------------------------------------------------------------------------- 1 | import { ServiceCollection as BaseServiceCollection } from 'jservice' 2 | import { isFunction, isConstructor } from './services/util/proto' 3 | import Loader from './loader' 4 | 5 | class ServiceCollection extends BaseServiceCollection { 6 | 7 | constructor(core) { 8 | super(core) 9 | this._core = core 10 | } 11 | 12 | _validate(service) { 13 | let err = null 14 | if (service.async) err = 'async' 15 | // Add more static validation 16 | if (err) throw new Error(`Invalid service signature "${err}"`) 17 | } 18 | 19 | _push(Service, name, config, skip) { 20 | this._validate(Service) 21 | // Check if service is async or not 22 | if (isFunction(Service) && !isConstructor(Service)) { 23 | // Async service 24 | Service.service = name 25 | const loader = new Loader(this._core.provider, Service, name, config) 26 | const LoaderService = () => loader 27 | LoaderService.type = Service.type 28 | LoaderService.service = name 29 | LoaderService.async = true 30 | Service = LoaderService 31 | } 32 | super._push(Service, name, config, skip) 33 | } 34 | 35 | isAsyncService(name) { 36 | const { names, services } = this 37 | for (const key in names) { 38 | if (names.hasOwnProperty(key) && key === name) { 39 | return services[names[key]].async || false 40 | } 41 | } 42 | return true 43 | } 44 | 45 | scoped() { 46 | throw new Error('Scoped services are not supported yet') 47 | } 48 | 49 | transient() { 50 | throw new Error('Transient services are not supported yet') 51 | } 52 | 53 | } 54 | 55 | export default ServiceCollection 56 | -------------------------------------------------------------------------------- /src/services/util/index.js: -------------------------------------------------------------------------------- 1 | import * as proto from './proto' 2 | 3 | class Util { 4 | static service = 'util' 5 | _core = null 6 | 7 | constructor(provider, custom) { 8 | this._core = provider.get('__core__') 9 | for (const key in custom) { 10 | if (this[key]) { 11 | // eslint-disable-next-line no-console 12 | console.warn(`Built-in util can't add existing "${key}"`) 13 | continue 14 | } 15 | this[key] = custom[key] 16 | } 17 | } 18 | 19 | getStatics(prop, options = {}) { 20 | let { names, services } = this._core.collection 21 | let result = {} 22 | names = this.invert(names) 23 | for (let i = 0; i < services.length; i++) { 24 | let service = services[i] 25 | if (service.async) { 26 | service = service()._service 27 | if (!service) continue 28 | } 29 | let value = service[prop] 30 | if (value === undefined) continue 31 | if (options.merge) { 32 | Object.assign(result, value) 33 | continue 34 | } 35 | result[names[i]] = value 36 | } 37 | return result 38 | } 39 | 40 | loadAsyncServices(...serviceNames) { 41 | let { names, services } = this._core.collection 42 | let toLoad = [] 43 | names = this.invert(names) 44 | for (let i = 0; i < services.length; i++) { 45 | let service = services[i] 46 | if (!service.async) continue 47 | service = service() 48 | if (serviceNames.length && 49 | serviceNames.indexOf(names[i]) === -1) 50 | continue 51 | toLoad.push(service.load()) 52 | } 53 | return Promise.all(toLoad) 54 | } 55 | 56 | } 57 | 58 | Object.assign(Util.prototype, proto) 59 | 60 | export default Util -------------------------------------------------------------------------------- /sample/basic/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/loader.js: -------------------------------------------------------------------------------- 1 | import { isFunction, isConstructor } from './services/util/proto' 2 | 3 | class Loader { 4 | 5 | constructor(provider, loader, name, config) { 6 | this._provider = provider 7 | this._loader = loader 8 | this._name = name 9 | this._config = config 10 | this._service = null 11 | this._loading = null 12 | this.value = null 13 | } 14 | 15 | _fetch() { 16 | if (this._service) return Promise.resolve(this._service) 17 | if (!this._loader) 18 | throw new Error('Async loader is not referenced to a service') 19 | return this._loader() 20 | .then(Service => { 21 | this._service = Service.default || Service 22 | return this._service 23 | }) 24 | } 25 | 26 | load(onlyNew) { 27 | if (this.value && onlyNew) return Promise.resolve() 28 | if (this._loading) { 29 | if (onlyNew) return Promise.resolve() 30 | return this._loading 31 | } 32 | if (this.value) return Promise.resolve(this.value) 33 | // Continue fetching and creating instance 34 | this._loading = this._fetch() 35 | .then(Service => { 36 | let result 37 | if (isConstructor(Service)) { 38 | result = new Service( 39 | this._provider, 40 | this._config && this._config(this._provider) 41 | ) 42 | } else if (isFunction(Service)) { 43 | result = Service() 44 | } else { 45 | result = Service 46 | } 47 | this.value = result 48 | this._loading = null 49 | if (Service.persist) { 50 | return this._provider.service('store') 51 | ._persistService(Service, this._name) 52 | } 53 | }) 54 | .then(() => this.value) 55 | return this._loading 56 | } 57 | 58 | } 59 | 60 | export default Loader 61 | -------------------------------------------------------------------------------- /sample/basic/src/reser/services/util/proto.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.get = get; 7 | exports.set = set; 8 | exports.pick = pick; 9 | exports.isEmpty = isEmpty; 10 | exports.mapObject = mapObject; 11 | exports.isFunction = isFunction; 12 | exports.isConstructor = isConstructor; 13 | exports.invert = invert; 14 | 15 | var _lodash = _interopRequireDefault(require("lodash.set")); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 18 | 19 | function get(obj, path) { 20 | var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; 21 | return String.prototype.split.call(path, /[,[\].]+?/).filter(Boolean).reduce(function (a, c) { 22 | return Object.hasOwnProperty.call(a, c) ? a[c] : defaultValue; 23 | }, obj); 24 | } 25 | 26 | function set(obj, val) { 27 | return (0, _lodash["default"])(obj, val); 28 | } 29 | 30 | function pick(object, keys) { 31 | return keys.reduce(function (obj, key) { 32 | if (object && object.hasOwnProperty(key)) { 33 | obj[key] = object[key]; 34 | } 35 | 36 | return obj; 37 | }, {}); 38 | } 39 | 40 | function isEmpty(obj) { 41 | return [Object, Array].includes((obj || {}).constructor) && !Object.entries(obj || {}).length; 42 | } 43 | 44 | function mapObject(object, mapFn) { 45 | return Object.keys(object).reduce(function (result, key) { 46 | result[key] = mapFn(object[key]); 47 | return result; 48 | }, {}); 49 | } 50 | 51 | function isFunction(fn) { 52 | return typeof fn === 'function'; 53 | } 54 | 55 | function isConstructor(fn) { 56 | return typeof fn === 'function' && fn.hasOwnProperty('prototype'); 57 | } 58 | 59 | function invert(obj) { 60 | var ret = {}; 61 | 62 | for (var key in obj) { 63 | ret[obj[key]] = key; 64 | } 65 | 66 | return ret; 67 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reser", 3 | "version": "0.0.0", 4 | "description": "An asynchronous DI framework for modular React and React-Native", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "npm run clean && babel src --out-dir lib", 8 | "watch": "npm run clean && babel src --watch --out-dir lib", 9 | "watch-basic": "npm run clean && babel src --watch --out-dir sample/basic/src/reser", 10 | "clean": "rm -rf lib && rm -rf sample/basic/src/reser", 11 | "prepare": "npm run build", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/rhaldkhein/reser.git" 17 | }, 18 | "author": "RhaldKhein", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/rhaldkhein/reser/issues" 22 | }, 23 | "homepage": "https://github.com/rhaldkhein/reser#readme", 24 | "files": [ 25 | "lib", 26 | "src" 27 | ], 28 | "dependencies": { 29 | "jservice": "^0.1.22", 30 | "lodash.set": "^4.3.2" 31 | }, 32 | "devDependencies": { 33 | "@babel/cli": "^7.5.0", 34 | "@babel/core": "^7.4.5", 35 | "@babel/plugin-proposal-class-properties": "^7.4.4", 36 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 37 | "@babel/preset-env": "^7.4.5", 38 | "@babel/preset-es2016": "^7.0.0-beta.53", 39 | "@babel/preset-react": "^7.0.0", 40 | "babel-eslint": "^10.0.1", 41 | "babel-loader": "^8.0.6", 42 | "eslint": "^5.16.0", 43 | "eslint-plugin-react-hooks": "^1.6.0", 44 | "node-localstorage": "^1.3.1", 45 | "react": "^16.8.6", 46 | "react-redux": "^7.1.0", 47 | "react-router-dom": "^5.0.1", 48 | "redux": "^4.0.4" 49 | }, 50 | "keywords": [ 51 | "react", 52 | "react native", 53 | "framework", 54 | "modular", 55 | "services", 56 | "di", 57 | "ioc", 58 | "di container", 59 | "dependency injection" 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /sample/basic/src/reser/services/storage/local.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 9 | 10 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 11 | 12 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 13 | 14 | /** 15 | * A polyfill for asyncStorage that uses localStorage 16 | */ 17 | var Storage = 18 | /*#__PURE__*/ 19 | function () { 20 | function Storage() { 21 | _classCallCheck(this, Storage); 22 | 23 | try { 24 | localStorage.setItem('____', ''); 25 | localStorage.removeItem('____'); 26 | this.available = true; 27 | } catch (e) { 28 | this.available = false; 29 | } 30 | } 31 | 32 | _createClass(Storage, [{ 33 | key: "setItem", 34 | value: function setItem(key, val) { 35 | if (this.available) localStorage.setItem(key, val); 36 | return Promise.resolve(); 37 | } 38 | }, { 39 | key: "getItem", 40 | value: function getItem(key) { 41 | var val = null; 42 | if (this.available) val = localStorage.getItem(key); 43 | return Promise.resolve(val); 44 | } 45 | }, { 46 | key: "getAllKeys", 47 | value: function getAllKeys() { 48 | return Promise.resolve(this.available ? Object.keys(localStorage) : []); 49 | } 50 | }]); 51 | 52 | return Storage; 53 | }(); 54 | 55 | var _default = Storage; 56 | exports["default"] = _default; -------------------------------------------------------------------------------- /sample/basic/src/reser/services/storage/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _local = _interopRequireDefault(require("./local")); 9 | 10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 11 | 12 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 13 | 14 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 15 | 16 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 17 | 18 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 19 | 20 | var Storage = 21 | /*#__PURE__*/ 22 | function () { 23 | function Storage(provider, config) { 24 | _classCallCheck(this, Storage); 25 | 26 | _defineProperty(this, "adapter", null); 27 | 28 | var result = typeof config === 'function' ? config() : this._defaultConfig(); 29 | this.adapter = result.adapter; 30 | } 31 | 32 | _createClass(Storage, [{ 33 | key: "_defaultConfig", 34 | value: function _defaultConfig() { 35 | return { 36 | adapter: new _local["default"]() 37 | }; 38 | } 39 | }, { 40 | key: "setItem", 41 | value: function setItem(key, val) { 42 | return this.adapter.setItem(key, val); 43 | } 44 | }, { 45 | key: "getItem", 46 | value: function getItem(key) { 47 | return this.adapter.getItem(key); 48 | } 49 | }, { 50 | key: "getAllKeys", 51 | value: function getAllKeys() { 52 | return this.adapter.getAllKeys(); 53 | } 54 | }]); 55 | 56 | return Storage; 57 | }(); 58 | 59 | _defineProperty(Storage, "service", 'storage'); 60 | 61 | var _default = Storage; 62 | exports["default"] = _default; -------------------------------------------------------------------------------- /sample/basic/src/reser/loader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _proto = require("./services/util/proto"); 9 | 10 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 11 | 12 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 13 | 14 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 15 | 16 | var Loader = 17 | /*#__PURE__*/ 18 | function () { 19 | function Loader(provider, loader, name, config) { 20 | _classCallCheck(this, Loader); 21 | 22 | this._provider = provider; 23 | this._loader = loader; 24 | this._name = name; 25 | this._config = config; 26 | this._service = null; 27 | this._loading = null; 28 | this.value = null; 29 | } 30 | 31 | _createClass(Loader, [{ 32 | key: "_fetch", 33 | value: function _fetch() { 34 | var _this = this; 35 | 36 | if (this._service) return Promise.resolve(this._service); 37 | if (!this._loader) throw new Error('Async loader is not referenced to a service'); 38 | return this._loader().then(function (Service) { 39 | _this._service = Service["default"] || Service; 40 | return _this._service; 41 | }); 42 | } 43 | }, { 44 | key: "load", 45 | value: function load(onlyNew) { 46 | var _this2 = this; 47 | 48 | if (this.value && onlyNew) return Promise.resolve(); 49 | 50 | if (this._loading) { 51 | if (onlyNew) return Promise.resolve(); 52 | return this._loading; 53 | } 54 | 55 | if (this.value) return Promise.resolve(this.value); // Continue fetching and creating instance 56 | 57 | this._loading = this._fetch().then(function (Service) { 58 | var result; 59 | 60 | if ((0, _proto.isConstructor)(Service)) { 61 | result = new Service(_this2._provider, _this2._config && _this2._config(_this2._provider)); 62 | } else if ((0, _proto.isFunction)(Service)) { 63 | result = Service(); 64 | } else { 65 | result = Service; 66 | } 67 | 68 | _this2.value = result; 69 | _this2._loading = null; 70 | 71 | if (Service.persist) { 72 | return _this2._provider.service('store')._persistService(Service, _this2._name); 73 | } 74 | }).then(function () { 75 | return _this2.value; 76 | }); 77 | return this._loading; 78 | } 79 | }]); 80 | 81 | return Loader; 82 | }(); 83 | 84 | var _default = Loader; 85 | exports["default"] = _default; -------------------------------------------------------------------------------- /sample/basic/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /sample/basic/src/reser/provider.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _jservice = require("jservice"); 9 | 10 | var _loader = _interopRequireDefault(require("./loader")); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 13 | 14 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 15 | 16 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 17 | 18 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 19 | 20 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 21 | 22 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 23 | 24 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 25 | 26 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 27 | 28 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 29 | 30 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 31 | 32 | var ServiceProvider = 33 | /*#__PURE__*/ 34 | function (_BaseServiceProvider) { 35 | _inherits(ServiceProvider, _BaseServiceProvider); 36 | 37 | function ServiceProvider() { 38 | _classCallCheck(this, ServiceProvider); 39 | 40 | return _possibleConstructorReturn(this, _getPrototypeOf(ServiceProvider).apply(this, arguments)); 41 | } 42 | 43 | _createClass(ServiceProvider, [{ 44 | key: "createServices", 45 | value: function createServices(serviceNames, callback) { 46 | var services = {}; 47 | 48 | for (var i = 0; i < serviceNames.length; i++) { 49 | var name = serviceNames[i]; 50 | var service = this.service(name); 51 | 52 | if (service instanceof _loader["default"]) { 53 | if (!service.value) service.load(true).then(function (isNew) { 54 | return isNew && callback(); 55 | }); 56 | service = service.value; 57 | } 58 | 59 | services[name] = service; 60 | } 61 | 62 | return services; 63 | } 64 | }]); 65 | 66 | return ServiceProvider; 67 | }(_jservice.ServiceProvider); 68 | 69 | var _default = ServiceProvider; 70 | exports["default"] = _default; -------------------------------------------------------------------------------- /sample/basic/src/reser/services/util/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var proto = _interopRequireWildcard(require("./proto")); 9 | 10 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj["default"] = obj; return newObj; } } 11 | 12 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 13 | 14 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 15 | 16 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 17 | 18 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 19 | 20 | var Util = 21 | /*#__PURE__*/ 22 | function () { 23 | function Util(provider, custom) { 24 | _classCallCheck(this, Util); 25 | 26 | _defineProperty(this, "_core", null); 27 | 28 | this._core = provider.get('__core__'); 29 | 30 | for (var key in custom) { 31 | if (this[key]) { 32 | // eslint-disable-next-line no-console 33 | console.warn("Built-in util can't add existing \"".concat(key, "\"")); 34 | continue; 35 | } 36 | 37 | this[key] = custom[key]; 38 | } 39 | } 40 | 41 | _createClass(Util, [{ 42 | key: "getStatics", 43 | value: function getStatics(prop) { 44 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 45 | var _this$_core$collectio = this._core.collection, 46 | names = _this$_core$collectio.names, 47 | services = _this$_core$collectio.services; 48 | var result = {}; 49 | names = this.invert(names); 50 | 51 | for (var i = 0; i < services.length; i++) { 52 | var service = services[i]; 53 | 54 | if (service.async) { 55 | service = service()._service; 56 | if (!service) continue; 57 | } 58 | 59 | var value = service[prop]; 60 | if (value === undefined) continue; 61 | 62 | if (options.merge) { 63 | Object.assign(result, value); 64 | continue; 65 | } 66 | 67 | result[names[i]] = value; 68 | } 69 | 70 | return result; 71 | } 72 | }, { 73 | key: "loadAsyncServices", 74 | value: function loadAsyncServices() { 75 | var _this$_core$collectio2 = this._core.collection, 76 | names = _this$_core$collectio2.names, 77 | services = _this$_core$collectio2.services; 78 | var toLoad = []; 79 | names = this.invert(names); 80 | 81 | for (var _len = arguments.length, serviceNames = new Array(_len), _key = 0; _key < _len; _key++) { 82 | serviceNames[_key] = arguments[_key]; 83 | } 84 | 85 | for (var i = 0; i < services.length; i++) { 86 | var service = services[i]; 87 | if (!service.async) continue; 88 | service = service(); 89 | if (serviceNames.length && serviceNames.indexOf(names[i]) === -1) continue; 90 | toLoad.push(service.load()); 91 | } 92 | 93 | return Promise.all(toLoad); 94 | } 95 | }]); 96 | 97 | return Util; 98 | }(); 99 | 100 | _defineProperty(Util, "service", 'util'); 101 | 102 | Object.assign(Util.prototype, proto); 103 | var _default = Util; 104 | exports["default"] = _default; -------------------------------------------------------------------------------- /src/services/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers } from 'redux' 2 | 3 | export default class Store { 4 | static service = 'store' 5 | 6 | base = null 7 | _namespace = 'rsrx:' 8 | _collection = null 9 | _config = null 10 | _reducers = null 11 | _storage = null 12 | _util = null 13 | 14 | constructor(provider, config) { 15 | this._config = config 16 | this._storage = provider.service('storage') 17 | this._util = provider.service('util') 18 | this._collection = provider.service('__core__').collection 19 | const { names, services } = this._collection 20 | let reducers = {} 21 | for (const name in names) { 22 | if (names.hasOwnProperty(name)) { 23 | const service = services[names[name]] 24 | if (service.reducer) 25 | reducers[name] = service.reducer 26 | } 27 | } 28 | this._reducers = reducers 29 | } 30 | 31 | _defaultConfig(arg) { 32 | return { 33 | store: createStore( 34 | arg.rootReducer || (s => s), 35 | arg.initialState || {} 36 | ) 37 | } 38 | } 39 | 40 | _getInitialState() { 41 | let state = {} 42 | let storage = this._storage 43 | if (storage) { 44 | return storage.getAllKeys() 45 | .then(keys => { 46 | const toGet = [] 47 | keys.sort((a, b) => a.length - b.length) 48 | keys.forEach(key => { 49 | if (!key.startsWith(this._namespace)) return 50 | const path = key.split(':')[1] 51 | // Async service should not be added in initial state 52 | toGet.push( 53 | storage.getItem(key).then(value => { 54 | this._util.set(state, path, JSON.parse(value)) 55 | }) 56 | ) 57 | }) 58 | return Promise.all(toGet) 59 | }) 60 | .then(() => state) 61 | } 62 | return Promise.resolve({}) 63 | } 64 | 65 | _createStore() { 66 | return Promise.resolve() 67 | .then(() => this._getInitialState()) 68 | .then(state => { 69 | let arg = { 70 | rootReducer: combineReducers(this._reducers), 71 | initialState: state 72 | } 73 | let result = ( 74 | typeof this._config === 'function' ? 75 | this._config(arg) : 76 | this._defaultConfig(arg) 77 | ) 78 | this._setStore(result.store) 79 | }) 80 | } 81 | 82 | _setStore(store) { 83 | this.base = store 84 | const { names, services } = this._collection 85 | for (const name in names) { 86 | if (!names.hasOwnProperty(name)) continue 87 | const service = services[names[name]] 88 | if (service.persist) { 89 | this._persistService(service, name) 90 | } 91 | } 92 | } 93 | 94 | _persistService(service, name) { 95 | if (service.persist === undefined) return 96 | if (!name) throw new Error('Persistent service must have name') 97 | // Write to storage 98 | if (service.persist === true) { 99 | // Persist whole service 100 | this._persistState(name) 101 | } else { 102 | // Should be array, persist by keys 103 | service.persist.forEach(path => { 104 | this._persistState(name + '.' + path) 105 | }) 106 | } 107 | // Mutate reducer 108 | this.base.replaceReducer( 109 | combineReducers({ 110 | ...this._reducers, 111 | [name]: service.reducer 112 | }) 113 | ) 114 | } 115 | 116 | _persistState(path) { 117 | let lastState 118 | let storage = this._storage 119 | let { get } = this._util 120 | let handleChange = () => { 121 | let currentState = get(this.base.getState(), path) 122 | if (currentState !== lastState) { 123 | lastState = currentState 124 | // Write to storage 125 | if (storage && path) 126 | storage.setItem(this._namespace + path, JSON.stringify(currentState)) 127 | } 128 | } 129 | let unsubscribe = this.base.subscribe(handleChange) 130 | handleChange() 131 | return unsubscribe 132 | } 133 | 134 | getStore() { 135 | return this.base 136 | } 137 | 138 | getState() { 139 | return this.base.getState() 140 | } 141 | 142 | dispatch(action) { 143 | return this.base.dispatch(action) 144 | } 145 | 146 | replaceReducer(nextReducer) { 147 | return this.base.replaceReducer(nextReducer) 148 | } 149 | 150 | subscribe(listener) { 151 | return this.base.subscribe(listener) 152 | } 153 | 154 | static start(provider) { 155 | const store = provider.service('store') 156 | return store._createStore() 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reser 2 | 3 | An asynchronous DI framework for modular React and React-Native using [JService](https://github.com/rhaldkhein/jservice). 4 | 5 | **Key Features** 6 | 7 | - Dependency Injection 8 | - Asynchronous Service 9 | - Code Splitting 10 | - Persistent `redux` State 11 | - Abstraction for `react-redux` 12 | - For `react` and `react-native` 13 | 14 | ## Install 15 | 16 | ```sh 17 | npm install reser 18 | ``` 19 | 20 | ## Basic Usage 21 | 22 | Create project with `create-react-app` and register all your services in registry file. 23 | 24 | In file `registry.js`, you can add or configure services. There are also some built-in services you can configure like `store` (derived from redux), `storage` (derived from localstorage), etc. 25 | 26 | ```javascript 27 | import UserService from './services/user.js' 28 | // ... 29 | 30 | export default function (services) { 31 | 32 | services.add(UserService) 33 | services.add(BookingService) 34 | 35 | // Services with components 36 | services.add(MapService) 37 | services.add(SocialService) 38 | 39 | // Async services 40 | services.add(() => import('./services/myAsync'), 'async') 41 | 42 | // ... 43 | } 44 | ``` 45 | 46 | In file `App.js`, you have to wrap it with `withContainer` and pass registry and root component. 47 | 48 | ```javascript 49 | import React from 'react' 50 | import { withContainer } from 'reser' 51 | import registry from './registry.js' 52 | // ... 53 | 54 | import Home from './routes/Home' 55 | // ... 56 | 57 | function App({ container }) { 58 | return ( 59 | container.isReady && 60 |
61 | 62 |
63 |
    64 |
  • Home
  • 65 |
  • About
  • 66 |
  • Users
  • 67 |
68 |
69 |
70 | 71 | 72 | 73 |
74 |
75 |
76 | ) 77 | } 78 | 79 | // Inject root component with DI container 80 | export default withContainer(registry)(App) 81 | ``` 82 | 83 | Services are the basic building blocks for dependency injection. And here's the file `services/user.js` that depends on `store` service to get the state or dispatch an action. 84 | 85 | ```javascript 86 | const initial = { 87 | id: null, 88 | name: 'Unknown', 89 | age: 0 90 | } 91 | 92 | function reducer(state = initial, action) { 93 | // ... 94 | } 95 | 96 | class UserService { 97 | static service = 'user' 98 | static reducer = reducer 99 | static persist = true // Save state to local storage 100 | 101 | constructor(provider) { 102 | // Let's inject the built-in store service 103 | this.store = provider.service('store') 104 | // And http service to handle REST request 105 | this.http = provider.service('http') 106 | } 107 | 108 | getCurrentUser() { 109 | return this.store.getState().user.current 110 | } 111 | 112 | signIn(email, password) { 113 | return this.http.post('/signin', { email, password }) 114 | .then(user => { 115 | return this.store.dispatch({ 116 | type: 'SET_CURRENT_USER', 117 | user 118 | }) 119 | }) 120 | } 121 | 122 | // ... 123 | } 124 | ``` 125 | 126 | Then route component `routes/Home.js` that depends on `user` service. To inject service in component, use `withService`. 127 | 128 | ```javascript 129 | import React from 'react' 130 | import { withService, andState } from 'reser' 131 | 132 | class Home { 133 | 134 | state = { email: null, password: null } 135 | 136 | constructor(props) { 137 | super(props) 138 | // Create a reference to user service 139 | this.userService = props.services.user 140 | } 141 | 142 | signIn = e => { 143 | e.preventDefault() 144 | // If the sign-in succedded, component will update automatically 145 | this.userService.signIn(this.state.email, this.state.password) 146 | } 147 | 148 | render() { 149 | const currentUser = this.props.state.user 150 | // It will display `Hello, Foo!` or sign-in form 151 | return ( 152 |
153 | { 154 | currentUser.id ? 155 |

Hello, {currentUser.name}!

: 156 |
157 | 158 | 159 | 160 |
161 | } 162 |
163 | ) 164 | } 165 | } 166 | 167 | // Here we inject `user` service and its state. 168 | // Remove andState, if you only need the service. 169 | export default withService('user', andState())(Home) 170 | ``` 171 | 172 | ## License 173 | 174 | MIT 175 | 176 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect, Provider } from 'react-redux' 3 | import BaseBuilder from 'jservice' 4 | import ServiceCollection from './collection' 5 | import ServiceProvider from './provider' 6 | 7 | // Built-in services 8 | import UtilService from './services/util' 9 | import StorageService from './services/storage' 10 | import StoreService from './services/store' 11 | 12 | // DI container contexts 13 | const ContainerContext = React.createContext() 14 | const AsyncCountContext = React.createContext(0) 15 | 16 | class ReactServices extends BaseBuilder { 17 | 18 | constructor() { 19 | super() 20 | this.collection = new ServiceCollection(this) 21 | this.provider = new ServiceProvider(this.collection) 22 | } 23 | 24 | createProvider() { 25 | throw new Error('Scoped providers are not supported yet') 26 | } 27 | 28 | } 29 | 30 | function createContainer() { 31 | return new ReactServices() 32 | .build(services => { 33 | services.add(UtilService) 34 | services.add(StorageService) 35 | services.add(StoreService) 36 | }) 37 | } 38 | 39 | function mapStateToProps(serviceNames, state) { 40 | return serviceNames.reduce((result, name) => { 41 | const val = state[name] 42 | if (val) result[name] = val 43 | return result 44 | }, {}) 45 | } 46 | 47 | export function withContainer(registry) { 48 | return function (ChildComponent) { 49 | return class extends React.Component { 50 | constructor(props) { 51 | super(props) 52 | this.state = { count: 0 } 53 | this.container = createContainer().build(registry) 54 | this.container.start().then(() => this.forceUpdate()) 55 | this.contextValue = { 56 | container: this.container, 57 | asyncLoaded: () => { 58 | this.setState({ count: this.state.count + 1 }) 59 | } 60 | } 61 | } 62 | render() { 63 | const store = this.container.provider.service('store').getStore() 64 | const childElement = React.createElement(ChildComponent, { 65 | container: this.container, 66 | ...this.props 67 | }) 68 | return React.createElement( 69 | ContainerContext.Provider, 70 | { value: this.contextValue }, 71 | React.createElement( 72 | AsyncCountContext.Provider, 73 | { value: this.state.count }, 74 | !store ? childElement : 75 | React.createElement( 76 | Provider, 77 | { store }, 78 | childElement 79 | )) 80 | ) 81 | } 82 | } 83 | } 84 | } 85 | 86 | export function withNewService(registry) { 87 | return function (ChildComponent) { 88 | return function (props) { 89 | return React.createElement(ContainerContext.Consumer, null, 90 | function (context) { 91 | context.container.build(registry) 92 | return React.createElement( 93 | ChildComponent, 94 | { container: context.container }, 95 | props.children 96 | ) 97 | } 98 | ) 99 | } 100 | } 101 | } 102 | 103 | export function withService(...serviceNames) { 104 | return function (ChildComponent) { 105 | if (Array.isArray(serviceNames[serviceNames.length - 1])) { 106 | const states = serviceNames.pop() 107 | ChildComponent = withState.apply(null, 108 | states.length ? states : serviceNames)(ChildComponent) 109 | } 110 | return function (props) { 111 | return React.createElement(ContainerContext.Consumer, null, 112 | function (context) { 113 | return React.createElement(AsyncCountContext.Consumer, null, 114 | function () { 115 | return React.createElement(ChildComponent, { 116 | services: context.container.provider.createServices( 117 | serviceNames, 118 | context.asyncLoaded 119 | ), 120 | ...props 121 | }) 122 | } 123 | ) 124 | } 125 | ) 126 | } 127 | } 128 | } 129 | 130 | export function withState(...serviceNames) { 131 | return function (ChildComponent) { 132 | if (Array.isArray(serviceNames[serviceNames.length - 1])) { 133 | const services = serviceNames.pop() 134 | ChildComponent = withService.apply(null, 135 | services.length ? services : serviceNames)(ChildComponent) 136 | } 137 | return connect( 138 | state => ({ 139 | state: typeof serviceNames[0] === 'function' ? 140 | serviceNames[0](state) : 141 | mapStateToProps(serviceNames, state) 142 | }) 143 | )(ChildComponent) 144 | } 145 | } 146 | 147 | export function and(...serviceNames) { 148 | return Array.isArray(serviceNames[0]) ? serviceNames[0] : serviceNames 149 | } 150 | 151 | export { 152 | // HOC 153 | createContainer, 154 | and as andService, 155 | and as andState, 156 | // Services 157 | StorageService, 158 | StoreService, 159 | UtilService 160 | } 161 | -------------------------------------------------------------------------------- /sample/basic/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /sample/basic/src/reser/collection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _jservice = require("jservice"); 9 | 10 | var _proto = require("./services/util/proto"); 11 | 12 | var _loader = _interopRequireDefault(require("./loader")); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 15 | 16 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 17 | 18 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 19 | 20 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 21 | 22 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 23 | 24 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 25 | 26 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 27 | 28 | function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } 29 | 30 | function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } 31 | 32 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 33 | 34 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 35 | 36 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 37 | 38 | var ServiceCollection = 39 | /*#__PURE__*/ 40 | function (_BaseServiceCollectio) { 41 | _inherits(ServiceCollection, _BaseServiceCollectio); 42 | 43 | function ServiceCollection(core) { 44 | var _this; 45 | 46 | _classCallCheck(this, ServiceCollection); 47 | 48 | _this = _possibleConstructorReturn(this, _getPrototypeOf(ServiceCollection).call(this, core)); 49 | _this._core = core; 50 | return _this; 51 | } 52 | 53 | _createClass(ServiceCollection, [{ 54 | key: "_validate", 55 | value: function _validate(service) { 56 | var err = null; 57 | if (service.async) err = 'async'; // Add more static validation 58 | 59 | if (err) throw new Error("Invalid service signature \"".concat(err, "\"")); 60 | } 61 | }, { 62 | key: "_push", 63 | value: function _push(Service, name, config, skip) { 64 | this._validate(Service); // Check if service is async or not 65 | 66 | 67 | if ((0, _proto.isFunction)(Service) && !(0, _proto.isConstructor)(Service)) { 68 | // Async service 69 | Service.service = name; 70 | var loader = new _loader["default"](this._core.provider, Service, name, config); 71 | 72 | var LoaderService = function LoaderService() { 73 | return loader; 74 | }; 75 | 76 | LoaderService.type = Service.type; 77 | LoaderService.service = name; 78 | LoaderService.async = true; 79 | Service = LoaderService; 80 | } 81 | 82 | _get(_getPrototypeOf(ServiceCollection.prototype), "_push", this).call(this, Service, name, config, skip); 83 | } 84 | }, { 85 | key: "isAsyncService", 86 | value: function isAsyncService(name) { 87 | var names = this.names, 88 | services = this.services; 89 | 90 | for (var key in names) { 91 | if (names.hasOwnProperty(key) && key === name) { 92 | return services[names[key]].async || false; 93 | } 94 | } 95 | 96 | return true; 97 | } 98 | }, { 99 | key: "scoped", 100 | value: function scoped() { 101 | throw new Error('Scoped services are not supported yet'); 102 | } 103 | }, { 104 | key: "transient", 105 | value: function transient() { 106 | throw new Error('Transient services are not supported yet'); 107 | } 108 | }]); 109 | 110 | return ServiceCollection; 111 | }(_jservice.ServiceCollection); 112 | 113 | var _default = ServiceCollection; 114 | exports["default"] = _default; -------------------------------------------------------------------------------- /sample/basic/src/reser/services/store.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _redux = require("redux"); 9 | 10 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { keys.push.apply(keys, Object.getOwnPropertySymbols(object)); } if (enumerableOnly) keys = keys.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); return keys; } 11 | 12 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } 13 | 14 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 15 | 16 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 17 | 18 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 19 | 20 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 21 | 22 | var Store = 23 | /*#__PURE__*/ 24 | function () { 25 | function Store(provider, config) { 26 | _classCallCheck(this, Store); 27 | 28 | _defineProperty(this, "base", null); 29 | 30 | _defineProperty(this, "_namespace", 'rsrx:'); 31 | 32 | _defineProperty(this, "_collection", null); 33 | 34 | _defineProperty(this, "_config", null); 35 | 36 | _defineProperty(this, "_reducers", null); 37 | 38 | _defineProperty(this, "_storage", null); 39 | 40 | _defineProperty(this, "_util", null); 41 | 42 | this._config = config; 43 | this._storage = provider.service('storage'); 44 | this._util = provider.service('util'); 45 | this._collection = provider.service('__core__').collection; 46 | var _this$_collection = this._collection, 47 | names = _this$_collection.names, 48 | services = _this$_collection.services; 49 | var reducers = {}; 50 | 51 | for (var name in names) { 52 | if (names.hasOwnProperty(name)) { 53 | var service = services[names[name]]; 54 | if (service.reducer) reducers[name] = service.reducer; 55 | } 56 | } 57 | 58 | this._reducers = reducers; 59 | } 60 | 61 | _createClass(Store, [{ 62 | key: "_defaultConfig", 63 | value: function _defaultConfig(arg) { 64 | return { 65 | store: (0, _redux.createStore)(arg.rootReducer || function (s) { 66 | return s; 67 | }, arg.initialState || {}) 68 | }; 69 | } 70 | }, { 71 | key: "_getInitialState", 72 | value: function _getInitialState() { 73 | var _this = this; 74 | 75 | var state = {}; 76 | var storage = this._storage; 77 | 78 | if (storage) { 79 | return storage.getAllKeys().then(function (keys) { 80 | var toGet = []; 81 | keys.sort(function (a, b) { 82 | return a.length - b.length; 83 | }); 84 | keys.forEach(function (key) { 85 | if (!key.startsWith(_this._namespace)) return; 86 | var path = key.split(':')[1]; // Async service should not be added in initial state 87 | 88 | toGet.push(storage.getItem(key).then(function (value) { 89 | _this._util.set(state, path, JSON.parse(value)); 90 | })); 91 | }); 92 | return Promise.all(toGet); 93 | }).then(function () { 94 | return state; 95 | }); 96 | } 97 | 98 | return Promise.resolve({}); 99 | } 100 | }, { 101 | key: "_createStore", 102 | value: function _createStore() { 103 | var _this2 = this; 104 | 105 | return Promise.resolve().then(function () { 106 | return _this2._getInitialState(); 107 | }).then(function (state) { 108 | var arg = { 109 | rootReducer: (0, _redux.combineReducers)(_this2._reducers), 110 | initialState: state 111 | }; 112 | var result = typeof _this2._config === 'function' ? _this2._config(arg) : _this2._defaultConfig(arg); 113 | 114 | _this2._setStore(result.store); 115 | }); 116 | } 117 | }, { 118 | key: "_setStore", 119 | value: function _setStore(store) { 120 | this.base = store; 121 | var _this$_collection2 = this._collection, 122 | names = _this$_collection2.names, 123 | services = _this$_collection2.services; 124 | 125 | for (var name in names) { 126 | if (!names.hasOwnProperty(name)) continue; 127 | var service = services[names[name]]; 128 | 129 | if (service.persist) { 130 | this._persistService(service, name); 131 | } 132 | } 133 | } 134 | }, { 135 | key: "_persistService", 136 | value: function _persistService(service, name) { 137 | var _this3 = this; 138 | 139 | if (service.persist === undefined) return; 140 | if (!name) throw new Error('Persistent service must have name'); // Write to storage 141 | 142 | if (service.persist === true) { 143 | // Persist whole service 144 | this._persistState(name); 145 | } else { 146 | // Should be array, persist by keys 147 | service.persist.forEach(function (path) { 148 | _this3._persistState(name + '.' + path); 149 | }); 150 | } // Mutate reducer 151 | 152 | 153 | this.base.replaceReducer((0, _redux.combineReducers)(_objectSpread({}, this._reducers, _defineProperty({}, name, service.reducer)))); 154 | } 155 | }, { 156 | key: "_persistState", 157 | value: function _persistState(path) { 158 | var _this4 = this; 159 | 160 | var lastState; 161 | var storage = this._storage; 162 | var get = this._util.get; 163 | 164 | var handleChange = function handleChange() { 165 | var currentState = get(_this4.base.getState(), path); 166 | 167 | if (currentState !== lastState) { 168 | lastState = currentState; // Write to storage 169 | 170 | if (storage && path) storage.setItem(_this4._namespace + path, JSON.stringify(currentState)); 171 | } 172 | }; 173 | 174 | var unsubscribe = this.base.subscribe(handleChange); 175 | handleChange(); 176 | return unsubscribe; 177 | } 178 | }, { 179 | key: "getStore", 180 | value: function getStore() { 181 | return this.base; 182 | } 183 | }, { 184 | key: "getState", 185 | value: function getState() { 186 | return this.base.getState(); 187 | } 188 | }, { 189 | key: "dispatch", 190 | value: function dispatch(action) { 191 | return this.base.dispatch(action); 192 | } 193 | }, { 194 | key: "replaceReducer", 195 | value: function replaceReducer(nextReducer) { 196 | return this.base.replaceReducer(nextReducer); 197 | } 198 | }, { 199 | key: "subscribe", 200 | value: function subscribe(listener) { 201 | return this.base.subscribe(listener); 202 | } 203 | }], [{ 204 | key: "start", 205 | value: function start(provider) { 206 | var store = provider.service('store'); 207 | return store._createStore(); 208 | } 209 | }]); 210 | 211 | return Store; 212 | }(); 213 | 214 | exports["default"] = Store; 215 | 216 | _defineProperty(Store, "service", 'store'); -------------------------------------------------------------------------------- /sample/basic/src/reser/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.withContainer = withContainer; 7 | exports.withNewService = withNewService; 8 | exports.withService = withService; 9 | exports.withState = withState; 10 | exports.andState = exports.andService = exports.and = and; 11 | exports.createContainer = createContainer; 12 | Object.defineProperty(exports, "UtilService", { 13 | enumerable: true, 14 | get: function get() { 15 | return _util["default"]; 16 | } 17 | }); 18 | Object.defineProperty(exports, "StorageService", { 19 | enumerable: true, 20 | get: function get() { 21 | return _storage["default"]; 22 | } 23 | }); 24 | Object.defineProperty(exports, "StoreService", { 25 | enumerable: true, 26 | get: function get() { 27 | return _store["default"]; 28 | } 29 | }); 30 | 31 | var _react = _interopRequireDefault(require("react")); 32 | 33 | var _reactRedux = require("react-redux"); 34 | 35 | var _jservice = _interopRequireDefault(require("jservice")); 36 | 37 | var _collection = _interopRequireDefault(require("./collection")); 38 | 39 | var _provider = _interopRequireDefault(require("./provider")); 40 | 41 | var _util = _interopRequireDefault(require("./services/util")); 42 | 43 | var _storage = _interopRequireDefault(require("./services/storage")); 44 | 45 | var _store = _interopRequireDefault(require("./services/store")); 46 | 47 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 48 | 49 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { keys.push.apply(keys, Object.getOwnPropertySymbols(object)); } if (enumerableOnly) keys = keys.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); return keys; } 50 | 51 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } 52 | 53 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 54 | 55 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 56 | 57 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 58 | 59 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 60 | 61 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 62 | 63 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 64 | 65 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 66 | 67 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 68 | 69 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 70 | 71 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 72 | 73 | // DI container contexts 74 | var ContainerContext = _react["default"].createContext(); 75 | 76 | var AsyncCountContext = _react["default"].createContext(0); 77 | 78 | var ReactServices = 79 | /*#__PURE__*/ 80 | function (_BaseBuilder) { 81 | _inherits(ReactServices, _BaseBuilder); 82 | 83 | function ReactServices() { 84 | var _this; 85 | 86 | _classCallCheck(this, ReactServices); 87 | 88 | _this = _possibleConstructorReturn(this, _getPrototypeOf(ReactServices).call(this)); 89 | _this.collection = new _collection["default"](_assertThisInitialized(_this)); 90 | _this.provider = new _provider["default"](_this.collection); 91 | return _this; 92 | } 93 | 94 | _createClass(ReactServices, [{ 95 | key: "createProvider", 96 | value: function createProvider() { 97 | throw new Error('Scoped providers are not supported yet'); 98 | } 99 | }]); 100 | 101 | return ReactServices; 102 | }(_jservice["default"]); 103 | 104 | function createContainer() { 105 | return new ReactServices().build(function (services) { 106 | services.add(_util["default"]); 107 | services.add(_storage["default"]); 108 | services.add(_store["default"]); 109 | }); 110 | } 111 | 112 | function mapStateToProps(serviceNames, state) { 113 | return serviceNames.reduce(function (result, name) { 114 | var val = state[name]; 115 | if (val) result[name] = val; 116 | return result; 117 | }, {}); 118 | } 119 | 120 | function withContainer(registry) { 121 | return function (ChildComponent) { 122 | return ( 123 | /*#__PURE__*/ 124 | function (_React$Component) { 125 | _inherits(_class, _React$Component); 126 | 127 | function _class(props) { 128 | var _this2; 129 | 130 | _classCallCheck(this, _class); 131 | 132 | _this2 = _possibleConstructorReturn(this, _getPrototypeOf(_class).call(this, props)); 133 | _this2.state = { 134 | count: 0 135 | }; 136 | _this2.container = createContainer().build(registry); 137 | 138 | _this2.container.start().then(function () { 139 | return _this2.forceUpdate(); 140 | }); 141 | 142 | _this2.contextValue = { 143 | container: _this2.container, 144 | asyncLoaded: function asyncLoaded() { 145 | _this2.setState({ 146 | count: _this2.state.count + 1 147 | }); 148 | } 149 | }; 150 | return _this2; 151 | } 152 | 153 | _createClass(_class, [{ 154 | key: "render", 155 | value: function render() { 156 | var store = this.container.provider.service('store').getStore(); 157 | 158 | var childElement = _react["default"].createElement(ChildComponent, _objectSpread({ 159 | container: this.container 160 | }, this.props)); 161 | 162 | return _react["default"].createElement(ContainerContext.Provider, { 163 | value: this.contextValue 164 | }, _react["default"].createElement(AsyncCountContext.Provider, { 165 | value: this.state.count 166 | }, !store ? childElement : _react["default"].createElement(_reactRedux.Provider, { 167 | store: store 168 | }, childElement))); 169 | } 170 | }]); 171 | 172 | return _class; 173 | }(_react["default"].Component) 174 | ); 175 | }; 176 | } 177 | 178 | function withNewService(registry) { 179 | return function (ChildComponent) { 180 | return function (props) { 181 | return _react["default"].createElement(ContainerContext.Consumer, null, function (context) { 182 | context.container.build(registry); 183 | return _react["default"].createElement(ChildComponent, { 184 | container: context.container 185 | }, props.children); 186 | }); 187 | }; 188 | }; 189 | } 190 | 191 | function withService() { 192 | for (var _len = arguments.length, serviceNames = new Array(_len), _key = 0; _key < _len; _key++) { 193 | serviceNames[_key] = arguments[_key]; 194 | } 195 | 196 | return function (ChildComponent) { 197 | if (Array.isArray(serviceNames[serviceNames.length - 1])) { 198 | var states = serviceNames.pop(); 199 | ChildComponent = withState.apply(null, states.length ? states : serviceNames)(ChildComponent); 200 | } 201 | 202 | return function (props) { 203 | return _react["default"].createElement(ContainerContext.Consumer, null, function (context) { 204 | return _react["default"].createElement(AsyncCountContext.Consumer, null, function () { 205 | return _react["default"].createElement(ChildComponent, _objectSpread({ 206 | services: context.container.provider.createServices(serviceNames, context.asyncLoaded) 207 | }, props)); 208 | }); 209 | }); 210 | }; 211 | }; 212 | } 213 | 214 | function withState() { 215 | for (var _len2 = arguments.length, serviceNames = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 216 | serviceNames[_key2] = arguments[_key2]; 217 | } 218 | 219 | return function (ChildComponent) { 220 | if (Array.isArray(serviceNames[serviceNames.length - 1])) { 221 | var services = serviceNames.pop(); 222 | ChildComponent = withService.apply(null, services.length ? services : serviceNames)(ChildComponent); 223 | } 224 | 225 | return (0, _reactRedux.connect)(function (state) { 226 | return { 227 | state: typeof serviceNames[0] === 'function' ? serviceNames[0](state) : mapStateToProps(serviceNames, state) 228 | }; 229 | })(ChildComponent); 230 | }; 231 | } 232 | 233 | function and() { 234 | for (var _len3 = arguments.length, serviceNames = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 235 | serviceNames[_key3] = arguments[_key3]; 236 | } 237 | 238 | return Array.isArray(serviceNames[0]) ? serviceNames[0] : serviceNames; 239 | } --------------------------------------------------------------------------------