Skip to content

Web Extension Integration

CoreLib Web Console can be extended by plugin-side frontend bundles. A plugin can expose file entries, custom GUI/item/resource editors, field metadata, list schemas, local preview fallbacks, and locale text.

The current integration has two layers:

  1. Server-side declarative registration through src/main/resources/web-console.yml.
  2. Frontend IIFE extension built from web-console/src/main.tsx into src/main/resources/web-extensions/*.js and loaded dynamically by the Web Console.

Minimal structure

text
YourPlugin/
├── src/main/java/.../YourPlugin.java
├── src/main/resources/
│   ├── plugin.yml
│   ├── web-console.yml
│   └── web-extensions/
│       └── yourplugin-extension.js
└── web-console/
    ├── package.json
    ├── vite.config.ts
    └── src/main.tsx

Register on enable:

java
@Override
public void onEnable() {
    WebConsoleRegistry.registerFromYaml(this);
}

Unregister on disable:

java
@Override
public void onDisable() {
    WebConsoleRegistry.unregisterModule(this);
}

web-console.yml

yaml
module:
  tone: custom
  icon: '<svg viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg"><path d="M8 8h22v22H8z" fill="none" stroke="currentColor"/></svg>'

files:
  - path: "config.yml"
    kind: CONFIG
  - path: "gui/**/*.yml"
    kind: GUI
    editor: "yourplugin:gui"
  - path: "items/**/*.yml"
    kind: ITEM
    editor: "yourplugin:item"
  - path: "sets/**/*.yml"
    kind: SET
    editor: "yourplugin:set"

extensions:
  - id: "yourplugin:editor"
    resource: "web-extensions/yourplugin-extension.js"

nodes:
  - path: "custom_templates"
    type: dynamic_map
    creatableChildren: true

Supported top-level sections:

SectionDescription
moduleModule display metadata: name, summary, tone, icon.
filesRegistered file entries. Each entry supports path, kind, title, comment, editor.
extensionsFrontend extension scripts. Each entry supports id and resource.
nodesStructured config node metadata. Each entry supports path, type, label, comment, creatableChildren.
commentsLegacy node-comment format kept for compatibility.

Built-in kind values are CONFIG, GUI, ITEM, and SCRIPT. Other values are registered as custom resource kinds and can be handled by custom surfaces.

Common node types include text, number, boolean, list, stringList, numberList, actions, object, dynamic_map, enum:A,B,C, and dynamic_enum:path.

Vite configuration

Extensions should be built as IIFE bundles and externalize React and the CoreLib frontend API:

ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react({ jsxRuntime: 'classic' })],
  build: {
    outDir: '../src/main/resources/web-extensions',
    emptyOutDir: false,
    lib: {
      entry: 'src/main.tsx',
      name: 'YourPluginWebConsoleExtension',
      formats: ['iife'],
      fileName: () => 'yourplugin-extension.js'
    },
    rollupOptions: {
      external: ['react', 'emaki-web-console'],
      output: {
        globals: {
          react: 'React',
          'emaki-web-console': 'EmakiWebConsole'
        }
      }
    }
  }
});

Key requirements:

  • Output to src/main/resources/web-extensions so the file is included in the plugin JAR.
  • Keep emptyOutDir: false when multiple resources may share the directory.
  • Use IIFE format because the Web Console loads the file through a <script> tag.
  • Externalize react to global React.
  • Externalize emaki-web-console to global EmakiWebConsole.

Extension entry

tsx
import { registerModuleLocale, registerPluginConfig, registerPluginGuiEditor } from 'emaki-web-console';

const MODULE = 'YourPlugin';

registerModuleLocale(MODULE, 'en-US', {
  'yourplugin.module.name': 'YourPlugin',
  'yourplugin.module.summary': 'Custom config, GUI, and item editing',
  'yourplugin.file.config.title': 'Main Config',
  'yourplugin.file.config.comment': 'Main plugin configuration.',
  'yourplugin.surface.gui': 'YourPlugin GUI'
});

registerPluginConfig({
  moduleId: MODULE,
  metaFields: [
    ['feature.enabled', 'Enabled', 'Controls whether this feature is enabled.', 'boolean'],
    ['feature.cooldown', 'Cooldown', 'Cooldown after triggering, in ticks.', 'number']
  ],
  ruleFields: {
    item_sources: ['Item Sources', 'Vanilla or external item source IDs.', 'stringList'],
    actions: ['Actions', 'CoreLib actions executed in order.', 'actions']
  }
});

