Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/kwasniew/hyperapp2-real-world-example
https://hyperapp2.netlify.com/
https://github.com/kwasniew/hyperapp2-real-world-example
Last synced: 2 months ago
JSON representation
https://hyperapp2.netlify.com/
- Host: GitHub
- URL: https://github.com/kwasniew/hyperapp2-real-world-example
- Owner: kwasniew
- Created: 2019-07-06T14:47:10.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2022-07-18T10:49:27.000Z (over 2 years ago)
- Last Synced: 2024-08-03T23:02:40.607Z (6 months ago)
- Language: JavaScript
- Homepage:
- Size: 1010 KB
- Stars: 15
- Watchers: 6
- Forks: 6
- Open Issues: 14
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- hyperawesome - RealWorld with Hyperapp
README
# Real Word Example App in Hyperapp v2
* 1280 LOC
* 14.2kB JS bundle
* instant dev mode (snowpack)
* instant prod mode (esbuild)
* because life is too short for big frameworks and slow tooling## Design decisions
### Zero-dependency micro libraries
For production dependencies prefer tiny zero-dependency libraries because:
* you can reason about their sources w/o being a core contributor
* they provide more stable abstraction and do one thing well
* they are easier to compose
* the app loads fast by default
* they don't bring their whole family to the party :)Examples (minified+gzipped):
* [hyperapp](https://bundlephobia.com/[email protected]) 1.9kB
* [hyperlit](https://bundlephobia.com/[email protected]) 0.8kB
* [hyperapp-fx](https://bundlephobia.com/[email protected]) 1.6kB
* [snarkdown](https://bundlephobia.com/[email protected]) 1.0kB
* [page](https://bundlephobia.com/[email protected]) 3.9kB (this one has 1 dependency)### Development without tooling
Point your static web server to your src directory and you're ready to go. Startup time in milliseconds.
No bundler, no transpiler, no file watcher needed. Just your web server, static files and a refresh button.
View-source driven development in its finest form.Common obstacles:
* browsers don't understand CommonJS syntax, so choose libraries with native ES6 modules support and write fully qualified
imports in your source code
* JSX requires transpilation, so use JavaScript (e.g. tagged templates in [hyperlit](https://github.com/zaceno/hyperlit) library)
* some transitive dependencies are not ES6 compliant, so translate npm dependencies for the browser at installation time with [snowpack](https://github.com/pikapkg/snowpack)
* IDEs/text editors can't handle tagged templates formatting, so use prettier which does it pretty well### Functional thinking over OO thinking
Most important functional concepts in our codebase (by convention since language can't make those guarantees):
* all JS functions w/o side effects
* one centralized immutable state and one way data flow inspired by the Elm Architecture
* view as a function of state
* actions implemented with [effects as data](https://github.com/okwolf/hyperapp-fx) so no callbacks/Promises/async/await in the
user spaceI find it easier to reason about and refactor functional code.
### Minimal logic in view functions
* View functions should mostly check the presence of fields and iterate over lists
* Try to move your logic to the actions
* If there's some derived state to compute, create selector functionsAdditionally this project uses functions with explicit input parameters destructuring. Tradeoff: more boilerplate but
no magic.### Navigation pattern
We're doing client-side routing to be compliant with the RealWorld app spec. Personally I'd prefer server-side
navigation between pages (less client side code, already implemented by the browser, no back-button amnesia etc.).This app's navigation flow:
* open some URL (directly in a navigation bar or with a link)
* client-side router triggers page init action
* init action sets immediate state and renders initial view
* if needed, init action triggers the effect to fetch server data
* when the server data arrives view rendering completesAbility to trigger action that sets immediate initial state removes unnecessary null checks
in the view code.### Business centric code organization
Main application pages are split into individual files (pages/article.js, pages/editor.js etc.) with shared fragments
extracted into a separate directory (pages/fragments).Inside each page file we have the following structure:
```
// Actions & Effects
// ------------------
// actions and effects specific to a page
// export loadPage action for a page// Views
// ------------------
// view fragments specific to a page
// selectors used by view fragments
// export main view function for a page
```
Since views and actions change together we keep them together (common closure principle). I find it easier to
refactor code this way.Also when we need to delete a business feature it's just a matter of deleting one file.
Finally, I prefer to keep folders relatively flat as too much nesting makes it hard for me to find things.
### Keeping language features to a minimum
Hyperapp makes it easy to use the small subset of standard JS while skipping the bad parts and nonstandard extensions.
Things I don't use and what is used instead:
* this, new, classes -> functions and object literals
* callbacks, promises, async/await, generators -> effects as data
* imperative loops -> Array.map/filter/find
* let, var -> const
* JSX -> tagged templates
* state hooks, reactive assignments -> Elm Architecture### Build tools as a production optimisation step
esbuild is used to create ES6 minified/tree-shaken bundle (even faster load time).
Raw source size: < 1300 LOC (60% of the Hyperapp v1 version)
Minified+gzipped bundle: 13.8kB (used to be 28.5kB in v1)
Lighthouse score: 98 (with CSS being a constraint)
It makes it one of the smallest and fastest to load versions of RealWorld app.
When comparing different implementations remember that:
* code formatting matters
* some versions are more buggy than the others and do less error handling
* some version lack features### No code splitting
I've run some experiments with this codebase and code splitting per route/page brings only minor improvements over single bundle when visiting a home page.
Single bundle minifies/gzips more efficiently so we ship fewer bytes overall when we navigate to other pages.