├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── prettier.config.js ├── scripts └── gh_pages.sh ├── src ├── actions │ └── index.js ├── components │ ├── Counter.js │ └── Description.js ├── index.html ├── index.js └── state │ └── index.js ├── styles └── app.css └── test ├── actions.test.js ├── counter.test.js └── description.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | *.log 4 | coverage 5 | *.DS_Store 6 | .cache/ 7 | dist/ 8 | .parcel-cache 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | - "12" 5 | - "14" 6 | 7 | cache: 8 | directories: 9 | - "node_modules" 10 | 11 | notifications: 12 | email: false 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018 Regis Boudinot (selfup) https://selfup.me 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyperapp One 2 | 3 | [![Build Status](https://travis-ci.org/selfup/hyperapp-one.svg?branch=master)](https://travis-ci.org/selfup/hyperapp-one) [![Slack](https://hyperappjs.herokuapp.com/badge.svg)](https://hyperappjs.herokuapp.com 'Join us') 4 | 5 | Hyperapp One is a Parcel boilerplate for quickstarting a web application with [Hyperapp V1](https://github.com/jorgebucaran/hyperapp/tree/V1), JSX, and Prettier. 6 | 7 |
8 | 9 | 10 | 11 |
12 | 13 | Redux Dev Tools support is included as well :smile: 14 | 15 | 16 | 17 |

Table of Contents

