pragmatic-view
v1.0.0
Published
JSX and TSX templating engine for node applications inspired by React and .NET
Downloads
15
Maintainers
Readme
Under development. Current build might be unstable.
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
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
}
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;