/reframe

Framework to create React web apps.

Primary LanguageJavaScriptCreative Commons Zero v1.0 UniversalCC0-1.0

Chat with Reframe author on discord.gg/kqXf65G

Reframe

Framework to create React web apps.

Easy - Create apps with page configs.     Universal - Create all kinds of apps.     No lock-in - Progressive escape.


Overview
Usage Manual
Customization Manual


Overview

Contents

What is Reframe

Reframe allows you to create web apps by simply defining so called "page configs". Reframe then takes care of the rest: It automatically transpiles, bundles, serves, and routes your pages.

// We create a landing page by defining a page config:
const LandingPage = {
    route: '/', // Page's URL
    view: () => <div>Welcome to Reframe</div>, // Page's root React component
    title: 'Welcome' // Page's <title>
};

A page config is a plain JavaScript object that configures a page by assigning it

  • a React component (required),
  • a route (required), and
  • further (optional) page configurations (such as the page's <title>, meta tags, script tags, whether the page should be hydrated or not, whether the page's HTML should be rendered at build-time or at request-time, etc.).

You can build a React web app with no build configuration and no server configuration.

All you need to create a web app is one React component and one page config per page.

And, if you need to, everything is customizable: You can customize the transpiling & bundling, the server, the browser entry, the Node.js entry, etc.

With Reframe you can create:

  • Server-side rendered apps
    Apps where pages are rendered to HTML on the server.
  • Static apps
    Apps where pages are rendered to HTML at build-time.
    These apps don't need a Node.js server and can be deployed to a static website hosting such as GitHub Pages or Netlify.
  • DOM-static apps
    Apps where the DOM is static and React is only used to render HTML.
    No (or almost no) JavaScript is loaded in the browser.
  • Hybrid apps
    Apps where pages are mixed: Some pages are rendered to HTML at build-time while others at request-time, and some pages have a static DOM while others have a dynamic DOM.

Reframe generates a certain type of app depending on how you configure your pages. For example, if you add htmlIsStatic: true to a page config, then that page's HTML is rendered at build-time instead of request-time. So, creating a static app is simply a matter of setting htmlIsStatic: true to all page configs.

Let's create a web app by defining a page config HelloPage:

// ~/tmp/reframe-playground/pages/HelloPage.html.js

import React from 'react';

const HelloComponent = (
    props => {
        // Our route argument `name` is available at `props.route.args.name`
        const name = props.route.args.name;
        return (
            <div>Hello {name}</div>
        );
    }
);

const HelloPage = {
    route: '/hello/:name', // Page's URL. (Reframe's routing is based on React Router.)
    title: 'Hi there', // Page's <title>
    view: HelloComponent, // Page's root React component
};

export default HelloPage;

The reframe CLI does the rest:

Reframe did the following:

  • Reframe searched for a /pages directory and found one at ~/tmp/reframe-playground/pages.
  • Reframe read the /pages directory and found our page config at ~/tmp/reframe-playground/pages/HelloPage.html.js.
  • Reframe used webpack to transpile HelloPage.html.js.
  • Reframe started a Node.js/hapi server serving all static browser assets and serving our page by (re-)rendering its HTML on every request.

The "Quick Start" section below gives a step-by-step guide to create a React app with Reframe.

Why Reframe

Ease of Use

Creating a React app is simply a matter of creating React components and page configs. The "Quick Start" section below shows how easy it is.

Universality

