Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nuxt + Paged.js komplizierte Beispiele ausprobieren #2021

Closed
2 tasks
manuelmeister opened this issue Oct 3, 2021 · 3 comments
Closed
2 tasks

Nuxt + Paged.js komplizierte Beispiele ausprobieren #2021

manuelmeister opened this issue Oct 3, 2021 · 3 comments
Assignees

Comments

@manuelmeister
Copy link
Member

  • Picasso
  • ContentNodes
@manuelmeister
Copy link
Member Author

Draft veröffentlichen

@carlobeltrame
Copy link
Member

carlobeltrame commented Jan 31, 2022

Ich habe auch mit Nuxt noch Activity Rendering implementiert. Mein Code ist auf https://github.com/carlobeltrame/ecamp3/tree/feature/print-nuxt-helm
Hier mal ein gedrucktes PDF, das einige in meinen Augen kritische Limitierungen mit Seitenumbrüchen demonstriert.
PrintedByBrowserless(4).pdf
Zum direkten Vergleich, derselbe Datenbankstand mit ReactPDF exportiert: document(4).pdf

Unten meine Notizen von diesem Prozess. Ich versuche meinen ehrlichen Eindruck von der Arbeit mit Nuxt + pagedjs + browserless festzuhalten. Insgesamt ist meine Meinung von diesem Stack durch die aktive Arbeit damit klar besser geworden. Vorausgesetzt wir verwenden Vuetify nicht für Print, könnte ich mit allen Limitierungen leben, ausser mit den Seitenumbruchs-Problemen die ich weiter unten genauer beschreibe.

