diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..118d755 --- /dev/null +++ b/.babelrc @@ -0,0 +1,9 @@ +{ + "plugins": [ + "add-module-exports", + "transform-es2015-modules-commonjs", + "transform-es2015-destructuring", + "transform-object-rest-spread", + ["transform-react-jsx", { "pragma": "h" }] + ] +} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..7ebc82f --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,54 @@ +{ + // http://eslint.org/docs/rules/ + "root": true, + "parser": "babel-eslint", + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 7, + "sourceType": "module", + "ecmaFeatures": { + "jsx": [1, { "pragma": "h" }], + "globalReturn ": true, + "impliedStrict": true + } + }, + "env": { + "browser": true, + "mocha": true, + "node": true, + "es6": true + }, + "rules": { + "semi": 0, + "no-var": 0, + "vars-on-top": 0, + "spaced-comment": 0, + "prefer-template": 0, + "no-unused-vars": 0, + "no-inner-declarations": 0, + "consistent-return": 0, + "comma-dangle": 0, + "no-use-before-define": 0, + "no-return-assign": 0, + "no-console": 0, + "max-len": 0, + "arrow-body-style": 0, + "new-cap": 0, + "quotes": 0, + "quote-props": 0, + "prefer-arrow-callback": 0, + "func-names": 0, + "padded-blocks": 0, + "keyword-spacing": 0, + "no-global-assign": 0, + "no-trailing-spaces": 0, + "no-unused-expressions": 0, + "space-before-function-paren": 0, + "global-require": 0, + "react/jsx-no-bind": 0, + "react/jsx-space-before-closing": 0, + "react/jsx-closing-bracket-location": 0, + "react/prop-types": 0, + "react/prefer-stateless-function": 0 + } +} diff --git a/DOM.js b/DOM.js new file mode 100644 index 0000000..2af1b1b --- /dev/null +++ b/DOM.js @@ -0,0 +1,40 @@ +/* eslint-disable */ +var jsdom = require('jsdom'); + +// Setup the jsdom environment +// @see https://github.com/facebook/react/issues/5046 +global.document = jsdom.jsdom(''); +global.window = document.defaultView; +global.navigator = global.window.navigator; +global.usingJSDOM = true; + +global.chai = require('chai'); +global.expect = global.chai.expect; +global.SVGElement = global.Element; + +//JSDOM doesn't support localStorage by default, so lets just fake it.. +if (!global.window.localStorage) { + global.window.localStorage = { + getItem() { return '{}'; }, + setItem() {} + }; +} + +// take all properties of the window object and also attach it to the +// mocha global object +propagateToGlobal(global.window); + +// from mocha-jsdom https://github.com/rstacruz/mocha-jsdom/blob/master/index.js#L80 +function propagateToGlobal (window) { + for (var key in window) { + if (!window.hasOwnProperty(key)) continue; + if (key in global) continue; + + global[key] = window[key]; + } +} +if (!global.requestAnimationFrame) { + global.requestAnimationFrame = function (func) { + setTimeout(func, 1000 / 60); + } +} diff --git a/README.md b/README.md index c8a5d9b..96fc50d 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,15 @@ Preact + This is a fork of [mobx-react](https://github.com/mobxjs/mobx-react) for [Preact](https://preactjs.com/) This package provides the bindings for [MobX](https://mobxjs.github.io/mobx). +**_It's not really maintained, if someone wants to take over, ping me on github!_** + +Consider using [mobx-observer](https://www.npmjs.com/package/mobx-observer) or another library instead. + Exports the `connect` (or alias `observer`) decorator and some development utilities. ## Installation diff --git a/mocha.opts b/mocha.opts index 59735e6..a202fec 100644 --- a/mocha.opts +++ b/mocha.opts @@ -1,4 +1,6 @@ +-r babel-register +--require ./DOM.js --recursive --colors -test/**/*.js +test/*.js diff --git a/package.json b/package.json index 905a9e6..a3225c4 100644 --- a/package.json +++ b/package.json @@ -36,10 +36,30 @@ "@types/core-js": "^0.9.35", "@types/mocha": "^2.2.39", "@types/node": "^7.0.5", + "babel-eslint": "^7.1.1", + "babel-plugin-add-module-exports": "0.2.1", + "babel-plugin-transform-es2015-arrow-functions": "6.8.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.8.0", + "babel-plugin-transform-es2015-block-scoping": "6.21.0", + "babel-plugin-transform-es2015-classes": "6.18.0", + "babel-plugin-transform-es2015-computed-properties": "6.8.0", + "babel-plugin-transform-es2015-destructuring": "6.19.0", + "babel-plugin-transform-es2015-literals": "6.8.0", + "babel-plugin-transform-es2015-modules-commonjs": "6.18.0", + "babel-plugin-transform-es2015-parameters": "6.21.0", + "babel-plugin-transform-es2015-shorthand-properties": "6.18.0", + "babel-plugin-transform-es2015-spread": "6.8.0", + "babel-plugin-transform-es2015-template-literals": "6.8.0", + "babel-plugin-transform-object-rest-spread": "^6.20.2", + "babel-plugin-transform-react-jsx": "^6.22.0", + "babel-register": "^6.22.0", "chai": "^3.5.0", + "eslint": "^3.15.0", + "jsdom": "^9.10.0", "mobx": "^3.1.0", "mocha": "^3.2.0", "preact": "^7.1.0", + "ts-node": "^2.0.0", "tslint": "^4.4.2", "typescript": "^2.1.5" } diff --git a/src/EventEmitter.ts b/src/EventEmitter.js similarity index 57% rename from src/EventEmitter.ts rename to src/EventEmitter.js index 5999713..4332326 100644 --- a/src/EventEmitter.ts +++ b/src/EventEmitter.js @@ -1,8 +1,9 @@ -export default class EventEmitter { - - private listeners: Function[] = []; +class EventEmitter { + constructor() { + this.listeners = []; + } - public on(cb: Function) { + on(cb) { this.listeners.push(cb); return () => { const index = this.listeners.indexOf(cb); @@ -12,15 +13,17 @@ export default class EventEmitter { }; } - public emit(data: any) { + emit(data) { this.listeners.forEach((fn) => fn(data)); - }; + } - public getTotalListeners(): number { + getTotalListeners() { return this.listeners.length; } - public clearListeners(): void { + clearListeners() { this.listeners = []; } } + +export default EventEmitter diff --git a/src/Provider.ts b/src/Provider.js similarity index 76% rename from src/Provider.ts rename to src/Provider.js index 9dd8a94..07f1fbf 100644 --- a/src/Provider.ts +++ b/src/Provider.js @@ -14,38 +14,24 @@ function childOnly(children) { return children.length ? children[0] : children; } -export default class Provider extends Component { - contextTypes: any = { - mobxStores() { - } - }; - childContextTypes: any = { - mobxStores() { - } - }; - private store: any; - - constructor(props?: any, context?: any) { +class Provider extends Component { + constructor(props, context) { super(props, context); this.store = props.store; } - public render() { - return childOnly(this.props.children); - } - getChildContext() { - let stores = {}; + const stores = {}; // inherit stores - let baseStores = this.context.mobxStores; + const baseStores = this.context.mobxStores; if (baseStores) { - for (let key in baseStores) { + for (const key in baseStores) { stores[key] = baseStores[key]; } } // add own stores - for (let key in this.props) { + for (const key in this.props) { if (!specialKeys[key]) { stores[key] = this.props[key]; } @@ -54,6 +40,10 @@ export default class Provider extends Component { mobxStores: stores }; } + + render() { + return childOnly(this.props.children); + } } if (process.env.NODE_ENV !== 'production') { @@ -64,7 +54,7 @@ if (process.env.NODE_ENV !== 'production') { 'MobX Provider: The set of provided stores has changed. ' + 'Please avoid changing stores as the change might not propagate to all children' ); - for (let key in nextProps) { + for (const key in nextProps) { warning(specialKeys[key] || this.props[key] === nextProps[key], `MobX Provider: Provided store '${key}' has changed. ` + `Please avoid replacing stores as the change might not propagate to all children` @@ -74,3 +64,12 @@ if (process.env.NODE_ENV !== 'production') { }; } +Provider.contextTypes = { + mobxStores() {} +}; + +Provider.childContextTypes = { + mobxStores() {} +}; + +export default Provider diff --git a/src/connect.ts b/src/connect.js similarity index 90% rename from src/connect.ts rename to src/connect.js index 4ab8ac8..e65d5a5 100644 --- a/src/connect.ts +++ b/src/connect.js @@ -1,4 +1,3 @@ -import invariant = require('invariant'); import { Component } from 'preact'; import createClass from 'preact-classless-component'; import inject from './inject'; @@ -8,7 +7,7 @@ import { throwError } from './utils/shared'; /** * Wraps a component and provides stores as props */ -function connect (arg1: any, arg2 = null): any { +function connect (arg1, arg2 = null) { if (typeof arg1 === 'string') { throwError('Store names should be provided as array'); } @@ -39,7 +38,7 @@ function connect (arg1: any, arg2 = null): any { contextTypes: componentClass.contextTypes, getDefaultProps: () => componentClass.defaultProps, render() { - return componentClass.call(this, this.props, this.state, this.context); + return componentClass.call(this, this.props, this.context, this.context); } }); diff --git a/src/index.ts b/src/index.js similarity index 100% rename from src/index.ts rename to src/index.js diff --git a/src/inject.ts b/src/inject.js similarity index 74% rename from src/inject.ts rename to src/inject.js index a43b617..7ffa655 100644 --- a/src/inject.ts +++ b/src/inject.js @@ -1,19 +1,15 @@ -import hoistStatics = require('hoist-non-react-statics'); +import hoistStatics from 'hoist-non-react-statics'; import { h } from 'preact'; import createComponent from 'preact-classless-component'; -interface IStoreProps { - ref: any; -} - /** * Store Injection */ -function createStoreInjector(grabStoresFn: Function, component) { - const Injector: any = createComponent({ +function createStoreInjector(grabStoresFn, component) { + const Injector = createComponent({ displayName: component.name, render() { - const newProps = {}; + const newProps = {}; for (let key in this.props) { if (this.props.hasOwnProperty(key)) { newProps[key] = this.props[key]; @@ -41,8 +37,8 @@ function createStoreInjector(grabStoresFn: Function, component) { return Injector; } -const grabStoresByName = function(storeNames: string[]): Function { - return function(baseStores: Object, nextProps: Object): Object { +const grabStoresByName = function(storeNames) { + return function(baseStores, nextProps) { storeNames.forEach(function(storeName) { // Prefer props over stores @@ -69,11 +65,11 @@ const grabStoresByName = function(storeNames: string[]): Function { * or a function that manually maps the available stores from the context to props: * storesToProps(mobxStores, props, context) => newProps */ -export default function inject(grabStoresFn?: Function | string): any { +export default function inject(grabStoresFn) { if (typeof grabStoresFn !== 'function') { - let storesNames: any = []; + let storesNames = []; for (let i = 0; i < arguments.length; i++) { storesNames[i] = arguments[i]; } @@ -81,5 +77,5 @@ export default function inject(grabStoresFn?: Function | string): any { grabStoresFn = grabStoresByName(storesNames); } - return (componentClass) => createStoreInjector(grabStoresFn as Function, componentClass); + return (componentClass) => createStoreInjector(grabStoresFn, componentClass); } diff --git a/src/makeReactive.ts b/src/makeReactive.js similarity index 93% rename from src/makeReactive.ts rename to src/makeReactive.js index 4dfe5bc..ddc5b1b 100644 --- a/src/makeReactive.ts +++ b/src/makeReactive.js @@ -8,7 +8,7 @@ import { throwError } from './utils/shared'; */ let isDevtoolsEnabled = false; -export const componentByNodeRegistery: WeakMap = new WeakMap(); +export const componentByNodeRegistery = new WeakMap(); export const renderReporter = new EventEmitter(); function reportRendering(component) { @@ -35,11 +35,6 @@ export function trackComponents() { } } -interface IReactiveRender { - $mobx?: Reaction; - (nextProps, nextState, nextContext): void; -} - export default function makeReactive(componentClass) { const target = componentClass.prototype || componentClass; @@ -52,7 +47,7 @@ export default function makeReactive(componentClass) { // Call original baseWillMount && baseWillMount.call(this); - let reaction: Reaction; + let reaction; let isRenderingPending = false; const initialName = this.displayName || this.name || (this.constructor && (this.constructor.displayName || this.constructor.name)) || ''; @@ -80,7 +75,7 @@ export default function makeReactive(componentClass) { return reactiveRender(nextProps, nextState, nextContext); }; - const reactiveRender: IReactiveRender = (nextProps, nextState, nextContext) => { + const reactiveRender = (nextProps, nextState, nextContext) => { isRenderingPending = false; let rendering = undefined; reaction.track(() => { diff --git a/src/utils/shared.ts b/src/utils/shared.js similarity index 51% rename from src/utils/shared.ts rename to src/utils/shared.js index 2037ab2..8d578a1 100644 --- a/src/utils/shared.ts +++ b/src/utils/shared.js @@ -1,9 +1,9 @@ -export function warning(condition, message: string) { +export function warning(condition, message) { if (!condition) { console.error(message); } } -export function throwError(message?: string) { +export function throwError(message) { throw new Error(`MobX-Preact Error: ${ message }`); } diff --git a/test/connect.spec.ts b/test/connect.spec.js similarity index 92% rename from test/connect.spec.ts rename to test/connect.spec.js index 3ad7d08..eaae0b9 100644 --- a/test/connect.spec.ts +++ b/test/connect.spec.js @@ -1,3 +1,4 @@ +import 'mocha'; import { expect } from 'chai'; import { h, render, Component } from 'preact'; import Provider from '../src/Provider'; @@ -27,7 +28,7 @@ describe('MobX inject()', () => { let container; beforeEach(() => { - container = document.createElement('div') as HTMLElement; + container = document.createElement('div'); container.style.display = 'none'; document.body.appendChild(container); }); @@ -37,7 +38,7 @@ describe('MobX inject()', () => { render(null, container); }); - class TestComponent extends Component { + class TestComponent extends Component { render({ testStore }) { return h('span', null, testStore); } @@ -91,16 +92,16 @@ describe('MobX inject()', () => { it('should create class with injected stores', () => { - class TestClass extends Component { - static defaultProps = { - world: 'world' - }; - + class TestClass extends Component { render({ hello, world }) { return h('span', null, hello + ' ' + world); } } + TestClass.defaultProps = { + world: 'world' + } + function App() { return h(Provider, { hello: 'hello' diff --git a/test/eventemitter.spec.ts b/test/eventemitter.spec.js similarity index 88% rename from test/eventemitter.spec.ts rename to test/eventemitter.spec.js index 619a340..f09325d 100644 --- a/test/eventemitter.spec.ts +++ b/test/eventemitter.spec.js @@ -1,15 +1,16 @@ +import 'mocha'; import { expect } from 'chai'; import EventEmitter from '../src/EventEmitter'; const testData = { testKey: 'testData', }; -const testListener = function(data: any) { +const testListener = function(data) { expect(data).to.equal(testData); }; describe('mobx - EventEmitter', () => { - it('should have an empty listner array on construction', () => { + it('should have an empty listeners array on construction', () => { const unit = new EventEmitter(); expect(unit.getTotalListeners()).to.equal(0); }); diff --git a/test/makeReactive.spec.tsx b/test/makeReactive.spec.js similarity index 86% rename from test/makeReactive.spec.tsx rename to test/makeReactive.spec.js index ed8ef19..5d3f5b0 100644 --- a/test/makeReactive.spec.tsx +++ b/test/makeReactive.spec.js @@ -1,11 +1,12 @@ +import 'mocha'; import { observable, extendObservable, toJS } from 'mobx'; import { expect } from 'chai'; -import { render, Component } from 'preact'; +import { h, render, Component } from 'preact'; import makeReactive from '../src/makeReactive'; let todoListRenderings = 0; let todoListWillReactCount = 0; -let store = { +const store = { todos: observable(['one', 'two']), extra: observable({ test: 'observable!' }) }; @@ -14,7 +15,7 @@ describe('MobX Observer', () => { let container; beforeEach(() => { - container = document.createElement('div') as HTMLElement; + container = document.createElement('div'); container.style.display = 'none'; document.body.appendChild(container); }); @@ -28,14 +29,14 @@ describe('MobX Observer', () => { return
  • { todo }
  • ; }); - const TodoList = makeReactive(class extends Component { + const TodoList = makeReactive(class extends Component { componentWillReact() { todoListWillReactCount++; } render() { todoListRenderings++; - let todos = store.todos; + const todos = store.todos; return
    {todos.map(todo => )}
    ; } }); @@ -56,7 +57,7 @@ describe('MobX Observer', () => { }); it('should render a todo list with non observale item', () => { - const FlatList = makeReactive(class extends Component { + const FlatList = makeReactive(class extends Component { render({ extra }) { return
    {store.todos.map(title =>
  • { title }{ extra.test }
  • )}
    ; } diff --git a/test/provider.spec.tsx b/test/provider.spec.js similarity index 88% rename from test/provider.spec.tsx rename to test/provider.spec.js index 4a4c341..62d8754 100644 --- a/test/provider.spec.tsx +++ b/test/provider.spec.js @@ -1,6 +1,7 @@ +import 'mocha'; import { observable } from 'mobx'; import { expect } from 'chai'; -import { render, Component } from 'preact'; +import { h, render, Component } from 'preact'; import Provider from '../src/Provider'; import connect from '../src/connect'; @@ -8,7 +9,7 @@ describe('MobX Provider', () => { let container; beforeEach(() => { - container = document.createElement('div') as HTMLElement; + container = document.createElement('div'); container.style.display = 'none'; document.body.appendChild(container); }); @@ -19,7 +20,7 @@ describe('MobX Provider', () => { }); describe('updating state', () => { - let stores: any = observable({ + const stores = observable({ store1: { data: 'one' }, @@ -28,7 +29,7 @@ describe('MobX Provider', () => { } }); - const Statefull = connect(['store1'], class extends Component { + const Statefull = connect(['store1'], class extends Component { render({ store1 }) { const update = () => store1.data = 'Statefull'; @@ -66,7 +67,7 @@ describe('MobX Provider', () => { it('should update a statefull component', () => { render(, container); - const link = container.querySelector('#update') as HTMLElement; + const link = container.querySelector('#update'); link.click(); expect(container.innerHTML).to.equal(''); @@ -75,7 +76,7 @@ describe('MobX Provider', () => { it('should update a stateless component', () => { render(, container); - const link = container.querySelector('#update') as HTMLElement; + const link = container.querySelector('#update'); link.click(); expect(container.innerHTML).to.equal(''); @@ -84,7 +85,7 @@ describe('MobX Provider', () => { it('should update a stateless component with stores', () => { render(, container); - const link = container.querySelector('#update') as HTMLElement; + const link = container.querySelector('#update'); link.click(); expect(container.innerHTML).to.equal(''); @@ -92,7 +93,7 @@ describe('MobX Provider', () => { }); describe('providing/updating stores', () => { - let stores: any = observable({ + const stores = observable({ store1: { data: 'one' }, diff --git a/test/tracking.spec.ts b/test/tracking.spec.js similarity index 96% rename from test/tracking.spec.ts rename to test/tracking.spec.js index 1a42f0c..3043a92 100644 --- a/test/tracking.spec.ts +++ b/test/tracking.spec.js @@ -1,3 +1,4 @@ +import 'mocha'; import { expect } from 'chai'; import { trackComponents } from '../src/makeReactive'; diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index be5c272..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "allowJs": true, - "allowSyntheticDefaultImports": true, - "sourceMap": false, - "outDir": "./lib", - "lib": [ - "es6", - "es7", - "dom" - ], - "types": [ - "node", - "chai", - "mocha" - ], - "jsx": "preserve", - "noUnusedLocals": true, - "strictNullChecks": false, - "removeComments": false - }, - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules" - ] -} diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 900bbdf..0000000 --- a/tslint.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "extends": "tslint:latest", - "rules": { - "quotemark": ["single"], - "forin": false, - "no-empty": false, - "indent": [ - "tabs" - ], - "arrow-parens": false, - "trailing-comma": false, - "ordered-imports": false, - "no-namespace": false, - "variable-name": [ - false - ], - "object-literal-sort-keys": false, - "no-debugger": false, - "interface-name": [ - false - ], - "max-classes-per-file": false, - "max-line-length": [ - false - ], - "member-ordering": false, - "only-arrow-functions": false, - "member-access": [ - false - ], - "one-variable-per-declaration": false, - "no-unused-expression": false, - "no-bitwise": false, - "prefer-for-of": false - } -}