0.1.0-beta.3 • Published 7 years ago

typerapp v0.1.0-beta.3

Weekly downloads
2
License
MIT
Repository
github
Last release
7 years ago

npm version

Typerapp

Typerapp is type-safe Hyperapp V2 + α. It's written in TypeScript.

Sample: Edit typerapp-sample

Minimum sample: Edit typerapp-minimum-sample

Install

npm install typerapp

Note: Typerapp uses TypeScript source file in node module. If you use Webpack ts-loader, enable allowTsInNodeModules option, and include node_modules/typerapp of tsconfig.json. It is not necessary for Parcel.

Modified points from Hyperapp

  1. Add dispatch to view arguments
  2. Pure DOM Events

Add dispatch to view arguments

Hyperapp:

app({
    view: state => ...
})

Typerapp:

app<State>({
    view: (state, dispatch) => ...
})

Pure DOM Events

In Typerapp, specify a function that takes Event as an argument to VDOM event.

Then, call the Action using the dispatch.

Hyperapp:

const Input = (state, ev) => ({ ...state, value: ev.currentTarget.value })

app({
    view: state => <div>
        <input
            value={state.value}
            onInput={Input}
        />
    </div>
})

Typerapp:

const Input: Action<State, string> = (state, value) => ({ ...state, value })

app<State>({
    view: (state, dispatch) => <div>
        <input
            value={state.value}
            onInput={ev => dispatch(Input, ev.currentTarget.value)}
        />
    </div>
})

Types

Type-safe Actions, Effects, Subscriptions, HTML Elements, and more...

Actions

Type:

export type ActionResult<S> = S | [S, ...Effect<S, any>[]]
export type Action<S, P = Empty> = (state: S, payload: P) => ActionResult<S>

Use:

// without parameter
const Increment: Action<State> = state => ({ ...state, value: state.value + 1 })

// with parameter
const Add: Action<State, { amount: number }> = (state, payload) => ({
    ...state,
    value: state.value + payload.amount
})

Effects

Type:

export type Effect<S, P = undefined> = [(dispatch: Dispatch<S>, props: P) => void, P]

Define Effect:

// Delay Runner Props
export type DelayProps<S, P> = {
    action: EffectAction<S, P>
    duration: number
}

// Delay Effect Runner
const DelayRunner = <S, P>(dispatch: Dispatch<S>, props: DelayProps<S, P>) => {
    setTimeout(() => dispatch(props.action), props.duration)
}

// Delay Effect Constructor
export function delay<S, P>(action: DelayProps<S, P>['action'], props: { duration: number }): Effect<S, DelayProps<S, P>> {
    return [DelayRunner, { action, duration: props.duration }];
}

Use:

// Increment with Delay
const DelayIncrement: Action<State> = state => [
    state,
    delay(Increment, { duration: 1000 })
]

// Add with Delay
const DelayAdd: Action<State, { amount: number }> = (state, params) => [
    state,
    delay([Add, { amount: params.amount }], { duration: 1000 })
]

Subscriptions

Type:

export type Subscription<S, P = undefined> = [(dispatch: Dispatch<S>, props: P) => () => void, P]

Define Subscription:

// Timer Runner Props
export type TimerProps<S, P> = {
    action: EffectAction<S, P>
    interval: number
}

// Timer Subscription Runner
const timerRunner = <S, P>(dispatch: Dispatch<S>, props: TimerProps<S, P>) => {
    const id = setInterval(() => dispatch(props.action), props.interval)
    return () => clearInterval(id)
}

// Timer Subscription Constructor
export function timer<S, P>(action: TimerProps<S, P>['action'], props: { interval: number }): Subscription<S, TimerProps<S, P>> {
    return [timerRunner, { action, interval: props.interval }]
}

Use:

app<State>({
    subscriptions: state => [
        timer(Increment, { interval: 1000 })
    ]
})

HTML Elements

Typerapp Html.d.ts forked from React of DefinitelyTyped.

Limitations

TypeScript is NO check for exceed property on Action.

const Act: Action<State> = state => ({
    ...state,
    typo: 1 // no error!
})

Workaround:

// type alias for Action/ActionResult (for writing short)
type MyAction<P = Empty> = Action<State, P>
type MyResult = ActionResult<State>

// explicit return type
const Act: MyAction = (state): MyResult => ({
    ...state,
    typo: 1 // error
})

For truly solution, please vote Exact Types.

Extras

Typerapp has extra features.

actionCreator

actionCreator is simple modularization function.

// part.tsx
import { h, View, actionCreator } from 'typerapp'

type State = {
    foo: string,
    part: {
    	value: number
    }
}

const createAction = actionCreator<State>()('part')

const Add = createAction<{ amount: number }>(state => ({
    ...state,
    value: state.value + params.amount
}))

export const view: View<State> = ({ part: state }, dispatch) => <div>
    {state.value} <button onClick={ev => dispatch(Add, { amount: 10 })}>request</button>
