Skip to content

Commit 4f2c9af

Browse files
committed
UPDATE: add an example for preact integration
1 parent 478332c commit 4f2c9af

File tree

12 files changed

+401
-5
lines changed

12 files changed

+401
-5
lines changed

.babelrc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"presets": [
3+
"env",
4+
"preact"
5+
],
6+
"plugins": [
7+
"transform-decorators-legacy",
8+
"transform-class-properties",
9+
[
10+
"transform-object-rest-spread",
11+
{
12+
"useBuiltIns": true
13+
}
14+
],
15+
[
16+
"transform-react-jsx",
17+
{
18+
"pragma": "h"
19+
}
20+
]
21+
]
22+
}

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ npm-debug.log*
55
yarn-debug.log*
66
yarn-error.log*
77

8+
/dist
9+
package-lock.json
10+
*.map
11+
.npmrc
12+
813
# Runtime data
914
pids
1015
*.pid

example/preact/.babelrc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"presets": [
3+
"env",
4+
"preact"
5+
],
6+
"plugins": [
7+
"transform-decorators-legacy",
8+
"transform-class-properties",
9+
[
10+
"transform-object-rest-spread",
11+
{
12+
"useBuiltIns": true
13+
}
14+
],
15+
[
16+
"transform-react-jsx",
17+
{
18+
"pragma": "h"
19+
}
20+
]
21+
]
22+
}

example/preact/.gitignore

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
dist
8+
/dist
9+
package-lock.json
10+
*.map
11+
# Runtime data
12+
pids
13+
*.pid
14+
*.seed
15+
*.pid.lock
16+
17+
# Directory for instrumented libs generated by jscoverage/JSCover
18+
lib-cov
19+
20+
# Coverage directory used by tools like istanbul
21+
coverage
22+
23+
# nyc test coverage
24+
.nyc_output
25+
26+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
27+
.grunt
28+
29+
# Bower dependency directory (https://bower.io/)
30+
bower_components
31+
32+
# node-waf configuration
33+
.lock-wscript
34+
35+
# Compiled binary addons (https://nodejs.org/api/addons.html)
36+
build/Release
37+
38+
# Dependency directories
39+
node_modules/
40+
jspm_packages/
41+
42+
# TypeScript v1 declaration files
43+
typings/
44+
45+
# Optional npm cache directory
46+
.npm
47+
48+
# Optional eslint cache
49+
.eslintcache
50+
51+
# Optional REPL history
52+
.node_repl_history
53+
54+
# Output of 'npm pack'
55+
*.tgz
56+
57+
# Yarn Integrity file
58+
.yarn-integrity
59+
60+
# dotenv environment variables file
61+
.env
62+
63+
# next.js build output
64+
.next

example/preact/ExamplePage.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { h } from 'preact';
2+
import component from '@js-factory/hoc/preact/component';
3+
4+
@component({
5+
beforeRender() {
6+
console.log('before render');
7+
},
8+
afterRender({ sayHello }) {
9+
console.log('afterRender');
10+
},
11+
state: {
12+
x: 1,
13+
},
14+
instanceProps: {
15+
y: 1
16+
},
17+
eventHandlers: {
18+
onClickHandler({ x, onClickAction, setState, getInstanceProps, setInstanceProps }) {
19+
const { y } = getInstanceProps();
20+
setInstanceProps({ y: y + 1 });
21+
setState({ x: x + 1 });
22+
}
23+
},
24+
template: (props) => {
25+
const { x, onClickHandler, getInstanceProps, state } = props;
26+
console.log('state', x);
27+
console.log('instance prop', getInstanceProps())
28+
return (
29+
<div>
30+
<h1>Example Page</h1>
31+
<button onClick={onClickHandler}>Increment</button>
32+
<p>state - {x}</p>
33+
<p>ip - {getInstanceProps().y}</p>
34+
</div>
35+
);
36+
}
37+
})
38+
export default class ExamplePage { }

example/preact/browser.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { h, render } from 'preact';
2+
import ExamplePage from './ExamplePage';
3+
4+
render(
5+
<div>
6+
<ExamplePage />
7+
</div>
8+
,
9+
document.body
10+
);

example/preact/package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"main": "webpack.config.js",
3+
"scripts": {
4+
"ex": "webpack --mode production --watch --progress",
5+
"server": "webpack-dev-server --open"
6+
},
7+
"dependencies": {
8+
"preact": "^8.4.2",
9+
"webpack": "^4.27.1",
10+
"webpack-dev-server": "^3.1.10"
11+
},
12+
"devDependencies": {
13+
"babel-cli": "^6.26.0",
14+
"babel-core": "^6.26.3",
15+
"babel-eslint": "^8.2.6",
16+
"babel-loader": "^7.1.5",
17+
"babel-plugin-transform-class-properties": "^6.24.1",
18+
"babel-plugin-transform-decorators": "^6.24.1",
19+
"babel-plugin-transform-decorators-legacy": "^1.3.5",
20+
"babel-plugin-transform-object-rest-spread": "^6.26.0",
21+
"babel-plugin-transform-react-jsx": "^6.24.1",
22+
"babel-preset-env": "^1.7.0",
23+
"babel-preset-es2017": "^6.24.1",
24+
"babel-preset-preact": "^1.1.0",
25+
"babel-preset-stage-3": "^6.24.1",
26+
"webpack-cli": "^3.1.2"
27+
}
28+
}

example/preact/webpack.config.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const webpack = require('webpack');
2+
const path = require('path');
3+
4+
const config = [
5+
{
6+
devtool: 'source-map',
7+
entry: './browser.js',
8+
module: {
9+
rules: [
10+
{
11+
test: /\.js$/,
12+
use: "babel-loader"
13+
}
14+
]
15+
},
16+
devServer: {
17+
contentBase: path.join(__dirname, 'dist'),
18+
compress: true,
19+
port: 9000
20+
}
21+
}
22+
];
23+
24+
module.exports = config;

