pragmatic-view
TypeScript icon, indicating that this package has built-in type declarations

1.0.0 • Public • Published

Under development. Current build might be unstable.

SymfonyInsight

npm node npm Coverage Status Gitlab pipeline status (branch)

PragmaticView

PragmaticView

JSX and TSX template engine for server-side Node.JS apps inspired by React and by .NET platform.

Typed for Typescript

npm install --save pragmatic-view

Why?

React brought awesome JSX syntax. JSX has become one of the most convenient ways to write HTML-like syntax in JavaScript. Due to it's deep interconnection with React, JSX has never become standalone language feature. This framework borrows React's JSX regarding the syntax while providing own functionality and features. Some part are inspired by ASP.NET. Eventually this framework will provide more ASP.NET like features such as server-side handlers.

Getting started

Checkout documentation

PragmaticView is usable together with transpiler like Babel or TSC (even TS-NODE and babel/register). These transpilers are set to work with React by default. In order to make PragmaticView a new pragma has to be set. Pragma is a function that JSX is transpiled to. In TypeScript it's called jsxFactory. PragmaticView also provides support for custom file extension ".pv" in order to be distinguished from JSX files created for React. Using ".pv" it's not mandatory and usually it's easier to write views in ".jsx" or ".tsx" files.

// With Babel (for TSC checkout documentation)
{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "pragma": "View", // <- PragmaticView's pragma
    }]
  ]
}

// Standalone - place register method before any template imports
ViewEngine.register(path.resolve(__dirname, './templates'));

import Document from "./templates/document.jsx";

PragmaticView can be used without any transpiler too. It utilizes the same trick that TS-NODE or Babel register does. ViewEngine class offers static method that adds a hook to Node's require method. This hook takes care of transpilation whenever a file is imported. In order not to confuse JSX meant for PragmaticView with JSX meant for React the method takes path as an argument. Only files imported form given path (and sub folders) are transpiled by PragmaticView.

// ES6
import { View } from from "pragmatic-view";
// CommonJS
const View = require("pragmatic-view").View;

/* Content */

Similarly to React, pragma function has to be always imported in the file where components are declared and used. React uses name "React" for it's pragma whereas PragmaticView uses name "View".

Main Parts

PragmaticView consists of four major parts: ViewEngine, ViewBuilders, Components.

ViewEngine is the heart of the application. It holds configuration, caches and other features (coming soon). The main objective of ViewEngine is to generate ViewEngines on demand. There is usually one ViewEngine per application (however, there is no limit).

// ES6
import { ViewEngine } from "pragmatic-view";
// CommonJS
const ViewEngine = require("pragmatic-view").ViewEngine;

let viewEngine = new ViewEngine();

ViewBuilders are used to generate HTML strings from components. There are generated by ViewEngine for each rendering (usually one per http request). ViewEngine has method dedicated to generating instances of ViewBuilders. They inherit config and/or layout components from parent ViewEngine. Layouts can be changed specifically on the instance of ViewBuilder thus overriding inherited one. ViewBuilder also consumes context object that contains values that will be used by components. Context is exposed to every single component as it's second argument (first argument is props).

// ES6
import { View } from from "pragmatic-view";
// CommonJS
const View = require("pragmatic-view").View;

const MyDocument = (props, context) => <div>Hello world!</div>;

let context = {
    path: "/foo/bar"
};

let viewBuilder = viewEngine.getBuilder(); // Contextless
let viewBuilder = viewEngine.getBuilder(context); // Contextwise
viewBuilder.root = MyDocument; // Passing root component after initialization

let viewBuilder = viewEngine.getBuilder(MyDocument); // Contextless
let viewBuilder = viewEngine.getBuilder(MyDocument, context); // Contextwise

As soon as ViewBuilder is fed all necessary data .toString() method can be invoked. String in promise is returned. Promises are returned as Class Component can contain async function that needs to be awaited.

// Inside async function
let html = await viewBuilder.toString();
console.log(html);

// Outside async function
viewBuilder.toString().then(html => console.log(html));

// logs: '<div>Hello World!</div>'

ViewEngine options

{
    beautifyOutput?: boolean,
    beautifyOptions?: HTMLBeautifyOptions,
    logger?: (message: string) => void,
    logRenderTime?: boolean,
    layout?: Component
}

More about options

Components

More about Components

Function Component

Very simple component that contains no advanced logic.

// MyDocument.tsx
import { View } from from "pragmatic-view";
import BreadCrumbs from "./BreadCrumbs";

export const MyDocument = (props, context) => {
    return (
        <html>
            <head>
                <title>My awesome page!</title>
            </head>
            <body>
                <BreadCrumbs className="bread-crumbs" />
                <h1>Welcome to my awesome page!</h1>
            </body>
        </html>
    );
}

Class Component

Powerful building block that can perform async operations such as database calls.

// BreadCrumbs.tsx
import { View } from from "pragmatic-view";

export class BreadCrumbs extends View.Component<{ className?: string }, { path: string }> {

    private slugs: string[];

    async onInit() {
        this.slugs = this.context.path.split('/');
        console.log('I am at path ' + this.context.path);
    }

    render() {
        return (
            <div className={this.props.className}>
                {this.slugs.map(slug => <span>{slug}</span>)}
            </div>
        )
    }

    async onRender(html: string) {
        return '<section>' + html + '</section>';
    }
}

Exotic Components

Exotic components are helpers exposed on View object.

Fragment

Fragments are quite similar to React's. They serve as wrappers around multiple sibling components without creating any additional markup.

import { View } from "pragmatic-view";

let Comp = () => {
    return (
        <View.Fragment>
            <h1>Inside fragment!</h1>
            <p>Fragment does not render in any kind of tag!</p>
        </View.Fragment>
    );
}

Layouts & Placeholders

Layouts are components that contain Placeholder component. Layouts are passed to ViewEngine or ViewBuilder as additional property. Placeholder component is replaced by rendered HTML of the root component.

import { View } from "pragmatic-view";

let layout = () => <html>
    <head>
        <title>Awesome layout!</title>
    </head>
    <body>
        <View.Placeholder/>
    </body>
</html>

// In ViewEngine
let config = {
    layout: layout
};

let viewEngien = new ViewEngine(config);

// In ViewBuilder
let viewBuilder = viewEngine.getBuilder(pageComponent);
viewBuilder.layout = layout;

More about layouts

Package Sidebar

Install

npm i pragmatic-view

Weekly Downloads

9

Version

1.0.0

License

MIT

Unpacked Size

194 kB

Total Files

239

Last publish

Collaborators

  • deathrage