m6web/draftjs-bundle
m6web/draftjs-bundle is a Symfony bundle for server-side rendering of Draft.js content. It converts Draft.js raw JSON into HTML, letting rich text created with the React editor be displayed in Twig templates without relying on client-side JavaScript.
Architecture fit — poor as a drop-in. Despite being filed under a "Laravel/PHP package" evaluation, m6web/draftjs-bundle is a Symfony bundle, not a Laravel package. It ships a bundle class, DIC service definitions, and (per its README) a Twig-oriented rendering path for converting Draft.js raw ContentState JSON into HTML. Laravel has no bundle loader, no services.yml config, and no Twig by default — so the package cannot be composer required and used. What has value here is the rendering logic: a block-renderer map plus entity decorators for paragraphs, headers, lists, blockquotes, and link/image entities. That logic is portable; the packaging around it is not.
Integration feasibility — feasible only as a fork or reimplementation. The realistic paths, in order of preference:
DraftJsRenderer contract, replacing Twig templates with Blade/HtmlString. Estimated 1–2 dev-days for the port.AppServiceProvider.<strong>/<em>) is on the order of 200–400 LOC and is well-specified by the Draft.js raw schema. This is often cheaper than reconciling a 2017 fork.Technical risk — high, and mostly non-technical. The package is archived, deprecated, last released 2017-02-21, 7 stars, 0 dependents. Composer constraints will almost certainly pin Symfony 2.x/3.x components and PHP ^5.6 || ^7.0, which conflict outright with Laravel 10/11 on PHP 8.2+. There will be no security patches, no PHP 8.x compatibility fixes, and no response to issues. Upstream Draft.js itself was deprecated by Meta in 2023, so the whole rendering model is on a legacy trajectory. Note the 0.81 "opportunity" score is an artifact of low adoption, not a signal of latent value — do not read it as endorsement.
Key questions for the team:
draft-js-export-html) and stored?{!! !!}, and what sanitization wraps it?Stack fit. Treat the package as a reference implementation and specification, not a dependency. Extract the semantics — which blockType maps to which tag, how inlineStyleRanges and entityRanges are applied and ordered, how nested lists are numbered — and re-express them in idiomatic Laravel. The Symfony Twig layer must be discarded entirely.
Migration path.
--ignore-platform-reqs to reproduce its output against our own content samples. This gives us a golden-output oracle without committing to the code.rawContentState documents covering every block/entity type in production, with expected HTML. This is the actual asset; the code is replaceable, the fixtures are not.DraftJsRenderer::toHtml(array $raw): string, implemented first by a thin port, later by anything else. Nothing in the app depends on the package namespace.body_html column; run a backfill job over historical rows; keep the renderer idempotent.Compatibility. Expect Composer conflicts with symfony/* components that Laravel already pulls in (6.x/7.x) and with PHP 8.2+. Namespace M6Web\Bundle\DraftjsBundle\ is safe to vendor privately but should be renamed or relocated on port. The Twig dependency is the biggest structural mismatch — Laravel has no Twig bridge by default, so any rendering must be rewritten regardless.
Sequencing. Do the fixture capture first, before touching code — it de-risks every subsequent option and is the only deliverable that survives whichever implementation wins. Only then decide port-vs-rewrite; the fixtures usually make "rewrite" the obvious answer.
When not to integrate: if the only consumer is the web frontend and the editor already lives in the browser, render client-side and skip server-side conversion entirely. Server-side rendering earns its cost only for SEO-indexed pages, emails, RSS/API consumers, or content that outlives the editor.
Maintenance. Zero upstream maintenance — the repo is archived and will never receive a patch. Any fork becomes a permanent in-house liability, though a small one: the logic is stateless string transformation with a narrow surface. Budget a modest ongoing ownership line and an explicit exit criterion (editor migration, or replacement with a maintained renderer) rather than letting a fork drift into permanent infrastructure.
Support. No community, no issue triage, no CVE feed, no documentation beyond the README. All knowledge transfer is by reading source. This is acceptable for a 300-line converter, unacceptable for anything handling authentication or untrusted HTML.
Security. The sharpest edge: converting user-authored JSON to HTML and emitting it unescaped is a stored-XSS vector if block text, entity data (link href, image src), or unknown block types pass through raw. Any port must (a) HTML-escape all text node content, (b) validate href/src against an allowlist of schemes, and (c) run the output through an HTML sanitizer (e.g., mews/purifier) as defense in depth. Assume the original bundle does not do this adequately — verify before trusting.
Scaling. Cheap: pure CPU string work, no I/O, trivially horizontally scalable. The scale concern is where it runs — rendering on every read adds latency to list endpoints and blocks on CPU under load. Precompute on write and store the HTML; that converts an O(n) render into a column read and makes the renderer a background concern.
Failure modes to instrument:
blockType → silently dropped content (worst failure: invisible data loss, no error).entityType or orphaned entityRanges → links rendered as plain text or as broken anchors.rawContentState JSON → exception or partial render.inlineStyleRanges uses UTF-16 offsets; naive substr will corrupt multi-byte text.Emit a metric or log line on every unknown block/entity type encountered; that counter is the early warning for both content loss and schema drift.
Ramp-up. Low for a mid-level PHP developer — 1–2 days to port, a day to wire the provider and Blade rendering. The real cost sits in fixture coverage, XSS hardening, and the backfill, not the code. Plan for roughly a week end-to-end including validation, and gate the read-path cutover on a sampled diff against the golden fixtures.
How can I help you explore Laravel packages today?