├── .gitignore
├── src
├── preact-create-element.js
├── inferno-create-element.js
├── react-create-element.js
├── preact.js
├── hyperscript.js
├── inferno.js
├── react.js
├── deku-create-element.js
├── bel.js
├── store.js
├── deku.js
├── hyperscript-create-element.js
└── bel-create-element.js
├── .babelrc
├── webpack.bel.config.js
├── webpack.deku.config.js
├── webpack.inferno.config.js
├── webpack.preact.config.js
├── webpack.react.config.js
├── webpack.hyperscript.config.js
├── components
├── Button.js
└── Hello.js
├── webpack.config.js
├── dist
└── index.html
├── README.md
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist/*.js
3 |
--------------------------------------------------------------------------------
/src/preact-create-element.js:
--------------------------------------------------------------------------------
1 |
2 | const h = require('preact').h
3 | module.exports = h
4 |
--------------------------------------------------------------------------------
/src/inferno-create-element.js:
--------------------------------------------------------------------------------
1 | const h = require('inferno-create-element')
2 | module.exports = h
3 |
--------------------------------------------------------------------------------
/src/react-create-element.js:
--------------------------------------------------------------------------------
1 |
2 | const h = require('react').createElement
3 | module.exports = h
4 |
--------------------------------------------------------------------------------
/src/preact.js:
--------------------------------------------------------------------------------
1 |
2 | import { render } from 'preact'
3 | import Hello from '../components/Hello'
4 |
5 | const div = document.getElementById('preact')
6 | render(, div)
7 |
8 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "stage-0"
5 | ],
6 | "plugins": [
7 | [
8 | "transform-react-jsx",
9 | { "pragma": "h" }
10 | ]
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/src/hyperscript.js:
--------------------------------------------------------------------------------
1 |
2 | import Hello from '../components/Hello'
3 |
4 | const div = document.getElementById('hyperscript')
5 | const tree =
6 |
7 | div.appendChild(tree)
8 |
9 |
--------------------------------------------------------------------------------
/src/inferno.js:
--------------------------------------------------------------------------------
1 | import InfernoDOM from 'inferno-dom'
2 | import Hello from '../components/Hello'
3 |
4 | const div = document.getElementById('inferno')
5 | InfernoDOM.render(, div)
6 |
--------------------------------------------------------------------------------
/src/react.js:
--------------------------------------------------------------------------------
1 |
2 | // import React from 'react'
3 | import ReactDOM from 'react-dom'
4 | import Hello from '../components/Hello'
5 |
6 | const div = document.getElementById('react')
7 | ReactDOM.render(, div)
8 |
9 |
--------------------------------------------------------------------------------
/webpack.bel.config.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path')
3 | const webpack = require('webpack')
4 | const config = require('./webpack.config')
5 |
6 | config.entry.bel = './src/bel.js'
7 |
8 | config.plugins = [
9 | new webpack.ProvidePlugin({
10 | h: path.resolve('./src/bel-create-element')
11 | })
12 | ]
13 |
14 | module.exports = config
15 |
16 |
--------------------------------------------------------------------------------
/webpack.deku.config.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path')
3 | const webpack = require('webpack')
4 | const config = require('./webpack.config')
5 |
6 | config.entry.deku = './src/deku.js'
7 |
8 | config.plugins = [
9 | new webpack.ProvidePlugin({
10 | h: path.resolve('./src/deku-create-element')
11 | })
12 | ]
13 |
14 | module.exports = config
15 |
16 |
--------------------------------------------------------------------------------
/webpack.inferno.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const config = require('./webpack.config')
4 |
5 | config.entry.inferno = './src/inferno.js'
6 |
7 | config.plugins = [
8 | new webpack.ProvidePlugin({
9 | h: path.resolve('./src/inferno-create-element')
10 | })
11 | ]
12 |
13 | module.exports = config
14 |
15 |
--------------------------------------------------------------------------------
/webpack.preact.config.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path')
3 | const webpack = require('webpack')
4 | const config = require('./webpack.config')
5 |
6 | config.entry.preact = './src/preact.js'
7 |
8 | config.plugins = [
9 | new webpack.ProvidePlugin({
10 | h: path.resolve('./src/preact-create-element')
11 | })
12 | ]
13 |
14 | module.exports = config
15 |
16 |
--------------------------------------------------------------------------------
/webpack.react.config.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path')
3 | const webpack = require('webpack')
4 | const config = require('./webpack.config')
5 |
6 | config.entry.react = './src/react.js'
7 |
8 | config.plugins = [
9 | new webpack.ProvidePlugin({
10 | h: path.resolve('./src/react-create-element')
11 | })
12 | ]
13 |
14 | module.exports = config
15 |
16 |
--------------------------------------------------------------------------------
/src/deku-create-element.js:
--------------------------------------------------------------------------------
1 |
2 | // Not working
3 |
4 | const h = require('deku').element
5 |
6 | module.exports = (tag, props, ...children) => {
7 | if (!children) {
8 | console.log('children', children)
9 | }
10 | if (typeof tag === 'function') {
11 | return {
12 | render: tag // ({ ...props, ...children })
13 | }
14 | }
15 |
16 | return h(tag, props, ...children)
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/webpack.hyperscript.config.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path')
3 | const webpack = require('webpack')
4 | const config = require('./webpack.config')
5 |
6 | config.entry.hyperscript = './src/hyperscript.js'
7 |
8 | config.plugins = [
9 | new webpack.ProvidePlugin({
10 | h: path.resolve('./src/hyperscript-create-element')
11 | // h: 'hyperscript'
12 | })
13 | ]
14 |
15 | module.exports = config
16 |
17 |
--------------------------------------------------------------------------------
/components/Button.js:
--------------------------------------------------------------------------------
1 |
2 | const sx = {
3 | fontFamily: 'inherit',
4 | fontSize: 14,
5 | display: 'inline-block',
6 | padding: 8,
7 | margin: 0,
8 | color: 'white',
9 | backgroundColor: 'black',
10 | border: 0,
11 | borderRadius: 3,
12 | WebkitAppearance: 'none',
13 | MozAppearance: 'none',
14 | cursor: 'pointer'
15 | }
16 |
17 | const Button = ({ children, ...props }) => (
18 |
19 | )
20 |
21 | export default Button
22 |
23 |
--------------------------------------------------------------------------------
/src/bel.js:
--------------------------------------------------------------------------------
1 |
2 | // import { update } from 'yo'
3 | import Hello from '../components/Hello'
4 | import Button from '../components/Button'
5 | import readme from '../README.md'
6 |
7 | const div = document.getElementById('bel')
8 |
9 | const tree =
10 | const btn = tree.querySelector('button')
11 | div.appendChild(tree)
12 |
13 | const sx = {
14 | padding: 32,
15 | maxWidth: 640
16 | }
17 | const content =
18 | content.innerHTML = readme
19 | document.body.appendChild(content)
20 |
21 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path')
3 |
4 | const config = {
5 | entry: {},
6 | output: {
7 | path: path.join(__dirname, 'dist'),
8 | filename: '[name].js'
9 | },
10 | module: {
11 | resolve: {
12 | root: [
13 | path.resolve('./src'),
14 | path.resolve('./components'),
15 | ]
16 | },
17 | loaders: [
18 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
19 | { test: /\.md/, loader: 'html!highlight!markdown' }
20 | ]
21 | },
22 | devServer: {
23 | contentBase: 'dist'
24 | }
25 | }
26 |
27 | module.exports = config
28 |
29 |
--------------------------------------------------------------------------------
/components/Hello.js:
--------------------------------------------------------------------------------
1 |
2 | import Button from './Button'
3 |
4 | const handleClick = lib => e => {
5 | console.log('Hello', lib)
6 | alert(`Hello ${lib}`)
7 | }
8 |
9 | const Hello = ({ lib }) => {
10 | const sx = {
11 | root: {
12 | padding: 16,
13 | backgroundColor: '#f6f6f6'
14 | },
15 | heading: {
16 | marginTop: 0
17 | }
18 | }
19 | return (
20 |
22 |
Hello {lib}
23 |
27 |
28 | )
29 | }
30 |
31 | export default Hello
32 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Universal Components
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 |
2 | let _state = {}
3 | let _reducer = () => _state
4 | const store = {
5 | get state () {
6 | return _state
7 | },
8 | set reducer (f) {
9 | _state = f(undefined, {})
10 | _reducer = f
11 | },
12 | dispatch: (action) => {
13 | _state = {
14 | ..._state,
15 | ..._reducer(_state, action)
16 | }
17 | store.listener(_state)
18 | },
19 | listener: () => {}
20 | }
21 |
22 | export const INC = 'INC'
23 | export const DEC = 'DEC'
24 |
25 | store.reducer = (state = {
26 | count: 0
27 | }, action) => {
28 | switch (action.type) {
29 | case INC:
30 | return {
31 | ...state,
32 | count: state.count + 1
33 | }
34 | case DEC:
35 | return {
36 | ...state,
37 | count: state.count - 1
38 | }
39 | default:
40 | return state
41 | }
42 | }
43 |
44 | export default store
45 |
46 |
--------------------------------------------------------------------------------
/src/deku.js:
--------------------------------------------------------------------------------
1 |
2 | import { createApp } from 'deku'
3 | import Hello from '../components/Hello'
4 | import Button from '../components/Button'
5 |
6 | let _state = {}
7 | let _reducer = () => _state
8 | const store = {
9 | get state () {
10 | return _state
11 | },
12 | set reducer (f) {
13 | _state = f(undefined, {})
14 | _reducer = f
15 | },
16 | dispatch: (action) => {
17 | _state = {
18 | ..._state,
19 | ..._reducer(_state, action)
20 | }
21 | store.listener(_state)
22 | },
23 | listener: () => {}
24 | }
25 |
26 | store.reducer = (state = {
27 | lib: 'Deku'
28 | }, action) => {
29 | switch (action.type) {
30 | default:
31 | return state
32 | }
33 | }
34 |
35 | const div = document.getElementById('deku')
36 | const render = createApp(div, store.dispatch)
37 |
38 | console.log('jsx test', render(
39 |
40 |
Hello
41 |
42 |
43 | ))
44 | // console.log('jsx test', render())
45 |
46 | // render(, store.state)
47 |
48 |
--------------------------------------------------------------------------------
/src/hyperscript-create-element.js:
--------------------------------------------------------------------------------
1 |
2 | const h = require('hyperscript').context()
3 | const addPx = require('add-px-to-style')
4 |
5 | const parseValue = (prop, val) => typeof val === 'number' ? addPx(prop, val) : val
6 | const kebab = (str) => str.replace(/([A-Z])/g, g => '-' + g.toLowerCase())
7 |
8 | const transformProps = (props) => {
9 | if (props.style && typeof props.style === 'object') {
10 | Object.keys(props.style)
11 | .forEach(key => {
12 | const kebabkey = kebab(key)
13 | props.style[kebabkey] = parseValue(key, props.style[key])
14 | if (kebabkey !== key) {
15 | delete props.style[key]
16 | }
17 | })
18 | }
19 |
20 | for (let key in props) {
21 | if (/^on/.test(key)) {
22 | const lowerkey = key.toLowerCase()
23 | if (lowerkey !== key) {
24 | props[lowerkey] = props[key]
25 | delete props[key]
26 | }
27 | }
28 | }
29 | return props
30 | }
31 |
32 | module.exports = (tag, props, ...children) => {
33 | if (props && props.children) {
34 | children = props.children
35 | delete props.children
36 | }
37 |
38 | props = transformProps(props || {})
39 |
40 | if (typeof tag === 'function') {
41 | props.children = children
42 | return tag(props, ...children)
43 | }
44 | return h(tag, props, ...children)
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/src/bel-create-element.js:
--------------------------------------------------------------------------------
1 |
2 | const createElement = require('bel').createElement
3 | const addPx = require('add-px-to-style')
4 |
5 | const parseValue = (prop, val) => typeof val === 'number' ? addPx(prop, val) : val
6 | const kebab = (str) => str.replace(/([A-Z])/g, g => '-' + g.toLowerCase())
7 | const styleToString = (style) => {
8 | return Object.keys(style)
9 | .filter(key => style[key] !== null)
10 | .map(key => `${kebab(key)}:${parseValue(key, style[key])}`)
11 | .join(';')
12 | }
13 |
14 | const transformProps = (props) => {
15 | if (props.style && typeof props.style === 'object') {
16 | props.style = styleToString(props.style)
17 | }
18 |
19 | for (let key in props) {
20 | if (/^on/.test(key)) {
21 | const lowerkey = key.toLowerCase()
22 | if (lowerkey !== key) {
23 | props[lowerkey] = props[key]
24 | delete props[key]
25 | }
26 | }
27 | }
28 | return props
29 | }
30 |
31 | const h = (tag, props = {}, ...children) => {
32 | if (props && props.children) {
33 | children = props.children
34 | delete props.children
35 | }
36 |
37 | props = transformProps(props || {})
38 |
39 | if (typeof tag === 'function') {
40 | props = props || {}
41 | props.children = children
42 | const root = tag(props, ...children)
43 | return root
44 | }
45 |
46 | return createElement(tag, props || {}, children)
47 | }
48 | module.exports = h
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Universal Components
3 |
4 | An experiment in creating library-agnostic universal functional UI components
5 |
6 | http://jxnblk.com/universal-components
7 |
8 | ```sh
9 | npm i
10 | npm run build
11 | ```
12 |
13 | This repo has two generic functional UI components in the [`/components`](/components) folder.
14 |
15 | ```js
16 | // Button.js
17 | const sx = {
18 | fontFamily: 'inherit',
19 | fontSize: 14,
20 | display: 'inline-block',
21 | padding: 8,
22 | margin: 0,
23 | color: 'white',
24 | backgroundColor: 'black',
25 | border: 0,
26 | borderRadius: 3,
27 | WebkitAppearance: 'none',
28 | MozAppearance: 'none',
29 | cursor: 'pointer'
30 | }
31 |
32 | const Button = ({ ...props }) => (
33 |
34 | )
35 |
36 | export default Button
37 | ```
38 |
39 | ```js
40 | // Hello.js
41 | import Button from './Button'
42 |
43 | const handleClick = lib => e => {
44 | console.log('Hello', lib)
45 | alert(`Hello ${lib}`)
46 | }
47 |
48 | const Hello = ({ lib }) => {
49 | const sx = {
50 | root: {
51 | padding: 16,
52 | backgroundColor: '#f6f6f6'
53 | },
54 | heading: {
55 | marginTop: 0
56 | }
57 | }
58 | return (
59 |
61 |
Hello {lib}
62 |
66 |
67 | )
68 | }
69 |
70 | export default Hello
71 | ```
72 |
73 | These components are rendered by five different libraries:
74 |
75 | - React
76 | - Preact
77 | - Hyperscript
78 | - Bel
79 | - Inferno
80 |
81 | ---
82 |
83 | ### To do:
84 |
85 | - [ ] Hook up a generic store
86 |
87 | MIT License
88 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "universal-components",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "react": "webpack -p --config webpack.react.config.js",
8 | "preact": "webpack -p --config webpack.preact.config.js",
9 | "hyperscript": "webpack -p --config webpack.hyperscript.config.js",
10 | "bel": "webpack -p --config webpack.bel.config.js",
11 | "inferno": "webpack -p --config webpack.inferno.config.js",
12 | "react-dev": "webpack-dev-server --config webpack.react.config.js",
13 | "preact-dev": "webpack-dev-server --config webpack.preact.config.js",
14 | "hyperscript-dev": "webpack-dev-server --config webpack.hyperscript.config.js",
15 | "bel-dev": "webpack-dev-server --config webpack.bel.config.js",
16 | "deku-dev": "webpack-dev-server --config webpack.deku.config.js",
17 | "inferno-dev": "webpack-dev-server --config webpack.inferno.config.js",
18 | "build": "mkdir -p dist && npm run react && npm run preact && npm run hyperscript && npm run bel && npm run inferno",
19 | "gh-pages": "gh-pages -d dist"
20 | },
21 | "keywords": [],
22 | "author": "",
23 | "license": "MIT",
24 | "devDependencies": {
25 | "add-px-to-style": "^1.0.0",
26 | "babel-loader": "^6.2.4",
27 | "babel-plugin-transform-react-jsx": "^6.8.0",
28 | "babel-preset-es2015": "^6.9.0",
29 | "babel-preset-stage-0": "^6.5.0",
30 | "babel-register": "^6.9.0",
31 | "bel": "^4.4.2",
32 | "deku": "^2.0.0-rc16",
33 | "gh-pages": "^0.11.0",
34 | "highlight-loader": "^0.7.2",
35 | "html-loader": "^0.4.3",
36 | "hyperscript": "^1.4.7",
37 | "inferno-create-element": "^0.7.24",
38 | "inferno-dom": "^0.7.24",
39 | "markdown-loader": "^0.1.7",
40 | "preact": "^4.8.0",
41 | "react": "^15.2.0",
42 | "react-dom": "^15.2.1",
43 | "webpack": "^1.13.1",
44 | "webpack-dev-server": "^1.14.1",
45 | "yo-yo": "^1.2.2"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------