SSR Framework and
static site generation for
WebComponents

Illustration code

Features

lit-html

Use the power of lit-html to define your templates and react on state changes.

Server Side Rendering

Use the same templating engine for the server and client and prerender your custom elements for better SEO

Interactivity

Hyrdate your prerendered elements on the client & make them interactive.

Static export

Create a static export of your site which is lightning fast and can be hosted virtually anywhere.

File based routing

Routes are easily registered using the filesystem. Each file represents one route. Even dynamic routes are supported.

CSS Builds

Import your css files directly in your JavaScript files with automatic post-processing with PostCSS.

Installation & first start

// Install the core luna-js package as a dependency
npm install --save @webtides/luna-js
// Install the luna-cli package as a development dependency
npm install --save-dev @webtides/luna-cli
// Start luna in development mode. You will be asked
// if the basic files should be generated.
npx luna --dev

Important: luna-js is currently in a very early development phase.
To get access to luna-js feel free to contact us under ls@webtides.dev.

Read the docs

Highlights

Write the same template
for server and client

You write your template once in lit-html and luna-js renders it on the server and automatically hydrates the element on the client.

  • Prerender applications on the server
  • Automatically rerenders on property changes
  • Define event listeners for native dom events
  • Use the dom the way it was meant to be used; no unnecessary abstractions or restrictions
  • Import your styles directly and use postcss for processing
                import {html, LunaElement} from "@webtides/luna-js";

// Imported css files will automatically be bundled.
import "./click-counter.css";

export default class ClickCounter extends LunaElement {

    properties() {
        return {
            numberOfClicks: 0
        };
    }

    events() {
        return {
            button: {
                click: () => this.numberOfClicks++
            }
        };
    }

    template() {
        return html`
            Number of clicks: ${this.numberOfClicks}
            <button>Click me</button>
        `;
    }
}

            

Data loading

You can load static and dynamic properties for each element to render dynamic content. The properties will be loaded, and thus are available, on the server.

The logic for loading your properties will be stripped from the client code. Which means that you have the full power of node available without having to think about compatibility with the browser.

  • Load dynamic properties on each request
  • Load static properties once on element registration or static export
  • Keep your logic, from fetching data, to displaying it, in the same element
  • Use your favorite headless cms for your content
                import {html, LunaElement} from "@webtides/luna-js";

export default class CartElement extends LunaElement {

    properties() {
        return {
            currentCart: {}
        };
    }

    async loadDynamicProperties({ request, response }) {
        const currentUserId = request.currentUser;

        const { loadCartOfUser } = await import("./cart-service.js");
        const currentCart = await loadCartOfUser(currentUserId);

        return {
            currentCart
        }
    }

    templateItem(item) {
        return html`
            <div class="cart-item">
                <h3>${item.name}</h3>
                <span>${item.price}</span>
            </div>
        `;
    }

    template() {
        return html`
            <div>
                ${this.currentCart.items.map(item => {
                    return this.templateItem(item)
                })}
            </div>
        `;
    }
}

            

Define pages and layouts

Each file inside your configured pages directory is used to register a new route.

  • Use placeholders for dynamic route parameters
  • Specify a layout which can be used for multiple pages
  • Use custom express middleware for each route
                import {html, LunaElement} from "@webtides/luna-js";

import layout from "../layout.js";

// Route/page specific middleware
const middleware = async () => {
    const {authMiddleware} = await("../auth.js");
    return [ authMiddleware ];
};

export { layout, middleware };

// File: /views/pages/profile/[id].js
export default class ProfilePage extends LunaElement {
    // A public property which can be used in the layout
    title = "Profile page";

    async loadDynamicProperties({ request }) {
        return {
            userId: request.params.id
        };
    }

    template() {
        return html`
            <profile-component id="${this.userId}"></profile-component>
        `;
    }
}

            

Ready for serverless

After building your luna-js application for production, it fulfills all requirements to be used inside a serverless environment. Serverless allows you to stop worrying about scaling and managing your servers. Read more in the docs.

  • Minimal startup time
  • Configurable route and element caching
  • Just an express application
  • Deploy your assets on a different domain or cdn
                // Serverless entry point for your luna application
const serverless = require("serverless-http");
const { prepareServer } = require("@webtides/luna-js/lib/framework");

const {callHook} = require("@webtides/luna-js/lib/framework/hooks");
const {HOOKS} = require("@webtides/luna-js/lib/framework/hooks/definitions");

module.exports.handler = async (event, context) => {
    const app = await prepareServer();

    await callHook(HOOKS.SERVER_STARTED, {
        app
    });

    return serverless(app)(event, context);
};