TSX-monorepo
This project is an opinionated collection of tools, apps, and libraries intended for supporting rapid React UI development. It was heavily inspired by GitHub's Primer design system.
Note: This library is no longer under active development. It was a fun project while it lasted, but I've taken it internal for use within Qualcomm.
Live build here: https://tsx-monorepo.com/
Disclaimers
Development for this project is ongoing - not sure how much longer the repo is going to stay public.
Mobile support is coming soon - just need to spruce up the sidebar. The nav menu doesn't have links yet, and you'll be stuck on the homepage unless you navigate to urls directly.
Table of contents
Goals
- Consistent code formatting in every package via ESLint.
- First class TypeScript support.
- Hot reloading adjacent packages for rapid development.
- First class documentation support via MDX and react-live.
- Consistent styling by use of styled-components and styled-system with a strongly typed design system.
- Tree shaking for every React library.
- This is accomplished using webpack's side effects. Learn more.
- Working "go to definition" feature in IDEs for every package.
- Icons by react-icons.
- Unit Testing support via Jest and React Testing Library.
- E2E testing with Playwright + Jest.
Setup
- Ensure that you have Node and Yarn installed.
- I run Node v14+ and haven't tried any lesser versions. Your mileage may vary if you're not on v14+.
- Yarn 2.0+ currently has an issue with the
&
operator inpackage.json
scripts. Use Yarn v1.22.10 until this is resolved.
Go-to definition (jump to the source file)
- WebStorm/IntelliJ
- This should work out of the box with either of these. If the
Go to Declaration or Usages
option is taking you to the dist/ folder of the target component, try using theGo to Implementation(s)
option instead.
- This should work out of the box with either of these. If the
- VSCode
- The
Go to Definition
option works out of the box for me. I'd highly recommend binding this toCtrl+Click
.
- The
Running projects and libs
- Unless otherwise noted, run all
yarn
commands from the root of this repository. This is particularly important foryarn install
. For more details on why this is necessary, read up on yarn workspaces and lerna.
Projects
- Standalone Front End applications are located within
packages/apps
. - Shared libraries and utilities are located within
packages/libs
. - Data fetching APIs and libraries are located within
packages/data
.- Note: it's perfectly fine for isolated apps to include their own data fetching apis within their respective package folders.
Docs
The NextJS application at packages/docs
is responsible for rendering MDX components. Shared components that contain a corresponding mdx file are aggregated by this package. For more information, check out the docs readme.
Running locally
- run
yarn install
from the root. - run
yarn docs
from the root of this repository. Executesyarn run docs --parallel --stream
which does the following:- starts
@rb/monorepo-docs
in dev mode usingnext dev
- runs
@rb/react-style-system
and@rb/react-components
in watch mode (usingtsc
) which triggers a recompile on change (hot reloading). - There's additional configuration details in the
packages/docs
package that forces the site to hot-reload when MDX files are changed.
- starts
Conventions
- Every package must contain at least the following:
- a
package.json
file. - either a
src
folder with code specific to the package's library or framework or anindex.js/index.ts
that exports the code or configuration of the package ( JS/TS packages only). - a
tsconfig.json
(TypeScript projects only).
- a
- Within a TypeScript React app, it's recommended that path aliasing be setup such that directories from the local package starts with
~
.- The base ESLint configuration is set up to split import groups based on external, internal, and sibling/parent/child. A 4th grouping is configured for apps-specific internal files. This is done for organizational purposes, as it's much easier to determine the origin of the imported code.
- Example for a package with
src/components
andsrc/pages
folder:
// src/components/simple-div/SimpleDiv.tsx
import React from "react"
export default function SimpleDiv(): JSX.Element {
return <div />
}
// src/pages/about.tsx
import React from "react"
import SimpleDiv from "~components/simple-div/SimpleDiv.tsx"
import {localUtilityFunc} from "./utils"
export default function AboutPage(): JSX.Element {
return <SimpleDiv onClick={localUtilityFunc} />
}
React Best Practices:
Separate your styles from your api logic. This makes testing significantly easier and keeps your component files small and self-contained.
- A
Data
component does data fetching and then renders its correspondingView
component. That’s it.
Problem:
// src/components/example/Example.tsx
import React from "react"
import useDataExample from "./utils"
export default function Example(): JSX.Element {
// Problem: we've now coupled this component to some arbitrary
// API logic. This component may not render properly if the api
// isn't available during testing (common during CI).
const {data} = useSomeApiData()
return (
<div>
{/* apply styles, etc... */}
{data}
</div>
)
}
Solution: split the component using the Data/View pattern.
// src/components/example/ExampleView.tsx
import React from "react"
export interface ViewProps {
// never use `any` like this. It's just an example.
data: any
}
export default function ExampleView({data}: ViewProps): JSX.Element {
return (
<div>
{/* apply styles, etc... */}
{data}
</div>
)
}
// src/components/example/ExampleData.tsx
import React from "react"
import ExampleView from "./ExampleView"
import useDataExample from "./utils"
export default function ExampleData(): JSX.Element {
const {data} = useSomeApiData()
return <TableView data={data} />
}
Now we're more flexible. We've eliminated the API coupling by placing the styles in a separate component. However, this presents with a tradeoff.
Pros:
- More flexible and likely more accurate tests.
- Can reuse the
ExampleData
container to supply the same logic to another component.
Cons:
- Additional file to maintain (potentially offset by SoC).
- We now have to mock the API logic in order for the tests to pass.
Read more on the Data/View pattern here.
Feature Driven Development (FDD).
- This is a lightweight Agile technique. It involves a project structure where your code is organized by what it accomplishes (i.e. features), rather than lumping all modules of like types into separate blobs of components, routes, logic, actions, etc.
- There is a direct correlation between the problem space (the requirements) and the implementation (the code)
FDD Folder Structure
-
Reusable components that are specific to an application but not a feature should reside in
src/components
. -
Reusable components without a specific application should be placed in the
react-components
package. -
For a given featured name
<feature-name>
, place all related code insrc/features/<feature-name>
.
packages/libs/react-*
- Should not include code specific to project.
- They should not rely on project specific code, constants, router, etc.
References
See the following blog posts. This guy is pretty good. I used his monorepo as a starting point for this repository: