Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 1.0, huge refactor and re-write of pieces #7

Merged
merged 44 commits into from
Oct 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
16ddb41
WIP Radical change in how the app runs, and how effects are passed ar…
mrozbarry Sep 21, 2018
fe154dc
More tests, app improvements, all goodness.
mrozbarry Sep 21, 2018
0097e9c
Some linting fixes.
mrozbarry Sep 21, 2018
515bddd
Updated travis to use npm ci instead of install.
mrozbarry Sep 21, 2018
e433280
Bumped up test coverage for delay and raf effects.
mrozbarry Sep 21, 2018
9b030e2
Got all but the game-input example completely updated, and a few opti…
mrozbarry Sep 22, 2018
31fb131
Added ferp.utils to export, added combineReducers since it will be a …
mrozbarry Sep 22, 2018
c5df4b8
Made rollup minify the compiled source.
mrozbarry Sep 23, 2018
6b7447d
Got test coverage to 100%, lots of improvements, need to work on bett…
mrozbarry Sep 23, 2018
04d90e6
Removed the game-input example, it was too complicated and didn't dem…
mrozbarry Sep 23, 2018
01c1272
Updated with-superfine example so render is a type of side-effect rat…
mrozbarry Sep 23, 2018
ffc8f46
Update remaining examples to remove the function wrapper around init.
mrozbarry Sep 23, 2018
2e7edf7
Merge branch 'master' of github.com:mrozbarry/ferp into experiment/ap…
mrozbarry Sep 23, 2018
be3f634
Merge branch 'master' of github.com:mrozbarry/ferp into experiment/ap…
mrozbarry Sep 23, 2018
194b6ab
Merge branch 'master' of github.com:mrozbarry/ferp into experiment/ap…
mrozbarry Sep 23, 2018
46e6e7d
Removed example lock files.
mrozbarry Sep 23, 2018
153086f
Merge branch 'master' of github.com:mrozbarry/ferp into experiment/ap…
mrozbarry Sep 23, 2018
b96c4bb
Updated documentation.
mrozbarry Sep 23, 2018
e1dda17
Disabled the result module for now, may bring it back in a post 1.0 v…
mrozbarry Sep 23, 2018
cbe5f57
Adds changelog.
mrozbarry Sep 24, 2018
c59b6f5
Updated docs, fixed order of the every subscription.
mrozbarry Sep 24, 2018
9c0538e
Bumps beta version.
mrozbarry Sep 25, 2018
ac87c09
Noticed delays weren't combining as expected, so I added effects.thun…
mrozbarry Sep 26, 2018
4e8aa52
Bumping beta version.
mrozbarry Sep 26, 2018
df23e1f
Merge pull request #6 from mrozbarry/experiment/app-runner
mrozbarry Sep 26, 2018
bfbf157
Added thunk to main ferp exports.
mrozbarry Sep 27, 2018
f419fc6
Updated cli examples to use the latest beta.
mrozbarry Sep 27, 2018
2844616
Some package restructuring, pulled the plug officially on result type.
mrozbarry Sep 28, 2018
e55ffe9
Internal updates, added to the main README.
mrozbarry Sep 29, 2018
bbbf1ce
Made the subscription manager a little more like the other managers, …
mrozbarry Sep 30, 2018
8d10772
chore; added dist to gitignore.
mrozbarry Sep 30, 2018
0df6756
Got examples/cli up to 100% test coverage.
mrozbarry Sep 30, 2018
385f732
Split out internals documentation, to keep README more simple.
mrozbarry Oct 1, 2018
5e2f26c
WIP Started testing documentation.
mrozbarry Oct 1, 2018
81604dc
Fixed gitter url.
mrozbarry Oct 2, 2018
41815bb
Added tests to message manager.
mrozbarry Oct 2, 2018
61ed496
Preparing http server and with-superfine to do tests.
mrozbarry Oct 2, 2018
c75fd84
Updated tagline to say this is javascript, since it wasn't clear.
mrozbarry Oct 2, 2018
0f9f3d6
Fixed messageManager test.
mrozbarry Oct 2, 2018
3affb70
Tested the http server.
mrozbarry Oct 3, 2018
2500f18
Added tests to with-superfine example.
mrozbarry Oct 3, 2018
b15d553
Identified and corrected an issue with combineReducers array method.
mrozbarry Oct 3, 2018
ba812e3
Simplified messageManager.
mrozbarry Oct 4, 2018
12bee7e
More documentation
mrozbarry Oct 4, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.DS_Store
node_modules
.cache
dist
.nyc_output
dist
coverage
examples/**/package-lock.json
ferp.js
10 changes: 10 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.nyc_output
.cache
coverage
examples
src/**/*.test.js
.babelrc
.eslint*
.nvmrc
.travis.yml
rollup.config.js
88 changes: 88 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Ferp Changelog

