Skip to content
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

115 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Peak.js

Easy reactive web framework with no setup required

<template>
  <button @click="greet">Click</button>
</template>

<script>
  export default class {
    greet() {
      alert('Welcome to Peak.js!')
    }
  }
</script>

<style>
  button { font-size: large }
</style>

Features

  • Reactive web framework based on Web Components
  • No build step necessary
  • Reusable single-file components
  • Scoped CSS styles per component
  • Optional url-based view routing
  • Lightweight at ~5kb gzipped
  • Support for server-side rendering (SSR)
  • Optional Vite plugin for bundling and HMR

Components

Components are defined in plain HTML files, with each file having a <template>, an optional <script>, and optional <style>.

<!-- components/x-counter.html -->

<template>
  <button @click="increment">
    <span x-text="count" />
  </button>
</template>

<script>
  export default class {
    initialize() {
      this.count = 0
    }
    increment() {
      this.count++
    }
  }
</script>

<style>
  button:active {
    filter: invert(1);
  }
</style>

Register components and use them directly in markup:

<!-- index.html -->

<x-counter></x-counter>

<script type="module">
  import { component } from '/vendor/peak.js'
  component('x-counter', '/components/x-counter.html')
</script>

Templates

x-if

Conditionally render a block

<img x-if="loading" src="spinner.svg">

Also available are x-else and x-else-if

<template x-if="loading">
  <img src="spinner.svg">
</template>

<template x-else-if="error">
  <img src="error.svg">
</template>

<template x-else>
  <x-content />
</template>

x-for

Render some HTML for each item in an array

<ul>
  <li x-for="item in items">
    <span x-text="item.title" />
  </li>
</ul>

x-text

Set the text content of an element

<span x-text="`Hello, ${name}`" />

x-html

Set the HTML content of an element

<div x-html="markdown.render('# Page title')"></div>

x-show

Set the visibility of an element

<div x-show="open">Content...</div>

x-ref

Refer to an HTML element via $refs

<input x-ref="searchInput">
<button @click="$refs.searchInput.focus()">Search</button>

Props

Specify props using a static props array:

<template>
  Greetings, <span x-text="name" />!
</template>

<script>
export default class {
  static props = ['name']
}
</script>

Component methods

initialize()

Run code when the component is initialized before mounted

<script>
export default class {
  initialize() {
    // initialize the component
    this.pollerId = setInterval(_ => {
      this.items = fetch('/feed')
    }, 30_000)
  }
}
</script>

mounted()

Run code when the component is mounted

teardown()

Run teardown code when the component is to be destroyed

<script>
export default class {
  initialize() {
    // ...
  }
  teardown() {
    // clean up when the component is destroyed
    clearInterval(this.pollerId)
  }
}
</script>

$watch(expr)

Run methods when reactive data changes

<template>
  <button @click="count++" x-text="count" />
</template>

<script>
export default class {
  initialize() {
    this.count = 0
    this.$watch('count', () => {
      console.log("count is now", this.count)
    })
  }
}
</script>

$emit(eventName, detail)

Emit events that bubble up to parent components

<template>
  <input @input="$emit('change')">
</template>

$on(eventName, handler)

Handle emitted events native and custom

Lifecycle events

  • initialize - component has been initialized but not yet mounted
  • mounted - component has been mounted in the document
  • teardown - component is no longer mounted

Component properties

$event

Refer to the event being handled

<template>
  <button @click="incrementBy(10)">Add 10</button>
</template>

<script>
export default class {
  incrementBy(n) {
    this.$event.stopPropagation()
    this.count += n
  }
}
</script>

$refs

Refer to elements within the component by the name in their x-ref attribute

Computed properties

Use instance getters for display formatting, and other derived properties

<template>
  <div x-text="formattedTime" />
</template>

<script>
export default class {
  get formattedTime() {
    return new this.time.toISOString()
  }
  created() {
    this.time = new Date;
  }
}
</script>

Style

Styles defined in the component are scoped to the component — they won't leak up to ancestor elements, nor down into nested components.

<template>
  <h1>Article title</h1>
  <p>This text in red won't leak to other components</p>
  <x-body />
</template>

<style>
  p { color: red }
</style>

Routing

Peak comes with an optional built-in router. Register views to route patterns for integration with the History API. Views are just regular components, associated with a route.

<nav>
  <a href="/">HOME</a>
  <a href="/about">ABOUT</a>
</nav>

<x-router-view></x-router-view>

<script type="module">
  import { router } from 'peak'

  router.route('/', '/views/home.html')
  router.route('/about', '/views/about.html')

  router.on('navigation', e => console.log(e))
  router.on('notFound', e => console.warn(e))
</script>

Building with Vite

Optionally, build with Vite in order to get HMR in dev, and bundling for production.

// vite.config.js
import { defineConfig } from 'vite'
import peakPlugin from 'peak/vite'

export default defineConfig({
  plugins: [peakPlugin()]
})

Make sure to import virtual:peak-components built by the vite build:

// index.html
<script type="module">
import "virtual:peak-components"
import { router } from './peak.js'

router.route('/', '/views/home.html')
</script>

About

Easy reactive web framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages