图书馆 / 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: 'Home page',
      bodyContent: {
        root: 'Hello world!',
      },
    }),
  );
});

app.listen(3000);

其中

interface RenderParams<Data, Plugins> {
  // 任何与 json 兼容的数据,将被设置到页面的 window.__DATA__ 中
  data?: Data;
  // favicon
  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 标签的类名
    className?: string;
    // body 属性
    attributes?: string;
    // id 为 root 的 div 标签之前的 body 内容
    beforeRoot?: string;
    // id 为 root 的 div 标签的 innerHtml 内容
    root?: string;
    // id 为 root 的 div 标签之后的 body 内容
    afterRoot?: string;
  };
  // 插件选项
  pluginsOptions?: Partial<PluginsOptions<Plugins>>;
}

Meta

描述 meta 标签:

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

示例:

const meta = [
  {name: 'description', content: 'some text'},
  {name: 'robots', content: 'noindex'},
  {name: 'og:title', content: 'Some title'},
];

将被渲染为:

<meta name="description" content="some text" />
<meta name="robots" content="noindex" />
<meta property="og:title" content="Some title" />

Icon

描述页面 favicon:

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; // 通过 `renderLayout` 函数中的 `pluginsOptions` 参数传递。
    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()]);

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

插件选项:

interface GoogleAnalyticsCounter {
  id: string;
}

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

Yandex Metrika

在页面上添加 Yandex Metrika 计数器。

用法:

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,
          },
        },
      },
    }),
  );
});

插件选项:

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

从 webpack 资源清单文件添加脚本和样式。

用法:

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

插件选项:

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

@gravity-ui/uikit

添加 body 属性。

用法:

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

插件选项:

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

Helpers

有一个帮助函数可以创建所有插件:

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: {...}
            },
        },
    }));
})

替代用法

通过 html 流使用部分渲染器 generateRenderContentrenderHeadContentrenderBodyContent

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;

  res.write(`
        <!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);
关于图书馆
明星
3
版本
2.2.2
上次更新
14.04.2025
存储库
github.com/gravity-ui/app-layout
执照
MIT License
贡献者