A fundamental aspect of the page config is that it allows you to configure a page to be what we call "HTML-static", "HTML-dynamic", "DOM-static" and "DOM-dynamic":

  • HTML-static
    The page is rendered to HTML at build-time.
    (In other words, the page is rendered to HTML only once, when Reframe is building the frontend.)
  • HTML-dynamic
    The page is rendered to HTML at request-time.
    (In other words, the page is (re-)rendered to HTML every time the user requests the page.)
  • DOM-static
    The page is not hydrated.
    (In other words, the DOM doesn't have any React component attached to it and the DOM will not change.)
  • DOM-dynamic
    The page is hydrated.
    (In other words, React components are attached to the DOM and the page's DOM will eventually be updated by React.)

This fine-grain control over the "static-ness" of your pages gives you the ability to implement all app types.

For example, you can create:

  • Server-side rendered apps.
    (In other words HTML-dynamic apps: All pages are configured to be HTML-dynamic.)
  • Static websites.
    (In other words HTML-static apps: All pages are configured to be HTML-static.)
    Since all pages are rendered to HTML at build-time, no Node.js server is needed. These apps can be deployed to static website hostings such as GitHub Pages or Netlify.
  • DOM-static apps
    (In other words, apps where all pages are configured to be DOM-static.)
    Since the page is not hydrated, React is not loaded in the browser. No (or almost no) JavaScript is loaded in the browser.
  • Hybrid apps.
    An app can have pages of different kinds: Some pages can be configured to be HTML-static, some pages to be HTML-dynamic, some others to be DOM-static, and some to be DOM-dynamic. You can for example configure your landing page to be HTML-static and DOM-static, your product page to be HTML-dynamic and DOM-static, and your product search page to be HTML-static and DOM-dynamic.

Reframe is the only React framework that supports all app types.

Instead of learning different frameworks to create different types of apps, you learn Reframe once to be able to create all types of apps.

Customization

Reframe is desgined with easy customization in mind, and the following examples are easy to achieve:

  • Custom server
    • Add server routes to add RESTful/GraphQL API endpoints, authentication endpoints, etc.
    • Custom hapi server config. (Reframe uses the hapi server framework by default.)
    • Use a process manager such as PM2.
    • etc.
  • Custom browser JavaScript
    • Add error logging, analytics, etc.
    • Full control of the hydration process.
    • etc.
  • Custom transpiling & bundling
    • Reframe can be used with almost any webpack config. (Reframe assumes pretty much nothing about the webpack config.)
    • TypeScript support
    • CSS preprocessors support, such as PostCSS, SASS, etc.
    • etc.
  • Custom routing library. (Reframe uses React Router by default.)
  • Custom view library such as Preact.

No Lock-in

Strictly speaking, this section should be called "Minimal Lock-in": When you write page configs (which only Reframe understands) then you are locking yourself into Reframe. But Reframe is designed to keep lock-in characteristics to a minimum, and to make the experience of escaping Reframe as smooth as possible.

Every framework can be escaped from but the escape-cost varies dramatically.

The escape-cost is measured by two criteria:

  1. How much code can be re-used after the escape?
  2. How progressively can the framework be escaped?

For example, if no code can be re-used after escaping a framework, then that framework is very bad from a lock-in perspective.

Let's measure Reframe's escape-cost.

Code re-use after escape

Reframe is built on top of widely-used and high-quality "Do One Thing and Do It Well" (DOTADIW) libraries such as webpack, hapi, React, React Router, etc. This means that you can re-use lot's of code after escaping Reframe: For example, if you escape from Reframe but still plan on using a hapi server, then your server code can be re-used after the escape.

In general, all code that your write against one of these DOTADIW library is code that you can use independently of Reframe.

Most of your code can be re-used after escaping Reframe.

When worrying about framework lock-in, one of the main question to ask yourself is "am I writing code against an interface introduced by the framework or against an interface of a DOTADIW library?". If the latter is the case, then you are not locking yourself into the framework.

Progressive escape

Reframe consists of three packages:

  • @reframe/build that transpiles code and bundles browser assets.
  • @reframe/server that creates and manages the server.
  • @reframe/browser that handles the browser side and hydrates the page.

Each of these packages can be replaced with code of your own.

For example, you can replace Reframe's server @reframe/server with your own server implementation. At that point, you have full control over the server, while still using the rest of Reframe.

Similarly, if you replace Reframe's build @reframe/build with your own build implementation, then you have full control over the build step, while still using the rest of Reframe.

Reframe can be escaped in a progressive manner.

By replacing all these three packages with code of your own, you effectively escape Reframe entirely.

The Customization Manual explains how to escape from these three packages and includes examples such as:

  • Entirely custom build using Rollup (instead of webpack and by getting rid of @reframe/build).
  • Entirely custom server using Express (instead of hapi and by getting rid of @reframe/server).
  • Entirely custom browser-side code (by getting rid of @reframe/browser).
Meteor, the counter example

On the other side of the escape-cost spectrum, there is Meteor.

It consists of many parts that you can only use if you use the entire Meteor stack. For example, you can use its build system only if you use Meteor in its entirely. This means that, if you want to escape Meteor, you will have to re-write all code related to these works-only-with-Meteor parts.

And it has not been designed with loosely coupled parts; With Meteor, it's mostly either all-in or all-out. In other words, you can't escape in a progressive manner.

Performance

  • Code splitting
    Every page loads two scripts: A script that is shared and cached accross all pages (which includes common code such as React and polyfills) and a another script that includes the React components of the page. This means that a KB-heavy page won't affect the KB-size of the other pages.
  • Optimal HTTP caching
    Every dynamic server response is cached with a Etag header, and every static server response is indefinitely cached. (Static assets are served under hashed URLs with the Cache-Control header set to immutable and max-age's maximum value.)
  • DOM-static pages
    A page can be configured to be rendered only on the server. These pages are faster to load as the page isn't hydrated and React is not loaded in the browser.
  • Partial DOM-dynamic pages
    A page can be configured so that only certain parts of the page are hydrated. This makes the hydration of the page quicker and less JavaScript is loaded in the browser. (Only the React components of the hydrated parts are loaded.)
  • HTML-static pages
    A page can be configured to be rendered to HTML at build-time instead of request-time. In other words, the page is rendered to HTML only once, when Reframe is building the frontend. The HTML is statically served and the load time is decreased.
  • SSR
    All pages are rendered to HTML before being hydrated, decreasing the (perceived) load time.

Reframe Project Scope

Reframe takes care of:

  • Building
    Reframe transpiles and bundles the browser static assets. (Uses webpack.)
  • Server
    Reframe sets up a Node.js server serving the browser static assets and your pages' HTML. (Uses hapi.)
  • Routing
    Reframe routes URLs to your pages.

Reframe doesn't take care of:

  • View logic / state management
    It's up to you to manage the state of your interactive views (or use Redux / MobX).
  • Database
    It's up to you to create, populate, and query databases. (You can add API endpoints to the hapi server that Reframe creates.)

Plugins

Languages
Routing
Kits
View renderers

Quick Start

Let's create a React app with Reframe.

  1. We create a pages/ directory:
mkdir -p ~/tmp/reframe-playground/pages
  1. Then, we create a new JavaScript file at ~/tmp/reframe-playground/pages/HelloWorldPage.html.js that exports the following page config:
import React from 'react';

const HelloWorldPage = {
    route: '/',
    view: () => (
        <div>
            Hello World, from Reframe.
        </div>
    ),
};

export default HelloWorldPage;
  1. We install Reframe's CLI and React:
npm install -g @reframe/cli
cd ~/tmp/reframe-playground/
npm install react
  1. And finally, we run the CLI:
cd ~/tmp/reframe-playground/
reframe

which prints

$ reframe ~/tmp/reframe-playground/pages
✔ Page directory found at ~/tmp/reframe-playground/pages
✔ Frontend built at ~/tmp/reframe-playground/dist/browser/ [DEV]
✔ Server running at http://localhost:3000

Our page is now live at http://localhost:3000.

And that's it: We have created a web app by simply creating one React Component and one page config.

The "Basic Usage" section of the Usage Manual contains further information, including:

  • How to add CSS and static assets.
  • How to link pages.
  • How to configure pages that need to (asynchronously) load data.
  • How to configure pages to be DOM-dynamic or DOM-static, and HTML-static or HTML-dynamic.