18 | 19 | 20 | 21 | - [Installing](#installing) 22 | - [Testing](#testing) 23 | - [Development](#development) 24 | - [Wiping the Commit History](#wiping-the-commit-history) 25 | - [Deploying to GitHub Pages](#deploying-to-github-pages) 26 | - [First Time](#first-time) 27 | - [Every Time After](#every-time-after) 28 | - [License](#license) 29 | 30 | 31 | 32 | ## Installing 33 | 34 | ```bash 35 | git clone https://github.com/selfup/hyperapp-one 36 | cd hyperapp-one 37 | npm install 38 | npm start 39 | ``` 40 | 41 | ## Testing 42 | 43 | ```bash 44 | npm test 45 | ``` 46 | 47 | All tests are in the root `test` directory. :tada: 48 | 49 | ## Development 50 | 51 | Access [localhost:1234](http://localhost:1234). 52 | 53 | The browser will reload as you save new code. 🚀 54 | 55 | Now go code something awesome! 56 | 57 | ### Wiping the Commit History 58 | 59 | Make sure you are in the boilerplate root and run: 60 | 61 | ```bash 62 | npm run wipe 63 | ``` 64 | 65 | Add your remote: 66 | 67 | ```bash 68 | git remote add origin 69 | ``` 70 | 71 | Then work as usual. 72 | 73 | Or if you prefer to do it yourself from scratch: 74 | 75 | ```bash 76 | rm -rf .git 77 | git init 78 | git add . 79 | git commit -m "initial commit" 80 | ``` 81 | 82 | Then add your remote and work from there as usual. 83 | 84 | ```bash 85 | git remote add origin 86 | ``` 87 | 88 | ### Deploying to GitHub Pages 89 | 90 | This will be interactive as it merges `master` into the `gh-pages` branch :pray: 91 | 92 | ```bash 93 | ./scripts/gh_pages.sh 94 | ``` 95 | 96 | Sometimes it will say there is nothing to commit even though you have more commits. 97 | 98 | Just do a `git push` or a `git push -f` and then check back out to master :pray: 99 | 100 | **Now visit**: 101 | 102 | - No custom domain: `yourUserName.github.io/yourRepoName` 103 | - With a custom domain: `yourCustomDomain/yourRepoName` 104 | 105 | ## License 106 | 107 | Hyperapp One is MIT licensed. See [LICENSE](LICENSE). 108 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperapp-one", 3 | "version": "1.0.1", 4 | "description": "Minimal web app", 5 | "scripts": { 6 | "start": "parcel ./src/index.html", 7 | "build": "parcel build ./src/index.html --public-url ./", 8 | "ghp-build": "parcel build ./src/index.html --dist-dir build/ --public-url ./build && cp ./build/index.html ./index.html", 9 | "wipe": "rm -rf .git && git init && git add . && git commit -m 'initial commit'", 10 | "test": "jest --coverage --no-cache" 11 | }, 12 | "author": "Regis Boudinot (selfup)", 13 | "license": "MIT", 14 | "dependencies": { 15 | "hyperapp": "^1.2.10", 16 | "hyperapp-redux-devtools": "^1.1.6" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.10.5", 20 | "@babel/plugin-transform-react-jsx": "^7.10.4", 21 | "@babel/preset-env": "^7.10.4", 22 | "imports": "^1.0.0", 23 | "jest": "^29.3.1", 24 | "jest-environment-jsdom": "^29.3.1", 25 | "parcel": "^2.8.0" 26 | }, 27 | "babel": { 28 | "presets": [ 29 | "@babel/env" 30 | ], 31 | "plugins": [ 32 | [ 33 | "@babel/transform-react-jsx", 34 | { 35 | "pragma": "h" 36 | } 37 | ] 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'all', 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | arrowParens: true, 7 | }; 8 | -------------------------------------------------------------------------------- /scripts/gh_pages.sh: -------------------------------------------------------------------------------- 1 | GH_PAGES=$(git branch | grep gh-pages) 2 | 3 | function buildCommitAndPushToGhPages() { 4 | git merge master \ 5 | && npm run ghp-build \ 6 | && git add . \ 7 | && git commit -m "built" \ 8 | && git push origin gh-pages -f \ 9 | && git checkout master 10 | } 11 | 12 | echo 'BUILDING AND DEPLOYING' 13 | 14 | if [[ $GH_PAGES != '' ]] 15 | then 16 | git checkout gh-pages && buildCommitAndPushToGhPages 17 | else 18 | git checkout -b gh-pages && buildCommitAndPushToGhPages 19 | fi 20 | 21 | echo 'DEPLOYED!' 22 | -------------------------------------------------------------------------------- /src/actions/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | add: (/* event (e) */) => ({ num }) => ({ num: num + 1 }), 3 | sub: (/* event (e) */) => ({ num }) => ({ num: num - 1 }), 4 | }; 5 | -------------------------------------------------------------------------------- /src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import { h } from 'hyperapp'; 2 | import Description from './Description'; 3 | 4 | /** 5 | * first object in the store is 'state' (an object - {}) 6 | * second object in the store is 'actions' (an object - {}) 7 | * here we destructure what is needed 8 | * 'num' from 'state' and 'add'/'sub' from 'actions' 9 | */ 10 | export default ({ num }, { add, sub }) => ( 11 |
12 | 13 |
14 | 17 |

{num}

18 | 21 |
22 |
23 | ); 24 | -------------------------------------------------------------------------------- /src/components/Description.js: -------------------------------------------------------------------------------- 1 | import { h } from 'hyperapp'; 2 | 3 | export default () => ( 4 |
5 |

hyperapp-one

6 |

7 | With JSX and Parcel 8 |

9 |
10 |
11 | ); 12 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hyperapp One 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { app } from 'hyperapp'; 2 | import '../styles/app.css'; 3 | import actions from './actions'; 4 | import state from './state'; 5 | import view from './components/Counter'; 6 | 7 | const appArgs = [state, actions, view, document.getElementById('app')]; 8 | 9 | function onMount(main) { 10 | const { add, sub } = main; 11 | 12 | /** 13 | * Hyperapp wires your actions so the view is re-rendered every time the state 14 | * changes as a result of calling any action. This object is useful because it 15 | * allows you to talk to your app from another app, framework, vanilla JS, etc. 16 | * 17 | * Here is an example on CodePen: https://codepen.io/selfup/pen/jLMRjO 18 | */ 19 | 20 | setTimeout(add, 1000); 21 | setTimeout(sub, 2000); 22 | } 23 | 24 | let main; 25 | 26 | if (process.env.NODE_ENV !== 'production') { 27 | import('hyperapp-redux-devtools').then((devtools) => { 28 | main = devtools(app)(...appArgs); 29 | 30 | onMount(main); 31 | }); 32 | } else { 33 | main = app(...appArgs); 34 | 35 | onMount(main); 36 | } 37 | -------------------------------------------------------------------------------- /src/state/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | num: 0, 3 | }; 4 | -------------------------------------------------------------------------------- /styles/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | min-height: 100vh; 7 | margin: 0; 8 | font-size: calc(10px + 2vmin); 9 | } 10 | 11 | section { 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | } 16 | 17 | section > * + * { 18 | margin-left: 2rem; 19 | } 20 | 21 | button { 22 | padding: 0 1rem; 23 | border: none; 24 | font-size: 2rem; 25 | height: 3rem; 26 | } 27 | 28 | h1 { 29 | font-size: calc(20px + 6vmin); 30 | margin-top: 0; 31 | margin-bottom: 1rem; 32 | text-align: center; 33 | } 34 | 35 | p { 36 | text-align: center; 37 | margin-top: 0; 38 | } 39 | 40 | .count { 41 | margin-top: 0; 42 | margin-bottom: 0; 43 | font-size: 4rem; 44 | color: #333; 45 | } 46 | 47 | [disabled] + .count { 48 | color: #999; 49 | } 50 | 51 | .add { 52 | background-color: mediumseagreen; 53 | color: #fff; 54 | } 55 | 56 | .sub { 57 | background-color: mediumvioletred; 58 | color: #fff; 59 | } 60 | 61 | .sub[disabled] { 62 | opacity: 0.5; 63 | } 64 | -------------------------------------------------------------------------------- /test/actions.test.js: -------------------------------------------------------------------------------- 1 | import actions from './../src/actions'; 2 | 3 | test('Actions add', () => { 4 | const addResult = actions.add()({ num: 0 }); 5 | 6 | expect(addResult).toEqual({ 7 | num: 1, 8 | }); 9 | }); 10 | 11 | test('Actions sub', () => { 12 | const addResult = actions.sub()({ num: 1 }); 13 | 14 | expect(addResult).toEqual({ 15 | num: 0, 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/counter.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | import { h, app } from 'hyperapp'; 6 | import appActions from './../src/actions'; 7 | import appState from './../src/state'; 8 | import Counter from './../src/components/Counter'; 9 | 10 | // This is not a static component so we inject it into an app and test it 11 | // This is a unit test and we are not testing actual clicks 12 | // We are simply testing via exposed actions 13 | // And checking the DOM for state changes 14 | beforeEach(() => { 15 | document.body.innerHTML = ''; 16 | }); 17 | 18 | test('Counter test', (done) => { 19 | const view = (state, actions) => 20 | h( 21 | 'div', 22 | { 23 | oncreate() { 24 | expect(!document.body.innerHTML.includes('0')).toBe(true); 25 | expect(document.body.innerHTML.includes('1')).toBe(true); 26 | done(); 27 | }, 28 | }, 29 | [Counter(state, actions)], 30 | ); 31 | 32 | const main = app(appState, appActions, view, document.body); 33 | 34 | main.add(); 35 | }); 36 | 37 | test('Counter test', (done) => { 38 | const view = (state, actions) => 39 | h( 40 | 'div', 41 | { 42 | oncreate() { 43 | expect(!document.body.innerHTML.includes('0')).toBe(true); 44 | expect(document.body.innerHTML.includes('-1')).toBe(true); 45 | done(); 46 | }, 47 | }, 48 | [Counter(state, actions)], 49 | ); 50 | 51 | const main = app(appState, appActions, view, document.body); 52 | 53 | main.sub(); 54 | }); 55 | -------------------------------------------------------------------------------- /test/description.test.js: -------------------------------------------------------------------------------- 1 | import Description from './../src/components/Description'; 2 | 3 | // Because this is a static component we just verify the results of h calls 4 | test('Description component renders', () => { 5 | expect(Description()).toEqual({ 6 | children: [ 7 | { 8 | children: ['hyperapp-one'], 9 | nodeName: 'h1', 10 | attributes: {}, 11 | key: null, 12 | }, 13 | { 14 | children: [ 15 | { 16 | children: ['With JSX and Parcel'], 17 | nodeName: 'em', 18 | attributes: {}, 19 | key: null, 20 | }, 21 | ], 22 | nodeName: 'p', 23 | attributes: {}, 24 | key: null, 25 | }, 26 | { 27 | children: [], 28 | nodeName: 'hr', 29 | attributes: {}, 30 | key: null, 31 | }, 32 | ], 33 | nodeName: 'div', 34 | attributes: {}, 35 | key: null, 36 | }); 37 | }); 38 | --------------------------------------------------------------------------------