Skip to content

Commit d41bdae

Browse files
committed
fix(history): rewrite for Cycle Unified
BREAKING CHANGES: New API. You no longer need createHistory() calls that create history objects to be passed to makeHistoryDriver(). You simply call makeHistoryDriver() and those history objects are created under the hood.
1 parent 16d394d commit d41bdae

File tree

16 files changed

+873
-581
lines changed

16 files changed

+873
-581
lines changed

history/package.json

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,27 @@
1717
"history": "^4.3.0"
1818
},
1919
"devDependencies": {
20-
"@cycle/base": "^4.1.1",
21-
"@cycle/dom": "^13.0.0",
22-
"@cycle/most-adapter": "^4.0.1",
23-
"@cycle/most-run": "^4.1.3",
24-
"@cycle/rxjs-adapter": "^3.0.3",
25-
"@cycle/rxjs-run": "^3.0.3",
26-
"@cycle/xstream-adapter": "^3.0.4",
27-
"@cycle/xstream-run": "^3.1.0",
20+
"@cycle/dom": "15.0.0-rc.1",
21+
"@cycle/rxjs-run": "4.0.0-rc.3",
22+
"@cycle/run": "1.0.0-rc.7",
2823
"@types/history": "^4.5.0",
2924
"@types/mocha": "^2.2.32",
3025
"@types/node": "^6.0.46",
31-
"most": "^1.0.4",
3226
"rxjs": "5.0.1",
3327
"saucie": "^1.4.1",
34-
"ts-node": "^1.6.1",
35-
"xstream": "9.x.x"
28+
"xstream": "10.x.x"
3629
},
3730
"scripts": {
3831
"lint": "../node_modules/.bin/tslint -c ../tslint.json src/**/*.ts",
39-
"test-node": "../node_modules/.bin/mocha -r ts-node/register test/node/*.ts",
40-
"build-browser-tests": "../node_modules/.bin/browserify -p tsify test/browser/index.ts -o test/browser/bundle.js",
41-
"test-browser": "../node_modules/.bin/testem ci -l Chrome,Firefox",
32+
"prelib": "rm -rf lib/ && mkdir -p lib",
33+
"lib": "../node_modules/.bin/tsc",
34+
"test-node": "../node_modules/.bin/mocha -r ts-node/register test/node/index.ts",
35+
"test-browser": "../node_modules/.bin/testem -l Chrome",
4236
"test-browser-ci": "../node_modules/.bin/testem ci",
4337
"test": "npm run lint && npm run lib && npm run test-node && npm run test-browser",
4438
"test-ci": "npm run lint && npm run lib && npm run test-node && npm run test-browser-ci",
4539
"browserify": "../node_modules/.bin/browserify lib/index.js --standalone CycleHistory --outfile dist/cycle-history.js",
4640
"uglify": "../node_modules/.bin/uglifyjs dist/cycle-history.js -o dist/cycle-history.min.js",
47-
"prelib": "rm -rf lib/ && mkdir -p lib",
48-
"lib": "../node_modules/.bin/tsc",
4941
"predist": "rm -rf dist/ && mkdir -p dist/",
5042
"dist": "npm run lib && npm run browserify && npm run uglify",
5143
"readme": "node ../.scripts/make-api-docs.js ${PWD##*/} && cat ./.scripts/template-readme.md ./generated-api.md > README.md && rm ./generated-api.md",

history/src/captureClicks.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1-
import { StreamAdapter } from '@cycle/base';
2-
3-
const clickEvent = 'undefined' !== typeof document && document.ontouchstart ?
4-
'touchstart' : 'click';
1+
import xs, {Stream, MemoryStream} from 'xstream';
2+
import {
3+
HistoryInput,
4+
HistoryDriver,
5+
GoBackHistoryInput,
6+
GoForwardHistoryInput,
7+
GoHistoryInput,
8+
PushHistoryInput,
9+
ReplaceHistoryInput,
10+
} from './types';
11+
12+
const CLICK_EVENT = typeof document !== 'undefined' && document.ontouchstart ?
13+
'touchstart' :
14+
'click';
515

616
function which(ev: any) {
717
if (typeof window === 'undefined') {
@@ -19,8 +29,8 @@ function sameOrigin(href: string) {
1929
return href && href.indexOf(window.location.origin) === 0;
2030
}
2131

22-
function makeClickListener(push: Function) {
23-
return function clickListener(event: any) {
32+
function makeClickListener(push: (p: string) => void) {
33+
return function clickListener(event: MouseEvent) {
2434
if (which(event) !== 1) {
2535
return;
2636
}
@@ -33,7 +43,7 @@ function makeClickListener(push: Function) {
3343
return;
3444
}
3545

36-
let element = event.target;
46+
let element: any = event.target;
3747
while (element && element.nodeName !== 'A') {
3848
element = element.parentNode;
3949
}
@@ -61,23 +71,20 @@ function makeClickListener(push: Function) {
6171
};
6272
}
6373

64-
function captureAnchorClicks(push: Function) {
74+
function captureAnchorClicks(push: (p: string) => void) {
6575
const listener = makeClickListener(push);
6676
if (typeof window !== 'undefined') {
67-
document.addEventListener(clickEvent, listener, false);
77+
document.addEventListener(CLICK_EVENT, listener, false);
6878
}
6979
}
7080

71-
export function captureClicks(historyDriver: (sink$: any, runStreamAdapter: StreamAdapter) => any) {
72-
return function historyDriverWithClickCapture(sink$: any, runStreamAdapter: StreamAdapter): any {
73-
const { observer, stream } = runStreamAdapter.makeSubject();
74-
81+
export function captureClicks(historyDriver: HistoryDriver): HistoryDriver {
82+
return function historyDriverWithClickCapture(sink$: Stream<HistoryInput | string>) {
83+
const internalSink$ = xs.create<HistoryInput | string>();
7584
captureAnchorClicks((pathname: string) => {
76-
observer.next({ type: 'push', pathname });
85+
internalSink$._n({type: 'push', pathname});
7786
});
78-
79-
runStreamAdapter.streamSubscribe(sink$, observer);
80-
81-
return historyDriver(stream, runStreamAdapter);
87+
sink$._add(internalSink$);
88+
return historyDriver(internalSink$);
8289
};
8390
}

history/src/createHistory$.ts

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,19 @@
1-
import { StreamAdapter } from '@cycle/base';
2-
import { Location, History, UnregisterCallback } from 'history';
3-
import { HistoryInput } from './types';
4-
5-
export function createHistory$ (history: History, sink$: any,
6-
runStreamAdapter: StreamAdapter): any {
7-
const push = makePushState(history);
8-
9-
const { observer, stream } = runStreamAdapter.makeSubject<Location>();
10-
11-
const history$ = runStreamAdapter.remember<Location>(stream);
12-
13-
const unlisten = history.listen((loc: Location) => {
14-
observer.next(loc);
15-
});
16-
17-
(history$ as any).dispose =
18-
runStreamAdapter.streamSubscribe(sink$, createObserver(push, unlisten));
19-
1+
import xs, {Stream, MemoryStream, Listener} from 'xstream';
2+
import {Location, History, UnregisterCallback} from 'history';
3+
import {HistoryInput} from './types';
4+
5+
export function createHistory$(history: History,
6+
sink$: Stream<HistoryInput | string>): MemoryStream<Location> {
7+
const history$ = xs.createWithMemory<Location>();
8+
const call = makeCallOnHistory(history);
9+
const unlisten = history.listen((loc: Location) => { history$._n(loc); });
10+
const sub = sink$.subscribe(createObserver(call, unlisten));
11+
(history$ as any).dispose = () => { sub.unsubscribe(); unlisten(); };
2012
return history$;
2113
};
2214

23-
function makePushState (history: History) {
24-
return function pushState (input: HistoryInput): void {
15+
function makeCallOnHistory(history: History) {
16+
return function call(input: HistoryInput): void {
2517
if (input.type === 'push') {
2618
history.push(input.pathname, input.state);
2719
}
@@ -44,17 +36,17 @@ function makePushState (history: History) {
4436
};
4537
}
4638

47-
function createObserver (push: (input: HistoryInput) => any,
48-
unlisten: UnregisterCallback) {
39+
function createObserver(call: (input: HistoryInput) => void,
40+
unlisten: UnregisterCallback): Listener<HistoryInput | string> {
4941
return {
50-
next (input: HistoryInput | String) {
42+
next (input: HistoryInput | string) {
5143
if (typeof input === 'string') {
52-
push({ type: 'push', pathname: input });
44+
call({type: 'push', pathname: input});
5345
} else {
54-
push(input as HistoryInput);
46+
call(input);
5547
}
5648
},
57-
error: unlisten,
58-
complete: unlisten,
49+
error: (err) => { unlisten(); },
50+
complete: () => { setTimeout(unlisten); },
5951
};
6052
}

history/src/drivers.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {Stream, MemoryStream} from 'xstream';
2+
import {
3+
createBrowserHistory,
4+
createMemoryHistory,
5+
createHashHistory,
6+
BrowserHistoryBuildOptions,
7+
MemoryHistoryBuildOptions,
8+
HashHistoryBuildOptions,
9+
Location,
10+
} from 'history';
11+
import {createHistory$} from './createHistory$';
12+
import {
13+
HistoryInput,
14+
HistoryDriver,
15+
GoBackHistoryInput,
16+
GoForwardHistoryInput,
17+
GoHistoryInput,
18+
PushHistoryInput,
19+
ReplaceHistoryInput,
20+
} from './types';
21+
22+
/**
23+
* Create a History Driver to be used in the browser.
24+
*/
25+
export function makeHistoryDriver(options?: BrowserHistoryBuildOptions): HistoryDriver {
26+
const history = createBrowserHistory(options);
27+
return function historyDriver(sink$: Stream<HistoryInput | string>) {
28+
return createHistory$(history, sink$);
29+
};
30+
}
31+
32+
/**
33+
* Create a History Driver to be used in non-browser enviroments
34+
* such as server-side node.js.
35+
*/
36+
export function makeServerHistoryDriver(options?: MemoryHistoryBuildOptions): HistoryDriver {
37+
const history = createMemoryHistory(options);
38+
return function serverHistoryDriver(sink$: Stream<HistoryInput | string>) {
39+
return createHistory$(history, sink$);
40+
};
41+
}
42+
43+
/**
44+
* Create a History Driver for older browsers using hash routing
45+
*/
46+
export function makeHashHistoryDriver(options?: HashHistoryBuildOptions): HistoryDriver {
47+
const history = createHashHistory(options);
48+
return function hashHistoryDriver(sink$: Stream<HistoryInput | string>) {
49+
return createHistory$(history, sink$);
50+
};
51+
}

history/src/historyDriver.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

history/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { Location } from 'history';
1+
export {Location} from 'history';
22
export * from './types';
3-
export * from './historyDriver';
3+
export * from './drivers';
44
export * from './captureClicks';

history/src/types.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
1+
import {Stream, MemoryStream} from 'xstream';
2+
import {Location} from 'history';
13
export type Pathname = string;
24

5+
export type HistoryDriver = (sink$: Stream<HistoryInput | string>) => MemoryStream<Location>;
6+
37
export interface PushHistoryInput {
48
type: 'push';
59
pathname: Pathname;
610
state?: any;
7-
};
11+
}
812

913
export interface ReplaceHistoryInput {
1014
type: 'replace';
1115
pathname: Pathname;
1216
state?: any;
13-
};
17+
}
1418

1519
export interface GoHistoryInput {
1620
type: 'go';
1721
amount: number;
18-
};
22+
}
1923

2024
export interface GoBackHistoryInput {
2125
type: 'goBack';
22-
};
26+
}
2327

2428
export interface GoForwardHistoryInput {
2529
type: 'goForward';
26-
};
30+
}
2731

2832
export type HistoryInput =
2933
PushHistoryInput

history/test/browser/captureClicks.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/// <reference path="../../node_modules/@types/mocha/index.d.ts" />
2+
/// <reference path="../../node_modules/@types/node/index.d.ts" />
3+
import * as assert from 'assert';
4+
import xs from 'xstream';
5+
import {makeHashHistoryDriver, captureClicks, Location} from '../../src';
6+
7+
describe('captureClicks', () => {
8+
beforeEach(() => {
9+
window.location.hash = '';
10+
});
11+
12+
it('should allow listening to link clicks and change route', function (done) {
13+
const historyDriver = makeHashHistoryDriver();
14+
const history$ = captureClicks(historyDriver)(xs.never());
15+
16+
const sub = history$.subscribe({
17+
next: (location: Location) => {
18+
assert.strictEqual(location.pathname, '/test');
19+
sub.unsubscribe();
20+
done();
21+
},
22+
error: (err) => {},
23+
complete: () => {},
24+
});
25+
26+
const a = document.createElement('a');
27+
a.href = '/test';
28+
document.body.appendChild(a);
29+
30+
setTimeout(() => {
31+
a.click();
32+
});
33+
});
34+
});

history/test/browser/common.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/// <reference path="../../node_modules/@types/mocha/index.d.ts" />
2+
/// <reference path="../../node_modules/@types/node/index.d.ts" />
3+
import * as assert from 'assert';
4+
import {makeHashHistoryDriver, makeHistoryDriver} from '../../src';
5+
6+
describe('makeHistoryDriver', () => {
7+
it('should be a function', () => {
8+
assert.strictEqual(typeof makeHistoryDriver, 'function');
9+
});
10+
11+
it('should return a function' , () => {
12+
assert.strictEqual(typeof makeHistoryDriver(), 'function');
13+
});
14+
});
15+
16+
describe('makeHashHistoryDriver', () => {
17+
it('should be a function', () => {
18+
assert.strictEqual(typeof makeHashHistoryDriver, 'function');
19+
});
20+
21+
it('should return a function' , () => {
22+
assert.strictEqual(typeof makeHashHistoryDriver(), 'function');
23+
});
24+
});

0 commit comments

Comments
 (0)