## Unreleased - 2018-09-XX - [git](#https://github.com/mrozbarry/ferp/compare/619723b8b35676acaa1196629c35331bcb978b0f...todo)

### Breaking changes

- Renames `Effect.map` to `Effect.batch`
- Removes `ferp.types.Effect`
- Moves effect primitives into `ferp.effects` as `none()`, `batch([])`, `defer(promise)`, and **new** `thunk(method)`
- Disables `ferp.types.Result` - It may come back, or be removed in a future release
- Changes `ferp.effects.delay`
- Exposed as a single function that delays for n milliseconds
- Reverses param order to be `ferp.effects.delay(message, milliseconds)` to align with the raf effect
- Changes `ferp.subscriptions.every`
- Exposed as a single function that ticks every n milliseconds
- Reverses param order to be `ferp.subscriptions.every(message, milliseconds)` to align with `effects.raf` and `effects.delay`

### Features

- Adds a changelog
- Huge test coverage improvement
- Many internal changes that simplify the `ferp.app` function
- Adds `ferp.util.combineReducers` to manage nested reducers that run effects
- Adds `ferp.effects.thunk` effect primitive


## v0.1.1 - 2018-09-20 - [git](https://github.com/mrozbarry/ferp/compare/4e1dbd1e8e82c8197be87ee59c7de827a6ca4741...619723b8b35676acaa1196629c35331bcb978b0f)

### Features

- Adds tests around subscriptionHandler

### Fixes

- Issue where subscriptions that immediately dispatch could cause an infinite loop


## v0.1.0 - 2018-09-16 - [git](https://github.com/mrozbarry/ferp/compare/da884fdcfeb8746af2e5b366cc1a7395c64f103d...4e1dbd1e8e82c8197be87ee59c7de827a6ca4741)

### Breaking changes

- Updated subscriptions to not require an id prefix


## v0.0.5 - 2018-09-09 - [git](https://github.com/mrozbarry/ferp/compare/9a51d0da69d68ddbbe2f86dbb325bb0e7f2e1e4e...da884fdcfeb8746af2e5b366cc1a7395c64f103d)

### Features

- Further linting
- Adds test for effect ordering

### Fixes

- Fixes issue with effects not running in a deterministic order


## v0.0.4 - 2018-09-08 - [git](https://github.com/mrozbarry/ferp/compare/4053daee2e434a9a66ad88d9de056e9d2621243b...9a51d0da69d68ddbbe2f86dbb325bb0e7f2e1e4e)

### Features

- Many fixes and touch-ups to the game-input example

### Fixes

- Issue where mapped effects weren't being executed properly


## v0.0.3 - 2018-09-07 - [git](https://github.com/mrozbarry/ferp/compare/1efebd9c67e9ab76c4c44c63d2ab021af1cd2f96...4053daee2e434a9a66ad88d9de056e9d2621243b)

### Features

- General code cleanup
- Adds `effects.delay.raf` to requestAnimationFrame

### Fixes

- Fixed ava tests that were being cached unexpectedly


