moved login component; improved documentation; other minor improvements
This commit is contained in:
781
README.DE.md
Normal file
781
README.DE.md
Normal file
@@ -0,0 +1,781 @@
|
||||
# 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 unter [support@phx-erp.de](mailto:support@phx-erp.de)
|
||||
|
||||
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
|
||||
|
||||
Eine gehostete Demo ist auf der PHX-Alpha-Instanz verfügbar:
|
||||
|
||||
**https://alpha.phx-erp.de/customElements/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<Injector | null>(null);
|
||||
private readonly _pluginServices = signal<IPluginServices | null>(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: `<router-outlet />`,
|
||||
styles: [`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`],
|
||||
})
|
||||
export class App {
|
||||
private readonly hostBridge = inject(PhoenixHostBridgeService);
|
||||
|
||||
readonly pluginServices = input<IPluginServices>({});
|
||||
readonly hostInjector = input<Injector | undefined>(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<Apollo>(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; [support@phx-erp.de](mailto:support@phx-erp.de) 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 [support@phx-erp.de](mailto:support@phx-erp.de).
|
||||
Reference in New Issue
Block a user