├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── examples ├── counter.tsx ├── gifs.tsx ├── greet.js ├── greet.jsx └── greet.tsx ├── package-lock.json ├── package.json ├── src ├── cmd.ts ├── cmds │ └── trigger.ts ├── component.ts ├── dispatcher.ts └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /examples 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 James Birtles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fpreact (Functional Preact) 2 | fpreact provides an alternative api for creating preact components, heavily inspired by elm. The api includes redux style state management and lends itself to functional programming, avoiding the use of `this` 3 | 4 | It has first class TypeScript support (it's written in it!), but also works great with regular JavaScript. 5 | 6 | ## Install 7 | ``` 8 | npm i -S fpreact preact 9 | ``` 10 | 11 | ## Example 12 | Find more in the `examples` folder. 13 | 14 | ### TypeScript 15 | ```tsx 16 | import { h, render, component, Message } from 'fpreact'; 17 | 18 | enum Msg { 19 | UpdateName, 20 | } 21 | 22 | interface Model { 23 | name: string; 24 | } 25 | 26 | type Messages = Message; 27 | 28 | const Greet = component({ 29 | update(model = { name: 'world' }, msg) { 30 | switch (msg.kind) { 31 | case Msg.UpdateName: 32 | return { ...model, name: msg.value }; 33 | } 34 | 35 | return model; 36 | }, 37 | 38 | view(model, dispatch) { 39 | return ( 40 |
41 |

Hello, {model.name}

42 | 46 |
47 | ); 48 | }, 49 | }); 50 | 51 | render(, document.body); 52 | ``` 53 | 54 | ### JavaScript (ES6 + JSX) 55 | ```jsx 56 | import { h, render, component } from 'fpreact'; 57 | 58 | const Msg = { 59 | // You don't have to use a number here. 60 | // You could just as easily use "UPDATE_NAME" or anything else if you desire, 61 | // just make sure each item has a unique value 62 | UpdateName: 0, 63 | }; 64 | 65 | const Greet = component({ 66 | update(model = { name: 'world' }, msg) { 67 | switch (msg.kind) { 68 | case Msg.UpdateName: 69 | return { ...model, name: msg.value }; 70 | } 71 | 72 | return model; 73 | }, 74 | 75 | view(model, dispatch) { 76 | return ( 77 |
78 |

Hello, {model.name}

79 | 83 |
84 | ); 85 | }, 86 | }); 87 | 88 | render(, document.body); 89 | ``` 90 | 91 | ### JavaScript (ES5) 92 | ```js 93 | 'use strict'; 94 | 95 | var fpreact = require('fpreact'); 96 | 97 | var Msg = { 98 | UpdateName: 0, 99 | }; 100 | 101 | var Greet = fpreact.component({ 102 | update: function(model, msg) { 103 | if (model == null) { 104 | return { name: 'world' }; 105 | } 106 | 107 | switch (msg.kind) { 108 | case Msg.UpdateName: 109 | return Object.assign({}, model, { name: msg.value }); 110 | } 111 | 112 | return model; 113 | }, 114 | 115 | view: function(model, dispatch) { 116 | return fpreact.h( 117 | 'div', 118 | null, 119 | fpreact.h('h1', null, 'Hello, ', model.name), 120 | fpreact.h( 121 | 'label', 122 | null, 123 | fpreact.h('span', null, 'Name:'), 124 | fpreact.h('input', { value: model.name, onInput: dispatch(Msg.UpdateName) }), 125 | ), 126 | ); 127 | }, 128 | }); 129 | 130 | fpreact.render(fpreact.h(Greet, null), document.body); 131 | ``` 132 | -------------------------------------------------------------------------------- /examples/counter.tsx: -------------------------------------------------------------------------------- 1 | import { h, render, component, Dispatcher, Message } from '..'; 2 | 3 | enum Msg { 4 | Increment, 5 | Decrement, 6 | SetCounter, 7 | StartInterval, 8 | StopInterval, 9 | TrackInterval, 10 | Reset, 11 | } 12 | 13 | type Messages = 14 | | Message 15 | | Message 16 | | Message 17 | | Message 18 | | Message 19 | | Message 20 | | Message; 21 | 22 | interface Model { 23 | counter: number; 24 | interval?: number; 25 | } 26 | 27 | function startInterval(dispatch: Dispatcher) { 28 | dispatch(Msg.TrackInterval)(setInterval(dispatch(Msg.Increment), 1000)); 29 | } 30 | 31 | function stopInterval(interval: number) { 32 | return (dispatch: Dispatcher) => { 33 | clearInterval(interval); 34 | dispatch(Msg.TrackInterval)(null); 35 | }; 36 | } 37 | 38 | const Counter = component({ 39 | props(props, dispatch) { 40 | dispatch(Msg.SetCounter)(props.value); 41 | }, 42 | 43 | update(model = { counter: 0 }, msg) { 44 | switch (msg.kind) { 45 | case Msg.Increment: 46 | return { ...model, counter: model.counter + 1 }; 47 | case Msg.Decrement: 48 | return { ...model, counter: model.counter - 1 }; 49 | case Msg.SetCounter: 50 | return { ...model, counter: msg.value }; 51 | case Msg.Reset: 52 | return { ...model, counter: 0 }; 53 | case Msg.StartInterval: 54 | if (model.interval != null) { 55 | return model; 56 | } 57 | 58 | return [model, startInterval]; 59 | case Msg.StopInterval: 60 | if (model.interval == null) { 61 | return model; 62 | } 63 | 64 | return [model, stopInterval(model.interval)]; 65 | case Msg.TrackInterval: 66 | return { ...model, interval: msg.value }; 67 | } 68 | 69 | return model; 70 | }, 71 | 72 | view(model, dispatch) { 73 | return ( 74 |
75 | {model.counter} 76 | 77 | 78 | 79 | 82 | 85 |
86 | ); 87 | }, 88 | }); 89 | 90 | render(, document.body); 91 | -------------------------------------------------------------------------------- /examples/gifs.tsx: -------------------------------------------------------------------------------- 1 | import { h, component, render, Dispatcher, Message } from '..'; 2 | 3 | enum Msg { 4 | MorePlease, 5 | NewGif, 6 | UpdateTopic, 7 | } 8 | 9 | interface Model { 10 | topic: string; 11 | gifUrl: string; 12 | error: string; 13 | } 14 | 15 | type Messages = 16 | | Message 17 | | Message 18 | | Message; 19 | 20 | function getRandomGif(topic: string) { 21 | return (dispatch: Dispatcher) => { 22 | fetch('https://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=' + topic) 23 | .then(res => { 24 | if (res.status !== 200) { 25 | throw new Error(`Unexpected response from server: ${res.status}`); 26 | } 27 | 28 | return res.json(); 29 | }) 30 | .then(res => dispatch(Msg.NewGif)(res.data.image_url)) 31 | .catch(err => dispatch(Msg.NewGif)(null, err)); 32 | }; 33 | } 34 | 35 | const initialModel: Model = { 36 | topic: 'cats', 37 | gifUrl: '', 38 | error: '', 39 | }; 40 | 41 | const RandomGifs = component({ 42 | init(model, dispatch) { 43 | dispatch(Msg.MorePlease)(); 44 | }, 45 | 46 | update(model = initialModel, msg) { 47 | switch (msg.kind) { 48 | case Msg.MorePlease: 49 | return [model, getRandomGif(model.topic)]; 50 | case Msg.NewGif: 51 | if (msg.error) { 52 | return { ...model, error: msg.error.message }; 53 | } 54 | 55 | return { ...model, gifUrl: msg.value, error: '' }; 56 | case Msg.UpdateTopic: 57 | return { ...model, topic: msg.value }; 58 | } 59 | 60 | return model; 61 | }, 62 | 63 | view(model, dispatch) { 64 | return ( 65 |
66 |

{model.topic}

67 | 68 | 69 |
70 | {model.error &&

An error has occurred: {model.error}

} 71 | 72 |
73 | ); 74 | }, 75 | }); 76 | 77 | render(, document.body); 78 | -------------------------------------------------------------------------------- /examples/greet.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fpreact = require('..'); 4 | 5 | var Msg = { 6 | UpdateName: 0, 7 | }; 8 | 9 | var Greet = fpreact.component({ 10 | update: function(model, msg) { 11 | if (model == null) { 12 | return { name: 'world' }; 13 | } 14 | 15 | switch (msg.kind) { 16 | case Msg.UpdateName: 17 | return Object.assign({}, model, { name: msg.value }); 18 | } 19 | 20 | return model; 21 | }, 22 | 23 | view: function(model, dispatch) { 24 | return fpreact.h( 25 | 'div', 26 | null, 27 | fpreact.h('h1', null, 'Hello, ', model.name), 28 | fpreact.h( 29 | 'label', 30 | null, 31 | fpreact.h('span', null, 'Name:'), 32 | fpreact.h('input', { value: model.name, onInput: dispatch(Msg.UpdateName) }), 33 | ), 34 | ); 35 | }, 36 | }); 37 | 38 | fpreact.render(fpreact.h(Greet, null), document.body); 39 | -------------------------------------------------------------------------------- /examples/greet.jsx: -------------------------------------------------------------------------------- 1 | import { h, render, component } from '..'; 2 | 3 | const Msg = { 4 | // You don't have to use a number here. 5 | // You could just as easily use "UPDATE_NAME" or anything else if you desire, 6 | // just make sure each item has a unique value 7 | UpdateName: 0, 8 | }; 9 | 10 | const Greet = component({ 11 | update(model = { name: 'world' }, msg) { 12 | switch (msg.kind) { 13 | case Msg.UpdateName: 14 | return { ...model, name: msg.value }; 15 | } 16 | 17 | return model; 18 | }, 19 | 20 | view(model, dispatch) { 21 | return ( 22 |
23 |

Hello, {model.name}

24 | 28 |
29 | ); 30 | }, 31 | }); 32 | 33 | render(, document.body); 34 | -------------------------------------------------------------------------------- /examples/greet.tsx: -------------------------------------------------------------------------------- 1 | import { h, render, component, Message } from '..'; 2 | 3 | enum Msg { 4 | UpdateName, 5 | } 6 | 7 | interface Model { 8 | name: string; 9 | } 10 | 11 | type Messages = Message; 12 | 13 | const Greet = component({ 14 | update(model = { name: 'world' }, msg) { 15 | switch (msg.kind) { 16 | case Msg.UpdateName: 17 | return { ...model, name: msg.value }; 18 | } 19 | 20 | return model; 21 | }, 22 | 23 | view(model, dispatch) { 24 | return ( 25 |
26 |

Hello, {model.name}

27 | 31 |
32 | ); 33 | }, 34 | }); 35 | 36 | render(, document.body); 37 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fpreact", 3 | "version": "0.2.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 11 | "dev": true 12 | }, 13 | "brace-expansion": { 14 | "version": "1.1.8", 15 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 16 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 17 | "dev": true, 18 | "requires": { 19 | "balanced-match": "1.0.0", 20 | "concat-map": "0.0.1" 21 | } 22 | }, 23 | "concat-map": { 24 | "version": "0.0.1", 25 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 26 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 27 | "dev": true 28 | }, 29 | "fs.realpath": { 30 | "version": "1.0.0", 31 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 32 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 33 | "dev": true 34 | }, 35 | "glob": { 36 | "version": "7.1.2", 37 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 38 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 39 | "dev": true, 40 | "requires": { 41 | "fs.realpath": "1.0.0", 42 | "inflight": "1.0.6", 43 | "inherits": "2.0.3", 44 | "minimatch": "3.0.4", 45 | "once": "1.4.0", 46 | "path-is-absolute": "1.0.1" 47 | } 48 | }, 49 | "inflight": { 50 | "version": "1.0.6", 51 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 52 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 53 | "dev": true, 54 | "requires": { 55 | "once": "1.4.0", 56 | "wrappy": "1.0.2" 57 | } 58 | }, 59 | "inherits": { 60 | "version": "2.0.3", 61 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 62 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 63 | "dev": true 64 | }, 65 | "minimatch": { 66 | "version": "3.0.4", 67 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 68 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 69 | "dev": true, 70 | "requires": { 71 | "brace-expansion": "1.1.8" 72 | } 73 | }, 74 | "once": { 75 | "version": "1.4.0", 76 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 77 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 78 | "dev": true, 79 | "requires": { 80 | "wrappy": "1.0.2" 81 | } 82 | }, 83 | "path-is-absolute": { 84 | "version": "1.0.1", 85 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 86 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 87 | "dev": true 88 | }, 89 | "preact": { 90 | "version": "8.2.5", 91 | "resolved": "https://registry.npmjs.org/preact/-/preact-8.2.5.tgz", 92 | "integrity": "sha1-y/o5YqgBJ2gVn20B1G+cHrMhPAo=", 93 | "dev": true 94 | }, 95 | "rimraf": { 96 | "version": "2.6.2", 97 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 98 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 99 | "dev": true, 100 | "requires": { 101 | "glob": "7.1.2" 102 | } 103 | }, 104 | "typescript": { 105 | "version": "2.5.3", 106 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz", 107 | "integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==", 108 | "dev": true 109 | }, 110 | "wrappy": { 111 | "version": "1.0.2", 112 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 113 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 114 | "dev": true 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fpreact", 3 | "version": "0.2.1", 4 | "description": "fpreact provides an alternative api for creating preact components, heavily inspired by elm.", 5 | "main": "dist/index.js", 6 | "typings": "dist/index", 7 | "scripts": { 8 | "build": "rimraf dist && tsc", 9 | "prepublishOnly": "npm run build" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/UnwrittenFun/fpreact.git" 14 | }, 15 | "keywords": [ 16 | "preact", 17 | "react" 18 | ], 19 | "author": "James Birtles ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/UnwrittenFun/fpreact/issues" 23 | }, 24 | "homepage": "https://github.com/UnwrittenFun/fpreact#readme", 25 | "peerDependencies": { 26 | "preact": "^8.2.5" 27 | }, 28 | "devDependencies": { 29 | "preact": "^8.2.5", 30 | "rimraf": "^2.6.2", 31 | "typescript": "^2.5.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/cmd.ts: -------------------------------------------------------------------------------- 1 | import { Dispatcher } from './dispatcher'; 2 | import { Component } from 'preact'; 3 | 4 | export type Cmd = (this: Component, dispatch: Dispatcher) => void; 5 | 6 | export function isCmdDispatch(v: any): v is [Model, Cmd] { 7 | return Array.isArray(v) && v.length === 2 && typeof v[1] === 'function'; 8 | } 9 | -------------------------------------------------------------------------------- /src/cmds/trigger.ts: -------------------------------------------------------------------------------- 1 | import { Component } from 'preact'; 2 | 3 | function upperFirst(s: string): string { 4 | return s.charAt(0).toUpperCase() + s.slice(1); 5 | } 6 | 7 | export function trigger(evt: string, ...args: any[]) { 8 | return function(this: Component) { 9 | const eventHandler = this.props[`on${upperFirst(evt)}`]; 10 | 11 | if (eventHandler != null) { 12 | eventHandler(...args); 13 | } 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ComponentConstructor } from 'preact'; 2 | import { Dispatcher } from './dispatcher'; 3 | import { Cmd, isCmdDispatch } from './cmd'; 4 | 5 | export interface Message { 6 | kind: K; 7 | value: V; 8 | error: E; 9 | } 10 | 11 | export interface ComponentDefinition, Props> { 12 | update(model: Model | undefined, msg: Msg): Model | [Model, Cmd]; 13 | view(model: Model, dispatch: Dispatcher): JSX.Element | null; 14 | 15 | init?(model: Model, dispatch: Dispatcher): void; 16 | props?(props: Props, dispatch: Dispatcher): void; 17 | } 18 | 19 | const noop = () => null; 20 | 21 | function runUpdate>( 22 | comp: ComponentDefinition, 23 | model: Model, 24 | msg: Msg, 25 | ): { model: Model; cmd: Cmd } { 26 | const res = comp.update(model, msg); 27 | let newModel: Model; 28 | let cmd: Cmd = noop; 29 | if (isCmdDispatch(res)) { 30 | newModel = res[0]; 31 | cmd = res[1]; 32 | } else { 33 | newModel = res; 34 | } 35 | 36 | return { model: newModel, cmd }; 37 | } 38 | 39 | export function component, Props = {}>( 40 | comp: ComponentDefinition, 41 | ): ComponentConstructor { 42 | return class extends Component { 43 | dispatchers = new Map void>(); 44 | 45 | constructor(...args: any[]) { 46 | super(...args); 47 | 48 | const update = runUpdate(comp, undefined, { 49 | kind: -1, 50 | value: undefined, 51 | error: undefined, 52 | } as any); 53 | 54 | // Clone the initial state onto the state object 55 | // to avoid preact from mutating the initial state 56 | // on later calls to setState 57 | this.state = { ...(update.model as any) }; 58 | update.cmd.call(this, this.dispatch); 59 | } 60 | 61 | componentWillMount() { 62 | if (comp.props != null) { 63 | comp.props(this.props, this.dispatch); 64 | } 65 | } 66 | 67 | componentDidMount() { 68 | if (comp.init != null) { 69 | comp.init(this.state, this.dispatch); 70 | } 71 | } 72 | 73 | componentWillReceiveProps(props: Props) { 74 | // TODO: only if changed? 75 | if (comp.props != null) { 76 | comp.props(props, this.dispatch); 77 | } 78 | } 79 | 80 | dispatch = (kind: Msg['kind']) => { 81 | let dispatcher = this.dispatchers.get(kind); 82 | if (dispatcher == null) { 83 | dispatcher = (value: any, err: Error) => { 84 | let msg = { kind, value, error: err }; 85 | if (value instanceof Event && value.target instanceof HTMLInputElement) { 86 | switch (value.target.type) { 87 | case 'checkbox': 88 | msg = { ...msg, value: value.target.checked }; 89 | break; 90 | default: 91 | msg = { ...msg, value: value.target.value }; 92 | break; 93 | } 94 | } 95 | 96 | const update = runUpdate(comp, this.state, msg as any); 97 | this.replaceState(update.model); 98 | update.cmd.call(this, this.dispatch); 99 | }; 100 | this.dispatchers.set(kind, dispatcher); 101 | } 102 | 103 | return dispatcher; 104 | }; 105 | 106 | // Copied from preact-compat 107 | replaceState(state: Model, callback?: () => void) { 108 | this.setState(state, callback); 109 | for (let i in this.state) { 110 | if (!(i in state)) { 111 | delete this.state[i]; 112 | } 113 | } 114 | } 115 | 116 | render() { 117 | return comp.view(this.state, this.dispatch); 118 | } 119 | }; 120 | } 121 | -------------------------------------------------------------------------------- /src/dispatcher.ts: -------------------------------------------------------------------------------- 1 | export type Dispatcher = (kind: K) => (value?: any, err?: Error) => void; 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { h, render } from 'preact'; 2 | 3 | export { Cmd } from './cmd'; 4 | export { component, ComponentDefinition, Message } from './component'; 5 | export { Dispatcher } from './dispatcher'; 6 | 7 | export { trigger } from './cmds/trigger'; 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "es2015", 5 | "jsx": "react", 6 | "jsxFactory": "h", 7 | "outDir": "./dist", 8 | "strict": true, 9 | "moduleResolution": "node", 10 | "lib": ["es2015", "dom"], 11 | "declaration": true 12 | }, 13 | "exclude": ["node_modules", "examples"] 14 | } 15 | --------------------------------------------------------------------------------