라이브러리 / 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> {
  // window.__DATA__에 설정될 JSON 호환 데이터
  data?: Data;
  // favicon
  icon?: Icon;
  // 적절한 태그에 설정될 nonce
  nonce?: string;

  // 공통 옵션
  // 페이지 제목
  title: string;
  // 페이지 언어, html 태그에 설정됨
  lang?: string;
  isMobile?: boolean;

  // html 속성
  htmlAttributes?: string;
  // 헤더 태그 내용
  // meta 태그
  meta?: Meta[];
  // link 태그
  links?: Link[];
  // script 태그
  scripts?: Script[];
  // style 태그
  styleSheets?: Stylesheet[];
  // 인라인 코드 스크립트 태그
  inlineScripts?: string[];
  // 인라인 스타일 태그
  inlineStyleSheets?: string[];

  // body 태그 내용
  bodyContent?: {
    // body 태그의 클래스 이름
    className?: string;
    // body 속성
    attributes?: string;
    // root div 태그 이전의 body 내용
    beforeRoot?: string;
    // root div 태그의 innerHtml 내용
    root?: string;
    // 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

스크립트 링크를 preload와 함께 설명합니다:

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" />

Plugins

렌더 함수는 플러그인으로 확장될 수 있습니다. 플러그인은 사용자 정의 렌더링 콘텐츠를 덮어쓸 수 있습니다. 플러그인은 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="/ko/README.md">Korean</a>
</div>

@gravity-ui/app-layout

This library provides a set of plugins for rendering HTML layouts with server-side rendering (SSR) support. It allows you to easily integrate various third-party services and features into your application's HTML structure.

Installation

npm install @gravity-ui/app-layout
# or
yarn add @gravity-ui/app-layout

Usage

The main idea is to create a renderLayout function using createRenderFunction and pass it an array of plugins. Then, you can use this function to render your HTML.

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

// Create a render function with your desired plugins
const renderLayout = createRenderFunction([
  // ... your plugins
]);

// Use the render function to generate HTML
app.get('/', (req, res) => {
  res.send(
    renderLayout({
      title: 'My Page Title',
      // ... other options
    }),
  );
});

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, // navigator.sendBeacon 사용 활성화
          counter: {
            id: 'some id',
          },
        },
      },
    }),
  );
});

Plugin options:

interface GoogleAnalyticsCounter {
  id: string;
}

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

Yandex Metrika

Adds Yandex Metrika 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 scripts and styles from the 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. This object 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 a 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 partial 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
기여자