# Renderer

Architecture In Genesis, the core is the renderer, which provides the most basic rendering capabilities. With it, you can implement micro frontend, micro service, remote components, first screen rendering, and even use with React, EJS, etc.

# How can it collaborate with you?

-If you are using traditional back-end rendering, you need to do SEO, but you want to introduce Vue in some layouts and some pages, then renderer.renderJson() is enough to pass the rendering results to the template engine for back-end rendering.

  • If you are a mid-to-back-end system and the business systems are all concentrated in one project, you want to be able to split the services according to the business, then <remote-view :fetch="fetch" /> sufficient
  • If you are a CSR rendering project, then renderer.renderHtml({ mode: 'csr-html' }) sufficient
  • If you are a SSR rendering project, then renderer.renderHtml({ mode: 'ssr-html' }) sufficient

If you want to be a micro frontend, micro service, then renderer is inherently equipped with such capabilities, you can use it as a tool function, you can access your service through various protocols such as HTTP, RPC, etc. Then use it for rendering

# Create a Renderer

# Development Environment

import { SSR } from '@fmfe/genesis-core';
import { Watch } from '@fmfe/genesis-compiler';

const start = async () => {
    const ssr = new SSR();
    const watch = new Watch(ssr);
    await watch.start();
    const renderer = watch.renderer;
    // After getting the renderer, do something ....
};

start();

If you are creating a project for the first time, the program will automatically create a basic Vue template in the project root directory.

.
├── src
│   ├── app.vue           Entrance page
│   ├── entry-client.ts   Client entry file
│   ├── entry-server.ts   Server entry file
│   └── shims-vue.d.ts    TS statement for vue file
│ 
└── package.json

Because in the actual development environment, we still need static resource files and hot updates, the watch object also provides corresponding middleware use, if you use the Express framework, you can directly use

/**
 * Static resource middleware
 */
app.use(watch.devMiddleware);
/**
 * Hot update middleware
 */
app.use(watch.hotMiddleware);

If you use Koa, or other frameworks, you need to wrap a layer of middleware on the corresponding framework.

# Production Environment

After the development is completed, the production environment needs to be released. We always need to compile the code in advance, so that when the user visits, it can be quickly rendered quickly, so the first step we need to compile the code first

import { SSR } from '@fmfe/genesis-core';
import { Build } from '@fmfe/genesis-compiler';

const start = () => {
    const ssr = new SSR();
    const build = new Build(ssr);
    return build.start();
};

start();

note

Here you need to set the environment variable NODE_ENV to production, otherwise the compiled code is in development mode, and the runtime performance will be very poor.

NODE_ENV=production ts-node index.ts

After executing the above compile command, we will get a dist directory where our compiled code is placed. If you want to change the address of the compilation output, or the name of the application, you can click here to learn more

.
├── dist
│   ├── ssr-genesis                           Application Name
│   │   ├── client                            Client resource files
│   │   |   ├── js                            script
│   │   |   ├── css                           style
│   │   |   ├── images                        image
│   │   |   ├── medias                        media resources
│   │   |   └── fonts                         Font file
│   │   ├── server                            Server resource file
│   │   │   ├── vue-ssr-client-manifest.json  Client build manifest
│   │   │   └── vue-ssr-server-bundle.json    Server application package
│ 
└── package.json

After the code is built, we can create a renderer directly in the production environment.

import { SSR } from '@fmfe/genesis-core';

const start = async () => {
    const ssr = new SSR();
    const renderer = ssr.createRenderer();
    // After getting the renderer, do something...
};

start();

In the production environment, static resources are based on the file name generated by the content hash, so when setting a static directory here, you can set a strong cache

app.use(
    renderer.staticPublicPath,
    express.static(renderer.staticDir, {
        immutable: true,
        maxAge: '31536000000'
    })
);

# Use of renderer

So far, whether it is a development environment or a production environment, we have already got a renderer, and then we can use the renderer to do some things.

