К содержанию
Medusa
Документация

Контракт frontend и backend

Контракт frontend и backend — это набор соглашений по структуре каталогов, именам блоков и HTML-фрагментам, которые формируются на backend и используются во frontend.

Структура

Backend ожидает структуру вида www/{source-path}/{entity}/{name}/{file}, где {source-path} — один из путей, заданных в src[].path файла www/config/frontend.json:

  • www/frontend/ — штатный путь из src[].path;
  • {entity} — имя сущности frontend-слоя (например, themes, templates, apps, components и т.д.);
  • {name} — имя блока внутри сущности (например, light, default, hero, sidebar и т.д.);
  • {file} — имя entry-файла или Twig-шаблона (например, entry.js, entry.custom.js, index.html.twig, view.html.twig и т.д.).

Mount и state

Backend формирует связанные фрагменты:

  • mount-контейнер:

    <div data-medusa-app-id="{app-id}" data-medusa-state-id="medusa-state-{name}-{index}"></div>
  • state-скрипт с raw JSON-данными:

    <script type="application/json" id="medusa-state-{name}-{index}">{json}</script>

Frontend читает эти значения через цепочку:

mount('{app-id}')
  .state(state)
  .each((node, props) => {...});

Чтение JSON-данных выполняется методом state('medusa-state-{name}-{index}').json().

Twig-секции

При смешанном подходе Vite-секции передаются в Twig-контекст по фиксированному соответствию:

  • секция preload -> контекст head.preload;
  • секция css -> контекст head.styles;
  • секция html -> контекст body.content;
  • секция js -> контекст body.scripts;
  • секция json -> контекст body.state.

Результат сборки

Frontend-сборка формирует артефакты в каталоге out.path из www/config/frontend.json. При штатном out.path = /build это www/build/ и манифест www/build/.vite/manifest.json. Backend получает по манифесту итоговые теги ассетов с хэшами.

Пример смешанного подхода

use Medusa\Helpers\Frontend;

$html = Frontend::twig('themes')
    ->include('default', [
        'html' => [
            'lang' => 'ru',
        ],
        'head' => [
            'title' => 'Приветствие',
        ],
    ])
    ->with(
        Frontend::vite('themes')->include('default', [
            'title' => 'Добро пожаловать!',
            'subTitle' => 'Это страница приветствия!',
        ])
    )
    ->html(true);

Пример содержимого файла www/frontend/themes/default/index.html.twig:

<!doctype html>
<html lang="{{ html.lang | default('en') }}">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{ head.title | default('Page') }}</title>

  {{ head.preload | join('\n\t') | raw }}

  {{ head.styles | join('\n\t') | raw }}
</head>
<body>
  {{ body.content | join('\n\t') | raw }}

  {{ body.scripts | join('\n\t') | raw }}

  {{ body.state | join('\n\t') | raw }}
</body>
</html>

Пример содержимого файла www/frontend/themes/default/entry.js:

import {mount} from '@shared/js/mount';
import {state} from '@shared/js/state';

import {createApp} from 'vue';
import DefaultTheme from './DefaultTheme.vue';
import './style.css';

mount('default')
   .state(state)
   .each((node, props) => {
      createApp(DefaultTheme, props).mount(node);
   });

Пример содержимого файла www/frontend/themes/default/DefaultTheme.vue:

<script setup>
import {defaultThemeProps} from './props.js';

defineProps(defaultThemeProps);
</script>

<template>
 <h1>{{ title }}</h1>
 <p>{{ subTitle }}</p>
</template>

Пример содержимого файла www/frontend/themes/default/props.js:

export const defaultThemeProps = {
  title: {
    type: String,
    required: true,
  },
  subTitle: {
    type: String,
    required: true,
  },
};

Пример содержимого файла www/frontend/themes/default/style.css:

h1 {
   color: blue;
}

p {
   color: green;
}