/ App Layout

App Layout

在我们的 SPA 应用中使用的 HTML 布局生成器。

@gravity-ui/app-layout · npm package CI

安装

npm install --save-dev @gravity-ui/app-layout

用法

使用 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: '首页',
      bodyContent: {
        root: '你好,世界!',
      },
    }),
  );
});

app.listen(3000);

其中

interface RenderParams<Data, Plugins> {
  // 任何 JSON 兼容的数据,将设置为页面的 window.__DATA__
  data?: Data;
  // 网站图标
  icon?: Icon;
  // 用于设置相应标签的 nonce
  nonce?: string;

  // 通用选项
  // 页面标题
  title: string;
  // 页面语言,将设置为 html 标签
  lang?: string;
  isMobile?: boolean;

  // html 属性
  htmlAttributes?: string;
  // header 标签内容
  // meta 标签
  meta?: Meta[];
  // link 标签
  links?: Link[];
  // script 标签
  scripts?: Script[];
  // style 标签
  styleSheets?: Stylesheet[];
  // 带有内联代码的 script 标签
  inlineScripts?: string[];
  // 带有内联样式的 style 标签
  inlineStyleSheets?: string[];

  // body 标签内容
  bodyContent?: {
    // body 标签的 class 名称
    className?: string;
    // body 属性
    attributes?: string;
    // root div 标签之前的内容
    beforeRoot?: string;
    // id 为 root 的 div 标签的 innerHtml 内容
    root?: string;
    // root div 标签之后的内容
    afterRoot?: string;
  };
  // 插件选项
  pluginsOptions?: Partial<PluginsOptions<Plugins>>;
}

Meta

描述 meta 标签:

interface Meta {
  name: string;
  content: string;
}

示例:

const meta = [
  {name: 'description', content: '一些文本'},
  {name: 'robots', content: 'noindex'},
  {name: 'og:title', content: '某个标题'},
];

将渲染为:

<meta name="description" content="一些文本" />
<meta name="robots" content="noindex" />
<meta property="og:title" content="某个标题" />

Icon

描述页面图标:

interface Icon {
  type?: string;
  sizes?: string;
  href?: string;
}

默认值为:

const icon = {
  type: 'image/png',
  sizes: '16x16',
  href: '/favicon.png',
};

描述 link 标签:

interface Link {
  as?: string;
  href: string;
  rel?: string;
  type?: string;
  sizes?: string;
  title?: HTMLLinkElement['title'];
  crossOrigin?: '' | 'anonymous' | 'use-credentials';
}

示例:

const link = {
  href: 'myFont.woff2',
  rel: 'preload',
  as: 'font',
  type: 'font/woff2',
  crossOrigin: 'anonymous',
};

将渲染为:

<link href="myFont.woff2" rel="preload" as="font" type="font/woff2" crossorigin="anonymous" />

Scripts

描述带有预加载的脚本链接:

interface Script {
  src: string;
  defer?: boolean;
  async?: boolean;
  crossOrigin?: '' | 'anonymous' | 'use-credentials';
  type?: 'importmap' | 'module' | string;
}

示例:

const script = {
  src: 'url/to/script',
  defer: true,
  async: false,
  crossOrigin: 'anonymous',
};

将渲染为:

<link href="url/to/script" rel="preload" as="script" crossorigin="anonymous" />

<script src="url/to/script" defer="true" async="false" crossorigin="anonymous" nonce="..."></script>

Style sheets

描述样式链接:

interface Stylesheet {
  href: string;
}

示例:

const styleSheet = {
  href: 'url/to/stylesheet',
};

将渲染为:

<link href="url/to/stylesheet" rel="stylesheet" />

插件

渲染函数可以通过插件进行扩展。插件可能会重写用户定义的渲染内容。 插件是一个具有 nameapply 属性的对象:

