create-react-adam 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -20
- package/bin/index.js +263 -100
- package/package.json +8 -3
- package/template/.github/workflows/check.yml +1 -1
- package/template/.github/workflows/e2e.yml +3 -3
- package/template/.github/workflows/lighthouse.yml +33 -0
- package/template/.nvmrc +1 -1
- package/template/README.md +174 -31
- package/template/e2e/gitignore +13 -0
- package/template/e2e/package.json +2 -2
- package/template/eslint-rules/prefer-webp-images.js +66 -0
- package/template/eslint.config.js +3 -0
- package/template/eslint.config.no-webp.js +54 -0
- package/template/index.html +19 -0
- package/template/lighthouserc.json +17 -0
- package/template/npmrc +2 -0
- package/template/package.json +18 -18
- package/template/public/fonts/InterVariable.woff2 +0 -0
- package/template/public/robots.txt +5 -0
- package/template/src/App.tsx +48 -14
- package/template/src/app.css +27 -9
- package/template/src/components/Button.tsx +27 -0
- package/template/src/components/PreloadLink.tsx +49 -0
- package/template/src/pages/About/index.no-utils.tsx +8 -3
- package/template/src/pages/About/index.tsx +25 -15
- package/template/src/pages/Home/index.no-utils.tsx +40 -4
- package/template/src/pages/Home/index.tsx +25 -15
- package/template/src/pages/NotFound/index.tsx +26 -19
- package/template/src/routes.ts +94 -0
- package/template/src/types/ui.ts +1 -0
- package/template/src/utils/classNames.ts +23 -2
- package/template/src/utils/helpers.ts +22 -0
- package/template/src/utils/useUrlState.ts +17 -4
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
- name: Setup Node.js
|
|
18
18
|
uses: actions/setup-node@v6
|
|
19
19
|
with:
|
|
20
|
-
node-version: "24.
|
|
20
|
+
node-version: "24.18.0"
|
|
21
21
|
cache: "npm"
|
|
22
22
|
|
|
23
23
|
- name: Install dependencies
|
|
@@ -36,7 +36,7 @@ jobs:
|
|
|
36
36
|
run: npm run test:e2e
|
|
37
37
|
|
|
38
38
|
- name: Upload Playwright report
|
|
39
|
-
uses: actions/upload-artifact@
|
|
39
|
+
uses: actions/upload-artifact@v6
|
|
40
40
|
if: always()
|
|
41
41
|
with:
|
|
42
42
|
name: playwright-report
|
|
@@ -44,7 +44,7 @@ jobs:
|
|
|
44
44
|
retention-days: 30
|
|
45
45
|
|
|
46
46
|
- name: Upload Allure results
|
|
47
|
-
uses: actions/upload-artifact@
|
|
47
|
+
uses: actions/upload-artifact@v6
|
|
48
48
|
if: always()
|
|
49
49
|
with:
|
|
50
50
|
name: allure-results
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Lighthouse CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
lighthouse:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v6
|
|
16
|
+
|
|
17
|
+
- name: Setup Node.js
|
|
18
|
+
uses: actions/setup-node@v6
|
|
19
|
+
with:
|
|
20
|
+
node-version: "24.18.0"
|
|
21
|
+
cache: "npm"
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: npm install
|
|
25
|
+
|
|
26
|
+
- name: Build
|
|
27
|
+
run: npm run build
|
|
28
|
+
|
|
29
|
+
- name: Run Lighthouse CI
|
|
30
|
+
uses: treosh/lighthouse-ci-action@v12
|
|
31
|
+
with:
|
|
32
|
+
configPath: ./lighthouserc.json
|
|
33
|
+
uploadArtifacts: true
|
package/template/.nvmrc
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
24.
|
|
1
|
+
24.18.0
|
|
2
2
|
|
package/template/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
2
|
|
|
3
|
-
A React application created with create-react-adam.
|
|
3
|
+
A React application created with [create-react-adam](https://github.com/adamjsturge/create-react-adam).
|
|
4
4
|
|
|
5
5
|
## Getting Started
|
|
6
6
|
|
|
@@ -12,14 +12,6 @@ This will start the development server at `http://localhost:5173`.
|
|
|
12
12
|
|
|
13
13
|
## Available Scripts
|
|
14
14
|
|
|
15
|
-
### `npm run itDoesNotWork`
|
|
16
|
-
|
|
17
|
-
Runs basic troubleshooting checks:
|
|
18
|
-
|
|
19
|
-
- Installs dependencies
|
|
20
|
-
- Checks if Vite dev server is running
|
|
21
|
-
- Provides helpful diagnostic information
|
|
22
|
-
|
|
23
15
|
### `npm run dev`
|
|
24
16
|
|
|
25
17
|
Starts the development server with hot module replacement.
|
|
@@ -32,49 +24,200 @@ Builds the app for production. The build artifacts will be stored in the `dist/`
|
|
|
32
24
|
|
|
33
25
|
Previews the production build locally.
|
|
34
26
|
|
|
35
|
-
### `npm run lint`
|
|
27
|
+
### `npm run lint` / `npm run lint:check`
|
|
28
|
+
|
|
29
|
+
Runs ESLint with auto-fix / check-only (for CI).
|
|
30
|
+
|
|
31
|
+
### `npm run format` / `npm run format:check`
|
|
32
|
+
|
|
33
|
+
Formats all files with Prettier (organizes imports, sorts Tailwind classes) / check-only (for CI).
|
|
34
|
+
|
|
35
|
+
### `npm run setFormatToPrecommitHook`
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Installs a git pre-commit hook that formats and lints staged files. If this
|
|
38
|
+
project was scaffolded outside an existing repository, the hook was already
|
|
39
|
+
installed for you.
|
|
38
40
|
|
|
39
|
-
### `npm run
|
|
41
|
+
### `npm run test:e2e` (if E2E was included)
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
Runs Playwright E2E tests from the `e2e/` directory. `test:e2e:ui` opens
|
|
44
|
+
Playwright UI mode and `test:e2e:report` generates an Allure report.
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
### `npm run itDoesNotWork`
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- **Vite** - Build tool and dev server
|
|
48
|
-
- **Wouter** - Lightweight routing
|
|
49
|
-
- **Tailwind CSS** - Utility-first CSS framework
|
|
50
|
-
- **ESLint & Prettier** - Code quality and formatting
|
|
48
|
+
Runs basic troubleshooting checks: installs dependencies, checks whether the
|
|
49
|
+
Vite dev server is running, and prints diagnostic information.
|
|
51
50
|
|
|
52
51
|
## Project Structure
|
|
53
52
|
|
|
54
53
|
```
|
|
55
54
|
src/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
├── pages/ # Route pages (Home, About, NotFound)
|
|
56
|
+
├── components/ # Reusable components (Button, PreloadLink)
|
|
57
|
+
├── types/ # Shared TypeScript types
|
|
58
|
+
├── utils/ # Utility functions (if included)
|
|
59
|
+
├── App.tsx # Routing, skip link, <main> landmark
|
|
60
|
+
├── routes.ts # Route import map + preload helpers
|
|
61
|
+
├── main.tsx # Entry point
|
|
62
|
+
└── app.css # Tailwind theme + base styles
|
|
62
63
|
```
|
|
63
64
|
|
|
65
|
+
Routing uses [Wouter](https://github.com/molefrog/wouter). Pages are
|
|
66
|
+
code-split with `React.lazy`, and each page sets its own `document.title`.
|
|
67
|
+
|
|
64
68
|
## Adding New Pages
|
|
65
69
|
|
|
66
70
|
1. Create a new component in `src/pages/`
|
|
67
|
-
2.
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
2. Add its import thunk to `src/routes.ts` (this both registers it for
|
|
72
|
+
preloading and gives `App.tsx` its lazy import)
|
|
73
|
+
3. Add a lazy component and route in `src/App.tsx`
|
|
70
74
|
|
|
71
75
|
```tsx
|
|
72
|
-
|
|
76
|
+
// src/routes.ts
|
|
77
|
+
export const routeImports: Record<string, PageLoader> = {
|
|
78
|
+
// ...
|
|
79
|
+
"/new": () => import("./pages/NewPage"),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/App.tsx
|
|
83
|
+
const NewPage = lazy(routeImports["/new"]);
|
|
73
84
|
|
|
74
85
|
// In the Switch component:
|
|
75
86
|
<Route path="/new" component={NewPage} />;
|
|
76
87
|
```
|
|
77
88
|
|
|
89
|
+
4. Link to it with `<PreloadLink href="/new">` so the chunk preloads on
|
|
90
|
+
hover/focus/touch (wouter's plain `<Link>` works too, just without the
|
|
91
|
+
preload).
|
|
92
|
+
|
|
93
|
+
## Route Preloading
|
|
94
|
+
|
|
95
|
+
Pages are lazy-loaded, so without preloading every navigation waits for a
|
|
96
|
+
network round trip. The template closes that gap in three layers:
|
|
97
|
+
|
|
98
|
+
1. **One import map** (`src/routes.ts`): the router and the preloaders share
|
|
99
|
+
the same import thunks, and dynamic imports dedupe by module — so a
|
|
100
|
+
preloaded chunk is never downloaded twice.
|
|
101
|
+
2. **Preload on intent** (`src/components/PreloadLink.tsx`): links fire the
|
|
102
|
+
target page's import on hover, focus, or touch. Hover-to-click is
|
|
103
|
+
typically 200-400ms — usually enough for the chunk to arrive first.
|
|
104
|
+
3. **Idle backstop** (`preloadAllRoutesWhenIdle` in `src/routes.ts`, called
|
|
105
|
+
from `App.tsx`): after the window `load` event, remaining chunks are
|
|
106
|
+
fetched one at a time during idle periods. It never competes with
|
|
107
|
+
first-paint resources (so Lighthouse is unaffected) and skips users on
|
|
108
|
+
data-saver or 2G connections.
|
|
109
|
+
|
|
110
|
+
`App.tsx` also routes against a `useDeferredValue`-deferred location, so the
|
|
111
|
+
previous page stays painted until the next page is ready instead of flashing
|
|
112
|
+
the blank `Suspense` fallback. Preloading makes navigation fast; the deferred
|
|
113
|
+
location makes it smooth.
|
|
114
|
+
|
|
115
|
+
**To remove preloading**, follow the steps in the header comment of
|
|
116
|
+
`src/routes.ts` — in short: inline the imports back into `lazy()` in
|
|
117
|
+
`App.tsx`, swap `<PreloadLink>` back to wouter's `<Link>`, and delete
|
|
118
|
+
`src/routes.ts` and `src/components/PreloadLink.tsx`.
|
|
119
|
+
|
|
120
|
+
## Performance, SEO, and Accessibility
|
|
121
|
+
|
|
122
|
+
- **Fonts**: Inter is self-hosted from `public/fonts/` (variable `woff2`,
|
|
123
|
+
`font-display: swap`, preloaded in `index.html`). No third-party requests.
|
|
124
|
+
- **SEO**: `index.html` ships meta description, Open Graph, and Twitter card
|
|
125
|
+
tags — update them as your app takes shape. `public/robots.txt` is included.
|
|
126
|
+
- **Accessibility**: a skip-to-content link, `<main>` landmark, and visible
|
|
127
|
+
`:focus-visible` styles are wired into `App.tsx` / `app.css`.
|
|
128
|
+
|
|
129
|
+
## ESLint: prefer-webp-images (if included)
|
|
130
|
+
|
|
131
|
+
A local rule in `eslint-rules/prefer-webp-images.js` errors on references to
|
|
132
|
+
`.png`, `.jpg`, `.jpeg`, `.gif`, or `.bmp` images in imports, JSX attributes,
|
|
133
|
+
and string literals. Convert images to WebP at
|
|
134
|
+
[tools.sturge.dev/webp](https://tools.sturge.dev/webp).
|
|
135
|
+
|
|
136
|
+
For the rare image that must stay in another format:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
// eslint-disable-next-line adamjsturge/prefer-webp-images
|
|
140
|
+
import legacyLogo from "./legacy-logo.png";
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Utilities (if included)
|
|
144
|
+
|
|
145
|
+
### `classNames`
|
|
146
|
+
|
|
147
|
+
Joins class names, skipping falsy values, with clsx-style conditional objects:
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import classNames from "./utils/classNames";
|
|
151
|
+
|
|
152
|
+
classNames("btn", isActive && "btn-active", { hidden: !isOpen });
|
|
153
|
+
// => "btn btn-active hidden" depending on the conditions
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### `storage` and `useReactPersist`
|
|
157
|
+
|
|
158
|
+
`localStorage` wrapper with JSON serialization and optional expiry, plus a
|
|
159
|
+
`useState`-compatible hook that persists across reloads:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
import storage, { useReactPersist } from "./utils/Storage";
|
|
163
|
+
|
|
164
|
+
storage.save("token", "abc123", { secondsTillExpiry: 3600 });
|
|
165
|
+
storage.load("token", null);
|
|
166
|
+
|
|
167
|
+
const [count, setCount] = useReactPersist("counter", 0);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### `useUrlState`
|
|
171
|
+
|
|
172
|
+
`useState`-compatible hook that syncs a value with a URL query parameter.
|
|
173
|
+
Supports string and number values (the type follows the default value):
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
import { useUrlState } from "./utils/useUrlState";
|
|
177
|
+
|
|
178
|
+
const [page, setPage] = useUrlState("page", 1);
|
|
179
|
+
const [tab, setTab] = useUrlState("tab", "overview");
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `useInternetConnected`
|
|
183
|
+
|
|
184
|
+
Runs a callback when the browser is online and every time the connection
|
|
185
|
+
comes back:
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
import { useInternetConnected } from "./utils/Internet";
|
|
189
|
+
|
|
190
|
+
useInternetConnected(() => refetch(), [refetch]);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### `safeTimeout`
|
|
194
|
+
|
|
195
|
+
Drop-in `setTimeout` replacement that clamps delays above the 32-bit signed
|
|
196
|
+
integer maximum (~24.8 days) instead of letting them overflow and fire
|
|
197
|
+
immediately:
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import { safeTimeout } from "./utils/helpers";
|
|
201
|
+
|
|
202
|
+
safeTimeout(() => expireSession(), thirtyDaysInMs);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## CI Workflows
|
|
206
|
+
|
|
207
|
+
- **Code Checks** (`.github/workflows/check.yml`): type checking, formatting,
|
|
208
|
+
linting, and a production build on every push and PR to `main`.
|
|
209
|
+
- **E2E Tests** (`.github/workflows/e2e.yml`, if included): Playwright tests
|
|
210
|
+
with report artifacts.
|
|
211
|
+
- **Lighthouse CI** (`.github/workflows/lighthouse.yml`, if included): audits
|
|
212
|
+
the production build and fails below accessibility ≥ 95, SEO ≥ 95, and
|
|
213
|
+
performance ≥ 85. Budgets live in `lighthouserc.json`.
|
|
214
|
+
|
|
215
|
+
## Dependency Management
|
|
216
|
+
|
|
217
|
+
Dependencies are pinned to exact versions and `.npmrc` sets
|
|
218
|
+
`save-exact=true`, so future installs stay pinned. The repo is
|
|
219
|
+
Renovate-friendly.
|
|
220
|
+
|
|
78
221
|
## Learn More
|
|
79
222
|
|
|
80
223
|
- [React Documentation](https://react.dev)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const NON_WEBP_IMAGE_PATTERN = /\.(png|jpe?g|gif|bmp)$/i;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Flags references to non-WebP raster images (.png, .jpg, .jpeg, .gif, .bmp)
|
|
5
|
+
* in imports, JSX attributes, and string literals. WebP/AVIF/SVG are allowed.
|
|
6
|
+
*/
|
|
7
|
+
function findNonWebpImage(value) {
|
|
8
|
+
if (typeof value !== "string") {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Handle srcSet-style values ("a.png 1x, b.png 2x") and Vite suffixes
|
|
13
|
+
// ("./a.png?url") by checking each path-like token.
|
|
14
|
+
for (const token of value.split(/[\s,]+/)) {
|
|
15
|
+
const path = token.split(/[?#]/)[0];
|
|
16
|
+
if (NON_WEBP_IMAGE_PATTERN.test(path)) {
|
|
17
|
+
return token;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const preferWebpImages = {
|
|
25
|
+
meta: {
|
|
26
|
+
type: "suggestion",
|
|
27
|
+
docs: {
|
|
28
|
+
description:
|
|
29
|
+
"Prefer WebP images over PNG/JPEG/GIF/BMP for smaller file sizes and faster page loads",
|
|
30
|
+
},
|
|
31
|
+
messages: {
|
|
32
|
+
preferWebp:
|
|
33
|
+
'Image "{{path}}" is not WebP. Convert it at https://tools.sturge.dev/webp',
|
|
34
|
+
},
|
|
35
|
+
schema: [],
|
|
36
|
+
},
|
|
37
|
+
create(context) {
|
|
38
|
+
function check(node, value) {
|
|
39
|
+
const match = findNonWebpImage(value);
|
|
40
|
+
if (match) {
|
|
41
|
+
context.report({
|
|
42
|
+
node,
|
|
43
|
+
messageId: "preferWebp",
|
|
44
|
+
data: { path: match },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
Literal(node) {
|
|
51
|
+
check(node, node.value);
|
|
52
|
+
},
|
|
53
|
+
TemplateLiteral(node) {
|
|
54
|
+
if (node.quasis.length === 1) {
|
|
55
|
+
check(node, node.quasis[0].value.cooked);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default {
|
|
63
|
+
rules: {
|
|
64
|
+
"prefer-webp-images": preferWebpImages,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
@@ -8,6 +8,7 @@ import reactHooks from "eslint-plugin-react-hooks";
|
|
|
8
8
|
import reactRefresh from "eslint-plugin-react-refresh";
|
|
9
9
|
import unicorn from "eslint-plugin-unicorn";
|
|
10
10
|
import tseslint from "typescript-eslint";
|
|
11
|
+
import adamjsturge from "./eslint-rules/prefer-webp-images.js";
|
|
11
12
|
|
|
12
13
|
export default tseslint.config({
|
|
13
14
|
extends: [
|
|
@@ -25,6 +26,7 @@ export default tseslint.config({
|
|
|
25
26
|
"jsx-a11y": jsxA11y,
|
|
26
27
|
"react-hooks": reactHooks,
|
|
27
28
|
"react-refresh": reactRefresh,
|
|
29
|
+
adamjsturge,
|
|
28
30
|
},
|
|
29
31
|
languageOptions: {
|
|
30
32
|
parserOptions: {
|
|
@@ -48,6 +50,7 @@ export default tseslint.config({
|
|
|
48
50
|
"warn",
|
|
49
51
|
{ allowConstantExport: true },
|
|
50
52
|
],
|
|
53
|
+
"adamjsturge/prefer-webp-images": "error",
|
|
51
54
|
},
|
|
52
55
|
files: ["**/*.{ts,tsx}"],
|
|
53
56
|
ignores: ["dist"],
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import eslint from "@eslint/js";
|
|
2
|
+
import prettier from "eslint-config-prettier";
|
|
3
|
+
import deMorgan from "eslint-plugin-de-morgan";
|
|
4
|
+
import importPlugin from "eslint-plugin-import";
|
|
5
|
+
import jsxA11y from "eslint-plugin-jsx-a11y";
|
|
6
|
+
import promisePlugin from "eslint-plugin-promise";
|
|
7
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
8
|
+
import reactRefresh from "eslint-plugin-react-refresh";
|
|
9
|
+
import unicorn from "eslint-plugin-unicorn";
|
|
10
|
+
import tseslint from "typescript-eslint";
|
|
11
|
+
|
|
12
|
+
export default tseslint.config({
|
|
13
|
+
extends: [
|
|
14
|
+
eslint.configs.recommended,
|
|
15
|
+
...tseslint.configs.recommended,
|
|
16
|
+
...tseslint.configs.strict,
|
|
17
|
+
prettier,
|
|
18
|
+
promisePlugin.configs["flat/recommended"],
|
|
19
|
+
importPlugin.flatConfigs.recommended,
|
|
20
|
+
importPlugin.flatConfigs.typescript,
|
|
21
|
+
unicorn.configs["unopinionated"],
|
|
22
|
+
deMorgan.configs.recommended,
|
|
23
|
+
],
|
|
24
|
+
plugins: {
|
|
25
|
+
"jsx-a11y": jsxA11y,
|
|
26
|
+
"react-hooks": reactHooks,
|
|
27
|
+
"react-refresh": reactRefresh,
|
|
28
|
+
},
|
|
29
|
+
languageOptions: {
|
|
30
|
+
parserOptions: {
|
|
31
|
+
ecmaFeatures: {
|
|
32
|
+
jsx: true,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
rules: {
|
|
37
|
+
"@typescript-eslint/no-unused-vars": [
|
|
38
|
+
"error",
|
|
39
|
+
{
|
|
40
|
+
argsIgnorePattern: "^_",
|
|
41
|
+
varsIgnorePattern: "^_",
|
|
42
|
+
caughtErrorsIgnorePattern: "^_",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
...jsxA11y.flatConfigs.strict.rules,
|
|
46
|
+
...reactHooks.configs.recommended.rules,
|
|
47
|
+
"react-refresh/only-export-components": [
|
|
48
|
+
"warn",
|
|
49
|
+
{ allowConstantExport: true },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
files: ["**/*.{ts,tsx}"],
|
|
53
|
+
ignores: ["dist"],
|
|
54
|
+
});
|
package/template/index.html
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta
|
|
7
|
+
name="description"
|
|
8
|
+
content="__PROJECT_NAME__ — a React app built with create-react-adam"
|
|
9
|
+
/>
|
|
10
|
+
<meta name="theme-color" content="#2563eb" />
|
|
11
|
+
<meta property="og:title" content="__PROJECT_NAME__" />
|
|
12
|
+
<meta
|
|
13
|
+
property="og:description"
|
|
14
|
+
content="__PROJECT_NAME__ — a React app built with create-react-adam"
|
|
15
|
+
/>
|
|
16
|
+
<meta property="og:type" content="website" />
|
|
17
|
+
<meta name="twitter:card" content="summary" />
|
|
18
|
+
<link
|
|
19
|
+
rel="preload"
|
|
20
|
+
href="/fonts/InterVariable.woff2"
|
|
21
|
+
as="font"
|
|
22
|
+
type="font/woff2"
|
|
23
|
+
crossorigin
|
|
24
|
+
/>
|
|
6
25
|
<title>__PROJECT_NAME__</title>
|
|
7
26
|
</head>
|
|
8
27
|
<body>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ci": {
|
|
3
|
+
"collect": {
|
|
4
|
+
"startServerCommand": "npm run preview",
|
|
5
|
+
"startServerReadyPattern": "Local",
|
|
6
|
+
"url": ["http://localhost:4173/"],
|
|
7
|
+
"numberOfRuns": 3
|
|
8
|
+
},
|
|
9
|
+
"assert": {
|
|
10
|
+
"assertions": {
|
|
11
|
+
"categories:accessibility": ["error", { "minScore": 0.95 }],
|
|
12
|
+
"categories:seo": ["error", { "minScore": 0.95 }],
|
|
13
|
+
"categories:performance": ["error", { "minScore": 0.85 }]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
package/template/npmrc
ADDED
package/template/package.json
CHANGED
|
@@ -19,32 +19,32 @@
|
|
|
19
19
|
"test:e2e:report": "cd e2e && allure generate ./allure-results --clean && allure open"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"react": "19.2.
|
|
23
|
-
"react-dom": "19.2.
|
|
24
|
-
"react-icons": "5.
|
|
25
|
-
"wouter": "3.
|
|
22
|
+
"react": "19.2.7",
|
|
23
|
+
"react-dom": "19.2.7",
|
|
24
|
+
"react-icons": "5.7.0",
|
|
25
|
+
"wouter": "3.10.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@eslint/js": "9.39.
|
|
29
|
-
"@tailwindcss/vite": "4.
|
|
30
|
-
"@types/react": "19.2.
|
|
28
|
+
"@eslint/js": "9.39.4",
|
|
29
|
+
"@tailwindcss/vite": "4.3.2",
|
|
30
|
+
"@types/react": "19.2.17",
|
|
31
31
|
"@types/react-dom": "19.2.3",
|
|
32
|
-
"@vitejs/plugin-react": "5.
|
|
33
|
-
"eslint": "9.39.
|
|
32
|
+
"@vitejs/plugin-react": "5.2.0",
|
|
33
|
+
"eslint": "9.39.4",
|
|
34
34
|
"eslint-config-prettier": "10.1.8",
|
|
35
|
-
"eslint-plugin-de-morgan": "2.
|
|
35
|
+
"eslint-plugin-de-morgan": "2.1.2",
|
|
36
36
|
"eslint-plugin-import": "2.32.0",
|
|
37
37
|
"eslint-plugin-jsx-a11y": "6.10.2",
|
|
38
|
-
"eslint-plugin-promise": "7.
|
|
39
|
-
"eslint-plugin-react-hooks": "7.
|
|
40
|
-
"eslint-plugin-react-refresh": "0.
|
|
38
|
+
"eslint-plugin-promise": "7.3.0",
|
|
39
|
+
"eslint-plugin-react-hooks": "7.1.1",
|
|
40
|
+
"eslint-plugin-react-refresh": "0.5.3",
|
|
41
41
|
"eslint-plugin-unicorn": "62.0.0",
|
|
42
|
-
"prettier": "3.
|
|
42
|
+
"prettier": "3.9.5",
|
|
43
43
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
44
|
-
"prettier-plugin-tailwindcss": "0.
|
|
45
|
-
"tailwindcss": "4.
|
|
44
|
+
"prettier-plugin-tailwindcss": "0.8.0",
|
|
45
|
+
"tailwindcss": "4.3.2",
|
|
46
46
|
"typescript": "5.9.3",
|
|
47
|
-
"typescript-eslint": "8.
|
|
48
|
-
"vite": "7.
|
|
47
|
+
"typescript-eslint": "8.63.0",
|
|
48
|
+
"vite": "7.3.6"
|
|
49
49
|
}
|
|
50
50
|
}
|
|
Binary file
|
package/template/src/App.tsx
CHANGED
|
@@ -1,17 +1,51 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import NotFound from "./pages/NotFound";
|
|
1
|
+
import { lazy, Suspense, useDeferredValue, useEffect } from "react";
|
|
2
|
+
import { Redirect, Route, Switch, useLocation } from "wouter";
|
|
3
|
+
import { preloadAllRoutesWhenIdle, routeImports } from "./routes";
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
// Pages are code-split: each chunk downloads on demand. The import thunks
|
|
6
|
+
// live in src/routes.ts so the preload helpers can warm the same chunks
|
|
7
|
+
// ahead of navigation (routes.ts documents how it works and how to remove).
|
|
8
|
+
const Home = lazy(routeImports["/"]);
|
|
9
|
+
const About = lazy(routeImports["/about"]);
|
|
10
|
+
const NotFound = lazy(routeImports["/404"]);
|
|
11
|
+
|
|
12
|
+
const App = () => {
|
|
13
|
+
const [location] = useLocation();
|
|
14
|
+
// Routing against a deferred location means React keeps the previous page
|
|
15
|
+
// painted while the next page's chunk loads and initializes, instead of
|
|
16
|
+
// flashing the blank Suspense fallback. Preloading makes navigation fast;
|
|
17
|
+
// this makes it smooth. To remove: pass nothing to <Switch> below and
|
|
18
|
+
// delete these two hook calls.
|
|
19
|
+
const deferredLocation = useDeferredValue(location);
|
|
20
|
+
|
|
21
|
+
// Idle backstop — quietly fetch all remaining page chunks once the page
|
|
22
|
+
// has fully loaded. Delete this effect to remove (see src/routes.ts).
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
preloadAllRoutesWhenIdle();
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<a
|
|
30
|
+
href="#main-content"
|
|
31
|
+
className="bg-brand-primary text-brand-white sr-only z-50 rounded-lg px-4 py-2 focus:not-sr-only focus:fixed focus:top-4 focus:left-4"
|
|
32
|
+
>
|
|
33
|
+
Skip to main content
|
|
34
|
+
</a>
|
|
35
|
+
<main id="main-content">
|
|
36
|
+
<Suspense fallback={null}>
|
|
37
|
+
<Switch location={deferredLocation}>
|
|
38
|
+
<Route path="/" component={Home} />
|
|
39
|
+
<Route path="/about" component={About} />
|
|
40
|
+
<Route path="/home">
|
|
41
|
+
<Redirect to="/" />
|
|
42
|
+
</Route>
|
|
43
|
+
<Route component={NotFound} />
|
|
44
|
+
</Switch>
|
|
45
|
+
</Suspense>
|
|
46
|
+
</main>
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
16
50
|
|
|
17
51
|
export default App;
|