Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/tim-w-james/timjames.dev

Personal site for Tim W James - Portfolio, Blog, and more. Built with TypeScript, React and Vite. Deployed to timjames.dev
https://github.com/tim-w-james/timjames.dev

blog eslint netlify personal-website portfolio-website reactjs scss storybook stylelint tailwindcss typescript

Last synced: 3 days ago
JSON representation

Personal site for Tim W James - Portfolio, Blog, and more. Built with TypeScript, React and Vite. Deployed to timjames.dev

Awesome Lists containing this project

README

        





Logo

timjames.dev


Personal site for Tim W James - Portfolio, Blog, and more


Deployed to timjames.dev 🌐



CI

Release

Renovate













Linkedin



Netlify Status



Table of Contents



  1. About The Project



  2. Getting Started



  3. Usage





  4. Development



  5. License

  6. Contact


## About The Project

screenshot

### Goals

- **Portfolio**: display and demo past projects
- **Blog**: display blog posts from [dev.to/timwjames](https://dev.to/timwjames)
- This project also serves as an environment for me to experiment with new
technologies and tools. Many of these tools are overkill of a project of this
scale (e.g., Redux), but this project acts as useful reference and
proof-of-concept.

### Built With

**Design**:

- [![Figma](https://img.shields.io/badge/-Figma-black?style=flat-square&logo=figma)](https://www.figma.com/file/pVOwmsYdIymurR4GmEFry1/Portfolio?node-id=9%3A117&t=c2WxYEnrq61XsoNL-1):
design tool. View the design
[online](https://www.figma.com/file/pVOwmsYdIymurR4GmEFry1/Portfolio?node-id=9%3A117&t=c2WxYEnrq61XsoNL-1)
or the exported file [`Portfolio.fig`](./Portfolio.fig)

**Development**:

- [![React](https://img.shields.io/badge/-React-black?style=flat-square&logo=react)](https://reactjs.org/docs/getting-started.html):
frontend framework
- [![Redux](https://img.shields.io/badge/-Redux-black?style=flat-square&logo=redux)](https://redux.js.org/):
state management for React
- [![Vite](https://img.shields.io/badge/-Vite-black?style=flat-square&logo=vite)](https://vitejs.dev/config/):
frontend build tool and dev server. Configured in
[`vite.config.ts`](./vite.config.ts)
- [![TypeScript
4.7](https://img.shields.io/badge/-TypeScript-black?style=flat-square&logo=typescript)](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html):
types for js. Configured in [`tsconfig.json`](./tsconfig.json)
- [![SASS](https://img.shields.io/badge/-SASS-black?style=flat-square&logo=sass)](https://sass-lang.com/):
CSS preprocessor. This repo uses a custom setup to auto-generate scoped type
definitions for CSS classes, go [here](#styling) for further details
- [![Tailwind](https://img.shields.io/badge/-Tailwind-black?style=flat-square&logo=tailwindcss)](https://tailwindcss.com/):
Utility-first CSS framework. This repo uses a custom setup to auto-generate
type definitions for Tailwind classes, go [here](#styling) for further details
- [![ESLint](https://img.shields.io/badge/-ESLint-black?style=flat-square&logo=eslint)](https://eslint.org/docs/2.0.0/user-guide/configuring):
Linter/code analyzer for TypeScript. Configured in
[`.eslintrc.cjs`](./.eslintrc.cjs) with rules from AirBnB and SonarJS
- [![Stylelint](https://img.shields.io/badge/-Stylelint-black?style=flat-square&logo=stylelint)](https://stylelint.io/):
Linter/code analyzer for SCSS. Configured in
[`.stylelintrc.cjs`](./.stylelintrc.cjs)
- [![Prettier](https://img.shields.io/badge/-Prettier-black?style=flat-square&logo=prettier)](https://prettier.io/docs/en/configuration.html):
Formatter. Configured in [`.prettierrc.cjs`](./.prettierrc.cjs)
- [![Vitest](https://img.shields.io/badge/-Vitest-black?style=flat-square&logo=vite)](https://vitest.dev/config/):
unit testing framework. Configured in [`vite.config.ts` >
`test`](./vite.config.ts#L52)
- [![Storybook](https://img.shields.io/badge/-Storybook-black?style=flat-square&logo=storybook)](https://storybook.timjames.dev):
view, document and test individual components and pages. Configured in
[`.storybook/main.cjs`](./.storybook/main.cjs). Automatically deployed to
[Github Pages](https://storybook.timjames.dev)
- [![Playwright](https://img.shields.io/badge/-Playwright-black?style=flat-square&logo=playwright)](https://playwright.dev/):
end-to-end tests. Configured in [`playwright.config.ts`](./playwright.config.ts)
and located in [`e2e/`](./e2e/)
- [![pnpm](https://img.shields.io/badge/-pnpm-black?style=flat-square&logo=pnpm)](https://pnpm.io/):
configuration for the `pnpm` package manager for better performance, lockfiles
and monorepo support. See steps below if you wish to use a
different package manager
- [![Husky](https://img.shields.io/badge/-Husky-black?style=flat-square&logo=git)](https://github.com/typicode/husky):
pre-commit Git hooks to lint, format and run tests. Configured in
[`.husky`](./.husky)
- [![Renovate](https://img.shields.io/badge/-Renovate-black?style=flat-square&logo=renovatebot)](https://github.com/Tim-W-James/timjames.dev/issues/2):
GitHub bot for automatic dependency updates. Configured in
[`renovate.json`](./renovate.json)
- [![GitHub
Actions](https://img.shields.io/badge/-GitHub%20Actions-black?style=flat-square&logo=githubactions)](https://github.com/Tim-W-James/timjames.dev/actions):
GitHub CI/CD pipeline. Used to ensure builds, linting rules and tests pass for
any [Pull Request](https://github.com/Tim-W-James/timjames.dev/pulls) against
the [`main`](https://github.com/Tim-W-James/timjames.dev/branches) branch.
Configured in [`.github/workflows`](./.github/workflows)

**Deployment**:

- [![Netlify](https://img.shields.io/badge/-Netlify-black?style=flat-square&logo=netlify)](https://www.netlify.com/):
Hosting, CDN, NS and continuous deployment for
[timjames.dev](https://timjames.dev). Netlify domain:
[timjames.netlify.app](https://timjames.netlify.app) - go [here](#deployment)
for details. Configured in [`netlify.toml`](./netlify.toml)
- [![GoDaddy](https://img.shields.io/badge/-GoDaddy-black?style=flat-square&logo=goDaddy)](https://www.godaddy.com/en-au/tlds/dev-domain):
Domain Registry for [timjames.dev](https://timjames.dev) using the `.dev` TLD
- [![Let's
Encrypt](https://img.shields.io/badge/-Let%27s%20Encrypt-black?style=flat-square&logo=letsencrypt)](https://letsencrypt.org/):
TLS/SSL Certificate Authority for [timjames.dev](https://timjames.dev)

## Getting Started

### Prerequisites

- Install [`node`](https://nodejs.org/en/) for the version in
[`.nvmrc`](./.nvmrc) or use [`nvm`](https://github.com/nvm-sh/nvm):

```sh
nvm install && nvm use
```

- Install the [`pnpm`](https://pnpm.io/installation) package manager. One option
is [`corepack`](https://nodejs.org/api/corepack.html) for automatic
installation, which is an experimental `node` feature that must be enabled
using:

```sh
corepack enable
```

### Installation

- Clone the repo:

```sh
git clone https://github.com/Tim-W-James/timjames.dev.git
```

- Install dependencies with [`pnpm`](https://pnpm.io/installation):

```sh
pnpm i
```

- Create a `.env` file and specify the following:

```sh
# Get the reCAPTCHA key from https://www.google.com/recaptcha/admin/site/599894418
VITE_SITE_RECAPTCHA_KEY=123
STORYBOOK_SITE_RECAPTCHA_KEY=123
```

## Usage

- Build to [`dist`](./dist) and preview:

```sh
pnpm build
pnpm preview
```

## Development

- Start a development environment:

```sh
pnpm dev
```

### Testing

- Run unit tests in watch mode (automatically reruns tests when source code
changes):

```sh
pnpm test
```

- Run coverage tests and output results to [`coverage`](./coverage):

```sh
pnpm coverage
```

- View individual components or pages and run interaction tests:

```sh
pnpm storybook
```

- Run [Storybook](https://storybook.js.org) tests to ensure all stories render
and interaction tests pass (requires Storybook to be running, or use `:ci`):

```sh
pnpm storybook:test
```

- Run End-to-End tests with [Playwright](https://playwright.dev/) (`:headed` to
view the tests being executed in the browser) for Firefox, Chromium, and
Webkit in both desktop and mobile viewports:

```sh
pnpm e2e
```

- View visual regression tests on
[Chromatic](https://www.chromatic.com/library?appId=63c678edef0d36c489eee3c1&branch=main)

#### Writing New Tests

This repo has several layers of tests:

- **Unit tests for TypeScript utilities** (those in [`src/utils`](./src/utils)): use
[Vitest](https://vitest.dev/).
- **Unit tests for React components**:
- First, consider creating a
[Storybook](https://storybook.js.org/docs/react/writing-tests/importing-stories-in-tests#example-with-testing-library)
story for the component, including decorators, args, etc.
- Storybook allows us to document and preview components in insolation, and we
can reuse stories in our tests without having to duplicate logic
- Use [React Testing
Library](https://testing-library.com/docs/react-testing-library/intro/) to
render the component from Storybook, then write tests
- Tests should use an
[arrange-act-assert](https://robertmarshall.dev/blog/arrange-act-and-assert-pattern-the-three-as-of-unit-testing/)
pattern and follow the React Testing Library [query
priorities](https://testing-library.com/docs/queries/about#priority).
[Testing playground](https://testing-playground.com/) is a useful tool for
finding good queries
- [Storybook interaction
tests](https://storybook.js.org/docs/react/writing-tests/interaction-testing)
can be used too, as this allows actions to be viewed visually. However,
this can result in tests being duplicated. Tests should not be part of
interactions where possible, instead they should be used to document complex
behaviour of a component (e.g., a form being filled out)
- **Accessibility tests**:
- The [Storybook ally
addon](https://storybook.js.org/addons/@storybook/addon-a11y) can be used to
check for accessibility issues
- The Netlify Lighthouse plugin also run on build to detect further
accessibility and performance issues
- **End-to-end tests**: [Playwright](https://playwright.dev/) tests in
[`e2e/`](./e2e/), with the testing library API
- **Visual regression tests**:

- [Avoid using snapshot tests](https://medium.com/@sapegin/whats-wrong-with-snapshot-tests-37fbe20dfe8e)
- Visual regression tests are run in CI using
[Chromatic](https://www.chromatic.com/), which allows changes to be reviewed
and approved
- Upload a new build manually using:

```sh
npx chromatic --project-token
```

- Chromatic also publishes Storybook
[here](https://main--63c678edef0d36c489eee3c1.chromatic.com)

**A note on code coverage**: when the component is exported from Storybook,
coverage of the component itself will not be tracked correctly. For this reason,
minimum coverage requirements are not enabled.

### Deployment

- Initialize Netlify CLI:

```sh
npx netlify init
```

or

```sh
pnpm init:netlify
```

- Build locally:

```sh
pnpm build:netlify
```

- Deploy to preview server:

```sh
pnpm run deploy
```

Note that any [Pull
Request](https://github.com/Tim-W-James/timjames.dev/pulls) will automatically
be deployed to a [Netlify preview
server](https://app.netlify.com/sites/timjames/deploys?filter=deploy+previews).

- Deploy to production: continuous deployment to
[Netlify](https://app.netlify.com/sites/timjames/deploys?filter=main) domain
[timjames.netlify.app](https://timjames.netlify.app) on the
[`main`](https://github.com/Tim-W-James/timjames.dev/branches) branch.

### Code Style

- Evaluate ESLint ([`.eslintrc.cjs`](./.eslintrc.cjs)) and StyleLint
([`.stylelintrc.cjs`](./.stylelintrc.cjs)) rules against source code:

```sh
pnpm lint
```

- Format source code with prettier ([`.prettierrc.cjs`](./.prettierrc.cjs))
and try to fix any ESLint ([`.eslintrc.cjs`](./.eslintrc.cjs)) or StyleLint
([`.stylelintrc.cjs`](./.stylelintrc.cjs)) errors:

```sh
pnpm format
```

### Styling

This repo takes a [utility-first](https://tailwindcss.com/docs/utility-first)
approach to styling. HTML tags (and React components) describe semantics (e.g.,
header, section). Where possible, CSS classes should instead describe utilities
(e.g., typography, layout).

When applying styles, consider the following:

1. Before adding CSS classes, consider whether the default HTML styles from the
[base
styles](https://tailwindcss.com/docs/adding-custom-styles#adding-base-styles)
in [`main.scss`](./src/styles/main.scss) are sufficient
2. Style the component with existing Tailwind classes
3. If existing Tailwind classes need to be customized (e.g., font-family),
configure the
[theme](https://tailwindcss.com/docs/adding-custom-styles#customizing-your-theme)
in [`tailwind.config.cjs`](./tailwind.config.cjs)
4. If there is no existing Tailwind class for the use case, **and** the style is
deemed to be reusable:
1. For a CSS feature Tailwind doesn’t support, create a [custom
utility](https://tailwindcss.com/docs/adding-custom-styles#adding-custom-utilities)
in [`main.scss`](./src/styles/main.scss)
2. For complex classes (i.e., more than 1 CSS property), semantic classes
(e.g., card, btn) can be created via [component
classes](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes)
in [`main.scss`](./src/styles/main.scss).
However, be careful to avoid [hasty
abstractions](https://tailwindcss.com/docs/reusing-styles#avoiding-premature-abstraction)
5. If the style is not deemed to be reusable, and custom CSS needs to be used,
create an SCSS module in the same directory as the parent component

#### Typed CSS

Avoid applying classes directly to a component, as this does not provide type
safety. Instead, use a custom utility function. For example:

```tsx
import cn from "@styles/cssUtils";
...


...
```

This works by generating CSS class names for any Tailwind or global classes from
the compiled CSS, and generating a union type in
[`cssClasses.d.ts`](./src/styles/cssClasses.d.ts). This is
automatically generated during development (run `pnpm dev`). Note that unused
Tailwind classes are purged and excluded from the type definition, so when
adding a new class your IDE will complain momentarily until the file is saved
and types are re-generated.

Due to a limitation with how TypeScript infers template literals, if a class
exists which is a prefix, it will break other classes that use that prefix.
For example: `flex-col` will be marked as invalid because `flex` is a class.
As a workaround, you can pass these classes as separate parameters:

```ts
cn("flex p-5", "flex-col", "flex-wrap");
```

To use scoped **SCSS modules** with type safety, separate
`./...module.scss.d.ts` files are generated in the directory of it's parent
component. This can be used with the `cnScoped` function, for example:

```tsx
import { cnScoped } from "@styles/cssUtils";
import styles from "./component.module.scss";
...


...
```

Some things to note:

- Classes in SCSS modules are named with a `_` prefix and are lowerCamelCase
- Using the `styles` import is required for the module to be compiled and avoid
name collisions, and gives intellisense
- The `ClassNames` type provides `cnScoped` with a union of all class names in
that SCSS module, so that it knows what valid classes are in scope. Note the
extra `()` since the function needs to by curried.

### Project Structure

- **Source Code**: [`src`](./src)
- Entry point and routes: [`index.tsx`](./src/index.tsx)
- Root component: [`App.tsx`](./src/App.tsx)
- Common components: [`components`](./src/components/). Has alias
`@components`. Group by type for `layout`, `buttons`, `forms`, etc.
- Common hooks: [`hooks`](./src/hooks/). Has alias `@hooks`
- Common utils: [`utils`](./src/utils/). Has alias `@utils`
- Common API functionality: [`services`](./src/services/). Has alias
`@services`
- Pages: [`pages`](./src/pages/). Has alias `@pages`
- Feature specific code: [`features`](./src/features/). Has alias `@features`.
Nest subfolders for `components`, `utils`, `hooks`, etc. depending on the
scope they apply to
- Root Redux State: [`app`](./src/app/)
- Context and Redux Slices: [`context`](./src/context/). Has alias `@context`
- Constants: [`constants`](./src/constants/). Has alias `@constants`
- Data: [`data`](./src/data/). Has alias `@data`
- **Unit Tests**: place `tests` adjacent to source code. Mock data goes in
[`src/mocks`](src/mocks/)
- **Storybook Stories**: place `stories` adjacent to source code. Config in
[`.storybook`](./.storybook/)
- **End-to-End Tests**: [`e2e`](./e2e)
- **CSS Styling**:
- Use [`main.scss`](./src/styles/main.scss) for base styles and custom
Tailwind
- Put global SCSS variables and mixins in [`src/styles`](./src/styles). These
are automatically imported via [`vite.config.ts`](./vite.config.ts) ->
`preprocessorOptions`
- Place page or component specific styles adjacent to source code, using
scoped `.modules`
- **Global TypeScript Types**: [`types`](./types)
- **Web Accessible Files** (`robots.txt`, `manifest.json`, etc.):
[`public`](./public)
- **Site Assets** (`favicon.ico`, images, etc.):
[`public/assets`](./public/assets). Has alias `@assets`

Define path alias in [`tsconfig.paths.json`](./tsconfig.paths.json).

I recommend using [VSCode file
nesting](https://code.visualstudio.com/updates/v1_64#_explorer-file-nesting) for
a [cleaner file tree](https://github.com/antfu/vscode-file-nesting-config).

### Documentation

- Document code with [JSDoc](https://jsdoc.app/about-getting-started.html)
- Document components or pages with [Storybook](https://storybook.js.org/) and
run with:

```sh
pnpm storybook
```

Storybook is automatically deployed to [Github
Pages](https://storybook.timjames.dev)

## License

Distributed under the MIT License. See [`LICENSE.txt`](./LICENSE.txt) for more
information.

## Contact

Email: [[email protected]](mailto:[email protected] "[email protected]")

Project Link:
[https://github.com/Tim-W-James/timjames.dev](https://github.com/Tim-W-James/timjames.dev)

↑ Back to Top ↑