При сохранении сцены каждый элемент записывается в JSON с полем className — идентификатором типа. При загрузке движок ищет конструктор в elementsLib[className], создаёт экземпляр и вызывает fromJSON().
Раньше в className попадало constructor.name. После минификации production-сборки имена классов в бандле становятся короткими (Hj, kj и т.п.), JSON перестаёт загружаться, а внешним модулям приходилось настраивать сборщик, чтобы сохранять имена.
Сейчас используется явное поле static readonly typeName — стабильный ID, не зависящий от минификации и от любого bundler.
На каждом конкретном cell-классе, который сериализуется или регистрируется в движке:
import { CellParametric } from "@engine/entities"
export class MyShape extends CellParametric {
static readonly typeName: string = "MyShape"
constructor(options?: CellOptions) {
super(options)
// ...
}
}
Требования:
| Правило | Пояснение |
|---|---|
| Уникальность | Два разных класса не могут иметь один typeName |
| Стабильность | Значение не меняют после релиза — иначе старые файлы не откроются |
| Явное объявление | Поле задаётся на самом классе, а не берётся из Function.name |
Тип : string |
Нужен для корректного переопределения в TypeScript у наследников |
Значение совпадает с тем, что попадает в JSON как className. Для совместимости с существующими файлами в движке используются имена вроде "Spline", "Link", "CellParametric".
На абстрактном GraphicElement typeName не задаётся — только на классах, экземпляры которых реально создаются.
В JavaScript статическое поле наследуется. Если у наследника нет своего typeName, используется значение родителя:
// ❌ Ошибка при registerElement и при new в dev
export class MyWire extends Segment {}
// MyWire.typeName === "Segment" — тот же ID, что у Segment
Правильно:
// ✅
export class MyWire extends Segment {
static readonly typeName: string = "MyWire"
}
Проверка выполняется функцией assertCellTypeName (см. @helpers/cellTypeName):
typeName не задан — ошибка с просьбой объявить поле.typeName совпадает с предком — ошибка: нужно переопределить.elementsLib — ошибка при registerElement.Проверка срабатывает при eulerGraph.registerElement(MyClass) и при new MyClass() в dev (в конструкторе GraphicElement).
import { MyShape } from "./MyShape"
// Вариант 1 — вручную
eulerGraph.registerElement(MyShape)
// Вариант 2 — через библиотеку Stencil (Library.register → registerElement для каждого shape)
const myLib = new Library("Моя библиотека", [MyShape])
eulerEngine.services.Stencil?.register(myLib)
После регистрации класс доступен по ключу elementsLib["MyShape"]. Сериализация (toJSON) записывает в JSON "className": "MyShape".
Минимальный контракт — три шага:
import { CellParametric, Library } from "@engine/index"
import type { CellOptions } from "@etypes/Cell"
export class GaugeShape extends CellParametric {
static readonly typeName: string = "GaugeShape"
constructor(options: CellOptions = {}) {
super({ name: "Датчик", ...options })
}
}
export const gaugeLib = new Library("Датчики", [GaugeShape])
Подключение при старте (см. Руководство пользователя):
eulerEngine.registerStandardLibs()
eulerEngine.services.Stencil?.register(gaugeLib)
eulerEngine.services.Stencil?.loadLibs()
Сборка модуля — любым инструментом. Главное — явный typeName на каждом классе фигуры.
«объявите static readonly typeName…»
На классе нет поля typeName. Добавьте строку из раздела «Объявление typeName».
«переопределите static readonly typeName — сейчас унаследовано "Segment" от Segment»
Класс наследует typeName от родителя. Задайте своё значение на дочернем классе.
«typeName "MyShape" уже зарегистрирован другим классом»
Такой ID уже занят в elementsLib. Выберите другое имя или не регистрируйте класс повторно.
Сцена сохранилась, но не открывается после production-сборки
Убедитесь, что класс зарегистрирован через registerElement / Library.register до загрузки JSON и что typeName в коде совпадает с className в файле.