## v0.0.2 - 2018-09-03 - [git](https://github.com/mrozbarry/ferp/compare/6b9a97ac89f8496a2efe865ea9197bcdf9856da3...1efebd9c67e9ab76c4c44c63d2ab021af1cd2f96)

### Features

- Adds testing with ava
- Adds linting with eslint
- Adds game-input example
- Adds rollup and config
- Upgrades code to use es6 imports, since ava already brings in babel dependencies
73 changes: 73 additions & 0 deletions INTERNALS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Ferp Internals

## Initializing your app

| Param | Type | Required |
| -------- | ----------- | -------- |
| init | Array/Tuple | Yes |

The `init` array/tuple lets you establish initial state, and run an initial [effect](./src/effects).
The structure of this data **must** be `[initialState, initialEffect]` where initialState is the state you want your app to start with, and initialEffect is an [effect](./src/effects) that tells the app how to proceed.


## Keeping up to date

| Param | Type | Required |
| -------- | -------- | -------- |
| update | Function | Yes |

The `update(message, state)` function gives you the opportunity to make changes to your state.
All updates **must** return the array `[updatedState, effect]`, where:

- `updatedState` is a new copy of state with any changes you have made
- `effect` is any effect you want to trigger. See [effects](./src/effects) for more information on what they are and how to use them.


## Subscribe to third-party events

| Param | Type | Required |
| ------------- | -------- | -------- |
| subscribe | Function | No |

The `subscribe(state)` function describes which [subscriptions](./src/subscriptions) are active and inactive.
This function is run each update, and can and should react to the new state to turn on and off subscriptions.
`subscribe` should return an array in the following format:

```javascript
subscribe: (state) => {
return [
[subscriptionMethod, param1, param2, param3, ...],
[subMethod, param1, param2, param3, ...],
...
]
}
```

Each subscription needs at least a subscription method.
You can use boolean operations to toggle subscriptions on and off like this:

```javascript
subscribe: (state) => {
return [
state.somethingMeaningful && [subscriptionMethod, param1, param2, param3, ...],
]
}
```

If you want to write your own subscription, methods should look like the following:

```javascript
const myCoolSubscription = (param1, param2, param3) => (dispatch) => {
// Start subscription here
// ...

return () => {
// Clean up subscription here
// ...
};
};
```

When a subscription is deactivated, ferp will run it's cleanup callback, and when it is (re-)activated, it will run a fresh copy of your subscription.
Subscriptions aren't re-used in ferp, so re-activating a subscription will not remember any internal state your subscription previously handled.
If you need your subscription to remember a previous state between activations, you may want to store that data in your state, and run an effect as your subscription ends to store that latest subscription state in your app state.
144 changes: 144 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Migrating from ferp 0.x to 1.x

## Minor changes to app initialization

Previously, your app entry code may have looked like this:

```javascript
const ferp = require('ferp');

ferp.app({
init: () => [initialState, initialEffect],
update: (message, previousState) => [previousState, ferp.types.Effect.none()],
subscribe: (state) => [],
});
```

Most of this is the same, but init is not a function any more, and effects have been move (more on this below).
Now the same code should look like this:

```javascript
const ferp = require('ferp');

ferp.app({
init: [initialState, initialEffect],
update: (message, previousState) => [previousState, ferp.effects.none()],
subscribe: (state) => [],
});
```

## Effects have moved, and they have some new friends

Previously, to use an effect, your code may have looked like one of these:

```javascript
const ferp = require('ferp');
const { Effect } = ferp.types;

const myNoOpEffect = Effect.none();

const myImmediateAsyncEffect = new Effect((done) => {
// Do something
done({ type: 'MESSAGE YOU WANT TO SEND' });
});

const myManyEffects = Effect.map([
someEffect1,
someEffect2,
]);

const myMessageEffect = Effect.immediate({ type: 'MESSAGE FOR RIGHT NOW' });

const { dispatch, effect } = Effect.defer();
const myDeferredAsyncEffect = effect;
// some other code calls dispatch({ type: 'MESSAGE DEFERRED' })
```