interface Plugin<Options = any, Name = string> {
  name: Name;
  apply: (params: {
    options: Options | undefined; // 通过 `pluginsOptions` 参数在 `renderLayout` 函数中传递。
    commonOptions: CommonOptions;
    renderContent: RenderContent;
    /** @deprecated 使用 `renderContent.helpers` 代替 */
    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;
}

此包中包含一些插件:

Google analytics

在页面上添加 Google Analytics 计数器。

用法:

import {createRenderFunction, createGoogleAnalyticsPlugin} from '@gravity-ui/app-layout';

const renderLayout = createRenderFunction([createGoogleAnalyticsPlugin()]);
<div class="languages">
  <a href="/en/README.md">English</a>
  <a href="/zh/README.md">Chinese</a>
</div>

@gravity-ui/app-layout

This library helps to create server-side rendered applications with the help of plugins.

Plugins

Google Analytics

Adds Google Analytics counters on the page.

Usage:

import {createRenderFunction, createGoogleAnalyticsPlugin} from '@gravity-ui/app-layout';

const renderLayout = createRenderFunction([createGoogleAnalyticsPlugin()]);

app.get((req, res) => {
  res.send(
    renderLayout({
      title: 'Home page',
      pluginsOptions: {
        googleAnalytics: {
          useBeaconTransport: true, // enables use of navigator.sendBeacon
          counter: {
            id: 'some id',
          },
        },
      },
    }),
  );
});

Plugin options:

interface GoogleAnalyticsCounter {
  id: string;
}

interface GoogleAnalyticsOptions {
  useBeaconTransport?: boolean;
  counter: GoogleAnalyticsCounter;
}

Yandex Metrika

Adds Yandex metrics counters on the page.

Usage:

import {createRenderFunction, createYandexMetrikaPlugin} from '@gravity-ui/app-layout';

const renderLayout = createRenderFunction([createYandexMetrikaPlugin()]);

app.get((req, res) => {
  res.send(
    renderLayout({
      title: 'Home page',
      pluginsOptions: {
        yandexMetrika: {
          counter: {
            id: 123123123,
            defer: true,
            clickmap: true,
            trackLinks: true,
            accurateTrackBounce: true,
          },
        },
      },
    }),
  );
});

Plugin options:

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

Adds script and styles from webpack assets manifest file.

Usage:

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: 'Home page',
      pluginsOptions: {
        layout: {
          name: 'home',
        },
      },
    }),
  );
});

Plugin options:

export interface LayoutOptions {
  name: string;
  prefix?: string;
}

@gravity-ui/uikit

Adds body attributes.

Usage:

import {createRenderFunction, createUikitPlugin} from '@gravity-ui/app-layout';

const renderLayout = createRenderFunction([createUikitPlugin()]);

app.get((req, res) => {
  res.send(
    renderLayout({
      title: 'Home page',
      pluginsOptions: {
        uikit: {
          theme: 'dark',
          direction: 'ltr',
        },
      },
    }),
  );
});

Plugin options:

interface UikitPluginOptions {
  theme: string;
  direction?: 'ltr' | 'rtl';
}

Remote Versions

Adds microfrontend versions information to the page.

This plugin creates a global window.__REMOTE_VERSIONS__ object containing the provided microfrontend versions, which can be used by module federation or similar microfrontend architectures to determine which versions of remote modules to load.

It can be used in combination with App Builder and the moduleFederation.remotesRuntimeVersioning option to automatically load remote modules with the corresponding versions.

Usage:

import {createRenderFunction, createRemoteVersionsPlugin} from '@gravity-ui/app-layout';

const renderLayout = createRenderFunction([createRemoteVersionsPlugin()]);

app.get((req, res) => {
  res.send(
    renderLayout({
      title: 'Home page',
      pluginsOptions: {
        remoteVersions: {
          header: '1.2.3',
          footer: '2.1.0',
          sidebar: '0.5.1',
        },
      },
    }),
  );
});

Plugin options:

type RemoteVersionsPluginOptions = Record<string, string>;

Helpers

There is helper to create all 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: 'Home page',
        pluginsOptions: {
            layout: {
                name: 'home'
            },
            googleAnalytics: {
                counter: {...}
            },
            yandexMetrika: {
                counter: {...}
            },
        },
    }));
})

Alternative usage

With parts renderers generateRenderContent, renderHeadContent, renderBodyContent via html streaming:

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: 'Home page',
  });

  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);
关于库
星标
4
版本
2.3.0
最后更新
22.08.2025
代码仓库
github.com/gravity-ui/app-layout
许可证
MIT License
贡献者