</div>

Helmet

Helmet renders to the head element of DOM.

import { Helmet } from 'typerapp/helment'

app<State>({
    view: (state, dispatch) => <div>
        <Helmet>
            <title>{state.title}</title>
        </Helmet>
    </div>
})

Recommend performance improvement with Lazy:

const renderHead = (props: { title: string }) => <Helmet>
    <title>{props.title}</title>
</Helmet>

app<State>({
    view: (state, dispatch) => <div>
        <Lazy key="head" view={renderHead} title={state.title} />
    </div>
})

Router

Typerapp Router is url-based routing Subscription. Router syncs URL to the state by History API.

import { createRouter, Link, RoutingInfo, Redirect } from 'typerapp/router'

// Update routing
const SetRoute: Action<State, RoutingInfo<State, RouteProps> | undefined> = (state, route) => ({
    ...state,
    routing: route,
})

// Create router
const router = createRouter<State, RouteProps>({
    routes: [{
        title: (state, params) => 'HOME',
        path: '/',
        view: (state, dispatch, params) => <div>home</div>,
    }, {
        title: (state, params) => 'Counter / ' + params.amount,
        path: '/counter/:amount',
        view: (state, dispatch, params) => {
            const amount = params.amount ? parseInt(params.amount, 10) : 1
            return <div>
            	<div>{state.value}</div>
                <button onClick={ev => dispatch(Add, { amount })}></button>
        	</div>
        },
    }, {
        title: (state, params) => 'Redirect!',
        path: '/redirect',
        view: (state, dispatch, params) => <Redirect to="/" />,
    }],
    matched: (routing, dispatch) => dispatch(SetRoute, routing),
})

app<State>({
    view: (state, dispatch) => <div>
        <div><Link to="/">Home</Link></div>
        <div><Link to="/Counter/10">Count10</Link></div>
        <div><Link to="/Redirect">Redirect</Link></div>
        {
            state.routing
                ? state.routing.route.view(state, dispatch, state.routing.params)
                : <div>404</div>
        }
    </div>
})

CSS-in-JS

Typerapp style forked from Picostyle.

import { style } from 'typerapp/style'

// styled div
const Wrapper = style('div')({
    backgroundColor: 'skyblue',
    width: '50px',
})

// styled div with parameter
const StyledText = style<{ color: string }>('div')(props => ({
    color: props.color,
    transition: "transform .2s ease-out",
    ":hover": {
        transform: "scale(1.5)",
    },
    "@media (orientation: landscape)": {
        fontWeight: "bold",
    },
}))

app<State>({
    view: (state, dispatch) => <div>
        <Wrapper>
            <StyledText color="green">text</StyledText>
        </Wrapper>
    </div>
})

SVG hyphened attributes alias

TypeScript is no check for hyphened attributes on TSX.

Please import typerapp/main/svg-alias for type checkable camel-case attributes.

In the below, strokeWidth and strokeDasharray is converted to stroke-width and stroke-dasharray.

import "typerapp/main/svg-alias"

<svg x="0px" y="0px" width="200px" height="3" viewBox="0 0 200 1">
    <line
        x1="0"
        y1="0.5"
        x2="200px"
        y2="0.5"
        stroke="skyblue"
        strokeWidth={3}
        strokeDasharray={5}
    />
</svg>

Changelog

See CHANGELOG.md

0.1.0-beta.3

7 years ago

0.1.0-beta.2

7 years ago

0.1.0-beta.1

7 years ago

0.1.0-beta.0

7 years ago

0.0.1

7 years ago

0.0.0-alpha.28

7 years ago

0.0.0-alpha.27

7 years ago

0.0.0-alpha.26

7 years ago

0.0.0-alpha.25

7 years ago

0.0.0-alpha.24

7 years ago

0.0.0-alpha.23

7 years ago

0.0.0-alpha.22

7 years ago

0.0.0-alpha.21

7 years ago

0.0.0-alpha.20

7 years ago

0.0.0-alpha.19

7 years ago

0.0.0-alpha.18

7 years ago

0.0.0-alpha.17

7 years ago

0.0.0-alpha.16

7 years ago

0.0.0-alpha.15

7 years ago

0.0.0-alpha.14

7 years ago

0.0.0-alpha.13

7 years ago

0.0.0-alpha.12

7 years ago

0.0.0-alpha.11

7 years ago

0.0.0-alpha.10

7 years ago

0.0.0-alpha.9

7 years ago

0.0.0-alpha.8

7 years ago

0.0.0-alpha.7

7 years ago

0.0.0-alpha.6

7 years ago

0.0.0-alpha.5

7 years ago

0.0.0-alpha.4

7 years ago

0.0.0-alpha.3

7 years ago

0.0.0-alpha.2

7 years ago

0.0.0-alpha.1

7 years ago