Saltar al contenido principal

Estructura del proyecto

El creador genera un proyecto pequeño con tres partes, sea cual sea el framework:

  1. El plugin (plugin.ts): lo que se construye y se publica. Exporta initialize y dispose.
  2. Sus vistas: los componentes que el plugin inyecta en las regiones del shell.
  3. El sandbox (src/sandbox.ts): arranca el shell de demostración con tu plugin. Solo se usa en desarrollo y no forma parte del plugin.
my-plugin/
├── index.html ← development page
├── src/
│ ├── plugin.ts ← initialize and dispose
│ ├── sandbox.ts ← starts the shell (dev only)
│ └── views/
│ ├── main-view.tsx ← a React component
│ └── main-view.css
├── vite.config.ts ← npm run dev / npm run build
├── tsconfig.json
└── package.json

El plugin: plugin.ts​

Es el punto de entrada del plugin, el único fichero que conoce el shell. El shell llama a initialize cuando carga el plugin y a dispose cuando lo descarga, cada vez con una API propia (consulta el shell de demostración).

src/plugin.ts
import { wrapReactViewFactory } from "@uxland/harmonix-adapters";
import type { DemoShellApi } from "@uxland/harmonix-demo-shell";
import { MainView } from "./views/main-view";
import styles from "./views/main-view.css?inline";

export const initialize = async (api: DemoShellApi) => {
// A view in the `main` region: a React component rendered inside a web component.
await api.regionManager.registerMainView({
id: "main",
factory: () => wrapReactViewFactory(MainView, styles, { fullHeight: true })({ api }),
});
// An item in the side menu that activates that view.
await api.regionManager.registerNavigationItem({ id: "menu", label: "My plugin", mainViewId: "main" });
};

export const dispose = async (api: DemoShellApi) => {
const { regions } = api.regionManager;
await api.regionManager.removeView(regions.sideMenu, "menu");
await api.regionManager.removeView(regions.main, "main");
};

Qué hace:

  • Una vista es un objeto con un id y una factory: una función asíncrona que devuelve el elemento HTML que se debe mostrar. El shell llama a la factoría cuando tiene que pintar la vista. Sea cual sea el framework, el shell solo ve un elemento HTML.
  • registerMainView registra la vista en la región main, y registerNavigationItem añade al menú lateral un elemento que la activa. Los dos son atajos del shell sobre el genérico registerView(region, view).
  • Las ids ("main", "menu") solo tienen que ser únicas dentro del plugin: el shell guarda las vistas como pluginId::viewId, así que dos plugins pueden usar las mismas ids.
  • dispose deshace lo que ha hecho initialize. El shell puede descargar y volver a cargar plugins sin recargar la página, así que un plugin no debe dejar nada atrás (consulta las buenas prácticas).

La vista​

La vista principal recibe la API desde plugin.ts, como cualquier otra entrada, y la usa para hablar con otros plugins a través del broker: el botón publica un evento my-plugin:greeting y la vista, suscrita a él, cuenta los saludos que recibe.

src/views/main-view.tsx
const GREETING = "my-plugin:greeting";

export const MainView = ({ api }: { api: DemoShellApi }) => {
const [greetings, setGreetings] = useState(0);

useEffect(() => {
const subscription = api.broker.subscribe(GREETING, () => setGreetings((count) => count + 1));
return () => subscription.dispose();
}, [api]);

return (
<section className="view">
{/* … */}
<button type="button" onClick={() => api.broker.publish(GREETING, { from: api.pluginInfo.pluginId })}>
Publish a greeting
</button>
<p className="result">Greetings received: {greetings}</p>
</section>
);
};

Los estilos de la vista usan los colores del shell a través de variables CSS, como var(--hx-primary-text, #00756a), con un valor por defecto por si otro shell carga el plugin.

El sandbox: src/sandbox.ts​

Arranca el shell de demostración y carga tu plugin desde su código fuente, así que cada cambio que guardas se recarga. Es el único lugar donde se define el id del plugin:

src/sandbox.ts
import { bootstrapPlugins, initializeShell } from "@uxland/harmonix-demo-shell";

initializeShell(document.body, { title: "my-plugin" });
await bootstrapPlugins([{ pluginId: "my-plugin", importer: () => import("./plugin") }]);

bootstrapPlugins recibe una lista: para probar tu plugin junto con otros, añádelos a ella. Una aplicación real hace lo mismo, pero obtiene la lista de plugins de la Plugin Store.

La configuración​

FicheroQué hace
package.jsonLos scripts (dev, build, typecheck) y las dependencias. name y version identifican el plugin cuando lo publicas, y module apunta al fichero construido.
vite.config.tsReact y Lit: npm run dev sirve index.html, y npm run build construye solo src/plugin.ts, dejando el framework y el shell fuera del bundle. Angular: el segundo paso del build (consulta Build y publicación).
angular.jsonSolo Angular: la aplicación sandbox, que sirve npm run dev, y la librería my-plugin.
tsconfig.jsonTypeScript. Lit activa los decoradores que necesita.