Removing React.js from the codebase and adapting Htmx for UI interactivity (2023)
Removing React.js from the codebase and adapting Htmx for UI interactivity (2023)
从代码库中移除 React.js 并采用 Htmx 实现 UI 交互 (2023)
Here’s how Misago works currently: You request https://misago-project.org. Django view gathers data to render list of threads. Django view renders template that contains almost complete HTML with list of threads, but also includes a JSON with same data used to render this HTML. And there is no interactivity because forms are disabled. JavaScript downloads. JavaScript runs, reads JSON embedded in the page’s body and replaces most of HTML rendered from Django templates with HTML from React.js components. Buttons become active, and timestamps in UI swap. 目前 Misago 的工作方式如下:当你访问 https://misago-project.org 时,Django 视图会收集数据以渲染主题列表。Django 视图渲染的模板包含了几乎完整的 HTML 主题列表,但同时也包含了一份用于渲染该 HTML 的相同数据的 JSON。此时页面没有任何交互性,因为表单是被禁用的。接着 JavaScript 开始下载并运行,它会读取嵌入在页面主体中的 JSON,并用 React.js 组件生成的 HTML 替换掉大部分由 Django 模板渲染的 HTML。此时按钮变为可用,UI 中的时间戳也会进行替换。
This approach has some problems: A lot of pages in Misago are implemented twice: as Django templates and React.js components. People who start customizing HTML get burned because they edit Django templates and see their changes flash for a second on a site before being replaced by React.js HTML which they didn’t know they also need to customize. Every view accessible to both users and guests needs to be done twice: as Django view with templates, and as React.js route with components. It also requires an API and JSON serializers to power data fetching. JSON serialization to pre-bake data for React.js app slows down response generation. A large part of translation messages is duplicated, living in both django.po and djangojs.po files. JavaScript translation files also increase the initial download size. Lots of JavaScript can kill performance, especially on older and slower mobile devices. Enabling plugins to replace or inject custom HTML to the page requires for those plugins to implement both Django templates and React.js components. And Misago will need to implement a JavaScript build step as part of site build in misago-docker. Plugin devs need to know both. 这种方法存在一些问题:Misago 中的许多页面被实现了两次:一次是 Django 模板,一次是 React.js 组件。开始自定义 HTML 的开发者会感到困惑,因为他们编辑了 Django 模板后,修改内容会在页面上闪现一秒,然后就被他们不知道还需要额外自定义的 React.js HTML 所覆盖。每个对用户和访客可见的视图都需要做两次:一次是带有模板的 Django 视图,另一次是带有组件的 React.js 路由。它还需要 API 和 JSON 序列化器来支持数据获取。为 React.js 应用预先生成数据的 JSON 序列化过程减慢了响应生成速度。大量的翻译信息是重复的,同时存在于 django.po 和 djangojs.po 文件中。JavaScript 翻译文件也增加了初始下载大小。大量的 JavaScript 会严重影响性能,尤其是在较旧、较慢的移动设备上。要让插件能够替换或注入自定义 HTML,插件开发者必须同时实现 Django 模板和 React.js 组件。此外,Misago 还必须在 misago-docker 的站点构建过程中实现一个 JavaScript 构建步骤。插件开发者需要同时掌握这两者。
I’ve considered two solutions to this problem: Drop Django views and templates, only keep those in minimal versions to keep search engine bots happy. Focus on implementing an API and having all UI as React.js app. Reduce Django to API and use JavaScript framework with serverside rendering, eg. Next.js or Remix.run. But here’s a thing: there’s a lot of forum software out there that still does the old way of rendering as much as possible on the server and using some JavaScript on client to improve its interactivity here and there. And people are happy with that. And this approach has none of the above issues. 我考虑过两种解决方案:一是放弃 Django 视图和模板,仅保留最小版本以满足搜索引擎爬虫的需求,专注于实现 API 并将所有 UI 作为 React.js 应用;二是将 Django 简化为 API,并使用支持服务端渲染的 JavaScript 框架,例如 Next.js 或 Remix.run。但问题在于:市面上有很多论坛软件仍然采用传统方式,即尽可能在服务端渲染,并仅在客户端使用少量 JavaScript 来增强局部交互性。用户对此感到满意,而且这种方法完全没有上述提到的那些问题。
Internet forum software has plenty of interactivity, but this interactivity is isolated to specific places on the page. Moderation actions, watching a thread, writing a reply, seeing last notifications, voting in a poll. All this stuff can be achieved without React.js and was achieved without it for years before we’ve decided that full page reload is something that needs to be avoided. 互联网论坛软件确实有很多交互需求,但这些交互通常局限于页面的特定区域。例如管理操作、关注主题、撰写回复、查看最新通知、参与投票等。所有这些功能都可以在没有 React.js 的情况下实现,并且在我们决定“必须避免全页刷新”之前,多年来一直都是这样实现的。
Now, what’s HTMX? HTMX is a tiny library that lets developers specify parts of HTML as dynamic islands that can be swapped by new server-rendered HTML on interaction. For example, list of actual threads is one such (large) island. With little bit of HTMX included in Django template, changing current category on threads list could pull new HTML from Django only for new threads list for selected category, while keeping rest of the page’s HTMX (eg. already loaded search results in navbar) unchanged. Misago’s backend code would only need to be changed to return only the HTML for this island when request comes from HTMX, instead of full page. There’s no need to do any JSON serialization or write dedicated JavaScript or React.js. HTMX is declarative way of doing $.get(“url”, “#outlet”) we’ve did in jQuery 20 years ago. Or Rails Turbolinks. I can’t believe I am writing this, but this is the way for forum software if you hate endless scrolling or want to keep things simple otherwise. GitHub issue: #1674
那么,什么是 HTMX?HTMX 是一个微型库,它允许开发者将 HTML 的部分区域指定为“动态孤岛”,这些区域可以在交互时被服务端渲染的新 HTML 替换。例如,主题列表就是一个这样的(大型)孤岛。在 Django 模板中加入少量 HTMX 后,切换主题列表的分类时,只需从 Django 获取该分类下的新主题列表 HTML,而保持页面其余部分(如导航栏中已加载的搜索结果)不变。Misago 的后端代码只需在收到来自 HTMX 的请求时,返回该孤岛的 HTML 片段,而不是整个页面。无需进行任何 JSON 序列化,也无需编写专门的 JavaScript 或 React.js。HTMX 是一种声明式的方法,类似于我们 20 年前在 jQuery 中使用的 $.get("url", "#outlet"),或者 Rails 的 Turbolinks。我简直不敢相信我正在写这些,但如果你讨厌无限滚动或者希望保持系统简洁,这确实是论坛软件的最佳选择。GitHub 问题:#1674
I’ve also decided against moving the admin panel to SPA. Currently I have a bunch of reusable Django views that take care of 90% of work, and adding new page to admin requires one to pick right base view, fill in blanks, define one or few forms and write very basic templates for those. Rest happens automagically and I like it. The effort required to move away from React.js and to HTMX will be a big one, but it can be done as multiple small steps. Eg. one release could move Navbar to HTMX, other threads list, next thread page, another user profiles, etc. etc. But plugins/permissions/new parser still have to happen first. So HTMX migration will have to wait until later 2024. I am fine with that becasue I would also want to move to latest Bootstrap, and those guys are cooking some crazy stuff in it. 我还决定不将管理后台迁移到 SPA(单页应用)。目前,我有一堆可复用的 Django 视图处理了 90% 的工作,添加新页面只需选择合适的基类视图、填空、定义一两个表单并编写非常基础的模板即可。其余工作会自动完成,我很喜欢这种方式。从 React.js 迁移到 HTMX 的工作量很大,但可以分多个小步骤完成。例如,一个版本可以将导航栏迁移到 HTMX,下一个版本迁移主题列表,再下一个迁移主题页面,然后是用户资料等。但插件、权限和新解析器的工作必须优先完成,因此 HTMX 的迁移将推迟到 2024 年晚些时候。我对此没意见,因为我也想升级到最新的 Bootstrap,他们在那里面搞了一些很棒的新东西。
For the record, JS sizes from Misago 0.39: vendor.js: 679kb, 214kb gzipped misago.js: 615kb, 124kb gzipped django-i18n.js: 102kb, 25.4kb gzipped Misago JS also includes lazily loaded: hljs.js: 144kb, 49kb gzipped zxcvbn.js: 820kb, 430kb gzipped I plan to return to those numbers once I start removing React.js (and other JS dependencies) from Misago. 记录一下,Misago 0.39 版本的 JS 大小: vendor.js: 679kb (Gzip 后 214kb) misago.js: 615kb (Gzip 后 124kb) django-i18n.js: 102kb (Gzip 后 25.4kb) Misago JS 还包含懒加载的: hljs.js: 144kb (Gzip 后 49kb) zxcvbn.js: 820kb (Gzip 后 430kb) 我计划在开始从 Misago 中移除 React.js(及其他 JS 依赖)后,让这些数值回归到更小的水平。
I also need to give a warning that we will likely have a transition period where parts of Misago will have no React.js, but also no HTMX either, effectively making them “multi-page application”. This may sound scary but it only means that clicking a link or submitting a form in, say, user options page will cause a full page reload. But I will still write a placeholder AJAX (or just use the HTMX) where full page reload will be absolutely unbearable, eg. for liking posts or voting in polls. 我还需要提醒大家,我们可能会经历一个过渡期,届时 Misago 的部分页面既没有 React.js,也没有 HTMX,实际上变成了“多页应用”。这听起来可能很可怕,但它仅仅意味着在用户选项页面点击链接或提交表单时会触发全页刷新。不过,对于那些全页刷新完全无法接受的地方(例如点赞帖子或参与投票),我仍然会编写占位用的 AJAX(或者直接使用 HTMX)。
“Forum options” page has been replaced with “Account settings”. This new page is done fully with Django views, and uses HTMX on some pages for dynamic updates. JS size change: misago.js: 578kb, 107kb gzipped (37kb/17kb less) “论坛选项”页面已被“账户设置”取代。这个新页面完全由 Django 视图实现,并在部分页面使用 HTMX 进行动态更新。 JS 大小变化: misago.js: 578kb (Gzip 后 107kb)(减少了 37kb/17kb)
Threads lists have been rewritten almost from scratch to use Django views and HTMX instead of React.js. JS size change: misago.js: 530kb, 99kb gzipped (48kb/8kb less) 主题列表几乎是从零开始重写的,现在使用 Django 视图和 HTMX,而不是 React.js。 JS 大小变化: misago.js: 530kb (Gzip 后 99kb)(减少了 48kb/8kb)