At QMENTA we have been rebuilding the front-end from scratch, seeking simplicity and performance not only in design, but also in the technology stack used for it . This article provides a comprehensive overview of the different parts that compound the new platform, such as how decorators are extensively used or the choice of technology stack.
This post was originally posted on Medium
The QMENTA platform has been going on for quite some time now. The old platform front-end was built using well established technologies like JQuery, Dojo or D3. While being feature rich, one of the problems was the scalability and maintainability of such a big codebase and it being quite complex in terms of UX/UI. In medical imaging storing platforms, data and operations are complex enough for the user to manage, so making it more difficult in terms of user experience or visual design is a no-go zone. 🙅🏻
One of the main challenges coming into this year was to rebuild the front-end from scratch to accommodate the new necessities of a growing neuroimaging processing hub, to make it clean and in a way that can be easily maintainable, scalable and up to date with the latest technologies on front-end development.
Having speed, performance and minimalism as a flagship, the aim was to build a single page web application using the FrameworkOfChoice
, which can be VueJS, AngularJS, React, or other frameworks such as Hyperapp.
jorgebucaran / hyperapp
1kB-ish JavaScript framework for building hypertext applications
Hyperapp
The tiny framework for building hypertext applications.
- Do more with less—We have minimized the concepts you need to learn to get stuff done. Views, actions, effects, and subscriptions are all pretty easy to get to grips with and work together seamlessly.
- Write what, not how—With a declarative API that's easy to read and fun to write, Hyperapp is the best way to build purely functional, feature-rich, browser-based apps using idiomatic JavaScript.
- Smaller than a favicon—1 kB, give or take. Hyperapp is an ultra-lightweight Virtual DOM, highly-optimized diff algorithm, and state management library obsessed with minimalism.
Here's the first example to get you started. Try it here—no build step required!
<script type="module">
import { h, text, app } from "https://unpkg.com/hyperapp"
const AddTodo = (state) => ({
...state,
value: "",
todos:
…Up until there, pretty standard and mainstream definition for a web application project. After pondering the different frameworks and technologies, we chose Preact coupled with Typescript as the core libraries of choice for the project.
But… why Preact + Typescript? 🤔
Preact, according to its official documentation, is a “fast 3kB alternative to React with the same modern API”. It offers everything we like and love about React such as the Virtual DOM, Component-Based development, lifecycle hooks and jsx syntax for generating dynamically, data-driven interfaces.
Preact does all this while keeping the API leaner (React 16.2.0 + React DOM is 31.8Kb while Preact is only 4Kb, both gzipped), making it faster, more lightweight, and reducing a lot of the noise added by React. The principal differences with React can be seen here. Some of them are:
this.props
andthis.state
are passed torender()
for you.You can just use class for CSS classes.
Getting rid of a lot of React / React DOM specific functions like
React.CreateElement
orReactDOM.render
, being separate exports thus avoiding the aliasing, making your code cleaner and more readable.
Regarding performance, testing it on a simple TODO application, the results are astounding, with Preact being among the fastest of them approached by Vue.js and circa 170ms faster than React. Amazing, isn’t it? 🌠
You can perform the performance test on your device by going here.
Not everything is great of course, one can find that the community is not as big or responsive as the React one (it is not that widely used after all). Some sweet functionalities are still not there, such as support for fragments (So you still have to create some good old div wrappers). Furthermore, some libraries are still not supported, but worry not, preact-compat creates a layer on top of Preact to make it fully compatible with React. Magic!⚡️
preactjs / preact-compat
ATTENTION: The React compatibility layer for Preact has moved to the main preact repo.
ATTENTION: preact-compat has moved to the main repo.
The code here is only meant for the older Preact 8.x release line. If you're still on Preact 8.x we highly recommend upgrading to the 10.x release line as it includes significant improvements and a much more stable React compatibility layer.
🚨 Note: This module is for Preact 8.x and prior - Preact X includes compat by default For Preact X, please uninstall
preact-compat
and replace your aliases withpreact/compat
.
This module is a compatibility layer that makes React-based modules work with Preact, without any code changes.
It provides the same exports as react
and react-dom
, meaning you can use your build tool of choice to drop it in where React is being depended on.
Interested? Here's an example project that uses
preact-compat
to work with an existing React library unmodified achieving more than 95% reduction in size:…
We ❤️ Typescript. A lot. For an application that is rich in data and manages a lot of different metadata and results from APIs, Typescript offers us static type checking that comes very handy when defining the different data structures that our application handles, as well as the structure of the props and state of the different components, so we know what to expect, have it documented and have all the data to be consistent at all different stages.
You can create interfaces with Typescript that characterize the input/output data your application manages. In our case, it helps modeling the data necessary to characterize a subject, or an analysis so everything is consistant and we know what to expect when dealing with those structures.
export interface AnalysisInput {
container_id: number,
date: { $date: number },
in_out: string,
filters?: any[][],
passed: boolean,
ssid: string,
subject_name: string
}
export interface AnalysisSettings {
acpc_alignment: string,
age_months: number,
alignment_tool: string,
input: AnalysisInput
}
With Typescript, you can also create Custom Components like the one below. This enforces the classes extending ComponentForm to implement methods for handling the form change and also setting the form model (i.e the object with all the fields required for the form such as the user, password, etc…), this model is then required as the state of the component, also a method submitForm()
has to be implemented. With that, we have a skeleton or structure that all forms follow with a generic form component that can have any given number of fields.
import { h, Component } from "preact";
interface FormManagement<M> {
form: M,
message: string
}
export abstract class ComponentForm<props, model> extends Component<props, FormManagement<model>> {
protected setFormModel (model: any) {
this.setState({ form: new model })
}
protected handleFormChange(change) {
this.setState(({ form, ...state }) => ({
form: { ...form, ...change },
...state
}));
}
abstract submitForm(...args): void;
}
An example of a simplified generic Preact Component our application uses that enforce Typescript. We can see the different lifecycle hooks and how the props and state of the component are set as Typescript interfaces.
mport { h, Component } from "preact";
export interface CommonSelectorAttrs {
options: any[];
class?: string;
value?: any;
printBy?: string;
onChange?: (any) => void;
}
export interface CommonSelectorState {
visible: boolean;
innerValue: string;
}
export default class CommonSelector extends Component<CommonSelectorAttrs, CommonSelectorState> {
state = {
visible: false,
innerValue: ""
};
getValue = (entry) => String(this.props.printBy ? entry[this.props.printBy] : entry);
componentDidMount(): void {
this.props.value && this.setState(() => ({ innerValue: this.getValue(this.props.value) }));
}
selectOption(opt: any): void {
this.props.onChange.call(null, { target: { value: opt }});
}
closeTooltip(): void {
this.setState(() => ({ visible: false }));
this.checkMatch();
}
getMatcher(opt: any): boolean {
return this.getValue(opt) === this.state.innerValue;
}
checkMatch(): void {
const match = this.props.options.find((opt) => this.getMatcher(opt));
!!match ? this.selectOption(match) : this.setState(() => ({ innerValue: "" }));
}
explicitSelection(opt: any): void {
const val = this.getValue(opt);
this.setState(() => ({ innerValue: val }));
this.closeTooltip();
}
setValue(){
if(this.state.innerValue === 'undefined'){
return "";
}else{
return this.state.innerValue;
}
}
render() {
return (
<div>
<div>
<p>
<input placeholder="Select one option" />
</p>
</div>
<div>
{this.props.options
.map((opt) => (
<div onClick={() => this.explicitSelection(opt)}>
<span>{this.getValue(opt)}</span>
</div>
))}
</div>
</div>
)
}
}
Decorators, Decorators… 💎
Ah, Javascript decorators. As a concept, they simply act as wrappers to another function, extending its functionality, like higher-order functions. We extensively use decorators to keep our code cleaner and separate the back-end, state or application management concerns and provide a more structured way to define common functions across components like connecting to the Redux store, connecting to the API or defining some asynchronous behavior to happen before or after the responses are sent (sort of like method proxies), so we do not have to repeat code and provide an elegant, minimal way to define these behaviors. We use them for:
- Asynchronous Method Interceptors
For managing asynchronous behavior we use kaop-TS
, which is a decorator library that provides some method interceptors written in Typescript. With them, we can plug in behavior at a join point on the asynchronous method, like perform some operations before the method starts, or after the method has finished, or plugging in a method that intercepts an error and performs some logic. We can see the beforeMethod
decorator being used in the snippet below where we define the http decorator.
k1r0s / kaop-ts
Simple Yet Powerful Library of ES2016 Decorators with Strongly typed method Interceptors like BeforeMethod, AfterMethod, OnException, etc
Lightweight, solid, framework agnostic and easy to use library written in TypeScript to deal with Cross Cutting Concerns and improve modularity in your code.
Short Description (or what is an Advice)
This library provides a straightforward manner to implement Advices in your app. Advices are pieces of code that can be plugged in several places within OOP paradigm like 'beforeMethod', 'afterInstance'.. etc. Advices are used to change, extend, modify the behavior of methods and constructors non-invasively.
For in deep information about this technique check the resources.
Demo
https://jsbin.com/bogecojuvi/edit?js,console
Get started
npm install kaop-ts --save
Use a join point to plug it to any method/class:
import { afterMethod } from 'kaop-ts'
const double = afterMethod(meta => meta.result *= 2)
class DummyExample {
@double
static calculateSomething (num, num2) {
return num * num2
}
}
DummyExample.calculateSomething(3, 3
…- Connecting with and Managing API calls
For managing the calls to the QMENTA API, we implemented a @platformRequest(url,method)
decorator that you can wrap in any function of with signature function(params,error?,result?)
and convert it into an asyncronous call to the API with certain parameters to send in the request and receive the result JSON object from the call or the error thrown by the request. The implementation of the decorator can be seen below as well as an example method calling it.
const http = options => meta => {
if(!options.method) options.method = "get";
const [ params, ...aditional ] = meta.args;
options[options.method === 'get' ? 'params' : 'data'] = params;
(request as any)({ ...options })
.then(res => {
const data = res.data;
meta.args = [params, ...aditional, null, res.data];
meta.commit();
})
.catch(err => {
meta.args = [params, ...aditional, err, null];
meta.commit();
})
};
const checkError = meta => {
const [err, result] = meta.args.slice(-2);
checkInvalidCredentials(err);
if(err) return;
if(typeof result !== "object") {
meta.args.splice(-2);
meta.args = [ ...meta.args, new Error("Wrong response format! (not a json obj)"), null ];
}
}
const stringifyParams = meta => {
const [ params, ...args ] = meta.args;
meta.args = [ stringify(params), ...args ];
}
export const platformRequest = (url, method = "get", noop = _ => _) => beforeMethod(
method === "get" ? noop : stringifyParams,
http({ method, url }),
checkError)
/* USAGE EXAMPLE */
interface ChangePasswordRequest {
old_password: string,
new_password: string,
new_password_confirm: string
}
@platformRequest("/change_password", "post")
RecoverPassword(params : ChangePasswordRequest, err?, result?) {
if(err) {
throw err;
//Cannot change the password
} else {
//Password changed succesfully!
}
}
The decorator uses axios under the hood to perform the request 🕵.
An implementation of it (as a minimal library) wrapping the Fetch API can be seen on the http-fetch-decorator
repo:
aalises / http-fetch-decorator
Simple Fetch interface http decorator wrapper for your functions.
HTTP Fetch Decorator
Simple Fetch interface decorator wrapper for your functions.
$ npm install http-fetch-decorator
Signature
Then you can place the decorator which has the same input structure as the fetch function. The parameters of the request or the data to send as a body are passed via arguments to the wrapped function, and the result and error objects are available as parameters of the function as well:
@Fetch(Url: String, initObj?: Object)
static wrappedFunction(params: Object, result? : Response, err? : any){
...
}
How to use it
import Fetch from "http-fetch-decorator"
class AnyES6Class {
@Fetch("apiexample/getsomething", {method: 'GET',mode:'cors'})
static parseResponse({id: '1'},result,err) {
if(err) throw err;
//Result contains the Response object from fetch, then you can parse
…Also, the state management and routing of our application use decorators to extend functionality to the components to, for example, connect it to the store or listen to be rendered at a specific route. On the next section we will talk a little bit more about it.
State Management and Routing 🚀
State management is one of the main concerns of any growing React application. When the number of entities and components keep growing, the need for global state management arises. Redux is one of the mainstream solutions that aims to solve that. This post assumes some previous knowledge of how Redux operates; if not, here you can find a guide on how it works .
In our application, we did not want to have a big store and we tried to keep it as small as possible, reducing the use of the shared state, enforcing encapsulation and separation of concerns. Given so, we decided to use a lightweight (less than 1Kb) version called Redux-Zero
. While having some differences, it also reduces a lot of the unnecessary overhead (for the purpose of our application) that Redux has. It still has a store (albeit just one), which provides all the global state that we want to manage; in our case, session and project information, current pages, notifications of the current project id, among others. It also has no reducers, which ironically does reduce the complexity of the library quite a lot.
redux-zero / redux-zero
A lightweight state container based on Redux
A lightweight state container based on Redux
Read the intro blog post
Table of Contents
Installation
To install the stable version:
npm i redux-zero
This assumes that you’re using npm with a module bundler like webpack
How
ES2015+:
import createStore from "redux-zero";
import { Provider, connect } from "redux-zero/react";
TypeScript:
import * as createStore from "redux-zero";
import { Provider, connect } from "redux-zero/react";
CommonJS:
const createStore = require("redux-zero");
const { Provider, connect } = require("redux-zero/react");
UMD:
<!-- the store -->
<script src="https://unpkg.com/redux-zero/dist/redux-zero.min.js"></script>
<!-- for react -->
<script src="https://unpkg.com/redux-zero/react/index.min.js"></script>
<!-- for preact -->
<script src="https://unpkg.com/redux-zero/preact/index.min.js"></script>
<!-- for vue -->
<script
…To get it up and working, just wrap your root component with the <Provider store={store}/>
component and set the store on it, an object created by the createStore(state)
method, where the state is the object that contains our shared state. To change that state, you create actions which are just pure functions that update this global state , e.g setSession = (state,val) => ({sessions: val});
To call those actions from a component, we have to connect that component to the store. Connecting a given component to the store allows the component to gain access to some actions and the global state via props. We created a decorator to simplify the process of connecting a component to the store.
import { h, Component } from "preact";
import { Connect } from "redux-zero/preact";
export default function(actions = {}): any {
return Child => props => (
<Connect mapToProps={state => ({ ...state })} actions={actions}>
{mappedProps => <Child {...mappedProps} {...props} />}
</Connect>
)
}
With this decorator, we can plug it in on top of any component specifying the actions the component will get as props. e.g by putting @connectStore({removeSession, setCurrentPages});
right on top of the declaration of the component, you have access to these actions inside the component which update the global state on the store, while also having access to the global state itself via props ( this.props.removeSession();
). With this method, we provide a cleaner, more elegant and compact way to connect a component to the store.
Another integral part of any modern application is the option to route between the different views of the project depending on the URL or other parameters, being able to pass parameters to the routes, etc. A common solution is to use the amazing router that comes with React . As much as we like it, it comes with a lot of functionality that we would not really be using, so we made our own preact-routlet , a simple router for Preact/React based in decorators.
k1r0s / preact-routlet
Simple `Component Driven` Routing for Preact/React using ES7 Decorators
Preact Routlet
This package contains routing functionalities for Preact and, now from 1.0.0 React
applications as well. Instead of using HTML5 history API it uses the oldie /#/what-ever
hash routing (this will change in the future).
This project was created by exploring contextual ways to define routes rather than placing all the routes in a single file.
Usage:
Available imports:
// if you're using React
import { renderOnRoute, navigate, RouterOutlet, PathLookup, routePool, Link } from "preact-routlet/react"
// if you're using Preact
import { renderOnRoute, navigate, RouterOutlet, PathLookup, routePool, Link } from "preact-routlet/preact"
Either from "preact-routlet/preact"
or from "preact-routlet/react"
Place your RouterOutlet
element somewhere in your JSX:
<div>
<RouterOutlet />
</div>
Plug the renderOnRoute
decorator on some component
@renderOnRoute("/login")
export default class Login extends Component<any, any> {
…The intricacies of it are simple, just wrap your application with the router component <RouterOutlet />
and you are ready to go. You can also wrap some components with <PathLookup shouldRender={condition}/>
specifying a condition to render some path, or use the <RouterOutlet redirect="/route" shouldRedirect={condition} />
to specify a condition which, in case it is met, the router automatically redirects to the specified route (for example, we use it to redirect to the login if the session is invalid or has expired).
To navigate to a route you just have to call navigate("/route")
and to specify a Component to be rendered at a specific route, you just have to plug the decorator on top of the component e.g @renderOnRoute("forgot-password")
making it clear and visual in which route the component is rendered.
With that, we have a compact way of representing routing and state management with the signatures on top of the component that makes it very readable. A dummy component that connects to the store and is rendered on a given route can be seen below.
import { h, Component } from "preact";
import { renderOnRoute, navigate } from "preact-routlet";
import { setLoginMessage } from "../actions";
import connectStore from "../../../connect";
import { platformRequest } from "../../../services/api-decorators";
//With these decorators, we know the route this component is rendered at and which actions does it have access to, in a compact way
@renderOnRoute("/forgot-password") @connectStore({ setLoginMessage })
export default class ForgotPassword extends Component<any, any> {
//Call the forgot_password API with a POST
@platformRequest("/forgot_password", "post")
performLogin(params, err?, result?) { //Params are the parameters of the request e.g {email: "test@gmail.com , user_id: 1234}
if(err || result.error) {
this.setState({ message: "There was an error with the request" })
} else {
this.props.setLoginMessage(result.message); //Calling the store to set a login warning message
navigate("/login"); //Navigate back to the login using the router
}
}
render() {
return (
<div>
Just an example component with some decorators...
</div>
}
}
Bundling with Parcel 📦
Parcel, not to be confused with the australian band Parcels, defines itself as a “blazing fast, zero configuration web application bundler”. At the start of the project, we used the fairly standard Webpack for bundling our application. It required quite a lot of plugins and configuration. (webpack.config.ts
). Switching to Parcel, we don’t need configuration for typescript or html files anymore, just adding npm install --save-dev parcel-bundler parcel-plugin-typescript
does the trick.
The only remaining thing is to specify an entry point and output directory and voilà, it just works. The difference in speed and performance compared to webpack is not very acute in our case (it’s essentially the same in terms of speed), but it’s the zero configuration and minimality of Parcel what makes it our bundler of choice for the project.
parcel-bundler / parcel
The zero configuration build tool for the web. 📦🚀
Parcel is a zero configuration build tool for the web. It combines a great out-of-the-box development experience with a scalable architecture that can take your project from just getting started to massive production application.
Features
- 😍 Zero config – Parcel supports many languages and file types out of the box, from web technologies like HTML, CSS, and JavaScript, to assets like images, fonts, videos, and more. It has a built-in dev server with hot reloading, beautiful error diagnostics, and much more. No configuration needed!
- ⚡️ Lightning fast – Parcel's JavaScript compiler is written in Rust for native performance. Your code is built in parallel using worker threads, utilizing all of the cores on your machine. Everything is cached, so you never build the same code twice. It's like using watch mode, but even when you restart Parcel!
- 🚀 Automatic production optimization – Parcel optimizes your whole app for production automatically…
The only downside is that, in order to get the hot reloading working for the dev server in Preact + Typescript, you have to add the module.hot.accept()
flag to your root component and specify some types in the render function (the third argument as foo.lastChild as Element
for Typescript not to complain. The fix can be seen on the following snippet.
import { h, render } from "preact";
import Main from "./components/main"
declare const module: any
const mountNode = document.getElementById('root');
render(<Main />, mountNode, mountNode.lastChild as Element);
// Hot Module Replacement
if (module.hot) {
module.hot.accept();
}
Unit and e2e acceptance testing with Jest + Puppeteer
Testing is an integral part of any project. In our project, we use the fairly standard Jest for testing our components coupled with Puppeteer , wich is a web scraper, or having your own highly trained monkey perform the operations you tell him/her on the browser, like clicking a certain button or dragging the mouse over an element. Using those, we can perform some operations on the front-end via a headless Chrome API and then check for expected results with Jest, like checking an element of confirmation appears or the warning message displayed is correct👌. If you want to learn more on how to use Jest + Puppeteer for testing in React, there is a nice article talking about it.
Puppeteer
Puppeteer is a JavaScript library which provides a high-level API to control Chrome or Firefox over the DevTools Protocol or WebDriver BiDi Puppeteer runs in the headless (no visible UI) by default
Get started | API | FAQ | Contributing | Troubleshooting
Installation
npm i puppeteer # Downloads compatible Chrome during installation.
npm i puppeteer-core # Alternatively, install as a library, without downloading Chrome.
Example
import puppeteer from 'puppeteer';
// Or import puppeteer from 'puppeteer-core';
// Launch the browser and open a new blank page
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Navigate the page to a URL.
await page.goto('https://developer.chrome.com/');
// Set screen size.
await page.setViewport({width: 1080, height: 1024});
// Type into search box.
await page
…Just an example of testing the pagination component using Jest.
import { h, render } from 'preact';
import Pagination, {generateMidPageTabValues} from '../../src/components/pagination-component';
const spy = jest.fn()
describe("<Pagination /> e2e tests", () => {
let scratch = null;
let component = null;
beforeEach(done => {
scratch = document.createElement('div');
render(<Pagination initialPage={1} ref={ref => component = ref} pageChange={(from, to) => spy(from, to)} totalItems={45}/>, scratch);
done()
});
it("should display proper information about pages", () => {
expect(scratch.innerHTML).toContain("Showing items 1 to 15 of 45");
});
it("shouldn't allow to decrease current page if we're on the first page", () => {
expect(component.state.prevAllowed).toBe(false);
});
it("should properly increase current page", (done) => {
component.pageForward();
setTimeout(_ => {
expect(scratch.innerHTML).toContain("Showing items 16 to 30 of 45");
expect(spy).toHaveBeenLastCalledWith(15, 30);
done();
}, 1000);
});
it("Should correctly compute first page indices", () => {
expect(generateMidPageTabValues(1,5,10)).toEqual([2,3,4,5,6]);
expect(generateMidPageTabValues(4,5,10)).toEqual([2,3,4,5,6]);
});
it("Should correctly compute last page indices", () => {
expect(generateMidPageTabValues(13,5,15)).toEqual([10,11,12,13,14]);
expect(generateMidPageTabValues(11,5,15)).toEqual([10,11,12,13,14]);
});
it("Should correctly compute middle pages indices", () => {
expect(generateMidPageTabValues(10,5,15)).toEqual([8,9,10,11,12]);
expect(generateMidPageTabValues(7,5,15)).toEqual([5,6,7,8,9]);
});
it("Should correctly compute indices when few pages", () => {
expect(generateMidPageTabValues(1,5,5)).toEqual([2,3,4]);
expect(generateMidPageTabValues(2,5,3)).toEqual([2]);
expect(generateMidPageTabValues(2,5,2)).toEqual([]);
expect(generateMidPageTabValues(1,5,1)).toEqual([]);
});
});
The building, testing and deploying process is automated using Jenkins , with the tests running in a docker container in order to perform the tests in an environment with all the graphical libraries puppeteer requires. The fairly simple pipeline can be seen in Figure 9:
We build the binaries, perform the unit tests and then deploy to a testing dominion which varies based on the git branch we are on. Then we perform the e2e tests on that dominion.
The docker image used for the container in which Jenkins runs the pipeline is a custom version of the Puppeteer Docker Image , based on the ubuntu 16.04 image with node 8. The dockerfile can be seen below:
We add the “deployuser” to the container because Jenkins mounts its workspace directory (owned by deployuser) and it requires the user inside the container and outside to be the same in order to perform all the operations required for the deployment to be succesful.
This way, we avoid the whole application to suddenly crash and burn or turn into one of those amazing 90’s websites.
Summary
In this article we have presented an overview of the technology stack we used for rebuilding the QMENTA front-end and how it focuses on being small, efficient and simple. We have also seen how we use decorators in our project and the different parts that compound it such as API calls, routing, state management etc.
By setting the foundations and patterns of the project, the next steps are continuously improving the new front-end in a scalable and maintainable way and adding meaningful features to match the functionality of the old platform, while keeping it minimal. You can see how it goes here. 👋
Special kudos 🙌 to for setting the foundations of the project, carrying the heavyweight through most of it and teaching me the ways of the H̶a̶c̶k̶e̶r̶m̶a̶n̶ Javascript ninja. 🤖 🏋🏻
Want to experiment with these technologies in your next projects? Yay! Here you have some boilerplates we created in github to get you up and running as soon as possible.🤘🏼
aalises / preact-typescript-parcel-starter
Starter with Preact - Typescript - Parcel Bundler
Preact + Typescript + Parcel Starter
Zero configuration bundling and lightweight state management and view => Preact + Typescript + Parcel Bundler + Redux Zero + Jest + Pupetteer
This starter project contains many features and technologies to start the development ASAP.
Features:
- TypeScript: Strong typing and the future of JavaScript
- Preact: dom manipulation + components
- Redux-Zero: state management
- Preact-Bind-Group: Group Form fields onChange Events to a single Callback, making forms easier to manage
- Parcel: 0 configuration bundler
- Jest: Delightful javascript testing
- Puppeteer: Headless chrome node API scrapper
- Lynt: 0 configuration linting for TS + React
run development server npm run dev
build the project (on /dist) npm run build
run tests npm run test
run linting npm run lint
The starter includes a basic sample application showcasing the different technologies using Bulma for styles. The example consists on a restaurant order making form with validation/model and some global state…
k1r0s / preact-poi-starter
Preact Example App powered by Poi and BabelJS
Preact Poi starter
So whats the deal here?
Poi justs works. Of course Poi will try to look up a .babelrc
. Since Preact has a different jsx pragma you must have transform-react-jsx plugin with { pragma: "h" }
opt at least to work with.
{
"sourceMaps": true,
"presets": [
["es2015", { "loose":true }],
"stage-0"
],
"plugins": [
["transform-decorators-legacy"],
["transform-react-jsx", { "pragma": "h" }]
]
}
For this example I added transform-decorators-legacy
as well.
run development server
npm run dev
aka $ poi [root file]
run production build
npm run build
aka $ poi build [root file]
k1r0s / bleeding-preact-starter
TypeScript · Preact · Redux-zero => frontend starter with bleeding features. Check it out!
Bleeding Frontend Starter
Preact + TypeScript + Redux-Zero + Moar..
This starter project contains many features to speedup development and have fun which is pretty handy today.
Features:
- TypeScript Strong typing and the future of JavaScript
- Preact dom manipulation + components
- Redux-Zero state management
- Preact-Routlet Simple Component drive Routing
- Http-Decorator Wrap your components with axios, no more async management
- Preact-Stylesheet-Decorator Wrap your components with scoped css
- Preact-Bind-Group Group Form fields onChange Events to a single Callback
common
folder contains Connect decorator for redux-zero (which is not yet included on the lib)
Powered by Webpack
Heading production env -> Polyfills, Shims & Extensions
I recommend to install core-js and import all its contents on your main file import 'core-js'
Top comments (4)
This is awesome work! Why has no one commented on this yet! O.o I'm firing up a workspace to play with this right now :)
Thank you very much Sean!! Tried to gave an overview of pretty much everything (except styling 💅). The boilerplate used for the project is exactly this one:
k1r0s / bleeding-preact-starter
TypeScript · Preact · Redux-zero => frontend starter with bleeding features. Check it out!
Bleeding Frontend Starter
Preact + TypeScript + Redux-Zero + Moar..
This starter project contains many features to speedup development and have fun which is pretty handy today.
Features:
Powered by Webpack
Heading production env -> Polyfills, Shims & Extensions
I recommend to install core-js and import all its contents on your main file
import 'core-js'
but switching Webpack for Parcel.
If you have any suggestion or idea on how to extend this stack or improve, or any question, let me know! 🤘
thanks for this great article. It is very detailed. I discovered Redux-zero will give it a try soon with preact.
Thumbs up!!!
Great article Albert! Really useful 🙂