Personal site for Tim W James - Portfolio, Blog, and more
Deployed to timjames.dev 🌐
Table of Contents
- Portfolio: display and demo past projects
- Blog: display blog posts from 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.
Design:
- :
design tool. View the design
online
or the exported file
Portfolio.fig
Development:
- : frontend framework
- : state management for React
- :
frontend build tool and dev server. Configured in
vite.config.ts
- :
types for js. Configured in
tsconfig.json
- : CSS preprocessor. This repo uses a custom setup to auto-generate scoped type definitions for CSS classes, go here for further details
- : Utility-first CSS framework. This repo uses a custom setup to auto-generate type definitions for Tailwind classes, go here for further details
- :
Linter/code analyzer for TypeScript. Configured in
.eslintrc.cjs
with rules from AirBnB and SonarJS - :
Linter/code analyzer for SCSS. Configured in
.stylelintrc.cjs
- :
Formatter. Configured in
.prettierrc.cjs
- :
unit testing framework. Configured in
vite.config.ts
>test
- :
view, document and test individual components and pages. Configured in
.storybook/main.cjs
. Automatically deployed to Github Pages - :
end-to-end tests. Configured in
playwright.config.ts
and located ine2e/
- :
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 - :
pre-commit Git hooks to lint, format and run tests. Configured in
.husky
- :
GitHub bot for automatic dependency updates. Configured in
renovate.json
- :
GitHub CI/CD pipeline. Used to ensure builds, linting rules and tests pass for
any Pull Request against
the
main
branch. Configured in.github/workflows
Deployment:
- :
Hosting, CDN, NS and continuous deployment for
timjames.dev. Netlify domain:
timjames.netlify.app - go here
for details. Configured in
netlify.toml
- :
Domain Registry for timjames.dev using the
.dev
TLD - : TLS/SSL Certificate Authority for timjames.dev
-
Install
node
for the version in.nvmrc
or usenvm
:nvm install && nvm use
-
Install the
pnpm
package manager. One option iscorepack
for automatic installation, which is an experimentalnode
feature that must be enabled using:corepack enable
-
Clone the repo:
git clone https://github.com/Tim-W-James/timjames.dev.git
-
Install dependencies with
pnpm
:pnpm i
-
Create a
.env
file and specify the following:# Get the reCAPTCHA key from https://www.google.com/recaptcha/admin/site/599894418 VITE_SITE_RECAPTCHA_KEY=123 STORYBOOK_SITE_RECAPTCHA_KEY=123
-
Build to
dist
and preview:pnpm build pnpm preview
-
Start a development environment:
pnpm dev
-
Run unit tests in watch mode (automatically reruns tests when source code changes):
pnpm test
-
Run coverage tests and output results to
coverage
:pnpm coverage
-
View individual components or pages and run interaction tests:
pnpm storybook
-
Run Storybook tests to ensure all stories render and interaction tests pass (requires Storybook to be running, or use
:ci
):pnpm storybook:test
-
Run End-to-End tests with Playwright (
:headed
to view the tests being executed in the browser) for Firefox, Chromium, and Webkit in both desktop and mobile viewports:pnpm e2e
-
View visual regression tests on Chromatic
This repo has several layers of tests:
-
Unit tests for TypeScript utilities (those in
src/utils
): use Vitest. -
Unit tests for React components:
- First, consider creating a Storybook 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 to render the component from Storybook, then write tests
- Tests should use an arrange-act-assert pattern and follow the React Testing Library query priorities. Testing playground is a useful tool for finding good queries
- Storybook interaction tests 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 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 tests in
e2e/
, with the testing library API -
Visual regression tests:
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.
-
Initialize Netlify CLI:
npx netlify init
or
pnpm init:netlify
-
Build locally:
pnpm build:netlify
-
Deploy to preview server:
pnpm run deploy
Note that any Pull Request will automatically be deployed to a Netlify preview server.
-
Deploy to production: continuous deployment to Netlify domain timjames.netlify.app on the
main
branch.
-
Evaluate ESLint (
.eslintrc.cjs
) and StyleLint (.stylelintrc.cjs
) rules against source code:pnpm lint
-
Format source code with prettier (
.prettierrc.cjs
) and try to fix any ESLint (.eslintrc.cjs
) or StyleLint (.stylelintrc.cjs
) errors:pnpm format
This repo takes a 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:
- Before adding CSS classes, consider whether the default HTML styles from the
base
styles
in
main.scss
are sufficient - Style the component with existing Tailwind classes
- If existing Tailwind classes need to be customized (e.g., font-family),
configure the
theme
in
tailwind.config.cjs
- If there is no existing Tailwind class for the use case, and the style is
deemed to be reusable:
- For a CSS feature Tailwind doesn’t support, create a custom
utility
in
main.scss
- For complex classes (i.e., more than 1 CSS property), semantic classes
(e.g., card, btn) can be created via component
classes
in
main.scss
. However, be careful to avoid hasty abstractions
- For a CSS feature Tailwind doesn’t support, create a custom
utility
in
- 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
Avoid applying classes directly to a component, as this does not provide type safety. Instead, use a custom utility function. For example:
import cn from "@styles/cssUtils";
...
<div className={cn("container p-5", { "text-lg": true })}>
...
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
. 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:
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:
import { cnScoped } from "@styles/cssUtils";
import styles from "./component.module.scss";
...
<div className={cnScoped(styles)(styles._component, "container p-5", {
"text-lg": true,
})}>
...
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 providescnScoped
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.
- Source Code:
src
- Entry point and routes:
index.tsx
- Root component:
App.tsx
- Common components:
components
. Has alias@components
. Group by type forlayout
,buttons
,forms
, etc. - Common hooks:
hooks
. Has alias@hooks
- Common utils:
utils
. Has alias@utils
- Common API functionality:
services
. Has alias@services
- Pages:
pages
. Has alias@pages
- Feature specific code:
features
. Has alias@features
. Nest subfolders forcomponents
,utils
,hooks
, etc. depending on the scope they apply to - Root Redux State:
app
- Context and Redux Slices:
context
. Has alias@context
- Constants:
constants
. Has alias@constants
- Data:
data
. Has alias@data
- Entry point and routes:
- Unit Tests: place
tests
adjacent to source code. Mock data goes insrc/mocks
- Storybook Stories: place
stories
adjacent to source code. Config in.storybook
- End-to-End Tests:
e2e
- CSS Styling:
- Use
main.scss
for base styles and custom Tailwind - Put global SCSS variables and mixins in
src/styles
. These are automatically imported viavite.config.ts
->preprocessorOptions
- Place page or component specific styles adjacent to source code, using
scoped
.modules
- Use
- Global TypeScript Types:
types
- Web Accessible Files (
robots.txt
,manifest.json
, etc.):public
- Site Assets (
favicon.ico
, images, etc.):public/assets
. Has alias@assets
Define path alias in tsconfig.paths.json
.
I recommend using VSCode file nesting for a cleaner file tree.
-
Document code with JSDoc
-
Document components or pages with Storybook and run with:
pnpm storybook
Storybook is automatically deployed to Github Pages
Distributed under the MIT License. See LICENSE.txt
for more
information.
Email: tim.james.work9800@gmail.com
Project Link: https://github.com/Tim-W-James/timjames.dev