├── .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 | [](https://travis-ci.org/selfup/hyperapp-one) [](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 |
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 |
15 | -
16 |
17 | {num}
18 |
19 | +
20 |
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 |
--------------------------------------------------------------------------------