als-render
v2.2.0
Published
Lightweight JS library for transforming JSX into raw HTML, enabling both server-side and client-side rendering without a virtual DOM. It offers a simplified component model with automated event handling and global state management.
Downloads
460
Maintainers
Readme
als-render Documentation
1. Introduction
als-render
is a frontend framework that offers a comprehensive solution for building dynamic components (components with dynamic state and hooks), importing them, integrating them with each other, with the ability to use a single global context, and with the ability to use a JSX-like syntax.
The framework works on the fly both in the browser and on the server, offering SSR on the fly and allowing you to use the same files both on the server and in the browser.
On the server, you can connect the browser part via routes in Express and do it on the fly.
The framework also has the ability to localize for translation into pre-defined languages.
What's New in Version 2.1.0:
Enhanced Rendering Flexibility:
- Added support for custom parameters, scripts to execute before rendering, and scripts to execute after rendering.
- Implementation includes a new
render
method and additional configuration options in the constructor. - Separate configurations for server-side rendering and bundling.
Browser-Specific Enhancements:
- Introduced support for custom parameters, pre-render scripts, and post-render scripts specifically for the browser version.
Backward Compatibility:
- The
build
method remains unchanged to ensure full backward compatibility.
- The
2. Installation and Setup
Installation
You can install als-render
via npm or use a CDN for quick testing in the browser:
- npm (recommended for full development):
npm install als-render
- CDN (for browser usage):
Requirements
- Browser: ES6 support is required.
- Node.js: Compatible with all Node.js versions supporting ES6.
Quick Start
Components example
App.js:
const Counter = require('./Counter');
class App extends Component {
constructor(props) { super(props)}
render(props) {
const { data } = props;
return (
<div>
<Counter count={data.count} />
</div>
);
}
}
module.exports = App;
Counter.js:
class Counter extends Component {
constructor(props) { super(props)}
render({ count }) {
return (
<div>
<button onclick={() => this.update({ count: count + 1 })}>
{ 'Increase' }
</button>
<span>{ count }</span>
<button onclick={() => this.update({ count: count - 1 })}>
{ 'Decrease' }
</button>
</div>
);
}
}
module.exports = Counter;
Browser Example
Here’s a minimal example of using als-render
in the browser to create a counter component:
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://www.unpkg.com/[email protected]/render.min.js"></script>
<title>Counter</title>
</head>
<body>
<div component="App"></div>
</body>
<script>
render('./App', { count: 0 });
</script>
</html>
Node.js Example (Server-Side Rendering)
You can use the same components for server-side rendering (SSR). Here’s an example:
Server Code:
const Render = require('als-render');
const fs = require('fs');
const rendered = new Render('./App');
const rawHtml = rendered.build({ count: 0 });
const html = `<!DOCTYPE html>
<html lang="en">
<head><title>Counter</title></head>
<body>${rawHtml}</body></html>
`
fs.writeFileSync('./counter.html', rawHtml, 'utf-8');
Example with Express
For dynamic SSR integration with Express:
const express = require('express');
const Render = require('als-render');
const app = express();
const rendered = new Render('./App');
Render.publish(app, express); // Adds bundle and public routes.
app.get('/', (req, res) => {
const html = rendered.build({ count: 0 }, 'ru');
res.send(`<!DOCTYPE html>
<html lang="en">
<head><title>Counter</title></head>
<body>${html}</body>
</html>`);
});
app.listen(3000, () => console.log('Server is running on http://localhost:3000'));
In the browser: The server dynamically generates the same HTML structure as the browser example and serves it, including all component functionalities.
Components
als-render
uses als-component
to create reactive components that work seamlessly in both server and browser environments. Components support lifecycle hooks, event handling, and state management.
For detailed information on component usage, refer to the als-component documentation.
By default the Component class available as global variable for rendered components.
It can be used like this:
class App extends Component {
constructor(props) { super(props) }
render({ count = 0 }) {
return `
<div>
<button click="${this.action('click', () => this.update({ count: count - 1 }))}">-</button>
<span>${count}</span>
<button click="${this.action('click', () => this.update({ count: count + 1 }))}">+</button>
</div>
`;
}
}
Or like this if options.jsx = true
(true by default):
class App extends Component {
constructor(props) { super(props) }
render({ count = 0 }) {
return (
<div>
<button onclick={() => this.update({ count: count - 1 })}>-</button>
<span>{count}</span>
<button onclick={() => this.update({ count: count + 1 })}>+</button>
</div>
);
}
}
The Component
has hooks mount and unmount and onload event:
this.on('mount', () => console.log('Component mounted'));
this.on('unmount', () => console.log('Component unmounted'));
3.2 Localization
als-render
includes built-in localization support through a global _()
function, available in every component. This function retrieves the appropriate translation for the current language.
Example:
Define your localization settings:
const langs = {
langs: ['en', 'ru'],
dictionary: {
Increase: ['Increase', 'Увеличить'],
Decrease: ['Decrease', 'Уменьшить']
}
}
const options = { langs,lang: 'ru'};
In your component:
class Counter extends Component {
render({ count }) {
return (
<div>
<button>{ _('Increase') }</button>
<span>{ count }</span>
<button>{ _('Decrease') }</button>
</div>
);
}
}
In browser:
<script>
render('./App', {count:0}, { langs,lang: 'ru'});
</script>
On server:
const rendered = new Render('./App', {langs});
const lang = 'ru';
const rawHtml = rendered.build({ count: 0 }, lang);
Global variables
By default, there are few global variables which available for all components:
context
- is the object which passed from outside and available as global for all componentspublics
- The object for adding static public files or directories right grom componentsroutes
- The object for adding routes with string content to add asapp.get(url,(req,res) => res.send(content))
isBrowser
- true in browser and false in NodeJs
_
- the localization function as showed above- Custom variables added by developer
context
const rendered = new Render('./App');
const context = {some:'Value for node'}
const browserContext = {some:'Value for browser'}
const rawHtml = rendered.build({ count: 0 },context,browserContext);
class App extends Component {
constructor(props) { super(props) }
render({ count = 0 }) {
if(context.isBrowser) {
const existCount = localstorage.getItem('count')
if(existCount == undefined) localstorage.setItem('count',count)
else this.props.count = Number(existCount)
}
return (
<div>
{context.some}
<button onclick={() => this.update({ count: this.props.count - 1 })}>-</button>
<span>{this.props.count}</span>
<button onclick={() => this.update({ count: this.props.count + 1 })}>+</button>
</div>
);
}
}
context.publics
and context.routes
The context.publics
available both on browser and nodejs versions, but used only in nodejs.
context.publics['/app/styles.css'] = './styles.css'
context.routes['main-style.css'] = `body {margin:0}`
class App extends Component {
...
}
const rendered = new Render('./App');
Render.publish(app, express); // Adds bundle and public routes.
const rawHtml = rendered.build({});
Custom variables and scripts
New renderOptions
and bundleRenderOptions
in Constructor
Updated Constructor Interface
const renderInstance = new Render('./components/App', {
langs: { langs: ['ru'], dictionary: { hello: ['привет'] } },
includebundle: true,
renderOptions: {
parameters: [], // Additional parameters for server-side rendering
scriptBefore: '', // Scripts executed before server-side rendering
scriptAfter: '' // Scripts executed after server-side rendering
},
bundleRenderOptions: {
parameters: [], // Additional parameters for the browser bundle
scriptBefore: '', // Scripts executed before the browser bundle
scriptAfter: '' // Scripts executed after the browser bundle
}
});
Parameters
The parameters should be strings and can include default value, like ['some="test"',now=Date.now()]
.
Behavior of scriptBefore
and scriptAfter
scriptBefore
- script for execution before requiring files.- For example
const SOME_TOKEN="token";
. Now SOME_TOKEN available anywhere in rendered code.
- For example
scriptAfter
- script for execution after requiring files.- For example you have access to
modules
andresult
(MainComponent class) variables.
- For example you have access to
!imprtant Server bundle function builded twice to get publics and routes. On first run custom parameters are not available and scriptBefore and scriptAfter can throw error
Method render
instead build
render(data, lang, options = {}) {
const {
context = {}, // Context for server-side rendering
browserContext = {}, // Context for browser-side rendering
parameters = [], // Parameters for server-side rendering
bundleParameters = [] // Parameters for browser-side rendering (must be strings)
} = options;
}
parameters
- can be any js parameter from server. Even request and response variables.bundleParameters
- must be strings (e.g., throughJSON.stringify
) because they are passed to the browser bundle.
Example
const renderInstance = new Render('./components/App', {
langs: { langs: ['ru'], dictionary: { hello: ['привет'] } },
includebundle: true,
renderOptions: {
parameters: ['customParam'],
scriptAfter: 'if(customParam) customParam.test = true;'
},
bundleRenderOptions: {
parameters: ['customBundleParam'],
scriptAfter: 'customBundleParam.test = true;'
}
});
const customParam = { test: false };
const customBundleParam = { test: false };
const result = renderInstance.render(
{ key: 'value' },
'ru',
{
context: { user: 'test' },
browserContext: { browser: true },
parameters: [customParam],
bundleParameters: [JSON.stringify(customBundleParam)]
}
);
console.log(customParam.test); // true
console.log(result.includes('customBundleParam.test = true')); // true
SSR
There are few options for server side rendering.
- Don't include bundle for browser (set options.includebundle = false)
- Include bundle for browser (set options.includebundle = true, or leave it, cause it's true by default)
- Include bundle for browser as script inside page (default option)
- This option adds js code directly to html which encreasing the page size for download
- Include bundle as Express route
- This option, adds route to express routes (app.get...) and use bundle as script[src]
- Recomended for production, because bundle cached by browser
- You can add version to bundle in (options.version) for renewing the cache
- Include bundle for browser as script inside page (default option)
Example:
const express = require('express');
const app = express();
const Render = require('als-render');
Render.env.bundleAsRoute = true
const rendered = new Render('./App',{ includebundle:true, version:'1.0.0' });
Render.publish(app, express); // Adds bundle and public routes.
Configuration Options
Render Class Interface
class Render {
static Require: typeof Require;
static publics: Record<string, string>; // Static public files or folders
static routes: Record<string, string>; // Static routes for Express
static env: {
bundleAsRoute: boolean; // Add bundle as route (default: false)
minify: boolean; // Minify the bundle (default: false)
jsx: boolean; // Enable JSX (default: true)
bundleName: string; // Name of the bundle function (default: 'bundleFn')
};
static publish(app: Express, express: typeof import('express')): void;
constructor(path: string, options?: {
includebundle?: boolean; // Include bundle in the result (default: true)
langs?: { langs: string[], dictionary: Record<string, string[]> };
defaultLang?: string; // Default language for `_()` (optional)
cyclicDependencies?: boolean; // Allow cyclic dependencies (default: false)
version:undefined; // Adds ?version={version} to bunlde as src
renderOptions: {
parameters?: string[], // Additional parameters for server-side rendering
scriptBefore?: string='', // Scripts executed before server-side rendering
scriptAfter?: string='' // Scripts executed after server-side rendering
},
bundleRenderOptions: {
parameters?: string[], // Additional parameters for the browser bundle
scriptBefore?: string='', // Scripts executed before the browser bundle
scriptAfter?: string=''// Scripts executed after the browser bundle
}
});
build(data: Record<string, any>, lang: string, context?: Record<string, any>, browserContext?: Record<string, any>): string;
render(data: Record<string, any>, lang: string, options = {}) {
const {
context = {}, // Context for server-side rendering
browserContext = {}, // Context for browser-side rendering
parameters = [], // Parameters for server-side rendering
bundleParameters = [] // Parameters for browser-side rendering (must be strings)
} = options;
}
}
Browser render() Function Interface
async function render(
path: string,
data?: Record<string, any>,
options?: {
selector?: string; // CSS selector for root component
version?: string; // Cache-busting version string (optional)
jsx?: boolean; // Enable JSX syntax (default: true)
context?: Record<string, any>; // Global context object (default: {})
langs?: { langs: string[], dictionary: Record<string, string[]> }; // Localization settings
lang?: string; // Current language for `_()`
cyclicDependencies?: boolean; // Allow cyclic dependencies (default: false)
logger?: Console; // Logger for debugging (default: console)
parameters = {}; // must be {key:value}
scriptBefore = '';
scriptAfter = '';
}
): Promise<void>;