# Rendering method

renderer.render().then((result) => {
    console.log(result.data);
});

In the default case, equivalent to the following

renderer.render({ url: '/', mode: 'ssr-html' }).then((result) => {
    console.log(result.data);
});

For more options about rendering methods, click here to learn The renderer.render method is the bottom-most method of the renderer. The following functions are encapsulated based on it.

# Rendering middleware

If your business is relatively simple, you can directly develop through our middleware, it is just a simple SSR middleware.

note

If SSR rendering fails, the middleware will not help you downgrade to CSR

app.use(renderer.renderMiddleware);

# Render HTML

const result = await renderer.renderHtml();
console.log(result);

# Render JSON

const result = await renderer.renderJson();
console.log(result);

# Downgrade rendering

For a better user experience, when SSR rendering fails, we expect it to be downgraded to CSR mode. We can wrap a layer of the rendering method and print out the error message. You can even use some monitoring tools to push to your mailbox and SMS for alarm.

const render = (options: RenderOptions = {}) => {
    return renderer.render(options).catch((err: Error) => {
        // Print error message of failed rendering
        console.error(err);
        const mode: RenderMode = options.mode || 'ssr-html';
        return renderer.render({
            ...options,
            mode: mode.indexOf('html') ? 'csr-html' : 'csr-json'
        });
    });
};
const result = await render();
console.log(result.data);

# Use routing

When calling the rendering function, pass in the address and routing mode to be rendered, because when using a remote component, we may not want this component to use historical mode rendering, or may use abstract mode rendering, it is best to use it Make dynamic parameters to control.

const result = await render({ url: '/', state: { routerMode: 'history' } });
console.log(result.data);

note

vue-router does not support creating multiple historical route instances on one page, otherwise when you call the router.push() method, it will create multiple History, in order to solve this problem, please use the route of genesis-app

# router.ts

Newly added routing configuration file, learn more click here

npm install vue-router
import Vue from 'vue';
import Router, { RouterMode } from 'vue-router';

Vue.use(Router);

export const createRouter = (mode: RouterMode = 'history') => {
    return new Router({
        mode: mode,
        routes: [
            // Configure your route
        ]
    });
};

# entry-server.ts

Modify our server entry file

import { RenderContext } from '@fmfe/genesis-core';
import Vue from 'vue';
import App from './app.vue';
import { createRouter } from './router';

export default async (renderContext: RenderContext): Promise<Vue> => {
    // Read the passed route pattern
    const mode = renderContext.data.state.routerMode;
    // Create route
    const router = await createRouter(mode);
    // Set the rendering address
    await router.push(renderContext.data.url);
    // Create a Vue instance
    return new Vue({
        // Incoming routing object
        router,
        renderContext,
        render(h) {
            return h(App);
        }
    });
};

# entry-client.ts

Modify our client entry file

import { ClientOptions } from '@fmfe/genesis-core';
import Vue from 'vue';
import App from './app.vue';
import { createRouter } from './router';

export default async (clientOptions: ClientOptions): Promise<Vue> => {
    // Read the routing pattern delivered by the server
    const mode = clientOptions.state.routerMode;
    // Create route
    const router = await createRouter(mode);
    // Set the rendering address
    await router.push(clientOptions.url);
    // Create a Vue instance
    return new Vue({
        // Incoming routing object
        router,
        clientOptions,
        render(h) {
            return h(App);
        }
    });
};

# app.vue

Modify our view file to support route rendering

<template>
    <div class="app">
        <h2>Hello world!</h2>
        <p v-if="show" @click="close" class="text">
            {{ installed ? 'The client should be installed successfully, click me to close!' : 'Not Installed' }}
        </p>
        <router-view />
    </div>
</template>

# Summarize

The above tutorials teach you how to use some basic renderers. With it, you can use it with various server frameworks. This tutorial, here provides a complete demo, click here to learn more.