registerPluginGuiEditor({
  moduleId: MODULE,
  editorId: 'yourplugin:gui',
  label: 'YourPlugin GUI',
  fields: [
    ['confirm', 'Confirm Button', 'Button slot that executes the main operation.', 'text'],
    ['preview', 'Preview Slot', 'Slot used to preview the current result.', 'text']
  ]
});

Surface matching

Surface matching order:

  1. Exact editorId.
  2. moduleId + kind.
  3. kind only.

Higher priority wins when multiple registrations match.

tsx
import type { SurfaceProps } from 'emaki-web-console';
import { registerSurface } from 'emaki-web-console';

function SetEditorSurface(props: SurfaceProps) {
  return <div>{props.module.name} / {props.file.path}</div>;
}

registerSurface({
  kind: 'SET',
  moduleId: 'YourPlugin',
  editorId: 'yourplugin:set',
  component: SetEditorSurface,
  label: 'Set',
  priority: 120
});

SurfaceProps includes the current module, file, ApiClient, child path, editor descriptor, reload callback, and toolbar control callback.

Useful registration functions

FunctionPurpose
registerModuleLocaleRegister module-specific translations.
registerSurfaceRegister a custom editor surface.
registerPluginGuiEditorRegister a GUI editor using CoreLib's generic GUI surface.
registerEditorDescriptor / registerEditorFieldAdd or override editor metadata and fields.
registerPluginConfigBatch-register config metadata, rules, templates, and list schemas.
registerConfigNodeMeta / registerConfigNodeRuleAdd exact or rule-based config node metadata.
registerConfigCreateTemplateAdd create templates to dynamic config nodes.
registerConfigListItemSchemaAdd schemas for list items.
registerFileKindLabelAdd a label for custom file kinds such as SET.
registerItemFieldRendererRegister a custom renderer for item editor fields.
registerItemPreviewFallbackRegister a local item preview fallback.
registerSourceDocumentAdapterOverride source read/save/parse/serialize behavior.
getRuntimeEnumRead runtime enum values returned by the backend registry.

ApiClient methods

Extensions can call props.api from a surface:

MethodPurpose
registry()Reload the registry.
registryFileNodes(moduleId, path)Load structured nodes for a glob child file.
saveRegistryValue(moduleId, filePath, path, value, revision)Save a structured config node.
createFile(moduleId, fileId, name, content?)Create a child file under a glob entry.
deleteFile(moduleId, fileId, path, confirmPath)Delete a child file.
readTextDocument() / saveTextDocument()Read or save source by kind.
readGui() / saveGui()Read or save GUI YAML.
readItem() / saveItem()Read or save item YAML.
readResource() / saveResource()Read or save custom resource YAML.
previewItem()Request server-side item preview.
actionTypes()Load name/lore action types.
economyProviders()Load economy provider options.

Write endpoints obey web_console.security.allow_config_write and use revision checks to avoid overwriting concurrent changes.

Built-in extension examples

ModuleExtension IDMain features
EmakiAttributeemakiattribute:localeAttribute, damage, condition, lore-format metadata and runtime DamageCause enum usage.
EmakiForgeemakiforge:gui-surfaceQuality config schemas, forge GUI editor, recipe and item adjustment metadata.
EmakiStrengthenemakistrengthen:gui-surfaceStrengthen config, recipes, and GUI editor.
EmakiCookingemakicooking:gui-surfaceStation config, display entity config, and cooking GUI editor.
EmakiGememakigem:item-surfaceGem/socket item editors, custom cost/effect/upgrade fields, local preview fallback, GUI editor.
EmakiSkillsemakiskills:gui-surfaceSkill, resource, trigger, and GUI metadata.
EmakiItememakiitem:localeSET kind, item editor, set editor, set field renderers, source document adapter.

Debug checklist

  1. Build the extension with npm run build in the plugin web-console directory.
  2. Confirm the generated script exists under src/main/resources/web-extensions.
  3. Build the plugin JAR and confirm it contains both web-console.yml and the extension script.
  4. Enable web_console.enabled=true and open the URL from /emakicorelib web.
  5. If the module is missing, check plugin startup, allowed_modules, and web-console.yml parsing warnings.
  6. If the extension does not load, inspect /api/registry, the /extensions/...js request, and browser console errors.
  7. Use /emakicorelib webdebug frontend or /emakicorelib webdebug backend temporarily while debugging.

CoreLib currently exposes frontend API version 1.1.0. Extension scripts should rely only on global React and EmakiWebConsole, and local previews should remain editing aids rather than substitutes for server-side logic.