Short guide to h's new Preact-based frontend code #9564
seanh
started this conversation in
Show and tell
Replies: 1 comment
-
We have migrated away from Mocha as part of the Karma-to-Vitest migration, as Vitest exposes the same globals that Mocha provides, so there's no longer a need for it. The only difference is that
Technically speaking, Vitest does provide mechanisms to perform assertions and expectations, as well as for mocking, so we could also replace Chai and Sinon eventually. However, that's a lot of rewriting and we haven't even discussed it's that's the way we want to go. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Tech stack
We use TypeScript not JavaScript: https://www.typescriptlang.org/.
And Preact not React: https://preactjs.com/ (but I highly recommend https://react.dev/learn).
We use Tailwind for CSS: https://tailwindcss.com/. If you haven't tried "utility first CSS" before this is very different, but once you get used to it it's amazing! Here's a good post about utility-first CSS, from the creator of Tailwind. This post is also good, and this one.
The frontend-shared repo contains various frontend components that're shared across our codebase. You can see a demo of them at https://patterns.hypothes.is/.
There are separate
make
commands for frontend things:make frontend-{format,lint,tests,typecheck}
.make sure
will also run these (along with all the backend stuff), as will CI.PRs
#8789 was the PR that originally added this new tech stack to h and added a first version of the create-group page using the new stack. The code has been extended and refactored somewhat since then, but the PR is still a good overview of everything that's involved in adding a new page using the new stack.
Pyramid routes
There's a few different routes/URLs used by the new group management pages (one for the group edit page, one for the group members page, etc):
h/h/routes.py
Lines 224 to 241 in 052e9e1
For the login and signup pages of course there are already
/login
and/signup
routes, so you shouldn't need to add any new routes.Pyramid views
The Pyramid view for the group edit page is
h/views/groups.py::GroupCreateEditController.edit()
:h/h/views/groups.py
Lines 34 to 52 in 052e9e1
Note that the same view handles three different routes
group_edit
,group_edit_members
andgroup_moderation
, and all three routes use the same template (defined in the@view_defaults
class decorator).This is because a single Preact app handles all three pages (more on that below), so Pyramid just needs to bootstrap the same Preact app for all three URLs. You'll want to do something similar for the different "pages" of the new login and signup mockups.
The view pretty much just renders the template. The one important thing to note is the
js_config
dict that is passed to the template. This contains the config settings for the Preact app.Jinja2 template
The group edit, group members and group moderation pages are all rendered by a single Jinja2 template
h/templates/groups/create_edit.html.jinja2
:h/h/templates/groups/create_edit.html.jinja2
Lines 1 to 26 in 052e9e1
Of course the Jinja2 template doesn't render the actual contents of the page, those're rendered by a Preact app (see below). The Jinja2 template just injects the CSS and JS for the Preact app based on the assets bundles that were defined in
assets.ini
(more on how these assets bundles are defined below).The Jinja2 template also renders a
<script type="application/json" class="js-config">
object containing the Preact app's config settings in JSON:CSS & Tailwind
The root file for the CSS is
h/static/scripts/group-forms.css
. You'll want to add another CSS file like this for the login/signup pages, or @robertknight & @acelaya might ask you to rename this one so it can be reused.There's a Gulp task to compile the
group-forms.css
file. Again you'll want to add another task like this or rename this task so it can be reused:h/gulpfile.js
Lines 30 to 32 in 052e9e1
Finally, there's an assets bundle in
assets.ini
to tell Pyramid about the CSS file. Again you'll want to add another of these or rename this one:h/h/assets.ini
Lines 28 to 29 in 052e9e1
JavaScript & Preact
Each Preact app needs to be registered in
rollup.config.js
for module bundling. You'll want to add another line like this one or rename this bundle so that it can be reused:h/rollup.config.js
Lines 48 to 49 in 052e9e1
As with the CSS there's an assets bundle in
assets.ini
to tell Pyramid about the JS file. Again you'll want to add another of these or rename this one:h/h/assets.ini
Lines 31 to 32 in 052e9e1
The source code for the group forms is in
h/static/scripts/group-forms/
. You'll want to add a new folderh/static/scripts/login-forms/
.index.tsx
is the file that bootstraps Preact, you'll want to have one of these:h/h/static/scripts/group-forms/index.tsx
Lines 1 to 13 in 052e9e1
config.ts
handles reading the Preact app's configuration settings from thejs-config
object, you'll want one of these too. As you can see, it defines a lot of TypeScript types:h/h/static/scripts/group-forms/config.ts
Lines 1 to 54 in 052e9e1
Most of the actual code can be found in the
components/
subdir (since a Preact app is mostly just a tree of components).The
<AppRoot/>
component is the top-level one:h/h/static/scripts/group-forms/components/AppRoot.tsx
Lines 16 to 59 in 052e9e1
<AppRoot/>
is the component thatindex.tsx
injects into the HTML:h/h/static/scripts/group-forms/index.tsx
Line 10 in 052e9e1
The
<Router>
and<Route>
stuff inAppRoot.tsx
is how a single Preact app handles different pages depending on the URL, and can also handle navigations between URLs without doing full-page reloads. We use https://www.npmjs.com/package/wouter-preact for this.From there, it's basically just a case of breaking
<AppRoot/>
down into separate sub-components in separate files, just because it would be too complicated if it was all one big component. As you can see,<AppRoot/>
injects either<CreateEditGroupForm/>
or<EditGroupMembersForm/>
or<GroupModeration/>
depending on the URL. These are all custom components defined in other files within thecomponents/
dir.Making API requests from JavaScript
We may not need to make any API requests for signup or login but just in case...
API requests from h's frontend code are authenticated using a secure, HTTPS-only,
SameSite=Strict
cookie plus a CSRF token. This is all set up already. There's a hard-coded list of API endpoints that the frontend is allowed to call. Any new endpoints that you want to call need to be added to this list.There's a JavaScript API client in
api.ts
(will need to move this to another folder if it's to be called by more than just the group pages): https://github.com/hypothesis/h/blob/main/h/static/scripts/group-forms/utils/api.tsJavaScript tests
Tests are done by unit testing one component at a time. For example for
components/CreateEditGroupForm.tsx
there'scomponents/test/CreateEditGroupForm-test.js
.Some notes about the JavaScript tests:
pytest
in our JavaScript test stack). Mocha provides thedescribe()
andit()
functions for writing tests. Alsobefore()
,after()
,beforeEach()
andafterEach()
for setup and teardown.assert
keyword). We use Chai for writing assertions, and use Chai's assert-style APIBeta Was this translation helpful? Give feedback.
All reactions