Developer Experience + Debuggability

  • Kein Live reload
    • Nuxt unterstützt natürlich Live reload. Wir haben das aber manuell deaktiviert (injectScripts: false), damit die Print Preview möglichst ähnlich wie das fertig gedruckte PDF aussieht (um definitiv zu verhindern dass in der Preview client-side JS-Code läuft), und weil es sich nicht mit pagedjs verträgt: Bei jeder Änderung auf der Seite müsste pagedjs komplett neu layouten, wofür ein Reload zwingend nötig ist weil pagedjs während dem Layouten den Seiteninhalt komplett ersetzt. Wenn ich injectScripts: true einfach trotzdem aktiviere bekomme ich jedenfalls Fehler àla _vm.period.scheduleEntries is not a function, also wäre aktuell sowieso hal-json-vuex nicht richtig eingerichtet.
    • Da wir Nuxt programmatisch verwenden (in der ServerMiddleware die nicht direkt zu Nuxt gehört), verlässt sich Nuxt darauf, dass die Views immer (bei jeder Änderung) vor dem Rendern gebuildet werden (i.e. Produktions-Build, npm run build).
    • Die offizielle "Lösung" von Nuxt ist, Code einzubauen der vor jedem solchen Render die Views programmatisch buildet: https://nuxtjs.org/docs/internals-glossary/nuxt/
    • Diese "Lösung" wird aber vermutlich noch viel langsamer, wenn wir vollständiges Vuetify im PDF verwenden... Aber ich bin sowieso dafür, Vuetify nicht für PDFs zu verwenden weil:
      • ... es mir bisher beim PDF mehr in den Weg gekommen ist als geholfen hat (z.B. die CSS-Regel word-break: none von Vuetify kann bei sehr langen Wörtern im Inhalt dazu führen, dass Flexboxes die Seitenbreite sprengen und in Folge pagedjs die Seite nicht korrekt rendert)
      • ... es die Komponentenstruktur schnell kompliziert macht und man diese nicht sinnvoll debuggen kann (Vue dev tools gehen nicht, siehe weiter unten)
      • ... es den Render-Prozess während der Entwicklung potenziell noch sehr viel langsamer macht, wegen dem langsamen SCSS-Variablen-Build. Dieses Problem sollte ja mit Vue 3 gelöst sein, aber im Moment kann Vuetify mit Vue 3 ja noch keinen Kalender, womit der grösste Vorteil von Vuetify vorläufig dahin wäre
      • ... es nicht auf Print ausgelegt ist: Die einzige Erwähnung von "print" in den Docs ist bei den display Utility-Klassen https://vuetifyjs.com/en/styles/display/#display-in-print. Wenn wir ein CSS-Framework verwenden dann lieber etwas leichtgewichtiges wie Tailwind o.ä.
    • Beim Builden in Development verhenkt sich Nuxt gerne mal, wenn eine Datei mehrmals zu schnell hintereinander gespeichert wird. Das äussert sich dann durch Syntaxfehler in beliebigen Dateien in ./.nuxt/, und kann nur durch Löschen des Inhalts des .nuxt Ordners und Neustarten des Print Containers (dauert ca. 1 Minute) behoben werden. Das ist mir beim Implementieren der Schedule Entries ca. 5-6 Mal passiert.
  • Die Print Preview (http://localhost:3003/picasso?period=%2Fperiods%2F1a2b3c4d&pagedjs=true) zu rendern dauert auf meinem Computer ca. 10 Sekunden. Ohne pagedjs dauerts ca. 3-4 Sekunden, aber ohne pagedjs ist die Vorschau nutzlos. Ein fertiges PDF via lokalen browserless-Container zu rendern dauert bei mir insgesamt ca. 20 Sekunden.
    • Wenn wir in Zukunft Vuetify einsetzen und noch das SCSS mit den custom variables zusammengesetzt werden muss kanns noch sehr viel länger dauern. Bei den obigen Zeiten habe ich mir Mühe gegeben, Vuetify möglichst auszuschalten.
    • Ein eigentliches PDF zu erstellen dauert so oder so sehr lange, das wird daher während der Entwicklung selten angeschaut werden, und Probleme die sich erst in Browserless einschleichen werden tendenziell spät bemerkt werden (z.B. siehe Webfonts weiter unten)
  • Das PDF-Layout kann in der Preview als HTML mit den Browser Dev Tools debuggt werden 🥳
    • Die Vue Dev Tools funktionieren aber nicht, aus dem gleichen Grund wie auch live reload nicht funktioniert: Der Server sendet fertig gerendertes HTML ohne JS Code zum Browser. Komponentenstruktur, API State etc. kann also nicht im Browser debuggt werden.
  • Bei obskureren Server-Side Fehlern habe ich keine Ahnung wie ich die auch nur ansatzweise debuggen könnte, insbesondere wenn die Fehler in pagedjs auftreten.
    • Ich muss die ganze Zeit den Code Editor, den Browser und die Konsole des Print-Containers offen haben um arbeiten zu können. Damit wäre die Arbeit unterwegs auf einem Laptop eher mühsam.
    • Die Preview rendert manchmal normal, obwohl intern beim SSR Fehler aufgetreten sind. Die Fehler sind dann auf der Konsole zu sehen, aber es gibt im Browser kein Anzeichen dafür, ausser man weiss genau wie die Vorschau gerade aussehen sollte. Das ist mir zum Beispiel beim Nested Content Nodes rendern passiert, als ich etwas mit den async imports der rekursiven Komponentenstruktur nicht richtig gemacht hatte. Um das zu debuggen hatte ich keine andere Möglichkeit als in alle Komponenten debug-Text zu schreiben.
  • Prettier und ESLint spammen die Konsole zu, sodass es nur schon schwierig ist zu erkennen welche Konsolenmeldungen noch vom aktuellen Request kommen. Immer wieder finde ich den von Prettier erforderten Code Style überhaupt nicht schöner, und schon gar nicht vorhersehbar. Wenn wir Prettier wirklich behalten wollen, sollten wir mindestens damit anfangen, uns immer und möglichst automatisiert danach zu richten, damit die Konsole für Debug Log Statements frei bleibt.
    • Während dem schnellen Entwickeln kommt mir Prettier ständig in den Weg. Beim Aufräumen von Code vor dem Committen etc. macht es aber sicher Sinn. Gibts hier irgendwelche Möglichkeiten von Prettier selber, damit umzugehen?
    • ESLint Code Style ist noch nicht derselbe wie im Frontend, z.B. kein Space vor den Klammern eines computed
  • Die Komponenten zu schreiben macht Spass, weil Vue. Das ist insbesondere ein Vorteil, wenn ein erstmaliger Contributor einen neuen ContentNode implementieren will. Aber ich als Maintainer verbringe gefühlt viel mehr Zeit damit, die Infrastruktur hinzubiegen und ewig auf Test-Renders zu warten, bevor ich überhaupt zum Vue Komponenten schreiben komme... Insgesamt finde ich Nuxt + pagedjs + puppeteer daher immer noch eine sehr mühsame Kombination. Alle drei für sich genommen fühlen sich schon nicht sehr battle-tested und stabil an, und in Kombination noch schlimmer.
  • IDE Unterstützung ist gut, weil es "normale" Vue-Komponenten sind, mit ein paar Nuxt-spezifischen Zusätzen.
    • Mit Ctrl+Klick zu einer Komponente navigieren funktioniert nur, wenn man Komponenten wirklich importiert. Ist aber sowieso zu empfehlen.
    • HTML und CSS Intellisense ist sehr nützlich im Vergleich zu ReactPDF
  • Beim Implementieren von neuen Content Nodes muss ich immer überlegen, ob eine gegebene Übersetzung jetzt ins Frontend oder ins Common gehört
  • Wenn der Inhalt die Breite der Seite sprengt dann schneidet pagedjs die HTML-Elemente die ausserhalb der Seite liegen einfach ab... Also sie werden wirklich komplett aus dem HTML entfernt. Das ist definitiv nicht erwartetes Verhalten für mich als Amateur-pagedjs-Nutzer, kann ich aber konsistent so reproduzieren.

Wartungs- und Betriebsaufwand

  • Wenn wir den hosted browserless.io Service verwenden, kostet uns ein PDF ca. 4 Sekunden browserless-Zeit, i.e. 0.07 Rappen, also für einen Franken können wir 1428 PDFs drucken. Abgeschätzt habe ich das mit dem lokalen Container, also vielleicht dauerts mit Connection nach San Francisco und mit mehr PDF-Inhalt auch doppelt so lang. Angenommen jedes PDF kostet im Schnitt 0.15 Rappen, dann würde sich ein höherer Plan bei Browserless (z.B. auch näher bei uns als in San Francisco) immer noch erst ab über 70'000 PDFs pro Monat lohnen.
    • Aber je nach dem können wir den hosted browserless.io Service in San Francisco gar nicht nutzen, wegen Webfonts (siehe unten) und allenfalls wegen Datenschutz.
  • Kosten wenn self-hosted: Unklar. Es braucht sicher mehr Leistung im Kubernetes-Cluster. Aber da müsste man vielleicht mal einen Lasttest machen, um herauszufinden wie die eingebaute Queue bei browserless wirklich funktioniert, was passiert wenn die Queue überfüllt ist, und ob wir das in diesem Fall z.B. mit Kubernetes automatisch hochskalieren könnten.
  • Security ist mehr ein Gebastel als eine moderne Lösung im Sinne einer 12-factor app
    • Das Cookie weitergeben wird in dem Moment kaputt gehen in dem wir unsere Domain-Struktur ändern. Bzw. machts das schwieriger, eCamp v3 an beliebigen Orten zu betreiben.
    • Sobald wir versuchen, automatisches Renewal des JWT Tokens zu implementieren wird das auch auf die Schnauze fallen
    • Falls der Print Service je nicht mehr unter der gleichen Domain läuft, muss das ganze JWT Token für JavaScript verfügbar gemacht werden, woraus folgt dass wir bereits jetzt noch vorsichtiger sein müssen kein XSS auf der Seite zuzulassen.
    • Eine "korrekte", 100% sichere Alternative wäre, dass die API auch ein OAuth Provider wird, und der Print Service so nie das JWT-Token des Users kennen muss. Scheint mir aber Overkill für MVP. Das wird dann erst später mal ein Thema wenn es mal noch andere API Clients gibt die anderswo gehostet werden.

Features

  • Webfonts (z.B. von Google Fonts) funktionieren nicht
    • Wir warten mit dem Drucken bereits, bis für 0.5 Sekunden keine Netzwerk-Requests mehr offen sind (waitUntil: networkidle0)
    • Workaround vom browserless Blog hilft auch nicht: https://docs.browserless.io/blog/2020/09/30/puppeteer-print.html#set-a-standard-user-agent-header
    • Vorinstalliert im browserless Container ist Roboto und die Ubuntu-Systemschriftarten, weshalb uns das bisher mit Vuetify nicht aufgefallen ist
    • Um eigene Fonts zu verwenden könnten wir sie in den Container installieren, aber dann können wir nicht mehr den Browserless.io Service verwenden sondern sind gezwungen selbst zu hosten
    • Versuch Hosting der Fonts direkt in den Nuxt Assets: Pagedjs hat Probleme beim Laden der Schriften via relative URLs... pageerror occurred: TypeError: Failed to construct 'URL': Invalid URL. Könnte man eventuell mit dem onUrl Hook von pagedjs angehen, habe ich noch nicht probiert.
    • Wenn ich nur absolute URLs fürs Laden der Schriftarten verwende versteht pagedjs was ich meine, aber es wird trotzdem nicht so ins PDF gerendert
    • Zusammenfassend ist die einzige Möglichkeit, die durchs Band funktioniert, um andere Fonts einzusetzen: Font im Container installieren (somit kann browserless.io Service nicht genutzt werden) und trotzdem noch die Google Fonts URL referenzieren (eher schlecht für Datenschutz).
    • Damit diese Lösung funktioniert müssen wir auch Fonts für Symbole und Emoji bereitstellen. Sonst kanns sehr subtile Unterschiede zwischen Vorschau und PDF geben, die zu sehr subtilen Bugs führen können
  • Das Kerning (optimierter Abstand zwischen den Buchstaben) im fertigen PDF ist sehr schlecht
  • Jede Vue Komponente kann selber angeben, welche Daten sie aus der API laden möchte 👍
    • Das hilft aber nur bedingt. Wenn wir wirklich N+1 API Requests vermeiden wollen, müssen wir trotzdem irgendwo im Top Level alle Daten fetchen, via HAL oder GraphQL
  • Um Rich Text zu rendern, können wir einfach einen read-only Tiptap verwenden. Das ist von Tiptap auch so vorgesehen.
    • Ich habe der Einfachheit halber bisher v-html verwendet. Das sollten wir aber aus XSS-Gründen (siehe Security weiter oben) nicht machen, auch wenn wir den Rich Text validieren / input-filtern. Es wird früher oder später sicher mal vorkommen, dass wir mal unerwartete Inhalte in der DB haben.
  • Kann wo angebracht echte HTML-Tabellen verwenden
  • Wenn ich Text aus dem PDF selektiere und kopiere bekomme ich fast perfekt den gewünschten Output. Nur Ligaturen (verbundene Buchstaben wie ff, fl, ft etc) werden nicht rauskopiert.
  • Seitenumbrüche: Standardverhalten hat viele Probleme. Siehe hochgeladenes PDF oben in diesem Kommentar. Ein paar grosse Probleme die mir in den ersten 10 Minuten rumprobieren aufgefallen sind:
    • Die Content Nodes einer Activity mit viel Inhalt starten unnötigerweise auf einer neuen Seite, was unter dem Activity Header viel ungenutzten weissen Platz lässt (nicht im Beispiel-PDF sichtbar) (Problem 1 in Demo der paged.js Paging Probleme #2418)
    • Bei einem Zweispalten-Layout bei dem die linke Spalte auf die nächste Seite umbricht, wird die rechte Spalte erst auf der zweiten Seite begonnen (Problem 2 in Demo der paged.js Paging Probleme #2418)
    • Storyboard mit einer extrem langer Section: Wird umgebrochen, aber die Flexbox wird falsch gerendert (auf der ersten Seite zu weit rechts, auf der zweiten Seite verschwindet die Zeit-Spalte ganz) (Problem 3 in Demo der paged.js Paging Probleme #2418)
    • Die Vorschau ohne pagedjs sieht gut aus, die Probleme kommen soweit ich beurteilen kann erst beim Layouten von pagedjs zustande
    • Soweit ich sehe kommt zumindest ein Teil der Probleme davon, dass pagedjs HTML-Elemente rausschneidet, die seiner Meinung nach keinen Einfluss auf die Seite haben weil sie ausserhalb des Seitenbereichs liegen oder weil sie unsichtbar sind (?), wie z.B. die Zeit- oder Responsible-Spalte im umgebrochenen Storyboard

To do / bisher nicht ausprobiert

  • PRIO 1: Seitenumbrüche und Verhalten von pagedjs optimieren
    • Gibts irgendwelche Konfiguration bei pagedjs um z.B. das Abschneiden von nicht sichtbaren HTML-Elementen auszuschalten? -> pagedjs hat eine Liste von Hooks bei denen man sich einhängen kann, mal durchprobieren was da möglich ist.
    • Gibts irgendwelche Möglichkeiten in paged media CSS um zu spezifizieren "Nach dem Titel einer Activity sollte auf der gleichen Seite noch mindestens x px Platz für die darauf folgenden Elemente sein. Wenn weniger Platz auf der aktuellen Seite vorhanden, soll der Titel direkt auf die nächste Seite gerendert werden." Oder anders gesagt, gibt es in CSS ein Äquivalent oder einen anderen Approach zu minPresenceAhead von ReactPDF?
      • Orphan und widow protection werden theoretisch in der CSS paged media Spec in der Übersicht erwähnt, aber ich habe nicht herausgefunden wie man das tatsächlich macht
  • Lasttest auf Kubernetes, und Verhalten bei voller browserless-Queue untersuchen?
  • Funktioniert Tiptap serverseitig? Wenn ja, Tiptap read-only zum Rendern von Rich Text verwenden. Sonst, Package verwenden, wie in 2554a3e für ReactPDF schon gemacht
  • ESLint bzw. Prettier Code Style zwischen print und frontend harmonisieren
  • Die URL für die Preview hat potenziell sehr viele Query-Parameter hinten dran, was ich nicht gerne den Usern exponieren möchte. Ich wäre daher dafür, das weiterhin nur z.B. in einem iFrame anzuzeigen. Eine Vorschau muss man ja nicht sharen können, dafür kann man sich dann das PDF drucken.
  • Sprache muss noch irgendwie mitgegeben werden (z.B. als weiteren Query-Parameter). Sonst kommts glaube ich immer in Englisch

@BacLuc
Copy link
Contributor

BacLuc commented Aug 27, 2023

Can we close this issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants