App Layout
Installation
npm install --save-dev @gravity-ui/app-layout
Utilisation
Avec express
:
import express from 'express';
import {createRenderFunction} from '@gravity-ui/app-layout';
const app = express();
const renderLayout = createRenderFunction();
app.get('/', function (req, res) {
res.send(
renderLayout({
// RenderParams
title: 'Page d\'accueil',
bodyContent: {
root: 'Bonjour le monde !',
},
}),
);
});
app.listen(3000);
où
interface RenderParams<Data, Plugins> {
// Toutes données compatibles JSON, sera défini sur window.__DATA__ sur la page
data?: Data;
// favicon
icon?: Icon;
// nonce à définir sur les balises appropriées
nonce?: string;
// options communes
// Titre de la page
title: string;
// langue de la page, sera défini sur la balise html
lang?: string;
isMobile?: boolean;
// attributs html
htmlAttributes?: string;
// contenu de la balise header
// balises meta
meta?: Meta[];
// balises link
links?: Link[];
// balises script
scripts?: Script[];
// feuilles de style
styleSheets?: Stylesheet[];
// balises script avec code inline
inlineScripts?: string[];
// balises style avec styles inline
inlineStyleSheets?: string[];
// contenu de la balise body
bodyContent?: {
// nom de classe pour la balise body
className?: string;
// attributs de la balise body
attributes?: string;
// contenu du body avant la balise div avec id root
beforeRoot?: string;
// contenu innerHtml de la balise div avec id root
root?: string;
// contenu du body après la balise div avec id root
afterRoot?: string;
};
// options des plugins
pluginsOptions?: Partial<PluginsOptions<Plugins>>;
}
Meta
Décrit la balise meta
:
interface Meta {
name: string;
content: string;
}
Exemple :
const meta = [
{name: 'description', content: 'quelque texte'},
{name: 'robots', content: 'noindex'},
{name: 'og:title', content: 'Un titre'},
];
Sera rendu comme :
<meta name="description" content="quelque texte" />
<meta name="robots" content="noindex" />
<meta property="og:title" content="Un titre" />
Icon
Décrit le favicon de la page :
interface Icon {
type?: string;
sizes?: string;
href?: string;
}
La valeur par défaut est :
const icon = {
type: 'image/png',
sizes: '16x16',
href: '/favicon.png',
};
Links
Décrit la balise link
:
interface Link {
as?: string;
href: string;
rel?: string;
type?: string;
sizes?: string;
title?: HTMLLinkElement['title'];
crossOrigin?: '' | 'anonymous' | 'use-credentials';
}
Exemple :
const link = {
href: 'myFont.woff2',
rel: 'preload',
as: 'font',
type: 'font/woff2',
crossOrigin: 'anonymous',
};
sera rendu comme :
<link href="myFont.woff2" rel="preload" as="font" type="font/woff2" crossorigin="anonymous" />
Scripts
Décrit le lien vers un script avec préchargement :
interface Script {
src: string;
defer?: boolean;
async?: boolean;
crossOrigin?: '' | 'anonymous' | 'use-credentials';
type?: 'importmap' | 'module' | string;
}
Exemple :
const script = {
src: 'url/to/script',
defer: true,
async: false,
crossOrigin: 'anonymous',
};
sera rendu comme :
<link href="url/to/script" rel="preload" as="script" crossorigin="anonymous" />
<script src="url/to/script" defer="true" async="false" crossorigin="anonymous" nonce="..."></script>
Feuilles de style
Décrivent le lien vers les styles :
interface Stylesheet {
href: string;
}
Exemple :
const styleSheet = {
href: 'url/to/stylesheet',
};
sera rendu comme :
<link href="url/to/stylesheet" rel="stylesheet" />
Plugins
La fonction de rendu peut être étendue par des plugins. Un plugin peut réécrire le contenu de rendu défini par l'utilisateur.
Un plugin est un objet avec les propriétés name
et apply
:
interface Plugin<Options = any, Name = string> {
name: Name;
apply: (params: {
options: Options | undefined; // passé via la fonction `renderLayout` dans le paramètre `pluginsOptions`.
commonOptions: CommonOptions;
renderContent: RenderContent;
/** @deprecated utiliser `renderContent.helpers` à la place */
utils: RenderHelpers;
}) => void;
}
interface CommonOptions {
name: string;
title: string;
lang?: string;
isMobile?: boolean;
}
export interface HeadContent {
scripts: Script[];
helpers: RenderHelpers;
links: Link[];
meta: Meta[];
styleSheets: Stylesheet[];
inlineStyleSheets: string[];
inlineScripts: string[];
title: string;
}
export interface BodyContent {
attributes: Attributes;
beforeRoot: string[];
root?: string;
afterRoot: string[];
}
export interface RenderContent extends HeadContent {
htmlAttributes: Attributes;
bodyContent: BodyContent;
}
export interface RenderHelpers {
renderScript(script: Script): string;
renderInlineScript(content: string): string;
renderStyle(style: Stylesheet): string;
renderInlineStyle(content: string): string;
renderMeta(meta: Meta): string;
renderLink(link: Link): string;
attrs(obj: Attributes): string;
}
Il existe quelques plugins dans ce package :
Google analytics
Ajoute un compteur Google Analytics sur la page.
Utilisation :
import {createRenderFunction, createGoogleAnalyticsPlugin} from '@gravity-ui/app-layout';
const renderLayout = createRenderFunction([createGoogleAnalyticsPlugin()]);
<div class="language-selector">
<a href="/README.md">English</a>
<a href="/README.fr.md">Français</a>
</div>
app.get((req, res) => {
res.send(
renderLayout({
title: 'Page d\'accueil',
pluginsOptions: {
googleAnalytics: {
useBeaconTransport: true, // active l'utilisation de navigator.sendBeacon
counter: {
id: 'un identifiant',
},
},
},
}),
);
});
Options du plugin :
interface GoogleAnalyticsCounter {
id: string;
}
interface GoogleAnalyticsOptions {
useBeaconTransport?: boolean;
counter: GoogleAnalyticsCounter;
}
Yandex Metrika
Ajoute les compteurs de métriques Yandex sur la page.
Utilisation :
import {createRenderFunction, createYandexMetrikaPlugin} from '@gravity-ui/app-layout';
const renderLayout = createRenderFunction([createYandexMetrikaPlugin()]);
app.get((req, res) => {
res.send(
renderLayout({
title: 'Page d\'accueil',
pluginsOptions: {
yandexMetrika: {
counter: {
id: 123123123,
defer: true,
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
},
},
},
}),
);
});
Options du plugin :
export type UserParams = {
[x: string]: boolean | string | number | null | UserParams;
};
export interface MetrikaCounter {
id: number;
defer: boolean;
clickmap: boolean;
trackLinks: boolean;
accurateTrackBounce: boolean | number;
webvisor?: boolean;
nonce?: string;
encryptedExperiments?: string;
triggerEvent?: boolean;
trackHash?: boolean;
ecommerce?: boolean | string;
type?: number;
userParams?: UserParams;
}
export type MetrikaOptions = {
src?: string;
counter: MetrikaCounter | MetrikaCounter[];
};
Layout
Ajoute les scripts et les styles du fichier manifest des assets webpack.
Utilisation :
import {createRenderFunction, createLayoutPlugin} from '@gravity-ui/app-layout';
const renderLayout = createRenderFunction([
createLayoutPlugin({manifest: 'path/to/assets-manifest.json', publicPath: '/build/'}),
]);
app.get((req, res) => {
res.send(
renderLayout({
title: 'Page d\'accueil',
pluginsOptions: {
layout: {
name: 'home',
},
},
}),
);
});
Options du plugin :
export interface LayoutOptions {
name: string;
prefix?: string;
}
@gravity-ui/uikit
Ajoute des attributs au corps de la page.
Utilisation :
import {createRenderFunction, createUikitPlugin} from '@gravity-ui/app-layout';
const renderLayout = createRenderFunction([createUikitPlugin()]);
app.get((req, res) => {
res.send(
renderLayout({
title: 'Page d\'accueil',
pluginsOptions: {
uikit: {
theme: 'dark',
direction: 'ltr',
},
},
}),
);
});
Options du plugin :
interface UikitPluginOptions {
theme: string;
direction?: 'ltr' | 'rtl';
}
Remote Versions
Ajoute les informations de version des micro-frontends à la page.
Ce plugin crée un objet global window.__REMOTE_VERSIONS__
contenant les versions des micro-frontends fournies, qui peut être utilisé par la fédération de modules ou des architectures de micro-frontends similaires pour déterminer quelles versions des modules distants charger.
Il peut être utilisé en combinaison avec App Builder et l'option moduleFederation.remotesRuntimeVersioning
pour charger automatiquement les modules distants avec les versions correspondantes.
Utilisation :
import {createRenderFunction, createRemoteVersionsPlugin} from '@gravity-ui/app-layout';
const renderLayout = createRenderFunction([createRemoteVersionsPlugin()]);
app.get((req, res) => {
res.send(
renderLayout({
title: 'Page d\'accueil',
pluginsOptions: {
remoteVersions: {
header: '1.2.3',
footer: '2.1.0',
sidebar: '0.5.1',
},
},
}),
);
});
Options du plugin :
type RemoteVersionsPluginOptions = Record<string, string>;
Helpers
Il existe une aide pour créer tous les plugins :
import {createMiddleware, createDefaultPlugins} from '@gravity-ui/app-layout';
const renderLayout = createRenderFunction(
createDefaultPlugins({layout: {manifest: 'path/to/assets-manifest.json'}})
);
app.get((req, res) => {
res.send(renderLayout({
title: 'Page d\'accueil',
pluginsOptions: {
layout: {
name: 'home'
},
googleAnalytics: {
counter: {...}
},
yandexMetrika: {
counter: {...}
},
},
}));
})
Utilisation alternative
Avec les renderers partiels generateRenderContent
, renderHeadContent
, renderBodyContent
via le streaming HTML :
import express from 'express';
import htmlescape from 'htmlescape';
import {
generateRenderContent,
renderHeadContent,
renderBodyContent,
createDefaultPlugins,
} from '@gravity-ui/app-layout';
const app = express();
app.get('/', async function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/html',
'Transfer-Encoding': 'chunked',
});
const plugins = createDefaultPlugins({layout: {manifest: 'path/to/assets-manifest.json'}});
const content = generateRenderContent(plugins, {
title: 'Page d\'accueil',
});
const {htmlAttributes, helpers, bodyContent} = content;
<!DOCTYPE html>
<html ${helpers.attrs({...htmlAttributes})}>
<head>
${renderHeadContent(content)}
</head>
<body ${helpers.attrs(bodyContent.attributes)}>
${renderBodyContent(content)}
`);
const data = await getUserData();
res.write(`
${content.renderHelpers.renderInlineScript(`
window.__DATA__ = ${htmlescape(data)};
`)}
</body>
</html>
`);
res.end();
});
app.listen(3000);