integration/preact/component.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/**
2+
* It's a higher order component provides a wrapper around given preact component `InnerComponent`
3+
*/
4+
5+
import { h, Component } from 'preact';
6+
import getProps from '../util/getProps';
7+
import bindHandler from '../util/bindHandler';
8+
9+
10+
/**
11+
* @description
12+
* This function creates a new object to handle instance properties
13+
* @param {*} instanceProps - Instance Properties to be passed as props
14+
*/
15+
function attachInstanceProps(instanceProps) {
16+
if (Object.prototype.toString.call(instanceProps) !== '[object Object]') {
17+
return {};
18+
}
19+
let localProps = instanceProps;
20+
return {
21+
getInstanceProps() {
22+
return localProps;
23+
},
24+
setInstanceProps(newProps) {
25+
localProps = {
26+
...localProps,
27+
...newProps
28+
};
29+
}
30+
}
31+
};
32+
33+
function exec(fn, ...rest) {
34+
return fn && fn.apply(this, rest);
35+
}
36+
37+
function hasWatches(watcher) {
38+
return storeActivated && watcher.length;
39+
}
40+
41+
let store = {};
42+
let storeActivated = false;
43+
44+
export default function component(options = {}) {
45+
const {
46+
state,
47+
template,
48+
templates,
49+
afterRender,
50+
afterUpdate,
51+
beforeRender,
52+
beforeUpdate,
53+
beforeUnmount,
54+
eventHandlers,
55+
instanceProps
56+
} = options;
57+
let { actions, watcher } = options;
58+
return function wrapper(InnerComponent) {
59+
return class Wrapper extends Component {
60+
constructor(props, context = {}) {
61+
super(props, context);
62+
const { actions: storeActions, watcher: storeWatcher = [] } = props;
63+
const setState = (props) => this.setState({ ...props });
64+
watcher = storeWatcher || watcher;
65+
actions = storeActions || actions;
66+
this.state = state || {};
67+
this.handlers = { setState };
68+
this.storeAttached = hasWatches(watcher);
69+
70+
// Attach handlers
71+
bindHandler.call(this, eventHandlers, this.bindLifecycleHandlers);
72+
bindHandler.call(this, actions, store.action);
73+
74+
// Bind lifecycle methods
75+
beforeRender && (this.componentWillMount = this.bindLifecycleHandlers(beforeRender, this));
76+
beforeUpdate && (this.componentWillUpdate = this.bindLifecycleHandlers(beforeUpdate, this));
77+
afterUpdate && (this.componentDidUpdate = this.bindLifecycleHandlers(afterUpdate, this));
78+
instanceProps && (this.instanceProps = attachInstanceProps(instanceProps));
79+
80+
let globalState = this.storeAttached ? getProps(watcher)(store ? store.getState() : {}, props) : {};
81+
const updateStore = () => {
82+
if (!this.storeAttached) {
83+
return;
84+
}
85+
let localState = getProps(watcher)(store ? store.getState() : {}, this.props);
86+
// if store value does change, do not call update
87+
for (let i in localState) if (localState[i] !== globalState[i]) {
88+
globalState = localState;
89+
return this.setState(null);
90+
}
91+
// if above condition fails & store contains additional props
92+
// update & cause re-render
93+
for (let i in globalState) if (!(i in localState)) {
94+
globalState = localState;
95+
return this.setState(null);
96+
}
97+
};
98+
99+
this.mergeProps = () => {
100+
const { state, handlers, instanceProps } = this;
101+
return {
102+
...state,
103+
...props,
104+
...globalState,
105+
...handlers,
106+
...instanceProps
107+
};
108+
}
109+
110+
this.componentDidMount = () => {
111+
exec.call(this, afterRender, this.mergeProps());
112+
if (this.storeAttached) {
113+
store.subscribe(updateStore);
114+
}
115+
}
116+
117+
this.componentWillUnmount = () => {
118+
exec.call(this, beforeUnmount, this.mergeProps());
119+
if (this.storeAttached) {
120+
store.unsubscribe(updateStore);
121+
}
122+
}
123+
124+
this.render = (props) => {
125+
const view = template || templates || InnerComponent;
126+
return h(view, this.mergeProps());
127+
};
128+
}
129+
bindLifecycleHandlers(func, context) {
130+
return func && function () {
131+
const { state, props, handlers, instanceProps, storeAttached } = this;
132+
let globalState = storeAttached ? getProps(watcher)(store ? store.getState() : {}) : {};
133+
func.apply(null, [{ ...state, ...props, ...instanceProps, ...handlers, ...globalState }, ...arguments]);
134+
}.bind(context);
135+
}
136+
};
137+
};
138+
}
139+
140+
export function injectStore(appStore) {
141+
store = appStore;
142+
storeActivated = true;
143+
}
144+

integration/util/bindHandler.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
function hasHandlers(handlers) {
3+
return handlers && Object.keys(handlers).length > 0;
4+
}
5+
6+
function bindHandlers(handlers, modifier) {
7+
hasHandlers(handlers) && Object.keys(handlers).map((key) => {
8+
const handler = handlers[key];
9+
if (typeof handler === 'function') {
10+
this.handlers[key] = modifier(handler, this);
11+
}
12+
});
13+
}
14+
15+
export default bindHandlers;

integration/util/getProps.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const noop = () => ({});
2+
export default function getProps(properties = []) {
3+
if (properties.length === 0) return noop;
4+
return state =>
5+
properties.reduce((selected, key) => {
6+
selected[key] = state[key];
7+
return selected;
8+
}, {});
9+
};

0 commit comments

Comments
 (0)