Status: accepted
We create widgets that extend the Element messenger.
They use a number of APIs to interact with the host messenger applications.
The widgets consume the matrix-widget-toolkit for common building blocks such as API access, error handling, and theming.
The widgets are developed with the React framework in TypeScript.
Each widget should follow a clear and common structure to strive for a high quality and to make it easily understandable for other developers (code is more often read than written). This also becomes relevant since some widgets are planned to be open sourced.
We will apply the following rules to each existing and upcoming widget development project:
- There is a separation between feature components and reusable components, and internal helper components.
- Components and helpers that belong together, should be stored in the same folder—this also includes (unit) tests.
- A folder should make selected components available to others by explicitly exporting a public API.
- Hierarchical folders can re-export children to compose a larger public API.
The src/ folder of each project contains a single components/ folder that includes all React components.
There can optionally be helper directories such as lib/ to store non-components or non-business functionality.
Some basic principles:
- Create well-scoped small components (that are easily testable).
- Create small components and compose them into larger ones.
- Select meaningful names for all helpers and components (ex:
<NameList/>instead of<Component2/>). - Store each component in a folder of the same name (ex:
<Component/>insrc/components/Component/Component.tsx). - Store sub-components and helpers in the same folder (ex:
<SubComponent/>insrc/components/Component/SubComponent.tsx). - Store test files as
<component>.test.tsxalongside the tested component (ex:src/components/Component/Component.test.tsx).
This is a resulting example folder structure:
src/
├─index.ts[x]
├─components/
│ ├─index.ts
│ ├─ComponentX/
│ │ ├─index.ts
│ │ ├─ComponentX.tsx
│ │ ├─ComponentX.test.tsx
│ │ ├─SubComponentY.tsx
│ │ ├─SubComponentY.test.tsx
│ └─ComponentY/
│ ├─index.ts
│ ├─ComponentY.tsx
│ ├─ComponentY.test.tsx
│ ├─SubComponentZ.tsx
│ ├─SubComponentZ.test.tsx
└─lib/
├─index.ts
└─UtilityX/
├─index.ts
├─UtilityX.tsx
├─UtilityX.test.tsx
├─helper.tsx
└─helper.test.tsx
We will use an export scheme that only exports public elements.
All of the above mentioned folders contain an index.ts that explicitly (re-)exports components and helpers for external use.
Some basic rules for exporting:
- Export from a file to access it in tests.
- Export from a folder to access it from other components.
- Don't do
defaultexports (one reason is consistent naming, learn about more reasons).- Export all types and interfaces that a consumer might need.
We will only import from the public API. We will use the highest exporting module relative to the importing file.
Some basic import rules:
- Never import from
.or..to not import yourself again.- Always import components from the same folder with
./<component>.tsx.- Always import components from a different folder with
../<component-folder>—choose the “top-most” export.
These rules apply to widget projects as well as to library projects.
The main difference is that a library has a top-level index.ts that exports its public API while a widget has an index.tsx as the application entrypoint.
Some additional guidelines:
-
Index files that are re-exporting symbols from non-index files should always enumerate all exports:
// in components/ComponentX/index.ts export { ComponentX } from './ComponentX'; export type { ComponentXProps } from './ComponentX';
-
Index files that are re-exporting other index files should always use the wildcard form:
// in components/index.ts export * from './ComponentX';
-
Internal cross-directory imports are allowed from non-index modules to index modules:
// in components/ComponentX/ComponentX.tsx import { UtilityX } from '../../lib/UtilityX';
-
Imports that bypass an index file are discouraged, but may sometimes be necessary:
// in components/ComponentX/ComponentX.tsx import { helperFunc } from '../../lib/UtilityX/helper';
We will refactor the existing widgets to follow this schema as good as possible. We will enforce these rules for any new widget that will be created.