# PHX Frontend Plugin Demo > [English version](README.md) Beispielprojekt, das zeigt, wie ein **PHX-ERP-Frontend-Plugin** als Angular-Web-Component mit `@phx/shared` und `@phx/shared-ui` erstellt wird. Dieselbe Codebasis unterstützt zwei Arbeitsweisen: | Modus | Zweck | Typischer Befehl | |-------|-------|------------------| | **Plugin host** | Ausführung in PHX als Custom Element | `yarn run plugin` | | **Standalone client** | Lokale Entwicklung wie eine normale Angular-App | `yarn client` | --- ## Inhaltsverzeichnis - [Übersicht](#übersicht) - [Voraussetzungen](#voraussetzungen) - [Schnellstart](#schnellstart) - [Live-Demo](#live-demo) - [Entwicklungsmodi](#entwicklungsmodi) - [Projektstruktur](#projektstruktur) - [Eigenes Plugin erstellen](#eigenes-plugin-erstellen) - [1. Grundsetup](#1-grundsetup) - [2. Tailwind CSS](#2-tailwind-css) - [3. GraphQL Codegen](#3-graphql-codegen) - [4. Environments](#4-environments) - [5. PHX-Bibliotheken](#5-phx-bibliotheken) - [6. Plugin-Setup](#6-plugin-setup) - [7. In PHX registrieren](#7-in-phx-registrieren) - [Weitere Themen](#weitere-themen) - [Plugin lokal bereitstellen](#plugin-lokal-bereitstellen) - [Apollo, Auth Guard und Login](#apollo-auth-guard-und-login) - [package.json-Skripte](#packagejson-skripte) - [Fehlerbehebung](#fehlerbehebung) --- ## Übersicht Ein PHX-Frontend-Plugin ist eine Angular-Anwendung, die als **Custom Element** (Web Component) verpackt wird. PHX lädt es über ein **Manifest**, das auf die kompilierte `main.js` verweist und den Element-Tag-Namen deklariert. ``` ┌─────────────────┐ manifest.json ┌──────────────────┐ │ PHX host │ ─────────────────────► │ main.js (+ deps) │ │ (ERP shell) │ loads & registers │ custom element │ └────────┬────────┘ └────────┬─────────┘ │ │ │ pluginServices (Apollo, notifications…) │ │ hostInjector │ └──────────────────────────────────────────┘ ``` **Was diese Demo enthält:** - Custom-Element-Tag: `frontend-plugin-demo` - Beispiel-Routen: Hello World, Product View, Address List - GraphQL-Abfragen über den PHX-Host-Apollo (Production) oder einen lokalen Apollo-Client (Development) - Login-Flow für die eigenständige Entwicklung ohne vorkonfigurierten API-Key - PrimeNG- und Tailwind-Styling im PHX-Stil --- ## Voraussetzungen - [Node.js](https://nodejs.org/) (LTS empfohlen) - [Yarn v4](https://yarnpkg.com/getting-started/install) - Eine laufende PHX-Instanz (für Plugin-Host-Tests und GraphQL-Schema/Codegen) - Ein **npm-Zugangstoken** mit Lesezugriff auf PHXGMBH-Pakete — anfordern über Ihren PHX-Partner Für die lokale eigenständige Entwicklung benötigen Sie außerdem eines der folgenden: - Einen PHX-API-Benutzer-Token in Ihrer Development-Umgebung, oder - Gültige Anmeldedaten für den integrierten Login-Bildschirm (siehe [Apollo, Auth Guard und Login](#apollo-auth-guard-und-login)) --- ## Schnellstart 1. **Yarn konfigurieren**, um auf die PHX-npm-Registry zuzugreifen (siehe [PHX-Bibliotheken](#5-phx-bibliotheken)). `.yarnrc.yml` nicht committen. 2. **Abhängigkeiten installieren:** ```bash yarn install ``` 3. **Lokale Development-Umgebung anlegen** (optional, für den Standalone-Modus): ```bash cp src/environments/environment.example.ts src/environments/environment.development.ts ``` Passen Sie `apiUrl`, `wsUrl` und optional `apiKey` in dieser Datei an. Die Datei ist gitignored. 4. **GraphQL-Typen generieren** (erfordert PHX Admin API unter der in `codegen.ts` konfigurierten Schema-URL): ```bash yarn codegen ``` 5. **In einem der Entwicklungsmodi** unten starten. --- ## Live-Demo Um diese Demo in Ihrer eigenen Instanz zu nutzen, fügen Sie unter **Admin → Custom Elements** (`https://your.phx.instance/admin/customElements`) ein Custom Element mit folgender Manifest-URL hinzu: ``` https://gitea.phx-erp.de/api/v1/repos/PHXGMBH/phx-frontend-plugin-webcomponent-demo/raw/master/latest/manifest.json ``` Das Manifest verweist auf die kompilierte `main.js` im Verzeichnis `latest/` dieses Repositorys. --- ## Entwicklungsmodi ### Standalone client (üblicher Angular-Workflow) Startet die App mit der **Development**-Build-Konfiguration auf Port **4201** — Routing, Login und ein lokaler Apollo-Client funktionieren ohne PHX. ```bash yarn client # equivalent: ng serve --port 4201 --watch --configuration development ``` Öffnen Sie **http://localhost:4201/**. ### Plugin-Host-Modus (innerhalb von PHX) Baut mit der **Production**-Konfiguration (ohne Output Hashing) und stellt die Ausgabe mit CORS- und No-Cache-Headern bereit, damit PHX während der Entwicklung aktuelle Bundles laden kann. ```bash yarn run plugin ``` Dies führt `yarn build` und `yarn serve` parallel aus: | Skript | Funktion | |--------|----------| | `yarn build` | Production-Watch-Build (in diesem Repo zusätzlich Sync nach `latest/` für die gehostete Demo) | | `yarn serve` | Stellt `dist/.../browser/` unter **http://localhost:3223/** bereit | Verweisen Sie Ihre PHX-Instanz auf das lokale Manifest: ``` http://localhost:3223/manifest.json ``` (`public/manifest.json` wird in die bereitgestellte Ausgabe kopiert.) > **Hinweis:** Verwenden Sie `yarn run plugin`, nicht `yarn plugin` — Yarn behandelt `plugin` als eingebauten Befehl. [Terminal Keeper](https://open-vsx.org/extension/nguyenngoclong/terminal-keeper) ist in `.vscode/sessions.json` vorkonfiguriert, um sowohl `yarn run plugin` als auch `yarn client` zu starten. ### Weitere nützliche Befehle ```bash yarn start # ng serve with development configuration (default port 4200) yarn codegen # Regenerate GraphQL types ``` --- ## Projektstruktur ``` phx-frontend-plugin-demo/ ├── latest/ # Published build output (main.js, manifest.json) ├── public/ │ └── manifest.json # Local manifest (path → localhost:3223) ├── scripts/ │ ├── copy-latest.mjs # (this repo only) Sync dist/*.js → latest/ for hosted demo │ └── serve-dist.mjs # Static server for plugin-host dev ├── src/ │ ├── app/ │ │ ├── components/ # Demo pages (hello-world, product-view, …) │ │ ├── login/ # Login form (standalone development) │ │ ├── services/ │ │ │ ├── apollo.service.ts │ │ │ └── phoenix-host-bridge.service.ts │ │ ├── app.config.ts # providePhoenixPluginWithPrimeNG, providers │ │ ├── app.routes.ts │ │ ├── apollo.provider.ts # Local Apollo (development only) │ │ └── auth-guard.ts │ ├── graphql/ # GraphQL documents for codegen │ ├── environments/ │ │ ├── environment.ts # Production defaults (used in PHX) │ │ ├── environment.example.ts # Template for local dev │ │ └── environment.development.ts # Local overrides (gitignored) │ └── main.ts # Registers the custom element ├── codegen.ts ├── serve.json # Cache headers for serve-dist.mjs └── tailwind.config.js ``` --- ## Eigenes Plugin erstellen Die folgenden Schritte führen durch die Erstellung eines Projekts, das dieser Demo ähnelt, mit [Yarn v4](https://yarnpkg.com/getting-started/install), [Angular 20](https://angular.dev/), [PrimeNG 20](https://primeng.org/) und [Tailwind CSS](https://tailwindcss.com/). Ersetzen Sie Platzhalter wie `*PROJECT-NAME*`, `*YOUR-TAG*` und `*YOUR-TOKEN*` durch Ihre Werte. ### 1. Grundsetup ```bash mkdir *PROJECT-NAME* cd *PROJECT-NAME* npx @angular/cli@20 new *PROJECT-NAME* --directory ./ --package-manager yarn --style scss --ssr false --routing --standalone yarn add primeng@20 @primeng/themes@20 tailwindcss@3.4.17 postcss tailwindcss-primeui @angular/animations@20 @angular/elements@20 yarn add -D typescript@5.9.2 @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typed-document-node concurrently ng add apollo-angular ``` ### 2. Tailwind CSS Erstellen Sie `tailwind.config.js` im Projektroot. Diese Konfiguration ist an PHX angelehnt: ```js /** @type {import('tailwindcss').Config} */ module.exports = { darkMode: 'selector', content: ['./src/**/*.{html,ts,scss}'], theme: { extend: { animation: { fadein: 'fadein 0.5s ease-in-out', fadeout: 'fadeout 0.5s ease-in-out', fadeinleft: 'fadeinleft 0.5s ease-in-out', fadeinright: 'fadeinright 0.5s ease-in-out', fadeintop: 'fadeintop 0.5s ease-in-out', fadeinbottom: 'fadeinbottom 0.5s ease-in-out', }, keyframes: { fadein: { '0%': { opacity: 0 }, '100%': { opacity: 1 } }, fadeout: { '0%': { opacity: 1 }, '100%': { opacity: 0 } }, fadeinleft: { '0%': { opacity: 0, transform: 'translateX(-100%)' }, '100%': { opacity: 1, transform: 'translateX(0)' } }, fadeinright: { '0%': { opacity: 0, transform: 'translateX(100%)' }, '100%': { opacity: 1, transform: 'translateX(0)' } }, fadeintop: { '0%': { opacity: 0, transform: 'translateY(-100%)' }, '100%': { opacity: 1, transform: 'translateY(0)' } }, fadeinbottom: { '0%': { opacity: 0, transform: 'translateY(100%)' }, '100%': { opacity: 1, transform: 'translateY(0)' } }, }, }, }, plugins: [require('tailwindcss-primeui')], }; ``` Aufgrund eines bekannten Problems müssen diese Direktiven in den **`styles` jeder Komponente** (und in `src/styles.scss` für die lokale Entwicklung) ergänzt werden: ```css @tailwind base; @tailwind components; @tailwind utilities; ``` ### 3. GraphQL Codegen Erstellen Sie `codegen.ts` im Projektroot (passen Sie die Schema-URL an, falls Ihre PHX-Instanz woanders läuft): ```ts import type { CodegenConfig } from '@graphql-codegen/cli'; const sharedConfig = { scalars: { DateTime: 'Date' }, immutableTypes: false, } as const; const config: CodegenConfig = { overwrite: true, schema: 'http://localhost:3000/admin-api/schema.gql', documents: './src/graphql/*.ts', ignoreNoDocuments: true, generates: { './src/app/schema-types.ts': { plugins: ['typescript'], config: sharedConfig, }, './src/app/generated.ts': { plugins: ['typescript-operations', 'typed-document-node'], config: { ...sharedConfig, importSchemaTypesFrom: './src/app/schema-types.ts', }, }, }, }; export default config; ``` In `package.json` ergänzen: ```json "codegen": "graphql-codegen" ``` Definieren Sie Queries und Mutations in `src/graphql/` und führen Sie anschließend aus: ```bash yarn codegen ``` Dies erzeugt `src/app/schema-types.ts` und `src/app/generated.ts`. ### 4. Environments Verwenden Sie getrennte Environments, damit derselbe Build als PHX-Plugin (Production) oder als eigenständige Dev-App (Development) läuft. **`src/environments/environment.interface.ts`** ```ts export abstract class Environment { production: boolean = false; apiUrl: string | undefined; wsUrl: string | undefined; apiKey: string | undefined; serverUrl: string = ''; } ``` **`src/environments/environment.ts`** (Production — verwendet bei Einbettung in PHX) ```ts import { Environment } from './environment.interface'; export const environment: Environment = { production: true, apiUrl: undefined, wsUrl: undefined, apiKey: undefined, serverUrl: '', }; ``` **`src/environments/environment.development.ts`** (lokale Entwicklung — in `.gitignore` aufnehmen) ```ts import { Environment } from './environment.interface'; export const environment: Environment = { production: false, apiUrl: 'http://localhost:3000/admin-api', wsUrl: 'ws://localhost:3000/admin-api', apiKey: undefined, // or a PHX API user token; otherwise use the login route serverUrl: 'https://localhost:4200', }; ``` Fügen Sie `fileReplacements` zur **Development**-Build-Konfiguration in `angular.json` hinzu: ```json "development": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.development.ts" } ] } ``` > **Tipp:** Legen Sie `environment.example.ts` im Repository ab und kopieren Sie es lokal nach `environment.development.ts`, wie diese Demo es tut. ### 5. PHX-Bibliotheken `@phx/shared` und `@phx/shared-ui` werden in der PHXGMBH-npm-Registry veröffentlicht. Erstellen Sie `.yarnrc.yml` im Projektroot (oder bearbeiten Sie `~/.yarnrc.yml` für eine globale Konfiguration): ```yml nodeLinker: node-modules npmScopes: phx: npmRegistryServer: "https://gitea.phx-erp.de/api/packages/PHXGMBH/npm/" npmAuthToken: "*YOUR-TOKEN*" ``` > **Wichtig:** Nehmen Sie `.yarnrc.yml` in `.gitignore` auf, wenn sie Ihren Token enthält. Installieren Sie anschließend die Bibliotheken: ```bash yarn add @phx/shared @phx/shared-ui ``` ### 6. Plugin-Setup #### Application config Fügen Sie `providePhoenixPluginWithPrimeNG` in `src/app/app.config.ts` hinzu: ```ts import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { providePhoenixPluginWithPrimeNG } from '@phx/shared-ui'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), // stripTrailingSegments: child route segments appended by PHX deep links // so APP_BASE_HREF still matches the host mount path. ...providePhoenixPluginWithPrimeNG({ stripTrailingSegments: ['*YOUR*', '*ROUTES*', '*HERE*'], }), provideRouter(routes), ], }; ``` > **Routing:** Übergeben Sie Ihre Routen-Pfadsegmente in `stripTrailingSegments`, oder leiten Sie sie automatisch ab: > > ```ts > stripTrailingSegments: routes.map((r) => r.path!).filter((p) => (p?.length ?? 0) > 0) > ``` #### Host bridge service Erstellen Sie `src/app/services/phoenix-host-bridge.service.ts`: ```ts import { Injectable, Injector, signal } from '@angular/core'; import type { IPluginServices } from '@phx/shared-ui'; @Injectable({ providedIn: 'root' }) export class PhoenixHostBridgeService { private readonly _hostInjector = signal(null); private readonly _pluginServices = signal(null); hostInjector(): Injector | null { return this._hostInjector(); } setHostInjector(injector: Injector): void { this._hostInjector.set(injector); } pluginServices(): IPluginServices | null { return this._pluginServices(); } setPluginServices(services: IPluginServices): void { this._pluginServices.set(services); } } ``` Die Host Bridge speichert von PHX bereitgestellte Services (Apollo Client, Notification Service usw.), damit Ihre Komponenten sie im Production-Modus nutzen können. #### Root component Ihre Root-Komponente empfängt `pluginServices` und `hostInjector` von PHX und leitet sie an die Bridge weiter: ```ts import { Component, effect, inject, Injector, input } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { PhoenixHostBridgeService } from './services/phoenix-host-bridge.service'; import { IPluginServices, syncPhoenixHostInjector } from '@phx/shared-ui'; @Component({ selector: 'app-root', imports: [RouterOutlet], template: ``, styles: [` @tailwind base; @tailwind components; @tailwind utilities; `], }) export class App { private readonly hostBridge = inject(PhoenixHostBridgeService); readonly pluginServices = input({}); readonly hostInjector = input(undefined); private readonly _syncHostInjector = syncPhoenixHostInjector(this.hostBridge, this.hostInjector); private readonly _syncPluginServices = effect(() => { this.hostBridge.setPluginServices(this.pluginServices()); }); } ``` #### Bootstrap / custom element registration Registrieren Sie in `src/main.ts` Ihr Plugin als Custom Element (ersetzen Sie `*YOUR-TAG*`): ```ts import { appConfig } from './app/app.config'; import { App } from './app/app'; import { bootstrapPhoenixPluginCustomElement } from '@phx/shared-ui'; import { environment } from './environments/environment'; bootstrapPhoenixPluginCustomElement(App, '*YOUR-TAG*', appConfig).then((app) => { if (!environment.production) { return app!.bootstrap(App); } return app; }); ``` - **`bootstrapPhoenixPluginCustomElement`** — erstellt die Angular-Anwendung und registriert das Custom Element für PHX. - **`app.bootstrap(App)` in Development** — mountet zusätzlich die Root-Komponente, damit `ng serve` / der Standalone-Modus mit Routing funktioniert. Der Custom-Element-Tag muss **kleingeschrieben mit Bindestrichen** sein (z. B. `my-company-orders`). Verwenden Sie denselben Tag in Ihrem Manifest. ### 7. In PHX registrieren #### Manifest-Format PHX lädt ein JSON-Manifest, das auf Ihr Entry-Skript verweist und den Custom-Element-Tag deklariert: ```json { "path": "https://example.com/path/to/main.js", "items": [ { "tagName": "*YOUR-TAG*" } ] } ``` | Feld | Beschreibung | |------|--------------| | `path` | Absolute URL zu `main.js` (und Basis für Chunk-Auflösung) | | `items[].tagName` | In `main.ts` registrierter Custom-Element-Tag | **Beispiel lokale Entwicklung** (`public/manifest.json` in diesem Repo): ```json { "path": "http://localhost:3223/main.js", "items": [{ "tagName": "frontend-plugin-demo" }] } ``` **Gehostetes Beispiel** (`latest/manifest.json` in diesem Repo): ```json { "path": "https://gitea.phx-erp.de/api/v1/repos/PHXGMBH/phx-frontend-plugin-webcomponent-demo/raw/master/latest/main.js", "items": [{ "tagName": "frontend-plugin-demo" }] } ``` #### Registrierungsschritte 1. Hosten Sie `main.js` (und ggf. weitere Chunks) unter einer URL, die aus den Browsern Ihrer Nutzer erreichbar ist — dieselben Netzwerkregeln wie für Ihre PHX-Instanz. 2. Veröffentlichen Sie ein Manifest-JSON unter einer stabilen URL. 3. In PHX: **Admin → Custom Elements** → Manifest-URL hinzufügen und Mount-Pfad oder Tag wählen. 4. Ab- und wieder anmelden. Das Plugin ist verfügbar unter `https://your.phx.instance/customElements/*PATH*`. Für die lokale Plugin-Host-Entwicklung siehe [Plugin lokal bereitstellen](#plugin-lokal-bereitstellen). --- ## Weitere Themen ### Plugin lokal bereitstellen Statischen Server installieren: ```bash yarn add -D serve ``` **`scripts/serve-dist.mjs`** ```js import { spawn } from 'node:child_process'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const root = join(__dirname, '..'); const port = process.env.PORT ?? '3223'; const serveBin = join(root, 'node_modules', '.bin', 'serve'); const child = spawn(serveBin, ['-l', port, '--cors', '--no-etag'], { stdio: 'inherit', shell: true, cwd: root, }); child.on('exit', (code) => process.exit(code ?? 0)); ``` **`serve.json`** (ersetzen Sie `*PROJECT-NAME*` durch Ihren Angular-Projektnamen aus `angular.json`) ```json { "public": "dist/*PROJECT-NAME*/browser", "headers": [ { "source": "**/*.{js,mjs}", "headers": [ { "key": "Cache-Control", "value": "no-store, no-cache, must-revalidate" } ] } ] } ``` Die Bereitstellung aus dem Projektroot (nicht direkt aus `dist/`) stellt sicher, dass `serve.json` angewendet wird und verhindert veraltete gecachte `main.js` nach Rebuilds. In `package.json` ergänzen: ```json "serve": "node ./scripts/serve-dist.mjs" ``` Eigener Port: `PORT=8080 yarn serve`. > **Nur in diesem Repository:** `scripts/copy-latest.mjs` kopiert gebaute JS-Dateien nach `latest/`, damit das [Live-Demo](#live-demo)-Manifest auf einen stabilen Pfad in Git verweisen kann. Für die lokale Plugin-Host-Entwicklung ist das nicht erforderlich. ### Apollo, Auth Guard und Login Im **Production**-Modus (eingebettet in PHX) übernimmt der Host die Authentifizierung. Nutzen Sie den Apollo Client aus `IPluginServices` über `PhoenixHostBridgeService` — ein separater Login-Flow ist nicht nötig. Im **Development**-Modus (Standalone) stellen Sie einen eigenen Apollo Client und optional einen Login bereit. #### Apollo provider Erstellen Sie `src/app/apollo.provider.ts` (vollständige Implementierung in diesem Repository). Er: - verbindet HTTP- und WebSocket-Links mit `environment.apiUrl` / `environment.wsUrl` - sendet `Authorization: Bearer …` aus `environment.apiKey` oder `localStorage` - speichert Tokens aus dem Response-Header `phoenix-auth-token` Nur in Development registrieren, z. B. in `app.config.ts`: ```ts ...(environment.production ? [] : [...apolloProvider()]) ``` #### Apollo service abstraction Ein kleiner Service wählt im Production-Modus den Host-Apollo-Client und im Development-Modus den lokalen Client: ```ts @Injectable({ providedIn: 'root' }) export class ApolloService { private readonly _apollo = signal(undefined!); constructor(private readonly injector: Injector) { if (environment.production) { this._apollo.set(injector.get(PhoenixHostBridgeService)?.pluginServices()?.apollo!); } else { this._apollo.set( injector.get(PhoenixHostBridgeService)?.pluginServices()?.apollo ?? injector.get(Apollo) ); } } apollo = () => this._apollo(); } ``` Wenden Sie dasselbe Muster auf andere Host-Services an (z. B. Notifications), wenn Sie Standalone-Fallbacks benötigen. #### Login component Für die Entwicklung ohne voreingestellten API-Key fügen Sie eine `/login`-Route hinzu, die die PHX-`login`-Mutation aufruft: ```gql mutation Login($username: String!, $password: String!) { login(username: $username, password: $password) { ... on CurrentUser { id identifier channels { id token } } ... on InvalidCredentialsError { errorCode message } ... on NativeAuthStrategyError { errorCode message } ... on EmailCodeAuthStrategyError { errorCode message } } } ``` Eine vollständige Formular-Implementierung finden Sie unter `src/app/login/` in diesem Repo. #### Auth guard Nicht authentifizierte Nutzer nur im Development-Modus zum Login weiterleiten: ```ts @Injectable() export class AuthGuard implements CanActivate { private readonly router = inject(Router); canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): boolean { const token = environment.apiKey ?? localStorage.getItem('api-key'); if (!environment.production && !token) { this.router.navigate(['login'], { queryParams: { redirectTo: btoa(window.location.pathname + window.location.search) }, }); } return true; } } ``` In `app.routes.ts` einbinden: ```ts const canActivate = [environment.production ? () => true : AuthGuard]; export const routes: Routes = [ { path: '', canActivate, component: HelloWorld }, { path: 'product-view', canActivate, component: ProductView }, { path: 'address-list', canActivate, component: AddressList }, { path: 'login', component: Login }, ]; ``` ### package.json scripts Empfohlene Skripte nach dieser Anleitung: ```json { "scripts": { "build": "ng build --watch --output-hashing none --configuration production", "serve": "node ./scripts/serve-dist.mjs", "plugin": "concurrently \"yarn build\" \"yarn serve\"", "client": "ng serve --port 4201 --watch --configuration development", "codegen": "graphql-codegen" } } ``` | Skript | Beschreibung | |--------|--------------| | `build` | Production-Watch-Build für PHX | | `serve` | Stellt kompilierte Assets für PHX bereit (Standard-Port 3223) | | `plugin` | Führt `build` + `serve` zusammen aus — verwenden Sie `yarn run plugin` | | `client` | Eigenständiger Angular-Dev-Server auf Port 4201 | | `codegen` | Generiert GraphQL-TypeScript-Typen neu | Dieses Repository führt zusätzlich `copy-latest.mjs` parallel zu `build` aus, um Artefakte nach `latest/` für die gehostete Demo zu veröffentlichen — das ist für Ihr eigenes Plugin nicht erforderlich. --- ## Fehlerbehebung | Symptom | Zu prüfen | |---------|-----------| | PHX zeigt eine alte Plugin-Version | Hard-Refresh; prüfen, ob `serve.json` `Cache-Control: no-store` für JS setzt; `yarn run plugin` neu starten | | `401` / GraphQL-Auth-Fehler im Standalone-Modus | `apiKey` in `environment.development.ts` setzen oder über `/login` anmelden | | `yarn add @phx/shared` schlägt fehl | Token in `.yarnrc.yml` prüfen; Ihren PHX-Partner kontaktieren | | Routing in PHX funktioniert nicht | Routen-Segmente zu `stripTrailingSegments` in `providePhoenixPluginWithPrimeNG` hinzufügen | | Tailwind-Klassen fehlen in einer Komponente | `@tailwind`-Direktiven in den `styles` der Komponente ergänzen | | `yarn codegen` schlägt fehl | PHX muss laufen und die Schema-URL in `codegen.ts` erreichbar sein | | Custom Element nicht gefunden | Tag im Manifest muss exakt mit `customElements.define` / `bootstrapPhoenixPluginCustomElement` übereinstimmen | --- ## Support Bei Registry-Zugang, Integrationsfragen oder PHX-spezifischen APIs wenden Sie sich an Ihren PHX-Partner.