Skip to content

TypeScript monorepo focused on code splitting, IDE support, Jest, and starter apps for Next, Gatsby, and CRA.

Notifications You must be signed in to change notification settings

R-Bower/tsx-monorepo

Repository files navigation

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 in package.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 the Go to Implementation(s) option instead.
  • VSCode
    • The Go to Definition option works out of the box for me. I'd highly recommend binding this to Ctrl+Click.

Running projects and libs

  • Unless otherwise noted, run all yarn commands from the root of this repository. This is particularly important for yarn 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. Executes yarn run docs --parallel --stream which does the following:
    • starts @rb/monorepo-docs in dev mode using next dev
    • runs @rb/react-style-system and @rb/react-components in watch mode (using tsc) 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.

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 an index.js/index.ts that exports the code or configuration of the package ( JS/TS packages only).
    • a tsconfig.json (TypeScript projects only).
  • 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 and src/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 corresponding View 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 in src/features/<feature-name>.

    • For large features, split the feature folder into multiple subfolders.

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:

About

TypeScript monorepo focused on code splitting, IDE support, Jest, and starter apps for Next, Gatsby, and CRA.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published