Now the same code looks like this:

```javascript
const ferp = require('ferp');
const { effects } = ferp;

const myNoOpEffect = effects.none(); // Looks mostly the same on the outside, much different on the inside.

const myImmediateAsyncEffect = effects.defer(new Promise((done) => { // You can provide your own promise now. Works much better for doing fetches and other things.
// Do something
done({ type: 'MESSAGE YOU WANT TO SEND' });
}));

const myManyEffects = effects.batch([ // Note the rename from map to batch
someEffect1,
someEffect2,
]);

const myMessageEffect = { type: 'MESSAGE FOR RIGHT NOW' }; // No wrapping effect now

let dispatch = () => {}; // A little more complicated solution here, but I think externally resolved promises like this will be unusual.
const deferredPromise = new Promise((done) => { dispatch = done });
const myDeferredAsyncEffect = effects.defer(deferredPromise);
// some other code calls dispatch({ type: 'MESSAGE DEFERRED' });
```

With a bonus, we have a new effect - introducing `effects.thunk`.
A thunk is a way to only evaluate an effect when it's handled internally.
For example, if you were to compose a new effect with delays, you'd want the inner delay to run after the external delay.
Here's a non-working version:

```javascript
const myDelayEffect = (innerEffect, milliseconds) => effects.defer(new Promise((done) => {
setTimeout(() => {
done(innerEffect);
}, milliseconds);
}));

const composedEffect = myDelay(myDelay({ type: 'INNER' }, 1000), 1000);
```

You would expect the `{ type: 'INNER' }` to be dispatched after 2000 milliseconds, but this is not the case.
Both effects would run almost immediately, and take a total of about 1010 milliseconds.
This is because the setTimeout will run as soon as the promise is created, even though we may not wait for it until later.
To fix this, we could use a thunk like this:

```javascript
const myDelayEffect = (innerEffect, milliseconds) => effects.thunk(() => effects.defer(new Promise((done) => {
setTimeout(() => {
done(innerEffect);
}, milliseconds);
})));

const composedEffect = myDelay(myDelay({ type: 'INNER' }, 1000), 1000);
```

The thunk prevents the promise from even being created until it's evaluated by ferp during a dispatch.

## As seen above, composable core effects

The previous effect implementation made composing effects hard.
Effects have been completely re-written from the ground up to support powerful composition.

## Timer param orderings

Doing `effects.delay`, `effects.raf`, and `subsciptions.every` have had their params re-ordered to be all consistent, `message, timeInformation`,
where `timeInformation` is the length of a delay for `effects.delay`, the last timestamp (or null) for `effects.raf` to calculate deltas, and millisecond interval for `subscriptions.every`.

## Removed timer helpers

Previously, `effects.delay` and `subscriptions.every` exported modules with `milliseconds`, `seconds`, `minutes`, and `hours` being options in how the delay would be timed.
These have been removed, and the exported `delay` and `every` are just the millisecond function variants that were previously available.

## No more types

Effects, as seen above, are first class citizens now, but more importantly, I removed the `Result` type. You can read more about that decision [on PR#7](https://github.com/mrozbarry/ferp/pull/7).
There are various enum and result type libraries available for javascript, and maybe one of those will be a good replacement if you have been using `Result`s.

## No more middleware

Middleware was a hard decision because many libraries like ferp have the concept of middleware in one form or another.
After much thought, I have decided to remove it for a few reasons:

- Middleware makes sense when a library provides both state and UI, since you may want to intercept state changes before it reaches the UI. Ferp doesn't provide a UI, so it makes this useless.
- The implementation I chose for ferp originally was inefficient, and allowed state mutations, but of which were hard to justify in an immutable functional app.
- Functional programming promotes the idea of high-order functions, so if it were absolutely necessary to have a middleware for ferp, it is easy enough to write a wrapping function for your update function.

You can read more on it [on PR#7](https://github.com/mrozbarry/ferp/pull/7)
Loading