@evertondgn/solid-tooling 0.2.0 → 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 +406 -20
- package/dist/has-match-BITlj0EE.mjs +9 -0
- package/dist/knip.d.mts +12 -0
- package/dist/knip.mjs +92 -0
- package/dist/lint-UTfyg7AR.mjs +983 -0
- package/dist/lint.mjs +1 -983
- package/dist/playwright-teardown.d.mts +1 -0
- package/dist/playwright-teardown.mjs +23 -0
- package/dist/playwright.d.mts +18 -0
- package/dist/playwright.mjs +158 -0
- package/dist/stylelint.d.mts +8 -0
- package/dist/stylelint.mjs +180 -0
- package/dist/vite.d.mts +40 -0
- package/dist/vite.mjs +341 -0
- package/dist/vitest-setup/browser.d.mts +1 -0
- package/dist/vitest-setup/browser.mjs +5 -0
- package/dist/vitest.d.mts +7 -0
- package/dist/vitest.mjs +129 -0
- package/package.json +50 -7
- package/tsconfig.app.json +21 -0
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# @evertondgn/solid-tooling
|
|
2
2
|
|
|
3
|
-
Lint preset, formatter config
|
|
3
|
+
Lint preset, formatter config, TypeScript configs, test configs, the Knip and
|
|
4
|
+
Stylelint configs and the Vite+ application config for SolidJS 2 applications
|
|
4
5
|
built with [Vite+](https://viteplus.dev/guide/).
|
|
5
6
|
|
|
6
|
-
The package contains the lint, format
|
|
7
|
+
The package contains the lint, format, TypeScript, test, build, dead code and
|
|
8
|
+
CSS lint setup of
|
|
7
9
|
[boilerplate_solidjs](https://github.com/everton-dgn/boilerplate_solidjs), a
|
|
8
10
|
SolidJS application template. Applications created from it install the package
|
|
9
11
|
instead of copying the files, so a rule change reaches them through a version
|
|
@@ -24,8 +26,24 @@ exact versions the package is tested with. Install the versions listed in its
|
|
|
24
26
|
`peerDependencies`. `typescript` is not a peer dependency: the package is tested
|
|
25
27
|
with the version in its `devDependencies`.
|
|
26
28
|
|
|
27
|
-
`eslint-plugin-solid`
|
|
28
|
-
declare
|
|
29
|
+
`eslint-plugin-solid`, `stylelint` and `stylelint-use-nesting` come with the
|
|
30
|
+
package, so the application does not declare them.
|
|
31
|
+
|
|
32
|
+
Some entries need tools the application already runs:
|
|
33
|
+
|
|
34
|
+
| Entry | The application installs |
|
|
35
|
+
| -------------- | ------------------------------------------------------------------------------- |
|
|
36
|
+
| `./vitest` | `@vitest/browser-playwright`, `@vitest/coverage-v8`, `happy-dom`, `playwright` |
|
|
37
|
+
| `./playwright` | `@playwright/test`, an optional peer dependency pinned like the others |
|
|
38
|
+
| `./knip` | `knip` and `filesystem-routing`, optional peer dependencies pinned the same way |
|
|
39
|
+
|
|
40
|
+
`./vite` also needs the test tools of `./vitest`, and loads four plugin
|
|
41
|
+
packages: `@solidjs/vite-plugin`, `nitro`, `filesystem-routing` and
|
|
42
|
+
`unplugin-icons`. They are optional peer dependencies pinned like the others.
|
|
43
|
+
Install the versions listed in `peerDependencies` as direct dependencies of the
|
|
44
|
+
application: Bun applies `overrides` and `patchedDependencies` only from the
|
|
45
|
+
root `package.json`, and the Knip plugin of `unplugin-icons` only turns on with
|
|
46
|
+
a direct dependency.
|
|
29
47
|
|
|
30
48
|
## Install
|
|
31
49
|
|
|
@@ -68,6 +86,323 @@ export default defineConfig({ fmt, lint })
|
|
|
68
86
|
|
|
69
87
|
`vp check`, `vp lint` and `vp fmt` read both configs from `vite.config.ts`.
|
|
70
88
|
|
|
89
|
+
## Tests
|
|
90
|
+
|
|
91
|
+
`solidTest` returns the `test` block of `vite.config.ts`, with three Vitest
|
|
92
|
+
projects:
|
|
93
|
+
|
|
94
|
+
| Project | Files | Environment |
|
|
95
|
+
| --------- | -------------------------------- | ------------------------------------------------ |
|
|
96
|
+
| `node` | `src/**/*.node.test.{ts,tsx}` | Node, with stand-ins for the build-only modules |
|
|
97
|
+
| `dom` | `src/**/*.dom.test.{ts,tsx}` | happy-dom at `http://localhost/en` |
|
|
98
|
+
| `browser` | `src/**/*.browser.test.{ts,tsx}` | Chromium through Playwright, with the CSS loaded |
|
|
99
|
+
|
|
100
|
+
`defineSolidApp` from `./vite` already calls `solidTest`. An application that
|
|
101
|
+
builds its own Vite config calls it directly:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// vite.config.ts
|
|
105
|
+
import { solidTest } from '@evertondgn/solid-tooling/vitest'
|
|
106
|
+
import solid from '@solidjs/vite-plugin'
|
|
107
|
+
import { defineConfig } from 'vite-plus'
|
|
108
|
+
|
|
109
|
+
export default defineConfig({
|
|
110
|
+
test: solidTest({
|
|
111
|
+
root: import.meta.dirname,
|
|
112
|
+
plugins: () => [solid({ serverFunctions: true })]
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`plugins` is a function that returns the Vite plugins the `dom` and `browser`
|
|
118
|
+
projects need to compile components: Solid, routes, icons and i18n. The preset
|
|
119
|
+
calls it once per project, because each project starts its own Vite server and a
|
|
120
|
+
plugin instance keeps the state of one server. The preset also sets the coverage
|
|
121
|
+
thresholds, the reporters and the workers on CI, passes `BASE_URL_TEST`, `HOST`
|
|
122
|
+
and `PORT` to the tests when they are set, and resets the Paraglide locale
|
|
123
|
+
before each browser test.
|
|
124
|
+
|
|
125
|
+
The `node` project does not load the Solid and routing plugins, so a plugin of
|
|
126
|
+
the preset serves `server-only`, `virtual:file-routes` and
|
|
127
|
+
`virtual:solid-server-function-handler`. Knip does not know `server-only`, a
|
|
128
|
+
virtual module of `@solidjs/vite-plugin`: pass it in the `ignoreDependencies` of
|
|
129
|
+
[`defineKnipConfig`](#knip).
|
|
130
|
+
|
|
131
|
+
`definePlaywrightConfig` builds the Playwright config from the application root:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
// playwright.config.ts
|
|
135
|
+
import { definePlaywrightConfig } from '@evertondgn/solid-tooling/playwright'
|
|
136
|
+
|
|
137
|
+
export default definePlaywrightConfig({ root: import.meta.dirname })
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
It reads `BASE_URL_TEST` from `.env.test`, builds the application (`e2e` mode
|
|
141
|
+
for the fixture routes, `production` with `TEST_PRODUCTION_ROUTES=true`), starts
|
|
142
|
+
the preview, and checks the server log for private markers when the suite ends.
|
|
143
|
+
`TEST_PREBUILT=true` reuses an existing production build.
|
|
144
|
+
|
|
145
|
+
| Option | Default | Use |
|
|
146
|
+
| ---------------- | ----------------- | ---------------------------------------------------------------------- |
|
|
147
|
+
| `testDir` | `src/tests/pages` | Folder of the `*.e2e.test.ts` files |
|
|
148
|
+
| `fixtureServers` | none | Extra servers for the fixture routes, as `{ command, url }` |
|
|
149
|
+
| `suites` | none | Globs of serial suites: `serverLogs`, `markdown` and `publicationLoad` |
|
|
150
|
+
|
|
151
|
+
Each serial suite gets its own project: `markdown` runs with one worker,
|
|
152
|
+
`serverLogs` runs last as the teardown of the others, and `publicationLoad` runs
|
|
153
|
+
alone, against two servers, with `TEST_PUBLICATION_LOAD=true`.
|
|
154
|
+
|
|
155
|
+
## Application config
|
|
156
|
+
|
|
157
|
+
`defineSolidApp` returns the whole `vite.config.ts` of a boilerplate
|
|
158
|
+
application: the Solid, routing, icons and Nitro plugins, the headers of the
|
|
159
|
+
static files, the build, CSS, server and preview blocks, the `vp run` tasks, and
|
|
160
|
+
`lint`, `fmt` and `test` from the presets above.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// vite.config.ts
|
|
164
|
+
import { defineSolidApp } from '@evertondgn/solid-tooling/vite'
|
|
165
|
+
|
|
166
|
+
import { SECURITY_HEADERS } from './src/middleware/securityHeaders/constants.ts'
|
|
167
|
+
|
|
168
|
+
export default defineSolidApp({
|
|
169
|
+
root: import.meta.dirname,
|
|
170
|
+
middleware: './src/middleware/index.ts',
|
|
171
|
+
securityHeaders: SECURITY_HEADERS,
|
|
172
|
+
staticPaths: ['/favicon/**']
|
|
173
|
+
})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
| Option | Default | Use |
|
|
177
|
+
| ----------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
178
|
+
| `root` | required | Application folder. `.env`, `.env.test`, `project.inlang/settings.json` and the icon folders resolve from it |
|
|
179
|
+
| `securityHeaders` | required | Values of `strict-transport-security`, `x-content-type-options` and `cross-origin-resource-policy` for the static files |
|
|
180
|
+
| `middleware` | none | `start.middleware` of the Solid plugin. It has to sit inside the application folder |
|
|
181
|
+
| `serverSetup` | `serverFunctions: true` | `serverFunctions.serverSetup` of the Solid plugin |
|
|
182
|
+
| `e2e` | production paths | `{ middleware, routes }` for `--mode e2e`: the middleware chain and the route folder of the fixtures, without route types |
|
|
183
|
+
| `icons` | none | Own `unplugin-icons` collections, as name to folder |
|
|
184
|
+
| `publicAssets` | none | `publicAssets` of Nitro. Each entry needs a `baseURL`, which also gets the static headers |
|
|
185
|
+
| `staticPaths` | none | Other static paths that get the headers, such as `/favicon/**` |
|
|
186
|
+
| `routeRules` | none | More Nitro route rules, applied after the static ones. A rule with the same path replaces the static one |
|
|
187
|
+
| `extend` | none | `(context) => ({ plugins, nitroModules })`, for plugins and Nitro modules the application still owns |
|
|
188
|
+
| `tasks` | none | Adds or replaces `vp run` tasks by name |
|
|
189
|
+
| `workspace` | none | `{ dependsOn, inputs }` for an application inside a monorepo, whose `bun.lock`, `bunfig.toml` and package builds sit at the root |
|
|
190
|
+
|
|
191
|
+
The preset fixes the rest:
|
|
192
|
+
|
|
193
|
+
- Plugins, in this order: the `extend` plugins, `unplugin-icons` (Solid
|
|
194
|
+
compiler, `aria-hidden` on every icon), `@solidjs/vite-plugin` with SSR and
|
|
195
|
+
server functions, `filesystem-routing` with HTTP methods and the route types
|
|
196
|
+
in `src/@types/routes.d.ts`, and Nitro with the `vercel` preset. The `dom` and
|
|
197
|
+
`browser` test projects get the `extend` plugins, the icons, Solid with server
|
|
198
|
+
functions only and the routes. The `test` mode loads no application plugin and
|
|
199
|
+
reads `.env.test` into `process.env`.
|
|
200
|
+
- The static headers go to `/assets/**`, each `publicAssets` folder, the
|
|
201
|
+
`staticPaths`, `/<locale>/**.md` for each locale of
|
|
202
|
+
`project.inlang/settings.json`, `/llms.txt`, `/llms-full.txt`, `/sitemap.xml`,
|
|
203
|
+
`/sitemaps/pages/**` and `/_markdown/static.json`. The static files leave the
|
|
204
|
+
CDN without going through the middleware, so these rules give them the headers
|
|
205
|
+
the middleware writes on the other responses.
|
|
206
|
+
- `build.manifest` only in the `e2e` mode, which the `TEST_PREBUILT` check of
|
|
207
|
+
`./playwright` relies on, and no comments in the output.
|
|
208
|
+
- Lightning CSS, with CSS Modules classes named `[content-hash]_[local]`.
|
|
209
|
+
- `resolve.tsconfigPaths`, and the dev server and the preview on the `HOST` and
|
|
210
|
+
`PORT` of `.env`. The dev server forwards the browser console to the terminal.
|
|
211
|
+
|
|
212
|
+
The plugins are imported inside `lazyPlugins`, so `vp lint`, `vp fmt` and
|
|
213
|
+
`vp check` read the config without loading them.
|
|
214
|
+
|
|
215
|
+
Nitro compiles the route rules with the `preMerge` of h3, which refuses rules
|
|
216
|
+
that overlap in part, such as `/pt/blog/**` next to `/pt/**.md`, and falls back
|
|
217
|
+
to its simple mode, with the same result and a warning. `defineSolidApp` throws
|
|
218
|
+
when a `publicAssets` entry has no `baseURL`, or one with a trailing slash, and
|
|
219
|
+
when a static path does not start with `/`. Build, dev and preview fail to load
|
|
220
|
+
the plugins when a `publicAssets` folder or a static path sits inside a locale
|
|
221
|
+
folder.
|
|
222
|
+
|
|
223
|
+
### `extend`
|
|
224
|
+
|
|
225
|
+
`extend` runs inside `lazyPlugins`: once for the application in build, dev and
|
|
226
|
+
preview (`target: 'app'`), and once for each test project with a Vite server
|
|
227
|
+
(`target: 'test'`). Every load of the config calls it again, so a build also
|
|
228
|
+
calls it for the two test projects, whose plugins Vite does not use; keep side
|
|
229
|
+
effects out of it. It never runs while Vite+ only reads the config. Return new
|
|
230
|
+
plugin instances on each call. The `plugins` go before the fixed ones, and the
|
|
231
|
+
`nitroModules` go to Nitro; the `test` target ignores them. One call returns
|
|
232
|
+
both, so a value that a plugin and a module share, such as a build id, stays the
|
|
233
|
+
same.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
export default defineSolidApp({
|
|
237
|
+
root: import.meta.dirname,
|
|
238
|
+
securityHeaders: SECURITY_HEADERS,
|
|
239
|
+
async extend() {
|
|
240
|
+
const { i18nPlugin } = await import('./tooling/i18n/i18nPlugin/index.ts')
|
|
241
|
+
return { plugins: [i18nPlugin()] }
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Tasks
|
|
247
|
+
|
|
248
|
+
| Task | Command |
|
|
249
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
250
|
+
| `i18n-generate` | `bun tooling/i18n/generate.ts` |
|
|
251
|
+
| `lint-css` | `bun tooling/css/check.ts` |
|
|
252
|
+
| `dead-code-project` | `knip-bun --config tooling/knip.ts`, then again with `--strict` |
|
|
253
|
+
| `lint-project` | `vp lint`, after `lint-css` |
|
|
254
|
+
| `check-project` | `vp fmt --check && vp lint`, after `lint-css` |
|
|
255
|
+
| `typecheck-node` | `tsc --project tsconfig.node.json` |
|
|
256
|
+
| `test-tooling` | `node --test` over `tooling/**/__tests__` except `tooling/release/`, three workers on CI, with no cache |
|
|
257
|
+
|
|
258
|
+
The commands run the scripts of the boilerplate layout. An application without
|
|
259
|
+
one of them replaces the task through `tasks`. Knip reads task commands only
|
|
260
|
+
from a `run.tasks` block written in `vite.config.ts`, so it does not see these:
|
|
261
|
+
list the scripts they run as Knip entries.
|
|
262
|
+
|
|
263
|
+
With `workspace`, the cache inputs read `bun.lock`, `bunfig.toml` and the
|
|
264
|
+
`inputs` from the workspace root, and `lint-css`, `dead-code-project` and
|
|
265
|
+
`typecheck-node` depend on the tasks in `dependsOn`:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
workspace: {
|
|
269
|
+
dependsOn: ['@evertondgn/solid-tooling#build'],
|
|
270
|
+
inputs: ['packages/solid-tooling/dist/**']
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Changing the result
|
|
275
|
+
|
|
276
|
+
Wrap the result in `defineConfig` and merge what the application needs. Vite+
|
|
277
|
+
removes the plugins it injected before it injects them again, so they are not
|
|
278
|
+
duplicated:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import { defineSolidApp } from '@evertondgn/solid-tooling/vite'
|
|
282
|
+
import { defineConfig, mergeConfig } from 'vite-plus'
|
|
283
|
+
|
|
284
|
+
const app = defineSolidApp({ root: import.meta.dirname, securityHeaders })
|
|
285
|
+
|
|
286
|
+
export default defineConfig(env =>
|
|
287
|
+
mergeConfig(app(env), { server: { strictPort: true } })
|
|
288
|
+
)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Knip
|
|
292
|
+
|
|
293
|
+
`defineKnipConfig` returns the Knip config of the application as a function, the
|
|
294
|
+
form Knip accepts as the default export of its config file:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
// tooling/knip.ts
|
|
298
|
+
import path from 'node:path'
|
|
299
|
+
|
|
300
|
+
import { defineKnipConfig } from '@evertondgn/solid-tooling/knip'
|
|
301
|
+
|
|
302
|
+
export default defineKnipConfig({
|
|
303
|
+
root: path.resolve(import.meta.dirname, '..'),
|
|
304
|
+
ignoreDependencies: ['server-only']
|
|
305
|
+
})
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Run it with `knip-bun --config tooling/knip.ts`, then with `--strict`. The
|
|
309
|
+
config lists:
|
|
310
|
+
|
|
311
|
+
- `src/entry-server.tsx`, `src/entry-client.tsx`, `src/middleware/index.ts`,
|
|
312
|
+
`env.ts` and `paraglide/runtime.js` as production entries. The Solid plugin
|
|
313
|
+
and the Paraglide runtime load them by path, and Knip does not read the Solid
|
|
314
|
+
plugin when `vite.config.ts` imports it with `import()`, as `./vite` does. An
|
|
315
|
+
application that imports `solid()` statically, with `start`, passes
|
|
316
|
+
`solidPluginImport: 'static'`: Knip's Solid plugin then finds the first four
|
|
317
|
+
itself, and listing them again would be a configuration hint.
|
|
318
|
+
- Every page and HTTP handler that `filesystem-routing` finds in `src/routes/`,
|
|
319
|
+
as production entries, and in `src/tests/fixtures/routes/` and
|
|
320
|
+
`src/tests/fixtures/e2e/routes/`, as test entries. The `filesystem-routing`
|
|
321
|
+
plugin of Knip is turned off, because it takes every file of `src/routes/` as
|
|
322
|
+
an entry and would hide unused helpers next to the routes.
|
|
323
|
+
- `src/**/*.{ts,tsx}` and `tooling/**/*.ts` as the project, with the tests out
|
|
324
|
+
of the production files.
|
|
325
|
+
|
|
326
|
+
The config turns on `treatConfigHintsAsErrors`, and Knip reports an entry or a
|
|
327
|
+
project pattern without files as a configuration hint. So each default above
|
|
328
|
+
enters only when the application has the file or the folder.
|
|
329
|
+
|
|
330
|
+
| Option | Default | Use |
|
|
331
|
+
| -------------------- | ----------------- | -------------------------------------------------------------- |
|
|
332
|
+
| `root` | required | The application folder |
|
|
333
|
+
| `configFile` | `tooling/knip.ts` | Path of the config file, relative to `root` |
|
|
334
|
+
| `entry` | none | More entries, such as a public API that only test fixtures use |
|
|
335
|
+
| `solidPluginImport` | `'lazy'` | `'static'` when `vite.config.ts` imports `solid()` statically |
|
|
336
|
+
| `ignoreBinaries` | none | Passed to Knip as is |
|
|
337
|
+
| `ignoreDependencies` | none | Passed to Knip as is |
|
|
338
|
+
| `ignoreIssues` | none | Passed to Knip, merged with the entry for `configFile` |
|
|
339
|
+
| `exclude` | none | Issue types to leave out, such as `catalogReferences` |
|
|
340
|
+
|
|
341
|
+
Knip adds the file given to `--config` to the entries in production mode, and
|
|
342
|
+
`--strict` only accepts `dependencies` there, so it reports this package as an
|
|
343
|
+
unlisted dependency of the config file. The config ignores `unlisted` issues in
|
|
344
|
+
`configFile` only: an import of this package from `src/` is still reported. An
|
|
345
|
+
application that keeps the config somewhere else passes its path in
|
|
346
|
+
`configFile`.
|
|
347
|
+
|
|
348
|
+
Nothing that depends on the application is a default. An ignore that matches
|
|
349
|
+
nothing is a configuration hint too, so `server-only` and the binaries of the
|
|
350
|
+
scripts are options. For a key without an option, wrap the function:
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
const configure = defineKnipConfig({ root })
|
|
354
|
+
|
|
355
|
+
export default async function configureKnip() {
|
|
356
|
+
return { ...(await configure()), ignoreUnresolved: ['virtual:example'] }
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## CSS lint
|
|
361
|
+
|
|
362
|
+
`checkCss(root)` lints `src/**/*.css` with Stylelint and the CSS policy of the
|
|
363
|
+
boilerplate, and checks the name of every CSS file in `src/` as
|
|
364
|
+
`project/css-filename` does for imports: `styles.module.css`, or `camelCase.css`
|
|
365
|
+
inside `src/theme/`. It returns the report and whether anything failed:
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
// tooling/css/check.ts
|
|
369
|
+
import path from 'node:path'
|
|
370
|
+
import process from 'node:process'
|
|
371
|
+
|
|
372
|
+
import { checkCss } from '@evertondgn/solid-tooling/stylelint'
|
|
373
|
+
|
|
374
|
+
const { report, hasErrors } = await checkCss(
|
|
375
|
+
path.resolve(import.meta.dirname, '../..')
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
process.stdout.write(report)
|
|
379
|
+
if (hasErrors) process.exitCode = 1
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`defineStylelintConfig(root)` returns the Stylelint config that `checkCss` uses.
|
|
383
|
+
The policy:
|
|
384
|
+
|
|
385
|
+
- Classes use snake_case, nesting goes at most two levels deep, and adjacent
|
|
386
|
+
state selectors and media queries are nested (`stylelint-use-nesting`).
|
|
387
|
+
- Unknown or invalid properties, values, units, selectors, at-rules and media
|
|
388
|
+
features are errors, as are deprecated and unmatchable selectors.
|
|
389
|
+
- Outside `src/theme/`, colors, gradients, radii, shadows, font sizes and
|
|
390
|
+
weights and `z-index` come from tokens with `var()`, from the matching token
|
|
391
|
+
family. Literal colors, color functions and system colors are errors.
|
|
392
|
+
- Inside `src/theme/`, the token rules are off, so the theme can define the
|
|
393
|
+
values with `oklch()`, `oklab()`, `color-mix()`, `light-dark()` and system
|
|
394
|
+
colors. Hex, named colors and the `rgb()`, `hsl()`, `hwb()`, `lab()`, `lch()`
|
|
395
|
+
and `color()` functions are still errors there.
|
|
396
|
+
- `var()` and animation names resolve against `src/theme/tokens/**/*.css` and
|
|
397
|
+
`src/theme/class/animation.css`. Stylelint stops the whole run when a
|
|
398
|
+
reference matches no file, so each one is passed only when it exists.
|
|
399
|
+
- An exception needs a `stylelint-disable` comment with the reason after `--`.
|
|
400
|
+
|
|
401
|
+
`stylelint` and `stylelint-use-nesting` are dependencies of this package. The
|
|
402
|
+
config passes the plugin by a path resolved from the package, because Stylelint
|
|
403
|
+
looks up a plugin name from the application and its working directory, where it
|
|
404
|
+
is not installed.
|
|
405
|
+
|
|
71
406
|
## Project layout the preset expects
|
|
72
407
|
|
|
73
408
|
The rules match file paths relative to the folder where lint runs, so run it
|
|
@@ -205,20 +540,30 @@ overrides: [
|
|
|
205
540
|
|
|
206
541
|
## Entry points
|
|
207
542
|
|
|
208
|
-
| Entry
|
|
209
|
-
|
|
|
210
|
-
| `./lint`
|
|
211
|
-
| `./fmt`
|
|
212
|
-
| `./tsconfig.base.json`
|
|
213
|
-
| `./
|
|
214
|
-
| `./
|
|
215
|
-
| `./
|
|
216
|
-
| `./
|
|
217
|
-
| `./
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
plugin
|
|
221
|
-
|
|
543
|
+
| Entry | Content |
|
|
544
|
+
| ------------------------ | ------------------------------------------------------------------------- |
|
|
545
|
+
| `./lint` | `lint`: the lint preset, with the plugins below registered |
|
|
546
|
+
| `./fmt` | `fmt`: the formatter config |
|
|
547
|
+
| `./tsconfig.base.json` | Compiler options, without application paths |
|
|
548
|
+
| `./tsconfig.app.json` | The base plus the `types` and `include` of the boilerplate layout |
|
|
549
|
+
| `./vite` | `defineSolidApp(options)`, the whole `vite.config.ts` of an application |
|
|
550
|
+
| `./vitest` | `solidTest(options)`, the `test` block of `vite.config.ts` |
|
|
551
|
+
| `./playwright` | `definePlaywrightConfig(options)`, the Playwright config |
|
|
552
|
+
| `./vitest-setup/browser` | Setup file of the browser project, loaded by `./vitest` |
|
|
553
|
+
| `./playwright-teardown` | Server log check, loaded by `./playwright` |
|
|
554
|
+
| `./project-plugin` | The `project` rules |
|
|
555
|
+
| `./backend-plugin` | The `backend` rules |
|
|
556
|
+
| `./architecture-plugin` | The `architecture` rule |
|
|
557
|
+
| `./solid-plugin` | Re-export of `eslint-plugin-solid` |
|
|
558
|
+
| `./css-filename` | `cssFilenameError(path)`, the CSS naming check, for a script outside lint |
|
|
559
|
+
| `./knip` | `defineKnipConfig(options)`, the Knip config |
|
|
560
|
+
| `./stylelint` | `checkCss(root)` and `defineStylelintConfig(root)`, the CSS lint |
|
|
561
|
+
|
|
562
|
+
An application imports `./vite`, which brings `./lint`, `./fmt` and `./vitest`
|
|
563
|
+
along, `./playwright`, `./knip`, `./stylelint` and one of the TypeScript
|
|
564
|
+
configs. An application that only lints imports `./lint` and `./fmt`. The plugin
|
|
565
|
+
entries exist because Oxlint loads plugins by specifier, from the application;
|
|
566
|
+
the setup and teardown entries are loaded by the test presets.
|
|
222
567
|
|
|
223
568
|
## Formatter
|
|
224
569
|
|
|
@@ -230,8 +575,36 @@ generated route types, SVG and lock files.
|
|
|
230
575
|
|
|
231
576
|
`tsconfig.base.json` targets ES2024 with bundler resolution, keeps JSX for the
|
|
232
577
|
Solid compiler (`jsxImportSource: @solidjs/web`), emits nothing and turns on the
|
|
233
|
-
unused code and unchecked index checks. It has no `paths`, `include` or `types
|
|
234
|
-
|
|
578
|
+
unused code and unchecked index checks. It has no `paths`, `include` or `types`.
|
|
579
|
+
|
|
580
|
+
`tsconfig.app.json` extends the base with the `types` of a boilerplate
|
|
581
|
+
application and an `include` resolved from the application folder with
|
|
582
|
+
`${configDir}`, which needs TypeScript 5.5 or later. Each entry of `types` comes
|
|
583
|
+
from a package the application installs:
|
|
584
|
+
|
|
585
|
+
| Entry of `types` | Package |
|
|
586
|
+
| --------------------------------------- | --------------------------- |
|
|
587
|
+
| `node` | `@types/node` |
|
|
588
|
+
| `vite/client` | `vite` |
|
|
589
|
+
| `vite-plus/test/globals` | `vite-plus` |
|
|
590
|
+
| `@testing-library/jest-dom/vitest` | `@testing-library/jest-dom` |
|
|
591
|
+
| `@solidjs/vite-plugin/boundary-modules` | `@solidjs/vite-plugin` |
|
|
592
|
+
| `@solidjs/vite-plugin/virtual-modules` | `@solidjs/vite-plugin` |
|
|
593
|
+
|
|
594
|
+
The application still declares its `paths`:
|
|
595
|
+
|
|
596
|
+
```json
|
|
597
|
+
{
|
|
598
|
+
"extends": "@evertondgn/solid-tooling/tsconfig.app.json",
|
|
599
|
+
"compilerOptions": {
|
|
600
|
+
"paths": { "@/*": ["./src/*"], "@paraglide/*": ["./paraglide/*"] }
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Playwright reads the `tsconfig.json` of the test files with its own loader,
|
|
606
|
+
which does not expand `${configDir}`, so paths declared in the package would
|
|
607
|
+
break the `@/` imports of the end-to-end tests.
|
|
235
608
|
|
|
236
609
|
## Known limits
|
|
237
610
|
|
|
@@ -246,6 +619,19 @@ those depend on the application and stay in its `tsconfig.json`.
|
|
|
246
619
|
installed even in an application without translations.
|
|
247
620
|
- Some rule messages point to `docs/architecture.md` and `AGENTS.md`. Those
|
|
248
621
|
files belong to the application created from boilerplate_solidjs.
|
|
622
|
+
- The test presets expect the boilerplate layout: tests named by suffix
|
|
623
|
+
(`.node.test`, `.dom.test`, `.browser.test`, `.e2e.test`), page tests in
|
|
624
|
+
`src/tests/pages/`, Paraglide output in `paraglide/` and `.env.test` at the
|
|
625
|
+
root.
|
|
626
|
+
- The Knip and CSS configs expect the same layout: pages in `src/routes/`,
|
|
627
|
+
fixture routes in `src/tests/fixtures/` and the theme in `src/theme/`.
|
|
628
|
+
- The Knip config ignores every unlisted dependency in its own file, not only
|
|
629
|
+
this package.
|
|
630
|
+
- `./vite` expects the same layout: its tasks run scripts in `tooling/`, and the
|
|
631
|
+
Markdown rules need the locale prefix of the Paraglide `urlPatterns` in every
|
|
632
|
+
URL. Nitro always uses the `vercel` preset. The plugin packages are pinned, so
|
|
633
|
+
a new version of Solid, Nitro, the router or the icons plugin needs a package
|
|
634
|
+
version tested with it.
|
|
249
635
|
|
|
250
636
|
## Versions
|
|
251
637
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { glob } from "node:fs/promises";
|
|
2
|
+
// src/has-match.ts
|
|
3
|
+
async function hasMatch({ root, pattern }) {
|
|
4
|
+
const matches = glob(pattern, { cwd: root });
|
|
5
|
+
const { done } = await matches.next();
|
|
6
|
+
await matches.return?.();
|
|
7
|
+
return !done;
|
|
8
|
+
}
|
|
9
|
+
export { hasMatch as t };
|
package/dist/knip.d.mts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { KnipConfiguration } from "knip";
|
|
2
|
+
type AppSettings = Pick<KnipConfiguration, 'ignoreBinaries' | 'ignoreDependencies' | 'ignoreIssues' | 'exclude'>;
|
|
3
|
+
type SolidPluginImport = 'lazy' | 'static';
|
|
4
|
+
type KnipOptions = AppSettings & {
|
|
5
|
+
root: string;
|
|
6
|
+
configFile?: string;
|
|
7
|
+
entry?: string[];
|
|
8
|
+
solidPluginImport?: SolidPluginImport;
|
|
9
|
+
};
|
|
10
|
+
type ConfigureKnip = () => Promise<KnipConfiguration>;
|
|
11
|
+
export declare function defineKnipConfig({ root, configFile, entry, solidPluginImport, ignoreBinaries, ignoreDependencies, ignoreIssues, exclude }: KnipOptions): ConfigureKnip;
|
|
12
|
+
export {};
|
package/dist/knip.mjs
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { t as hasMatch } from "./has-match-BITlj0EE.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { PageFileSystemRouter } from "filesystem-routing";
|
|
4
|
+
import { DEFAULT_EXTENSIONS } from "filesystem-routing/vite";
|
|
5
|
+
// src/knip/index.ts
|
|
6
|
+
const DEFAULT_CONFIG_FILE = "tooling/knip.ts";
|
|
7
|
+
const SOLID_ENTRIES = [
|
|
8
|
+
"src/entry-server.tsx",
|
|
9
|
+
"src/entry-client.tsx",
|
|
10
|
+
"src/middleware/index.ts",
|
|
11
|
+
"env.ts"
|
|
12
|
+
];
|
|
13
|
+
const RUNTIME_ENTRIES = ["paraglide/runtime.js"];
|
|
14
|
+
const ROUTE_DIRECTORIES = [
|
|
15
|
+
{
|
|
16
|
+
directory: "src/routes",
|
|
17
|
+
isProduction: true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
directory: "src/tests/fixtures/routes",
|
|
21
|
+
isProduction: false
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
directory: "src/tests/fixtures/e2e/routes",
|
|
25
|
+
isProduction: false
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
const TOOLING_PROJECT = "tooling/**/*.ts";
|
|
29
|
+
function isModuleRef(value) {
|
|
30
|
+
return typeof value === "object" && value !== null && "src" in value && typeof value.src === "string";
|
|
31
|
+
}
|
|
32
|
+
function routeSources(route) {
|
|
33
|
+
return Object.entries(route).flatMap(([key, value]) => key.startsWith("$") && isModuleRef(value) ? [value.src] : []);
|
|
34
|
+
}
|
|
35
|
+
async function discoverRouteModules({ root, directory, isProduction }) {
|
|
36
|
+
const routes = await new PageFileSystemRouter({
|
|
37
|
+
dir: path.join(root, directory),
|
|
38
|
+
extensions: DEFAULT_EXTENSIONS,
|
|
39
|
+
httpMethods: true
|
|
40
|
+
}).getRoutes();
|
|
41
|
+
return [...new Set(routes.flatMap((route) => routeSources(route)))].map((source) => path.relative(root, source) + (isProduction ? "!" : ""));
|
|
42
|
+
}
|
|
43
|
+
async function conventionEntries({ root, patterns }) {
|
|
44
|
+
return (await Promise.all(patterns.map(async (pattern) => await hasMatch({
|
|
45
|
+
root,
|
|
46
|
+
pattern
|
|
47
|
+
}) ? [`${pattern}!`] : []))).flat();
|
|
48
|
+
}
|
|
49
|
+
function defineKnipConfig({ root, configFile = DEFAULT_CONFIG_FILE, entry = [], solidPluginImport = "lazy", ignoreBinaries, ignoreDependencies, ignoreIssues = {}, exclude }) {
|
|
50
|
+
const patterns = solidPluginImport === "static" ? RUNTIME_ENTRIES : [...SOLID_ENTRIES, ...RUNTIME_ENTRIES];
|
|
51
|
+
return async function configureKnip() {
|
|
52
|
+
const [conventions, routes, hasTooling] = await Promise.all([
|
|
53
|
+
conventionEntries({
|
|
54
|
+
root,
|
|
55
|
+
patterns
|
|
56
|
+
}),
|
|
57
|
+
Promise.all(ROUTE_DIRECTORIES.map((route) => discoverRouteModules({
|
|
58
|
+
root,
|
|
59
|
+
...route
|
|
60
|
+
}))),
|
|
61
|
+
hasMatch({
|
|
62
|
+
root,
|
|
63
|
+
pattern: TOOLING_PROJECT
|
|
64
|
+
})
|
|
65
|
+
]);
|
|
66
|
+
const configIssues = ignoreIssues[configFile] ?? [];
|
|
67
|
+
return {
|
|
68
|
+
entry: [.../* @__PURE__ */ new Set([
|
|
69
|
+
...conventions,
|
|
70
|
+
...entry,
|
|
71
|
+
...routes.flat()
|
|
72
|
+
])],
|
|
73
|
+
project: [
|
|
74
|
+
"src/**/*.{ts,tsx}!",
|
|
75
|
+
...hasTooling ? [TOOLING_PROJECT] : [],
|
|
76
|
+
"!src/tests/**!",
|
|
77
|
+
"!src/**/__tests__/**!",
|
|
78
|
+
"!src/**/*.test.{ts,tsx}!"
|
|
79
|
+
],
|
|
80
|
+
"filesystem-routing": false,
|
|
81
|
+
...ignoreBinaries ? { ignoreBinaries } : {},
|
|
82
|
+
...ignoreDependencies ? { ignoreDependencies } : {},
|
|
83
|
+
ignoreIssues: {
|
|
84
|
+
...ignoreIssues,
|
|
85
|
+
[configFile]: [.../* @__PURE__ */ new Set([...configIssues, "unlisted"])]
|
|
86
|
+
},
|
|
87
|
+
...exclude ? { exclude } : {},
|
|
88
|
+
treatConfigHintsAsErrors: true
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export { defineKnipConfig };
|